preprepare/0000755000401600037200000000000012251637404012340 5ustar cbecredativpreprepare/pre_prepare--unpackaged--0.4.sql0000644000401600037200000000025311631160334020110 0ustar cbecredativ alter extension pre_prepare add function discard(); alter extension pre_prepare add function prepare_all(); alter extension pre_prepare add function prepare_all(text); preprepare/Vagrantfile0000664000401600040160000000047712251626514013466 0ustar cbecbe# -*- mode: ruby -*- # vi: set ft=ruby : # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2" Vagrant.configure("2") do |config| config.vm.box = "wheezy64" config.vm.provision "shell" do |s| s.path = "bootstrap.sh" s.privileged = false end end preprepare/expected/0000775000401600040160000000000012251635415013072 5ustar cbecbepreprepare/expected/create_extension.out0000664000401600040160000000003612251633556017165 0ustar cbecbecreate extension pre_prepare; preprepare/expected/pre_prepare.out0000664000401600040160000000070512251636165016134 0ustar cbecbeCREATE SCHEMA pre_prepare; SET preprepare.relation = 'pre_prepare.statements'; SET client_min_messages = warning; CREATE TABLE pre_prepare.statements(name text primary key, statement text); RESET client_min_messages; INSERT INTO pre_prepare.statements VALUES ('test', 'prepare test as select 1'); SELECT prepare_all(); NOTICE: Preparing statement name: test prepare_all ------------- (1 row) EXECUTE test; ?column? ---------- 1 (1 row) preprepare/expected/create_module.out0000664000401600040160000000001612251636104016424 0ustar cbecbe\set ECHO off preprepare/changelog0000644000401600037200000000044112251637404014211 0ustar cbecredativpreprepare (0.6) upstream; urgency=medium * Add regression test. -- Christoph Berg Tue, 10 Dec 2013 17:18:47 +0100 preprepare (0.1-1) unstable; urgency=low * Initial release -- Dimitri Fontaine Wed, 13 May 2009 16:42:29 +0200 preprepare/build/0000775000401600040160000000000012251626514012370 5ustar cbecbepreprepare/Makefile0000664000401600040160000000224212251633262012727 0ustar cbecbeEXTENSION = pre_prepare MODULES = pre_prepare DATA = pre_prepare--0.4.sql pre_prepare--unpackaged--0.4.sql DATA_built = pre_prepare--0.4.sql # use extenstion in 9.1+ SETUPSQL = $(shell $(PG_CONFIG) --version | grep -qE " 8\.| 9\.0" && echo create_module || echo create_extension) REGRESS = $(SETUPSQL) pre_prepare PG_CONFIG = pg_config PGXS = $(shell $(PG_CONFIG) --pgxs) include $(PGXS) SRC = . TARGET = ./build BUILDDIR = /tmp/preprepare ORIG = preprepare.orig DEBIAN = debian PACKAGE = postgresql-?.?-preprepare SOURCE = preprepare README.html: README.asciidoc asciidoc -a toc README.asciidoc pre_prepare--0.4.sql: pre_prepare.sql cp $< $@ unsign-deb: prepare cd $(BUILDDIR)/$(SOURCE) && debuild -us -uc cp $(BUILDDIR)/$(PACKAGE)_* $(TARGET) cp $(BUILDDIR)/$(SOURCE)_* $(TARGET) deb: prepare cd $(BUILDDIR)/$(SOURCE) && debuild cp $(BUILDDIR)/$(PACKAGE)_* $(TARGET) cp $(BUILDDIR)/$(SOURCE)_* $(TARGET) prepare: -test -d $(BUILDDIR) && rm -rf $(BUILDDIR) mkdir -p $(BUILDDIR)/$(SOURCE) rsync -Ca --exclude $(DEBIAN) $(SRC)/* $(BUILDDIR)/$(SOURCE) rsync -Ca $(BUILDDIR)/$(SOURCE)/ $(BUILDDIR)/$(ORIG)/ rsync -Ca $(DEBIAN) $(BUILDDIR)/$(SOURCE) preprepare/pre_prepare.c0000664000401600040160000002145012251626514013743 0ustar cbecbe/* * pre_prepare allows to store statements in a table and will prepare them * all when the prepare_all() function is being called. * * Unfortunately it's not possible to SPI_connect() when LOAD is done via * local_preload_libraries, so the function call can't be made transparent * to the client connection. */ #include #include "postgres.h" #include "executor/spi.h" #include "utils/guc.h" #include "utils/elog.h" #include "utils/palloc.h" #include "utils/builtins.h" #include "libpq/pqformat.h" #include "access/xact.h" /* #define DEBUG */ /* * This code has only been tested with PostgreSQL 8.3. */ #ifdef PG_VERSION_NUM #define PG_MAJOR_VERSION (PG_VERSION_NUM / 100) #else #error "Unknown postgresql version" #endif #if PG_MAJOR_VERSION != 803 && PG_MAJOR_VERSION != 804 \ && PG_MAJOR_VERSION != 900 && PG_MAJOR_VERSION != 901 \ && PG_MAJOR_VERSION != 902 && PG_MAJOR_VERSION != 903 \ && PG_MAJOR_VERSION != 904 #error "Unsupported postgresql version" #endif PG_MODULE_MAGIC; /* * In 8.3 it seems that snapmgr.h is unavailable for module code * * That means there's no support for at_init and local_preload_libraries in * this version. */ #if PG_MAJOR_VERSION > 803 #include "utils/snapmgr.h" static bool pre_prepare_at_init = false; #endif static char *pre_prepare_relation = NULL; void _PG_init(void); Datum prepare_all(PG_FUNCTION_ARGS); /* * Check that pre_prepare.relation is setup to an existing table. * * The catalog inquiry could use some optimisation (index use prevented). */ static inline bool check_pre_prepare_relation(const char *relation_name) { int err; char *stmpl = "SELECT 1 FROM pg_class WHERE " \ "(SELECT nspname from pg_namespace WHERE oid = relnamespace) " \ "|| '.' || relname = '%s';"; int len = (strlen(stmpl) - 2) + strlen(relation_name) + 1; char *select = (char *)palloc(len * sizeof(char)); snprintf(select, len, stmpl, relation_name); #ifdef DEBUG elog(NOTICE, select); #endif err = SPI_execute(select, true, 1); if( err != SPI_OK_SELECT ) elog(ERROR, "SPI_execute: %s", SPI_result_code_string(err)); return 1 == SPI_processed; } /* * Prepare all the statements in pre_prepare.relation */ static inline int pre_prepare_all(const char *relation_name) { int err, nbrows = 0; char *stmpl = "SELECT name, statement FROM %s"; int len = (strlen(stmpl) - 2) + strlen(relation_name) + 1; char *select = (char *)palloc(len); snprintf(select, len, stmpl, relation_name); err = SPI_execute(select, true, 0); if( err != SPI_OK_SELECT ) { elog(ERROR, "SPI_execute: %s", SPI_result_code_string(err)); return -1; } nbrows = SPI_processed; if( nbrows > 0 && SPI_tuptable != NULL ) { TupleDesc tupdesc = SPI_tuptable->tupdesc; SPITupleTable *tuptable = SPI_tuptable; int row; for (row = 0; row < nbrows; row++) { HeapTuple tuple = tuptable->vals[row]; char *name = SPI_getvalue(tuple, tupdesc, 1); char *stmt = SPI_getvalue(tuple, tupdesc, 2); elog(NOTICE, "Preparing statement name: %s", name); err = SPI_execute(stmt, false, 0); if( err != SPI_OK_UTILITY ) { elog(ERROR, "SPI_execute: %s", SPI_result_code_string(err)); return -1; } } } else elog(NOTICE, "No statement to prepare found in '%s'", pre_prepare_relation); return nbrows; } /* * _PG_init() - library load-time initialization * * DO NOT make this static nor change its name! * * Init the module, all we have to do here is getting our GUC */ void _PG_init(void) { PG_TRY(); { #if PG_MAJOR_VERSION == 804 bool at_init = false; if( parse_bool(GetConfigOptionByName("prepare.at_init", NULL), &at_init) ) pre_prepare_at_init = at_init; #endif pre_prepare_relation = GetConfigOptionByName("prepare.relation", NULL); } PG_CATCH(); { /* * From 8.4 the Custom variables take two new options, the default value * and a flags field */ #if PG_MAJOR_VERSION == 803 DefineCustomStringVariable("preprepare.relation", "Table name where to find statements to prepare", "Can be schema qualified, must have columns " "\"name\" and \"statement\"", &pre_prepare_relation, PGC_USERSET, NULL, NULL); /* * It's missing a way to use PushActiveSnapshot/PopActiveSnapshot from * within a module in 8.3 for this to be useful. * DefineCustomBoolVariable("preprepare.at_init", "Do we prepare the statements at backend start", "You have to setup local_preload_libraries too", &pre_prepare_at_init, PGC_USERSET, NULL, NULL); */ EmitWarningsOnPlaceholders("prepare.relation"); #elif PG_MAJOR_VERSION == 804 || PG_MAJOR_VERSION == 900 DefineCustomStringVariable("preprepare.relation", "Table name where to find statements to prepare", "Can be schema qualified, must have columns " "\"name\" and \"statement\"", &pre_prepare_relation, "", PGC_USERSET, GUC_NOT_IN_SAMPLE, NULL, NULL); DefineCustomBoolVariable("preprepare.at_init", "Do we prepare the statements at backend start", "You have to setup local_preload_libraries too", &pre_prepare_at_init, false, PGC_USERSET, GUC_NOT_IN_SAMPLE, NULL, NULL); EmitWarningsOnPlaceholders("prepare.relation"); EmitWarningsOnPlaceholders("prepare.at_init"); #else DefineCustomStringVariable("preprepare.relation", "Table name where to find statements to prepare", "Can be schema qualified, must have columns " "\"name\" and \"statement\"", &pre_prepare_relation, "", PGC_USERSET, GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); DefineCustomBoolVariable("preprepare.at_init", "Do we prepare the statements at backend start", "You have to setup local_preload_libraries too", &pre_prepare_at_init, false, PGC_USERSET, GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); EmitWarningsOnPlaceholders("prepare.relation"); EmitWarningsOnPlaceholders("prepare.at_init"); #endif } PG_END_TRY(); #if PG_MAJOR_VERSION >= 804 if( pre_prepare_at_init ) { int err; /* * We want to use SPI, so we need to ensure there's a current started * transaction, then take a snapshot start_xact_command(); */ Snapshot snapshot; StartTransactionCommand(); /* CommandCounterIncrement(); */ snapshot = GetTransactionSnapshot(); PushActiveSnapshot(snapshot); err = SPI_connect(); if (err != SPI_OK_CONNECT) elog(ERROR, "SPI_connect: %s", SPI_result_code_string(err)); if( ! check_pre_prepare_relation(pre_prepare_relation) ) { ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("Can not find relation '%s'", pre_prepare_relation), errhint("Set preprepare.relation to an existing table."))); } pre_prepare_all(pre_prepare_relation); err = SPI_finish(); if (err != SPI_OK_FINISH) elog(ERROR, "SPI_finish: %s", SPI_result_code_string(err)); PopActiveSnapshot(); CommitTransactionCommand(); } #endif } /* * PostgreSQL interface, with the SPI_connect and SPI_finish calls. */ PG_FUNCTION_INFO_V1(prepare_all); Datum prepare_all(PG_FUNCTION_ARGS) { char * relation = NULL; int err; /* * we support for the user to override the GUC by passing in the relation name * SELECT prepare_all('some_other_statements'); */ if( PG_NARGS() == 1 ) relation = DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(PG_GETARG_TEXT_P(0)))); else { relation = pre_prepare_relation; /* * The function is STRICT so we don't check this error case in the * previous branch */ if( relation == NULL ) ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("The custom variable preprepare.relation is not set."), errhint("Set preprepare.relation to an existing table."))); } err = SPI_connect(); if (err != SPI_OK_CONNECT) elog(ERROR, "SPI_connect: %s", SPI_result_code_string(err)); if( ! check_pre_prepare_relation(relation) ) { char *hint = "Set preprepare.relation to an existing table, schema qualified"; if( PG_NARGS() == 1 ) hint = "prepare_all requires you to schema qualify the relation name"; ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("Can not find relation '%s'", relation), errhint("%s", hint))); } #ifdef DEBUG elog(NOTICE, "preprepare.relation is found, proceeding"); #endif pre_prepare_all(relation); /* done with SPI */ err = SPI_finish(); if (err != SPI_OK_FINISH) elog(ERROR, "SPI_finish: %s", SPI_result_code_string(err)); PG_RETURN_VOID(); } preprepare/pgbouncer.ini0000644000401600037200000000103611631160334015017 0ustar cbecredativ[pgbouncer] auth_type = trust admin_users = postgres, dim log_disconnections = 1 listen_port = 63210 default_pool_size = 100 server_reset_query = server_check_query = select 1 max_client_conn = 1024 client_login_timeout = 0 log_connections = 1 server_check_delay = 10 listen_addr = * auth_file = /home/dim/pgsql/8.3/data/global/pg_auth log_pooler_errors = 1 unix_socket_dir = /tmp logfile = /tmp/pgbouncer.log pidfile = /tmp/pgbouncer.pid pool_mode = session stats_users = [databases] dim = port=3210 connect_query='SELECT prepare_all();' preprepare/README.html0000664000401600040160000005544012251626514013124 0ustar cbecbe pre_prepare

