Package backend :: Package server :: Package rhnSQL :: Module sql_base
[hide private]
[frames] | no frames]

Source Code for Module backend.server.rhnSQL.sql_base

  1  # 
  2  # Copyright (c) 2008--2016 Red Hat, Inc. 
  3  # 
  4  # This software is licensed to you under the GNU General Public License, 
  5  # version 2 (GPLv2). There is NO WARRANTY for this software, express or 
  6  # implied, including the implied warranties of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 
  8  # along with this software; if not, see 
  9  # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. 
 10  # 
 11  # Red Hat trademarks are not licensed under GPLv2. No permission is 
 12  # granted to use or replicate Red Hat trademarks that are incorporated 
 13  # in this software or its documentation. 
 14  # 
 15  # This file defines the base classes for the objects and classes used 
 16  # by the generic SQL interfaces so we can make sure that all backends 
 17  # adhere and provide the same API to the generic layer 
 18  # 
 19  # This file provides a skeleton defnition of functions RHN uses and 
 20  # expects to be available. The interface drivers should only inherit 
 21  # from the Database class and feel free to use their own cursors, 
 22  # provided they make available the methods defined by the Cursor 
 23  # class. 
 24  # 
 25   
 26  import string 
 27  import sys 
 28  import sql_types 
 29  from spacewalk.common import usix 
 30   
 31   
