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, ¶mNames);
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, ¶mIdx) != 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, ¶mNameObj);
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, ¶mValObj)
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 ¶mExternalLen);
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, ¶mLen);
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(¶mExternal);
3878 Tcl_UtfToExternalDString(NULL, paramVal, paramLen,
3879 ¶mExternal);
3880 paramExternalLen = Tcl_DStringLength(¶mExternal);
3881 rdata->bindStrings[nBound] = (SQLCHAR*)
3882 ckalloc(paramExternalLen + 1);
3883 memcpy(rdata->bindStrings[nBound],
3884 Tcl_DStringValue(¶mExternal),
3885 paramExternalLen + 1);
3886 rdata->bindStringLengths[nBound] = paramExternalLen;
3887 Tcl_DStringFree(¶mExternal);
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