Initial revision
authorMarko Kreen <markokr@gmail.com>
Tue, 13 Mar 2007 12:51:55 +0000 (12:51 +0000)
committerMarko Kreen <markokr@gmail.com>
Tue, 13 Mar 2007 12:51:55 +0000 (12:51 +0000)
38 files changed:
AUTHORS [new file with mode: 0644]
COPYRIGHT [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
config/simple.config.sql [new file with mode: 0644]
debian/packages [new file with mode: 0644]
doc/Makefile [new file with mode: 0644]
doc/config.txt [new file with mode: 0644]
doc/overview.txt [new file with mode: 0644]
doc/syntax.txt [new file with mode: 0644]
doc/todo.txt [new file with mode: 0644]
expected/plproxy_clustermap.out [new file with mode: 0644]
expected/plproxy_errors.out [new file with mode: 0644]
expected/plproxy_init.out [new file with mode: 0644]
expected/plproxy_many.out [new file with mode: 0644]
expected/plproxy_select.out [new file with mode: 0644]
expected/plproxy_test.out [new file with mode: 0644]
plproxy.sql.in [new file with mode: 0644]
sql/plproxy_clustermap.sql [new file with mode: 0644]
sql/plproxy_errors.sql [new file with mode: 0644]
sql/plproxy_init.sql [new file with mode: 0644]
sql/plproxy_many.sql [new file with mode: 0644]
sql/plproxy_select.sql [new file with mode: 0644]
sql/plproxy_test.sql [new file with mode: 0644]
src/cluster.c [new file with mode: 0644]
src/execute.c [new file with mode: 0644]
src/function.c [new file with mode: 0644]
src/main.c [new file with mode: 0644]
src/parser.tab.c [new file with mode: 0644]
src/parser.tab.h [new file with mode: 0644]
src/parser.y [new file with mode: 0644]
src/plproxy.h [new file with mode: 0644]
src/query.c [new file with mode: 0644]
src/result.c [new file with mode: 0644]
src/scanner.c [new file with mode: 0644]
src/scanner.h [new file with mode: 0644]
src/scanner.l [new file with mode: 0644]
src/type.c [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..8989fa5
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,4 @@
+
+Sven Suursoho <sven.suursoho@skype.net>        - PLproxy 1, PLproxy 2 core
+Marko Kreen <marko.kreen@skype.net>    - final PLproxy 2, current maintainer
+
diff --git a/COPYRIGHT b/COPYRIGHT
new file mode 100644 (file)
index 0000000..923f5c1
--- /dev/null
+++ b/COPYRIGHT
@@ -0,0 +1,17 @@
+PL/Proxy - easy access to partitioned database.
+
+Copyright (c) 2006 Sven Suursoho, Skype Technologies OÜ
+Copyright (c) 2007 Marko Kreen, Skype Technologies OÜ
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..65fdb05
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,69 @@
+
+# PL/Proxy version
+PLPROXY_VERSION = 2.0
+
+# libpq config
+PQINC = $(shell pg_config --includedir)
+PQLIB = $(shell pg_config --libdir)
+
+# module setup
+MODULE_big = plproxy
+SRCS = src/cluster.c src/execute.c src/function.c src/main.c \
+       src/query.c src/result.c src/type.c
+OBJS = src/scanner.o src/parser.tab.o $(SRCS:.c=.o)
+DATA_built = plproxy.sql
+EXTRA_CLEAN = #src/scanner.[ch] src/parser.tab.[ch]
+PG_CPPFLAGS = -I$(PQINC)
+SHLIB_LINK = -L$(PQLIB) -lpq
+
+DIST_FILES = Makefile src/plproxy.h src/scanner.l src/parser.y \
+            sql/*.sql expected/*.out db/*.sql doc/*.txt doc/Makefile \
+            AUTHORS COPYRIGHT README
+DIST_DIRS = src sql expected db doc
+TARNAME = plproxy-$(PLPROXY_VERSION)
+
+# regression testing setup
+REGRESS = plproxy_init plproxy_test plproxy_select plproxy_many \
+         plproxy_errors plproxy_clustermap
+REGRESS_OPTS = --load-language=plpgsql
+
+# load PGXS makefile
+PGXS = $(shell pg_config --pgxs)
+include $(PGXS)
+
+# parser rules
+
+gen:
+       cd src; bison -d parser.y
+       cd src; flex -o scanner.c scanner.l
+
+# dependencies
+$(OBJS): src/plproxy.h
+
+# utility rules
+
+tags:
+       cscope -I src -b -f .cscope.out src/*.c
+
+tgz:
+       rm -rf $(TARNAME)
+       mkdir -p $(TARNAME)
+       tar c $(DIST_FILES) $(SRCS) | tar x -C $(TARNAME)
+       tar czf $(TARNAME).tar.gz $(TARNAME)
+
+clean: tgzclean
+
+tgzclean:
+       rm -rf $(TARNAME) $(TARNAME).tar.gz
+
+test: install
+       make installcheck || { cat regression.diffs; exit 1; }
+
+deb:
+       (stamp=`date -R 2>/dev/null` || stamp=`gdate -R`; \
+               echo "plproxy2 ($(PLPROXY_VERSION)) unstable; urgency=low"; \
+               echo ""; echo "  * New build"; echo ""; \
+               echo " -- BuildDaemon <dev@null>  $$stamp") > debian/changelog
+       yada rebuild
+       debuild -uc -us -b
+
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..670c072
--- /dev/null
+++ b/README
@@ -0,0 +1,19 @@
+
+PL/Proxy 2.0
+============
+
+Sven Suursoho & Marko Kreen
+
+Installation
+------------
+
+For installation there must be PostgreSQL dev environment installed
+and pg_config in the PATH.   Then just run:
+
+       $ make
+       $ make install
+
+To run regression tests:
+
+       $ make installcheck
+
diff --git a/config/simple.config.sql b/config/simple.config.sql
new file mode 100644 (file)
index 0000000..d2411d3
--- /dev/null
@@ -0,0 +1,35 @@
+
+-- create cluster info functions
+create schema plproxy;
+
+create or replace function
+plproxy.get_cluster_version(cluster_name text)
+returns integer as $$
+begin
+    if cluster_name = 'testcluster' then
+        return 5;
+    end if;
+    raise exception 'no such cluster: %', cluster_name;
+end; $$ language plpgsql;
+
+create or replace function
+plproxy.get_cluster_partitions(cluster_name text)
+returns setof text as $$
+begin
+    if cluster_name = 'testcluster' then
+        return next 'host=127.0.0.1 dbname=test_part';
+        return;
+    end if;
+    raise exception 'no such cluster: %', cluster_name;
+end; $$ language plpgsql;
+
+create or replace function
+plproxy.get_cluster_config(cluster_name text, out key text, out val text)
+returns setof record as $$
+begin
+    key := 'connection_lifetime';
+    val := 30*60;
+    return next;
+    return;
+end; $$ language plpgsql;
+
diff --git a/debian/packages b/debian/packages
new file mode 100644 (file)
index 0000000..7fac36c
--- /dev/null
@@ -0,0 +1,24 @@
+## debian/packages for plproxy 2
+
+Source: plproxy2
+Section: contrib/misc
+Priority: extra
+Maintainer: Marko Kreen <marko.kreen@skype.net>
+Standards-Version: 3.6.2
+Description: Query partitioner for PostgreSQL
+Copyright: BSD
+ Copyright 2006 Marko Kreen
+Build: sh
+ make
+Clean: sh
+ make clean || true
+Build-Depends: postgresql-server-dev-8.2
+
+Package: plproxy2
+Architecture: any
+Depends: []
+Description: Query partitioner for PostgreSQL
+ .
+Install: sh
+ make install DESTDIR=$ROOT
+
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644 (file)
index 0000000..7ffa719
--- /dev/null
@@ -0,0 +1,15 @@
+
+wiki = https://developer.skype.com/SkypeGarage/DbProjects/PlProxy
+
+web = mkz@shell.pgfoundry.org:/home/pgfoundry.org/groups/plproxy/htdocs/
+
+all:
+
+upload:
+       devupload.sh overview.txt $(wiki)
+       devupload.sh config.txt $(wiki)/ClusterConfig
+       devupload.sh syntax.txt $(wiki)/LanguageSyntax
+
+clean:
+       rm -rf api
+
diff --git a/doc/config.txt b/doc/config.txt
new file mode 100644 (file)
index 0000000..6e2194e
--- /dev/null
@@ -0,0 +1,73 @@
+#pragma section-numbers 2
+
+= PL/Proxy Cluster Configuration API =
+
+[[TableOfContents]]
+
+When running plproxy calls these functions:
+
+
+== plproxy.get_cluster_version(cluster) ==
+
+{{{
+plproxy.get_cluster_version(cluster_name text)
+returns integer
+}}}
+
+It is called on each request, it should return version of particular
+cluster config.  If version is higher than cached, config and partitions
+are reloaded.
+
+
+== plproxy.get_cluster_partitions(cluster) ==
+
+{{{
+plproxy.get_cluster_partitions(cluster_name text)
+returns setof text
+}}}
+
+This is called when new partitions need to be loaded.  Should returns
+connstrings to partitions, in right order.  Total count must
+be power of 2.  If connstrings are equal, they will use same connection.
+
+If the string "user=" does not appear in connstring there will be
+user=CURRENT_USER appended to connection string to forward current
+user name.  As plproxy does not know any passwords, partition database
+should be using "trust" authentication method then.
+
+
+== plproxy.get_cluster_config(cluster) ==
+
+{{{
+plproxy.get_cluster_config(cluster_name text,
+                          out key text, out val text)
+returns setof record
+}}}
+
+Should return pairs of key-value pairs. All of them are optional.
+Timeouts/lifetime are given in seconds. If 0 or NULL then disabled.
+
+  connection_lifetime::
+
+       PL/Proxy will drop older connections.
+
+  connect_timeout::
+
+       Initial connect is canceled, if it takes more that this.
+       Duplicated connstring setting (should be just added to connstr?)
+
+  statement_timeout::
+
+       If set, then SET statement_timeout = X command is sent to remote
+       database.
+       
+  query_timeout::
+
+       If query result does not appear in this time, the connection
+       is closed.  If set then also statement_timeout should be set
+       and to somewhat smaller value, so it takes effect earlier.
+       It is meant for surviving network problems, not long queries.
+
+  disable_binary::
+
+       Do not use binary I/O for connections to this cluster.
diff --git a/doc/overview.txt b/doc/overview.txt
new file mode 100644 (file)
index 0000000..aad6c9d
--- /dev/null
@@ -0,0 +1,68 @@
+#pragma section-numbers 2
+
+= PL/Proxy =
+
+[[TableOfContents]]
+
+== What is pl/proxy - a short history ==
+
+Short version - pl/proxy is a proxy language used for remote database 
+procedure calls and data partitioning between databases based on hashing
+field values.
+
+
+Longer version - 
+
+== subpages ==
+
+Downloads: http://pgfoundry.org/projects/PlProxy
+
+Detailed info: ./LanguageSyntax  ./ClusterConfig
+
+
+== Short intro ==
+
+=== Simple remote function call ===
+
+Connect to `dbname=users` and run following SQL there: `SELECT * from get_user_email($1);`
+
+{{{
+CREATE FUNCTION get_user_email(username text)
+RETURNS text AS $$
+    CONNECT 'dbname=users';
+$$ LANGUAGE plproxy;
+}}}
+
+=== Partitioned remote call ===
+
+Users are spread over several databases,
+partition number is aquired by taking hashtext(username).  This
+needs also configuring the cluster, described later.  After this
+is done, actual proxy function looks following:
+
+{{{
+CREATE FUNCTION get_user_email(username text)
+RETURNS text AS $$
+    CLUSTER 'userdb';
+    RUN ON hashtext(username);
+$$ LANGUAGE plproxy;
+}}}
+
+=== Run user-specified SELECT statement remotely ===
+
+{{{
+CREATE FUNCTION get_user_location(text) RETURNS text AS $$
+    CLUSTER 'userdb';
+    RUN ON hashtext($1);
+    SELECT email FROM users WHERE user = $1;
+$$ LANGUAGE plproxy;
+}}}
+
+
+
+== Restrictions ==
+
+ * All SELECTs/functions are run in autocommit mode on the remote server
+
+ * Only one SELECT statement is allowed, if you need to do more, then you have to 
+ write a pl function on remote side
diff --git a/doc/syntax.txt b/doc/syntax.txt
new file mode 100644 (file)
index 0000000..ab493f5
--- /dev/null
@@ -0,0 +1,37 @@
+#pragma section-numbers 2
+
+== Language Syntax ==
+
+The language contains only 4 statements: CONNECT, CLUSTER, RUN and SELECT.
+
+Each function needs to have either CONNECT or CLUSTER+RUN statement
+to specify where to run the function.  The SELECT statement is optional,
+if it is missing, there will be default query generated based on proxy
+function signature.
+
+=== CONNECT ===
+`CONNECT 'libpq connstr';`
+}}}
+
+=== CLUSTER ===
+
+`CLUSTER 'cluster_name' | cluster_func(..);`
+
+=== RUN ON ===
+{{{
+
+RUN ON (partition_func(..) | ALL | ANY | <NR>) ;
+
+}}}
+ * Arguments for hashfunc and SELECT can be both $1 and full name.
+ * Hashfunc can return "setof int4", several servers are tagged then.
+ * If query is ran on several server, the execution will happen in parallel.
+
+=== SELECT ===
+{{{
+SELECT .... ;
+}}}
+
+By default runs SELECT * from funcname(..); on remote side
+where funcname is name of plproxy function.  
+ * Result fields will be mapped on name.
diff --git a/doc/todo.txt b/doc/todo.txt
new file mode 100644 (file)
index 0000000..87c6b60
--- /dev/null
@@ -0,0 +1,17 @@
+
+= pl/proxy todo list =
+
+== Priority items ==
+
+ * Clean up the binary-or-not decision.  There should be possible
+   to have partitions with different versions.  That may mean
+   lazy typeout generations.
+
+== Just thoughts ==
+
+ * A way to give config to CONNECT functions.
+   * SET param = xxx;
+ * RUN ON ALL: sort by?
+ * RUN ON ALL: ignore errors?
+ * RUN ON ANY: if one con failed, try another
+
diff --git a/expected/plproxy_clustermap.out b/expected/plproxy_clustermap.out
new file mode 100644 (file)
index 0000000..83f3c67
--- /dev/null
@@ -0,0 +1,71 @@
+create or replace function plproxy.get_cluster_version(cluster_name text)
+returns integer as $$
+begin
+    if cluster_name = 'testcluster' then
+        return 6;
+    elsif cluster_name = 'map0' then
+        return 1;
+    elsif cluster_name = 'map1' then
+        return 1;
+    elsif cluster_name = 'map2' then
+        return 1;
+    elsif cluster_name = 'map3' then
+        return 1;
+    end if;
+    raise exception 'no such cluster: %', cluster_name;
+end; $$ language plpgsql;
+create or replace function plproxy.get_cluster_partitions(cluster_name text)
+returns setof text as $$
+begin
+    if cluster_name = 'testcluster' then
+        return next 'host=127.0.0.1 dbname=test_part0';
+        return next 'host=127.0.0.1 dbname=test_part1';
+        return next 'host=127.0.0.1 dbname=test_part2';
+        return next 'host=127.0.0.1 dbname=test_part3';
+    elsif cluster_name = 'map0' then
+        return next 'host=127.0.0.1 dbname=test_part0';
+    elsif cluster_name = 'map1' then
+        return next 'host=127.0.0.1 dbname=test_part1';
+    elsif cluster_name = 'map2' then
+        return next 'host=127.0.0.1 dbname=test_part2';
+    elsif cluster_name = 'map3' then
+        return next 'host=127.0.0.1 dbname=test_part3';
+    else
+        raise exception 'no such cluster: %', cluster_name;
+    end if;
+    return;
+end; $$ language plpgsql;
+create function map_cluster(part integer) returns text as $$
+begin
+    return 'map' || part;
+end;
+$$ language plpgsql;
+create function test_clustermap(part integer) returns setof text as $$
+    cluster map_cluster(part);
+    run on 0;
+    select current_database();
+$$ language plproxy;
+select * from test_clustermap(0);
+ test_clustermap 
+-----------------
+ test_part0
+(1 row)
+
+select * from test_clustermap(1);
+ test_clustermap 
+-----------------
+ test_part1
+(1 row)
+
+select * from test_clustermap(2);
+ test_clustermap 
+-----------------
+ test_part2
+(1 row)
+
+select * from test_clustermap(3);
+ test_clustermap 
+-----------------
+ test_part3
+(1 row)
+
diff --git a/expected/plproxy_errors.out b/expected/plproxy_errors.out
new file mode 100644 (file)
index 0000000..3a0f2dc
--- /dev/null
@@ -0,0 +1,55 @@
+-- test bad arg
+create function test_err1(dat text)
+returns text as $$
+    cluster 'testcluster';
+    run on hashtext(username);
+$$ language plproxy;
+select * from test_err1('dat');
+ERROR:  column "username" does not exist
+LINE 1: select * from hashtext(username)
+                               ^
+QUERY:  select * from hashtext(username)
+create function test_err2(dat text)
+returns text as $$
+    cluster 'testcluster';
+    run on hashtext($2);
+$$ language plproxy;
+select * from test_err2('dat');
+ERROR:  PL/Proxy function public.test_err2(1): Compile error at line 3: invalid argument reference: $2
+create function test_err3(dat text)
+returns text as $$
+    cluster 'nonexists';
+    run on hashtext($1);
+$$ language plproxy;
+select * from test_err3('dat');
+ERROR:  no such cluster: nonexists
+CONTEXT:  SQL statement "select * from plproxy.get_cluster_version($1)"
+--- result map errors
+create function test_map_err1(dat text)
+returns text as $$ cluster 'testcluster'; run on 0;
+    select dat as "foo", 'asd' as "bar";
+$$ language plproxy;
+select * from test_map_err1('dat');
+ERROR:  PL/Proxy function public.test_map_err1(1): single field function but got record
+create function test_map_err2(dat text, out res1 text, out res2 text)
+returns record as $$ cluster 'testcluster'; run on 0;
+    select dat as res1;
+$$ language plproxy;
+select * from test_map_err2('dat');
+ERROR:  PL/Proxy function public.test_map_err2(1): Got too few fields from remote end
+create function test_map_err3(dat text, out res1 text, out res2 text)
+returns record as $$ cluster 'testcluster'; run on 0;
+    select dat as res1, 'foo' as res_none;
+$$ language plproxy;
+select * from test_map_err3('dat');
+ERROR:  PL/Proxy function public.test_map_err3(1): Field res2 does not exists in result
+create function test_map_err4(dat text, out res1 text, out res2 text)
+returns record as $$ cluster 'testcluster'; run on 0;
+    select dat as res2, 'foo' as res1;
+$$ language plproxy;
+select * from test_map_err4('dat');
+ res1 | res2 
+------+------
+ foo  | dat
+(1 row)
+
diff --git a/expected/plproxy_init.out b/expected/plproxy_init.out
new file mode 100644 (file)
index 0000000..25fdbb1
--- /dev/null
@@ -0,0 +1 @@
+\set ECHO none
diff --git a/expected/plproxy_many.out b/expected/plproxy_many.out
new file mode 100644 (file)
index 0000000..a99b71b
--- /dev/null
@@ -0,0 +1,116 @@
+create or replace function plproxy.get_cluster_version(cluster_name text)
+returns integer as $$
+begin
+    if cluster_name = 'testcluster' then
+        return 6;
+    end if;
+    raise exception 'no such cluster: %', cluster_name;
+end; $$ language plpgsql;
+create or replace function plproxy.get_cluster_partitions(cluster_name text)
+returns setof text as $$
+begin
+    if cluster_name = 'testcluster' then
+        return next 'host=127.0.0.1 dbname=test_part0';
+        return next 'host=127.0.0.1 dbname=test_part1';
+        return next 'host=127.0.0.1 dbname=test_part2';
+        return next 'host=127.0.0.1 dbname=test_part3';
+        return;
+    end if;
+    raise exception 'no such cluster: %', cluster_name;
+end; $$ language plpgsql;
+\c test_part0
+create function test_multi(part integer, username text)
+returns integer as $$ begin return 0; end; $$ language plpgsql;
+\c test_part1
+create function test_multi(part integer, username text)
+returns integer as $$ begin return 1; end; $$ language plpgsql;
+\c test_part2
+create function test_multi(part integer, username text)
+returns integer as $$ begin return 2; end; $$ language plpgsql;
+\c test_part3
+create function test_multi(part integer, username text)
+returns integer as $$ begin return 3; end; $$ language plpgsql;
+\c regression
+create function test_multi(part integer, username text)
+returns integer as $$ cluster 'testcluster'; run on int4(part); $$ language plproxy;
+select test_multi(0, 'foo');
+ test_multi 
+------------
+          0
+(1 row)
+
+select test_multi(1, 'foo');
+ test_multi 
+------------
+          1
+(1 row)
+
+select test_multi(2, 'foo');
+ test_multi 
+------------
+          2
+(1 row)
+
+select test_multi(3, 'foo');
+ test_multi 
+------------
+          3
+(1 row)
+
+-- test RUN ON ALL
+drop function test_multi(integer, text);
+create function test_multi(part integer, username text)
+returns setof integer as $$ cluster 'testcluster'; run on all; $$ language plproxy;
+select test_multi(0, 'foo');
+ test_multi 
+------------
+          0
+          1
+          2
+          3
+(4 rows)
+
+-- test RUN ON 2
+drop function test_multi(integer, text);
+create function test_multi(part integer, username text)
+returns setof integer as $$ cluster 'testcluster'; run on 2; $$ language plproxy;
+select test_multi(0, 'foo');
+ test_multi 
+------------
+          2
+(1 row)
+
+-- test RUN ON RANDOM
+select setseed(0);
+ setseed 
+---------
+       0
+(1 row)
+
+drop function test_multi(integer, text);
+create function test_multi(part integer, username text)
+returns setof integer as $$ cluster 'testcluster'; run on any; $$ language plproxy;
+select test_multi(0, 'foo');
+ test_multi 
+------------
+          1
+(1 row)
+
+select test_multi(0, 'foo');
+ test_multi 
+------------
+          1
+(1 row)
+
+select test_multi(0, 'foo');
+ test_multi 
+------------
+          3
+(1 row)
+
+select test_multi(0, 'foo');
+ test_multi 
+------------
+          3
+(1 row)
+
diff --git a/expected/plproxy_select.out b/expected/plproxy_select.out
new file mode 100644 (file)
index 0000000..8048e9f
--- /dev/null
@@ -0,0 +1,37 @@
+-- test regular sql
+create function test_select(xuser text, tmp boolean)
+returns integer as $x$
+    cluster 'testcluster';
+    run on hashtext(xuser);
+    select /*********
+    junk ;
+    ********** ****/ id from sel_test where username = xuser
+     and ';' <> 'as;d''a ; sd'
+    and $tmp$ ; 'a' $tmp$ <> 'as;d''a ; sd'
+    and $tmp$ $ $$  $foo$tmp$ <> 'x';
+$x$ language plproxy;
+\c test_part
+create table sel_test (
+    id integer,
+    username text
+);
+insert into sel_test values ( 1, 'user');
+\c regression
+select * from test_select('user', true);
+ test_select 
+-------------
+           1
+(1 row)
+
+select * from test_select('xuser', false);
+ERROR:  PL/Proxy function public.test_select(2): bug: no result
+-- test errors
+create function test_select_err(xuser text, tmp boolean)
+returns integer as $$
+    cluster 'testcluster';
+    run on hashtext(xuser);
+    select id from sel_test where username = xuser;
+    select id from sel_test where username = xuser;
+$$ language plproxy;
+select * from test_select_err('user', true);
+ERROR:  PL/Proxy function public.test_select_err(2): Compile error at line 5: Only one SELECT statement allowed
diff --git a/expected/plproxy_test.out b/expected/plproxy_test.out
new file mode 100644 (file)
index 0000000..d269fd6
--- /dev/null
@@ -0,0 +1,245 @@
+-- test normal function
+create function testfunc(username text, id integer, data text)
+returns text as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy;
+\c test_part
+create function testfunc(username text, id integer, data text)
+returns text as $$ begin return 'username=' || username; end; $$ language plpgsql;
+\c regression
+select * from testfunc('user', 1, 'foo');
+   testfunc    
+---------------
+ username=user
+(1 row)
+
+select * from testfunc('user', 1, 'foo');
+   testfunc    
+---------------
+ username=user
+(1 row)
+
+select * from testfunc('user', 1, 'foo');
+   testfunc    
+---------------
+ username=user
+(1 row)
+
+-- test setof text
+create function test_set(username text, num integer)
+returns setof text as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy;
+\c test_part
+create function test_set(username text, num integer)
+returns setof text as $$
+declare i integer;
+begin
+    i := 0;
+    while i < num loop
+        return next 'username=' || username || ' row=' || i;
+        i := i + 1;
+    end loop;
+    return;
+end; $$ language plpgsql;
+\c regression
+select * from test_set('user', 1);
+      test_set       
+---------------------
+ username=user row=0
+(1 row)
+
+select * from test_set('user', 0);
+ test_set 
+----------
+(0 rows)
+
+select * from test_set('user', 3);
+      test_set       
+---------------------
+ username=user row=0
+ username=user row=1
+ username=user row=2
+(3 rows)
+
+-- test record
+create type ret_test_rec as ( id integer, dat text);
+create function test_record(username text, num integer)
+returns ret_test_rec as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy;
+\c test_part
+create type ret_test_rec as ( id integer, dat text);
+create function test_record(username text, num integer)
+returns ret_test_rec as $$
+declare ret ret_test_rec%rowtype;
+begin
+    ret := (num, username);
+    return ret;
+end; $$ language plpgsql;
+\c regression
+select * from test_record('user', 3);
+ id | dat  
+----+------
+  3 | user
+(1 row)
+
+-- test setof record
+create function test_record_set(username text, num integer)
+returns setof ret_test_rec as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy;
+\c test_part
+create function test_record_set(username text, num integer)
+returns setof ret_test_rec as $$
+declare ret ret_test_rec%rowtype; i integer;
+begin
+    i := 0;
+    while i < num loop
+        ret := (i, username);
+        i := i + 1;
+        return next ret;
+    end loop;
+    return;
+end; $$ language plpgsql;
+\c regression
+select * from test_record_set('user', 1);
+ id | dat  
+----+------
+  0 | user
+(1 row)
+
+select * from test_record_set('user', 0);
+ id | dat 
+----+-----
+(0 rows)
+
+select * from test_record_set('user', 3);
+ id | dat  
+----+------
+  0 | user
+  1 | user
+  2 | user
+(3 rows)
+
+-- test void
+create function test_void(username text, num integer)
+returns void as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy;
+\c test_part
+create function test_void(username text, num integer)
+returns void as $$
+begin
+    return;
+end; $$ language plpgsql;
+-- look what void actually looks
+select * from test_void('void', 2);
+ test_void 
+-----------
+(1 row)
+
+select test_void('void', 2);
+ test_void 
+-----------
+(1 row)
+
+\c regression
+select * from test_void('user', 1);
+ test_void 
+-----------
+(1 row)
+
+select * from test_void('user', 3);
+ test_void 
+-----------
+(1 row)
+
+select test_void('user', 3);
+ test_void 
+-----------
+(1 row)
+
+select test_void('user', 3);
+ test_void 
+-----------
+(1 row)
+
+-- test normal outargs
+create function test_out1(username text, id integer, out data text)
+as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy;
+\c test_part
+create function test_out1(username text, id integer, out data text)
+returns text as $$ begin data := 'username=' || username; return; end; $$ language plpgsql;
+\c regression
+select * from test_out1('user', 1);
+     data      
+---------------
+ username=user
+(1 row)
+
+-- test complicated outargs
+create function test_out2(username text, id integer, out out_id integer, xdata text, inout xdata2 text, out odata text)
+as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy;
+\c test_part
+create function test_out2(username text, id integer, out out_id integer, xdata text, inout xdata2 text, out odata text)
+as $$ begin
+    out_id = id;
+    xdata2 := xdata2 || xdata;
+    odata := 'username=' || username;
+    return;
+end; $$ language plpgsql;
+\c regression
+select * from test_out2('user', 1, 'xdata', 'xdata2');
+ out_id |   xdata2    |     odata     
+--------+-------------+---------------
+      1 | xdata2xdata | username=user
+(1 row)
+
+-- test various types
+create function test_types(username text, inout vbool boolean, inout xdate timestamp, inout bin bytea)
+as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy;
+\c test_part
+create function test_types(username text, inout vbool boolean, inout xdate timestamp, inout bin bytea)
+as $$ begin return; end; $$ language plpgsql;
+\c regression
+select * from test_types('types', true, '2009-11-04 12:12:02', E'a\\000\\001\\002b');
+ vbool |          xdate           |      bin       
+-------+--------------------------+----------------
+ t     | Wed Nov 04 12:12:02 2009 | a\000\001\002b
+(1 row)
+
+select * from test_types('types', NULL, NULL, NULL);
+ vbool | xdate | bin 
+-------+-------+-----
+       |       | 
+(1 row)
+
+-- test user defined types
+create domain posint as int4 check (value > 0);
+create type struct as (id int4, data text);
+create function test_types2(username text, inout v_posint posint, inout v_struct struct, inout arr int8[])
+as $$ cluster 'testcluster'; run on 0; $$ language plproxy;
+\c test_part
+create domain posint as int4 check (value > 0);
+create type struct as (id int4, data text);
+create function test_types2(username text, inout v_posint posint, inout v_struct struct, inout arr int8[])
+as $$ begin return; end; $$ language plpgsql;
+\c regression
+select * from test_types2('types', 4, (2, 'asd'), array[1,2,3]);
+ v_posint | v_struct |   arr   
+----------+----------+---------
+        4 | (2,asd)  | {1,2,3}
+(1 row)
+
+select * from test_types2('types', NULL, NULL, NULL);
+ v_posint | v_struct | arr 
+----------+----------+-----
+          | (,)      | 
+(1 row)
+
+-- test CONNECT
+create function test_connect1() returns text
+as $$ connect 'dbname=test_part'; select current_database(); $$ language plproxy;
+select * from test_connect1();
+ test_connect1 
+---------------
+ test_part
+(1 row)
+
diff --git a/plproxy.sql.in b/plproxy.sql.in
new file mode 100644 (file)
index 0000000..2fdb8f3
--- /dev/null
@@ -0,0 +1,8 @@
+
+-- handler function
+CREATE FUNCTION plproxy_call_handler ()
+RETURNS language_handler AS 'MODULE_PATHNAME' LANGUAGE C;
+
+-- language
+CREATE LANGUAGE plproxy HANDLER plproxy_call_handler;
+
diff --git a/sql/plproxy_clustermap.sql b/sql/plproxy_clustermap.sql
new file mode 100644 (file)
index 0000000..b4715bd
--- /dev/null
@@ -0,0 +1,56 @@
+create or replace function plproxy.get_cluster_version(cluster_name text)
+returns integer as $$
+begin
+    if cluster_name = 'testcluster' then
+        return 6;
+    elsif cluster_name = 'map0' then
+        return 1;
+    elsif cluster_name = 'map1' then
+        return 1;
+    elsif cluster_name = 'map2' then
+        return 1;
+    elsif cluster_name = 'map3' then
+        return 1;
+    end if;
+    raise exception 'no such cluster: %', cluster_name;
+end; $$ language plpgsql;
+
+create or replace function plproxy.get_cluster_partitions(cluster_name text)
+returns setof text as $$
+begin
+    if cluster_name = 'testcluster' then
+        return next 'host=127.0.0.1 dbname=test_part0';
+        return next 'host=127.0.0.1 dbname=test_part1';
+        return next 'host=127.0.0.1 dbname=test_part2';
+        return next 'host=127.0.0.1 dbname=test_part3';
+    elsif cluster_name = 'map0' then
+        return next 'host=127.0.0.1 dbname=test_part0';
+    elsif cluster_name = 'map1' then
+        return next 'host=127.0.0.1 dbname=test_part1';
+    elsif cluster_name = 'map2' then
+        return next 'host=127.0.0.1 dbname=test_part2';
+    elsif cluster_name = 'map3' then
+        return next 'host=127.0.0.1 dbname=test_part3';
+    else
+        raise exception 'no such cluster: %', cluster_name;
+    end if;
+    return;
+end; $$ language plpgsql;
+
+create function map_cluster(part integer) returns text as $$
+begin
+    return 'map' || part;
+end;
+$$ language plpgsql;
+
+create function test_clustermap(part integer) returns setof text as $$
+    cluster map_cluster(part);
+    run on 0;
+    select current_database();
+$$ language plproxy;
+
+select * from test_clustermap(0);
+select * from test_clustermap(1);
+select * from test_clustermap(2);
+select * from test_clustermap(3);
+
diff --git a/sql/plproxy_errors.sql b/sql/plproxy_errors.sql
new file mode 100644 (file)
index 0000000..042c142
--- /dev/null
@@ -0,0 +1,53 @@
+
+-- test bad arg
+create function test_err1(dat text)
+returns text as $$
+    cluster 'testcluster';
+    run on hashtext(username);
+$$ language plproxy;
+select * from test_err1('dat');
+
+create function test_err2(dat text)
+returns text as $$
+    cluster 'testcluster';
+    run on hashtext($2);
+$$ language plproxy;
+select * from test_err2('dat');
+
+create function test_err3(dat text)
+returns text as $$
+    cluster 'nonexists';
+    run on hashtext($1);
+$$ language plproxy;
+select * from test_err3('dat');
+
+
+--- result map errors
+create function test_map_err1(dat text)
+returns text as $$ cluster 'testcluster'; run on 0;
+    select dat as "foo", 'asd' as "bar";
+$$ language plproxy;
+select * from test_map_err1('dat');
+
+create function test_map_err2(dat text, out res1 text, out res2 text)
+returns record as $$ cluster 'testcluster'; run on 0;
+    select dat as res1;
+$$ language plproxy;
+select * from test_map_err2('dat');
+
+create function test_map_err3(dat text, out res1 text, out res2 text)
+returns record as $$ cluster 'testcluster'; run on 0;
+    select dat as res1, 'foo' as res_none;
+$$ language plproxy;
+select * from test_map_err3('dat');
+
+create function test_map_err4(dat text, out res1 text, out res2 text)
+returns record as $$ cluster 'testcluster'; run on 0;
+    select dat as res2, 'foo' as res1;
+$$ language plproxy;
+select * from test_map_err4('dat');
+
+
+
+
+
diff --git a/sql/plproxy_init.sql b/sql/plproxy_init.sql
new file mode 100644 (file)
index 0000000..a287ef8
--- /dev/null
@@ -0,0 +1,66 @@
+
+\set ECHO none
+
+\i plproxy.sql
+
+-- create cluster info functions
+create schema plproxy;
+create or replace function plproxy.get_cluster_version(cluster_name text)
+returns integer as $$
+begin
+    if cluster_name = 'testcluster' then
+        return 5;
+    end if;
+    raise exception 'no such cluster: %', cluster_name;
+end; $$ language plpgsql;
+
+create or replace function
+plproxy.get_cluster_partitions(cluster_name text)
+returns setof text as $$
+begin
+    if cluster_name = 'testcluster' then
+        return next 'host=127.0.0.1 dbname=test_part';
+        return;
+    end if;
+    raise exception 'no such cluster: %', cluster_name;
+end; $$ language plpgsql;
+
+create or replace function
+plproxy.get_cluster_config(cluster_name text, out key text, out val text)
+returns setof record as $$
+begin
+    key := 'statement_timeout';
+    val := 60;
+    return next;
+    return;
+end; $$ language plpgsql;
+
+-------------------------------------------------
+-- intialize part
+-------------------------------------------------
+drop database if exists test_part;
+create database test_part;
+\c test_part
+create language plpgsql;
+
+drop database if exists test_part0;
+create database test_part0;
+\c test_part0
+create language plpgsql;
+
+drop database if exists test_part1;
+create database test_part1;
+\c test_part1
+create language plpgsql;
+
+drop database if exists test_part2;
+create database test_part2;
+\c test_part2
+create language plpgsql;
+
+drop database if exists test_part3;
+create database test_part3;
+\c test_part3
+create language plpgsql;
+
+
diff --git a/sql/plproxy_many.sql b/sql/plproxy_many.sql
new file mode 100644 (file)
index 0000000..3560dd9
--- /dev/null
@@ -0,0 +1,66 @@
+create or replace function plproxy.get_cluster_version(cluster_name text)
+returns integer as $$
+begin
+    if cluster_name = 'testcluster' then
+        return 6;
+    end if;
+    raise exception 'no such cluster: %', cluster_name;
+end; $$ language plpgsql;
+
+create or replace function plproxy.get_cluster_partitions(cluster_name text)
+returns setof text as $$
+begin
+    if cluster_name = 'testcluster' then
+        return next 'host=127.0.0.1 dbname=test_part0';
+        return next 'host=127.0.0.1 dbname=test_part1';
+        return next 'host=127.0.0.1 dbname=test_part2';
+        return next 'host=127.0.0.1 dbname=test_part3';
+        return;
+    end if;
+    raise exception 'no such cluster: %', cluster_name;
+end; $$ language plpgsql;
+
+\c test_part0
+create function test_multi(part integer, username text)
+returns integer as $$ begin return 0; end; $$ language plpgsql;
+\c test_part1
+create function test_multi(part integer, username text)
+returns integer as $$ begin return 1; end; $$ language plpgsql;
+\c test_part2
+create function test_multi(part integer, username text)
+returns integer as $$ begin return 2; end; $$ language plpgsql;
+\c test_part3
+create function test_multi(part integer, username text)
+returns integer as $$ begin return 3; end; $$ language plpgsql;
+
+\c regression
+create function test_multi(part integer, username text)
+returns integer as $$ cluster 'testcluster'; run on int4(part); $$ language plproxy;
+select test_multi(0, 'foo');
+select test_multi(1, 'foo');
+select test_multi(2, 'foo');
+select test_multi(3, 'foo');
+
+-- test RUN ON ALL
+drop function test_multi(integer, text);
+create function test_multi(part integer, username text)
+returns setof integer as $$ cluster 'testcluster'; run on all; $$ language plproxy;
+select test_multi(0, 'foo');
+
+-- test RUN ON 2
+drop function test_multi(integer, text);
+create function test_multi(part integer, username text)
+returns setof integer as $$ cluster 'testcluster'; run on 2; $$ language plproxy;
+select test_multi(0, 'foo');
+
+-- test RUN ON RANDOM
+select setseed(0);
+drop function test_multi(integer, text);
+create function test_multi(part integer, username text)
+returns setof integer as $$ cluster 'testcluster'; run on any; $$ language plproxy;
+select test_multi(0, 'foo');
+select test_multi(0, 'foo');
+select test_multi(0, 'foo');
+select test_multi(0, 'foo');
+
+
diff --git a/sql/plproxy_select.sql b/sql/plproxy_select.sql
new file mode 100644 (file)
index 0000000..5676997
--- /dev/null
@@ -0,0 +1,37 @@
+
+-- test regular sql
+create function test_select(xuser text, tmp boolean)
+returns integer as $x$
+    cluster 'testcluster';
+    run on hashtext(xuser);
+    select /*********
+    junk ;
+    ********** ****/ id from sel_test where username = xuser
+     and ';' <> 'as;d''a ; sd'
+    and $tmp$ ; 'a' $tmp$ <> 'as;d''a ; sd'
+    and $tmp$ $ $$  $foo$tmp$ <> 'x';
+$x$ language plproxy;
+
+\c test_part
+create table sel_test (
+    id integer,
+    username text
+);
+insert into sel_test values ( 1, 'user');
+
+\c regression
+select * from test_select('user', true);
+select * from test_select('xuser', false);
+
+
+-- test errors
+create function test_select_err(xuser text, tmp boolean)
+returns integer as $$
+    cluster 'testcluster';
+    run on hashtext(xuser);
+    select id from sel_test where username = xuser;
+    select id from sel_test where username = xuser;
+$$ language plproxy;
+
+select * from test_select_err('user', true);
+
diff --git a/sql/plproxy_test.sql b/sql/plproxy_test.sql
new file mode 100644 (file)
index 0000000..ece9c99
--- /dev/null
@@ -0,0 +1,147 @@
+
+-- test normal function
+create function testfunc(username text, id integer, data text)
+returns text as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy;
+\c test_part
+create function testfunc(username text, id integer, data text)
+returns text as $$ begin return 'username=' || username; end; $$ language plpgsql;
+\c regression
+select * from testfunc('user', 1, 'foo');
+select * from testfunc('user', 1, 'foo');
+select * from testfunc('user', 1, 'foo');
+
+
+-- test setof text
+create function test_set(username text, num integer)
+returns setof text as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy;
+\c test_part
+create function test_set(username text, num integer)
+returns setof text as $$
+declare i integer;
+begin
+    i := 0;
+    while i < num loop
+        return next 'username=' || username || ' row=' || i;
+        i := i + 1;
+    end loop;
+    return;
+end; $$ language plpgsql;
+\c regression
+select * from test_set('user', 1);
+select * from test_set('user', 0);
+select * from test_set('user', 3);
+
+-- test record
+create type ret_test_rec as ( id integer, dat text);
+create function test_record(username text, num integer)
+returns ret_test_rec as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy;
+\c test_part
+create type ret_test_rec as ( id integer, dat text);
+create function test_record(username text, num integer)
+returns ret_test_rec as $$
+declare ret ret_test_rec%rowtype;
+begin
+    ret := (num, username);
+    return ret;
+end; $$ language plpgsql;
+\c regression
+select * from test_record('user', 3);
+
+-- test setof record
+create function test_record_set(username text, num integer)
+returns setof ret_test_rec as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy;
+\c test_part
+create function test_record_set(username text, num integer)
+returns setof ret_test_rec as $$
+declare ret ret_test_rec%rowtype; i integer;
+begin
+    i := 0;
+    while i < num loop
+        ret := (i, username);
+        i := i + 1;
+        return next ret;
+    end loop;
+    return;
+end; $$ language plpgsql;
+\c regression
+select * from test_record_set('user', 1);
+select * from test_record_set('user', 0);
+select * from test_record_set('user', 3);
+
+
+-- test void
+create function test_void(username text, num integer)
+returns void as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy;
+\c test_part
+create function test_void(username text, num integer)
+returns void as $$
+begin
+    return;
+end; $$ language plpgsql;
+-- look what void actually looks
+select * from test_void('void', 2);
+select test_void('void', 2);
+\c regression
+select * from test_void('user', 1);
+select * from test_void('user', 3);
+select test_void('user', 3);
+select test_void('user', 3);
+
+
+-- test normal outargs
+create function test_out1(username text, id integer, out data text)
+as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy;
+\c test_part
+create function test_out1(username text, id integer, out data text)
+returns text as $$ begin data := 'username=' || username; return; end; $$ language plpgsql;
+\c regression
+select * from test_out1('user', 1);
+
+-- test complicated outargs
+create function test_out2(username text, id integer, out out_id integer, xdata text, inout xdata2 text, out odata text)
+as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy;
+\c test_part
+create function test_out2(username text, id integer, out out_id integer, xdata text, inout xdata2 text, out odata text)
+as $$ begin
+    out_id = id;
+    xdata2 := xdata2 || xdata;
+    odata := 'username=' || username;
+    return;
+end; $$ language plpgsql;
+\c regression
+select * from test_out2('user', 1, 'xdata', 'xdata2');
+
+-- test various types
+create function test_types(username text, inout vbool boolean, inout xdate timestamp, inout bin bytea)
+as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy;
+\c test_part
+create function test_types(username text, inout vbool boolean, inout xdate timestamp, inout bin bytea)
+as $$ begin return; end; $$ language plpgsql;
+\c regression
+select * from test_types('types', true, '2009-11-04 12:12:02', E'a\\000\\001\\002b');
+select * from test_types('types', NULL, NULL, NULL);
+
+
+-- test user defined types
+create domain posint as int4 check (value > 0);
+create type struct as (id int4, data text);
+
+create function test_types2(username text, inout v_posint posint, inout v_struct struct, inout arr int8[])
+as $$ cluster 'testcluster'; run on 0; $$ language plproxy;
+
+\c test_part
+create domain posint as int4 check (value > 0);
+create type struct as (id int4, data text);
+create function test_types2(username text, inout v_posint posint, inout v_struct struct, inout arr int8[])
+as $$ begin return; end; $$ language plpgsql;
+\c regression
+select * from test_types2('types', 4, (2, 'asd'), array[1,2,3]);
+select * from test_types2('types', NULL, NULL, NULL);
+
+-- test CONNECT
+create function test_connect1() returns text
+as $$ connect 'dbname=test_part'; select current_database(); $$ language plproxy;
+select * from test_connect1();
+
+
+
diff --git a/src/cluster.c b/src/cluster.c
new file mode 100644 (file)
index 0000000..b650c74
--- /dev/null
@@ -0,0 +1,536 @@
+/*
+ * PL/Proxy - easy access to partitioned database.
+ * 
+ * Copyright (c) 2006 Sven Suursoho, Skype Technologies OÜ
+ * Copyright (c) 2007 Marko Kreen, Skype Technologies OÜ
+ * 
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Cluster info management.
+ *
+ * Info structures are kept in separate memory context: cluster_mem.
+ */
+
+#include "plproxy.h"
+
+/* Permanent memory area for cluster info structures */
+static MemoryContext cluster_mem;
+
+/*
+ * Singly linked list of clusters.
+ *
+ * For searching by name.  If there will be lots of clusters
+ * should use some faster search method, HTAB probably.
+ */
+static ProxyCluster *cluster_list = NULL;
+
+/*
+ * Similar list for fake clusters (for CONNECT functions).
+ *
+ * Cluster name will be actual connect string.
+ */
+static ProxyCluster *fake_cluster_list = NULL;
+
+/* plan for fetching cluster version */
+static void *version_plan;
+
+/* plan for fetching cluster partitions */
+static void *partlist_plan;
+
+/* plan for fetching cluster config */
+static void *config_plan;
+
+/* query for fetching cluster version */
+static const char version_sql[] = "select * from plproxy.get_cluster_version($1)";
+
+/* query for fetching cluster partitions */
+static const char part_sql[] = "select * from plproxy.get_cluster_partitions($1)";
+
+/* query for fetching cluster config */
+static const char config_sql[] = "select * from plproxy.get_cluster_config($1)";
+
+
+/*
+ * Connsetion count should be non-zero and power of 2.
+ */
+static bool
+check_valid_partcount(int n)
+{
+       return (n > 0) && !(n & (n - 1));
+}
+
+/*
+ * Create cache memory area and prepare plans
+ */
+void
+plproxy_cluster_cache_init(void)
+{
+       void       *plan;
+       Oid                     types[] = {TEXTOID};
+
+       /*
+        * create long-lived memory context
+        */
+
+       cluster_mem = AllocSetContextCreate(TopMemoryContext,
+                                                                               "PL/Proxy cluster context",
+                                                                               ALLOCSET_SMALL_MINSIZE,
+                                                                               ALLOCSET_SMALL_INITSIZE,
+                                                                               ALLOCSET_SMALL_MAXSIZE);
+
+       /*
+        * prepare plans for fetching configuration.
+        */
+
+       plan = SPI_prepare(version_sql, 1, types);
+       if (plan == NULL)
+               plproxy_error(NULL, "VERSION_SQL: %s",
+                                         SPI_result_code_string(SPI_result));
+       version_plan = SPI_saveplan(plan);
+
+       plan = SPI_prepare(part_sql, 1, types);
+       if (plan == NULL)
+               plproxy_error(NULL, "PART_SQL: %s",
+                                         SPI_result_code_string(SPI_result));
+       partlist_plan = SPI_saveplan(plan);
+
+       plan = SPI_prepare(config_sql, 1, types);
+       if (plan == NULL)
+               plproxy_error(NULL, "CONFIG_SQL: %s",
+                                         SPI_result_code_string(SPI_result));
+       config_plan = SPI_saveplan(plan);
+}
+
+/*
+ * Drop partition and connection data from cluster.
+ */
+static void
+free_connlist(ProxyCluster *cluster)
+{
+       int                     i;
+       ProxyConnection *conn;
+
+       for (i = 0; i < cluster->conn_count; i++)
+       {
+               conn = &cluster->conn_list[i];
+               if (conn->db)
+                       PQfinish(conn->db);
+               if (conn->res)
+                       PQclear(conn->res);
+               if (conn->connstr)
+                       pfree((void *) conn->connstr);
+       }
+       pfree(cluster->part_map);
+       pfree(cluster->conn_list);
+
+       cluster->part_map = NULL;
+       cluster->part_count = 0;
+       cluster->part_mask = 0;
+       cluster->conn_list = NULL;
+       cluster->conn_count = 0;
+}
+
+/*
+ * Add new database connection if it does not exists.
+ */
+static ProxyConnection *
+add_connection(ProxyCluster *cluster, char *connstr)
+{
+       int                     i,
+                               len;
+       ProxyConnection *conn;
+       MemoryContext old_ctx;
+       char       *username,
+                          *newstr;
+
+       /* append current user if not specified in connstr */
+       if (strstr(connstr, "user=") == NULL)
+       {
+               username = GetUserNameFromId(GetSessionUserId());
+               len = strlen(connstr) + strlen(username) + 6 + 1;
+               newstr = palloc(len);
+               strcpy(newstr, connstr);
+               strcat(newstr, " user=");
+               strcat(newstr, username);
+               connstr = newstr;
+       }
+
+       /* check if already have it */
+       for (i = 0; i < cluster->conn_count; i++)
+       {
+               conn = &cluster->conn_list[i];
+               if (strcmp(conn->connstr, connstr) == 0)
+                       return conn;
+       }
+
+       /* add new connection */
+       old_ctx = MemoryContextSwitchTo(cluster_mem);
+       conn = &cluster->conn_list[cluster->conn_count++];
+       conn->connstr = pstrdup(connstr);
+       MemoryContextSwitchTo(old_ctx);
+
+       return conn;
+}
+
+/*
+ * Fetch cluster version.
+ * Called for each execution.
+ */
+static int
+get_version(ProxyFunction *func, Datum dname)
+{
+       Datum           bin_val;
+       bool            isnull;
+       char            nulls[1];
+       int                     err;
+
+       nulls[0] = (dname == (Datum) NULL) ? 'n' : ' ';
+
+       err = SPI_execute_plan(version_plan, &dname, nulls, false, 0);
+       if (err != SPI_OK_SELECT)
+               plproxy_error(func, "get_version: spi error: %s",
+                                         SPI_result_code_string(err));
+       if (SPI_processed != 1)
+               plproxy_error(func, "get_version: got %d rows",
+                                         SPI_processed);
+
+       bin_val = SPI_getbinval(SPI_tuptable->vals[0],
+                                                       SPI_tuptable->tupdesc, 1, &isnull);
+       if (isnull)
+               plproxy_error(func, "get_version: got NULL?");
+
+       return DatumGetInt32(bin_val);
+}
+
+/*
+ * Fetch cluster configuration.
+ */
+static int
+get_config(ProxyCluster *cluster, Datum dname, ProxyFunction *func)
+{
+       int                     err,
+                               i;
+       TupleDesc       desc;
+       const char *key,
+                          *val;
+       ProxyConfig *cf = &cluster->config;
+
+       /* run query */
+       err = SPI_execute_plan(config_plan, &dname, NULL, false, 0);
+       if (err != SPI_OK_SELECT)
+               plproxy_error(func, "fetch_config: spi error");
+
+       /* check column types */
+       desc = SPI_tuptable->tupdesc;
+       if (desc->natts != 2)
+               plproxy_error(func, "Cluster config must have 2 columns");
+       if (SPI_gettypeid(desc, 1) != TEXTOID)
+               plproxy_error(func, "Config column 1 must be text");
+       if (SPI_gettypeid(desc, 2) != TEXTOID)
+               plproxy_error(func, "Config column 2 must be text");
+
+       /* fill values */
+       for (i = 0; i < SPI_processed; i++)
+       {
+               HeapTuple       row = SPI_tuptable->vals[i];
+
+               key = SPI_getvalue(row, desc, 1);
+               if (key == NULL)
+                       plproxy_error(func, "key must not be NULL");
+
+               val = SPI_getvalue(row, desc, 2);
+               if (val == NULL)
+                       plproxy_error(func, "val must not be NULL");
+
+               if (strcasecmp(key, "statement_timeout") == 0)
+                       cf->statement_timeout = atoi(val);
+               else if (strcasecmp("connection_lifetime", key) == 0)
+                       cf->connection_lifetime = atoi(val);
+               else if (strcasecmp("query_timeout", key) == 0)
+                       cf->query_timeout = atoi(val);
+               else if (strcasecmp("disable_binary", key) == 0)
+                       cf->disable_binary = atoi(val);
+               else
+                       plproxy_error(func, "Unknown config param: %s", key);
+       }
+
+       return 0;
+}
+
+/* fetch list of parts */
+static int
+reload_parts(ProxyCluster *cluster, Datum dname, ProxyFunction *func)
+{
+       int                     err,
+                               i;
+       ProxyConnection *conn;
+       char       *connstr;
+       MemoryContext old_ctx;
+       TupleDesc       desc;
+       HeapTuple       row;
+
+       /* run query */
+       err = SPI_execute_plan(partlist_plan, &dname, NULL, false, 0);
+       if (err != SPI_OK_SELECT)
+               plproxy_error(func, "get_partlist: spi error");
+       if (!check_valid_partcount(SPI_processed))
+               plproxy_error(func, "get_partlist: invalid part count");
+
+       /* check column types */
+       desc = SPI_tuptable->tupdesc;
+       if (desc->natts < 1)
+               plproxy_error(func, "Partition config must have at least 1 columns");
+       if (SPI_gettypeid(desc, 1) != TEXTOID)
+               plproxy_error(func, "partition column 1 must be text");
+
+       /* free old one */
+       if (cluster->conn_list)
+               free_connlist(cluster);
+
+       cluster->part_count = SPI_processed;
+       cluster->part_mask = cluster->part_count - 1;
+
+       /* allocate lists */
+       old_ctx = MemoryContextSwitchTo(cluster_mem);
+       cluster->part_map = palloc0(SPI_processed * sizeof(ProxyConnection *));
+       cluster->conn_list = palloc0(SPI_processed * sizeof(ProxyConnection));
+       MemoryContextSwitchTo(old_ctx);
+
+       /* fill values */
+       for (i = 0; i < SPI_processed; i++)
+       {
+               row = SPI_tuptable->vals[i];
+
+               connstr = SPI_getvalue(row, desc, 1);
+               if (connstr == NULL)
+                       plproxy_error(func, "connstr must not be NULL");
+
+               conn = add_connection(cluster, connstr);
+               cluster->part_map[i] = conn;
+       }
+
+       return 0;
+}
+
+/* allocate new cluster */
+static ProxyCluster *
+new_cluster(const char *name)
+{
+       ProxyCluster *cluster;
+       MemoryContext old_ctx;
+
+       old_ctx = MemoryContextSwitchTo(cluster_mem);
+
+       cluster = palloc0(sizeof(*cluster));
+       cluster->name = pstrdup(name);
+
+       cluster->config.statement_timeout = -1;
+
+       MemoryContextSwitchTo(old_ctx);
+
+       return cluster;
+}
+
+/*
+ * Get cached or create new fake cluster.
+ */
+static ProxyCluster *
+fake_cluster(ProxyFunction *func)
+{
+       ProxyCluster *cluster;
+       ProxyConnection *conn;
+       MemoryContext old_ctx;
+
+       /* search if cached */
+       for (cluster = fake_cluster_list; cluster; cluster = cluster->next)
+       {
+               if (strcmp(cluster->name, func->connect_str) == 0)
+                       break;
+       }
+
+       if (cluster)
+               return cluster;
+
+       /* create if not */
+
+       old_ctx = MemoryContextSwitchTo(cluster_mem);
+
+       cluster = palloc0(sizeof(*cluster));
+       cluster->name = pstrdup(func->connect_str);
+       cluster->version = 1;
+       cluster->part_count = 1;
+       cluster->part_mask = 0;
+       cluster->conn_count = 1;
+       cluster->part_map = palloc(sizeof(ProxyConnection *));
+       cluster->conn_list = palloc0(sizeof(ProxyConnection));
+       conn = &cluster->conn_list[0];
+       cluster->part_map[0] = conn;
+
+       conn->connstr = pstrdup(cluster->name);
+       conn->state = C_NONE;
+
+       cluster->config.statement_timeout = -1;
+
+       MemoryContextSwitchTo(old_ctx);
+
+       cluster->next = fake_cluster_list;
+       fake_cluster_list = cluster;
+
+       if (0)
+               get_config(cluster, (Datum) NULL, func);
+
+       return cluster;
+}
+
+/*
+ * Call resolve function
+ */
+static const char *
+cluster_resolve_name(ProxyFunction *func, FunctionCallInfo fcinfo)
+{
+       const char *name;
+       HeapTuple       row;
+       TupleDesc       desc;
+
+       plproxy_query_exec(func, fcinfo, func->cluster_sql);
+
+       if (SPI_processed != 1)
+               plproxy_error(func, "'%s' returned %d rows, expected 1",
+                                         func->cluster_sql->sql, SPI_processed);
+
+       desc = SPI_tuptable->tupdesc;
+       if (SPI_gettypeid(desc, 1) != TEXTOID)
+               plproxy_error(func, "expected text");
+
+       row = SPI_tuptable->vals[0];
+       name = SPI_getvalue(row, desc, 1);
+       if (name == NULL)
+               plproxy_error(func, "Cluster name map func returned NULL");
+
+       return name;
+}
+
+/*
+ * Find cached cluster of create new one.
+ *
+ * Function argument is only for error handling.
+ * Just func->cluster_name is used.
+ */
+ProxyCluster *
+plproxy_find_cluster(ProxyFunction *func, FunctionCallInfo fcinfo)
+{
+       ProxyCluster *cluster;
+       int                     cur_version;
+       const char *name;
+       Datum           dname;
+
+       if (func->connect_str)
+               return fake_cluster(func);
+
+       if (func->cluster_sql)
+               name = cluster_resolve_name(func, fcinfo);
+       else
+               name = func->cluster_name;
+
+       /* create Datum for name */
+       dname = DirectFunctionCall1(textin, CStringGetDatum(name));
+
+       /* fetch serial, also check if exists */
+       cur_version = get_version(func, dname);
+
+       /* search if cached */
+       for (cluster = cluster_list; cluster; cluster = cluster->next)
+       {
+               if (strcmp(cluster->name, name) == 0)
+                       break;
+       }
+
+       /* create if not */
+       if (!cluster)
+       {
+               cluster = new_cluster(name);
+               cluster->next = cluster_list;
+               cluster_list = cluster;
+       }
+
+       /* update if needed */
+       if (cur_version != cluster->version)
+       {
+               reload_parts(cluster, dname, func);
+               get_config(cluster, dname, func);
+               cluster->version = cur_version;
+       }
+
+       return cluster;
+}
+
+static void
+clean_cluster(ProxyCluster *cluster, struct timeval * now)
+{
+       ProxyConnection *conn;
+       ProxyConfig *cf = &cluster->config;
+       time_t          age;
+       int                     i;
+       bool            drop;
+
+       for (i = 0; i < cluster->conn_count; i++)
+       {
+               conn = &cluster->conn_list[i];
+               if (conn->res)
+               {
+                       PQclear(conn->res);
+                       conn->res = NULL;
+               }
+               if (!conn->db)
+                       continue;
+
+               drop = false;
+               if (PQstatus(conn->db) != CONNECTION_OK)
+               {
+                       drop = true;
+               }
+               else if (cf->connection_lifetime <= 0)
+               {
+                       /* no aging */
+               }
+               else
+               {
+                       age = now->tv_sec - conn->connect_time;
+                       if (age >= cf->connection_lifetime)
+                               drop = true;
+               }
+
+               if (drop)
+               {
+                       PQfinish(conn->db);
+                       conn->db = NULL;
+                       conn->state = C_NONE;
+               }
+       }
+}
+
+/*
+ * Clean old connections and results from all clusters.
+ */
+void
+plproxy_cluster_maint(struct timeval * now)
+{
+       ProxyCluster *cluster;
+
+       for (cluster = cluster_list; cluster; cluster = cluster->next)
+               clean_cluster(cluster, now);
+       for (cluster = fake_cluster_list; cluster; cluster = cluster->next)
+               clean_cluster(cluster, now);
+}
diff --git a/src/execute.c b/src/execute.c
new file mode 100644 (file)
index 0000000..623fbbe
--- /dev/null
@@ -0,0 +1,740 @@
+/*
+ * PL/Proxy - easy access to partitioned database.
+ * 
+ * Copyright (c) 2006 Sven Suursoho, Skype Technologies OÜ
+ * Copyright (c) 2007 Marko Kreen, Skype Technologies OÜ
+ * 
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Actual execution logic is here.
+ *
+ * - Tag particural databases, where query must be sent.
+ * - Send the query.
+ * - Fetch the results.
+ *
+ * Fixme:
+ * - should also loop over untagged connections, waiting for READ events?
+ *      that would allow to track conn status better.
+ */
+
+#include "plproxy.h"
+
+#include <sys/time.h>
+#include <sys/select.h>
+
+/* some error happened */
+static void
+conn_error(ProxyFunction *func, ProxyConnection *conn, const char *desc)
+{
+       plproxy_error(func, "libpq error in %s: %s",
+                                 desc, PQerrorMessage(conn->db));
+}
+
+/* Compare if major/minor match. Works on "MAJ.MIN.*" */
+static bool
+cmp_branch(const char *this, const char *that)
+{
+       int dot = 0;
+       int i;
+
+       for (i = 0; this[i] || that[i]; i++)
+       {
+               /* allow just maj.min verson */
+               if (dot && this[i] == '.' && !that[i])
+                       return true;
+               if (dot && that[i] == '.' && !this[i])
+                       return true;
+
+               /* compare, different length is also handled here */
+               if (this[i] != that[i])
+                       return false;
+
+               /* stop on second dot */
+               if (this[i] == '.' && dot++)
+                       return true;
+       }
+       return true;
+}
+
+/*
+ * Small sanity checking for new connections.
+ *
+ * Current checks:
+ * - Does there happen any encoding conversations?
+ * - Difference in standard_conforming_strings.
+ */
+static void
+check_new_connection(ProxyConnection *conn)
+{
+       const char *px_client,
+                          *px_server,
+                          *dst_client,
+                          *dst_server;
+       const char *q_server;
+       const char *dst_ver;
+       int                     srvquotes = 0;
+
+       dst_ver = PQparameterStatus(conn->db, "server_version");
+       conn->same_ver = cmp_branch(dst_ver, PG_VERSION);
+
+       q_server = PQparameterStatus(conn->db, "standard_conforming_strings");
+       if (q_server && strcasecmp(q_server, "off") != 0)
+               srvquotes = 1;
+       if (standard_conforming_strings != srvquotes)
+               elog(WARNING, "PL/Proxy: different setting of"
+                        " standard_conforming_strings");
+
+       px_client = pg_get_client_encoding_name();
+       px_server = GetDatabaseEncodingName();
+       dst_client = PQparameterStatus(conn->db, "client_encoding");
+       dst_server = PQparameterStatus(conn->db, "server_encoding");
+       if (strcmp(px_client, px_server)
+               || strcmp(px_client, dst_client)
+               || (dst_server && strcmp(px_client, dst_server)))
+       {
+               elog(WARNING, "PL/Proxy: encoding mismatch:"
+                        " proxy client/server: %s/%s,"
+                        " partition client/server: %s/%s",
+                        px_client, px_server, dst_client, dst_server);
+       }
+}
+
+/* send the query to server connection */
+static void
+send_query(ProxyFunction *func, ProxyConnection *conn,
+                  const char **values, int *plengths, int *pformats)
+{
+       int                     res;
+       struct timeval now;
+       ProxyQuery *q = func->remote_sql;
+       const char *sql;
+       ProxyConfig *cf = &func->cur_cluster->config;
+       int                     binary_result = 0;
+       StringInfoData buf;
+
+       gettimeofday(&now, NULL);
+       conn->query_time = now.tv_sec;
+
+       /* change state to tag conn unclean */
+       conn->state = C_QUERY_WRITE;
+
+       /* use binary result only on same backend ver */
+       if (cf->disable_binary == 0 && conn->same_ver)
+       {
+               /* binary recv for non-record types */
+               if (func->ret_scalar)
+               {
+                       if (func->ret_scalar->has_recv)
+                               binary_result = 1;
+               }
+               else
+               {
+                       if (func->ret_composite->use_binary)
+                               binary_result = 1;
+               }
+       }
+
+       /* prepared sql, no buffer */
+       sql = q->sql;
+       buf.data = NULL;
+
+       /* add statement_timeout to query */
+       if (cf->statement_timeout >= 0)
+       {
+               initStringInfo(&buf);
+               appendStringInfo(&buf, "SET statement_timeout=%d; %s",
+                                                cf->statement_timeout, q->sql);
+               sql = buf.data;
+       }
+
+       /* send query */
+       res = PQsendQueryParams(conn->db, q->sql, q->arg_count,
+                                                       NULL,           /* paramTypes */
+                                                       values,         /* paramValues */
+                                                       plengths,       /* paramLengths */
+                                                       pformats,       /* paramFormats */
+                                                       binary_result);         /* resultformat, 0-text, 1-bin */
+       if (!res)
+               conn_error(func, conn, "PQsendQueryParams");
+
+       /* flush it down */
+       res = PQflush(conn->db);
+
+       /* set actual state */
+       if (res > 0)
+               conn->state = C_QUERY_WRITE;
+       else if (res == 0)
+               conn->state = C_QUERY_READ;
+       else
+               conn_error(func, conn, "PQflush");
+
+       /* if buffer, free it */
+       if (buf.data)
+               pfree(buf.data);
+}
+
+/* returns false of conn should be dropped */
+static bool
+check_old_conn(ProxyFunction *func, ProxyConnection *conn, struct timeval * now)
+{
+       time_t          t;
+       int                     res;
+       fd_set          fds;
+       int                     fd;
+       struct timeval notimeout = {0, 0};
+       ProxyConfig *cf = &func->cur_cluster->config;
+
+       if (PQstatus(conn->db) != CONNECTION_OK)
+               return false;
+
+       /* check if too old */
+       if (cf->connection_lifetime > 0)
+       {
+               t = now->tv_sec - conn->connect_time;
+               if (t >= cf->connection_lifetime)
+                       return false;
+       }
+
+       /* how long ts been idle */
+       t = now->tv_sec - conn->query_time;
+#ifdef PLPROXY_IDLE_CONN_CHECK
+       if (t < PLPROXY_IDLE_CONN_CHECK)
+#else
+       if (1)
+#endif
+               return true;
+
+       /*
+        * There was a idea to call PQconsumeInput couple of times on a long-idle
+        * connections, to see if they are still alive.
+        *
+        * As this is complicated, then ATM just do a select(,,,0) on fd.
+        * Stable conn should have no events pending.
+        */
+intr_loop:
+       fd = PQsocket(conn->db);
+       FD_ZERO(&fds);
+       FD_SET(fd, &fds);
+       res = select(fd + 1, &fds, NULL, NULL, &notimeout);
+       if (res > 0)
+       {
+               elog(WARNING, "PL/Proxy: detected unstable connection");
+               return false;
+       }
+       else if (res < 0)
+       {
+               if (errno == EINTR)
+                       goto intr_loop;
+               plproxy_error(NULL, "check_old_conn: select failed: %s",
+                                         strerror(errno));
+       }
+
+       /* seems ok */
+       return true;
+}
+
+/* check existing conn status or launch new conn */
+static void
+prepare_conn(ProxyFunction *func, ProxyConnection *conn)
+{
+       struct timeval now;
+
+       gettimeofday(&now, NULL);
+
+       /* state should be C_READY or C_NONE */
+       switch (conn->state)
+       {
+               case C_DONE:
+                       conn->state = C_READY;
+               case C_READY:
+                       if (check_old_conn(func, conn, &now))
+                               return;
+
+               case C_CONNECT_READ:
+               case C_CONNECT_WRITE:
+               case C_QUERY_READ:
+               case C_QUERY_WRITE:
+                       /* close rotten connection */
+                       elog(NOTICE, "PL/Proxy: dropping stale conn");
+                       PQfinish(conn->db);
+                       conn->db = NULL;
+                       conn->state = C_NONE;
+               case C_NONE:
+                       break;
+       }
+
+       conn->connect_time = now.tv_sec;
+
+       /* launch new connection */
+       conn->db = PQconnectStart(conn->connstr);
+       if (conn->db == NULL)
+               plproxy_error(func, "No memory for PGconn");
+
+       /* tag connection dirty */
+       conn->state = C_CONNECT_WRITE;
+
+       if (PQstatus(conn->db) == CONNECTION_BAD)
+               conn_error(func, conn, "PQconnectStart");
+}
+
+/*
+ * Connection has a resultset avalable, fetch it.
+ *
+ * Returns true if there may be more results coming,
+ * false if all done.
+ */
+static bool
+another_result(ProxyFunction *func, ProxyConnection *conn)
+{
+       PGresult   *res;
+
+       /* got one */
+       res = PQgetResult(conn->db);
+       if (res == NULL)
+       {
+               conn->state = C_DONE;
+               return false;
+       }
+
+       switch (PQresultStatus(res))
+       {
+               case PGRES_TUPLES_OK:
+                       if (conn->res)
+                               conn_error(func, conn, "double result?");
+                       conn->res = res;
+                       break;
+               case PGRES_COMMAND_OK:
+                       PQclear(res);
+                       break;
+               default:
+                       PQclear(res);
+                       conn_error(func, conn, "weird result");
+       }
+       return true;
+}
+
+/*
+ * Called when select() told that conn is avail for reading/writing.
+ *
+ * It should call postgres handlers and then change state if needed.
+ */
+static void
+handle_conn(ProxyFunction *func, ProxyConnection *conn)
+{
+       int                     res;
+       PostgresPollingStatusType poll_res;
+
+       switch (conn->state)
+       {
+               case C_CONNECT_READ:
+               case C_CONNECT_WRITE:
+                       poll_res = PQconnectPoll(conn->db);
+                       switch (poll_res)
+                       {
+                               case PGRES_POLLING_WRITING:
+                                       conn->state = C_CONNECT_WRITE;
+                                       break;
+                               case PGRES_POLLING_READING:
+                                       conn->state = C_CONNECT_READ;
+                                       break;
+                               case PGRES_POLLING_OK:
+                                       conn->state = C_READY;
+                                       check_new_connection(conn);
+                                       break;
+                               case PGRES_POLLING_ACTIVE:
+                               case PGRES_POLLING_FAILED:
+                                       conn_error(func, conn, "PQconnectPoll");
+                       }
+                       break;
+               case C_QUERY_WRITE:
+                       res = PQflush(conn->db);
+                       if (res > 0)
+                               conn->state = C_QUERY_WRITE;
+                       else if (res == 0)
+                               conn->state = C_QUERY_READ;
+                       else
+                               conn_error(func, conn, "PQflush");
+                       break;
+               case C_QUERY_READ:
+                       res = PQconsumeInput(conn->db);
+                       if (res == 0)
+                               conn_error(func, conn, "PQconsumeInput");
+
+                       /* loop until PQgetResult returns NULL */
+                       while (1)
+                       {
+                               /* if PQisBusy, then incomplete result */
+                               if (PQisBusy(conn->db))
+                                       break;
+
+                               /* got one */
+                               if (!another_result(func, conn))
+                                       break;
+                       }
+               case C_NONE:
+               case C_DONE:
+               case C_READY:
+                       break;
+       }
+}
+
+/*
+ * Check if tagged connections have interesting events.
+ *
+ * Currenly uses select() as it should be enough
+ * on small number of sockets.
+ */
+static int
+poll_conns(ProxyFunction *func, ProxyCluster *cluster)
+{
+       int                     i,
+                               res,
+                               fd,
+                               fd_max = 0;
+       fd_set          read_fds;
+       fd_set          write_fds;
+       fd_set     *cur_set = NULL;
+       struct timeval timeout;
+       ProxyConnection *conn;
+
+       FD_ZERO(&read_fds);
+       FD_ZERO(&write_fds);
+
+       for (i = 0; i < cluster->conn_count; i++)
+       {
+               conn = &cluster->conn_list[i];
+               if (!conn->run_on)
+                       continue;
+
+               /* decide what to do */
+               switch (conn->state)
+               {
+                       case C_DONE:
+                       case C_READY:
+                       case C_NONE:
+                               continue;
+                       case C_CONNECT_READ:
+                       case C_QUERY_READ:
+                               cur_set = &read_fds;
+                               break;
+                       case C_CONNECT_WRITE:
+                       case C_QUERY_WRITE:
+                               cur_set = &write_fds;
+                               break;
+               }
+
+               /* add fd to proper set */
+               fd = PQsocket(conn->db);
+               if (fd > fd_max)
+                       fd_max = fd;
+               FD_SET(fd, cur_set);
+       }
+
+       /* set timeout */
+       timeout.tv_sec = 1;
+       timeout.tv_usec = 0;
+
+       /* wait for events */
+       res = select(fd_max + 1, &read_fds, &write_fds, NULL, &timeout);
+       if (res == 0)
+               return 0;
+       if (res < 0)
+       {
+               if (errno == EINTR)
+                       return 0;
+               plproxy_error(func, "select() failed: %s", strerror(errno));
+       }
+
+       /* now recheck the conns */
+       for (i = 0; i < cluster->conn_count; i++)
+       {
+               conn = &cluster->conn_list[i];
+               if (!conn->run_on)
+                       continue;
+
+               /* look in which set it should be */
+               switch (conn->state)
+               {
+                       case C_DONE:
+                       case C_READY:
+                       case C_NONE:
+                               continue;
+                       case C_CONNECT_READ:
+                       case C_QUERY_READ:
+                               cur_set = &read_fds;
+                               break;
+                       case C_CONNECT_WRITE:
+                       case C_QUERY_WRITE:
+                               cur_set = &write_fds;
+                               break;
+               }
+
+               /* check */
+               fd = PQsocket(conn->db);
+               if (FD_ISSET(fd, cur_set))
+                       handle_conn(func, conn);
+       }
+       return 1;
+}
+
+/* Check if some operation has gone over limit */
+static void
+check_timeouts(ProxyFunction *func, ProxyCluster *cluster, ProxyConnection *conn, time_t now)
+{
+       ProxyConfig *cf = &cluster->config;
+
+       switch (conn->state)
+       {
+               case C_CONNECT_READ:
+               case C_CONNECT_WRITE:
+                       if (cf->connect_timeout <= 0)
+                               break;
+                       if (now - conn->connect_time <= cf->connect_timeout)
+                               break;
+                       plproxy_error(func, "connect timeout to: %s", conn->connstr);
+                       break;
+
+               case C_QUERY_READ:
+               case C_QUERY_WRITE:
+                       if (cf->query_timeout <= 0)
+                               break;
+                       if (now - conn->query_time <= cf->query_timeout)
+                               break;
+                       plproxy_error(func, "query timeout");
+                       break;
+               default:
+                       break;
+       }
+}
+
+/* Run the query on all tagged connections in parallel */
+static void
+remote_execute(ProxyFunction *func,
+                          const char **values, int *plengths, int *pformats)
+{
+       ExecStatusType err;
+       ProxyConnection *conn;
+       ProxyCluster *cluster = func->cur_cluster;
+       int                     i,
+                               pending;
+       struct timeval now;
+
+       /* either launch connection or send query */
+       for (i = 0; i < cluster->conn_count; i++)
+       {
+               conn = &cluster->conn_list[i];
+               if (!conn->run_on)
+                       continue;
+
+               /* check if conn is alive, and launch if not */
+               prepare_conn(func, conn);
+
+               /* if conn is ready, then send query away */
+               if (conn->state == C_READY)
+                       send_query(func, conn, values, plengths, pformats);
+       }
+
+       /* now loop until all results are arrived */
+       pending = 1;
+       while (pending)
+       {
+               /* allow postgres to cancel processing */
+               CHECK_FOR_INTERRUPTS();
+
+               /* wait for events */
+               if (poll_conns(func, cluster) == 0)
+                       continue;
+
+               /* recheck */
+               pending = 0;
+               gettimeofday(&now, NULL);
+               for (i = 0; i < cluster->conn_count; i++)
+               {
+                       conn = &cluster->conn_list[i];
+                       if (!conn->run_on)
+                               continue;
+
+                       /* login finished, send query */
+                       if (conn->state == C_READY)
+                               send_query(func, conn, values, plengths, pformats);
+
+                       if (conn->state != C_DONE)
+                               pending++;
+
+                       check_timeouts(func, cluster, conn, now.tv_sec);
+               }
+       }
+
+       /* review results, calculate total */
+       for (i = 0; i < cluster->conn_count; i++)
+       {
+               conn = &cluster->conn_list[i];
+
+               if ((conn->run_on || conn->res)
+                       && !(conn->run_on && conn->res))
+                       plproxy_error(func, "run_on does not match res");
+
+               if (!conn->run_on)
+                       continue;
+
+               if (conn->state != C_DONE)
+                       plproxy_error(func, "Unfinished connection");
+               if (conn->res == NULL)
+                       plproxy_error(func, "Lost result");
+
+               err = PQresultStatus(conn->res);
+               if (err != PGRES_TUPLES_OK)
+                       plproxy_error(func, "Remote error: %s",
+                                                 PQresultErrorMessage(conn->res));
+
+               cluster->ret_total += PQntuples(conn->res);
+       }
+}
+
+/* Run hash function and tag connections */
+static void
+tag_hash_partitions(ProxyFunction *func, FunctionCallInfo fcinfo)
+{
+       int                     i;
+       TupleDesc       desc;
+       ProxyCluster *cluster = func->cur_cluster;
+
+       /* execute cached plan */
+       plproxy_query_exec(func, fcinfo, func->hash_sql);
+
+       /* get header */
+       desc = SPI_tuptable->tupdesc;
+
+       /* check if type is ok */
+       if (SPI_gettypeid(desc, 1) != INT4OID)
+               plproxy_error(func, "Hash result must be int4");
+
+       /* tag connections */
+       for (i = 0; i < SPI_processed; i++)
+       {
+               bool            isnull;
+               int                     hashval;
+               HeapTuple       row = SPI_tuptable->vals[i];
+               Datum           val = SPI_getbinval(row, desc, 1, &isnull);
+
+               if (isnull)
+                       plproxy_error(func, "Hash function returned NULL");
+               hashval = DatumGetInt32(val) & cluster->part_mask;
+               cluster->part_map[hashval]->run_on = 1;
+       }
+
+       /* sanity check */
+       if (SPI_processed == 0 || SPI_processed > 1)
+               if (!fcinfo->flinfo->fn_retset)
+                       plproxy_error(func, "Only set-returning function"
+                                                 " allows hashcount <> 1");
+}
+
+/* Clean old results and prepare for new one */
+void
+plproxy_clean_results(ProxyCluster *cluster)
+{
+       int                     i;
+       ProxyConnection *conn;
+
+       if (!cluster)
+               return;
+
+       cluster->ret_total = 0;
+       cluster->ret_cur_conn = 0;
+
+       for (i = 0; i < cluster->conn_count; i++)
+       {
+               conn = &cluster->conn_list[i];
+               if (conn->res)
+               {
+                       PQclear(conn->res);
+                       conn->res = NULL;
+               }
+               conn->pos = 0;
+               conn->run_on = 0;
+       }
+       /* conn state checks are done in prepare_conn */
+}
+
+/* Select partitions and execute query on them */
+void
+plproxy_exec(ProxyFunction *func, FunctionCallInfo fcinfo)
+{
+       const char *values[FUNC_MAX_ARGS];
+       int                     plengths[FUNC_MAX_ARGS];
+       int                     pformats[FUNC_MAX_ARGS];
+       int                     i;
+       int                     gotbin;
+       ProxyCluster *cluster = func->cur_cluster;
+
+       /* clean old results */
+       plproxy_clean_results(cluster);
+
+       /* tag interesting partitions */
+       switch (func->run_type)
+       {
+               case R_HASH:
+                       tag_hash_partitions(func, fcinfo);
+                       break;
+               case R_ALL:
+                       for (i = 0; i < cluster->part_count; i++)
+                               cluster->part_map[i]->run_on = 1;
+                       break;
+               case R_EXACT:
+                       i = func->exact_nr;
+                       if (i < 0 || i >= cluster->part_count)
+                               plproxy_error(func, "part number out of range");
+                       cluster->part_map[i]->run_on = 1;
+                       break;
+               case R_ANY:
+                       i = random() & cluster->part_mask;
+                       cluster->part_map[i]->run_on = 1;
+                       break;
+               default:
+                       plproxy_error(func, "uninitialized run_type");
+       }
+
+       /* prepare args */
+       gotbin = 0;
+       for (i = 0; i < func->remote_sql->arg_count; i++)
+       {
+               plengths[i] = 0;
+               pformats[i] = 0;
+               if (PG_ARGISNULL(i))
+               {
+                       values[i] = NULL;
+               }
+               else
+               {
+                       int                     idx = func->remote_sql->arg_lookup[i];
+                       bool            bin = cluster->config.disable_binary ? 0 : 1;
+
+                       values[i] = plproxy_send_type(func->arg_types[idx],
+                                                                                 PG_GETARG_DATUM(idx),
+                                                                                 bin,
+                                                                                 &plengths[i],
+                                                                                 &pformats[i]);
+
+                       if (pformats[i])
+                               gotbin = 1;
+               }
+       }
+
+       if (gotbin)
+               remote_execute(func, values, plengths, pformats);
+       else
+               remote_execute(func, values, NULL, NULL);
+}
diff --git a/src/function.c b/src/function.c
new file mode 100644 (file)
index 0000000..78c86a0
--- /dev/null
@@ -0,0 +1,413 @@
+/*
+ * PL/Proxy - easy access to partitioned database.
+ * 
+ * Copyright (c) 2006 Sven Suursoho, Skype Technologies OÜ
+ * Copyright (c) 2007 Marko Kreen, Skype Technologies OÜ
+ * 
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Function compilation and caching.
+ *
+ * Functions here are called with CurrentMemoryContext == SPP Proc context.
+ * They switch to per-function context only during allocations.
+ */
+
+#include "plproxy.h"
+
+
+/*
+ * Function cache entry.
+ *
+ * As PL/Proxy does not do trigger functions,
+ * its enough to index just on OID.
+ *
+ * This structure is kept in HTAB's context.
+ */
+typedef struct
+{
+       /* Key value.   Must be at the start */
+       Oid                     oid;
+       /* Pointer to function data */
+       ProxyFunction *function;
+}      HashEntry;
+
+/* Function cache */
+static HTAB *fn_cache = NULL;
+
+/*
+ * During compilation function is linked here.
+ *
+ * This avoids memleaks when throwing errors.
+ */
+static ProxyFunction *partial_func = NULL;
+
+
+
+/* Allocate memory in the function's context */
+void *
+plproxy_func_alloc(ProxyFunction *func, int size)
+{
+       return MemoryContextAlloc(func->ctx, size);
+}
+
+/* Allocate string in the function's context */
+char *
+plproxy_func_strdup(ProxyFunction *func, const char *s)
+{
+       int                     len = strlen(s) + 1;
+       char       *res = plproxy_func_alloc(func, len);
+
+       memcpy(res, s, len);
+       return res;
+}
+
+
+/* Initialize PL/Proxy function cache */
+void
+plproxy_function_cache_init(void)
+{
+       HASHCTL         ctl;
+       int                     flags;
+       int                     max_funcs = 128;
+
+       /* don't allow multiple initializations */
+       Assert(fn_cache == NULL);
+
+       MemSet(&ctl, 0, sizeof(ctl));
+       ctl.keysize = sizeof(Oid);
+       ctl.entrysize = sizeof(HashEntry);
+       ctl.hash = oid_hash;
+       flags = HASH_ELEM | HASH_FUNCTION;
+       fn_cache = hash_create("PL/Proxy function cache", max_funcs, &ctl, flags);
+}
+
+
+/* Search for function in cache */
+static ProxyFunction *
+fn_cache_lookup(Oid fn_oid)
+{
+       HashEntry  *hentry;
+
+       hentry = hash_search(fn_cache, &fn_oid, HASH_FIND, NULL);
+       if (hentry)
+               return hentry->function;
+       return NULL;
+}
+
+
+/* Insert function into cache */
+static void
+fn_cache_insert(ProxyFunction *func)
+{
+       HashEntry  *hentry;
+       bool            found;
+
+       hentry = hash_search(fn_cache, &func->oid, HASH_ENTER, &found);
+       Assert(found == false);
+
+       hentry->function = func;
+}
+
+
+/* Delete function from cache */
+static void
+fn_cache_delete(ProxyFunction *func)
+{
+       HashEntry  *hentry;
+
+       hentry = hash_search(fn_cache, &func->oid, HASH_REMOVE, NULL);
+       Assert(hentry != NULL);
+}
+
+/*
+ * Allocate storage for function.
+ *
+ * Each functions has its own MemoryContext,
+ * where everything is allocated.
+ */
+static ProxyFunction *
+fn_new(FunctionCallInfo fcinfo, HeapTuple proc_tuple)
+{
+       ProxyFunction *f;
+       MemoryContext f_ctx,
+                               old_ctx;
+
+       f_ctx = AllocSetContextCreate(TopMemoryContext,
+                                                                 "PL/Proxy function context",
+                                                                 ALLOCSET_SMALL_MINSIZE,
+                                                                 ALLOCSET_SMALL_INITSIZE,
+                                                                 ALLOCSET_SMALL_MAXSIZE);
+
+       old_ctx = MemoryContextSwitchTo(f_ctx);
+
+       f = palloc0(sizeof(*f));
+       f->ctx = f_ctx;
+       f->oid = fcinfo->flinfo->fn_oid;
+       f->xmin = HeapTupleHeaderGetXmin(proc_tuple->t_data);
+       f->cmin = HeapTupleHeaderGetCmin(proc_tuple->t_data);
+
+       MemoryContextSwitchTo(old_ctx);
+
+       return f;
+}
+
+
+/*
+ * Delete function and release all associated storage
+ *
+ * Function is also deleted from cache.
+ */
+static void
+fn_delete(ProxyFunction *func, bool in_cache)
+{
+       if (in_cache)
+               fn_cache_delete(func);
+
+       /* free cached plans */
+       plproxy_query_freeplan(func->hash_sql);
+       plproxy_query_freeplan(func->cluster_sql);
+
+       /* release function storage */
+       MemoryContextDelete(func->ctx);
+}
+
+/*
+ * Construct fully-qualified name for function.
+ */
+static void
+fn_set_name(ProxyFunction *func, HeapTuple proc_tuple)
+{
+       char            namebuf[NAMEDATALEN * 2 + 3];
+       Form_pg_proc proc_struct;
+       Form_pg_namespace ns_struct;
+       HeapTuple       ns_tup;
+       Oid                     nsoid;
+
+       proc_struct = (Form_pg_proc) GETSTRUCT(proc_tuple);
+       nsoid = proc_struct->pronamespace;
+
+       ns_tup = SearchSysCache(NAMESPACEOID,
+                                                       ObjectIdGetDatum(nsoid), 0, 0, 0);
+       if (!HeapTupleIsValid(ns_tup))
+               plproxy_error(func, "Cannot find namespace %u", nsoid);
+       ns_struct = (Form_pg_namespace) GETSTRUCT(ns_tup);
+
+       sprintf(namebuf, "%s.%s", NameStr(ns_struct->nspname),
+                       NameStr(proc_struct->proname));
+       func->name = plproxy_func_strdup(func, namebuf);
+
+       ReleaseSysCache(ns_tup);
+}
+
+
+/*
+ * Parse source.
+ *
+ * It just fetches source and calls actual parser.
+ */
+static void
+fn_parse(ProxyFunction *func, HeapTuple proc_tuple)
+{
+       bool            isnull;
+       Datum           source;
+
+       source = SysCacheGetAttr(PROCOID, proc_tuple, Anum_pg_proc_prosrc, &isnull);
+       if (isnull)
+               plproxy_error(func, "procedure source datum is null");
+
+       plproxy_run_parser(func, VARDATA(source), VARSIZE(source) - VARHDRSZ);
+}
+
+/*
+ * Get info about own arguments.
+ */
+static void
+fn_get_arguments(ProxyFunction *func,
+                                FunctionCallInfo fcinfo,
+                                HeapTuple proc_tuple)
+{
+       Oid                *types;
+       char      **names,
+                          *modes;
+       int                     i,
+                               pos,
+                               total;
+       ProxyType  *type;
+
+       total = get_func_arg_info(proc_tuple, &types, &names, &modes);
+
+       func->arg_types = plproxy_func_alloc(func, sizeof(ProxyType *) * total);
+       func->arg_names = plproxy_func_alloc(func, sizeof(char *) * total);
+       func->arg_count = 0;
+
+       for (i = 0; i < total; i++)
+       {
+               if (modes && modes[i] == 'o')
+                       continue;
+               type = plproxy_find_type_info(func, types[i], 1);
+               pos = func->arg_count++;
+               func->arg_types[pos] = type;
+               if (names && names[i])
+                       func->arg_names[pos] = plproxy_func_strdup(func, names[i]);
+               else
+                       func->arg_names[pos] = NULL;
+       }
+}
+
+/*
+ * Get info about return type.
+ *
+ * Fills one of ret_scalar or ret_composite.
+ */
+static void
+fn_get_return_type(ProxyFunction *func,
+                                  FunctionCallInfo fcinfo,
+                                  HeapTuple proc_tuple)
+{
+       Oid                     ret_oid;
+       TupleDesc       ret_tup;
+       TypeFuncClass rtc;
+       MemoryContext old_ctx;
+       int                     natts;
+
+       old_ctx = MemoryContextSwitchTo(func->ctx);
+       rtc = get_call_result_type(fcinfo, &ret_oid, &ret_tup);
+       MemoryContextSwitchTo(old_ctx);
+
+       switch (rtc)
+       {
+               case TYPEFUNC_COMPOSITE:
+                       func->ret_composite = plproxy_composite_info(func, ret_tup);
+                       natts = func->ret_composite->tupdesc->natts;
+                       func->result_map = plproxy_func_alloc(func, natts * sizeof(int));
+                       break;
+               case TYPEFUNC_SCALAR:
+                       func->ret_scalar = plproxy_find_type_info(func, ret_oid, 0);
+                       func->result_map = NULL;
+                       break;
+               case TYPEFUNC_RECORD:
+               case TYPEFUNC_OTHER:
+                       /* fixme: void type here? */
+                       plproxy_error(func, "unsupported type");
+                       break;
+       }
+}
+
+/* Show part of compilation -- get source and parse */
+static ProxyFunction *
+fn_compile(FunctionCallInfo fcinfo,
+                  HeapTuple proc_tuple,
+                  bool validate)
+{
+       ProxyFunction *f;
+       Form_pg_proc proc_struct;
+
+       proc_struct = (Form_pg_proc) GETSTRUCT(proc_tuple);
+       if (proc_struct->provolatile != 'v')
+               elog(ERROR, "PL/Proxy functions must be volatile");
+
+       f = fn_new(fcinfo, proc_tuple);
+
+       /* keep reference in case of error half-way */
+       partial_func = f;
+
+       /* info from system tables */
+       fn_set_name(f, proc_tuple);
+       fn_get_return_type(f, fcinfo, proc_tuple);
+       fn_get_arguments(f, fcinfo, proc_tuple);
+
+       /* parse body */
+       fn_parse(f, proc_tuple);
+
+       /* create SELECT stmt if not specified */
+       if (f->remote_sql == NULL)
+               f->remote_sql = plproxy_standard_query(f, true);
+
+       /* prepare local queries */
+       if (f->cluster_sql)
+               plproxy_query_prepare(f, fcinfo, f->cluster_sql);
+       if (f->hash_sql)
+               plproxy_query_prepare(f, fcinfo, f->hash_sql);
+
+       /* sanity check */
+       if (f->run_type == R_ALL && !fcinfo->flinfo->fn_retset)
+               plproxy_error(f, "RUN ON ALL requires set-returning function");
+
+       return f;
+}
+
+/*
+ * Compile and cache PL/Proxy function.
+ */
+ProxyFunction *
+plproxy_compile(FunctionCallInfo fcinfo, bool validate)
+{
+       ProxyFunction *f;
+       HeapTuple       proc_tuple;
+       Oid                     oid;
+
+       /* clean interrupted compile */
+       if (partial_func)
+       {
+               fn_delete(partial_func, false);
+               partial_func = NULL;
+       }
+
+       /* get current fn oid */
+       oid = fcinfo->flinfo->fn_oid;
+
+       /* lookup the pg_proc tuple */
+       proc_tuple = SearchSysCache(PROCOID, ObjectIdGetDatum(oid), 0, 0, 0);
+       if (!HeapTupleIsValid(proc_tuple))
+               elog(ERROR, "cache lookup failed for function %u", oid);
+
+       /* fn_extra not used, do lookup */
+       f = fn_cache_lookup(oid);
+
+       /* got, but is it still valid? */
+       if (f)
+       {
+               bool            drop = 0;
+
+               if (f->xmin != HeapTupleHeaderGetXmin(proc_tuple->t_data))
+               {
+                       drop = 1;
+               }
+               else if (f->cmin == HeapTupleHeaderGetCmin(proc_tuple->t_data))
+               {
+                       drop = 1;
+               }
+
+               if (drop)
+               {
+                       fn_delete(f, true);
+                       f = NULL;
+               }
+       }
+
+       if (!f)
+       {
+               f = fn_compile(fcinfo, proc_tuple, validate);
+
+               fn_cache_insert(f);
+
+               /* now its safe to drop reference */
+               partial_func = NULL;
+       }
+
+       ReleaseSysCache(proc_tuple);
+
+       return f;
+}
diff --git a/src/main.c b/src/main.c
new file mode 100644 (file)
index 0000000..3ef0709
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ * PL/Proxy - easy access to partitioned database.
+ * 
+ * Copyright (c) 2006 Sven Suursoho, Skype Technologies OÜ
+ * Copyright (c) 2007 Marko Kreen, Skype Technologies OÜ
+ * 
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * External interface for PostgreSQL core.
+ *
+ * List of memory contexts that are touched by this code:
+ *
+ * - Query context that is active when plproxy_call_handler is called.
+ *      Function results should be allocated from here.
+ *
+ * - SPI Proc context that activates in SPI_connect() and is freed
+ *      in SPI_finish().  This is used for compile-time short-term storage.
+ *
+ * - HTAB has its own memory context.
+ *
+ * - ProxyFunction->ctx for long-term allocations for functions.
+ *
+ * - cluster_mem where info about clusters is stored.
+ *
+ * - SPI_saveplan() stores plan info in separate context,
+ *      so it must be freed explicitly.
+ *
+ * - libpq uses malloc() so it must be freed explicitly
+ *
+ * Because SPI functions do not honour CurrentMemoryContext
+ * and code should not have assumptions whether core
+ * functions do allocations or not, the per-function and
+ * cluster MemoryContext is switched on only when doing actual
+ * allocations.  Otherwise the default context is kept.
+ */
+
+#include "plproxy.h"
+
+#include <sys/time.h>
+
+#ifndef PG_MODULE_MAGIC
+#error PL/Proxy requires 8.2
+#else
+PG_MODULE_MAGIC;
+#endif
+
+PG_FUNCTION_INFO_V1(plproxy_call_handler);
+
+/*
+ * Centralised error reporting.
+ *
+ * Also frees any pending results.
+ */
+void
+plproxy_error(ProxyFunction *func, const char *fmt,...)
+{
+       char            msg[1024];
+       va_list         ap;
+
+       va_start(ap, fmt);
+       vsnprintf(msg, sizeof(msg), fmt, ap);
+       va_end(ap);
+
+       plproxy_clean_results(func->cur_cluster);
+
+       elog(ERROR, "PL/Proxy function %s(%d): %s",
+                func->name, func->arg_count, msg);
+}
+
+/*
+ * Library load-time initialization.
+ * Do the initialization when SPI is active to simplify the code.
+ */
+static void
+plproxy_startup_init(void)
+{
+       static bool initialized = false;
+
+       if (initialized)
+               return;
+
+       plproxy_function_cache_init();
+       plproxy_cluster_cache_init();
+
+       initialized = true;
+}
+
+/*
+ * Regular maintenance over all clusters.
+ */
+static void
+run_maint(void)
+{
+       static struct timeval last = {0, 0};
+       struct timeval now;
+
+       gettimeofday(&now, NULL);
+       if (now.tv_sec - last.tv_sec < 2 * 60)
+               return;
+       last = now;
+
+       plproxy_cluster_maint(&now);
+}
+
+/*
+ * Do compilation and execution under SPI.
+ *
+ * Result conversion will be done without SPI.
+ */
+static ProxyFunction *
+compile_and_execute(FunctionCallInfo fcinfo)
+{
+       int                     err;
+       ProxyFunction *func;
+       ProxyCluster *cluster;
+
+       /* prepare SPI */
+       err = SPI_connect();
+       if (err != SPI_OK_CONNECT)
+               elog(ERROR, "SPI_connect: %s", SPI_result_code_string(err));
+
+       /* do the initialization also under SPI */
+       plproxy_startup_init();
+
+       /* compile code */
+       func = plproxy_compile(fcinfo, false);
+
+       /* get actual cluster to run on */
+       cluster = plproxy_find_cluster(func, fcinfo);
+
+       /* fetch PGresults */
+       func->cur_cluster = cluster;
+       plproxy_exec(func, fcinfo);
+
+       /* done with SPI */
+       err = SPI_finish();
+       if (err != SPI_OK_FINISH)
+               elog(ERROR, "SPI_finish: %s", SPI_result_code_string(err));
+
+       return func;
+}
+
+/*
+ * Logic for set-returning functions.
+ *
+ * Currently it uses the simplest, return
+ * one value/tuple per call mechanism.
+ */
+static Datum
+handle_ret_set(FunctionCallInfo fcinfo)
+{
+       ProxyFunction *func;
+       FuncCallContext *ret_ctx;
+
+       if (SRF_IS_FIRSTCALL())
+       {
+               func = compile_and_execute(fcinfo);
+               ret_ctx = SRF_FIRSTCALL_INIT();
+               ret_ctx->user_fctx = func;
+       }
+
+       ret_ctx = SRF_PERCALL_SETUP();
+       func = ret_ctx->user_fctx;
+
+       if (func->cur_cluster->ret_total > 0)
+       {
+               SRF_RETURN_NEXT(ret_ctx, plproxy_result(func, fcinfo));
+       }
+       else
+       {
+               plproxy_clean_results(func->cur_cluster);
+               SRF_RETURN_DONE(ret_ctx);
+       }
+}
+
+/*
+ * The PostgreSQL function & trigger manager calls this function
+ * for execution of PL/Proxy procedures.
+ *
+ * Main entry point for rest of the code.
+ */
+Datum
+plproxy_call_handler(PG_FUNCTION_ARGS)
+{
+       ProxyFunction *func;
+       Datum           ret;
+
+       if (CALLED_AS_TRIGGER(fcinfo))
+               elog(ERROR, "PL/Proxy procedures can't be used as triggers");
+
+       /* clean old results */
+       if (!fcinfo->flinfo->fn_retset || SRF_IS_FIRSTCALL())
+               run_maint();
+
+       if (fcinfo->flinfo->fn_retset)
+       {
+               ret = handle_ret_set(fcinfo);
+       }
+       else
+       {
+               func = compile_and_execute(fcinfo);
+               ret = plproxy_result(func, fcinfo);
+               plproxy_clean_results(func->cur_cluster);
+       }
+       return ret;
+}
diff --git a/src/parser.tab.c b/src/parser.tab.c
new file mode 100644 (file)
index 0000000..38d6970
--- /dev/null
@@ -0,0 +1,1778 @@
+/* A Bison parser, made by GNU Bison 2.3.  */
+
+/* Skeleton implementation for Bison's Yacc-like parsers in C
+
+   Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+   Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.  */
+
+/* As a special exception, you may create a larger work that contains
+   part or all of the Bison parser skeleton and distribute that work
+   under terms of your choice, so long as that work isn't itself a
+   parser generator using the skeleton or a modified version thereof
+   as a parser skeleton.  Alternatively, if you modify or redistribute
+   the parser skeleton itself, you may (at your option) remove this
+   special exception, which will cause the skeleton and the resulting
+   Bison output files to be licensed under the GNU General Public
+   License without this special exception.
+
+   This special exception was added by the Free Software Foundation in
+   version 2.2 of Bison.  */
+
+/* C LALR(1) parser skeleton written by Richard Stallman, by
+   simplifying the original so-called "semantic" parser.  */
+
+/* All symbols defined below should begin with yy or YY, to avoid
+   infringing on user name space.  This should be done even for local
+   variables, as they might otherwise be expanded by user macros.
+   There are some unavoidable exceptions within include files to
+   define necessary library symbols; they are noted "INFRINGES ON
+   USER NAME SPACE" below.  */
+
+/* Identify Bison output.  */
+#define YYBISON 1
+
+/* Bison version.  */
+#define YYBISON_VERSION "2.3"
+
+/* Skeleton name.  */
+#define YYSKELETON_NAME "yacc.c"
+
+/* Pure parsers.  */
+#define YYPURE 0
+
+/* Using locations.  */
+#define YYLSP_NEEDED 0
+
+/* Substitute the variable and function names.  */
+#define yyparse plproxy_yyparse
+#define yylex   plproxy_yylex
+#define yyerror plproxy_yyerror
+#define yylval  plproxy_yylval
+#define yychar  plproxy_yychar
+#define yydebug plproxy_yydebug
+#define yynerrs plproxy_yynerrs
+
+
+/* Tokens.  */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+   /* Put the tokens into the symbol table, so that GDB and other debuggers
+      know about them.  */
+   enum yytokentype {
+     CONNECT = 258,
+     CLUSTER = 259,
+     RUN = 260,
+     ON = 261,
+     ALL = 262,
+     ANY = 263,
+     SELECT = 264,
+     IDENT = 265,
+     CONST = 266,
+     NUMBER = 267,
+     FNCALL = 268,
+     STRING = 269,
+     SQLIDENT = 270,
+     SQLPART = 271
+   };
+#endif
+/* Tokens.  */
+#define CONNECT 258
+#define CLUSTER 259
+#define RUN 260
+#define ON 261
+#define ALL 262
+#define ANY 263
+#define SELECT 264
+#define IDENT 265
+#define CONST 266
+#define NUMBER 267
+#define FNCALL 268
+#define STRING 269
+#define SQLIDENT 270
+#define SQLPART 271
+
+
+
+
+/* Copy the first part of user declarations.  */
+#line 1 "parser.y"
+
+/*
+ * PL/Proxy - easy access to partitioned database.
+ * 
+ * Copyright (c) 2006 Sven Suursoho, Skype Technologies OÜ
+ * Copyright (c) 2007 Marko Kreen, Skype Technologies OÜ
+ * 
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "plproxy.h"
+#include "scanner.h"
+
+/* avoid permanent allocations */
+#define YYSTACK_USE_ALLOCA 1
+/* remove unused code */
+#define YY_LOCATION_PRINT(File, Loc) (0)
+#define YY_(x) (x)
+
+/* during parsing, keep reference to function here */
+static ProxyFunction *xfunc;
+
+/* remember what happened */
+static int got_run, got_cluster, got_connect;
+
+static QueryBuffer *cluster_sql;
+static QueryBuffer *select_sql;
+static QueryBuffer *hash_sql;
+static QueryBuffer *cur_sql;
+
+
+
+/* Enabling traces.  */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+
+/* Enabling verbose error messages.  */
+#ifdef YYERROR_VERBOSE
+# undef YYERROR_VERBOSE
+# define YYERROR_VERBOSE 1
+#else
+# define YYERROR_VERBOSE 0
+#endif
+
+/* Enabling the token table.  */
+#ifndef YYTOKEN_TABLE
+# define YYTOKEN_TABLE 0
+#endif
+
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+typedef union YYSTYPE
+#line 50 "parser.y"
+{
+       const char *str;
+}
+/* Line 187 of yacc.c.  */
+#line 182 "parser.tab.c"
+       YYSTYPE;
+# define yystype YYSTYPE /* obsolescent; will be withdrawn */
+# define YYSTYPE_IS_DECLARED 1
+# define YYSTYPE_IS_TRIVIAL 1
+#endif
+
+
+
+/* Copy the second part of user declarations.  */
+
+
+/* Line 216 of yacc.c.  */
+#line 195 "parser.tab.c"
+
+#ifdef short
+# undef short
+#endif
+
+#ifdef YYTYPE_UINT8
+typedef YYTYPE_UINT8 yytype_uint8;
+#else
+typedef unsigned char yytype_uint8;
+#endif
+
+#ifdef YYTYPE_INT8
+typedef YYTYPE_INT8 yytype_int8;
+#elif (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+typedef signed char yytype_int8;
+#else
+typedef short int yytype_int8;
+#endif
+
+#ifdef YYTYPE_UINT16
+typedef YYTYPE_UINT16 yytype_uint16;
+#else
+typedef unsigned short int yytype_uint16;
+#endif
+
+#ifdef YYTYPE_INT16
+typedef YYTYPE_INT16 yytype_int16;
+#else
+typedef short int yytype_int16;
+#endif
+
+#ifndef YYSIZE_T
+# ifdef __SIZE_TYPE__
+#  define YYSIZE_T __SIZE_TYPE__
+# elif defined size_t
+#  define YYSIZE_T size_t
+# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+#  include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+#  define YYSIZE_T size_t
+# else
+#  define YYSIZE_T unsigned int
+# endif
+#endif
+
+#define YYSIZE_MAXIMUM ((YYSIZE_T) -1)
+
+#ifndef YY_
+# if YYENABLE_NLS
+#  if ENABLE_NLS
+#   include <libintl.h> /* INFRINGES ON USER NAME SPACE */
+#   define YY_(msgid) dgettext ("bison-runtime", msgid)
+#  endif
+# endif
+# ifndef YY_
+#  define YY_(msgid) msgid
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E.  */
+#if ! defined lint || defined __GNUC__
+# define YYUSE(e) ((void) (e))
+#else
+# define YYUSE(e) /* empty */
+#endif
+
+/* Identity function, used to suppress warnings about constant conditions.  */
+#ifndef lint
+# define YYID(n) (n)
+#else
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static int
+YYID (int i)
+#else
+static int
+YYID (i)
+    int i;
+#endif
+{
+  return i;
+}
+#endif
+
+#if ! defined yyoverflow || YYERROR_VERBOSE
+
+/* The parser invokes alloca or malloc; define the necessary symbols.  */
+
+# ifdef YYSTACK_USE_ALLOCA
+#  if YYSTACK_USE_ALLOCA
+#   ifdef __GNUC__
+#    define YYSTACK_ALLOC __builtin_alloca
+#   elif defined __BUILTIN_VA_ARG_INCR
+#    include <alloca.h> /* INFRINGES ON USER NAME SPACE */
+#   elif defined _AIX
+#    define YYSTACK_ALLOC __alloca
+#   elif defined _MSC_VER
+#    include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+#    define alloca _alloca
+#   else
+#    define YYSTACK_ALLOC alloca
+#    if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+#     include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+#     ifndef _STDLIB_H
+#      define _STDLIB_H 1
+#     endif
+#    endif
+#   endif
+#  endif
+# endif
+
+# ifdef YYSTACK_ALLOC
+   /* Pacify GCC's `empty if-body' warning.  */
+#  define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0))
+#  ifndef YYSTACK_ALLOC_MAXIMUM
+    /* The OS might guarantee only one guard page at the bottom of the stack,
+       and a page size can be as small as 4096 bytes.  So we cannot safely
+       invoke alloca (N) if N exceeds 4096.  Use a slightly smaller number
+       to allow for a few compiler-allocated temporary stack slots.  */
+#   define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
+#  endif
+# else
+#  define YYSTACK_ALLOC YYMALLOC
+#  define YYSTACK_FREE YYFREE
+#  ifndef YYSTACK_ALLOC_MAXIMUM
+#   define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
+#  endif
+#  if (defined __cplusplus && ! defined _STDLIB_H \
+       && ! ((defined YYMALLOC || defined malloc) \
+            && (defined YYFREE || defined free)))
+#   include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+#   ifndef _STDLIB_H
+#    define _STDLIB_H 1
+#   endif
+#  endif
+#  ifndef YYMALLOC
+#   define YYMALLOC malloc
+#   if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
+#   endif
+#  endif
+#  ifndef YYFREE
+#   define YYFREE free
+#   if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+void free (void *); /* INFRINGES ON USER NAME SPACE */
+#   endif
+#  endif
+# endif
+#endif /* ! defined yyoverflow || YYERROR_VERBOSE */
+
+
+#if (! defined yyoverflow \
+     && (! defined __cplusplus \
+        || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
+
+/* A type that is properly aligned for any stack member.  */
+union yyalloc
+{
+  yytype_int16 yyss;
+  YYSTYPE yyvs;
+  };
+
+/* The size of the maximum gap between one aligned stack and the next.  */
+# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1)
+
+/* The size of an array large to enough to hold all stacks, each with
+   N elements.  */
+# define YYSTACK_BYTES(N) \
+     ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \
+      + YYSTACK_GAP_MAXIMUM)
+
+/* Copy COUNT objects from FROM to TO.  The source and destination do
+   not overlap.  */
+# ifndef YYCOPY
+#  if defined __GNUC__ && 1 < __GNUC__
+#   define YYCOPY(To, From, Count) \
+      __builtin_memcpy (To, From, (Count) * sizeof (*(From)))
+#  else
+#   define YYCOPY(To, From, Count)             \
+      do                                       \
+       {                                       \
+         YYSIZE_T yyi;                         \
+         for (yyi = 0; yyi < (Count); yyi++)   \
+           (To)[yyi] = (From)[yyi];            \
+       }                                       \
+      while (YYID (0))
+#  endif
+# endif
+
+/* Relocate STACK from its old location to the new one.  The
+   local variables YYSIZE and YYSTACKSIZE give the old and new number of
+   elements in the stack, and YYPTR gives the new location of the
+   stack.  Advance YYPTR to a properly aligned location for the next
+   stack.  */
+# define YYSTACK_RELOCATE(Stack)                                       \
+    do                                                                 \
+      {                                                                        \
+       YYSIZE_T yynewbytes;                                            \
+       YYCOPY (&yyptr->Stack, Stack, yysize);                          \
+       Stack = &yyptr->Stack;                                          \
+       yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \
+       yyptr += yynewbytes / sizeof (*yyptr);                          \
+      }                                                                        \
+    while (YYID (0))
+
+#endif
+
+/* YYFINAL -- State number of the termination state.  */
+#define YYFINAL  2
+/* YYLAST -- Last index in YYTABLE.  */
+#define YYLAST   29
+
+/* YYNTOKENS -- Number of terminals.  */
+#define YYNTOKENS  18
+/* YYNNTS -- Number of nonterminals.  */
+#define YYNNTS  16
+/* YYNRULES -- Number of rules.  */
+#define YYNRULES  26
+/* YYNRULES -- Number of states.  */
+#define YYNSTATES  38
+
+/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX.  */
+#define YYUNDEFTOK  2
+#define YYMAXUTOK   271
+
+#define YYTRANSLATE(YYX)                                               \
+  ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK)
+
+/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX.  */
+static const yytype_uint8 yytranslate[] =
+{
+       0,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,    17,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     1,     2,     3,     4,
+       5,     6,     7,     8,     9,    10,    11,    12,    13,    14,
+      15,    16
+};
+
+#if YYDEBUG
+/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in
+   YYRHS.  */
+static const yytype_uint8 yyprhs[] =
+{
+       0,     0,     3,     4,     7,     9,    11,    13,    15,    19,
+      21,    25,    27,    30,    32,    34,    39,    42,    44,    46,
+      48,    50,    54,    56,    58,    61,    63
+};
+
+/* YYRHS -- A `-1'-separated list of the rules' RHS.  */
+static const yytype_int8 yyrhs[] =
+{
+      19,     0,    -1,    -1,    19,    20,    -1,    23,    -1,    27,
+      -1,    30,    -1,    21,    -1,     3,    22,    17,    -1,    14,
+      -1,     4,    24,    17,    -1,    26,    -1,    25,    32,    -1,
+      13,    -1,    14,    -1,     5,     6,    28,    17,    -1,    29,
+      32,    -1,    12,    -1,     8,    -1,     7,    -1,    13,    -1,
+      31,    32,    17,    -1,     9,    -1,    33,    -1,    32,    33,
+      -1,    16,    -1,    15,    -1
+};
+
+/* YYRLINE[YYN] -- source line where rule number YYN was defined.  */
+static const yytype_uint8 yyrline[] =
+{
+       0,    56,    56,    56,    58,    58,    58,    58,    60,    67,
+      70,    76,    76,    79,    85,    88,    93,    94,    95,    96,
+      99,   105,   107,   113,   114,   116,   117
+};
+#endif
+
+#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE
+/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+   First, the terminals, then, starting at YYNTOKENS, nonterminals.  */
+static const char *const yytname[] =
+{
+  "$end", "error", "$undefined", "CONNECT", "CLUSTER", "RUN", "ON", "ALL",
+  "ANY", "SELECT", "IDENT", "CONST", "NUMBER", "FNCALL", "STRING",
+  "SQLIDENT", "SQLPART", "';'", "$accept", "body", "stmt", "connect_stmt",
+  "connect_spec", "cluster_stmt", "cluster_spec", "cluster_func",
+  "cluster_name", "run_stmt", "run_spec", "hash_func", "select_stmt",
+  "sql_start", "sql_token_list", "sql_token", 0
+};
+#endif
+
+# ifdef YYPRINT
+/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to
+   token YYLEX-NUM.  */
+static const yytype_uint16 yytoknum[] =
+{
+       0,   256,   257,   258,   259,   260,   261,   262,   263,   264,
+     265,   266,   267,   268,   269,   270,   271,    59
+};
+# endif
+
+/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives.  */
+static const yytype_uint8 yyr1[] =
+{
+       0,    18,    19,    19,    20,    20,    20,    20,    21,    22,
+      23,    24,    24,    25,    26,    27,    28,    28,    28,    28,
+      29,    30,    31,    32,    32,    33,    33
+};
+
+/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN.  */
+static const yytype_uint8 yyr2[] =
+{
+       0,     2,     0,     2,     1,     1,     1,     1,     3,     1,
+       3,     1,     2,     1,     1,     4,     2,     1,     1,     1,
+       1,     3,     1,     1,     2,     1,     1
+};
+
+/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state
+   STATE-NUM when YYTABLE doesn't specify something else to do.  Zero
+   means the default is an error.  */
+static const yytype_uint8 yydefact[] =
+{
+       2,     0,     1,     0,     0,     0,    22,     3,     7,     4,
+       5,     6,     0,     9,     0,    13,    14,     0,     0,    11,
+       0,    26,    25,     0,    23,     8,    10,    12,    19,    18,
+      17,    20,     0,     0,    21,    24,    15,    16
+};
+
+/* YYDEFGOTO[NTERM-NUM].  */
+static const yytype_int8 yydefgoto[] =
+{
+      -1,     1,     7,     8,    14,     9,    17,    18,    19,    10,
+      32,    33,    11,    12,    23,    24
+};
+
+/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+   STATE-NUM.  */
+#define YYPACT_NINF -22
+static const yytype_int8 yypact[] =
+{
+     -22,     4,   -22,   -13,     6,    -3,   -22,   -22,   -22,   -22,
+     -22,   -22,     9,   -22,   -12,   -22,   -22,    11,     9,   -22,
+      10,   -22,   -22,    -5,   -22,   -22,   -22,     9,   -22,   -22,
+     -22,   -22,    12,     9,   -22,   -22,   -22,     9
+};
+
+/* YYPGOTO[NTERM-NUM].  */
+static const yytype_int8 yypgoto[] =
+{
+     -22,   -22,   -22,   -22,   -22,   -22,   -22,   -22,   -22,   -22,
+     -22,   -22,   -22,   -22,   -18,   -21
+};
+
+/* YYTABLE[YYPACT[STATE-NUM]].  What to do in state STATE-NUM.  If
+   positive, shift that token.  If negative, reduce the rule which
+   number is the opposite.  If zero, do what YYDEFACT says.
+   If YYTABLE_NINF, syntax error.  */
+#define YYTABLE_NINF -1
+static const yytype_uint8 yytable[] =
+{
+      27,    13,    35,    20,     2,    25,    35,     3,     4,     5,
+      21,    22,    34,     6,     0,    37,    35,    28,    29,    15,
+      16,     0,    30,    31,    21,    22,     0,     0,    26,    36
+};
+
+static const yytype_int8 yycheck[] =
+{
+      18,    14,    23,     6,     0,    17,    27,     3,     4,     5,
+      15,    16,    17,     9,    -1,    33,    37,     7,     8,    13,
+      14,    -1,    12,    13,    15,    16,    -1,    -1,    17,    17
+};
+
+/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing
+   symbol of state STATE-NUM.  */
+static const yytype_uint8 yystos[] =
+{
+       0,    19,     0,     3,     4,     5,     9,    20,    21,    23,
+      27,    30,    31,    14,    22,    13,    14,    24,    25,    26,
+       6,    15,    16,    32,    33,    17,    17,    32,     7,     8,
+      12,    13,    28,    29,    17,    33,    17,    32
+};
+
+#define yyerrok                (yyerrstatus = 0)
+#define yyclearin      (yychar = YYEMPTY)
+#define YYEMPTY                (-2)
+#define YYEOF          0
+
+#define YYACCEPT       goto yyacceptlab
+#define YYABORT                goto yyabortlab
+#define YYERROR                goto yyerrorlab
+
+
+/* Like YYERROR except do call yyerror.  This remains here temporarily
+   to ease the transition to the new meaning of YYERROR, for GCC.
+   Once GCC version 2 has supplanted version 1, this can go.  */
+
+#define YYFAIL         goto yyerrlab
+
+#define YYRECOVERING()  (!!yyerrstatus)
+
+#define YYBACKUP(Token, Value)                                 \
+do                                                             \
+  if (yychar == YYEMPTY && yylen == 1)                         \
+    {                                                          \
+      yychar = (Token);                                                \
+      yylval = (Value);                                                \
+      yytoken = YYTRANSLATE (yychar);                          \
+      YYPOPSTACK (1);                                          \
+      goto yybackup;                                           \
+    }                                                          \
+  else                                                         \
+    {                                                          \
+      yyerror (YY_("syntax error: cannot back up")); \
+      YYERROR;                                                 \
+    }                                                          \
+while (YYID (0))
+
+
+#define YYTERROR       1
+#define YYERRCODE      256
+
+
+/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N].
+   If N is 0, then set CURRENT to the empty location which ends
+   the previous symbol: RHS[0] (always defined).  */
+
+#define YYRHSLOC(Rhs, K) ((Rhs)[K])
+#ifndef YYLLOC_DEFAULT
+# define YYLLOC_DEFAULT(Current, Rhs, N)                               \
+    do                                                                 \
+      if (YYID (N))                                                    \
+       {                                                               \
+         (Current).first_line   = YYRHSLOC (Rhs, 1).first_line;        \
+         (Current).first_column = YYRHSLOC (Rhs, 1).first_column;      \
+         (Current).last_line    = YYRHSLOC (Rhs, N).last_line;         \
+         (Current).last_column  = YYRHSLOC (Rhs, N).last_column;       \
+       }                                                               \
+      else                                                             \
+       {                                                               \
+         (Current).first_line   = (Current).last_line   =              \
+           YYRHSLOC (Rhs, 0).last_line;                                \
+         (Current).first_column = (Current).last_column =              \
+           YYRHSLOC (Rhs, 0).last_column;                              \
+       }                                                               \
+    while (YYID (0))
+#endif
+
+
+/* YY_LOCATION_PRINT -- Print the location on the stream.
+   This macro was not mandated originally: define only if we know
+   we won't break user code: when these are the locations we know.  */
+
+#ifndef YY_LOCATION_PRINT
+# if YYLTYPE_IS_TRIVIAL
+#  define YY_LOCATION_PRINT(File, Loc)                 \
+     fprintf (File, "%d.%d-%d.%d",                     \
+             (Loc).first_line, (Loc).first_column,     \
+             (Loc).last_line,  (Loc).last_column)
+# else
+#  define YY_LOCATION_PRINT(File, Loc) ((void) 0)
+# endif
+#endif
+
+
+/* YYLEX -- calling `yylex' with the right arguments.  */
+
+#ifdef YYLEX_PARAM
+# define YYLEX yylex (YYLEX_PARAM)
+#else
+# define YYLEX yylex ()
+#endif
+
+/* Enable debugging if requested.  */
+#if YYDEBUG
+
+# ifndef YYFPRINTF
+#  include <stdio.h> /* INFRINGES ON USER NAME SPACE */
+#  define YYFPRINTF fprintf
+# endif
+
+# define YYDPRINTF(Args)                       \
+do {                                           \
+  if (yydebug)                                 \
+    YYFPRINTF Args;                            \
+} while (YYID (0))
+
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location)                   \
+do {                                                                     \
+  if (yydebug)                                                           \
+    {                                                                    \
+      YYFPRINTF (stderr, "%s ", Title);                                          \
+      yy_symbol_print (stderr,                                           \
+                 Type, Value); \
+      YYFPRINTF (stderr, "\n");                                                  \
+    }                                                                    \
+} while (YYID (0))
+
+
+/*--------------------------------.
+| Print this symbol on YYOUTPUT.  |
+`--------------------------------*/
+
+/*ARGSUSED*/
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static void
+yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep)
+#else
+static void
+yy_symbol_value_print (yyoutput, yytype, yyvaluep)
+    FILE *yyoutput;
+    int yytype;
+    YYSTYPE const * const yyvaluep;
+#endif
+{
+  if (!yyvaluep)
+    return;
+# ifdef YYPRINT
+  if (yytype < YYNTOKENS)
+    YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep);
+# else
+  YYUSE (yyoutput);
+# endif
+  switch (yytype)
+    {
+      default:
+       break;
+    }
+}
+
+
+/*--------------------------------.
+| Print this symbol on YYOUTPUT.  |
+`--------------------------------*/
+
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static void
+yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep)
+#else
+static void
+yy_symbol_print (yyoutput, yytype, yyvaluep)
+    FILE *yyoutput;
+    int yytype;
+    YYSTYPE const * const yyvaluep;
+#endif
+{
+  if (yytype < YYNTOKENS)
+    YYFPRINTF (yyoutput, "token %s (", yytname[yytype]);
+  else
+    YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]);
+
+  yy_symbol_value_print (yyoutput, yytype, yyvaluep);
+  YYFPRINTF (yyoutput, ")");
+}
+
+/*------------------------------------------------------------------.
+| yy_stack_print -- Print the state stack from its BOTTOM up to its |
+| TOP (included).                                                   |
+`------------------------------------------------------------------*/
+
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static void
+yy_stack_print (yytype_int16 *bottom, yytype_int16 *top)
+#else
+static void
+yy_stack_print (bottom, top)
+    yytype_int16 *bottom;
+    yytype_int16 *top;
+#endif
+{
+  YYFPRINTF (stderr, "Stack now");
+  for (; bottom <= top; ++bottom)
+    YYFPRINTF (stderr, " %d", *bottom);
+  YYFPRINTF (stderr, "\n");
+}
+
+# define YY_STACK_PRINT(Bottom, Top)                           \
+do {                                                           \
+  if (yydebug)                                                 \
+    yy_stack_print ((Bottom), (Top));                          \
+} while (YYID (0))
+
+
+/*------------------------------------------------.
+| Report that the YYRULE is going to be reduced.  |
+`------------------------------------------------*/
+
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static void
+yy_reduce_print (YYSTYPE *yyvsp, int yyrule)
+#else
+static void
+yy_reduce_print (yyvsp, yyrule)
+    YYSTYPE *yyvsp;
+    int yyrule;
+#endif
+{
+  int yynrhs = yyr2[yyrule];
+  int yyi;
+  unsigned long int yylno = yyrline[yyrule];
+  YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n",
+            yyrule - 1, yylno);
+  /* The symbols being reduced.  */
+  for (yyi = 0; yyi < yynrhs; yyi++)
+    {
+      fprintf (stderr, "   $%d = ", yyi + 1);
+      yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi],
+                      &(yyvsp[(yyi + 1) - (yynrhs)])
+                                      );
+      fprintf (stderr, "\n");
+    }
+}
+
+# define YY_REDUCE_PRINT(Rule)         \
+do {                                   \
+  if (yydebug)                         \
+    yy_reduce_print (yyvsp, Rule); \
+} while (YYID (0))
+
+/* Nonzero means print parse trace.  It is left uninitialized so that
+   multiple parsers can coexist.  */
+int yydebug;
+#else /* !YYDEBUG */
+# define YYDPRINTF(Args)
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location)
+# define YY_STACK_PRINT(Bottom, Top)
+# define YY_REDUCE_PRINT(Rule)
+#endif /* !YYDEBUG */
+
+
+/* YYINITDEPTH -- initial size of the parser's stacks.  */
+#ifndef        YYINITDEPTH
+# define YYINITDEPTH 200
+#endif
+
+/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
+   if the built-in stack extension method is used).
+
+   Do not make this value too large; the results are undefined if
+   YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
+   evaluated with infinite-precision integer arithmetic.  */
+
+#ifndef YYMAXDEPTH
+# define YYMAXDEPTH 10000
+#endif
+
+\f
+
+#if YYERROR_VERBOSE
+
+# ifndef yystrlen
+#  if defined __GLIBC__ && defined _STRING_H
+#   define yystrlen strlen
+#  else
+/* Return the length of YYSTR.  */
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static YYSIZE_T
+yystrlen (const char *yystr)
+#else
+static YYSIZE_T
+yystrlen (yystr)
+    const char *yystr;
+#endif
+{
+  YYSIZE_T yylen;
+  for (yylen = 0; yystr[yylen]; yylen++)
+    continue;
+  return yylen;
+}
+#  endif
+# endif
+
+# ifndef yystpcpy
+#  if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE
+#   define yystpcpy stpcpy
+#  else
+/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in
+   YYDEST.  */
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static char *
+yystpcpy (char *yydest, const char *yysrc)
+#else
+static char *
+yystpcpy (yydest, yysrc)
+    char *yydest;
+    const char *yysrc;
+#endif
+{
+  char *yyd = yydest;
+  const char *yys = yysrc;
+
+  while ((*yyd++ = *yys++) != '\0')
+    continue;
+
+  return yyd - 1;
+}
+#  endif
+# endif
+
+# ifndef yytnamerr
+/* Copy to YYRES the contents of YYSTR after stripping away unnecessary
+   quotes and backslashes, so that it's suitable for yyerror.  The
+   heuristic is that double-quoting is unnecessary unless the string
+   contains an apostrophe, a comma, or backslash (other than
+   backslash-backslash).  YYSTR is taken from yytname.  If YYRES is
+   null, do not copy; instead, return the length of what the result
+   would have been.  */
+static YYSIZE_T
+yytnamerr (char *yyres, const char *yystr)
+{
+  if (*yystr == '"')
+    {
+      YYSIZE_T yyn = 0;
+      char const *yyp = yystr;
+
+      for (;;)
+       switch (*++yyp)
+         {
+         case '\'':
+         case ',':
+           goto do_not_strip_quotes;
+
+         case '\\':
+           if (*++yyp != '\\')
+             goto do_not_strip_quotes;
+           /* Fall through.  */
+         default:
+           if (yyres)
+             yyres[yyn] = *yyp;
+           yyn++;
+           break;
+
+         case '"':
+           if (yyres)
+             yyres[yyn] = '\0';
+           return yyn;
+         }
+    do_not_strip_quotes: ;
+    }
+
+  if (! yyres)
+    return yystrlen (yystr);
+
+  return yystpcpy (yyres, yystr) - yyres;
+}
+# endif
+
+/* Copy into YYRESULT an error message about the unexpected token
+   YYCHAR while in state YYSTATE.  Return the number of bytes copied,
+   including the terminating null byte.  If YYRESULT is null, do not
+   copy anything; just return the number of bytes that would be
+   copied.  As a special case, return 0 if an ordinary "syntax error"
+   message will do.  Return YYSIZE_MAXIMUM if overflow occurs during
+   size calculation.  */
+static YYSIZE_T
+yysyntax_error (char *yyresult, int yystate, int yychar)
+{
+  int yyn = yypact[yystate];
+
+  if (! (YYPACT_NINF < yyn && yyn <= YYLAST))
+    return 0;
+  else
+    {
+      int yytype = YYTRANSLATE (yychar);
+      YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]);
+      YYSIZE_T yysize = yysize0;
+      YYSIZE_T yysize1;
+      int yysize_overflow = 0;
+      enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 };
+      char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM];
+      int yyx;
+
+# if 0
+      /* This is so xgettext sees the translatable formats that are
+        constructed on the fly.  */
+      YY_("syntax error, unexpected %s");
+      YY_("syntax error, unexpected %s, expecting %s");
+      YY_("syntax error, unexpected %s, expecting %s or %s");
+      YY_("syntax error, unexpected %s, expecting %s or %s or %s");
+      YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s");
+# endif
+      char *yyfmt;
+      char const *yyf;
+      static char const yyunexpected[] = "syntax error, unexpected %s";
+      static char const yyexpecting[] = ", expecting %s";
+      static char const yyor[] = " or %s";
+      char yyformat[sizeof yyunexpected
+                   + sizeof yyexpecting - 1
+                   + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2)
+                      * (sizeof yyor - 1))];
+      char const *yyprefix = yyexpecting;
+
+      /* Start YYX at -YYN if negative to avoid negative indexes in
+        YYCHECK.  */
+      int yyxbegin = yyn < 0 ? -yyn : 0;
+
+      /* Stay within bounds of both yycheck and yytname.  */
+      int yychecklim = YYLAST - yyn + 1;
+      int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS;
+      int yycount = 1;
+
+      yyarg[0] = yytname[yytype];
+      yyfmt = yystpcpy (yyformat, yyunexpected);
+
+      for (yyx = yyxbegin; yyx < yyxend; ++yyx)
+       if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR)
+         {
+           if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM)
+             {
+               yycount = 1;
+               yysize = yysize0;
+               yyformat[sizeof yyunexpected - 1] = '\0';
+               break;
+             }
+           yyarg[yycount++] = yytname[yyx];
+           yysize1 = yysize + yytnamerr (0, yytname[yyx]);
+           yysize_overflow |= (yysize1 < yysize);
+           yysize = yysize1;
+           yyfmt = yystpcpy (yyfmt, yyprefix);
+           yyprefix = yyor;
+         }
+
+      yyf = YY_(yyformat);
+      yysize1 = yysize + yystrlen (yyf);
+      yysize_overflow |= (yysize1 < yysize);
+      yysize = yysize1;
+
+      if (yysize_overflow)
+       return YYSIZE_MAXIMUM;
+
+      if (yyresult)
+       {
+         /* Avoid sprintf, as that infringes on the user's name space.
+            Don't have undefined behavior even if the translation
+            produced a string with the wrong number of "%s"s.  */
+         char *yyp = yyresult;
+         int yyi = 0;
+         while ((*yyp = *yyf) != '\0')
+           {
+             if (*yyp == '%' && yyf[1] == 's' && yyi < yycount)
+               {
+                 yyp += yytnamerr (yyp, yyarg[yyi++]);
+                 yyf += 2;
+               }
+             else
+               {
+                 yyp++;
+                 yyf++;
+               }
+           }
+       }
+      return yysize;
+    }
+}
+#endif /* YYERROR_VERBOSE */
+\f
+
+/*-----------------------------------------------.
+| Release the memory associated to this symbol.  |
+`-----------------------------------------------*/
+
+/*ARGSUSED*/
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static void
+yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep)
+#else
+static void
+yydestruct (yymsg, yytype, yyvaluep)
+    const char *yymsg;
+    int yytype;
+    YYSTYPE *yyvaluep;
+#endif
+{
+  YYUSE (yyvaluep);
+
+  if (!yymsg)
+    yymsg = "Deleting";
+  YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp);
+
+  switch (yytype)
+    {
+
+      default:
+       break;
+    }
+}
+\f
+
+/* Prevent warnings from -Wmissing-prototypes.  */
+
+#ifdef YYPARSE_PARAM
+#if defined __STDC__ || defined __cplusplus
+int yyparse (void *YYPARSE_PARAM);
+#else
+int yyparse ();
+#endif
+#else /* ! YYPARSE_PARAM */
+#if defined __STDC__ || defined __cplusplus
+int yyparse (void);
+#else
+int yyparse ();
+#endif
+#endif /* ! YYPARSE_PARAM */
+
+
+
+/* The look-ahead symbol.  */
+int yychar;
+
+/* The semantic value of the look-ahead symbol.  */
+YYSTYPE yylval;
+
+/* Number of syntax errors so far.  */
+int yynerrs;
+
+
+
+/*----------.
+| yyparse.  |
+`----------*/
+
+#ifdef YYPARSE_PARAM
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+int
+yyparse (void *YYPARSE_PARAM)
+#else
+int
+yyparse (YYPARSE_PARAM)
+    void *YYPARSE_PARAM;
+#endif
+#else /* ! YYPARSE_PARAM */
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+int
+yyparse (void)
+#else
+int
+yyparse ()
+
+#endif
+#endif
+{
+  
+  int yystate;
+  int yyn;
+  int yyresult;
+  /* Number of tokens to shift before error messages enabled.  */
+  int yyerrstatus;
+  /* Look-ahead token as an internal (translated) token number.  */
+  int yytoken = 0;
+#if YYERROR_VERBOSE
+  /* Buffer for error messages, and its allocated size.  */
+  char yymsgbuf[128];
+  char *yymsg = yymsgbuf;
+  YYSIZE_T yymsg_alloc = sizeof yymsgbuf;
+#endif
+
+  /* Three stacks and their tools:
+     `yyss': related to states,
+     `yyvs': related to semantic values,
+     `yyls': related to locations.
+
+     Refer to the stacks thru separate pointers, to allow yyoverflow
+     to reallocate them elsewhere.  */
+
+  /* The state stack.  */
+  yytype_int16 yyssa[YYINITDEPTH];
+  yytype_int16 *yyss = yyssa;
+  yytype_int16 *yyssp;
+
+  /* The semantic value stack.  */
+  YYSTYPE yyvsa[YYINITDEPTH];
+  YYSTYPE *yyvs = yyvsa;
+  YYSTYPE *yyvsp;
+
+
+
+#define YYPOPSTACK(N)   (yyvsp -= (N), yyssp -= (N))
+
+  YYSIZE_T yystacksize = YYINITDEPTH;
+
+  /* The variables used to return semantic value and location from the
+     action routines.  */
+  YYSTYPE yyval;
+
+
+  /* The number of symbols on the RHS of the reduced rule.
+     Keep to zero when no symbol should be popped.  */
+  int yylen = 0;
+
+  YYDPRINTF ((stderr, "Starting parse\n"));
+
+  yystate = 0;
+  yyerrstatus = 0;
+  yynerrs = 0;
+  yychar = YYEMPTY;            /* Cause a token to be read.  */
+
+  /* Initialize stack pointers.
+     Waste one element of value and location stack
+     so that they stay on the same level as the state stack.
+     The wasted elements are never initialized.  */
+
+  yyssp = yyss;
+  yyvsp = yyvs;
+
+  goto yysetstate;
+
+/*------------------------------------------------------------.
+| yynewstate -- Push a new state, which is found in yystate.  |
+`------------------------------------------------------------*/
+ yynewstate:
+  /* In all cases, when you get here, the value and location stacks
+     have just been pushed.  So pushing a state here evens the stacks.  */
+  yyssp++;
+
+ yysetstate:
+  *yyssp = yystate;
+
+  if (yyss + yystacksize - 1 <= yyssp)
+    {
+      /* Get the current used size of the three stacks, in elements.  */
+      YYSIZE_T yysize = yyssp - yyss + 1;
+
+#ifdef yyoverflow
+      {
+       /* Give user a chance to reallocate the stack.  Use copies of
+          these so that the &'s don't force the real ones into
+          memory.  */
+       YYSTYPE *yyvs1 = yyvs;
+       yytype_int16 *yyss1 = yyss;
+
+
+       /* Each stack pointer address is followed by the size of the
+          data in use in that stack, in bytes.  This used to be a
+          conditional around just the two extra args, but that might
+          be undefined if yyoverflow is a macro.  */
+       yyoverflow (YY_("memory exhausted"),
+                   &yyss1, yysize * sizeof (*yyssp),
+                   &yyvs1, yysize * sizeof (*yyvsp),
+
+                   &yystacksize);
+
+       yyss = yyss1;
+       yyvs = yyvs1;
+      }
+#else /* no yyoverflow */
+# ifndef YYSTACK_RELOCATE
+      goto yyexhaustedlab;
+# else
+      /* Extend the stack our own way.  */
+      if (YYMAXDEPTH <= yystacksize)
+       goto yyexhaustedlab;
+      yystacksize *= 2;
+      if (YYMAXDEPTH < yystacksize)
+       yystacksize = YYMAXDEPTH;
+
+      {
+       yytype_int16 *yyss1 = yyss;
+       union yyalloc *yyptr =
+         (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize));
+       if (! yyptr)
+         goto yyexhaustedlab;
+       YYSTACK_RELOCATE (yyss);
+       YYSTACK_RELOCATE (yyvs);
+
+#  undef YYSTACK_RELOCATE
+       if (yyss1 != yyssa)
+         YYSTACK_FREE (yyss1);
+      }
+# endif
+#endif /* no yyoverflow */
+
+      yyssp = yyss + yysize - 1;
+      yyvsp = yyvs + yysize - 1;
+
+
+      YYDPRINTF ((stderr, "Stack size increased to %lu\n",
+                 (unsigned long int) yystacksize));
+
+      if (yyss + yystacksize - 1 <= yyssp)
+       YYABORT;
+    }
+
+  YYDPRINTF ((stderr, "Entering state %d\n", yystate));
+
+  goto yybackup;
+
+/*-----------.
+| yybackup.  |
+`-----------*/
+yybackup:
+
+  /* Do appropriate processing given the current state.  Read a
+     look-ahead token if we need one and don't already have one.  */
+
+  /* First try to decide what to do without reference to look-ahead token.  */
+  yyn = yypact[yystate];
+  if (yyn == YYPACT_NINF)
+    goto yydefault;
+
+  /* Not known => get a look-ahead token if don't already have one.  */
+
+  /* YYCHAR is either YYEMPTY or YYEOF or a valid look-ahead symbol.  */
+  if (yychar == YYEMPTY)
+    {
+      YYDPRINTF ((stderr, "Reading a token: "));
+      yychar = YYLEX;
+    }
+
+  if (yychar <= YYEOF)
+    {
+      yychar = yytoken = YYEOF;
+      YYDPRINTF ((stderr, "Now at end of input.\n"));
+    }
+  else
+    {
+      yytoken = YYTRANSLATE (yychar);
+      YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
+    }
+
+  /* If the proper action on seeing token YYTOKEN is to reduce or to
+     detect an error, take that action.  */
+  yyn += yytoken;
+  if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
+    goto yydefault;
+  yyn = yytable[yyn];
+  if (yyn <= 0)
+    {
+      if (yyn == 0 || yyn == YYTABLE_NINF)
+       goto yyerrlab;
+      yyn = -yyn;
+      goto yyreduce;
+    }
+
+  if (yyn == YYFINAL)
+    YYACCEPT;
+
+  /* Count tokens shifted since error; after three, turn off error
+     status.  */
+  if (yyerrstatus)
+    yyerrstatus--;
+
+  /* Shift the look-ahead token.  */
+  YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
+
+  /* Discard the shifted token unless it is eof.  */
+  if (yychar != YYEOF)
+    yychar = YYEMPTY;
+
+  yystate = yyn;
+  *++yyvsp = yylval;
+
+  goto yynewstate;
+
+
+/*-----------------------------------------------------------.
+| yydefault -- do the default action for the current state.  |
+`-----------------------------------------------------------*/
+yydefault:
+  yyn = yydefact[yystate];
+  if (yyn == 0)
+    goto yyerrlab;
+  goto yyreduce;
+
+
+/*-----------------------------.
+| yyreduce -- Do a reduction.  |
+`-----------------------------*/
+yyreduce:
+  /* yyn is the number of a rule to reduce with.  */
+  yylen = yyr2[yyn];
+
+  /* If YYLEN is nonzero, implement the default value of the action:
+     `$$ = $1'.
+
+     Otherwise, the following line sets YYVAL to garbage.
+     This behavior is undocumented and Bison
+     users should not rely upon it.  Assigning to YYVAL
+     unconditionally makes the parser a bit smaller, and it avoids a
+     GCC warning that YYVAL may be used uninitialized.  */
+  yyval = yyvsp[1-yylen];
+
+
+  YY_REDUCE_PRINT (yyn);
+  switch (yyn)
+    {
+        case 8:
+#line 60 "parser.y"
+    {
+                                       if (got_connect)
+                                               yyerror("Only one CONNECT statement allowed");
+                                       xfunc->run_type = R_EXACT;
+                                       got_connect = 1; ;}
+    break;
+
+  case 9:
+#line 67 "parser.y"
+    { xfunc->connect_str = plproxy_func_strdup(xfunc, (yyvsp[(1) - (1)].str)); ;}
+    break;
+
+  case 10:
+#line 70 "parser.y"
+    {
+                                                       if (got_cluster)
+                                                               yyerror("Only one CLUSTER statement allowed");
+                                                       got_cluster = 1; ;}
+    break;
+
+  case 13:
+#line 79 "parser.y"
+    { cluster_sql = plproxy_query_start(xfunc, false);
+                                                 cur_sql = cluster_sql;
+                                                 plproxy_query_add_const(cur_sql, "select ");
+                                                 plproxy_query_add_const(cur_sql, (yyvsp[(1) - (1)].str)); ;}
+    break;
+
+  case 14:
+#line 85 "parser.y"
+    { xfunc->cluster_name = plproxy_func_strdup(xfunc, (yyvsp[(1) - (1)].str)); ;}
+    break;
+
+  case 15:
+#line 88 "parser.y"
+    { if (got_run)
+                                                                       yyerror("Only one RUN statement allowed");
+                                                                 got_run = 1; ;}
+    break;
+
+  case 16:
+#line 93 "parser.y"
+    { xfunc->run_type = R_HASH; ;}
+    break;
+
+  case 17:
+#line 94 "parser.y"
+    { xfunc->run_type = R_EXACT; xfunc->exact_nr = atoi((yyvsp[(1) - (1)].str)); ;}
+    break;
+
+  case 18:
+#line 95 "parser.y"
+    { xfunc->run_type = R_ANY; ;}
+    break;
+
+  case 19:
+#line 96 "parser.y"
+    { xfunc->run_type = R_ALL; ;}
+    break;
+
+  case 20:
+#line 99 "parser.y"
+    { hash_sql = plproxy_query_start(xfunc, false);
+                                         cur_sql = hash_sql;
+                                         plproxy_query_add_const(cur_sql, "select * from ");
+                                         plproxy_query_add_const(cur_sql, (yyvsp[(1) - (1)].str)); ;}
+    break;
+
+  case 22:
+#line 107 "parser.y"
+    { if (select_sql)
+                                                       yyerror("Only one SELECT statement allowed");
+                                                 select_sql = plproxy_query_start(xfunc, true);
+                                                 cur_sql = select_sql;
+                                                 plproxy_query_add_const(cur_sql, (yyvsp[(1) - (1)].str)); ;}
+    break;
+
+  case 25:
+#line 116 "parser.y"
+    { plproxy_query_add_const(cur_sql, (yyvsp[(1) - (1)].str)); ;}
+    break;
+
+  case 26:
+#line 117 "parser.y"
+    { if (!plproxy_query_add_ident(cur_sql, (yyvsp[(1) - (1)].str)))
+                                                       yyerror("invalid argument reference: %s", (yyvsp[(1) - (1)].str)); ;}
+    break;
+
+
+/* Line 1267 of yacc.c.  */
+#line 1500 "parser.tab.c"
+      default: break;
+    }
+  YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc);
+
+  YYPOPSTACK (yylen);
+  yylen = 0;
+  YY_STACK_PRINT (yyss, yyssp);
+
+  *++yyvsp = yyval;
+
+
+  /* Now `shift' the result of the reduction.  Determine what state
+     that goes to, based on the state we popped back to and the rule
+     number reduced by.  */
+
+  yyn = yyr1[yyn];
+
+  yystate = yypgoto[yyn - YYNTOKENS] + *yyssp;
+  if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp)
+    yystate = yytable[yystate];
+  else
+    yystate = yydefgoto[yyn - YYNTOKENS];
+
+  goto yynewstate;
+
+
+/*------------------------------------.
+| yyerrlab -- here on detecting error |
+`------------------------------------*/
+yyerrlab:
+  /* If not already recovering from an error, report this error.  */
+  if (!yyerrstatus)
+    {
+      ++yynerrs;
+#if ! YYERROR_VERBOSE
+      yyerror (YY_("syntax error"));
+#else
+      {
+       YYSIZE_T yysize = yysyntax_error (0, yystate, yychar);
+       if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM)
+         {
+           YYSIZE_T yyalloc = 2 * yysize;
+           if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM))
+             yyalloc = YYSTACK_ALLOC_MAXIMUM;
+           if (yymsg != yymsgbuf)
+             YYSTACK_FREE (yymsg);
+           yymsg = (char *) YYSTACK_ALLOC (yyalloc);
+           if (yymsg)
+             yymsg_alloc = yyalloc;
+           else
+             {
+               yymsg = yymsgbuf;
+               yymsg_alloc = sizeof yymsgbuf;
+             }
+         }
+
+       if (0 < yysize && yysize <= yymsg_alloc)
+         {
+           (void) yysyntax_error (yymsg, yystate, yychar);
+           yyerror (yymsg);
+         }
+       else
+         {
+           yyerror (YY_("syntax error"));
+           if (yysize != 0)
+             goto yyexhaustedlab;
+         }
+      }
+#endif
+    }
+
+
+
+  if (yyerrstatus == 3)
+    {
+      /* If just tried and failed to reuse look-ahead token after an
+        error, discard it.  */
+
+      if (yychar <= YYEOF)
+       {
+         /* Return failure if at end of input.  */
+         if (yychar == YYEOF)
+           YYABORT;
+       }
+      else
+       {
+         yydestruct ("Error: discarding",
+                     yytoken, &yylval);
+         yychar = YYEMPTY;
+       }
+    }
+
+  /* Else will try to reuse look-ahead token after shifting the error
+     token.  */
+  goto yyerrlab1;
+
+
+/*---------------------------------------------------.
+| yyerrorlab -- error raised explicitly by YYERROR.  |
+`---------------------------------------------------*/
+yyerrorlab:
+
+  /* Pacify compilers like GCC when the user code never invokes
+     YYERROR and the label yyerrorlab therefore never appears in user
+     code.  */
+  if (/*CONSTCOND*/ 0)
+     goto yyerrorlab;
+
+  /* Do not reclaim the symbols of the rule which action triggered
+     this YYERROR.  */
+  YYPOPSTACK (yylen);
+  yylen = 0;
+  YY_STACK_PRINT (yyss, yyssp);
+  yystate = *yyssp;
+  goto yyerrlab1;
+
+
+/*-------------------------------------------------------------.
+| yyerrlab1 -- common code for both syntax error and YYERROR.  |
+`-------------------------------------------------------------*/
+yyerrlab1:
+  yyerrstatus = 3;     /* Each real token shifted decrements this.  */
+
+  for (;;)
+    {
+      yyn = yypact[yystate];
+      if (yyn != YYPACT_NINF)
+       {
+         yyn += YYTERROR;
+         if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR)
+           {
+             yyn = yytable[yyn];
+             if (0 < yyn)
+               break;
+           }
+       }
+
+      /* Pop the current state because it cannot handle the error token.  */
+      if (yyssp == yyss)
+       YYABORT;
+
+
+      yydestruct ("Error: popping",
+                 yystos[yystate], yyvsp);
+      YYPOPSTACK (1);
+      yystate = *yyssp;
+      YY_STACK_PRINT (yyss, yyssp);
+    }
+
+  if (yyn == YYFINAL)
+    YYACCEPT;
+
+  *++yyvsp = yylval;
+
+
+  /* Shift the error token.  */
+  YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp);
+
+  yystate = yyn;
+  goto yynewstate;
+
+
+/*-------------------------------------.
+| yyacceptlab -- YYACCEPT comes here.  |
+`-------------------------------------*/
+yyacceptlab:
+  yyresult = 0;
+  goto yyreturn;
+
+/*-----------------------------------.
+| yyabortlab -- YYABORT comes here.  |
+`-----------------------------------*/
+yyabortlab:
+  yyresult = 1;
+  goto yyreturn;
+
+#ifndef yyoverflow
+/*-------------------------------------------------.
+| yyexhaustedlab -- memory exhaustion comes here.  |
+`-------------------------------------------------*/
+yyexhaustedlab:
+  yyerror (YY_("memory exhausted"));
+  yyresult = 2;
+  /* Fall through.  */
+#endif
+
+yyreturn:
+  if (yychar != YYEOF && yychar != YYEMPTY)
+     yydestruct ("Cleanup: discarding lookahead",
+                yytoken, &yylval);
+  /* Do not reclaim the symbols of the rule which action triggered
+     this YYABORT or YYACCEPT.  */
+  YYPOPSTACK (yylen);
+  YY_STACK_PRINT (yyss, yyssp);
+  while (yyssp != yyss)
+    {
+      yydestruct ("Cleanup: popping",
+                 yystos[*yyssp], yyvsp);
+      YYPOPSTACK (1);
+    }
+#ifndef yyoverflow
+  if (yyss != yyssa)
+    YYSTACK_FREE (yyss);
+#endif
+#if YYERROR_VERBOSE
+  if (yymsg != yymsgbuf)
+    YYSTACK_FREE (yymsg);
+#endif
+  /* Make sure YYID is used.  */
+  return YYID (yyresult);
+}
+
+
+#line 121 "parser.y"
+
+
+/*
+ * report parser error.
+ */
+void yyerror(const char *fmt, ...)
+{
+       char buf[1024];
+       int lineno = plproxy_yyget_lineno();
+       va_list ap;
+
+       va_start(ap, fmt);
+       vsnprintf(buf, sizeof(buf), fmt, ap);
+       va_end(ap);
+
+       /* reinitialize scanner */
+       plproxy_yylex_destroy();
+
+       plproxy_error(xfunc, "Compile error at line %d: %s", lineno, buf);
+}
+
+
+/* actually run the flex/bison parser */
+void plproxy_run_parser(ProxyFunction *func, const char *body, int len)
+{
+       xfunc = func;
+       got_run = got_cluster = got_connect = 0;
+
+       cur_sql = select_sql = NULL;
+
+       /* setup scanner */
+       plproxy_yy_scan_bytes(body, len);
+
+       /* run parser */
+       yyparse();
+
+       /* check for mandatory statements */
+       if (got_connect) {
+               if (got_cluster || got_run)
+                       yyerror("CONNECT cannot be used with CLUSTER/RUN");
+       } else {
+               if (!got_cluster)
+                       yyerror("CLUSTER statement missing");
+               if (!got_run)
+                       yyerror("RUN statement missing");
+       }
+
+       /* reinitialize scanner */
+       plproxy_yylex_destroy();
+
+       /* copy hash data if needed */
+       if (xfunc->run_type == R_HASH)
+               xfunc->hash_sql = plproxy_query_finish(hash_sql);
+
+       /* store sql */
+       if (select_sql)
+               xfunc->remote_sql = plproxy_query_finish(select_sql);
+       
+       if (cluster_sql)
+               xfunc->cluster_sql = plproxy_query_finish(cluster_sql);
+
+       xfunc = NULL;
+}
+
+
diff --git a/src/parser.tab.h b/src/parser.tab.h
new file mode 100644 (file)
index 0000000..ad57a49
--- /dev/null
@@ -0,0 +1,92 @@
+/* A Bison parser, made by GNU Bison 2.3.  */
+
+/* Skeleton interface for Bison's Yacc-like parsers in C
+
+   Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+   Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.  */
+
+/* As a special exception, you may create a larger work that contains
+   part or all of the Bison parser skeleton and distribute that work
+   under terms of your choice, so long as that work isn't itself a
+   parser generator using the skeleton or a modified version thereof
+   as a parser skeleton.  Alternatively, if you modify or redistribute
+   the parser skeleton itself, you may (at your option) remove this
+   special exception, which will cause the skeleton and the resulting
+   Bison output files to be licensed under the GNU General Public
+   License without this special exception.
+
+   This special exception was added by the Free Software Foundation in
+   version 2.2 of Bison.  */
+
+/* Tokens.  */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+   /* Put the tokens into the symbol table, so that GDB and other debuggers
+      know about them.  */
+   enum yytokentype {
+     CONNECT = 258,
+     CLUSTER = 259,
+     RUN = 260,
+     ON = 261,
+     ALL = 262,
+     ANY = 263,
+     SELECT = 264,
+     IDENT = 265,
+     CONST = 266,
+     NUMBER = 267,
+     FNCALL = 268,
+     STRING = 269,
+     SQLIDENT = 270,
+     SQLPART = 271
+   };
+#endif
+/* Tokens.  */
+#define CONNECT 258
+#define CLUSTER 259
+#define RUN 260
+#define ON 261
+#define ALL 262
+#define ANY 263
+#define SELECT 264
+#define IDENT 265
+#define CONST 266
+#define NUMBER 267
+#define FNCALL 268
+#define STRING 269
+#define SQLIDENT 270
+#define SQLPART 271
+
+
+
+
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+typedef union YYSTYPE
+#line 50 "parser.y"
+{
+       const char *str;
+}
+/* Line 1489 of yacc.c.  */
+#line 85 "parser.tab.h"
+       YYSTYPE;
+# define yystype YYSTYPE /* obsolescent; will be withdrawn */
+# define YYSTYPE_IS_DECLARED 1
+# define YYSTYPE_IS_TRIVIAL 1
+#endif
+
+extern YYSTYPE plproxy_yylval;
+
diff --git a/src/parser.y b/src/parser.y
new file mode 100644 (file)
index 0000000..5bbe049
--- /dev/null
@@ -0,0 +1,184 @@
+%{
+/*
+ * PL/Proxy - easy access to partitioned database.
+ * 
+ * Copyright (c) 2006 Sven Suursoho, Skype Technologies OÜ
+ * Copyright (c) 2007 Marko Kreen, Skype Technologies OÜ
+ * 
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "plproxy.h"
+#include "scanner.h"
+
+/* avoid permanent allocations */
+#define YYSTACK_USE_ALLOCA 1
+/* remove unused code */
+#define YY_LOCATION_PRINT(File, Loc) (0)
+#define YY_(x) (x)
+
+/* during parsing, keep reference to function here */
+static ProxyFunction *xfunc;
+
+/* remember what happened */
+static int got_run, got_cluster, got_connect;
+
+static QueryBuffer *cluster_sql;
+static QueryBuffer *select_sql;
+static QueryBuffer *hash_sql;
+static QueryBuffer *cur_sql;
+
+%}
+
+%name-prefix="plproxy_yy"
+
+%token <str> CONNECT CLUSTER RUN ON ALL ANY SELECT
+%token <str> IDENT CONST NUMBER FNCALL STRING
+%token <str> SQLIDENT SQLPART
+
+%union
+{
+       const char *str;
+}
+
+%%
+
+body: | body stmt ;
+
+stmt: cluster_stmt | run_stmt | select_stmt | connect_stmt ;
+
+connect_stmt: CONNECT connect_spec ';' {
+                                       if (got_connect)
+                                               yyerror("Only one CONNECT statement allowed");
+                                       xfunc->run_type = R_EXACT;
+                                       got_connect = 1; }
+                       ;
+
+connect_spec: STRING   { xfunc->connect_str = plproxy_func_strdup(xfunc, $1); }
+                       ;
+
+cluster_stmt: CLUSTER cluster_spec ';' {
+                                                       if (got_cluster)
+                                                               yyerror("Only one CLUSTER statement allowed");
+                                                       got_cluster = 1; }
+                       ;
+
+cluster_spec: cluster_name | cluster_func sql_token_list
+                       ;
+
+cluster_func: FNCALL   { cluster_sql = plproxy_query_start(xfunc, false);
+                                                 cur_sql = cluster_sql;
+                                                 plproxy_query_add_const(cur_sql, "select ");
+                                                 plproxy_query_add_const(cur_sql, $1); }
+                       ;
+
+cluster_name: STRING   { xfunc->cluster_name = plproxy_func_strdup(xfunc, $1); }
+                       ;
+
+run_stmt: RUN ON run_spec ';'  { if (got_run)
+                                                                       yyerror("Only one RUN statement allowed");
+                                                                 got_run = 1; }
+               ;
+
+run_spec: hash_func sql_token_list     { xfunc->run_type = R_HASH; }
+               | NUMBER                                        { xfunc->run_type = R_EXACT; xfunc->exact_nr = atoi($1); }
+               | ANY                                           { xfunc->run_type = R_ANY; }
+               | ALL                                           { xfunc->run_type = R_ALL; }
+               ;
+
+hash_func: FNCALL      { hash_sql = plproxy_query_start(xfunc, false);
+                                         cur_sql = hash_sql;
+                                         plproxy_query_add_const(cur_sql, "select * from ");
+                                         plproxy_query_add_const(cur_sql, $1); }
+                ;
+
+select_stmt: sql_start sql_token_list ';' ;
+
+sql_start: SELECT              { if (select_sql)
+                                                       yyerror("Only one SELECT statement allowed");
+                                                 select_sql = plproxy_query_start(xfunc, true);
+                                                 cur_sql = select_sql;
+                                                 plproxy_query_add_const(cur_sql, $1); }
+                ;
+sql_token_list: sql_token
+                         | sql_token_list sql_token
+                     ;
+sql_token: SQLPART             { plproxy_query_add_const(cur_sql, $1); }
+                | SQLIDENT             { if (!plproxy_query_add_ident(cur_sql, $1))
+                                                       yyerror("invalid argument reference: %s", $1); }
+                ;
+
+%%
+
+/*
+ * report parser error.
+ */
+void yyerror(const char *fmt, ...)
+{
+       char buf[1024];
+       int lineno = plproxy_yyget_lineno();
+       va_list ap;
+
+       va_start(ap, fmt);
+       vsnprintf(buf, sizeof(buf), fmt, ap);
+       va_end(ap);
+
+       /* reinitialize scanner */
+       plproxy_yylex_destroy();
+
+       plproxy_error(xfunc, "Compile error at line %d: %s", lineno, buf);
+}
+
+
+/* actually run the flex/bison parser */
+void plproxy_run_parser(ProxyFunction *func, const char *body, int len)
+{
+       xfunc = func;
+       got_run = got_cluster = got_connect = 0;
+
+       cur_sql = select_sql = NULL;
+
+       /* setup scanner */
+       plproxy_yy_scan_bytes(body, len);
+
+       /* run parser */
+       yyparse();
+
+       /* check for mandatory statements */
+       if (got_connect) {
+               if (got_cluster || got_run)
+                       yyerror("CONNECT cannot be used with CLUSTER/RUN");
+       } else {
+               if (!got_cluster)
+                       yyerror("CLUSTER statement missing");
+               if (!got_run)
+                       yyerror("RUN statement missing");
+       }
+
+       /* reinitialize scanner */
+       plproxy_yylex_destroy();
+
+       /* copy hash data if needed */
+       if (xfunc->run_type == R_HASH)
+               xfunc->hash_sql = plproxy_query_finish(hash_sql);
+
+       /* store sql */
+       if (select_sql)
+               xfunc->remote_sql = plproxy_query_finish(select_sql);
+       
+       if (cluster_sql)
+               xfunc->cluster_sql = plproxy_query_finish(cluster_sql);
+
+       xfunc = NULL;
+}
+
diff --git a/src/plproxy.h b/src/plproxy.h
new file mode 100644 (file)
index 0000000..2c37aaa
--- /dev/null
@@ -0,0 +1,294 @@
+/*
+ * PL/Proxy - easy access to partitioned database.
+ * 
+ * Copyright (c) 2006 Sven Suursoho, Skype Technologies OÜ
+ * Copyright (c) 2007 Marko Kreen, Skype Technologies OÜ
+ * 
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Data structures for PL/Proxy function handler.
+ */
+
+#ifndef plproxy_h_included
+#define plproxy_h_included
+
+#include <postgres.h>
+#include <funcapi.h>
+#include <executor/spi.h>
+
+#include <access/tupdesc.h>
+#include <catalog/pg_namespace.h>
+#include <catalog/pg_proc.h>
+#include <catalog/pg_type.h>
+#include <commands/trigger.h>
+#include <mb/pg_wchar.h>
+#include <miscadmin.h>
+#include <utils/builtins.h>
+#include <utils/hsearch.h>
+#include <utils/lsyscache.h>
+#include <utils/memutils.h>
+#include <utils/syscache.h>
+/* for standard_conforming_strings */
+#include <parser/gramparse.h>
+
+#include <libpq-fe.h>
+
+/*
+ * Maintenece period in seconds.  Connnections will be freed
+ * from stale results, and checked for lifetime.
+ */
+#define PLPROXY_MAINT_PERIOD           (2*60)
+
+/*
+ * Check connections that are idle more than this many seconds.
+ * Undefine to disable.
+ */
+#define PLPROXY_IDLE_CONN_CHECK                10
+
+/* Flag indicating where function should be executed */
+typedef enum RunOnType
+{
+       R_HASH = 1,                             /* partition(s) returned by hash function */
+       R_ALL = 2,                              /* on all partitions */
+       R_ANY = 3,                              /* decide randomly during runtime */
+       R_EXACT = 4                             /* exact part number */
+} RunOnType;
+
+/* Connection states for async handler */
+typedef enum ConnState
+{
+       C_NONE = 0,                                     /* no connection object yet */
+       C_CONNECT_WRITE,                        /* login phase: sending data */
+       C_CONNECT_READ,                         /* login phase: waiting for server */
+       C_READY,                                        /* connection ready for query */
+       C_QUERY_WRITE,                          /* query phase: sending data */
+       C_QUERY_READ,                           /* query phase: waiting for server */
+       C_DONE,                                         /* query done, result available */
+} ConnState;
+
+/* Stores result from plproxy.get_cluster_config() */
+typedef struct ProxyConfig
+{
+       int                     connect_timeout;                /* How long connect may take (secs) */
+       int                     query_timeout;                  /* How long query may take (secs) */
+       int                     connection_lifetime;    /* How long the connection may live (secs) */
+       int                     statement_timeout;              /* Do remotely: SET statement_timeout */
+       int                     disable_binary;                 /* Avoid binary I/O */
+} ProxyConfig;
+
+/* Single database connection */
+typedef struct
+{
+       const char *connstr;            /* Connection string for libpq */
+
+       /* state */
+       PGconn     *db;                         /* libpq connection handle */
+       PGresult   *res;                        /* last resultset */
+       int                     pos;                    /* Current position inside res */
+       ConnState       state;                  /* Connection state */
+       time_t          connect_time;   /* When connection was started */
+       time_t          query_time;             /* When last query was sent */
+       unsigned        run_on:1;               /* True it this connection should be used */
+       unsigned        same_ver:1;             /* True if dest backend has same X.Y ver */
+} ProxyConnection;
+
+/* Info about one cluster */
+typedef struct ProxyCluster
+{
+       struct ProxyCluster *next;      /* Pointer for building singly-linked list */
+
+       const char *name;                       /* Cluster name */
+       int                     version;                /* Cluster version */
+       ProxyConfig config;                     /* Cluster config */
+
+       int                     part_count;             /* Number of partitions - power of 2 */
+       int                     part_mask;              /* Mask to use to get part number from hash */
+       ProxyConnection **part_map; /* Pointers to conn_list */
+
+       int                     conn_count;             /* Number of actual database connections */
+       ProxyConnection *conn_list; /* List of actual database connections */
+
+       int                     ret_cur_conn;   /* Result walking: index of current conn */
+       int                     ret_cur_pos;    /* Result walking: index of current row */
+       int                     ret_total;              /* Result walking: total rows left */
+} ProxyCluster;
+
+/**
+ * Type info cache.
+ *
+ * As the decision to send/receive binary may
+ * change in runtime, both text and binary
+ * function calls must be cached.
+ */
+typedef struct ProxyType
+{
+       Oid                     type_oid;               /* Oid of the type */
+       char       *name;                       /* Name of the type */
+
+       /* I/O functions */
+       union
+       {
+               struct
+               {
+                       FmgrInfo        output_func;
+                       FmgrInfo        send_func;
+               }                       out;
+               struct
+               {
+                       FmgrInfo        input_func;
+                       FmgrInfo        recv_func;
+               }                       in;
+       }                       io;
+
+       Oid                     io_param;               /* Extra arg for input_func */
+       unsigned        for_send:1;             /* True if for outputting */
+       unsigned        has_send:1;             /* Has binary output */
+       unsigned        has_recv:1;             /* Has binary input */
+       unsigned        by_value:1;             /* False if Datum is a pointer to data */
+} ProxyType;
+
+/*
+ * Info cache for composite return type.
+ *
+ * There is AttInMetadata in core, but it does not support
+ * binary receive, so need our own struct.
+ */
+typedef struct ProxyComposite
+{
+       TupleDesc       tupdesc;                /* Return tuple descriptor */
+       ProxyType **type_list;          /* Column type info */
+       char      **name_list;          /* Column names */
+       unsigned        use_binary:1;   /* True if all columns support binary recv */
+} ProxyComposite;
+
+/* Temp structure for query parsing */
+typedef struct QueryBuffer QueryBuffer;
+
+/*
+ * Parsed query where references to function arguments
+ * are replaced with local args numbered sequentially: $1..$n.
+ */
+typedef struct ProxyQuery
+{
+       char       *sql;                        /* Prepared SQL string */
+       int                     arg_count;              /* Argument count for ->sql */
+       int                *arg_lookup;         /* Maps local references to function args */
+       void       *plan;                       /* Optional prepared plan for local queries */
+} ProxyQuery;
+
+/*
+ * Complete info about compiled function.
+ *
+ * Note: only IN and INOUT arguments are cached here.
+ */
+typedef struct ProxyFunction
+{
+       const char *name;                       /* Fully-quelified function name */
+       Oid                     oid;                    /* Function OID */
+       MemoryContext ctx;                      /* Where runtime allocations should happen */
+
+       TransactionId xmin;                     /* pg_proc xmin for cache validation */
+       CommandId       cmin;                   /* pg_proc cmin for cache validation */
+
+       int                     arg_count;              /* Argument count of proxy function */
+       ProxyType **arg_types;          /* Info about arguments */
+       char      **arg_names;          /* Argument names, may contain NULLs */
+
+       /* One of them is defined, other NULL */
+       ProxyType  *ret_scalar;         /* Type info for scalar return val */
+       ProxyComposite *ret_composite;  /* Type info for composite return val */
+
+       /* data from function body */
+       const char *cluster_name;       /* Cluster where function should run */
+       ProxyQuery *cluster_sql;        /* Optional query for name resolving */
+
+       RunOnType       run_type;               /* Run type */
+       ProxyQuery *hash_sql;           /* Hash execution for R_HASH */
+       int                     exact_nr;               /* Hash value for R_EXACT */
+       const char *connect_str;        /* libpq string for CONNECT function */
+
+       /*
+        * calculated data
+        */
+
+       ProxyQuery *remote_sql;         /* query to be run repotely */
+
+       /*
+        * current execution data
+        */
+
+       /*
+        * Cluster to be executed on.  In case of CONNECT,
+        * function's private fake cluster object.
+        */
+       ProxyCluster *cur_cluster;
+
+       /*
+        * Maps result field num to libpq column num.
+        * It is filled for each result.  NULL when scalar result.
+        */
+       int                *result_map;
+} ProxyFunction;
+
+/* main.c */
+Datum          plproxy_call_handler(PG_FUNCTION_ARGS);
+void           plproxy_error(ProxyFunction *func, const char *fmt,...);
+
+/* function.c */
+void           plproxy_function_cache_init(void);
+void      *plproxy_func_alloc(ProxyFunction *func, int size);
+char      *plproxy_func_strdup(ProxyFunction *func, const char *s);
+ProxyFunction *plproxy_compile(FunctionCallInfo fcinfo, bool validate);
+
+/* execute.c */
+void           plproxy_exec(ProxyFunction *func, FunctionCallInfo fcinfo);
+void           plproxy_clean_results(ProxyCluster *cluster);
+
+/* scanner.c */
+int                    plproxy_yyget_lineno(void);
+int                    plproxy_yylex_destroy(void);
+int                    plproxy_yylex(void);
+void           plproxy_scanner_sqlmode(bool val);
+
+/* parser.y */
+void           plproxy_run_parser(ProxyFunction *func, const char *body, int len);
+void           plproxy_yyerror(const char *fmt,...);
+
+/* type.c */
+ProxyComposite *plproxy_composite_info(ProxyFunction *func, TupleDesc tupdesc);
+ProxyType  *plproxy_find_type_info(ProxyFunction *func, Oid oid, bool for_send);
+char      *plproxy_send_type(ProxyType *type, Datum val, bool allow_bin, int *len, int *fmt);
+Datum          plproxy_recv_type(ProxyType *type, char *str, int len, bool bin);
+HeapTuple      plproxy_recv_composite(ProxyComposite *meta, char **values, int *lengths, int *fmts);
+
+/* cluster.c */
+void           plproxy_cluster_cache_init(void);
+ProxyCluster *plproxy_find_cluster(ProxyFunction *func, FunctionCallInfo fcinfo);
+void           plproxy_cluster_maint(struct timeval * now);
+
+/* result.c */
+Datum          plproxy_result(ProxyFunction *func, FunctionCallInfo fcinfo);
+
+/* query.c */
+QueryBuffer *plproxy_query_start(ProxyFunction *func, bool add_types);
+bool           plproxy_query_add_const(QueryBuffer *q, const char *data);
+bool           plproxy_query_add_ident(QueryBuffer *q, const char *ident);
+ProxyQuery *plproxy_query_finish(QueryBuffer *q);
+ProxyQuery *plproxy_standard_query(ProxyFunction *func, bool add_types);
+void           plproxy_query_prepare(ProxyFunction *func, FunctionCallInfo fcinfo, ProxyQuery *q);
+void           plproxy_query_exec(ProxyFunction *func, FunctionCallInfo fcinfo, ProxyQuery *q);
+void           plproxy_query_freeplan(ProxyQuery *q);
+
+#endif
diff --git a/src/query.c b/src/query.c
new file mode 100644 (file)
index 0000000..8c1e471
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+ * PL/Proxy - easy access to partitioned database.
+ * 
+ * Copyright (c) 2006 Sven Suursoho, Skype Technologies OÜ
+ * Copyright (c) 2007 Marko Kreen, Skype Technologies OÜ
+ * 
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * SQL statement generation helpers.
+ */
+
+#include "plproxy.h"
+
+/*
+ * Temporary info structure for generation.
+ *
+ * Later it will be used to make ProxyQuery.
+ */
+struct QueryBuffer
+{
+       ProxyFunction *func;
+       StringInfo      sql;
+       int                     arg_count;
+       int                *arg_lookup;
+       bool            add_types;
+};
+
+/*
+ * Prepare temporary structure for query generation.
+ */
+QueryBuffer *
+plproxy_query_start(ProxyFunction *func, bool add_types)
+{
+       QueryBuffer *q = palloc(sizeof(*q));
+
+       q->func = func;
+       q->sql = makeStringInfo();
+       q->arg_count = 0;
+       q->add_types = add_types;
+       q->arg_lookup = palloc(sizeof(int) * func->arg_count);
+       return q;
+}
+
+/*
+ * Add string fragment to query.
+ */
+bool
+plproxy_query_add_const(QueryBuffer *q, const char *data)
+{
+       appendStringInfoString(q->sql, data);
+       return true;
+}
+
+/*
+ * Helper for adding a parameter reference to the query
+ */
+static void
+add_ref(StringInfo buf, int sql_idx, ProxyFunction *func, int fn_idx, bool add_type)
+{
+       char            tmp[32];
+
+       if (add_type)
+               sprintf(tmp, "$%d::%s", sql_idx + 1,
+                               func->arg_types[fn_idx]->name);
+       else
+               sprintf(tmp, "$%d", sql_idx + 1);
+       appendStringInfoString(buf, tmp);
+}
+
+/*
+ * Add a SQL identifier to the query that may possibly be
+ * a parameter reference.
+ */
+bool
+plproxy_query_add_ident(QueryBuffer *q, const char *ident)
+{
+       int                     i,
+                               fn_idx = -1,
+                               sql_idx = -1;
+
+       if (ident[0] == '$')
+       {
+               fn_idx = atoi(ident + 1) - 1;
+               if (fn_idx < 0 || fn_idx >= q->func->arg_count)
+                       return false;
+       }
+       else
+       {
+               for (i = 0; i < q->func->arg_count; i++)
+               {
+                       if (strcasecmp(ident, q->func->arg_names[i]) == 0)
+                       {
+                               fn_idx = i;
+                               break;
+                       }
+               }
+       }
+       if (fn_idx >= 0)
+       {
+               for (i = 0; i < q->arg_count; i++)
+               {
+                       if (q->arg_lookup[i] == fn_idx)
+                       {
+                               sql_idx = i;
+                               break;
+                       }
+               }
+               if (sql_idx < 0)
+               {
+                       sql_idx = q->arg_count++;
+                       q->arg_lookup[sql_idx] = fn_idx;
+               }
+               add_ref(q->sql, sql_idx, q->func, fn_idx, q->add_types);
+       }
+       else
+               appendStringInfoString(q->sql, ident);
+       return true;
+}
+
+/*
+ * Create a ProxyQuery based on temporary QueryBuffer.
+ */
+ProxyQuery *
+plproxy_query_finish(QueryBuffer *q)
+{
+       ProxyQuery *pq;
+       MemoryContext old;
+       int                     len;
+
+       old = MemoryContextSwitchTo(q->func->ctx);
+
+       pq = palloc(sizeof(*pq));
+       pq->sql = pstrdup(q->sql->data);
+       pq->arg_count = q->arg_count;
+       len = q->arg_count * sizeof(int);
+       pq->arg_lookup = palloc(len);
+       pq->plan = NULL;
+       memcpy(pq->arg_lookup, q->arg_lookup, len);
+
+       MemoryContextSwitchTo(old);
+
+       /* unnecessary actually, but lets be correct */
+       if (1)
+       {
+               pfree(q->sql->data);
+               pfree(q->sql);
+               pfree(q->arg_lookup);
+               memset(q, 0, sizeof(*q));
+               pfree(q);
+       }
+
+       return pq;
+}
+
+/*
+ * Generate a functioncall based on own signature.
+ */
+ProxyQuery *
+plproxy_standard_query(ProxyFunction *func, bool add_types)
+{
+       StringInfoData sql;
+       ProxyQuery *pq;
+       int                     i,
+                               len;
+
+       pq = plproxy_func_alloc(func, sizeof(*pq));
+       pq->sql = NULL;
+       pq->plan = NULL;
+       pq->arg_count = func->arg_count;
+       len = pq->arg_count * sizeof(int);
+       pq->arg_lookup = plproxy_func_alloc(func, len);
+
+       initStringInfo(&sql);
+       appendStringInfo(&sql, "select * from %s(", func->name);
+       for (i = 0; i < func->arg_count; i++)
+       {
+               if (i > 0)
+                       appendStringInfoChar(&sql, ',');
+
+               add_ref(&sql, i, func, i, add_types);
+               pq->arg_lookup[i] = i;
+       }
+       appendStringInfoChar(&sql, ')');
+
+       pq->sql = plproxy_func_strdup(func, sql.data);
+       pfree(sql.data);
+
+       return pq;
+}
+
+/*
+ * Prepare ProxyQuery for local execution
+ */
+void
+plproxy_query_prepare(ProxyFunction *func, FunctionCallInfo fcinfo, ProxyQuery *q)
+{
+       int                     i;
+       Oid                     types[FUNC_MAX_ARGS];
+       void       *plan;
+
+       /* create sql statement in sql */
+       for (i = 0; i < q->arg_count; i++)
+       {
+               int                     idx = q->arg_lookup[i];
+
+               types[i] = func->arg_types[idx]->type_oid;
+       }
+
+       /* prepare & store plan */
+       plan = SPI_prepare(q->sql, q->arg_count, types);
+       q->plan = SPI_saveplan(plan);
+}
+
+/*
+ * Execute ProxyQuery locally.
+ *
+ * Result will be in SPI_tuptable.
+ */
+void
+plproxy_query_exec(ProxyFunction *func, FunctionCallInfo fcinfo, ProxyQuery *q)
+{
+       int                     i,
+                               idx,
+                               err;
+       ProxyType  *type;
+       char            arg_nulls[FUNC_MAX_ARGS];
+       Datum           arg_values[FUNC_MAX_ARGS];
+
+       /* fill args */
+       for (i = 0; i < q->arg_count; i++)
+       {
+               idx = q->arg_lookup[i];
+               type = func->arg_types[idx];
+               if (PG_ARGISNULL(idx))
+               {
+                       arg_nulls[i] = 'n';
+                       arg_values[i] = (Datum) NULL;
+               }
+               else
+               {
+                       arg_nulls[i] = ' ';
+                       arg_values[i] = PG_GETARG_DATUM(idx);
+               }
+       }
+
+       /* run query */
+       err = SPI_execute_plan(q->plan, arg_values, arg_nulls, true, 0);
+       if (err != SPI_OK_SELECT)
+               plproxy_error(func, "query '%s' failed: %s",
+                                         q->sql, SPI_result_code_string(err));
+}
+
+/*
+ * Free cached plan.
+ */
+void
+plproxy_query_freeplan(ProxyQuery *q)
+{
+       if (!q || !q->plan)
+               return;
+       SPI_freeplan(q->plan);
+       q->plan = NULL;
+}
diff --git a/src/result.c b/src/result.c
new file mode 100644 (file)
index 0000000..9bf4527
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * PL/Proxy - easy access to partitioned database.
+ * 
+ * Copyright (c) 2006 Sven Suursoho, Skype Technologies OÜ
+ * Copyright (c) 2007 Marko Kreen, Skype Technologies OÜ
+ * 
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Conversion from PGresult to Datum.
+ *
+ * Functions here are called with CurrentMemoryContext == query context
+ * so that palloc()-ed memory stays valid after return to postgres.
+ */
+
+#include "plproxy.h"
+
+static bool
+name_matches(ProxyFunction *func, const char *aname, PGresult *res, int col)
+{
+       const char *fname = PQfname(res, col);
+
+       if (fname == NULL)
+               plproxy_error(func, "Unnamed result column %d", col + 1);
+       if (strcasecmp(aname, fname) == 0)
+               return true;
+       return false;
+}
+
+/* fill func->result_map */
+static void
+map_results(ProxyFunction *func, PGresult *res)
+{
+       int                     i,
+                               j,
+                               natts,
+                               nfields = PQnfields(res);
+       Form_pg_attribute a;
+       const char *aname;
+
+       if (func->ret_scalar)
+       {
+               if (nfields != 1)
+                       plproxy_error(func,
+                                                 "single field function but got record");
+               return;
+       }
+
+       natts = func->ret_composite->tupdesc->natts;
+       if (nfields < natts)
+               plproxy_error(func, "Got too few fields from remote end");
+       if (nfields > natts)
+               plproxy_error(func, "Got too many fields from remote end");
+
+       for (i = 0; i < natts; i++)
+       {
+               a = func->ret_composite->tupdesc->attrs[i];
+               aname = NameStr(a->attname);
+
+               func->result_map[i] = -1;
+               if (name_matches(func, aname, res, i))
+                       /* fast case: 1:1 mapping */
+                       func->result_map[i] = i;
+               else
+               {
+                       /* slow case: messed up ordering */
+                       for (j = 0; j < nfields; j++)
+                       {
+                               /* already tried this one */
+                               if (j == i)
+                                       continue;
+
+                               /*
+                                * fixme: somehow remember the ones that are already mapped?
+                                */
+                               if (name_matches(func, aname, res, j))
+                               {
+                                       func->result_map[i] = j;
+                                       break;
+                               }
+                       }
+               }
+               if (func->result_map[i] < 0)
+                       plproxy_error(func,
+                                                 "Field %s does not exists in result", aname);
+
+               /* oid sanity check.  does not seem to work. */
+               if (0)
+               {
+                       Oid                     arg_oid = func->ret_composite->type_list[i]->type_oid;
+                       Oid                     col_oid = PQftype(res, func->result_map[i]);
+
+                       if (arg_oid < 2000 || col_oid < 2000)
+                       {
+                               if (arg_oid != col_oid)
+                                       elog(WARNING, "oids do not match:%d/%d",
+                                                arg_oid, col_oid);
+                       }
+               }
+       }
+}
+
+/* Return connection where are unreturned rows */
+static ProxyConnection *
+walk_results(ProxyFunction *func, ProxyCluster *cluster)
+{
+       ProxyConnection *conn;
+
+       for (; cluster->ret_cur_conn < cluster->conn_count;
+                cluster->ret_cur_conn++)
+       {
+               conn = cluster->conn_list + cluster->ret_cur_conn;
+               if (conn->res == NULL)
+                       continue;
+               if (conn->pos == PQntuples(conn->res))
+                       continue;
+
+               /* first time on this connection? */
+               if (conn->pos == 0)
+                       map_results(func, conn->res);
+
+               return conn;
+       }
+
+       plproxy_error(func, "bug: no result");
+       return NULL;
+}
+
+/* Return a tuple */
+static Datum
+return_composite(ProxyFunction *func, ProxyConnection *conn, FunctionCallInfo fcinfo)
+{
+       int                     i,
+                               col;
+       char       *values[FUNC_MAX_ARGS];
+       int                     fmts[FUNC_MAX_ARGS];
+       int                     lengths[FUNC_MAX_ARGS];
+       HeapTuple       tup;
+       ProxyComposite *meta = func->ret_composite;
+
+       for (i = 0; i < meta->tupdesc->natts; i++)
+       {
+               col = func->result_map[i];
+               if (PQgetisnull(conn->res, conn->pos, col))
+               {
+                       values[i] = NULL;
+                       lengths[i] = 0;
+                       fmts[i] = 0;
+               }
+               else
+               {
+                       values[i] = PQgetvalue(conn->res, conn->pos, col);
+                       lengths[i] = PQgetlength(conn->res, conn->pos, col);
+                       fmts[i] = PQfformat(conn->res, col);
+               }
+       }
+       tup = plproxy_recv_composite(meta, values, lengths, fmts);
+       return HeapTupleGetDatum(tup);
+}
+
+/* Return scalar value */
+static Datum
+return_scalar(ProxyFunction *func, ProxyConnection *conn, FunctionCallInfo fcinfo)
+{
+       Datum           dat;
+       char       *val;
+       PGresult   *res = conn->res;
+       int                     row = conn->pos;
+
+       if (func->ret_scalar->type_oid == VOIDOID)
+       {
+               dat = (Datum) NULL;
+       }
+       else if (PQgetisnull(res, row, 0))
+       {
+               fcinfo->isnull = true;
+               dat = (Datum) NULL;
+       }
+       else
+       {
+               val = PQgetvalue(res, row, 0);
+               if (val == NULL)
+                       plproxy_error(func, "unexcpected NULL");
+               dat = plproxy_recv_type(func->ret_scalar, val,
+                                                               PQgetlength(res, row, 0),
+                                                               PQfformat(res, 0));
+       }
+       return dat;
+}
+
+/* Return next result Datum */
+Datum
+plproxy_result(ProxyFunction *func, FunctionCallInfo fcinfo)
+{
+       Datum           dat;
+       ProxyCluster *cluster = func->cur_cluster;
+       ProxyConnection *conn;
+
+       conn = walk_results(func, cluster);
+
+       if (func->ret_composite)
+               dat = return_composite(func, conn, fcinfo);
+       else
+               dat = return_scalar(func, conn, fcinfo);
+
+       cluster->ret_total--;
+       conn->pos++;
+
+       return dat;
+}
diff --git a/src/scanner.c b/src/scanner.c
new file mode 100644 (file)
index 0000000..abc520b
--- /dev/null
@@ -0,0 +1,2275 @@
+#line 2 "scanner.c"
+
+#line 4 "scanner.c"
+
+#define  YY_INT_ALIGNED short int
+
+/* A lexical scanner generated by flex */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 5
+#define YY_FLEX_SUBMINOR_VERSION 33
+#if YY_FLEX_SUBMINOR_VERSION > 0
+#define FLEX_BETA
+#endif
+
+/* First, we deal with  platform-specific or compiler-specific issues. */
+
+/* begin standard C headers. */
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+/* end standard C headers. */
+
+/* flex integer type definitions */
+
+#ifndef FLEXINT_H
+#define FLEXINT_H
+
+/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */
+
+#if __STDC_VERSION__ >= 199901L
+
+/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h,
+ * if you want the limit (max/min) macros for int types. 
+ */
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS 1
+#endif
+
+#include <inttypes.h>
+typedef int8_t flex_int8_t;
+typedef uint8_t flex_uint8_t;
+typedef int16_t flex_int16_t;
+typedef uint16_t flex_uint16_t;
+typedef int32_t flex_int32_t;
+typedef uint32_t flex_uint32_t;
+#else
+typedef signed char flex_int8_t;
+typedef short int flex_int16_t;
+typedef int flex_int32_t;
+typedef unsigned char flex_uint8_t; 
+typedef unsigned short int flex_uint16_t;
+typedef unsigned int flex_uint32_t;
+#endif /* ! C99 */
+
+/* Limits of integral types. */
+#ifndef INT8_MIN
+#define INT8_MIN               (-128)
+#endif
+#ifndef INT16_MIN
+#define INT16_MIN              (-32767-1)
+#endif
+#ifndef INT32_MIN
+#define INT32_MIN              (-2147483647-1)
+#endif
+#ifndef INT8_MAX
+#define INT8_MAX               (127)
+#endif
+#ifndef INT16_MAX
+#define INT16_MAX              (32767)
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX              (2147483647)
+#endif
+#ifndef UINT8_MAX
+#define UINT8_MAX              (255U)
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX             (65535U)
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX             (4294967295U)
+#endif
+
+#endif /* ! FLEXINT_H */
+
+#ifdef __cplusplus
+
+/* The "const" storage-class-modifier is valid. */
+#define YY_USE_CONST
+
+#else  /* ! __cplusplus */
+
+#if __STDC__
+
+#define YY_USE_CONST
+
+#endif /* __STDC__ */
+#endif /* ! __cplusplus */
+
+#ifdef YY_USE_CONST
+#define yyconst const
+#else
+#define yyconst
+#endif
+
+/* Returned upon end-of-file. */
+#define YY_NULL 0
+
+/* Promotes a possibly negative, possibly signed char to an unsigned
+ * integer for use as an array index.  If the signed char is negative,
+ * we want to instead treat it as an 8-bit unsigned char, hence the
+ * double cast.
+ */
+#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c)
+
+/* Enter a start condition.  This macro really ought to take a parameter,
+ * but we do it the disgusting crufty way forced on us by the ()-less
+ * definition of BEGIN.
+ */
+#define BEGIN (yy_start) = 1 + 2 *
+
+/* Translate the current start state into a value that can be later handed
+ * to BEGIN to return to the state.  The YYSTATE alias is for lex
+ * compatibility.
+ */
+#define YY_START (((yy_start) - 1) / 2)
+#define YYSTATE YY_START
+
+/* Action number for EOF rule of a given start state. */
+#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1)
+
+/* Special action meaning "start processing a new file". */
+#define YY_NEW_FILE plproxy_yyrestart(plproxy_yyin  )
+
+#define YY_END_OF_BUFFER_CHAR 0
+
+/* Size of default input buffer. */
+#ifndef YY_BUF_SIZE
+#define YY_BUF_SIZE 16384
+#endif
+
+/* The state buf must be large enough to hold one state per character in the main buffer.
+ */
+#define YY_STATE_BUF_SIZE   ((YY_BUF_SIZE + 2) * sizeof(yy_state_type))
+
+#ifndef YY_TYPEDEF_YY_BUFFER_STATE
+#define YY_TYPEDEF_YY_BUFFER_STATE
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+#endif
+
+extern int plproxy_yyleng;
+
+extern FILE *plproxy_yyin, *plproxy_yyout;
+
+#define EOB_ACT_CONTINUE_SCAN 0
+#define EOB_ACT_END_OF_FILE 1
+#define EOB_ACT_LAST_MATCH 2
+
+    /* Note: We specifically omit the test for yy_rule_can_match_eol because it requires
+     *       access to the local variable yy_act. Since yyless() is a macro, it would break
+     *       existing scanners that call yyless() from OUTSIDE plproxy_yylex. 
+     *       One obvious solution it to make yy_act a global. I tried that, and saw
+     *       a 5% performance hit in a non-plproxy_yylineno scanner, because yy_act is
+     *       normally declared as a register variable-- so it is not worth it.
+     */
+    #define  YY_LESS_LINENO(n) \
+            do { \
+                int yyl;\
+                for ( yyl = n; yyl < plproxy_yyleng; ++yyl )\
+                    if ( plproxy_yytext[yyl] == '\n' )\
+                        --plproxy_yylineno;\
+            }while(0)
+    
+/* Return all but the first "n" matched characters back to the input stream. */
+#define yyless(n) \
+       do \
+               { \
+               /* Undo effects of setting up plproxy_yytext. */ \
+        int yyless_macro_arg = (n); \
+        YY_LESS_LINENO(yyless_macro_arg);\
+               *yy_cp = (yy_hold_char); \
+               YY_RESTORE_YY_MORE_OFFSET \
+               (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \
+               YY_DO_BEFORE_ACTION; /* set up plproxy_yytext again */ \
+               } \
+       while ( 0 )
+
+#define unput(c) yyunput( c, (yytext_ptr)  )
+
+/* The following is because we cannot portably get our hands on size_t
+ * (without autoconf's help, which isn't available because we want
+ * flex-generated scanners to compile on their own).
+ */
+
+#ifndef YY_TYPEDEF_YY_SIZE_T
+#define YY_TYPEDEF_YY_SIZE_T
+typedef unsigned int yy_size_t;
+#endif
+
+#ifndef YY_STRUCT_YY_BUFFER_STATE
+#define YY_STRUCT_YY_BUFFER_STATE
+struct yy_buffer_state
+       {
+       FILE *yy_input_file;
+
+       char *yy_ch_buf;                /* input buffer */
+       char *yy_buf_pos;               /* current position in input buffer */
+
+       /* Size of input buffer in bytes, not including room for EOB
+        * characters.
+        */
+       yy_size_t yy_buf_size;
+
+       /* Number of characters read into yy_ch_buf, not including EOB
+        * characters.
+        */
+       int yy_n_chars;
+
+       /* Whether we "own" the buffer - i.e., we know we created it,
+        * and can realloc() it to grow it, and should free() it to
+        * delete it.
+        */
+       int yy_is_our_buffer;
+
+       /* Whether this is an "interactive" input source; if so, and
+        * if we're using stdio for input, then we want to use getc()
+        * instead of fread(), to make sure we stop fetching input after
+        * each newline.
+        */
+       int yy_is_interactive;
+
+       /* Whether we're considered to be at the beginning of a line.
+        * If so, '^' rules will be active on the next match, otherwise
+        * not.
+        */
+       int yy_at_bol;
+
+    int yy_bs_lineno; /**< The line count. */
+    int yy_bs_column; /**< The column count. */
+    
+       /* Whether to try to fill the input buffer when we reach the
+        * end of it.
+        */
+       int yy_fill_buffer;
+
+       int yy_buffer_status;
+
+#define YY_BUFFER_NEW 0
+#define YY_BUFFER_NORMAL 1
+       /* When an EOF's been seen but there's still some text to process
+        * then we mark the buffer as YY_EOF_PENDING, to indicate that we
+        * shouldn't try reading from the input source any more.  We might
+        * still have a bunch of tokens to match, though, because of
+        * possible backing-up.
+        *
+        * When we actually see the EOF, we change the status to "new"
+        * (via plproxy_yyrestart()), so that the user can continue scanning by
+        * just pointing plproxy_yyin at a new input file.
+        */
+#define YY_BUFFER_EOF_PENDING 2
+
+       };
+#endif /* !YY_STRUCT_YY_BUFFER_STATE */
+
+/* Stack of input buffers. */
+static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */
+static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */
+static YY_BUFFER_STATE * yy_buffer_stack = 0; /**< Stack as an array. */
+
+/* We provide macros for accessing buffer states in case in the
+ * future we want to put the buffer states in a more general
+ * "scanner state".
+ *
+ * Returns the top of the stack, or NULL.
+ */
+#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \
+                          ? (yy_buffer_stack)[(yy_buffer_stack_top)] \
+                          : NULL)
+
+/* Same as previous macro, but useful when we know that the buffer stack is not
+ * NULL or when we need an lvalue. For internal use only.
+ */
+#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)]
+
+/* yy_hold_char holds the character lost when plproxy_yytext is formed. */
+static char yy_hold_char;
+static int yy_n_chars;         /* number of characters read into yy_ch_buf */
+int plproxy_yyleng;
+
+/* Points to current character in buffer. */
+static char *yy_c_buf_p = (char *) 0;
+static int yy_init = 0;                /* whether we need to initialize */
+static int yy_start = 0;       /* start state number */
+
+/* Flag which is used to allow plproxy_yywrap()'s to do buffer switches
+ * instead of setting up a fresh plproxy_yyin.  A bit of a hack ...
+ */
+static int yy_did_buffer_switch_on_eof;
+
+void plproxy_yyrestart (FILE *input_file  );
+void plproxy_yy_switch_to_buffer (YY_BUFFER_STATE new_buffer  );
+YY_BUFFER_STATE plproxy_yy_create_buffer (FILE *file,int size  );
+void plproxy_yy_delete_buffer (YY_BUFFER_STATE b  );
+void plproxy_yy_flush_buffer (YY_BUFFER_STATE b  );
+void plproxy_yypush_buffer_state (YY_BUFFER_STATE new_buffer  );
+void plproxy_yypop_buffer_state (void );
+
+static void plproxy_yyensure_buffer_stack (void );
+static void plproxy_yy_load_buffer_state (void );
+static void plproxy_yy_init_buffer (YY_BUFFER_STATE b,FILE *file  );
+
+#define YY_FLUSH_BUFFER plproxy_yy_flush_buffer(YY_CURRENT_BUFFER )
+
+YY_BUFFER_STATE plproxy_yy_scan_buffer (char *base,yy_size_t size  );
+YY_BUFFER_STATE plproxy_yy_scan_string (yyconst char *yy_str  );
+YY_BUFFER_STATE plproxy_yy_scan_bytes (yyconst char *bytes,int len  );
+
+void *plproxy_yyalloc (yy_size_t  );
+void *plproxy_yyrealloc (void *,yy_size_t  );
+void plproxy_yyfree (void *  );
+
+#define yy_new_buffer plproxy_yy_create_buffer
+
+#define yy_set_interactive(is_interactive) \
+       { \
+       if ( ! YY_CURRENT_BUFFER ){ \
+        plproxy_yyensure_buffer_stack (); \
+               YY_CURRENT_BUFFER_LVALUE =    \
+            plproxy_yy_create_buffer(plproxy_yyin,YY_BUF_SIZE ); \
+       } \
+       YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \
+       }
+
+#define yy_set_bol(at_bol) \
+       { \
+       if ( ! YY_CURRENT_BUFFER ){\
+        plproxy_yyensure_buffer_stack (); \
+               YY_CURRENT_BUFFER_LVALUE =    \
+            plproxy_yy_create_buffer(plproxy_yyin,YY_BUF_SIZE ); \
+       } \
+       YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \
+       }
+
+#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol)
+
+/* Begin user sect3 */
+
+#define plproxy_yywrap(n) 1
+#define YY_SKIP_YYWRAP
+
+typedef unsigned char YY_CHAR;
+
+FILE *plproxy_yyin = (FILE *) 0, *plproxy_yyout = (FILE *) 0;
+
+typedef int yy_state_type;
+
+extern int plproxy_yylineno;
+
+int plproxy_yylineno = 1;
+
+extern char *plproxy_yytext;
+#define yytext_ptr plproxy_yytext
+
+static yy_state_type yy_get_previous_state (void );
+static yy_state_type yy_try_NUL_trans (yy_state_type current_state  );
+static int yy_get_next_buffer (void );
+static void yy_fatal_error (yyconst char msg[]  );
+
+/* Done after the current pattern has been matched and before the
+ * corresponding action - sets up plproxy_yytext.
+ */
+#define YY_DO_BEFORE_ACTION \
+       (yytext_ptr) = yy_bp; \
+       plproxy_yyleng = (size_t) (yy_cp - yy_bp); \
+       (yy_hold_char) = *yy_cp; \
+       *yy_cp = '\0'; \
+       (yy_c_buf_p) = yy_cp;
+
+#define YY_NUM_RULES 52
+#define YY_END_OF_BUFFER 53
+/* This struct is not used in this scanner,
+   but its presence is necessary. */
+struct yy_trans_info
+       {
+       flex_int32_t yy_verify;
+       flex_int32_t yy_nxt;
+       };
+static yyconst flex_int16_t yy_accept[142] =
+    {   0,
+        0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+        0,    0,    0,    0,    0,    0,   53,   20,    9,    9,
+       20,   20,   20,   20,   18,   16,   16,   16,   16,   16,
+       16,   51,   49,   49,   48,   31,   51,   36,   48,   48,
+       47,   50,   45,   45,   32,   34,   32,   37,   39,   40,
+       40,   43,   44,   23,   23,   26,   26,   28,   30,   12,
+       12,   15,   15,    9,   17,    0,   19,   10,   11,   18,
+        0,    8,    0,   16,   16,   16,   16,   16,    4,   16,
+       16,   49,   27,   46,    0,   21,   22,   47,    0,    0,
+       45,   35,   32,   32,   33,   37,   38,   40,   41,   42,
+
+       23,   24,    0,   25,   28,   30,   29,   30,   12,   13,
+        0,   14,    0,   10,    0,   16,    5,    6,   16,   16,
+        3,   16,    0,   21,    0,   45,   30,   16,   16,   16,
+       16,   45,   16,   16,   16,   16,   16,    7,    1,    2,
+        0
+    } ;
+
+static yyconst flex_int32_t yy_ec[256] =
+    {   0,
+        1,    1,    1,    1,    1,    1,    1,    1,    2,    3,
+        1,    1,    2,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    2,    4,    5,    4,    6,    4,    4,    7,    8,
+        4,    9,    4,    4,   10,   11,   12,   13,   13,   13,
+       13,   13,   13,   13,   13,   13,   13,    4,   14,    4,
+        4,    4,    4,    4,   17,   18,   19,   18,   20,   18,
+       18,   18,   18,   18,   18,   21,   18,   22,   23,   18,
+       18,   24,   25,   26,   27,   18,   18,   18,   28,   18,
+        4,   15,    4,    4,   16,    1,   17,   18,   19,   18,
+
+       20,   18,   18,   18,   18,   18,   18,   21,   18,   22,
+       23,   18,   18,   24,   25,   26,   27,   18,   18,   18,
+       28,   18,    4,    4,    4,    4,    1,   29,   29,   29,
+       29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
+       29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
+       29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
+       29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
+       29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
+       29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
+       29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
+
+       29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
+       29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
+       29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
+       29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
+       29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
+       29,   29,   29,   29,   29
+    } ;
+
+static yyconst flex_int32_t yy_meta[30] =
+    {   0,
+        1,    2,    3,    1,    4,    5,    6,    7,    8,    1,
+        9,    8,   10,    1,   11,   10,   12,   12,   12,   12,
+       12,   12,   12,   12,   12,   12,   12,   12,   13
+    } ;
+
+static yyconst flex_int16_t yy_base[170] =
+    {   0,
+        0,    0,   29,    0,   54,   55,  484,  463,   58,   59,
+       68,   69,  448,  425,   73,   80,  413,  581,   61,   65,
+      395,  396,  380,  353,  344,   91,   93,   63,  332,  326,
+      332,  581,   85,   88,  581,  581,   92,  581,  341,  341,
+       95,  581,  107,  112,    0,  581,  117,    0,  338,    0,
+        0,  336,    0,    0,    0,   88,  581,    0,  123,    0,
+        0,  144,  581,  114,  329,  334,  333,    0,  581,  324,
+      152,  581,  155,    0,  313,  304,  304,  308,    0,  213,
+      213,  159,  581,  220,  225,    0,  581,  153,  165,  167,
+      223,  581,    0,    0,  581,    0,  581,    0,  581,  581,
+
+        0,  162,  163,  581,    0,    0,  581,  176,    0,  197,
+      198,  581,  218,    0,  209,  211,    0,    0,  196,  198,
+        0,  188,  167,    0,  213,  215,    0,  221,  139,  139,
+      102,  225,   91,   88,   77,   55,   49,    0,    0,    0,
+      581,  236,  249,  262,  275,  288,  301,  314,  326,  334,
+      346,  358,  371,  384,  397,  410,  423,  436,  449,  462,
+      475,  488,  500,  508,  521,  533,  545,  557,  568
+    } ;
+
+static yyconst flex_int16_t yy_def[170] =
+    {   0,
+      141,    1,  141,    3,  142,  142,  143,  143,  144,  144,
+      145,  145,  146,  146,  147,  147,  141,  141,  141,  141,
+      141,  148,  141,  141,  141,  149,  149,   27,   27,   27,
+       27,  141,  141,  141,  141,  141,  150,  141,  141,  141,
+      141,  141,  151,  151,  152,  141,  153,  154,  141,  155,
+      155,  141,  156,  157,  157,  158,  141,  159,  141,  160,
+      160,  161,  141,  141,  141,  148,  141,  162,  141,  141,
+      141,  141,  163,   27,   27,   27,   27,   27,   27,   27,
+       27,  141,  141,  141,  164,  165,  141,  141,  141,  166,
+       44,  141,  152,  152,  141,  154,  141,  155,  141,  141,
+
+      157,  158,  158,  141,  159,  167,  141,  141,  160,  161,
+      161,  141,  148,  162,  163,  168,   27,   27,   27,   27,
+       27,   27,  164,  165,  166,  169,  108,  168,   27,   27,
+       27,  169,   27,   27,   27,   27,   27,   27,   27,   27,
+        0,  141,  141,  141,  141,  141,  141,  141,  141,  141,
+      141,  141,  141,  141,  141,  141,  141,  141,  141,  141,
+      141,  141,  141,  141,  141,  141,  141,  141,  141
+    } ;
+
+static yyconst flex_int16_t yy_nxt[611] =
+    {   0,
+       18,   19,   20,   18,   18,   21,   22,   18,   18,   23,
+       18,   24,   25,   18,   18,   18,   26,   27,   28,   27,
+       27,   27,   29,   30,   31,   27,   27,   27,   18,   32,
+       33,   34,   35,   36,   37,   38,   35,   35,   39,   32,
+       40,   41,   42,   32,   32,   43,   43,   43,   44,   43,
+       43,   43,   43,   43,   43,   43,   43,   32,   46,   46,
+       51,   51,   64,   64,   52,   52,   64,   64,   47,   47,
+       55,   55,   53,   53,  140,   61,   56,   56,  139,   57,
+       57,   62,   61,   77,   63,   78,   82,   82,   62,   82,
+       82,   63,   71,   71,   71,   71,  103,   83,   72,  104,
+
+       72,   73,  138,   73,   84,   88,  137,   88,   89,   89,
+      136,   75,   76,   89,   89,   64,   64,   90,   92,   93,
+      135,   95,   90,  106,  106,  106,  106,  106,  107,  106,
+      106,  106,  106,  106,  106,  106,  106,  106,  108,  108,
+      108,  108,  108,  108,  108,  108,  108,  108,  108,  108,
+      108,  108,  111,   71,   71,  112,  115,  115,  134,   72,
+       82,   82,   73,   88,  133,   88,   89,   89,  125,  125,
+      141,  103,   83,  141,  104,   90,  106,  106,  106,  106,
+      106,  107,  106,  106,  106,  106,  106,  106,  127,  106,
+      106,  127,  127,  127,  127,  127,  127,  127,  127,  127,
+
+      127,  127,  127,  127,  127,  141,  111,  131,  141,  112,
+      115,  115,   71,   71,  125,  125,   89,   89,   72,  130,
+      129,   73,   71,   71,   67,   90,   89,   89,   72,  141,
+       83,   73,   84,  122,  121,   90,   45,   45,   45,   45,
+       45,   45,   45,   45,   45,   45,   45,   45,   45,   48,
+       48,   48,   48,   48,   48,   48,   48,   48,   48,   48,
+       48,   48,   50,   50,   50,   50,   50,   50,   50,   50,
+       50,   50,   50,   50,   50,   54,   54,   54,   54,   54,
+       54,   54,   54,   54,   54,   54,   54,   54,   58,   58,
+       58,   58,   58,   58,   58,   58,   58,   58,   58,   58,
+
+       58,   60,   60,   60,   60,   60,   60,   60,   60,   60,
+       60,   60,   60,   60,   66,   66,   66,   66,   66,   66,
+       66,   66,   66,   66,   66,   66,   66,   74,   74,  120,
+      119,  118,   74,  117,   74,   74,   70,   74,   85,  113,
+       67,   65,   99,   85,   97,   85,   85,   91,   91,   87,
+       86,   81,   80,   79,   91,   91,   70,   91,   93,   93,
+       93,   69,   93,   93,   93,   93,   93,   93,   93,   93,
+       93,   94,   94,   94,   94,   94,   94,   94,   94,   94,
+       94,   94,   94,   94,   96,   96,   96,   96,   96,   68,
+       96,   96,   96,   96,   96,   96,   96,   98,   98,   98,
+
+       98,   98,   67,   98,   98,   98,   98,   65,   98,   98,
+      100,  100,  141,  100,  100,  100,  100,  100,  100,  100,
+      100,  100,  100,  101,  101,  101,  101,  101,  101,  101,
+       59,  101,  101,  101,  101,  101,  102,  102,  102,  102,
+      102,  102,  102,  102,  102,  102,  102,  102,  102,  105,
+      105,  105,  105,   59,  105,  105,  105,  105,  105,  105,
+      105,  105,  109,  109,  109,  109,  109,  109,  109,   49,
+      109,  109,  109,  109,  109,  110,  110,  110,  110,  110,
+      110,  110,  110,  110,  110,  110,  110,  110,  114,  114,
+       49,  114,  114,  114,  114,  114,  114,  114,  114,  114,
+
+      114,  116,  116,  141,  141,  141,  141,  141,  141,  141,
+      141,  116,  123,  141,  141,  141,  141,  123,  141,  123,
+      123,  124,  124,  141,  124,  124,  124,  124,  124,  124,
+      124,  124,  124,  124,  126,  126,  141,  141,  141,  141,
+      141,  141,  141,  141,  126,  106,  106,  106,  106,  141,
+      106,  106,  106,  106,  106,  106,  106,  106,  128,  128,
+      141,  141,  141,  128,  141,  128,  128,  141,  128,  132,
+      132,  141,  141,  141,  141,  141,  132,  132,  141,  132,
+       17,  141,  141,  141,  141,  141,  141,  141,  141,  141,
+      141,  141,  141,  141,  141,  141,  141,  141,  141,  141,
+
+      141,  141,  141,  141,  141,  141,  141,  141,  141,  141
+    } ;
+
+static yyconst flex_int16_t yy_chk[611] =
+    {   0,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    3,
+        3,    3,    3,    3,    3,    3,    3,    3,    3,    3,
+        3,    3,    3,    3,    3,    3,    3,    3,    3,    3,
+        3,    3,    3,    3,    3,    3,    3,    3,    5,    6,
+        9,   10,   19,   19,    9,   10,   20,   20,    5,    6,
+       11,   12,    9,   10,  137,   15,   11,   12,  136,   11,
+       12,   15,   16,   28,   15,   28,   33,   33,   16,   34,
+       34,   16,   26,   26,   27,   27,   56,   37,   26,   56,
+
+       27,   26,  135,   27,   37,   41,  134,   41,   43,   43,
+      133,   26,   26,   44,   44,   64,   64,   43,   44,   47,
+      131,   47,   44,   59,   59,   59,   59,   59,   59,   59,
+       59,   59,   59,   59,   59,   59,   59,   59,   59,   59,
+       59,   59,   59,   59,   59,   59,   59,   59,   59,   59,
+       59,   59,   62,   71,   71,   62,   73,   73,  130,   71,
+       82,   82,   71,   88,  129,   88,   89,   89,   90,   90,
+      102,  103,  123,  102,  103,   89,  108,  108,  108,  108,
+      108,  108,  108,  108,  108,  108,  108,  108,  108,  108,
+      108,  108,  108,  108,  108,  108,  108,  108,  108,  108,
+
+      108,  108,  108,  108,  108,  110,  111,  122,  110,  111,
+      115,  115,  116,  116,  125,  125,  126,  126,  116,  120,
+      119,  116,  128,  128,  113,  126,  132,  132,  128,   91,
+       85,  128,   84,   81,   80,  132,  142,  142,  142,  142,
+      142,  142,  142,  142,  142,  142,  142,  142,  142,  143,
+      143,  143,  143,  143,  143,  143,  143,  143,  143,  143,
+      143,  143,  144,  144,  144,  144,  144,  144,  144,  144,
+      144,  144,  144,  144,  144,  145,  145,  145,  145,  145,
+      145,  145,  145,  145,  145,  145,  145,  145,  146,  146,
+      146,  146,  146,  146,  146,  146,  146,  146,  146,  146,
+
+      146,  147,  147,  147,  147,  147,  147,  147,  147,  147,
+      147,  147,  147,  147,  148,  148,  148,  148,  148,  148,
+      148,  148,  148,  148,  148,  148,  148,  149,  149,   78,
+       77,   76,  149,   75,  149,  149,   70,  149,  150,   67,
+       66,   65,   52,  150,   49,  150,  150,  151,  151,   40,
+       39,   31,   30,   29,  151,  151,   25,  151,  152,  152,
+      152,   24,  152,  152,  152,  152,  152,  152,  152,  152,
+      152,  153,  153,  153,  153,  153,  153,  153,  153,  153,
+      153,  153,  153,  153,  154,  154,  154,  154,  154,   23,
+      154,  154,  154,  154,  154,  154,  154,  155,  155,  155,
+
+      155,  155,   22,  155,  155,  155,  155,   21,  155,  155,
+      156,  156,   17,  156,  156,  156,  156,  156,  156,  156,
+      156,  156,  156,  157,  157,  157,  157,  157,  157,  157,
+       14,  157,  157,  157,  157,  157,  158,  158,  158,  158,
+      158,  158,  158,  158,  158,  158,  158,  158,  158,  159,
+      159,  159,  159,   13,  159,  159,  159,  159,  159,  159,
+      159,  159,  160,  160,  160,  160,  160,  160,  160,    8,
+      160,  160,  160,  160,  160,  161,  161,  161,  161,  161,
+      161,  161,  161,  161,  161,  161,  161,  161,  162,  162,
+        7,  162,  162,  162,  162,  162,  162,  162,  162,  162,
+
+      162,  163,  163,    0,    0,    0,    0,    0,    0,    0,
+        0,  163,  164,    0,    0,    0,    0,  164,    0,  164,
+      164,  165,  165,    0,  165,  165,  165,  165,  165,  165,
+      165,  165,  165,  165,  166,  166,    0,    0,    0,    0,
+        0,    0,    0,    0,  166,  167,  167,  167,  167,    0,
+      167,  167,  167,  167,  167,  167,  167,  167,  168,  168,
+        0,    0,    0,  168,    0,  168,  168,    0,  168,  169,
+      169,    0,    0,    0,    0,    0,  169,  169,    0,  169,
+      141,  141,  141,  141,  141,  141,  141,  141,  141,  141,
+      141,  141,  141,  141,  141,  141,  141,  141,  141,  141,
+
+      141,  141,  141,  141,  141,  141,  141,  141,  141,  141
+    } ;
+
+/* Table of booleans, true if rule could match eol. */
+static yyconst flex_int32_t yy_rule_can_match_eol[53] =
+    {   0,
+0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 
+    0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 
+    1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,     };
+
+static yy_state_type yy_last_accepting_state;
+static char *yy_last_accepting_cpos;
+
+extern int plproxy_yy_flex_debug;
+int plproxy_yy_flex_debug = 0;
+
+/* The intent behind this definition is that it'll catch
+ * any uses of REJECT which flex missed.
+ */
+#define REJECT reject_used_but_not_detected
+#define yymore() yymore_used_but_not_detected
+#define YY_MORE_ADJ 0
+#define YY_RESTORE_YY_MORE_OFFSET
+char *plproxy_yytext;
+#line 1 "scanner.l"
+#line 2 "scanner.l"
+
+/*
+ * PL/Proxy - easy access to partitioned database.
+ * 
+ * Copyright (c) 2006 Sven Suursoho, Skype Technologies OÜ
+ * Copyright (c) 2007 Marko Kreen, Skype Technologies OÜ
+ * 
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "plproxy.h"
+#include "parser.tab.h"
+
+/* point to parser value */
+#define yylval plproxy_yylval
+
+/* 
+ * Allocate in CurrentMemoryContext.  That means plproxy_yylex_destroy()
+ * must be called before SPI_finish().
+ */
+void *plproxy_yyalloc(yy_size_t len) { return palloc(len); }
+void *plproxy_yyrealloc(void *ptr,yy_size_t len) { return repalloc(ptr, len); }
+void plproxy_yyfree(void *ptr) { pfree(ptr); }
+
+/* own error handling */
+#define YY_FATAL_ERROR(msg) plproxy_yyerror(msg)
+
+/* disable stdio related code */
+#define YY_INPUT(buf, res, maxlen) { res = 0; }
+
+/* shut down crappy flex warnings */
+int plproxy_yyget_lineno(void);
+int plproxy_yyget_leng(void);
+FILE *plproxy_yyget_in(void);
+FILE *plproxy_yyget_out(void);
+char *plproxy_yyget_text(void);
+void plproxy_yyset_lineno(int);
+void plproxy_yyset_in(FILE *);
+void plproxy_yyset_out(FILE *);
+int plproxy_yyget_debug(void);
+void plproxy_yyset_debug(int);
+int plproxy_yylex_destroy(void);
+
+/* shortcut for returning CONST */
+#define RETPART do { yylval.str = plproxy_yytext; return SQLPART; } while (0)
+
+/* dollar quoting helpers */
+static void dlr_start(const char *txt);
+static bool dlr_stop(const char *txt);
+
+static const char *unquote(const char *qstr, bool std);
+
+/* states */
+
+
+
+
+
+
+
+/* whitespace */
+/* sql ident.  include dotted parts also */
+/* argument ref by val: $1 */
+/* regular int value for hash spec */
+/* SQL numeric value */
+/* 
+ * Symbols that may exist in sql.   They must be matched one-by-one,
+ * to avoid conflics with combos.
+ *
+ * Excludes: [$'";`]
+ */
+/* Dollar quote ID */
+#line 743 "scanner.c"
+
+#define INITIAL 0
+#define sql 1
+#define qident 2
+#define stdq 3
+#define extq 4
+#define longcom 5
+#define dolq 6
+#define plcom 7
+
+#ifndef YY_NO_UNISTD_H
+/* Special case for "unistd.h", since it is non-ANSI. We include it way
+ * down here because we want the user's section 1 to have been scanned first.
+ * The user has a chance to override it with an option.
+ */
+#include <unistd.h>
+#endif
+
+#ifndef YY_EXTRA_TYPE
+#define YY_EXTRA_TYPE void *
+#endif
+
+static int yy_init_globals (void );
+
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef YY_SKIP_YYWRAP
+#ifdef __cplusplus
+extern "C" int plproxy_yywrap (void );
+#else
+extern int plproxy_yywrap (void );
+#endif
+#endif
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char *,yyconst char *,int );
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (yyconst char * );
+#endif
+
+#ifndef YY_NO_INPUT
+
+#ifdef __cplusplus
+static int yyinput (void );
+#else
+static int input (void );
+#endif
+
+#endif
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#define YY_READ_BUF_SIZE 8192
+#endif
+
+/* Copy whatever the last rule matched to the standard output. */
+#ifndef ECHO
+/* This used to be an fputs(), but since the string might contain NUL's,
+ * we now use fwrite().
+ */
+#define ECHO (void) fwrite( plproxy_yytext, plproxy_yyleng, 1, plproxy_yyout )
+#endif
+
+/* Gets input and stuffs it into "buf".  number of characters read, or YY_NULL,
+ * is returned in "result".
+ */
+#ifndef YY_INPUT
+#define YY_INPUT(buf,result,max_size) \
+       if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \
+               { \
+               int c = '*'; \
+               size_t n; \
+               for ( n = 0; n < max_size && \
+                            (c = getc( plproxy_yyin )) != EOF && c != '\n'; ++n ) \
+                       buf[n] = (char) c; \
+               if ( c == '\n' ) \
+                       buf[n++] = (char) c; \
+               if ( c == EOF && ferror( plproxy_yyin ) ) \
+                       YY_FATAL_ERROR( "input in flex scanner failed" ); \
+               result = n; \
+               } \
+       else \
+               { \
+               errno=0; \
+               while ( (result = fread(buf, 1, max_size, plproxy_yyin))==0 && ferror(plproxy_yyin)) \
+                       { \
+                       if( errno != EINTR) \
+                               { \
+                               YY_FATAL_ERROR( "input in flex scanner failed" ); \
+                               break; \
+                               } \
+                       errno=0; \
+                       clearerr(plproxy_yyin); \
+                       } \
+               }\
+\
+
+#endif
+
+/* No semi-colon after return; correct usage is to write "yyterminate();" -
+ * we don't want an extra ';' after the "return" because that will cause
+ * some compilers to complain about unreachable statements.
+ */
+#ifndef yyterminate
+#define yyterminate() return YY_NULL
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Report a fatal error. */
+#ifndef YY_FATAL_ERROR
+#define YY_FATAL_ERROR(msg) yy_fatal_error( msg )
+#endif
+
+/* end tables serialization structures and prototypes */
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL_IS_OURS 1
+
+extern int plproxy_yylex (void);
+
+#define YY_DECL int plproxy_yylex (void)
+#endif /* !YY_DECL */
+
+/* Code executed at the beginning of each rule, after plproxy_yytext and plproxy_yyleng
+ * have been set up.
+ */
+#ifndef YY_USER_ACTION
+#define YY_USER_ACTION
+#endif
+
+/* Code executed at the end of each rule. */
+#ifndef YY_BREAK
+#define YY_BREAK break;
+#endif
+
+#define YY_RULE_SETUP \
+       YY_USER_ACTION
+
+/** The main scanner function which does all the work.
+ */
+YY_DECL
+{
+       register yy_state_type yy_current_state;
+       register char *yy_cp, *yy_bp;
+       register int yy_act;
+    
+#line 110 "scanner.l"
+
+
+       /* PL/Proxy language keywords */
+
+#line 906 "scanner.c"
+
+       if ( !(yy_init) )
+               {
+               (yy_init) = 1;
+
+#ifdef YY_USER_INIT
+               YY_USER_INIT;
+#endif
+
+               if ( ! (yy_start) )
+                       (yy_start) = 1; /* first start state */
+
+               if ( ! plproxy_yyin )
+                       plproxy_yyin = stdin;
+
+               if ( ! plproxy_yyout )
+                       plproxy_yyout = stdout;
+
+               if ( ! YY_CURRENT_BUFFER ) {
+                       plproxy_yyensure_buffer_stack ();
+                       YY_CURRENT_BUFFER_LVALUE =
+                               plproxy_yy_create_buffer(plproxy_yyin,YY_BUF_SIZE );
+               }
+
+               plproxy_yy_load_buffer_state( );
+               }
+
+       while ( 1 )             /* loops until end-of-file is reached */
+               {
+               yy_cp = (yy_c_buf_p);
+
+               /* Support of plproxy_yytext. */
+               *yy_cp = (yy_hold_char);
+
+               /* yy_bp points to the position in yy_ch_buf of the start of
+                * the current run.
+                */
+               yy_bp = yy_cp;
+
+               yy_current_state = (yy_start);
+yy_match:
+               do
+                       {
+                       register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)];
+                       if ( yy_accept[yy_current_state] )
+                               {
+                               (yy_last_accepting_state) = yy_current_state;
+                               (yy_last_accepting_cpos) = yy_cp;
+                               }
+                       while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+                               {
+                               yy_current_state = (int) yy_def[yy_current_state];
+                               if ( yy_current_state >= 142 )
+                                       yy_c = yy_meta[(unsigned int) yy_c];
+                               }
+                       yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+                       ++yy_cp;
+                       }
+               while ( yy_current_state != 141 );
+               yy_cp = (yy_last_accepting_cpos);
+               yy_current_state = (yy_last_accepting_state);
+
+yy_find_action:
+               yy_act = yy_accept[yy_current_state];
+
+               YY_DO_BEFORE_ACTION;
+
+               if ( yy_act != YY_END_OF_BUFFER && yy_rule_can_match_eol[yy_act] )
+                       {
+                       int yyl;
+                       for ( yyl = 0; yyl < plproxy_yyleng; ++yyl )
+                               if ( plproxy_yytext[yyl] == '\n' )
+                                          
+    plproxy_yylineno++;
+;
+                       }
+
+do_action:     /* This label is used only to access EOF actions. */
+
+               switch ( yy_act )
+       { /* beginning of action switch */
+                       case 0: /* must back up */
+                       /* undo the effects of YY_DO_BEFORE_ACTION */
+                       *yy_cp = (yy_hold_char);
+                       yy_cp = (yy_last_accepting_cpos);
+                       yy_current_state = (yy_last_accepting_state);
+                       goto yy_find_action;
+
+case 1:
+YY_RULE_SETUP
+#line 114 "scanner.l"
+{ return CLUSTER; }
+       YY_BREAK
+case 2:
+YY_RULE_SETUP
+#line 115 "scanner.l"
+{ return CONNECT; }
+       YY_BREAK
+case 3:
+YY_RULE_SETUP
+#line 116 "scanner.l"
+{ return RUN; }
+       YY_BREAK
+case 4:
+YY_RULE_SETUP
+#line 117 "scanner.l"
+{ return ON; }
+       YY_BREAK
+case 5:
+YY_RULE_SETUP
+#line 118 "scanner.l"
+{ return ALL; }
+       YY_BREAK
+case 6:
+YY_RULE_SETUP
+#line 119 "scanner.l"
+{ return ANY; }
+       YY_BREAK
+case 7:
+YY_RULE_SETUP
+#line 120 "scanner.l"
+{ BEGIN(sql); yylval.str = plproxy_yytext; return SELECT; }
+       YY_BREAK
+/* function call */
+case 8:
+/* rule 8 can match eol */
+YY_RULE_SETUP
+#line 124 "scanner.l"
+{ BEGIN(sql); yylval.str = plproxy_yytext; return FNCALL; }
+       YY_BREAK
+/* PL/Proxy language comments/whitespace */
+case 9:
+/* rule 9 can match eol */
+YY_RULE_SETUP
+#line 128 "scanner.l"
+{ }
+       YY_BREAK
+case 10:
+YY_RULE_SETUP
+#line 129 "scanner.l"
+{ }
+       YY_BREAK
+case 11:
+YY_RULE_SETUP
+#line 130 "scanner.l"
+{ BEGIN(plcom); }
+       YY_BREAK
+case 12:
+/* rule 12 can match eol */
+YY_RULE_SETUP
+#line 131 "scanner.l"
+{ }
+       YY_BREAK
+case 13:
+/* rule 13 can match eol */
+YY_RULE_SETUP
+#line 132 "scanner.l"
+{ }
+       YY_BREAK
+case 14:
+YY_RULE_SETUP
+#line 133 "scanner.l"
+{ BEGIN(INITIAL); }
+       YY_BREAK
+case 15:
+YY_RULE_SETUP
+#line 134 "scanner.l"
+{ }
+       YY_BREAK
+/* PL/Proxy non-keyword elements */
+case 16:
+/* rule 16 can match eol */
+YY_RULE_SETUP
+#line 138 "scanner.l"
+{ yylval.str = plproxy_yytext; return IDENT; }
+       YY_BREAK
+case 17:
+YY_RULE_SETUP
+#line 139 "scanner.l"
+{ yylval.str = plproxy_yytext; return IDENT; }
+       YY_BREAK
+case 18:
+YY_RULE_SETUP
+#line 140 "scanner.l"
+{ yylval.str = plproxy_yytext; return NUMBER; }
+       YY_BREAK
+case 19:
+/* rule 19 can match eol */
+YY_RULE_SETUP
+#line 141 "scanner.l"
+{ yylval.str = unquote(plproxy_yytext, true); return STRING; }
+       YY_BREAK
+/* unparsed symbol, let parser decide */
+case 20:
+YY_RULE_SETUP
+#line 145 "scanner.l"
+{ return *(plproxy_yytext); }
+       YY_BREAK
+/*
+        * Following is parser for SQL statements.
+        */
+/* SQL line comment */
+case 21:
+YY_RULE_SETUP
+#line 153 "scanner.l"
+{ /* \n will be parsed as whitespace */ }
+       YY_BREAK
+/* C comment, parse it as whitespace */
+case 22:
+YY_RULE_SETUP
+#line 157 "scanner.l"
+{ BEGIN(longcom); }
+       YY_BREAK
+case 23:
+/* rule 23 can match eol */
+YY_RULE_SETUP
+#line 158 "scanner.l"
+{ }
+       YY_BREAK
+case 24:
+/* rule 24 can match eol */
+YY_RULE_SETUP
+#line 159 "scanner.l"
+{ }
+       YY_BREAK
+case 25:
+YY_RULE_SETUP
+#line 160 "scanner.l"
+{ BEGIN(sql); yylval.str = " "; return SQLPART; }
+       YY_BREAK
+case 26:
+YY_RULE_SETUP
+#line 161 "scanner.l"
+{ }
+       YY_BREAK
+/* Dollar quoted string */
+case 27:
+YY_RULE_SETUP
+#line 165 "scanner.l"
+{ BEGIN(dolq); dlr_start(plproxy_yytext); RETPART; }
+       YY_BREAK
+case 28:
+/* rule 28 can match eol */
+YY_RULE_SETUP
+#line 166 "scanner.l"
+{ RETPART; }
+       YY_BREAK
+case 29:
+YY_RULE_SETUP
+#line 167 "scanner.l"
+{ if (dlr_stop(plproxy_yytext)) { BEGIN(sql); RETPART; }
+                         /* if wrong one, report only 1 char */
+                         else { yyless(1); RETPART; } }
+       YY_BREAK
+case 30:
+/* rule 30 can match eol */
+YY_RULE_SETUP
+#line 170 "scanner.l"
+{ RETPART; }
+       YY_BREAK
+/* quoted indentifier */
+case 31:
+YY_RULE_SETUP
+#line 174 "scanner.l"
+{ BEGIN(qident); RETPART; }
+       YY_BREAK
+case 32:
+/* rule 32 can match eol */
+YY_RULE_SETUP
+#line 175 "scanner.l"
+{ RETPART; }
+       YY_BREAK
+case 33:
+YY_RULE_SETUP
+#line 176 "scanner.l"
+{ RETPART; }
+       YY_BREAK
+case 34:
+YY_RULE_SETUP
+#line 177 "scanner.l"
+{ BEGIN(sql); RETPART; }
+       YY_BREAK
+/* quoted string start */
+case 35:
+YY_RULE_SETUP
+#line 181 "scanner.l"
+{ BEGIN(extq); RETPART; }
+       YY_BREAK
+case 36:
+YY_RULE_SETUP
+#line 182 "scanner.l"
+{ if (standard_conforming_strings)
+                           BEGIN(stdq); else BEGIN(extq);
+                         RETPART; }
+       YY_BREAK
+/* SQL standard quoted string body */
+case 37:
+/* rule 37 can match eol */
+YY_RULE_SETUP
+#line 188 "scanner.l"
+{ RETPART; }
+       YY_BREAK
+case 38:
+YY_RULE_SETUP
+#line 189 "scanner.l"
+{ RETPART; }
+       YY_BREAK
+case 39:
+YY_RULE_SETUP
+#line 190 "scanner.l"
+{ BEGIN(sql); RETPART; }
+       YY_BREAK
+/* extended quoted string body */
+case 40:
+/* rule 40 can match eol */
+YY_RULE_SETUP
+#line 194 "scanner.l"
+{ RETPART; }
+       YY_BREAK
+case 41:
+YY_RULE_SETUP
+#line 195 "scanner.l"
+{ RETPART; }
+       YY_BREAK
+case 42:
+YY_RULE_SETUP
+#line 196 "scanner.l"
+{ RETPART; }
+       YY_BREAK
+case 43:
+YY_RULE_SETUP
+#line 197 "scanner.l"
+{ BEGIN(sql); RETPART; }
+       YY_BREAK
+case 44:
+YY_RULE_SETUP
+#line 198 "scanner.l"
+{ RETPART; }
+       YY_BREAK
+/* SQL identifier */
+case 45:
+/* rule 45 can match eol */
+YY_RULE_SETUP
+#line 202 "scanner.l"
+{ yylval.str = plproxy_yytext; return SQLIDENT; }
+       YY_BREAK
+/* $x argument reference */
+case 46:
+YY_RULE_SETUP
+#line 206 "scanner.l"
+{ yylval.str = plproxy_yytext; return SQLIDENT; }
+       YY_BREAK
+/* SQL number */
+case 47:
+YY_RULE_SETUP
+#line 210 "scanner.l"
+{ RETPART; }
+       YY_BREAK
+/* SQL symbol, parse them one-by-one */
+case 48:
+YY_RULE_SETUP
+#line 214 "scanner.l"
+{ RETPART; }
+       YY_BREAK
+/* compress whitespace to singe ' ' */
+case 49:
+/* rule 49 can match eol */
+YY_RULE_SETUP
+#line 218 "scanner.l"
+{ yylval.str = " "; return SQLPART; }
+       YY_BREAK
+/* SQL statement end */
+case 50:
+YY_RULE_SETUP
+#line 222 "scanner.l"
+{ BEGIN(INITIAL); return *(plproxy_yytext); }
+       YY_BREAK
+/* unparsed symbol, let the parser error out */
+case 51:
+YY_RULE_SETUP
+#line 226 "scanner.l"
+{ return *(plproxy_yytext); }
+       YY_BREAK
+case 52:
+YY_RULE_SETUP
+#line 228 "scanner.l"
+YY_FATAL_ERROR( "flex scanner jammed" );
+       YY_BREAK
+#line 1295 "scanner.c"
+case YY_STATE_EOF(INITIAL):
+case YY_STATE_EOF(sql):
+case YY_STATE_EOF(qident):
+case YY_STATE_EOF(stdq):
+case YY_STATE_EOF(extq):
+case YY_STATE_EOF(longcom):
+case YY_STATE_EOF(dolq):
+case YY_STATE_EOF(plcom):
+       yyterminate();
+
+       case YY_END_OF_BUFFER:
+               {
+               /* Amount of text matched not including the EOB char. */
+               int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1;
+
+               /* Undo the effects of YY_DO_BEFORE_ACTION. */
+               *yy_cp = (yy_hold_char);
+               YY_RESTORE_YY_MORE_OFFSET
+
+               if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW )
+                       {
+                       /* We're scanning a new file or input source.  It's
+                        * possible that this happened because the user
+                        * just pointed plproxy_yyin at a new source and called
+                        * plproxy_yylex().  If so, then we have to assure
+                        * consistency between YY_CURRENT_BUFFER and our
+                        * globals.  Here is the right place to do so, because
+                        * this is the first action (other than possibly a
+                        * back-up) that will match for the new input source.
+                        */
+                       (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+                       YY_CURRENT_BUFFER_LVALUE->yy_input_file = plproxy_yyin;
+                       YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL;
+                       }
+
+               /* Note that here we test for yy_c_buf_p "<=" to the position
+                * of the first EOB in the buffer, since yy_c_buf_p will
+                * already have been incremented past the NUL character
+                * (since all states make transitions on EOB to the
+                * end-of-buffer state).  Contrast this with the test
+                * in input().
+                */
+               if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] )
+                       { /* This was really a NUL. */
+                       yy_state_type yy_next_state;
+
+                       (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text;
+
+                       yy_current_state = yy_get_previous_state(  );
+
+                       /* Okay, we're now positioned to make the NUL
+                        * transition.  We couldn't have
+                        * yy_get_previous_state() go ahead and do it
+                        * for us because it doesn't know how to deal
+                        * with the possibility of jamming (and we don't
+                        * want to build jamming into it because then it
+                        * will run more slowly).
+                        */
+
+                       yy_next_state = yy_try_NUL_trans( yy_current_state );
+
+                       yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+
+                       if ( yy_next_state )
+                               {
+                               /* Consume the NUL. */
+                               yy_cp = ++(yy_c_buf_p);
+                               yy_current_state = yy_next_state;
+                               goto yy_match;
+                               }
+
+                       else
+                               {
+                               yy_cp = (yy_last_accepting_cpos);
+                               yy_current_state = (yy_last_accepting_state);
+                               goto yy_find_action;
+                               }
+                       }
+
+               else switch ( yy_get_next_buffer(  ) )
+                       {
+                       case EOB_ACT_END_OF_FILE:
+                               {
+                               (yy_did_buffer_switch_on_eof) = 0;
+
+                               if ( plproxy_yywrap( ) )
+                                       {
+                                       /* Note: because we've taken care in
+                                        * yy_get_next_buffer() to have set up
+                                        * plproxy_yytext, we can now set up
+                                        * yy_c_buf_p so that if some total
+                                        * hoser (like flex itself) wants to
+                                        * call the scanner after we return the
+                                        * YY_NULL, it'll still work - another
+                                        * YY_NULL will get returned.
+                                        */
+                                       (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ;
+
+                                       yy_act = YY_STATE_EOF(YY_START);
+                                       goto do_action;
+                                       }
+
+                               else
+                                       {
+                                       if ( ! (yy_did_buffer_switch_on_eof) )
+                                               YY_NEW_FILE;
+                                       }
+                               break;
+                               }
+
+                       case EOB_ACT_CONTINUE_SCAN:
+                               (yy_c_buf_p) =
+                                       (yytext_ptr) + yy_amount_of_matched_text;
+
+                               yy_current_state = yy_get_previous_state(  );
+
+                               yy_cp = (yy_c_buf_p);
+                               yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+                               goto yy_match;
+
+                       case EOB_ACT_LAST_MATCH:
+                               (yy_c_buf_p) =
+                               &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)];
+
+                               yy_current_state = yy_get_previous_state(  );
+
+                               yy_cp = (yy_c_buf_p);
+                               yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+                               goto yy_find_action;
+                       }
+               break;
+               }
+
+       default:
+               YY_FATAL_ERROR(
+                       "fatal flex scanner internal error--no action found" );
+       } /* end of action switch */
+               } /* end of scanning one token */
+} /* end of plproxy_yylex */
+
+/* yy_get_next_buffer - try to read in a new buffer
+ *
+ * Returns a code representing an action:
+ *     EOB_ACT_LAST_MATCH -
+ *     EOB_ACT_CONTINUE_SCAN - continue scanning from current position
+ *     EOB_ACT_END_OF_FILE - end of file
+ */
+static int yy_get_next_buffer (void)
+{
+       register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf;
+       register char *source = (yytext_ptr);
+       register int number_to_move, i;
+       int ret_val;
+
+       if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] )
+               YY_FATAL_ERROR(
+               "fatal flex scanner internal error--end of buffer missed" );
+
+       if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 )
+               { /* Don't try to fill the buffer, so this is an EOF. */
+               if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 )
+                       {
+                       /* We matched a single character, the EOB, so
+                        * treat this as a final EOF.
+                        */
+                       return EOB_ACT_END_OF_FILE;
+                       }
+
+               else
+                       {
+                       /* We matched some text prior to the EOB, first
+                        * process it.
+                        */
+                       return EOB_ACT_LAST_MATCH;
+                       }
+               }
+
+       /* Try to read more data. */
+
+       /* First move last chars to start of buffer. */
+       number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr)) - 1;
+
+       for ( i = 0; i < number_to_move; ++i )
+               *(dest++) = *(source++);
+
+       if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING )
+               /* don't do the read, it's not guaranteed to return an EOF,
+                * just force an EOF
+                */
+               YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0;
+
+       else
+               {
+                       int num_to_read =
+                       YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1;
+
+               while ( num_to_read <= 0 )
+                       { /* Not enough room in the buffer - grow it. */
+
+                       /* just a shorter name for the current buffer */
+                       YY_BUFFER_STATE b = YY_CURRENT_BUFFER;
+
+                       int yy_c_buf_p_offset =
+                               (int) ((yy_c_buf_p) - b->yy_ch_buf);
+
+                       if ( b->yy_is_our_buffer )
+                               {
+                               int new_size = b->yy_buf_size * 2;
+
+                               if ( new_size <= 0 )
+                                       b->yy_buf_size += b->yy_buf_size / 8;
+                               else
+                                       b->yy_buf_size *= 2;
+
+                               b->yy_ch_buf = (char *)
+                                       /* Include room in for 2 EOB chars. */
+                                       plproxy_yyrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2  );
+                               }
+                       else
+                               /* Can't grow it, we don't own it. */
+                               b->yy_ch_buf = 0;
+
+                       if ( ! b->yy_ch_buf )
+                               YY_FATAL_ERROR(
+                               "fatal error - scanner input buffer overflow" );
+
+                       (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset];
+
+                       num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size -
+                                               number_to_move - 1;
+
+                       }
+
+               if ( num_to_read > YY_READ_BUF_SIZE )
+                       num_to_read = YY_READ_BUF_SIZE;
+
+               /* Read in more data. */
+               YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]),
+                       (yy_n_chars), num_to_read );
+
+               YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+               }
+
+       if ( (yy_n_chars) == 0 )
+               {
+               if ( number_to_move == YY_MORE_ADJ )
+                       {
+                       ret_val = EOB_ACT_END_OF_FILE;
+                       plproxy_yyrestart(plproxy_yyin  );
+                       }
+
+               else
+                       {
+                       ret_val = EOB_ACT_LAST_MATCH;
+                       YY_CURRENT_BUFFER_LVALUE->yy_buffer_status =
+                               YY_BUFFER_EOF_PENDING;
+                       }
+               }
+
+       else
+               ret_val = EOB_ACT_CONTINUE_SCAN;
+
+       (yy_n_chars) += number_to_move;
+       YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR;
+       YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR;
+
+       (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0];
+
+       return ret_val;
+}
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+    static yy_state_type yy_get_previous_state (void)
+{
+       register yy_state_type yy_current_state;
+       register char *yy_cp;
+    
+       yy_current_state = (yy_start);
+
+       for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp )
+               {
+               register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1);
+               if ( yy_accept[yy_current_state] )
+                       {
+                       (yy_last_accepting_state) = yy_current_state;
+                       (yy_last_accepting_cpos) = yy_cp;
+                       }
+               while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+                       {
+                       yy_current_state = (int) yy_def[yy_current_state];
+                       if ( yy_current_state >= 142 )
+                               yy_c = yy_meta[(unsigned int) yy_c];
+                       }
+               yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+               }
+
+       return yy_current_state;
+}
+
+/* yy_try_NUL_trans - try to make a transition on the NUL character
+ *
+ * synopsis
+ *     next_state = yy_try_NUL_trans( current_state );
+ */
+    static yy_state_type yy_try_NUL_trans  (yy_state_type yy_current_state )
+{
+       register int yy_is_jam;
+       register char *yy_cp = (yy_c_buf_p);
+
+       register YY_CHAR yy_c = 1;
+       if ( yy_accept[yy_current_state] )
+               {
+               (yy_last_accepting_state) = yy_current_state;
+               (yy_last_accepting_cpos) = yy_cp;
+               }
+       while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+               {
+               yy_current_state = (int) yy_def[yy_current_state];
+               if ( yy_current_state >= 142 )
+                       yy_c = yy_meta[(unsigned int) yy_c];
+               }
+       yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+       yy_is_jam = (yy_current_state == 141);
+
+       return yy_is_jam ? 0 : yy_current_state;
+}
+
+#ifndef YY_NO_INPUT
+#ifdef __cplusplus
+    static int yyinput (void)
+#else
+    static int input  (void)
+#endif
+
+{
+       int c;
+    
+       *(yy_c_buf_p) = (yy_hold_char);
+
+       if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR )
+               {
+               /* yy_c_buf_p now points to the character we want to return.
+                * If this occurs *before* the EOB characters, then it's a
+                * valid NUL; if not, then we've hit the end of the buffer.
+                */
+               if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] )
+                       /* This was really a NUL. */
+                       *(yy_c_buf_p) = '\0';
+
+               else
+                       { /* need more input */
+                       int offset = (yy_c_buf_p) - (yytext_ptr);
+                       ++(yy_c_buf_p);
+
+                       switch ( yy_get_next_buffer(  ) )
+                               {
+                               case EOB_ACT_LAST_MATCH:
+                                       /* This happens because yy_g_n_b()
+                                        * sees that we've accumulated a
+                                        * token and flags that we need to
+                                        * try matching the token before
+                                        * proceeding.  But for input(),
+                                        * there's no matching to consider.
+                                        * So convert the EOB_ACT_LAST_MATCH
+                                        * to EOB_ACT_END_OF_FILE.
+                                        */
+
+                                       /* Reset buffer status. */
+                                       plproxy_yyrestart(plproxy_yyin );
+
+                                       /*FALLTHROUGH*/
+
+                               case EOB_ACT_END_OF_FILE:
+                                       {
+                                       if ( plproxy_yywrap( ) )
+                                               return EOF;
+
+                                       if ( ! (yy_did_buffer_switch_on_eof) )
+                                               YY_NEW_FILE;
+#ifdef __cplusplus
+                                       return yyinput();
+#else
+                                       return input();
+#endif
+                                       }
+
+                               case EOB_ACT_CONTINUE_SCAN:
+                                       (yy_c_buf_p) = (yytext_ptr) + offset;
+                                       break;
+                               }
+                       }
+               }
+
+       c = *(unsigned char *) (yy_c_buf_p);    /* cast for 8-bit char's */
+       *(yy_c_buf_p) = '\0';   /* preserve plproxy_yytext */
+       (yy_hold_char) = *++(yy_c_buf_p);
+
+       if ( c == '\n' )
+                  
+    plproxy_yylineno++;
+;
+
+       return c;
+}
+#endif /* ifndef YY_NO_INPUT */
+
+/** Immediately switch to a different input stream.
+ * @param input_file A readable stream.
+ * 
+ * @note This function does not reset the start condition to @c INITIAL .
+ */
+    void plproxy_yyrestart  (FILE * input_file )
+{
+    
+       if ( ! YY_CURRENT_BUFFER ){
+        plproxy_yyensure_buffer_stack ();
+               YY_CURRENT_BUFFER_LVALUE =
+            plproxy_yy_create_buffer(plproxy_yyin,YY_BUF_SIZE );
+       }
+
+       plproxy_yy_init_buffer(YY_CURRENT_BUFFER,input_file );
+       plproxy_yy_load_buffer_state( );
+}
+
+/** Switch to a different input buffer.
+ * @param new_buffer The new input buffer.
+ * 
+ */
+    void plproxy_yy_switch_to_buffer  (YY_BUFFER_STATE  new_buffer )
+{
+    
+       /* TODO. We should be able to replace this entire function body
+        * with
+        *              plproxy_yypop_buffer_state();
+        *              plproxy_yypush_buffer_state(new_buffer);
+     */
+       plproxy_yyensure_buffer_stack ();
+       if ( YY_CURRENT_BUFFER == new_buffer )
+               return;
+
+       if ( YY_CURRENT_BUFFER )
+               {
+               /* Flush out information for old buffer. */
+               *(yy_c_buf_p) = (yy_hold_char);
+               YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p);
+               YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+               }
+
+       YY_CURRENT_BUFFER_LVALUE = new_buffer;
+       plproxy_yy_load_buffer_state( );
+
+       /* We don't actually know whether we did this switch during
+        * EOF (plproxy_yywrap()) processing, but the only time this flag
+        * is looked at is after plproxy_yywrap() is called, so it's safe
+        * to go ahead and always set it.
+        */
+       (yy_did_buffer_switch_on_eof) = 1;
+}
+
+static void plproxy_yy_load_buffer_state  (void)
+{
+       (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+       (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos;
+       plproxy_yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file;
+       (yy_hold_char) = *(yy_c_buf_p);
+}
+
+/** Allocate and initialize an input buffer state.
+ * @param file A readable stream.
+ * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE.
+ * 
+ * @return the allocated buffer state.
+ */
+    YY_BUFFER_STATE plproxy_yy_create_buffer  (FILE * file, int  size )
+{
+       YY_BUFFER_STATE b;
+    
+       b = (YY_BUFFER_STATE) plproxy_yyalloc(sizeof( struct yy_buffer_state )  );
+       if ( ! b )
+               YY_FATAL_ERROR( "out of dynamic memory in plproxy_yy_create_buffer()" );
+
+       b->yy_buf_size = size;
+
+       /* yy_ch_buf has to be 2 characters longer than the size given because
+        * we need to put in 2 end-of-buffer characters.
+        */
+       b->yy_ch_buf = (char *) plproxy_yyalloc(b->yy_buf_size + 2  );
+       if ( ! b->yy_ch_buf )
+               YY_FATAL_ERROR( "out of dynamic memory in plproxy_yy_create_buffer()" );
+
+       b->yy_is_our_buffer = 1;
+
+       plproxy_yy_init_buffer(b,file );
+
+       return b;
+}
+
+/** Destroy the buffer.
+ * @param b a buffer created with plproxy_yy_create_buffer()
+ * 
+ */
+    void plproxy_yy_delete_buffer (YY_BUFFER_STATE  b )
+{
+    
+       if ( ! b )
+               return;
+
+       if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */
+               YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0;
+
+       if ( b->yy_is_our_buffer )
+               plproxy_yyfree((void *) b->yy_ch_buf  );
+
+       plproxy_yyfree((void *) b  );
+}
+
+/* Initializes or reinitializes a buffer.
+ * This function is sometimes called more than once on the same buffer,
+ * such as during a plproxy_yyrestart() or at EOF.
+ */
+    static void plproxy_yy_init_buffer  (YY_BUFFER_STATE  b, FILE * file )
+
+{
+       int oerrno = errno;
+    
+       plproxy_yy_flush_buffer(b );
+
+       b->yy_input_file = file;
+       b->yy_fill_buffer = 1;
+
+    /* If b is the current buffer, then plproxy_yy_init_buffer was _probably_
+     * called from plproxy_yyrestart() or through yy_get_next_buffer.
+     * In that case, we don't want to reset the lineno or column.
+     */
+    if (b != YY_CURRENT_BUFFER){
+        b->yy_bs_lineno = 1;
+        b->yy_bs_column = 0;
+    }
+
+        b->yy_is_interactive = 0;
+    
+       errno = oerrno;
+}
+
+/** Discard all buffered characters. On the next scan, YY_INPUT will be called.
+ * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER.
+ * 
+ */
+    void plproxy_yy_flush_buffer (YY_BUFFER_STATE  b )
+{
+       if ( ! b )
+               return;
+
+       b->yy_n_chars = 0;
+
+       /* We always need two end-of-buffer characters.  The first causes
+        * a transition to the end-of-buffer state.  The second causes
+        * a jam in that state.
+        */
+       b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR;
+       b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR;
+
+       b->yy_buf_pos = &b->yy_ch_buf[0];
+
+       b->yy_at_bol = 1;
+       b->yy_buffer_status = YY_BUFFER_NEW;
+
+       if ( b == YY_CURRENT_BUFFER )
+               plproxy_yy_load_buffer_state( );
+}
+
+/** Pushes the new state onto the stack. The new state becomes
+ *  the current state. This function will allocate the stack
+ *  if necessary.
+ *  @param new_buffer The new state.
+ *  
+ */
+void plproxy_yypush_buffer_state (YY_BUFFER_STATE new_buffer )
+{
+       if (new_buffer == NULL)
+               return;
+
+       plproxy_yyensure_buffer_stack();
+
+       /* This block is copied from plproxy_yy_switch_to_buffer. */
+       if ( YY_CURRENT_BUFFER )
+               {
+               /* Flush out information for old buffer. */
+               *(yy_c_buf_p) = (yy_hold_char);
+               YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p);
+               YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+               }
+
+       /* Only push if top exists. Otherwise, replace top. */
+       if (YY_CURRENT_BUFFER)
+               (yy_buffer_stack_top)++;
+       YY_CURRENT_BUFFER_LVALUE = new_buffer;
+
+       /* copied from plproxy_yy_switch_to_buffer. */
+       plproxy_yy_load_buffer_state( );
+       (yy_did_buffer_switch_on_eof) = 1;
+}
+
+/** Removes and deletes the top of the stack, if present.
+ *  The next element becomes the new top.
+ *  
+ */
+void plproxy_yypop_buffer_state (void)
+{
+       if (!YY_CURRENT_BUFFER)
+               return;
+
+       plproxy_yy_delete_buffer(YY_CURRENT_BUFFER );
+       YY_CURRENT_BUFFER_LVALUE = NULL;
+       if ((yy_buffer_stack_top) > 0)
+               --(yy_buffer_stack_top);
+
+       if (YY_CURRENT_BUFFER) {
+               plproxy_yy_load_buffer_state( );
+               (yy_did_buffer_switch_on_eof) = 1;
+       }
+}
+
+/* Allocates the stack if it does not exist.
+ *  Guarantees space for at least one push.
+ */
+static void plproxy_yyensure_buffer_stack (void)
+{
+       int num_to_alloc;
+    
+       if (!(yy_buffer_stack)) {
+
+               /* First allocation is just for 2 elements, since we don't know if this
+                * scanner will even need a stack. We use 2 instead of 1 to avoid an
+                * immediate realloc on the next call.
+         */
+               num_to_alloc = 1;
+               (yy_buffer_stack) = (struct yy_buffer_state**)plproxy_yyalloc
+                                                               (num_to_alloc * sizeof(struct yy_buffer_state*)
+                                                               );
+               
+               memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*));
+                               
+               (yy_buffer_stack_max) = num_to_alloc;
+               (yy_buffer_stack_top) = 0;
+               return;
+       }
+
+       if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){
+
+               /* Increase the buffer to prepare for a possible push. */
+               int grow_size = 8 /* arbitrary grow size */;
+
+               num_to_alloc = (yy_buffer_stack_max) + grow_size;
+               (yy_buffer_stack) = (struct yy_buffer_state**)plproxy_yyrealloc
+                                                               ((yy_buffer_stack),
+                                                               num_to_alloc * sizeof(struct yy_buffer_state*)
+                                                               );
+
+               /* zero only the new slots.*/
+               memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*));
+               (yy_buffer_stack_max) = num_to_alloc;
+       }
+}
+
+/** Setup the input buffer state to scan directly from a user-specified character buffer.
+ * @param base the character buffer
+ * @param size the size in bytes of the character buffer
+ * 
+ * @return the newly allocated buffer state object. 
+ */
+YY_BUFFER_STATE plproxy_yy_scan_buffer  (char * base, yy_size_t  size )
+{
+       YY_BUFFER_STATE b;
+    
+       if ( size < 2 ||
+            base[size-2] != YY_END_OF_BUFFER_CHAR ||
+            base[size-1] != YY_END_OF_BUFFER_CHAR )
+               /* They forgot to leave room for the EOB's. */
+               return 0;
+
+       b = (YY_BUFFER_STATE) plproxy_yyalloc(sizeof( struct yy_buffer_state )  );
+       if ( ! b )
+               YY_FATAL_ERROR( "out of dynamic memory in plproxy_yy_scan_buffer()" );
+
+       b->yy_buf_size = size - 2;      /* "- 2" to take care of EOB's */
+       b->yy_buf_pos = b->yy_ch_buf = base;
+       b->yy_is_our_buffer = 0;
+       b->yy_input_file = 0;
+       b->yy_n_chars = b->yy_buf_size;
+       b->yy_is_interactive = 0;
+       b->yy_at_bol = 1;
+       b->yy_fill_buffer = 0;
+       b->yy_buffer_status = YY_BUFFER_NEW;
+
+       plproxy_yy_switch_to_buffer(b  );
+
+       return b;
+}
+
+/** Setup the input buffer state to scan a string. The next call to plproxy_yylex() will
+ * scan from a @e copy of @a str.
+ * @param str a NUL-terminated string to scan
+ * 
+ * @return the newly allocated buffer state object.
+ * @note If you want to scan bytes that may contain NUL values, then use
+ *       plproxy_yy_scan_bytes() instead.
+ */
+YY_BUFFER_STATE plproxy_yy_scan_string (yyconst char * yystr )
+{
+    
+       return plproxy_yy_scan_bytes(yystr,strlen(yystr) );
+}
+
+/** Setup the input buffer state to scan the given bytes. The next call to plproxy_yylex() will
+ * scan from a @e copy of @a bytes.
+ * @param bytes the byte buffer to scan
+ * @param len the number of bytes in the buffer pointed to by @a bytes.
+ * 
+ * @return the newly allocated buffer state object.
+ */
+YY_BUFFER_STATE plproxy_yy_scan_bytes  (yyconst char * yybytes, int  _yybytes_len )
+{
+       YY_BUFFER_STATE b;
+       char *buf;
+       yy_size_t n;
+       int i;
+    
+       /* Get memory for full buffer, including space for trailing EOB's. */
+       n = _yybytes_len + 2;
+       buf = (char *) plproxy_yyalloc(n  );
+       if ( ! buf )
+               YY_FATAL_ERROR( "out of dynamic memory in plproxy_yy_scan_bytes()" );
+
+       for ( i = 0; i < _yybytes_len; ++i )
+               buf[i] = yybytes[i];
+
+       buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR;
+
+       b = plproxy_yy_scan_buffer(buf,n );
+       if ( ! b )
+               YY_FATAL_ERROR( "bad buffer in plproxy_yy_scan_bytes()" );
+
+       /* It's okay to grow etc. this buffer, and we should throw it
+        * away when we're done.
+        */
+       b->yy_is_our_buffer = 1;
+
+       return b;
+}
+
+#ifndef YY_EXIT_FAILURE
+#define YY_EXIT_FAILURE 2
+#endif
+
+static void yy_fatal_error (yyconst char* msg )
+{
+       (void) fprintf( stderr, "%s\n", msg );
+       exit( YY_EXIT_FAILURE );
+}
+
+/* Redefine yyless() so it works in section 3 code. */
+
+#undef yyless
+#define yyless(n) \
+       do \
+               { \
+               /* Undo effects of setting up plproxy_yytext. */ \
+        int yyless_macro_arg = (n); \
+        YY_LESS_LINENO(yyless_macro_arg);\
+               plproxy_yytext[plproxy_yyleng] = (yy_hold_char); \
+               (yy_c_buf_p) = plproxy_yytext + yyless_macro_arg; \
+               (yy_hold_char) = *(yy_c_buf_p); \
+               *(yy_c_buf_p) = '\0'; \
+               plproxy_yyleng = yyless_macro_arg; \
+               } \
+       while ( 0 )
+
+/* Accessor  methods (get/set functions) to struct members. */
+
+/** Get the current line number.
+ * 
+ */
+int plproxy_yyget_lineno  (void)
+{
+        
+    return plproxy_yylineno;
+}
+
+/** Get the input stream.
+ * 
+ */
+FILE *plproxy_yyget_in  (void)
+{
+        return plproxy_yyin;
+}
+
+/** Get the output stream.
+ * 
+ */
+FILE *plproxy_yyget_out  (void)
+{
+        return plproxy_yyout;
+}
+
+/** Get the length of the current token.
+ * 
+ */
+int plproxy_yyget_leng  (void)
+{
+        return plproxy_yyleng;
+}
+
+/** Get the current token.
+ * 
+ */
+
+char *plproxy_yyget_text  (void)
+{
+        return plproxy_yytext;
+}
+
+/** Set the current line number.
+ * @param line_number
+ * 
+ */
+void plproxy_yyset_lineno (int  line_number )
+{
+    
+    plproxy_yylineno = line_number;
+}
+
+/** Set the input stream. This does not discard the current
+ * input buffer.
+ * @param in_str A readable stream.
+ * 
+ * @see plproxy_yy_switch_to_buffer
+ */
+void plproxy_yyset_in (FILE *  in_str )
+{
+        plproxy_yyin = in_str ;
+}
+
+void plproxy_yyset_out (FILE *  out_str )
+{
+        plproxy_yyout = out_str ;
+}
+
+int plproxy_yyget_debug  (void)
+{
+        return plproxy_yy_flex_debug;
+}
+
+void plproxy_yyset_debug (int  bdebug )
+{
+        plproxy_yy_flex_debug = bdebug ;
+}
+
+static int yy_init_globals (void)
+{
+        /* Initialization is the same as for the non-reentrant scanner.
+     * This function is called from plproxy_yylex_destroy(), so don't allocate here.
+     */
+
+    /* We do not touch plproxy_yylineno unless the option is enabled. */
+    plproxy_yylineno =  1;
+    
+    (yy_buffer_stack) = 0;
+    (yy_buffer_stack_top) = 0;
+    (yy_buffer_stack_max) = 0;
+    (yy_c_buf_p) = (char *) 0;
+    (yy_init) = 0;
+    (yy_start) = 0;
+
+/* Defined in main.c */
+#ifdef YY_STDINIT
+    plproxy_yyin = stdin;
+    plproxy_yyout = stdout;
+#else
+    plproxy_yyin = (FILE *) 0;
+    plproxy_yyout = (FILE *) 0;
+#endif
+
+    /* For future reference: Set errno on error, since we are called by
+     * plproxy_yylex_init()
+     */
+    return 0;
+}
+
+/* plproxy_yylex_destroy is for both reentrant and non-reentrant scanners. */
+int plproxy_yylex_destroy  (void)
+{
+    
+    /* Pop the buffer stack, destroying each element. */
+       while(YY_CURRENT_BUFFER){
+               plproxy_yy_delete_buffer(YY_CURRENT_BUFFER  );
+               YY_CURRENT_BUFFER_LVALUE = NULL;
+               plproxy_yypop_buffer_state();
+       }
+
+       /* Destroy the stack itself. */
+       plproxy_yyfree((yy_buffer_stack) );
+       (yy_buffer_stack) = NULL;
+
+    /* Reset the globals. This is important in a non-reentrant scanner so the next time
+     * plproxy_yylex() is called, initialization will occur. */
+    yy_init_globals( );
+
+    return 0;
+}
+
+/*
+ * Internal utility routines.
+ */
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char* s1, yyconst char * s2, int n )
+{
+       register int i;
+       for ( i = 0; i < n; ++i )
+               s1[i] = s2[i];
+}
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (yyconst char * s )
+{
+       register int n;
+       for ( n = 0; s[n]; ++n )
+               ;
+
+       return n;
+}
+#endif
+
+#define YYTABLES_NAME "yytables"
+
+#line 228 "scanner.l"
+
+
+
+static char *dlr_token = NULL;
+
+/* remember dollar quote name */
+static void dlr_start(const char *txt)
+{
+       dlr_token = pstrdup(txt);
+       if (0) yy_fatal_error("silence 'unused' warning");
+}
+
+/* check if matches stored name */
+static bool dlr_stop(const char *txt)
+{
+       bool res =  strcmp(txt, dlr_token) == 0;
+       if (res) {
+               pfree(dlr_token);
+               dlr_token = NULL;
+       }
+       return res;
+}
+
+static const char *unquote(const char *qstr, bool std)
+{
+       const char *p;
+       StringInfoData buf;
+
+       initStringInfo(&buf);
+       for (p = qstr + 1; *p; p++) {
+               if (*p == '\'') {
+                       if (*++p == 0)
+                               break;
+                       appendStringInfoChar(&buf, *p);
+               } else
+                       appendStringInfoChar(&buf, *p);
+       }
+       /* leak buf.data */
+       return buf.data;
+}
+
+
diff --git a/src/scanner.h b/src/scanner.h
new file mode 100644 (file)
index 0000000..f23271b
--- /dev/null
@@ -0,0 +1,298 @@
+#ifndef plproxy_yyHEADER_H
+#define plproxy_yyHEADER_H 1
+#define plproxy_yyIN_HEADER 1
+
+#line 6 "scanner.h"
+
+#line 8 "scanner.h"
+
+#define  YY_INT_ALIGNED short int
+
+/* A lexical scanner generated by flex */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 5
+#define YY_FLEX_SUBMINOR_VERSION 33
+#if YY_FLEX_SUBMINOR_VERSION > 0
+#define FLEX_BETA
+#endif
+
+/* First, we deal with  platform-specific or compiler-specific issues. */
+
+/* begin standard C headers. */
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+/* end standard C headers. */
+
+/* flex integer type definitions */
+
+#ifndef FLEXINT_H
+#define FLEXINT_H
+
+/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */
+
+#if __STDC_VERSION__ >= 199901L
+
+/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h,
+ * if you want the limit (max/min) macros for int types. 
+ */
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS 1
+#endif
+
+#include <inttypes.h>
+typedef int8_t flex_int8_t;
+typedef uint8_t flex_uint8_t;
+typedef int16_t flex_int16_t;
+typedef uint16_t flex_uint16_t;
+typedef int32_t flex_int32_t;
+typedef uint32_t flex_uint32_t;
+#else
+typedef signed char flex_int8_t;
+typedef short int flex_int16_t;
+typedef int flex_int32_t;
+typedef unsigned char flex_uint8_t; 
+typedef unsigned short int flex_uint16_t;
+typedef unsigned int flex_uint32_t;
+#endif /* ! C99 */
+
+/* Limits of integral types. */
+#ifndef INT8_MIN
+#define INT8_MIN               (-128)
+#endif
+#ifndef INT16_MIN
+#define INT16_MIN              (-32767-1)
+#endif
+#ifndef INT32_MIN
+#define INT32_MIN              (-2147483647-1)
+#endif
+#ifndef INT8_MAX
+#define INT8_MAX               (127)
+#endif
+#ifndef INT16_MAX
+#define INT16_MAX              (32767)
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX              (2147483647)
+#endif
+#ifndef UINT8_MAX
+#define UINT8_MAX              (255U)
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX             (65535U)
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX             (4294967295U)
+#endif
+
+#endif /* ! FLEXINT_H */
+
+#ifdef __cplusplus
+
+/* The "const" storage-class-modifier is valid. */
+#define YY_USE_CONST
+
+#else  /* ! __cplusplus */
+
+#if __STDC__
+
+#define YY_USE_CONST
+
+#endif /* __STDC__ */
+#endif /* ! __cplusplus */
+
+#ifdef YY_USE_CONST
+#define yyconst const
+#else
+#define yyconst
+#endif
+
+#ifndef YY_TYPEDEF_YY_BUFFER_STATE
+#define YY_TYPEDEF_YY_BUFFER_STATE
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+#endif
+
+extern int plproxy_yyleng;
+
+extern FILE *plproxy_yyin, *plproxy_yyout;
+
+/* The following is because we cannot portably get our hands on size_t
+ * (without autoconf's help, which isn't available because we want
+ * flex-generated scanners to compile on their own).
+ */
+
+#ifndef YY_TYPEDEF_YY_SIZE_T
+#define YY_TYPEDEF_YY_SIZE_T
+typedef unsigned int yy_size_t;
+#endif
+
+#ifndef YY_STRUCT_YY_BUFFER_STATE
+#define YY_STRUCT_YY_BUFFER_STATE
+struct yy_buffer_state
+       {
+       FILE *yy_input_file;
+
+       char *yy_ch_buf;                /* input buffer */
+       char *yy_buf_pos;               /* current position in input buffer */
+
+       /* Size of input buffer in bytes, not including room for EOB
+        * characters.
+        */
+       yy_size_t yy_buf_size;
+
+       /* Number of characters read into yy_ch_buf, not including EOB
+        * characters.
+        */
+       int yy_n_chars;
+
+       /* Whether we "own" the buffer - i.e., we know we created it,
+        * and can realloc() it to grow it, and should free() it to
+        * delete it.
+        */
+       int yy_is_our_buffer;
+
+       /* Whether this is an "interactive" input source; if so, and
+        * if we're using stdio for input, then we want to use getc()
+        * instead of fread(), to make sure we stop fetching input after
+        * each newline.
+        */
+       int yy_is_interactive;
+
+       /* Whether we're considered to be at the beginning of a line.
+        * If so, '^' rules will be active on the next match, otherwise
+        * not.
+        */
+       int yy_at_bol;
+
+    int yy_bs_lineno; /**< The line count. */
+    int yy_bs_column; /**< The column count. */
+    
+       /* Whether to try to fill the input buffer when we reach the
+        * end of it.
+        */
+       int yy_fill_buffer;
+
+       int yy_buffer_status;
+
+       };
+#endif /* !YY_STRUCT_YY_BUFFER_STATE */
+
+void plproxy_yyrestart (FILE *input_file  );
+void plproxy_yy_switch_to_buffer (YY_BUFFER_STATE new_buffer  );
+YY_BUFFER_STATE plproxy_yy_create_buffer (FILE *file,int size  );
+void plproxy_yy_delete_buffer (YY_BUFFER_STATE b  );
+void plproxy_yy_flush_buffer (YY_BUFFER_STATE b  );
+void plproxy_yypush_buffer_state (YY_BUFFER_STATE new_buffer  );
+void plproxy_yypop_buffer_state (void );
+
+YY_BUFFER_STATE plproxy_yy_scan_buffer (char *base,yy_size_t size  );
+YY_BUFFER_STATE plproxy_yy_scan_string (yyconst char *yy_str  );
+YY_BUFFER_STATE plproxy_yy_scan_bytes (yyconst char *bytes,int len  );
+
+void *plproxy_yyalloc (yy_size_t  );
+void *plproxy_yyrealloc (void *,yy_size_t  );
+void plproxy_yyfree (void *  );
+
+/* Begin user sect3 */
+
+#define plproxy_yywrap(n) 1
+#define YY_SKIP_YYWRAP
+
+extern int plproxy_yylineno;
+
+extern char *plproxy_yytext;
+#define yytext_ptr plproxy_yytext
+
+#ifdef YY_HEADER_EXPORT_START_CONDITIONS
+#define INITIAL 0
+#define sql 1
+#define qident 2
+#define stdq 3
+#define extq 4
+#define longcom 5
+#define dolq 6
+#define plcom 7
+
+#endif
+
+#ifndef YY_NO_UNISTD_H
+/* Special case for "unistd.h", since it is non-ANSI. We include it way
+ * down here because we want the user's section 1 to have been scanned first.
+ * The user has a chance to override it with an option.
+ */
+#include <unistd.h>
+#endif
+
+#ifndef YY_EXTRA_TYPE
+#define YY_EXTRA_TYPE void *
+#endif
+
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef YY_SKIP_YYWRAP
+#ifdef __cplusplus
+extern "C" int plproxy_yywrap (void );
+#else
+extern int plproxy_yywrap (void );
+#endif
+#endif
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char *,yyconst char *,int );
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (yyconst char * );
+#endif
+
+#ifndef YY_NO_INPUT
+
+#endif
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#define YY_READ_BUF_SIZE 8192
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL_IS_OURS 1
+
+extern int plproxy_yylex (void);
+
+#define YY_DECL int plproxy_yylex (void)
+#endif /* !YY_DECL */
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+#undef YY_NEW_FILE
+#undef YY_FLUSH_BUFFER
+#undef yy_set_bol
+#undef yy_new_buffer
+#undef yy_set_interactive
+#undef YY_DO_BEFORE_ACTION
+
+#ifdef YY_DECL_IS_OURS
+#undef YY_DECL_IS_OURS
+#undef YY_DECL
+#endif
+
+#line 228 "scanner.l"
+
+
+#line 297 "scanner.h"
+#undef plproxy_yyIN_HEADER
+#endif /* plproxy_yyHEADER_H */
diff --git a/src/scanner.l b/src/scanner.l
new file mode 100644 (file)
index 0000000..9ef395e
--- /dev/null
@@ -0,0 +1,267 @@
+%{
+
+/*
+ * PL/Proxy - easy access to partitioned database.
+ * 
+ * Copyright (c) 2006 Sven Suursoho, Skype Technologies OÜ
+ * Copyright (c) 2007 Marko Kreen, Skype Technologies OÜ
+ * 
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "plproxy.h"
+#include "parser.tab.h"
+
+/* point to parser value */
+#define yylval plproxy_yylval
+
+/* 
+ * Allocate in CurrentMemoryContext.  That means plproxy_yylex_destroy()
+ * must be called before SPI_finish().
+ */
+void *yyalloc(yy_size_t len) { return palloc(len); }
+void *yyrealloc(void *ptr, yy_size_t len) { return repalloc(ptr, len); }
+void yyfree(void *ptr) { pfree(ptr); }
+
+/* own error handling */
+#define YY_FATAL_ERROR(msg) plproxy_yyerror(msg)
+
+/* disable stdio related code */
+#define YY_INPUT(buf, res, maxlen) { res = 0; }
+
+/* shut down crappy flex warnings */
+int yyget_lineno(void);
+int yyget_leng(void);
+FILE *yyget_in(void);
+FILE *yyget_out(void);
+char *yyget_text(void);
+void plproxy_yyset_lineno(int);
+void plproxy_yyset_in(FILE *);
+void plproxy_yyset_out(FILE *);
+int plproxy_yyget_debug(void);
+void plproxy_yyset_debug(int);
+int plproxy_yylex_destroy(void);
+
+/* shortcut for returning CONST */
+#define RETPART do { yylval.str = yytext; return SQLPART; } while (0)
+
+/* dollar quoting helpers */
+static void dlr_start(const char *txt);
+static bool dlr_stop(const char *txt);
+
+static const char *unquote(const char *qstr, bool std);
+
+%}
+
+%option 8bit case-insensitive
+%option warn nodefault yylineno
+%option nounput noyywrap never-interactive batch
+%option prefix="plproxy_yy" header="scanner.h"
+%option noyyalloc noyyrealloc noyyfree
+
+/* states */
+%x sql
+%x qident
+%x stdq
+%x extq
+%x longcom
+%x dolq
+%x plcom
+
+/* whitespace */
+SPACE          [ \t\n\r]
+
+/* sql ident.  include dotted parts also */
+WORD           [a-z][a-z0-9_]*
+IDENT          {WORD}({SPACE}*[.]{SPACE}*{WORD})*
+
+/* argument ref by val: $1 */
+NUMIDENT       [$][0-9]+
+
+/* regular int value for hash spec */
+PLNUMBER       [0-9]+
+
+/* SQL numeric value */
+SQLNUM         [0-9][.0-9]*
+
+/* 
+ * Symbols that may exist in sql.   They must be matched one-by-one,
+ * to avoid conflics with combos.
+ *
+ * Excludes: [$'";`]
+ */
+SQLSYM         [-!#%&()*+,/:<=>?@\[\]^{|}~]
+
+/* Dollar quote ID */
+DOLQ_START      [a-z\200-\377_]
+DOLQ_CONT       [a-z\200-\377_0-9]
+DOLQ           ({DOLQ_START}{DOLQ_CONT}*)
+
+%%
+
+       /* PL/Proxy language keywords */
+
+cluster                        { return CLUSTER; }
+connect                        { return CONNECT; }
+run                    { return RUN; }
+on                     { return ON; }
+all                    { return ALL; }
+any                    { return ANY; }
+select                 { BEGIN(sql); yylval.str = yytext; return SELECT; }
+
+       /* function call */
+
+{IDENT}{SPACE}*[(]     { BEGIN(sql); yylval.str = yytext; return FNCALL; }
+
+       /* PL/Proxy language comments/whitespace */
+
+{SPACE}+               { }
+[-][-][^\n]*           { }
+[/][*]                 { BEGIN(plcom); }
+<plcom>[^*/]+          { }
+<plcom>[*]+[^*/]+      { }
+<plcom>[*]+[/]         { BEGIN(INITIAL); }
+<plcom>.               { }
+
+       /* PL/Proxy non-keyword elements */
+
+{IDENT}                        { yylval.str = yytext; return IDENT; }
+{NUMIDENT}             { yylval.str = yytext; return IDENT; }
+{PLNUMBER}             { yylval.str = yytext; return NUMBER; }
+[']([^']+|[']['])*[']  { yylval.str = unquote(yytext, true); return STRING; }
+
+       /* unparsed symbol, let parser decide */
+
+.                      { return *(yytext); }
+
+       /*
+        * Following is parser for SQL statements.
+        */
+
+       /* SQL line comment */
+
+<sql>[-][-][^\n]*      { /* \n will be parsed as whitespace */ }
+
+       /* C comment, parse it as whitespace */
+
+<sql>[/][*]            { BEGIN(longcom); }
+<longcom>[^*/]+                { }
+<longcom>[*]+[^*/]+    { }
+<longcom>[*]+[/]       { BEGIN(sql); yylval.str = " "; return SQLPART; }
+<longcom>.             { }
+
+       /* Dollar quoted string */
+
+<sql>[$]{DOLQ}?[$]     { BEGIN(dolq); dlr_start(yytext); RETPART; }
+<dolq>[^$]+            { RETPART; }
+<dolq>[$]{DOLQ}?[$]    { if (dlr_stop(yytext)) { BEGIN(sql); RETPART; }
+                         /* if wrong one, report only 1 char */
+                         else { yyless(1); RETPART; } }
+<dolq>[$][^$]*         { RETPART; }
+
+       /* quoted indentifier */
+
+<sql>["]               { BEGIN(qident); RETPART; }
+<qident>[^"]+          { RETPART; }
+<qident>[\\].          { RETPART; }
+<qident>["]            { BEGIN(sql); RETPART; }
+
+       /* quoted string start */
+
+<sql>E[']              { BEGIN(extq); RETPART; }
+<sql>[']               { if (standard_conforming_strings)
+                           BEGIN(stdq); else BEGIN(extq);
+                         RETPART; }
+
+       /* SQL standard quoted string body */
+
+<stdq>[^']+            { RETPART; }
+<stdq>['][']           { RETPART; }
+<stdq>[']              { BEGIN(sql); RETPART; }
+
+       /* extended quoted string body */
+
+<extq>[^'\\]+          { RETPART; }
+<extq>['][']           { RETPART; }
+<extq>[\\].            { RETPART; }
+<extq>[']              { BEGIN(sql); RETPART; }
+<extq>.                        { RETPART; }
+
+       /* SQL identifier */
+
+<sql>{IDENT}           { yylval.str = yytext; return SQLIDENT; }
+
+       /* $x argument reference */
+
+<sql>{NUMIDENT}                { yylval.str = yytext; return SQLIDENT; }
+
+       /* SQL number */
+
+<sql>{SQLNUM}          { RETPART; }
+
+       /* SQL symbol, parse them one-by-one */
+
+<sql>{SQLSYM}          { RETPART; }
+
+       /* compress whitespace to singe ' ' */
+
+<sql>{SPACE}+          { yylval.str = " "; return SQLPART; }
+
+       /* SQL statement end */
+
+<sql>[;]               { BEGIN(INITIAL); return *(yytext); }
+
+       /* unparsed symbol, let the parser error out */
+
+<sql>.                 { return *(yytext); }
+
+%%
+
+static char *dlr_token = NULL;
+
+/* remember dollar quote name */
+static void dlr_start(const char *txt)
+{
+       dlr_token = pstrdup(txt);
+       if (0) yy_fatal_error("silence 'unused' warning");
+}
+
+/* check if matches stored name */
+static bool dlr_stop(const char *txt)
+{
+       bool res =  strcmp(txt, dlr_token) == 0;
+       if (res) {
+               pfree(dlr_token);
+               dlr_token = NULL;
+       }
+       return res;
+}
+
+static const char *unquote(const char *qstr, bool std)
+{
+       const char *p;
+       StringInfoData buf;
+
+       initStringInfo(&buf);
+       for (p = qstr + 1; *p; p++) {
+               if (*p == '\'') {
+                       if (*++p == 0)
+                               break;
+                       appendStringInfoChar(&buf, *p);
+               } else
+                       appendStringInfoChar(&buf, *p);
+       }
+       /* leak buf.data */
+       return buf.data;
+}
+
diff --git a/src/type.c b/src/type.c
new file mode 100644 (file)
index 0000000..68678bd
--- /dev/null
@@ -0,0 +1,304 @@
+/*
+ * PL/Proxy - easy access to partitioned database.
+ * 
+ * Copyright (c) 2006 Sven Suursoho, Skype Technologies OÜ
+ * Copyright (c) 2007 Marko Kreen, Skype Technologies OÜ
+ * 
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Caches I/O info about scalar values.
+ */
+
+#include "plproxy.h"
+
+/*
+ * Checks if we can safely use binary.
+ */
+static bool usable_binary(Oid oid)
+{
+       switch (oid)
+       {
+               case BOOLOID:
+               case INT2OID:
+               case INT4OID:
+               case INT8OID:
+               case TEXTOID:
+               case BPCHAROID:
+               case VARCHAROID:
+               case FLOAT4OID:
+               case FLOAT8OID:
+               case NUMERICOID:
+               case DATEOID:
+               case TIMEOID:
+               case TIMESTAMPOID:
+               case TIMESTAMPTZOID:
+               case BYTEAOID:
+                       return true;
+
+               /* interval binary fmt changed in 8.1 */
+               case INTERVALOID:
+               default:
+                       return false;
+       }
+}
+
+/*
+ * Collects info about fields of a composite type.
+ *
+ * Based on TupleDescGetAttInMetadata.
+ */
+ProxyComposite *
+plproxy_composite_info(ProxyFunction *func, TupleDesc tupdesc)
+{
+       int                     i,
+                               natts = tupdesc->natts;
+       ProxyComposite *ret;
+       MemoryContext old_ctx;
+       Form_pg_attribute a;
+       ProxyType  *type;
+       const char *name;
+
+       old_ctx = MemoryContextSwitchTo(func->ctx);
+
+       ret = palloc(sizeof(*ret));
+       ret->type_list = palloc(sizeof(ProxyType *) * natts);
+       ret->name_list = palloc0(sizeof(char *) * natts);
+       ret->tupdesc = BlessTupleDesc(tupdesc);
+       ret->use_binary = 1;
+
+       MemoryContextSwitchTo(old_ctx);
+
+       for (i = 0; i < natts; i++)
+       {
+               a = tupdesc->attrs[i];
+               if (a->attisdropped)
+                       plproxy_error(func, "dropped attrs not supported");
+
+               name = NameStr(a->attname);
+               ret->name_list[i] = plproxy_func_strdup(func, name);
+
+               type = plproxy_find_type_info(func, a->atttypid, 0);
+               ret->type_list[i] = type;
+
+               if (!type->has_recv)
+                       ret->use_binary = 0;
+       }
+
+       return ret;
+}
+
+/*
+ * Build result tuplw from binary or CString values.
+ *
+ * Based on BuildTupleFromCStrings.
+ */
+HeapTuple
+plproxy_recv_composite(ProxyComposite *meta, char **values, int *lengths, int *fmts)
+{
+       TupleDesc       tupdesc = meta->tupdesc;
+       int                     natts = tupdesc->natts;
+       Datum      *dvalues;
+       char       *nulls;
+       int                     i;
+       HeapTuple       tuple;
+
+       dvalues = (Datum *) palloc(natts * sizeof(Datum));
+       nulls = (char *) palloc(natts * sizeof(char));
+
+       /* Call the recv function for each attribute */
+       for (i = 0; i < natts; i++)
+       {
+               if (tupdesc->attrs[i]->attisdropped)
+                       elog(ERROR, "dropped attrs not supported");
+
+               dvalues[i] = plproxy_recv_type(meta->type_list[i],
+                                                                          values[i], lengths[i], fmts[i]);
+               nulls[i] = (values[i] != NULL) ? ' ' : 'n';
+       }
+
+       /* Form a tuple */
+       tuple = heap_formtuple(tupdesc, dvalues, nulls);
+
+       /*
+        * Release locally palloc'd space.
+        */
+       for (i = 0; i < natts; i++)
+       {
+               if (nulls[i] == 'n')
+                       continue;
+               if (meta->type_list[i]->by_value)
+                       continue;
+               pfree(DatumGetPointer(dvalues[i]));
+       }
+       pfree(dvalues);
+       pfree(nulls);
+
+       return tuple;
+}
+
+/* Find info about scalar type */
+ProxyType *
+plproxy_find_type_info(ProxyFunction *func, Oid oid, bool for_send)
+{
+       ProxyType  *type;
+       HeapTuple       t_type,
+                               t_nsp;
+       Form_pg_type s_type;
+       Form_pg_namespace s_nsp;
+       char            namebuf[NAMEDATALEN * 2 + 3];
+       Oid                     nsoid;
+
+       /* fetch pg_type row */
+       t_type = SearchSysCache(TYPEOID, ObjectIdGetDatum(oid), 0, 0, 0);
+       if (!HeapTupleIsValid(t_type))
+               plproxy_error(func, "cache lookup failed for type %u", oid);
+
+       /* typname, typnamespace, PG_CATALOG_NAMESPACE, PG_PUBLIC_NAMESPACE */
+       s_type = (Form_pg_type) GETSTRUCT(t_type);
+       nsoid = s_type->typnamespace;
+
+       if (nsoid != PG_CATALOG_NAMESPACE)
+       {
+               t_nsp = SearchSysCache(NAMESPACEOID, ObjectIdGetDatum(nsoid), 0, 0, 0);
+               if (!HeapTupleIsValid(t_nsp))
+                       plproxy_error(func, "cache lookup failed for namespace %u", nsoid);
+               s_nsp = (Form_pg_namespace) GETSTRUCT(t_nsp);
+               sprintf(namebuf, "%s.%s", NameStr(s_nsp->nspname), NameStr(s_type->typname));
+               ReleaseSysCache(t_nsp);
+       }
+       else
+       {
+               sprintf(namebuf, "%s", NameStr(s_type->typname));
+       }
+
+       /* sanity check */
+       switch (s_type->typtype)
+       {
+               default:
+               case 'p':
+                       if (oid != VOIDOID)
+                               plproxy_error(func, "unsupported pseudo type: %s (%u)",
+                                                         namebuf, oid);
+               case 'b':
+               case 'c':
+               case 'd':
+                       break;
+       }
+
+       /* allocate & fill structure */
+       type = plproxy_func_alloc(func, sizeof(*type));
+       memset(type, 0, sizeof(*type));
+
+       type->type_oid = oid;
+       type->io_param = getTypeIOParam(t_type);
+       type->for_send = for_send;
+       type->by_value = s_type->typbyval;
+       type->name = plproxy_func_strdup(func, namebuf);
+
+       /* decide what function is needed */
+       if (for_send)
+       {
+               fmgr_info_cxt(s_type->typoutput, &type->io.out.output_func, func->ctx);
+               if (OidIsValid(s_type->typsend) && usable_binary(oid))
+               {
+                       fmgr_info_cxt(s_type->typsend, &type->io.out.send_func, func->ctx);
+                       type->has_send = 1;
+               }
+       }
+       else
+       {
+               fmgr_info_cxt(s_type->typinput, &type->io.in.input_func, func->ctx);
+               if (OidIsValid(s_type->typreceive) && usable_binary(oid))
+               {
+                       fmgr_info_cxt(s_type->typreceive, &type->io.in.recv_func, func->ctx);
+                       type->has_recv = 1;
+               }
+       }
+
+       ReleaseSysCache(t_type);
+
+       return type;
+}
+
+
+/* Convert a Datum to parameter for libpq */
+char *
+plproxy_send_type(ProxyType *type, Datum val, bool allow_bin, int *len, int *fmt)
+{
+       bytea      *bin;
+       char       *res;
+
+       Assert(type->for_send == 1);
+
+       if (allow_bin && type->has_send)
+       {
+               bin = SendFunctionCall(&type->io.out.send_func, val);
+               res = VARDATA(bin);
+               *len = VARSIZE(bin) - VARHDRSZ;
+               *fmt = 1;
+       }
+       else
+       {
+               res = OutputFunctionCall(&type->io.out.output_func, val);
+               *len = 0;
+               *fmt = 0;
+       }
+       return res;
+}
+
+/*
+ * Point StringInfo to fixed buffer.
+ *
+ * Supposedly StringInfo wants 0 at the end.
+ * Luckily libpq already provides it so all is fine.
+ *
+ * Although it should not matter to binary I/O functions.
+ */
+static void
+setFixedStringInfo(StringInfo str, void *data, int len)
+{
+       str->data = data;
+       str->maxlen = len;
+       str->len = len;
+       str->cursor = 0;
+}
+
+/* Convert a libpq result to Datum */
+Datum
+plproxy_recv_type(ProxyType *type, char *val, int len, bool bin)
+{
+       Datum           res;
+       StringInfoData buf;
+
+       Assert(type->for_send == 0);
+
+       if (bin)
+       {
+               if (!type->has_recv)
+                       elog(ERROR, "PL/Proxy: type %u recv not supported", type->type_oid);
+
+               /* avoid unnecessary copy */
+               setFixedStringInfo(&buf, val, len);
+
+               res = ReceiveFunctionCall(&type->io.in.recv_func,
+                                                                 &buf, type->io_param, -1);
+       }
+       else
+       {
+               res = InputFunctionCall(&type->io.in.input_func,
+                                                               val, type->io_param, -1);
+       }
+       return res;
+}