Package elisa :: Package core :: Module db_backend
[hide private]
[frames] | no frames]

Source Code for Module elisa.core.db_backend

  1  # -*- coding: utf-8 -*- 
  2  # Elisa - Home multimedia server 
  3  # Copyright (C) 2006-2008 Fluendo Embedded S.L. (www.fluendo.com). 
  4  # All rights reserved. 
  5  # 
  6  # This file is available under one of two license agreements. 
  7  # 
  8  # This file is licensed under the GPL version 3. 
  9  # See "LICENSE.GPL" in the root of this distribution including a special 
 10  # exception to use Elisa with Fluendo's plugins. 
 11  # 
 12  # The GPL part of Elisa is also available under a commercial licensing 
 13  # agreement from Fluendo. 
 14  # See "LICENSE.Elisa" in the root directory of this distribution package 
 15  # for details on that license. 
 16   
 17   
 18  __maintainer__ = 'Philippe Normand <philippe@fluendo.com>' 
 19   
 20   
 21  from elisa.core import log, media_uri 
 22  from elisa.core.utils import locale_helper 
 23  from elisa.extern import db_row, translation 
 24  import time 
 25  import re, platform 
 26   
 27  # provide user friendly names for db adaptors 
 28  DB_BACKENDS={'sqlite': 'pysqlite2.dbapi2'} 
 29   
 30  # increase cache size from 2000 to 66666 pages 
 31  BACKEND_SPECIFIC_SQL={'sqlite':"""\ 
 32  PRAGMA default_synchronous = OFF; 
 33  pragma default_cache_size = 66666; 
 34  """ 
 35                        } 
 36  TABLE_COLUMNS = {} 
 37   
