1

I am using pl/pgsql in PostgreSQL 10, to create complex queries. I am testing a query with a couple of JOINs and ANDs. This is what I have so far:

DROP FUNCTION IF EXISTS search_person(name text);
CREATE  FUNCTION search_person(name text) RETURNS TABLE(address_id integer, address_geom text, event_name text) AS $$
--DECLARE 

BEGIN
    RETURN QUERY EXECUTE 
    'SELECT address.id, event.name, address.geom 
    FROM  event JOIN person JOIN address JOIN person_address JOIN event_person
    WHERE 
    person_address.event_id = event.id AND
    event_person.event_id = event.id AND
    person.id = event_person.person_id AND
    person.name like
    $1'        

    USING name;
END;
$$
LANGUAGE plpgsql;

I get no errors while creating this function. I call it like so select search_person('nick'); and I get:

ERROR:  syntax error at or near "WHERE"
LINE 3:     WHERE 
            ^
QUERY:  SELECT address.id, event.name, address.geom 
    FROM  event JOIN person JOIN address JOIN person_address JOIN event_person
    WHERE 
    person_address.event_id = event.id AND
    event_person.event_id = event.id AND
    person.id = event_person.person_id AND
    person.name like
    $1
CONTEXT:  PL/pgSQL function search_creator(text) line 5 at RETURN QUERY
SQL state: 42601

I cannot see or fix the problem. I tried replacing AND with || in the WHERE clause, but nothing changed.

What should I do?

EDIT

This is the code I have now and I get an empty table, even though I should get results, according to my database data that I checked.

CREATE  FUNCTION search_person(name character(600)) RETURNS TABLE(address_id bigint, address_geom geometry, event_name character(200)) AS $$

BEGIN
    RETURN QUERY EXECUTE 
    'SELECT address.id, address.geom, event.name        

    FROM
    person 
    JOIN event_creator ON event_person.person_id = person.id
    JOIN event ON event.id = event_person.event_id 
    JOIN person_address ON person_address.event_id = event.id 
    JOIN address ON address.id = cep.address_id

    WHERE person.name LIKE $1'
    USING name;

