From 35ba9de276150fd3d589509a86ae651924f34cb3 Mon Sep 17 00:00:00 2001 From: Michael Meskes Date: Wed, 16 Feb 2000 16:18:29 +0000 Subject: [PATCH] *** empty log message *** --- src/interfaces/ecpg/ChangeLog | 5 + src/interfaces/ecpg/README.dynSQL | 20 + src/interfaces/ecpg/TODO | 2 - src/interfaces/ecpg/include/Makefile | 2 + src/interfaces/ecpg/include/ecpgerrno.h | 4 + src/interfaces/ecpg/include/ecpglib.h | 12 + src/interfaces/ecpg/include/sql3types.h | 38 ++ src/interfaces/ecpg/lib/Makefile.in | 4 +- src/interfaces/ecpg/lib/dynamic.c | 290 ++++++++++++ src/interfaces/ecpg/lib/ecpglib.c | 25 +- src/interfaces/ecpg/preproc/ecpg_keywords.c | 5 + src/interfaces/ecpg/preproc/extern.h | 1 + src/interfaces/ecpg/preproc/preproc.y | 468 +++++++++++++++++++- src/interfaces/ecpg/preproc/type.h | 14 + src/interfaces/ecpg/test/Makefile | 7 +- src/interfaces/ecpg/test/dyntest.pgc | 127 ++++++ 16 files changed, 1002 insertions(+), 22 deletions(-) create mode 100644 src/interfaces/ecpg/README.dynSQL create mode 100644 src/interfaces/ecpg/include/sql3types.h create mode 100644 src/interfaces/ecpg/lib/dynamic.c create mode 100644 src/interfaces/ecpg/test/dyntest.pgc diff --git a/src/interfaces/ecpg/ChangeLog b/src/interfaces/ecpg/ChangeLog index 3d5828f6ba..157f219a30 100644 --- a/src/interfaces/ecpg/ChangeLog +++ b/src/interfaces/ecpg/ChangeLog @@ -809,5 +809,10 @@ Tue Feb 15 17:39:19 CET 2000 Wed Feb 16 11:57:02 CET 2000 - Fixed library to be able to input complete arrays. + +Wed Feb 16 17:04:41 CET 2000 + + - Apply patch by Christof Petig that adds + descriptors. - Set library version to 3.1.0. - Set ecpg version to 2.7.0. diff --git a/src/interfaces/ecpg/README.dynSQL b/src/interfaces/ecpg/README.dynSQL new file mode 100644 index 0000000000..fedcf80402 --- /dev/null +++ b/src/interfaces/ecpg/README.dynSQL @@ -0,0 +1,20 @@ +descriptor statements have the following shortcomings + +- up to now the only reasonable statement is + FETCH ... INTO SQL DESCRIPTOR + no input variables allowed! + + Reason: to fully support dynamic SQL the frontend/backend communication + should change to recognize input parameters. + Since this is not likely to happen in the near future and you + can cover the same functionality with the existing infrastructure + I'll leave the work to someone else. + +- string buffer overflow does not always generate warnings + (beware: terminating 0 may be missing because strncpy is used) + :var=data sets sqlwarn accordingly (but not indicator) + +- char variables pointing to NULL are not allocated on demand + +- string truncation does not show up in indicator + diff --git a/src/interfaces/ecpg/TODO b/src/interfaces/ecpg/TODO index 542894945d..747a87ac3f 100644 --- a/src/interfaces/ecpg/TODO +++ b/src/interfaces/ecpg/TODO @@ -18,8 +18,6 @@ cvariable for an array var How can one insert arrays from c variables? -support for dynamic SQL with unknown number of variables with DESCRIPTORS - What happens to the output variable during read if there was an indicator-error? diff --git a/src/interfaces/ecpg/include/Makefile b/src/interfaces/ecpg/include/Makefile index db0ea7954e..6ab79fd856 100644 --- a/src/interfaces/ecpg/include/Makefile +++ b/src/interfaces/ecpg/include/Makefile @@ -10,11 +10,13 @@ install:: $(INSTALL) $(INSTLOPTS) ecpglib.h $(HEADERDIR) $(INSTALL) $(INSTLOPTS) ecpgtype.h $(HEADERDIR) $(INSTALL) $(INSTLOPTS) sqlca.h $(HEADERDIR) + $(INSTALL) $(INSTLOPTS) sql3types.h $(HEADERDIR) uninstall:: rm -f $(HEADERDIR)/ecpgerrno.h rm -f $(HEADERDIR)/ecpglib.h rm -f $(HEADERDIR)/ecpgtype.h rm -f $(HEADERDIR)/sqlca.h + rm -f $(HEADERDIR)/sql3types.h dep depend: diff --git a/src/interfaces/ecpg/include/ecpgerrno.h b/src/interfaces/ecpg/include/ecpgerrno.h index db2618f31d..429703a29c 100644 --- a/src/interfaces/ecpg/include/ecpgerrno.h +++ b/src/interfaces/ecpg/include/ecpgerrno.h @@ -29,6 +29,10 @@ #define ECPG_INVALID_STMT -230 +/* dynamic SQL related */ +#define ECPG_UNKNOWN_DESCRIPTOR -240 +#define ECPG_INVALID_DESCRIPTOR_INDEX -241 + /* finally the backend error messages, they start at 400 */ #define ECPG_PGSQL -400 #define ECPG_TRANS -401 diff --git a/src/interfaces/ecpg/include/ecpglib.h b/src/interfaces/ecpg/include/ecpglib.h index 9ceb691695..f50a2bb09d 100644 --- a/src/interfaces/ecpg/include/ecpglib.h +++ b/src/interfaces/ecpg/include/ecpglib.h @@ -1,4 +1,5 @@ #include +#include #ifdef __cplusplus extern "C" @@ -49,6 +50,17 @@ extern "C" #define SQLCODE sqlca.sqlcode +/* dynamic SQL */ + + unsigned int ECPGDynamicType(Oid type); + unsigned int ECPGDynamicType_DDT(Oid type); + PGresult * ECPGresultByDescriptor(int line,const char *name); + bool ECPGdo_descriptor(int line,const char *connection, + const char *descriptor,const char *query); + bool ECPGdeallocate_desc(int line,const char *name); + bool ECPGallocate_desc(int line,const char *name); + void ECPGraise(int line,int code); + #ifdef __cplusplus } diff --git a/src/interfaces/ecpg/include/sql3types.h b/src/interfaces/ecpg/include/sql3types.h new file mode 100644 index 0000000000..c844975b4a --- /dev/null +++ b/src/interfaces/ecpg/include/sql3types.h @@ -0,0 +1,38 @@ +/* SQL3 dynamic type codes + * + * Copyright (c) 2000, Christof Petig + * + * $Header: /cvsroot/pgsql/src/interfaces/ecpg/include/sql3types.h,v 1.1 2000/02/16 16:18:03 meskes Exp $ + */ + +/* chapter 13.1 table 2: Codes used for SQL data types in Dynamic SQL */ + +enum { SQL3_CHARACTER=1, + SQL3_NUMERIC, + SQL3_DECIMAL, + SQL3_INTEGER, + SQL3_SMALLINT, + SQL3_FLOAT, + SQL3_REAL, + SQL3_DOUBLE_PRECISION, + SQL3_DATE_TIME_TIMESTAMP, + SQL3_INTERVAL, //10 + SQL3_CHARACTER_VARYING=12, + SQL3_ENUMERATED, + SQL3_BIT, + SQL3_BIT_VARYING, + SQL3_BOOLEAN, + SQL3_abstract + // the rest is xLOB stuff + }; + +/* chapter 13.1 table 3: Codes associated with datetime data types in Dynamic SQL */ + +enum { SQL3_DDT_DATE=1, + SQL3_DDT_TIME, + SQL3_DDT_TIMESTAMP, + SQL3_DDT_TIME_WITH_TIME_ZONE, + SQL3_DDT_TIMESTAMP_WITH_TIME_ZONE, + + SQL3_DDT_ILLEGAL /* not a datetime data type (not part of standard) */ + }; diff --git a/src/interfaces/ecpg/lib/Makefile.in b/src/interfaces/ecpg/lib/Makefile.in index 296c3c5a40..947f9fd1a9 100644 --- a/src/interfaces/ecpg/lib/Makefile.in +++ b/src/interfaces/ecpg/lib/Makefile.in @@ -6,7 +6,7 @@ # Copyright (c) 1994, Regents of the University of California # # IDENTIFICATION -# $Header: /cvsroot/pgsql/src/interfaces/ecpg/lib/Attic/Makefile.in,v 1.57 2000/02/16 11:52:24 meskes Exp $ +# $Header: /cvsroot/pgsql/src/interfaces/ecpg/lib/Attic/Makefile.in,v 1.58 2000/02/16 16:18:05 meskes Exp $ # #------------------------------------------------------------------------- @@ -36,7 +36,7 @@ include $(SRCDIR)/Makefile.shlib install: install-lib $(install-shlib-dep) # Handmade dependencies in case make depend not done -ecpglib.o : ecpglib.c ../include/ecpglib.h ../include/ecpgtype.h +ecpglib.o : ecpglib.c ../include/ecpglib.h ../include/ecpgtype.h dynamic.c typename.o : typename.c ../include/ecpgtype.h diff --git a/src/interfaces/ecpg/lib/dynamic.c b/src/interfaces/ecpg/lib/dynamic.c new file mode 100644 index 0000000000..ec8e927d64 --- /dev/null +++ b/src/interfaces/ecpg/lib/dynamic.c @@ -0,0 +1,290 @@ +/* dynamic SQL support routines + * + * Copyright (c) 2000, Christof Petig + * + * $Header: /cvsroot/pgsql/src/interfaces/ecpg/lib/Attic/dynamic.c,v 1.1 2000/02/16 16:18:12 meskes Exp $ + */ + +/* I borrowed the include files from ecpglib.c, maybe we don't need all of them */ + +#if 0 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#endif +#include + +static struct descriptor +{ char *name; + PGresult *result; + struct descriptor *next; +} *all_descriptors=NULL; + +PGconn *ECPG_internal_get_connection(char *name); + +unsigned int ECPGDynamicType(Oid type) +{ switch(type) + { case 16: return SQL3_BOOLEAN; /* bool */ + case 21: return SQL3_SMALLINT; /* int2 */ + case 23: return SQL3_INTEGER; /* int4 */ + case 25: return SQL3_CHARACTER; /* text */ + case 700: return SQL3_REAL; /* float4 */ + case 701: return SQL3_DOUBLE_PRECISION; /* float8 */ + case 1042: return SQL3_CHARACTER; /* bpchar */ + case 1043: return SQL3_CHARACTER_VARYING; /* varchar */ + case 1082: return SQL3_DATE_TIME_TIMESTAMP; /* date */ + case 1083: return SQL3_DATE_TIME_TIMESTAMP; /* time */ + case 1184: return SQL3_DATE_TIME_TIMESTAMP; /* datetime */ + case 1296: return SQL3_DATE_TIME_TIMESTAMP; /* timestamp */ + case 1700: return SQL3_NUMERIC; /* numeric */ + default: + return -type; + } +} + +unsigned int ECPGDynamicType_DDT(Oid type) +{ switch(type) + { + case 1082: return SQL3_DDT_DATE; /* date */ + case 1083: return SQL3_DDT_TIME; /* time */ + case 1184: return SQL3_DDT_TIMESTAMP_WITH_TIME_ZONE; /* datetime */ + case 1296: return SQL3_DDT_TIMESTAMP_WITH_TIME_ZONE; /* timestamp */ + default: + return SQL3_DDT_ILLEGAL; + } +} + +// like ECPGexecute +static bool execute_descriptor(int lineno,const char *query + ,struct connection *con,PGresult **resultptr) +{ + bool status = false; + PGresult *results; + PGnotify *notify; + + /* Now the request is built. */ + + if (con->committed && !con->autocommit) + { + if ((results = PQexec(con->connection, "begin transaction")) == NULL) + { + register_error(ECPG_TRANS, "Error in transaction processing line %d.", lineno); + return false; + } + PQclear(results); + con->committed = false; + } + + ECPGlog("execute_descriptor line %d: QUERY: %s on connection %s\n", lineno, query, con->name); + results = PQexec(con->connection, query); + + if (results == NULL) + { + ECPGlog("ECPGexecute line %d: error: %s", lineno, + PQerrorMessage(con->connection)); + register_error(ECPG_PGSQL, "Postgres error: %s line %d.", + PQerrorMessage(con->connection), lineno); + } + else + { *resultptr=results; + switch (PQresultStatus(results)) + { int ntuples; + case PGRES_TUPLES_OK: + status = true; + sqlca.sqlerrd[2] = ntuples = PQntuples(results); + if (ntuples < 1) + { + ECPGlog("execute_descriptor line %d: Incorrect number of matches: %d\n", + lineno, ntuples); + register_error(ECPG_NOT_FOUND, "No data found line %d.", lineno); + status = false; + break; + } + break; +#if 1 /* strictly these are not needed (yet) */ + case PGRES_EMPTY_QUERY: + /* do nothing */ + register_error(ECPG_EMPTY, "Empty query line %d.", lineno); + break; + case PGRES_COMMAND_OK: + status = true; + sqlca.sqlerrd[1] = atol(PQoidStatus(results)); + sqlca.sqlerrd[2] = atol(PQcmdTuples(results)); + ECPGlog("ECPGexecute line %d Ok: %s\n", lineno, PQcmdStatus(results)); + break; + case PGRES_COPY_OUT: + ECPGlog("ECPGexecute line %d: Got PGRES_COPY_OUT ... tossing.\n", lineno); + PQendcopy(con->connection); + break; + case PGRES_COPY_IN: + ECPGlog("ECPGexecute line %d: Got PGRES_COPY_IN ... tossing.\n", lineno); + PQendcopy(con->connection); + break; +#else + case PGRES_EMPTY_QUERY: + case PGRES_COMMAND_OK: + case PGRES_COPY_OUT: + case PGRES_COPY_IN: + break; +#endif + case PGRES_NONFATAL_ERROR: + case PGRES_FATAL_ERROR: + case PGRES_BAD_RESPONSE: + ECPGlog("ECPGexecute line %d: Error: %s", + lineno, PQerrorMessage(con->connection)); + register_error(ECPG_PGSQL, "Postgres error: %s line %d.", + PQerrorMessage(con->connection), lineno); + status = false; + break; + default: + ECPGlog("ECPGexecute line %d: Got something else, postgres error.\n", + lineno); + register_error(ECPG_PGSQL, "Postgres error: %s line %d.", + PQerrorMessage(con->connection), lineno); + status = false; + break; + } + } + + /* check for asynchronous returns */ + notify = PQnotifies(con->connection); + if (notify) + { + ECPGlog("ECPGexecute line %d: ASYNC NOTIFY of '%s' from backend pid '%d' received\n", + lineno, notify->relname, notify->be_pid); + free(notify); + } + return status; +} + +/* like ECPGdo */ +static bool do_descriptor2(int lineno,const char *connection_name, + PGresult **resultptr, const char *query) +{ + struct connection *con = get_connection(connection_name); + bool status=true; + char *locale = setlocale(LC_NUMERIC, NULL); + + /* Make sure we do NOT honor the locale for numeric input/output */ + /* since the database wants teh standard decimal point */ + setlocale(LC_NUMERIC, "C"); + + if (!ecpg_init(con, connection_name, lineno)) + { setlocale(LC_NUMERIC, locale); + return(false); + } + + /* are we connected? */ + if (con == NULL || con->connection == NULL) + { + ECPGlog("ECPGdo: not connected to %s\n", con->name); + register_error(ECPG_NOT_CONN, "Not connected in line %d.", lineno); + setlocale(LC_NUMERIC, locale); + return false; + } + + status = execute_descriptor(lineno,query,con,resultptr); + + /* and reset locale value so our application is not affected */ + setlocale(LC_NUMERIC, locale); + return (status); +} + +bool ECPGdo_descriptor(int line,const char *connection, + const char *descriptor,const char *query) +{ + struct descriptor *i; + for (i=all_descriptors;i!=NULL;i=i->next) + { if (!strcmp(descriptor,i->name)) + { + bool status; + + /* free previous result */ + if (i->result) PQclear(i->result); + i->result=NULL; + + status=do_descriptor2(line,connection,&i->result,query); + + if (!i->result) PQmakeEmptyPGresult(NULL, 0); + return (status); + } + } + ECPGraise(line,ECPG_UNKNOWN_DESCRIPTOR); + return false; +} + +PGresult *ECPGresultByDescriptor(int line,const char *name) +{ + struct descriptor *i; + for (i=all_descriptors;i!=NULL;i=i->next) + { if (!strcmp(name,i->name)) return i->result; + } + ECPGraise(line,ECPG_UNKNOWN_DESCRIPTOR); + return 0; +} + + +bool ECPGdeallocate_desc(int line,const char *name) +{ + struct descriptor *i; + struct descriptor **lastptr=&all_descriptors; + for (i=all_descriptors;i;lastptr=&i->next,i=i->next) + { if (!strcmp(name,i->name)) + { *lastptr=i->next; + free(i->name); + PQclear(i->result); + free(i); + return true; + } + } + ECPGraise(line,ECPG_UNKNOWN_DESCRIPTOR); + return false; +} + +bool ECPGallocate_desc(int line,const char *name) +{ + struct descriptor *new=(struct descriptor *)malloc(sizeof(struct descriptor)); + + new->next=all_descriptors; + new->name=malloc(strlen(name)+1); + new->result=PQmakeEmptyPGresult(NULL, 0); + strcpy(new->name,name); + all_descriptors=new; + return true; +} + +void ECPGraise(int line,int code) +{ sqlca.sqlcode=code; + switch (code) + { case ECPG_NOT_FOUND: + snprintf(sqlca.sqlerrm.sqlerrmc,sizeof(sqlca.sqlerrm.sqlerrmc), + "No data found line %d.",line); + break; + case ECPG_MISSING_INDICATOR: + snprintf(sqlca.sqlerrm.sqlerrmc,sizeof(sqlca.sqlerrm.sqlerrmc), + "NULL value without indicator, line %d.",line); + break; + case ECPG_UNKNOWN_DESCRIPTOR: + snprintf(sqlca.sqlerrm.sqlerrmc,sizeof(sqlca.sqlerrm.sqlerrmc), + "descriptor not found, line %d.",line); + break; + case ECPG_INVALID_DESCRIPTOR_INDEX: + snprintf(sqlca.sqlerrm.sqlerrmc,sizeof(sqlca.sqlerrm.sqlerrmc), + "descriptor index out of range, line %d.",line); + break; + default: + snprintf(sqlca.sqlerrm.sqlerrmc,sizeof(sqlca.sqlerrm.sqlerrmc), + "SQL error #%d, line %d.",code,line); + break; + } +} diff --git a/src/interfaces/ecpg/lib/ecpglib.c b/src/interfaces/ecpg/lib/ecpglib.c index 5074bc20e0..a6e2b23e01 100644 --- a/src/interfaces/ecpg/lib/ecpglib.c +++ b/src/interfaces/ecpg/lib/ecpglib.c @@ -20,7 +20,6 @@ #include #include -#include #include #include #include @@ -753,7 +752,7 @@ ECPGexecute(struct statement * stmt) { char *pval; char *scan_length; - char *array_query; + char *array_query; if (var == NULL) { @@ -1127,36 +1126,44 @@ ECPGexecute(struct statement * stmt) bool ECPGdo(int lineno, const char *connection_name, char *query,...) { - va_list args; - struct statement *stmt; - struct connection *con = get_connection(connection_name); - bool status; - char *locale = setlocale(LC_NUMERIC, NULL); + va_list args; + struct statement *stmt; + struct connection *con = get_connection(connection_name); + bool status=true; + char *locale = setlocale(LC_NUMERIC, NULL); /* Make sure we do NOT honor the locale for numeric input/output */ /* since the database wants teh standard decimal point */ setlocale(LC_NUMERIC, "C"); if (!ecpg_init(con, connection_name, lineno)) + { + setlocale(LC_NUMERIC, locale); return(false); + } va_start(args, query); if (create_statement(lineno, con, &stmt, query, args) == false) + { + setlocale(LC_NUMERIC, locale); return (false); + } va_end(args); /* are we connected? */ if (con == NULL || con->connection == NULL) { + free_statement(stmt); ECPGlog("ECPGdo: not connected to %s\n", con->name); register_error(ECPG_NOT_CONN, "Not connected in line %d.", lineno); + setlocale(LC_NUMERIC, locale); return false; } status = ECPGexecute(stmt); free_statement(stmt); - /* and reser value so our application is not affected */ + /* and reset locale value so our application is not affected */ setlocale(LC_NUMERIC, locale); return (status); } @@ -1508,3 +1515,5 @@ ECPGprepared_statement(char *name) for (this = prep_stmts; this != NULL && strcmp(this->name, name) != 0; this = this->next); return (this) ? this->stmt->command : NULL; } + +#include "dynamic.c" diff --git a/src/interfaces/ecpg/preproc/ecpg_keywords.c b/src/interfaces/ecpg/preproc/ecpg_keywords.c index 89676e7c34..0cc7699a6d 100644 --- a/src/interfaces/ecpg/preproc/ecpg_keywords.c +++ b/src/interfaces/ecpg/preproc/ecpg_keywords.c @@ -19,6 +19,7 @@ */ static ScanKeyword ScanKeywords[] = { /* name value */ + {"allocate", SQL_ALLOCATE}, {"at", SQL_AT}, {"autocommit", SQL_AUTOCOMMIT}, {"bool", SQL_BOOL}, @@ -28,10 +29,12 @@ static ScanKeyword ScanKeywords[] = { {"connection", SQL_CONNECTION}, {"continue", SQL_CONTINUE}, {"deallocate", SQL_DEALLOCATE}, + {"descriptor", SQL_DESCRIPTOR}, {"disconnect", SQL_DISCONNECT}, {"enum", SQL_ENUM}, {"found", SQL_FOUND}, {"free", SQL_FREE}, + {"get", SQL_GET}, {"go", SQL_GO}, {"goto", SQL_GOTO}, {"identified", SQL_IDENTIFIED}, @@ -46,12 +49,14 @@ static ScanKeyword ScanKeywords[] = { {"section", SQL_SECTION}, {"short", SQL_SHORT}, {"signed", SQL_SIGNED}, + {"sql",SQL_SQL}, // strange thing, used for into sql descriptor MYDESC; {"sqlerror", SQL_SQLERROR}, {"sqlprint", SQL_SQLPRINT}, {"sqlwarning", SQL_SQLWARNING}, {"stop", SQL_STOP}, {"struct", SQL_STRUCT}, {"unsigned", SQL_UNSIGNED}, + {"value", SQL_VALUE}, {"var", SQL_VAR}, {"whenever", SQL_WHENEVER}, }; diff --git a/src/interfaces/ecpg/preproc/extern.h b/src/interfaces/ecpg/preproc/extern.h index 682ead43ec..2a1da562a7 100644 --- a/src/interfaces/ecpg/preproc/extern.h +++ b/src/interfaces/ecpg/preproc/extern.h @@ -29,6 +29,7 @@ extern struct arguments *argsinsert; extern struct arguments *argsresult; extern struct when when_error, when_nf, when_warn; extern struct ECPGstruct_member *struct_member_list[STRUCT_DEPTH]; +extern struct descriptor *descriptors; /* functions */ diff --git a/src/interfaces/ecpg/preproc/preproc.y b/src/interfaces/ecpg/preproc/preproc.y index 8abcec55e5..0c492febf0 100644 --- a/src/interfaces/ecpg/preproc/preproc.y +++ b/src/interfaces/ecpg/preproc/preproc.y @@ -23,6 +23,8 @@ int struct_level = 0; char errortext[128]; static char *connection = NULL; +static char *descriptor_name = NULL; +static char *descriptor_index= NULL; static int QueryIsRule = 0, ForUpdateNotAllowed = 0, FoundInto = 0; static int FoundSort = 0; static int initializer = 0; @@ -38,6 +40,11 @@ struct variable no_indicator = {"no_indicator", &ecpg_no_indicator, 0, NULL}; struct ECPGtype ecpg_query = {ECPGt_char_variable, 0L, {NULL}}; +/* variable lookup */ + +static struct variable * find_variable(char * name); +static void whenever_action(int mode); + /* * Handle parsing errors and warnings */ @@ -80,6 +87,275 @@ output_simple_statement(char *cmd) free(cmd); } +/* + * assignment handling function (descriptor) + */ + +static struct assignment *assignments; + +static void push_assignment(char *var,char *value) +{ + struct assignment *new=(struct assignment *)mm_alloc(sizeof(struct assignment)); + + new->next=assignments; + new->variable=mm_alloc(strlen(var)+1); + strcpy(new->variable,var); + new->value=mm_alloc(strlen(value)+1); + strcpy(new->value,value); + assignments=new; +} + +static void drop_assignments(void) +{ while (assignments) + { struct assignment *old_head=assignments; + assignments=old_head->next; + free(old_head->variable); + free(old_head->value); + free(old_head); + } +} + +/* XXX: these should be more accurate (consider ECPGdump_a_* ) */ +static void ECPGnumeric_lvalue(FILE *f,char *name) +{ const struct variable *v=find_variable(name); + switch(v->type->typ) + { case ECPGt_short: + case ECPGt_int: + case ECPGt_long: + case ECPGt_unsigned_short: + case ECPGt_unsigned_int: + case ECPGt_unsigned_long: + fputs(name,yyout); + break; + default: + snprintf(errortext,sizeof errortext,"variable %s: numeric type needed" + ,name); + mmerror(ET_ERROR,errortext); + break; + } +} + +static void ECPGstring_buffer(FILE *f,char *name) +{ const struct variable *v=find_variable(name); + switch(v->type->typ) + { case ECPGt_varchar: + fprintf(yyout,"%s.arr",name); + break; + + case ECPGt_char: + case ECPGt_unsigned_char: + fputs(name,yyout); + break; + + default: + snprintf(errortext,sizeof errortext,"variable %s: character type needed" + ,name); + mmerror(ET_ERROR,errortext); + break; + } +} + +static void ECPGstring_length(FILE *f,char *name) +{ const struct variable *v=find_variable(name); + switch(v->type->typ) + { case ECPGt_varchar: + case ECPGt_char: + case ECPGt_unsigned_char: + if (!v->type->size) + { snprintf(errortext,sizeof errortext,"zero length char variable %s for assignment", + v->name); + mmerror(ET_ERROR,errortext); + } + fprintf(yyout,"%ld",v->type->size); + break; + default: + snprintf(errortext,sizeof errortext,"variable %s: character type needed" + ,name); + mmerror(ET_ERROR,errortext); + break; + } +} + +static void ECPGdata_assignment(char *variable,char *index_plus_1) +{ const struct variable *v=find_variable(variable); + fprintf(yyout,"\t\t\tif (!PQgetisnull(ECPGresult,0,(%s)-1))\n",index_plus_1); + switch(v->type->typ) + { case ECPGt_short: + case ECPGt_int: /* use the same conversion as ecpglib does */ + case ECPGt_long: + fprintf(yyout,"\t\t\t\t%s=strtol(PQgetvalue(ECPGresult,0,(%s)-1),NULL,10);\n" + ,variable,index_plus_1); + break; + case ECPGt_unsigned_short: + case ECPGt_unsigned_int: + case ECPGt_unsigned_long: + fprintf(yyout,"\t\t\t\t%s=strtoul(PQgetvalue(ECPGresult,0,(%s)-1),NULL,10);\n" + ,variable,index_plus_1); + break; + case ECPGt_float: + case ECPGt_double: + fprintf(yyout,"\t\t\t\t%s=strtod(PQgetvalue(ECPGresult,0,(%s)-1),NULL);\n" + ,variable,index_plus_1); + break; + + case ECPGt_bool: + fprintf(yyout,"\t\t\t\t%s=PQgetvalue(ECPGresult,0,(%s)-1)[0]=='t';\n" + ,variable,index_plus_1); + break; + + case ECPGt_varchar: + fprintf(yyout,"\t\t\t{\tstrncpy(%s.arr,PQgetvalue(ECPGresult,0,(%s)-1),%ld);\n" + ,variable,index_plus_1,v->type->size); + fprintf(yyout,"\t\t\t\t%s.len=strlen(PQgetvalue(ECPGresult,0,(%s)-1)\n" + ,variable,index_plus_1); + fprintf(yyout,"\t\t\t\tif (%s.len>%ld) { %s.len=%ld; sqlca.sqlwarn[0]=sqlca.sqlwarn[1]='W'; }\n" + ,variable,v->type->size,variable,v->type->size); + fputs("\t\t\t}\n",yyout); + break; + + case ECPGt_char: + case ECPGt_unsigned_char: + if (!v->type->size) + { snprintf(errortext,sizeof errortext,"zero length char variable %s for DATA assignment", + v->name); + mmerror(ET_ERROR,errortext); + } + fprintf(yyout,"\t\t\t{\tstrncpy(%s,PQgetvalue(ECPGresult,0,(%s)-1),%ld);\n" + ,variable,index_plus_1,v->type->size); + fprintf(yyout,"\t\t\t\tif (strlen(PQgetvalue(ECPGresult,0,(%s)-1))>=%ld)\n" + "\t\t\t\t{ %s[%ld]=0; sqlca.sqlwarn[0]=sqlca.sqlwarn[1]='W'; }\n" + ,index_plus_1,v->type->size,variable,v->type->size-1); + fputs("\t\t\t}\n",yyout); + break; + + default: + snprintf(errortext,sizeof errortext,"unknown variable type %d for DATA assignment" + ,v->type->typ); + mmerror(ET_ERROR,errortext); + break; + } +} + +static void +output_get_descr_header(char *desc_name) +{ struct assignment *results; + fprintf(yyout,"{\tPGresult *ECPGresult=ECPGresultByDescriptor(%d, \"%s\");\n" + ,yylineno,desc_name); + fputs("\tif (ECPGresult)\n\t{",yyout); + for (results=assignments;results!=NULL;results=results->next) + { if (!strcasecmp(results->value,"count")) + { fputs("\t\t",yyout); + ECPGnumeric_lvalue(yyout,results->variable); + fputs("=PQnfields(ECPGresult);\n",yyout); + } + else + { snprintf(errortext,sizeof errortext,"unknown descriptor header item '%s'",results->value); + mmerror(ET_WARN,errortext); + } + } + drop_assignments(); + fputs("}",yyout); + + whenever_action(2|1); +} + +static void +output_get_descr(char *desc_name) +{ struct assignment *results; + int flags=0; + const int DATA_SEEN=1; + const int INDICATOR_SEEN=2; + + fprintf(yyout,"{\tPGresult *ECPGresult=ECPGresultByDescriptor(%d, \"%s\");\n" + ,yylineno,desc_name); + fputs("\tif (ECPGresult)\n\t{",yyout); + fprintf(yyout,"\tif (PQntuples(ECPGresult)<1) ECPGraise(%d,ECPG_NOT_FOUND);\n",yylineno); + fprintf(yyout,"\t\telse if (%s<1 || %s>PQnfields(ECPGresult))\n" + "\t\t\tECPGraise(%d,ECPG_INVALID_DESCRIPTOR_INDEX);\n" + ,descriptor_index,descriptor_index,yylineno); + fputs("\t\telse\n\t\t{\n",yyout); + for (results=assignments;results!=NULL;results=results->next) + { if (!strcasecmp(results->value,"type")) + { fputs("\t\t\t",yyout); + ECPGnumeric_lvalue(yyout,results->variable); + fprintf(yyout,"=ECPGDynamicType(PQftype(ECPGresult,(%s)-1));\n",descriptor_index); + } + else if (!strcasecmp(results->value,"datetime_interval_code")) + { fputs("\t\t\t",yyout); + ECPGnumeric_lvalue(yyout,results->variable); + fprintf(yyout,"=ECPGDynamicType_DDT(PQftype(ECPGresult,(%s)-1));\n",descriptor_index); + } + else if (!strcasecmp(results->value,"length")) + { fputs("\t\t\t",yyout); + ECPGnumeric_lvalue(yyout,results->variable); + fprintf(yyout,"=PQfmod(ECPGresult,(%s)-1)-VARHDRSZ;\n",descriptor_index); + } + else if (!strcasecmp(results->value,"octet_length")) + { fputs("\t\t\t",yyout); + ECPGnumeric_lvalue(yyout,results->variable); + fprintf(yyout,"=PQfsize(ECPGresult,(%s)-1);\n",descriptor_index); + } + else if (!strcasecmp(results->value,"returned_length") + || !strcasecmp(results->value,"returned_octet_length")) + { fputs("\t\t\t",yyout); + ECPGnumeric_lvalue(yyout,results->variable); + fprintf(yyout,"=PQgetlength(ECPGresult,0,(%s)-1);\n",descriptor_index); + } + else if (!strcasecmp(results->value,"precision")) + { fputs("\t\t\t",yyout); + ECPGnumeric_lvalue(yyout,results->variable); + fprintf(yyout,"=PQfmod(ECPGresult,(%s)-1)>>16;\n",descriptor_index); + } + else if (!strcasecmp(results->value,"scale")) + { fputs("\t\t\t",yyout); + ECPGnumeric_lvalue(yyout,results->variable); + fprintf(yyout,"=(PQfmod(ECPGresult,(%s)-1)-VARHDRSZ)&0xffff;\n",descriptor_index); + } + else if (!strcasecmp(results->value,"nullable")) + { mmerror(ET_WARN,"nullable is always 1"); + fputs("\t\t\t",yyout); + ECPGnumeric_lvalue(yyout,results->variable); + fprintf(yyout,"=1;\n"); + } + else if (!strcasecmp(results->value,"key_member")) + { mmerror(ET_WARN,"key_member is always 0"); + fputs("\t\t\t",yyout); + ECPGnumeric_lvalue(yyout,results->variable); + fprintf(yyout,"=0;\n"); + } + else if (!strcasecmp(results->value,"name")) + { fputs("\t\t\tstrncpy(",yyout); + ECPGstring_buffer(yyout,results->variable); + fprintf(yyout,",PQfname(ECPGresult,(%s)-1),",descriptor_index); + ECPGstring_length(yyout,results->variable); + fputs(");\n",yyout); + } + else if (!strcasecmp(results->value,"indicator")) + { flags|=INDICATOR_SEEN; + fputs("\t\t\t",yyout); + ECPGnumeric_lvalue(yyout,results->variable); + fprintf(yyout,"=-PQgetisnull(ECPGresult,0,(%s)-1);\n",descriptor_index); + } + else if (!strcasecmp(results->value,"data")) + { flags|=DATA_SEEN; + ECPGdata_assignment(results->variable,descriptor_index); + } + else + { snprintf(errortext,sizeof errortext,"unknown descriptor header item '%s'",results->value); + mmerror(ET_WARN,errortext); + } + } + if (flags==DATA_SEEN) /* no indicator */ + { fprintf(yyout,"\t\t\tif (PQgetisnull(ECPGresult,0,(%s)-1))\n" + "\t\t\t\tECPGraise(%d,ECPG_MISSING_INDICATOR);\n" + ,descriptor_index,yylineno); + } + drop_assignments(); + fputs("\t\t}\n\t}\n",yyout); + + whenever_action(2|1); +} + /* * store the whenever action here */ @@ -157,8 +433,6 @@ new_variable(const char * name, struct ECPGtype * type) return(p); } -static struct variable * find_variable(char * name); - static struct variable * find_struct_member(char *name, char *str, struct ECPGstruct_member *members) { @@ -401,6 +675,67 @@ check_indicator(struct ECPGtype *var) } } +/* + * descriptor name lookup + */ + +static struct descriptor *descriptors; + +static void add_descriptor(char *name,char *connection) +{ + struct descriptor *new=(struct descriptor *)mm_alloc(sizeof(struct descriptor)); + + new->next=descriptors; + new->name=mm_alloc(strlen(name)+1); + strcpy(new->name,name); + if (connection) + { new->connection=mm_alloc(strlen(connection)+1); + strcpy(new->connection,connection); + } + else new->connection=connection; + descriptors=new; +} + +static void drop_descriptor(char *name,char *connection) +{ struct descriptor *i; + struct descriptor **lastptr=&descriptors; + for (i=descriptors;i;lastptr=&i->next,i=i->next) + { if (!strcmp(name,i->name)) + { if ((!connection && !i->connection) + || (connection && i->connection + && !strcmp(connection,i->connection))) + { *lastptr=i->next; + if (i->connection) free(i->connection); + free(i->name); + free(i); + return; + } + } + } + snprintf(errortext,sizeof errortext,"unknown descriptor %s",name); + mmerror(ET_WARN,errortext); +} + +static struct descriptor *lookup_descriptor(char *name,char *connection) +{ struct descriptor *i; + for (i=descriptors;i;i=i->next) + { if (!strcmp(name,i->name)) + { if ((!connection && !i->connection) + || (connection && i->connection + && !strcmp(connection,i->connection))) + { return i; + } + } + } + snprintf(errortext,sizeof errortext,"unknown descriptor %s",name); + mmerror(ET_WARN,errortext); + return NULL; +} + +/* + * string concatenation + */ + static char * cat2_str(char *str1, char *str2) { @@ -522,6 +857,32 @@ output_statement(char * stmt, int mode) free(connection); } +static void +output_statement_desc(char * stmt, int mode) +{ + int i, j=strlen(stmt); + + fprintf(yyout, "{ ECPGdo_descriptor(__LINE__, %s, \"%s\", \"", + connection ? connection : "NULL", descriptor_name); + + /* do this char by char as we have to filter '\"' */ + for (i = 0;i < j; i++) { + if (stmt[i] != '\"') + fputc(stmt[i], yyout); + else + fputs("\\\"", yyout); + } + + fputs("\");", yyout); + + mode |= 2; + whenever_action(mode); + free(stmt); + if (connection != NULL) + free(connection); + free(descriptor_name); +} + static struct typedefs * get_typedef(char *name) { @@ -632,15 +993,16 @@ adjust_array(enum ECPGttype type_enum, int *dimension, int *length, int type_dim } /* special embedded SQL token */ -%token SQL_AT SQL_AUTOCOMMIT SQL_BOOL SQL_BREAK +%token SQL_ALLOCATE SQL_AT SQL_AUTOCOMMIT SQL_BOOL SQL_BREAK %token SQL_CALL SQL_CONNECT SQL_CONNECTION SQL_CONTINUE -%token SQL_DEALLOCATE SQL_DISCONNECT SQL_ENUM -%token SQL_FOUND SQL_FREE SQL_GO SQL_GOTO +%token SQL_DEALLOCATE SQL_DESCRIPTOR SQL_DISCONNECT SQL_ENUM +%token SQL_FOUND SQL_FREE SQL_GET SQL_GO SQL_GOTO %token SQL_IDENTIFIED SQL_INDICATOR SQL_INT SQL_LONG %token SQL_OFF SQL_OPEN SQL_PREPARE SQL_RELEASE SQL_REFERENCE -%token SQL_SECTION SQL_SHORT SQL_SIGNED SQL_SQLERROR SQL_SQLPRINT +%token SQL_SECTION SQL_SHORT SQL_SIGNED SQL_SQL +%token SQL_SQLERROR SQL_SQLPRINT %token SQL_SQLWARNING SQL_START SQL_STOP SQL_STRUCT SQL_UNSIGNED -%token SQL_VAR SQL_WHENEVER +%token SQL_VALUE SQL_VAR SQL_WHENEVER /* C token */ %token S_ANYTHING S_AUTO S_CONST S_EXTERN @@ -829,6 +1191,9 @@ adjust_array(enum ECPGttype type_enum, int *dimension, int *length, int type_dim %type ECPGFree ECPGDeclare ECPGVar opt_at enum_definition %type struct_type s_struct declaration declarations variable_declarations %type s_struct s_union union_type ECPGSetAutocommit on_off +%type ECPGAllocateDescr ECPGDeallocateDescr +%type ECPGGetDescriptor ECPGGetDescriptorHeader +%type FetchDescriptorStmt %type simple_type signed_type unsigned_type varchar_type @@ -879,6 +1244,7 @@ stmt: AlterTableStmt { output_statement($1, 0); } | ExtendStmt { output_statement($1, 0); } | ExplainStmt { output_statement($1, 0); } | FetchStmt { output_statement($1, 1); } + | FetchDescriptorStmt { output_statement_desc($1, 1); } | GrantStmt { output_statement($1, 0); } | IndexStmt { output_statement($1, 0); } | ListenStmt { output_statement($1, 0); } @@ -912,6 +1278,10 @@ stmt: AlterTableStmt { output_statement($1, 0); } | VariableShowStmt { output_statement($1, 0); } | VariableResetStmt { output_statement($1, 0); } | ConstraintsSetStmt { output_statement($1, 0); } + | ECPGAllocateDescr { fprintf(yyout,"ECPGallocate_desc(__LINE__, \"%s\");",$1); + whenever_action(0); + free($1); + } | ECPGConnect { if (connection) mmerror(ET_ERROR, "no at option for connect statement.\n"); @@ -932,6 +1302,10 @@ stmt: AlterTableStmt { output_statement($1, 0); } whenever_action(2); free($1); } + | ECPGDeallocateDescr { fprintf(yyout,"ECPGdeallocate_desc(__LINE__, \"%s\");",$1); + whenever_action(0); + free($1); + } | ECPGDeclare { output_simple_statement($1); } @@ -952,6 +1326,14 @@ stmt: AlterTableStmt { output_statement($1, 0); } whenever_action(2); free($1); } + | ECPGGetDescriptor { + lookup_descriptor($1,connection); + output_get_descr($1); + } + | ECPGGetDescriptorHeader { + lookup_descriptor($1,connection); + output_get_descr_header($1); + } | ECPGOpen { struct cursor *ptr; @@ -5014,6 +5396,78 @@ ECPGPrepare: SQL_PREPARE ident FROM execstring $$ = cat2_str(make3_str(make_str("\""), $2, make_str("\",")), $4); } +/* + * dynamic SQL: descriptor based access + * written by Christof Petig + */ + +/* + * deallocate a descriptor + */ +ECPGDeallocateDescr: SQL_DEALLOCATE SQL_DESCRIPTOR ident +{ drop_descriptor($3,connection); + $$ = $3; +} + +/* + * allocate a descriptor + */ +ECPGAllocateDescr: SQL_ALLOCATE SQL_DESCRIPTOR ident +{ add_descriptor($3,connection); + $$ = $3; +} + +/* + * read from descriptor + */ + +ECPGGetDescHeaderItem: cvariable '=' ident { + push_assignment($1,$3); +} + +ECPGGetDescItem: cvariable '=' ident { + push_assignment($1,$3); +} + | cvariable '=' TYPE_P { + push_assignment($1,"type"); +} + | cvariable '=' PRECISION { + push_assignment($1,"precision"); +} + | cvariable '=' SQL_INDICATOR { + push_assignment($1,"indicator"); +} + +ECPGGetDescHeaderItems: ECPGGetDescHeaderItem + | ECPGGetDescHeaderItems ',' ECPGGetDescHeaderItem; + +ECPGGetDescItems: ECPGGetDescItem + | ECPGGetDescItems ',' ECPGGetDescItem; + +ECPGGetDescriptorHeader: SQL_GET SQL_DESCRIPTOR ident ECPGGetDescHeaderItems +{ $$ = $3; } + +ECPGGetDescriptor: SQL_GET SQL_DESCRIPTOR ident SQL_VALUE cvariable ECPGGetDescItems +{ $$ = $3; descriptor_index=$5; } + | SQL_GET SQL_DESCRIPTOR ident SQL_VALUE Iconst ECPGGetDescItems +{ $$ = $3; descriptor_index=$5; } + +/* + * fetch [ in | from ] into sql descriptor + */ + +FetchDescriptorStmt: FETCH from_in name INTO SQL_SQL SQL_DESCRIPTOR ident + { + $$ = cat_str(3, make_str("fetch"), $2, $3); + descriptor_name=$7; + } + | FETCH name INTO SQL_SQL SQL_DESCRIPTOR ident + { + $$ = cat2_str(make_str("fetch"), $2); + descriptor_name=$6; + } + ; + /* * for compatibility with ORACLE we will also allow the keyword RELEASE * after a transaction statement to disconnect from the database. diff --git a/src/interfaces/ecpg/preproc/type.h b/src/interfaces/ecpg/preproc/type.h index efc8c66923..4a8814fcdd 100644 --- a/src/interfaces/ecpg/preproc/type.h +++ b/src/interfaces/ecpg/preproc/type.h @@ -139,4 +139,18 @@ struct arguments struct arguments *next; }; +struct descriptor +{ + char *name; + char *connection; + struct descriptor *next; +}; + +struct assignment +{ + char *variable; + char *value; + struct assignment *next; +}; + enum errortype {ET_WARN, ET_ERROR, ET_FATAL}; diff --git a/src/interfaces/ecpg/test/Makefile b/src/interfaces/ecpg/test/Makefile index 03afc89b6d..9b945793e2 100644 --- a/src/interfaces/ecpg/test/Makefile +++ b/src/interfaces/ecpg/test/Makefile @@ -1,8 +1,8 @@ -all: stp.so test1 test2 test3 test4 test5 perftest +all: stp.so test1 test2 test3 test4 test5 perftest dyntest #LDFLAGS=-g -I /usr/local/pgsql/include -L/usr/local/pgsql/lib -lecpg -lpq -lcrypt -#LDFLAGS=-g -I../include -I/usr/include/postgresql -L/usr/lib/postgresql -L../lib -lecpg -lpq -lcrypt -LDFLAGS=-g -I/usr/include/postgresql -lecpg -lpq -lcrypt +LDFLAGS=-g -I../include -I/usr/include/postgresql -L/usr/lib/postgresql -L../lib -lecpg -lpq -lcrypt +#LDFLAGS=-g -I/usr/include/postgresql -lecpg -lpq -lcrypt #ECPG=/usr/local/pgsql/bin/ecpg ECPG=../preproc/ecpg -I../include @@ -16,6 +16,7 @@ test3: test3.c test4: test4.c test5: test5.c perftest: perftest.c +dyntest: dyntest.c .pgc.c: $(ECPG) $? diff --git a/src/interfaces/ecpg/test/dyntest.pgc b/src/interfaces/ecpg/test/dyntest.pgc new file mode 100644 index 0000000000..451d82ad90 --- /dev/null +++ b/src/interfaces/ecpg/test/dyntest.pgc @@ -0,0 +1,127 @@ +/* dynamic SQL test program + * + * Copyright (c) 2000, Christof Petig + * + * $Header: /cvsroot/pgsql/src/interfaces/ecpg/test/Attic/dyntest.pgc,v 1.1 2000/02/16 16:18:29 meskes Exp $ + */ + +#include + +exec sql include sql3types; +exec sql include sqlca; + +void error() +{ printf("#%d:%s\n",sqlca.sqlcode,sqlca.sqlerrm.sqlerrmc); + exit(1); +} + +int main(int argc,char **argv) +{ exec sql begin declare section; + int COUNT; + int INTVAR; + int INDEX; + int INDICATOR; + int TYPE,LENGTH,OCTET_LENGTH,PRECISION,SCALE,NULLABLE,RETURNED_OCTET_LENGTH; + int DATETIME_INTERVAL_CODE; + char NAME[120]; + char STRINGVAR[1024]; + float FLOATVAR; + double DOUBLEVAR; + char QUERY[1024]; + exec sql end declare section; + int done=0; + + snprintf(QUERY,sizeof QUERY,"select * from %s",argc>1?argv[1]:"pg_tables"); + + exec sql whenever sqlerror do error(); + + exec sql allocate descriptor MYDESC; + + exec sql connect to test; + + exec sql prepare MYQUERY from :QUERY; + exec sql declare MYCURS cursor for MYQUERY; + + exec sql open MYCURS; + + while (1) + { exec sql fetch in MYCURS into sql descriptor MYDESC; + + if (sqlca.sqlcode) break; + + exec sql get descriptor MYDESC :COUNT = count; + if (!done) + { printf("Count %d\n",COUNT); + done=1; + } + + for (INDEX=1;INDEX<=COUNT;++INDEX) + { exec sql get descriptor MYDESC value :INDEX + :TYPE = type, + :LENGTH = length, :OCTET_LENGTH=octet_length, + :RETURNED_OCTET_LENGTH=returned_octet_length, + :PRECISION = precision, :SCALE=scale, + :NULLABLE=nullable, :NAME=name, + :INDICATOR=indicator; + printf("%2d %s %d(%d)(%d,%d) %d,%d %d = " + ,INDEX,NAME,TYPE,LENGTH,PRECISION,SCALE + ,OCTET_LENGTH,RETURNED_OCTET_LENGTH,NULLABLE); + if (INDICATOR==-1) printf("NULL\n"); + else switch (TYPE) + { case SQL3_BOOLEAN: + exec sql get descriptor MYDESC value :INDEX :INTVAR=data; + printf("%s\n",INTVAR?"true":"false"); + break; + case SQL3_NUMERIC: + case SQL3_DECIMAL: + if (SCALE==0) + { exec sql get descriptor MYDESC value :INDEX :INTVAR=data; + printf("%d\n",INTVAR); + } + else + { exec sql get descriptor MYDESC value :INDEX :FLOATVAR=data; + printf("%.*f\n",SCALE,FLOATVAR); + } + break; + case SQL3_INTEGER: + case SQL3_SMALLINT: + exec sql get descriptor MYDESC value :INDEX :INTVAR=data; + printf("%d\n",INTVAR); + break; + case SQL3_FLOAT: + case SQL3_REAL: + exec sql get descriptor MYDESC value :INDEX :FLOATVAR=data; + printf("%.*f\n",PRECISION,FLOATVAR); + break; + case SQL3_DOUBLE_PRECISION: + exec sql get descriptor MYDESC value :INDEX :DOUBLEVAR=data; + printf("%.*f\n",PRECISION,DOUBLEVAR); + break; + case SQL3_DATE_TIME_TIMESTAMP: + exec sql get descriptor MYDESC value :INDEX + :DATETIME_INTERVAL_CODE=datetime_interval_code, + :STRINGVAR=data; + printf("%d \"%s\"\n",DATETIME_INTERVAL_CODE,STRINGVAR); + break; + case SQL3_INTERVAL: + exec sql get descriptor MYDESC value :INDEX :STRINGVAR=data; + printf("\"%s\"\n",STRINGVAR); + break; + case SQL3_CHARACTER: + case SQL3_CHARACTER_VARYING: + exec sql get descriptor MYDESC value :INDEX :STRINGVAR=data; + printf("\"%s\"\n",STRINGVAR); + break; + default: + exec sql get descriptor MYDESC value :INDEX :STRINGVAR=data; + printf("<\"%s\">\n",STRINGVAR); + break; + } + } + } + + exec sql close MYCURS; + + exec sql deallocate descriptor MYDESC; + return 0; +}