Ideas

The pre_prepare module aims to prepare all your statements as soon as possible and in a way that allows client queries not to bother at all and just call EXECUTE.

Setup

compile and install

The module is using the PG_XS build infrastructure, so just:

make
make install
psql -f `pg_config --sharedir`/contrib/pre_prepare.sql -U user dbname

Depending on the way you got PostgreSQL installed in the first place, you may need to use sudo for the make install step.

postgresql.conf

Add a custom class then the following settings:

custom_variable_classes = 'preprepare'
preprepare.relation = 'preprepare.statements'

The pre_prepare.relation is the name of the table where you’ll put all the statements you want to module to prepare. The following SQL query against the given relation must return the names and SQL statements to be prepared.

SELECT name, statement FROM <pre_prepare.relation>;

The statements won’t be edited, so must be the all PREPARE stuff.

pgbouncer

When using pgbouncer, which is a good idea, consider setting up connect_query to prepare all the statements at server connection time.

[databases]
foo = port=5432 connect_query='SELECT prepare_all();'

Of course, if using pre_prepare, you’ll want to avoid using DISCARD ALL as your reset_query

local_preload_libraries

If you have 8.3

It’s unfortunately not possible to call SPI_connect() from the module initialization routine (_PG_init()) when called via local_preload_libraries, it’s too much early. So fully transparent preparing of user given statement is not possible with the pre_prepare module, you still have to explicitely call SELECT prepare_all().

