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

Source Code for Module ais.sqlhelp

  1  #!/usr/bin/env python 
  2   
  3  __version__ = '$Revision: 5593 $'.split()[1] # See man ident 
  4  __date__ = '$Date: 2007-02-16 09:06:05 -0500 (Fri, 16 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  @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  # 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,dbType='postgres'):
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): s += self.fields[i]+',' 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
148 -class create:
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 = []; # Tuples of (field,typeName,dim,srid) 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
186 - def addPrimaryKey(self,keyName='key'):
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
200 - def addInt(self,field):
201 ''' 202 SQL integer field 203 @param field: name of the field 204 ''' 205 self.fields.append(field) 206 self.types.append("INTEGER")
207
208 - def addReal(self,field):
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
217 - def addVarChar(self,field,length):
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
227 - def addBool(self,field):
228 ''' 229 SQL Boolean field 230 @param field: name of the field 231 ''' 232 self.fields.append(field) 233 self.types.append("BOOL")
234
235 - def addBitVarying(self,field,length):
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
246 - def addDecimal(self,field,precision=5,scale=0):
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
254 - def addTimestamp(self,field):
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) # Force this to be an int 280 self.postgis.append((field,typeName,dimension,SRID))
281 282
283 - def __str__(self):
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
317 -class insert:
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
339 - def dump(self):
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
346 - def __str__(self):
347 "Return the SQL string for the insert" 348 if 0==len(self.fields): 349 print "WARNING: empty insert. returning empty string" 350 return "" # FIX: throw exception and a hissy fit 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 #s1 = '' 363 #s2 = '' 364 365 # FIX: insert the 1st without a leading ',' 366 #s1 += str(self.fields[0]) 367 #if str == type(self.values[0]): s2 += '"'+str(self.values[0])+'"' 368 #else: s2 += str(self.values[0]) 369 370 # FIX: switch to join of strings for speed AND SIMPLICITY!! 371 #s1List=[] 372 s2List=[] 373 for i in range(len(fields)): 374 #s1List.append(str(fields[i])) 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 # Prevent quotes from breaking out of a string/varchar. "" is SQL for " in a character string 415 value = value.replace('"','""') 416 self.fields.append(field) 417 self.values.append(value) 418 return
419 420 421 422 ###################################################################### 423 import datetime 424
425 -def sqlInsertStrFromList (table,aList,dbType='postgres'):
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 # Make sure to quote all strings and timestamps 453 # print type(value) 454 # <type 'DateTime'> 455 # What way is better to handle this? 456 #if type(value) == str or type(value) == type(datetime()): ins+="'" 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 #if type(value) == str or type(value) == type(datetime()): ins+="'" 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