Index: mod_nsd/postgres.c =================================================================== RCS file: /usr/local/cvsroot/mod_nsd/postgres.c,v diff -u -r1.1 -r1.2 --- mod_nsd/postgres.c 13 Apr 2001 21:09:32 -0000 1.1 +++ mod_nsd/postgres.c 14 Apr 2001 07:17:26 -0000 1.2 @@ -1,3 +1,4 @@ + /* $Header$ */ /* * The contents of this file are subject to the AOLserver Public License @@ -28,6 +29,7 @@ * version of this file under either the License or the GPL. */ +/* NOTE: for ACS/pg use, you need to define FOR_ACS_USE! */ #include "ns.h" /* If we're under AOLserver 3, we don't need some things. @@ -112,6 +114,39 @@ skeleton. Will revisit this implementation for cleanup once functionality fully verified. Lamar Owen Feb 6, 2000. */ +/* Merge with AOLserver 3.0rc1's nspostgres.c -- more error checking from + Jan Wieck. + + Also, changed behavior: if the datestyle parameter is not set in config + file, set it to be 'iso' by default -- it was not getting correctly set. + + Wrapped ACS stuff inside FOR_ACS_USE #ifdef's. + + 3-21-2000 lamar.owen@wgcr.org */ + +/* 2000-03-28: added check for the existence of the PGDATESTYLE envvar + and do no setting of datestyle if it exists. lamar.owen@wgcr.org */ + +/* 2000-03-28: take two: make datestyle parameter override envvar, and make + the default go away. LRO */ + +/* 2000-05-04: Added blob_select_file command. Needed an inverse to + blob_dml_file to support porting of webmail. danw@rtp.ericsson.se*/ + +/* 2000-12-30: Added bind variable emulation to support acs 4.0 porting. + dcwickstrom@earthlink.net*/ + +/* Contributors to this file include: + + Don Baccus + Lamar Owen + Jan Wieck + Keith Pasket (SDL/USU) + Scott Cannon, Jr. (SDL/USU) + Dan Wickstrom + + Original example driver by Jim Davidson */ + #define DRIVER_NAME "PostgreSQL" #define OID_QUOTED_STRING " oid = '" #define STRING_BUF_LEN 256 @@ -123,22 +158,37 @@ #define TRUE 1 #endif -static void Ns_PgUnQuoteOidString(Ns_DString *sql); +static void Ns_PgUnQuoteOidString(Ns_DString *sql); static char *Ns_PgName(Ns_DbHandle *handle); static char *Ns_PgDbType(Ns_DbHandle *handle); static int Ns_PgOpenDb(Ns_DbHandle *dbhandle); static int Ns_PgCloseDb(Ns_DbHandle *dbhandle); static int Ns_PgGetRow(Ns_DbHandle *handle, Ns_Set *row); static int Ns_PgFlush(Ns_DbHandle *handle); -/* ns_column and ns_table reimplement for ACS */ -static Ns_DbTableInfo *Ns_PgGetTableInfo(Ns_DbHandle *handle, char *table); -static char *Ns_PgTableList(Ns_DString *pds, Ns_DbHandle *handle, int includesystem); +/* Clunky construct follows :-) We want these statics for either AS 2.3 + OR for ACS/pg under AS3 -- plain AS3 doesn't get these */ /* Hack out the extended_table_info stuff if AOLserver 3, and add in our driver's reimplement of ns_table and ns_column */ #ifdef NS_AOLSERVER_3_PLUS +#ifdef FOR_ACS_USE + +/* So that we don't have to do this clunky thing again, set a define */ +#define NOT_AS3_PLAIN + +/* A linked list to use when parsing SQL. */ + +typedef struct _string_list_elt { + char *string; + struct _string_list_elt *next; +} string_list_elt_t; + + +static Ns_DbTableInfo *Ns_PgGetTableInfo(Ns_DbHandle *handle, char *table); +static char *Ns_PgTableList(Ns_DString *pds, Ns_DbHandle *handle, int includesystem); + static int pg_column_command (ClientData dummy, Tcl_Interp *interp, int argc, char *argv[]); static int pg_table_command (ClientData dummy, Tcl_Interp *interp, @@ -149,19 +199,32 @@ static void Ns_DbAddColumnInfo (Ns_DbTableInfo *tinfo, Ns_Set *column_info); static int Ns_DbColumnIndex (Ns_DbTableInfo *tinfo, char *name); -#else +#endif /* FOR_ACS_USE */ +/* PLAIN AS 3! */ +#define AS3_PLAIN /* that is, AS3 without the ACS extensions */ + +#else /* NS_AOLSERVER_3_PLUS */ + +/* define NOT_AS3_PLAIN here as well, so that a single ifdef can be used */ +#define NOT_AS3_PLAIN + +static Ns_DbTableInfo *Ns_PgGetTableInfo(Ns_DbHandle *handle, char *table); +static char *Ns_PgTableList(Ns_DString *pds, Ns_DbHandle *handle, int includesystem); + static char *Ns_PgBestRowId(Ns_DString *pds, Ns_DbHandle *handle, char *table); #endif /* NS_AOLSERVER_3_PLUS */ -static int Ns_PgServerInit(char *hServer, char *hModule, char *hDriver); -static char *pgName = DRIVER_NAME; -static unsigned int pgCNum = 0; +static int Ns_PgServerInit(char *hServer, char *hModule, char *hDriver); +static void Ns_PgSetErrorstate(Ns_DbHandle *handle); static int Ns_PgExec(Ns_DbHandle *handle, char *sql); static Ns_Set *Ns_PgBindRow(Ns_DbHandle *handle); static int Ns_PgResetHandle(Ns_DbHandle *handle); +static char *pgName = DRIVER_NAME; +static unsigned int pgCNum = 0; + /*- * * The NULL-terminated PgProcs[] array of Ns_DbProc structures is the @@ -217,6 +280,7 @@ Ns_DbDriverInit(char *hDriver, char *configPath) { char *t; + char *e; /* * Register the PostgreSQL driver functions with nsdb. @@ -230,6 +294,8 @@ } Ns_Log(Notice, "%s loaded.", pgName); + e = getenv("PGDATESTYLE"); + t = Ns_ConfigGetValue(configPath, "DateStyle"); strcpy(datestyle, ""); @@ -240,10 +306,17 @@ strcpy(datestyle, "set datestyle to '"); strcat(datestyle, t); strcat(datestyle, "'"); - } else { + if (e) { + Ns_Log(Notice, "PGDATESTYLE overridden by datestyle param."); + } + } else { Ns_Log(Error, "Illegal value for datestyle - ignored"); - } - } + } + } else { + if (e) { + Ns_Log(Notice, "PGDATESTYLE setting used for datestyle."); + } + } return NS_OK; } @@ -299,7 +372,7 @@ *db++ = '\0'; if (!strcmp(host, "localhost")) { Ns_Log(Notice, "Opening %s on %s", db, host); - pgConn = PQsetdbLogin(NULL, NULL, NULL, NULL, db, handle->user, + pgConn = PQsetdbLogin(NULL, port, NULL, NULL, db, handle->user, handle->password); } else { Ns_Log(Notice, "Opening %s on %s, port %s", db, host, port); @@ -323,7 +396,7 @@ handle->connection = nsConn; if (strlen(datestyle)) { - return Ns_PgExec(handle, "set datestyle to 'ISO'") == NS_DML ? + return Ns_PgExec(handle, datestyle) == NS_DML ? NS_OK : NS_ERROR; } return NS_OK; @@ -371,7 +444,37 @@ return NS_OK; } +static void +Ns_PgSetErrorstate(Ns_DbHandle *handle) +{ + NsPgConn *nsConn = handle->connection; + Ns_DString *nsMsg = &(handle->dsExceptionMsg); + Ns_DStringTrunc(nsMsg, 0); + + switch (PQresultStatus(nsConn->res)) { + case PGRES_EMPTY_QUERY: + case PGRES_COMMAND_OK: + case PGRES_TUPLES_OK: + case PGRES_COPY_OUT: + case PGRES_COPY_IN: + case PGRES_NONFATAL_ERROR: + Ns_DStringAppend(nsMsg, PQresultErrorMessage(nsConn->res)); + break; + + case PGRES_FATAL_ERROR: + Ns_DStringAppend(nsMsg, PQresultErrorMessage(nsConn->res)); + break; + + case PGRES_BAD_RESPONSE: + Ns_DStringAppend(nsMsg, "PGRES_BAD_RESPONSE "); + Ns_DStringAppend(nsMsg, PQresultErrorMessage(nsConn->res)); + break; + } +} + + + /* Set the current transaction state based on the query pointed to by * "sql". Should be called only after the query has successfully been * executed. @@ -445,6 +548,12 @@ nsConn->res = PQexec(nsConn->conn, dsSql.string); + /* Set error result for exception message -- not sure that this + belongs here in DRB-improved driver..... but, here it is + anyway, as it can't really hurt anything :-) */ + + Ns_PgSetErrorstate(handle); + /* This loop should actually be safe enough, but we'll take no * chances and guard against infinite retries with a counter. */ @@ -460,6 +569,8 @@ */ int retry_query = PQresultStatus(nsConn->res) == PGRES_NONFATAL_ERROR; + + /* Reconnect messages need to be logged regardless of Verbose. */ Ns_Log(Notice, "%s: Trying to reopen database connection", asfuncname); @@ -570,8 +681,9 @@ return NS_OK; } +/* EXCISE for PLAIN AS3 */ +#ifdef NOT_AS3_PLAIN - /* * Ns_PgSelect - Send a query which should return rows. * @@ -622,8 +734,10 @@ return (row); } +#endif /*NOT_AS3_PLAIN */ + /* - * Ns_PgGetRow - Fetch rows after an Ns_PgSelect. + * Ns_PgGetRow - Fetch rows after an Ns_PgSelect or Ns_PgExec. */ static int Ns_PgGetRow(Ns_DbHandle *handle, Ns_Set *row) { @@ -691,7 +805,9 @@ return NS_OK; } +/* Not needed for a PLAIN AS3 driver */ +#ifdef NOT_AS3_PLAIN /* * Ns_DbTableInfo - Return system catalog information (columns, types, etc.) * about a table. @@ -729,8 +845,8 @@ if (row != NULL) { while ((status = Ns_PgGetRow(handle, row)) == NS_OK) { - name = Ns_SetValue (row, 0); - type = Ns_SetValue (row, 1); + name = row->fields[0].value; + type = row->fields[1].value; if (name == NULL || type == NULL) { Ns_Log(Error, "Ns_PgGetTableInfo(%s): Invalid 'pg_attribute' entry for table: %s", handle->datasource, table); @@ -740,10 +856,13 @@ /* * NB: Move the fields directly from the row * Ns_Set to the col Ns_Set to avoid a data copy. - * We don't bother... */ - col = Ns_SetCreate(name); - Ns_SetPut(col, "type", type); + col = Ns_SetCreate(NULL); + col->name = name; + Ns_SetPut(col, "type", NULL); + col->fields[0].value = type; + row->fields[0].value = NULL; + row->fields[1].value = NULL; if (tinfo == NULL) { tinfo = Ns_DbNewTableInfo(table); } @@ -792,7 +911,7 @@ if (row != NULL) { while ((status = Ns_DbGetRow(handle, row)) == NS_OK) { - table = Ns_SetValue (row, 0); + table = row->fields[0].value; if (table == NULL) { Ns_Log(Warning, "Ns_PgTableList(%s): NULL relname in 'pg_class' table.", handle->datasource); @@ -808,6 +927,8 @@ return (NULL); } +#endif /*NOT_AS3_PLAIN */ + /* Excise for AOLserver 3*/ #ifndef NS_AOLSERVER_3_PLUS /* @@ -830,8 +951,9 @@ /* end of extended table info stuff */ #endif /* NS_AOLSERVER_3_PLUS */ -#include +/* ACS BLOBing stuff */ +#ifdef FOR_ACS_USE /* * This is a slight modification of the encoding scheme used by @@ -900,12 +1022,62 @@ *buf++ = DEC(c3) << 6 | DEC(c4); } -/* ns_pg blob_write db blob_id - * Write a pseudo-blob to the current connection. Some of this - * shamelessly lifted from ora8.c +/* ns_pg blob_get db blob_id + * returns the value of the blob to the Tcl caller. */ static int +blob_get(Tcl_Interp *interp, Ns_DbHandle *handle, char* lob_id) +{ + NsPgConn *nsConn = (NsPgConn *) handle->connection; + int segment; + char query[100]; + char *segment_pos; + int nbytes = 0; + + segment = 1; + + strcpy(query, "SELECT BYTE_LEN, DATA FROM LOB_DATA WHERE LOB_ID = "); + strcat(query, lob_id); + strcat(query, " AND SEGMENT = "); + + segment_pos = query + strlen(query); + + for (;;) { + char *data_column; + char *byte_len_column; + int i, j, n, byte_len; + char buf[6001]; + + sprintf(segment_pos, "%d", segment); + if (Ns_PgExec(handle, query) != NS_ROWS) { + Tcl_AppendResult(interp, "Error selecting data from BLOB", NULL); + return TCL_ERROR; + } + + if (PQntuples(nsConn->res) == 0) break; + + byte_len_column = PQgetvalue(nsConn->res, 0, 0); + data_column = PQgetvalue(nsConn->res, 0, 1); + sscanf(byte_len_column, "%d", &byte_len); + nbytes += byte_len; + n = byte_len; + for (i=0, j=0; n > 0; i += 4, j += 3, n -= 3) { + decode3(&data_column[i], &buf[j], n); + } + buf[byte_len] = '\0'; + Tcl_AppendResult(interp, buf, NULL); + segment++; + } + + PQclear(nsConn->res); + nsConn->res = NULL; + + return TCL_OK; +} + + +static int blob_write(Tcl_Interp *interp, Ns_DbHandle *handle, char* lob_id) { NsPgConn *nsConn = (NsPgConn *) handle->connection; @@ -1020,6 +1192,46 @@ return TCL_OK; } +/* ns_pg blob_put blob_id value + * Stuff the contents of value into the pseudo-blob blob_id + */ + +static int +blob_put(Tcl_Interp *interp, Ns_DbHandle *handle, char* blob_id, + char* value) +{ + int i, j, segment, value_len, segment_len; + char out_buf[8001], query[100]; + char *segment_pos, *value_ptr; + + value_len = strlen(value); + value_ptr = value; + + strcpy(query, "INSERT INTO LOB_DATA VALUES("); + strcat(query, blob_id); + strcat(query, ","); + segment_pos = query + strlen(query); + segment = 1; + + while (value_len > 0) { + segment_len = value_len > 6000 ? 6000 : value_len; + value_len -= segment_len; + for (i = 0, j = 0; i < segment_len; i += 3, j+=4) { + encode3(&value_ptr[i], &out_buf[j]); + } + out_buf[j] = '\0'; + sprintf(segment_pos, "%d, %d, '%s')", segment, segment_len, out_buf); + if (Ns_PgExec(handle, query) != NS_DML) { + Tcl_AppendResult(interp, "Error inserting data into BLOB", NULL); + return TCL_ERROR; + } + value_ptr += segment_len; + segment++; + } + Ns_Log(Notice, "done"); + return TCL_OK; +} + /* ns_pg blob_dml_file blob_id file_name * Stuff the contents of file_name into the pseudo-blob blob_id */ @@ -1029,7 +1241,7 @@ char* filename) { int fd, i, j, segment, readlen; - char in_buf[6000], out_buf[8000], query[100]; + char in_buf[6000], out_buf[8001], query[100]; char *segment_pos; fd = open (filename, O_RDONLY); @@ -1067,7 +1279,528 @@ return TCL_OK; } +/* ns_pg blob_select_file db blob_id filename + * Write a pseudo-blob to the passed in temp file name. Some of this + * shamelessly lifted from ora8.c. + * DanW - This is just blob_write, except it doesn't send anything out the + * connection. + * . + */ + +static int +blob_select_file(Tcl_Interp *interp, Ns_DbHandle *handle, char* lob_id, + char* filename) +{ + NsPgConn *nsConn = (NsPgConn *) handle->connection; + int segment; + char query[100]; + int fd; + char *segment_pos; + int nbytes = 0; + + /* spool to a tmp file, then return that out the conn + * note that tmpnam returns malloc'd memory. + */ + + if (filename == NULL) + { + Tcl_AppendResult (interp, "could not create temporary file to spool " + "BLOB/CLOB result", NULL); + return TCL_ERROR; + } + + + fd = open (filename, O_CREAT | O_TRUNC | O_WRONLY, 0600); + + if (fd < 0) + { + Ns_Log (Error, "Can't open %s for writing. error %d(%s)", + filename, errno, strerror(errno)); + Tcl_AppendResult (interp, "can't open file ", filename, + " for writing. ", + "received error ", strerror(errno), NULL); + return TCL_ERROR; + } + + segment = 1; + + strcpy(query, "SELECT BYTE_LEN, DATA FROM LOB_DATA WHERE LOB_ID = "); + strcat(query, lob_id); + strcat(query, " AND SEGMENT = "); + + segment_pos = query + strlen(query); + + for (;;) { + char *data_column; + char *byte_len_column; + int i, j, n, byte_len; + char buf[6000]; + + sprintf(segment_pos, "%d", segment); + if (Ns_PgExec(handle, query) != NS_ROWS) { + Tcl_AppendResult(interp, "Error selecting data from BLOB", NULL); + return TCL_ERROR; + } + + if (PQntuples(nsConn->res) == 0) break; + + byte_len_column = PQgetvalue(nsConn->res, 0, 0); + data_column = PQgetvalue(nsConn->res, 0, 1); + sscanf(byte_len_column, "%d", &byte_len); + nbytes += byte_len; + n = byte_len; + for (i=0, j=0; n > 0; i += 4, j += 3, n -= 3) { + decode3(&data_column[i], &buf[j], n); + } + write(fd, buf, byte_len); + segment++; + } + + close(fd); + + PQclear(nsConn->res); + nsConn->res = NULL; + + return TCL_OK; +} + + +#endif /* FOR_ACS_USE */ + +#ifdef FOR_ACS_USE + /* + *---------------------------------------------------------------------- + * BadArgs -- + * + * Common routine that creates bad arguments message. + * + * Results: + * Return TCL_ERROR and set bad argument message as Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +BadArgs(Tcl_Interp *interp, char **argv, char *args) +{ + Tcl_AppendResult(interp, "wrong # args: should be \"", + argv[0], " ", argv[1], NULL); + if (args != NULL) { + Tcl_AppendResult(interp, " ", args, NULL); + } + Tcl_AppendResult(interp, "\"", NULL); + + return TCL_ERROR; +} + + +/* + *---------------------------------------------------------------------- + * DbFail -- + * + * Common routine that creates database failure message. + * + * Results: + * Return TCL_ERROR and set database failure message as Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +DbFail(Tcl_Interp *interp, Ns_DbHandle *handle, char *cmd, char* sql) +{ + Ns_Free(sql); + Tcl_AppendResult(interp, "Database operation \"", cmd, "\" failed", NULL); + if (handle->cExceptionCode[0] != '\0') { + Tcl_AppendResult(interp, " (exception ", handle->cExceptionCode, + NULL); + if (handle->dsExceptionMsg.length > 0) { + Tcl_AppendResult(interp, ", \"", handle->dsExceptionMsg.string, + "\"", NULL); + } + Tcl_AppendResult(interp, ")", NULL); + } + + return TCL_ERROR; +} + +/* + * utility functions for dealing with string lists + */ + +static string_list_elt_t * +string_list_elt_new(char *string) +{ + string_list_elt_t *elt = + (string_list_elt_t *) Ns_Malloc(sizeof(string_list_elt_t)); + elt->string = string; + elt->next = 0; + + return elt; + +} /* string_list_elt_new */ + + + +static int +string_list_len (string_list_elt_t *head) +{ + int i = 0; + + while (head != NULL) { + i++; + head = head->next; + } + + return i; + +} /* string_list_len */ + + + +/* Free the whole list and the strings in it. */ + +static void +string_list_free_list (string_list_elt_t *head) +{ + string_list_elt_t *elt; + + while (head) { + Ns_Free(head->string); + elt = head->next; + Ns_Free(head); + head = elt; + } + +} /* string_list_free_list */ + + + +/* Parse a SQL string and return a list of all + * the bind variables found in it. + */ + +static void +parse_bind_variables(char *input, + string_list_elt_t **bind_variables, + string_list_elt_t **fragments) +{ + char *p, lastchar; + enum { base, instr, bind } state; + char *bindbuf, *bp; + char *fragbuf, *fp; + string_list_elt_t *elt, *head=0, *tail=0; + string_list_elt_t *felt, *fhead=0, *ftail=0; + int current_string_length = 0; + int first_bind = 0; + + fragbuf = (char*)Ns_Malloc((strlen(input)+1)*sizeof(char)); + fp = fragbuf; + bindbuf = (char*)Ns_Malloc((strlen(input)+1)*sizeof(char)); + bp = bindbuf; + + for (p = input, state=base, lastchar='\0'; *p != '\0'; lastchar = *p, p++) { + + switch (state) { + case base: + if (*p == '\'') { + state = instr; + current_string_length = 0; + *fp++ = *p; + } else if ((*p == ':') && (*(p + 1) != ':') && (lastchar != ':')) { + bp = bindbuf; + state = bind; + *fp = '\0'; + felt = string_list_elt_new(Ns_StrDup(fragbuf)); + if(ftail == 0) { + fhead = ftail = felt; + } else { + ftail->next = felt; + ftail = felt; + } + } else { + *fp++ = *p; + } + break; + + case instr: + if (*p == '\'' && (lastchar != '\'' || current_string_length == 0)) { + state = base; + } + current_string_length++; + *fp++ = *p; + break; + + case bind: + if (*p == '=') { + state = base; + bp = bindbuf; + fp = fragbuf; + } else if (!(*p == '_' || *p == '$' || *p == '#' || isalnum((int)*p))) { + *bp = '\0'; + elt = string_list_elt_new(Ns_StrDup(bindbuf)); + if (tail == 0) { + head = tail = elt; + } else { + tail->next = elt; + tail = elt; + } + state = base; + fp = fragbuf; + p--; + } else { + *bp++ = *p; + } + break; + } + } + + if (state == bind) { + *bp = '\0'; + elt = string_list_elt_new(Ns_StrDup(bindbuf)); + if (tail == 0) { + head = tail = elt; + } else { + tail->next = elt; + tail = elt; + } + } else { + *fp = '\0'; + felt = string_list_elt_new(Ns_StrDup(fragbuf)); + if (ftail == 0) { + fhead = ftail = felt; + } else { + ftail->next = felt; + ftail = felt; + } + } + + Ns_Free(fragbuf); + Ns_Free(bindbuf); + *bind_variables = head; + *fragments = fhead; + + return; + +} /* parse_bind_variables */ + + +/* + * PgBindCmd - This function implements the "ns_pg_bind" Tcl command + * installed into each interpreter of each virtual server. It provides + * for the parsing and substitution of bind variables into the original + * sql query. This is an emulation only. Postgresql doesn't currently + * support true bind variables yet. + */ + +static int +PgBindCmd(ClientData dummy, Tcl_Interp *interp, int argc, char **argv) { + + string_list_elt_t *bind_variables; + string_list_elt_t *var_p; + string_list_elt_t *sql_fragments; + string_list_elt_t *frag_p; + Ns_DString ds; + Ns_DbHandle *handle; + Ns_Set *rowPtr; + Ns_Set *set = NULL; + char *cmd; + char *sql; + char *value = NULL; + char *p; + int value_frag_len = 0; + + if (argc < 4 || (!STREQ("-bind", argv[3]) && (argc != 4)) || + (STREQ("-bind", argv[3]) && (argc != 6))) { + return BadArgs(interp, argv, "dbId sql"); + } + + if (Ns_TclDbGetHandle(interp, argv[2], &handle) != TCL_OK) { + return TCL_ERROR; + } + + Ns_DStringFree(&handle->dsExceptionMsg); + handle->cExceptionCode[0] = '\0'; + + /* + * Make sure this is a PostgreSQL handle before accessing + * handle->connection as an NsPgConn. + */ + + if (Ns_DbDriverName(handle) != pgName) { + Tcl_AppendResult(interp, "handle \"", argv[1], "\" is not of type \"", + pgName, "\"", NULL); + return TCL_ERROR; + } + + cmd = argv[1]; + + if (STREQ("-bind", argv[3])) { + set = Ns_TclGetSet(interp, argv[4]); + if (set == NULL) { + Tcl_AppendResult (interp, "invalid set id `", argv[4], "'", NULL); + return TCL_ERROR; + } + sql = Ns_StrDup(argv[5]); + } else { + sql = Ns_StrDup(argv[3]); + } + + Ns_Log(Debug,"PgBindCmd: sql = %s", sql); + + /* + * Parse the query string and find the bind variables. Return + * the sql fragments so that the query can be rebuilt with the + * bind variable values interpolated into the original query. + */ + + parse_bind_variables(sql, &bind_variables, &sql_fragments); + + if (string_list_len(bind_variables) > 0) { + + Ns_DStringInit(&ds); + + /* + * Rebuild the query and substitute the actual tcl variable values + * for the bind variables. + */ + + for (var_p = bind_variables, frag_p = sql_fragments; + var_p != NULL || frag_p != NULL;) { + + if (frag_p != NULL) { + Ns_DStringAppend(&ds, frag_p->string); + frag_p = frag_p->next; + } + + if (var_p != NULL) { + if (set == NULL) { + value = Tcl_GetVar(interp, var_p->string, 0); + } else { + value = Ns_SetGet(set, var_p->string); + } + if (value == NULL) { + Tcl_AppendResult (interp, "undefined variable `", var_p->string, + "'", NULL); + Ns_DStringFree(&ds); + string_list_free_list(bind_variables); + string_list_free_list(sql_fragments); + Ns_Free(sql); + return TCL_ERROR; + } + Ns_Log(Debug,"PgBindCmd: bind var: %s = %s", var_p->string, value); + + if ( strlen(value) == 0 ) { + /* + * DRB: If the Tcl variable contains the empty string, pass a NULL + * as the value. + */ + Ns_DStringAppend(&ds, "NULL"); + } else { + /* + * DRB: We really only need to quote strings, but there is one benefit + * to quoting numeric values as well. A value like '35 union select...' + * substituted for a legitimate value in a URL to "smuggle" SQL into a + * script will cause a string-to-integer conversion error within Postgres. + * This conversion is done before optimization of the query, so indices are + * still used when appropriate. + */ + Ns_DStringAppend(&ds, "'"); + + /* + * DRB: Unfortunately, we need to double-quote quotes as well... + */ + for (p = value; *p; p++) { + if (*p == '\'') { + if (p > value) { + Ns_DStringNAppend(&ds, value, p-value); + } + value = p; + Ns_DStringAppend(&ds, "'"); + } + } + + if (p > value) { + Ns_DStringAppend(&ds, value); + } + + Ns_DStringAppend(&ds, "'"); + } + var_p = var_p->next; + } + } + + Ns_Free(sql); + sql = Ns_DStringExport(&ds); + Ns_DStringFree(&ds); + Ns_Log(Debug, "PgBindCmd: query with bind variables substituted = %s",sql); + } + + string_list_free_list(bind_variables); + string_list_free_list(sql_fragments); + + if (STREQ(cmd, "dml")) { + if (Ns_DbDML(handle, sql) != NS_OK) { + return DbFail(interp, handle, cmd, sql); + } + } else if (STREQ(cmd, "1row")) { + rowPtr = Ns_Db1Row(handle, sql); + if (rowPtr == NULL) { + return DbFail(interp, handle, cmd, sql); + } + Ns_TclEnterSet(interp, rowPtr, 1); + + } else if (STREQ(cmd, "0or1row")) { + int nrows; + + rowPtr = Ns_Db0or1Row(handle, sql, &nrows); + if (rowPtr == NULL) { + return DbFail(interp, handle, cmd, sql); + } + if (nrows == 0) { + Ns_SetFree(rowPtr); + } else { + Ns_TclEnterSet(interp, rowPtr, 1); + } + + } else if (STREQ(cmd, "select")) { + rowPtr = Ns_DbSelect(handle, sql); + if (rowPtr == NULL) { + return DbFail(interp, handle, cmd, sql); + } + Ns_TclEnterSet(interp, rowPtr, 0); + + } else if (STREQ(cmd, "exec")) { + switch (Ns_DbExec(handle, sql)) { + case NS_DML: + Tcl_SetResult(interp, "NS_DML", TCL_STATIC); + break; + case NS_ROWS: + Tcl_SetResult(interp, "NS_ROWS", TCL_STATIC); + break; + default: + return DbFail(interp, handle, cmd, sql); + break; + } + + } else { + return DbFail(interp, handle, cmd, sql); + } + Ns_Free(sql); + + return TCL_OK; +} + +#endif /* FOR_ACS_USE */ + +/* * PgCmd - This function implements the "ns_pg" Tcl command installed into * each interpreter of each virtual server. It provides access to features * specific to the PostgreSQL driver. @@ -1094,17 +1827,44 @@ return TCL_ERROR; } +/* BLOBing functions only if FOR_ACS_USE */ +#ifdef FOR_ACS_USE + if (!strcmp(argv[1], "blob_write")) { if (argc != 4) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " command dbId blobId\"", NULL); return TCL_ERROR; } return blob_write(interp, handle, argv[3]); + } else if (!strcmp(argv[1], "blob_get")) { + if (argc != 4) { + Tcl_AppendResult(interp, "wrong # args: should be \"", + argv[0], " command dbId blobId\"", NULL); + return TCL_ERROR; + } + if (!pgconn->in_transaction) { + Tcl_AppendResult(interp, + "blob_get only allowed in transaction", NULL); + return TCL_ERROR; + } + return blob_get(interp, handle, argv[3]); + } else if (!strcmp(argv[1], "blob_put")) { + if (argc != 5) { + Tcl_AppendResult(interp, "wrong # args: should be \"", + argv[0], " command dbId blobId value\"", NULL); + return TCL_ERROR; + } + if (!pgconn->in_transaction) { + Tcl_AppendResult(interp, + "blob_put only allowed in transaction", NULL); + return TCL_ERROR; + } + return blob_put(interp, handle, argv[3], argv[4]); } else if (!strcmp(argv[1], "blob_dml_file")) { if (argc != 5) { Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " command dbId\"", NULL); + argv[0], " command dbId blobId filename\"", NULL); return TCL_ERROR; } if (!pgconn->in_transaction) { @@ -1113,8 +1873,17 @@ return TCL_ERROR; } return blob_dml_file(interp, handle, argv[3], argv[4]); + } else if (!strcmp(argv[1], "blob_select_file")) { + if (argc != 5) { + Tcl_AppendResult(interp, "wrong # args: should be \"", + argv[0], " command dbId blobId filename\"", NULL); + return TCL_ERROR; + } + return blob_select_file(interp, handle, argv[3], argv[4]); } +#endif /* FOR_ACS_USE */ + if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " command dbId\"", NULL); @@ -1146,7 +1915,11 @@ Tcl_SetResult(interp, string, TCL_VOLATILE); } else { Tcl_AppendResult(interp, "unknown command \"", argv[2], - "\": should be db, host, options, port, error or status.", NULL); + "\": should be db, host, options, port, error, ntuples, ", +#ifdef FOR_ACS_USE + "blob_write, blob_dml_file, blob_select_file, blob_put, ", +#endif + "or status.", NULL); return TCL_ERROR; } return TCL_OK; @@ -1159,10 +1932,17 @@ static int Ns_PgInterpInit(Tcl_Interp *interp, void *ignored) { Tcl_CreateCommand(interp, "ns_pg", PgCmd, NULL, NULL); +#ifdef FOR_ACS_USE + Tcl_CreateCommand(interp, "ns_pg_bind", PgBindCmd, NULL, NULL); +#endif #ifdef NS_AOLSERVER_3_PLUS +#ifdef FOR_ACS_USE Tcl_CreateCommand (interp, "ns_column", pg_column_command, NULL, NULL); Tcl_CreateCommand (interp, "ns_table", pg_table_command, NULL, NULL); -#endif + +#endif /* FOR_ACS_USE */ +#endif /* NS_AOLSERVER_3_PLUS */ + return NS_OK; } @@ -1249,9 +2029,11 @@ } } -/* Reimplement some things dropped from AOLserver 3 */ -#if defined(NS_AOLSERVER_3_PLUS) +/* Reimplement some things dropped from AOLserver 3 for the ACS */ +#ifdef NS_AOLSERVER_3_PLUS +#ifdef FOR_ACS_USE + /* the AOLserver3 team removed some commands that are pretty vital * to the normal operation of the ACS (ArsDigita Community System). * We include definitions for them here @@ -1658,5 +2440,5 @@ } /* Ns_DbColumnIndex */ - +#endif /* FOR_ACS_USE */ #endif /* NS_AOLSERVER_3_PLUS */