1
2
3 __version__ = '$Revision: 5432 $'.split()[1]
4 __date__ = '$Date: 2007-01-23 08:22:13 -0500 (Tue, 23 Jan 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 s1 += str(self.fields[0])
295 if str == type(self.values[0]): s2 += '"'+str(self.values[0])+'"'
296 else: s2 += str(self.values[0])
297
298 for i in range(1,len(self.fields)):
299 s1 += ','+str(self.fields[i])
300 if bool == type(self.values[i]):
301 if self.values[i]: s2 += ',1'
302 else: s2 += ',0'
303 if isinstance(self.values[i],BitVector):
304 print 'FOUND BITVECTOR'
305 s2 += ',"'+str(self.values[i])+'"'
306
307
308 elif str == type(self.values[i]):
309 s2 += ',"'+str(self.values[i])+'"'
310 elif type(self.values[i]) in (int, float):
311 s2 += ','+str(self.values[i])
312
313 elif not self.values[i]:
314 s2 += ',NULL'
315 else:
316 s2 += ','+str(self.values[i])
317
318 s += '(' + s1 + ') VALUES (' + s2 + ');'
319 return s
320
321 - def add(self,field,value):
322 '''Add a field value pair to the insert
323
324 @note: Integers and floats should NOT be converted to strings.
325 @param field: name of the field
326 @param value: value to be assigned to that field.
327 '''
328
329 self.fields.append(field)
330 self.values.append(value)
331 return
332
333
334
335
336 import datetime
337
339 ''' Take a list and make an insert string. This works with
340 dictionaries too. Here is a quick example:
341
342 >>> aList = [('one',1),('2','two'),('threepoint',3.)]
343 >>> sqlInsertStrFromList('myTable',aList)
344 "insert into myTable (one,2,threepoint) values (1,'two',3.0);"
345
346 @param table: Which table to insert into
347 @type table: str
348 @param aList: list of tubles pairs to insert - (name, value)
349 @type aList(list)
350 @return: complete SQL insert command
351 @rtype: str
352 '''
353 ins = "insert into " + table + " ("
354 first=True
355 for pair in aList:
356 key = pair[0]
357 if first: first=False
358 else: ins+=","
359 ins+=str(key)
360 ins += ") values ("
361 first=True
362 for pair in aList:
363 value = pair[1]
364 if first: first=False
365 else: ins+=","
366
367
368
369
370
371 if type(value)!=int and type(value)!=float: ins+="'"
372 ins+=str(value)
373 if type(value)!=int and type(value)!=float: ins+="'"
374
375 ins += ");"
376 return ins
377
378
379
380
381 if __name__=='__main__':
382 from optparse import OptionParser
383 myparser = OptionParser(usage="%prog [options]",
384 version="%prog "+__version__)
385 myparser.add_option('--test','--doc-test',dest='doctest',default=False,action='store_true',
386 help='run the documentation tests')
387
388 addVerbosityOptions(myparser)
389 (options,args) = myparser.parse_args()
390
391 success=True
392
393 if options.doctest:
394 import os; print os.path.basename(sys.argv[0]), 'doctests ...',
395 sys.argv= [sys.argv[0]]
396 if options.verbosity>=VERBOSE: sys.argv.append('-v')
397 import doctest
398 numfail,numtests=doctest.testmod()
399 if numfail==0: print 'ok'
400 else:
401 print 'FAILED'
402 success=False
403
404 if not success:
405 sys.exit('Something Failed')
406