1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
28 DB_BACKENDS={'sqlite': 'pysqlite2.dbapi2'}
29
30
31 BACKEND_SPECIFIC_SQL={'sqlite':"""\
32 PRAGMA default_synchronous = OFF;
33 pragma default_cache_size = 66666;
34 """
35 }
36 TABLE_COLUMNS = {}
37
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
55
57 self.message = message
58
61
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
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
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
123 self._params = {'database': database,'check_same_thread': True}
124
125 self.connect()
126
128 """
129 Commit changes to the database and disconnect.
130 """
131 self.save_changes()
132 self._db.close()
133
135 """
136 Connect to the database, set L{_db} instance variable.
137 """
138 self._db = self._db_module.connect(**self._params)
139
140
141 self._build_rows = False
142
144 """
145 Disconnect and reconnect to the database.
146 """
147 self.disconnect()
148 self.connect()
149
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
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
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
234 else:
235 if cursor.description:
236 all_rows = cursor.fetchall()
237 if not self._build_rows:
238
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
268
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
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