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

Source Code for Module ais.sqlhelp

  1  #!/usr/bin/env python 
  2   
  3  __version__ = '$Revision: 5548 $'.split()[1] # See man ident 
  4  __date__ = '$Date: 2007-02-08 09:13:50 -0500 (Thu, 08 Feb 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 comma = '' # Skip the command on the first entry 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 # elif self.values[i] in ('0',0): 309 # s2 += comma+str(self.values[i]) 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]: # FIX: what was I trying to accomplish with this? 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 # Prevent quotes from breaking out of a string/varchar. "" is SQL for " in a character string 335 value = value.replace('"','""') 336 self.fields.append(field) 337 self.values.append(value) 338 return
339 340 341 342 ###################################################################### 343 import datetime 344
345 -def sqlInsertStrFromList (table,aList):
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 # Make sure to quote all strings and timestamps 374 # print type(value) 375 # <type 'DateTime'> 376 # What way is better to handle this? 377 #if type(value) == str or type(value) == type(datetime()): ins+="'" 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 #if type(value) == str or type(value) == type(datetime()): ins+="'" 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