|
a |
|
b/sqlite3tcsq.tcl |
|
|
1 |
# @(#$Id$ (C) 2013 J.F. Dockes
|
|
|
2 |
#
|
|
|
3 |
# Permission to use, copy, modify, distribute, and sell this software
|
|
|
4 |
# and its documentation for any purpose is hereby granted without fee,
|
|
|
5 |
# provided that the above copyright notice and this permission notice
|
|
|
6 |
# appear in all copies of the software and related documentation.
|
|
|
7 |
#
|
|
|
8 |
# THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
|
|
|
9 |
# EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
|
|
|
10 |
# WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
11 |
#
|
|
|
12 |
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
|
|
|
13 |
# INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES
|
|
|
14 |
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT
|
|
|
15 |
# ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY,
|
|
|
16 |
# ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
|
|
17 |
# SOFTWARE.
|
|
|
18 |
#
|
|
|
19 |
# The low level access layer for tcsq when using sqlite3.
|
|
|
20 |
# The SQLDBTYPE should be set to SQLITE3 for this to be used.
|
|
|
21 |
|
|
|
22 |
package provide SQLITE3tcsq 0.1
|
|
|
23 |
package require sqlite3
|
|
|
24 |
#set tcsqdebuglevel 2
|
|
|
25 |
|
|
|
26 |
### SQLITE3 CODE############################################
|
|
|
27 |
|
|
|
28 |
# Pseudo cursor management
|
|
|
29 |
array set _SQLITE3stmts {}
|
|
|
30 |
array set _SQLITE3cursors {}
|
|
|
31 |
|
|
|
32 |
# Open a connection. This is normally used to connect and identify to
|
|
|
33 |
# a database server, without mention of a specific database. This has
|
|
|
34 |
# no meaning for SQLITE3
|
|
|
35 |
set _sql3hdlnum 0
|
|
|
36 |
proc SQLITE3connect {{host ""} {user ""} {passwd ""}} {
|
|
|
37 |
global _sql3hdlnum
|
|
|
38 |
set _sql3hdlnum [expr {$_sql3hdlnum + 1}]
|
|
|
39 |
return "sql3hdl$_sql3hdlnum"
|
|
|
40 |
}
|
|
|
41 |
|
|
|
42 |
# Connect to a database.
|
|
|
43 |
proc SQLITE3use {hdl database} {
|
|
|
44 |
tcsqdebug "SQLITE3use: '$hdl', '$database'"
|
|
|
45 |
set cmd [list sqlite3 $hdl $database]
|
|
|
46 |
tcsqdebug "SQLITE3use: '$cmd'" 2
|
|
|
47 |
eval $cmd
|
|
|
48 |
return $hdl
|
|
|
49 |
}
|
|
|
50 |
|
|
|
51 |
# Open a select statement and return a cursor. Sqlite really has no
|
|
|
52 |
# notion of this, it returns the whole result set in return from
|
|
|
53 |
# statement evaluation. So we have to store the result and the current pointer.
|
|
|
54 |
proc SQLITE3opensel {hdl stmt} {
|
|
|
55 |
global _SQLITE3stmts _SQLITE3cursors
|
|
|
56 |
tcsqdebug "SQLITE3opensel: hdl '$hdl', stmt '$stmt'"
|
|
|
57 |
set ok 0
|
|
|
58 |
for {set i 1} {$i < 100000} {incr i} {
|
|
|
59 |
set qry "qry$i"
|
|
|
60 |
if {![info exists _SQLITE3stmts($qry)]} {
|
|
|
61 |
set _SQLITE3stmts($qry) []
|
|
|
62 |
set _SQLITE3cursors($qry) -1
|
|
|
63 |
set ok 1
|
|
|
64 |
break
|
|
|
65 |
}
|
|
|
66 |
}
|
|
|
67 |
if {$ok == 0} {
|
|
|
68 |
return -code error "SQLITE3opensel: can't allocate statement handle"
|
|
|
69 |
}
|
|
|
70 |
$hdl eval $stmt resarray {
|
|
|
71 |
# resarray[*] has the column names in appropriate order
|
|
|
72 |
set row []
|
|
|
73 |
foreach col $resarray(*) {
|
|
|
74 |
lappend row $resarray($col)
|
|
|
75 |
}
|
|
|
76 |
lappend _SQLITE3stmts($qry) $row
|
|
|
77 |
}
|
|
|
78 |
return $qry
|
|
|
79 |
}
|
|
|
80 |
|
|
|
81 |
proc SQLITE3next {qry} {
|
|
|
82 |
global _SQLITE3stmts _SQLITE3cursors
|
|
|
83 |
incr _SQLITE3cursors($qry)
|
|
|
84 |
set res [lindex $_SQLITE3stmts($qry) $_SQLITE3cursors($qry)]
|
|
|
85 |
return $res
|
|
|
86 |
}
|
|
|
87 |
proc SQLITE3rew {qry} {
|
|
|
88 |
global _SQLITE3cursors
|
|
|
89 |
set _SQLITE3cursors($qry) -1
|
|
|
90 |
}
|
|
|
91 |
proc SQLITE3closel {qry} {
|
|
|
92 |
tcsqdebug "SQLITE3closel: qry '$qry'"
|
|
|
93 |
global _SQLITE3stmts _SQLITE3cursors
|
|
|
94 |
unset _SQLITE3stmts($qry)
|
|
|
95 |
unset _SQLITE3cursors($qry)
|
|
|
96 |
}
|
|
|
97 |
|
|
|
98 |
proc SQLITE3exec {hdl stmt} {
|
|
|
99 |
tcsqdebug "SQLITE3exec: hdl '$hdl', stmt '$stmt'"
|
|
|
100 |
$hdl eval $stmt
|
|
|
101 |
set res [$hdl changes]
|
|
|
102 |
tcsqdebug "SQLITE3exec: returning: $res"
|
|
|
103 |
return $res
|
|
|
104 |
}
|
|
|
105 |
|
|
|
106 |
# Retrieve auto_increment value for the last insert.
|
|
|
107 |
# Don't know how portable this, probably not much. Works with MySQL
|
|
|
108 |
proc SQLITE3insertid {hdl} {
|
|
|
109 |
return $hdl last_insert_rowid
|
|
|
110 |
}
|
|
|
111 |
|
|
|
112 |
# Retrieve unique id for the specified table. This should not ever be called
|
|
|
113 |
proc SQLITE3uniqueid {hdl tbl} {
|
|
|
114 |
return ""
|
|
|
115 |
}
|
|
|
116 |
|
|
|
117 |
proc SQLITE3discon {hdl} {
|
|
|
118 |
tcsqdebug "SQLITE3discon: hdl '$hdl'"
|
|
|
119 |
$hdl close
|
|
|
120 |
}
|
|
|
121 |
|
|
|
122 |
proc SQLITE3tabinfo {hdl} {
|
|
|
123 |
return [$hdl eval "select name from sqlite_master where type='table'"]
|
|
|
124 |
}
|
|
|
125 |
|
|
|
126 |
# Extract type and length information from sqlite3 pragma table_info "type"
|
|
|
127 |
# column
|
|
|
128 |
# typ[(len)]
|
|
|
129 |
proc SQLITE3gettyplen {typlen typnm lennm} {
|
|
|
130 |
upvar $typnm typ
|
|
|
131 |
upvar $lennm len
|
|
|
132 |
|
|
|
133 |
tcsqdebug "SQLITE3gettyplen: '$typlen'"
|
|
|
134 |
|
|
|
135 |
set parO [string first {(} $typlen]
|
|
|
136 |
if {$parO < 0} {
|
|
|
137 |
set typ [string tolower [lindex $typlen 0]]
|
|
|
138 |
set ll {}
|
|
|
139 |
} else {
|
|
|
140 |
set typ [string tolower [string range $typlen 0 [expr {$parO -1}]]]
|
|
|
141 |
set parC [string first {)} $typlen]
|
|
|
142 |
incr parO
|
|
|
143 |
incr parC -1
|
|
|
144 |
set ll [split [string range $typlen $parO $parC] ,]
|
|
|
145 |
}
|
|
|
146 |
#puts "SQLITE3gettyplen: typlen '$typlen', typ '$typ', ll '$ll'"
|
|
|
147 |
switch $typ {
|
|
|
148 |
date {set len 10}
|
|
|
149 |
time {set len 8}
|
|
|
150 |
datetime {set len 19}
|
|
|
151 |
timestamp {set len 10}
|
|
|
152 |
enum -
|
|
|
153 |
default {set len [lindex $ll 0]}
|
|
|
154 |
}
|
|
|
155 |
# Catch weird cases like blob, etc... and unexpected ones... Set
|
|
|
156 |
# arbitrary length.
|
|
|
157 |
if {$len == ""} {
|
|
|
158 |
set len 13
|
|
|
159 |
}
|
|
|
160 |
tcsqdebug "col typlen '$typlen', typ '$typ', len '$len'"
|
|
|
161 |
}
|
|
|
162 |
|
|
|
163 |
# One thing that we'd like to do, but don't know how is retrieve the
|
|
|
164 |
# "auto_increment attribute.
|
|
|
165 |
proc SQLITE3colinfo {hdl tbl arnm} {
|
|
|
166 |
tcsqdebug "SQLITE3colinfo hdl '$hdl' tbl '$tbl' arnm '$arnm'"
|
|
|
167 |
upvar $arnm ar
|
|
|
168 |
global sqlsc_def_maxlen env
|
|
|
169 |
set primkeycols {}
|
|
|
170 |
|
|
|
171 |
# Retrieve column info. List elements:
|
|
|
172 |
# 0 col# (cid) / 1 name / 2 type / 3 notnull / 4 dflt_value / 5 pk
|
|
|
173 |
$hdl eval "pragma table_info($tbl)" myar {
|
|
|
174 |
tcsqdebug "table_info row"
|
|
|
175 |
set nm [string tolower $myar(name)]
|
|
|
176 |
lappend allcols $nm
|
|
|
177 |
SQLITE3gettyplen $myar(type) typ($nm) len($nm)
|
|
|
178 |
if {$myar(pk)} {
|
|
|
179 |
lappend primkeycols $nm
|
|
|
180 |
if {$typ($nm) == "int"} {
|
|
|
181 |
set ar(tabcolserial) $nm
|
|
|
182 |
}
|
|
|
183 |
}
|
|
|
184 |
}
|
|
|
185 |
|
|
|
186 |
# Build the column list for the screen:
|
|
|
187 |
if {![info exists ar(columns)]} {
|
|
|
188 |
# If it was not specified, we use all columns
|
|
|
189 |
set ar(columns) $allcols
|
|
|
190 |
set autocols 1
|
|
|
191 |
} else {
|
|
|
192 |
# The user-specified list may include fully qualified
|
|
|
193 |
# names. Compute the list of simple column names.
|
|
|
194 |
# scol == "" may happen because of the "\n" in the column list
|
|
|
195 |
foreach col $ar(columns) {
|
|
|
196 |
set scol [_tcsqsimplecolname $tbl $col]
|
|
|
197 |
if {$scol != ""} {
|
|
|
198 |
lappend scols [string tolower $scol]
|
|
|
199 |
}
|
|
|
200 |
}
|
|
|
201 |
set autocols 0
|
|
|
202 |
}
|
|
|
203 |
|
|
|
204 |
# Possibly build updateindex from the primary key, if not
|
|
|
205 |
# specified by the caller.
|
|
|
206 |
# If the column list was specified, but not the updateindex list,
|
|
|
207 |
# we set the latter only if all its components are included in the
|
|
|
208 |
# column list.
|
|
|
209 |
if {![info exists ar(updateindex)]} {
|
|
|
210 |
set ar(updateindex) $primkeycols
|
|
|
211 |
if {$autocols == 0} {
|
|
|
212 |
foreach col $primkeycols {
|
|
|
213 |
if {[lsearch -exact $scols $col] == -1} {
|
|
|
214 |
unset ar(updateindex)
|
|
|
215 |
break
|
|
|
216 |
}
|
|
|
217 |
}
|
|
|
218 |
}
|
|
|
219 |
}
|
|
|
220 |
|
|
|
221 |
catch {tcsqdebug "$tbl: updateindex: $ar(updateindex)" 1}
|
|
|
222 |
|
|
|
223 |
# Set the column types and lengths in the array
|
|
|
224 |
foreach col $ar(columns) {
|
|
|
225 |
# Get the simple name and check that it is for this table
|
|
|
226 |
set scol [_tcsqsimplecolname $tbl $col]
|
|
|
227 |
if {$scol == "" || ![info exists typ($scol)]} {
|
|
|
228 |
# Column probably from another table
|
|
|
229 |
tcsqdebug "No type info for column '$scol'"
|
|
|
230 |
continue
|
|
|
231 |
}
|
|
|
232 |
# In all cases, remember type and length as from db
|
|
|
233 |
set ar(sqlsc_${col}_dbtype) $typ($scol)
|
|
|
234 |
set ar(sqlsc_${col}_dblen) $len($scol)
|
|
|
235 |
# puts "$col: Dbtyp: $typ($scol), Dblen: $len($scol)"
|
|
|
236 |
set typind "sqlsc_${col}_type"
|
|
|
237 |
set lenind "sqlsc_${col}_len"
|
|
|
238 |
if {![info exists ar($lenind)]} {
|
|
|
239 |
set ar($lenind) $len($scol)
|
|
|
240 |
# puts "$col: length not preset, set to $ar($lenind)"
|
|
|
241 |
if {$ar($lenind) > $sqlsc_def_maxlen} {
|
|
|
242 |
# puts "$col: limiting width to $sqlsc_def_maxlen"
|
|
|
243 |
set ar($lenind) $sqlsc_def_maxlen
|
|
|
244 |
}
|
|
|
245 |
}
|
|
|
246 |
|
|
|
247 |
# SQLITE3 types ?
|
|
|
248 |
switch [set lowtyp [string tolower $typ($scol)]] {
|
|
|
249 |
char -
|
|
|
250 |
varchar -
|
|
|
251 |
longvarchar {
|
|
|
252 |
set ar($typind) "char"
|
|
|
253 |
# We don't do upshift automatically with mysql except
|
|
|
254 |
# in CDKIT where we need to stay compatible with
|
|
|
255 |
# informix
|
|
|
256 |
if {$autocols && [info exists env(CDKITDB)]} {
|
|
|
257 |
lappend ar(upshiftcols) $col
|
|
|
258 |
}
|
|
|
259 |
}
|
|
|
260 |
date -
|
|
|
261 |
datetime -
|
|
|
262 |
timestamp {
|
|
|
263 |
set ar($typind) $lowtyp
|
|
|
264 |
}
|
|
|
265 |
default {
|
|
|
266 |
set ar($typind) "bin"
|
|
|
267 |
}
|
|
|
268 |
}
|
|
|
269 |
tcsqdebug "name: $col, typ $ar($typind) len $ar($lenind)" 2
|
|
|
270 |
}
|
|
|
271 |
|
|
|
272 |
catch {tcsqdebug "$tbl: tabcolserial: $ar(tabcolserial)" 1}
|
|
|
273 |
}
|
|
|
274 |
|
|
|
275 |
proc SQLITE3quotequote {in} {
|
|
|
276 |
regsub -all "'" "$in" "''" out
|
|
|
277 |
return $out
|
|
|
278 |
}
|
|
|
279 |
|
|
|
280 |
# Quote bad chars in a text blob (which is a tcl string, no need to
|
|
|
281 |
# worry about zeros).
|
|
|
282 |
# note that we quote \ first, else we are going to requote those introduced
|
|
|
283 |
# by further operations !
|
|
|
284 |
# This is duplicated from the MYSQL module, not sure at all it is correct here.
|
|
|
285 |
proc SQLITE3quoteblob {blb} {
|
|
|
286 |
# puts "quoteblob: in: --$blb--"
|
|
|
287 |
regsub -all {\\} $blb {\\\\} blb
|
|
|
288 |
regsub -all {'} $blb {''} blb
|
|
|
289 |
regsub -all {"} $blb {\\"} blb
|
|
|
290 |
# puts "quoteblob: out: --$blb--"
|
|
|
291 |
return $blb
|
|
|
292 |
}
|
|
|
293 |
|
|
|
294 |
### END SQLITE3 CODE ############################################
|