END;
$$
LANGUAGE plpgsql;
slevin
  • 4,166
  • 20
  • 69
  • 129
  • 4
    Your `JOIN` clauses have no join conditions. See the [docs](https://www.postgresql.org/docs/current/static/sql-select.html) for the correct syntax. – Nick Barnes Jan 21 '18 at 22:34
  • @NickBarnes that's ANSI-89 join syntax, which is valid but dangerous. One of the dangers, which is made quite clear from this query, is that there is no `JOIN` predicate for the table `address`, meaning there will be a Cartesian product of `event` and `address`. @OP, what is the `JOIN` condition of the `address` table? – e_i_pi Jan 21 '18 at 22:38
  • Correction, it seems PostgreSQL have ditched ANSI-89 support, so your comment is correct on the malformed `JOIN` syntax – e_i_pi Jan 21 '18 at 22:40
  • @e_i_pi.no, Postgres has not "ditched" support for the old implicit join conditions in the WHERE clause. But in this case it's an invalid mix of the explicit JOIN operator and the implicit join conditions. –  Jan 22 '18 at 07:00

1 Answers1

2

When creating a PL/pgSQL function, the function body is saved as string literal as is. Only superficial syntax checks are applied. Contained statements are not actually executed or tested on a deeper level.

However, basic syntax errors like you have in your query string would still be detected in actual SQL statements. But you are using dynamic SQL with EXECUTE. The statement is contained in a nested string literal and is your responsibility alone.

This seems to be misguided to begin with. There is no apparent reason for dynamic SQL. (Unless you have very uneven data distribution and want to force Postgres to generate a custom plan for each input value.)

If you had used a plain SQL statement, you would have gotten the error message at creation time:

CREATE OR REPLACE FUNCTION search_person(name text)  -- still incorrect!
  RETURNS TABLE(address_id integer, address_geom text, event_name text) AS
$func$
BEGIN
   RETURN QUERY
   SELECT address.id, event.name, address.geom 
   FROM  event JOIN person JOIN address JOIN person_address JOIN event_person
   WHERE 
   person_address.event_id = event.id AND
   event_person.event_id = event.id AND
   person.id = event_person.person_id AND
   person.name like $1;  -- still $1, but refers to func param now!
END
$func$  LANGUAGE plpgsql;

The SQL statement is still invalid. [INNER] JOIN requires a join condition - like Nick commented. And I don't see the need for PL/pgSQL at all. A simple SQL function should serve well:

CREATE FUNCTION search_person(name text)
  RETURNS TABLE(address_id integer, address_geom text, event_name text) AS
$func$
   SELECT a.id, a.geom, e.name  -- also fixed column order to match return type
   FROM   person         AS p
   JOIN   event_person   AS ep ON ep.person_id = p.id
   JOIN   event          AS e  ON e.id = ep.event_id
   JOIN   person_address AS pa ON pa.event_id = e.id
   JOIN   address        AS a  ON a.id = pa.address_id -- missing join condition !!
   WHERE  p.name LIKE $1;
$func$  LANGUAGE sql;

I rewrote the query to fix syntax error, using table aliases for better readability. Finally, I also added one more missing condition based on an educated guess: a.id = pa.address_id.

Now it should work.

Related:

Or no function at all, just use a prepared statement instead. Example:

If you need dynamic SQL after all, pass values with the USING clause like you had it and make sure to defend against SQL injection when concatenating queries. Postgres provides various tools:

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • Thanks for the answer. Coming from a mySQL background, I forgot the `JOIN-AS` part. Also, about the "no apparent reason for dynamic SQL" : I am new to pl/pgsql, so this is a test and when it works as is, then I will add more parameters that the user may or may not choose and form the `FROM` and `WHERE` parts with `IF`s. It will be an all-in-one query. I am trying to create something like [this](https://stackoverflow.com/questions/48272339/query-plan-caching-with-pl-pgsql), but for more tables and parameters. – slevin Jan 22 '18 at 10:53
  • According to you and what I saw in the error warning "the function body is saved as string literal as is". That means no prepared statements for dynamic parameters and `EXECUTE`, right? So, practically no safety at all. Since my queries will contain user input, I guess I should abandon pl/pgsql and concatenate prepared statements before executing them. Right? – slevin Jan 22 '18 at 11:01
  • ...about `EXECUTE` safety, I am checking your answers and the postgre documentation now... – slevin Jan 22 '18 at 11:38
  • @slevin - the function body is stored as literal in table pg_proc. But before execution it is parsed to some graph and this graph structure is evaluated. For embedded SQL the prepared statements are used transparently - and its are safe against SQL injection. – Pavel Stehule Jan 22 '18 at 12:37
  • @slevin: Postgres provides all the tools you need to defend against SQL injection, I added some pointers above. – Erwin Brandstetter Jan 22 '18 at 15:30
  • @ErwinBrandstetter Yes, I think `format` with `quote_literal` will do the trick. Um, about the query, I get an empty table. But if I modify the function to get an id as input and the where clause like so `WHERE person.id = $1` I get results. What is wrong with text? I even tried to use quotes around `$1` and the problem remains. If you like, check the original question edit for my whole code. Thanks again. – slevin Jan 22 '18 at 16:24
  • 1
    @slevin: You do not need `quote_literal()` or `quote_ident()` with `format()`, which has built-in quoting capabilities. It's one or the other, not both. About your empty result: with a probability bordering on certainty you don't want to use `character(600)` etc. See: https://stackoverflow.com/a/20334221/939860 If doubts remains, start a *new question*, where you also provide table definition, sample values and a description of what your query is supposed to achieve exactly. – Erwin Brandstetter Jan 22 '18 at 16:51
  • @PavelStehule interesting. I dont see a pg_proc table. Where can I find it? Thanks – slevin Jan 22 '18 at 21:39
  • 1
    @slevin: [Here.](https://www.postgresql.org/docs/current/static/catalog-pg-proc.html) It's a system catalog in the schema `pg_catalog`. Don't mess with it. – Erwin Brandstetter Jan 22 '18 at 22:32
  • @ErwinBrandstetter Hi again Erwin, sorry for bothering you, I have one last question. `plpgsql` is really hard and the learning curve is huge. Thanks for your overall help and patient. Do you have any tutorial or series of articles that explain `plpgsql` step by step, using examples? If so, please share so I can study from zero and understand, that would be better than me asking random stuff around and not making progress. The postgresql documentation is somewhat confusing. Thanks again. – slevin Jan 25 '18 at 12:24
  • @slevin: It's been a while since I've last been looking for tutorials. My main source of information has always been the excellent manual. (Not exactly a tutorial, granted.) Answers here and on dba.SE should be helpful for many questions. I posted lots of code examples with explanation. – Erwin Brandstetter Jan 31 '18 at 03:32