1
2
3 __version__ = '$Revision: 5548 $'.split()[1]
4 __date__ = '$Date: 2007-02-08 09:13:50 -0500 (Thu, 08 Feb 2007) $'.split()[1]
5 __author__ = 'Kurt Schwehr'
6 __doc__='''
7 Helper functions to create SQL statements.
8
9 @license: GPL
10 @todo: How do I assemble queries like this::
11
12 SELECT COUNT(samplename) AS count FROM \\
13 (SELECT DISTINCT(samplename) AS samplename FROM \\
14 ams WHERE corenum=1 AND coretype='p');
15
16 @todo: subqueries
17 @todo: make a super class so that inserts and selects can verify based on the create str
18 @todo: take the super class info from the database?
19
20 @bug: FIX: write some doc tests!
21 @bug: had no protection from SQL injection attacks or quoting mistakes
22
23 @note: This is not as snazzy as SQLAlchemy or SQLObject, but it works and is simple
24
25 @author: '''+__author__+'''
26 @version: ''' + __version__ +'''
27 @copyright: 2006
28
29 @var __date__: Date of last svn commit
30
31 @undocumented: __version__ __author__ __doc__ myparser
32 '''
33
34
35 import sys
36
37 from BitVector import BitVector
38
39
40
41
42
43 BOMBASTIC= 4
44 VERBOSE = 3
45 TRACE = 2
46 TERSE = 1
47 ALWAYS = 0
48 NEVER = 0
49
50
51
53 '''
54 Added the verbosity options to a parser
55 '''
56 parser.add_option('-v','--verbose',action="count",dest='verbosity',default=0,
57 help='how much information to give. Specify multiple times to increase verbosity')
58 parser.add_option('--verbosity',dest='verbosity',type='int',
59 help='Specify verbosity. Should be in the range of '
60 +str(ALWAYS)+'...'+str(BOMBASTIC)+' (None...Bombastic)')
61 parser.add_option('--noisy',dest='verbosity', action='store_const', const=2*BOMBASTIC,
62 help='Go for the max verbosity ['+str(2*BOMBASTIC)+']')
63
64
65
67 '''
68 Construct an sql select query
69
70 Sometimes it just gets ugly having all that comma and WHERE AND
71 logic in there. This code takes care of that
72 '''
74 self.fields = []
75 self.where = []
76 self.limit = None
77 self.from_tables = []
78 self.orderby = None
79 self.desc = False
80 return
81
83 "Make the returned rows come in some order"
84 if str != type(field): print "ERROR: fix throw type exception"
85 self.orderby = field
86 self.desc = desc
87 return
88
90 "Add a field name to return"
91 if str != type(fieldname): print "ERROR: fix throw type exception"
92 self.fields.append(fieldname)
93 return
94
96 " Add expressions to chain together with ANDs"
97 if str != type(boolTest):
98 print "ERROR: fix throw type exception"
99 self.where.append(boolTest)
100 return
101
103 "Which tables the query will pull from"
104 if str != type(tableName):
105 print "ERROR: fix throw type exception"
106 self.from_tables.append(tableName)
107 return
108
110 "Set the maximum number of items to return"
111 if int != type(numOfItems):
112 print "ERROR: fix throw type exception"
113 self.limit = numOfItems
114 return
115
117 "Return the query as a string"
118 if len(self.fields) < 1: print "ERROR: Must specify at least one from!\n FIX: throw some exception?"
119 s = 'SELECT '
120 for i in range (len(self.fields)-1):
121 s += self.fields[i]+','
122 s += self.fields[-1] + ' '
123
124 if len(self.from_tables)<1: print "ERROR: fix throw some exception"
125 s += 'FROM '
126 for i in range (len(self.from_tables)-1):
127 s += self.from_tables[i]+','
128 s += self.from_tables[-1]
129
130 if (len(self.where)>0): s += ' WHERE '
131 for i in range (len(self.where)-1):
132 s += self.where[i]+' AND '
133 if (len(self.where)>0): s += self.where[-1]
134
135 if (None != self.orderby):
136 s += ' ORDER BY ' + self.orderby
137 if self.desc: s += ' DESC'
138
139 if (None != self.limit):
140 s += ' LIMIT ' + str(self.limit)
141
142 s += ';'
143 return s
144
146 '''
147 Helper for building create SQL commands.
148
149 FIX: add type checking - what did I mean by this???
150 @todo: FIX - add a remove command to nuke a field
151 '''
152
154 '''Kick it off with no fields
155
156 table - which table are we going to insert into'''
157 self.table = table
158 self.fields = []
159 self.types = []
160 return
161
162 - def add(self,field,typeStr):
163 '''
164 Unchecked field. Provide the field and type all in one. Use
165 this if nothing matches what you need.
166
167 e.g.:
168 create.add('corenumber','INTEGER')
169 create.add('username','VARCHAR(40)')
170 create.add('id','INTEGER PRIMARY KEY')
171
172 @param field: name of the field
173 @param typeStr: the type of field
174
175 @todo: Allow setting of primary key in a simple way
176 '''
177 self.fields.append(field)
178 self.types.append(typeStr)
179 return
180
182 '''
183 SQL integer field
184 @param field: name of the field
185 '''
186 self.fields.append(field)
187 self.types.append("INTEGER")
188
190 '''
191 SQL floating point field
192 @param field: name of the field
193 '''
194
195 self.fields.append(field)
196 self.types.append("REAL")
197
199 '''
200 SQL VARCHAR field... variable length up to a max size
201 @param field: name of the field
202 @param length: max length of the field
203 '''
204 self.fields.append(field)
205 self.types.append("VARCHAR("+str(length)+")")
206
207
209 '''
210 SQL Boolean field
211 @param field: name of the field
212 '''
213 self.fields.append(field)
214 self.types.append("BOOL")
215
217 '''
218 SQL Boolean field
219 @param field: name of the field
220 @param length: largest possible size
221 '''
222 assert (length>0)
223 self.fields.append(field)
224 self.types.append('BIT VARYING('+str(length)+')')
225
226
228 '''
229 @param precision: overall digits including to right of decimal
230 @param scale: number of digits to the right of decimal
231 '''
232 self.fields.append(field)
233 self.types.append('DECIMAL('+str(precision)+','+str(scale)+')')
234
236 '''SQL TIMESTAMP field
237 @param field: name of the field
238 '''
239 self.fields.append(field)
240 self.types.append("TIMESTAMP")
241 return
242
244 '''Return the SQL string for the table creation
245 @rtype: str'''
246 assert (len(self.fields)>0)
247 assert (len(self.types)>0)
248 assert (len(self.fields)==len(self.types))
249 cstr = 'CREATE TABLE '+self.table+' ( '
250 for i in range(len(self.fields)-1):
251 cstr += str(self.fields[i])+' '+str(self.types[i])+', '
252 cstr += str(self.fields[-1])+' '+str(self.types[-1])
253 cstr += ' ); '
254 return cstr
255
257 '''
258 Help create an SQL insert statement for injecting data into a database. Wee!
259
260 @todo: FIX: provide some sort of validation, maybe with the CREATE string or class?
261 @todo: Put in a remove/delete call to pull a value out so that it is not inserted
262
263 @todo: FIX: MUST REWRITE THIS CLASS TO BE TYPE AWARE.
264 '''
266 '''Create an insert with no values
267
268 @param table: which table are we going to insert into'''
269 self.table = table
270 self.fields = []
271 self.values = []
272 return
273
275 '''Print out a safer dump to std out rather than str for debugging'''
276 print '\n === dump insert for table',self.table,'==='
277 for i in range(1,len(self.fields)):
278 print self.fields[i], self.values[i],' (',type(self.fields[i]), type(self.values[i]),')'
279 print
280
282 "Return the SQL string for the insert"
283 if 0==len(self.fields):
284 print "WARNING: empty insert. returning empty string"
285 return ""
286
287 s = 'INSERT INTO ' + self.table + ' '
288
289 assert(len(self.fields)==len(self.values))
290 s1 = ''
291 s2 = ''
292
293
294
295
296
297
298 comma = ''
299
300 for i in range(len(self.fields)):
301 s1 += comma+str(self.fields[i])
302 if bool == type(self.values[i]):
303 if self.values[i]: s2 += comma+'1'
304 else: s2 += comma+'0'
305 elif isinstance(self.values[i],BitVector):
306 print 'FOUND BITVECTOR'
307 s2 += comma+'"'+str(self.values[i])+'"'
308
309
310 elif str == type(self.values[i]):
311 s2 += comma+'"'+str(self.values[i])+'"'
312 elif type(self.values[i]) in (int, float):
313 s2 += comma+str(self.values[i])
314
315 elif not self.values[i]:
316 s2 += comma+'NULL'
317 else:
318 s2 += comma+str(self.values[i])
319
320 comma = ','
321
322 s += '(' + s1 + ') VALUES (' + s2 + ');'
323 return s
324
325 - def add(self,field,value):
326 '''Add a field value pair to the insert
327
328 @note: Integers and floats should NOT be converted to strings.
329 @param field: name of the field
330 @param value: value to be assigned to that field.
331 '''
332
333 if type(value)==str:
334
335 value = value.replace('"','""')
336 self.fields.append(field)
337 self.values.append(value)
338 return
339
340
341
342
343 import datetime
344
346 ''' Take a list and make an insert string. This works with
347 dictionaries too. Here is a quick example:
348
349 >>> aList = [('one',1),('2','two'),('threepoint',3.)]
350 >>> sqlInsertStrFromList('myTable',aList)
351 "insert into myTable (one,2,threepoint) values (1,'two',3.0);"
352
353 @param table: Which table to insert into
354 @type table: str
355 @param aList: list of tubles pairs to insert - (name, value)
356 @type aList(list)
357 @return: complete SQL insert command
358 @rtype: str
359 '''
360 ins = "insert into " + table + " ("
361 first=True
362 for pair in aList:
363 key = pair[0]
364 if first: first=False
365 else: ins+=","
366 ins+=str(key)
367 ins += ") values ("
368 first=True
369 for pair in aList:
370 value = pair[1]
371 if first: first=False
372 else: ins+=","
373
374
375
376
377
378 if type(value)!=int and type(value)!=float: ins+="'"
379 ins+=str(value)
380 if type(value)!=int and type(value)!=float: ins+="'"
381
382 ins += ");"
383 return ins
384
385
386
387
388 if __name__=='__main__':
389 from optparse import OptionParser
390 myparser = OptionParser(usage="%prog [options]",
391 version="%prog "+__version__)
392 myparser.add_option('--test','--doc-test',dest='doctest',default=False,action='store_true',
393 help='run the documentation tests')
394
395 addVerbosityOptions(myparser)
396 (options,args) = myparser.parse_args()
397
398 success=True
399
400 if options.doctest:
401 import os; print os.path.basename(sys.argv[0]), 'doctests ...',
402 sys.argv= [sys.argv[0]]
403 if options.verbosity>=VERBOSE: sys.argv.append('-v')
404 import doctest
405 numfail,numtests=doctest.testmod()
406 if numfail==0: print 'ok'
407 else:
408 print 'FAILED'
409 success=False
410
411 if not success:
412 sys.exit('Something Failed')
413