38 -def table_names(schema):
39 """ 40 Scan given db SQL schema and find all table names by looking for 41 "CREATE TABLE" statements. 42 43 @param schema: SQL schema definition 44 @type schema: string 45 @rtype: list of strings 46 """ 47 names = [] 48 for line in schema.splitlines(): 49 result = re.search('CREATE TABLE (\w+)\s*', line) 50 if result: 51 names.append(result.groups()[0]) 52 return names
53
54 -class DBBackendError(Exception):
55
56 - def __init__(self, message):
57 self.message = message
58
59 - def __str__(self):
60 return self.message
61
62 -class DBBackend(log.Loggable):
63 """ 64 Python DB API 2.0 backend support. 65 66 In addition to DB API 2.0 support this class implements an SQL 67 query filter, based on a set of rules (which remain to be 68 defined...) 69 70 Components can alter the database by adding new tables in the 71 schema. These new tables can only be accessed via raw SQL queries. 72 73 @todo: We need to define a set of rules that specify which 74 component can alter/insert data in core_tables. 75 76 """ 77
78 - def __init__(self, **config):
79 """ Connect to a db backend hosting the given database. 80 81 @keyword username: the user name to use to connect to the database 82 @type username: string or None if no username required by backend 83 @keyword password: the password to use to connect to the database 84 @type password: string or None if no password required by backend 85 @keyword hostname: the host name to connect to if the backend runs on 86 a specific machine 87 @type hostname: string or None if no hostname required by backend 88 89 @raise DBBackendError : When no db backend has been specified or if it 90 cannot be used. 91 """ 92 self.log_category = 'db_backend' 93 log.Loggable.__init__(self) 94 95 backend_name = '' 96 database = '' 97 98 self._config = config 99 backend_name = config.get('db_backend') 100 database = config.get('database') 101 charset = locale_helper.system_encoding() 102 database = database.decode(charset).encode('utf8') 103 104 if not backend_name: 105 raise DBBackendError("No db backend specified") 106 107 mod_name = DB_BACKENDS.get(backend_name) 108 self.info("Using %r database backend on %r database", mod_name, database) 109 self.name = backend_name 110 111 try: 112 # import the DBM python package 113 if mod_name.find('.') > -1: 114 parts = mod_name.split('.') 115 self._db_module = __import__(mod_name, globals(), 116 locals(), [parts[-1],]) 117 else: 118 self._db_module = __import__(mod_name, globals(), locals(),[]) 119 except ImportError, error: 120 raise DBBackendError(str(error)) 121 122 # TODO: remove SQLite specific options 123 self._params = {'database': database,'check_same_thread': True} 124 125 self.connect()
126
127 - def disconnect(self):
128 """ 129 Commit changes to the database and disconnect. 130 """ 131 self.save_changes() 132 self._db.close()
133
134 - def connect(self):
135 """ 136 Connect to the database, set L{_db} instance variable. 137 """ 138 self._db = self._db_module.connect(**self._params) 139 140 # TODO: remove these SQLite specific bits 141 self._build_rows = False
142
143 - def reconnect(self):
144 """ 145 Disconnect and reconnect to the database. 146 """ 147 self.disconnect() 148 self.connect()
149
150 - def save_changes(self):
151 """ 152 Commit changes to the database 153 154 @raise Exception: if the save has failed 155 """ 156 157 try: 158 self._db.commit() 159 self.debug('Committed changes to DB') 160 except Exception, ex: 161 self.debug('Commit failed, %s' % ex) 162 raise
163 164
165 - def insert(self, sql_query, *params):
166 """ Execute an INSERT SQL query in the db backend 167 and return the ID fo the row if AUTOINCREMENT is used 168 169 @param sql_query: the SQL query data to execute 170 @type sql_query: string 171 @rtype: int 172 """ 173 174 t0 = time.time() 175 176 cursor = self._db.cursor() 177 result = -1 178 new_params = self._fix_params(params) 179 180 debug_msg = u"%s params=%r" % (sql_query, new_params) 181 debug_msg = u''.join(debug_msg.splitlines()) 182 self.debug(debug_msg) 183 184 try: 185 cursor.execute(sql_query, new_params) 186 except Exception, exception: 187 self.warning(exception) 188 #FIXME: raise again ? 189 else: 190 result = cursor.lastrowid 191 192 cursor.close() 193 delta = time.time() - t0 194 self.log("SQL insert took %s seconds" % delta) 195 196 return result
197
198 - def sql_execute(self, sql_query, *params, **kw):
199 """ Execute a SQL query in the db backend 200 201 @param sql_query: the SQL query data to execute 202 @type sql_query: string 203 @rtype: L{elisa.extern.db_row.DBRow} list 204 """ 205 t0 = time.time() 206 result = self._query(sql_query, *params, **kw) 207 208 delta = time.time() - t0 209 self.log("SQL request took %s seconds" % delta) 210 211 return result
212
213 - def _query(self, request, *params, **keywords):
214 quiet = keywords.get('quiet', False) 215 216 debug_msg = request 217 if params: 218 params = self._fix_params(params) 219 debug_msg = u"%s params=%r" % (request, params) 220 debug_msg = u''.join(debug_msg.splitlines()) 221 if debug_msg: 222 self.debug('QUERY: %s', debug_msg) 223 224 do_commit = keywords.get('do_commit', False) 225 cursor = self._db.cursor() 226 result = [] 227 new_params = self._fix_params(params) 228 try: 229 cursor.execute(request, new_params) 230 except Exception, exception: 231 if not quiet: 232 self.warning(exception) 233 #FIXME: raise again ? 234 else: 235 if cursor.description: 236 all_rows = cursor.fetchall() 237 if not self._build_rows: 238 # self.debug("Query result: %r", all_rows) 239 result = db_row.getdict(all_rows, cursor.description) 240 else: 241 result = all_rows 242 cursor.close() 243 if do_commit: 244 self.save_changes() 245 return result
246
247 - def _fix_params(self, params):
248 # For some unknown reasons escaping uris obtained from MediaUri.join() 249 # doesn't work, so I need to check force repr() manually 250 r = [] 251 for p in params: 252 if isinstance(p, media_uri.MediaUri): 253 r.append(unicode(p)) 254 elif isinstance(p, translation.TranslatableSingular): 255 # FIXME: this doesn't really belong here.. should 256 # be in Translatable.__str__ (IMO) 257 r.append(p.format % p.args) 258 elif isinstance(p, translation.TranslatablePlural): 259 # FIXME: this doesn't really belong here.. should 260 # be in Translatable.__str__ (IMO) 261 if len(p.count) > 0: 262 r.append(p.plural % p.args) 263 else: 264 r.append(p.singular % p.args) 265 else: 266 r.append(p) 267 return tuple(r)
268
269 - def table_columns(self, table_name):
270 """ Introspect given table and retrieve its column names in a cache 271 272 The cache is global to the module, not DBBackend instance specific. 273 274 @param table_name: name of the db table to introspect 275 @type table_name: string 276 @rtype: string list 277 """ 278 global TABLE_COLUMNS 279 if table_name not in TABLE_COLUMNS: 280 result = [] 281 282 # FIXME: support more RDBMS here 283 if self._backend_name == 'sqlite': 284 result = self.sql_execute("PRAGMA table_info(%s)" % table_name) 285 286 col_names = set([ r.name for r in result ]) 287 TABLE_COLUMNS[table_name] = col_names 288 289 return TABLE_COLUMNS.get(table_name)
290