Stack Overflow is a community of 4.7 million programmers, just like you, helping each other.

Join them; it only takes a minute:

Sign up
Join the Stack Overflow community to:
  1. Ask programming questions
  2. Answer and help your peers
  3. Get recognized for your expertise

I want to pass a table name as a parameter in a Postgres function. I tried this code:

CREATE OR REPLACE FUNCTION some_f(param character varying) RETURNS integer 
AS $$
    BEGIN
    IF EXISTS (select * from quote_ident($1) where quote_ident($1).id=1) THEN
     return 1;
    END IF;
    return 0;
    END;
$$ LANGUAGE plpgsql;

select some_f('table_name');

And I got this:

ERROR:  syntax error at or near "."
LINE 4: ...elect * from quote_ident($1) where quote_ident($1).id=1)...
                                                             ^

********** Error **********

ERROR: syntax error at or near "."

And here is the error I got when changed to this select * from quote_ident($1) tab where tab.id=1:

ERROR:  column tab.id does not exist
LINE 1: ...T EXISTS (select * from quote_ident($1) tab where tab.id...

Probably, quote_ident($1) works, because without the where quote_ident($1).id=1 part I get 1, which means something is selected. Why may the first quote_ident($1) work and the second one not at the same time? And how could this be solved?

share|improve this question
    
I know this question is kind of old, but I found it while searching for the answer to another issue. Couldn't your function just query the informational_schema? I mean, that's kind of what it's for in a way - to let you query and see what objects exist in the database. Just an idea. – David S Sep 25 '12 at 21:24
    
@DavidS Thanks for a comment, I'll try that. – John Doe Sep 26 '12 at 15:14
up vote 47 down vote accepted

This can be further simplified and improved:

CREATE OR REPLACE FUNCTION some_f(_tbl regclass, OUT result integer) AS
$func$
BEGIN
EXECUTE format('SELECT (EXISTS (SELECT 1 FROM %s WHERE id = 1))::int', _tbl)
INTO result;
END
$func$ LANGUAGE plpgsql;

Call (example with schema-qualified name - see below):

SELECT some_f('myschema.mytable');  -- would fail with quote_ident()

Or:

SELECT some_f('"my very uncommon table name"')

Major points

  • Use an OUT parameter to simplify the function. You can directly select the result of the dynamic SQL into it and be done. No need for additional variables and code.

  • EXISTS does exactly what you want, I kept it in my query. There are various ways to do this.

  • You seem to want an integer back, so I cast the boolean result from EXISTS() to integer, which yields exactly what you had. I would return boolean instead.

  • I use the object identifier type regclass as input type for _tbl. That does everything quote_ident(_tbl) or format('%I', _tbl) would do, but better, because:

    • .. it prevents SQL injection just as well.

    • .. it fails immediately and more gracefully if the table name is invalid / does not exist / is invisible to the current user.

    • .. it works with schema-qualified table names, where a plain quote_ident(_tbl) or format(%I) would fail because they cannot resolve the ambiguity.

  • I still use format(), because it simplifies the syntax (and to demonstrate how it's used), but with %s instead of %I. For the simple example we could as well just concatenate:

    EXECUTE 'SELECT (EXISTS (SELECT 1 FROM ' || _tbl || ' WHERE id = 1))::int'
    
  • No need to table-qualify the id column.

Tested with PostgreSQL 9.1. format() requires at least that version.

This is why you always escape user input for dynamic SQL properly:
SQL Fiddle demo for SQL injection

share|improve this answer
    
Looks neat, thanks. – John Doe May 23 '12 at 6:43
1  
Can you execute an analyze command? – suhprano Feb 23 '15 at 20:48
2  
@suhprano: Sure. Try it: DO $$BEGIN EXECUTE 'ANALYZE mytbl'; END$$; – Erwin Brandstetter Feb 23 '15 at 20:58

Don't do this.

That's the answer. It's a terrible anti-pattern. What purpose does it serve? If the client knows the table it wants data from, then SELECT FROM ThatTable! If you've designed your database in a way that this is required, you've probably designed it wrong. If your data access layer needs to know if a value exists in a table, it is trivially easy to do the dynamic SQL part in that code. Pushing it into the database is not good.

I have an idea: let's install a device inside elevators where you can type in the number of the floor you want. Then when you press "Go", it moves a mechanical hand over to the correct button for the desired floor and presses it for you. Revolutionary!

Apparently my answer was too short on explanation so I am repairing this defect with more detail.

I had no intention of mockery. My silly elevator example was the very best device I could imagine for succinctly pointing out the flaws of the technique suggested in the question. That technique adds a completely useless layer of indirection, and needlessly moves table name choice from a caller space using a robust and well-understood DSL (SQL) into a hybrid using obscure/bizarre server-side SQL code.

Such responsibility splitting through movement of query construction logic into dynamic SQL makes the code harder to understand. It destroys a perfectly reasonable convention (how a SQL query chooses what to select) in the name of custom code fraught with potential for error.

  • Dynamic SQL offers the possibility of SQL injection that is hard to recognize in the front end code or the back end code singly (one must inspect them together to see this).
  • At least in SQL Server, dynamic SQL executes with the privileges of the caller, not the running code. I wouldn't be surprised if PostgreSQL functioned the same way. This can render the entire application impervious to a reasonable change in security design, such as using stored procedures where the SP does things the caller had no permission for.
  • When a developer must understand what the application code is doing in order to modify it or fix a bug, he'll find it very difficult to get the exact SQL query being executed. SQL profiler can be used, but this takes special privileges and can have negative performance effects on production systems. The executed query can be logged by the SP but this increases complexity for no reason (maintaining new tables, purging old data, etc.) and is totally non-obvious. In fact, some applications are architected such that the developer does not have database credentials, so it becomes almost impossible for him to actually see the query being submitted.

Here's a far better example in pseudo-C# of switching table names based on a parameter:

string sql = string.Format("SELECT * FROM {0};", escapeSqlIdentifier(tableName));
results = connection.Execute(sql);

Every flaw I have mentioned with the other technique is completely absent from this example.

share|improve this answer
1  
I don't completely agree with that. Say, you press this "Go" button and then some mechanism checks, if the floor exists. Functions may be used in triggers, which in turn can check some conditions. This desicion may not be the most beautiful, but if the system is big enough already and you need to make some corrections in its logic, well, this choice is not so dramatic, I suppose. – John Doe Jul 11 '12 at 5:04
    
@john Oh, you can come up with rationales that sound good. But they're not. Really. – ErikE Jul 11 '12 at 5:49
    
But consider that the action of trying to press a button that doesn't exist will simply generate an exception no matter how you handle it. You can't actually push a nonexistent button, so there is no benefit to adding, on top of button-pushing, a layer to check for nonexistent numbers, since such number entry didn't exist before you created said layer! Abstraction is in my opinion the most powerful tool in programming. However, adding a layer that merely poorly duplicates an existing abstraction is wrong. The database itself is already an abstraction layer that maps names to data sets. – ErikE Dec 31 '13 at 21:02
    
Spot on. The whole point of SQL is to express the set of data that you want extracted. The only thing that this function does is to encapsulate a "canned" SQL statement. Given the fact that the identifier is also hard coded the whole thing has a bad smell to it. – Nick Hristov May 19 '14 at 16:14
1  
@three-cups I did update with a lot more detail on why it's a bad idea. – ErikE Apr 8 at 22:58

Inside plpgsql code, The EXECUTE statement must be used for queries in which table names or columns come from variables. Also the IF EXISTS (<query>) construct is not allowed when query is dynamically generated.

Here's your function with both problems fixed:

CREATE OR REPLACE FUNCTION some_f(param character varying) RETURNS integer 
AS $$
DECLARE
 v int;
BEGIN
      EXECUTE 'select 1 FROM ' || quote_ident(param) || ' WHERE '
            || quote_ident(param) || '.id = 1' INTO v;
      IF v THEN return 1; ELSE return 0; END IF;
END;
$$ LANGUAGE plpgsql;
share|improve this answer
    
Thank you, I was making just the same a couple of minutes ago when read your answer. The only difference is I had to remove quote_ident() because it added extra quotes, which surprised me a little, well, 'cause it is used in most examples. – John Doe May 22 '12 at 19:01
    
Those extra quotes will be needed if/when the table name contains characters outside [a-z], or if/when it clashes with a reserved identifier (example: "group" as a table name) – Daniel Vérité May 22 '12 at 19:09
    
And, by the way, could you please provide a link that would prove that IF EXISTS <query> construct doesn't exist? I'm pretty sure I saw something like that as a working code sample. – John Doe May 22 '12 at 21:44
    
@JohnDoe: IF EXISTS (<query>) THEN ... is a perfectly valid construct in plpgsql. Just not with dynamic SQL for <query>. I use it a lot. Also, this function can be improved quite a bit. I posted an answer. – Erwin Brandstetter May 22 '12 at 23:10
1  
Sorry, you're right about if exists(<query>), it's valid in the general case. Just checked and modified the answer accordingly. – Daniel Vérité May 22 '12 at 23:59

The first doesn't actually "work" in the sense that you mean, it works only in so far as it does not generate an error.

Try SELECT * FROM quote_ident('table_that_does_not_exist');, and you will see why your function returns 1: the select is returning a table with one column (named quote_ident) with one row (the variable $1 or in this particular case table_that_does_not_exist).

What you want to do will require dynamic SQL, which is actually the place that the quote_* functions are meant to be used.

share|improve this answer
    
Thanks a lot, Matt, table_that_does_not_exist gave the same result, you're right. – John Doe May 22 '12 at 17:26

I have 9.4 version of PostgreSQL and I always use this code:

CREATE FUNCTION add_new_table(text) RETURNS void AS
$BODY$
begin
    execute
        'CREATE TABLE ' || $1 || '(
        item_1      type,
        item_2      type
        )';
end;
$BODY$
LANGUAGE plpgsql

And then:

SELECT add_new_table('my_table_name');

It works good for me.

Attention! Above example is one of those which shows "How do not if we want to keep safety during querying the database" :P

share|improve this answer
    
Creating a new table is different from operating with the name of an existing table. Either way, you should escape text parameters executed as code or you are open to SQL injection. – Erwin Brandstetter May 8 '15 at 20:35
    
Oh, yeah, my mistake. The topic misled me and in addition I didn't read it to the end. Normally in my case. :P Why code with a text parameter is exposed to injection? – dm3 May 8 '15 at 23:31
    
That's why: sqlfiddle.com/#!15/70fd5/1 More: bobby-tables.com – Erwin Brandstetter May 8 '15 at 23:53
    
Oops, it's really dangerous. Thank you for the answer! – dm3 May 10 '15 at 16:33

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.