If running 8.4 or later

To call SPI_connect() in fact what’s needed is an opened transaction and a current active Snapshot. Both are possible to acquire from within a dynamically loaded module, so preprepare is supporting the case.

Update your postgresql.conf to have the following:

custom_variable_classes = 'preprepare'
preprepare.at_init  = on
preprepare.relation = 'preprepare.statements'

Then reload PostgreSQL and enjoy: any new connection will have already prepared your statements by the time you’re able to send queries, so you can forget about PREPARE and directly EXECUTE. No need for extra software.

Please note that if your statements contain any error, PostgreSQL will handle it as a FATAL error and this will effectively prevent you from connecting to your server. You’ll have to turn preprepare.at_init off again then reload, or remove pre_prepare from local_preload_libraries then restart.

Usage

First create a table where to store your statements, e.g.:

create table pre_prepare.statements(name text primary key, statement text);

The statement stored is the complete statement including the PREPARE name AS part.

insert into pre_prepare.statements values ('test', 'prepare test as select 1');

prepare_all()

Then connect to PostgreSQL (via pgbouncer if not using local_preload_libraries) and run the EXECUTE command matching with your PREPARE statements, they all are already prepared.

If not using pgbouncer nor preprepare.at_init = on, you’ll have to call the function yourself to have your statements prepared:

