--- /dev/null
+
+Sven Suursoho <sven.suursoho@skype.net> - PLproxy 1, PLproxy 2 core
+Marko Kreen <marko.kreen@skype.net> - final PLproxy 2, current maintainer
+
--- /dev/null
+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.
+
--- /dev/null
+
+# 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
+
--- /dev/null
+
+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
+
--- /dev/null
+
+-- 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;
+
--- /dev/null
+## 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
+
--- /dev/null
+
+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
+
--- /dev/null
+#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.
--- /dev/null
+#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
--- /dev/null
+#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.
--- /dev/null
+
+= 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
+
--- /dev/null
+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)
+
--- /dev/null
+-- 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)
+
--- /dev/null
+\set ECHO none
--- /dev/null
+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)
+
--- /dev/null
+-- 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
--- /dev/null
+-- 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)
+
--- /dev/null
+
+-- handler function
+CREATE FUNCTION plproxy_call_handler ()
+RETURNS language_handler AS 'MODULE_PATHNAME' LANGUAGE C;
+
+-- language
+CREATE LANGUAGE plproxy HANDLER plproxy_call_handler;
+
--- /dev/null
+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);
+
--- /dev/null
+
+-- 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');
+
+
+
+
+
--- /dev/null
+
+\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;
+
+
--- /dev/null
+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');
+
+
--- /dev/null
+
+-- 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);
+
--- /dev/null
+
+-- 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();
+
+
+
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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, ¬imeout);
+ 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);
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/* 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;
+}
+
+
--- /dev/null
+/* 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;
+
--- /dev/null
+%{
+/*
+ * 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;
+}
+
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+#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;
+}
+
+
--- /dev/null
+#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 */
--- /dev/null
+%{
+
+/*
+ * 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;
+}
+
--- /dev/null
+/*
+ * 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;
+}