Package ais :: Module sqlhelp
[hide private]
[frames] | no frames]

Source Code for Module ais.sqlhelp

  1  #!/usr/bin/env python 
  2   
  3  __version__ = '$Revision: 5432 $'.split()[1] # See man ident 
  4  __date__ = '$Date: 2007-01-23 08:22:13 -0500 (Tue, 23 Jan 2007) $'.split()[1] # FIX: pull out just the date 
  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  # Python standard libraries 
 35  import sys 
 36   
 37  from BitVector import BitVector 
 38   
 39  # Local modules 
 40  # import verbosity 
 41  # from verbosity import BOMBASTIC,VERBOSE,TRACE,TERSE,ALWAYS 
 42   
 43  BOMBASTIC= 4 
 44  VERBOSE  = 3 
 45  TRACE    = 2 
 46  TERSE    = 1 
 47  ALWAYS   = 0 
 48  NEVER    = 0 # Confusing, eh? 
 49  # Pass in 0 for NEVER from the user side 
 50   
 51  #  
52 -def addVerbosityOptions(parser):
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 ######################################################################
66 -class select:
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 '''
73 - def __init__(self):
74 self.fields = [] 75 self.where = [] 76 self.limit = None 77 self.from_tables = [] 78 self.orderby = None 79 self.desc = False # descending sort if true 80 return
81
82 - def setorderby(self,field,desc=False):
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
89 - def addfield(self,fieldname):
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
95 - def addwhere(self,boolTest):
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
102 - def addfrom(self,tableName):
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
109 - def setlimit(self,numOfItems):
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
116 - def __str__(self):
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
145 -class create:
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
153 - def __init__(self,table):
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
181 - def addInt(self,field):
182 ''' 183 SQL integer field 184 @param field: name of the field 185 ''' 186 self.fields.append(field) 187 self.types.append("INTEGER")
188
189 - def addReal(self,field):
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
198 - def addVarChar(self,field,length):
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
208 - def addBool(self,field):
209 ''' 210 SQL Boolean field 211 @param field: name of the field 212 ''' 213 self.fields.append(field) 214 self.types.append("BOOL")
215
216 - def addBitVarying(self,field,length):
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
227 - def addDecimal(self,field,precision=5,scale=0):
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
235 - def addTimestamp(self,field):
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
243 - def __str__(self):
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
256 -class insert:
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 '''
265 - def __init__(self,table):
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
274 - def dump(self):
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
281 - def __str__(self):
282 "Return the SQL string for the insert" 283 if 0==len(self.fields): 284 print "WARNING: empty insert. returning empty string" 285 return "" # FIX: throw exception and a hissy fit 286 287 s = 'INSERT INTO ' + self.table + ' ' 288 289 assert(len(self.fields)==len(self.values)) 290 s1 = '' 291 s2 = '' 292 293 # FIX: insert the 1st without a leading ',' 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 # elif self.values[i] in ('0',0): 307 # s2 += ','+str(self.values[i]) 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]: # FIX: what was I trying to accomplish with this? 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
338 -def sqlInsertStrFromList (table,aList):
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 # Make sure to quote all strings and timestamps 367 # print type(value) 368 # <type 'DateTime'> 369 # What way is better to handle this? 370 #if type(value) == str or type(value) == type(datetime()): ins+="'" 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 #if type(value) == str or type(value) == type(datetime()): ins+="'" 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