pax_global_header00006660000000000000000000000064135375364750014534gustar00rootroot0000000000000052 comment=20be56bc77b24fe1e58ed4a3b33f116cd71e9ae0 plproxy-2.9/000077500000000000000000000000001353753647500131235ustar00rootroot00000000000000plproxy-2.9/AUTHORS000066400000000000000000000010631353753647500141730ustar00rootroot00000000000000 Original Authors ---------------- Idea - Hannu Krosing Language Design - Hannu Krosing & Asko Oja Implementation - Sven Suursoho Maintainer ---------- Marko Kreen Contributors ------------ Christoph Berg Christoph Moench-Tegeder David E. Wheeler Hans-Jürgen Schönig Hiroshi Saito Ian Sollars João Matos Jonah Harris Fazal Majid Gabriel Linder Laurenz Albe Lei Yonghua Luca Ferrari Luis Bosque Martin Pihlak Nikolay Samokhvalov Oskari Saarenmaa Peter Eisentraut Petr Jelinek Steve Singer Tarvi Pillessaar Ye Wenbin Zoltán Böszörményi plproxy-2.9/COPYRIGHT000066400000000000000000000015411353753647500144170ustar00rootroot00000000000000PL/Proxy - easy access to partitioned database. Copyright (c) 2006 Sven Suursoho, Skype Technologies OÜ Copyright (c) 2007-2009 Marko Kreen, Skype Technologies OÜ Permission to use, copy, modify, and/or 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. plproxy-2.9/META.json000066400000000000000000000024421353753647500145460ustar00rootroot00000000000000{ "name": "plproxy", "abstract": "Database partitioning implemented as procedural language", "description": "PL/Proxy is database partitioning system implemented as PL language.", "version": "2.9.0", "maintainer": [ "Marko Kreen " ], "license": { "ISC": "http://www.opensource.org/licenses/ISC" }, "provides": { "plproxy": { "abstract": "Database partitioning implemented as procedural language", "file": "sql/plproxy.sql", "docfile": "doc/tutorial.md", "version": "2.9.0" } }, "prereqs": { "runtime": { "requires": { "PostgreSQL": "9.3.0" } } }, "resources": { "homepage": "https://plproxy.github.io/", "bugtracker": { "web": "https://github.com/plproxy/plproxy/issues/" }, "repository": { "url": "https://github.com/plproxy/plproxy", "web": "https://github.com/plproxy/plproxy", "type": "git" } }, "generated_by": "David E. Wheeler", "meta-spec": { "version": "1.0.0", "url": "http://pgxn.org/meta/spec.txt" }, "tags": [ "proxy", "sharding", "rpc", "remote procedue call", "procedural language", "sql med", "PL" ] } plproxy-2.9/Makefile000066400000000000000000000065051353753647500145710ustar00rootroot00000000000000EXTENSION = plproxy # sync with NEWS, META.json, plproxy.control DISTVERSION = 2.9 EXTVERSION = 2.9.0 UPGRADE_VERS = 2.3.0 2.4.0 2.5.0 2.6.0 2.7.0 2.8.0 # set to 1 to disallow functions containing SELECT NO_SELECT = 0 # libpq config PG_CONFIG = pg_config PQINCSERVER = $(shell $(PG_CONFIG) --includedir-server) PQINC = $(shell $(PG_CONFIG) --includedir) PQLIB = $(shell $(PG_CONFIG) --libdir) # module setup MODULE_big = $(EXTENSION) SRCS = src/cluster.c src/execute.c src/function.c src/main.c \ src/query.c src/result.c src/type.c src/poll_compat.c src/aatree.c OBJS = src/scanner.o src/parser.tab.o $(SRCS:.c=.o) EXTRA_CLEAN = src/scanner.[ch] src/parser.tab.[ch] libplproxy.* plproxy.so SHLIB_LINK = -L$(PQLIB) -lpq HDRS = src/plproxy.h src/rowstamp.h src/aatree.h src/poll_compat.h # Server include must come before client include, because there could # be mismatching libpq-dev and postgresql-server-dev installed. PG_CPPFLAGS = -I$(PQINCSERVER) -I$(PQINC) -DNO_SELECT=$(NO_SELECT) ifdef VPATH PG_CPPFLAGS += -I$(VPATH)/src endif DISTNAME = $(EXTENSION)-$(DISTVERSION) # regression testing setup REGRESS = plproxy_init plproxy_test plproxy_select plproxy_many \ plproxy_errors plproxy_clustermap plproxy_dynamic_record \ plproxy_encoding plproxy_split plproxy_target plproxy_alter \ plproxy_cancel REGRESS_OPTS = --dbname=regression --inputdir=test # pg9.1 ignores --dbname override CONTRIB_TESTDB := regression # sql source PLPROXY_SQL = sql/plproxy_lang.sql # Generated SQL files EXTSQL = sql/$(EXTENSION)--$(EXTVERSION).sql \ $(foreach v,$(UPGRADE_VERS),sql/plproxy--$(v)--$(EXTVERSION).sql) \ sql/plproxy--unpackaged--$(EXTVERSION).sql # pg84: SQL/MED available, add foreign data wrapper and regression tests REGRESS += plproxy_sqlmed plproxy_table PLPROXY_SQL += sql/plproxy_fdw.sql # pg91: SQL for extensions DATA_built = $(EXTSQL) DATA = $(EXTMISC) EXTRA_CLEAN += sql/plproxy.sql # pg92: range type REGRESS += plproxy_range # # load PGXS makefile # PGXS = $(shell $(PG_CONFIG) --pgxs) include $(PGXS) ifeq ($(PORTNAME), win32) SHLIB_LINK += -lws2_32 -lpgport endif # PGXS may define them as empty FLEX := $(if $(FLEX),$(FLEX),flex) BISON := $(if $(BISON),$(BISON),bison) # parser rules src/scanner.o: src/parser.tab.h src/parser.tab.h: src/parser.tab.c src/parser.tab.c: src/parser.y @mkdir -p src $(BISON) -b src/parser -d $< src/parser.o: src/scanner.h src/scanner.h: src/scanner.c src/scanner.c: src/scanner.l @mkdir -p src $(FLEX) -o$@ --header-file=$(@:.c=.h) $< sql/plproxy.sql: $(PLPROXY_SQL) @mkdir -p sql cat $^ > $@ # plain plproxy.sql is not installed, but used in tests sql/$(EXTENSION)--$(EXTVERSION).sql: $(PLPROXY_SQL) @mkdir -p sql echo "create extension plproxy;" > sql/plproxy.sql cat $^ > $@ $(foreach v,$(UPGRADE_VERS),sql/plproxy--$(v)--$(EXTVERSION).sql): sql/ext_update_validator.sql @mkdir -p sql cat $< >$@ sql/plproxy--unpackaged--$(EXTVERSION).sql: sql/ext_unpackaged.sql @mkdir -p sql cat $< > $@ # dependencies $(OBJS): $(HDRS) # utility rules tags: $(SRCS) $(HDRS) ctags $(SRCS) $(HDRS) tgz: git archive --prefix=$(DISTNAME)/ HEAD | gzip -9 > $(DISTNAME).tar.gz zip: git archive -o $(DISTNAME).zip --format zip --prefix=$(DISTNAME)/ HEAD test: install $(MAKE) installcheck || { filterdiff --format=unified regression.diffs | less; exit 1; } ack: cp results/*.out test/expected/ plproxy-2.9/NEWS.md000066400000000000000000000300121353753647500142150ustar00rootroot00000000000000 # PL/Proxy Changelog **2019-09-15 - PL/Proxy 2.9 - "Don't Look Into The Eye"** - Fixes: * Dynamic record-type functions can crash backend if called in scalar context. - Changes: * Support for PG11 and PG12. * Drop support for PG9.2 and earlier. * Drop local Debian packaging, it seems unused. * Drop support for keepalive cluster options. These are replaced by keepalive support in libpq. Removing OS-specific code makes PL/Proxy also more portable. **2017-10-08 - PL/Proxy 2.8 - "Entropy Always Wins"** - Fixes: * PG10: Support more than one digit versions in Makefile. (Luis Bosque) * PG10: Use TupleDescAttr() to handle changed type in tupdesc. * Use corrent detection macro. (Christoph Moench-Tegeder) **2016-12-27 - PL/Proxy 2.7 - "Never Trust Sober Santa"** - Fixes * Update to newer `heap_form_tuple()` API. (Peter Eisentraut) * Handle 64-bit `SPI_processed`. (Peter Eisentraut) **2015-08-26 - PL/Proxy 2.6 - "Released via Bottle Mail"** - Features * Language validator. (Peter Eisentraut) * Support Postgres 9.5. (Peter Eisentraut) - Fixes * Query cancel fixes. (Fazal Majid, Tarvi Pillessaar) * Fix `yy_scan_bytes()` prototype. (Peter Eisentraut) * Use a proper sqlstate with non-setof function errors. (Oskari Saarenmaa) - Cleanups * Convert docs to markdown. * Debian packaging cleanup. **2012-11-27 - PL/Proxy 2.5 - "With Extra Leg For Additional Stability"** - Features * Support `RETURNS TABLE` syntax. * Support range types. * Make it build against 9.3dev. (Peter Eisentraut) - Fixes * When sending cancel request to server, wait for answer and discard it. Without waiting the local backend might be killed and cancel request dropped before it reaches remote server - eg. when there is pgbouncer in the middle. * When return record is based on type or table, detect changes and reload the type. * Allow type or table to have dropped fields. * Fix crash when `RETURNS TABLE` syntax is used. **2012-05-07 - PL/Proxy 2.4 - "Light Eater"** - Features * Use `current_user` as default user for remote connections. Allow access with different users to same cluster in same backend. Old default was `session_user`, but that does not seem useful anymore. * Support ENUM types. (Zoltán Böszörményi) * Support building as Postgres extension on 9.1+. (David E. Wheeler) * Support building as [PGXN](http://pgxn.org) extension. (David E. Wheeler) * Support Postgres 9.2. **2011-10-25 - PL/Proxy 2.3 - "Unmanned Crowd Control"** - Features * Global SQL/MED options: `ALTER FOREIGN DATA WRAPPER plproxy OPTIONS ... ;` (Petr Jelinek) * New config options: `keepalive_idle`, `keepalive_interval`, `keepalive_count`. For TCP keepalive tuning. Alternative to libpq keepalive options when older libpq is used or when central tuning is preferrable. - Fixes * Fix memory leak in SPLIT - it was leaking array element type info. - Doc * Use Asciidoc ListingBlock for code - results in nicer HTML. **2011-02-18 - PL/Proxy 2.2 - "Cover With Milk To See Secret Message"** - Features * New TARGET statement to specify different function to call on remote side. * Make possible to compile out support for SELECT statement: $ make all NO_SELECT=1 - Fixes * Fix returning of many-column (>100) result rows. Old code assumed FUNC_MAX_ARGS is max, but that does not apply to result columns. (Hans-Jürgen Schönig) * Survive missing sqlstate field on error messages. Local libpq errors do not set it. * More portable workaround for empty FLEX/BISON. (Peter Eisentraut) * win32: Fix poll compat to work with large amount of fds. Old compat assument bitmap representation for fd_set, but win32 uses array. **2010-04-23 - PL/Proxy 2.1 - "Quality Horrorshow"** - Features * SPLIT: New `SPLIT` statement to convert incoming array arguments into smaller per-partition arrays. (Martin Pihlak) * SQL/MED: Cluster can be defined with SQL/MED facilities, instead of old-style plproxy.* functions. (Martin Pihlak) - Minor fixes/features * Allow to customize location to `pg_config` via `PG_CONFIG` make variable. (David E. Wheeler) * Remote errors and notices are now passed upwards with all details. Previously only error message pas passed and notices were ignored. * Show remote database name in error messages. * Compatible with Postgres 9.0. * Compatible with flex 2.5.35+ - it now properly defines it's own functions, so PL/Proxy does not need to do it. Otherwise compilation will fail if flex definitions are hacked (MacOS). * Rework regests to make them work across 8.2..9.0 and decrease chance of spurious failures. The encoding test still fails if Postgres instance is not created with LANG=C. * deb: per-version packaging: `make debXY` will create `postgresql-plproxy-X.Y` package. **2009-10-28 - PL/Proxy 2.0.9 - "Five-Nines Guarantee For Not Bricking The Server"** - Features * More flexible CONNECT statement. CONNECT func(..); CONNECT argname; CONNECT $argnum; NB: giving untrusted users ability to specify full connect string creates security hole. Eg it can used to read cleartext passwords from .pgpass/pg_service. If such function cannot be avoided, it's access rights need to be restricted. (Ian Sollars) - Fixes * Avoid parsing `SELECT (` as function call. Otherwise following query fails to parse: `SELECT (0*0);` (Peter Eisentraut) * Make scanner accept dot as standalone symbol. Otherwise following query fails to parse: `SELECT (ret_numtuple(1)).num, (ret_numtuple(1)).name;` (Peter Eisentraut) * Argument type name length over 32 could cause buffer overflow. (Ian Sollars) * Fix crash with incoming NULL value in function containing SELECT with different argument order. Due to thinko, NULL check was done with query arg index, instead of function arg index. (João Matos) * geterrcode(): Switch memory context to work around Assert() in CopyErrorData(). **2009-01-16 - PL/Proxy 2.0.8 - "Simple Multi-Tentacle Arhitecture"** - Features * If query is canceled, send cancel request to remote db too. (Ye Wenbin) * Allow direct argument references in RUN ON statement. Now this works: `RUN ON $1;` and `RUN ON arg;` * Add FAQ to docs which answers few common questions. - Fixes * Clear `->tuning` bit on connection close. If `SET client_encoding` fails, the bit can stay set, thus creating always-failing connection slot. Reported and analyzed by Jonah Harris. **2008-09-29 - PL/Proxy 2.0.7 - "The Ninja of Shadow"** - Fixes * Make sure `client_encoding` on remote server encoding is set to local server encoding. Currently plproxy set it to local `client_encoding`, which is wrong as all data is immediately converted to `server_encoding` by Postgres. The problem went mostly undetected because of plproxy use of binary I/O which bypasses encoding conversions. (Hiroshi Saito) So if you pass non-ascii data around and your client, proxy server and target server may have different encodings, you may want to re-check your data. * Disable binary i/o completely. Currently the decision is done too early, before remote connection is established. Currently the fix was to use only "safe" types for binary I/O, but now that text types are also unsafe, it's pointless. Instead type handling should be rewritten to allow lazy decision-making. * Fix crash with unnamed function input arguments. * Fix compilation with 8.2 on Win32 by providing PGDLLIMPORT if unset. (Hiroshi Saito) * Accept >128 chars as part of identifier names. * New regtest to detect encoding problems. * Use `pg_strcasecmp()` instead of `strcasecmp()` to be consistent with Postgres code. * deb: Survive empty FLEX/BISON defs from PGXS. Accept also postgresql-server-dev-8.3 as build dep. **2008-09-05 - PL/Proxy 2.0.6 - "Agile Voodoo"** - Features * Support functions that return plain RECORD without OUT parameters. Such functions need result type specified on each call with AS clause and the types need to be sent to remote database also. (Lei Yonghua) This makes possible to use PL/Proxy for dynamic queries: CREATE FUNCTION run_query(sql text) RETURNS RECORD .. SELECT * FROM run_query('select a,b from ..') AS (a int, b text); * Accept int2/int8 values from hash function, in addition to int4. - Fixes * Replace bitfields with bool to conform better with Postgres coding style. * Don't use alloca() in parser. * Make scanner more robust to allocation errors by doing total reset before parsing. * Require exactly one row from remote query for non-SETOF functions. * Docs: tag all functions with embedded SELECT with SETOF. * Make it compile on win32 (Hiroshi Saito) * Make regtest tolerant to random() implementation differneces between different OSes. **2008-06-06 - PL/Proxy 2.0.5 - "Universal Shredder"** - Fixes: * Fix crash if a function with `CLUSTER 'name';` is parsed after function with `CLUSTER func();`. A palloc()ed pointer was not reset. * `RUN ON` is now optional, defaulting to `RUN ON ANY;` Should make creating simple RPC and load-balancing functions easier easier. * Make compat poll() function behave more like actual poll(). **2008-01-04 - PL/Proxy 2.0.4 - "Vampire-proof"** - Fixes * Fix crash due to bad error reporting when remote db closes socket unexpectedly. * Use `pg_strcasecmp()` to compare encodings. * Log encoding values if it fails to apply. * Replace select(2) with poll(2) to allow large fd values. Old select(2) usage could cause problems when plproxy was installed on database with lot of tables/indexes, where Postgres itself could have had lot of files open. * Disable binary I/O for timestamp(tz) and date/time types, to avoid problems when local and remote Postgres have different setting for integer_datetimes. **2007-12-10 - PL/Proxy 2.0.3 - "Faster Than A Fresh Zombie"** - Features * Explicitly specify result column names and types in query. Lets say there is function somefunc(out id int4, out data text). Previously pl/proxy issued following query: SELECT * FROM somefunc() And later tried to work out which column goes where. Now it issues: SELECT id::int4, data::text FROM somefunc() For functions without named return paramenters, eg. just "RETURNS text": SELECT r::text FROM anotherfunc() r This gives better type safety when using binary I/O, allows signatures differ in deterministic ways and creates safe upgrade path for signatures. Only downside is that existing functions with wildly different signatures stop working, but as they work on pure luck anyway, I'm not worried. * Quote function and result column names properly. * Set `client_encoding` on remote database to be equal to local one. * Tutorial by Steve Singer. - Fixes * Support 8.3 (handle short varlena header) * Support old flex (2.5.4) Previously flex >= 2.5.33 was required. * Fix `make deb`, include actual debian/changelog. * Remove config paramenter `statement_timeout`. It was ignored previously and it cannot be made work in live env when working thru pgbouncer, so its better to drop it completely. The setting can be always set via normal ways. **2007-04-16 - PL/Proxy 2.0.2 - "No news is good news?"** - Cleanups * Include plproxy.sql.in in tgz. * Clean `add_connection()` function by using StringInfo instead open-coded string shuffling. **2007-03-30 - PL/Proxy 2.0.1 - "PL/Proxy 2.0.1"** - Fixes * Support for 8.3 * Seems v2.0 invalidated cache more than intended. Fix. **2007-03-13 - PL/Proxy 2.0 - "Skype Presents"** - Initial [public release](http://www.postgresql.org/message-id/54335.194.126.108.9.1173801315.squirrel@mail.skype.net) of v2.0 rewrite. plproxy-2.9/README.md000066400000000000000000000011251353753647500144010ustar00rootroot00000000000000 # PL/Proxy 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 Location to `pg_config` can be set via `PG_CONFIG` variable: $ make PG_CONFIG=/path/to/pg_config $ make install PG_CONFIG=/path/to/pg_config $ make installcheck PG_CONFIG=/path/to/pg_config Note: Encoding regression test fails if the Postgres instance is not created with C locale. It can be considered expected failure then. plproxy-2.9/config/000077500000000000000000000000001353753647500143705ustar00rootroot00000000000000plproxy-2.9/config/simple.config.sql000066400000000000000000000015471353753647500176550ustar00rootroot00000000000000 -- 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; plproxy-2.9/doc/000077500000000000000000000000001353753647500136705ustar00rootroot00000000000000plproxy-2.9/doc/config.md000066400000000000000000000203121353753647500154550ustar00rootroot00000000000000 # PL/Proxy Cluster Configuration PL/Proxy can be used in either CONNECT mode or CLUSTER mode. In CONNECT mode PL/Proxy acts as a pass through proxy to another database. Each PL/Proxy function contains a libpq connect string for the connection to a database it will proxy the request to. PL/Proxy can also be used in CLUSTER mode where it provides support for partitioning data across multiple databases based on a clustering function. When using PL/Proxy in CONNECT mode no special configuration is required. However, using PL/Proxy in CLUSTER mode requires the cluster configuration to be defined, either by the cluster configuration API or SQL/MED. ## Cluster configuration API The following plproxy schema functions are used to define the clusters: ### plproxy.get_cluster_version(cluster_name) plproxy.get_cluster_version(cluster_name text) returns integer The `get_cluster_version()` function is called on each request, it should return the version number of the current configuration for a particular cluster. If the version number returned by this function is higher than the one plproxy has cached, then the configuration and partition information will be reloaded by calling the `get_cluster_config()` and `get_cluster_partitions()` functions. This is an example function that does not lookup the version number for an external source such as a configuration table. CREATE OR REPLACE FUNCTION plproxy.get_cluster_version(cluster_name text) RETURNS int4 AS $$ BEGIN IF cluster_name = 'a_cluster' THEN RETURN 1; END IF; RAISE EXCEPTION 'Unknown cluster'; END; $$ LANGUAGE plpgsql; ### plproxy.get_cluster_partitions(cluster_name) plproxy.get_cluster_partitions(cluster_name text) returns setof text This is called when a new partition configuration needs to be loaded. It should return connect strings to the partitions in the cluster. The connstrings should be returned in the correct order. The total number of connstrings returned must be a power of 2. If two or more connstrings are equal then they will use the same connection. If the string `user=` does not appear in a connect string then `user=CURRENT_USER` will be appended to the connection string by PL/Proxy. This will cause PL/Proxy to connect to the partition database using the same username as was used to connect to the proxy database. This also avoids the accidental `user=postgres` connections. There are several approaches how to handle passwords: * Store passwords in `.pgpass` or `pg_service.conf`. Secure. (unless you have dblink installed on same Postgres instance.) Only problem is that it's not administrable from inside the database. * Load per-user password from table/file and append it to connect string. Slight problem - users can see the password. * Use single user/password for all users and put it into connect string. Bigger problem - users can see the password. * Use **trust** authentication on a pooler listening on locahost/unix socket. This is good combination with PgBouncer as it can load passwords directly from Postgres own `pg_auth` file and use them for remote connections. * Use **trust** authentication on remote database. Obviously bad idea. An example function without the use of separate configuration tables: CREATE OR REPLACE FUNCTION plproxy.get_cluster_partitions(cluster_name text) RETURNS SETOF text AS $$ BEGIN IF cluster_name = 'a_cluster' THEN RETURN NEXT 'dbname=part00 host=127.0.0.1'; RETURN NEXT 'dbname=part01 host=127.0.0.1'; RETURN NEXT 'dbname=part02 host=127.0.0.1'; RETURN NEXT 'dbname=part03 host=127.0.0.1'; RETURN; END IF; RAISE EXCEPTION 'Unknown cluster'; END; $$ LANGUAGE plpgsql; ### plproxy.get_cluster_config(cluster) plproxy.get_cluster_config( IN cluster_name text, OUT key text, OUT val text) RETURNS SETOF record The `get_cluster_config()` function returns a set of key-value pairs that can consist of any of the following configuration parameters. All of them are optional. Timeouts/lifetime values are given in seconds. If the value is 0 or NULL then the parameter is disabled (a default value will be used). * `connection_lifetime` The maximum age a connection (in seconds) to a remote database will be kept open for. If this is disabled (0) then connections to remote databases will be kept open as long as they are valid. Otherwise once a connection reaches the age indicated it will be closed. * `query_timeout` If a query result does not appear in this time, the connection is closed. If set then `statement_timeout` should also be set on remote server to a 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. * `connect_timeout` Initial connect is canceled, if it takes more that this. **Deprecated**: it duplicates libpq connect string parameter with same name. Its better to just add the parameter to connect string. * `default_user` Either `current_user` (default) or `session_user`. They have same meaning as SQL tokens. The specified user is used to look up SQL/MED user mapping. In case of non-SQL/MED cluster, the user is put directly to connect string, unless there already exist `user=` key. The user is also used to cache the connections. Thus PL/Proxy 2.4+ supports connecting to single cluster from same backend with different users. **Deprecated**: it's use is to restore pre-2.4 default of `session_user`. Example function without the use of separate tables for storing parameters. CREATE OR REPLACE FUNCTION plproxy.get_cluster_config( IN cluster_name text, OUT key text, OUT val text) RETURNS SETOF record AS $$ BEGIN -- lets use same config for all clusters key := 'connection_lifetime'; val := 30*60; -- 30m RETURN NEXT; RETURN; END; $$ LANGUAGE plpgsql; ## SQL/MED cluster definitions Pl/Proxy can take advantage of SQL/MED connection info management available in PostgreSQL 8.4 and above. The benefits of using SQL/MED are simplified cluster definition management and slightly improved performance. Both SQL/MED defined clusters and configuration function based clusters can coexist in the same database. If a cluster is defined in both, SQL/MED takes precedence. If no SQL/MED cluster is found an attempt is made to fall back to configuration functions. ### Configuring SQL/MED clusters First we need to create a foreign data wrapper. Generally the FDW is a kind of driver that provides remote database access, data marshalling etc. In this case, it's main role is to provide a validation function that sanity checks your cluster definitions. Note: the validation function is known to be broken in PostgreSQL 8.4.2 and below. CREATE FOREIGN DATA WRAPPER plproxy [ VALIDATOR plproxy_fdw_validator ] [ OPTIONS global options ] ; Next we need to define a CLUSTER, this is done by creating a SERVER that uses the plproxy FDW. The options to the SERVER are PL/Proxy configuration settings and the list of cluster partitions. Note: USAGE access to the SERVER must be explicitly granted. Without this, users are unable to use the cluster. CREATE SERVER a_cluster FOREIGN DATA WRAPPER plproxy OPTIONS ( connection_lifetime '1800', disable_binary '1', p0 'dbname=part00 host=127.0.0.1', p1 'dbname=part01 host=127.0.0.1', p2 'dbname=part02 host=127.0.0.1', p3 'dbname=part03 host=127.0.0.1' ); Finally we need to create a user mapping for the Pl/Proxy users. One might create individual mappings for specific users: CREATE USER MAPPING FOR bob SERVER a_cluster OPTIONS (user 'bob', password 'secret'); or create a PUBLIC mapping for all users of the system: CREATE USER MAPPING FOR public SERVER a_cluster OPTIONS (user 'plproxy', password 'foo'); Also it is possible to create both individual and PUBLIC mapping, in this case the individual mapping takes precedence. plproxy-2.9/doc/faq.md000066400000000000000000000311001353753647500147540ustar00rootroot00000000000000 # PL/Proxy FAQ ## General ### What is PL/Proxy? PL/Proxy is compact language for remote calls between PostgreSQL databases. Syntax is similar to PL/pgSql and language contains only 4 statements. With PL/Proxy user can create proxy functions that have same signature as remote functions to be called. The function body describes how the remote connection should be acquired. When such proxy function is called, PL/Proxy: 1. Automatically generates the SQL query to be executed from function signature 2. Executes statements in function body to get the connection 3. Uses function arguments as input parameters to query 4. Passes the query result back as function result ### Why functions? Concentrating on just function-calls allows PL/Proxy to keep its code small and also to present user simple and compact API. - The actual query run on remote database can be generated based on plproxy function signature. User just needs to specify how the connection to remote database must be acquired. - There is no need for complex transaction handling as any multi-statement transactions can be put into functions. PL/Proxy can just execute all queries in 'autocommit' mode. - Simple autocommit transactions mean that the connection handling is simple and can be done automatically. Using function-based database access has more general good points: - It's good to have SQL statements that operate on data near to tables. That makes life of DBA's easier. - It makes it possible to optimize and reorganize tables transparently to the application. - Enables DBA's to move parts of database into another database without changing application interface. - Easier to manage security if you don't have to do it on table level. In most cases you need to control what user can do and on which data not on what tables. - All transactions can be made in 'autocommit' mode. That means absolutely minimal amount of roundtrips (1) for each query and also each transaction takes shortest possible amount of time on server - remember that various locks that transactions aquire are released on COMMIT. ### Why not develop it into Remote Parallel PL/SQL? Huge benefit of PL/Proxy is it's compactness and efficiency. As it does not need to parse queries going through it adds very little overhead. Making it full-blown language for SQL execution would mean reimplementing PL/pgSQL, PL/Perl, parts of pgpool and more, which is waste of effort. Also when plproxy functions mirror actual functions, the PL/Proxy becomes optional component of setup - the client apps can bypass PL/Proxy and work directly on actual database. This is good for testing and also live deployment - we let clients work on smaller databases directly, they are put behind PL/Proxy only when load gets too high and we need to partition a database. ### What can PL/Proxy be used for? - Remote calls from one database to another either used inside SQL or other procedures. (If used as part of local transaction need to make sure only one side is allowed to write to database, PL/Proxy does not guarantee transactionality between 2 databases.) - Proxy databases for better security and data protection. - Proxy databases for hiding complexity of databases from application, eg. if you have functions distributed between several databases - Horizontal partitioning. Instead of buying more powerful servers you can split your data between several servers and then use PL/Proxy to redirect function calls into right partitions. - Load balancing if you have several read only replicas of your data. ### How does it compare to dblink? - PL/Proxy handles connections automatically, dblink forces user to handle them. - PL/Proxy has single place where result column types are specified - function signature. dblink requires them to be specified in each query. - PL/Proxy makes easy to run a query in several remote servers in parallel. Seems that dblink async API makes that also possible, but the usage is complex. - dblink allows arbitrary complex transactions, PL/Proxy runs everything in autocommit mode. As previourly discussed, when using functions the complex transactions are not needed and with such tradeoff PL/Proxy can offer much simpler API. ### How are PL/Proxy and PgBouncer related? PL/Proxy version 1 had PL and pooler integrated. But such design caused a lot of unnecessary complexity. With PL/Proxy version 2, we wrote both pooler and PL part from scratch, both designed to be standalone components. That allowed both components to be tested and used separately and resulted in compact and robust codebase. So PgBouncer can be used with PL/Proxy to lessen connection count on partition server, but such usage is not mandatory. ## Internals ### What are the external dependencies? It depends only on libpq and poll(2) + gettimeofday(2) system calls. So it should be quite portable. ### How the remote calls are done? First a SELECT query is generated based on PL/Proxy function signature. A function signature of: CREATE FUNCTION get_data(IN first_name text, IN last_name text, OUT bdate date, OUT balance numeric(20,10)) Results in following query: SELECT bdate::date, balance::numeric(20,10) FROM public.get_data($1::text, $2::text); The casts and explicit `OUT` parameter names are used to survive minor type or result column order differences between local and remote databases. Then the `CLUSTER` statement is processed, optionally executing function. This result in cluster name. Then `plproxy.get_cluster_version()` is executed. This gives numeric version number for cluster. If resulting version number differs from version in cached cluster, the cache is dropped. If cluster information in not cached, the `plproxy.get_cluster_partitions()` function is executed, resulting in list of connect strings for that cluster. Then `RUN` statement is processed, optionally executing function if requested. This will tag one or more connections in cluster to be used for query execution. Then the query is sent to remote server using libpq async API. If there are several remote connections tagged, the execution will happen in parallel. PL/Proxy then waits until it has acquired resultsets from all connections and then returns them to local backend. ### How does PL/Proxy handle connections? It opens them lazily - only when needed. Then keeps them open until it libpq reports error on it or connection lifetime is over - which is by default 2h. There is a safety hack used - before sending query to already open connection a poll(2) call is run on connection. If poll() shows events the connection is dropped to avoid use of likely broken connection. ### Can PL/Proxy survive different settings in local and remote database? * `client_encoding` If it differs, PL/Proxy sets the `client_encoding` on remote database to be equal to local one. * `standard_conforming_strings` Query parameters are passed separately, so in general the difference should not matter. Except when function uses explicit SELECT and it contains literal strings. Fix is to avoid use of SELECT. * `datestyle`, `timezone` Currently no handling is done. * Rest of parameters Cannot be handled. ### Why does PL/Proxy require the number of partition be power of 2? There is no deep reason, mostly because of following points: - To have minimal sanity-checking on the output of `get_cluster_partitions()`. - To have clear way to map hashes to partition. As users quite likely need to write their own code for splitting and sanity checking their data, the algorithm should be as simple as possible. It would be easy to use mod N internally, but: - We would lose the sanity checking. - We would need to define mod function for negative integers that maps to positive range. This sounds like a source for confusion and bugs. So it seems it's preferable to keep the power-of-2 requirement. This may seem to require that the number of servers be also power of 2, but this is not so - to make future administration easier it is always preferable to split database into more parts than you immediately need. Such splitting also overcomes the power-of-2 requirement. For example, if user needs to spread the load over 3 servers, the database can be split to 16 partitions and then 2 servers get 5 partitions and last one 6. ## Partitioning ### How to partition data? There are several usage patterns how PL/Proxy can be used to distribute load on several servers - Vertical partitioning. Data is divided into separate servers table by table and PL/Proxy calls are used to direct calls to right databases. In some cases wrapper functions that do several remote calls into other databases are needed. - Horizontal partitioning. Using hashtext function any field can be converted into integer. In simpler case you can use just your id field. Number of partitions must be power of two in cluster and PL/Proxy uses bitwise and to get number of partition from given integer. - Two-level vertical partitioning. PL/Proxy allows the cluster name also be calculated on function arguments. So it is possible to dedicate different clusters to different categories or one cluster to read-queries, second cluster to write-queries and then do the usual hash-based partitioning inside clusters. - Read only replicas. Load can be divided on read only replicas. You can define cluster to have more partitions in cluster that you have actual databases and use repeating connect strings as weights on servers. In many of these scenarios good replication software like Londiste from SkyTools is handy. ### How to spread single large query over several partitions? If each partition holds only part of the total data this happens automatically - just use RUN ON ALL. If the partitions are copies of each other or the query does not follow the split pattern for some other reason, you need to use `SPLIT` command to give each partition part of the data. ### How to do aggregated queries? Aggregation needs to happen in 3 steps: 1. Function on partition that does per-partition aggregation. 2. PL/Proxy function that collects the result of per-partition aggregation. It will return a row for each partition. 3. Top-level aggregation that does the final aggregation on the resultset of PL/Proxy function. A regular PL/pgSQL function can be used or this can be done outside database by client application. Note: some of the aggregations cannot be done naively - eg. `avg()`. Instead each partition must do `sum() + count()` and the top-level aggregator calculates actual average. ### How to add partitions? The simple way would be to collect data from all partitions together then split it again to new partitions. But that is a waste of resources. Few things to keep in mind to make the addition easier: - Always partition data to more pieces that you actually need. Eg. if you think 2 servers would handle the load, then do the split into 8 partitions, keeping 4 of them on single server. That way when load grows you just need to move databases to separate server, not rehash your data. That also allows you to load-balance between servers with inequal power - keep more partitions on server that has more power. - Split one partition at a time, splitting it to 2 (preferably 4 or 8). You just need to keep duplicate entries in partition list for partitions that are not split yet. ### Can I have foreign keys on my data? Yes, unless the data you want to partition on references itself. Another common scenario is that there are some big data tables that user wants to partition but they reference various smaller common tables that are not partitionable. In such situation the common tables should be managed from single external database and replicated to each partition. That gives single place to manipulate data and correct transactionality when spreading data out. ### What happens if I do updates in remote database? PL/Proxy is in autocommit mode so if remote function succeeds then changes are automatically committed at once. Special handling is needed if updates are done in both databases. If remote call fails both are rolled back but if remote call succeeds and local updates fail then only local updates are rolled back. Usually PgQ based solutions are used in these situations. ### How to handle sequences? Best way is to use separate ranges for each partition. In our case, no code uses serials directly, instead they use wrapper function that combines unique ID each database has and plain sequence. That way we don't need to manage sequences explicitly, instead only thing we need to do is to assign each database unique ID. plproxy-2.9/doc/syntax.md000066400000000000000000000131061353753647500155410ustar00rootroot00000000000000 # PL/Proxy Language Syntax The language is similar to plpgsql - string quoting, comments, semicolon at the statements end. It contains only 4 statements: `CONNECT`, `CLUSTER`, `RUN` and `SELECT`. Each function needs to have either `CONNECT` or pair of `CLUSTER` + `RUN` statements 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. The `RUN` statment is also optional, it defaults to `RUN ON ANY` which means the query will be run random partition. ## CONNECT CONNECT 'libpq connstr'; Specifies exact location where to connect and execute the query. If several functions have same connstr, they will use same connection. CONNECT connect_func(...); CONNECT argname; CONNECT $argnr; Connect string is taken from function result or directly from argument. If several functions have same connstr, they will use same connection. _(New in 2.0.9)_ **NB**: giving untrusted users ability to specify full connect string creates security hole. Eg it can used to read cleartext passwords from `~/.pgpass` or `pg_service.conf`. If such function cannot be avoided, it's access rights need to be restricted. ## CLUSTER CLUSTER 'cluster_name'; Specifies exact cluster name to be run on. The cluster name will be passed to `plproxy.get_cluster_*` functions. CLUSTER cluster_func(..); Cluster name can be dynamically decided upon proxy function arguments. `cluster_func` should return text value of final cluster name. ## RUN ON ... RUN ON ALL; Query will be run on all partitions in cluster in parallel. RUN ON ANY; Query will be run on random partition. RUN ON ; Run on partition number ``. RUN ON partition_func(..); Run `partition_func()` which should return one or more hash values. (int4) Query will be run on tagged partitions. If more than one partition was tagged, query will be sent in parallel to them. RUN ON argname; RUN ON $1; Take hash value directly from function argument. _(New in 2.0.8)_ ## SPLIT SPLIT array_arg_1 [ , array_arg_2 ... ] ; SPLIT ALL ; Split the input arrays based on RUN ON statement into per-partition arrays. This is done by evaluating RUN ON condition for each array element and building per-partition parameter arrays for each matching partition. During execution each tagged partition then gets its own subset of the array to process. _(New in 2.1)_ The semantics of RUN ON statement is slightly changed with SPLIT arrays: RUN ON partition_func(..); The array is split between the partitions matching `partition_func()`. Any SPLIT parameters passed to the function are actually replaced with the individual array elements. RUN ON argname; RUN ON $1; An array of partition numbers (or hashes) can be passed as `argname`. The function shall be run on the partitions specified in the array. RUN ON ANY; Each element is assigned to random partition. RUN ON ALL; RUN ON ; Unaffected, except for the added overhead of array copying. Example: CREATE FUNCTION set_profiles(i_users text[], i_profiles text[]) RETURNS SETOF text AS $$ CLUSTER 'userdb'; SPLIT i_users, i_profiles; RUN ON hashtext(i_users); $$ LANGUAGE plproxy; Given query: SELECT * FROM set_profiles(ARRAY['foo', 'bar'], ARRAY['a', 'b']); The hash function is called 2 times: SELECT * FROM hashtext('foo'); SELECT * FROM hashtext('bar'); And target partitions get queries: SELECT * FROM set_profiles(ARRAY['foo'], ARRAY['a']); SELECT * FROM set_profiles(ARRAY['bar'], ARRAY['b']); ## TARGET Specify function name on remote side to be called. By default PL/Proxy uses current function name. _(New in 2.2)_ Following function: CREATE FUNCTION some_function(username text, num int4) RETURNS SETOF text AS $$ CLUSTER 'userdb'; RUN ON hashtext(username); TARGET other_function; $$ LANGUAGE plproxy; will run following query on remote side: SELECT * FROM other_function(username, num); ## SELECT SELECT .... ; By default, PL/Proxy generates query based on its own signature. But this can be overrided by giving explicit `SELECT` statement to run. Everything after `SELECT` until semicolon is taken as SQL to be passed on. Only argument substitution is done on the contents, otherwise the text is unparsed. To avoid a table column to be parsed as function argument, table aliases should be used. Query result should have same number of columns as function result and same names too. ## Argument substitution Proxy function arguments can be referenced using name or `$n` syntax. Everything that is not argument reference is just passed on. ## Dynamic records PL/Proxy supports function returning plain RECORD type. Such functions need the result type specified at call site. Main use-case is to run random queries on partitions. _(New in 2.0.6)_ Very simple example: CREATE OR REPLACE FUNCTION dynamic_query(q text) RETURNS SETOF RECORD AS $$ CLUSTER 'mycluster'; RUN ON ALL; $$ LANGUAGE plproxy; Corresponding function in partitions: CREATE OR REPLACE FUNCTION dynamic_query(sql text) RETURNS SETOF RECORD AS $$ DECLARE rec RECORD; BEGIN FOR rec IN EXECUTE sql LOOP RETURN NEXT rec; END LOOP; RETURN; END; $$ LANGUAGE plpgsql; Sample request: SELECT * FROM dynamic_query('SELECT id, username FROM sometable') AS (id integer, username text); The types given in AS clause must match actual types from query. plproxy-2.9/doc/todo.md000066400000000000000000000011511353753647500151550ustar00rootroot00000000000000 # PL/Proxy todo list ## Near future * Lazy value type cache, per-conn binary I/O decision. * Clean up the binary-or-not decision. There should be possible to have partitions with different versions. Requires lazy values. ## Good to have * RUN ON ALL: ignore errors? * RUN ON ANY: if one con failed, try another ## Just thoughts * Drop `plproxy.get_cluster_config()` * Streaming for big resultsets, to avoid loading them fully in memory. This needs also backend changes. * integrate with memcache: set_object(id, data) CACHE SET object(id) = data; get_object(id) CACHE GET object(id) plproxy-2.9/doc/tutorial.md000066400000000000000000000174451353753647500160700ustar00rootroot00000000000000 # PL/Proxy tutorial This section explains how to use PL/Proxy to proxy queries across a set of remote databases. For purposes of this intro we assume each remote database has a "user" table that contains a username and an email column. We also assume that the data is partitioned across remote databases by taking a hash of the username and assigning it to one of 2 servers. Real applications should use a partitioning scheme that is appropriate to their requirements. For the purposes of this example assume that the partition databases part00 and part01 both contain a table resembling CREATE TABLE users ( username text, email text ); ## Installation 1. Download PL/Proxy from [https://plproxy.github.com]() and extract. 2. Build PL/Proxy by running `make` and `make install` inside of the plproxy directory. If your having problems make sure that pg_config from the postgresql bin directory is in your path. 3. To install PL/Proxy in a database execute the commands in the `plproxy.sql` file. For example `psql -f $SHAREDIR/contrib/plproxy.sql mydatabase` Steps 1 and 2 can be skipped if your installed pl/proxy from a packaging system such as RPM. ## Simple remote function call Here we will create a plproxy function that will run on the proxy database which will connect to a remote database named 'part00' and return a users email address. This example uses plproxy in CONNECT mode, it will connect to `dbname=part00` and run following SQL there: CREATE FUNCTION get_user_email(i_username text) RETURNS SETOF text AS $$ CONNECT 'dbname=part00'; SELECT email FROM users WHERE username = $1; $$ LANGUAGE plproxy; SELECT * from get_user_email($1); The above example uses plproxy to proxy the query to the remote database but doesn't handle partitioning of data. It assumes that the entire users table is in the remote users database. The next few steps will describe how to partition data with PL/Proxy. ## Create configuration functions Using PL/Proxy for partitioning requires setting up some configuration functions. Alternatively, if you are running PostgreSQL 8.4 or above you can take advantage of the SQL/MED connection management facilities. See below. When a query needs to be forwarded to a remote database the function `plproxy.get_cluster_partitions(cluster)` is invoked by plproxy to get the connection string to use for each partition. The following is an example CREATE OR REPLACE FUNCTION plproxy.get_cluster_partitions(cluster_name text) RETURNS SETOF text AS $$ BEGIN IF cluster_name = 'usercluster' THEN RETURN NEXT 'dbname=part00 host=127.0.0.1'; RETURN NEXT 'dbname=part01 host=127.0.0.1'; RETURN; END IF; RAISE EXCEPTION 'Unknown cluster'; END; $$ LANGUAGE plpgsql; A production application might query some configuration tables to return the connstrings. The number of partitions must be a power of 2. Next define a `plproxy.get_cluster_version(cluster_name)` function. This is called on each request and determines if the output from a cached result from `plproxy.get_cluster_partitions()` can be reused. CREATE OR REPLACE FUNCTION plproxy.get_cluster_version(cluster_name text) RETURNS int4 AS $$ BEGIN IF cluster_name = 'usercluster' THEN RETURN 1; END IF; RAISE EXCEPTION 'Unknown cluster'; END; $$ LANGUAGE plpgsql; We also need to provide a `plproxy.get_cluster_config()` function, ours will provide a value for the connection lifetime. See the configuration section for details on what this function can do. CREATE OR REPLACE FUNCTION plproxy.get_cluster_config( in cluster_name text, out key text, out val text) RETURNS SETOF record AS $$ BEGIN -- lets use same config for all clusters key := 'connection_lifetime'; val := 30*60; -- 30m RETURN NEXT; RETURN; END; $$ LANGUAGE plpgsql; The config section contains more information on all of these functions. ## Configuring Pl/Proxy clusters with SQL/MED First we need a foreign data wrapper. This is mostly a placeholder, but can be extended with a validator function to verify the cluster definition. See [CREATE FOREIGN DATA WRAPPER](http://www.postgresql.org/docs/9.4/static/sql-createforeigndatawrapper.html) documentation for additional details of how to manage the SQL/MED catalog. CREATE FOREIGN DATA WRAPPER plproxy; Then the actual cluster with its configuration options and partitions: CREATE SERVER usercluster FOREIGN DATA WRAPPER plproxy OPTIONS (connection_lifetime '1800', p0 'dbname=part00 host=127.0.0.1', p1 'dbname=part01 host=127.0.0.1' ); We also need a user mapping that maps local PostgreSQL users to remote partitions. It is possible to create PUBLIC mapping that applies for all users in the local system: CREATE USER MAPPING FOR PUBLIC SERVER usercluster; Or a private mapping that can only be used by specific users: CREATE USER MAPPING FOR bob SERVER usercluster OPTIONS (user 'plproxy', password 'salakala'); Finally we need to grant USAGE on the cluster to specific users: GRANT USAGE ON SERVER usercluster TO bob; ## Partitioned remote call Here we assume that the user table is spread over several databases based on a hash of the username. The connection string for the partitioned databases are contained in the `get_cluster_partitions()` function described above. Below is a `get_user_email()` function that is executed on the proxy server,which will make a remote connection to the appropriate partitioned database. The user's email address will be returned. This function should be created in the proxy database. CREATE OR REPLACE FUNCTION get_user_email(i_username text) RETURNS SETOF text AS $$ CLUSTER 'usercluster'; RUN ON hashtext(i_username) ; SELECT email FROM users WHERE username = i_username; $$ LANGUAGE plproxy; ## Inserting into the proper partition Next we provide a simple INSERT function. Inserting data through plproxy requires functions to be defined on the proxy databases that will perform the insert. We define this function on both part00 and part01 CREATE OR REPLACE FUNCTION insert_user(i_username text, i_emailaddress text) RETURNS integer AS $$ INSERT INTO users (username, email) VALUES ($1,$2); SELECT 1; $$ LANGUAGE SQL; Now we define a proxy function inside the proxy database to send the INSERT's to the appropriate target. CREATE OR REPLACE FUNCTION insert_user(i_username text, i_emailaddress text) RETURNS integer AS $$ CLUSTER 'usercluster'; RUN ON hashtext(i_username); $$ LANGUAGE plproxy; ## Putting it all together Connect to the proxy database (The one we installed plproxy and the plproxy functions on). SELECT insert_user('Sven','sven@somewhere.com'); SELECT insert_user('Marko', 'marko@somewhere.com'); SELECT insert_user('Steve','steve@somewhere.cm'); Now connect to the `plproxy_1` and `plproxy_2` databases. Sven and Marko should be in `plproxy_2`, and Steve should be in `plproxy_1`. When connected to the proxy user you can obtain data by doing SELECT get_user_email('Sven'); SELECT get_user_email('Marko'); SELECT get_user_email('Steve'); ## Connection pooling When used in partitioned setup, PL/Proxy somewhat wastes connections as it opens connection to each partition from each backend process. So it's good idea to use a pooler that can take queries from several connections and funnel them via smaller number of connections to actual database. We use and recommend [PgBouncer](https://pgbouncer.github.io) for that. ## More resources Kristo Kaiv has his own take on tutorial here: [https://kaiv.wordpress.com/category/plproxy]() plproxy-2.9/plproxy.control000066400000000000000000000003221353753647500162370ustar00rootroot00000000000000# plproxy extension comment = 'Database partitioning implemented as procedural language' default_version = '2.9.0' module_pathname = '$libdir/plproxy' relocatable = false # schema = pg_catalog superuser = true plproxy-2.9/sql/000077500000000000000000000000001353753647500137225ustar00rootroot00000000000000plproxy-2.9/sql/ext_unpackaged.sql000066400000000000000000000003571353753647500174320ustar00rootroot00000000000000ALTER EXTENSION plproxy ADD FUNCTION plproxy_call_handler(); ALTER EXTENSION plproxy ADD LANGUAGE plproxy; ALTER EXTENSION plproxy ADD FUNCTION plproxy_fdw_validator(text[], oid); ALTER EXTENSION plproxy ADD FOREIGN DATA WRAPPER plproxy; plproxy-2.9/sql/ext_update_validator.sql000066400000000000000000000002671353753647500206570ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION plproxy_validator (oid) RETURNS void AS 'plproxy' LANGUAGE C; CREATE OR REPLACE LANGUAGE plproxy HANDLER plproxy_call_handler VALIDATOR plproxy_validator; plproxy-2.9/sql/plproxy_fdw.sql000066400000000000000000000003351353753647500170210ustar00rootroot00000000000000-- validator function CREATE OR REPLACE FUNCTION plproxy_fdw_validator (text[], oid) RETURNS boolean AS 'plproxy' LANGUAGE C; -- foreign data wrapper CREATE FOREIGN DATA WRAPPER plproxy VALIDATOR plproxy_fdw_validator; plproxy-2.9/sql/plproxy_lang.sql000066400000000000000000000005251353753647500171630ustar00rootroot00000000000000 -- handler function CREATE OR REPLACE FUNCTION plproxy_call_handler () RETURNS language_handler AS 'plproxy' LANGUAGE C; -- validator function CREATE OR REPLACE FUNCTION plproxy_validator (oid) RETURNS void AS 'plproxy' LANGUAGE C; -- language CREATE OR REPLACE LANGUAGE plproxy HANDLER plproxy_call_handler VALIDATOR plproxy_validator; plproxy-2.9/src/000077500000000000000000000000001353753647500137125ustar00rootroot00000000000000plproxy-2.9/src/aatree.c000066400000000000000000000163641353753647500153310ustar00rootroot00000000000000/* * AA-Tree - Binary tree with embeddable nodes. * * Copyright (c) 2007-2009 Marko Kreen, Skype Technologies OÜ * * Permission to use, copy, modify, and/or 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. */ /* * Self-balancing binary tree. * * Here is an implementation of AA-tree (Arne Andersson tree) * which is simplification of Red-Black tree. * * Red-black tree has following properties that must be kept: * 1. A node is either red or black. * 2. The root is black. * 3. All leaves (NIL nodes) are black. * 4. Both childen of red node are black. * 5. Every path from root to leaf contains same number of black nodes. * * AA-tree adds additional property: * 6. Red node can exist only as a right node. * * Red-black tree properties quarantee that the longest path is max 2x longer * than shortest path (B-R-B-R-B-R-B vs. B-B-B-B) thus the tree will be roughly * balanced. Also it has good worst-case guarantees for insertion and deletion, * which makes it good tool for real-time applications. * * AA-tree removes most special cases from RB-tree, thus making resulting * code lot simpler. It requires slightly more rotations when inserting * and deleting but also keeps the tree more balanced. */ #include "aatree.h" typedef struct AATree Tree; typedef struct AANode Node; /* * NIL node */ #define NIL ((struct AANode *)&_nil) static const struct AANode _nil = { NIL, NIL, 0 }; /* * Rebalancing. AA-tree needs only 2 operations * to keep the tree balanced. */ /* * Fix red on left. * * X Y * / --> \ * Y X * \ / * a a */ static inline Node * skew(Node *x) { Node *y = x->left; if (x->level == y->level && x != NIL) { x->left = y->right; y->right = x; return y; } return x; } /* * Fix 2 reds on right. * * X Y * \ / \ * Y --> X Z * / \ \ * a Z a */ static inline Node * split(Node *x) { Node *y = x->right; if (x->level == y->right->level && x != NIL) { x->right = y->left; y->left = x; y->level++; return y; } return x; } /* insert is easy */ static Node *rebalance_on_insert(Node *current) { return split(skew(current)); } /* remove is bit more tricky */ static Node *rebalance_on_remove(Node *current) { /* * Removal can create a gap in levels, * fix it by lowering current->level. */ if (current->left->level < current->level - 1 || current->right->level < current->level - 1) { current->level--; /* if ->right is red, change it's level too */ if (current->right->level > current->level) current->right->level = current->level; /* reshape, ask Arne about those */ current = skew(current); current->right = skew(current->right); current->right->right = skew(current->right->right); current = split(current); current->right = split(current->right); } return current; } /* * Recursive insertion */ static Node * insert_sub(Tree *tree, Node *current, uintptr_t value, Node *node) { int cmp; if (current == NIL) { /* * Init node as late as possible, to avoid corrupting * the tree in case it is already added. */ node->left = node->right = NIL; node->level = 1; tree->count++; return node; } /* recursive insert */ cmp = tree->node_cmp(value, current); if (cmp > 0) current->right = insert_sub(tree, current->right, value, node); else if (cmp < 0) current->left = insert_sub(tree, current->left, value, node); else /* already exists? */ return current; return rebalance_on_insert(current); } void aatree_insert(Tree *tree, uintptr_t value, Node *node) { tree->root = insert_sub(tree, tree->root, value, node); } /* * Recursive removal */ /* remove_sub could be used for that, but want to avoid comparisions */ static Node *steal_leftmost(Tree *tree, Node *current, Node **save_p) { if (current->left == NIL) { *save_p = current; return current->right; } current->left = steal_leftmost(tree, current->left, save_p); return rebalance_on_remove(current); } /* drop this node from tree */ static Node *drop_this_node(Tree *tree, Node *old) { Node *new = NIL; if (old->left == NIL) new = old->right; else if (old->right == NIL) new = old->left; else { /* * Picking nearest from right is better than from left, * due to asymmetry of the AA-tree. It will result in * less tree operations in the long run, */ old->right = steal_leftmost(tree, old->right, &new); /* take old node's place */ *new = *old; } /* cleanup for old node */ if (tree->release_cb) tree->release_cb(old, tree); tree->count--; return new; } static Node *remove_sub(Tree *tree, Node *current, uintptr_t value) { int cmp; /* not found? */ if (current == NIL) return current; cmp = tree->node_cmp(value, current); if (cmp > 0) current->right = remove_sub(tree, current->right, value); else if (cmp < 0) current->left = remove_sub(tree, current->left, value); else current = drop_this_node(tree, current); return rebalance_on_remove(current); } void aatree_remove(Tree *tree, uintptr_t value) { tree->root = remove_sub(tree, tree->root, value); } /* * Walking all nodes */ static void walk_sub(Node *current, enum AATreeWalkType wtype, aatree_walker_f walker, void *arg) { if (current == NIL) return; switch (wtype) { case AA_WALK_IN_ORDER: walk_sub(current->left, wtype, walker, arg); walker(current, arg); walk_sub(current->right, wtype, walker, arg); break; case AA_WALK_POST_ORDER: walk_sub(current->left, wtype, walker, arg); walk_sub(current->right, wtype, walker, arg); walker(current, arg); break; case AA_WALK_PRE_ORDER: walker(current, arg); walk_sub(current->left, wtype, walker, arg); walk_sub(current->right, wtype, walker, arg); break; } } /* walk tree in correct order */ void aatree_walk(Tree *tree, enum AATreeWalkType wtype, aatree_walker_f walker, void *arg) { walk_sub(tree->root, wtype, walker, arg); } /* walk tree in bottom-up order, so that walker can destroy the nodes */ void aatree_destroy(Tree *tree) { walk_sub(tree->root, AA_WALK_POST_ORDER, tree->release_cb, tree); /* reset tree */ tree->root = NIL; tree->count = 0; } /* prepare tree */ void aatree_init(Tree *tree, aatree_cmp_f cmpfn, aatree_walker_f release_cb) { tree->root = NIL; tree->count = 0; tree->node_cmp = cmpfn; tree->release_cb = release_cb; } /* * search function */ Node *aatree_search(Tree *tree, uintptr_t value) { Node *current = tree->root; while (current != NIL) { int cmp = tree->node_cmp(value, current); if (cmp > 0) current = current->right; else if (cmp < 0) current = current->left; else return current; } return NULL; } plproxy-2.9/src/aatree.h000066400000000000000000000053551353753647500153340ustar00rootroot00000000000000/* * Copyright (c) 2007-2009 Marko Kreen, Skype Technologies OÜ * * Permission to use, copy, modify, and/or 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. */ /** @file * * AA-Tree - Binary tree with embeddable nodes. * * AA-Tree (Arne Andersson tree) is a simplified Red-Black tree. */ #ifndef _USUAL_AATREE_H_ #define _USUAL_AATREE_H_ #include #include struct AATree; struct AANode; /** Callback for node comparision against value */ typedef int (*aatree_cmp_f)(uintptr_t, struct AANode *node); /** Callback for walking the tree */ typedef void (*aatree_walker_f)(struct AANode *n, void *arg); /** * Tree header, for storing helper functions. */ struct AATree { struct AANode *root; int count; aatree_cmp_f node_cmp; aatree_walker_f release_cb; }; /** * Tree node. Embeddable, parent structure should be taken * with container_of(). * * Techinally, the full level is not needed and 2-lowest * bits of either ->left or ->right would be enough * to keep track of structure. Currently this is not * done to keep code simple. */ struct AANode { struct AANode *left; /**< smaller values */ struct AANode *right; /**< larger values */ int level; /**< number of black nodes to leaf */ }; /** * Walk order types. */ enum AATreeWalkType { AA_WALK_IN_ORDER = 0, /* left->self->right */ AA_WALK_PRE_ORDER = 1, /* self->left->right */ AA_WALK_POST_ORDER = 2, /* left->right->self */ }; /** Initialize structure */ void aatree_init(struct AATree *tree, aatree_cmp_f cmpfn, aatree_walker_f release_cb); /** Search for node */ struct AANode *aatree_search(struct AATree *tree, uintptr_t value); /** Insert new node */ void aatree_insert(struct AATree *tree, uintptr_t value, struct AANode *node); /** Remote node */ void aatree_remove(struct AATree *tree, uintptr_t value); /** Walk over all nodes */ void aatree_walk(struct AATree *tree, enum AATreeWalkType wtype, aatree_walker_f walker, void *arg); /** Free */ void aatree_destroy(struct AATree *tree); /** Check if terminal node. */ static inline int aatree_is_nil_node(const struct AANode *node) { return (node->left == node); } #endif plproxy-2.9/src/cluster.c000066400000000000000000000762311353753647500155500ustar00rootroot00000000000000/* * 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; /* * Tree of clusters. * * For searching by name. */ static struct AATree cluster_tree; /* * Similar list for fake clusters (for CONNECT functions). * * Cluster name will be actual connect string. */ static struct AATree fake_cluster_tree; /* 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)"; /* list of all the valid configuration options to plproxy cluster */ static const char *cluster_config_options[] = { "statement_timeout", "connection_lifetime", "query_timeout", "disable_binary", /* deprecated */ "keepalive_idle", "keepalive_interval", "keepalive_count", NULL }; extern Datum plproxy_fdw_validator(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(plproxy_fdw_validator); /* * Connection count should be non-zero and power of 2. */ static bool check_valid_partcount(int n) { return (n > 0) && !(n & (n - 1)); } static int cluster_name_cmp(uintptr_t val, struct AANode *node) { const char *name = (const char *)val; const ProxyCluster *cluster = container_of(node, ProxyCluster, node); return strcmp(name, cluster->name); } static int conn_cstr_cmp(uintptr_t val, struct AANode *node) { const char *name = (const char *)val; const ProxyConnection *conn = container_of(node, ProxyConnection, node); return strcmp(name, conn->connstr); } static void conn_free(struct AANode *node, void *arg) { ProxyConnection *conn = container_of(node, ProxyConnection, node); aatree_destroy(&conn->userstate_tree); if (conn->res) PQclear(conn->res); pfree(conn); } static int state_user_cmp(uintptr_t val, struct AANode *node) { const char *name = (const char *)val; const ProxyConnectionState *state = container_of(node, ProxyConnectionState, node); return strcmp(name, state->userinfo->username); } static void state_free(struct AANode *node, void *arg) { ProxyConnectionState *state = container_of(node, ProxyConnectionState, node); plproxy_disconnect(state); memset(state, 0, sizeof(*state)); pfree(state); } static int userinfo_cmp(uintptr_t val, struct AANode *node) { const char *name = (const char *)val; const ConnUserInfo *info = container_of(node, ConnUserInfo, node); return strcmp(name, info->username); } static void userinfo_free(struct AANode *node, void *arg) { ConnUserInfo *info = container_of(node, ConnUserInfo, node); pfree(info->username); if (info->extra_connstr) { memset(info->extra_connstr, 0, strlen(info->extra_connstr)); pfree(info->extra_connstr); } memset(info, 0, sizeof(*info)); pfree(info); } /* * Create cache memory area and prepare plans */ void plproxy_cluster_cache_init(void) { /* * create long-lived memory context */ cluster_mem = AllocSetContextCreate(TopMemoryContext, "PL/Proxy cluster context", ALLOCSET_SMALL_SIZES); aatree_init(&cluster_tree, cluster_name_cmp, NULL); aatree_init(&fake_cluster_tree, cluster_name_cmp, NULL); } /* initialize plans on demand */ static void plproxy_cluster_plan_init(void) { void *tmp_ver_plan, *tmp_part_plan, *tmp_conf_plan; Oid types[] = {TEXTOID}; static int init_done = 0; if (init_done) return; /* * prepare plans for fetching configuration. */ tmp_ver_plan = SPI_prepare(version_sql, 1, types); if (tmp_ver_plan == NULL) elog(ERROR, "PL/Proxy: plproxy.get_cluster_version() SQL fails: %s", SPI_result_code_string(SPI_result)); tmp_part_plan = SPI_prepare(part_sql, 1, types); if (tmp_part_plan == NULL) elog(ERROR, "PL/Proxy: plproxy.get_cluster_partitions() SQL fails: %s", SPI_result_code_string(SPI_result)); tmp_conf_plan = SPI_prepare(config_sql, 1, types); if (tmp_conf_plan == NULL) elog(ERROR, "PL/Proxy: plproxy.get_cluster_config() SQL fails: %s", SPI_result_code_string(SPI_result)); /* * Store them only if all successful. */ version_plan = SPI_saveplan(tmp_ver_plan); partlist_plan = SPI_saveplan(tmp_part_plan); config_plan = SPI_saveplan(tmp_conf_plan); init_done = 1; } /* * Drop partition and connection data from cluster. */ static void free_connlist(ProxyCluster *cluster) { aatree_destroy(&cluster->conn_tree); pfree(cluster->part_map); pfree(cluster->active_list); cluster->part_map = NULL; cluster->part_count = 0; cluster->part_mask = 0; cluster->active_count = 0; } /* * Add new database connection if it does not exists. */ static void add_connection(ProxyCluster *cluster, const char *connstr, int part_num) { struct AANode *node; ProxyConnection *conn = NULL; /* check if already have it */ node = aatree_search(&cluster->conn_tree, (uintptr_t)connstr); if (node) conn = container_of(node, ProxyConnection, node); /* add new connection */ if (!conn) { conn = MemoryContextAllocZero(cluster_mem, sizeof(ProxyConnection)); conn->connstr = MemoryContextStrdup(cluster_mem, connstr); conn->cluster = cluster; aatree_init(&conn->userstate_tree, state_user_cmp, state_free); aatree_insert(&cluster->conn_tree, (uintptr_t)connstr, &conn->node); } cluster->part_map[part_num] = 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", (int) 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); } /* forget old values */ static void clear_config(ProxyConfig *cf) { memset(cf, 0, sizeof(*cf)); } /* set a configuration option. */ static void set_config_key(ProxyFunction *func, ProxyConfig *cf, const char *key, const char *val) { static int did_warn = 0; if (pg_strcasecmp(key, "statement_timeout") == 0) /* ignore */ ; else if (pg_strcasecmp("connection_lifetime", key) == 0) cf->connection_lifetime = atoi(val); else if (pg_strcasecmp("query_timeout", key) == 0) cf->query_timeout = atoi(val); else if (pg_strcasecmp("disable_binary", key) == 0) cf->disable_binary = atoi(val); else if (pg_strcasecmp("keepalive_idle", key) == 0 || pg_strcasecmp("keepalive_interval", key) == 0 || pg_strcasecmp("keepalive_count", key) == 0) { if (atoi(val) > 0 && !did_warn) { did_warn = 1; elog(WARNING, "Use libpq keepalive options, PL/Proxy keepalive options not supported"); } } else if (pg_strcasecmp("default_user", key) == 0) snprintf(cf->default_user, sizeof(cf->default_user), "%s", val); else plproxy_error(func, "Unknown config param: %s", key); } /* * Fetch cluster configuration. */ static int get_config(ProxyCluster *cluster, Datum dname, ProxyFunction *func) { int err, i; TupleDesc desc; const char *key, *val; /* 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"); clear_config(&cluster->config); /* 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"); set_config_key(func, &cluster->config, key, val); } return 0; } /* allocate memory for cluster partitions */ static void allocate_cluster_partitions(ProxyCluster *cluster, int nparts) { MemoryContext old_ctx; /* free old one */ if (cluster->part_map) free_connlist(cluster); cluster->part_count = nparts; cluster->part_mask = cluster->part_count - 1; /* allocate lists */ old_ctx = MemoryContextSwitchTo(cluster_mem); cluster->part_map = palloc0(nparts * sizeof(ProxyConnection *)); cluster->active_list = palloc0(nparts * sizeof(ProxyConnection *)); MemoryContextSwitchTo(old_ctx); } /* fetch list of parts */ static int reload_parts(ProxyCluster *cluster, Datum dname, ProxyFunction *func) { int err, i; char *connstr; 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"); allocate_cluster_partitions(cluster, SPI_processed); /* 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"); add_connection(cluster, connstr, i); } return 0; } /* extract a partition number from foreign server option */ static bool extract_part_num(const char *partname, int *part_num) { char *partition_tags[] = { "p", "partition_", NULL }; char **part_tag; char *errptr; for (part_tag = partition_tags; *part_tag; part_tag++) { if (strstr(partname, *part_tag) == partname) { *part_num = (int) strtoul(partname + strlen(*part_tag), &errptr, 10); if (*errptr == '\0') return true; } } return false; } /* * Validate single cluster option */ static void validate_cluster_option(const char *name, const char *arg) { const char **opt; /* see that a valid config option is specified */ for (opt = cluster_config_options; *opt; opt++) { if (pg_strcasecmp(*opt, name) == 0) break; } if (*opt == NULL) elog(ERROR, "Pl/Proxy: invalid server option: %s", name); else if (strspn(arg, "0123456789") != strlen(arg)) elog(ERROR, "Pl/Proxy: only integer options are allowed: %s=%s", name, arg); } /* * Validate the generic option given to servers or user mappings defined with * plproxy foreign data wrapper. Raise an ERROR if the option or its value is * considered invalid. */ Datum plproxy_fdw_validator(PG_FUNCTION_ARGS) { List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); Oid catalog = PG_GETARG_OID(1); ListCell *cell; int part_count = 0; /* Pre 8.4.3 databases have broken validator interface, warn the user */ if (catalog == InvalidOid) { ereport(NOTICE, (errcode(ERRCODE_WARNING), errmsg("Pl/Proxy: foreign data wrapper validator disabled"), errhint("validator is usable starting from PostgreSQL version 8.4.3"))); PG_RETURN_BOOL(false); } foreach(cell, options_list) { DefElem *def = lfirst(cell); char *arg = strVal(def->arg); int part_num; if (catalog == ForeignServerRelationId) { if (extract_part_num(def->defname, &part_num)) { /* partition definition */ if (part_num != part_count) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Pl/Proxy: partitions must be numbered consecutively"), errhint("next valid partition number is %d", part_count))); ++part_count; } else { validate_cluster_option(def->defname, arg); } } else if (catalog == UserMappingRelationId) { /* user mapping only accepts "user" and "password" */ if (pg_strcasecmp(def->defname, "user") != 0 && pg_strcasecmp(def->defname, "password") != 0) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Pl/Proxy: invalid option to user mapping"), errhint("valid options are \"user\" and \"password\""))); } } else if (catalog == ForeignDataWrapperRelationId) { validate_cluster_option(def->defname, arg); } } if (catalog == ForeignServerRelationId) { if (!check_valid_partcount(part_count)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Pl/Proxy: invalid number of partitions"), errhint("the number of partitions in a cluster must be power of 2 (attempted %d)", part_count))); } PG_RETURN_BOOL(true); } static void reload_sqlmed_user(ProxyFunction *func, ProxyCluster *cluster) { ConnUserInfo *userinfo = cluster->cur_userinfo; UserMapping *um; HeapTuple tup; StringInfoData cstr; ListCell *cell; AclResult aclresult; bool got_user; Oid umid; um = GetUserMapping(userinfo->user_oid, cluster->sqlmed_server_oid); /* retry same lookup so we can set cache stamp... */ tup = SearchSysCache(USERMAPPINGUSERSERVER, ObjectIdGetDatum(um->userid), ObjectIdGetDatum(um->serverid), 0, 0); if (!HeapTupleIsValid(tup)) { /* Specific mapping not found, try PUBLIC */ tup = SearchSysCache(USERMAPPINGUSERSERVER, ObjectIdGetDatum(InvalidOid), ObjectIdGetDatum(um->serverid), 0, 0); if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for user mapping (%u,%u)", um->userid, um->serverid); } #if PG_VERSION_NUM >= 90600 umid = um->umid; #else umid = HeapTupleGetOid(tup); #endif scstamp_set(USERMAPPINGOID, &userinfo->umStamp, umid); ReleaseSysCache(tup); /* * Check permissions, user must have usage on the server. */ aclresult = pg_foreign_server_aclcheck(um->serverid, um->userid, ACL_USAGE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_FOREIGN_SERVER, cluster->name); /* Extract the common connect string elements from user mapping */ got_user = false; initStringInfo(&cstr); foreach(cell, um->options) { DefElem *def = lfirst(cell); if (strcmp(def->defname, "user") == 0) got_user = true; appendStringInfo(&cstr, " %s='%s'", def->defname, strVal(def->arg)); } /* make sure we have 'user=' in connect string */ if (!got_user) appendStringInfo(&cstr, " user='%s'", userinfo->username); /* free old string */ if (userinfo->extra_connstr) { memset(userinfo->extra_connstr, 0, strlen(userinfo->extra_connstr)); pfree(userinfo->extra_connstr); userinfo->extra_connstr = NULL; } /* set up new connect string */ userinfo->extra_connstr = MemoryContextStrdup(cluster_mem, cstr.data); memset(cstr.data, 0, cstr.len); pfree(cstr.data); } /* * Reload the cluster configuration and partitions from SQL/MED catalogs. */ static void reload_sqlmed_cluster(ProxyFunction *func, ProxyCluster *cluster, ForeignServer *foreign_server) { ConnUserInfo *info = cluster->cur_userinfo; ForeignDataWrapper *fdw; HeapTuple tup; AclResult aclresult; ListCell *cell; int part_count = 0; int part_num; fdw = GetForeignDataWrapper(foreign_server->fdwid); /* * Look up the server and user mapping TIDs for handling syscache invalidations. */ tup = SearchSysCache(FOREIGNSERVEROID, ObjectIdGetDatum(foreign_server->serverid), 0, 0, 0); if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for foreign server %u", foreign_server->serverid); scstamp_set(FOREIGNSERVEROID, &cluster->clusterStamp, foreign_server->serverid); ReleaseSysCache(tup); /* * Check permissions, user must have usage on the server. */ aclresult = pg_foreign_server_aclcheck(foreign_server->serverid, info->user_oid, ACL_USAGE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_FOREIGN_SERVER, foreign_server->servername); /* drop old config values */ clear_config(&cluster->config); /* * Collect the configuration definitions from foreign data wrapper. */ foreach(cell, fdw->options) { DefElem *def = lfirst(cell); set_config_key(func, &cluster->config, def->defname, strVal(def->arg)); } /* * Collect the cluster configuration and partition definitions from foreign * server options. At first pass just collect the cluster options and count * the number of partitions. */ foreach(cell, foreign_server->options) { DefElem *def = lfirst(cell); if (extract_part_num(def->defname, &part_num)) { if (part_num != part_count) plproxy_error(func, "partitions numbers must be consecutive"); part_count++; } else set_config_key(func, &cluster->config, def->defname, strVal(def->arg)); } if (!check_valid_partcount(part_count)) plproxy_error(func, "invalid partition count"); /* * Now that the partition count is known, allocate the partitions and make * a second pass over the options adding each connstr to cluster. */ allocate_cluster_partitions(cluster, part_count); foreach(cell, foreign_server->options) { DefElem *def = lfirst(cell); if (!extract_part_num(def->defname, &part_num)) continue; add_connection(cluster, strVal(def->arg), part_num); } } /* * We have no SQL/MED definition for the cluster, determine if the * cluster is defined using the compat functions. Raise an ERROR * if the plproxy schema functions don't exist. */ static void determine_compat_mode(ProxyCluster *cluster) { bool have_compat = false; HeapTuple tup; /* * See that we have plproxy schema and all the necessary functions */ tup = SearchSysCache(NAMESPACENAME, PointerGetDatum("plproxy"), 0, 0, 0); if (HeapTupleIsValid(tup)) { Oid namespaceId = XNamespaceTupleGetOid(tup); Oid paramOids[] = { TEXTOID }; oidvector *parameterTypes = buildoidvector(paramOids, 1); const char **funcname; /* All of the functions required to run pl/proxy in compat mode */ static const char *compat_functions[] = { "get_cluster_version", "get_cluster_config", "get_cluster_partitions", NULL }; for (funcname = compat_functions; *funcname; funcname++) { if (!SearchSysCacheExists(PROCNAMEARGSNSP, PointerGetDatum(*funcname), PointerGetDatum(parameterTypes), ObjectIdGetDatum(namespaceId), 0)) break; } /* we have the schema and all the functions - use compat */ if (! *funcname) have_compat = true; ReleaseSysCache(tup); } if (!have_compat) elog(ERROR, "Pl/Proxy: cluster not found: %s", cluster->name); } static void inval_one_umap(struct AANode *n, void *arg) { ConnUserInfo *info = container_of(n, ConnUserInfo, node); SCInvalArg newStamp; if (info->needs_reload) /* already invalidated */ return; if (arg == NULL) { info->needs_reload = true; return; } newStamp = *(SCInvalArg *)arg; if (scstamp_check(USERMAPPINGOID, &info->umStamp, newStamp)) /* user mappings changed */ info->needs_reload = true; } static void inval_umapping(struct AANode *n, void *arg) { ProxyCluster *cluster = container_of(n, ProxyCluster, node); aatree_walk(&cluster->userinfo_tree, AA_WALK_IN_ORDER, inval_one_umap, arg); } static void inval_fserver(struct AANode *n, void *arg) { ProxyCluster *cluster = container_of(n, ProxyCluster, node); SCInvalArg newStamp = *(SCInvalArg *)arg; if (cluster->needs_reload) /* already invalidated */ return; else if (!cluster->sqlmed_cluster) /* allow new SQL/MED servers to override compat definitions */ cluster->needs_reload = true; else if (scstamp_check(FOREIGNSERVEROID, &cluster->clusterStamp, newStamp)) /* server definitions changed */ cluster->needs_reload = true; /* tag all users too */ if (cluster->needs_reload) inval_umapping(&cluster->node, NULL); } /* * Syscache inval callback function for foreign servers and user mappings. * * Note: this invalidates compat clusters on any foreign server change. This * allows SQL/MED clusters to override those defined by plproxy schema * functions. */ static void ClusterSyscacheCallback(Datum arg, int cacheid, SCInvalArg newStamp) { if (cacheid == FOREIGNSERVEROID) aatree_walk(&cluster_tree, AA_WALK_IN_ORDER, inval_fserver, &newStamp); else if (cacheid == USERMAPPINGOID) aatree_walk(&cluster_tree, AA_WALK_IN_ORDER, inval_umapping, &newStamp); } /* * Register syscache invalidation callbacks for SQL/MED clusters. */ void plproxy_syscache_callback_init(void) { CacheRegisterSyscacheCallback(FOREIGNSERVEROID, ClusterSyscacheCallback, (Datum) 0); CacheRegisterSyscacheCallback(USERMAPPINGOID, ClusterSyscacheCallback, (Datum) 0); } /* * Reload the cluster configuration and partitions from plproxy.get_cluster* * functions. */ static void reload_plproxy_cluster(ProxyFunction *func, ProxyCluster *cluster) { Datum dname = DirectFunctionCall1(textin, CStringGetDatum(cluster->name)); int cur_version; plproxy_cluster_plan_init(); /* fetch serial, also check if exists */ cur_version = get_version(func, dname); /* update if needed */ if (cur_version != cluster->version || cluster->needs_reload) { reload_parts(cluster, dname, func); get_config(cluster, dname, func); cluster->version = cur_version; } } /* 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); aatree_init(&cluster->conn_tree, conn_cstr_cmp, conn_free); aatree_init(&cluster->userinfo_tree, userinfo_cmp, userinfo_free); MemoryContextSwitchTo(old_ctx); return cluster; } /* * Invalidate all connections for particular user */ static void inval_userinfo_state(struct AANode *node, void *arg) { ProxyConnectionState *cur = container_of(node, ProxyConnectionState, node); ConnUserInfo *userinfo = arg; if (cur->userinfo == userinfo && cur->db) plproxy_disconnect(cur); } static void inval_userinfo_conn(struct AANode *node, void *arg) { ProxyConnection *conn = container_of(node, ProxyConnection, node); ConnUserInfo *userinfo = arg; aatree_walk(&conn->userstate_tree, AA_WALK_IN_ORDER, inval_userinfo_state, userinfo); } static void inval_user_connections(ProxyCluster *cluster, ConnUserInfo *userinfo) { /* find all connections with this user and drop them */ aatree_walk(&cluster->conn_tree, AA_WALK_IN_ORDER, inval_userinfo_conn, userinfo); /* * We can clear the flag only when it's certain * that no connections with old info exist */ userinfo->needs_reload = false; } /* * Initialize user info struct */ static ConnUserInfo * get_userinfo(ProxyCluster *cluster, Oid user_oid) { ConnUserInfo *userinfo; struct AANode *node; const char *username; username = GetUserNameFromId(user_oid #if PG_VERSION_NUM >= 90500 , false #endif ); node = aatree_search(&cluster->userinfo_tree, (uintptr_t)username); if (node) { userinfo = container_of(node, ConnUserInfo, node); } else { userinfo = MemoryContextAllocZero(cluster_mem, sizeof(*userinfo)); userinfo->username = MemoryContextStrdup(cluster_mem, username); aatree_insert(&cluster->userinfo_tree, (uintptr_t)username, &userinfo->node); } if (userinfo->user_oid != user_oid) { /* user got renamed? */ userinfo->user_oid = user_oid; userinfo->needs_reload = true; } return userinfo; } /* * Refresh the cluster. */ static void refresh_cluster(ProxyFunction *func, ProxyCluster *cluster) { ConnUserInfo *uinfo; ProxyConfig *cf = &cluster->config; Oid user_oid = InvalidOid; /* * Decide which user to use for connections. */ if (cf->default_user[0]) { if (strcmp(cf->default_user, "session_user") == 0) user_oid = GetSessionUserId(); else if (strcmp(cf->default_user, "current_user") == 0) user_oid = GetUserId(); else if (1) /* dont support custom users, seems unnecessary */ elog(ERROR, "default_user: Expect 'current_user' or 'session_user', got '%s'", cf->default_user); else /* easy to support, but seems confusing conceptually */ user_oid = get_role_oid(cf->default_user, false); } else { /* default: current_user */ user_oid = GetUserId(); } /* set up user cache */ uinfo = get_userinfo(cluster, user_oid); cluster->cur_userinfo = uinfo; /* SQL/MED server reload */ if (cluster->needs_reload) { ForeignServer *server; /* * Determine if this is a SQL/MED server name or a pl/proxy compat cluster. * Fallback to plproxy.get_cluster*() functions if a foreign server is not * found. */ server = GetForeignServerByName(cluster->name, true); cluster->sqlmed_cluster = (server != NULL); if (!cluster->sqlmed_cluster) determine_compat_mode(cluster); else { cluster->sqlmed_server_oid = server->serverid; reload_sqlmed_cluster(func, cluster, server); } } /* SQL/MED user reload */ if (uinfo->needs_reload) { if (cluster->sqlmed_cluster) { inval_user_connections(cluster, uinfo); reload_sqlmed_user(func, cluster); } else uinfo->needs_reload = false; } /* old-style cluster reload */ if (!cluster->sqlmed_cluster && !cluster->fake_cluster) reload_plproxy_cluster(func, cluster); cluster->needs_reload = false; } /* * Get cached or create new fake cluster. */ static ProxyCluster * fake_cluster(ProxyFunction *func, const char *connect_str) { ProxyCluster *cluster; MemoryContext old_ctx; struct AANode *n; /* search if cached */ n = aatree_search(&fake_cluster_tree, (uintptr_t)connect_str); if (n) { cluster = container_of(n, ProxyCluster, node); goto done; } /* create if not */ cluster = new_cluster(connect_str); old_ctx = MemoryContextSwitchTo(cluster_mem); cluster->fake_cluster = true; cluster->version = 1; cluster->part_count = 1; cluster->part_mask = 0; cluster->part_map = palloc(cluster->part_count * sizeof(ProxyConnection *)); cluster->active_list = palloc(cluster->part_count * sizeof(ProxyConnection *)); MemoryContextSwitchTo(old_ctx); add_connection(cluster, connect_str, 0); aatree_insert(&fake_cluster_tree, (uintptr_t)connect_str, &cluster->node); done: refresh_cluster(func, cluster); return cluster; } /* * Call resolve function */ static const char * resolve_query(ProxyFunction *func, FunctionCallInfo fcinfo, ProxyQuery *query) { const char *name; HeapTuple row; TupleDesc desc; plproxy_query_exec(func, fcinfo, query, NULL, 0); if (SPI_processed != 1) plproxy_error(func, "'%s' returned %d rows, expected 1", query->sql, (int) 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/connect 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 = NULL; const char *name; struct AANode *node; /* functions used CONNECT with query */ if (func->connect_sql) { const char *cstr; cstr = resolve_query(func, fcinfo, func->connect_sql); return fake_cluster(func, cstr); } /* functions used straight CONNECT */ if (func->connect_str) return fake_cluster(func, func->connect_str); /* Cluster statement, either a lookup function or a name */ if (func->cluster_sql) name = resolve_query(func, fcinfo, func->cluster_sql); else name = func->cluster_name; /* search if cached */ node = aatree_search(&cluster_tree, (uintptr_t)name); if (node) cluster = container_of(node, ProxyCluster, node); /* create if not */ if (!cluster) { cluster = new_cluster(name); cluster->needs_reload = true; aatree_insert(&cluster_tree, (uintptr_t)name, &cluster->node); } /* determine cluster type, reload parts if necessary */ refresh_cluster(func, cluster); return cluster; } /* * Move connection to active list and init current * connection state. */ void plproxy_activate_connection(struct ProxyConnection *conn) { ProxyCluster *cluster = conn->cluster; ConnUserInfo *userinfo = cluster->cur_userinfo; const char *username = userinfo->username; struct AANode *node; ProxyConnectionState *cur; /* move connection to active_list */ cluster->active_list[cluster->active_count] = conn; cluster->active_count++; /* fill ->cur pointer */ node = aatree_search(&conn->userstate_tree, (uintptr_t)username); if (node) { cur = container_of(node, ProxyConnectionState, node); } else { cur = MemoryContextAllocZero(cluster_mem, sizeof(*cur)); cur->userinfo = userinfo; aatree_insert(&conn->userstate_tree, (uintptr_t)username, &cur->node); } conn->cur = cur; } /* * Clean old connections and results from all clusters. */ struct MaintInfo { struct ProxyConfig *cf; struct timeval *now; }; static void clean_state(struct AANode *node, void *arg) { ProxyConnectionState *cur = container_of(node, ProxyConnectionState, node); ConnUserInfo *uinfo = cur->userinfo; struct MaintInfo *maint = arg; ProxyConfig *cf = maint->cf; struct timeval *now = maint->now; time_t age; bool drop; if (!cur->db) return; drop = false; if (PQstatus(cur->db) != CONNECTION_OK) { drop = true; } else if (uinfo->needs_reload) { drop = true; } else if (cf->connection_lifetime <= 0) { /* no aging */ } else { age = now->tv_sec - cur->connect_time; if (age >= cf->connection_lifetime) drop = true; } if (drop) plproxy_disconnect(cur); } static void clean_conn(struct AANode *node, void *arg) { ProxyConnection *conn = container_of(node, ProxyConnection, node); struct MaintInfo *maint = arg; if (conn->res) { PQclear(conn->res); conn->res = NULL; } aatree_walk(&conn->userstate_tree, AA_WALK_IN_ORDER, clean_state, maint); } static void clean_cluster(struct AANode *n, void *arg) { ProxyCluster *cluster = container_of(n, ProxyCluster, node); struct MaintInfo maint; maint.cf = &cluster->config; maint.now = arg; aatree_walk(&cluster->conn_tree, AA_WALK_IN_ORDER, clean_conn, &maint); } void plproxy_cluster_maint(struct timeval * now) { aatree_walk(&cluster_tree, AA_WALK_IN_ORDER, clean_cluster, now); aatree_walk(&fake_cluster_tree, AA_WALK_IN_ORDER, clean_cluster, now); } plproxy-2.9/src/execute.c000066400000000000000000000636531353753647500155350ustar00rootroot00000000000000/* * 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. */ #include "plproxy.h" #include #include "poll_compat.h" /* some error happened */ static void conn_error(ProxyFunction *func, ProxyConnection *conn, const char *desc) { plproxy_error(func, "[%s] %s: %s", PQdb(conn->cur->db), desc, PQerrorMessage(conn->cur->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; } static void flush_connection(ProxyFunction *func, ProxyConnection *conn) { int res; /* flush it down */ res = PQflush(conn->cur->db); /* set actual state */ if (res > 0) conn->cur->state = C_QUERY_WRITE; else if (res == 0) conn->cur->state = C_QUERY_READ; else conn_error(func, conn, "PQflush"); } /* * Small sanity checking for new connections. * * Current checks: * - Does there happen any encoding conversations? * - Difference in standard_conforming_strings. */ static int tune_connection(ProxyFunction *func, ProxyConnection *conn) { const char *this_enc, *dst_enc; const char *dst_ver; StringInfo sql = NULL; /* * check if target server has same backend version. */ dst_ver = PQparameterStatus(conn->cur->db, "server_version"); conn->cur->same_ver = cmp_branch(dst_ver, PG_VERSION); /* * Make sure remote I/O is done using local server_encoding. */ this_enc = GetDatabaseEncodingName(); dst_enc = PQparameterStatus(conn->cur->db, "client_encoding"); if (dst_enc && strcmp(this_enc, dst_enc)) { if (!sql) sql = makeStringInfo(); appendStringInfo(sql, "set client_encoding = '%s'; ", this_enc); } /* * if second time in this function, they should be active already. */ if (sql && conn->cur->tuning) { /* display SET query */ appendStringInfo(sql, "-- does not seem to apply"); conn_error(func, conn, sql->data); } /* * send tuning query */ if (sql) { conn->cur->tuning = 1; conn->cur->state = C_QUERY_WRITE; if (!PQsendQuery(conn->cur->db, sql->data)) conn_error(func, conn, "PQsendQuery"); pfree(sql->data); pfree(sql); flush_connection(func, conn); return 1; } conn->cur->tuning = 0; return 0; } /* 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; ProxyConfig *cf = &func->cur_cluster->config; int binary_result = 0; gettimeofday(&now, NULL); conn->cur->query_time = now.tv_sec; tune_connection(func, conn); if (conn->cur->tuning) return; /* use binary result only on same backend ver */ if (cf->disable_binary == 0 && conn->cur->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; } } /* send query */ conn->cur->state = C_QUERY_WRITE; res = PQsendQueryParams(conn->cur->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 */ flush_connection(func, conn); } /* returns false of conn should be dropped */ static bool check_old_conn(ProxyFunction *func, ProxyConnection *conn, struct timeval * now) { time_t t; int res; struct pollfd pfd; ProxyConfig *cf = &func->cur_cluster->config; if (PQstatus(conn->cur->db) != CONNECTION_OK) return false; /* check if too old */ if (cf->connection_lifetime > 0) { t = now->tv_sec - conn->cur->connect_time; if (t >= cf->connection_lifetime) return false; } /* how long ts been idle */ t = now->tv_sec - conn->cur->query_time; if (t < PLPROXY_IDLE_CONN_CHECK) return true; /* * Simple way to check if old connection is stable - look if there * are events pending. If there are drop the connection. */ intr_loop: pfd.fd = PQsocket(conn->cur->db); pfd.events = POLLIN; pfd.revents = 0; res = poll(&pfd, 1, 0); if (res > 0) { elog(WARNING, "PL/Proxy: detected unstable connection"); return false; } else if (res < 0) { if (errno == EINTR) goto intr_loop; plproxy_error(func, "check_old_conn: select failed: %s", strerror(errno)); } /* seems ok */ return true; } static void handle_notice(void *arg, const PGresult *res) { ProxyConnection *conn = arg; ProxyCluster *cluster = conn->cluster; plproxy_remote_error(cluster->cur_func, conn, res, false); } static const char * get_connstr(ProxyConnection *conn) { StringInfoData cstr; ConnUserInfo *info = conn->cluster->cur_userinfo; if (strstr(conn->connstr, "user=") != NULL) return pstrdup(conn->connstr); initStringInfo(&cstr); if (info->extra_connstr) appendStringInfo(&cstr, "%s %s", conn->connstr, info->extra_connstr); else appendStringInfo(&cstr, "%s user='%s'", conn->connstr, info->username); return cstr.data; } /* check existing conn status or launch new conn */ static void prepare_conn(ProxyFunction *func, ProxyConnection *conn) { struct timeval now; const char *connstr; gettimeofday(&now, NULL); conn->cur->waitCancel = 0; /* state should be C_READY or C_NONE */ switch (conn->cur->state) { case C_DONE: conn->cur->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"); plproxy_disconnect(conn->cur); case C_NONE: break; } conn->cur->connect_time = now.tv_sec; /* launch new connection */ connstr = get_connstr(conn); conn->cur->db = PQconnectStart(connstr); if (conn->cur->db == NULL) plproxy_error(func, "No memory for PGconn"); /* tag connection dirty */ conn->cur->state = C_CONNECT_WRITE; if (PQstatus(conn->cur->db) == CONNECTION_BAD) conn_error(func, conn, "PQconnectStart"); /* override default notice handler */ PQsetNoticeReceiver(conn->cur->db, handle_notice, conn); } /* * 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->cur->db); if (res == NULL) { conn->cur->waitCancel = 0; if (conn->cur->tuning) conn->cur->state = C_READY; else conn->cur->state = C_DONE; return false; } /* ignore result when waiting for cancel */ if (conn->cur->waitCancel) { PQclear(res); return true; } switch (PQresultStatus(res)) { case PGRES_TUPLES_OK: if (conn->res) { PQclear(res); conn_error(func, conn, "double result?"); } conn->res = res; break; case PGRES_COMMAND_OK: PQclear(res); break; case PGRES_FATAL_ERROR: if (conn->res) PQclear(conn->res); conn->res = res; plproxy_remote_error(func, conn, res, true); break; default: if (conn->res) PQclear(conn->res); conn->res = res; plproxy_error(func, "Unexpected result type: %s", PQresStatus(PQresultStatus(res))); break; } 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->cur->state) { case C_CONNECT_READ: case C_CONNECT_WRITE: poll_res = PQconnectPoll(conn->cur->db); switch (poll_res) { case PGRES_POLLING_WRITING: conn->cur->state = C_CONNECT_WRITE; break; case PGRES_POLLING_READING: conn->cur->state = C_CONNECT_READ; break; case PGRES_POLLING_OK: conn->cur->state = C_READY; break; case PGRES_POLLING_ACTIVE: case PGRES_POLLING_FAILED: conn_error(func, conn, "PQconnectPoll"); } break; case C_QUERY_WRITE: flush_connection(func, conn); break; case C_QUERY_READ: res = PQconsumeInput(conn->cur->db); if (res == 0) conn_error(func, conn, "PQconsumeInput"); /* loop until PQgetResult returns NULL */ while (1) { /* if PQisBusy, then incomplete result */ if (PQisBusy(conn->cur->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) { static struct pollfd *pfd_cache = NULL; static int pfd_allocated = 0; int i, res, fd; ProxyConnection *conn; struct pollfd *pf; int numfds = 0; int ev = 0; if (pfd_allocated < cluster->active_count) { struct pollfd *tmp; int num = cluster->active_count; if (num < 64) num = 64; if (pfd_cache == NULL) tmp = malloc(num * sizeof(struct pollfd)); else tmp = realloc(pfd_cache, num * sizeof(struct pollfd)); if (!tmp) elog(ERROR, "no mem for pollfd cache"); pfd_cache = tmp; pfd_allocated = num; } for (i = 0; i < cluster->active_count; i++) { conn = cluster->active_list[i]; if (!conn->run_tag) continue; /* decide what to do */ switch (conn->cur->state) { case C_DONE: case C_READY: case C_NONE: continue; case C_CONNECT_READ: case C_QUERY_READ: ev = POLLIN; break; case C_CONNECT_WRITE: case C_QUERY_WRITE: ev = POLLOUT; break; } /* add fd to proper set */ pf = pfd_cache + numfds++; pf->fd = PQsocket(conn->cur->db); pf->events = ev; pf->revents = 0; } /* wait for events */ res = poll(pfd_cache, numfds, 1000); if (res == 0) return 0; if (res < 0) { if (errno == EINTR) return 0; plproxy_error(func, "poll() failed: %s", strerror(errno)); } /* now recheck the conns */ pf = pfd_cache; for (i = 0; i < cluster->active_count; i++) { conn = cluster->active_list[i]; if (!conn->run_tag) continue; switch (conn->cur->state) { case C_DONE: case C_READY: case C_NONE: continue; case C_CONNECT_READ: case C_QUERY_READ: case C_CONNECT_WRITE: case C_QUERY_WRITE: break; } /* * they should be in same order as called, */ fd = PQsocket(conn->cur->db); if (pf->fd != fd) elog(WARNING, "fd order from poll() is messed up?"); if (pf->revents) handle_conn(func, conn); pf++; } 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->cur->state) { case C_CONNECT_READ: case C_CONNECT_WRITE: if (cf->connect_timeout <= 0) break; if (now - conn->cur->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->cur->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) { ExecStatusType err; ProxyConnection *conn; ProxyCluster *cluster = func->cur_cluster; int i, pending = 0; struct timeval now; /* either launch connection or send query */ for (i = 0; i < cluster->active_count; i++) { conn = cluster->active_list[i]; if (!conn->run_tag) continue; /* check if conn is alive, and launch if not */ prepare_conn(func, conn); pending++; /* if conn is ready, then send query away */ if (conn->cur->state == C_READY) send_query(func, conn, conn->param_values, conn->param_lengths, conn->param_formats); } /* now loop until all results are arrived */ 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->active_count; i++) { conn = cluster->active_list[i]; if (!conn->run_tag) continue; /* login finished, send query */ if (conn->cur->state == C_READY) send_query(func, conn, conn->param_values, conn->param_lengths, conn->param_formats); if (conn->cur->state != C_DONE) pending++; check_timeouts(func, cluster, conn, now.tv_sec); } } /* review results, calculate total */ for (i = 0; i < cluster->active_count; i++) { conn = cluster->active_list[i]; if ((conn->run_tag || conn->res) && !(conn->run_tag && conn->res)) plproxy_error(func, "run_tag does not match res"); if (!conn->run_tag) continue; if (conn->cur->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); } } static void remote_wait_for_cancel(ProxyFunction *func) { ProxyConnection *conn; ProxyCluster *cluster = func->cur_cluster; int i, pending; struct timeval now; /* now loop until all results are arrived */ while (1) { /* allow postgres to cancel processing */ CHECK_FOR_INTERRUPTS(); /* recheck */ pending = 0; gettimeofday(&now, NULL); for (i = 0; i < cluster->active_count; i++) { conn = cluster->active_list[i]; if (!conn->run_tag) continue; if (conn->cur->state == C_QUERY_READ) pending++; check_timeouts(func, cluster, conn, now.tv_sec); } if (!pending) break; /* wait for events */ poll_conns(func, cluster); } /* review results, calculate total */ for (i = 0; i < cluster->active_count; i++) { conn = cluster->active_list[i]; if (!conn->run_tag) continue; if (conn->cur->state != C_DONE && conn->cur->state != C_NONE) plproxy_error(func, "Unfinished connection: %d", conn->cur->state); if (conn->res != NULL) { PQclear(conn->res); conn->res = NULL; } } } static void remote_cancel(ProxyFunction *func) { ProxyConnection *conn; ProxyCluster *cluster = func->cur_cluster; PGcancel *cancel; char errbuf[256]; int ret; int i; if (cluster == NULL) return; for (i = 0; i < cluster->active_count; i++) { conn = cluster->active_list[i]; switch (conn->cur->state) { case C_NONE: case C_READY: case C_DONE: break; case C_QUERY_WRITE: case C_CONNECT_READ: case C_CONNECT_WRITE: plproxy_disconnect(conn->cur); break; case C_QUERY_READ: cancel = PQgetCancel(conn->cur->db); if (cancel == NULL) { elog(NOTICE, "Invalid connection!"); continue; } ret = PQcancel(cancel, errbuf, sizeof(errbuf)); PQfreeCancel(cancel); if (ret == 0) elog(NOTICE, "Cancel query failed!"); else conn->cur->waitCancel = 1; break; } } remote_wait_for_cancel(func); } /* * Tag & move tagged connections to active list */ static void tag_part(struct ProxyCluster *cluster, int i, int tag) { ProxyConnection *conn = cluster->part_map[i]; if (!conn->run_tag) plproxy_activate_connection(conn); conn->run_tag = tag; } /* * Run hash function and tag connections. If any of the hash function * arguments are mentioned in the split_arrays an element of the array * is used instead of the actual array. */ static void tag_hash_partitions(ProxyFunction *func, FunctionCallInfo fcinfo, int tag, DatumArray **array_params, int array_row) { int i; TupleDesc desc; Oid htype; ProxyCluster *cluster = func->cur_cluster; /* execute cached plan */ plproxy_query_exec(func, fcinfo, func->hash_sql, array_params, array_row); /* get header */ desc = SPI_tuptable->tupdesc; htype = SPI_gettypeid(desc, 1); /* tag connections */ for (i = 0; i < SPI_processed; i++) { bool isnull; uint32 hashval = 0; HeapTuple row = SPI_tuptable->vals[i]; Datum val = SPI_getbinval(row, desc, 1, &isnull); if (isnull) plproxy_error(func, "Hash function returned NULL"); if (htype == INT4OID) hashval = DatumGetInt32(val); else if (htype == INT8OID) hashval = DatumGetInt64(val); else if (htype == INT2OID) hashval = DatumGetInt16(val); else plproxy_error(func, "Hash result must be int2, int4 or int8"); hashval &= cluster->part_mask; tag_part(cluster, hashval, tag); } /* sanity check */ if (SPI_processed == 0 || SPI_processed > 1) if (!fcinfo->flinfo->fn_retset) plproxy_error(func, "Only set-returning function" " allows hashcount <> 1"); } /* * Deconstruct an array type to array of Datums, note NULL elements * and determine the element type information. */ static DatumArray * make_datum_array(ProxyFunction *func, ArrayType *v, ProxyType *array_type) { DatumArray *da = palloc0(sizeof(*da)); da->type = plproxy_get_elem_type(func, array_type, true); if (v) deconstruct_array(v, da->type->type_oid, da->type->length, da->type->by_value, da->type->alignment, &da->values, &da->nulls, &da->elem_count); return da; } /* * Evaluate the run condition. Tag the matching connections with the specified * tag. * * Note that we don't allow nested plproxy calls on the same cluster (ie. * remote hash functions). The cluster and connection state are global and * would easily get messed up. */ static void tag_run_on_partitions(ProxyFunction *func, FunctionCallInfo fcinfo, int tag, DatumArray **array_params, int array_row) { ProxyCluster *cluster = func->cur_cluster; int i; switch (func->run_type) { case R_HASH: tag_hash_partitions(func, fcinfo, tag, array_params, array_row); break; case R_ALL: for (i = 0; i < cluster->part_count; i++) tag_part(cluster, i, tag); break; case R_EXACT: i = func->exact_nr; if (i < 0 || i >= cluster->part_count) plproxy_error(func, "part number out of range"); tag_part(cluster, i, tag); break; case R_ANY: i = random() & cluster->part_mask; tag_part(cluster, i, tag); break; default: plproxy_error(func, "uninitialized run_type"); } } /* * Tag the partitions to be run on, if split is requested prepare the * per-partition split array parameters. * * This is done by looping over all of the split arrays side-by-side, for each * tuple see if it satisfies the RUN ON condition. If so, copy the tuple * to the partition's private array parameters. */ static void prepare_and_tag_partitions(ProxyFunction *func, FunctionCallInfo fcinfo) { int i, row, col; int split_array_len = -1; int split_array_count = 0; ProxyCluster *cluster = func->cur_cluster; DatumArray *arrays_to_split[FUNC_MAX_ARGS]; /* * See if we have any arrays to split. If so, make them manageable by * converting them to Datum arrays. During the process verify that all * the arrays are of the same length. */ for (i = 0; i < func->arg_count; i++) { ArrayType *v; if (!IS_SPLIT_ARG(func, i)) { arrays_to_split[i] = NULL; continue; } if (PG_ARGISNULL(i)) v = NULL; else { v = PG_GETARG_ARRAYTYPE_P(i); if (ARR_NDIM(v) > 1) plproxy_error(func, "split multi-dimensional arrays are not supported"); } arrays_to_split[i] = make_datum_array(func, v, func->arg_types[i]); /* Check that the element counts match */ if (split_array_len < 0) split_array_len = arrays_to_split[i]->elem_count; else if (arrays_to_split[i]->elem_count != split_array_len) plproxy_error(func, "split arrays must be of identical lengths"); ++split_array_count; } /* If nothing to split, just tag the partitions and be done with it */ if (!split_array_count) { tag_run_on_partitions(func, fcinfo, 1, NULL, 0); return; } /* Need to split, evaluate the RUN ON condition for each of the elements. */ for (row = 0; row < split_array_len; row++) { int part; int my_tag = row+1; /* * Tag the run-on partitions with a tag that allows us us to identify * which partitions need the set of elements from this row. */ tag_run_on_partitions(func, fcinfo, my_tag, arrays_to_split, row); /* Add the array elements to the partitions tagged in previous step */ for (part = 0; part < cluster->active_count; part++) { ProxyConnection *conn = cluster->active_list[part]; if (conn->run_tag != my_tag) continue; if (!conn->bstate) conn->bstate = palloc0(func->arg_count * sizeof(*conn->bstate)); /* Add this set of elements to the partition specific arrays */ for (col = 0; col < func->arg_count; col++) { if (!IS_SPLIT_ARG(func, col)) continue; conn->bstate[col] = accumArrayResult(conn->bstate[col], arrays_to_split[col]->values[row], arrays_to_split[col]->nulls[row], arrays_to_split[col]->type->type_oid, CurrentMemoryContext); } } } /* * Finally, copy the accumulated arrays to the actual connections * to be used as parameters. */ for (i = 0; i < cluster->active_count; i++) { ProxyConnection *conn = cluster->active_list[i]; if (!conn->run_tag) continue; conn->split_params = palloc(func->arg_count * sizeof(*conn->split_params)); for (col = 0; col < func->arg_count; col++) { if (!IS_SPLIT_ARG(func, col)) conn->split_params[col] = PointerGetDatum(NULL); else conn->split_params[col] = makeArrayResult(conn->bstate[col], CurrentMemoryContext); } } } /* * Prepare parameters for the query. */ static void prepare_query_parameters(ProxyFunction *func, FunctionCallInfo fcinfo) { int i; ProxyCluster *cluster = func->cur_cluster; for (i = 0; i < func->remote_sql->arg_count; i++) { int idx = func->remote_sql->arg_lookup[i]; bool bin = cluster->config.disable_binary ? 0 : 1; const char *fixed_param_val = NULL; int fixed_param_len, fixed_param_fmt; int part; /* Avoid doing multiple conversions for fixed parameters */ if (!IS_SPLIT_ARG(func, idx) && !PG_ARGISNULL(idx)) { fixed_param_val = plproxy_send_type(func->arg_types[idx], PG_GETARG_DATUM(idx), bin, &fixed_param_len, &fixed_param_fmt); } /* Add the parameters to partitions */ for (part = 0; part < cluster->active_count; part++) { ProxyConnection *conn = cluster->active_list[part]; if (!conn->run_tag) continue; if (PG_ARGISNULL(idx)) { conn->param_values[i] = NULL; conn->param_lengths[i] = 0; conn->param_formats[i] = 0; } else { if (IS_SPLIT_ARG(func, idx)) { conn->param_values[i] = plproxy_send_type(func->arg_types[idx], conn->split_params[idx], bin, &conn->param_lengths[i], &conn->param_formats[i]); } else { conn->param_values[i] = fixed_param_val; conn->param_lengths[i] = fixed_param_len; conn->param_formats[i] = fixed_param_fmt; } } } } } /* 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->active_count; i++) { conn = cluster->active_list[i]; if (conn->res) { PQclear(conn->res); conn->res = NULL; } conn->pos = 0; conn->run_tag = 0; conn->bstate = NULL; conn->cur = NULL; cluster->active_list[i] = NULL; } /* reset active_list */ cluster->active_count = 0; /* conn state checks are done in prepare_conn */ } /* Drop one connection */ void plproxy_disconnect(ProxyConnectionState *cur) { if (cur->db) PQfinish(cur->db); cur->db = NULL; cur->state = C_NONE; cur->tuning = 0; cur->connect_time = 0; cur->query_time = 0; cur->same_ver = 0; cur->tuning = 0; cur->waitCancel = 0; } /* Select partitions and execute query on them */ void plproxy_exec(ProxyFunction *func, FunctionCallInfo fcinfo) { /* * Prepare parameters and run query. On cancel, send cancel request to * partitions too. */ PG_TRY(); { func->cur_cluster->busy = true; func->cur_cluster->cur_func = func; /* clean old results */ plproxy_clean_results(func->cur_cluster); /* tag the partitions and prepare per-partition parameters */ prepare_and_tag_partitions(func, fcinfo); /* prepare the target query parameters */ prepare_query_parameters(func, fcinfo); remote_execute(func); func->cur_cluster->busy = false; } PG_CATCH(); { func->cur_cluster->busy = false; if (geterrcode() == ERRCODE_QUERY_CANCELED) remote_cancel(func); /* plproxy_remote_error() cannot clean itself, do it here */ plproxy_clean_results(func->cur_cluster); PG_RE_THROW(); } PG_END_TRY(); } plproxy-2.9/src/function.c000066400000000000000000000340271353753647500157110ustar00rootroot00000000000000/* * 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; } /* Find the index of a named parameter, -1 if not found */ int plproxy_get_parameter_index(ProxyFunction *func, const char *ident) { int i; if (ident[0] == '$') { /* Probably a $# parameter reference */ i = atoi(ident + 1) - 1; if (i >= 0 && i < func->arg_count) return i; } else if (func->arg_names) { /* Named parameter, go through the argument names */ for (i = 0; i < func->arg_count; i++) { if (!func->arg_names[i]) continue; if (pg_strcasecmp(ident, func->arg_names[i]) == 0) return i; } } return -1; } /* Add a new split argument by position */ static void plproxy_split_add_arg(ProxyFunction *func, int argindex) { if (!func->split_args) { size_t alloc_size = sizeof(*func->split_args) * func->arg_count; func->split_args = plproxy_func_alloc(func, alloc_size); MemSet(func->split_args, 0, alloc_size); } func->split_args[argindex] = true; } /* Add a new split argument by argument name */ bool plproxy_split_add_ident(ProxyFunction *func, const char *ident) { int argindex; if ((argindex = plproxy_get_parameter_index(func, ident)) < 0) return false; /* Already split? */ if (IS_SPLIT_ARG(func, argindex)) plproxy_error(func, "SPLIT parameter specified more than once: %s", ident); /* Is it an array? */ if (!func->arg_types[argindex]->is_array) plproxy_error(func, "SPLIT parameter is not an array: %s", ident); plproxy_split_add_arg(func, argindex); return true; } /* Tag all array arguments for splitting */ void plproxy_split_all_arrays(ProxyFunction *func) { int i; for (i = 0; i < func->arg_count; i++) { if (func->arg_types[i]->is_array) plproxy_split_add_arg(func, i); } } /* 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) { #ifdef USE_ASSERT_CHECKING HashEntry *hentry; hentry = hash_search(fn_cache, &func->oid, HASH_REMOVE, NULL); Assert(hentry != NULL); #else hash_search(fn_cache, &func->oid, HASH_REMOVE, NULL); #endif } /* check if function returns untyped RECORD which needs the AS clause */ static bool fn_returns_dynamic_record(HeapTuple proc_tuple) { Form_pg_proc proc_struct; proc_struct = (Form_pg_proc) GETSTRUCT(proc_tuple); if (proc_struct->prorettype == RECORDOID && (heap_attisnull(proc_tuple, Anum_pg_proc_proargmodes #if PG_VERSION_NUM >= 110000 , NULL #endif ) || heap_attisnull(proc_tuple, Anum_pg_proc_proargnames #if PG_VERSION_NUM >= 110000 , NULL #endif ))) return true; return false; } /* * Allocate storage for function. * * Each functions has its own MemoryContext, * where everything is allocated. */ static ProxyFunction * fn_new(HeapTuple proc_tuple) { ProxyFunction *f; MemoryContext f_ctx, old_ctx; f_ctx = AllocSetContextCreate(TopMemoryContext, "PL/Proxy function context", ALLOCSET_SMALL_SIZES); old_ctx = MemoryContextSwitchTo(f_ctx); f = palloc0(sizeof(*f)); f->ctx = f_ctx; f->oid = XProcTupleGetOid(proc_tuple); plproxy_set_stamp(&f->stamp, proc_tuple); if (fn_returns_dynamic_record(proc_tuple)) f->dynamic_record = 1; 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); plproxy_query_freeplan(func->connect_sql); /* release function storage */ MemoryContextDelete(func->ctx); } /* * Construct fully-qualified name for function. */ static void fn_set_name(ProxyFunction *func, HeapTuple proc_tuple) { /* 2 names, size can double, "" + . + "" + NUL */ char namebuf[NAMEDATALEN * 4 + 2 + 1 + 2 + 1]; 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); snprintf(namebuf, sizeof(namebuf), "%s.%s", quote_identifier(NameStr(ns_struct->nspname)), quote_identifier(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 src_raw, src_detoast; char *data; int size; src_raw = SysCacheGetAttr(PROCOID, proc_tuple, Anum_pg_proc_prosrc, &isnull); if (isnull) plproxy_error(func, "procedure source datum is null"); src_detoast = PointerGetDatum(PG_DETOAST_DATUM_PACKED(src_raw)); data = VARDATA_ANY(src_detoast); size = VARSIZE_ANY_EXHDR(src_detoast); plproxy_run_parser(func, data, size); if (src_raw != src_detoast) pfree(DatumGetPointer(src_detoast)); } /* * Get info about own arguments. */ static void fn_get_arguments(ProxyFunction *func, 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++) { char mode = modes ? modes[i] : PROARGMODE_IN; switch (mode) { case PROARGMODE_IN: case PROARGMODE_INOUT: 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; break; case PROARGMODE_VARIADIC: elog(ERROR, "PL/Proxy does not support variadic args"); break; case PROARGMODE_OUT: case PROARGMODE_TABLE: /* output args, ignore */ break; default: elog(ERROR, "PL/Proxy: unknown value in proargmodes: %c", mode); break; } } } /* * 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; /* * get_call_result_type() will return newly allocated tuple, * except in case of untyped RECORD functions. */ old_ctx = MemoryContextSwitchTo(func->ctx); rtc = get_call_result_type(fcinfo, &ret_oid, &ret_tup); if (func->dynamic_record && ret_tup) ret_tup = CreateTupleDescCopy(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: #if PG_VERSION_NUM >= 110000 case TYPEFUNC_COMPOSITE_DOMAIN: #endif /* fixme: void type here? */ plproxy_error(func, "unsupported type"); break; } } /* * Check if cached ->ret_composite is valid, refresh if needed. */ static void fn_refresh_record(FunctionCallInfo fcinfo, ProxyFunction *func, HeapTuple proc_tuple) { TupleDesc tuple_current, tuple_cached; MemoryContext old_ctx; Oid tuple_oid; int natts; TypeFuncClass rtc; /* * Compare cached tuple to current one. */ tuple_cached = func->ret_composite->tupdesc; rtc = get_call_result_type(fcinfo, &tuple_oid, &tuple_current); if (rtc != TYPEFUNC_COMPOSITE) { elog(ERROR, "Function used in wrong context"); } if (equalTupleDescs(tuple_current, tuple_cached)) return; /* move to function context */ old_ctx = MemoryContextSwitchTo(func->ctx); tuple_current = CreateTupleDescCopy(tuple_current); MemoryContextSwitchTo(old_ctx); /* release old data */ plproxy_free_composite(func->ret_composite); pfree(func->result_map); pfree(func->remote_sql); /* construct new data */ func->ret_composite = plproxy_composite_info(func, tuple_current); natts = func->ret_composite->tupdesc->natts; func->result_map = plproxy_func_alloc(func, natts * sizeof(int)); func->remote_sql = plproxy_standard_query(func, true); } /* * Show part of compilation -- get source and parse * * When called from the validator, validate_only is true, but there is no * fcinfo. */ ProxyFunction * plproxy_compile(FunctionCallInfo fcinfo, HeapTuple proc_tuple, bool validate_only) { ProxyFunction *f; Form_pg_proc proc_struct; Assert(fcinfo || validate_only); proc_struct = (Form_pg_proc) GETSTRUCT(proc_tuple); if (proc_struct->provolatile != PROVOLATILE_VOLATILE) elog(ERROR, "PL/Proxy functions must be volatile"); f = fn_new(proc_tuple); /* keep reference in case of error half-way */ if (!validate_only) partial_func = f; /* info from system tables */ fn_set_name(f, proc_tuple); /* * Cannot check return type in validator, because there is no call info to * resolve polymorphic types against. */ if (!validate_only) fn_get_return_type(f, fcinfo, proc_tuple); fn_get_arguments(f, proc_tuple); /* parse body */ fn_parse(f, proc_tuple); if (f->dynamic_record && f->remote_sql) plproxy_error(f, "SELECT statement not allowed for dynamic RECORD functions"); /* sanity check */ if (f->run_type == R_ALL && (fcinfo ? !fcinfo->flinfo->fn_retset : !get_func_retset(XProcTupleGetOid(proc_tuple)))) plproxy_error(f, "RUN ON ALL requires set-returning function"); return f; } /* * Compile and cache PL/Proxy function. */ ProxyFunction * plproxy_compile_and_cache(FunctionCallInfo fcinfo) { 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); /* if cached, is it still valid? */ if (f && !plproxy_check_stamp(&f->stamp, proc_tuple)) { fn_delete(f, true); f = NULL; } if (!f) { f = plproxy_compile(fcinfo, proc_tuple, false); /* 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, false); if (f->hash_sql) plproxy_query_prepare(f, fcinfo, f->hash_sql, true); if (f->connect_sql) plproxy_query_prepare(f, fcinfo, f->connect_sql, false); fn_cache_insert(f); /* now its safe to drop reference */ partial_func = NULL; } else if (f->dynamic_record) { /* in case of untyped RECORD, check if cached type is valid */ fn_refresh_record(fcinfo, f, proc_tuple); } else if (f->ret_composite) { if (!plproxy_composite_valid(f->ret_composite)) fn_refresh_record(fcinfo, f, proc_tuple); } ReleaseSysCache(proc_tuple); return f; } plproxy-2.9/src/main.c000066400000000000000000000170721353753647500150110ustar00rootroot00000000000000/* * 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 #ifndef PG_MODULE_MAGIC #error PL/Proxy requires 8.2 #else PG_MODULE_MAGIC; #endif PG_FUNCTION_INFO_V1(plproxy_call_handler); PG_FUNCTION_INFO_V1(plproxy_validator); /* * Centralised error reporting. * * Also frees any pending results. */ void plproxy_error_with_state(ProxyFunction *func, int sqlstate, 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); ereport(ERROR, ( errcode(sqlstate), errmsg("PL/Proxy function %s(%d): %s", func->name, func->arg_count, msg))); } /* * Pass remote error/notice/warning through. */ void plproxy_remote_error(ProxyFunction *func, ProxyConnection *conn, const PGresult *res, bool iserr) { const char *ss = PQresultErrorField(res, PG_DIAG_SQLSTATE); const char *sev = PQresultErrorField(res, PG_DIAG_SEVERITY); const char *msg = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY); const char *det = PQresultErrorField(res, PG_DIAG_MESSAGE_DETAIL); const char *hint = PQresultErrorField(res, PG_DIAG_MESSAGE_HINT); const char *spos = PQresultErrorField(res, PG_DIAG_STATEMENT_POSITION); const char *ipos = PQresultErrorField(res, PG_DIAG_INTERNAL_POSITION); const char *iquery = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY); const char *ctx = PQresultErrorField(res, PG_DIAG_CONTEXT); int elevel; /* libpq errors may not have sqlstate */ if (!ss) ss = "XX000"; if (iserr) /* must ignore remote level, as it may be FATAL/PANIC */ elevel = ERROR; else /* cannot look at sev here, as it may be localized */ elevel = !strncmp(ss, "00", 2) ? NOTICE : WARNING; ereport(elevel, ( errcode(MAKE_SQLSTATE(ss[0], ss[1], ss[2], ss[3], ss[4])), errmsg("%s(%d): [%s] REMOTE %s: %s", func->name, func->arg_count, PQdb(conn->cur->db), sev, msg), det ? errdetail("Remote detail: %s", det) : 0, hint ? errhint("Remote hint: %s", hint) : 0, spos ? errposition(atoi(spos)) : 0, ipos ? internalerrposition(atoi(ipos)) : 0, iquery ? internalerrquery(iquery) : 0, ctx ? errcontext("Remote context: %s", ctx) : 0)); } /* * Library load-time initialization. * Do the initialization when SPI is active to simplify the code. */ static bool initialized = false; static void plproxy_startup_init(void) { if (initialized) return; plproxy_function_cache_init(); plproxy_cluster_cache_init(); plproxy_syscache_callback_init(); initialized = true; } /* * Regular maintenance over all clusters. */ static void run_maint(void) { static struct timeval last = {0, 0}; struct timeval now; if (!initialized) return; 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_and_cache(fcinfo); /* get actual cluster to run on */ cluster = plproxy_find_cluster(func, fcinfo); /* Don't allow nested calls on the same cluster */ if (cluster->busy) plproxy_error(func, "Nested PL/Proxy calls to the same cluster are not supported."); /* 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); if (func->cur_cluster->ret_total != 1) plproxy_error_with_state(func, (func->cur_cluster->ret_total < 1) ? ERRCODE_NO_DATA_FOUND : ERRCODE_TOO_MANY_ROWS, "Non-SETOF function requires 1 row from remote query, got %d", func->cur_cluster->ret_total); ret = plproxy_result(func, fcinfo); plproxy_clean_results(func->cur_cluster); } return ret; } /* * This function is called when a PL/Proxy function is created to * check the syntax. */ Datum plproxy_validator(PG_FUNCTION_ARGS) { Oid oid = PG_GETARG_OID(0); HeapTuple proc_tuple; if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, oid)) PG_RETURN_VOID(); proc_tuple = SearchSysCache(PROCOID, ObjectIdGetDatum(oid), 0, 0, 0); if (!HeapTupleIsValid(proc_tuple)) elog(ERROR, "cache lookup failed for function %u", oid); plproxy_compile(NULL, proc_tuple, true); ReleaseSysCache(proc_tuple); PG_RETURN_VOID(); } plproxy-2.9/src/parser.y000066400000000000000000000162741353753647500154120ustar00rootroot00000000000000%{ /* * 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 YYMALLOC palloc #define YYFREE pfree /* 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, got_split, got_target; static QueryBuffer *cluster_sql; static QueryBuffer *select_sql; static QueryBuffer *hash_sql; static QueryBuffer *connect_sql; /* points to one of the above ones */ static QueryBuffer *cur_sql; /* keep the resetting code together with variables */ static void reset_parser_vars(void) { got_run = got_cluster = got_connect = got_split = got_target = 0; cur_sql = select_sql = cluster_sql = hash_sql = connect_sql = NULL; xfunc = NULL; } %} /* * Preferred syntax in Bison 2.0+ is [%name-prefix "plproxy_yy"]. * Documentation for building PostgreSQL 9.6 states that Bison 1.875 or later * is required, assume it is safe to consider a recent version of Bison. */ %define api.prefix {plproxy_yy} %token CONNECT CLUSTER RUN ON ALL ANY SELECT %token IDENT NUMBER FNCALL SPLIT STRING %token SQLIDENT SQLPART TARGET %union { const char *str; } %% body: | body stmt ; stmt: cluster_stmt | split_stmt | run_stmt | select_stmt | connect_stmt | target_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: connect_func sql_token_list | connect_name | connect_direct ; connect_direct: IDENT { connect_sql = plproxy_query_start(xfunc, false); cur_sql = connect_sql; plproxy_query_add_const(cur_sql, "select "); if (!plproxy_query_add_ident(cur_sql, $1)) yyerror("invalid argument reference: %s", $1); } ; connect_name: STRING { xfunc->connect_str = plproxy_func_strdup(xfunc, $1); } ; connect_func: FNCALL { connect_sql = plproxy_query_start(xfunc, false); cur_sql = connect_sql; plproxy_query_add_const(cur_sql, "select * from "); plproxy_query_add_const(cur_sql, $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); } ; target_stmt: TARGET target_name ';' { if (got_target) yyerror("Only one TARGET statement allowed"); got_target = 1; } ; target_name: IDENT { xfunc->target_name = plproxy_func_strdup(xfunc, $1); } ; split_stmt: SPLIT split_spec ';' { if (got_split) yyerror("Only one SPLIT statement allowed"); got_split = 1; } ; split_spec: ALL { plproxy_split_all_arrays(xfunc); } | split_param_list ; split_param_list: split_param | split_param_list ',' split_param ; split_param: IDENT { if (!plproxy_split_add_ident(xfunc, $1)) yyerror("invalid argument reference: %s", $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_direct { xfunc->run_type = R_HASH; } ; hash_direct: IDENT { hash_sql = plproxy_query_start(xfunc, false); cur_sql = hash_sql; plproxy_query_add_const(cur_sql, "select "); if (!plproxy_query_add_ident(cur_sql, $1)) yyerror("invalid argument reference: %s", $1); } ; 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) { /* reset variables, in case there was error exit */ reset_parser_vars(); /* make current function visible to parser */ xfunc = func; /* By default expect RUN ON ANY; */ xfunc->run_type = R_ANY; /* reinitialize scanner */ plproxy_yylex_startup(); /* 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"); } /* disallow SELECT if requested */ #if NO_SELECT if (select_sql) yyerror("SELECT statement not allowed"); #endif if (select_sql && got_target) yyerror("TARGET cannot be used with SELECT"); /* release scanner resources */ 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); if (connect_sql) xfunc->connect_sql = plproxy_query_finish(connect_sql); reset_parser_vars(); } plproxy-2.9/src/plproxy.h000066400000000000000000000363271353753647500156130ustar00rootroot00000000000000/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils/inval.h" #include #include #include #include "aatree.h" #include "rowstamp.h" #if PG_VERSION_NUM < 90300 #error PL/Proxy requires 9.3+ #endif /* give offset of a field inside struct */ #ifndef offsetof #define offsetof(type, field) ((unsigned long)&(((type *)0)->field)) #endif /* given pointer to field inside struct, return pointer to struct */ #ifndef container_of #define container_of(ptr, type, field) ((type *)((char *)(ptr) - offsetof(type, field))) #endif /* * backwards compat with 8.2 */ #ifndef VARDATA_ANY #define VARDATA_ANY(x) VARDATA(x) #endif #ifndef VARSIZE_ANY_EXHDR #define VARSIZE_ANY_EXHDR(x) (VARSIZE(x) - VARHDRSZ) #endif #ifndef PG_DETOAST_DATUM_PACKED #define PG_DETOAST_DATUM_PACKED(x) PG_DETOAST_DATUM(x) #endif #ifndef PG_PRINTF_ATTRIBUTE #ifdef WIN32 #define PG_PRINTF_ATTRIBUTE gnu_printf #else #define PG_PRINTF_ATTRIBUTE printf #endif #endif /* * backwards compat with 8.4 */ #ifndef PROARGMODE_IN #define PROARGMODE_IN 'i' #endif #ifndef PROARGMODE_OUT #define PROARGMODE_OUT 'o' #endif #ifndef PROARGMODE_INOUT #define PROARGMODE_INOUT 'b' #endif #ifndef PROARGMODE_VARIADIC #define PROARGMODE_VARIADIC 'v' #endif #ifndef PROARGMODE_TABLE #define PROARGMODE_TABLE 't' #endif #ifndef TYPTYPE_BASE #define TYPTYPE_BASE 'b' #endif #ifndef TYPTYPE_COMPOSITE #define TYPTYPE_COMPOSITE 'c' #endif #ifndef TYPTYPE_DOMAIN #define TYPTYPE_DOMAIN 'd' #endif #ifndef TYPTYPE_ENUM #define TYPTYPE_ENUM 'e' #endif #ifndef TYPTYPE_PSEUDO #define TYPTYPE_PSEUDO 'p' #endif #ifndef TYPTYPE_RANGE #define TYPTYPE_RANGE 'r' #endif #ifndef TupleDescAttr #define TupleDescAttr(tupdesc, i) ((tupdesc)->attrs[(i)]) #endif /* * backwards compatibility with v10. */ #if PG_VERSION_NUM >= 110000 #define ACL_KIND_FOREIGN_SERVER OBJECT_FOREIGN_SERVER #endif /* * Determine if this argument is to SPLIT */ #define IS_SPLIT_ARG(func, arg) ((func)->split_args && (func)->split_args[arg]) /* * 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. * Set 0 to always check. */ #define PLPROXY_IDLE_CONN_CHECK 2 /* 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 disable_binary; /* Avoid binary I/O */ char default_user[NAMEDATALEN]; } ProxyConfig; typedef struct ConnUserInfo { struct AANode node; Oid user_oid; char *username; char *extra_connstr; /* user= and password= */ SysCacheStamp umStamp; bool needs_reload; } ConnUserInfo; typedef struct ProxyConnectionState { struct AANode node; /* node head in user->state tree */ ConnUserInfo *userinfo; PGconn *db; /* libpq connection handle */ ConnState state; /* Connection state */ time_t connect_time; /* When connection was started */ time_t query_time; /* When last query was sent */ bool same_ver; /* True if dest backend has same X.Y ver */ bool tuning; /* True if tuning query is running on conn */ bool waitCancel; /* True if waiting for answer from cancel */ } ProxyConnectionState; /* Single database connection */ typedef struct ProxyConnection { struct AANode node; struct ProxyCluster *cluster; const char *connstr; /* Connection string for libpq */ struct AATree userstate_tree; /* user->state tree */ /* state */ PGresult *res; /* last resultset */ int pos; /* Current position inside res */ ProxyConnectionState *cur; /* * Nonzero if this connection should be used. The actual tag value is only * used by SPLIT processing, others should treat it as a boolean value. */ int run_tag; /* * Per-connection parameters. These are a assigned just before the * remote call is made. */ Datum *split_params; /* Split array parameters */ ArrayBuildState **bstate; /* Temporary build state */ const char *param_values[FUNC_MAX_ARGS]; /* Parameter values */ int param_lengths[FUNC_MAX_ARGS]; /* Parameter lengths (binary io) */ int param_formats[FUNC_MAX_ARGS]; /* Parameter formats (binary io) */ } ProxyConnection; /* Info about one cluster */ typedef struct ProxyCluster { struct AANode node; /* Node in name => cluster lookup tree */ 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 ProxyConnections */ int active_count; /* number of active connections */ ProxyConnection **active_list; /* active ProxyConnection in current query */ struct AATree conn_tree; /* connstr -> ProxyConnection */ struct AATree userinfo_tree; /* username->userinfo tree */ ConnUserInfo *cur_userinfo; /* userinfo struct for current request */ 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 */ Oid sqlmed_server_oid; bool fake_cluster; /* single connect-string cluster */ bool sqlmed_cluster; /* True if the cluster is defined using SQL/MED */ bool needs_reload; /* True if the cluster partition list should be reloaded */ bool busy; /* True if the cluster is already involved in execution */ /* * SQL/MED clusters: TIDs of the foreign server and user mapping catalog tuples. * Used in to perform cluster invalidation in syscache callbacks. */ SysCacheStamp clusterStamp; /* notice processing: provide info about currently executing function */ struct ProxyFunction *cur_func; } 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 { char *name; /* Name of the type */ Oid type_oid; /* Oid of the type */ Oid io_param; /* Extra arg for input_func */ bool for_send; /* True if for outputting */ bool has_send; /* Has binary output */ bool has_recv; /* Has binary input */ bool by_value; /* False if Datum is a pointer to data */ char alignment; /* Type alignment */ bool is_array; /* True if array */ Oid elem_type_oid; /* Array element type oid */ struct ProxyType *elem_type_t; /* Elem type info, filled lazily */ short length; /* Type length */ /* I/O functions */ union { struct { FmgrInfo output_func; FmgrInfo send_func; } out; struct { FmgrInfo input_func; FmgrInfo recv_func; } in; } io; } 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; /* Quoted column names */ int nfields; /* number of non-dropped fields */ bool use_binary; /* True if all columns support binary recv */ bool alterable; /* if it's real table that can change */ RowStamp stamp; } 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; /* * Deconstructed array parameters */ typedef struct DatumArray { ProxyType *type; Datum *values; bool *nulls; int elem_count; } DatumArray; /* * Complete info about compiled function. * * Note: only IN and INOUT arguments are cached here. */ typedef struct ProxyFunction { const char *name; /* Fully-qualified and quoted function name */ Oid oid; /* Function OID */ MemoryContext ctx; /* Where runtime allocations should happen */ RowStamp stamp; /* for pg_proc cache validation */ ProxyType **arg_types; /* Info about arguments */ char **arg_names; /* Argument names, may contain NULLs */ short arg_count; /* Argument count of proxy function */ bool *split_args; /* Map of arguments to split */ /* if the function returns untyped RECORD that needs AS clause */ bool dynamic_record; /* 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 */ ProxyQuery *connect_sql; /* Optional query for CONNECT function */ const char *target_name; /* Optional target function name */ /* * 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); Datum plproxy_validator(PG_FUNCTION_ARGS); void plproxy_error_with_state(ProxyFunction *func, int sqlstate, const char *fmt, ...) __attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 4))); void plproxy_remote_error(ProxyFunction *func, ProxyConnection *conn, const PGresult *res, bool iserr); #define plproxy_error(func,...) plproxy_error_with_state((func), ERRCODE_INTERNAL_ERROR, __VA_ARGS__) /* 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); int plproxy_get_parameter_index(ProxyFunction *func, const char *ident); bool plproxy_split_add_ident(ProxyFunction *func, const char *ident); void plproxy_split_all_arrays(ProxyFunction *func); ProxyFunction *plproxy_compile_and_cache(FunctionCallInfo fcinfo); ProxyFunction *plproxy_compile(FunctionCallInfo fcinfo, HeapTuple proc_tuple, bool validate_only); /* execute.c */ void plproxy_exec(ProxyFunction *func, FunctionCallInfo fcinfo); void plproxy_clean_results(ProxyCluster *cluster); void plproxy_disconnect(ProxyConnectionState *cur); /* scanner.c */ int plproxy_yyget_lineno(void); int plproxy_yylex_destroy(void); int plproxy_yylex(void); void plproxy_scanner_sqlmode(bool val); void plproxy_yylex_startup(void); /* parser.y */ void plproxy_run_parser(ProxyFunction *func, const char *body, int len); void plproxy_yyerror(const char *fmt, ...) __attribute__((format(PG_PRINTF_ATTRIBUTE, 1, 2))); /* type.c */ ProxyComposite *plproxy_composite_info(ProxyFunction *func, TupleDesc tupdesc); ProxyType *plproxy_find_type_info(ProxyFunction *func, Oid oid, bool for_send); ProxyType *plproxy_get_elem_type(ProxyFunction *func, ProxyType *type, 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); void plproxy_free_type(ProxyType *type); void plproxy_free_composite(ProxyComposite *meta); bool plproxy_composite_valid(ProxyComposite *type); /* cluster.c */ void plproxy_cluster_cache_init(void); void plproxy_syscache_callback_init(void); ProxyCluster *plproxy_find_cluster(ProxyFunction *func, FunctionCallInfo fcinfo); void plproxy_cluster_maint(struct timeval * now); void plproxy_activate_connection(struct ProxyConnection *conn); /* 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, bool split_support); void plproxy_query_exec(ProxyFunction *func, FunctionCallInfo fcinfo, ProxyQuery *q, DatumArray **array_params, int array_row); void plproxy_query_freeplan(ProxyQuery *q); #endif plproxy-2.9/src/poll_compat.c000066400000000000000000000055631353753647500164000ustar00rootroot00000000000000 #include "postgres.h" #include "poll_compat.h" #ifdef PLPROXY_POLL_COMPAT /* * Emulate poll() with select() */ #include #ifdef HAVE_SYS_SELECT_H #include #endif /* * dynamic buffer for fd_set to avoid depending on FD_SETSIZE */ struct fd_buf { fd_set *set; int alloc_bytes; }; static void fdbuf_zero(struct fd_buf *buf) { if (buf->set) memset(buf->set, 0, buf->alloc_bytes); } static bool fdbuf_resize(struct fd_buf *buf, int fd) { int need_bytes; unsigned char *ptr; /* default allocation */ int alloc = sizeof(fd_set); #ifdef WIN32 int cnt = buf->set ? buf->set->fd_count : 0; /* win32 fd_set is array of handles, +8 for count&padding */ need_bytes = (cnt + 1) * sizeof(buf->set->fd_array[0]) + 8; #else /* otherwise, fd_set is bitmap, +8 for int/long alignment */ need_bytes = fd / 8 + 8; #endif if (buf->alloc_bytes < need_bytes) { while (alloc < need_bytes) alloc *= 2; if (!buf->set) ptr = malloc(alloc); else ptr = realloc(buf->set, alloc); if (!ptr) return false; /* clean new area */ memset(ptr + buf->alloc_bytes, 0, alloc - buf->alloc_bytes); buf->set = (fd_set *)ptr; buf->alloc_bytes = alloc; } return true; } /* win32: make macros ignore FD_SETSIZE */ #undef FD_SETSIZE #define FD_SETSIZE (1 << 30) int poll(struct pollfd *fds, nfds_t nfds, int timeout_ms) { static struct fd_buf readfds = { NULL, 0 }; static struct fd_buf writefds = { NULL, 0 }; struct pollfd *pf; int res, fd_max = 0; struct timeval *tv = NULL; struct timeval tvreal; unsigned i; /* convert timeout_ms to timeval */ if (timeout_ms >= 0) { tvreal.tv_sec = timeout_ms / 1000; tvreal.tv_usec = (timeout_ms % 1000) * 1000; tv = &tvreal; } else if (timeout_ms < -1) goto err_inval; /* * Convert pollfds to fd sets. */ fdbuf_zero(&readfds); fdbuf_zero(&writefds); for (i = 0; i < nfds; i++) { pf = fds + i; if (pf->fd < 0) goto err_badf; /* sets must be equal size */ if (!fdbuf_resize(&readfds, pf->fd)) goto err_nomem; if (!fdbuf_resize(&writefds, pf->fd)) goto err_nomem; if (pf->events & POLLIN) FD_SET((unsigned)pf->fd, readfds.set); if (pf->events & POLLOUT) FD_SET((unsigned)pf->fd, writefds.set); if (pf->fd > fd_max) fd_max = pf->fd; } res = select(fd_max + 1, readfds.set, writefds.set, NULL, tv); if (res <= 0) return res; /* * select() and poll() count fd-s differently, * need to recount them here. */ res = 0; for (i = 0; i < nfds; i++) { pf = fds + i; pf->revents = 0; if ((pf->events & POLLIN) && FD_ISSET(pf->fd, readfds.set)) pf->revents |= POLLIN; if ((pf->events & POLLOUT) && FD_ISSET(pf->fd, writefds.set)) pf->revents |= POLLOUT; if (pf->revents) res += 1; } return res; err_nomem: errno = ENOMEM; return -1; err_badf: errno = EBADF; return -1; err_inval: errno = EINVAL; return -1; } #endif /* PLPROXY_POLL_COMPAT */ plproxy-2.9/src/poll_compat.h000066400000000000000000000020001353753647500163640ustar00rootroot00000000000000 #ifndef POLL_COMPAT_H #define POLL_COMPAT_H /* define to test poll() compat function */ #if 0 #define PLPROXY_POLL_COMPAT #endif #include /* see if real poll() can be used */ #ifndef PLPROXY_POLL_COMPAT #ifdef HAVE_POLL_H #include #else #ifdef HAVE_SYS_POLL_H #include #else #define PLPROXY_POLL_COMPAT #endif #endif #endif /* * Emulate poll() with select(), if needed. */ #ifdef PLPROXY_POLL_COMPAT /* in/out event types */ #define POLLIN (1 << 0) #define POLLOUT (1 << 1) /* rest are unused in this implementation */ #define POLLHUP (1 << 2) #define POLLPRI (1 << 3) #define POLLNVAL (1 << 4) #define POLLERR (1 << 5) /* avoid namespace conflicts */ #define pollfd plproxy_compat_pollfd #define poll plproxy_compat_poll #define nfds_t plproxy_compat_nfds_t struct pollfd { int fd; short events; short revents; }; typedef unsigned long nfds_t; int poll(struct pollfd *fds, nfds_t nfds, int timeout_ms); #endif /* PLPROXY_POLL_COMPAT */ #endif /* POLL_COMPAT_H */ plproxy-2.9/src/query.c000066400000000000000000000157001353753647500152260ustar00rootroot00000000000000/* * 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[1 + 3 + 2 + NAMEDATALEN*2 + 1]; if (add_type) snprintf(tmp, sizeof(tmp), "$%d::%s", sql_idx + 1, func->arg_types[fn_idx]->name); else snprintf(tmp, sizeof(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; fn_idx = plproxy_get_parameter_index(q->func, ident); 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 { if (ident[0] == '$') return false; 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 function call based on own signature. */ ProxyQuery * plproxy_standard_query(ProxyFunction *func, bool add_types) { StringInfoData sql; ProxyQuery *pq; const char *target; 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 "); /* try to fill in all result column names */ if (func->ret_composite) { ProxyComposite *t = func->ret_composite; for (i = 0; i < t->tupdesc->natts; i++) { if (TupleDescAttr(t->tupdesc, i)->attisdropped) continue; appendStringInfo(&sql, "%s%s::%s", ((i > 0) ? ", " : ""), t->name_list[i], t->type_list[i]->name); } } else /* names not available, do a simple query */ appendStringInfo(&sql, "r::%s", func->ret_scalar->name); /* function call */ target = func->target_name ? func->target_name : func->name; appendStringInfo(&sql, " from %s(", target); /* fill in function arguments */ 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, ')'); /* * Untyped RECORD needs types specified in AS (..) clause. */ if (func->dynamic_record) { ProxyComposite *t = func->ret_composite; appendStringInfo(&sql, " as ("); for (i = 0; i < t->tupdesc->natts; i++) { if (TupleDescAttr(t->tupdesc, i)->attisdropped) continue; appendStringInfo(&sql, "%s%s %s", ((i > 0) ? ", " : ""), t->name_list[i], t->type_list[i]->name); } appendStringInfoChar(&sql, ')'); } if (func->ret_scalar) appendStringInfo(&sql, " r"); 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, bool split_support) { 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]; if (split_support && IS_SPLIT_ARG(func, idx)) /* for SPLIT arguments use array element type instead */ types[i] = func->arg_types[idx]->elem_type_oid; else 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, DatumArray **array_params, int array_row) { int i, idx, err; 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]; if (PG_ARGISNULL(idx)) { arg_nulls[i] = 'n'; arg_values[i] = (Datum) NULL; } else if (array_params && IS_SPLIT_ARG(func, idx)) { DatumArray *ats = array_params[idx]; arg_nulls[i] = ats->nulls[array_row] ? 'n' : ' '; arg_values[i] = ats->nulls[array_row] ? (Datum) NULL : ats->values[array_row]; } 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; } plproxy-2.9/src/result.c000066400000000000000000000124121353753647500153740ustar00rootroot00000000000000/* * 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 (strcmp(aname, fname) == 0) return true; return false; } /* fill func->result_map */ static void map_results(ProxyFunction *func, PGresult *res) { int i, /* non-dropped column index */ xi, /* tupdesc index */ j, /* result column index */ 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 < func->ret_composite->nfields) plproxy_error(func, "Got too few fields from remote end"); if (nfields > func->ret_composite->nfields) plproxy_error(func, "Got too many fields from remote end"); for (i = -1, xi = 0; xi < natts; xi++) { /* ->name_list has quoted names, take unquoted from ->tupdesc */ a = TupleDescAttr(func->ret_composite->tupdesc, xi); func->result_map[xi] = -1; if (a->attisdropped) continue; i++; aname = NameStr(a->attname); if (name_matches(func, aname, res, i)) /* fast case: 1:1 mapping */ func->result_map[xi] = 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[xi] = j; break; } } } if (func->result_map[xi] < 0) plproxy_error(func, "Field %s does not exists in result", aname); } } /* Return connection where are unreturned rows */ static ProxyConnection * walk_results(ProxyFunction *func, ProxyCluster *cluster) { ProxyConnection *conn; for (; cluster->ret_cur_conn < cluster->active_count; cluster->ret_cur_conn++) { conn = cluster->active_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; int *fmts; int *lengths; HeapTuple tup; ProxyComposite *meta = func->ret_composite; values = palloc(meta->tupdesc->natts * sizeof(char *)); fmts = palloc(meta->tupdesc->natts * sizeof(int)); lengths = palloc(meta->tupdesc->natts * sizeof(int)); for (i = 0; i < meta->tupdesc->natts; i++) { col = func->result_map[i]; if (col < 0 || 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); pfree(lengths); pfree(fmts); pfree(values); 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; } plproxy-2.9/src/rowstamp.h000066400000000000000000000027701353753647500157450ustar00rootroot00000000000000 static inline Oid XProcTupleGetOid(HeapTuple proc_tup) { #if PG_VERSION_NUM >= 120000 Form_pg_proc proc_struct; proc_struct = (Form_pg_proc) GETSTRUCT(proc_tup); return proc_struct->oid; #else return HeapTupleGetOid(proc_tup); #endif } static inline Oid XNamespaceTupleGetOid(HeapTuple ns_tup) { #if PG_VERSION_NUM >= 120000 Form_pg_namespace ns_struct; ns_struct = (Form_pg_namespace) GETSTRUCT(ns_tup); return ns_struct->oid; #else return HeapTupleGetOid(ns_tup); #endif } /* * Row version check */ typedef struct RowStamp { TransactionId xmin; ItemPointerData tid; } RowStamp; static inline void plproxy_set_stamp(RowStamp *stamp, HeapTuple tup) { stamp->xmin = HeapTupleHeaderGetXmin(tup->t_data); stamp->tid = tup->t_self; } static inline bool plproxy_check_stamp(RowStamp *stamp, HeapTuple tup) { return stamp->xmin == HeapTupleHeaderGetXmin(tup->t_data) && ItemPointerEquals(&stamp->tid, &tup->t_self); } /* * System cache stamp */ typedef uint32 SCInvalArg; typedef struct SysCacheStamp { uint32 cacheid; uint32 hashValue; } SysCacheStamp; static inline void scstamp_set(int cache, SysCacheStamp *stamp, Oid oid) { stamp->cacheid = cache; stamp->hashValue = GetSysCacheHashValue1(cache, oid); } static inline bool scstamp_check(int cache, SysCacheStamp *stamp, uint32 hashValue) { if (stamp->cacheid == 0) return true; if (cache != stamp->cacheid) elog(WARNING, "cache id mismatch: stamp:%d cur:%d", stamp->cacheid, cache); return !hashValue || stamp->hashValue == hashValue; } plproxy-2.9/src/scanner.l000066400000000000000000000164141353753647500155260ustar00rootroot00000000000000%{ /* * 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" /* import standard_conforming_strings */ #include /* * Calculare numeric flex version. */ #if !defined(YY_FLEX_MAJOR_VERSION) || !defined(YY_FLEX_MINOR_VERSION) #error Flex required #endif #ifndef YY_FLEX_SUBMINOR_VERSION #define YY_FLEX_SUBMINOR_VERSION 0 #endif #define FLXVER ((YY_FLEX_MAJOR_VERSION*1000 + YY_FLEX_MINOR_VERSION)*1000 + YY_FLEX_SUBMINOR_VERSION) /* shut down crappy flex warnings */ #if FLXVER < 2005035 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); #endif /* point to parser value */ #define yylval plproxy_yylval /* * Allocate in CurrentMemoryContext. * * If we want to support flex 2.5.4, we cannot use * options noyyalloc, noyyrealloc, noyyfree. * * Thus such need to hack malloc() et al. */ #define malloc palloc #define realloc repalloc #define free(p) do { if (p) pfree(p); } while (0) void plproxy_yylex_startup(void) { /* there may be stale pointers around, drop them */ #if FLXVER < 2005031 (YY_CURRENT_BUFFER) = NULL; #else (yy_buffer_stack) = NULL; #endif plproxy_yylex_destroy(); } /* * compat stuff for older flex */ #if FLXVER < 2005031 /* old flex */ int plproxy_yylex_destroy(void) { plproxy_yy_delete_buffer(YY_CURRENT_BUFFER); YY_CURRENT_BUFFER = NULL; yy_start = 0; yy_init = 1; yylineno = 1; return 0; } int plproxy_yyget_lineno(void) { return yylineno; } #endif /* own error handling */ #define YY_FATAL_ERROR(msg) plproxy_yyerror(msg) /* disable stdio related code */ #define YY_INPUT(buf, res, maxlen) { res = 0; } #define YY_NO_INPUT /* shortcut for returning SQLPART */ #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 %option prefix="plproxy_yy" /* 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\200-\377][a-z0-9_\200-\377]* 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; } split { return SPLIT; } target { return TARGET; } select { BEGIN(sql); yylval.str = yytext; return SELECT; } /* function call */ /* hack to avoid parsing "SELECT (" as function call */ select{SPACE}*[(] { yyless(6); BEGIN(sql); yylval.str = yytext; return SELECT; } {IDENT}{SPACE}*[(] { BEGIN(sql); yylval.str = yytext; return FNCALL; } /* PL/Proxy language comments/whitespace */ {SPACE}+ { } [-][-][^\n]* { } [/][*] { BEGIN(plcom); } [^*/]+ { } [*]+[^*/]+ { } [*]+[/] { BEGIN(INITIAL); } . { } /* 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 */ [-][-][^\n]* { /* \n will be parsed as whitespace */ } /* C comment, parse it as whitespace */ [/][*] { BEGIN(longcom); } [^*/]+ { } [*]+[^*/]+ { } [*]+[/] { BEGIN(sql); yylval.str = " "; return SQLPART; } . { } /* Dollar quoted string */ [$]{DOLQ}?[$] { BEGIN(dolq); dlr_start(yytext); RETPART; } [^$]+ { RETPART; } [$]{DOLQ}?[$] { if (dlr_stop(yytext)) { BEGIN(sql); RETPART; } /* if wrong one, report only 1 char */ else { yyless(1); RETPART; } } [$][^$]* { RETPART; } /* quoted indentifier */ ["] { BEGIN(qident); RETPART; } [^"]+ { RETPART; } [\\]. { RETPART; } ["] { BEGIN(sql); RETPART; } /* quoted string start */ E['] { BEGIN(extq); RETPART; } ['] { if (standard_conforming_strings) BEGIN(stdq); else BEGIN(extq); RETPART; } /* SQL standard quoted string body */ [^']+ { RETPART; } [']['] { RETPART; } ['] { BEGIN(sql); RETPART; } /* extended quoted string body */ [^'\\]+ { RETPART; } [']['] { RETPART; } [\\]. { RETPART; } ['] { BEGIN(sql); RETPART; } . { RETPART; } /* SQL identifier */ {IDENT} { yylval.str = yytext; return SQLIDENT; } /* $x argument reference */ {NUMIDENT} { yylval.str = yytext; return SQLIDENT; } /* SQL number */ {SQLNUM} { RETPART; } /* SQL symbol, parse them one-by-one */ {SQLSYM} { RETPART; } /* compress whitespace to singe ' ' */ {SPACE}+ { yylval.str = " "; return SQLPART; } /* SQL statement end */ [;] { BEGIN(INITIAL); return *(yytext); } /* unparsed symbol, let the parser error out */ . { return *(yytext); } %% static char *dlr_token = NULL; /* remember dollar quote name */ static void dlr_start(const char *txt) { dlr_token = pstrdup(txt); } /* 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); } if (0) yy_fatal_error("avoid unused func warning"); /* leak buf.data */ return buf.data; } plproxy-2.9/src/type.c000066400000000000000000000241761353753647500150510ustar00rootroot00000000000000/* * 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) { /* * We need to properly track if remote server has: * - same major:minor version * - same server_encoding * - same integer_timestamps * * Currently plproxy does the decision too early, * thus no safe types are left. Disable is totally, * until lazy decision-making is possible. */ if (1) return false; switch (oid) { case BOOLOID: case INT2OID: case INT4OID: case INT8OID: case FLOAT4OID: case FLOAT8OID: case NUMERICOID: case BYTEAOID: return true; /* client_encoding issue */ case TEXTOID: case BPCHAROID: case VARCHAROID: /* integer vs. float issue */ case TIMESTAMPOID: case TIMESTAMPTZOID: case DATEOID: case TIMEOID: /* interval binary fmt changed in 8.1 */ case INTERVALOID: default: return false; } } bool plproxy_composite_valid(ProxyComposite *type) { HeapTuple type_tuple; HeapTuple rel_tuple; Form_pg_type pg_type; Oid oid = type->tupdesc->tdtypeid; bool res; if (!type->alterable) return true; type_tuple = SearchSysCache(TYPEOID, ObjectIdGetDatum(oid), 0, 0, 0); if (!HeapTupleIsValid(type_tuple)) elog(ERROR, "cache lookup failed for type %u", oid); pg_type = (Form_pg_type) GETSTRUCT(type_tuple); rel_tuple = SearchSysCache(RELOID, ObjectIdGetDatum(pg_type->typrelid), 0, 0, 0); if (!HeapTupleIsValid(rel_tuple)) elog(ERROR, "cache lookup failed for type relation %u", pg_type->typrelid); res = plproxy_check_stamp(&type->stamp, rel_tuple); ReleaseSysCache(rel_tuple); ReleaseSysCache(type_tuple); return res; } /* * 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; Oid oid = tupdesc->tdtypeid; 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; ret->alterable = 0; if (oid != RECORDOID) { HeapTuple type_tuple; HeapTuple rel_tuple; Form_pg_type pg_type; type_tuple = SearchSysCache(TYPEOID, ObjectIdGetDatum(oid), 0, 0, 0); if (!HeapTupleIsValid(type_tuple)) elog(ERROR, "cache lookup failed for type %u", oid); pg_type = (Form_pg_type) GETSTRUCT(type_tuple); rel_tuple = SearchSysCache(RELOID, ObjectIdGetDatum(pg_type->typrelid), 0, 0, 0); if (!HeapTupleIsValid(rel_tuple)) elog(ERROR, "cache lookup failed for type relation %u", pg_type->typrelid); plproxy_set_stamp(&ret->stamp, rel_tuple); ReleaseSysCache(rel_tuple); ReleaseSysCache(type_tuple); ret->alterable = 1; if (ret->tupdesc->tdtypeid != oid) elog(ERROR, "lost oid"); } MemoryContextSwitchTo(old_ctx); ret->nfields = 0; for (i = 0; i < natts; i++) { a = TupleDescAttr(tupdesc, i); if (a->attisdropped) { ret->name_list[i] = NULL; ret->type_list[i] = NULL; continue; } ret->nfields++; name = quote_identifier(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; } void plproxy_free_composite(ProxyComposite *rec) { int i; int natts = rec->tupdesc->natts; for (i = 0; i < natts; i++) { plproxy_free_type(rec->type_list[i]); if (rec->name_list[i]) pfree(rec->name_list[i]); } pfree(rec->type_list); pfree(rec->name_list); FreeTupleDesc(rec->tupdesc); pfree(rec); } void plproxy_free_type(ProxyType *type) { if (type == NULL) return; if (type->name) pfree(type->name); if (type->elem_type_t) plproxy_free_type(type->elem_type_t); /* hopefully I/O functions do not use ->fn_extra */ pfree(type); } /* * Build result tuple 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; bool *nulls; int i; HeapTuple tuple; dvalues = (Datum *) palloc(natts * sizeof(Datum)); nulls = (bool *) palloc(natts * sizeof(bool)); /* Call the recv function for each attribute */ for (i = 0; i < natts; i++) { if (TupleDescAttr(tupdesc, i)->attisdropped) { dvalues[i] = (Datum)NULL; nulls[i] = true; continue; } dvalues[i] = plproxy_recv_type(meta->type_list[i], values[i], lengths[i], fmts[i]); nulls[i] = (values[i] == NULL); } /* Form a tuple */ tuple = heap_form_tuple(tupdesc, dvalues, nulls); /* * Release locally palloc'd space. */ for (i = 0; i < natts; i++) { if (nulls[i]) 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 * 4 + 2 + 1 + 2 + 1]; 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); snprintf(namebuf, sizeof(namebuf), "%s.%s", quote_identifier(NameStr(s_nsp->nspname)), quote_identifier(NameStr(s_type->typname))); ReleaseSysCache(t_nsp); } else { snprintf(namebuf, sizeof(namebuf), "%s", quote_identifier(NameStr(s_type->typname))); } /* sanity check */ switch (s_type->typtype) { default: plproxy_error(func, "unsupported type code: %s (%u)", namebuf, oid); break; case TYPTYPE_PSEUDO: if (oid != VOIDOID) plproxy_error(func, "unsupported pseudo type: %s (%u)", namebuf, oid); break; case TYPTYPE_BASE: case TYPTYPE_COMPOSITE: case TYPTYPE_DOMAIN: case TYPTYPE_ENUM: case TYPTYPE_RANGE: 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); type->is_array = (s_type->typelem != 0 && s_type->typlen == -1); type->elem_type_oid = s_type->typelem; type->elem_type_t = NULL; type->alignment = s_type->typalign; type->length = s_type->typlen; /* 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; } /* Get cached type info for array elems */ ProxyType *plproxy_get_elem_type(ProxyFunction *func, ProxyType *type, bool for_send) { if (!type->elem_type_t) type->elem_type_t = plproxy_find_type_info(func, type->elem_type_oid, for_send); return type->elem_type_t; } /* 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; } plproxy-2.9/test/000077500000000000000000000000001353753647500141025ustar00rootroot00000000000000plproxy-2.9/test/expected/000077500000000000000000000000001353753647500157035ustar00rootroot00000000000000plproxy-2.9/test/expected/plproxy_alter.out000066400000000000000000000033621353753647500213440ustar00rootroot00000000000000\c test_part0 create function test_table1(out id int4, out data text, out data2 text, out data3 text) as $$ begin id = 1; data = 'Data1'; data2 = 'Data2'; data3 = 'Data3'; return; end; $$ language plpgsql; select * from test_table1(); id | data | data2 | data3 ----+-------+-------+------- 1 | Data1 | Data2 | Data3 (1 row) \c regression create table ret_table ( id int4, data text ); create function test_table1() returns ret_table as $$ cluster 'testcluster'; run on 0; $$ language plproxy; select * from test_table1(); id | data ----+------- 1 | Data1 (1 row) -- add column alter table ret_table add column data2 text; select * from test_table1(); id | data | data2 ----+-------+------- 1 | Data1 | Data2 (1 row) \c regression select * from test_table1(); id | data | data2 ----+-------+------- 1 | Data1 | Data2 (1 row) -- drop & add alter table ret_table drop column data2; alter table ret_table add column data3 text; select * from test_table1(); id | data | data3 ----+-------+------- 1 | Data1 | Data3 (1 row) \c regression select * from test_table1(); id | data | data3 ----+-------+------- 1 | Data1 | Data3 (1 row) -- drop alter table ret_table drop column data3; select * from test_table1(); id | data ----+------- 1 | Data1 (1 row) \c regression select * from test_table1(); id | data ----+------- 1 | Data1 (1 row) -- add again alter table ret_table add column data3 text; alter table ret_table add column data2 text; select * from test_table1(); id | data | data3 | data2 ----+-------+-------+------- 1 | Data1 | Data3 | Data2 (1 row) \c regression select * from test_table1(); id | data | data3 | data2 ----+-------+-------+------- 1 | Data1 | Data3 | Data2 (1 row) plproxy-2.9/test/expected/plproxy_cancel.out000066400000000000000000000015751353753647500214660ustar00rootroot00000000000000-- test canceling \c test_part0 create function rsleep(val int4) returns int4 as $$ begin perform pg_sleep(val); return 1; end; $$ language plpgsql; \c test_part1 create function rsleep(val int4) returns int4 as $$ begin perform pg_sleep(val); return 1; end; $$ language plpgsql; \c test_part2 create function rsleep(val int4) returns int4 as $$ begin perform pg_sleep(val); return 1; end; $$ language plpgsql; \c test_part3 create function rsleep(val int4) returns int4 as $$ begin perform pg_sleep(val); return 1; end; $$ language plpgsql; \c regression create function rsleep(val int4, out res int4) returns setof int4 as $$ cluster 'testcluster'; run on all; $$ language plproxy; set statement_timeout = '1000'; select * from rsleep(10); ERROR: canceling statement due to statement timeout -- test if works later select * from rsleep(0); res ----- 1 1 1 1 (4 rows) plproxy-2.9/test/expected/plproxy_clustermap.out000066400000000000000000000037061353753647500224160ustar00rootroot00000000000000create 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) plproxy-2.9/test/expected/plproxy_dynamic_record.out000066400000000000000000000037061353753647500232210ustar00rootroot00000000000000-- dynamic query support testing create or replace function dynamic_query(q text) returns setof record as $x$ cluster 'map0'; run on all; $x$ language plproxy; \c test_part0 create or replace function dynamic_query(q text) returns setof record as $x$ declare ret record; begin for ret in execute q loop return next ret; end loop; return; end; $x$ language plpgsql; create table dynamic_query_test ( id integer, username text, other text ); insert into dynamic_query_test values ( 1, 'user1', 'blah'); insert into dynamic_query_test values ( 2, 'user2', 'foo'); \c regression select * from dynamic_query('select * from dynamic_query_test') as (id integer, username text, other text); id | username | other ----+----------+------- 1 | user1 | blah 2 | user2 | foo (2 rows) select * from dynamic_query('select id, username from dynamic_query_test') as foo(id integer, username text); id | username ----+---------- 1 | user1 2 | user2 (2 rows) -- invalid usage select * from dynamic_query('select count(1) from pg_class'); ERROR: a column definition list is required for functions returning "record" LINE 1: select * from dynamic_query('select count(1) from pg_class')... ^ select dynamic_query('select count(1) from pg_class'); ERROR: Function used in wrong context -- test errors create or replace function dynamic_query_select() returns setof record as $x$ cluster 'map0'; run on all; select id, username from dynamic_query_test; $x$ language plproxy; ERROR: PL/Proxy function public.dynamic_query_select(0): SELECT statement not allowed for dynamic RECORD functions select * from dynamic_query_select() as (id integer, username text); ERROR: function dynamic_query_select() does not exist LINE 1: select * from dynamic_query_select() as (id integer, usernam... ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. plproxy-2.9/test/expected/plproxy_encoding.out000066400000000000000000000153641353753647500220300ustar00rootroot00000000000000------------------------------------------------- -- encoding tests ------------------------------------------------- set client_encoding = 'utf8'; -- google translate says: -- column: コラム -- table: テーブル -- client data: クライアント側のデータ -- proxy data: プロキシデータ -- remote data: リモートデータ -- argument: 引数 set client_min_messages = 'warning'; drop database if exists test_enc_proxy; drop database if exists test_enc_part; create database test_enc_proxy with encoding 'euc_jp' template template0; create database test_enc_part with encoding 'utf-8' template template0; -- initialize proxy db \c test_enc_proxy set client_encoding = 'utf-8'; create or replace language plpgsql; set client_min_messages = 'warning'; \set ECHO none create schema plproxy; create or replace function plproxy.get_cluster_version(cluster_name text) returns integer as $$ begin return 1; 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 return; end; $$ language plpgsql; create or replace function plproxy.get_cluster_partitions(cluster_name text) returns setof text as $$ begin return next 'host=127.0.0.1 dbname=test_enc_part'; return; end; $$ language plpgsql; create table intl_data (id int4, "コラム" text); create function test_encoding() returns setof intl_data as $$ cluster 'testcluster'; run on 0; select * from intl_data order by 1; $$ language plproxy; create function test_encoding2(text) returns setof intl_data as $$ cluster 'testcluster'; run on 0; select 0 as id, $1 as "コラム"; $$ language plproxy; create function test_encoding3(text) returns setof intl_data as $$ cluster 'testcluster'; run on 0; $$ language plproxy; -- initialize part db \c test_enc_part create or replace language plpgsql; set client_min_messages = 'warning'; set client_encoding = 'utf8'; create table intl_data (id int4, "コラム" text); insert into intl_data values (1, 'リモートデータ'); create function test_encoding3(text) returns setof intl_data as $$ declare rec intl_data%rowtype; begin raise notice 'got: %', $1; rec := (3, $1); return next rec; return; end; $$ language plpgsql; set client_encoding = 'sjis'; select * from intl_data order by 1; id | R ----+---------------- 1 | [gf[^ (1 row) set client_encoding = 'euc_jp'; select * from intl_data order by 1; id | ----+---------------- 1 | ⡼ȥǡ (1 row) set client_encoding = 'utf-8'; select * from intl_data order by 1; id | コラム ----+---------------- 1 | リモートデータ (1 row) -- test \c test_enc_proxy set client_encoding = 'sjis'; select * from test_encoding(); id | R ----+---------------- 1 | [gf[^ (1 row) set client_encoding = 'euc_jp'; select * from test_encoding(); id | ----+---------------- 1 | ⡼ȥǡ (1 row) set client_encoding = 'utf8'; select * from test_encoding(); id | コラム ----+---------------- 1 | リモートデータ (1 row) select * from test_encoding2('クライアント側のデータ'); id | コラム ----+------------------------ 0 | クライアント側のデータ (1 row) select * from test_encoding3('クライアント側のデータ'); NOTICE: public.test_encoding3(1): [test_enc_part] REMOTE NOTICE: got: クライアント側のデータ id | コラム ----+------------------------ 3 | クライアント側のデータ (1 row) \c template1 set client_min_messages = 'warning'; drop database if exists test_enc_proxy; drop database if exists test_enc_part; create database test_enc_proxy with encoding 'utf-8' template template0; create database test_enc_part with encoding 'euc_jp' template template0; -- initialize proxy db \c test_enc_proxy create or replace language plpgsql; set client_min_messages = 'warning'; \set ECHO none set client_encoding = 'utf8'; create schema plproxy; create or replace function plproxy.get_cluster_version(cluster_name text) returns integer as $$ begin return 1; 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 return; end; $$ language plpgsql; create or replace function plproxy.get_cluster_partitions(cluster_name text) returns setof text as $$ begin return next 'host=127.0.0.1 dbname=test_enc_part'; return; end; $$ language plpgsql; create table intl_data (id int4, "コラム" text); create function test_encoding() returns setof intl_data as $$ cluster 'testcluster'; run on 0; select * from intl_data order by 1; $$ language plproxy; create function test_encoding2(text) returns setof intl_data as $$ cluster 'testcluster'; run on 0; select 0 as id, $1 as "コラム"; $$ language plproxy; create function test_encoding3(text) returns setof intl_data as $$ cluster 'testcluster'; run on 0; $$ language plproxy; -- initialize part db \c test_enc_part create or replace language plpgsql; set client_min_messages = 'warning'; set client_encoding = 'utf8'; create table intl_data (id int4, "コラム" text); insert into intl_data values (1, 'リモートデータ'); create function test_encoding3(text) returns setof intl_data as $$ declare rec intl_data%rowtype; begin raise notice 'got: %', $1; rec := (3, $1); return next rec; return; end; $$ language plpgsql; set client_encoding = 'sjis'; select * from intl_data order by 1; id | R ----+---------------- 1 | [gf[^ (1 row) set client_encoding = 'euc_jp'; select * from intl_data order by 1; id | ----+---------------- 1 | ⡼ȥǡ (1 row) set client_encoding = 'utf-8'; select * from intl_data order by 1; id | コラム ----+---------------- 1 | リモートデータ (1 row) -- test \c test_enc_proxy set client_encoding = 'utf8'; set client_encoding = 'sjis'; select * from test_encoding(); id | R ----+---------------- 1 | [gf[^ (1 row) set client_encoding = 'euc_jp'; select * from test_encoding(); id | ----+---------------- 1 | ⡼ȥǡ (1 row) set client_encoding = 'utf-8'; select * from test_encoding(); id | コラム ----+---------------- 1 | リモートデータ (1 row) select * from test_encoding2('クライアント側のデータ'); id | コラム ----+------------------------ 0 | クライアント側のデータ (1 row) select * from test_encoding3('クライアント側のデータ'); NOTICE: public.test_encoding3(1): [test_enc_part] REMOTE NOTICE: got: クライアント側のデータ id | コラム ----+------------------------ 3 | クライアント側のデータ (1 row) plproxy-2.9/test/expected/plproxy_errors.out000066400000000000000000000114661353753647500215550ustar00rootroot00000000000000\set VERBOSITY terse -- 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 at character 24 create function test_err2(dat text) returns text as $$ cluster 'testcluster'; run on hashtext($2); $$ language plproxy; ERROR: PL/Proxy function public.test_err2(1): Compile error at line 3: invalid argument reference: $2 select * from test_err2('dat'); ERROR: function test_err2(unknown) does not exist at character 15 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 -- should work create function test_err_none(dat text) returns text as $$ cluster 'testcluster'; run on hashtext($1); select 'ok'; $$ language plproxy; select * from test_err_none('dat'); test_err_none --------------- ok (1 row) --- 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 hashtext(dat); select dat as res2, 'foo' as res1; $$ language plproxy; ERROR: PL/Proxy function public.test_map_err4(1): Compile error at line 5: CLUSTER statement missing select * from test_map_err4('dat'); ERROR: function test_map_err4(unknown) does not exist at character 15 create function test_variadic_err(first text, rest variadic text[]) returns text as $$ cluster 'testcluster'; $$ language plproxy; ERROR: PL/Proxy does not support variadic args select * from test_variadic_err('dat', 'dat', 'dat'); ERROR: function test_variadic_err(unknown, unknown, unknown) does not exist at character 15 create function test_volatile_err(dat text) returns text stable as $$ cluster 'testcluster'; $$ language plproxy; ERROR: PL/Proxy functions must be volatile select * from test_volatile_err('dat'); ERROR: function test_volatile_err(unknown) does not exist at character 15 create function test_pseudo_arg_err(dat cstring) returns text as $$ cluster 'testcluster'; $$ language plproxy; ERROR: PL/Proxy function public.test_pseudo_arg_err(0): unsupported pseudo type: cstring (2275) select * from test_pseudo_arg_err(textout('dat')); ERROR: function test_pseudo_arg_err(cstring) does not exist at character 15 create function test_pseudo_ret_err(dat text) returns cstring as $$ cluster 'testcluster'; $$ language plproxy; -- not detected in validator select * from test_pseudo_ret_err('dat'); ERROR: PL/Proxy function public.test_pseudo_ret_err(0): unsupported pseudo type: cstring (2275) create function test_runonall_err(dat text) returns text as $$ cluster 'testcluster'; run on all; $$ language plproxy; ERROR: PL/Proxy function public.test_runonall_err(1): RUN ON ALL requires set-returning function select * from test_runonall_err('dat'); ERROR: function test_runonall_err(unknown) does not exist at character 15 -- make sure that errors from non-setof functions returning <> 1 row have -- a proper sqlstate create function test_no_results_plproxy() returns int as $$ cluster 'testcluster'; run on any; select 1 from pg_database where datname = ''; $$ language plproxy; create function test_no_results() returns void as $$ begin begin perform test_no_results_plproxy(); exception when no_data_found then null; end; end; $$ language plpgsql; select * from test_no_results(); test_no_results ----------------- (1 row) create function test_multi_results_plproxy() returns int as $$ cluster 'testcluster'; run on any; select 1 from pg_database; $$ language plproxy; create function test_multi_results() returns void as $$ begin begin perform test_multi_results_plproxy(); exception when too_many_rows then null; end; end; $$ language plpgsql; select * from test_multi_results(); test_multi_results -------------------- (1 row) plproxy-2.9/test/expected/plproxy_init.out000066400000000000000000000000171353753647500211720ustar00rootroot00000000000000\set ECHO none plproxy-2.9/test/expected/plproxy_many.out000066400000000000000000000053431353753647500212020ustar00rootroot00000000000000create 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') order by 1; 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.5); setseed --------- (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; -- expect that 20 calls use all partitions select distinct test_multi(0, 'foo') from generate_series(1,20) order by 1; test_multi ------------ 0 1 2 3 (4 rows) plproxy-2.9/test/expected/plproxy_range.out000066400000000000000000000014641353753647500213320ustar00rootroot00000000000000\c test_part0 CREATE TYPE f8range AS RANGE ( subtype = float8, subtype_diff = float8mi ); create or replace function test_range(in id int, inout frange f8range, out irange int4range) returns record as $$ begin irange := '[20,30)'; return; end; $$ language plpgsql; select * from test_range(0, '(1.5,2.4]'); frange | irange -----------+--------- (1.5,2.4] | [20,30) (1 row) \c regression CREATE TYPE f8range AS RANGE ( subtype = float8, subtype_diff = float8mi ); create or replace function test_range(in _id integer, inout frange f8range, out irange int4range) returns setof record as $$ cluster 'testcluster'; run on _id; $$ language plproxy; select * from test_range(0, '(1.5,2.4]'); frange | irange -----------+--------- (1.5,2.4] | [20,30) (1 row) plproxy-2.9/test/expected/plproxy_select.out000066400000000000000000000063511353753647500215150ustar00rootroot00000000000000-- 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): Non-SETOF function requires 1 row from remote query, got 0 -- 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; ERROR: PL/Proxy function public.test_select_err(2): Compile error at line 5: Only one SELECT statement allowed select * from test_select_err('user', true); ERROR: function test_select_err(unknown, boolean) does not exist LINE 1: select * from test_select_err('user', true); ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. create function get_zero() returns setof integer as $x$ cluster 'testcluster'; run on all; select (0*0); $x$ language plproxy; select * from get_zero(); get_zero ---------- 0 (1 row) \c test_part create table numbers ( num int, name text ); insert into numbers values (1, 'one'); insert into numbers values (2, 'two'); create function ret_numtuple(int) returns numbers as $x$ select num, name from numbers where num = $1; $x$ language sql; \c regression create type numbers_type as (num int, name text); create function get_one() returns setof numbers_type as $x$ cluster 'testcluster'; run on all; select (ret_numtuple(1)).num, (ret_numtuple(1)).name; $x$ language plproxy; select * from get_one(); num | name -----+------ 1 | one (1 row) \c test_part create function remote_func(a varchar, b varchar, c varchar) returns void as $$ begin return; end; $$ language plpgsql; \c regression CREATE OR REPLACE FUNCTION test1(x integer, a varchar, b varchar, c varchar) RETURNS void AS $$ CLUSTER 'testcluster'; RUN ON 0; SELECT * FROM remote_func(a, b, c); $$ LANGUAGE plproxy; select * from test1(1, 'a', NULL,NULL); test1 ------- (1 row) select * from test1(1, NULL, NULL,NULL); test1 ------- (1 row) CREATE OR REPLACE FUNCTION test2(a varchar, b varchar, c varchar) RETURNS void AS $$ CLUSTER 'testcluster'; RUN ON 0; SELECT * FROM remote_func(a, b, c); $$ LANGUAGE plproxy; select * from test2(NULL, NULL, NULL); test2 ------- (1 row) select * from test2('a', NULL, NULL); test2 ------- (1 row) CREATE OR REPLACE FUNCTION test3(a varchar, b varchar, c varchar) RETURNS void AS $$ CLUSTER 'testcluster'; RUN ON 0; SELECT * FROM remote_func(a, c, b); $$ LANGUAGE plproxy; select * from test3(NULL,NULL, 'a'); test3 ------- (1 row) select * from test3('a', NULL,NULL); test3 ------- (1 row) plproxy-2.9/test/expected/plproxy_split.out000066400000000000000000000200471353753647500213670ustar00rootroot00000000000000-- partition functions \c test_part0 create or replace function test_array(a text[], b text[], c text) returns text as $$ select current_database() || ' $1:' || array_to_string($1, ',') || ' $2:' || array_to_string($2, ',') || ' $3:' || $3; $$ language sql; \c test_part1 create or replace function test_array(a text[], b text[], c text) returns text as $$ select current_database() || ' $1:' || array_to_string($1, ',') || ' $2:' || array_to_string($2, ',') || ' $3:' || $3; $$ language sql; \c test_part2 create or replace function test_array(a text[], b text[], c text) returns text as $$ select current_database() || ' $1:' || array_to_string($1, ',') || ' $2:' || array_to_string($2, ',') || ' $3:' || $3; $$ language sql; \c test_part3 create or replace function test_array(a text[], b text[], c text) returns text as $$ select current_database() || ' $1:' || array_to_string($1, ',') || ' $2:' || array_to_string($2, ',') || ' $3:' || $3; $$ language sql; \c regression -- invalid arg reference create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split $4; cluster 'testcluster'; run on 0;$$ language plproxy; ERROR: PL/Proxy function public.test_array(3): Compile error at line 1: invalid argument reference: $4 select * from test_array(array['a'], array['g'], 'foo'); ERROR: function test_array(text[], text[], unknown) does not exist LINE 1: select * from test_array(array['a'], array['g'], 'foo'); ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. -- invalid arg name create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split x; cluster 'testcluster'; run on 0; $$ language plproxy; ERROR: PL/Proxy function public.test_array(3): Compile error at line 1: invalid argument reference: x select * from test_array(array['a'], array['b', 'c'], 'foo'); ERROR: function test_array(text[], text[], unknown) does not exist LINE 1: select * from test_array(array['a'], array['b', 'c'], 'foo')... ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. -- cannot split more than once create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split a, b, b; cluster 'testcluster'; run on 0; $$ language plproxy; ERROR: PL/Proxy function public.test_array(3): SPLIT parameter specified more than once: b select * from test_array(array['a'], array['b', 'c'], 'foo'); ERROR: function test_array(text[], text[], unknown) does not exist LINE 1: select * from test_array(array['a'], array['b', 'c'], 'foo')... ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. -- attempt to split non-array create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split $3; cluster 'testcluster'; run on 0;$$ language plproxy; ERROR: PL/Proxy function public.test_array(3): SPLIT parameter is not an array: $3 select * from test_array(array['a'], array['g'], 'foo'); ERROR: function test_array(text[], text[], unknown) does not exist LINE 1: select * from test_array(array['a'], array['g'], 'foo'); ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. -- array size/dimensions mismatch create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split a, b; cluster 'testcluster'; run on 0; $$ language plproxy; select * from test_array(array['a'], array['b', 'c'], 'foo'); ERROR: PL/Proxy function public.test_array(3): split arrays must be of identical lengths select * from test_array(array['a','b','c','d'], null, 'foo'); ERROR: PL/Proxy function public.test_array(3): split arrays must be of identical lengths select * from test_array(null, array['e','f','g','h'], 'foo'); ERROR: PL/Proxy function public.test_array(3): split arrays must be of identical lengths select * from test_array(array[array['a1'],array['a2']], array[array['b1'],array['b2']], 'foo'); ERROR: PL/Proxy function public.test_array(3): split multi-dimensional arrays are not supported -- run on array hash, split one array create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split a; cluster 'testcluster'; run on ascii(a);$$ language plproxy; select * from test_array(array['a','b','c','d'], array['e','f','g','h'], 'foo'); test_array ----------------------------------- test_part1 $1:a $2:e,f,g,h $3:foo test_part2 $1:b $2:e,f,g,h $3:foo test_part3 $1:c $2:e,f,g,h $3:foo test_part0 $1:d $2:e,f,g,h $3:foo (4 rows) -- run on text hash, split two arrays (nop split) create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split a, b; cluster 'testcluster'; run on ascii(c);$$ language plproxy; select * from test_array(array['a','b','c','d'], array['e','f','g','h'], 'foo'); test_array ----------------------------------------- test_part2 $1:a,b,c,d $2:e,f,g,h $3:foo (1 row) -- run on array hash, split two arrays create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split a, b; cluster 'testcluster'; run on ascii(a);$$ language plproxy; select * from test_array(array['a','b','c','d'], array['e','f','g','h'], 'foo'); test_array ----------------------------- test_part1 $1:a $2:e $3:foo test_part2 $1:b $2:f $3:foo test_part3 $1:c $2:g $3:foo test_part0 $1:d $2:h $3:foo (4 rows) select * from test_array(null, null, null); test_array ------------ (0 rows) select * from test_array('{}'::text[], '{}'::text[], 'foo'); test_array ------------ (0 rows) -- run on text hash, split all arrays create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split all; cluster 'testcluster'; run on ascii(c);$$ language plproxy; select * from test_array(array['a','b','c','d'], array['e','f','g','h'], 'foo'); test_array ----------------------------------------- test_part2 $1:a,b,c,d $2:e,f,g,h $3:foo (1 row) -- run on text hash, attempt to split all arrays but none are present create or replace function test_nonarray_split(a text, b text, c text) returns setof text as $$ split all; cluster 'testcluster'; run on ascii(a); select * from test_array(array[a], array[b], c); $$ language plproxy; select * from test_nonarray_split('a', 'b', 'c'); test_nonarray_split --------------------------- test_part1 $1:a $2:b $3:c (1 row) -- run on array hash, split all arrays create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split all; cluster 'testcluster'; run on ascii(a);$$ language plproxy; select * from test_array(array['a','b','c','d'], array['e','f','g','h'], 'foo'); test_array ----------------------------- test_part1 $1:a $2:e $3:foo test_part2 $1:b $2:f $3:foo test_part3 $1:c $2:g $3:foo test_part0 $1:d $2:h $3:foo (4 rows) -- run on arg create or replace function test_array_direct(a integer[], b text[], c text) returns setof text as $$ split a; cluster 'testcluster'; run on a; select test_array('{}'::text[], b, c);$$ language plproxy; select * from test_array_direct(array[2,3], array['a','b','c','d'], 'foo'); test_array_direct ---------------------------------- test_part2 $1: $2:a,b,c,d $3:foo test_part3 $1: $2:a,b,c,d $3:foo (2 rows) create or replace function test_array_direct(a integer[], b text[], c text) returns setof text as $$ split a, b; cluster 'testcluster'; run on a; select test_array('{}'::text[], b, c);$$ language plproxy; select * from test_array_direct(array[0,1,2,3], array['a','b','c','d'], 'foo'); test_array_direct ---------------------------- test_part0 $1: $2:a $3:foo test_part1 $1: $2:b $3:foo test_part2 $1: $2:c $3:foo test_part3 $1: $2:d $3:foo (4 rows) plproxy-2.9/test/expected/plproxy_sqlmed.out000066400000000000000000000131431353753647500215200ustar00rootroot00000000000000\set VERBOSITY terse set client_min_messages = 'warning'; create server sqlmedcluster foreign data wrapper plproxy options ( partition_0 'dbname=test_part3 host=localhost', partition_1 'dbname=test_part2 host=localhost', partition_2 'dbname=test_part1 host=localhost', partition_3 'dbname=test_part0 host=localhost'); create or replace function sqlmed_test1() returns setof text as $$ cluster 'sqlmedcluster'; run on 0; select 'plproxy: user=' || current_user || ' dbname=' || current_database(); $$ language plproxy; drop user if exists test_user_alice; drop user if exists test_user_bob; drop user if exists test_user_charlie; create user test_user_alice password 'supersecret'; create user test_user_bob password 'secret'; create user test_user_charlie password 'megasecret'; -- no user mapping set session authorization test_user_bob; select * from sqlmed_test1(); ERROR: permission denied for foreign server sqlmedcluster reset session authorization; -- add a public user mapping create user mapping for public server sqlmedcluster options ( user 'test_user_bob', password 'secret1'); -- no access to foreign server set session authorization test_user_bob; select * from sqlmed_test1(); ERROR: permission denied for foreign server sqlmedcluster reset session authorization; -- ok, access granted grant usage on foreign server sqlmedcluster to test_user_bob; set session authorization test_user_bob; select * from sqlmed_test1(); sqlmed_test1 ----------------------------------------------- plproxy: user=test_user_bob dbname=test_part3 (1 row) reset session authorization; -- test security definer create user mapping for test_user_alice server sqlmedcluster; create user mapping for test_user_charlie server sqlmedcluster; grant usage on foreign server sqlmedcluster to test_user_alice; grant usage on foreign server sqlmedcluster to test_user_charlie; create or replace function sqlmed_test_alice() returns setof text as $$ cluster 'sqlmedcluster'; run on 0; select 'plproxy: user=' || current_user || ' dbname=' || current_database(); $$ language plproxy security definer; alter function sqlmed_test_alice() owner to test_user_alice; create or replace function sqlmed_test_charlie() returns setof text as $$ cluster 'sqlmedcluster'; run on 0; select 'plproxy: user=' || current_user || ' dbname=' || current_database(); $$ language plproxy security definer; alter function sqlmed_test_charlie() owner to test_user_charlie; -- call as alice set session authorization test_user_alice; select * from sqlmed_test_alice(); sqlmed_test_alice ------------------------------------------------- plproxy: user=test_user_alice dbname=test_part3 (1 row) select * from sqlmed_test_charlie(); sqlmed_test_charlie --------------------------------------------------- plproxy: user=test_user_charlie dbname=test_part3 (1 row) reset session authorization; -- call as charlie set session authorization test_user_charlie; select * from sqlmed_test_alice(); sqlmed_test_alice ------------------------------------------------- plproxy: user=test_user_alice dbname=test_part3 (1 row) select * from sqlmed_test_charlie(); sqlmed_test_charlie --------------------------------------------------- plproxy: user=test_user_charlie dbname=test_part3 (1 row) reset session authorization; -- test refresh too alter user mapping for test_user_charlie server sqlmedcluster options (add user 'test_user_alice'); set session authorization test_user_bob; select * from sqlmed_test_charlie(); sqlmed_test_charlie ------------------------------------------------- plproxy: user=test_user_alice dbname=test_part3 (1 row) reset session authorization; -- cluster definition validation -- partition numbers must be consecutive alter server sqlmedcluster options (drop partition_2); ERROR: Pl/Proxy: partitions must be numbered consecutively select * from sqlmed_test1(); sqlmed_test1 ----------------------------------------------- plproxy: user=test_user_bob dbname=test_part3 (1 row) -- invalid partition count alter server sqlmedcluster options (drop partition_3, add partition_2 'dbname=test_part1 host=localhost'); ERROR: option "partition_2" provided more than once select * from sqlmed_test1(); sqlmed_test1 ----------------------------------------------- plproxy: user=test_user_bob dbname=test_part3 (1 row) -- switching betweem SQL/MED and compat mode create or replace function sqlmed_compat_test() returns setof text as $$ cluster 'testcluster'; run on 0; select 'plproxy: part=' || current_database(); $$ language plproxy; -- testcluster select * from sqlmed_compat_test(); sqlmed_compat_test -------------------------- plproxy: part=test_part0 (1 row) -- override the test cluster with a SQL/MED definition drop server if exists testcluster cascade; create server testcluster foreign data wrapper plproxy options (partition_0 'dbname=regression host=localhost'); create user mapping for public server testcluster; -- sqlmed testcluster select * from sqlmed_compat_test(); sqlmed_compat_test -------------------------- plproxy: part=regression (1 row) -- now drop the SQL/MED testcluster, and test fallback drop server testcluster cascade; -- back on testcluster again select * from sqlmed_compat_test(); sqlmed_compat_test -------------------------- plproxy: part=test_part0 (1 row) plproxy-2.9/test/expected/plproxy_table.out000066400000000000000000000015071353753647500213230ustar00rootroot00000000000000\c test_part0 create or replace function test_ret_table(id int) returns table(id int, t text) as $$ select * from (values(1, 'test'),(2, 'toto') ) as toto; $$ language sql; select * from test_ret_table(0); id | t ----+------ 1 | test 2 | toto (2 rows) \c regression create or replace function test_ret_table_normal(in _id integer, out id integer, out t text) returns setof record as $$ cluster 'testcluster'; run on _id; target test_ret_table; $$ language plproxy; select * from test_ret_table_normal(0); id | t ----+------ 1 | test 2 | toto (2 rows) create or replace function test_ret_table(in _id integer) returns table (id integer, t text) as $$ cluster 'testcluster'; run on _id; $$ language plproxy; select * from test_ret_table(0); id | t ----+------ 1 | test 2 | toto (2 rows) plproxy-2.9/test/expected/plproxy_target.out000066400000000000000000000030251353753647500215170ustar00rootroot00000000000000-- test target clause create function test_target(xuser text, tmp boolean) returns text as $$ cluster 'testcluster'; run on 0; target test_target_dst; $$ language plproxy; \c test_part0 create function test_target_dst(xuser text, tmp boolean) returns text as $$ begin return 'dst'; end; $$ language plpgsql; \c regression select * from test_target('foo', true); test_target ------------- dst (1 row) -- test errors create function test_target_err1(xuser text) returns text as $$ cluster 'testcluster'; run on 0; target test_target_dst; target test_target_dst; $$ language plproxy; ERROR: PL/Proxy function public.test_target_err1(1): Compile error at line 5: Only one TARGET statement allowed select * from test_target_err1('asd'); ERROR: function test_target_err1(unknown) does not exist LINE 1: select * from test_target_err1('asd'); ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. create function test_target_err2(xuser text) returns text as $$ cluster 'testcluster'; run on 0; target test_target_dst; select 1; $$ language plproxy; ERROR: PL/Proxy function public.test_target_err2(1): Compile error at line 6: TARGET cannot be used with SELECT select * from test_target_err2('asd'); ERROR: function test_target_err2(unknown) does not exist LINE 1: select * from test_target_err2('asd'); ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. plproxy-2.9/test/expected/plproxy_test.out000066400000000000000000000270611353753647500212160ustar00rootroot00000000000000\set VERBOSITY terse -- 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 1 from (select set_config(name, 'escape', false) as ignore from pg_settings where name = 'bytea_output') x where x.ignore = 'foo'; ?column? ---------- (0 rows) 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'; $$ 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) -- test CONNECT $argument create function test_connect2(connstr text) returns text as $$ connect connstr; select current_database(); $$ language plproxy; select * from test_connect2('dbname=test_part'); test_connect2 --------------- test_part (1 row) -- test CONNECT function($argument) create function test_connect3(connstr text) returns text as $$ connect text(connstr); select current_database(); $$ language plproxy; select * from test_connect3('dbname=test_part'); test_connect3 --------------- test_part (1 row) -- test quoting function create type "RetWeird" as ( "ColId" int4, "ColData" text ); create function "testQuoting"(username text, id integer, data text) returns "RetWeird" as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy; \c test_part create type "RetWeird" as ( "ColId" int4, "ColData" text ); create function "testQuoting"(username text, id integer, data text) returns "RetWeird" as $$ select 1::int4, 'BazOoka'::text $$ language sql; \c regression select * from "testQuoting"('user', '1', 'dat'); ColId | ColData -------+--------- 1 | BazOoka (1 row) -- test arg type quoting create domain "bad type" as text; create function test_argq(username text, "some arg" integer, "other arg" "bad type", out "bad out" text, out "bad out2" "bad type") as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy; \c test_part create domain "bad type" as text; create function test_argq(username text, "some arg" integer, "other arg" "bad type", out "bad out" text, out "bad out2" "bad type") as $$ begin return; end; $$ language plpgsql; \c regression select * from test_argq('user', 1, 'q'); bad out | bad out2 ---------+---------- | (1 row) -- test hash types function create or replace function t_hash16(int4) returns int2 as $$ declare res int2; begin res = $1::int2; return res; end; $$ language plpgsql; create or replace function t_hash64(int4) returns int8 as $$ declare res int8; begin res = $1; return res; end; $$ language plpgsql; create function test_hash16(id integer, data text) returns text as $$ cluster 'testcluster'; run on t_hash16(id); select data; $$ language plproxy; select * from test_hash16('0', 'hash16'); test_hash16 ------------- hash16 (1 row) create function test_hash64(id integer, data text) returns text as $$ cluster 'testcluster'; run on t_hash64(id); select data; $$ language plproxy; select * from test_hash64('0', 'hash64'); test_hash64 ------------- hash64 (1 row) -- test argument difference \c test_part create function test_difftypes(username text, out val1 int2, out val2 float8) as $$ begin val1 = 1; val2 = 3;return; end; $$ language plpgsql; \c regression create function test_difftypes(username text, out val1 int4, out val2 float4) as $$ cluster 'testcluster'; run on 0; $$ language plproxy; select * from test_difftypes('types'); val1 | val2 ------+------ 1 | 3 (1 row) -- test simple hash \c test_part create function test_simple(partno int4) returns int4 as $$ begin return $1; end; $$ language plpgsql; \c regression create function test_simple(partno int4) returns int4 as $$ cluster 'testcluster'; run on $1; $$ language plproxy; select * from test_simple(0); test_simple ------------- 0 (1 row) drop function test_simple(int4); create function test_simple(partno int4) returns int4 as $$ cluster 'testcluster'; run on partno; $$ language plproxy; select * from test_simple(0); test_simple ------------- 0 (1 row) -- test error passing \c test_part create function test_error1() returns int4 as $$ begin select line2err; return 0; end; $$ language plpgsql; \c regression create function test_error1() returns int4 as $$ cluster 'testcluster'; run on 0; $$ language plproxy; select * from test_error1(); ERROR: public.test_error1(0): [test_part] REMOTE ERROR: column "line2err" does not exist at character 8 create function test_error2() returns int4 as $$ cluster 'testcluster'; run on 0; select err; $$ language plproxy; select * from test_error2(); NOTICE: PL/Proxy: dropping stale conn ERROR: public.test_error2(0): [test_part] REMOTE ERROR: column "err" does not exist at character 8 create function test_error3() returns int4 as $$ connect 'dbname=test_part'; $$ language plproxy; select * from test_error3(); ERROR: public.test_error3(0): [test_part] REMOTE ERROR: function public.test_error3() does not exist at character 21 -- test invalid db create function test_bad_db() returns int4 as $$ cluster 'badcluster'; $$ language plproxy; select * from test_bad_db(); ERROR: PL/Proxy function public.test_bad_db(0): [nonex_db] PQconnectPoll: FATAL: database "nonex_db" does not exist create function test_bad_db2() returns int4 as $$ connect 'dbname=wrong_name_db'; $$ language plproxy; select * from test_bad_db2(); ERROR: PL/Proxy function public.test_bad_db2(0): [wrong_name_db] PQconnectPoll: FATAL: database "wrong_name_db" does not exist plproxy-2.9/test/expected/plproxy_test_1.out000066400000000000000000000271741353753647500214430ustar00rootroot00000000000000\set VERBOSITY terse -- 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 1 from (select set_config(name, 'escape', false) as ignore from pg_settings where name = 'bytea_output') x where x.ignore = 'foo'; ?column? ---------- (0 rows) 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'; $$ 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 host=localhost'; select current_database(); $$ language plproxy; select * from test_connect1(); test_connect1 --------------- test_part (1 row) -- test CONNECT $argument create function test_connect2(connstr text) returns text as $$ connect connstr; select current_database(); $$ language plproxy; select * from test_connect2('dbname=test_part host=localhost'); test_connect2 --------------- test_part (1 row) -- test CONNECT function($argument) create function test_connect3(connstr text) returns text as $$ connect text(connstr); select current_database(); $$ language plproxy; select * from test_connect3('dbname=test_part host=localhost'); test_connect3 --------------- test_part (1 row) -- test quoting function create type "RetWeird" as ( "ColId" int4, "ColData" text ); create function "testQuoting"(username text, id integer, data text) returns "RetWeird" as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy; \c test_part create type "RetWeird" as ( "ColId" int4, "ColData" text ); create function "testQuoting"(username text, id integer, data text) returns "RetWeird" as $$ select 1::int4, 'BazOoka'::text $$ language sql; \c regression select * from "testQuoting"('user', '1', 'dat'); ColId | ColData -------+--------- 1 | BazOoka (1 row) -- test arg type quoting create domain "bad type" as text; create function test_argq(username text, "some arg" integer, "other arg" "bad type", out "bad out" text, out "bad out2" "bad type") as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy; \c test_part create domain "bad type" as text; create function test_argq(username text, "some arg" integer, "other arg" "bad type", out "bad out" text, out "bad out2" "bad type") as $$ begin return; end; $$ language plpgsql; \c regression select * from test_argq('user', 1, 'q'); bad out | bad out2 ---------+---------- | (1 row) -- test hash types function create or replace function t_hash16(int4) returns int2 as $$ declare res int2; begin res = $1::int2; return res; end; $$ language plpgsql; create or replace function t_hash64(int4) returns int8 as $$ declare res int8; begin res = $1; return res; end; $$ language plpgsql; create function test_hash16(id integer, data text) returns text as $$ cluster 'testcluster'; run on t_hash16(id); select data; $$ language plproxy; select * from test_hash16('0', 'hash16'); test_hash16 ------------- hash16 (1 row) create function test_hash64(id integer, data text) returns text as $$ cluster 'testcluster'; run on t_hash64(id); select data; $$ language plproxy; select * from test_hash64('0', 'hash64'); test_hash64 ------------- hash64 (1 row) -- test argument difference \c test_part create function test_difftypes(username text, out val1 int2, out val2 float8) as $$ begin val1 = 1; val2 = 3;return; end; $$ language plpgsql; \c regression create function test_difftypes(username text, out val1 int4, out val2 float4) as $$ cluster 'testcluster'; run on 0; $$ language plproxy; select * from test_difftypes('types'); val1 | val2 ------+------ 1 | 3 (1 row) -- test simple hash \c test_part create function test_simple(partno int4) returns int4 as $$ begin return $1; end; $$ language plpgsql; \c regression create function test_simple(partno int4) returns int4 as $$ cluster 'testcluster'; run on $1; $$ language plproxy; select * from test_simple(0); test_simple ------------- 0 (1 row) drop function test_simple(int4); create function test_simple(partno int4) returns int4 as $$ cluster 'testcluster'; run on partno; $$ language plproxy; select * from test_simple(0); test_simple ------------- 0 (1 row) -- test error passing \c test_part create function test_error1() returns int4 as $$ begin select line2err; return 0; end; $$ language plpgsql; \c regression create function test_error1() returns int4 as $$ cluster 'testcluster'; run on 0; $$ language plproxy; select * from test_error1(); ERROR: public.test_error1(0): [test_part] REMOTE ERROR: column "line2err" does not exist at character 8 create function test_error2() returns int4 as $$ cluster 'testcluster'; run on 0; select err; $$ language plproxy; select * from test_error2(); NOTICE: PL/Proxy: dropping stale conn ERROR: public.test_error2(0): [test_part] REMOTE ERROR: column "err" does not exist at character 8 create function test_error3() returns int4 as $$ connect 'dbname=test_part host=localhost'; $$ language plproxy; select * from test_error3(); ERROR: public.test_error3(0): [test_part] REMOTE ERROR: function public.test_error3() does not exist at character 21 -- test invalid db create function test_bad_db() returns int4 as $$ cluster 'badcluster'; $$ language plproxy; select * from test_bad_db(); ERROR: PL/Proxy function public.test_bad_db(0): [nonex_db] PQconnectPoll: FATAL: database "nonex_db" does not exist create function test_bad_db2() returns int4 as $$ connect 'dbname=wrong_name_db host=localhost'; $$ language plproxy; select * from test_bad_db2(); ERROR: PL/Proxy function public.test_bad_db2(0): [wrong_name_db] PQconnectPoll: FATAL: database "wrong_name_db" does not exist plproxy-2.9/test/expected/plproxy_test_2.out000066400000000000000000000270611353753647500214370ustar00rootroot00000000000000\set VERBOSITY terse -- 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 1 from (select set_config(name, 'escape', false) as ignore from pg_settings where name = 'bytea_output') x where x.ignore = 'foo'; ?column? ---------- (0 rows) 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'; $$ 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) -- test CONNECT $argument create function test_connect2(connstr text) returns text as $$ connect connstr; select current_database(); $$ language plproxy; select * from test_connect2('dbname=test_part'); test_connect2 --------------- test_part (1 row) -- test CONNECT function($argument) create function test_connect3(connstr text) returns text as $$ connect text(connstr); select current_database(); $$ language plproxy; select * from test_connect3('dbname=test_part'); test_connect3 --------------- test_part (1 row) -- test quoting function create type "RetWeird" as ( "ColId" int4, "ColData" text ); create function "testQuoting"(username text, id integer, data text) returns "RetWeird" as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy; \c test_part create type "RetWeird" as ( "ColId" int4, "ColData" text ); create function "testQuoting"(username text, id integer, data text) returns "RetWeird" as $$ select 1::int4, 'BazOoka'::text $$ language sql; \c regression select * from "testQuoting"('user', '1', 'dat'); ColId | ColData -------+--------- 1 | BazOoka (1 row) -- test arg type quoting create domain "bad type" as text; create function test_argq(username text, "some arg" integer, "other arg" "bad type", out "bad out" text, out "bad out2" "bad type") as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy; \c test_part create domain "bad type" as text; create function test_argq(username text, "some arg" integer, "other arg" "bad type", out "bad out" text, out "bad out2" "bad type") as $$ begin return; end; $$ language plpgsql; \c regression select * from test_argq('user', 1, 'q'); bad out | bad out2 ---------+---------- | (1 row) -- test hash types function create or replace function t_hash16(int4) returns int2 as $$ declare res int2; begin res = $1::int2; return res; end; $$ language plpgsql; create or replace function t_hash64(int4) returns int8 as $$ declare res int8; begin res = $1; return res; end; $$ language plpgsql; create function test_hash16(id integer, data text) returns text as $$ cluster 'testcluster'; run on t_hash16(id); select data; $$ language plproxy; select * from test_hash16('0', 'hash16'); test_hash16 ------------- hash16 (1 row) create function test_hash64(id integer, data text) returns text as $$ cluster 'testcluster'; run on t_hash64(id); select data; $$ language plproxy; select * from test_hash64('0', 'hash64'); test_hash64 ------------- hash64 (1 row) -- test argument difference \c test_part create function test_difftypes(username text, out val1 int2, out val2 float8) as $$ begin val1 = 1; val2 = 3;return; end; $$ language plpgsql; \c regression create function test_difftypes(username text, out val1 int4, out val2 float4) as $$ cluster 'testcluster'; run on 0; $$ language plproxy; select * from test_difftypes('types'); val1 | val2 ------+------ 1 | 3 (1 row) -- test simple hash \c test_part create function test_simple(partno int4) returns int4 as $$ begin return $1; end; $$ language plpgsql; \c regression create function test_simple(partno int4) returns int4 as $$ cluster 'testcluster'; run on $1; $$ language plproxy; select * from test_simple(0); test_simple ------------- 0 (1 row) drop function test_simple(int4); create function test_simple(partno int4) returns int4 as $$ cluster 'testcluster'; run on partno; $$ language plproxy; select * from test_simple(0); test_simple ------------- 0 (1 row) -- test error passing \c test_part create function test_error1() returns int4 as $$ begin select line2err; return 0; end; $$ language plpgsql; \c regression create function test_error1() returns int4 as $$ cluster 'testcluster'; run on 0; $$ language plproxy; select * from test_error1(); ERROR: public.test_error1(0): [test_part] REMOTE ERROR: column "line2err" does not exist at character 8 create function test_error2() returns int4 as $$ cluster 'testcluster'; run on 0; select err; $$ language plproxy; select * from test_error2(); NOTICE: PL/Proxy: dropping stale conn ERROR: public.test_error2(0): [test_part] REMOTE ERROR: column "err" does not exist at character 8 create function test_error3() returns int4 as $$ connect 'dbname=test_part'; $$ language plproxy; select * from test_error3(); ERROR: public.test_error3(0): [test_part] REMOTE ERROR: function public.test_error3() does not exist at character 21 -- test invalid db create function test_bad_db() returns int4 as $$ cluster 'badcluster'; $$ language plproxy; select * from test_bad_db(); ERROR: PL/Proxy function public.test_bad_db(0): [nonex_db] PQconnectPoll: FATAL: database "nonex_db" does not exist create function test_bad_db2() returns int4 as $$ connect 'dbname=wrong_name_db'; $$ language plproxy; select * from test_bad_db2(); ERROR: PL/Proxy function public.test_bad_db2(0): [wrong_name_db] PQconnectPoll: FATAL: database "wrong_name_db" does not exist plproxy-2.9/test/sql/000077500000000000000000000000001353753647500147015ustar00rootroot00000000000000plproxy-2.9/test/sql/plproxy_alter.sql000066400000000000000000000020701353753647500203250ustar00rootroot00000000000000 \c test_part0 create function test_table1(out id int4, out data text, out data2 text, out data3 text) as $$ begin id = 1; data = 'Data1'; data2 = 'Data2'; data3 = 'Data3'; return; end; $$ language plpgsql; select * from test_table1(); \c regression create table ret_table ( id int4, data text ); create function test_table1() returns ret_table as $$ cluster 'testcluster'; run on 0; $$ language plproxy; select * from test_table1(); -- add column alter table ret_table add column data2 text; select * from test_table1(); \c regression select * from test_table1(); -- drop & add alter table ret_table drop column data2; alter table ret_table add column data3 text; select * from test_table1(); \c regression select * from test_table1(); -- drop alter table ret_table drop column data3; select * from test_table1(); \c regression select * from test_table1(); -- add again alter table ret_table add column data3 text; alter table ret_table add column data2 text; select * from test_table1(); \c regression select * from test_table1(); plproxy-2.9/test/sql/plproxy_cancel.sql000066400000000000000000000014431353753647500204460ustar00rootroot00000000000000 -- test canceling \c test_part0 create function rsleep(val int4) returns int4 as $$ begin perform pg_sleep(val); return 1; end; $$ language plpgsql; \c test_part1 create function rsleep(val int4) returns int4 as $$ begin perform pg_sleep(val); return 1; end; $$ language plpgsql; \c test_part2 create function rsleep(val int4) returns int4 as $$ begin perform pg_sleep(val); return 1; end; $$ language plpgsql; \c test_part3 create function rsleep(val int4) returns int4 as $$ begin perform pg_sleep(val); return 1; end; $$ language plpgsql; \c regression create function rsleep(val int4, out res int4) returns setof int4 as $$ cluster 'testcluster'; run on all; $$ language plproxy; set statement_timeout = '1000'; select * from rsleep(10); -- test if works later select * from rsleep(0); plproxy-2.9/test/sql/plproxy_clustermap.sql000066400000000000000000000033471353753647500214050ustar00rootroot00000000000000create 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); plproxy-2.9/test/sql/plproxy_dynamic_record.sql000066400000000000000000000023321353753647500222010ustar00rootroot00000000000000-- dynamic query support testing create or replace function dynamic_query(q text) returns setof record as $x$ cluster 'map0'; run on all; $x$ language plproxy; \c test_part0 create or replace function dynamic_query(q text) returns setof record as $x$ declare ret record; begin for ret in execute q loop return next ret; end loop; return; end; $x$ language plpgsql; create table dynamic_query_test ( id integer, username text, other text ); insert into dynamic_query_test values ( 1, 'user1', 'blah'); insert into dynamic_query_test values ( 2, 'user2', 'foo'); \c regression select * from dynamic_query('select * from dynamic_query_test') as (id integer, username text, other text); select * from dynamic_query('select id, username from dynamic_query_test') as foo(id integer, username text); -- invalid usage select * from dynamic_query('select count(1) from pg_class'); select dynamic_query('select count(1) from pg_class'); -- test errors create or replace function dynamic_query_select() returns setof record as $x$ cluster 'map0'; run on all; select id, username from dynamic_query_test; $x$ language plproxy; select * from dynamic_query_select() as (id integer, username text); plproxy-2.9/test/sql/plproxy_encoding.sql000066400000000000000000000124241353753647500210100ustar00rootroot00000000000000 ------------------------------------------------- -- encoding tests ------------------------------------------------- set client_encoding = 'utf8'; -- google translate says: -- column: コラム -- table: テーブル -- client data: クライアント側のデータ -- proxy data: プロキシデータ -- remote data: リモートデータ -- argument: 引数 set client_min_messages = 'warning'; drop database if exists test_enc_proxy; drop database if exists test_enc_part; create database test_enc_proxy with encoding 'euc_jp' template template0; create database test_enc_part with encoding 'utf-8' template template0; -- initialize proxy db \c test_enc_proxy set client_encoding = 'utf-8'; create or replace language plpgsql; set client_min_messages = 'warning'; \set ECHO none \i sql/plproxy.sql \set ECHO all create schema plproxy; create or replace function plproxy.get_cluster_version(cluster_name text) returns integer as $$ begin return 1; 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 return; end; $$ language plpgsql; create or replace function plproxy.get_cluster_partitions(cluster_name text) returns setof text as $$ begin return next 'host=127.0.0.1 dbname=test_enc_part'; return; end; $$ language plpgsql; create table intl_data (id int4, "コラム" text); create function test_encoding() returns setof intl_data as $$ cluster 'testcluster'; run on 0; select * from intl_data order by 1; $$ language plproxy; create function test_encoding2(text) returns setof intl_data as $$ cluster 'testcluster'; run on 0; select 0 as id, $1 as "コラム"; $$ language plproxy; create function test_encoding3(text) returns setof intl_data as $$ cluster 'testcluster'; run on 0; $$ language plproxy; -- initialize part db \c test_enc_part create or replace language plpgsql; set client_min_messages = 'warning'; set client_encoding = 'utf8'; create table intl_data (id int4, "コラム" text); insert into intl_data values (1, 'リモートデータ'); create function test_encoding3(text) returns setof intl_data as $$ declare rec intl_data%rowtype; begin raise notice 'got: %', $1; rec := (3, $1); return next rec; return; end; $$ language plpgsql; set client_encoding = 'sjis'; select * from intl_data order by 1; set client_encoding = 'euc_jp'; select * from intl_data order by 1; set client_encoding = 'utf-8'; select * from intl_data order by 1; -- test \c test_enc_proxy set client_encoding = 'sjis'; select * from test_encoding(); set client_encoding = 'euc_jp'; select * from test_encoding(); set client_encoding = 'utf8'; select * from test_encoding(); select * from test_encoding2('クライアント側のデータ'); select * from test_encoding3('クライアント側のデータ'); \c template1 set client_min_messages = 'warning'; drop database if exists test_enc_proxy; drop database if exists test_enc_part; create database test_enc_proxy with encoding 'utf-8' template template0; create database test_enc_part with encoding 'euc_jp' template template0; -- initialize proxy db \c test_enc_proxy create or replace language plpgsql; set client_min_messages = 'warning'; \set ECHO none \i sql/plproxy.sql \set ECHO all set client_encoding = 'utf8'; create schema plproxy; create or replace function plproxy.get_cluster_version(cluster_name text) returns integer as $$ begin return 1; 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 return; end; $$ language plpgsql; create or replace function plproxy.get_cluster_partitions(cluster_name text) returns setof text as $$ begin return next 'host=127.0.0.1 dbname=test_enc_part'; return; end; $$ language plpgsql; create table intl_data (id int4, "コラム" text); create function test_encoding() returns setof intl_data as $$ cluster 'testcluster'; run on 0; select * from intl_data order by 1; $$ language plproxy; create function test_encoding2(text) returns setof intl_data as $$ cluster 'testcluster'; run on 0; select 0 as id, $1 as "コラム"; $$ language plproxy; create function test_encoding3(text) returns setof intl_data as $$ cluster 'testcluster'; run on 0; $$ language plproxy; -- initialize part db \c test_enc_part create or replace language plpgsql; set client_min_messages = 'warning'; set client_encoding = 'utf8'; create table intl_data (id int4, "コラム" text); insert into intl_data values (1, 'リモートデータ'); create function test_encoding3(text) returns setof intl_data as $$ declare rec intl_data%rowtype; begin raise notice 'got: %', $1; rec := (3, $1); return next rec; return; end; $$ language plpgsql; set client_encoding = 'sjis'; select * from intl_data order by 1; set client_encoding = 'euc_jp'; select * from intl_data order by 1; set client_encoding = 'utf-8'; select * from intl_data order by 1; -- test \c test_enc_proxy set client_encoding = 'utf8'; set client_encoding = 'sjis'; select * from test_encoding(); set client_encoding = 'euc_jp'; select * from test_encoding(); set client_encoding = 'utf-8'; select * from test_encoding(); select * from test_encoding2('クライアント側のデータ'); select * from test_encoding3('クライアント側のデータ'); plproxy-2.9/test/sql/plproxy_errors.sql000066400000000000000000000064731353753647500205450ustar00rootroot00000000000000\set VERBOSITY terse -- 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'); -- should work create function test_err_none(dat text) returns text as $$ cluster 'testcluster'; run on hashtext($1); select 'ok'; $$ language plproxy; select * from test_err_none('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 hashtext(dat); select dat as res2, 'foo' as res1; $$ language plproxy; select * from test_map_err4('dat'); create function test_variadic_err(first text, rest variadic text[]) returns text as $$ cluster 'testcluster'; $$ language plproxy; select * from test_variadic_err('dat', 'dat', 'dat'); create function test_volatile_err(dat text) returns text stable as $$ cluster 'testcluster'; $$ language plproxy; select * from test_volatile_err('dat'); create function test_pseudo_arg_err(dat cstring) returns text as $$ cluster 'testcluster'; $$ language plproxy; select * from test_pseudo_arg_err(textout('dat')); create function test_pseudo_ret_err(dat text) returns cstring as $$ cluster 'testcluster'; $$ language plproxy; -- not detected in validator select * from test_pseudo_ret_err('dat'); create function test_runonall_err(dat text) returns text as $$ cluster 'testcluster'; run on all; $$ language plproxy; select * from test_runonall_err('dat'); -- make sure that errors from non-setof functions returning <> 1 row have -- a proper sqlstate create function test_no_results_plproxy() returns int as $$ cluster 'testcluster'; run on any; select 1 from pg_database where datname = ''; $$ language plproxy; create function test_no_results() returns void as $$ begin begin perform test_no_results_plproxy(); exception when no_data_found then null; end; end; $$ language plpgsql; select * from test_no_results(); create function test_multi_results_plproxy() returns int as $$ cluster 'testcluster'; run on any; select 1 from pg_database; $$ language plproxy; create function test_multi_results() returns void as $$ begin begin perform test_multi_results_plproxy(); exception when too_many_rows then null; end; end; $$ language plpgsql; select * from test_multi_results(); plproxy-2.9/test/sql/plproxy_init.sql000066400000000000000000000035631353753647500201710ustar00rootroot00000000000000 \set ECHO none set client_min_messages = 'warning'; \i sql/plproxy.sql create or replace language plpgsql; set client_min_messages = 'warning'; -- 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; if cluster_name = 'badcluster' 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; if cluster_name = 'badcluster' then return next 'host=127.0.0.1 dbname=nonex_db'; 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 return; end; $$ language plpgsql; ------------------------------------------------- -- intialize part ------------------------------------------------- drop database if exists test_part; drop database if exists test_part0; drop database if exists test_part1; drop database if exists test_part2; drop database if exists test_part3; create database test_part; create database test_part0; create database test_part1; create database test_part2; create database test_part3; drop database if exists test_enc_proxy; drop database if exists test_enc_part; \c test_part create or replace language plpgsql; \c test_part0 create or replace language plpgsql; \c test_part1 create or replace language plpgsql; \c test_part2 create or replace language plpgsql; \c test_part3 create or replace language plpgsql; plproxy-2.9/test/sql/plproxy_many.sql000066400000000000000000000044711353753647500201710ustar00rootroot00000000000000create 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') order by 1; -- 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.5); 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; -- expect that 20 calls use all partitions select distinct test_multi(0, 'foo') from generate_series(1,20) order by 1; plproxy-2.9/test/sql/plproxy_range.sql000066400000000000000000000012511353753647500203120ustar00rootroot00000000000000 \c test_part0 CREATE TYPE f8range AS RANGE ( subtype = float8, subtype_diff = float8mi ); create or replace function test_range(in id int, inout frange f8range, out irange int4range) returns record as $$ begin irange := '[20,30)'; return; end; $$ language plpgsql; select * from test_range(0, '(1.5,2.4]'); \c regression CREATE TYPE f8range AS RANGE ( subtype = float8, subtype_diff = float8mi ); create or replace function test_range(in _id integer, inout frange f8range, out irange int4range) returns setof record as $$ cluster 'testcluster'; run on _id; $$ language plproxy; select * from test_range(0, '(1.5,2.4]'); plproxy-2.9/test/sql/plproxy_select.sql000066400000000000000000000050121353753647500204740ustar00rootroot00000000000000 -- 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); create function get_zero() returns setof integer as $x$ cluster 'testcluster'; run on all; select (0*0); $x$ language plproxy; select * from get_zero(); \c test_part create table numbers ( num int, name text ); insert into numbers values (1, 'one'); insert into numbers values (2, 'two'); create function ret_numtuple(int) returns numbers as $x$ select num, name from numbers where num = $1; $x$ language sql; \c regression create type numbers_type as (num int, name text); create function get_one() returns setof numbers_type as $x$ cluster 'testcluster'; run on all; select (ret_numtuple(1)).num, (ret_numtuple(1)).name; $x$ language plproxy; select * from get_one(); \c test_part create function remote_func(a varchar, b varchar, c varchar) returns void as $$ begin return; end; $$ language plpgsql; \c regression CREATE OR REPLACE FUNCTION test1(x integer, a varchar, b varchar, c varchar) RETURNS void AS $$ CLUSTER 'testcluster'; RUN ON 0; SELECT * FROM remote_func(a, b, c); $$ LANGUAGE plproxy; select * from test1(1, 'a', NULL,NULL); select * from test1(1, NULL, NULL,NULL); CREATE OR REPLACE FUNCTION test2(a varchar, b varchar, c varchar) RETURNS void AS $$ CLUSTER 'testcluster'; RUN ON 0; SELECT * FROM remote_func(a, b, c); $$ LANGUAGE plproxy; select * from test2(NULL, NULL, NULL); select * from test2('a', NULL, NULL); CREATE OR REPLACE FUNCTION test3(a varchar, b varchar, c varchar) RETURNS void AS $$ CLUSTER 'testcluster'; RUN ON 0; SELECT * FROM remote_func(a, c, b); $$ LANGUAGE plproxy; select * from test3(NULL,NULL, 'a'); select * from test3('a', NULL,NULL); plproxy-2.9/test/sql/plproxy_split.sql000066400000000000000000000117311353753647500203550ustar00rootroot00000000000000-- partition functions \c test_part0 create or replace function test_array(a text[], b text[], c text) returns text as $$ select current_database() || ' $1:' || array_to_string($1, ',') || ' $2:' || array_to_string($2, ',') || ' $3:' || $3; $$ language sql; \c test_part1 create or replace function test_array(a text[], b text[], c text) returns text as $$ select current_database() || ' $1:' || array_to_string($1, ',') || ' $2:' || array_to_string($2, ',') || ' $3:' || $3; $$ language sql; \c test_part2 create or replace function test_array(a text[], b text[], c text) returns text as $$ select current_database() || ' $1:' || array_to_string($1, ',') || ' $2:' || array_to_string($2, ',') || ' $3:' || $3; $$ language sql; \c test_part3 create or replace function test_array(a text[], b text[], c text) returns text as $$ select current_database() || ' $1:' || array_to_string($1, ',') || ' $2:' || array_to_string($2, ',') || ' $3:' || $3; $$ language sql; \c regression -- invalid arg reference create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split $4; cluster 'testcluster'; run on 0;$$ language plproxy; select * from test_array(array['a'], array['g'], 'foo'); -- invalid arg name create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split x; cluster 'testcluster'; run on 0; $$ language plproxy; select * from test_array(array['a'], array['b', 'c'], 'foo'); -- cannot split more than once create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split a, b, b; cluster 'testcluster'; run on 0; $$ language plproxy; select * from test_array(array['a'], array['b', 'c'], 'foo'); -- attempt to split non-array create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split $3; cluster 'testcluster'; run on 0;$$ language plproxy; select * from test_array(array['a'], array['g'], 'foo'); -- array size/dimensions mismatch create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split a, b; cluster 'testcluster'; run on 0; $$ language plproxy; select * from test_array(array['a'], array['b', 'c'], 'foo'); select * from test_array(array['a','b','c','d'], null, 'foo'); select * from test_array(null, array['e','f','g','h'], 'foo'); select * from test_array(array[array['a1'],array['a2']], array[array['b1'],array['b2']], 'foo'); -- run on array hash, split one array create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split a; cluster 'testcluster'; run on ascii(a);$$ language plproxy; select * from test_array(array['a','b','c','d'], array['e','f','g','h'], 'foo'); -- run on text hash, split two arrays (nop split) create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split a, b; cluster 'testcluster'; run on ascii(c);$$ language plproxy; select * from test_array(array['a','b','c','d'], array['e','f','g','h'], 'foo'); -- run on array hash, split two arrays create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split a, b; cluster 'testcluster'; run on ascii(a);$$ language plproxy; select * from test_array(array['a','b','c','d'], array['e','f','g','h'], 'foo'); select * from test_array(null, null, null); select * from test_array('{}'::text[], '{}'::text[], 'foo'); -- run on text hash, split all arrays create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split all; cluster 'testcluster'; run on ascii(c);$$ language plproxy; select * from test_array(array['a','b','c','d'], array['e','f','g','h'], 'foo'); -- run on text hash, attempt to split all arrays but none are present create or replace function test_nonarray_split(a text, b text, c text) returns setof text as $$ split all; cluster 'testcluster'; run on ascii(a); select * from test_array(array[a], array[b], c); $$ language plproxy; select * from test_nonarray_split('a', 'b', 'c'); -- run on array hash, split all arrays create or replace function test_array(a text[], b text[], c text) returns setof text as $$ split all; cluster 'testcluster'; run on ascii(a);$$ language plproxy; select * from test_array(array['a','b','c','d'], array['e','f','g','h'], 'foo'); -- run on arg create or replace function test_array_direct(a integer[], b text[], c text) returns setof text as $$ split a; cluster 'testcluster'; run on a; select test_array('{}'::text[], b, c);$$ language plproxy; select * from test_array_direct(array[2,3], array['a','b','c','d'], 'foo'); create or replace function test_array_direct(a integer[], b text[], c text) returns setof text as $$ split a, b; cluster 'testcluster'; run on a; select test_array('{}'::text[], b, c);$$ language plproxy; select * from test_array_direct(array[0,1,2,3], array['a','b','c','d'], 'foo'); plproxy-2.9/test/sql/plproxy_sqlmed.sql000066400000000000000000000076421353753647500205150ustar00rootroot00000000000000 \set VERBOSITY terse set client_min_messages = 'warning'; create server sqlmedcluster foreign data wrapper plproxy options ( partition_0 'dbname=test_part3 host=localhost', partition_1 'dbname=test_part2 host=localhost', partition_2 'dbname=test_part1 host=localhost', partition_3 'dbname=test_part0 host=localhost'); create or replace function sqlmed_test1() returns setof text as $$ cluster 'sqlmedcluster'; run on 0; select 'plproxy: user=' || current_user || ' dbname=' || current_database(); $$ language plproxy; drop user if exists test_user_alice; drop user if exists test_user_bob; drop user if exists test_user_charlie; create user test_user_alice password 'supersecret'; create user test_user_bob password 'secret'; create user test_user_charlie password 'megasecret'; -- no user mapping set session authorization test_user_bob; select * from sqlmed_test1(); reset session authorization; -- add a public user mapping create user mapping for public server sqlmedcluster options ( user 'test_user_bob', password 'secret1'); -- no access to foreign server set session authorization test_user_bob; select * from sqlmed_test1(); reset session authorization; -- ok, access granted grant usage on foreign server sqlmedcluster to test_user_bob; set session authorization test_user_bob; select * from sqlmed_test1(); reset session authorization; -- test security definer create user mapping for test_user_alice server sqlmedcluster; create user mapping for test_user_charlie server sqlmedcluster; grant usage on foreign server sqlmedcluster to test_user_alice; grant usage on foreign server sqlmedcluster to test_user_charlie; create or replace function sqlmed_test_alice() returns setof text as $$ cluster 'sqlmedcluster'; run on 0; select 'plproxy: user=' || current_user || ' dbname=' || current_database(); $$ language plproxy security definer; alter function sqlmed_test_alice() owner to test_user_alice; create or replace function sqlmed_test_charlie() returns setof text as $$ cluster 'sqlmedcluster'; run on 0; select 'plproxy: user=' || current_user || ' dbname=' || current_database(); $$ language plproxy security definer; alter function sqlmed_test_charlie() owner to test_user_charlie; -- call as alice set session authorization test_user_alice; select * from sqlmed_test_alice(); select * from sqlmed_test_charlie(); reset session authorization; -- call as charlie set session authorization test_user_charlie; select * from sqlmed_test_alice(); select * from sqlmed_test_charlie(); reset session authorization; -- test refresh too alter user mapping for test_user_charlie server sqlmedcluster options (add user 'test_user_alice'); set session authorization test_user_bob; select * from sqlmed_test_charlie(); reset session authorization; -- cluster definition validation -- partition numbers must be consecutive alter server sqlmedcluster options (drop partition_2); select * from sqlmed_test1(); -- invalid partition count alter server sqlmedcluster options (drop partition_3, add partition_2 'dbname=test_part1 host=localhost'); select * from sqlmed_test1(); -- switching betweem SQL/MED and compat mode create or replace function sqlmed_compat_test() returns setof text as $$ cluster 'testcluster'; run on 0; select 'plproxy: part=' || current_database(); $$ language plproxy; -- testcluster select * from sqlmed_compat_test(); -- override the test cluster with a SQL/MED definition drop server if exists testcluster cascade; create server testcluster foreign data wrapper plproxy options (partition_0 'dbname=regression host=localhost'); create user mapping for public server testcluster; -- sqlmed testcluster select * from sqlmed_compat_test(); -- now drop the SQL/MED testcluster, and test fallback drop server testcluster cascade; -- back on testcluster again select * from sqlmed_compat_test(); plproxy-2.9/test/sql/plproxy_table.sql000066400000000000000000000012511353753647500203050ustar00rootroot00000000000000 \c test_part0 create or replace function test_ret_table(id int) returns table(id int, t text) as $$ select * from (values(1, 'test'),(2, 'toto') ) as toto; $$ language sql; select * from test_ret_table(0); \c regression create or replace function test_ret_table_normal(in _id integer, out id integer, out t text) returns setof record as $$ cluster 'testcluster'; run on _id; target test_ret_table; $$ language plproxy; select * from test_ret_table_normal(0); create or replace function test_ret_table(in _id integer) returns table (id integer, t text) as $$ cluster 'testcluster'; run on _id; $$ language plproxy; select * from test_ret_table(0); plproxy-2.9/test/sql/plproxy_target.sql000066400000000000000000000015011353753647500205020ustar00rootroot00000000000000 -- test target clause create function test_target(xuser text, tmp boolean) returns text as $$ cluster 'testcluster'; run on 0; target test_target_dst; $$ language plproxy; \c test_part0 create function test_target_dst(xuser text, tmp boolean) returns text as $$ begin return 'dst'; end; $$ language plpgsql; \c regression select * from test_target('foo', true); -- test errors create function test_target_err1(xuser text) returns text as $$ cluster 'testcluster'; run on 0; target test_target_dst; target test_target_dst; $$ language plproxy; select * from test_target_err1('asd'); create function test_target_err2(xuser text) returns text as $$ cluster 'testcluster'; run on 0; target test_target_dst; select 1; $$ language plproxy; select * from test_target_err2('asd'); plproxy-2.9/test/sql/plproxy_test.sql000066400000000000000000000221241353753647500201770ustar00rootroot00000000000000\set VERBOSITY terse -- 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 1 from (select set_config(name, 'escape', false) as ignore from pg_settings where name = 'bytea_output') x where x.ignore = 'foo'; 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'; $$ 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(); -- test CONNECT $argument create function test_connect2(connstr text) returns text as $$ connect connstr; select current_database(); $$ language plproxy; select * from test_connect2('dbname=test_part'); -- test CONNECT function($argument) create function test_connect3(connstr text) returns text as $$ connect text(connstr); select current_database(); $$ language plproxy; select * from test_connect3('dbname=test_part'); -- test quoting function create type "RetWeird" as ( "ColId" int4, "ColData" text ); create function "testQuoting"(username text, id integer, data text) returns "RetWeird" as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy; \c test_part create type "RetWeird" as ( "ColId" int4, "ColData" text ); create function "testQuoting"(username text, id integer, data text) returns "RetWeird" as $$ select 1::int4, 'BazOoka'::text $$ language sql; \c regression select * from "testQuoting"('user', '1', 'dat'); -- test arg type quoting create domain "bad type" as text; create function test_argq(username text, "some arg" integer, "other arg" "bad type", out "bad out" text, out "bad out2" "bad type") as $$ cluster 'testcluster'; run on hashtext(username); $$ language plproxy; \c test_part create domain "bad type" as text; create function test_argq(username text, "some arg" integer, "other arg" "bad type", out "bad out" text, out "bad out2" "bad type") as $$ begin return; end; $$ language plpgsql; \c regression select * from test_argq('user', 1, 'q'); -- test hash types function create or replace function t_hash16(int4) returns int2 as $$ declare res int2; begin res = $1::int2; return res; end; $$ language plpgsql; create or replace function t_hash64(int4) returns int8 as $$ declare res int8; begin res = $1; return res; end; $$ language plpgsql; create function test_hash16(id integer, data text) returns text as $$ cluster 'testcluster'; run on t_hash16(id); select data; $$ language plproxy; select * from test_hash16('0', 'hash16'); create function test_hash64(id integer, data text) returns text as $$ cluster 'testcluster'; run on t_hash64(id); select data; $$ language plproxy; select * from test_hash64('0', 'hash64'); -- test argument difference \c test_part create function test_difftypes(username text, out val1 int2, out val2 float8) as $$ begin val1 = 1; val2 = 3;return; end; $$ language plpgsql; \c regression create function test_difftypes(username text, out val1 int4, out val2 float4) as $$ cluster 'testcluster'; run on 0; $$ language plproxy; select * from test_difftypes('types'); -- test simple hash \c test_part create function test_simple(partno int4) returns int4 as $$ begin return $1; end; $$ language plpgsql; \c regression create function test_simple(partno int4) returns int4 as $$ cluster 'testcluster'; run on $1; $$ language plproxy; select * from test_simple(0); drop function test_simple(int4); create function test_simple(partno int4) returns int4 as $$ cluster 'testcluster'; run on partno; $$ language plproxy; select * from test_simple(0); -- test error passing \c test_part create function test_error1() returns int4 as $$ begin select line2err; return 0; end; $$ language plpgsql; \c regression create function test_error1() returns int4 as $$ cluster 'testcluster'; run on 0; $$ language plproxy; select * from test_error1(); create function test_error2() returns int4 as $$ cluster 'testcluster'; run on 0; select err; $$ language plproxy; select * from test_error2(); create function test_error3() returns int4 as $$ connect 'dbname=test_part'; $$ language plproxy; select * from test_error3(); -- test invalid db create function test_bad_db() returns int4 as $$ cluster 'badcluster'; $$ language plproxy; select * from test_bad_db(); create function test_bad_db2() returns int4 as $$ connect 'dbname=wrong_name_db'; $$ language plproxy; select * from test_bad_db2(); plproxy-2.9/test/test-upgrade.sh000077500000000000000000000006431353753647500170500ustar00rootroot00000000000000#! /bin/sh set -e dbname=plproxyupgrade upgrade() { first="$1" second="$2" dropdb ${dbname} || true make -s -C $first clean install createdb ${dbname} psql -d $dbname -c 'create extension plproxy;' make -s -C $second clean install psql -d $dbname -c 'alter extension plproxy update;' } upgrade plproxy-2.6 plproxy-2.9 upgrade plproxy-2.7 plproxy-2.9 upgrade plproxy-2.8 plproxy-2.9