32 -def ociDict(names=None, row=None):
33 """ Create a dictionary from a row description and its values. """ 34 data = {} 35 if not names: 36 raise AttributeError("Class initialization requires a description hash") 37 if row is None: 38 return data 39 for x in range(len(names)): 40 name, value = __oci_name_value(names[x], row[x]) 41 data[name] = value 42 return data
43 44
45 -def __oci_name_value(names, value):
46 """ Extract the name, value pair needed by ociDict function. """ 47 # the format of the names is 48 name, dbitype, dsize, dbsize, prec, scale, nullok = names 49 name = name.lower() 50 return name, value
51 52 53 # this is for when an execute statement went bad...
54 -class SQLError(Exception):
55 pass
56 57 58 # other Schema Errors
59 -class SQLSchemaError(SQLError):
60
61 - def __init__(self, errno, errmsg, *args):
62 self.errno = errno 63 (self.errmsg, errmsg) = string.split(errmsg, '\n', 1) 64 SQLError.__init__(self, self.errno, self.errmsg, errmsg, *args)
65 66 67 # SQL connect error
68 -class SQLConnectError(SQLError):
69
70 - def __init__(self, db, errno, errmsg, *args):
71 self.db = db 72 self.errno = errno 73 self.errmsg = errmsg 74 SQLError.__init__(self, errno, errmsg, db, *args)
75 76 77 # Cannot prepare statement
78 -class SQLStatementPrepareError(SQLError):
79
80 - def __init__(self, db, errmsg, *args):
81 self.db = db 82 self.errmsg = errmsg 83 SQLError.__init__(self, errmsg, db, *args)
84 85
86 -class ModifiedRowError(SQLError):
87 pass
88 89
90 -class Cursor:
91 92 """ A class to implement generic SQL Cursor operations. """ 93 94 # The cursor cache is a hash of: 95 # id(dbh) as keys 96 # hash with the sql statement as a key and the cursor as a value 97 _cursor_cache = {} 98
99 - def __init__(self, dbh=None, sql=None, force=None):
100 self.sql = sql 101 self.dbh = dbh 102 103 self.reparsed = 0 104 self._real_cursor = None 105 self._dbh_id = id(dbh) 106 107 self.description = None 108 109 if self._dbh_id not in self._cursor_cache: 110 self._cursor_cache[self._dbh_id] = {} 111 112 # Store a reference to the underlying Python DB API Cursor: 113 self._real_cursor = self._prepare(force=force)
114
115 - def _prepare_sql(self):
116 raise NotImplementedError()
117
118 - def _prepare(self, force=None):
119 if self.sql: 120 # Check the cache 121 _h = self._cursor_cache[self._dbh_id] 122 if not force and self.sql in _h: 123 return _h[self.sql] 124 cursor = self._prepare_sql() 125 if self.sql: 126 _h[self.sql] = cursor 127 return cursor
128
129 - def prepare(self, sql, force=None):
130 """ 131 Prepares the current statement. 132 133 Must be called prior to execute even if the underlying database driver 134 does not support an explicit prepare before execution. 135 """ 136 if sql is None: 137 raise Exception("XXX Unable to prepare None") 138 self.sql = sql 139 self._real_cursor = self._prepare(force=force)
140
141 - def update_blob(self, table_name, column_name, where_clause, 142 data, **kwargs):
143 """ 144 Abstraction for the update of a blob column which can vary wildly 145 between different database implementations. 146 """ 147 raise NotImplementedError()
148
149 - def execute(self, *p, **kw):
150 """ Execute a single query. """ 151 return self._execute_wrapper(self._execute, *p, **kw)
152
153 - def executemany(self, *p, **kw):
154 """ 155 Execute a query multiple times with different data sets. 156 157 Call with keyword arguments mapping to ordered lists. 158 i.e. cursor.executemany(id=[1, 2], name=["Bill", "Mary"]) 159 """ 160 return self._execute_wrapper(self._executemany, *p, **kw)
161
162 - def execute_bulk(self, dict, chunk_size=100):
163 """ 164 Uses executemany but chops the incoming dict into chunks for each 165 call. 166 167 When attempting to execute bulk operations with a lot of rows in the 168 arrays, 169 Oracle may occasionally lock (probably the oracle client library). 170 I noticed this previously with the import code. -- misa 171 This function executes bulk operations in smaller chunks 172 dict is supposed to be the dictionary that we normally apply to 173 statement.execute. 174 """ 175 ret = 0 176 start_chunk = 0 177 while 1: 178 subdict = {} 179 for k, arr in dict.items(): 180 subarr = arr[start_chunk:start_chunk + chunk_size] 181 if not subarr: 182 # Nothing more to do here - we exhausted the array(s) 183 return ret 184 subdict[k] = subarr 185 ret = ret + self.executemany(**subdict) 186 start_chunk = start_chunk + chunk_size 187 188 # Should never reach this point 189 return ret
190
191 - def _execute_wrapper(self, function, *p, **kw):
192 """ 193 Database specific execute wrapper. Mostly used just to catch DB 194 exceptions and wrap them. 195 196 Must be subclasses by database specific drivers. 197 """ 198 raise NotImplementedError()
199
200 - def _execute(self, *args, **kwargs):
201 if kwargs: 202 val = list(kwargs.values())[0] 203 if self._is_sequence_type(val): 204 sys.stderr.write("WARNING: calling execute with named bound arrays\n") 205 return self._execute_(args, kwargs)
206
207 - def _executemany(self, *args, **kwargs):
208 raise NotImplementedError()
209
210 - def _execute_(self, args, kwargs):
211 """ Database specific execution of the query. """ 212 raise NotImplementedError()
213 214 # DATA RETRIEVAL 215 # Please note: these functions return None if no data is available, 216 # not an empty tuple or a list of empty tuples, or an empty list 217 # or any other combination you can imagine with the word "empty" in it.
218 - def fetchone(self):
219 return self._real_cursor.fetchone()
220
221 - def fetchall(self):
222 rows = self._real_cursor.fetchall() 223 return rows
224
225 - def fetchone_dict(self):
226 """ 227 Return a dictionary for the row returned mapping column name to 228 it's value. 229 """ 230 ret = ociDict(self.description, self._real_cursor.fetchone()) 231 232 if len(ret) == 0: 233 return None 234 return ret
235
236 - def fetchall_dict(self):
237 """ 238 Fetch all rows as a list of dictionaries. 239 """ 240 rows = self._real_cursor.fetchall() 241 242 ret = [] 243 for x in rows: 244 d = ociDict(self.description, x) 245 if len(d) > 0: 246 ret.append(d) 247 if ret == []: 248 return None 249 return ret
250
251 - def _is_sequence_type(self, val):
252 if type(val) in (usix.ListType, usix.TupleType): 253 return 1 254 return 0
255 256
257 -class Procedure:
258 259 """ 260 Class for calling out to stored procedures. 261 262 Written to behave very much like a Python function in that these 263 Procedure objects are "callable". 264 265 See database specific implementations for more details. 266 """ 267
268 - def __init__(self, name, cursor):
269 self.name = name 270 self.cursor = cursor
271
272 - def __del__(self):
273 if self.cursor: 274 self.cursor.close() 275 self.cursor = None
276 277
278 -class Database:
279 280 """ 281 Base class for handling database operations. 282 283 Inherited from by the backend specific classes for Oracle, PostgreSQL, etc. 284 """ 285 _procedure_class = Procedure 286 TimestampFromTicks = None 287
288 - def __init__(self):
289 pass
290
291 - def connect(self, reconnect=1):
292 """ Opens a connection to the database. """ 293 raise NotImplementedError()
294
295 - def check_connection(self):
296 """ Check that this connection is still valid. """ 297 # Delegates to sub-classes as this is usually done with a DB specific 298 # query: 299 raise NotImplementedError()
300
301 - def prepare(self, sql, force=0):
302 """ Prepare an SQL statement. """ 303 raise NotImplementedError()
304
305 - def commit(self):
306 """ Commit changes """ 307 raise NotImplementedError()
308
309 - def procedure(self, name):
310 """Return a pointer to a callable instance for a given stored 311 procedure. 312 The return value is a (possibly modified) copy of the arguments passed 313 in. see cx_Oracle's Cursor.callproc for more details""" 314 return self._procedure_class(name, None) 315 316 return self._procedure_class(name, None)
317
318 - def function(self, name, ret_type):
319 """ 320 Return a pointer to a callable instance for a given stored 321 function. 322 323 The return value is the return value of the function. 324 One has to properly define the return type for the function, since 325 usually the database drivers do not allow for auto-discovery. 326 See cx_Oracle's Cursor.callfunc for more details. 327 """ 328 if not isinstance(ret_type, sql_types.DatabaseDataType): 329 raise SQLError("Invalid return type specified", ret_type) 330 return self._function(name, ret_type)
331
332 - def _function(self, name, ret_type):
333 raise NotImplementedError()
334
335 - def transaction(self, name):
336 "set a transaction point to which we can rollback to" 337 pass
338
339 - def rollback(self, name=None):
340 "rollback changes, optionally to a previously set transaction point" 341 pass
342
343 - def close(self):
344 "Close the connection" 345 pass
346
347 - def cursor(self):
348 "return an empty Cursor object" 349 return Cursor()
350
351 - def _fix_environment_vars(self):
352 "Fix environment variables (to be redefined in subclasses)" 353 pass
354
355 - def _read_lob(self, lob):
356 "Reads a lob's contents" 357 return None
358
359 - def is_connected_to(self, backend, host, port, username, password, 360 database, sslmode):
361 """ 362 Check if this database matches the given connection parameters. 363 """ 364 raise NotImplementedError()
365
366 - def Date(self, year, month, day):
367 "Returns a Date object" 368 raise NotImplementedError()
369
370 - def DateFromTicks(self, ticks):
371 "Returns a Date object" 372 raise NotImplementedError()
373 374 375 # Class that we use just as a markup for queries/statements; if the statement 376 # is available upon import, we can automatically check for the statements' 377 # correctness
378 -class Statement:
379
380 - def __init__(self, statement):
381 self.statement = statement
382
383 - def __repr__(self):
384 return "<%s instance at %s; statement=%s" % ( 385 self.__class__, id(self), self.statement)
386
387 - def __str__(self):
388 return self.statement
389