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 ############################################