comparison CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/tdbcodbc1.1.5/tdbcodbc.tcl @ 68:5028fdace37b

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 16:23:26 -0400
parents
children
comparison
equal deleted inserted replaced
67:0e9998148a16 68:5028fdace37b
1 # tdbcodbc.tcl --
2 #
3 # Class definitions and Tcl-level methods for the tdbc::odbc bridge.
4 #
5 # Copyright (c) 2008 by Kevin B. Kenny
6 # See the file "license.terms" for information on usage and redistribution
7 # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
8 #
9 # RCS: @(#) $Id: tdbcodbc.tcl,v 1.47 2008/02/27 02:08:27 kennykb Exp $
10 #
11 #------------------------------------------------------------------------------
12
13 package require tdbc
14
15 ::namespace eval ::tdbc::odbc {
16
17 namespace export connection datasources drivers
18
19 # Data types that are predefined in ODBC
20
21 variable sqltypes [dict create \
22 1 char \
23 2 numeric \
24 3 decimal \
25 4 integer \
26 5 smallint \
27 6 float \
28 7 real \
29 8 double \
30 9 datetime \
31 12 varchar \
32 91 date \
33 92 time \
34 93 timestamp \
35 -1 longvarchar \
36 -2 binary \
37 -3 varbinary \
38 -4 longvarbinary \
39 -5 bigint \
40 -6 tinyint \
41 -7 bit \
42 -8 wchar \
43 -9 wvarchar \
44 -10 wlongvarchar \
45 -11 guid]
46 }
47
48 #------------------------------------------------------------------------------
49 #
50 # tdbc::odbc::connection --
51 #
52 # Class representing a connection to a database through ODBC.
53 #
54 #-------------------------------------------------------------------------------
55
56 ::oo::class create ::tdbc::odbc::connection {
57
58 superclass ::tdbc::connection
59
60 variable statementSeq typemap
61
62 # The constructor is written in C. It takes the connection string
63 # as its argument It sets up a namespace to hold the statements
64 # associated with the connection, and then delegates to the 'init'
65 # method (written in C) to do the actual work of attaching to the
66 # database. When that comes back, it sets up a statement to query
67 # the support types, makes a dictionary to enumerate them, and
68 # calls back to set a flag if WVARCHAR is seen (If WVARCHAR is
69 # seen, the database supports Unicode.)
70
71 # The 'statementCreate' method forwards to the constructor of the
72 # statement class
73
74 forward statementCreate ::tdbc::odbc::statement create
75
76 # The 'tables' method returns a dictionary describing the tables
77 # in the database
78
79 method tables {{pattern %}} {
80 set stmt [::tdbc::odbc::tablesStatement create \
81 Stmt::[incr statementSeq] [self] $pattern]
82 set status [catch {
83 set retval {}
84 $stmt foreach -as dicts row {
85 if {[dict exists $row TABLE_NAME]} {
86 dict set retval [dict get $row TABLE_NAME] $row
87 }
88 }
89 set retval
90 } result options]
91 catch {rename $stmt {}}
92 return -level 0 -options $options $result
93 }
94
95 # The 'columns' method returns a dictionary describing the tables
96 # in the database
97
98 method columns {table {pattern %}} {
99 # Make sure that the type map is initialized
100 my typemap
101
102 # Query the columns from the database
103
104 set stmt [::tdbc::odbc::columnsStatement create \
105 Stmt::[incr statementSeq] [self] $table $pattern]
106 set status [catch {
107 set retval {}
108 $stmt foreach -as dicts origrow {
109
110 # Map the type, precision, scale and nullable indicators
111 # to tdbc's notation
112
113 set row {}
114 dict for {key value} $origrow {
115 dict set row [string tolower $key] $value
116 }
117 if {[dict exists $row column_name]} {
118 if {[dict exists $typemap \
119 [dict get $row data_type]]} {
120 dict set row type \
121 [dict get $typemap \
122 [dict get $row data_type]]
123 } else {
124 dict set row type [dict get $row type_name]
125 }
126 if {[dict exists $row column_size]} {
127 dict set row precision \
128 [dict get $row column_size]
129 }
130 if {[dict exists $row decimal_digits]} {
131 dict set row scale \
132 [dict get $row decimal_digits]
133 }
134 if {![dict exists $row nullable]} {
135 dict set row nullable \
136 [expr {!![string trim [dict get $row is_nullable]]}]
137 }
138 dict set retval [dict get $row column_name] $row
139 }
140 }
141 set retval
142 } result options]
143 catch {rename $stmt {}}
144 return -level 0 -options $options $result
145 }
146
147 # The 'primarykeys' method returns a dictionary describing the primary
148 # keys of a table
149
150 method primarykeys {tableName} {
151 set stmt [::tdbc::odbc::primarykeysStatement create \
152 Stmt::[incr statementSeq] [self] $tableName]
153 set status [catch {
154 set retval {}
155 $stmt foreach -as dicts row {
156 foreach {odbcKey tdbcKey} {
157 TABLE_CAT tableCatalog
158 TABLE_SCHEM tableSchema
159 TABLE_NAME tableName
160 COLUMN_NAME columnName
161 KEY_SEQ ordinalPosition
162 PK_NAME constraintName
163 } {
164 if {[dict exists $row $odbcKey]} {
165 dict set row $tdbcKey [dict get $row $odbcKey]
166 dict unset row $odbcKey
167 }
168 }
169 lappend retval $row
170 }
171 set retval
172 } result options]
173 catch {rename $stmt {}}
174 return -level 0 -options $options $result
175 }
176
177 # The 'foreignkeys' method returns a dictionary describing the foreign
178 # keys of a table
179
180 method foreignkeys {args} {
181 set stmt [::tdbc::odbc::foreignkeysStatement create \
182 Stmt::[incr statementSeq] [self] {*}$args]
183 set status [catch {
184 set fkseq 0
185 set retval {}
186 $stmt foreach -as dicts row {
187 foreach {odbcKey tdbcKey} {
188 PKTABLE_CAT primaryCatalog
189 PKTABLE_SCHEM primarySchema
190 PKTABLE_NAME primaryTable
191 PKCOLUMN_NAME primaryColumn
192 FKTABLE_CAT foreignCatalog
193 FKTABLE_SCHEM foreignSchema
194 FKTABLE_NAME foreignTable
195 FKCOLUMN_NAME foreignColumn
196 UPDATE_RULE updateRule
197 DELETE_RULE deleteRule
198 DEFERRABILITY deferrable
199 KEY_SEQ ordinalPosition
200 FK_NAME foreignConstraintName
201 } {
202 if {[dict exists $row $odbcKey]} {
203 dict set row $tdbcKey [dict get $row $odbcKey]
204 dict unset row $odbcKey
205 }
206 }
207 # Horrible kludge: If the driver doesn't report FK_NAME,
208 # make one up.
209 if {![dict exists $row foreignConstraintName]} {
210 if {![dict exists $row ordinalPosition]
211 || [dict get $row ordinalPosition] == 1} {
212 set fkname ?[dict get $row foreignTable]?[incr fkseq]
213 }
214 dict set row foreignConstraintName $fkname
215 }
216 lappend retval $row
217 }
218 set retval
219 } result options]
220 catch {rename $stmt {}}
221 return -level 0 -options $options $result
222 }
223
224 # The 'evaldirect' evaluates driver-native SQL code without preparing it,
225 # and returns a list of dicts (similar to '$connection allrows -as dicts').
226
227 method evaldirect {sqlStatement} {
228 set stmt [::tdbc::odbc::evaldirectStatement create \
229 Stmt::[incr statementSeq] [self] $sqlStatement]
230 set status [catch {
231 $stmt allrows -as dicts
232 } result options]
233 catch {rename $stmt {}}
234 return -level 0 -options $options $result
235 }
236
237 # The 'prepareCall' method gives a portable interface to prepare
238 # calls to stored procedures. It delegates to 'prepare' to do the
239 # actual work.
240
241 method preparecall {call} {
242
243 regexp {^[[:space:]]*(?:([A-Za-z_][A-Za-z_0-9]*)[[:space:]]*=)?(.*)} \
244 $call -> varName rest
245 if {$varName eq {}} {
246 my prepare \\{CALL $rest\\}
247 } else {
248 my prepare \\{:$varName=CALL $rest\\}
249 }
250
251 if 0 {
252 # Kevin thinks this is going to be
253
254 if {![regexp -expanded {
255 ^\s* # leading whitespace
256 (?::([[:alpha:]_][[:alnum:]_]*)\s*=\s*) # possible variable name
257 (?:(?:([[:alpha:]_][[:alnum:]_]*)\s*[.]\s*)? # catalog
258 ([[:alpha:]_][[:alnum:]_]*)\s*[.]\s*)? # schema
259 ([[:alpha:]_][[:alnum:]_]*)\s* # procedure
260 (.*)$ # argument list
261 } $call -> varName catalog schema procedure arglist]} {
262 return -code error \
263 -errorCode [list TDBC \
264 SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION \
265 42000 ODBC -1] \
266 "Syntax error in stored procedure call"
267 } else {
268 my PrepareCall $varName $catalog $schema $procedure $arglist
269 }
270
271 # at least if making all parameters 'inout' doesn't work.
272
273 }
274
275 }
276
277 # The 'typemap' method returns the type map
278
279 method typemap {} {
280 if {![info exists typemap]} {
281 set typemap $::tdbc::odbc::sqltypes
282 set typesStmt [tdbc::odbc::typesStatement new [self]]
283 $typesStmt foreach row {
284 set typeNum [dict get $row DATA_TYPE]
285 if {![dict exists $typemap $typeNum]} {
286 dict set typemap $typeNum [string tolower \
287 [dict get $row TYPE_NAME]]
288 }
289 switch -exact -- $typeNum {
290 -9 {
291 [self] HasWvarchar 1
292 }
293 -5 {
294 [self] HasBigint 1
295 }
296 }
297 }
298 rename $typesStmt {}
299 }
300 return $typemap
301 }
302
303 # The 'begintransaction', 'commit' and 'rollback' methods are
304 # implemented in C.
305
306 }
307
308 #-------------------------------------------------------------------------------
309 #
310 # tdbc::odbc::statement --
311 #
312 # The class 'tdbc::odbc::statement' models one statement against a
313 # database accessed through an ODBC connection
314 #
315 #-------------------------------------------------------------------------------
316
317 ::oo::class create ::tdbc::odbc::statement {
318
319 superclass ::tdbc::statement
320
321 # The constructor is implemented in C. It accepts the handle to
322 # the connection and the SQL code for the statement to prepare.
323 # It creates a subordinate namespace to hold the statement's
324 # active result sets, and then delegates to the 'init' method,
325 # written in C, to do the actual work of preparing the statement.
326
327 # The 'resultSetCreate' method forwards to the result set constructor
328
329 forward resultSetCreate ::tdbc::odbc::resultset create
330
331 # The 'params' method describes the parameters to the statement
332
333 method params {} {
334 set typemap [[my connection] typemap]
335 set result {}
336 foreach {name flags typeNum precision scale nullable} [my ParamList] {
337 set lst [dict create \
338 name $name \
339 direction [lindex {unknown in out inout} \
340 [expr {($flags & 0x06) >> 1}]] \
341 type [dict get $typemap $typeNum] \
342 precision $precision \
343 scale $scale]
344 if {$nullable in {0 1}} {
345 dict set list nullable $nullable
346 }
347 dict set result $name $lst
348 }
349 return $result
350 }
351
352 # Methods implemented in C:
353 # init statement ?dictionary?
354 # Does the heavy lifting for the constructor
355 # connection
356 # Returns the connection handle to which this statement belongs
357 # paramtype paramname ?direction? type ?precision ?scale??
358 # Declares the type of a parameter in the statement
359
360 }
361
362 #------------------------------------------------------------------------------
363 #
364 # tdbc::odbc::tablesStatement --
365 #
366 # The class 'tdbc::odbc::tablesStatement' represents the special
367 # statement that queries the tables in a database through an ODBC
368 # connection.
369 #
370 #------------------------------------------------------------------------------
371
372 oo::class create ::tdbc::odbc::tablesStatement {
373
374 superclass ::tdbc::statement
375
376 # The constructor is written in C. It accepts the handle to the
377 # connection and a pattern to match table names. It works in all
378 # ways like the constructor of the 'statement' class except that
379 # its 'init' method sets up to enumerate tables and not run a SQL
380 # query.
381
382 # The 'resultSetCreate' method forwards to the result set constructor
383
384 forward resultSetCreate ::tdbc::odbc::resultset create
385
386 }
387
388 #------------------------------------------------------------------------------
389 #
390 # tdbc::odbc::columnsStatement --
391 #
392 # The class 'tdbc::odbc::tablesStatement' represents the special
393 # statement that queries the columns of a table or view
394 # in a database through an ODBC connection.
395 #
396 #------------------------------------------------------------------------------
397
398 oo::class create ::tdbc::odbc::columnsStatement {
399
400 superclass ::tdbc::statement
401
402 # The constructor is written in C. It accepts the handle to the
403 # connection, a table name, and a pattern to match column
404 # names. It works in all ways like the constructor of the
405 # 'statement' class except that its 'init' method sets up to
406 # enumerate tables and not run a SQL query.
407
408 # The 'resultSetCreate' class forwards to the constructor of the
409 # result set
410
411 forward resultSetCreate ::tdbc::odbc::resultset create
412
413 }
414
415 #------------------------------------------------------------------------------
416 #
417 # tdbc::odbc::primarykeysStatement --
418 #
419 # The class 'tdbc::odbc::primarykeysStatement' represents the special
420 # statement that queries the primary keys on a table through an ODBC
421 # connection.
422 #
423 #------------------------------------------------------------------------------
424
425 oo::class create ::tdbc::odbc::primarykeysStatement {
426
427 superclass ::tdbc::statement
428
429 # The constructor is written in C. It accepts the handle to the
430 # connection and a table name. It works in all
431 # ways like the constructor of the 'statement' class except that
432 # its 'init' method sets up to enumerate primary keys and not run a SQL
433 # query.
434
435 # The 'resultSetCreate' method forwards to the result set constructor
436
437 forward resultSetCreate ::tdbc::odbc::resultset create
438
439 }
440
441 #------------------------------------------------------------------------------
442 #
443 # tdbc::odbc::foreignkeysStatement --
444 #
445 # The class 'tdbc::odbc::foreignkeysStatement' represents the special
446 # statement that queries the foreign keys on a table through an ODBC
447 # connection.
448 #
449 #------------------------------------------------------------------------------
450
451 oo::class create ::tdbc::odbc::foreignkeysStatement {
452
453 superclass ::tdbc::statement
454
455 # The constructor is written in C. It accepts the handle to the
456 # connection and the -primary and -foreign options. It works in all
457 # ways like the constructor of the 'statement' class except that
458 # its 'init' method sets up to enumerate foreign keys and not run a SQL
459 # query.
460
461 # The 'resultSetCreate' method forwards to the result set constructor
462
463 forward resultSetCreate ::tdbc::odbc::resultset create
464
465 }
466
467 #------------------------------------------------------------------------------
468 #
469 # tdbc::odbc::evaldirectStatement --
470 #
471 # The class 'tdbc::odbc::evaldirectStatement' provides a mechanism to
472 # execute driver-name SQL code through an ODBC connection. The SQL code
473 # is not prepared and no tokenization or variable substitution is done.
474 #
475 #------------------------------------------------------------------------------
476
477 oo::class create ::tdbc::odbc::evaldirectStatement {
478
479 superclass ::tdbc::statement
480
481 # The constructor is written in C. It accepts the handle to the
482 # connection and a SQL statement. It works in all
483 # ways like the constructor of the 'statement' class except that
484 # its 'init' method does not tokenize or prepare the SQL statement, and
485 # sets up to run the SQL query without performing variable substitution.
486
487 # The 'resultSetCreate' method forwards to the result set constructor
488
489 forward resultSetCreate ::tdbc::odbc::resultset create
490
491 }
492
493 #------------------------------------------------------------------------------
494 #
495 # tdbc::odbc::typesStatement --
496 #
497 # The class 'tdbc::odbc::typesStatement' represents the special
498 # statement that queries the types available in a database through
499 # an ODBC connection.
500 #
501 #------------------------------------------------------------------------------
502
503
504 oo::class create ::tdbc::odbc::typesStatement {
505
506 superclass ::tdbc::statement
507
508 # The constructor is written in C. It accepts the handle to the
509 # connection, and (optionally) a data type number. It works in all
510 # ways like the constructor of the 'statement' class except that
511 # its 'init' method sets up to enumerate types and not run a SQL
512 # query.
513
514 # The 'resultSetCreate' method forwards to the constructor of result sets
515
516 forward resultSetCreate ::tdbc::odbc::resultset create
517
518 # The C code contains a variant implementation of the 'init' method.
519
520 }
521
522 #------------------------------------------------------------------------------
523 #
524 # tdbc::odbc::resultset --
525 #
526 # The class 'tdbc::odbc::resultset' models the result set that is
527 # produced by executing a statement against an ODBC database.
528 #
529 #------------------------------------------------------------------------------
530
531 ::oo::class create ::tdbc::odbc::resultset {
532
533 superclass ::tdbc::resultset
534
535 # Methods implemented in C include:
536
537 # constructor statement ?dictionary?
538 # -- Executes the statement against the database, optionally providing
539 # a dictionary of substituted parameters (default is to get params
540 # from variables in the caller's scope).
541 # columns
542 # -- Returns a list of the names of the columns in the result.
543 # nextdict
544 # -- Stores the next row of the result set in the given variable in
545 # the caller's scope as a dictionary whose keys are
546 # column names and whose values are column values.
547 # nextlist
548 # -- Stores the next row of the result set in the given variable in
549 # the caller's scope as a list of cells.
550 # rowcount
551 # -- Returns a count of rows affected by the statement, or -1
552 # if the count of rows has not been determined.
553
554 }