1 /*
2  * tdbcodbc.c --
3  *
4  *	Bridge between TDBC (Tcl DataBase Connectivity) and ODBC.
5  *
6  * Copyright (c) 2008, 2009, 2011 by Kevin B. Kenny.
7  *
8  * Please refer to the file, 'license.terms' for the conditions on
9  * redistribution of this file and for a DISCLAIMER OF ALL WARRANTIES.
10  *
11  * $Id: $
12  *
13  *-----------------------------------------------------------------------------
14  */
15 
16 #ifdef _MSC_VER
17 #  define _CRT_SECURE_NO_DEPRECATE
18 #endif
19 
20 #include <tcl.h>
21 #include <tclOO.h>
22 #include <tdbc.h>
23 
24 #include <stdio.h>
25 #include <string.h>
26 
27 #ifdef HAVE_STDINT_H
28 #  include <stdint.h>
29 #endif
30 
31 #include "int2ptr_ptr2int.h"
32 
33 #ifdef _WIN32
34 #  define WIN32_LEAN_AND_MEAN
35 #  include <windows.h>
36 #endif
37 
38 #include "fakesql.h"
39 
40 /* Static data contained in this file */
41 
42 TCL_DECLARE_MUTEX(hEnvMutex);	/* Mutex protecting the environment handle
43 				 * and its reference count */
44 
45 static Tcl_LoadHandle odbcLoadHandle = NULL;
46 				/* Handle to the ODBC client library */
47 static Tcl_LoadHandle odbcInstLoadHandle = NULL;
48 				/* Handle to the ODBC installer library */
49 static SQLHENV hEnv = SQL_NULL_HENV;
50 				/* Handle to the global ODBC environment */
51 static size_t hEnvRefCount = 0;	/* Reference count on the global environment */
52 static size_t sizeofSQLWCHAR = sizeof(SQLWCHAR);
53 				/* Preset, will be autodetected later */
54 
55 /*
56  * Objects to create within the literal pool
57  */
58 
59 const char* const LiteralValues[] = {
60     "0",
61     "1",
62     "-encoding",
63     "-isolation",
64     "-readonly",
65     "-timeout",
66     "id",
67     "readuncommitted",
68     "readcommitted",
69     "repeatableread",
70     "serializable",
71     "::winfo",
72     NULL
73 };
74 enum LiteralIndex {
75     LIT_0,
76     LIT_1,
77     LIT_ENCODING,
78     LIT_ISOLATION,
79     LIT_READONLY,
80     LIT_TIMEOUT,
81     LIT_ID,
82     LIT_READUNCOMMITTED,
83     LIT_READCOMMITTED,
84     LIT_REPEATABLEREAD,
85     LIT_SERIALIZABLE,
86     LIT_WINFO,
87     LIT__END
88 };
89 
90 /*
91  * Structure that holds per-interpreter data for the ODBC package.
92  */
93 
94 typedef struct PerInterpData {
95     size_t refCount;		/* Reference count */
96     SQLHENV hEnv;		/* ODBC environment handle */
97     Tcl_Obj* literals[LIT__END];
98 				/* Literal pool */
99 } PerInterpData;
100 #define IncrPerInterpRefCount(x)  \
101     do {			  \
102 	++((x)->refCount);	  \
103     } while(0)
104 #define DecrPerInterpRefCount(x)		\
105     do {					\
106 	PerInterpData* _pidata = x;		\
107 	if (_pidata->refCount-- <= 1) {	\
108 	    DeletePerInterpData(_pidata);	\
109 	}					\
110     } while(0)
111 
112 /*
113  * Structure that carries the data for an ODBC connection
114  *
115  * 	The ConnectionData structure is refcounted to simplify the
116  *	destruction of statements associated with a connection.
117  *	When a connection is destroyed, the subordinate namespace that
118  *	contains its statements is taken down, destroying them. It's
119  *	not safe to take down the ConnectionData until nothing is
120  *	referring to it, which avoids taking down the hDBC until the
121  *	other objects that refer to it vanish.
122  */
123 
124 typedef struct ConnectionData {
125     size_t refCount;		/* Reference count. */
126     PerInterpData* pidata;	/* Per-interpreter data */
127     Tcl_Obj* connectionString;	/* Connection string actually used to
128 				 * connect to the database */
129     SQLHDBC hDBC;		/* Connection handle */
130     int flags;			/* Flags describing the state of the
131 				 * connection */
132 } ConnectionData;
133 
134 /*
135  * Flags for the state of an ODBC connection
136  */
137 
138 #define CONNECTION_FLAG_AUTOCOMMIT	(1<<0)
139 				/* Connection is in auto-commit mode */
140 #define CONNECTION_FLAG_XCN_ACTIVE	(1<<1)
141 				/* Connection has a transaction in progress.
142 				 * (Note that ODBC does not support nesting
143 				 * of transactions.) */
144 #define CONNECTION_FLAG_HAS_WVARCHAR	(1<<2)
145 				/* Connection supports WVARCHAR */
146 #define CONNECTION_FLAG_HAS_BIGINT	(1<<3)
147 				/* Connection supports WVARCHAR */
148 
149 #define IncrConnectionRefCount(x) \
150     do {			  \
151 	++((x)->refCount);	  \
152     } while(0)
153 #define DecrConnectionRefCount(x)		\
154     do {					\
155 	ConnectionData* conn = x;		\
156 	if (conn->refCount-- <= 1) {	\
157 	    DeleteConnection(conn);		\
158 	}					\
159     } while(0)
160 
161 /*
162  * Structure that carries the data for an ODBC prepared statement.
163  *
164  *	Just as with connections, statements need to defer taking down
165  *	their client data until other objects (i.e., result sets) that
166  * 	refer to them have had a chance to clean up. Hence, this
167  *	structure is reference counted as well.
168  */
169 
170 typedef struct StatementData {
171     size_t refCount;		/* Reference count */
172     Tcl_Object connectionObject;
173 				/* The connection object */
174     ConnectionData* cdata;	/* Data for the connection to which this
175 				 * statement pertains. */
176     Tcl_Obj* subVars;	        /* List of variables to be substituted, in the
177 				 * order in which they appear in the
178 				 * statement */
179     SQLHSTMT hStmt;		/* Handle to the ODBC statement */
180     SQLWCHAR* nativeSqlW;	/* SQL statement as wide chars */
181     size_t nativeSqlLen;		/* Length of the statement */
182     SQLWCHAR* nativeMatchPatternW;
183 				/* Match pattern for metadata queries */
184     size_t nativeMatchPatLen;	/* Length of the match pattern */
185     struct ParamData* params;	/* Pointer to an array of ParamData
186 				 * structures that describe the data types
187 				 * of substituted parameters. */
188     int typeNum;		/* Type number for a query of data types */
189     int flags;			/* Flags tracking the state of the
190 				 * StatementData */
191 } StatementData;
192 #define IncrStatementRefCount(x)		\
193     do {					\
194 	++((x)->refCount);			\
195     } while (0)
196 #define DecrStatementRefCount(x)		\
197     do {					\
198 	StatementData* stmt = (x);		\
199 	if (stmt->refCount-- <= 1) {		\
200 	    DeleteStatement(stmt);		\
201 	}					\
202     } while(0)
203 
204 /* Flags in StatementData */
205 
206 #define STATEMENT_FLAG_HSTMT_BUSY 0x1
207 				/* This flag is set if hStmt is in use, in
208 				 * which case the progam must clone it if
209 				 * another result set is needed */
210 /*
211  * Stored procedure calls and statements that return multiple
212  * results defeat the attempt to cache result set metadata, so
213  * the following flag is now obsolete.
214  */
215 #if 0
216 #define STATEMENT_FLAG_RESULTS_KNOWN 0x2
217 				/* This flag is set if the result set
218 				 * has already been described. The result
219 				 * set metadata for a given statement is
220 				 * queried only once, and retained for
221 				 * use in future invocations. */
222 #endif
223 #define STATEMENT_FLAG_TABLES 0x4
224 				/* This flag is set if the statement is
225 				 * asking for table metadata */
226 #define STATEMENT_FLAG_COLUMNS 0x8
227 				/* This flag is set if the statement is
228 				 * asking for column metadata */
229 #define STATEMENT_FLAG_TYPES 0x10
230 				/* This flag is set if the statement is
231 				 * asking for data type metadata */
232 #define STATEMENT_FLAG_PRIMARYKEYS 0x20
233 				/* This flag is set if the statement is
234 				 * asking for primary key metadata */
235 #define STATEMENT_FLAG_FOREIGNKEYS 0x40
236 				/* This flag is set if the statement is
237 				 * asking for primary key metadata */
238 
239 /*
240  * Structure describing the data types of substituted parameters in
241  * a SQL statement.
242  */
243 
244 typedef struct ParamData {
245     int flags;			/* Flags regarding the parameters - see below */
246     SQLSMALLINT dataType;	/* Data type */
247     SQLULEN precision;		/* Size of the expected data */
248     SQLSMALLINT scale;		/* Digits after decimal point of the
249 				 * expected data */
250     SQLSMALLINT nullable;	/* Flag == 1 if the parameter is nullable */
251 } ParamData;
252 
253 #define PARAM_KNOWN	1<<0	/* Something is known about the parameter */
254 #define PARAM_IN 	1<<1	/* Parameter is an input parameter */
255 #define PARAM_OUT 	1<<2	/* Parameter is an output parameter */
256 				/* (Both bits are set if parameter is
257 				 * an INOUT parameter) */
258 
259 /*
260  * Structure describing an ODBC result set.  The object that the Tcl
261  * API terms a "result set" actually has to be represented by an ODBC
262  * "statement", since an ODBC statement can have only one set of results
263  * at any given time.
264  */
265 
266 typedef struct ResultSetData {
267     size_t refCount;		/* Reference count */
268     StatementData* sdata;	/* Statement that generated this result set */
269     SQLHSTMT hStmt;		/* Handle to the ODBC statement object */
270     SQLCHAR** bindStrings;	/* Buffers for binding string parameters */
271     SQLLEN* bindStringLengths;	/* Lengths of the buffers */
272     SQLLEN rowCount;		/* Number of rows affected by the statement */
273     Tcl_Obj* resultColNames;	/* Names of the columns in the result set */
274     struct ParamData* results;	/* Pointer to the description of the
275 				 * result set columns */
276 } ResultSetData;
277 
278 #define IncrResultSetRefCount(x)		\
279     do {					\
280 	++((x)->refCount);			\
281     } while (0)
282 #define DecrResultSetRefCount(x)		\
283     do {					\
284 	ResultSetData* rs = (x);		\
285 	if (rs->refCount-- <= 1) {		\
286 	    DeleteResultSet(rs);		\
287 	}					\
288     } while(0)
289 
290 /*
291  * Structure for looking up a string that maps to an ODBC constant
292  */
293 
294 typedef struct OdbcConstant {
295     const char* name;		/* Constant name */
296     int value;		/* Constant value */
297 } OdbcConstant;
298 
299 /*
300  * Constants for the directions of parameter transmission
301  */
302 
303 static const OdbcConstant OdbcParamDirections[] = {
304     { "in",		PARAM_KNOWN | PARAM_IN, },
305     { "out",		PARAM_KNOWN | PARAM_OUT },
306     { "inout",		PARAM_KNOWN | PARAM_IN | PARAM_OUT },
307     { NULL,		0 }
308 };
309 
310 /*
311  * ODBC constants for the names of data types
312  */
313 
314 static const OdbcConstant OdbcTypeNames[] = {
315     { "bigint",		SQL_BIGINT },
316     { "binary",		SQL_BINARY },
317     { "bit",		SQL_BIT } ,
318     { "char",		SQL_CHAR } ,
319     { "date",		SQL_DATE } ,
320     { "decimal",	SQL_DECIMAL } ,
321     { "double",		SQL_DOUBLE } ,
322     { "float",		SQL_FLOAT } ,
323     { "integer",	SQL_INTEGER } ,
324     { "longvarbinary",	SQL_LONGVARBINARY } ,
325     { "longvarchar",	SQL_LONGVARCHAR } ,
326     { "numeric",	SQL_NUMERIC } ,
327     { "real",		SQL_REAL } ,
328     { "smallint",	SQL_SMALLINT } ,
329     { "time",		SQL_TIME } ,
330     { "timestamp",	SQL_TIMESTAMP } ,
331     { "tinyint",	SQL_TINYINT } ,
332     { "varbinary",	SQL_VARBINARY } ,
333     { "varchar",	SQL_VARCHAR } ,
334     { NULL,		-1 }
335 };
336 
337 static const OdbcConstant OdbcIsolationLevels[] = {
338     { "readuncommitted",	SQL_TXN_READ_UNCOMMITTED },
339     { "readcommitted",		SQL_TXN_READ_COMMITTED },
340     { "repeatableread",		SQL_TXN_REPEATABLE_READ },
341     { "serializable",		SQL_TXN_SERIALIZABLE },
342     { NULL,			0 }
343 };
344 
345 static const OdbcConstant OdbcErrorCodeNames[] = {
346     { "GENERAL_ERR",			ODBC_ERROR_GENERAL_ERR },
347     { "INVALID_BUFF_LEN",		ODBC_ERROR_INVALID_BUFF_LEN },
348     { "INVALID_HWND",			ODBC_ERROR_INVALID_HWND },
349     { "INVALID_STR",			ODBC_ERROR_INVALID_STR },
350     { "INVALID_REQUEST_TYPE",		ODBC_ERROR_INVALID_REQUEST_TYPE },
351     { "COMPONENT_NOT_FOUND",		ODBC_ERROR_COMPONENT_NOT_FOUND },
352     { "INVALID_NAME",			ODBC_ERROR_INVALID_NAME },
353     { "INVALID_KEYWORD_VALUE",		ODBC_ERROR_INVALID_KEYWORD_VALUE },
354     { "INVALID_DSN",			ODBC_ERROR_INVALID_DSN },
355     { "INVALID_INF",			ODBC_ERROR_INVALID_INF },
356     { "REQUEST_FAILED",			ODBC_ERROR_REQUEST_FAILED },
357     { "LOAD_LIB_FAILED",		ODBC_ERROR_LOAD_LIB_FAILED },
358     { "INVALID_PARAM_SEQUENCE",		ODBC_ERROR_INVALID_PARAM_SEQUENCE },
359     { "INVALID_LOG_FILE",		ODBC_ERROR_INVALID_LOG_FILE },
360     { "USER_CANCELED",			ODBC_ERROR_USER_CANCELED },
361     { "USAGE_UPDATE_FAILED",		ODBC_ERROR_USAGE_UPDATE_FAILED },
362     { "CREATE_DSN_FAILED",		ODBC_ERROR_CREATE_DSN_FAILED },
363     { "WRITING_SYSINFO_FAILED",		ODBC_ERROR_WRITING_SYSINFO_FAILED },
364     { "REMOVE_DSN_FAILED",		ODBC_ERROR_REMOVE_DSN_FAILED },
365     { "OUT_OF_MEM",			ODBC_ERROR_OUT_OF_MEM },
366     { "OUTPUT_STRING_TRUNCATED",	ODBC_ERROR_OUTPUT_STRING_TRUNCATED },
367     { NULL,				0 }
368 };
369 
370 /* Prototypes for static functions appearing in this file */
371 
372 static void DStringAppendWChars(Tcl_DString* ds, SQLWCHAR* ws, size_t len);
373 static SQLWCHAR* GetWCharStringFromObj(Tcl_Obj* obj, size_t* lengthPtr);
374 
375 static void TransferSQLError(Tcl_Interp* interp, SQLSMALLINT handleType,
376 			     SQLHANDLE handle, const char* info);
377 static int SQLStateIs(SQLSMALLINT handleType, SQLHANDLE handle,
378 		      const char* sqlstate);
379 static int LookupOdbcConstant(Tcl_Interp* interp, const OdbcConstant* table,
380 			      const char* kind, Tcl_Obj* name,
381 			      SQLSMALLINT* valuePtr);
382 static int LookupOdbcType(Tcl_Interp* interp, Tcl_Obj* name,
383 			  SQLSMALLINT* valuePtr);
384 static Tcl_Obj* TranslateOdbcIsolationLevel(SQLINTEGER level,
385 					    Tcl_Obj* literals[]);
386 static SQLHENV GetHEnv(Tcl_Interp* interp);
387 static void DismissHEnv(void);
388 static SQLHSTMT AllocAndPrepareStatement(Tcl_Interp* interp,
389 					  StatementData* sdata);
390 static int GetResultSetDescription(Tcl_Interp* interp, ResultSetData* rdata);
391 static int ConfigureConnection(Tcl_Interp* interp,
392 			       SQLHDBC hDBC,
393 			       PerInterpData* pidata,
394 			       int objc, Tcl_Obj *const objv[],
395 			       SQLUSMALLINT* connectFlagsPtr,
396 			       HWND* hParentWindowPtr);
397 static int ConnectionConstructor(ClientData clientData, Tcl_Interp* interp,
398 				 Tcl_ObjectContext context,
399 				 int objc, Tcl_Obj *const objv[]);
400 static int ConnectionBeginTransactionMethod(ClientData clientData,
401 					    Tcl_Interp* interp,
402 					    Tcl_ObjectContext context,
403 					    int objc, Tcl_Obj *const objv[]);
404 static int ConnectionConfigureMethod(ClientData clientData,
405 				     Tcl_Interp* interp,
406 				     Tcl_ObjectContext context,
407 				     int objc, Tcl_Obj *const objv[]);
408 static int ConnectionEndXcnMethod(ClientData clientData,
409 				  Tcl_Interp* interp,
410 				  Tcl_ObjectContext context,
411 				  int objc, Tcl_Obj *const objv[]);
412 static int ConnectionHasBigintMethod(ClientData clientData,
413 				     Tcl_Interp* interp,
414 				     Tcl_ObjectContext context,
415 				     int objc, Tcl_Obj *const objv[]);
416 static int ConnectionHasWvarcharMethod(ClientData clientData,
417 				       Tcl_Interp* interp,
418 				       Tcl_ObjectContext context,
419 				       int objc, Tcl_Obj *const objv[]);
420 static int SetAutocommitFlag(Tcl_Interp* interp, ConnectionData* cdata,
421 			     SQLINTEGER flag);
422 static void DeleteCmd(ClientData clientData);
423 static int CloneCmd(Tcl_Interp* interp,
424 		    ClientData oldMetadata, ClientData* newMetadata);
425 static void DeleteConnectionMetadata(ClientData clientData);
426 static void DeleteConnection(ConnectionData* cdata);
427 static int CloneConnection(Tcl_Interp* interp, ClientData oldClientData,
428 			   ClientData* newClientData);
429 static StatementData* NewStatement(ConnectionData* cdata,
430 				   Tcl_Object connectionObject);
431 static int StatementConstructor(ClientData clientData, Tcl_Interp* interp,
432 				Tcl_ObjectContext context,
433 				int objc, Tcl_Obj *const objv[]);
434 static int StatementConnectionMethod(ClientData clientData, Tcl_Interp* interp,
435 				     Tcl_ObjectContext context,
436 				     int objc, Tcl_Obj *const objv[]);
437 static int StatementParamListMethod(ClientData clientData, Tcl_Interp* interp,
438 				    Tcl_ObjectContext context,
439 				    int objc, Tcl_Obj *const objv[]);
440 static int StatementParamtypeMethod(ClientData clientData, Tcl_Interp* interp,
441 				    Tcl_ObjectContext context,
442 				    int objc, Tcl_Obj *const objv[]);
443 static int TablesStatementConstructor(ClientData clientData, Tcl_Interp* interp,
444 				      Tcl_ObjectContext context,
445 				      int objc, Tcl_Obj *const objv[]);
446 static int ColumnsStatementConstructor(ClientData clientData,
447 				       Tcl_Interp* interp,
448 				       Tcl_ObjectContext context,
449 				       int objc, Tcl_Obj *const objv[]);
450 static int PrimarykeysStatementConstructor(ClientData clientData,
451 					   Tcl_Interp* interp,
452 					   Tcl_ObjectContext context,
453 					   int objc, Tcl_Obj *const objv[]);
454 static int ForeignkeysStatementConstructor(ClientData clientData,
455 					   Tcl_Interp* interp,
456 					   Tcl_ObjectContext context,
457 					   int objc, Tcl_Obj *const objv[]);
458 static int TypesStatementConstructor(ClientData clientData, Tcl_Interp* interp,
459 				     Tcl_ObjectContext context,
460 				     int objc, Tcl_Obj *const objv[]);
461 static void DeleteStatementMetadata(ClientData clientData);
462 static void DeleteStatement(StatementData* sdata);
463 static int CloneStatement(Tcl_Interp* interp, ClientData oldClientData,
464 			  ClientData* newClientData);
465 static int ResultSetConstructor(ClientData clientData, Tcl_Interp* interp,
466 				Tcl_ObjectContext context,
467 				int objc, Tcl_Obj *const objv[]);
468 static int ResultSetColumnsMethod(ClientData clientData, Tcl_Interp* interp,
469 				  Tcl_ObjectContext context,
470 				  int objc, Tcl_Obj *const objv[]);
471 static int ResultSetNextrowMethod(ClientData clientData, Tcl_Interp* interp,
472 				  Tcl_ObjectContext context,
473 				  int objc, Tcl_Obj *const objv[]);
474 static int ResultSetNextresultsMethod(ClientData clientData, Tcl_Interp* interp,
475 				      Tcl_ObjectContext context,
476 				      int objc, Tcl_Obj *const objv[]);
477 static int GetCell(ResultSetData* rdata, Tcl_Interp* interp,
478 		   int columnIndex, Tcl_Obj** retval);
479 static int ResultSetRowcountMethod(ClientData clientData, Tcl_Interp* interp,
480 				   Tcl_ObjectContext context,
481 				   int objc, Tcl_Obj *const objv[]);
482 static void DeleteResultSetMetadata(ClientData clientData);
483 static void DeleteResultSet(ResultSetData* rdata);
484 static void DeleteResultSetDescription(ResultSetData* rdata);
485 static int CloneResultSet(Tcl_Interp* interp, ClientData oldClientData,
486 			  ClientData* newClientData);
487 static void FreeBoundParameters(ResultSetData* rdata);
488 static void DeletePerInterpData(PerInterpData* pidata);
489 static int DatasourcesObjCmd(ClientData clientData, Tcl_Interp* interp,
490 			      int objc, Tcl_Obj *const objv[]);
491 static int DriversObjCmd(ClientData clientData, Tcl_Interp* interp,
492 			 int objc, Tcl_Obj *const objv[]);
493 
494 /* Metadata type that holds connection data */
495 
496 static const Tcl_ObjectMetadataType connectionDataType = {
497     TCL_OO_METADATA_VERSION_CURRENT,
498 				/* version */
499     "ConnectionData",		/* name */
500     DeleteConnectionMetadata,	/* deleteProc */
501     CloneConnection		/* cloneProc - should cause an error
502 				 * 'cuz connections aren't clonable */
503 };
504 
505 /* Metadata type that holds statement data */
506 
507 static const Tcl_ObjectMetadataType statementDataType = {
508     TCL_OO_METADATA_VERSION_CURRENT,
509 				/* version */
510     "StatementData",		/* name */
511     DeleteStatementMetadata,	/* deleteProc */
512     CloneStatement		/* cloneProc - should cause an error
513 				 * 'cuz statements aren't clonable */
514 };
515 
516 /* Metadata type for result set data */
517 
518 static const Tcl_ObjectMetadataType resultSetDataType = {
519     TCL_OO_METADATA_VERSION_CURRENT,
520 				/* version */
521     "ResultSetData",		/* name */
522     DeleteResultSetMetadata,	/* deleteProc */
523     CloneResultSet		/* cloneProc - should cause an error
524 				 * 'cuz result sets aren't clonable */
525 };
526 
527 /* Method types of the connection methods that are implemented in C */
528 
529 static const Tcl_MethodType ConnectionConstructorType = {
530     TCL_OO_METHOD_VERSION_CURRENT,
531 				/* version */
532     "CONSTRUCTOR",		/* name */
533     ConnectionConstructor,	/* callProc */
534     DeleteCmd,			/* deleteProc */
535     CloneCmd			/* cloneProc */
536 };
537 static const Tcl_MethodType ConnectionBeginTransactionMethodType = {
538     TCL_OO_METHOD_VERSION_CURRENT,
539 				/* version */
540     "begintransaction",		/* name */
541     ConnectionBeginTransactionMethod,
542 				/* callProc */
543     NULL,			/* deleteProc */
544     CloneCmd			/* cloneProc */
545 };
546 static const Tcl_MethodType ConnectionConfigureMethodType = {
547     TCL_OO_METHOD_VERSION_CURRENT,
548 				/* version */
549     "configure",		/* name */
550     ConnectionConfigureMethod,	/* callProc */
551     NULL,			/* deleteProc */
552     CloneCmd			/* cloneProc */
553 };
554 static const Tcl_MethodType ConnectionEndXcnMethodType = {
555     TCL_OO_METHOD_VERSION_CURRENT,
556 				/* version */
557     "endtransaction",		/* name */
558     ConnectionEndXcnMethod,	/* callProc */
559     NULL,			/* deleteProc */
560     CloneCmd			/* cloneProc */
561 };
562 static const Tcl_MethodType ConnectionHasBigintMethodType = {
563     TCL_OO_METHOD_VERSION_CURRENT,
564 				/* version */
565     "HasBigint",		/* name */
566     ConnectionHasBigintMethod,
567 				/* callProc */
568     NULL,			/* deleteProc */
569     CloneCmd			/* cloneProc */
570 };
571 static const Tcl_MethodType ConnectionHasWvarcharMethodType = {
572     TCL_OO_METHOD_VERSION_CURRENT,
573 				/* version */
574     "HasWvarchar",		/* name */
575     ConnectionHasWvarcharMethod,
576 				/* callProc */
577     NULL,			/* deleteProc */
578     CloneCmd			/* cloneProc */
579 };
580 
581 /*
582  * Methods to create on the connection class. Note that 'init', 'commit' and
583  * 'rollback' are all special because they have non-NULL clientData.
584  */
585 
586 static const Tcl_MethodType* ConnectionMethods[] = {
587     &ConnectionBeginTransactionMethodType,
588     &ConnectionConfigureMethodType,
589     &ConnectionHasBigintMethodType,
590     &ConnectionHasWvarcharMethodType,
591     NULL
592 };
593 
594 /* Method types of the statement methods that are implemented in C */
595 
596 static const Tcl_MethodType StatementConstructorType = {
597     TCL_OO_METHOD_VERSION_CURRENT,
598 				/* version */
599     "CONSTRUCTOR",		/* name */
600     StatementConstructor,	/* callProc */
601     NULL,			/* deleteProc */
602     NULL			/* cloneProc */
603 };
604 static const Tcl_MethodType StatementConnectionMethodType = {
605     TCL_OO_METHOD_VERSION_CURRENT,
606 				/* version */
607     "connection",		/* name */
608     StatementConnectionMethod,	/* callProc */
609     NULL,			/* deleteProc */
610     NULL			/* cloneProc */
611 };
612 static const Tcl_MethodType StatementParamListMethodType = {
613     TCL_OO_METHOD_VERSION_CURRENT,
614 				/* version */
615     "ParamList",		/* name */
616     StatementParamListMethod,	/* callProc */
617     NULL,			/* deleteProc */
618     NULL			/* cloneProc */
619 };
620 static const Tcl_MethodType StatementParamtypeMethodType = {
621     TCL_OO_METHOD_VERSION_CURRENT,
622 				/* version */
623     "paramtype",		/* name */
624     StatementParamtypeMethod,	/* callProc */
625     NULL,			/* deleteProc */
626     NULL			/* cloneProc */
627 };
628 
629 /*
630  * Methods to create on the statement class.
631  */
632 
633 static const Tcl_MethodType* StatementMethods[] = {
634     &StatementConnectionMethodType,
635     &StatementParamListMethodType,
636     &StatementParamtypeMethodType,
637     NULL
638 };
639 
640 /*
641  * Constructor type for the class that implements the fake 'statement'
642  * used to query the names and attributes of database tables.
643  */
644 
645 static const Tcl_MethodType TablesStatementConstructorType = {
646     TCL_OO_METHOD_VERSION_CURRENT,
647 				/* version */
648     "CONSTRUCTOR",		/* name */
649     TablesStatementConstructor,	/* callProc */
650     NULL,			/* deleteProc */
651     NULL			/* cloneProc */
652 };
653 
654 /*
655  * Method types for the class that implements the fake 'statement'
656  * used to query the names and attributes of database columns.
657  */
658 
659 static const Tcl_MethodType ColumnsStatementConstructorType = {
660     TCL_OO_METHOD_VERSION_CURRENT,
661 				/* version */
662     "CONSTRUCTOR",		/* name */
663     ColumnsStatementConstructor,
664 				/* callProc */
665     NULL,			/* deleteProc */
666     NULL			/* cloneProc */
667 };
668 
669 /*
670  * Method types for the class that implements the fake 'statement'
671  * used to query the names and attributes of primary keys.
672  */
673 
674 static const Tcl_MethodType PrimarykeysStatementConstructorType = {
675     TCL_OO_METHOD_VERSION_CURRENT,
676 				/* version */
677     "CONSTRUCTOR",		/* name */
678     PrimarykeysStatementConstructor,
679 				/* callProc */
680     NULL,			/* deleteProc */
681     NULL			/* cloneProc */
682 };
683 
684 /*
685  * Method types for the class that implements the fake 'statement'
686  * used to query the names and attributes of foreign keys.
687  */
688 
689 static const Tcl_MethodType ForeignkeysStatementConstructorType = {
690     TCL_OO_METHOD_VERSION_CURRENT,
691 				/* version */
692     "CONSTRUCTOR",		/* name */
693     ForeignkeysStatementConstructor,
694 				/* callProc */
695     NULL,			/* deleteProc */
696     NULL			/* cloneProc */
697 };
698 
699 /*
700  * Constructor type for the class that implements the fake 'statement'
701  * used to query the names and attributes of database types.
702  */
703 
704 static const Tcl_MethodType TypesStatementConstructorType = {
705     TCL_OO_METHOD_VERSION_CURRENT,
706 				/* version */
707     "CONSTRUCTOR",		/* name */
708     &TypesStatementConstructor,	/* callProc */
709     NULL,			/* deleteProc */
710     NULL			/* cloneProc */
711 };
712 
713 /* Method types of the result set methods that are implemented in C */
714 
715 static const Tcl_MethodType ResultSetConstructorType = {
716     TCL_OO_METHOD_VERSION_CURRENT,
717 				/* version */
718     "CONSTRUCTOR",		/* name */
719     ResultSetConstructor,	/* callProc */
720     NULL,			/* deleteProc */
721     NULL			/* cloneProc */
722 };
723 static const Tcl_MethodType ResultSetColumnsMethodType = {
724     TCL_OO_METHOD_VERSION_CURRENT,
725 				/* version */    "columns",			/* name */
726     ResultSetColumnsMethod,	/* callProc */
727     NULL,			/* deleteProc */
728     NULL			/* cloneProc */
729 };
730 static const Tcl_MethodType ResultSetNextresultsMethodType = {
731     TCL_OO_METHOD_VERSION_CURRENT,
732 				/* version */
733     "nextresults",		/* name */
734     ResultSetNextresultsMethod,	/* callProc */
735     NULL,			/* deleteProc */
736     NULL			/* cloneProc */
737 };
738 static const Tcl_MethodType ResultSetNextrowMethodType = {
739     TCL_OO_METHOD_VERSION_CURRENT,
740 				/* version */
741     "nextrow",			/* name */
742     ResultSetNextrowMethod,	/* callProc */
743     NULL,			/* deleteProc */
744     NULL			/* cloneProc */
745 };
746 static const Tcl_MethodType ResultSetRowcountMethodType = {
747     TCL_OO_METHOD_VERSION_CURRENT,
748 				/* version */
749     "rowcount",			/* name */
750     ResultSetRowcountMethod,	/* callProc */
751     NULL,			/* deleteProc */
752     NULL			/* cloneProc */
753 };
754 
755 
756 static const Tcl_MethodType* ResultSetMethods[] = {
757     &ResultSetColumnsMethodType,
758     &ResultSetNextresultsMethodType,
759     &ResultSetRowcountMethodType,
760     NULL
761 };
762 
763 /*
764  *-----------------------------------------------------------------------------
765  *
766  * DStringAppendWChars --
767  *
768  *	Converts a wide-character string returned from ODBC into UTF-8
769  *	and appends the result to a Tcl_DString.
770  *
771  * Results:
772  *	None.
773  *
774  * Side effects:
775  *	Appends the given SQLWCHAR string to the given Tcl_DString, which
776  *	must have been previously initialized.
777  *
778  *-----------------------------------------------------------------------------
779  */
780 
781 static void
DStringAppendWChars(Tcl_DString * ds,SQLWCHAR * ws,size_t len)782 DStringAppendWChars(
783     Tcl_DString* ds,		/* Output string */
784     SQLWCHAR* ws,		/* Input string */
785     size_t len			/* Length of the input string in characters */
786 ) {
787     size_t i;
788     char buf[4] = "";
789 
790     if (sizeofSQLWCHAR == sizeof(unsigned short)) {
791 	unsigned short* ptr16 = (unsigned short*) ws;
792 
793 	for (i = 0; i < len; ++i) {
794 	    unsigned int ch;
795 	    size_t bytes;
796 
797 	    ch = ptr16[i];
798 	    bytes = Tcl_UniCharToUtf(ch, buf);
799 	    Tcl_DStringAppend(ds, buf, bytes);
800 	}
801     } else {
802 	unsigned int* ptr32 = (unsigned int*) ws;
803 
804 	for (i = 0; i < len; ++i) {
805 	    unsigned int ch;
806 	    size_t bytes;
807 
808 	    ch = ptr32[i];
809 	    if (ch > 0x10ffff) {
810 		ch = 0xfffd;
811 	    }
812 	    bytes = Tcl_UniCharToUtf(ch, buf);
813 	    Tcl_DStringAppend(ds, buf, bytes);
814 	}
815     }
816 }
817 
818 /*
819  *-----------------------------------------------------------------------------
820  *
821  * GetWCharStringFromObj --
822  *
823  *	Get a string of SQLWCHAR from the string value of a Tcl object.
824  *
825  * Results:
826  *	Returns a pointer to the string, which the caller is responsible
827  *	for freeing.
828  *
829  * Side effects:
830  *	Stores the length of the string in '*lengthPtr' if 'lengthPtr'
831  *	is not NULL
832  *
833  *-----------------------------------------------------------------------------
834  */
835 
836 static SQLWCHAR*
GetWCharStringFromObj(Tcl_Obj * obj,size_t * lengthPtr)837 GetWCharStringFromObj(
838     Tcl_Obj* obj,		/* Tcl object whose string rep is desired */
839     size_t* lengthPtr		/* Length of the string */
840 ) {
841     char* bytes = Tcl_GetString(obj);
842 				/* UTF-8 representation of the input string */
843     size_t len = obj->length;	/* Length of the input string in bytes */
844     char* end = bytes + len;	/* End of UTF-8 representation */
845     SQLWCHAR* retval;		/* Buffer to hold the converted string */
846     SQLWCHAR* wcPtr;
847     int shrink = 0;
848     Tcl_UniChar ch = 0;
849 
850     len = (len + 1) * sizeofSQLWCHAR;
851     if (sizeofSQLWCHAR < sizeof(Tcl_UniChar)) {
852 	len *= 2;	/* doubled space for surrogates */
853 	shrink = 1;
854     }
855     retval = wcPtr = (SQLWCHAR*) ckalloc(len);
856 
857     if (sizeofSQLWCHAR == sizeof(unsigned short)) {
858 	unsigned short *ptr16 = (unsigned short*) wcPtr;
859 
860 	while (bytes < end) {
861 	    unsigned int uch;
862 
863 	    if (Tcl_UtfCharComplete(bytes, end - bytes)) {
864 		bytes += Tcl_UtfToUniChar(bytes, &ch);
865 	    } else {
866 		ch = *bytes++ & 0x00ff;
867 	    }
868 	    uch = ch;
869 	    if ((sizeof(Tcl_UniChar) > 2) && (uch > 0xffff)) {
870 		*ptr16++ = (((uch - 0x10000) >> 10) & 0x3ff) | 0xd800;
871 		uch = ((uch - 0x10000) & 0x3ff) | 0xdc00;
872 	    }
873 	    if (uch > 0x7f) {
874 		shrink = 1;
875 	    }
876 	    *ptr16++ = uch;
877 	}
878 	*ptr16 = 0;
879 	len = ptr16 - (unsigned short*) retval;
880 	wcPtr = (SQLWCHAR*) ptr16;
881     } else {
882 	unsigned int *ptr32 = (unsigned int*) wcPtr;
883 
884 	while (bytes < end) {
885 	    unsigned int uch;
886 
887 	    if (Tcl_UtfCharComplete(bytes, end - bytes)) {
888 		bytes += Tcl_UtfToUniChar(bytes, &ch);
889 	    } else {
890 		ch = *bytes++ & 0x00ff;
891 	    }
892 	    uch = ch;
893 	    if ((sizeof(Tcl_UniChar) == 2) && ((uch & 0xfc00) == 0xd800)) {
894 		if (Tcl_UtfCharComplete(bytes, end - bytes)) {
895 		    len = Tcl_UtfToUniChar(bytes, &ch);
896 		    if ((ch & 0xfc00) == 0xdc00) {
897 			bytes += len;
898 			uch = (((uch & 0x3ff) << 10) | (ch & 0x3ff)) + 0x10000;
899 		    }
900 		}
901 	    }
902 	    if (uch > 0x7f) {
903 		shrink = 1;
904 	    }
905 	    *ptr32++ = uch;
906 	}
907 	*ptr32 = 0;
908 	len = ptr32 - (unsigned int*) retval;
909 	wcPtr = (SQLWCHAR*) ptr32;
910     }
911 
912     if (shrink) {
913 	/* Shrink buffer to fit result */
914 	wcPtr = (SQLWCHAR*) ckrealloc(retval, (len + 1) * sizeofSQLWCHAR);
915 	if (wcPtr != NULL) {
916 	    retval = wcPtr;
917 	}
918     }
919     if (lengthPtr != NULL) {
920 	*lengthPtr = len;
921     }
922     return retval;
923 }
924 
925 /*
926  *-----------------------------------------------------------------------------
927  *
928  * TransferSQLError --
929  *
930  *	Transfers an error message and associated error code from ODBC
931  *	to Tcl.
932  *
933  * Results:
934  *	None.
935  *
936  * Side effects:
937  *	The interpreter's result is set to a formatted error message, and
938  *	the error code is set to a three-element list: TDBC ODBC xxxxx,
939  *	where xxxxx is the SQL state code.
940  *
941  *-----------------------------------------------------------------------------
942  */
943 
944 static void
TransferSQLError(Tcl_Interp * interp,SQLSMALLINT handleType,SQLHANDLE handle,const char * info)945 TransferSQLError(
946     Tcl_Interp* interp,		/* Tcl interpreter */
947     SQLSMALLINT handleType,	/* Type of the handle for which the error
948 				 * has been reported. */
949     SQLHANDLE handle,		/* Handle that reported the error */
950     const char* info		/* Additional information to report */
951 ) {
952     SQLWCHAR state[6*2];	/* SQL state code */
953     SQLINTEGER nativeError;	/* Native error code */
954     SQLSMALLINT msgLen;		/* Length of the error message */
955     SQLWCHAR msg[(SQL_MAX_MESSAGE_LENGTH+1)*2];
956 				/* Buffer to hold the error message */
957     SQLSMALLINT i;		/* Loop index for going through diagnostics */
958     const char* sep = "";	/* Separator string for messages */
959     const char* sqlstate;	/* SQL state */
960     Tcl_Obj* resultObj;		/* Result string containing error message */
961     Tcl_Obj* codeObj;		/* Error code object */
962     Tcl_Obj* lineObj;		/* Object holding one diagnostic */
963     Tcl_DString bufferDS;	/* Buffer for transferring messages */
964     SQLRETURN rc;		/* SQL result */
965 
966     resultObj = Tcl_NewObj();
967     codeObj = Tcl_NewStringObj("TDBC", -1);
968 
969     /* Loop through the diagnostics */
970 
971     i = 1;
972     while (1) {
973 	msg[0] = msg[1] = 0;
974 	msgLen = 0;
975 	state[0] = state[1] = 0;
976 	rc = SQLGetDiagRecW(handleType, handle, i, state, &nativeError,
977 				msg, SQL_MAX_MESSAGE_LENGTH, &msgLen);
978 	if (!SQL_SUCCEEDED(rc) || rc == SQL_NO_DATA) {
979 	    break;
980 	}
981 
982 	/* Add the diagnostic to ::errorCode */
983 
984 	Tcl_DStringInit(&bufferDS);
985 	DStringAppendWChars(&bufferDS, state, 5);
986 	sqlstate = Tcl_DStringValue(&bufferDS);
987 	lineObj = Tcl_NewStringObj(sqlstate, Tcl_DStringLength(&bufferDS));
988 	if (i == 1) {
989 	    Tcl_Obj* stateObj = Tcl_NewStringObj(Tdbc_MapSqlState(sqlstate),
990 						 -1);
991 	    Tcl_ListObjAppendElement(NULL, codeObj, stateObj);
992 	}
993 	Tcl_DStringFree(&bufferDS);
994 	Tcl_ListObjAppendElement(NULL, codeObj, lineObj);
995 	if (i == 1) {
996 	    Tcl_ListObjAppendElement(NULL, codeObj,
997 				     Tcl_NewStringObj("ODBC", -1));
998 	}
999 	Tcl_ListObjAppendElement(NULL, codeObj, Tcl_NewWideIntObj(nativeError));
1000 
1001 	/* Add the error message to the return value */
1002 
1003 	Tcl_DStringInit(&bufferDS);
1004 	DStringAppendWChars(&bufferDS, msg, msgLen);
1005 	Tcl_AppendToObj(resultObj, sep, -1);
1006 	Tcl_AppendToObj(resultObj, Tcl_DStringValue(&bufferDS),
1007 			Tcl_DStringLength(&bufferDS));
1008 	Tcl_DStringFree(&bufferDS);
1009 	sep = "\n";
1010 	++i;
1011     }
1012     if (info != NULL) {
1013 	Tcl_AppendToObj(resultObj, "\n", -1);
1014 	Tcl_AppendToObj(resultObj, info, -1);
1015     }
1016 
1017     /* Stash the information into the interpreter */
1018 
1019     Tcl_SetObjResult(interp, resultObj);
1020     Tcl_SetObjErrorCode(interp, codeObj);
1021 }
1022 
1023 /*
1024  *-----------------------------------------------------------------------------
1025  *
1026  * SQLStateIs --
1027  *
1028  *	Determines whther SQLSTATE in the set of diagnostic records
1029  *	contains a particular state.
1030  *
1031  * Results:
1032  *	Returns 1 if the state matches, and 0 otherwise.
1033  *
1034  * This function is used primarily to look for the state "HYC00"
1035  * (Optional Function Not Implemented), but may also be used for
1036  * other states such as "HYT00" (Timeout Expired), "HY008"
1037  * (Operation Cancelled), "01004" (Data Truncated) and "01S02"
1038  * (Option Value Changed).
1039  *
1040  *-----------------------------------------------------------------------------
1041  */
1042 
1043 static int
SQLStateIs(SQLSMALLINT handleType,SQLHANDLE handle,const char * sqlstate)1044 SQLStateIs(
1045     SQLSMALLINT handleType, 	/* Type of handle reporting the state */
1046     SQLHANDLE handle,		/* Handle that reported the state */
1047     const char* sqlstate	/* State to look for */
1048 ) {
1049     SQLCHAR state[6];		/* SQL state code from the diagnostic record */
1050     SQLSMALLINT stateLen;	/* String length of the state code */
1051     SQLSMALLINT i;		/* Loop index */
1052     SQLRETURN rc;		/* SQL result */
1053 
1054     i = 1;
1055     while (1) {
1056 	state[0] = 0;
1057 	stateLen = 0,
1058 	rc = SQLGetDiagFieldA(handleType, handle, i, SQL_DIAG_SQLSTATE,
1059 				(SQLPOINTER) state, sizeof(state), &stateLen);
1060 	if (!SQL_SUCCEEDED(rc) || rc == SQL_NO_DATA) {
1061 	    break;
1062 	}
1063 	if (stateLen >= 0 && !strcmp(sqlstate, (const char*) state)) {
1064 	    return 1;
1065 	}
1066     }
1067     return 0;
1068 }
1069 
1070 /*
1071  *-----------------------------------------------------------------------------
1072  *
1073  * LookupOdbcConstant --
1074  *
1075  *	Looks up an ODBC enumerated constant in a table.
1076  *
1077  * Results:
1078  *	Returns a standard Tcl result, with an error message stored in
1079  *	the result of the provided Tcl_Interp if it is not NULL.
1080  *
1081  * Side effects:
1082  *	If successful, stores the enumerated value in '*valuePtr'
1083  *
1084  * Notes:
1085  *	The 'table' argument must be constant and statically allocated.
1086  *
1087  *-----------------------------------------------------------------------------
1088  */
1089 
1090 static int
LookupOdbcConstant(Tcl_Interp * interp,const OdbcConstant * table,const char * kind,Tcl_Obj * name,SQLSMALLINT * valuePtr)1091 LookupOdbcConstant(
1092     Tcl_Interp* interp,		/* Tcl interpreter */
1093     const OdbcConstant* table,	/* Table giving the enumerations */
1094     const char* kind,		/* String descibing the kind of enumerated
1095 				 * object being looked up */
1096     Tcl_Obj* name,		/* Name being looked up */
1097     SQLSMALLINT* valuePtr	/* Pointer to the returned value */
1098 ) {
1099     int index;
1100     if (Tcl_GetIndexFromObjStruct(interp, name, (void*)table,
1101 				  sizeof(OdbcConstant), kind, TCL_EXACT,
1102 				  &index) != TCL_OK) {
1103 	return TCL_ERROR;
1104     }
1105     *valuePtr = (SQLSMALLINT) table[index].value;
1106     return TCL_OK;
1107 }
1108 
LookupOdbcType(Tcl_Interp * interp,Tcl_Obj * name,SQLSMALLINT * valuePtr)1109 static inline int LookupOdbcType(
1110     Tcl_Interp* interp,
1111     Tcl_Obj* name,
1112     SQLSMALLINT* valuePtr
1113 ) {
1114     return LookupOdbcConstant(interp, OdbcTypeNames, "SQL data type",
1115 			      name, valuePtr);
1116 }
1117 
1118 /*
1119  *-----------------------------------------------------------------------------
1120  *
1121  * TranslateOdbcIsolationLevel --
1122  *
1123  *	Translates an ODBC isolation level into human-readable form.
1124  *
1125  * Results:
1126  *	Returns a Tcl_Obj with the human-readable level.
1127  *
1128  *-----------------------------------------------------------------------------
1129  */
1130 
1131 static Tcl_Obj*
TranslateOdbcIsolationLevel(SQLINTEGER level,Tcl_Obj * literals[])1132 TranslateOdbcIsolationLevel(
1133     SQLINTEGER level, 		/* Isolation level */
1134     Tcl_Obj* literals[]		/* Pointer to the literal pool */
1135 ) {
1136     if (level & SQL_TXN_SERIALIZABLE) {
1137 	return literals[LIT_SERIALIZABLE];
1138     }
1139     if (level & SQL_TXN_REPEATABLE_READ) {
1140 	return literals[LIT_REPEATABLEREAD];
1141     }
1142     if (level & SQL_TXN_READ_COMMITTED) {
1143 	return literals[LIT_READCOMMITTED];
1144     }
1145     return literals[LIT_READUNCOMMITTED];
1146 }
1147 
1148 /*
1149  *-----------------------------------------------------------------------------
1150  *
1151  * GetHEnv --
1152  *
1153  *	Retrieves the global environment handle for ODBC.
1154  *
1155  * Results:
1156  *	Returns the global environment handle. If the allocation of the
1157  *	global enviroment fails, returns SQL_NULL_ENV. If 'interp' is
1158  *	not NULL, stores an error message in the interpreter.
1159  *
1160  * Maintains a reference count so that the handle is closed when the
1161  * last use of ODBC in the process goes away.
1162  *
1163  *-----------------------------------------------------------------------------
1164  */
1165 
1166 static SQLHENV
GetHEnv(Tcl_Interp * interp)1167 GetHEnv(
1168     Tcl_Interp* interp		/* Interpreter for error reporting, or NULL */
1169 ) {
1170     RETCODE rc;			/* Return from ODBC calls */
1171     Tcl_MutexLock(&hEnvMutex);
1172     if (hEnvRefCount == 0) {
1173 	/*
1174 	 * This is the first reference to ODBC in this process.
1175 	 * Load the ODBC client library.
1176 	 */
1177 	if ((odbcLoadHandle = OdbcInitStubs(interp, &odbcInstLoadHandle))
1178 	    == NULL) {
1179 	    Tcl_MutexUnlock(&hEnvMutex);
1180 	    return SQL_NULL_HENV;
1181 	}
1182 	/*
1183 	 * Allocate the ODBC environment
1184 	 */
1185 	rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);
1186 	if (SQL_SUCCEEDED(rc)) {
1187 	    rc = SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION,
1188 			       (SQLPOINTER) SQL_OV_ODBC3, 0);
1189 	}
1190 	if (!SQL_SUCCEEDED(rc)) {
1191 	    /*
1192 	     * The call failed. Report the error.
1193 	     */
1194 	    if (hEnv != SQL_NULL_HENV) {
1195 		if (interp != NULL) {
1196 		    TransferSQLError(interp, SQL_HANDLE_ENV, hEnv,
1197 				     "(allocating environment handle)");
1198 		}
1199 		SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
1200 		hEnv = SQL_NULL_HENV;
1201 	    } else {
1202 		Tcl_SetObjResult(interp,
1203 				 Tcl_NewStringObj("Could not allocate the "
1204 						  "ODBC SQL environment.", -1));
1205 		Tcl_SetErrorCode(interp, "TDBC", "GENERAL_ERROR",
1206 				 "HY001", "ODBC", "-1", NULL);
1207 	    }
1208 	} else {
1209 	    /*
1210 	     * Detect real size of SQLWCHAR used by the driver manager.
1211 	     */
1212 	    SQLHDBC hDBC = SQL_NULL_HDBC;
1213 
1214 	    sizeofSQLWCHAR = sizeof(SQLWCHAR);		/* fallback */
1215 	    rc = SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDBC);
1216 	    if (SQL_SUCCEEDED(rc)) {
1217 		SQLSMALLINT infoLen;
1218 		int i;
1219 		char info[64];
1220 
1221 		rc = SQLGetInfoW(hDBC, SQL_ODBC_VER, (SQLPOINTER) info,
1222 				 sizeof(info), &infoLen);
1223 		if (SQL_SUCCEEDED(rc) && infoLen >= 8) {
1224 		    static const char BE32sig[] = {
1225 			'\0', '\0', '\0', '#', '\0', '\0', '\0', '#'
1226 		    };
1227 		    static const char LE32sig[] = {
1228 			'#', '\0', '\0', '\0', '#', '\0', '\0', '\0'
1229 		    };
1230 		    static const char BE16sig[] = {
1231 			'\0', '#', '\0', '#'
1232 		    };
1233 		    static const char LE16sig[] = {
1234 			'#', '\0', '#', '\0'
1235 		    };
1236 
1237 		    if ((size_t)infoLen > sizeof(info)) {
1238 			infoLen = sizeof(info);
1239 		    }
1240 		    for (i = 0; i < infoLen; i++) {
1241 			if (info[i] >= '0' && info[i] <= '9') {
1242 			    info[i] = '#';
1243 			}
1244 		    }
1245 		    if (memcmp(info, BE32sig, sizeof(BE32sig)) == 0 ||
1246 			memcmp(info, LE32sig, sizeof(LE32sig)) == 0) {
1247 			sizeofSQLWCHAR = 4;
1248 		    } else if (memcmp(info, BE16sig, sizeof(BE16sig)) == 0 ||
1249 			       memcmp(info, LE16sig, sizeof(LE16sig)) == 0) {
1250 			sizeofSQLWCHAR = 2;
1251 		    }
1252 		}
1253 		SQLFreeHandle(SQL_HANDLE_DBC, hDBC);
1254 	    }
1255 	}
1256     }
1257     /*
1258      * On subsequent calls, simply adjust the refcount
1259      */
1260     if (hEnv != SQL_NULL_HENV) {
1261 	++hEnvRefCount;
1262     }
1263     Tcl_MutexUnlock(&hEnvMutex);
1264     return hEnv;
1265 }
1266 
1267 /*
1268  *-----------------------------------------------------------------------------
1269  *
1270  * DismissHEnv --
1271  *
1272  *	Notifies that the SQLHENV returned from GetHEnv is no longer
1273  *	in use.
1274  *
1275  * Side effects:
1276  *	Decreases the refcount of the handle, and returns it if all
1277  *	extant refs have gone away.
1278  *
1279  *-----------------------------------------------------------------------------
1280  */
1281 
1282 static void
DismissHEnv(void)1283 DismissHEnv(void)
1284 {
1285     Tcl_MutexLock(&hEnvMutex);
1286     if (hEnvRefCount-- <= 1) {
1287 	SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
1288 	hEnv = SQL_NULL_HANDLE;
1289 	if (odbcInstLoadHandle != NULL) {
1290 	    Tcl_FSUnloadFile(NULL, odbcInstLoadHandle);
1291 	    odbcInstLoadHandle = NULL;
1292 	}
1293 	Tcl_FSUnloadFile(NULL, odbcLoadHandle);
1294 	odbcLoadHandle = NULL;
1295     }
1296     Tcl_MutexUnlock(&hEnvMutex);
1297 }
1298 
1299 /*
1300  *-----------------------------------------------------------------------------
1301  *
1302  * AllocAndPrepareStatement --
1303  *
1304  *	Allocates an ODBC statement handle, and prepares SQL code in it.
1305  *
1306  * Results:
1307  *	Returns the handle, or SQL_NULL_HSTMT if an error occurs.
1308  *
1309  *-----------------------------------------------------------------------------
1310  */
1311 
1312 static SQLHSTMT
AllocAndPrepareStatement(Tcl_Interp * interp,StatementData * sdata)1313 AllocAndPrepareStatement(
1314     Tcl_Interp* interp,		/* Tcl interpreter for error reporting */
1315     StatementData* sdata	/* Data describing the statement */
1316 ) {
1317     SQLRETURN rc;
1318     SQLHSTMT hStmt;
1319     ConnectionData* cdata = sdata->cdata;
1320     if (sdata->flags & (STATEMENT_FLAG_TABLES
1321 			| STATEMENT_FLAG_COLUMNS
1322 			| STATEMENT_FLAG_PRIMARYKEYS
1323 			| STATEMENT_FLAG_FOREIGNKEYS
1324 			| STATEMENT_FLAG_TYPES)) {
1325 	Tcl_SetObjResult(interp, Tcl_NewStringObj("cannot have multiple result "
1326 						  "sets in this context", -1));
1327 	return SQL_NULL_HSTMT;
1328     }
1329     rc = SQLAllocHandle(SQL_HANDLE_STMT, cdata->hDBC, &hStmt);
1330     if (!SQL_SUCCEEDED(rc)) {
1331 	TransferSQLError(interp, SQL_HANDLE_DBC, cdata->hDBC,
1332 			 "(allocating statement handle)");
1333 	return SQL_NULL_HSTMT;
1334     }
1335     rc = SQLPrepareW(hStmt, sdata->nativeSqlW, sdata->nativeSqlLen);
1336     if (!SQL_SUCCEEDED(rc)) {
1337 	TransferSQLError(interp, SQL_HANDLE_STMT, hStmt,
1338 			 "(preparing statement)");
1339 	SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
1340 	return SQL_NULL_HSTMT;
1341     }
1342     return hStmt;
1343 }
1344 
1345 /*
1346  *-----------------------------------------------------------------------------
1347  *
1348  * GetResultSetDescription --
1349  *
1350  *	Describes the result set of an ODBC statement
1351  *
1352  * Results:
1353  *	Returns a standard Tcl result and stores an error message in the
1354  *	interpreter result if a failure occurs.
1355  *
1356  * Side effects:
1357  *	Stores column names and type information in 'sdata' and
1358  *	updates the flags to indicate that the data are present.
1359  *
1360  *-----------------------------------------------------------------------------
1361  */
1362 
1363 static int
GetResultSetDescription(Tcl_Interp * interp,ResultSetData * rdata)1364 GetResultSetDescription(
1365     Tcl_Interp* interp,		/* Tcl interpreter */
1366     ResultSetData* rdata	/* Result set data object */
1367 ) {
1368     SQLHSTMT hStmt = rdata->hStmt;
1369 				/* Statement handle */
1370     SQLRETURN rc;		/* Return code from ODBC operations */
1371     Tcl_Obj* colNames;		/* List of the column names */
1372     SQLSMALLINT nColumns;	/* Number of result set columns */
1373     SQLWCHAR colNameBuf[41*2];	/* Buffer to hold the column name */
1374     SQLSMALLINT colNameLen = 40;
1375 				/* Length of the column name */
1376     SQLSMALLINT colNameAllocLen = 40;
1377 				/* Allocated length of the column name */
1378     SQLWCHAR* colNameW = colNameBuf;
1379 				/* Name of the current column */
1380     Tcl_DString colNameDS;	/* Name of the current column, translated */
1381     Tcl_Obj* colNameObj;	/* Name of the current column, packaged in
1382 				 * a Tcl_Obj */
1383     Tcl_HashTable nameHash;	/* Hash table to manage column name
1384 				 * uniqueness. */
1385     Tcl_HashEntry* nameEntry;	/* Hash table entry for the current name */
1386     int isNew;			/* Flag that column name is unique */
1387     int count;			/* Count to append to the name */
1388     char numbuf[16];		/* Buffer to hold the appended count */
1389     SQLSMALLINT i;
1390     int retry;
1391     int status = TCL_ERROR;
1392 
1393     /* Create a hash table to manage column name uniqueness */
1394 
1395     Tcl_InitHashTable(&nameHash, TCL_STRING_KEYS);
1396     nameEntry = Tcl_CreateHashEntry(&nameHash, "", &isNew);
1397     Tcl_SetHashValue(nameEntry, (ClientData) 0);
1398 
1399     /* Count the columns of the result set */
1400 
1401     rc = SQLNumResultCols(hStmt, &nColumns);
1402     if (!SQL_SUCCEEDED(rc)) {
1403 	TransferSQLError(interp, SQL_HANDLE_STMT, hStmt,
1404 			 "(getting number of result columns)");
1405 	return TCL_ERROR;
1406     }
1407     colNames = Tcl_NewObj();
1408     Tcl_IncrRefCount(colNames);
1409     if (nColumns != 0) {
1410 
1411 	/*
1412 	 * If there are columns in the result set, find their names and
1413 	 * data types.
1414 	 */
1415 
1416 	rdata->results = (ParamData*) ckalloc(nColumns * sizeof(ParamData));
1417 	for (i = 0; i < nColumns; ++i) {
1418 	    retry = 0;
1419 	    do {
1420 
1421 		/* Describe one column of the result set */
1422 
1423 		rc = SQLDescribeColW(hStmt, i + 1, colNameW,
1424 				     colNameAllocLen, &colNameLen,
1425 				     &(rdata->results[i].dataType),
1426 				     &(rdata->results[i].precision),
1427 				     &(rdata->results[i].scale),
1428 				     &(rdata->results[i].nullable));
1429 
1430 		/*
1431 		 * Reallocate the name buffer and retry if the buffer was
1432 		 * too small.
1433 		 */
1434 
1435 		if (colNameLen < colNameAllocLen) {
1436 		    retry = 0;
1437 		} else {
1438 		    colNameAllocLen = 2 * colNameLen + 1;
1439 		    if (colNameW != colNameBuf) {
1440 			ckfree((char*) colNameW);
1441 		    }
1442 		    colNameW = (SQLWCHAR*)
1443 			ckalloc(colNameAllocLen * sizeofSQLWCHAR);
1444 		    retry = 1;
1445 		}
1446 	    } while (retry);
1447 
1448 	    /* Bail out on an ODBC error */
1449 
1450 	    if (!SQL_SUCCEEDED(rc)) {
1451 		char info[80];
1452 		sprintf(info, "(describing result column #%d)", i+1);
1453 		TransferSQLError(interp, SQL_HANDLE_STMT, hStmt, info);
1454 		Tcl_DecrRefCount(colNames);
1455 		ckfree((char*)rdata->results);
1456 		goto cleanup;
1457 	    }
1458 
1459 	    /* Make a Tcl_Obj for the column name */
1460 
1461 	    Tcl_DStringInit(&colNameDS);
1462 	    DStringAppendWChars(&colNameDS, colNameW, colNameLen);
1463 	    colNameObj = Tcl_NewStringObj(Tcl_DStringValue(&colNameDS),
1464 					  Tcl_DStringLength(&colNameDS));
1465 
1466 	    /* Test if column name is unique */
1467 
1468 	    for (;;) {
1469 		nameEntry = Tcl_CreateHashEntry(&nameHash,
1470 						Tcl_GetString(colNameObj),
1471 						&isNew);
1472 		if (isNew) {
1473 		    Tcl_SetHashValue(nameEntry, (ClientData) 1);
1474 		    break;
1475 		}
1476 
1477 		/*
1478 		 * Non-unique name - append a # and the number of times
1479 		 * we've seen it before.
1480 		 */
1481 
1482 		count = PTR2INT(Tcl_GetHashValue(nameEntry));
1483 		++count;
1484 		Tcl_SetHashValue(nameEntry, INT2PTR(count));
1485 		sprintf(numbuf, "#%d", count);
1486 		Tcl_AppendToObj(colNameObj, numbuf, -1);
1487 	    }
1488 
1489 	    /* Add column name to the list of column names */
1490 
1491 	    Tcl_ListObjAppendElement(NULL, colNames, colNameObj);
1492 	    Tcl_DStringFree(&colNameDS);
1493 	}
1494     }
1495 
1496     /* Success: store the list of column names */
1497 
1498     if (rdata->resultColNames != NULL) {
1499 	Tcl_DecrRefCount(rdata->resultColNames);
1500     }
1501     rdata->resultColNames = colNames;
1502     status = TCL_OK;
1503 
1504     /* Clean up the column name buffer if we reallocated it. */
1505 
1506  cleanup:
1507     Tcl_DeleteHashTable(&nameHash);
1508     if (colNameW != colNameBuf) {
1509 	ckfree((char*) colNameW);
1510     }
1511     return status;
1512 
1513 }
1514 
1515 /*
1516  *-----------------------------------------------------------------------------
1517  *
1518  * ConfigureConnection --
1519  *
1520  *	Processes configuration options for an ODBC connection.
1521  *
1522  * Results:
1523  *	Returns a standard Tcl result; if TCL_ERROR is returned, the
1524  *	interpreter result is set to an error message.
1525  *
1526  * Side effects:
1527  *	Makes appropriate SQLSetConnectAttr calls to set the connection
1528  *	attributes.  If connectFlagsPtr or hMainWindowPtr are not NULL,
1529  *	also accepts a '-parent' option, sets *connectFlagsPtr to
1530  *	SQL_DRIVER_COMPLETE_REQUIED or SQL_DRIVER_NOPROMPT according
1531  *	to whether '-parent' is supplied, and *hParentWindowPtr to the
1532  *	HWND corresponding to the parent window.
1533  *
1534  * objc,objv are presumed to frame just the options, with positional
1535  * parameters already stripped. The following options are accepted:
1536  *
1537  * -parent PATH
1538  *	Specifies the path name of a parent window to use in a connection
1539  *	dialog.
1540  *
1541  *-----------------------------------------------------------------------------
1542  */
1543 
1544 static int
ConfigureConnection(Tcl_Interp * interp,SQLHDBC hDBC,PerInterpData * pidata,int objc,Tcl_Obj * const objv[],SQLUSMALLINT * connectFlagsPtr,HWND * hParentWindowPtr)1545 ConfigureConnection(
1546     Tcl_Interp* interp,		/* Tcl interpreter */
1547     SQLHDBC hDBC,		/* Handle to the connection */
1548     PerInterpData* pidata,	/* Package-global data */
1549     int objc,			/* Option count */
1550     Tcl_Obj *const objv[],	/* Option vector */
1551     SQLUSMALLINT* connectFlagsPtr,
1552 				/* Pointer to the driver connection options */
1553     HWND* hParentWindowPtr	/* Handle to the parent window for a
1554 				 * connection dialog */
1555 ) {
1556 
1557     /* Configuration options */
1558 
1559     static const char* options[] = {
1560 	"-encoding",
1561 	"-isolation",
1562 	"-parent",
1563 	"-readonly",
1564 	"-timeout",
1565 	NULL
1566     };
1567     enum optionType {
1568 	COPTION_ENCODING,
1569 	COPTION_ISOLATION,
1570 	COPTION_PARENT,
1571 	COPTION_READONLY,
1572 	COPTION_TIMEOUT
1573     };
1574 
1575     int indx;			/* Index of the current option */
1576     Tcl_Obj** literals = pidata->literals;
1577 				/* Literal pool */
1578     Tcl_Obj* retval;		/* Return value from this command */
1579     Tcl_Obj* command;		/* Tcl command executed to find parent win */
1580     Tcl_Encoding sysEncoding;	/* The system encoding */
1581     Tcl_Encoding newEncoding;	/* The requested encoding */
1582     const char* encName;	/* The name of the system encoding */
1583     int i;
1584     int j;
1585     SQLINTEGER mode;		/* Access mode of the database */
1586     SQLSMALLINT isol;		/* Isolation level */
1587     SQLINTEGER seconds;		/* Timeout value in seconds */
1588     SQLRETURN rc;		/* Return code from SQL operations */
1589     int w;			/* Window ID of the parent window */
1590     int status;			/* Return call from Tcl */
1591 
1592     if (connectFlagsPtr) {
1593 	*connectFlagsPtr = SQL_DRIVER_NOPROMPT;
1594     }
1595     if (hParentWindowPtr) {
1596 	*hParentWindowPtr = NULL;
1597     }
1598 
1599     if (objc == 0) {
1600 
1601 	/* return configuration options */
1602 
1603 	retval = Tcl_NewObj();
1604 
1605 	/* -encoding -- The ODBC encoding should be the system encoding */
1606 
1607 	sysEncoding = Tcl_GetEncoding(interp, NULL);
1608 	if (sysEncoding == NULL) {
1609 	    encName = "iso8859-1";
1610 	} else {
1611 	    encName = Tcl_GetEncodingName(sysEncoding);
1612 	}
1613 	Tcl_ListObjAppendElement(NULL, retval, literals[LIT_ENCODING]);
1614 	Tcl_ListObjAppendElement(NULL, retval, Tcl_NewStringObj(encName, -1));
1615 	if (sysEncoding != NULL) {
1616 	    Tcl_FreeEncoding(sysEncoding);
1617 	}
1618 
1619 	/* -isolation */
1620 
1621 	rc = SQLGetConnectAttr(hDBC, SQL_ATTR_TXN_ISOLATION,
1622 			       (SQLPOINTER) &mode, 0, NULL);
1623 	if (!SQL_SUCCEEDED(rc)) {
1624 	    TransferSQLError(interp, SQL_HANDLE_DBC, hDBC,
1625 			     "(getting isolation level of connection)");
1626 	    return TCL_ERROR;
1627 	}
1628 	Tcl_ListObjAppendElement(NULL, retval, literals[LIT_ISOLATION]);
1629 	Tcl_ListObjAppendElement(NULL, retval,
1630 				 TranslateOdbcIsolationLevel(mode, literals));
1631 
1632 	/* -readonly */
1633 
1634 	rc = SQLGetConnectAttr(hDBC, SQL_ATTR_ACCESS_MODE,
1635 			       (SQLPOINTER) &mode, 0, NULL);
1636 	if (!SQL_SUCCEEDED(rc)) {
1637 	    TransferSQLError(interp, SQL_HANDLE_DBC, hDBC,
1638 			     "(getting access mode of connection)");
1639 	    return TCL_ERROR;
1640 	}
1641 	Tcl_ListObjAppendElement(NULL, retval, literals[LIT_READONLY]);
1642 	Tcl_ListObjAppendElement(NULL, retval,
1643 				 Tcl_NewWideIntObj(mode == SQL_MODE_READ_ONLY));
1644 
1645 	/* -timeout */
1646 
1647 	rc = SQLGetConnectAttr(hDBC, SQL_ATTR_CONNECTION_TIMEOUT,
1648 			       (SQLPOINTER)&seconds, 0, NULL);
1649 	if (!SQL_SUCCEEDED(rc)) {
1650 	    if (SQLStateIs(SQL_HANDLE_DBC, hDBC, "HYC00")) {
1651 		seconds = 0;
1652 	    } else {
1653 		TransferSQLError(interp, SQL_HANDLE_DBC, hDBC,
1654 				 "(getting connection timeout value)");
1655 		return TCL_ERROR;
1656 	    }
1657 	}
1658 	Tcl_ListObjAppendElement(NULL, retval, literals[LIT_TIMEOUT]);
1659 	Tcl_ListObjAppendElement(NULL, retval,
1660 				 Tcl_NewWideIntObj(1000 * (Tcl_WideInt)seconds));
1661 
1662 	/* end of options */
1663 
1664 	Tcl_SetObjResult(interp, retval);
1665 	return TCL_OK;
1666 
1667     } else if (objc == 1) {
1668 
1669 	/* look up a single configuration option */
1670 
1671 	if (Tcl_GetIndexFromObjStruct(interp, objv[0], options, sizeof(char *),
1672 				"option", 0, &indx) != TCL_OK) {
1673 	    return TCL_ERROR;
1674 	}
1675 
1676 	switch (indx) {
1677 
1678 	case COPTION_ENCODING:
1679 	    sysEncoding = Tcl_GetEncoding(interp, NULL);
1680 	    if (sysEncoding == NULL) {
1681 		encName = "iso8859-1";
1682 	    } else {
1683 		encName = Tcl_GetEncodingName(sysEncoding);
1684 	    }
1685 	    Tcl_SetObjResult(interp, Tcl_NewStringObj(encName, -1));
1686 	    if (sysEncoding != NULL) {
1687 		Tcl_FreeEncoding(sysEncoding);
1688 	    }
1689 	    break;
1690 
1691 	case COPTION_ISOLATION:
1692 	    rc = SQLGetConnectAttr(hDBC, SQL_ATTR_TXN_ISOLATION,
1693 				   (SQLPOINTER) &mode, 0, NULL);
1694 	    if (!SQL_SUCCEEDED(rc)) {
1695 		TransferSQLError(interp, SQL_HANDLE_DBC, hDBC,
1696 				 "(getting isolation level of connection)");
1697 		return TCL_ERROR;
1698 	    }
1699 	    Tcl_SetObjResult(interp,
1700 			     TranslateOdbcIsolationLevel(mode, literals));
1701 	    break;
1702 
1703 	case COPTION_PARENT:
1704 	    Tcl_SetObjResult(interp,
1705 			     Tcl_NewStringObj("-parent option cannot "
1706 					      "be used after connection "
1707 					      "is established", -1));
1708 	    Tcl_SetErrorCode(interp, "TDBC", "GENERAL_ERROR", "HY010",
1709 			     "ODBC", "-1", NULL);
1710 	    return TCL_ERROR;
1711 
1712 	case COPTION_READONLY:
1713 	    rc = SQLGetConnectAttr(hDBC, SQL_ATTR_ACCESS_MODE,
1714 				   (SQLPOINTER) &mode, 0, NULL);
1715 	    if (!SQL_SUCCEEDED(rc)) {
1716 		TransferSQLError(interp, SQL_HANDLE_DBC, hDBC,
1717 				 "(getting access mode of connection)");
1718 		return TCL_ERROR;
1719 	    }
1720 	    Tcl_SetObjResult(interp,
1721 			     Tcl_NewWideIntObj(mode == SQL_MODE_READ_ONLY));
1722 	    break;
1723 
1724 	case COPTION_TIMEOUT:
1725 	    rc = SQLGetConnectAttr(hDBC, SQL_ATTR_CONNECTION_TIMEOUT,
1726 				   (SQLPOINTER)&seconds, 0, NULL);
1727 	    if (SQLStateIs(SQL_HANDLE_DBC, hDBC, "HYC00")) {
1728 		seconds = 0;
1729 	    } else {
1730 		if (!SQL_SUCCEEDED(rc)) {
1731 		    TransferSQLError(interp, SQL_HANDLE_DBC, hDBC,
1732 				     "(getting connection timeout value)");
1733 		    return TCL_ERROR;
1734 		}
1735 	    }
1736 	    Tcl_SetObjResult(interp, Tcl_NewWideIntObj(1000 * (Tcl_WideInt) seconds));
1737 	    break;
1738 
1739 	}
1740 
1741 	return TCL_OK;
1742 
1743     }
1744 
1745     /* set configuration options */
1746 
1747     for (i = 0; i < objc; i+=2) {
1748 
1749 	if (Tcl_GetIndexFromObjStruct(interp, objv[i], options, sizeof(char *),
1750 				"option", 0, &indx) != TCL_OK) {
1751 	    return TCL_ERROR;
1752 	}
1753 	switch (indx) {
1754 
1755 	case COPTION_ENCODING:
1756 	    /*
1757 	     * Encoding - report "not implemented" unless the encoding
1758 	     * would not be changed.
1759 	     */
1760 
1761 	    newEncoding = Tcl_GetEncoding(interp, Tcl_GetString(objv[i+1]));
1762 	    if (newEncoding == NULL) {
1763 		return TCL_ERROR;
1764 	    }
1765 	    sysEncoding = Tcl_GetEncoding(interp, NULL);
1766 	    Tcl_FreeEncoding(newEncoding);
1767 	    if (sysEncoding != NULL) {
1768 		Tcl_FreeEncoding(sysEncoding);
1769 	    }
1770 	    if (newEncoding != sysEncoding) {
1771 		Tcl_SetObjResult(interp,
1772 				 Tcl_NewStringObj("optional function "
1773 						  "not implemented", -1));
1774 		Tcl_SetErrorCode(interp, "TDBC", "GENERAL_ERROR", "HYC00",
1775 				 "ODBC", "-1", NULL);
1776 		return TCL_ERROR;
1777 	    }
1778 	    break;
1779 
1780 	case COPTION_ISOLATION:
1781 	    /* Transaction isolation level */
1782 
1783 	    if (LookupOdbcConstant(interp, OdbcIsolationLevels,
1784 				   "isolation level", objv[i+1],
1785 				   &isol) != TCL_OK) {
1786 		return TCL_ERROR;
1787 	    }
1788 	    mode = isol;
1789 	    rc = SQLSetConnectAttr(hDBC, SQL_ATTR_TXN_ISOLATION,
1790 				   (SQLPOINTER)(INT2PTR(mode)), 0);
1791 	    if (!SQL_SUCCEEDED(rc)) {
1792 		TransferSQLError(interp, SQL_HANDLE_DBC, hDBC,
1793 				 "(setting isolation level of connection)");
1794 		return TCL_ERROR;
1795 	    }
1796 	    break;
1797 
1798 	case COPTION_PARENT:
1799 	    /* Parent window for connection dialog */
1800 
1801 	    /* Make sure we haven't connected already */
1802 
1803 	    if (connectFlagsPtr == NULL || hParentWindowPtr == NULL) {
1804 		Tcl_SetObjResult(interp,
1805 				 Tcl_NewStringObj("-parent option cannot "
1806 						  "be used after connection "
1807 						  "is established", -1));
1808 		Tcl_SetErrorCode(interp, "TDBC", "GENERAL_ERROR", "HY010",
1809 				 "ODBC", "-1", NULL);
1810 		return TCL_ERROR;
1811 	    }
1812 
1813 	    /* Make sure that Tk is present. */
1814 
1815 	    if (Tcl_PkgPresentEx(interp, "Tk", "8.4", 0, NULL) == NULL) {
1816 		Tcl_ResetResult(interp);
1817 		Tcl_SetObjResult(interp,
1818 				 Tcl_NewStringObj("cannot use -parent "
1819 						  "option because Tk is not "
1820 						  "loaded", -1));
1821 		Tcl_SetErrorCode(interp, "TDBC", "GENERAL_ERROR", "HY000",
1822 				 "ODBC", "-1", NULL);
1823 		return TCL_ERROR;
1824 	    }
1825 
1826 	    /* Try to obtain the HWND of the parent window. */
1827 
1828 	    command = Tcl_NewObj();
1829 	    Tcl_ListObjAppendElement(NULL, command, literals[LIT_WINFO]);
1830 	    Tcl_ListObjAppendElement(NULL, command, literals[LIT_ID]);
1831 	    Tcl_ListObjAppendElement(NULL, command, objv[i+1]);
1832 	    Tcl_IncrRefCount(command);
1833 	    status = Tcl_EvalObjEx(interp, command, 0);
1834 	    if (status == TCL_OK) {
1835 		status = Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp),
1836 					   &w);
1837 	    }
1838 	    Tcl_DecrRefCount(command);
1839 	    if (status != TCL_OK) {
1840 		Tcl_AppendObjToErrorInfo(interp, Tcl_NewStringObj(
1841 				 "\n    (retrieving ID of parent window)", -1));
1842 		return status;
1843 	    }
1844 	    Tcl_ResetResult(interp);
1845 	    *hParentWindowPtr = INT2PTR(w);
1846 	    *connectFlagsPtr = SQL_DRIVER_COMPLETE_REQUIRED;
1847 	    break;
1848 
1849 	case COPTION_READONLY:
1850 	    /* read-only indicator */
1851 
1852 	    if (Tcl_GetBooleanFromObj(interp, objv[i+1], &j) != TCL_OK) {
1853 		return TCL_ERROR;
1854 	    }
1855 	    if (j) {
1856 		mode = SQL_MODE_READ_ONLY;
1857 	    } else {
1858 		mode = SQL_MODE_READ_WRITE;
1859 	    }
1860 	    rc = SQLSetConnectAttr(hDBC, SQL_ATTR_ACCESS_MODE,
1861 				   (SQLPOINTER)(INT2PTR(mode)), 0);
1862 	    if (!SQL_SUCCEEDED(rc)) {
1863 		TransferSQLError(interp, SQL_HANDLE_DBC, hDBC,
1864 				 "(setting access mode of connection)");
1865 		return TCL_ERROR;
1866 	    }
1867 	    break;
1868 
1869 	case COPTION_TIMEOUT:
1870 	    /* timeout value */
1871 
1872 	    if (Tcl_GetIntFromObj(interp, objv[i+1], &j) != TCL_OK) {
1873 		return TCL_ERROR;
1874 	    }
1875 	    seconds = (SQLINTEGER)((j + 999) / 1000);
1876 	    rc = SQLSetConnectAttr(hDBC, SQL_ATTR_CONNECTION_TIMEOUT,
1877 				   (SQLPOINTER)(INT2PTR(seconds)), 0);
1878 	    if (!SQL_SUCCEEDED(rc)) {
1879 		/*
1880 		 * A failure is OK if the SQL state is "Optional
1881 		 * Function Not Implemented" and we were trying to set
1882 		 * a zero timeout.
1883 		 */
1884 		if (!SQLStateIs(SQL_HANDLE_DBC, hDBC, "HYC00")
1885 		    || seconds != 0) {
1886 		    TransferSQLError(interp, SQL_HANDLE_DBC, hDBC,
1887 				     "(setting access mode of connection)");
1888 		    return TCL_ERROR;
1889 		}
1890 	    }
1891 	    break;
1892 	}
1893     }
1894     return TCL_OK;
1895 }
1896 
1897 /*
1898  *-----------------------------------------------------------------------------
1899  *
1900  * ConnectionConstructor --
1901  *
1902  *	Initializer for ::tdbc::odbc::connection, which represents a
1903  *	database connection.
1904  *
1905  * Parameters:
1906  *	Accepts a connection string followed by alternating keywords
1907  *	and values. Refer to the manual page for the acceptable options.
1908  *
1909  * Results:
1910  *	Returns a standard Tcl result.
1911  *
1912  *-----------------------------------------------------------------------------
1913  */
1914 
1915 static int
ConnectionConstructor(ClientData clientData,Tcl_Interp * interp,Tcl_ObjectContext objectContext,int objc,Tcl_Obj * const objv[])1916 ConnectionConstructor(
1917     ClientData clientData,	/* Environment handle */
1918     Tcl_Interp* interp,		/* Tcl interpreter */
1919     Tcl_ObjectContext objectContext, /* Object context */
1920     int objc,			/* Parameter count */
1921     Tcl_Obj *const objv[]	/* Parameter vector */
1922 ) {
1923     PerInterpData* pidata = (PerInterpData*) clientData;
1924 				/* Per-interp data for the ODBC package */
1925     Tcl_Object thisObject = Tcl_ObjectContextObject(objectContext);
1926 				/* The current connection object */
1927     int skip = Tcl_ObjectContextSkippedArgs(objectContext);
1928 				/* Number of leading args to skip */
1929     SQLHDBC hDBC = SQL_NULL_HDBC;
1930 				/* Handle to the database connection */
1931     SQLRETURN rc;		/* Return code from ODBC calls */
1932     HWND hParentWindow = NULL;	/* Windows handle of the main window */
1933     SQLWCHAR* connectionStringReq;
1934 				/* Connection string requested by the caller */
1935     size_t connectionStringReqLen;
1936 				/* Length of the requested connection string */
1937     SQLWCHAR connectionString[1025*2];
1938 				/* Connection string actually used */
1939     SQLSMALLINT connectionStringLen;
1940 				/* Length of the actual connection string */
1941     Tcl_DString connectionStringDS;
1942 				/* Connection string converted to UTF-8 */
1943     SQLUSMALLINT connectFlags = SQL_DRIVER_NOPROMPT;
1944 				/* Driver options */
1945     ConnectionData *cdata;	/* Client data for the connection object */
1946 
1947 
1948     /*
1949      * Check param count
1950      */
1951 
1952     if (objc < skip+1 || ((objc-skip) % 2) != 1) {
1953 	Tcl_WrongNumArgs(interp, skip, objv,
1954 			 "connection-string ?-option value?...");
1955 	return TCL_ERROR;
1956     }
1957 
1958     /*
1959      * Allocate a connection handle
1960      */
1961 
1962     rc = SQLAllocHandle(SQL_HANDLE_DBC, pidata->hEnv, (SQLHANDLE*) &hDBC);
1963     if (!SQL_SUCCEEDED(rc)) {
1964 	TransferSQLError(interp, SQL_HANDLE_ENV, pidata->hEnv,
1965 			 "(allocating connection handle)");
1966 	return TCL_ERROR;
1967     }
1968 
1969     /*
1970      * Grab configuration options.
1971      */
1972 
1973     if (objc > skip+1
1974 	&& ConfigureConnection(interp, hDBC, pidata, objc-skip-1, objv+skip+1,
1975 			       &connectFlags, &hParentWindow) != TCL_OK) {
1976 	SQLFreeHandle(SQL_HANDLE_DBC, hDBC);
1977 	return TCL_ERROR;
1978     }
1979 
1980     /*
1981      * Connect to the database (SQLConnect, SQLDriverConnect, SQLBrowseConnect)
1982      */
1983 
1984     connectionStringReq = GetWCharStringFromObj(objv[skip],
1985 						&connectionStringReqLen);
1986     rc = SQLDriverConnectW(hDBC, hParentWindow, connectionStringReq,
1987 			   (SQLSMALLINT) connectionStringReqLen,
1988 			   connectionString, 1024, &connectionStringLen,
1989 			   connectFlags);
1990     ckfree((char*) connectionStringReq);
1991     if (rc == SQL_NO_DATA) {
1992 	Tcl_SetObjResult(interp, Tcl_NewStringObj("operation cancelled", -1));
1993 	SQLFreeHandle(SQL_HANDLE_DBC, hDBC);
1994 	return TCL_ERROR;
1995     } else if (!SQL_SUCCEEDED(rc)) {
1996 	TransferSQLError(interp, SQL_HANDLE_DBC, hDBC,
1997 			 "(connecting to database)");
1998 	SQLFreeHandle(SQL_HANDLE_DBC, hDBC);
1999 	return TCL_ERROR;
2000     }
2001 
2002     /* Attach data about the connection to the object metadata */
2003 
2004     cdata = (ConnectionData*) ckalloc(sizeof(ConnectionData));
2005     cdata->refCount = 1;
2006     cdata->pidata = pidata;
2007     IncrPerInterpRefCount(pidata);
2008     cdata->hDBC = hDBC;
2009     Tcl_DStringInit(&connectionStringDS);
2010     DStringAppendWChars(&connectionStringDS,
2011 			connectionString, connectionStringLen);
2012     cdata->connectionString =
2013 	Tcl_NewStringObj(Tcl_DStringValue(&connectionStringDS),
2014 			 Tcl_DStringLength(&connectionStringDS));
2015     Tcl_IncrRefCount(cdata->connectionString);
2016     Tcl_DStringFree(&connectionStringDS);
2017     cdata->flags = CONNECTION_FLAG_AUTOCOMMIT;
2018     Tcl_ObjectSetMetadata(thisObject, &connectionDataType, (ClientData) cdata);
2019     return TCL_OK;
2020 }
2021 
2022 /*
2023  *-----------------------------------------------------------------------------
2024  *
2025  * ConnectionBeginTransactionMethod --
2026  *
2027  *	Method that requests that following operations on an OBBC connection
2028  *	be executed as an atomic transaction.
2029  *
2030  * Usage:
2031  *	$connection begintransaction
2032  *
2033  * Parameters:
2034  *	None.
2035  *
2036  * Results:
2037  *	Returns an empty result if successful, and throws an error otherwise.
2038  *
2039  *-----------------------------------------------------------------------------
2040 */
2041 
2042 static int
ConnectionBeginTransactionMethod(ClientData dummy,Tcl_Interp * interp,Tcl_ObjectContext objectContext,int objc,Tcl_Obj * const objv[])2043 ConnectionBeginTransactionMethod(
2044     ClientData dummy,	/* Unused */
2045     Tcl_Interp* interp,		/* Tcl interpreter */
2046     Tcl_ObjectContext objectContext, /* Object context */
2047     int objc,			/* Parameter count */
2048     Tcl_Obj *const objv[]	/* Parameter vector */
2049 ) {
2050     Tcl_Object thisObject = Tcl_ObjectContextObject(objectContext);
2051 				/* The current connection object */
2052     ConnectionData* cdata = (ConnectionData*)
2053 	Tcl_ObjectGetMetadata(thisObject, &connectionDataType);
2054     (void)dummy;
2055 
2056     /* Check parameters */
2057 
2058     if (objc != 2) {
2059 	Tcl_WrongNumArgs(interp, 2, objv, "");
2060 	return TCL_ERROR;
2061     }
2062 
2063     /* Reject attempts at nested transactions */
2064 
2065     if (cdata->flags & CONNECTION_FLAG_XCN_ACTIVE) {
2066 	Tcl_SetObjResult(interp, Tcl_NewStringObj("ODBC does not support "
2067 						  "nested transactions", -1));
2068 	Tcl_SetErrorCode(interp, "TDBC", "GENERAL_ERROR", "HYC00",
2069 			 "ODBC", "-1", NULL);
2070 	return TCL_ERROR;
2071     }
2072     cdata->flags |= CONNECTION_FLAG_XCN_ACTIVE;
2073 
2074     /* Turn off autocommit for the duration of the transaction */
2075 
2076     if (cdata->flags & CONNECTION_FLAG_AUTOCOMMIT) {
2077 	if (SetAutocommitFlag(interp, cdata, 0) != TCL_OK) {
2078 	    return TCL_ERROR;
2079 	}
2080 	cdata->flags &= ~CONNECTION_FLAG_AUTOCOMMIT;
2081     }
2082 
2083     return TCL_OK;
2084 }
2085 
2086 /*
2087  *-----------------------------------------------------------------------------
2088  *
2089  * ConnectionConfigureMethod --
2090  *
2091  *	Method that changes the configuration of an ODBC connection
2092  *
2093  * Usage:
2094  *	$connection configure
2095  * -or- $connection configure -option
2096  * -or- $connection configure ?-option value?...
2097  *
2098  * Parameters:
2099  *	Alternating options and values
2100  *
2101  * Results:
2102  *	With no arguments, returns a complete list of configuration options.
2103  *	With a single argument, returns the value of the given configuration
2104  *	option.  With two or more arguments, sets the given configuration
2105  *	options to the given values.
2106  *
2107  *-----------------------------------------------------------------------------
2108  */
2109 
2110 static int
ConnectionConfigureMethod(ClientData dummy,Tcl_Interp * interp,Tcl_ObjectContext objectContext,int objc,Tcl_Obj * const objv[])2111 ConnectionConfigureMethod(
2112     ClientData dummy,	/* Not used */
2113     Tcl_Interp* interp,		/* Tcl interpreter */
2114     Tcl_ObjectContext objectContext, /* Object context */
2115     int objc,			/* Parameter count */
2116     Tcl_Obj *const objv[]	/* Parameter vector */
2117 ) {
2118     Tcl_Object thisObject = Tcl_ObjectContextObject(objectContext);
2119 				/* The current connection object */
2120     ConnectionData* cdata = (ConnectionData*)
2121 	Tcl_ObjectGetMetadata(thisObject, &connectionDataType);
2122 				/* Instance data */
2123     (void)dummy;
2124 
2125     /* Check parameters */
2126 
2127     if (objc != 2 && objc != 3 && (objc%2) != 0) {
2128 	Tcl_WrongNumArgs(interp, 2, objv,
2129 			 "?" "?-option? value? ?-option value?...");
2130 	return TCL_ERROR;
2131     }
2132 
2133     return ConfigureConnection(interp, cdata->hDBC, cdata->pidata,
2134 			       objc-2, objv+2, NULL, NULL);
2135 }
2136 
2137 /*
2138  *-----------------------------------------------------------------------------
2139  *
2140  * ConnectionEndXcnMethod --
2141  *
2142  *	Method that requests that a pending transaction against a database
2143  * 	be committed or rolled back.
2144  *
2145  * Usage:
2146  *	$connection commit
2147  * -or- $connection rollback
2148  *
2149  * Parameters:
2150  *	None.
2151  *
2152  * Results:
2153  *	Returns an empty Tcl result if successful, and throws an error
2154  *	otherwise.
2155  *
2156  *-----------------------------------------------------------------------------
2157  */
2158 
2159 static int
ConnectionEndXcnMethod(ClientData clientData,Tcl_Interp * interp,Tcl_ObjectContext objectContext,int objc,Tcl_Obj * const objv[])2160 ConnectionEndXcnMethod(
2161     ClientData clientData,	/* Completion type */
2162     Tcl_Interp* interp,		/* Tcl interpreter */
2163     Tcl_ObjectContext objectContext, /* Object context */
2164     int objc,			/* Parameter count */
2165     Tcl_Obj *const objv[]	/* Parameter vector */
2166 ) {
2167     SQLSMALLINT completionType = (SQLSMALLINT) PTR2INT(clientData);
2168     Tcl_Object thisObject = Tcl_ObjectContextObject(objectContext);
2169 				/* The current connection object */
2170     ConnectionData* cdata = (ConnectionData*)
2171 	Tcl_ObjectGetMetadata(thisObject, &connectionDataType);
2172 				/* Instance data */
2173     SQLRETURN rc;		/* Result code from ODBC operations */
2174 
2175     /* Check parameters */
2176 
2177     if (objc != 2) {
2178 	Tcl_WrongNumArgs(interp, 2, objv, "");
2179 	return TCL_ERROR;
2180     }
2181 
2182     /* Reject the request if no transaction is in progress */
2183 
2184     if (!(cdata->flags & CONNECTION_FLAG_XCN_ACTIVE)) {
2185 	Tcl_SetObjResult(interp, Tcl_NewStringObj("no transaction is in "
2186 						  "progress", -1));
2187 	Tcl_SetErrorCode(interp, "TDBC", "GENERAL_ERROR", "HY010",
2188 			 "ODBC", "-1", NULL);
2189 	return TCL_ERROR;
2190     }
2191 
2192     /* End transaction, turn off "transaction in progress", and report status */
2193 
2194     rc = SQLEndTran(SQL_HANDLE_DBC, cdata->hDBC, completionType);
2195     cdata->flags &= ~ CONNECTION_FLAG_XCN_ACTIVE;
2196     if (!SQL_SUCCEEDED(rc)) {
2197 	TransferSQLError(interp, SQL_HANDLE_DBC, cdata->hDBC,
2198 			 "(ending the transaction)");
2199 	return TCL_ERROR;
2200     }
2201     return TCL_OK;
2202 }
2203 
2204 /*
2205  *-----------------------------------------------------------------------------
2206  *
2207  * ConnectionHasBigintMethod --
2208  *
2209  *	Private method that informs the code whether the connection supports
2210  *	64-bit ints.
2211  *
2212  * Usage:
2213  *	$connection HasBigint boolean
2214  *
2215  * Parameters:
2216  *	boolean - 1 if the connection supports BIGINT, 0 otherwise
2217  *
2218  * Results:
2219  *	Returns an empty Tcl result.
2220  *
2221  *-----------------------------------------------------------------------------
2222  */
2223 
2224 static int
ConnectionHasBigintMethod(ClientData dummy,Tcl_Interp * interp,Tcl_ObjectContext objectContext,int objc,Tcl_Obj * const objv[])2225 ConnectionHasBigintMethod(
2226     ClientData dummy,	/* Not used */
2227     Tcl_Interp* interp,		/* Tcl interpreter */
2228     Tcl_ObjectContext objectContext, /* Object context */
2229     int objc,			/* Parameter count */
2230     Tcl_Obj *const objv[]	/* Parameter vector */
2231 ) {
2232     Tcl_Object thisObject = Tcl_ObjectContextObject(objectContext);
2233 				/* The current connection object */
2234     ConnectionData* cdata = (ConnectionData*)
2235 	Tcl_ObjectGetMetadata(thisObject, &connectionDataType);
2236 				/* Instance data */
2237     int flag;
2238     (void)dummy;
2239 
2240     /* Check parameters */
2241 
2242     if (objc != 3) {
2243 	Tcl_WrongNumArgs(interp, 2, objv, "flag");
2244 	return TCL_ERROR;
2245     }
2246     if (Tcl_GetBooleanFromObj(interp, objv[2], &flag) != TCL_OK) {
2247 	return TCL_ERROR;
2248     }
2249     if (flag) {
2250 	cdata->flags |= CONNECTION_FLAG_HAS_BIGINT;
2251     } else {
2252 	cdata->flags &= ~CONNECTION_FLAG_HAS_BIGINT;
2253     }
2254     return TCL_OK;
2255 }
2256 
2257 /*
2258  *-----------------------------------------------------------------------------
2259  *
2260  * ConnectionHasWvarcharMethod --
2261  *
2262  *	Private method that informs the code whether the connection supports
2263  *	WVARCHAR strings.
2264  *
2265  * Usage:
2266  *	$connection HasWvarchar boolean
2267  *
2268  * Parameters:
2269  *	boolean - 1 if the connection supports WVARCHAR, 0 otherwise
2270  *
2271  * Results:
2272  *	Returns an empty Tcl result.
2273  *
2274  *-----------------------------------------------------------------------------
2275  */
2276 
2277 static int
ConnectionHasWvarcharMethod(ClientData dummy,Tcl_Interp * interp,Tcl_ObjectContext objectContext,int objc,Tcl_Obj * const objv[])2278 ConnectionHasWvarcharMethod(
2279     ClientData dummy,	/* Not used */
2280     Tcl_Interp* interp,		/* Tcl interpreter */
2281     Tcl_ObjectContext objectContext, /* Object context */
2282     int objc,			/* Parameter count */
2283     Tcl_Obj *const objv[]	/* Parameter vector */
2284 ) {
2285     Tcl_Object thisObject = Tcl_ObjectContextObject(objectContext);
2286 				/* The current connection object */
2287     ConnectionData* cdata = (ConnectionData*)
2288 	Tcl_ObjectGetMetadata(thisObject, &connectionDataType);
2289 				/* Instance data */
2290     int flag;
2291     (void)dummy;
2292 
2293     /* Check parameters */
2294 
2295     if (objc != 3) {
2296 	Tcl_WrongNumArgs(interp, 2, objv, "flag");
2297 	return TCL_ERROR;
2298     }
2299     if (Tcl_GetBooleanFromObj(interp, objv[2], &flag) != TCL_OK) {
2300 	return TCL_ERROR;
2301     }
2302     if (flag) {
2303 	cdata->flags |= CONNECTION_FLAG_HAS_WVARCHAR;
2304     } else {
2305 	cdata->flags &= ~CONNECTION_FLAG_HAS_WVARCHAR;
2306     }
2307     return TCL_OK;
2308 }
2309 
2310 /*
2311  *-----------------------------------------------------------------------------
2312  *
2313  * SetAutocommitFlag --
2314  *
2315  *	Turns autocommit on or off at the ODBC level.
2316  *
2317  * Results:
2318  *	Returns TCL_OK if successful, TCL_ERROR otherwise. Stores error message
2319  *	in the interpreter.
2320  *
2321  *-----------------------------------------------------------------------------
2322  */
2323 
2324 static int
SetAutocommitFlag(Tcl_Interp * interp,ConnectionData * cdata,SQLINTEGER flag)2325 SetAutocommitFlag(
2326     Tcl_Interp* interp,		/* Tcl interpreter */
2327     ConnectionData* cdata,	/* Instance data for the connection */
2328     SQLINTEGER flag		/* Auto-commit indicator */
2329 ) {
2330     SQLRETURN rc;
2331     rc = SQLSetConnectAttr(cdata->hDBC, SQL_ATTR_AUTOCOMMIT,
2332 			   (SQLPOINTER)(INT2PTR(flag)), 0);
2333     if (!SQL_SUCCEEDED(rc)) {
2334 	TransferSQLError(interp, SQL_HANDLE_DBC, cdata->hDBC,
2335 			 "(changing the 'autocommit' attribute)");
2336 	return TCL_ERROR;
2337     }
2338     return TCL_OK;
2339 }
2340 
2341 /*
2342  *-----------------------------------------------------------------------------
2343  *
2344  * DeleteCmd --
2345  *
2346  *	Callback executed when the initialization method of the connection
2347  *	class is deleted.
2348  *
2349  * Side effects:
2350  *	Dismisses the environment, which has the effect of shutting
2351  *	down ODBC when it is no longer required.
2352  *
2353  *-----------------------------------------------------------------------------
2354  */
2355 
2356 static void
DeleteCmd(ClientData clientData)2357 DeleteCmd (
2358     ClientData clientData	/* Environment handle */
2359 ) {
2360     PerInterpData* pidata = (PerInterpData*) clientData;
2361     DecrPerInterpRefCount(pidata);
2362 }
2363 
2364 /*
2365  *-----------------------------------------------------------------------------
2366  *
2367  * CloneCmd --
2368  *
2369  *	Callback executed when any of the ODBC client methods is cloned.
2370  *
2371  * Results:
2372  *	Returns TCL_OK to allow the method to be copied.
2373  *
2374  * Side effects:
2375  *	Obtains a fresh copy of the environment handle, to keep the
2376  *	refcounts accurate
2377  *
2378  *-----------------------------------------------------------------------------
2379  */
2380 
2381 static int
CloneCmd(Tcl_Interp * dummy,ClientData oldClientData,ClientData * newClientData)2382 CloneCmd(
2383     Tcl_Interp* dummy,		/* Tcl interpreter */
2384     ClientData oldClientData,	/* Environment handle to be discarded */
2385     ClientData* newClientData	/* New environment handle to be used */
2386 ) {
2387     (void)dummy;
2388     (void)oldClientData;
2389 
2390     *newClientData = GetHEnv(NULL);
2391     return TCL_OK;
2392 }
2393 
2394 /*
2395  *-----------------------------------------------------------------------------
2396  *
2397  * DeleteConnectionMetadata, DeleteConnection --
2398  *
2399  *	Cleans up when a database connection is deleted.
2400  *
2401  * Results:
2402  *	None.
2403  *
2404  * Side effects:
2405  *	Terminates the connection and frees all system resources associated
2406  *	with it.
2407  *
2408  *-----------------------------------------------------------------------------
2409  */
2410 
2411 static void
DeleteConnectionMetadata(ClientData clientData)2412 DeleteConnectionMetadata(
2413     ClientData clientData	/* Instance data for the connection */
2414 ) {
2415     DecrConnectionRefCount((ConnectionData*)clientData);
2416 }
2417 static void
DeleteConnection(ConnectionData * cdata)2418 DeleteConnection(
2419     ConnectionData* cdata	/* Instance data for the connection */
2420 ) {
2421     /*
2422      * All SQL errors are ignored here because we can't do anything
2423      * about them, anyway.
2424      */
2425 
2426     if (cdata->flags & CONNECTION_FLAG_XCN_ACTIVE) {
2427 	SQLEndTran(SQL_HANDLE_DBC, cdata->hDBC, SQL_ROLLBACK);
2428     }
2429     SQLDisconnect(cdata->hDBC);
2430     SQLFreeHandle(SQL_HANDLE_DBC, cdata->hDBC);
2431     Tcl_DecrRefCount(cdata->connectionString);
2432     DecrPerInterpRefCount(cdata->pidata);
2433     ckfree((char*) cdata);
2434 }
2435 
2436 /*
2437  *-----------------------------------------------------------------------------
2438  *
2439  * CloneConnection --
2440  *
2441  *	Attempts to clone an ODBC connection's metadata.
2442  *
2443  * Results:
2444  *	Returns the new metadata
2445  *
2446  * At present, we don't attempt to clone connections - it's not obvious
2447  * that such an action would ever even make sense.  Instead, we return NULL
2448  * to indicate that the metadata should not be cloned. (Note that this
2449  * action isn't right, either. What *is* right is to indicate that the object
2450  * is not clonable, but the API gives us no way to do that.
2451  *
2452  *-----------------------------------------------------------------------------
2453  */
2454 
2455 static int
CloneConnection(Tcl_Interp * interp,ClientData metadata,ClientData * newMetaData)2456 CloneConnection(
2457     Tcl_Interp* interp,		/* Tcl interpreter for error reporting */
2458     ClientData metadata,	/* Metadata to be cloned */
2459     ClientData* newMetaData	/* Where to put the cloned metadata */
2460 ) {
2461     (void)metadata;
2462     (void)newMetaData;
2463 
2464     Tcl_SetObjResult(interp,
2465 		     Tcl_NewStringObj("ODBC connections are not clonable", -1));
2466     return TCL_ERROR;
2467 }
2468 
2469 /*
2470  *-----------------------------------------------------------------------------
2471  *
2472  * NewStatement --
2473  *
2474  *	Creates an empty object to hold statement data.
2475  *
2476  * Results:
2477  *	Returns a pointer to the newly-created object.
2478  *
2479  *-----------------------------------------------------------------------------
2480  */
2481 
2482 static StatementData*
NewStatement(ConnectionData * cdata,Tcl_Object connectionObject)2483 NewStatement(
2484     ConnectionData* cdata,	/* Instance data for the connection */
2485     Tcl_Object connectionObject	/* Object handle wrapping the instance */
2486 ) {
2487     StatementData* sdata = (StatementData*) ckalloc(sizeof(StatementData));
2488     sdata->refCount = 1;
2489     sdata->cdata = cdata;
2490     sdata->connectionObject = connectionObject;
2491     IncrConnectionRefCount(cdata);
2492     sdata->subVars = Tcl_NewObj();
2493     Tcl_IncrRefCount(sdata->subVars);
2494     sdata->hStmt = SQL_NULL_HANDLE;
2495     sdata->nativeSqlW = NULL;
2496     sdata->nativeSqlLen = 0;
2497     sdata->nativeMatchPatternW = NULL;
2498     sdata->nativeMatchPatLen = 0;
2499     sdata->params = NULL;
2500     sdata->flags = 0;
2501     sdata->typeNum = SQL_ALL_TYPES;
2502     return sdata;
2503 }
2504 
2505 /*
2506  *-----------------------------------------------------------------------------
2507  *
2508  * StatementConstructor --
2509  *
2510  *	C-level initialization for the object representing an ODBC prepared
2511  *	statement.
2512  *
2513  * Parameters:
2514  *	Accepts a 4-element 'objv': statement new $connection $statementText,
2515  *	where $connection is the ODBC connection object, and $statementText
2516  *	is the text of the statement to prepare.
2517  *
2518  * Results:
2519  *	Returns a standard Tcl result
2520  *
2521  * Side effects:
2522  *	Prepares the statement, and stores it (plus a reference to the
2523  *	connection) in instance metadata.
2524  *
2525  *-----------------------------------------------------------------------------
2526  */
2527 
2528 static int
StatementConstructor(ClientData dummy,Tcl_Interp * interp,Tcl_ObjectContext context,int objc,Tcl_Obj * const objv[])2529 StatementConstructor(
2530     ClientData dummy,	/* Not used */
2531     Tcl_Interp* interp,		/* Tcl interpreter */
2532     Tcl_ObjectContext context,	/* Object context  */
2533     int objc, 			/* Parameter count */
2534     Tcl_Obj *const objv[]	/* Parameter vector */
2535 ) {
2536     Tcl_Object thisObject = Tcl_ObjectContextObject(context);
2537 				/* The current statement object */
2538     int skip = Tcl_ObjectContextSkippedArgs(context);
2539 				/* The number of args to skip */
2540     Tcl_Object connectionObject;
2541 				/* The database connection as a Tcl_Object */
2542     ConnectionData* cdata;	/* The connection object's data */
2543     StatementData* sdata;	/* The statement's object data */
2544     Tcl_Obj* tokens = NULL;	/* The tokens of the statement to be prepared */
2545     int tokenc;			/* Length of the 'tokens' list */
2546     Tcl_Obj** tokenv;		/* Exploded tokens from the list */
2547     Tcl_Obj* nativeSql;		/* SQL statement mapped to ODBC form */
2548     char* tokenStr;		/* Token string */
2549     size_t tokenLen;		/* Length of a token */
2550     RETCODE rc;			/* Return code from ODBC */
2551     SQLSMALLINT nParams;	/* Number of parameters in the ODBC statement */
2552     int i, j;
2553     (void)dummy;
2554 
2555     /* Find the connection object, and get its data. */
2556 
2557     if (objc != skip+2) {
2558 	Tcl_WrongNumArgs(interp, skip, objv, "connection statementText");
2559 	return TCL_ERROR;
2560     }
2561 
2562     connectionObject = Tcl_GetObjectFromObj(interp, objv[skip]);
2563     if (connectionObject == NULL) {
2564 	return TCL_ERROR;
2565     }
2566     cdata = (ConnectionData*) Tcl_ObjectGetMetadata(connectionObject,
2567 						    &connectionDataType);
2568     if (cdata == NULL) {
2569 	Tcl_AppendResult(interp, Tcl_GetString(objv[skip]),
2570 			 " does not refer to an ODBC connection", NULL);
2571 	return TCL_ERROR;
2572     }
2573 
2574     /*
2575      * Allocate an object to hold data about this statement
2576      */
2577 
2578     sdata = NewStatement(cdata, connectionObject);
2579 
2580     /* Tokenize the statement */
2581 
2582     tokens = Tdbc_TokenizeSql(interp, Tcl_GetString(objv[skip+1]));
2583     if (tokens == NULL) {
2584 	goto freeSData;
2585     }
2586     Tcl_IncrRefCount(tokens);
2587 
2588     /*
2589      * Rewrite the tokenized statement to ODBC syntax. Reject the
2590      * statement if it is actually multiple statements.
2591      */
2592 
2593     if (Tcl_ListObjGetElements(interp, tokens, &tokenc, &tokenv) != TCL_OK) {
2594 	Tcl_DecrRefCount(tokens);
2595 	goto freeSData;
2596     }
2597     nativeSql = Tcl_NewObj();
2598     Tcl_IncrRefCount(nativeSql);
2599     for (i = 0; i < tokenc; ++i) {
2600 	tokenStr = Tcl_GetString(tokenv[i]);
2601 	tokenLen = tokenv[i]->length;
2602 
2603 	switch (tokenStr[0]) {
2604 	case '$':
2605 	case ':':
2606 	    Tcl_AppendToObj(nativeSql, "?", 1);
2607 	    Tcl_ListObjAppendElement(NULL, sdata->subVars,
2608 				     Tcl_NewStringObj(tokenStr+1, tokenLen-1));
2609 	    break;
2610 
2611 	default:
2612 	    Tcl_AppendToObj(nativeSql, tokenStr, tokenLen);
2613 	    break;
2614 
2615 	}
2616     }
2617     Tcl_DecrRefCount(tokens);
2618 
2619     /* Allocate an ODBC statement handle, and prepare the statement */
2620 
2621     sdata->nativeSqlW = GetWCharStringFromObj(nativeSql, &sdata->nativeSqlLen);
2622     Tcl_DecrRefCount(nativeSql);
2623     sdata->hStmt = AllocAndPrepareStatement(interp, sdata);
2624     if (sdata->hStmt == SQL_NULL_HANDLE) {
2625 	goto freeSData;
2626     }
2627 
2628     /* Determine the number of parameters that ODBC thinks are in the
2629      * statement. */
2630 
2631     Tcl_ListObjLength(NULL, sdata->subVars, &i);
2632     sdata->params = (ParamData*) ckalloc(i * sizeof(ParamData));
2633     for (j = 0; j < i; ++j) {
2634 	/*
2635 	 * Supply defaults in case the driver doesn't support introspection
2636 	 * of parameters.  Since not all drivers do WVARCHAR, VARCHAR
2637 	 * appears to be the only workable option.
2638 	 */
2639 	if (cdata->flags & CONNECTION_FLAG_HAS_WVARCHAR) {
2640 	    sdata->params[j].dataType = SQL_WVARCHAR;
2641 	} else {
2642 	    sdata->params[j].dataType = SQL_VARCHAR;
2643 	}
2644 	sdata->params[j].precision = 255;
2645 	sdata->params[j].scale = 0;
2646 	sdata->params[j].nullable = SQL_NULLABLE_UNKNOWN;
2647 	sdata->params[j].flags = PARAM_IN;
2648     }
2649     rc = SQLNumParams(sdata->hStmt, &nParams);
2650     if (SQL_SUCCEEDED(rc)) {
2651 	if (nParams != i) {
2652 	    Tcl_SetObjResult(interp,
2653 			     Tcl_NewStringObj("The SQL statement appears "
2654 					      "to contain parameters in "
2655 					      "native SQL syntax. You need "
2656 					      "to replace them with ones "
2657 					      "in ':variableName' form.", -1));
2658 	    Tcl_SetErrorCode(interp, "TDBC", "DYNAMIC_SQL_ERROR", "07002",
2659 			     "ODBC", "-1", NULL);
2660 	    goto freeSData;
2661 	}
2662 
2663 	/*
2664 	 * Try to describe the parameters for the sake of consistency
2665 	 * in conversion and efficiency in execution.
2666 	 */
2667 
2668 	for (i = 0; i < nParams; ++i) {
2669 	    rc = SQLDescribeParam(sdata->hStmt, i+1,
2670 				  &(sdata->params[i].dataType),
2671 				  &(sdata->params[i].precision),
2672 				  &(sdata->params[i].scale),
2673 				  &(sdata->params[i].nullable));
2674 	    if (SQL_SUCCEEDED(rc)) {
2675 		/*
2676 		 * FIXME: SQLDescribeParam doesn't actually describe
2677 		 *        the direction of parameter transmission for
2678 		 *	  stored procedure calls.  It appears simply
2679 		 *	  to be the caller's responsibility to know
2680 		 *	  these things.  If anyone has an idea how to
2681 		 *	  determine this, please send a patch! (Remember
2682 		 *	  that the patch has to work with DB2 and
2683 		 *	  unixodbc as well as Microsoft.)
2684 		 */
2685 		sdata->params[i].flags = PARAM_IN | PARAM_KNOWN;
2686 	    } else {
2687 		/*
2688 		 * Supply defaults in case the driver doesn't support
2689 		 * introspection of parameters. Again, not all drivers can
2690 		 * handle WVARCHAR, so VARCHAR seems to be the only
2691 		 * workable option.
2692 		 */
2693 		if (cdata->flags & CONNECTION_FLAG_HAS_WVARCHAR) {
2694 		    sdata->params[i].dataType = SQL_WVARCHAR;
2695 		} else {
2696 		    sdata->params[i].dataType = SQL_VARCHAR;
2697 		}
2698 		sdata->params[i].precision = 255;
2699 		sdata->params[i].scale = 0;
2700 		sdata->params[i].nullable = SQL_NULLABLE_UNKNOWN;
2701 		sdata->params[i].flags = PARAM_IN;
2702 	    }
2703 	}
2704     }
2705 
2706     /* Attach the current statement data as metadata to the current object */
2707 
2708     Tcl_ObjectSetMetadata(thisObject, &statementDataType, (ClientData) sdata);
2709     return TCL_OK;
2710 
2711     /* On error, unwind all the resource allocations */
2712 
2713  freeSData:
2714     DecrStatementRefCount(sdata);
2715     return TCL_ERROR;
2716 }
2717 
2718 /*
2719  *-----------------------------------------------------------------------------
2720  *
2721  * StatementConnectionMethod --
2722  *
2723  *	Retrieves the handle of the connection to which a statement belongs
2724  *
2725  * Parameters:
2726  *	None.
2727  *
2728  * Results:
2729  *	Returns the connection handle
2730  *
2731  *-----------------------------------------------------------------------------
2732  */
2733 
2734 static int
StatementConnectionMethod(ClientData dummy,Tcl_Interp * interp,Tcl_ObjectContext context,int objc,Tcl_Obj * const objv[])2735 StatementConnectionMethod(
2736     ClientData dummy,	/* Not used */
2737     Tcl_Interp* interp,		/* Tcl interpreter */
2738     Tcl_ObjectContext context,	/* Object context  */
2739     int objc, 			/* Parameter count */
2740     Tcl_Obj *const objv[]	/* Parameter vector */
2741 ) {
2742     Tcl_Object thisObject = Tcl_ObjectContextObject(context);
2743 				/* The current statement object */
2744     StatementData* sdata;	/* The current statement */
2745     Tcl_Object connectionObject;
2746 				/* The object representing the connection */
2747     Tcl_Command connectionCommand;
2748 				/* The command representing the object */
2749     Tcl_Obj* retval = Tcl_NewObj();
2750 				/* The command name */
2751     (void)dummy;
2752     (void)objc;
2753     (void)objv;
2754 
2755     sdata = (StatementData*) Tcl_ObjectGetMetadata(thisObject,
2756 						   &statementDataType);
2757     connectionObject = sdata->connectionObject;
2758     connectionCommand = Tcl_GetObjectCommand(connectionObject);
2759     Tcl_GetCommandFullName(interp, connectionCommand, retval);
2760     Tcl_SetObjResult(interp, retval);
2761     return TCL_OK;
2762 }
2763 
2764 /*
2765  *-----------------------------------------------------------------------------
2766  *
2767  * StatementParamListMethod --
2768  *
2769  *	Lists the parameters to an ODBC statement
2770  *
2771  * Usage:
2772  *	$statement ParamList
2773  *
2774  * Results:
2775  *	Returns a standard Tcl result that is a list of alternating
2776  *	elements: paramName flags typeNumber precision scale nullable
2777  *
2778  *-----------------------------------------------------------------------------
2779  */
2780 
2781 static int
StatementParamListMethod(ClientData dummy,Tcl_Interp * interp,Tcl_ObjectContext context,int objc,Tcl_Obj * const objv[])2782 StatementParamListMethod(
2783     ClientData dummy,	/* Not used */
2784     Tcl_Interp* interp,		/* Tcl interpreter */
2785     Tcl_ObjectContext context,	/* Object context  */
2786     int objc, 			/* Parameter count */
2787     Tcl_Obj *const objv[]	/* Parameter vector */
2788 ) {
2789     Tcl_Object thisObject = Tcl_ObjectContextObject(context);
2790 				/* The current statement object */
2791     StatementData* sdata;	/* The current statement */
2792     Tcl_Obj **paramNames;	/* Parameter list to the current statement */
2793     int nParams;		/* Parameter count for the current statement */
2794     int i;			/* Current parameter index */
2795     Tcl_Obj* retval;		/* Return value from this command */
2796     (void)dummy;
2797     (void)objc;
2798     (void)objv;
2799 
2800     sdata = (StatementData*) Tcl_ObjectGetMetadata(thisObject,
2801 						   &statementDataType);
2802 
2803     retval = Tcl_NewObj();
2804     if (sdata->subVars != NULL) {
2805 	Tcl_ListObjGetElements(NULL, sdata->subVars, &nParams, &paramNames);
2806 	for (i = 0; i < nParams; ++i) {
2807 	    ParamData* pd = sdata->params + i;
2808 	    Tcl_ListObjAppendElement(NULL, retval, paramNames[i]);
2809 	    Tcl_ListObjAppendElement(NULL, retval, Tcl_NewWideIntObj(pd->flags));
2810 	    Tcl_ListObjAppendElement(NULL, retval, Tcl_NewWideIntObj(pd->dataType));
2811 	    Tcl_ListObjAppendElement(NULL, retval,
2812 				     Tcl_NewWideIntObj(pd->precision));
2813 	    Tcl_ListObjAppendElement(NULL, retval, Tcl_NewWideIntObj(pd->scale));
2814 	    Tcl_ListObjAppendElement(NULL, retval, Tcl_NewWideIntObj(pd->nullable));
2815 	}
2816     }
2817     Tcl_SetObjResult(interp, retval);
2818     return TCL_OK;
2819 }
2820 
2821 /*
2822  *-----------------------------------------------------------------------------
2823  *
2824  * StatementParamtypeMethod --
2825  *
2826  *	Defines a parameter type in an ODBC statement.
2827  *
2828  * Usage:
2829  *	$statement paramtype paramName ?direction? type ?precision ?scale??
2830  *
2831  * Results:
2832  *	Returns a standard Tcl result.
2833  *
2834  * Side effects:
2835  *	Updates the description of the given parameter.
2836  *
2837  *-----------------------------------------------------------------------------
2838  */
2839 
2840 static int
StatementParamtypeMethod(ClientData dummy,Tcl_Interp * interp,Tcl_ObjectContext context,int objc,Tcl_Obj * const objv[])2841 StatementParamtypeMethod(
2842     ClientData dummy,	/* Not used */
2843     Tcl_Interp* interp,		/* Tcl interpreter */
2844     Tcl_ObjectContext context,	/* Object context  */
2845     int objc, 			/* Parameter count */
2846     Tcl_Obj *const objv[]	/* Parameter vector */
2847 ) {
2848     Tcl_Object thisObject = Tcl_ObjectContextObject(context);
2849 				/* The current statement object */
2850     StatementData* sdata;	/* The current statement */
2851     int matchCount = 0;		/* The number of variables in the given
2852 				 * statement that match the given one */
2853     int nParams;		/* Number of parameters to the statement */
2854     const char* paramName;	/* Name of the parameter being set */
2855     Tcl_Obj* targetNameObj;	/* Name of the ith parameter in the statement */
2856     const char* targetName;	/* Name of a candidate parameter in the
2857 				 * statement */
2858     Tcl_Obj* errorObj;		/* Error message */
2859     int i;
2860     SQLSMALLINT dir = PARAM_IN | PARAM_KNOWN;
2861 				/* Direction of parameter transmssion */
2862     SQLSMALLINT odbcType = SQL_VARCHAR;
2863 				/* ODBC type of the parameter */
2864     int precision = 0;		/* Length of the parameter */
2865     int scale = 0;		/* Precision of the parameter */
2866     (void)dummy;
2867 
2868     sdata = (StatementData*) Tcl_ObjectGetMetadata(thisObject,
2869 						   &statementDataType);
2870     /* Check parameters */
2871 
2872     if (objc < 4) {
2873 	goto wrongNumArgs;
2874     }
2875     i = 3;
2876     if (LookupOdbcConstant(NULL, OdbcParamDirections, "direction", objv[i],
2877 			   &dir) == TCL_OK) {
2878 	++i;
2879     }
2880     if (i >= objc) {
2881 	goto wrongNumArgs;
2882     }
2883     if (LookupOdbcType(interp, objv[i], &odbcType) == TCL_OK) {
2884 	++i;
2885     } else {
2886 	return TCL_ERROR;
2887     }
2888     if (i < objc) {
2889 	if (Tcl_GetIntFromObj(interp, objv[i], &precision) == TCL_OK) {
2890 	    ++i;
2891 	} else {
2892 	    return TCL_ERROR;
2893 	}
2894     }
2895     if (i < objc) {
2896 	if (Tcl_GetIntFromObj(interp, objv[i], &scale) == TCL_OK) {
2897 	    ++i;
2898 	} else {
2899 	    return TCL_ERROR;
2900 	}
2901     }
2902     if (i != objc) {
2903 	goto wrongNumArgs;
2904     }
2905 
2906     Tcl_ListObjLength(NULL, sdata->subVars, &nParams);
2907     paramName = Tcl_GetString(objv[2]);
2908     for (i = 0; i < nParams; ++i) {
2909 	Tcl_ListObjIndex(NULL, sdata->subVars, i, &targetNameObj);
2910 	targetName = Tcl_GetString(targetNameObj);
2911 	if (!strcmp(paramName, targetName)) {
2912 	    ++matchCount;
2913 
2914 	    sdata->params[i].flags = dir;
2915 	    sdata->params[i].dataType = odbcType;
2916 	    sdata->params[i].precision = precision;
2917 	    sdata->params[i].scale = scale;
2918 	    sdata->params[i].nullable = 1;
2919 				/* TODO - Update TIP so that user
2920 				 * can specify nullable? */
2921 	}
2922     }
2923     if (matchCount == 0) {
2924 	errorObj = Tcl_NewStringObj("unknown parameter \"", -1);
2925 	Tcl_AppendToObj(errorObj, paramName, -1);
2926 	Tcl_AppendToObj(errorObj, "\": must be ", -1);
2927 	for (i = 0; i < nParams; ++i) {
2928 	    Tcl_ListObjIndex(NULL, sdata->subVars, i, &targetNameObj);
2929 	    Tcl_AppendObjToObj(errorObj, targetNameObj);
2930 	    if (i < nParams-2) {
2931 		Tcl_AppendToObj(errorObj, ", ", -1);
2932 	    } else if (i == nParams-2) {
2933 		Tcl_AppendToObj(errorObj, " or ", -1);
2934 	    }
2935 	}
2936 	Tcl_SetObjResult(interp, errorObj);
2937 	return TCL_ERROR;
2938     }
2939 
2940     return TCL_OK;
2941  wrongNumArgs:
2942     Tcl_WrongNumArgs(interp, 2, objv,
2943 		     "name ?direction? type ?precision ?scale??");
2944     return TCL_ERROR;
2945 }
2946 
2947 /*
2948  *-----------------------------------------------------------------------------
2949  *
2950  * TablesStatementConstructor --
2951  *
2952  *	C-level initialization for the object representing an ODBC query
2953  *	for table metadata
2954  *
2955  * Parameters:
2956  *	Accepts a 4-element 'objv': $object init $connection $pattern,
2957  *	where $connection is the ODBC connection object, and $pattern
2958  *	is the pattern to match table names.
2959  *
2960  * Results:
2961  *	Returns a standard Tcl result
2962  *
2963  * Side effects:
2964  *	Prepares the statement, and stores it (plus a reference to the
2965  *	connection) in instance metadata.
2966  *
2967  *-----------------------------------------------------------------------------
2968  */
2969 
2970 static int
TablesStatementConstructor(ClientData dummy,Tcl_Interp * interp,Tcl_ObjectContext context,int objc,Tcl_Obj * const objv[])2971 TablesStatementConstructor(
2972     ClientData dummy,	/* Not used */
2973     Tcl_Interp* interp,		/* Tcl interpreter */
2974     Tcl_ObjectContext context,	/* Object context  */
2975     int objc, 			/* Parameter count */
2976     Tcl_Obj *const objv[]	/* Parameter vector */
2977 ) {
2978 
2979     Tcl_Object thisObject = Tcl_ObjectContextObject(context);
2980 				/* The current statement object */
2981     int skip = Tcl_ObjectContextSkippedArgs(context);
2982 				/* The number of initial args to this call */
2983     Tcl_Object connectionObject;
2984 				/* The database connection as a Tcl_Object */
2985     ConnectionData* cdata;	/* The connection object's data */
2986     StatementData* sdata;	/* The statement's object data */
2987     RETCODE rc;			/* Return code from ODBC */
2988     (void)dummy;
2989 
2990     /* Find the connection object, and get its data. */
2991 
2992     if (objc != skip+2) {
2993 	Tcl_WrongNumArgs(interp, skip, objv, "connection pattern");
2994 	return TCL_ERROR;
2995     }
2996 
2997     connectionObject = Tcl_GetObjectFromObj(interp, objv[skip]);
2998     if (connectionObject == NULL) {
2999 	return TCL_ERROR;
3000     }
3001     cdata = (ConnectionData*) Tcl_ObjectGetMetadata(connectionObject,
3002 						    &connectionDataType);
3003     if (cdata == NULL) {
3004 	Tcl_AppendResult(interp, Tcl_GetString(objv[skip]),
3005 			 " does not refer to an ODBC connection", NULL);
3006 	return TCL_ERROR;
3007     }
3008 
3009     /*
3010      * Allocate an object to hold data about this statement
3011      */
3012 
3013     sdata = NewStatement(cdata, connectionObject);
3014 
3015     /* Allocate an ODBC statement handle */
3016 
3017     rc = SQLAllocHandle(SQL_HANDLE_STMT, cdata->hDBC, &(sdata->hStmt));
3018     if (!SQL_SUCCEEDED(rc)) {
3019 	TransferSQLError(interp, SQL_HANDLE_DBC, cdata->hDBC,
3020 			 "(allocating statement handle)");
3021 	goto freeSData;
3022     }
3023 
3024     /*
3025      * Stash the table pattern in the statement data, and set a flag that
3026      * that's what we have there.
3027      */
3028 
3029     sdata->nativeSqlW = GetWCharStringFromObj(objv[skip+1],
3030 					      &(sdata->nativeSqlLen));
3031     sdata->nativeMatchPatternW = NULL;
3032     sdata->flags |= STATEMENT_FLAG_TABLES;
3033 
3034     /* Attach the current statement data as metadata to the current object */
3035 
3036     Tcl_ObjectSetMetadata(thisObject, &statementDataType, (ClientData) sdata);
3037     return TCL_OK;
3038 
3039     /* On error, unwind all the resource allocations */
3040 
3041  freeSData:
3042     DecrStatementRefCount(sdata);
3043     return TCL_ERROR;
3044 }
3045 
3046 /*
3047  *-----------------------------------------------------------------------------
3048  *
3049  * ColumnsStatementConstructor --
3050  *
3051  *	C-level initialization for the object representing an ODBC query
3052  *	for column metadata
3053  *
3054  * Parameters:
3055  *	Accepts a 5-element 'objv':
3056  *		columnsStatement new $connection $table $pattern,
3057  *	where $connection is the ODBC connection object, $table is the
3058  *	name of the table being queried, and $pattern is the pattern to
3059  *	match column names.
3060  *
3061  * Results:
3062  *	Returns a standard Tcl result
3063  *
3064  * Side effects:
3065  *	Prepares the statement, and stores it (plus a reference to the
3066  *	connection) in instance metadata.
3067  *
3068  *-----------------------------------------------------------------------------
3069  */
3070 
3071 static int
ColumnsStatementConstructor(ClientData dummy,Tcl_Interp * interp,Tcl_ObjectContext context,int objc,Tcl_Obj * const objv[])3072 ColumnsStatementConstructor(
3073     ClientData dummy,	/* Not used */
3074     Tcl_Interp* interp,		/* Tcl interpreter */
3075     Tcl_ObjectContext context,	/* Object context  */
3076     int objc, 			/* Parameter count */
3077     Tcl_Obj *const objv[]	/* Parameter vector */
3078 ) {
3079 
3080     Tcl_Object thisObject = Tcl_ObjectContextObject(context);
3081 				/* The current statement object */
3082     int skip = Tcl_ObjectContextSkippedArgs(context);
3083 				/* The number of parameters to skip */
3084     Tcl_Object connectionObject;
3085 				/* The database connection as a Tcl_Object */
3086     ConnectionData* cdata;	/* The connection object's data */
3087     StatementData* sdata;	/* The statement's object data */
3088     RETCODE rc;			/* Return code from ODBC */
3089     (void)dummy;
3090 
3091 
3092     /* Check param count */
3093 
3094     if (objc != skip+3) {
3095 	Tcl_WrongNumArgs(interp, skip, objv, "connection tableName pattern");
3096 	return TCL_ERROR;
3097     }
3098 
3099     /* Find the connection object, and get its data. */
3100 
3101     connectionObject = Tcl_GetObjectFromObj(interp, objv[skip]);
3102     if (connectionObject == NULL) {
3103 	return TCL_ERROR;
3104     }
3105     cdata = (ConnectionData*) Tcl_ObjectGetMetadata(connectionObject,
3106 						    &connectionDataType);
3107     if (cdata == NULL) {
3108 	Tcl_AppendResult(interp, Tcl_GetString(objv[skip]),
3109 			 " does not refer to an ODBC connection", NULL);
3110 	return TCL_ERROR;
3111     }
3112 
3113     /*
3114      * Allocate an object to hold data about this statement
3115      */
3116 
3117     sdata = NewStatement(cdata, connectionObject);
3118 
3119     /* Allocate an ODBC statement handle */
3120 
3121     rc = SQLAllocHandle(SQL_HANDLE_STMT, cdata->hDBC, &(sdata->hStmt));
3122     if (!SQL_SUCCEEDED(rc)) {
3123 	TransferSQLError(interp, SQL_HANDLE_DBC, cdata->hDBC,
3124 			 "(allocating statement handle)");
3125 	goto freeSData;
3126     }
3127 
3128     /*
3129      * Stash the table name and match pattern in the statement data,
3130      * and set a flag that that's what we have there.
3131      */
3132 
3133     sdata->nativeSqlW = GetWCharStringFromObj(objv[skip+1],
3134 					      &(sdata->nativeSqlLen));
3135     sdata->nativeMatchPatternW =
3136 	GetWCharStringFromObj(objv[skip+2], &(sdata->nativeMatchPatLen));
3137     sdata->flags = STATEMENT_FLAG_COLUMNS;
3138 
3139     /* Attach the current statement data as metadata to the current object */
3140 
3141     Tcl_ObjectSetMetadata(thisObject, &statementDataType, (ClientData) sdata);
3142     return TCL_OK;
3143 
3144     /* On error, unwind all the resource allocations */
3145 
3146  freeSData:
3147     DecrStatementRefCount(sdata);
3148     return TCL_ERROR;
3149 }
3150 
3151 /*
3152  *-----------------------------------------------------------------------------
3153  *
3154  * PrimarykeysStatementConstructor --
3155  *
3156  *	C-level initialization for the object representing an ODBC query
3157  *	for primary key metadata
3158  *
3159  * Parameters:
3160  *	Accepts a 4-element 'objv':
3161  *		columnsStatement new $connection $table,
3162  *	where $connection is the ODBC connection object and $table is the
3163  *	name of the table being queried.
3164  *
3165  * Results:
3166  *	Returns a standard Tcl result
3167  *
3168  * Side effects:
3169  *	Prepares the statement, and stores it (plus a reference to the
3170  *	connection) in instance metadata.
3171  *
3172  *-----------------------------------------------------------------------------
3173  */
3174 
3175 static int
PrimarykeysStatementConstructor(ClientData dummy,Tcl_Interp * interp,Tcl_ObjectContext context,int objc,Tcl_Obj * const objv[])3176 PrimarykeysStatementConstructor(
3177     ClientData dummy,	/* Not used */
3178     Tcl_Interp* interp,		/* Tcl interpreter */
3179     Tcl_ObjectContext context,	/* Object context  */
3180     int objc, 			/* Parameter count */
3181     Tcl_Obj *const objv[]	/* Parameter vector */
3182 ) {
3183 
3184     Tcl_Object thisObject = Tcl_ObjectContextObject(context);
3185 				/* The current statement object */
3186     int skip = Tcl_ObjectContextSkippedArgs(context);
3187 				/* The number of parameters to skip */
3188     Tcl_Object connectionObject;
3189 				/* The database connection as a Tcl_Object */
3190     ConnectionData* cdata;	/* The connection object's data */
3191     StatementData* sdata;	/* The statement's object data */
3192     RETCODE rc;			/* Return code from ODBC */
3193     (void)dummy;
3194 
3195 
3196     /* Check param count */
3197 
3198     if (objc != skip+2) {
3199 	Tcl_WrongNumArgs(interp, skip, objv, "connection tableName");
3200 	return TCL_ERROR;
3201     }
3202 
3203     /* Find the connection object, and get its data. */
3204 
3205     connectionObject = Tcl_GetObjectFromObj(interp, objv[skip]);
3206     if (connectionObject == NULL) {
3207 	return TCL_ERROR;
3208     }
3209     cdata = (ConnectionData*) Tcl_ObjectGetMetadata(connectionObject,
3210 						    &connectionDataType);
3211     if (cdata == NULL) {
3212 	Tcl_AppendResult(interp, Tcl_GetString(objv[skip]),
3213 			 " does not refer to an ODBC connection", NULL);
3214 	return TCL_ERROR;
3215     }
3216 
3217     /*
3218      * Allocate an object to hold data about this statement
3219      */
3220 
3221     sdata = NewStatement(cdata, connectionObject);
3222 
3223     /* Allocate an ODBC statement handle */
3224 
3225     rc = SQLAllocHandle(SQL_HANDLE_STMT, cdata->hDBC, &(sdata->hStmt));
3226     if (!SQL_SUCCEEDED(rc)) {
3227 	TransferSQLError(interp, SQL_HANDLE_DBC, cdata->hDBC,
3228 			 "(allocating statement handle)");
3229 	goto freeSData;
3230     }
3231 
3232     /*
3233      * Stash the table name in the statement data,
3234      * and set a flag that that's what we have there.
3235      */
3236 
3237     sdata->nativeSqlW = GetWCharStringFromObj(objv[skip+1],
3238 					      &(sdata->nativeSqlLen));
3239     sdata->flags = STATEMENT_FLAG_PRIMARYKEYS;
3240 
3241     /* Attach the current statement data as metadata to the current object */
3242 
3243     Tcl_ObjectSetMetadata(thisObject, &statementDataType, (ClientData) sdata);
3244     return TCL_OK;
3245 
3246     /* On error, unwind all the resource allocations */
3247 
3248  freeSData:
3249     DecrStatementRefCount(sdata);
3250     return TCL_ERROR;
3251 }
3252 
3253 /*
3254  *-----------------------------------------------------------------------------
3255  *
3256  * ForeignkeysStatementConstructor --
3257  *
3258  *	C-level initialization for the object representing an ODBC query
3259  *	for foreign key metadata
3260  *
3261  * Parameters:
3262  *	Accepts a variadic 'objv':
3263  *		columnsStatement new $connection ?-keyword value?...
3264  *	where $connection is the ODBC connection object. The keyword options
3265  *	include '-primary', which gives the name of a primary table, and
3266  *	'-foreign', which gives the name of a foreign table.
3267  *
3268  * Results:
3269  *	Returns a standard Tcl result
3270  *
3271  * Side effects:
3272  *	Prepares the statement, and stores it (plus a reference to the
3273  *	connection) in instance metadata.
3274  *
3275  *-----------------------------------------------------------------------------
3276  */
3277 
3278 static int
ForeignkeysStatementConstructor(ClientData dummy,Tcl_Interp * interp,Tcl_ObjectContext context,int objc,Tcl_Obj * const objv[])3279 ForeignkeysStatementConstructor(
3280     ClientData dummy,	/* Not used */
3281     Tcl_Interp* interp,		/* Tcl interpreter */
3282     Tcl_ObjectContext context,	/* Object context  */
3283     int objc, 			/* Parameter count */
3284     Tcl_Obj *const objv[]	/* Parameter vector */
3285 ) {
3286 
3287     Tcl_Object thisObject = Tcl_ObjectContextObject(context);
3288 				/* The current statement object */
3289     int skip = Tcl_ObjectContextSkippedArgs(context);
3290 				/* The number of parameters to skip */
3291     Tcl_Object connectionObject;
3292 				/* The database connection as a Tcl_Object */
3293     ConnectionData* cdata;	/* The connection object's data */
3294     StatementData* sdata;	/* The statement's object data */
3295     RETCODE rc;			/* Return code from ODBC */
3296     static const char* options[] = {	/* Option table */
3297 	"-foreign",
3298 	"-primary",
3299 	NULL
3300     };
3301     enum {
3302 	OPT_FOREIGN=0,
3303 	OPT_PRIMARY,
3304 	OPT__END
3305     };
3306 
3307     int i;
3308     int paramIdx;		/* Index of the current option in the option
3309 				 * table */
3310     unsigned char have[OPT__END];
3311 				/* Flags for whether given -keywords have been
3312 				 * seen. */
3313     Tcl_Obj* resultObj;		/* Interpreter result */
3314     (void)dummy;
3315 
3316     /* Check param count */
3317 
3318     if (objc < skip+1 || (objc-skip) % 2 != 1) {
3319 	Tcl_WrongNumArgs(interp, skip, objv, "connection ?-option value?...");
3320 	return TCL_ERROR;
3321     }
3322 
3323     /* Find the connection object, and get its data. */
3324 
3325     connectionObject = Tcl_GetObjectFromObj(interp, objv[skip]);
3326     if (connectionObject == NULL) {
3327 	return TCL_ERROR;
3328     }
3329     cdata = (ConnectionData*) Tcl_ObjectGetMetadata(connectionObject,
3330 						    &connectionDataType);
3331     if (cdata == NULL) {
3332 	Tcl_AppendResult(interp, Tcl_GetString(objv[skip]),
3333 			 " does not refer to an ODBC connection", NULL);
3334 	return TCL_ERROR;
3335     }
3336 
3337     /*
3338      * Allocate an object to hold data about this statement
3339      */
3340 
3341     sdata = NewStatement(cdata, connectionObject);
3342 
3343     /* Absorb parameters */
3344 
3345     have[OPT_FOREIGN] = have[OPT_PRIMARY] = 0;
3346     for (i = skip+1; i+1 < objc; i+=2) {
3347 	if (Tcl_GetIndexFromObjStruct(interp, objv[i], options, sizeof(char *),
3348 				"option", 0, &paramIdx) != TCL_OK) {
3349 	    goto freeSData;
3350 	}
3351 	if (have[paramIdx]) {
3352 	    resultObj = Tcl_NewStringObj("duplicate option \"", -1);
3353 	    Tcl_AppendObjToObj(resultObj, objv[i]);
3354 	    Tcl_AppendToObj(resultObj, "\"", -1);
3355 	    Tcl_SetErrorCode(interp, "TDBC", "GENERAL_ERROR", "HY001",
3356 			     "ODBC", "-1", NULL);
3357 	    Tcl_SetObjResult(interp, resultObj);
3358 	    goto freeSData;
3359 	}
3360 	switch(paramIdx) {
3361 	case OPT_FOREIGN:
3362 	    sdata->nativeMatchPatternW =
3363 		GetWCharStringFromObj(objv[i+1], &(sdata->nativeMatchPatLen));
3364 	    break;
3365 	case OPT_PRIMARY:
3366 	    sdata->nativeSqlW =
3367 		GetWCharStringFromObj(objv[i+1], &(sdata->nativeSqlLen));
3368 	    break;
3369 	}
3370 	have[paramIdx] = 1;
3371     }
3372 
3373     /* Allocate an ODBC statement handle */
3374 
3375     rc = SQLAllocHandle(SQL_HANDLE_STMT, cdata->hDBC, &(sdata->hStmt));
3376     if (!SQL_SUCCEEDED(rc)) {
3377 	TransferSQLError(interp, SQL_HANDLE_DBC, cdata->hDBC,
3378 			 "(allocating statement handle)");
3379 	goto freeSData;
3380     }
3381 
3382     sdata->flags = STATEMENT_FLAG_FOREIGNKEYS;
3383 
3384     /* Attach the current statement data as metadata to the current object */
3385 
3386     Tcl_ObjectSetMetadata(thisObject, &statementDataType, (ClientData) sdata);
3387     return TCL_OK;
3388 
3389     /* On error, unwind all the resource allocations */
3390 
3391  freeSData:
3392     DecrStatementRefCount(sdata);
3393     return TCL_ERROR;
3394 }
3395 
3396 /*
3397  *-----------------------------------------------------------------------------
3398  *
3399  * TypesStatementConstructor --
3400  *
3401  *	C-level initialization for the object representing an ODBC query
3402  *	for data type metadata
3403  *
3404  * Parameters:
3405  *	Accepts a 3- or 4-element 'objv':
3406  *		typesStatement new $connection ?$typeNum?
3407  *	where $connection is the ODBC connection object, and $typeNum,
3408  *	if present, makes the query match only the given type.
3409  *
3410  * Results:
3411  *	Returns a standard Tcl result
3412  *
3413  * Side effects:
3414  *	Prepares the statement, and stores it (plus a reference to the
3415  *	connection) in instance metadata.
3416  *
3417  *-----------------------------------------------------------------------------
3418  */
3419 
3420 static int
TypesStatementConstructor(ClientData dummy,Tcl_Interp * interp,Tcl_ObjectContext context,int objc,Tcl_Obj * const objv[])3421 TypesStatementConstructor(
3422     ClientData dummy,	/* Not used */
3423     Tcl_Interp* interp,		/* Tcl interpreter */
3424     Tcl_ObjectContext context,	/* Object context  */
3425     int objc, 			/* Parameter count */
3426     Tcl_Obj *const objv[]	/* Parameter vector */
3427 ) {
3428 
3429     Tcl_Object thisObject = Tcl_ObjectContextObject(context);
3430 				/* The current statement object */
3431     int skip = Tcl_ObjectContextSkippedArgs(context);
3432 				/* The number of leading args to skip */
3433     Tcl_Object connectionObject;
3434 				/* The database connection as a Tcl_Object */
3435     ConnectionData* cdata;	/* The connection object's data */
3436     StatementData* sdata;	/* The statement's object data */
3437     RETCODE rc;			/* Return code from ODBC */
3438     int typeNum;		/* Data type number */
3439     (void)dummy;
3440 
3441     /* Parse args */
3442 
3443     if (objc == skip+1) {
3444 	typeNum = SQL_ALL_TYPES;
3445     } else if (objc == skip+2) {
3446 	if (Tcl_GetIntFromObj(interp, objv[skip+1], &typeNum) != TCL_OK) {
3447 	    return TCL_ERROR;
3448 	}
3449     } else {
3450 	Tcl_WrongNumArgs(interp, skip, objv, "connection ?typeNum?");
3451 	return TCL_ERROR;
3452     }
3453 
3454     /* Find the connection object, and get its data. */
3455 
3456     connectionObject = Tcl_GetObjectFromObj(interp, objv[skip]);
3457     if (connectionObject == NULL) {
3458 	return TCL_ERROR;
3459     }
3460     cdata = (ConnectionData*) Tcl_ObjectGetMetadata(connectionObject,
3461 						    &connectionDataType);
3462     if (cdata == NULL) {
3463 	Tcl_AppendResult(interp, Tcl_GetString(objv[skip]),
3464 			 " does not refer to an ODBC connection", NULL);
3465 	return TCL_ERROR;
3466     }
3467 
3468     /*
3469      * Allocate an object to hold data about this statement
3470      */
3471 
3472     sdata = NewStatement(cdata, connectionObject);
3473 
3474     /* Allocate an ODBC statement handle */
3475 
3476     rc = SQLAllocHandle(SQL_HANDLE_STMT, cdata->hDBC, &(sdata->hStmt));
3477     if (!SQL_SUCCEEDED(rc)) {
3478 	TransferSQLError(interp, SQL_HANDLE_DBC, cdata->hDBC,
3479 			 "(allocating statement handle)");
3480 	goto freeSData;
3481     }
3482 
3483     /*
3484      * Stash the type number in the statement data, and set a flag
3485      * that that's what we have there.
3486      */
3487 
3488     sdata->typeNum = typeNum;
3489     sdata->flags = STATEMENT_FLAG_TYPES;
3490 
3491     /* Attach the current statement data as metadata to the current object */
3492 
3493 
3494     Tcl_ObjectSetMetadata(thisObject, &statementDataType, (ClientData) sdata);
3495     return TCL_OK;
3496 
3497     /* On error, unwind all the resource allocations */
3498 
3499  freeSData:
3500     DecrStatementRefCount(sdata);
3501     return TCL_ERROR;
3502 }
3503 
3504 /*
3505  *-----------------------------------------------------------------------------
3506  *
3507  * DeleteStatementMetadata, DeleteStatement --
3508  *
3509  *	Cleans up when an ODBC statement is no longer required.
3510  *
3511  * Side effects:
3512  *	Frees all resources associated with the statement.
3513  *
3514  *-----------------------------------------------------------------------------
3515  */
3516 
3517 static void
DeleteStatementMetadata(ClientData clientData)3518 DeleteStatementMetadata(
3519     ClientData clientData	/* Instance data for the connection */
3520 ) {
3521     DecrStatementRefCount((StatementData*)clientData);
3522 }
3523 static void
DeleteStatement(StatementData * sdata)3524 DeleteStatement(
3525     StatementData* sdata	/* Metadata for the statement */
3526 ) {
3527     if (sdata->hStmt != SQL_NULL_HANDLE) {
3528 	SQLFreeHandle(SQL_HANDLE_STMT, sdata->hStmt);
3529     }
3530     if (sdata->params != NULL) {
3531 	ckfree((char*) sdata->params);
3532     }
3533     Tcl_DecrRefCount(sdata->subVars);
3534     if (sdata->nativeSqlW != NULL) {
3535 	ckfree((char*) sdata->nativeSqlW);
3536     }
3537     if (sdata->nativeMatchPatternW != NULL) {
3538 	ckfree((char*) sdata->nativeMatchPatternW);
3539     }
3540     DecrConnectionRefCount(sdata->cdata);
3541     ckfree((char*)sdata);
3542 }
3543 
3544 /*
3545  *-----------------------------------------------------------------------------
3546  *
3547  * CloneStatement --
3548  *
3549  *	Attempts to clone an ODBC statement's metadata.
3550  *
3551  * Results:
3552  *	Returns the new metadata
3553  *
3554  * At present, we don't attempt to clone statements - it's not obvious
3555  * that such an action would ever even make sense.  Instead, we return NULL
3556  * to indicate that the metadata should not be cloned. (Note that this
3557  * action isn't right, either. What *is* right is to indicate that the object
3558  * is not clonable, but the API gives us no way to do that.
3559  *
3560  *-----------------------------------------------------------------------------
3561  */
3562 
3563 static int
CloneStatement(Tcl_Interp * interp,ClientData metadata,ClientData * newMetaData)3564 CloneStatement(
3565     Tcl_Interp* interp,		/* Tcl interpreter for error reporting */
3566     ClientData metadata,	/* Metadata to be cloned */
3567     ClientData* newMetaData	/* Where to put the cloned metadata */
3568 ) {
3569     (void)metadata;
3570     (void)newMetaData;
3571 
3572     Tcl_SetObjResult(interp,
3573 		     Tcl_NewStringObj("ODBC statements are not clonable", -1));
3574     return TCL_ERROR;
3575 }
3576 
3577 /*
3578  *-----------------------------------------------------------------------------
3579  *
3580  * ResultSetConstructor --
3581  *
3582  *	Constructs a new result set.
3583  *
3584  * Usage:
3585  *	$resultSet new statement ?dictionary?
3586  *	$resultSet create name statement ?dictionary?
3587  *
3588  * Parameters:
3589  *	statement -- The statement object to which the result set belongs
3590  *	dictionary -- Dictionary containing the substitutions for named
3591  *		      parameters in the given statement.
3592  *
3593  * Results:
3594  *	Returns a standard Tcl result.  On error, the interpreter result
3595  *	contains an appropriate message.
3596  *
3597  *-----------------------------------------------------------------------------
3598  */
3599 
3600 static int
ResultSetConstructor(ClientData dummy,Tcl_Interp * interp,Tcl_ObjectContext context,int objc,Tcl_Obj * const objv[])3601 ResultSetConstructor(
3602     ClientData dummy,	/* Not used */
3603     Tcl_Interp* interp,		/* Tcl interpreter */
3604     Tcl_ObjectContext context,	/* Object context  */
3605     int objc, 			/* Parameter count */
3606     Tcl_Obj *const objv[]	/* Parameter vector */
3607 ) {
3608 
3609     Tcl_Object thisObject = Tcl_ObjectContextObject(context);
3610 				/* The current result set object */
3611     int skip = Tcl_ObjectContextSkippedArgs(context);
3612 				/* Number of skipped args in the
3613 				 * method invocation */
3614     Tcl_Object statementObject;	/* The current statement object */
3615     ConnectionData* cdata;	/* The ODBC connection object's data */
3616     StatementData* sdata;	/* The statement object's data */
3617     ResultSetData* rdata;	/* THe result set object's data */
3618     int nParams;		/* Number of substituted parameters in
3619 				 * the statement */
3620     int nBound;			/* Number of substituted parameters that
3621 				 * have been bound successfully */
3622     SQLSMALLINT dataType;	/* Data type of a parameter */
3623     Tcl_Obj* paramNameObj;	/* Name of a substituted parameter */
3624     const char* paramName;	/* Name of a substituted parameter */
3625     Tcl_Obj* paramValObj;	/* Value of a substituted parameter */
3626     const char* paramVal;	/* Value of a substituted parameter */
3627     size_t paramLen;		/* String length of the parameter value */
3628     Tcl_DString paramExternal;	/* Substituted parameter, converted to
3629 				 * system encoding */
3630     int paramExternalLen;	/* Length of the substituted parameter
3631 				 * after conversion */
3632     SQLRETURN rc;		/* Return code from ODBC calls */
3633     unsigned char* byteArrayPtr; /* Pointer to a BINARY or VARBINARY
3634 				 * parameter, expressed as a byte array.*/
3635     int i;
3636     (void)dummy;
3637 
3638     /* Check parameter count */
3639 
3640     if (objc != skip+1 && objc != skip+2) {
3641 	Tcl_WrongNumArgs(interp, skip, objv, "statement ?dictionary?");
3642 	return TCL_ERROR;
3643     }
3644 
3645     /* Initialize superclasses */
3646 
3647     Tcl_ObjectContextInvokeNext(interp, context, skip, objv, skip);
3648 
3649     /* Find the statement object, and get the statement data */
3650 
3651     statementObject = Tcl_GetObjectFromObj(interp, objv[skip]);
3652     if (statementObject == NULL) {
3653 	return TCL_ERROR;
3654     }
3655     sdata = (StatementData*) Tcl_ObjectGetMetadata(statementObject,
3656 						   &statementDataType);
3657     if (sdata == NULL) {
3658 	Tcl_AppendResult(interp, Tcl_GetString(objv[skip]),
3659 			 " does not refer to an ODBC statement", NULL);
3660 	return TCL_ERROR;
3661     }
3662 
3663     /*
3664      * If there is no transaction in progress, turn on auto-commit so that
3665      * this statement will execute directly.
3666      */
3667 
3668     cdata = sdata->cdata;
3669     if ((cdata->flags & (CONNECTION_FLAG_XCN_ACTIVE
3670 			 | CONNECTION_FLAG_AUTOCOMMIT)) == 0) {
3671 	cdata->flags |= CONNECTION_FLAG_AUTOCOMMIT;
3672 	if (SetAutocommitFlag(interp, cdata, 1) != TCL_OK) {
3673 	    return TCL_ERROR;
3674 	}
3675     }
3676 
3677     /* Allocate an object to hold data about this result set */
3678 
3679     rdata = (ResultSetData*) ckalloc(sizeof(ResultSetData));
3680     rdata->refCount = 1;
3681     rdata->sdata = sdata;
3682     rdata->hStmt = NULL;
3683     rdata->results = NULL;
3684     rdata->resultColNames = NULL;
3685     IncrStatementRefCount(sdata);
3686     Tcl_ObjectSetMetadata(thisObject, &resultSetDataType, (ClientData) rdata);
3687 
3688     /*
3689      * Find a statement handle that we can use to execute the SQL code.
3690      * If the main statement handle associated with the statement
3691      * is idle, we can use it.  Otherwise, we have to allocate and
3692      * prepare a fresh one.
3693      */
3694 
3695     if (sdata->flags & STATEMENT_FLAG_HSTMT_BUSY) {
3696 	rdata->hStmt = AllocAndPrepareStatement(interp, sdata);
3697 	if (rdata->hStmt == NULL) {
3698 	    return TCL_ERROR;
3699 	}
3700     } else {
3701 	rdata->hStmt = sdata->hStmt;
3702 	sdata->flags |= STATEMENT_FLAG_HSTMT_BUSY;
3703     }
3704 
3705     /* Allocate an array to hold SQLWCHAR strings with parameter data */
3706 
3707     Tcl_ListObjLength(NULL, sdata->subVars, &nParams);
3708     rdata->bindStrings = (SQLCHAR**) ckalloc(nParams * sizeof(SQLCHAR*));
3709     rdata->bindStringLengths = (SQLLEN*) ckalloc(nParams * sizeof(SQLLEN));
3710     for (i = 0; i < nParams; ++i) {
3711 	rdata->bindStrings[i] = NULL;
3712 	rdata->bindStringLengths[i] = SQL_NULL_DATA;
3713     }
3714 
3715     /* Bind the substituted parameters */
3716 
3717     for (nBound = 0; nBound < nParams; ++nBound) {
3718 	Tcl_ListObjIndex(NULL, sdata->subVars, nBound, &paramNameObj);
3719 	paramName = Tcl_GetString(paramNameObj);
3720 	if (objc == skip+2) {
3721 
3722 	    /* Param from a dictionary */
3723 
3724 	    if (Tcl_DictObjGet(interp, objv[skip+1], paramNameObj, &paramValObj)
3725 		!= TCL_OK) {
3726 		return TCL_ERROR;
3727 	    }
3728 	} else {
3729 
3730 	    /* Param from a variable */
3731 
3732 	    paramValObj = Tcl_GetVar2Ex(interp, paramName, NULL,
3733 					TCL_LEAVE_ERR_MSG);
3734 
3735 	}
3736 
3737 	/*
3738 	 * Choose the C->SQL data conversion based on the parameter type
3739 	 */
3740 
3741 	if (paramValObj != NULL) {
3742 
3743 	    switch (sdata->params[nBound].dataType) {
3744 
3745 	    case SQL_NUMERIC:
3746 	    case SQL_DECIMAL:
3747 
3748 		/*
3749 		 * A generic 'numeric' type may fit in an int, wide,
3750 		 * or double, and gets converted specially if it does.
3751 		 */
3752 
3753 		if (sdata->params[nBound].scale == 0) {
3754 		    if (sdata->params[nBound].precision < 10) {
3755 			goto is_integer;
3756 		    } else if (sdata->params[nBound].precision < 19
3757 			       && (cdata->flags & CONNECTION_FLAG_HAS_BIGINT)) {
3758 			goto is_wide;
3759 		    } else {
3760 			/*
3761 			 * It is tempting to convert wider integers as bignums,
3762 			 * but Tcl does not yet export its copy of libtommath
3763 			 * into the public API.
3764 			 */
3765 			goto is_string;
3766 		    }
3767 		} else if (sdata->params[nBound].precision <= 15) {
3768 		    goto is_float;
3769 		} else {
3770 		    goto is_string;
3771 		}
3772 
3773 	    case SQL_REAL:
3774 	    case SQL_DOUBLE:
3775 	    is_float:
3776 
3777 		/* Pass floating point numbers through to SQL without
3778 		 * conversion */
3779 
3780 		rdata->bindStrings[nBound] =
3781 		    (SQLCHAR*) ckalloc(sizeof(double));
3782 		if (Tcl_GetDoubleFromObj(interp, paramValObj,
3783 					 (double*)(rdata->bindStrings[nBound]))
3784 		    != TCL_OK) {
3785 		    ckfree((char*)(rdata->bindStrings[nBound]));
3786 		    goto is_string;
3787 		}
3788 		dataType = SQL_C_DOUBLE;
3789 		paramExternalLen = sizeof(double);
3790 		rdata->bindStringLengths[nBound] = paramExternalLen;
3791 		break;
3792 
3793 	    case SQL_BIGINT:
3794 	    is_wide:
3795 
3796 		/* Pass 64-bit integers through to SQL without conversion */
3797 
3798 		rdata->bindStrings[nBound] =
3799 		    (SQLCHAR*) ckalloc(sizeof(SQLBIGINT));
3800 		if (Tcl_GetWideIntFromObj(interp, paramValObj,
3801 					  (SQLBIGINT*)
3802 					  (rdata->bindStrings[nBound]))
3803 		    != TCL_OK) {
3804 		    ckfree((char*)(rdata->bindStrings[nBound]));
3805 		    goto is_string;
3806 		}
3807 		dataType = SQL_C_SBIGINT;
3808 		paramExternalLen = sizeof(SQLBIGINT);
3809 		rdata->bindStringLengths[nBound] = paramExternalLen;
3810 		break;
3811 
3812 	    case SQL_INTEGER:
3813 	    case SQL_SMALLINT:
3814 	    case SQL_TINYINT:
3815 	    case SQL_BIT:
3816 	    is_integer:
3817 
3818 		/* Pass integers through to SQL without conversion */
3819 
3820 		rdata->bindStrings[nBound] =
3821 		    (SQLCHAR*) ckalloc(sizeof(long));
3822 		if (Tcl_GetLongFromObj(interp, paramValObj,
3823 				       (long*)(rdata->bindStrings[nBound]))
3824 		    != TCL_OK) {
3825 		    ckfree((char*)(rdata->bindStrings[nBound]));
3826 		    goto is_string;
3827 		}
3828 		dataType = SQL_C_LONG;
3829 		paramExternalLen = sizeof(long);
3830 		rdata->bindStringLengths[nBound] = paramExternalLen;
3831 		break;
3832 
3833 	    case SQL_BINARY:
3834 	    case SQL_VARBINARY:
3835 	    case SQL_LONGVARBINARY:
3836 
3837 		/*
3838 		 * Binary strings are shipped as byte arrays. It would
3839 		 * be nice to avoid an extra copy, but it's possible
3840 		 * for the byte array to shimmer away before ODBC has
3841 		 * a chance to work with it.
3842 		 */
3843 		byteArrayPtr = Tcl_GetByteArrayFromObj(paramValObj,
3844 						       &paramExternalLen);
3845 		dataType = SQL_C_BINARY;
3846 		rdata->bindStringLengths[nBound] = paramExternalLen;
3847 		rdata->bindStrings[nBound] =
3848 		    (SQLCHAR*) ckalloc(paramExternalLen);
3849 		memcpy(rdata->bindStrings[nBound], byteArrayPtr,
3850 		       paramExternalLen);
3851 		break;
3852 
3853 	    default:
3854 	    is_string:
3855 
3856 		/* Everything else is converted as a string */
3857 
3858 		if (cdata->flags & CONNECTION_FLAG_HAS_WVARCHAR) {
3859 
3860 		    /* We prefer to transfer strings in Unicode if possible */
3861 
3862 		    dataType = SQL_C_WCHAR;
3863 		    rdata->bindStrings[nBound] = (SQLCHAR*)
3864 			GetWCharStringFromObj(paramValObj, &paramLen);
3865 		    rdata->bindStringLengths[nBound] = paramExternalLen =
3866 			paramLen * sizeofSQLWCHAR;
3867 
3868 		} else {
3869 
3870 		    /*
3871 		     * We need to convert the character string to system
3872 		     * encoding and store in rdata->bindStrings[nBound].
3873 		     */
3874 		    dataType = SQL_C_CHAR;
3875 		    paramVal = Tcl_GetString(paramValObj);
3876 		    paramLen = paramValObj->length;
3877 		    Tcl_DStringInit(&paramExternal);
3878 		    Tcl_UtfToExternalDString(NULL, paramVal, paramLen,
3879 					     &paramExternal);
3880 		    paramExternalLen = Tcl_DStringLength(&paramExternal);
3881 		    rdata->bindStrings[nBound] = (SQLCHAR*)
3882 			ckalloc(paramExternalLen + 1);
3883 		    memcpy(rdata->bindStrings[nBound],
3884 			   Tcl_DStringValue(&paramExternal),
3885 			   paramExternalLen + 1);
3886 		    rdata->bindStringLengths[nBound] = paramExternalLen;
3887 		    Tcl_DStringFree(&paramExternal);
3888 		}
3889 
3890 	    }
3891 
3892 	} else {
3893 
3894 	    /* Parameter is NULL */
3895 
3896 	    dataType = SQL_C_CHAR;
3897 	    rdata->bindStrings[nBound] = NULL;
3898 	    paramExternalLen = paramLen = 0;
3899 	    rdata->bindStringLengths[nBound] = SQL_NULL_DATA;
3900 	}
3901 	rc = SQLBindParameter(rdata->hStmt,
3902 			      nBound + 1,
3903 			      SQL_PARAM_INPUT, /* TODO - Fix this! */
3904 			      dataType,
3905 			      sdata->params[nBound].dataType,
3906 			      sdata->params[nBound].precision,
3907 			      sdata->params[nBound].scale,
3908 			      rdata->bindStrings[nBound],
3909 			      paramExternalLen,
3910 			      rdata->bindStringLengths + nBound);
3911 	if (!SQL_SUCCEEDED(rc)) {
3912 	    char* info = (char *)ckalloc(80 * strlen(paramName));
3913 	    sprintf(info, "(binding the '%s' parameter)", paramName);
3914 	    TransferSQLError(interp, SQL_HANDLE_STMT, rdata->hStmt, info);
3915 	    ckfree(info);
3916 	    return TCL_ERROR;
3917 	}
3918     }
3919 
3920     /* Execute the statement */
3921 
3922     if (sdata->flags & STATEMENT_FLAG_TABLES) {
3923 	rc = SQLTablesW(rdata->hStmt, NULL, 0, NULL, 0,
3924 			sdata->nativeSqlW, sdata->nativeSqlLen, NULL, 0);
3925     } else if (sdata->flags & STATEMENT_FLAG_COLUMNS) {
3926 	rc = SQLColumnsW(rdata->hStmt, NULL, 0, NULL, 0,
3927 			 sdata->nativeSqlW, sdata->nativeSqlLen,
3928 			 sdata->nativeMatchPatternW, sdata->nativeMatchPatLen);
3929     } else if (sdata->flags & STATEMENT_FLAG_TYPES) {
3930 	rc = SQLGetTypeInfo(rdata->hStmt, sdata->typeNum);
3931     } else if (sdata->flags & STATEMENT_FLAG_PRIMARYKEYS) {
3932 	rc = SQLPrimaryKeysW(rdata->hStmt, NULL, 0, NULL, 0,
3933 			    sdata->nativeSqlW, sdata->nativeSqlLen);
3934     } else if (sdata->flags & STATEMENT_FLAG_FOREIGNKEYS) {
3935 	rc = SQLForeignKeysW(rdata->hStmt, NULL, 0, NULL, 0,
3936 			     sdata->nativeSqlW, sdata->nativeSqlLen,
3937 			     NULL, 0, NULL, 0,
3938 			     sdata->nativeMatchPatternW,
3939 			     sdata->nativeMatchPatLen);
3940     } else {
3941 	rc = SQLExecute(rdata->hStmt);
3942     }
3943     if (!SQL_SUCCEEDED(rc) && rc != SQL_NO_DATA) {
3944 	TransferSQLError(interp, SQL_HANDLE_STMT, rdata->hStmt,
3945 			 "(executing the statement)");
3946 	return TCL_ERROR;
3947     }
3948 
3949     /* Extract the column information for the result set. */
3950 
3951     if (GetResultSetDescription(interp, rdata) != TCL_OK) {
3952 	return TCL_ERROR;
3953     }
3954 
3955     /* Determine and store the row count. Note: iodbc makes it illegal
3956      * to call SQLRowCount after an operation has returned SQL_NO_DATA,
3957      * so bypass the SQLRowCount call if there are no results.
3958      */
3959 
3960     if (rc == SQL_NO_DATA) {
3961 	rdata->rowCount = 0;
3962     } else {
3963 	rc = SQLRowCount(rdata->hStmt, &(rdata->rowCount));
3964 	if (!SQL_SUCCEEDED(rc)) {
3965 	    TransferSQLError(interp, SQL_HANDLE_STMT, rdata->hStmt,
3966 			     "(counting rows in the result)");
3967 	    return TCL_ERROR;
3968 	}
3969     }
3970 
3971     return TCL_OK;
3972 }
3973 
3974 /*
3975  *----------------------------------------------------------------------
3976  *
3977  * ResultSetColumnsMethod --
3978  *
3979  *	Retrieves the list of columns from a result set.
3980  *
3981  * Usage:
3982  *	$resultSet columns
3983  *
3984  * Results:
3985  *	Returns the count of columns
3986  *
3987  *-----------------------------------------------------------------------------
3988  */
3989 
3990 static int
ResultSetColumnsMethod(ClientData dummy,Tcl_Interp * interp,Tcl_ObjectContext context,int objc,Tcl_Obj * const objv[])3991 ResultSetColumnsMethod(
3992     ClientData dummy,	/* Not used */
3993     Tcl_Interp* interp,		/* Tcl interpreter */
3994     Tcl_ObjectContext context,	/* Object context  */
3995     int objc, 			/* Parameter count */
3996     Tcl_Obj *const objv[]	/* Parameter vector */
3997 ) {
3998     Tcl_Object thisObject = Tcl_ObjectContextObject(context);
3999 				/* The current result set object */
4000     ResultSetData* rdata = (ResultSetData*)
4001 	Tcl_ObjectGetMetadata(thisObject, &resultSetDataType);
4002     (void)dummy;
4003 
4004     if (objc != 2) {
4005 	Tcl_WrongNumArgs(interp, 2, objv, "");
4006 	return TCL_ERROR;
4007     }
4008 
4009     /* Extract the column information for the result set. */
4010 
4011     if (rdata->resultColNames == NULL) {
4012 	if (GetResultSetDescription(interp, rdata) != TCL_OK) {
4013 	    return TCL_ERROR;
4014 	}
4015     }
4016 
4017     Tcl_SetObjResult(interp, rdata->resultColNames);
4018     return TCL_OK;
4019 
4020 }
4021 
4022 /*
4023  *-----------------------------------------------------------------------------
4024  *
4025  * ResultSetNextresultsMethod --
4026  *
4027  *	Advances a result set to the next group of rows (next result set
4028  *	from a query that returns multiple result sets)
4029  *
4030  * Usage:
4031  *	$resultSet nextresults
4032  *
4033  * Parameters:
4034  *	None.
4035  *
4036  * Results:
4037  *	Returns a standard Tcl result. If successful, the result is '1' if
4038  *	more results remain and '0' if no more results remain.  In the event
4039  *	of failure, the result is a Tcl error message describing the problem.
4040  *
4041  *-----------------------------------------------------------------------------
4042  */
4043 
4044 static int
ResultSetNextresultsMethod(ClientData dummy,Tcl_Interp * interp,Tcl_ObjectContext context,int objc,Tcl_Obj * const objv[])4045 ResultSetNextresultsMethod(
4046     ClientData dummy,	/* Not used */
4047     Tcl_Interp* interp,		/* Tcl interpreter */
4048     Tcl_ObjectContext context,	/* Object context  */
4049     int objc, 			/* Parameter count */
4050     Tcl_Obj *const objv[]	/* Parameter vector */
4051 ) {
4052     Tcl_Object thisObject = Tcl_ObjectContextObject(context);
4053 				/* The current result set object */
4054     ResultSetData* rdata = (ResultSetData*)
4055 	Tcl_ObjectGetMetadata(thisObject, &resultSetDataType);
4056 				/* Data pertaining to the current result set */
4057     StatementData* sdata = (StatementData*) rdata->sdata;
4058 				/* Statement that yielded the result set */
4059     ConnectionData* cdata = (ConnectionData*) sdata->cdata;
4060 				/* Connection that opened the statement */
4061     PerInterpData* pidata = (PerInterpData*) cdata->pidata;
4062 				/* Per interpreter data */
4063     Tcl_Obj** literals = pidata->literals;
4064 				/* Literal pool */
4065     SQLRETURN rc;		/* Return code from ODBC operations */
4066     (void)dummy;
4067     (void)objc;
4068     (void)objv;
4069 
4070     /*
4071      * Once we are advancing the results, any data that we think we know
4072      * about the columns in the result set are incorrect. Discard them.
4073      */
4074 
4075     DeleteResultSetDescription(rdata);
4076 
4077     /* Advance to the next results */
4078 
4079     rc = SQLMoreResults(rdata->hStmt);
4080     if (rc == SQL_NO_DATA) {
4081 	Tcl_SetObjResult(interp, literals[LIT_0]);
4082 	return TCL_OK;
4083     }
4084     if (!SQL_SUCCEEDED(rc)) {
4085 	TransferSQLError(interp, SQL_HANDLE_STMT, rdata->hStmt,
4086 			 "(advancing to next result set)");
4087 	return TCL_ERROR;
4088     }
4089     if (GetResultSetDescription(interp, rdata) != TCL_OK) {
4090 	return TCL_ERROR;
4091     }
4092 
4093     /* Determine and store the row count */
4094 
4095     rc = SQLRowCount(rdata->hStmt, &(rdata->rowCount));
4096     if (!SQL_SUCCEEDED(rc)) {
4097 	TransferSQLError(interp, SQL_HANDLE_STMT, rdata->hStmt,
4098 			 "(counting rows in the result)");
4099 	return TCL_ERROR;
4100     } else {
4101 	Tcl_SetObjResult(interp, literals[LIT_1]);
4102 	return TCL_OK;
4103     }
4104 }
4105 
4106 
4107 /*
4108  *-----------------------------------------------------------------------------
4109  *
4110  * ResultSetNextrowMethod --
4111  *
4112  *	Retrieves the next row from a result set.
4113  *
4114  * Usage:
4115  *	$resultSet nextrow ?-as lists|dicts? ?--? variableName
4116  *
4117  * Options:
4118  *	-as	Selects the desired form for returning the results.
4119  *
4120  * Parameters:
4121  *	variableName -- Variable in which the results are to be returned
4122  *
4123  * Results:
4124  *	Returns a standard Tcl result.  The interpreter result is 1 if there
4125  *	are more rows remaining, and 0 if no more rows remain.
4126  *
4127  * Side effects:
4128  *	Stores in the given variable either a list or a dictionary
4129  *	containing one row of the result set.
4130  *
4131  *-----------------------------------------------------------------------------
4132  */
4133 
4134 static int
ResultSetNextrowMethod(ClientData clientData,Tcl_Interp * interp,Tcl_ObjectContext context,int objc,Tcl_Obj * const objv[])4135 ResultSetNextrowMethod(
4136     ClientData clientData,	/* 1 if lists are to be returned, 0 if
4137 				 * dicts are to be returned */
4138     Tcl_Interp* interp,		/* Tcl interpreter */
4139     Tcl_ObjectContext context,	/* Object context  */
4140     int objc, 			/* Parameter count */
4141     Tcl_Obj *const objv[]	/* Parameter vector */
4142 ) {
4143 
4144     int lists = PTR2INT(clientData);
4145 				/* Flag == 1 if lists are to be returned,
4146 				 * 0 if dicts are to be returned */
4147 
4148     Tcl_Object thisObject = Tcl_ObjectContextObject(context);
4149 				/* The current result set object */
4150     ResultSetData* rdata = (ResultSetData*)
4151 	Tcl_ObjectGetMetadata(thisObject, &resultSetDataType);
4152 				/* Data pertaining to the current result set */
4153     StatementData* sdata = (StatementData*) rdata->sdata;
4154 				/* Statement that yielded the result set */
4155     ConnectionData* cdata = (ConnectionData*) sdata->cdata;
4156 				/* Connection that opened the statement */
4157     PerInterpData* pidata = (PerInterpData*) cdata->pidata;
4158 				/* Per interpreter data */
4159     Tcl_Obj** literals = pidata->literals;
4160 				/* Literal pool */
4161 
4162     int nColumns;		/* Number of columns in the result set */
4163     Tcl_Obj* colName;		/* Name of the current column */
4164     Tcl_Obj* resultRow;		/* Row of the result set under construction */
4165 
4166     Tcl_Obj* colObj;		/* Column obtained from the row */
4167     SQLRETURN rc;		/* Return code from ODBC operations */
4168     int status = TCL_ERROR;	/* Status return from this command */
4169 
4170     int i;
4171 
4172     if (objc != 3) {
4173 	Tcl_WrongNumArgs(interp, 2, objv, "varName");
4174 	return TCL_ERROR;
4175     }
4176 
4177     /* Extract the column information for the result set. */
4178 
4179     if (rdata->resultColNames == NULL) {
4180 	if (GetResultSetDescription(interp, rdata) != TCL_OK) {
4181 	    return TCL_ERROR;
4182 	}
4183     }
4184     Tcl_ListObjLength(NULL, rdata->resultColNames, &nColumns);
4185     if (nColumns == 0) {
4186 	Tcl_SetObjResult(interp, literals[LIT_0]);
4187 	return TCL_OK;
4188     }
4189 
4190     /* Advance to the next row of the result set */
4191 
4192     rc = SQLFetch(rdata->hStmt);
4193     if (rc == SQL_NO_DATA) {
4194 	Tcl_SetObjResult(interp, literals[LIT_0]);
4195 	return TCL_OK;
4196     } else if (!SQL_SUCCEEDED(rc)) {
4197 	TransferSQLError(interp, SQL_HANDLE_STMT, rdata->hStmt,
4198 			 "(fetching the next row of the result set)");
4199 	return TCL_ERROR;
4200     }
4201 
4202     /* Walk through the current row, storing data for each column */
4203 
4204     resultRow = Tcl_NewObj();
4205     Tcl_IncrRefCount(resultRow);
4206     for (i = 0; i < nColumns; ++i) {
4207 	if (GetCell(rdata, interp, i, &colObj) != TCL_OK) {
4208 	    goto cleanup;
4209 	}
4210 
4211 	if (lists) {
4212 	    if (colObj == NULL) {
4213 		colObj = Tcl_NewObj();
4214 	    }
4215 	    Tcl_ListObjAppendElement(NULL, resultRow, colObj);
4216 	} else {
4217 	    if (colObj != NULL) {
4218 		Tcl_ListObjIndex(NULL, rdata->resultColNames, i, &colName);
4219 		Tcl_DictObjPut(NULL, resultRow, colName, colObj);
4220 	    }
4221 	}
4222     }
4223 
4224     /* Save the row in the given variable */
4225 
4226     if (Tcl_SetVar2Ex(interp, Tcl_GetString(objv[2]), NULL,
4227 		      resultRow, TCL_LEAVE_ERR_MSG) == NULL) {
4228 	goto cleanup;
4229     }
4230 
4231     Tcl_SetObjResult(interp, literals[LIT_1]);
4232     status = TCL_OK;
4233 
4234  cleanup:
4235     Tcl_DecrRefCount(resultRow);
4236     return status;
4237 }
4238 
4239 /*
4240  *-----------------------------------------------------------------------------
4241  *
4242  * GetCell --
4243  *
4244  *	Service procedure to retrieve a single column in a row of a result
4245  *	set.
4246  *
4247  * Results:
4248  *	Returns a standard Tcl result.
4249  *
4250  * Side effects:
4251  *	If the result is TCL_OK, the column value is stored in *colObjPtr,
4252  *	with a zero refcount. (If the column value is NULL, NULL is stored.)
4253  *	If the result is TCL_ERROR, *colObjPtr is left alone, but an error
4254  *	message is stored in the interpreter result.
4255  *
4256  *-----------------------------------------------------------------------------
4257  */
4258 
4259 static int
GetCell(ResultSetData * rdata,Tcl_Interp * interp,int i,Tcl_Obj ** colObjPtr)4260 GetCell(
4261     ResultSetData* rdata,	/* Instance data for the result set */
4262     Tcl_Interp* interp,		/* Tcl interpreter */
4263     int i,			/* Column position */
4264     Tcl_Obj** colObjPtr		/* Returned: Tcl_Obj containing the content
4265 				 * or NULL */
4266 ) {
4267 
4268 #define BUFSIZE 256
4269     StatementData* sdata = rdata->sdata;
4270     ConnectionData* cdata = sdata->cdata;
4271     SQLSMALLINT dataType;	/* Type of character data to retrieve */
4272     SQLWCHAR colWBuf[(BUFSIZE+1)*2];
4273 				/* Buffer to hold the string value of a
4274 				 * column */
4275     SQLCHAR* colBuf = (SQLCHAR*) colWBuf;
4276     SQLCHAR* colPtr = colBuf;	/* Pointer to the current allocated buffer
4277 				 * (which may have grown) */
4278     SQLLEN colAllocLen = BUFSIZE * sizeofSQLWCHAR;
4279 				/* Current allocated size of the buffer,
4280 				 * in bytes */
4281     SQLLEN colLen;		/* Actual size of the return value, in bytes */
4282     SQLINTEGER colLong;		/* Integer value of the column */
4283     SQLBIGINT colWide;		/* Wide-integer value of the column */
4284     SQLDOUBLE colDouble;	/* Double value of the column */
4285     Tcl_DString colDS;		/* Column expressed as a Tcl_DString */
4286     Tcl_Obj* colObj;		/* Column expressed as a Tcl_Obj */
4287     SQLRETURN rc;		/* ODBC result code */
4288     int retry;			/* Flag that the last ODBC operation should
4289 				 * be retried */
4290     SQLINTEGER offset;		/* Offset in the buffer for retrying large
4291 				 * object operations */
4292 
4293     colObj = NULL;
4294     *colObjPtr = NULL;
4295     switch(rdata->results[i].dataType) {
4296 	/* TODO: Need to return binary data as byte arrays. */
4297 
4298     case SQL_NUMERIC:
4299     case SQL_DECIMAL:
4300 
4301 	/*
4302 	 * A generic 'numeric' type may fit in an int, wide,
4303 	 * or double, and gets converted specially if it does.
4304 	 */
4305 
4306 	if (rdata->results[i].scale == 0) {
4307 	    if (rdata->results[i].precision < 10) {
4308 		goto convertLong;
4309 	    } else if (rdata->results[i].precision < 19
4310 		       && (cdata->flags & CONNECTION_FLAG_HAS_BIGINT)) {
4311 		goto convertWide;
4312 	    } else {
4313 		/*
4314 		 * It is tempting to convert wider integers as bignums,
4315 		 * but Tcl does not yet export its copy of libtommath
4316 		 * into the public API.
4317 		 */
4318 		goto convertUnknown;
4319 	    }
4320 	} else if (rdata->results[i].precision <= 15) {
4321 	    goto convertDouble;
4322 	} else {
4323 	    goto convertUnknown;
4324 	}
4325 
4326     case SQL_BIGINT:
4327     convertWide:
4328 	/* A wide integer */
4329 	colLen = sizeof(colWide); colWide = 0;
4330 	rc = SQLGetData(rdata->hStmt, i+1, SQL_C_SBIGINT,
4331 			(SQLPOINTER) &colWide, sizeof(colWide), &colLen);
4332 	if (!SQL_SUCCEEDED(rc)) {
4333 	    char info[80];
4334 	    sprintf(info, "(retrieving result set column #%d)\n", i+1);
4335 	    TransferSQLError(interp, SQL_HANDLE_STMT, rdata->hStmt, info);
4336 	    return TCL_ERROR;
4337 	}
4338 	if (colLen != SQL_NULL_DATA && colLen != SQL_NO_TOTAL) {
4339 	    colObj = Tcl_NewWideIntObj((Tcl_WideInt)colWide);
4340 	}
4341 	break;
4342 
4343     case SQL_BIT:
4344     case SQL_INTEGER:
4345     case SQL_SMALLINT:
4346     case SQL_TINYINT:
4347     convertLong:
4348 	/* An integer no larger than 'long' */
4349 	colLen = sizeof(colLong); colLong = 0;
4350 	rc = SQLGetData(rdata->hStmt, i+1, SQL_C_SLONG,
4351 			(SQLPOINTER) &colLong, sizeof(colLong), &colLen);
4352 	if (!SQL_SUCCEEDED(rc)) {
4353 	    char info[80];
4354 	    sprintf(info, "(retrieving result set column #%d)\n", i+1);
4355 	    TransferSQLError(interp, SQL_HANDLE_STMT, rdata->hStmt, info);
4356 	    ckfree(info);
4357 	    return TCL_ERROR;
4358 	}
4359 	if (colLen != SQL_NULL_DATA && colLen != SQL_NO_TOTAL) {
4360 	    colObj = Tcl_NewWideIntObj(colLong);
4361 	}
4362 	break;
4363 
4364     case SQL_FLOAT:
4365 	/*
4366 	 * A 'float' is converted to a 'double' if it fits;
4367 	 * to a string, otherwise.
4368 	 */
4369 	if (rdata->results[i].precision <= 53) {
4370 	    goto convertDouble;
4371 	} else {
4372 	    goto convertUnknown;
4373 	}
4374 
4375     case SQL_REAL:
4376     case SQL_DOUBLE:
4377     convertDouble:
4378 	/*
4379 	 * A single- or double-precision floating point number.
4380 	 * Reals are widened to doubles.
4381 	 */
4382 	colLen = sizeof(colDouble); colDouble = 0.0;
4383 	rc = SQLGetData(rdata->hStmt, i+1, SQL_C_DOUBLE,
4384 			(SQLPOINTER) &colDouble, sizeof(colDouble),
4385 			&colLen);
4386 	if (!SQL_SUCCEEDED(rc)) {
4387 	    char info[80];
4388 	    sprintf(info, "(retrieving result set column #%d)\n", i+1);
4389 	    TransferSQLError(interp, SQL_HANDLE_STMT, rdata->hStmt, info);
4390 	    ckfree(info);
4391 	    return TCL_ERROR;
4392 	}
4393 	if (colLen != SQL_NULL_DATA && colLen != SQL_NO_TOTAL) {
4394 	    colObj = Tcl_NewDoubleObj(colDouble);
4395 	}
4396 	break;
4397 
4398     case SQL_CHAR:
4399     case SQL_VARCHAR:
4400     case SQL_LONGVARCHAR:
4401 	dataType = SQL_C_CHAR;
4402 	goto convertString;
4403 
4404     case SQL_WCHAR:
4405     case SQL_WVARCHAR:
4406     case SQL_WLONGVARCHAR:
4407 	dataType = SQL_C_WCHAR;
4408 	goto convertString;
4409 
4410     case SQL_BINARY:
4411     case SQL_VARBINARY:
4412     case SQL_LONGVARBINARY:
4413 	dataType = SQL_C_BINARY;
4414 	goto convertString;
4415 
4416     default:
4417     convertUnknown:
4418 	if (cdata->flags & CONNECTION_FLAG_HAS_WVARCHAR) {
4419 	    dataType = SQL_C_WCHAR;
4420 	} else {
4421 	    dataType = SQL_C_CHAR;
4422 	}
4423 	goto convertString;
4424 
4425     convertString:
4426 	/* Anything else is converted as a string */
4427 	offset = 0;
4428 	retry = 0;
4429 	do {
4430 	    retry = 0;
4431 	    /*
4432 	     * It's possible that SQLGetData won't update colLen if
4433 	     * SQL_ERROR is returned. Store a background of zero so
4434 	     * that it's always initialized.
4435 	     */
4436 	    colLen = 0;
4437 
4438 	    /* Try to get the string */
4439 
4440 	    rc = SQLGetData(rdata->hStmt, i+1, dataType,
4441 			    (SQLPOINTER) (((char*)colPtr)+offset),
4442 			    colAllocLen - offset,
4443 			    &colLen);
4444 	    if (rc == SQL_SUCCESS_WITH_INFO
4445 		&& SQLStateIs(SQL_HANDLE_STMT, rdata->hStmt, "01004")) {
4446 		/*
4447 		 * The requested buffer was too small to hold the
4448 		 * data.
4449 		 */
4450 		offset = colAllocLen;
4451 		if (dataType == SQL_C_BINARY) {
4452 		    /* no NULL terminator */
4453 		} else if (dataType == SQL_C_CHAR) {
4454 		    --offset;
4455 		} else {
4456 		    offset -= sizeofSQLWCHAR;
4457 		}
4458 		if (colLen == SQL_NO_TOTAL) {
4459 		    /*
4460 		     * The driver wouldn't tell us how much space was
4461 		     * needed, but we got a full bufferload (less the
4462 		     * terminating NULL character)
4463 		     */
4464 		    colAllocLen = 2 * colAllocLen;
4465 		} else {
4466 		    colAllocLen += colLen;
4467 		}
4468 		if (colPtr == colBuf) {
4469 		    colPtr = (SQLCHAR*) ckalloc(colAllocLen + sizeofSQLWCHAR);
4470 		    memcpy(colPtr, colBuf, BUFSIZE * sizeofSQLWCHAR);
4471 		} else {
4472 		    colPtr = (SQLCHAR*)
4473 			ckrealloc((char*)colPtr, colAllocLen + sizeofSQLWCHAR);
4474 		}
4475 		retry = 1;
4476 	    }
4477 	} while (retry);
4478 	if (!SQL_SUCCEEDED(rc)) {
4479 	    char info[80];
4480 	    sprintf(info, "(retrieving result set column #%d)\n", i+1);
4481 	    TransferSQLError(interp, SQL_HANDLE_STMT, rdata->hStmt, info);
4482 	    if (colPtr != colBuf) {
4483 		ckfree((char*) colPtr);
4484 	    }
4485 	    return TCL_ERROR;
4486 	}
4487 	if (colLen >= 0) {
4488 	    Tcl_DStringInit(&colDS);
4489 	    if (dataType == SQL_C_BINARY) {
4490 		colObj = Tcl_NewByteArrayObj((const unsigned char*) colPtr,
4491 					     (int) (colLen + offset));
4492 	    } else {
4493 		if (dataType == SQL_C_CHAR) {
4494 		    Tcl_ExternalToUtfDString(NULL, (char*) colPtr,
4495 					     (int) (colLen + offset),
4496 					     &colDS);
4497 		} else {
4498 		    DStringAppendWChars(&colDS, (SQLWCHAR*) colPtr,
4499 					(int)((colLen + offset)
4500 					      / sizeofSQLWCHAR));
4501 		}
4502 		colObj = Tcl_NewStringObj(Tcl_DStringValue(&colDS),
4503 					  Tcl_DStringLength(&colDS));
4504 		Tcl_DStringFree(&colDS);
4505 	    }
4506 	}
4507 	if (colPtr != colBuf) {
4508 	    ckfree((char*) colPtr);
4509 	}
4510 	break;
4511 
4512     } /* end of switch */
4513 
4514     *colObjPtr = colObj;
4515     return TCL_OK;
4516 }
4517 
4518 /*
4519  *-----------------------------------------------------------------------------
4520  *
4521  * ResultSetRowcountMethod --
4522  *
4523  *	Returns (if known) the number of rows affected by an ODBC statement.
4524  *
4525  * Usage:
4526  *	$resultSet rowcount
4527  *
4528  * Results:
4529  *	Returns a standard Tcl result giving the number of affected rows.
4530  *
4531  *-----------------------------------------------------------------------------
4532  */
4533 
4534 static int
ResultSetRowcountMethod(ClientData dummy,Tcl_Interp * interp,Tcl_ObjectContext context,int objc,Tcl_Obj * const objv[])4535 ResultSetRowcountMethod(
4536     ClientData dummy,	/* Not used */
4537     Tcl_Interp* interp,		/* Tcl interpreter */
4538     Tcl_ObjectContext context,	/* Object context  */
4539     int objc, 			/* Parameter count */
4540     Tcl_Obj *const objv[]	/* Parameter vector */
4541 ) {
4542     Tcl_Object thisObject = Tcl_ObjectContextObject(context);
4543 				/* The current result set object */
4544     ResultSetData* rdata = (ResultSetData*)
4545 	Tcl_ObjectGetMetadata(thisObject, &resultSetDataType);
4546 				/* Data pertaining to the current result set */
4547     (void)dummy;
4548 
4549     if (objc != 2) {
4550 	Tcl_WrongNumArgs(interp, 2, objv, "");
4551 	return TCL_ERROR;
4552     }
4553     Tcl_SetObjResult(interp, Tcl_NewWideIntObj(rdata->rowCount));
4554     return TCL_OK;
4555 }
4556 
4557 /*
4558  *-----------------------------------------------------------------------------
4559  *
4560  * DeleteResultSetMetadata, DeleteResultSet --
4561  *
4562  *	Cleans up when an ODBC result set is no longer required.
4563  *
4564  * Side effects:
4565  *	Frees all resources associated with the result set.
4566  *
4567  *-----------------------------------------------------------------------------
4568  */
4569 
4570 static void
DeleteResultSetMetadata(ClientData clientData)4571 DeleteResultSetMetadata(
4572     ClientData clientData	/* Instance data for the connection */
4573 ) {
4574     DecrResultSetRefCount((ResultSetData*)clientData);
4575 }
4576 static void
DeleteResultSet(ResultSetData * rdata)4577 DeleteResultSet(
4578     ResultSetData* rdata	/* Metadata for the result set */
4579 ) {
4580     StatementData* sdata = rdata->sdata;
4581     FreeBoundParameters(rdata);
4582     if (rdata->hStmt != NULL) {
4583 	if (rdata->hStmt != sdata->hStmt) {
4584 	    SQLFreeHandle(SQL_HANDLE_STMT, rdata->hStmt);
4585 	} else {
4586 	    SQLCloseCursor(rdata->hStmt);
4587 	    sdata->flags &= ~STATEMENT_FLAG_HSTMT_BUSY;
4588 	}
4589     }
4590     DeleteResultSetDescription(rdata);
4591     DecrStatementRefCount(rdata->sdata);
4592     ckfree((char*)rdata);
4593 }
4594 static void
DeleteResultSetDescription(ResultSetData * rdata)4595 DeleteResultSetDescription(
4596     ResultSetData* rdata	/* Metadata for the result set */
4597 ) {
4598     if (rdata->resultColNames != NULL) {
4599 	Tcl_DecrRefCount(rdata->resultColNames);
4600 	rdata->resultColNames = NULL;
4601     }
4602     if (rdata->results != NULL) {
4603 	ckfree((char*) (rdata->results));
4604 	rdata->results = NULL;
4605     }
4606 }
4607 
4608 
4609 /*
4610  *-----------------------------------------------------------------------------
4611  *
4612  * CloneResultSet --
4613  *
4614  *	Attempts to clone an ODBC result set's metadata.
4615  *
4616  * Results:
4617  *	Returns the new metadata
4618  *
4619  * At present, we don't attempt to clone result sets - it's not obvious
4620  * that such an action would ever even make sense.  Instead, we throw an
4621  * error.
4622  *
4623  *-----------------------------------------------------------------------------
4624  */
4625 
4626 static int
CloneResultSet(Tcl_Interp * interp,ClientData metadata,ClientData * newMetaData)4627 CloneResultSet(
4628     Tcl_Interp* interp,		/* Tcl interpreter for error reporting */
4629     ClientData metadata,	/* Metadata to be cloned */
4630     ClientData* newMetaData	/* Where to put the cloned metadata */
4631 ) {
4632     (void)metadata;
4633     (void)newMetaData;
4634 
4635     Tcl_SetObjResult(interp,
4636 		     Tcl_NewStringObj("ODBC result sets are not clonable", -1));
4637     return TCL_ERROR;
4638 }
4639 
4640 /*
4641  *-----------------------------------------------------------------------------
4642  *
4643  * FreeBoundParameters --
4644  *
4645  *	Frees the bound parameters in a result set after it has been executed
4646  *	or when an error prevents its execution
4647  *
4648  *-----------------------------------------------------------------------------
4649  */
4650 
4651 static void
FreeBoundParameters(ResultSetData * rdata)4652 FreeBoundParameters(
4653     ResultSetData* rdata	/* Result set being abandoned */
4654 ) {
4655     int nParams;
4656     int i;
4657     if (rdata->bindStrings != NULL) {
4658 	Tcl_ListObjLength(NULL, rdata->sdata->subVars, &nParams);
4659 	for (i = 0; i < nParams; ++i) {
4660 	    if (rdata->bindStrings[i] != NULL) {
4661 		ckfree((char*) rdata->bindStrings[i]);
4662 	    }
4663 	}
4664 	ckfree((char*) rdata->bindStrings);
4665 	ckfree((char*) rdata->bindStringLengths);
4666 	rdata->bindStrings = NULL;
4667     }
4668 }
4669 
4670 /*
4671  *-----------------------------------------------------------------------------
4672  *
4673  * Datasources_ObjCmd --
4674  *
4675  *	Enumerates the ODBC data sources.
4676  *
4677  * Usage:
4678  *
4679  *	tdbc::odbc::datasources ?-system | -user?
4680  *
4681  * Results:
4682  *	Returns a dictionary whose keys are the names of data sources and
4683  *	whose values are data source descriptions.
4684  *
4685  * The -system flag restricts the data sources to system data sources;
4686  * the -user flag to user data sources. If no flag is specified, both types
4687  * are returned.
4688  *
4689  *-----------------------------------------------------------------------------
4690  */
4691 
4692 static int
DatasourcesObjCmd(ClientData clientData,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])4693 DatasourcesObjCmd(
4694     ClientData clientData,	/* Opaque pointer to per-interp data */
4695     Tcl_Interp* interp,		/* Tcl interpreter */
4696     int objc,			/* Parameter count */
4697     Tcl_Obj *const objv[]	/* Parameter vector */
4698 ) {
4699     PerInterpData* pidata = (PerInterpData*) clientData;
4700     SQLSMALLINT initDirection = SQL_FETCH_FIRST;
4701     SQLSMALLINT direction;
4702     static const struct flag {
4703 	const char* name;
4704 	SQLSMALLINT value;
4705     } flags[] = {
4706 	{ "-system", SQL_FETCH_FIRST_SYSTEM },
4707 	{ "-user", SQL_FETCH_FIRST_USER },
4708 	{ NULL, 0 }
4709     };
4710     int flagIndex;
4711     SQLRETURN rc;		/* SQL result code */
4712     SQLWCHAR serverName[(SQL_MAX_DSN_LENGTH+1)*2];
4713 				/* Data source name */
4714     SQLSMALLINT serverNameLen;	/* Length of the DSN */
4715     SQLWCHAR *description;	/* Data source descroption */
4716     SQLSMALLINT descLen;	/* Length of the description */
4717     SQLSMALLINT descAllocLen;	/* Allocated size of the description */
4718     SQLSMALLINT descLenNeeded;	/* Length needed for the description */
4719     Tcl_Obj* retval;		/* Return value */
4720     Tcl_DString nameDS;		/* Buffer for a name or description */
4721     Tcl_Obj* nameObj;		/* Name or description as a Tcl object */
4722     int finished;		/* Flag == 1 if a complete list of data
4723 				 * sources has been constructed */
4724     int status = TCL_OK;	/* Status return from this call */
4725 
4726     /* Get the argument */
4727 
4728     if (objc > 2) {
4729 	Tcl_WrongNumArgs(interp, 1, objv, "?-system|-user?");
4730 	return TCL_ERROR;
4731     }
4732     if (objc == 2) {
4733 	if (Tcl_GetIndexFromObjStruct(interp, objv[1], (const void*) flags,
4734 				      sizeof(struct flag),
4735 				      "option", 0, &flagIndex) != TCL_OK) {
4736 	    return TCL_ERROR;
4737 	}
4738 	initDirection = flags[flagIndex].value;
4739     }
4740 
4741     /* Allocate memory */
4742 
4743     retval = Tcl_NewObj();
4744     Tcl_IncrRefCount(retval);
4745     descLenNeeded = 32;
4746     finished = 0;
4747 
4748     while (!finished) {
4749 
4750 	direction = initDirection;
4751 	finished = 1;
4752 	descAllocLen = descLenNeeded;
4753 	description = (SQLWCHAR*)
4754 	    ckalloc(sizeofSQLWCHAR * (descAllocLen + 1));
4755 	Tcl_SetListObj(retval, 0, NULL);
4756 
4757 	/* Enumerate the data sources */
4758 
4759 	while (1) {
4760 	    rc = SQLDataSourcesW(pidata->hEnv, direction, serverName,
4761 				 SQL_MAX_DSN_LENGTH + 1, &serverNameLen,
4762 				 description, descAllocLen, &descLen);
4763 	    direction = SQL_FETCH_NEXT;
4764 
4765 	    if (rc == SQL_SUCCESS_WITH_INFO && descLen > descLenNeeded) {
4766 
4767 		/* The description buffer wasn't big enough. */
4768 
4769 		descLenNeeded = 2 * descLen;
4770 		finished = 0;
4771 		break;
4772 
4773 	    } else if (SQL_SUCCEEDED(rc)) {
4774 
4775 		/* Got a data source; add key and value to the dictionary */
4776 
4777 		Tcl_DStringInit(&nameDS);
4778 		DStringAppendWChars(&nameDS, serverName, serverNameLen);
4779 		nameObj = Tcl_NewStringObj(Tcl_DStringValue(&nameDS),
4780 					   Tcl_DStringLength(&nameDS));
4781 		Tcl_ListObjAppendElement(NULL, retval, nameObj);
4782 		Tcl_DStringFree(&nameDS);
4783 
4784 		Tcl_DStringInit(&nameDS);
4785 		DStringAppendWChars(&nameDS, description, descLen);
4786 		nameObj = Tcl_NewStringObj(Tcl_DStringValue(&nameDS),
4787 					   Tcl_DStringLength(&nameDS));
4788 		Tcl_ListObjAppendElement(NULL, retval, nameObj);
4789 		Tcl_DStringFree(&nameDS);
4790 
4791 	    } else if (rc == SQL_NO_DATA) {
4792 
4793 		/* End of data sources */
4794 
4795 		if (finished) {
4796 		    Tcl_SetObjResult(interp, retval);
4797 		    status = TCL_OK;
4798 		}
4799 		break;
4800 
4801 	    } else {
4802 
4803 		/* Anything else is an error */
4804 
4805 		TransferSQLError(interp, SQL_HANDLE_ENV, pidata->hEnv,
4806 				 "(retrieving data source names)");
4807 		status = TCL_ERROR;
4808 		finished = 1;
4809 		break;
4810 	    }
4811 	}
4812 
4813 	ckfree((char*) description);
4814     }
4815     Tcl_DecrRefCount(retval);
4816 
4817     return status;
4818 }
4819 
4820 /*
4821  *-----------------------------------------------------------------------------
4822  *
4823  * Drivers_ObjCmd --
4824  *
4825  *	Enumerates the ODBC drivers.
4826  *
4827  * Usage:
4828  *
4829  *	tdbc::odbc::drivers
4830  *
4831  * Results:
4832  *	Returns a dictionary whose keys are the names of drivers and
4833  *	whose values are lists of attributes
4834  *
4835  *-----------------------------------------------------------------------------
4836  */
4837 
4838 static int
DriversObjCmd(ClientData clientData,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])4839 DriversObjCmd(
4840     ClientData clientData,	/* Opaque pointer to per-interp data */
4841     Tcl_Interp* interp,		/* Tcl interpreter */
4842     int objc,			/* Parameter count */
4843     Tcl_Obj *const objv[]	/* Parameter vector */
4844 ) {
4845     PerInterpData* pidata = (PerInterpData*) clientData;
4846     SQLSMALLINT direction;
4847     SQLRETURN rc;		/* SQL result code */
4848     SQLWCHAR *driver;		/* Driver name */
4849     SQLSMALLINT driverLen = 0;	/* Length of the driver name */
4850     SQLSMALLINT driverAllocLen; /* Allocated size of the driver name */
4851     SQLSMALLINT driverLenNeeded; /* Required size of the driver name */
4852     SQLWCHAR *attributes;	/* Driver attributes */
4853     SQLSMALLINT attrLen = 0;	/* Length of the driver attributes */
4854     SQLSMALLINT attrAllocLen;	/* Allocated size of the driver attributes */
4855     SQLSMALLINT attrLenNeeded;	/* Length needed for the driver attributes */
4856     Tcl_Obj* retval;		/* Return value */
4857     Tcl_Obj* attrObj;		/* Tcl object to hold driver attribute list */
4858     Tcl_DString nameDS;		/* Buffer for a name or attribute */
4859     Tcl_Obj* nameObj;		/* Name or attribute as a Tcl object */
4860     int finished;		/* Flag == 1 if a complete list of drivers
4861 				 * has been constructed */
4862     int status = TCL_OK;	/* Status return from this call */
4863     int i, j;
4864 
4865     /* Get the argument */
4866 
4867     if (objc > 1) {
4868 	Tcl_WrongNumArgs(interp, 1, objv, "");
4869 	return TCL_ERROR;
4870     }
4871 
4872     /* Allocate memory */
4873 
4874     retval = Tcl_NewObj();
4875     Tcl_IncrRefCount(retval);
4876     driverLenNeeded = 32;
4877     attrLenNeeded = 32;
4878     finished = 0;
4879 
4880     while (!finished) {
4881 
4882 	finished = 1;
4883 	driverAllocLen = driverLenNeeded;
4884 	driver = (SQLWCHAR*)
4885 	    ckalloc(sizeofSQLWCHAR * (driverAllocLen + 1));
4886 	*driver = 0;
4887 	attrAllocLen = attrLenNeeded;
4888 	attributes = (SQLWCHAR*)
4889 	    ckalloc(sizeofSQLWCHAR * (attrAllocLen + 1));
4890 	*attributes = 0;
4891 	Tcl_SetListObj(retval, 0, NULL);
4892 	direction = SQL_FETCH_FIRST;
4893 
4894 	/* Enumerate the data sources */
4895 
4896 	while (1) {
4897 	    rc = SQLDriversW(pidata->hEnv, direction, driver,
4898 			     driverAllocLen, &driverLen,
4899 			     attributes, attrAllocLen, &attrLen);
4900 	    direction = SQL_FETCH_NEXT;
4901 
4902 	    if (rc == SQL_SUCCESS_WITH_INFO && driverLen > driverLenNeeded) {
4903 
4904 		/* The description buffer wasn't big enough. */
4905 
4906 		driverLenNeeded = 2 * driverLen;
4907 		finished = 0;
4908 		break;
4909 	    }
4910 	    if (rc == SQL_SUCCESS_WITH_INFO && attrLen > attrLenNeeded) {
4911 
4912 		/* The attributes buffer wasn't big enough. */
4913 
4914 		attrLenNeeded = 2 * attrLen;
4915 		finished = 0;
4916 		break;
4917 	    }
4918 
4919 	    if (finished) {
4920 		if (SQL_SUCCEEDED(rc)) {
4921 
4922 		    /* Got a data source; add key and value to the dictionary */
4923 
4924 		    Tcl_DStringInit(&nameDS);
4925 		    DStringAppendWChars(&nameDS, driver, driverLen);
4926 		    nameObj = Tcl_NewStringObj(Tcl_DStringValue(&nameDS),
4927 					       Tcl_DStringLength(&nameDS));
4928 		    Tcl_ListObjAppendElement(NULL, retval, nameObj);
4929 		    Tcl_DStringFree(&nameDS);
4930 
4931 		    /*
4932 		     * Attributes are a set of U+0000-terminated
4933 		     * strings, ending with an extra U+0000
4934 		     */
4935 		    attrObj = Tcl_NewObj();
4936 		    for (i = 0; attributes[i] != 0; ) {
4937 			for (j = i; attributes[j] != 0; ++j) {
4938 			    /* do nothing */
4939 			}
4940 			Tcl_DStringInit(&nameDS);
4941 			DStringAppendWChars(&nameDS, attributes+i, j-i);
4942 			nameObj = Tcl_NewStringObj(Tcl_DStringValue(&nameDS),
4943 						   Tcl_DStringLength(&nameDS));
4944 			Tcl_ListObjAppendElement(NULL, attrObj, nameObj);
4945 			Tcl_DStringFree(&nameDS);
4946 			i = j + 1;
4947 		    }
4948 		    Tcl_ListObjAppendElement(NULL, retval, attrObj);
4949 
4950 		} else if (rc == SQL_NO_DATA) {
4951 
4952 		    /* End of data sources */
4953 
4954 		    if (finished) {
4955 			Tcl_SetObjResult(interp, retval);
4956 			status = TCL_OK;
4957 		    }
4958 		    break;
4959 
4960 		} else {
4961 
4962 		    /* Anything else is an error */
4963 
4964 		    TransferSQLError(interp, SQL_HANDLE_ENV, pidata->hEnv,
4965 				     "(retrieving data source names)");
4966 		    status = TCL_ERROR;
4967 		    finished = 1;
4968 		    break;
4969 		}
4970 	    }
4971 	}
4972 	ckfree((char*) driver);
4973 	ckfree((char*) attributes);
4974     }
4975     Tcl_DecrRefCount(retval);
4976 
4977     return status;
4978 }
4979 
4980 /*
4981  *-----------------------------------------------------------------------------
4982  *
4983  * DatasourceObjCmdW --
4984  *
4985  *	Command that does configuration of ODBC data sources when the
4986  *	ODBCCP32 library supports Unicode
4987  *
4988  * Usage:
4989  *	::tdbc::odbc::datasource subcommand driver ?keyword=value?...
4990  *
4991  * Parameters:
4992  *	subcommand - One of 'add', 'add_system', 'configure',
4993  *		'configure_system', 'remove', or 'remove_system'
4994  *	driver - Name of the ODBC driver to use in configuring the data source.
4995  *	keyword=value - Keyword-value pairs as defined by the driver.
4996  *
4997  * Results:
4998  *	Returns a standard Tcl result, which is empty if the operation
4999  *	is successful.
5000  *
5001  *-----------------------------------------------------------------------------
5002  */
5003 
5004 static int
DatasourceObjCmdW(ClientData dummy,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])5005 DatasourceObjCmdW(
5006     ClientData dummy,	/* Unused */
5007     Tcl_Interp* interp,		/* Tcl interpreter */
5008     int objc,			/* Parameter count */
5009     Tcl_Obj *const objv[]	/* Parameter vector */
5010 ) {
5011     static const struct flag {
5012 	const char* name;
5013 	WORD value;
5014     } flags[] = {
5015 	{ "add",		ODBC_ADD_DSN },
5016 	{ "add_system",		ODBC_ADD_SYS_DSN },
5017 	{ "configure",		ODBC_CONFIG_DSN },
5018 	{ "configure_system",	ODBC_CONFIG_SYS_DSN },
5019 	{ "remove",		ODBC_REMOVE_DSN },
5020 	{ "remove_system", 	ODBC_REMOVE_SYS_DSN },
5021 	{ NULL,			0 }
5022     };
5023     int flagIndex;		/* Index of the subcommand */
5024     WCHAR* driverName;		/* Name of the ODBC driver */
5025     WCHAR* attributes;		/* NULL-delimited attribute values */
5026     char errorMessage[SQL_MAX_MESSAGE_LENGTH+1];
5027 				/* Error message from ODBC operations */
5028     size_t driverNameLen;		/* Length of the driver name */
5029     Tcl_Obj* attrObj;		/* NULL-delimited attribute values */
5030     size_t attrLen;		/* Length of the attribute values */
5031     const char* sep;		/* Separator for attribute values */
5032     DWORD errorCode;		/* Error code */
5033     WORD errorMessageLen;	/* Length of the returned error message */
5034     RETCODE errorMessageStatus;	/* Status of the error message formatting */
5035     Tcl_DString retvalDS;	/* Return value */
5036     Tcl_DString errorMessageDS;	/* DString to convert error message
5037 				 * from system encoding */
5038     Tcl_Obj* errorCodeObj;	/* Tcl error code */
5039     int i, j;
5040     BOOL ok;
5041     int status = TCL_OK;
5042     int finished = 0;
5043     (void)dummy;
5044 
5045     /* Check args */
5046 
5047     if (objc < 4) {
5048 	Tcl_WrongNumArgs(interp, 1, objv,
5049 			 "operation driver ?keyword=value?...");
5050 	return TCL_ERROR;
5051     }
5052     if (Tcl_GetIndexFromObjStruct(interp, objv[1], flags, sizeof(struct flag),
5053 				  "operation", 0, &flagIndex) != TCL_OK) {
5054 	return TCL_ERROR;
5055     }
5056 
5057     /* Convert driver name to the appropriate encoding */
5058 
5059     driverName = GetWCharStringFromObj(objv[2], &driverNameLen);
5060 
5061     /*
5062      * Convert driver attributes to the appropriate encoding, separated
5063      * by NUL bytes.
5064      */
5065 
5066     attrObj = Tcl_NewObj();
5067     Tcl_IncrRefCount(attrObj);
5068     sep = "";
5069     for (i = 3; i < objc; ++i) {
5070 	Tcl_AppendToObj(attrObj, sep, -1);
5071 	Tcl_AppendObjToObj(attrObj, objv[i]);
5072 	sep = "\xc0\x80";
5073     }
5074     Tcl_AppendToObj(attrObj, "\xc0\x80", 2);
5075     attributes = GetWCharStringFromObj(attrObj, &attrLen);
5076     Tcl_DecrRefCount(attrObj);
5077 
5078     /*
5079      * Configure the data source
5080      */
5081 
5082     ok = SQLConfigDataSourceW(NULL, flags[flagIndex].value,
5083 			      driverName, attributes);
5084     ckfree((char*) attributes);
5085     ckfree((char*) driverName);
5086 
5087     /* Check the ODBC status return */
5088 
5089     if (!ok) {
5090 	status = TCL_ERROR;
5091 	i = 1;
5092 	sep = "";
5093 	Tcl_DStringInit(&retvalDS);
5094 	errorCodeObj = Tcl_NewStringObj("TDBC ODBC", -1);
5095 	Tcl_IncrRefCount(errorCodeObj);
5096 	finished = 0;
5097 	while (!finished) {
5098 	    errorMessageLen = SQL_MAX_MESSAGE_LENGTH;
5099 	    errorMessageStatus =
5100 		SQLInstallerError(i, &errorCode, errorMessage,
5101 				   SQL_MAX_MESSAGE_LENGTH-1, &errorMessageLen);
5102 	    switch(errorMessageStatus) {
5103 	    case SQL_SUCCESS:
5104 		Tcl_DStringAppend(&retvalDS, sep, -1);
5105 		Tcl_DStringInit(&errorMessageDS);
5106 		Tcl_ExternalToUtfDString(NULL, errorMessage, errorMessageLen,
5107 					 &errorMessageDS);
5108 		Tcl_DStringAppend(&retvalDS,
5109 				  Tcl_DStringValue(&errorMessageDS),
5110 				  Tcl_DStringLength(&errorMessageDS));
5111 		Tcl_DStringFree(&errorMessageDS);
5112 		break;
5113 	    case SQL_NO_DATA:
5114 		break;
5115 	    default:
5116 		Tcl_DStringAppend(&retvalDS, sep, -1);
5117 		Tcl_DStringAppend(&retvalDS, "cannot retrieve error message",
5118 				  -1);
5119 		break;
5120 	    }
5121 	    switch(errorMessageStatus) {
5122 	    case SQL_SUCCESS:
5123 	    case SQL_SUCCESS_WITH_INFO:
5124 		for (j = 0; OdbcErrorCodeNames[j].name != NULL; ++j) {
5125 		    if (OdbcErrorCodeNames[j].value == (int)errorCode) {
5126 			break;
5127 		    }
5128 		}
5129 		if (OdbcErrorCodeNames[j].name == NULL) {
5130 		    Tcl_ListObjAppendElement(NULL, errorCodeObj,
5131 					     Tcl_NewStringObj("?", -1));
5132 		} else {
5133 		    Tcl_ListObjAppendElement(NULL, errorCodeObj,
5134 			Tcl_NewStringObj(OdbcErrorCodeNames[j].name, -1));
5135 		}
5136 		Tcl_ListObjAppendElement(NULL, errorCodeObj,
5137 					 Tcl_NewWideIntObj(errorCode));
5138 		/* FALLTHRU */
5139 	    case SQL_NO_DATA:
5140 	    case SQL_ERROR:
5141 		finished = 1;
5142 		break;
5143 	    }
5144 
5145 	    sep = "\n";
5146 	    ++i;
5147 	}
5148 	Tcl_SetObjResult(interp,
5149 			 Tcl_NewStringObj(Tcl_DStringValue(&retvalDS),
5150 					  Tcl_DStringLength(&retvalDS)));
5151 	Tcl_DStringFree(&retvalDS);
5152 	Tcl_SetObjErrorCode(interp, errorCodeObj);
5153 	Tcl_DecrRefCount(errorCodeObj);
5154     }
5155 
5156     return status;
5157 }
5158 
5159 /*
5160  *-----------------------------------------------------------------------------
5161  *
5162  * Datasource_ObjCmdA --
5163  *
5164  *	Command that does configuration of ODBC data sources when the
5165  *	native ODBCCP32 library does not support Unicode
5166  *
5167  * Usage:
5168  *	::tdbc::odbc::datasource subcommand driver ?keyword=value?...
5169  *
5170  * Parameters:
5171  *	subcommand - One of 'add', 'add_system', 'configure',
5172  *		'configure_system', 'remove', or 'remove_system'
5173  *	driver - Name of the ODBC driver to use in configuring the data source.
5174  *	keyword=value - Keyword-value pairs as defined by the driver.
5175  *
5176  * Results:
5177  *	Returns a standard Tcl result, which is empty if the operation
5178  *	is successful.
5179  *
5180  *-----------------------------------------------------------------------------
5181  */
5182 
5183 static int
DatasourceObjCmdA(ClientData dummy,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])5184 DatasourceObjCmdA(
5185     ClientData dummy,	/* Unused */
5186     Tcl_Interp* interp,		/* Tcl interpreter */
5187     int objc,			/* Parameter count */
5188     Tcl_Obj *const objv[]	/* Parameter vector */
5189 ) {
5190     static const struct flag {
5191 	const char* name;
5192 	WORD value;
5193     } flags[] = {
5194 	{ "add",		ODBC_ADD_DSN },
5195 	{ "add_system",		ODBC_ADD_SYS_DSN },
5196 	{ "configure",		ODBC_CONFIG_DSN },
5197 	{ "configure_system",	ODBC_CONFIG_SYS_DSN },
5198 	{ "remove",		ODBC_REMOVE_DSN },
5199 	{ "remove_system", 	ODBC_REMOVE_SYS_DSN },
5200 	{ NULL,			0 }
5201     };
5202     int flagIndex;		/* Index of the subcommand */
5203     Tcl_DString driverNameDS;
5204     Tcl_DString attributesDS;
5205     char* driverName;		/* Name of the ODBC driver in system
5206 				 * encoding */
5207     char* attributes;		/* Attributes of the data source in
5208 				 * system encoding */
5209     char errorMessage[SQL_MAX_MESSAGE_LENGTH+1];
5210 				/* Error message from ODBC operations */
5211     Tcl_DString errorMessageDS;	/* Error message in UTF-8 */
5212     char* p;
5213     size_t driverNameLen;		/* Length of the driver name */
5214     Tcl_Obj* attrObj;		/* NULL-delimited attribute values */
5215     size_t attrLen;		/* Length of the attribute values */
5216     const char* sep;		/* Separator for attribute values */
5217     DWORD errorCode;		/* Error code */
5218     WORD errorMessageLen;	/* Length of the returned error message */
5219     RETCODE errorMessageStatus;	/* Status of the error message formatting */
5220     Tcl_DString retvalDS;	/* Return value */
5221     Tcl_Obj* errorCodeObj;	/* Tcl error code */
5222     int i, j;
5223     BOOL ok;
5224     int status = TCL_OK;
5225     int finished = 0;
5226     (void)dummy;
5227 
5228     /* Check args */
5229 
5230     if (objc < 4) {
5231 	Tcl_WrongNumArgs(interp, 1, objv,
5232 			 "operation driver ?keyword=value?...");
5233 	return TCL_ERROR;
5234     }
5235     if (Tcl_GetIndexFromObjStruct(interp, objv[1], flags, sizeof(struct flag),
5236 				  "operation", 0, &flagIndex) != TCL_OK) {
5237 	return TCL_ERROR;
5238     }
5239 
5240     /* Convert driver name to the appropriate encoding */
5241 
5242     Tcl_DStringInit(&driverNameDS);
5243     p = Tcl_GetString(objv[2]);
5244     driverNameLen = objv[2]->length;
5245     Tcl_UtfToExternalDString(NULL, p, driverNameLen, &driverNameDS);
5246     driverName = Tcl_DStringValue(&driverNameDS);
5247     driverNameLen = Tcl_DStringLength(&driverNameDS);
5248 
5249     /*
5250      * Convert driver attributes to the appropriate encoding, separated
5251      * by NUL bytes.
5252      */
5253 
5254     attrObj = Tcl_NewObj();
5255     Tcl_IncrRefCount(attrObj);
5256     sep = "";
5257     for (i = 3; i < objc; ++i) {
5258 	Tcl_AppendToObj(attrObj, sep, -1);
5259 	Tcl_AppendObjToObj(attrObj, objv[i]);
5260 	sep = "\xc0\x80";
5261     }
5262     Tcl_AppendToObj(attrObj, "\xc0\x80", 2);
5263     Tcl_DStringInit(&attributesDS);
5264     p = Tcl_GetString(attrObj);
5265     attrLen = attrObj->length;
5266     Tcl_UtfToExternalDString(NULL, p, attrLen, &attributesDS);
5267     attributes = Tcl_DStringValue(&attributesDS);
5268     attrLen = Tcl_DStringLength(&attributesDS);
5269     Tcl_DecrRefCount(attrObj);
5270 
5271     /*
5272      * Configure the data source
5273      */
5274 
5275     ok = SQLConfigDataSource(NULL, flags[flagIndex].value,
5276 			      driverName, attributes);
5277     Tcl_DStringFree(&attributesDS);
5278     Tcl_DStringFree(&driverNameDS);
5279 
5280     /* Check the ODBC status return */
5281 
5282     if (!ok) {
5283 	status = TCL_ERROR;
5284 	i = 1;
5285 	sep = "";
5286 	Tcl_DStringInit(&retvalDS);
5287 	errorCodeObj = Tcl_NewStringObj("TDBC ODBC", -1);
5288 	Tcl_IncrRefCount(errorCodeObj);
5289 	finished = 0;
5290 	while (!finished) {
5291 	    errorMessageLen = SQL_MAX_MESSAGE_LENGTH;
5292 	    errorMessageStatus =
5293 		SQLInstallerError(i, &errorCode, errorMessage,
5294 				  SQL_MAX_MESSAGE_LENGTH-1, &errorMessageLen);
5295 	    switch(errorMessageStatus) {
5296 	    case SQL_SUCCESS:
5297 		Tcl_DStringAppend(&retvalDS, sep, -1);
5298 		Tcl_DStringInit(&errorMessageDS);
5299 		Tcl_ExternalToUtfDString(NULL, errorMessage, errorMessageLen,
5300 					 &errorMessageDS);
5301 		Tcl_DStringAppend(&retvalDS,
5302 				  Tcl_DStringValue(&errorMessageDS),
5303 				  Tcl_DStringLength(&errorMessageDS));
5304 		Tcl_DStringFree(&errorMessageDS);
5305 		break;
5306 	    case SQL_NO_DATA:
5307 		break;
5308 	    default:
5309 		Tcl_DStringAppend(&retvalDS, sep, -1);
5310 		Tcl_DStringAppend(&retvalDS, "cannot retrieve error message",
5311 				  -1);
5312 		break;
5313 	    }
5314 	    switch(errorMessageStatus) {
5315 	    case SQL_SUCCESS:
5316 	    case SQL_SUCCESS_WITH_INFO:
5317 		for (j = 0; OdbcErrorCodeNames[j].name != NULL; ++j) {
5318 		    if (OdbcErrorCodeNames[j].value == (int)errorCode) {
5319 			break;
5320 		    }
5321 		}
5322 		if (OdbcErrorCodeNames[j].name == NULL) {
5323 		    Tcl_ListObjAppendElement(NULL, errorCodeObj,
5324 					     Tcl_NewStringObj("?", -1));
5325 		} else {
5326 		    Tcl_ListObjAppendElement(NULL, errorCodeObj,
5327 			Tcl_NewStringObj(OdbcErrorCodeNames[j].name, -1));
5328 		}
5329 		Tcl_ListObjAppendElement(NULL, errorCodeObj,
5330 					 Tcl_NewWideIntObj(errorCode));
5331 
5332 		/* FALLTHRU */
5333 	    case SQL_NO_DATA:
5334 	    case SQL_ERROR:
5335 		finished = 1;
5336 		break;
5337 	    }
5338 
5339 	    sep = "\n";
5340 	    ++i;
5341 	}
5342 	Tcl_SetObjResult(interp,
5343 			 Tcl_NewStringObj(Tcl_DStringValue(&retvalDS),
5344 					  Tcl_DStringLength(&retvalDS)));
5345 	Tcl_DStringFree(&retvalDS);
5346 	Tcl_SetObjErrorCode(interp, errorCodeObj);
5347 	Tcl_DecrRefCount(errorCodeObj);
5348     }
5349 
5350     return status;
5351 }
5352 
5353 /*
5354  *-----------------------------------------------------------------------------
5355  *
5356  * Tdbcodbc_Init --
5357  *
5358  *	Initializes the TDBC-ODBC bridge when this library is loaded.
5359  *
5360  * Side effects:
5361  *	Creates the ::tdbc::odbc namespace and the commands that reside in it.
5362  *	Initializes the ODBC environment.
5363  *
5364  *-----------------------------------------------------------------------------
5365  */
5366 
5367 #ifdef __cplusplus
5368 extern "C" {
5369 #endif  /* __cplusplus */
5370 DLLEXPORT int
Tdbcodbc_Init(Tcl_Interp * interp)5371 Tdbcodbc_Init(
5372     Tcl_Interp* interp		/* Tcl interpreter */
5373 ) {
5374     SQLHENV hEnv;		/* ODBC environemnt */
5375     PerInterpData* pidata;	/* Per-interpreter data for this package */
5376     Tcl_Obj* nameObj;		/* Name of a class or method being looked up */
5377     Tcl_Object curClassObject;  /* Tcl_Object representing the current class */
5378     Tcl_Class curClass;		/* Tcl_Class representing the current class */
5379     int i;
5380 
5381     /* Require all package dependencies */
5382 
5383     if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) {
5384 	return TCL_ERROR;
5385     }
5386     if (TclOOInitializeStubs(interp, "1.0") == NULL) {
5387 	return TCL_ERROR;
5388     }
5389     if (Tdbc_InitStubs(interp) == NULL) {
5390 	return TCL_ERROR;
5391     }
5392 
5393     /* Provide the current package */
5394 
5395     if (Tcl_PkgProvideEx(interp, "tdbc::odbc", PACKAGE_VERSION, NULL) == TCL_ERROR) {
5396 	return TCL_ERROR;
5397     }
5398 
5399     /* Initialize the ODBC environment */
5400 
5401     hEnv = GetHEnv(interp);
5402     if (hEnv == SQL_NULL_HANDLE) {
5403 	return TCL_ERROR;
5404     }
5405 
5406     /*
5407      * Create per-interpreter data for the package
5408      */
5409 
5410     pidata = (PerInterpData*) ckalloc(sizeof(PerInterpData));
5411     pidata->refCount = 0;
5412     pidata->hEnv = GetHEnv(NULL);
5413     for (i = 0; i < LIT__END; ++i) {
5414 	pidata->literals[i] = Tcl_NewStringObj(LiteralValues[i], -1);
5415 	Tcl_IncrRefCount(pidata->literals[i]);
5416     }
5417 
5418     /*
5419      * Find the connection class, and attach the constructor to
5420      * it. Hold the SQLENV in the method's client data.
5421      */
5422 
5423     nameObj = Tcl_NewStringObj("::tdbc::odbc::connection", -1);
5424     Tcl_IncrRefCount(nameObj);
5425     if ((curClassObject = Tcl_GetObjectFromObj(interp, nameObj)) == NULL) {
5426 	Tcl_DecrRefCount(nameObj);
5427 	return TCL_ERROR;
5428     }
5429     Tcl_DecrRefCount(nameObj);
5430     curClass = Tcl_GetObjectAsClass(curClassObject);
5431     IncrPerInterpRefCount(pidata);
5432     Tcl_ClassSetConstructor(interp, curClass,
5433 			    Tcl_NewMethod(interp, curClass, NULL, 0,
5434 					  &ConnectionConstructorType,
5435 					  (ClientData) pidata));
5436 
5437     /* Attach the other methods to the connection class */
5438 
5439     nameObj = Tcl_NewStringObj("commit", -1);
5440     Tcl_IncrRefCount(nameObj);
5441     Tcl_NewMethod(interp, curClass, nameObj, 1,
5442 		       &ConnectionEndXcnMethodType, (ClientData) SQL_COMMIT);
5443     Tcl_DecrRefCount(nameObj);
5444     nameObj = Tcl_NewStringObj("rollback", -1);
5445     Tcl_IncrRefCount(nameObj);
5446     Tcl_NewMethod(interp, curClass, nameObj, 1,
5447 		       &ConnectionEndXcnMethodType, (ClientData) SQL_ROLLBACK);
5448     Tcl_DecrRefCount(nameObj);
5449     for (i = 0; ConnectionMethods[i] != NULL; ++i) {
5450 	nameObj = Tcl_NewStringObj(ConnectionMethods[i]->name, -1);
5451 	Tcl_IncrRefCount(nameObj);
5452 	Tcl_NewMethod(interp, curClass, nameObj, 1, ConnectionMethods[i],
5453 			   (ClientData) NULL);
5454 	Tcl_DecrRefCount(nameObj);
5455     }
5456 
5457     /* Look up the 'statement' class */
5458 
5459     nameObj = Tcl_NewStringObj("::tdbc::odbc::statement", -1);
5460     Tcl_IncrRefCount(nameObj);
5461     if ((curClassObject = Tcl_GetObjectFromObj(interp, nameObj)) == NULL) {
5462 	Tcl_DecrRefCount(nameObj);
5463 	return TCL_ERROR;
5464     }
5465     Tcl_DecrRefCount(nameObj);
5466     curClass = Tcl_GetObjectAsClass(curClassObject);
5467 
5468     /* Attach the constructor to the 'statement' class */
5469 
5470     Tcl_ClassSetConstructor(interp, curClass,
5471 			    Tcl_NewMethod(interp, curClass, NULL, 1,
5472 					  &StatementConstructorType,
5473 					  (ClientData) NULL));
5474 
5475     /* Attach the methods to the 'statement' class */
5476 
5477     for (i = 0; StatementMethods[i] != NULL; ++i) {
5478 	nameObj = Tcl_NewStringObj(StatementMethods[i]->name, -1);
5479 	Tcl_IncrRefCount(nameObj);
5480 	Tcl_NewMethod(interp, curClass, nameObj, 1, StatementMethods[i],
5481 			   (ClientData) NULL);
5482 	Tcl_DecrRefCount(nameObj);
5483     }
5484 
5485     /* Look up the 'tablesStatement' class */
5486 
5487     nameObj = Tcl_NewStringObj("::tdbc::odbc::tablesStatement", -1);
5488     Tcl_IncrRefCount(nameObj);
5489     if ((curClassObject = Tcl_GetObjectFromObj(interp, nameObj)) == NULL) {
5490 	Tcl_DecrRefCount(nameObj);
5491 	return TCL_ERROR;
5492     }
5493     Tcl_DecrRefCount(nameObj);
5494     curClass = Tcl_GetObjectAsClass(curClassObject);
5495 
5496     /* Attach the constructor to the 'tablesStatement' class */
5497 
5498     Tcl_ClassSetConstructor(interp, curClass,
5499 			    Tcl_NewMethod(interp, curClass, NULL, 1,
5500 					  &TablesStatementConstructorType,
5501 					  (ClientData) NULL));
5502 
5503     /* Look up the 'columnsStatement' class */
5504 
5505     nameObj = Tcl_NewStringObj("::tdbc::odbc::columnsStatement", -1);
5506     Tcl_IncrRefCount(nameObj);
5507     if ((curClassObject = Tcl_GetObjectFromObj(interp, nameObj)) == NULL) {
5508 	Tcl_DecrRefCount(nameObj);
5509 	return TCL_ERROR;
5510     }
5511     Tcl_DecrRefCount(nameObj);
5512     curClass = Tcl_GetObjectAsClass(curClassObject);
5513 
5514     /* Attach the constructor to the 'columnsStatement' class */
5515 
5516     Tcl_ClassSetConstructor(interp, curClass,
5517 			    Tcl_NewMethod(interp, curClass, NULL, 1,
5518 					  &ColumnsStatementConstructorType,
5519 					  (ClientData) NULL));
5520 
5521     /* Look up the 'primarykeysStatement' class */
5522 
5523     nameObj = Tcl_NewStringObj("::tdbc::odbc::primarykeysStatement", -1);
5524     Tcl_IncrRefCount(nameObj);
5525     if ((curClassObject = Tcl_GetObjectFromObj(interp, nameObj)) == NULL) {
5526 	Tcl_DecrRefCount(nameObj);
5527 	return TCL_ERROR;
5528     }
5529     Tcl_DecrRefCount(nameObj);
5530     curClass = Tcl_GetObjectAsClass(curClassObject);
5531 
5532     /* Attach the constructor to the 'primarykeysStatement' class */
5533 
5534     Tcl_ClassSetConstructor(interp, curClass,
5535 			    Tcl_NewMethod(interp, curClass, NULL, 1,
5536 					  &PrimarykeysStatementConstructorType,
5537 					  (ClientData) NULL));
5538 
5539     /* Look up the 'typesStatement' class */
5540 
5541     nameObj = Tcl_NewStringObj("::tdbc::odbc::typesStatement", -1);
5542     Tcl_IncrRefCount(nameObj);
5543     if ((curClassObject = Tcl_GetObjectFromObj(interp, nameObj)) == NULL) {
5544 	Tcl_DecrRefCount(nameObj);
5545 	return TCL_ERROR;
5546     }
5547     Tcl_DecrRefCount(nameObj);
5548     curClass = Tcl_GetObjectAsClass(curClassObject);
5549 
5550     /* Look up the 'foreignkeysStatement' class */
5551 
5552     nameObj = Tcl_NewStringObj("::tdbc::odbc::foreignkeysStatement", -1);
5553     Tcl_IncrRefCount(nameObj);
5554     if ((curClassObject = Tcl_GetObjectFromObj(interp, nameObj)) == NULL) {
5555 	Tcl_DecrRefCount(nameObj);
5556 	return TCL_ERROR;
5557     }
5558     Tcl_DecrRefCount(nameObj);
5559     curClass = Tcl_GetObjectAsClass(curClassObject);
5560 
5561     /* Attach the constructor to the 'foreignkeysStatement' class */
5562 
5563     Tcl_ClassSetConstructor(interp, curClass,
5564 			    Tcl_NewMethod(interp, curClass, NULL, 1,
5565 					  &ForeignkeysStatementConstructorType,
5566 					  (ClientData) NULL));
5567 
5568     /* Look up the 'typesStatement' class */
5569 
5570     nameObj = Tcl_NewStringObj("::tdbc::odbc::typesStatement", -1);
5571     Tcl_IncrRefCount(nameObj);
5572     if ((curClassObject = Tcl_GetObjectFromObj(interp, nameObj)) == NULL) {
5573 	Tcl_DecrRefCount(nameObj);
5574 	return TCL_ERROR;
5575     }
5576     Tcl_DecrRefCount(nameObj);
5577     curClass = Tcl_GetObjectAsClass(curClassObject);
5578 
5579     /* Attach the constructor to the 'typesStatement' class */
5580 
5581     Tcl_ClassSetConstructor(interp, curClass,
5582 			    Tcl_NewMethod(interp, curClass, NULL, 1,
5583 					  &TypesStatementConstructorType,
5584 					  (ClientData) NULL));
5585 
5586     /* Look up the 'resultSet' class */
5587 
5588     nameObj = Tcl_NewStringObj("::tdbc::odbc::resultset", -1);
5589     Tcl_IncrRefCount(nameObj);
5590     if ((curClassObject = Tcl_GetObjectFromObj(interp, nameObj)) == NULL) {
5591 	Tcl_DecrRefCount(nameObj);
5592 	return TCL_ERROR;
5593     }
5594     Tcl_DecrRefCount(nameObj);
5595     curClass = Tcl_GetObjectAsClass(curClassObject);
5596 
5597     /* Attach the constructor to the 'resultSet' class */
5598 
5599     Tcl_ClassSetConstructor(interp, curClass,
5600 			    Tcl_NewMethod(interp, curClass, NULL, 1,
5601 					  &ResultSetConstructorType,
5602 					  (ClientData) NULL));
5603 
5604     /* Attach the methods to the 'resultSet' class */
5605 
5606     for (i = 0; ResultSetMethods[i] != NULL; ++i) {
5607 	nameObj = Tcl_NewStringObj(ResultSetMethods[i]->name, -1);
5608 	Tcl_IncrRefCount(nameObj);
5609 	Tcl_NewMethod(interp, curClass, nameObj, 1, ResultSetMethods[i],
5610 			   (ClientData) NULL);
5611 	Tcl_DecrRefCount(nameObj);
5612     }
5613     nameObj = Tcl_NewStringObj("nextlist", -1);
5614     Tcl_IncrRefCount(nameObj);
5615     Tcl_NewMethod(interp, curClass, nameObj, 1, &ResultSetNextrowMethodType,
5616 		  (ClientData) 1);
5617     Tcl_DecrRefCount(nameObj);
5618     nameObj = Tcl_NewStringObj("nextdict", -1);
5619     Tcl_IncrRefCount(nameObj);
5620     Tcl_NewMethod(interp, curClass, nameObj, 1, &ResultSetNextrowMethodType,
5621 		  (ClientData) 0);
5622     Tcl_DecrRefCount(nameObj);
5623 
5624     IncrPerInterpRefCount(pidata);
5625     Tcl_CreateObjCommand(interp, "tdbc::odbc::datasources",
5626 			 DatasourcesObjCmd, (ClientData) pidata, DeleteCmd);
5627     IncrPerInterpRefCount(pidata);
5628     Tcl_CreateObjCommand(interp, "tdbc::odbc::drivers",
5629 			 DriversObjCmd, (ClientData) pidata, DeleteCmd);
5630 
5631     if (SQLConfigDataSourceW != NULL && SQLInstallerError != NULL) {
5632 	Tcl_CreateObjCommand(interp, "tdbc::odbc::datasource",
5633 			     DatasourceObjCmdW, NULL, NULL);
5634     } else if (SQLConfigDataSource != NULL && SQLInstallerError != NULL) {
5635 	Tcl_CreateObjCommand(interp, "tdbc::odbc::datasource",
5636 			     DatasourceObjCmdA, NULL, NULL);
5637     }
5638 
5639     DismissHEnv();
5640     return TCL_OK;
5641 }
5642 #ifdef __cplusplus
5643 }
5644 #endif  /* __cplusplus */
5645 
5646 /*
5647  *-----------------------------------------------------------------------------
5648  *
5649  * DeletePerInterpData --
5650  *
5651  *	Delete per-interpreter data when the ODBC package is finalized
5652  *
5653  * Side effects:
5654  *	Releases the (presumably last) reference on the environment handle,
5655  *	cleans up the literal pool, and deletes the per-interp data structure.
5656  *
5657  *-----------------------------------------------------------------------------
5658  */
5659 
5660 static void
DeletePerInterpData(PerInterpData * pidata)5661 DeletePerInterpData(
5662     PerInterpData* pidata	/* Data structure to clean up */
5663 ) {
5664     int i;
5665     DismissHEnv();
5666     for (i = 0; i < LIT__END; ++i) {
5667 	Tcl_DecrRefCount(pidata->literals[i]);
5668     }
5669     ckfree((char *) pidata);
5670 }
5671