1
2
3 __version__ = '$Revision: 5593 $'.split()[1]
4 __date__ = '$Date: 2007-02-16 09:06:05 -0500 (Fri, 16 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 @note: postgres is sort of case sensitive, so go all lowercase for fields and tables
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
121 if dbType == 'postgres':
122 s+=','.join([f.lower() for f in self.fields])
123 else:
124 s+=','.join(self.fields)
125 s += self.fields[-1] + ' '
126
127 if len(self.from_tables)<1: print "ERROR: fix throw some exception"
128 s += 'FROM '
129 for i in range (len(self.from_tables)-1):
130 s += self.from_tables[i]+','
131 s += self.from_tables[-1]
132
133 if (len(self.where)>0): s += ' WHERE '
134 for i in range (len(self.where)-1):
135 s += self.where[i]+' AND '
136 if (len(self.where)>0): s += self.where[-1]
137
138 if (None != self.orderby):
139 s += ' ORDER BY ' + self.orderby
140 if self.desc: s += ' DESC'
141
142 if (None != self.limit):
143 s += ' LIMIT ' + str(self.limit)
144
145 s += ';'
146 return s
147
149 '''
150 Helper for building create SQL commands.
151
152 FIX: add type checking - what did I mean by this???
153 @todo: FIX - add a remove command to nuke a field
154 '''
155
156 - def __init__(self,table,dbType='postgres'):
157 '''Kick it off with no fields
158
159 table - which table are we going to insert into'''
160 self.table = table
161 self.dbType = dbType
162 self.fields = []
163 self.types = []
164 self.postgis = [];
165 return
166
167 - def add(self,field,typeStr):
168 '''
169 Unchecked field. Provide the field and type all in one. Use
170 this if nothing matches what you need.
171
172 e.g.:
173 create.add('corenumber','INTEGER')
174 create.add('username','VARCHAR(40)')
175 create.add('id','INTEGER PRIMARY KEY')
176
177 @param field: name of the field
178 @param typeStr: the type of field
179
180 @todo: Allow setting of primary key in a simple way
181 '''
182 self.fields.append(field)
183 self.types.append(typeStr)
184 return
185
187 '''
188 Add a primary key based on the field name.
189 @FIX: complain if trying to add a second primary key
190 '''
191
192 self.fields.append(keyName)
193 if 'sqlite' ==self.dbType: self.types.append('INTEGER PRIMARY KEY')
194 elif 'postgres'==self.dbType: self.types.append('SERIAL PRIMARY KEY')
195 else:
196 print 'Do not know how to construct a primary key for database type of',self.dbType
197 assert False
198 return
199
201 '''
202 SQL integer field
203 @param field: name of the field
204 '''
205 self.fields.append(field)
206 self.types.append("INTEGER")
207
209 '''
210 SQL floating point field
211 @param field: name of the field
212 '''
213
214 self.fields.append(field)
215 self.types.append("REAL")
216
218 '''
219 SQL VARCHAR field... variable length up to a max size
220 @param field: name of the field
221 @param length: max length of the field
222 '''
223 self.fields.append(field)
224 self.types.append("VARCHAR("+str(length)+")")
225
226
228 '''
229 SQL Boolean field
230 @param field: name of the field
231 '''
232 self.fields.append(field)
233 self.types.append("BOOL")
234
236 '''
237 SQL Boolean field
238 @param field: name of the field
239 @param length: largest possible size
240 '''
241 assert (length>0)
242 self.fields.append(field)
243 self.types.append('BIT VARYING('+str(length)+')')
244
245
247 '''
248 @param precision: overall digits including to right of decimal
249 @param scale: number of digits to the right of decimal
250 '''
251 self.fields.append(field)
252 self.types.append('DECIMAL('+str(precision)+','+str(scale)+')')
253
255 '''SQL TIMESTAMP field
256 @param field: name of the field
257 '''
258 self.fields.append(field)
259 self.types.append("TIMESTAMP")
260 return
261
262 - def addPostGIS(self,field,typeName,dimension,SRID='-1'):
263 '''
264
265 Add a spatial column to the table using the OpenGIS
266 AddGeometryColumn function using current schema:
267
268 AddGeometryColumn(<table_name>,
269 <column_name>, <srid>, <type>,
270 <dimension>)
271
272 @param field: Name of the field in the db table
273 @param typeName: OpenGIS geometry type (e.g. POINT)
274 @param dimension: x,y would be 2
275 @type dimension: int
276 @SRID: spatial referencing system identifier (FIX: give some more info!)
277 '''
278
279 int(dimension)
280 self.postgis.append((field,typeName,dimension,SRID))
281
282
284 '''Return the SQL string for the table creation
285 @rtype: str'''
286 assert (len(self.fields)>0)
287 assert (len(self.types)>0)
288 assert (len(self.fields)==len(self.types))
289 cstr = 'CREATE TABLE '
290 if 'postgres'==self.dbType:
291 cstr += self.table.lower()+' ('
292 for i in range(len(self.fields)-1):
293 cstr += str(self.fields[i].lower())+' '+str(self.types[i])+', '
294 cstr += str(self.fields[-1].lower())+' '+str(self.types[-1])
295 else:
296 cstr += self.table+' ( '
297 for i in range(len(self.fields)-1):
298 cstr += str(self.fields[i])+' '+str(self.types[i])+', '
299 cstr += str(self.fields[-1])+' '+str(self.types[-1])
300 cstr += ' ); '
301
302 cmds=[]
303 for postgisFields in self.postgis:
304 table = '\''+self.table.lower()+'\''
305 field = '\''+postgisFields[0].lower()+'\''
306 typeName = '\''+postgisFields[1]+'\''
307 dim = str(postgisFields[2])
308 SRID = str(postgisFields[3])
309 fieldStr = ','.join((table,field,SRID,typeName,dim))
310 addCmd = 'SELECT AddGeometryColumn('+fieldStr+')'
311 cmds.append(addCmd)
312
313 retStr = cstr + ';'.join(cmds)
314 if len(cmds)>0: retStr += ';'
315 return retStr
316
318 '''
319 Help create an SQL insert statement for injecting data into a database. Wee!
320
321 @todo: FIX: provide some sort of validation, maybe with the CREATE string or class?
322 @todo: Put in a remove/delete call to pull a value out so that it is not inserted
323
324 @todo: FIX: MUST REWRITE THIS CLASS TO BE TYPE AWARE.
325 '''
326 - def __init__(self,table,dbType='postgres'):
327 '''Create an insert with no values
328
329 @param table: which table are we going to insert into
330 @param dbType: sqlite can not handle True/False keyworks (at version 3.2.8)
331 '''
332 self.table = table
333 self.dbType = dbType
334 self.fields = []
335 self.values = []
336 self.postGIS = []
337 return
338
340 '''Print out a safer dump to std out rather than str for debugging'''
341 print '\n === dump insert for table',self.table,'==='
342 for i in range(1,len(self.fields)):
343 print self.fields[i], self.values[i],' (',type(self.fields[i]), type(self.values[i]),')'
344 print
345
347 "Return the SQL string for the insert"
348 if 0==len(self.fields):
349 print "WARNING: empty insert. returning empty string"
350 return ""
351
352 s = 'INSERT INTO '
353
354 if 'postgres'==self.dbType: s+= self.table.lower() + ' '
355 else: s+= self.table + ' '
356
357 assert(len(self.fields)==len(self.values))
358 fields = None
359 if 'postgres'==self.dbType:
360 fields = [f.lower() for f in self.fields]
361 else: fields = self.fields
362
363
364
365
366
367
368
369
370
371
372 s2List=[]
373 for i in range(len(fields)):
374
375 if bool == type(self.values[i]):
376 if 'sqlite'==self.dbType:
377 if self.values[i]: s2List.append('1')
378 else: s2List.append('0')
379 else: s2List.append(str(self.values[i]))
380 elif isinstance(self.values[i],BitVector): s2List.append('\''+str(self.values[i])+'\'')
381 elif str == type(self.values[i]): s2List.append('\''+str(self.values[i])+'\'')
382 elif type(self.values[i]) in (int, float): s2List.append(str(self.values[i]))
383
384 elif not self.values[i]:
385 print 'FIX: what was I trying to accomplish with this?',fields[i],self.values[i]
386 s2List.append('NULL')
387 else:
388 s2List.append(str(self.values[i]))
389
390 s1List = fields
391 for entry in self.postGIS:
392 s1List.append(entry[0].lower())
393 s2List.append('GeomFromText(\'' + entry[1] + '\')')
394
395 s += '(' + ','.join(s1List) + ') VALUES (' + ','.join(s2List) + ');'
396 return s
397
398 - def addPostGIS(self,field,value):
399 '''
400 Handle postGIS geometry
401 '''
402 self.postGIS.append((field,value))
403
404
405 - def add(self,field,value):
406 '''Add a field value pair to the insert
407
408 @note: Integers and floats should NOT be converted to strings.
409 @param field: name of the field
410 @param value: value to be assigned to that field.
411 '''
412
413 if type(value)==str:
414
415 value = value.replace('"','""')
416 self.fields.append(field)
417 self.values.append(value)
418 return
419
420
421
422
423 import datetime
424
426 ''' Take a list and make an insert string. This works with
427 dictionaries too. Here is a quick example:
428
429 >>> aList = [('one',1),('2','two'),('threepoint',3.)]
430 >>> sqlInsertStrFromList('myTable',aList)
431 "insert into myTable (one,2,threepoint) values (1,'two',3.0);"
432
433 @param table: Which table to insert into
434 @type table: str
435 @param aList: list of tubles pairs to insert - (name, value)
436 @type aList(list)
437 @return: complete SQL insert command
438 @rtype: str
439 '''
440
441 if 'postgres'==dbType: table = table.lower()
442 ins = "insert into " + table + " ("
443 first = []
444 if 'postgres'==dbType: first = [f[0].lower() for f in aList]
445 else: first = [f[0] for f in aList]
446 ins += ','.join(first) + ") values ("
447 first=True
448 for pair in aList:
449 value = pair[1]
450 if first: first=False
451 else: ins+=","
452
453
454
455
456
457 if type(value)!=int and type(value)!=float: ins+="'"
458 ins+=str(value)
459 if type(value)!=int and type(value)!=float: ins+="'"
460
461 ins += ");"
462 return ins
463
464
465
466
467 if __name__=='__main__':
468 from optparse import OptionParser
469 myparser = OptionParser(usage="%prog [options]",
470 version="%prog "+__version__)
471 myparser.add_option('--test','--doc-test',dest='doctest',default=False,action='store_true',
472 help='run the documentation tests')
473
474 addVerbosityOptions(myparser)
475 (options,args) = myparser.parse_args()
476
477 success=True
478
479 if options.doctest:
480 import os; print os.path.basename(sys.argv[0]), 'doctests ...',
481 sys.argv= [sys.argv[0]]
482 if options.verbosity>=VERBOSE: sys.argv.append('-v')
483 import doctest
484 numfail,numtests=doctest.testmod()
485 if numfail==0: print 'ok'
486 else:
487 print 'FAILED'
488 success=False
489
490 if not success:
491 sys.exit('Something Failed')
492