diff --git a/.gitattributes b/.gitattributes index e293d87cd986e81952b11f7f6ba8a0401bac1b36..3040264f193cae97094d7d051b553decf304bcab 100644 --- a/.gitattributes +++ b/.gitattributes @@ -113,6 +113,10 @@ testfont binary /src/modules/Oracle/oracle.c foreign_ident /src/modules/Pipe/acconfig.h foreign_ident /src/modules/Pipe/pipe.c foreign_ident +/src/modules/Postgres/Makefile.in foreign_ident +/src/modules/Postgres/configure.in foreign_ident +/src/modules/Postgres/pgres_config.h.in foreign_ident +/src/modules/Postgres/postgres.c foreign_ident /src/modules/Regexp/acconfig.h foreign_ident /src/modules/Ssleay/ssleay.c foreign_ident /src/modules/Yp/acconfig.h foreign_ident diff --git a/src/modules/Postgres/ChangeLog b/src/modules/Postgres/ChangeLog new file mode 100644 index 0000000000000000000000000000000000000000..3aa1cca1ce63d1a1d96e921b74f451545d9da812 --- /dev/null +++ b/src/modules/Postgres/ChangeLog @@ -0,0 +1,65 @@ +Tue Jul 8 20:23:06 1997 Francesco Chemolli <kinkie@ai-chan> + + * postgres.c: + Corrected a small bug which manifested itself when building in a + non_threaded environment. + +Mon Jun 30 13:57:08 1997 Francesco Chemolli <kinkie@ai-chan> + + * configure.in, postgres.c, configure: + More configure.in fixes and postgres/6.1 compatibility issues addressed + + * configure: *** empty log message *** + + * configure.in: + Corretto un leggero baco nella versione aggiornata (un path sballato) + + * configure.in, configure: + Razionalizzato configure.in, ora calcola il reasonable searchpath facendo + il merge di un insieme di directories. Dovrebbe essere impercettibilmente piu` + lento ma molto piu` flessibile e semplice da mantenere. Penso che esportero` + il meccanismo anche verso altri moduli. + +Fri Jun 27 09:55:51 1997 Francesco Chemolli <kinkie@ai-chan> + + * quickmanual.txt: Quickmanual.txt: aggiornata la documentazione + + * lib/postgres.pike: Postgres.pike: + - modificato in modo da gestire l'interfaccia con meno argomenti di + Postgres.postgres + - modificato il costruttore: invece che + create(host,db,user,pass,port) (tutti facoltativi) + e`: + create([host[:port]],[db],[user],[pass]) + Questo permette di funzionare al modulo SQL generico senza grossi problemi + + * postgres.c: + Postgres.c: modificato in modo da prendere solo tre argomenti. + Tanto non e` interface-compliant in ogni caso, tanto vale fare una cosa + proprietaria ma semplice. Ci pensera` poi il modulo di interfaccia a + gestire la traduzione e a ottenere la compliancy. + +Thu Jun 19 12:35:12 1997 Francesco Chemolli <kinkie@ai-chan> + + * lib/postgres.pike: Corretto un baco in lib/postgres.pike + + * pgresult.c: *** empty log message *** + + * pgresult.h: Aggiunto pgresult.h + + * dependencies: Repackaging minore. + + * lib/postgres_result.pike, lib/postgres.h, lib/postgres.pike, configure.in, pgresult.c, postgres.c, Makefile.in, configure: + Aggiornato a pike/0.5b8 + Ora la classe in Sql.pmod puo` ereditare. Piu` efficiente, e migliore + controllo degli errori. + +Wed Apr 2 11:56:14 1997 Francesco Chemolli <kinkie@ai-chan> + + * Makefile.in, configure, configure.in, extras/test_notify.pike, extras/test_schema.pike, lib/postgres.h, pg_types.h, pgres_config.h.in, pgresult.c, postgres.c, quickmanual.txt, testsuite.in: + Initial revision + + * Makefile.in, configure, configure.in, extras/test_notify.pike, extras/test_schema.pike, lib/postgres.h, pg_types.h, pgres_config.h.in, pgresult.c, postgres.c, quickmanual.txt, testsuite.in: + Initial version of the Postgres module for Pike. + Apparently working. + diff --git a/src/modules/Postgres/Makefile.in b/src/modules/Postgres/Makefile.in new file mode 100644 index 0000000000000000000000000000000000000000..fe267ce7becf9d63d75f095860aff8f8ccbbc83b --- /dev/null +++ b/src/modules/Postgres/Makefile.in @@ -0,0 +1,29 @@ +# $Id: Makefile.in,v 1.1.1.1 1997/10/14 22:07:21 grubba Exp $ +# (C) 1997 Francesco Chemolli <kinkie@comedia.it> + +SRCDIR=@srcdir@ +VPATH=@srcdir@:@srcdir@/../..:../.. +MODULE_CPPFLAGS=@DEFS@ @CPPFLAGS@ +MODULE_LDFLAGS=@LDFLAGS@ @LIBS@ +OBJS=postgres.o pgresult.o +POSTGRES_SUPPORTED=@POSTGRES_SUPPORTED@ + +all: module_install + +module_install: include_install glue_install mod_install + +include_install: lib/postgres.h + if [ x$(POSTGRES_SUPPORTED) = xyes ]; then cp $^ $(TMP_LIBDIR)/include; fi + +glue_install: lib/postgres.pike lib/postgres_result.pike + if [ x$(POSTGRES_SUPPORTED) = xyes ]; then cp $^ $(TMP_LIBDIR)/modules/Sql.pmod; fi + +mod_install: + if [ x$(POSTGRES_SUPPORTED) = xyes ] ; then make dummy; fi + +@dynamic_module_makefile@ + +spotless: clean + -rm config.* pgres_config.h + +@dependencies@ diff --git a/src/modules/Postgres/configure.in b/src/modules/Postgres/configure.in new file mode 100644 index 0000000000000000000000000000000000000000..d35e89614dd431d2affa74bff50ec01b1dbeb383 --- /dev/null +++ b/src/modules/Postgres/configure.in @@ -0,0 +1,96 @@ +dnl $Id: configure.in,v 1.1.1.1 1997/10/14 22:07:21 grubba Exp $ +dnl (C) 1997 Francesco Chemolli <kinkie@comedia.it> + +AC_INIT(postgres.c) +AC_CONFIG_HEADER(pgres_config.h) + +echo "Configuring Postgres module, (C) 1997 Francesco Chemolli <kinkie@comedia.it>" + +AC_ARG_WITH(postgres,[ --with(out)-postgres postgres95 DB server support], + [],[with_postgres=yes]) + +sinclude(../module_configure.in) + +ac_pike_postgres_skip=no + +dnl set up "reasonable" search paths +pike_postgres_reasonable_prefixes="/usr/local /usr /opt" +pike_postgres_reasonable_directories="postgres95 postgres pgsql /" +pike_postgres_extra_include_directories="$HOME/include" +pike_postgres_extra_lib_directories="$HOME/lib" + +for a in $pike_postgres_reasonable_prefixes +do + for b in $pike_postgres_reasonable_directories + do + pike_postgres_reasonable_include_searchpath="$pike_postgres_reasonable_include_searchpath $a/$b/include" + pike_postgres_reasonable_lib_searchpath="$pike_postgres_reasonable_lib_searchpath $a/$b/lib" + done +done +pike_postgres_reasonable_include_searchpath="$pike_postgres_reasonable_include_searchpath $pike_postgres_extra_include_directories" +pike_postgres_reasonable_lib_searchpath="$pike_postgres_reasonable_lib_searchpath $pike_postgres_extra_lib_directories" + +dnl start actual work +if test x$with_postgres = xyes; then + OLD_CPPFLAGS=$CPPFLAGS + OLD_LIBS=$LIBS + OLD_LDFLAGS=$LDFLAGS + + AC_MSG_CHECKING(for location of the Postgres include files) + AC_CACHE_VAL(pike_cv_pgres_include_dir, [ + for pike_cv_pgres_include_dir in $pike_postgres_reasonable_include_searchpath no + do + if test -f $pike_cv_pgres_include_dir/postgres.h; then + break + fi + done + ]) + if test x$pike_cv_pgres_include_dir != xno; then + AC_MSG_RESULT(found.) + else + AC_MSG_RESULT(not found.) + fi + if test x$pike_cv_pgres_include_dir != xno; then + CPPFLAGS="$CPPFLAGS -I$pike_cv_pgres_include_dir" + fi + AC_CHECK_HEADERS(postgres.h) + AC_CHECK_HEADERS(libpq-fe.h) + if test x$ac_cv_header_libpq_fe_h = xno ; then + ac_cv_header_postgres_h=no + ac_pike_postgres_skip=yes + fi + + if test x$ac_pike_postgres_skip = xno; then + AC_MSG_CHECKING(for location of the Postgres library) + AC_CACHE_VAL(pike_cv_pgres_lib_dir,[ + for pike_cv_pgres_lib_dir in $pike_postgres_reasonable_lib_searchpath no + do + if test -f $pike_cv_pgres_lib_dir/libpq.a; then + break + fi + done + ]) + if test x$pike_cv_pgres_lib_dir != xno; then + AC_MSG_RESULT(found.) + else + AC_MSG_RESULT(not found.) + fi + if test x$pike_cv_pgres_lib_dir != xno; then + LDFLAGS="$LDFLAGS -L$pike_cv_pgres_lib_dir" + fi + AC_CHECK_LIB(pq,PQclear) + fi +fi + +if test x$ac_cv_lib_pq_PQclear != xyes; then + CPPFLAGS=$OLD_CPPFLAGS + LDFLAGS=$OLD_LDFLAGS + LIBS=$OLD_LIBS + echo "Postgres not found. We won't support it." +else + echo "Postgres found, module configured. Thank you for your collaboration." + POSTGRES_SUPPORTED="yes" +fi + +AC_SUBST(POSTGRES_SUPPORTED) +AC_OUTPUT(Makefile) diff --git a/src/modules/Postgres/extras/test_notify.pike b/src/modules/Postgres/extras/test_notify.pike new file mode 100644 index 0000000000000000000000000000000000000000..61c5e4e73b992fd65c787a367c1514e01c677313 --- /dev/null +++ b/src/modules/Postgres/extras/test_notify.pike @@ -0,0 +1,32 @@ +#include <postgres.h> +/* This simple program connects to the template1 database "template1" + * (it comes default with postgres95) and sits there waiting for + * someone to issue a notify sql command on a table named "prova". + * When this happens, it prints a message and exits. + * To test it, do this: + * $ psql + * => create table prova (bah int)\g + * [other shell] $ pike test_notify.pike + * => notify prova\g + * =>\q + * If it worked you should have read 'notification: "prova"' as output + * from the script. + */ + +void notify_cb(string s) { + write("notification: \""+s+"\"\n"); +// exit(0); +} + +int main () { + object o,r; + array row; + mixed err; + int pid; + o=Sql.postgres("","template1"); + write ("Setting notify callback\n"); + o->set_notify_callback(notify_cb,2); + write ("Trying to trigger the notify callback.\n"); + o->big_query("LISTEN prova"); + return -1; +} diff --git a/src/modules/Postgres/extras/test_schema.pike b/src/modules/Postgres/extras/test_schema.pike new file mode 100644 index 0000000000000000000000000000000000000000..930b36236d719198c44739267a6854eba66becc0 --- /dev/null +++ b/src/modules/Postgres/extras/test_schema.pike @@ -0,0 +1,13 @@ +//#include "postgres.h" +#include <sql.h> + +int main () { + object o,r; + array row; + mixed err; + int pid; + o=Sql.postgres("","template1"); + write(sprintf("****** Databases: ******\n%O\n",o->list_dbs())); + write(sprintf("****** Tables: ******\n%O\n",o->list_tables())); + write(sprintf("****** Attributes: ******\n%O\n",o->list_fields("prova"))); +} diff --git a/src/modules/Postgres/pg_types.h b/src/modules/Postgres/pg_types.h new file mode 100644 index 0000000000000000000000000000000000000000..bcad9bcedce5e39f490c690043c2c6ca476d79b9 --- /dev/null +++ b/src/modules/Postgres/pg_types.h @@ -0,0 +1,22 @@ +#ifndef _PG_TYPES_H_ +#define _PG_TYPES_H_ + +#include <program.h> +#include <svalue.h> + +struct postgres_result_object_data { + PGresult * result; + int cursor; +}; + +struct pgres_object_data { + PGconn *dblink; + struct pike_string *last_error; + PGresult * last_result; + struct svalue * notify_callback; +}; + +/* The header name could be deceiving, but who cares? */ +extern struct program *postgres_program, *pgresult_program; + +#endif diff --git a/src/modules/Postgres/pgres_config.h.in b/src/modules/Postgres/pgres_config.h.in new file mode 100644 index 0000000000000000000000000000000000000000..e3e2cf876d8fdd99dc518ed251a904509d20e026 --- /dev/null +++ b/src/modules/Postgres/pgres_config.h.in @@ -0,0 +1,12 @@ +/* $Id: pgres_config.h.in,v 1.1.1.1 1997/10/14 22:07:21 grubba Exp $ */ +#undef STDC_HEADERS + +#undef HAVE_POSTGRES_H +#undef HAVE_LIBPQ_FE_H +#undef HAVE_LIBPQ + + +/* End of autoconfigurable section */ +#if defined (HAVE_LIBPQ) && defined (HAVE_POSTGRES_H) && defined (HAVE_LIBPQ_FE_H) +#define HAVE_POSTGRES +#endif diff --git a/src/modules/Postgres/pgresult.c b/src/modules/Postgres/pgresult.c new file mode 100644 index 0000000000000000000000000000000000000000..e6613ce3bca9aec98ab35127c9d566ac22151287 --- /dev/null +++ b/src/modules/Postgres/pgresult.c @@ -0,0 +1,244 @@ +/* + * Postgres95 support for pike/0.5 and up + * + * (C) 1997 Francesco Chemolli <kinkie@comedia.it> + * + * This code is provided AS IS, and may be copied and distributed freely, + * under the terms of the GNU General Public License, version 2. + * + * You might notice that this code resembles Henrik Grubbestrom's mysql + * module. It's just the most efficient way of doing things. This doesn't + * imply I didn't peek at his code ^_^' + */ + +/* + * Although Postgres allows great flexibility in returning values from the + * backend connection, in order to keep this code interface-compliant + * I'll have to do some serious emulation stuff, somehow making pike + * code less easy to write. + * I'm sticking to the object result interface because it allows + * for bigger query results. + * For now I'm handling only text. (external) type handling for postgres + * is _REALLY_ messy. Not to talk the big_objects handling, and COPY and + * the status... yuck. + * The potential to powerfully store (also) binary data is there, but + * it's dampened by a really messy interface implementation. + */ + +/* + * Notes for Henrik: + * - I suggest allowing negative seek() arguments for the generic interface, + * moving the check inside the actual sql classes. + */ + +#include "pgres_config.h" +#ifdef HAVE_POSTGRES + +/* #define PGRESDEBUG */ + +/* <sigh> Postgres stores strings internally padding with whitespaces + * to their field length. If CUT_TRAILING_SPACES is defined, all + * trailing spaces will be cut, regardless they were meant or not. + * This is meant for 'compatibility' versus other database servers, like + * Msql, where a declaration of char(20) means 'any string long at most + * 20 characters', where in Postgres it means 'any string long exactly 20 + * characters'. With Postgres you have to use type varchar otherwise, but + * this makes its SQL incompatible with other servers'. + */ + +#define CUT_TRAILING_SPACES + +#include <stdio.h> +#include <libpq-fe.h> + +/* Pike includes */ +#include <global.h> +#include <stralloc.h> +#include <object.h> +#include <threads.h> +#include <array.h> +#include <mapping.h> +#include <builtin_functions.h> +#include <module_support.h> + +#ifdef _REENTRANT +MUTEX_T pike_postgres_result_mutex; +#define PQ_LOCK() mt_lock(&pike_postgres_mutex) +#define PQ_UNLOCK() mt_unlock(&pike_postgres_mutex) +#define THREAD() THREADS_ALLOW();PQ_LOCK() +#define UNTHREAD() THREADS_DISQLLOW(); PQ_UNLOCK() +#else +#define THREAD() /**/ +#define UNTHREAD() /**/ +#endif + +#include "pg_types.h" + +#define THIS ((struct postgres_result_object_data *) fp->current_storage) + +#ifdef PGRESDEBUG +#define pgdebug printf +#else +static void pgdebug (char * a, ...) {} +#endif + +void result_create (struct object * o) { + pgdebug("result_create().\n"); + THIS->result=NULL; + THIS->cursor=0; +} + +void result_destroy (struct object * o) { + pgdebug("result_destroy().\n"); + PQclear(THIS->result); +} + +static void f_create (INT32 args) +{ + char *storage; + check_all_args("postgres_result->create",args,BIT_OBJECT,0); + pgdebug("result->f_create(%d).\n",args); +#if OLD + if (sp[-args].u.object->prog != postgres_program) + error ("Need a postgres type object.\n"); + THIS->result= + ((struct pgres_object_data *)sp[-args].u.object->storage)->last_result; + ((struct pgres_object_data *) sp[-args].u.object->storage)->last_result=NULL; +#endif + + storage=get_storage(sp[-args].u.object,postgres_program); + if (!storage) + error ("I need a Postgres object or an heir of it.\n"); + THIS->result=((struct pgres_object_data *)storage)->last_result; + ((struct pgres_object_data *) sp[-args].u.object->storage)->last_result=NULL; + /* no fear of memory leaks, we've only moved the pointer from there to here */ + + pop_n_elems(args); + if (!THIS->result) /*this ensures we _DO_ have a result*/ + error ("Bad result.\n"); +#ifdef PGRESDEBUG + pgdebug("Got %d tuples.\n",PQntuples(THIS->result)); +#endif +} + +static void f_num_rows (INT32 args) +{ + check_all_args("postgres_result->num_rows",args,0); + if (PQresultStatus(THIS->result)!=PGRES_TUPLES_OK) { + push_int(0); + return; + } + push_int(PQntuples(THIS->result)); + return; +} + +static void f_num_fields (INT32 args) +{ + check_all_args("postgres_result->num_fields",args,0); + if (PQresultStatus(THIS->result)!=PGRES_TUPLES_OK) { + push_int(0); + return; + } + push_int(PQnfields(THIS->result)); + return; +} + +static void f_fetch_fields (INT32 args) +{ + int j, numfields, tmp; + PGresult * res=THIS->result; + + check_all_args("postgres_result->fetch_fields",args,0); + numfields=PQnfields(res); + for (j=0;j<numfields;j++) + { + push_text("name"); + push_text(PQfname(res,j)); + /* no table information is availible */ + /* no default value information is availible */ + push_text("type"); + push_int(PQftype(res,j)); + /* ARGH! I'd kill 'em! How am I supposed to know how types are coded + * internally!?!?!?!? + */ + push_text("length"); + tmp=PQfsize(res,j); + if (tmp>=0) + push_int(tmp); + else + push_text("variable"); + f_aggregate_mapping(6); + } + f_aggregate(numfields); + return; +} + +static void f_seek (INT32 args) +{ + int howmuch; + check_all_args("postgres_result->seek",args,BIT_INT,0); + howmuch=sp[-args].u.integer; + if (THIS->cursor+howmuch < 0) + error ("Cannot seek to negative result indexes!\n"); + if (THIS->cursor+howmuch > PQntuples(THIS->result)) + error ("Cannot seek past result's end!.\n"); + pop_n_elems(args); + THIS->cursor += howmuch; + return; +} + +static void f_fetch_row (INT32 args) +{ + int j,k,numfields; + char * value; + + check_all_args("postgres_result->fetch_row",args,0); + pgdebug("f_fectch_row(); cursor=%d.\n",THIS->cursor); + if (THIS->cursor>=PQntuples(THIS->result)) { + push_int(0); + return; + } + numfields=PQnfields(THIS->result); + for (j=0;j<numfields;j++) { + value=PQgetvalue(THIS->result,THIS->cursor,j); +#ifdef CUT_TRAILING_SPACES + /* damn! We need to cut the trailing whitespace... */ + for (k=PQgetlength(THIS->result,THIS->cursor,j)-1;k>=0;k--) + if (value[k]!=' ') + break; + push_string(make_shared_binary_string(value,k+1)); +#else + push_text(value); +#endif + } + f_aggregate(numfields); + THIS->cursor++; + return; +} + +struct program * pgresult_program; + +void pgresult_init (void) +{ + start_new_program(); + add_storage(sizeof(struct postgres_result_object_data)); + set_init_callback(result_create); + set_exit_callback(result_destroy); + + add_function("create",f_create,"function(object:void)",OPT_SIDE_EFFECT); + add_function("num_rows",f_num_rows,"function(void:int)", + OPT_EXTERNAL_DEPEND|OPT_RETURN); + add_function("num_fields",f_num_fields,"function(void:int)", + OPT_EXTERNAL_DEPEND|OPT_RETURN); + add_function("fetch_fields",f_fetch_fields, + "function(void:void|array(mapping(string:mixed)))", + OPT_EXTERNAL_DEPEND|OPT_RETURN); + add_function("seek",f_seek,"function(int:void)",OPT_SIDE_EFFECT); + add_function("fetch_row",f_fetch_row,"function(void:void|array(mixed))", + OPT_EXTERNAL_DEPEND|OPT_RETURN); + pgresult_program=end_program(); + add_program_constant("postgres_result",pgresult_program,0); + pgresult_program->refs++; +} + +#endif diff --git a/src/modules/Postgres/pgresult.h b/src/modules/Postgres/pgresult.h new file mode 100644 index 0000000000000000000000000000000000000000..cfd6bf1fd3b6b1ebc65b842b2d17245db19752b3 --- /dev/null +++ b/src/modules/Postgres/pgresult.h @@ -0,0 +1 @@ +void pgresult_init(void); diff --git a/src/modules/Postgres/postgres.c b/src/modules/Postgres/postgres.c new file mode 100644 index 0000000000000000000000000000000000000000..b96059c6fecafb94a39772246f95b5f42e796c73 --- /dev/null +++ b/src/modules/Postgres/postgres.c @@ -0,0 +1,424 @@ +/* + * Postgres95 support for pike/0.5 and up + * + * (C) 1997 Francesco Chemolli <kinkie@comedia.it> + * + * This code is provided AS IS, and may be distributed under the terms + * of the GNU General Public License, version 2. + */ + +#include "pgres_config.h" +#ifdef HAVE_POSTGRES + +/* #define PGDEBUG */ + +/* System includes */ +#include <stdlib.h> +#include <stdio.h> /* Needed or libpq-fe.h pukes :( */ +#include <malloc.h> +#include <string.h> + +/* Pike includes */ +#include <global.h> +#include <las.h> +#include <machine.h> +#include <svalue.h> +#include <threads.h> +#include <stralloc.h> +#include <object.h> +#include <module_support.h> +#include <operators.h> + +/* Postgres includes */ +/* A hack, because DEBUG is defined both in pike's machine.h and in postgres */ +#ifdef DEBUG +#undef DEBUG +#endif +#include <postgres.h> +#include <libpq-fe.h> + +#include "pgresult.h" + +/* Actual code */ +#ifdef _REENTRANT +MUTEX_T pike_postgres_mutex; +#define PQ_LOCK() mt_lock(&pike_postgres_mutex); +#define PQ_UNLOCK() mt_unlock(&pike_postgres_mutex); +#else +#define PQ_LOCK() +#define PQ_UNLOCK() +#endif + +#include "pg_types.h" + +#ifdef PGDEBUG +#define pgdebug printf +#else +static void pgdebug (char * a, ...) {} +#endif + +struct program * postgres_program; + +RCSID("$Id: postgres.c,v 1.1.1.1 1997/10/14 22:07:21 grubba Exp $"); + +#define THIS ((struct pgres_object_data *) fp->current_storage) + +static void set_error (char * newerror) +{ + pgdebug("set_error(%s).\n",newerror); + if (THIS->last_error) + free_string(THIS->last_error); + THIS->last_error=make_shared_string(newerror); + return; +} + +static void pgres_create (struct object * o) { + pgdebug ("pgres_create().\n"); + THIS->dblink=NULL; + THIS->last_error=NULL; + THIS->notify_callback=malloc(sizeof(struct svalue)); + if (!THIS->notify_callback) + error ("Memory allocation error.\n"); + THIS->notify_callback->type=T_INT; +} + +static void pgres_destroy (struct object * o) +{ + PGconn * conn; + pgdebug ("pgres_destroy().\n"); + if ((conn=THIS->dblink)) { + PQ_LOCK(); + THREADS_ALLOW(); + PQfinish(conn); + THREADS_DISALLOW(); + PQ_UNLOCK(); + THIS->dblink=NULL; + } + if(THIS->last_error) { + if (THIS->last_error) + free_string(THIS->last_error); + THIS->last_error=NULL; + } + if (THIS->notify_callback->type!=T_INT) { + free_svalue(THIS->notify_callback); + free(THIS->notify_callback); + } +} + +static void f_create (INT32 args) +{ + char * host=NULL, *db=NULL, *port=NULL; + PGconn * conn; + + check_all_args("Postgres->create",args, + BIT_STRING|BIT_VOID,BIT_STRING|BIT_VOID, + BIT_INT|BIT_VOID,0); + + if (THIS->dblink) { + conn=THIS->dblink; + PQ_LOCK(); + THREADS_ALLOW(); + PQfinish(conn); + THREADS_DISALLOW(); + PQ_UNLOCK(); + } + if (args>=1) + if(sp[-args].u.string->len) + host=sp[-args].u.string->str; + /* postgres docs say they use hardwired defaults if no variable is found*/ + if (args>=2) + if (sp[1-args].u.string->len) + db=sp[1-args].u.string->str; + if (args==3) + if (sp[2-args].u.integer <=65535 && sp[2-args].u.integer>0) { + port=malloc(10*sizeof(char)); /*it's enough, we need only 6*/ + sprintf(port,"%d",sp[2-args].u.integer); + } + else + error ("You must specify a TCP/IP port number as argument 5 to postgres->create().\n"); + PQ_LOCK(); + THREADS_ALLOW(); + pgdebug("f_create(%s,%s,%s).\n",host,port,db); + conn=PQsetdb(host,port,NULL,NULL,db); + THREADS_DISALLOW(); + PQ_UNLOCK(); + if (!conn) + error ("Internal error: PQserdb returned NULL!\n"); + if (PQstatus(conn)!=CONNECTION_OK) { + set_error(PQerrorMessage(conn)); + PQ_LOCK(); + THREADS_ALLOW(); + PQfinish(conn); + THREADS_DISALLOW(); + PQ_UNLOCK(); + error("Could not connect to database.\n"); + } + THIS->dblink=conn; + if (!THIS->dblink) + error ("Huh? Weirdness here! Internal error!\n"); + pop_n_elems(args); +} + +static void f_select_db (INT32 args) +{ + char *host, *port, *options, *tty, *db; + PGconn * conn, *newconn; + + check_all_args("Postgres->select_db",args,BIT_STRING,0); + + if (!THIS->dblink) + error ("Internal error. How can you possibly not be linked to a " + "database already?\n"); + conn=THIS->dblink; + PQ_LOCK(); + THREADS_ALLOW(); + host=PQhost(conn); + port=PQport(conn); + options=PQoptions(conn); + tty=PQtty(conn); + db=PQdb(conn); + THREADS_DISALLOW(); + PQ_UNLOCK(); +#if 0 + /* This is an optimization, but people may want to reset a connection + * re-selecting its database. + */ + if (!strcmp(sp[-args].u.string->str,db)) { + pop_n_elems(args); + return; + } +#endif + db=sp[-args].u.string->str; + /* This could be really done calling f_create, but it's more efficient this + * way */ + PQ_LOCK(); + THREADS_ALLOW(); + /* using newconn is necessary or otherwise the datastructures I use + * as arguments get freed by PQfinish. Could be a problem under extreme + * situations (i.e. if the temporary use of _one_ more filedescriptor + * is not possible. + */ + newconn=PQsetdb(host,port,options,tty,db); + PQfinish(conn); + conn=newconn; + THREADS_DISALLOW(); + PQ_UNLOCK(); + if (PQstatus(conn)==CONNECTION_BAD) { + set_error(PQerrorMessage(conn)); + PQfinish(conn); + error("Could not connect to database.\n"); + } + THIS->dblink=conn; + pop_n_elems(args); +} + +static void f_big_query(INT32 args) +{ + PGconn *conn = THIS->dblink; + PGresult * res; + PGnotify * notification; + char *query; + + check_all_args("Postgres->big_query",args,BIT_STRING,0); + + if (!conn) + error ("Not connected.\n"); + + if (sp[-args].u.string->len) + query=sp[-args].u.string->str; + else + query=" "; + + PQ_LOCK(); + THREADS_ALLOW(); + pgdebug("f_big_query(\"%s\")\n",query); + res=PQexec(conn,query); + notification=PQnotifies(conn); + THREADS_DISALLOW(); + PQ_UNLOCK(); + + pop_n_elems(args); + if (notification!=NULL) { + pgdebug("Incoming notification: \"%s\"\n",notification->relname); + push_text(notification->relname); + apply_svalue(THIS->notify_callback,1); + /* apply_svalue simply returns if the first argument is a T_INT */ + free (notification); + } + if (!res) { + set_error(PQerrorMessage(conn)); + if (!strncmp(THIS->last_error->str,"WARN",4)) { + /* Sigh... woldn't a NONFATAL_ERROR be MUCH better? */ + push_int(1); + return; + } + error ("Error in query.\n"); + } + + switch (PQresultStatus(res)) + { + case PGRES_EMPTY_QUERY: + case PGRES_COMMAND_OK: + pgdebug("\tOk.\n"); + THIS->last_result=NULL; + PQclear(res); + push_int(0); + return; + case PGRES_NONFATAL_ERROR: + pgdebug ("Warning.\n"); + set_error(PQerrorMessage(conn)); + push_int(1); + return; + case PGRES_BAD_RESPONSE: + case PGRES_FATAL_ERROR: + pgdebug("\tBad.\n"); + set_error(PQerrorMessage(conn)); + PQclear(res); + error ("Error in frontend-backend communications.\n"); + case PGRES_TUPLES_OK: + pgdebug("\tResult.\n"); + THIS->last_result=res; + push_object(this_object()); + push_object(clone_object(pgresult_program,1)); + return; + default: + error ("Unimplemented server feature.\n"); + } + error ("Internal error in postgresmodule.\n"); +} + +static void f_error (INT32 args) +{ + check_all_args("Postgres->error",args,0); + + if (THIS->last_error) + push_string(THIS->last_error); + else + push_int(0); + return; +} + +static void f_reset (INT32 args) +{ + PGconn * conn; + + check_all_args("Postgres->reset",args,0); + + if (!THIS->dblink) + error ("Not connected.\n"); + conn=THIS->dblink; + PQ_LOCK(); + THREADS_ALLOW(); + PQreset(conn); + THREADS_DISALLOW(); + PQ_UNLOCK(); + if (PQstatus(conn)==CONNECTION_BAD) { + set_error(PQerrorMessage(conn)); + error("Bad connection.\n"); + } +} + +#if 0 +/* This was cut (for now) because of the difficulty of obtaining + * a valid FILE * from a fileobject. + */ +static void f_trace (INT32 args) +{ + if (args!=1) + error ("Wrong args for trace().\n"); + if (sp[-args].type==T_INT) + if (sp[-args].u.integer==0) + PQuntrace(THIS->dblink); + else + error ("Wrong argument for postgres->trace().\n"); + else +/* I really don't know how to check that the argument is an instance of + * /precompiled/file... I guess there's the name stored somewhere.. + * For now let's presume that if it's an object, then it's a /precompiled/file*/ + PQtrace(THIS->dblink, + ((struct file_struct*)sp[-args].u.object->storage)->fd); +} +#endif + +/* I hope I'm doing this right... */ +static void f_callback(INT32 args) +{ + if (sp[-args].type==T_INT) { + if (THIS->notify_callback->type!=T_INT) { + free_svalue(THIS->notify_callback); + THIS->notify_callback->type=T_INT; + } + pop_n_elems(args); + return; + } + /*let's assume it's a function otherwise*/ + assign_svalue(THIS->notify_callback,sp-args); + pop_n_elems(args); +} + +static void f_host_info (INT32 args) +{ + check_all_args("Postgres->host_info",args,0); + + if (PQstatus(THIS->dblink)!=CONNECTION_BAD) { + push_text("TCP/IP connection to "); + push_text(PQhost(THIS->dblink)); + f_add(2); + return; + } + set_error(PQerrorMessage(THIS->dblink)); + error ("Bad connection.\n"); +} + +void pike_module_init (void) +{ + start_new_program(); + add_storage(sizeof(struct pgres_object_data)); + set_init_callback(pgres_create); + set_exit_callback(pgres_destroy); + + /* sql-interface compliant functions */ + add_function ("create",f_create, + "function(void|string,void|string,int|void:void)", + OPT_EXTERNAL_DEPEND); + /* That is: create(hostname,database,port) + * It depends on the environment variables: + * PGHOST, PGOPTIONS, PGPORT, PGTTY(don't use!), PGDATABASE + * Notice: Postgres _requires_ a database to be selected upon connection + */ + add_function("select_db",f_select_db,"function(string:void)", + OPT_EXTERNAL_DEPEND); + add_function("big_query",f_big_query, "function(string:int|object)", + OPT_EXTERNAL_DEPEND|OPT_RETURN); + add_function("error",f_error,"function(void:string)", + OPT_EXTERNAL_DEPEND|OPT_RETURN); + add_function("host_info",f_host_info,"function(void:string)", + OPT_EXTERNAL_DEPEND|OPT_RETURN); + + /* postgres-specific functions */ + add_function("reset",f_reset, + "function(void:void)",OPT_EXTERNAL_DEPEND|OPT_SIDE_EFFECT); + +#if 0 + add_function("trace",f_trace,"function(object|int:void)", + OPT_EXTERNAL_DEPEND|OPT_SIDE_EFFECT); + /* If given a clone of /precompiled/file, traces to that file. + * If given 0, stops tracing. + * See note on the implementation. + */ +#endif + add_function("_set_notify_callback",f_callback, + "function(int|function(string:void):void)", + OPT_SIDE_EFFECT); + + postgres_program = end_program(); + add_program_constant("postgres",postgres_program,0); + postgres_program->refs++; + pgresult_init(); +} + +#else /* HAVE_POSTGRES */ +void pike_module_init(void) {} +#endif /* HAVE_POSTGRES */ + +void pike_module_exit(void) {} diff --git a/src/modules/Postgres/quickmanual.txt b/src/modules/Postgres/quickmanual.txt new file mode 100644 index 0000000000000000000000000000000000000000..c4b48745d74b7aec0e9462409bdb551895596dd3 --- /dev/null +++ b/src/modules/Postgres/quickmanual.txt @@ -0,0 +1,115 @@ +Note: these are the notes about version pre1 for pike/0.4. Differences +for version 0.5b4 and later of the Pike interpreter are at the bottom of +the file. + +I don't have the time now to work on a BMML or HTML manual, so here's +a quick'n dirty one. +This module implements the generic interface to databases as defined +by Henrik Grubbstrom and myself. +There are a few noticeable differences, due to the peculiarities of +the postgres database. Unluckily, it can't be helped. + +The programs in this connectivity systems are: +/precompiled/raw/sql/postgres +/precompiled/sql/postgres +/precompiled/sql/postgres_result + +The first should not be used. Some functionalities are better offered +via a pike wrapper than by the atual connection tool, and those are implemented +by /precompiled/sql/postgres. You should also consider using the +generic SQL interface instead, as defined in /precompiled/sql + +/precompiled/raw/sql/postgres has all that is needed to do queries, but lacks +all schema-related functions, since those muse be implemented via queries +to the system tables in the postgres database. +It implements the following functions (when nothing is specified, +it means they are interface-compilant): +- create : this is not exactly interface-compliant in that: + -it ALWAYS has to bind to a database. In postgres connecting to a server + and to a database on that server are undistingushable operations. + It accepts four arguments as the default interface, + plus a fifth optional argument which is the port number to connect to. +- select_db +- big_query : in Postgres there are different kind of errors + (that means: different errors are reported differently by the frontend + library). Those errors can be divided into three main categories: + fatal errors, nonfatal errors, no errors. This is reflected + by the returned values of the function: a result object if there is a result + to return, 0 if all went fine but there is no result to return, an exception + if there has been an error fatal error, or 1 if there has been a nonfatal + error (maybe this one should be reported via exceptions too?) +- error +- void reset() : tries to reset the connection to the database +- void set_notify_callback (int|function(string:void)) + Postgres supports asyncronous notification to clients. + If this callback is set, it will be invoked when such a notification + is received. Notice that notifications are carried piggy-back on + query results. In the /precompiled/sql/posgres this function will + allow a second argument, which will be the delay of a virtual cycle + (implemented via call_out()s) of empty queries to the server, to + allow such notifications to be delivered. + Giving an integer argument resets the callback. + +big_query() returns a clone of /precompiled/sql/postgres_result, which +implements: +- num_rows() +- num_fields() +- fetch_fields(): returns 0 or an array of mappings describing the fields. +- seek(n): seeks to the (current + n-th) row. n can be negative. +- fetch_row(): returns 0 or an array(mixed) with the data in the row. + +Notice that postgres pads strings that shorter than the declared field +length with whitespaces (otherwise you have to use the varchar type). +This is incompatible with other servers, so I implemented a feature +that all trailing whitespaces in queries are cut. To change this behavior, +undefine CUT_TRAILING_SPACES in pgresult.c. + +/precompiled/sql/postgres is a wrapper around the actual postgres +class, and handles all the schema-related parts of the interface, +as well as some high-level core functions. Its main difference over the +/precompiled/raw/sql/postgres is in the set_notify_callback function. +This one accepts a second (optional) integer argument. If given it +specifies the number of seconds to wait to poll the database. + +************* CHANGES FOR VERSION 0.2pre1 **************** +Notice: I HAVEN'T TESTED IT YET! +0.1pre seemed to work, and the actual interface code hasn't changed. +I just adapted the interface to the pike counterpart and added arguments +typechecking. +The module system is still pretty much a mystery to me in its deepest +implications, and other modules (in particular Grubba's Mysql module) can +help me only so much, since my interface involves 3 pike programs and not +two. Furthermore, the three programs are not of the same 'type', since two +are C programs, the other one is a pike wrapper program. To sum it up, I might +have come up with a way to reference them all in a consistent way, or I +might have just failed miserably ;) +The three modules are referenced as "Sql.postgres", "Postgres.raw_postgres" +and "Sql.postgres_result" or "Postgres.postgres_result" (it should be the same, +really). Although the raw module _is_ in fact a complete module, +I strongly discourage you to use it. +To use them, you have to copy the files in the modules/Postgres/lib directory +by hand. postgres.h goes to the pike include dir +(i.e. /usr/local/lib/pike/include) +while the files in the directory Sql.pmod go to the Sql.pmod subdirectory in +your pike modules directory (i.e. /usr/local/lib/pike/modules/Sql.pmod). +In the extras/ directory there are a couple example files. + +********* VERSION 0.3 ***************** +Adapts the interface to Pike/0.5beta6 and later. +Also cleaned up the installation process: now install only if postgres is +really supported + +*********** VERSION 0.4 *************** +Changes in the create() interface: +Postgres.postgres()->create() now accepts only 3 arguments (all optional): +-string hostname +-string database name +-int port +The interface compliancy is however guarranteed by Sql.postgres: +Sql.postgres()->create() accepts 4 arguments (instead of 5): +-string hostname +-string database name +-string user (ignored) +-string password (ignored) +the port number is encoded in the hostname, which now takes the form: +"hostname[:portnumber]" diff --git a/src/modules/Postgres/testsuite.in b/src/modules/Postgres/testsuite.in new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391