SELECT prepare_all();

prepare_all(schema.table)

If you’re not in a position to always prepare the same set of queries, you can use the second form of prepare_all calling, which accepts a specific statements table:

SELECT prepare_all('public.expensive_planning');

This can be automated in a special pgbouncer fake database where the connect_query will prepare specific queries when you know you need them in some occasion, but they’re way to expensive to always prepare ahead of time.

discard()

The module also offers a discard() function which does the same as DISCARD ALL except that it won’t call DEALLOCATE ALL.


preprepare/pre_prepare.control0000644000401600037200000000023511631160334016240 0ustar cbecredativ# preprepare extension comment = 'Pre Prepare your Statement server side' default_version = '0.4' module_pathname = '$libdir/pre_prepare' relocatable = true preprepare/README.asciidoc0000664000401600040160000000762512251626514013740 0ustar cbecbe= pre_prepare == Ideas The +pre_prepare+ module aims to prepare all your statements as soon as possible and in a way that allows client queries not to bother at all and just call +EXECUTE+. == Setup === compile and install The module is using the PG_XS build infrastructure, so just: make make install psql -f `pg_config --sharedir`/contrib/pre_prepare.sql -U user dbname Depending on the way you got PostgreSQL installed in the first place, you may need to use sudo for the +make install+ step. === postgresql.conf Add a custom class then the following settings: custom_variable_classes = 'preprepare' preprepare.relation = 'preprepare.statements' The +pre_prepare.relation+ is the name of the table where you'll put all the statements you want to module to prepare. The following SQL query against the given relation must return the names and SQL statements to be prepared. SELECT name, statement FROM ; The statements won't be edited, so must be the all PREPARE stuff. === pgbouncer When using pgbouncer, which is a good idea, consider setting up +connect_query+ to prepare all the statements at server connection time. [databases] foo = port=5432 connect_query='SELECT prepare_all();' Of course, if using +pre_prepare+, you'll want to avoid using +DISCARD ALL+ as your +reset_query+... === local_preload_libraries ==== If you have +8.3+ It's unfortunately not possible to call +SPI_connect()+ from the module initialization routine (+_PG_init()+) when called via +local_preload_libraries+, it's too much early. So fully transparent preparing of user given statement is not possible with the +pre_prepare+ module, you still have to explicitely call +SELECT prepare_all()+. ==== If running 8.4 or later To call +SPI_connect()+ in fact what's needed is an opened transaction and a current active Snapshot. Both are possible to acquire from within a dynamically loaded module, so +preprepare+ is supporting the case. Update your +postgresql.conf+ to have the following: custom_variable_classes = 'preprepare' preprepare.at_init = on preprepare.relation = 'preprepare.statements' Then +reload+ PostgreSQL and enjoy: any new connection will have already prepared your statements by the time you're able to send queries, so you can forget about +PREPARE+ and directly +EXECUTE+. No need for extra software. Please note that if your statements contain any error, PostgreSQL will handle it as a +FATAL+ error and this will effectively prevent you from connecting to your server. You'll have to turn preprepare.at_init +off+ again then +reload+, or remove +pre_prepare+ from +local_preload_libraries+ then +restart+. == Usage First create a table where to store your statements, e.g.: create table pre_prepare.statements(name text primary key, statement text); The statement stored is the complete statement including the +PREPARE name AS+ part. insert into pre_prepare.statements values ('test', 'prepare test as select 1'); === prepare_all() Then connect to PostgreSQL (via +pgbouncer+ if not using +local_preload_libraries+) and run the +EXECUTE+ command matching with your +PREPARE+ statements, they all are already prepared. If not using +pgbouncer+ nor +preprepare.at_init = on+, you'll have to call the function yourself to have your statements prepared: SELECT prepare_all(); === prepare_all('schema.table') If you're not in a position to always prepare the same set of queries, you can use the second form of +prepare_all+ calling, which accepts a specific statements table: SELECT prepare_all('public.expensive_planning'); This can be automated in a special +pgbouncer+ fake +database+ where the +connect_query+ will prepare specific queries when you know you need them in some occasion, but they're way to expensive to always prepare ahead of time. === discard() The module also offers a +discard()+ function which does the same as +DISCARD ALL+ except that it won't call +DEALLOCATE ALL+. preprepare/pre_prepare.sql0000664000401600040160000000102312251626514014312 0ustar cbecbe--- --- pre_prepare exports only a prepare_all() function. --- CREATE OR REPLACE FUNCTION prepare_all() RETURNS void AS '$libdir/pre_prepare', 'prepare_all' LANGUAGE C STRICT VOLATILE; CREATE OR REPLACE FUNCTION prepare_all(text) RETURNS void AS '$libdir/pre_prepare', 'prepare_all' LANGUAGE C STRICT VOLATILE; CREATE OR REPLACE FUNCTION discard() RETURNS void LANGUAGE SQL AS $$ SET SESSION AUTHORIZATION DEFAULT; RESET ALL; CLOSE ALL; UNLISTEN *; SELECT pg_advisory_unlock_all(); DISCARD PLANS; DISCARD TEMP; $$; preprepare/sql/0000775000401600040160000000000012251637103012064 5ustar cbecbepreprepare/sql/create_extension.sql0000664000401600040160000000003612251633262016145 0ustar cbecbecreate extension pre_prepare; preprepare/sql/create_module.sql0000664000401600040160000000004612251635367015427 0ustar cbecbe\set ECHO off \i pre_prepare--0.4.sql preprepare/sql/pre_prepare.sql0000664000401600040160000000052112251634630015111 0ustar cbecbeCREATE SCHEMA pre_prepare; SET preprepare.relation = 'pre_prepare.statements'; SET client_min_messages = warning; CREATE TABLE pre_prepare.statements(name text primary key, statement text); RESET client_min_messages; INSERT INTO pre_prepare.statements VALUES ('test', 'prepare test as select 1'); SELECT prepare_all(); EXECUTE test; preprepare/bootstrap.sh0000664000401600040160000000077312251626514013651 0ustar cbecbe#!/usr/bin/env bash # PostgreSQL sidsrc=/etc/apt/sources.list.d/sid-src.list echo "deb-src http://ftp.fr.debian.org/debian/ sid main" | sudo tee $sidsrc pgdg=/etc/apt/sources.list.d/pgdg.list pgdgkey=https://www.postgresql.org/media/keys/ACCC4CF8.asc echo "deb http://apt.postgresql.org/pub/repos/apt/ wheezy-pgdg main" | sudo tee $pgdg wget --quiet -O - ${pgdgkey} | sudo apt-key add - sudo apt-get update sudo apt-get install -y postgresql-server-dev-all rsync devscripts make -C /vagrant unsign-deb