Question

I have the following in a file called function.sql:

CREATE FUNCTION increment(i integer) RETURNS integer AS $$
        BEGIN
                RETURN i + 1;
        END;
$$ LANGUAGE plpgsql;

And I'm trying to execute it against a local Postgres (9.3.4) database with Ant:

<target name = "create" >
    <sql driver="org.postgresql.Driver" url="${db.url}" userid="${db.username}" password="${db.password}">

        <transaction  src="create/functions.sql"/>  

        <classpath>
            <pathelement location="lib/postgresql-9.3-1101.jdbc41.jar"/>
        </classpath>
    </sql>
</target>

(I've created an example project on github here.)

When I execute ant, I get the error:

BUILD FAILED
/home/paul/jobhop-workspace/AntSqlPostgresFunctions/build.xml:17: org.postgresql.util.PSQLException: ERROR: syntax error at or near "$"
  Position: 58

However if I execute the file directly:

psql -d <mydb> functions.sql

it creates the function just fine. I've tried the solutions suggested here and here, which sound like the same problem, but they didn't work for me.

Was it helpful?

Solution

The error message is because Ant's property expansion behaviour sees the $$ and replaces it with a single $ before it gets to PostgreSQL (see http://ant.apache.org/manual/properties.html, section $$ Expansion) We can get around that in a few ways. Doubling up the dollar characters as recommended in the Ant manual works, but at the expense of making the file no longer work when passed to the psql client. I've had more success using $BODY$ or ($whatever-you-fancy$) instead of $$ to delimit the function body, or by setting the SQL task's expandproperties argument to false in the build.xml.

Once that's done we hit a second problem: the Ant task parses your file to find statements which it feeds to JDBC. However, it's not very smart: it just splits on ';' characters, even when they're in the middle of the body of your function definition (see http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/SQLExec.java?view=markup#l724), so then we'll see an error about an unterminated string (i.e. the first half of the function body).

We can get round that by using a different delimiter and telling the SQL task about it via the delimiter and delimitertype arguments. The following patch to your project gets it working for me:

diff --git a/build.xml b/build.xml
index 7d8a990..5dbbdc5 100644
--- a/build.xml
+++ b/build.xml
@@ -14,7 +14,7 @@
    </target>

    <target name = "create" >
-               <sql driver="org.postgresql.Driver" url="${db.url}"
userid="${db.username}" password="${db.password}">
+               <sql driver="org.postgresql.Driver" url="${db.url}"
userid="${db.username}" password="${db.password}" delimiter="/* END_STATEMENT */" delimitertype="row" >

                    <transaction  src="create/functions.sql"/>
diff --git a/create/functions.sql b/create/functions.sql
index 3238d3e..1a8518f 100644
--- a/create/functions.sql
+++ b/create/functions.sql
@@ -1,9 +1,9 @@
-CREATE FUNCTION increment(i integer) RETURNS integer AS $$
+CREATE FUNCTION increment(i integer) RETURNS integer AS $BODY$
     BEGIN
             RETURN i + 1;
     END;
-$$ LANGUAGE plpgsql;
-
+$BODY$ LANGUAGE plpgsql;
+/* END_STATEMENT */

I initially put the statement delimiter in a comment of the form -- END_STATEMENT. That only gives the illusion of working, since, when parsing the SQL, the Ant task discards those comments before it goes looking for delimiters. Finding no delimiter it just passes the entire contents of the file to PostgreSQL as a single statement, which works in your simple case but won't be ideal as the list of functions gets longer.

Instead, I used C-style comments which are recognized by PostgreSQL, but not (currently) by the Ant task's parser. Of course, this could stop working if the Ant task's parser is changed.

If the functions.sql file doesn't need to work from psql then you can consider using anything you like as delimiter. It doesn't even have to be valid SQL syntax since the Ant task removes it before passing on the statements to PostgreSQL.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top