Mercurial > libervia-backend
comparison src/memory/sqlite.py @ 2143:c3cac21157d4
memory (sqlite): introduced component table, schema updated:
components are special profiles, entry_point will be the plugin to launch when they are connected.
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 07 Feb 2017 00:15:03 +0100 |
parents | b536dd121da1 |
children | 1d3f73e065e1 |
comparison
equal
deleted
inserted
replaced
2142:be96beb7ca14 | 2143:c3cac21157d4 |
---|---|
32 import os.path | 32 import os.path |
33 import cPickle as pickle | 33 import cPickle as pickle |
34 import hashlib | 34 import hashlib |
35 import sqlite3 | 35 import sqlite3 |
36 | 36 |
37 CURRENT_DB_VERSION = 3 | 37 CURRENT_DB_VERSION = 4 |
38 | 38 |
39 # XXX: DATABASE schemas are used in the following way: | 39 # XXX: DATABASE schemas are used in the following way: |
40 # - 'current' key is for the actual database schema, for a new base | 40 # - 'current' key is for the actual database schema, for a new base |
41 # - x(int) is for update needed between x-1 and x. All number are needed between y and z to do an update | 41 # - x(int) is for update needed between x-1 and x. All number are needed between y and z to do an update |
42 # e.g.: if CURRENT_DB_VERSION is 6, 'current' is the actuel DB, and to update from version 3, numbers 4, 5 and 6 are needed | 42 # e.g.: if CURRENT_DB_VERSION is 6, 'current' is the actuel DB, and to update from version 3, numbers 4, 5 and 6 are needed |
43 # a 'current' data dict can contains the keys: | 43 # a 'current' data dict can contains the keys: |
44 # - 'CREATE': it contains an Ordered dict with table to create as keys, and a len 2 tuple as value, where value[0] are the columns definitions and value[1] are the table constraints | 44 # - 'CREATE': it contains an Ordered dict with table to create as keys, and a len 2 tuple as value, where value[0] are the columns definitions and value[1] are the table constraints |
45 # - 'INSERT': it contains an Ordered dict with table where values have to be inserted, and many tuples containing values to insert in the order of the rows (#TODO: manage named columns) | 45 # - 'INSERT': it contains an Ordered dict with table where values have to be inserted, and many tuples containing values to insert in the order of the rows (#TODO: manage named columns) |
46 # an update data dict (the ones with a number) can contains the keys 'create', 'delete', 'cols create', 'cols delete', 'cols modify', 'insert' or 'specific'. See Updater.generateUpdateData for more infos. This method can be used to autogenerate update_data, to ease the work of the developers. | 46 # an update data dict (the ones with a number) can contains the keys 'create', 'delete', 'cols create', 'cols delete', 'cols modify', 'insert' or 'specific'. See Updater.generateUpdateData for more infos. This method can be used to autogenerate update_data, to ease the work of the developers. |
47 # TODO: this database currently doesn't use indexes, it should | |
47 | 48 |
48 DATABASE_SCHEMAS = { | 49 DATABASE_SCHEMAS = { |
49 "current": {'CREATE': OrderedDict(( | 50 "current": {'CREATE': OrderedDict(( |
50 ('profiles', (("id INTEGER PRIMARY KEY ASC", "name TEXT"), | 51 ('profiles', (("id INTEGER PRIMARY KEY ASC", "name TEXT"), |
51 ("UNIQUE (name)",))), | 52 ("UNIQUE (name)",))), |
53 ('components', (("profile_id INTEGER PRIMARY KEY", "entry_point TEXT NOT NULL"), | |
54 ("FOREIGN KEY(profile_id) REFERENCES profiles(id) ON DELETE CASCADE",))), | |
52 ('message_types', (("type TEXT PRIMARY KEY",), | 55 ('message_types', (("type TEXT PRIMARY KEY",), |
53 tuple())), | 56 tuple())), |
54 ('history', (("uid TEXT PRIMARY KEY", "update_uid TEXT", "profile_id INTEGER", "source TEXT", "dest TEXT", "source_res TEXT", "dest_res TEXT", | 57 ('history', (("uid TEXT PRIMARY KEY", "update_uid TEXT", "profile_id INTEGER", "source TEXT", "dest TEXT", "source_res TEXT", "dest_res TEXT", |
55 "timestamp DATETIME NOT NULL", "received_timestamp DATETIME", # XXX: timestamp is the time when the message was emitted. If received time stamp is not NULL, the message was delayed and timestamp is the declared value (and received_timestamp the time of reception) | 58 "timestamp DATETIME NOT NULL", "received_timestamp DATETIME", # XXX: timestamp is the time when the message was emitted. If received time stamp is not NULL, the message was delayed and timestamp is the declared value (and received_timestamp the time of reception) |
56 "type TEXT", "extra BLOB"), | 59 "type TEXT", "extra BLOB"), |
83 ("'normal'",), | 86 ("'normal'",), |
84 ("'info'",) # info is not standard, but used to keep track of info like join/leave in a MUC | 87 ("'info'",) # info is not standard, but used to keep track of info like join/leave in a MUC |
85 )), | 88 )), |
86 )), | 89 )), |
87 }, | 90 }, |
91 4: {'create': {'components': (('profile_id INTEGER PRIMARY KEY', 'entry_point TEXT NOT NULL'), ('FOREIGN KEY(profile_id) REFERENCES profiles(id) ON DELETE CASCADE',))} | |
92 }, | |
88 3: {'specific': 'update_v3' | 93 3: {'specific': 'update_v3' |
89 }, | 94 }, |
90 2: {'specific': 'update2raw_v2' | 95 2: {'specific': 'update2raw_v2' |
91 }, | 96 }, |
92 1: {'cols create': {'history': ('extra BLOB',)}, | 97 1: {'cols create': {'history': ('extra BLOB',)}, |
111 class SqliteStorage(object): | 116 class SqliteStorage(object): |
112 """This class manage storage with Sqlite database""" | 117 """This class manage storage with Sqlite database""" |
113 | 118 |
114 def __init__(self, db_filename, sat_version): | 119 def __init__(self, db_filename, sat_version): |
115 """Connect to the given database | 120 """Connect to the given database |
116 @param db_filename: full path to the Sqlite database""" | 121 |
122 @param db_filename: full path to the Sqlite database | |
123 """ | |
117 self.initialized = defer.Deferred() # triggered when memory is fully initialised and ready | 124 self.initialized = defer.Deferred() # triggered when memory is fully initialised and ready |
118 self.profiles = {} # we keep cache for the profiles (key: profile name, value: profile id) | 125 self.profiles = {} # we keep cache for the profiles (key: profile name, value: profile id) |
119 | 126 |
120 log.info(_("Connecting database")) | 127 log.info(_("Connecting database")) |
121 new_base = not os.path.exists(db_filename) # do we have to create the database ? | 128 new_base = not os.path.exists(db_filename) # do we have to create the database ? |
152 | 159 |
153 init_defer.addCallback(lambda ignore: getNewBaseSql() if new_base else getUpdateSql()) | 160 init_defer.addCallback(lambda ignore: getNewBaseSql() if new_base else getUpdateSql()) |
154 init_defer.addCallback(commitStatements) | 161 init_defer.addCallback(commitStatements) |
155 | 162 |
156 def fillProfileCache(ignore): | 163 def fillProfileCache(ignore): |
157 d = self.dbpool.runQuery("SELECT name,id FROM profiles").addCallback(self._profilesCache) | 164 d = self.dbpool.runQuery("SELECT profile_id, entry_point FROM components").addCallback(self._cacheComponentsAndProfiles) |
158 d.chainDeferred(self.initialized) | 165 d.chainDeferred(self.initialized) |
159 | 166 |
160 init_defer.addCallback(fillProfileCache) | 167 init_defer.addCallback(fillProfileCache) |
161 | 168 |
162 def _updateDb(self, interaction, statements): | 169 def _updateDb(self, interaction, statements): |
163 for statement in statements: | 170 for statement in statements: |
164 interaction.execute(statement) | 171 interaction.execute(statement) |
165 | 172 |
166 #Profiles | 173 #Profiles |
167 def _profilesCache(self, profiles_result): | 174 |
175 def _cacheComponentsAndProfiles(self, components_result): | |
176 """Get components results and send requests profiles | |
177 | |
178 they will be both put in cache in _profilesCache | |
179 """ | |
180 return self.dbpool.runQuery("SELECT name,id FROM profiles").addCallback( | |
181 self._cacheComponentsAndProfiles2, components_result) | |
182 | |
183 def _cacheComponentsAndProfiles2(self, profiles_result, components): | |
168 """Fill the profiles cache | 184 """Fill the profiles cache |
169 @param profiles_result: result of the sql profiles query""" | 185 |
186 @param profiles_result: result of the sql profiles query | |
187 """ | |
188 self.components = dict(components) | |
170 for profile in profiles_result: | 189 for profile in profiles_result: |
171 name, id_ = profile | 190 name, id_ = profile |
172 self.profiles[name] = id_ | 191 self.profiles[name] = id_ |
173 | 192 |
174 def getProfilesList(self): | 193 def getProfilesList(self): |
175 """"Return list of all registered profiles""" | 194 """"Return list of all registered profiles""" |
176 return self.profiles.keys() | 195 return self.profiles.keys() |
177 | 196 |
178 def hasProfile(self, profile_name): | 197 def hasProfile(self, profile_name): |
179 """return True if profile_name exists | 198 """return True if profile_name exists |
180 @param profile_name: name of the profile to check""" | 199 |
200 @param profile_name: name of the profile to check | |
201 """ | |
181 return profile_name in self.profiles | 202 return profile_name in self.profiles |
203 | |
204 def profileIsComponent(self, profile_name): | |
205 try: | |
206 return self.profiles[profile_name] in self.components | |
207 except KeyError: | |
208 raise exceptions.NotFound(u"the requested profile doesn't exists") | |
182 | 209 |
183 def createProfile(self, name): | 210 def createProfile(self, name): |
184 """Create a new profile | 211 """Create a new profile |
212 | |
185 @param name: name of the profile | 213 @param name: name of the profile |
186 @return: deferred triggered once profile is actually created""" | 214 @return: deferred triggered once profile is actually created |
215 """ | |
187 | 216 |
188 def getProfileId(ignore): | 217 def getProfileId(ignore): |
189 return self.dbpool.runQuery("SELECT (id) FROM profiles WHERE name = ?", (name, )) | 218 return self.dbpool.runQuery("SELECT (id) FROM profiles WHERE name = ?", (name, )) |
190 | 219 |
191 def profile_created(profile_id): | 220 def profile_created(profile_id): |
197 d.addCallback(profile_created) | 226 d.addCallback(profile_created) |
198 return d | 227 return d |
199 | 228 |
200 def deleteProfile(self, name): | 229 def deleteProfile(self, name): |
201 """Delete profile | 230 """Delete profile |
231 | |
202 @param name: name of the profile | 232 @param name: name of the profile |
203 @return: deferred triggered once profile is actually deleted""" | 233 @return: deferred triggered once profile is actually deleted |
234 """ | |
204 def deletionError(failure_): | 235 def deletionError(failure_): |
205 log.error(_(u"Can't delete profile [%s]") % name) | 236 log.error(_(u"Can't delete profile [%s]") % name) |
206 return failure_ | 237 return failure_ |
207 | 238 |
208 def delete(txn): | 239 def delete(txn): |
214 # this need more investigation | 245 # this need more investigation |
215 txn.execute("DELETE FROM history WHERE profile_id = ?", (profile_id,)) | 246 txn.execute("DELETE FROM history WHERE profile_id = ?", (profile_id,)) |
216 txn.execute("DELETE FROM param_ind WHERE profile_id = ?", (profile_id,)) | 247 txn.execute("DELETE FROM param_ind WHERE profile_id = ?", (profile_id,)) |
217 txn.execute("DELETE FROM private_ind WHERE profile_id = ?", (profile_id,)) | 248 txn.execute("DELETE FROM private_ind WHERE profile_id = ?", (profile_id,)) |
218 txn.execute("DELETE FROM private_ind_bin WHERE profile_id = ?", (profile_id,)) | 249 txn.execute("DELETE FROM private_ind_bin WHERE profile_id = ?", (profile_id,)) |
250 txn.execute("DELETE FROM components WHERE profile_id = ?", (profile_id,)) | |
219 return None | 251 return None |
220 | 252 |
221 d = self.dbpool.runInteraction(delete) | 253 d = self.dbpool.runInteraction(delete) |
222 d.addCallback(lambda ignore: log.info(_("Profile [%s] deleted") % name)) | 254 d.addCallback(lambda ignore: log.info(_("Profile [%s] deleted") % name)) |
223 d.addErrback(deletionError) | 255 d.addErrback(deletionError) |
224 return d | 256 return d |
225 | 257 |
226 #Params | 258 #Params |
227 def loadGenParams(self, params_gen): | 259 def loadGenParams(self, params_gen): |
228 """Load general parameters | 260 """Load general parameters |
261 | |
229 @param params_gen: dictionary to fill | 262 @param params_gen: dictionary to fill |
230 @return: deferred""" | 263 @return: deferred |
264 """ | |
231 | 265 |
232 def fillParams(result): | 266 def fillParams(result): |
233 for param in result: | 267 for param in result: |
234 category, name, value = param | 268 category, name, value = param |
235 params_gen[(category, name)] = value | 269 params_gen[(category, name)] = value |
236 log.debug(_(u"loading general parameters from database")) | 270 log.debug(_(u"loading general parameters from database")) |
237 return self.dbpool.runQuery("SELECT category,name,value FROM param_gen").addCallback(fillParams) | 271 return self.dbpool.runQuery("SELECT category,name,value FROM param_gen").addCallback(fillParams) |
238 | 272 |
239 def loadIndParams(self, params_ind, profile): | 273 def loadIndParams(self, params_ind, profile): |
240 """Load individual parameters | 274 """Load individual parameters |
275 | |
241 @param params_ind: dictionary to fill | 276 @param params_ind: dictionary to fill |
242 @param profile: a profile which *must* exist | 277 @param profile: a profile which *must* exist |
243 @return: deferred""" | 278 @return: deferred |
279 """ | |
244 | 280 |
245 def fillParams(result): | 281 def fillParams(result): |
246 for param in result: | 282 for param in result: |
247 category, name, value = param | 283 category, name, value = param |
248 params_ind[(category, name)] = value | 284 params_ind[(category, name)] = value |
251 d.addCallback(fillParams) | 287 d.addCallback(fillParams) |
252 return d | 288 return d |
253 | 289 |
254 def getIndParam(self, category, name, profile): | 290 def getIndParam(self, category, name, profile): |
255 """Ask database for the value of one specific individual parameter | 291 """Ask database for the value of one specific individual parameter |
292 | |
256 @param category: category of the parameter | 293 @param category: category of the parameter |
257 @param name: name of the parameter | 294 @param name: name of the parameter |
258 @param profile: %(doc_profile)s | 295 @param profile: %(doc_profile)s |
259 @return: deferred""" | 296 @return: deferred |
297 """ | |
260 d = self.dbpool.runQuery("SELECT value FROM param_ind WHERE category=? AND name=? AND profile_id=?", (category, name, self.profiles[profile])) | 298 d = self.dbpool.runQuery("SELECT value FROM param_ind WHERE category=? AND name=? AND profile_id=?", (category, name, self.profiles[profile])) |
261 d.addCallback(self.__getFirstResult) | 299 d.addCallback(self.__getFirstResult) |
262 return d | 300 return d |
263 | 301 |
264 def setGenParam(self, category, name, value): | 302 def setGenParam(self, category, name, value): |
271 d.addErrback(lambda ignore: log.error(_(u"Can't set general parameter (%(category)s/%(name)s) in database" % {"category": category, "name": name}))) | 309 d.addErrback(lambda ignore: log.error(_(u"Can't set general parameter (%(category)s/%(name)s) in database" % {"category": category, "name": name}))) |
272 return d | 310 return d |
273 | 311 |
274 def setIndParam(self, category, name, value, profile): | 312 def setIndParam(self, category, name, value, profile): |
275 """Save the individual parameters in database | 313 """Save the individual parameters in database |
314 | |
276 @param category: category of the parameter | 315 @param category: category of the parameter |
277 @param name: name of the parameter | 316 @param name: name of the parameter |
278 @param value: value to set | 317 @param value: value to set |
279 @param profile: a profile which *must* exist | 318 @param profile: a profile which *must* exist |
280 @return: deferred""" | 319 @return: deferred |
320 """ | |
281 d = self.dbpool.runQuery("REPLACE INTO param_ind(category,name,profile_id,value) VALUES (?,?,?,?)", (category, name, self.profiles[profile], value)) | 321 d = self.dbpool.runQuery("REPLACE INTO param_ind(category,name,profile_id,value) VALUES (?,?,?,?)", (category, name, self.profiles[profile], value)) |
282 d.addErrback(lambda ignore: log.error(_(u"Can't set individual parameter (%(category)s/%(name)s) for [%(profile)s] in database" % {"category": category, "name": name, "profile": profile}))) | 322 d.addErrback(lambda ignore: log.error(_(u"Can't set individual parameter (%(category)s/%(name)s) for [%(profile)s] in database" % {"category": category, "name": name, "profile": profile}))) |
283 return d | 323 return d |
284 | 324 |
285 #History | 325 #History |
465 return d | 505 return d |
466 | 506 |
467 #Private values | 507 #Private values |
468 def loadGenPrivates(self, private_gen, namespace): | 508 def loadGenPrivates(self, private_gen, namespace): |
469 """Load general private values | 509 """Load general private values |
510 | |
470 @param private_gen: dictionary to fill | 511 @param private_gen: dictionary to fill |
471 @param namespace: namespace of the values | 512 @param namespace: namespace of the values |
472 @return: deferred""" | 513 @return: deferred |
514 """ | |
473 | 515 |
474 def fillPrivates(result): | 516 def fillPrivates(result): |
475 for private in result: | 517 for private in result: |
476 key, value = private | 518 key, value = private |
477 private_gen[key] = value | 519 private_gen[key] = value |
479 d = self.dbpool.runQuery("SELECT key,value FROM private_gen WHERE namespace=?", (namespace, )).addCallback(fillPrivates) | 521 d = self.dbpool.runQuery("SELECT key,value FROM private_gen WHERE namespace=?", (namespace, )).addCallback(fillPrivates) |
480 return d.addErrback(lambda x: log.debug(_(u"No data present in database for namespace %s") % namespace)) | 522 return d.addErrback(lambda x: log.debug(_(u"No data present in database for namespace %s") % namespace)) |
481 | 523 |
482 def loadIndPrivates(self, private_ind, namespace, profile): | 524 def loadIndPrivates(self, private_ind, namespace, profile): |
483 """Load individual private values | 525 """Load individual private values |
526 | |
484 @param privates_ind: dictionary to fill | 527 @param privates_ind: dictionary to fill |
485 @param namespace: namespace of the values | 528 @param namespace: namespace of the values |
486 @param profile: a profile which *must* exist | 529 @param profile: a profile which *must* exist |
487 @return: deferred""" | 530 @return: deferred |
531 """ | |
488 | 532 |
489 def fillPrivates(result): | 533 def fillPrivates(result): |
490 for private in result: | 534 for private in result: |
491 key, value = private | 535 key, value = private |
492 private_ind[key] = value | 536 private_ind[key] = value |
495 d.addCallback(fillPrivates) | 539 d.addCallback(fillPrivates) |
496 return d.addErrback(lambda x: log.debug(_(u"No data present in database for namespace %s") % namespace)) | 540 return d.addErrback(lambda x: log.debug(_(u"No data present in database for namespace %s") % namespace)) |
497 | 541 |
498 def setGenPrivate(self, namespace, key, value): | 542 def setGenPrivate(self, namespace, key, value): |
499 """Save the general private value in database | 543 """Save the general private value in database |
544 | |
500 @param category: category of the privateeter | 545 @param category: category of the privateeter |
501 @param key: key of the private value | 546 @param key: key of the private value |
502 @param value: value to set | 547 @param value: value to set |
503 @return: deferred""" | 548 @return: deferred |
549 """ | |
504 d = self.dbpool.runQuery("REPLACE INTO private_gen(namespace,key,value) VALUES (?,?,?)", (namespace, key, value)) | 550 d = self.dbpool.runQuery("REPLACE INTO private_gen(namespace,key,value) VALUES (?,?,?)", (namespace, key, value)) |
505 d.addErrback(lambda ignore: log.error(_(u"Can't set general private value (%(key)s) [namespace:%(namespace)s] in database" % | 551 d.addErrback(lambda ignore: log.error(_(u"Can't set general private value (%(key)s) [namespace:%(namespace)s] in database" % |
506 {"namespace": namespace, "key": key}))) | 552 {"namespace": namespace, "key": key}))) |
507 return d | 553 return d |
508 | 554 |
509 def setIndPrivate(self, namespace, key, value, profile): | 555 def setIndPrivate(self, namespace, key, value, profile): |
510 """Save the individual private value in database | 556 """Save the individual private value in database |
557 | |
511 @param namespace: namespace of the value | 558 @param namespace: namespace of the value |
512 @param key: key of the private value | 559 @param key: key of the private value |
513 @param value: value to set | 560 @param value: value to set |
514 @param profile: a profile which *must* exist | 561 @param profile: a profile which *must* exist |
515 @return: deferred""" | 562 @return: deferred |
563 """ | |
516 d = self.dbpool.runQuery("REPLACE INTO private_ind(namespace,key,profile_id,value) VALUES (?,?,?,?)", (namespace, key, self.profiles[profile], value)) | 564 d = self.dbpool.runQuery("REPLACE INTO private_ind(namespace,key,profile_id,value) VALUES (?,?,?,?)", (namespace, key, self.profiles[profile], value)) |
517 d.addErrback(lambda ignore: log.error(_(u"Can't set individual private value (%(key)s) [namespace: %(namespace)s] for [%(profile)s] in database" % | 565 d.addErrback(lambda ignore: log.error(_(u"Can't set individual private value (%(key)s) [namespace: %(namespace)s] for [%(profile)s] in database" % |
518 {"namespace": namespace, "key": key, "profile": profile}))) | 566 {"namespace": namespace, "key": key, "profile": profile}))) |
519 return d | 567 return d |
520 | 568 |
521 def delGenPrivate(self, namespace, key): | 569 def delGenPrivate(self, namespace, key): |
522 """Delete the general private value from database | 570 """Delete the general private value from database |
571 | |
523 @param category: category of the privateeter | 572 @param category: category of the privateeter |
524 @param key: key of the private value | 573 @param key: key of the private value |
525 @return: deferred""" | 574 @return: deferred |
575 """ | |
526 d = self.dbpool.runQuery("DELETE FROM private_gen WHERE namespace=? AND key=?", (namespace, key)) | 576 d = self.dbpool.runQuery("DELETE FROM private_gen WHERE namespace=? AND key=?", (namespace, key)) |
527 d.addErrback(lambda ignore: log.error(_(u"Can't delete general private value (%(key)s) [namespace:%(namespace)s] in database" % | 577 d.addErrback(lambda ignore: log.error(_(u"Can't delete general private value (%(key)s) [namespace:%(namespace)s] in database" % |
528 {"namespace": namespace, "key": key}))) | 578 {"namespace": namespace, "key": key}))) |
529 return d | 579 return d |
530 | 580 |
531 def delIndPrivate(self, namespace, key, profile): | 581 def delIndPrivate(self, namespace, key, profile): |
532 """Delete the individual private value from database | 582 """Delete the individual private value from database |
583 | |
533 @param namespace: namespace of the value | 584 @param namespace: namespace of the value |
534 @param key: key of the private value | 585 @param key: key of the private value |
535 @param profile: a profile which *must* exist | 586 @param profile: a profile which *must* exist |
536 @return: deferred""" | 587 @return: deferred |
588 """ | |
537 d = self.dbpool.runQuery("DELETE FROM private_ind WHERE namespace=? AND key=? AND profile=?)", (namespace, key, self.profiles[profile])) | 589 d = self.dbpool.runQuery("DELETE FROM private_ind WHERE namespace=? AND key=? AND profile=?)", (namespace, key, self.profiles[profile])) |
538 d.addErrback(lambda ignore: log.error(_(u"Can't delete individual private value (%(key)s) [namespace: %(namespace)s] for [%(profile)s] in database" % | 590 d.addErrback(lambda ignore: log.error(_(u"Can't delete individual private value (%(key)s) [namespace: %(namespace)s] for [%(profile)s] in database" % |
539 {"namespace": namespace, "key": key, "profile": profile}))) | 591 {"namespace": namespace, "key": key, "profile": profile}))) |
540 return d | 592 return d |
541 | 593 |
542 def loadGenPrivatesBinary(self, private_gen, namespace): | 594 def loadGenPrivatesBinary(self, private_gen, namespace): |
543 """Load general private binary values | 595 """Load general private binary values |
596 | |
544 @param private_gen: dictionary to fill | 597 @param private_gen: dictionary to fill |
545 @param namespace: namespace of the values | 598 @param namespace: namespace of the values |
546 @return: deferred""" | 599 @return: deferred |
600 """ | |
547 | 601 |
548 def fillPrivates(result): | 602 def fillPrivates(result): |
549 for private in result: | 603 for private in result: |
550 key, value = private | 604 key, value = private |
551 private_gen[key] = pickle.loads(str(value)) | 605 private_gen[key] = pickle.loads(str(value)) |
553 d = self.dbpool.runQuery("SELECT key,value FROM private_gen_bin WHERE namespace=?", (namespace, )).addCallback(fillPrivates) | 607 d = self.dbpool.runQuery("SELECT key,value FROM private_gen_bin WHERE namespace=?", (namespace, )).addCallback(fillPrivates) |
554 return d.addErrback(lambda x: log.debug(_(u"No binary data present in database for namespace %s") % namespace)) | 608 return d.addErrback(lambda x: log.debug(_(u"No binary data present in database for namespace %s") % namespace)) |
555 | 609 |
556 def loadIndPrivatesBinary(self, private_ind, namespace, profile): | 610 def loadIndPrivatesBinary(self, private_ind, namespace, profile): |
557 """Load individual private binary values | 611 """Load individual private binary values |
612 | |
558 @param privates_ind: dictionary to fill | 613 @param privates_ind: dictionary to fill |
559 @param namespace: namespace of the values | 614 @param namespace: namespace of the values |
560 @param profile: a profile which *must* exist | 615 @param profile: a profile which *must* exist |
561 @return: deferred""" | 616 @return: deferred |
617 """ | |
562 | 618 |
563 def fillPrivates(result): | 619 def fillPrivates(result): |
564 for private in result: | 620 for private in result: |
565 key, value = private | 621 key, value = private |
566 private_ind[key] = pickle.loads(str(value)) | 622 private_ind[key] = pickle.loads(str(value)) |
569 d.addCallback(fillPrivates) | 625 d.addCallback(fillPrivates) |
570 return d.addErrback(lambda x: log.debug(_(u"No binary data present in database for namespace %s") % namespace)) | 626 return d.addErrback(lambda x: log.debug(_(u"No binary data present in database for namespace %s") % namespace)) |
571 | 627 |
572 def setGenPrivateBinary(self, namespace, key, value): | 628 def setGenPrivateBinary(self, namespace, key, value): |
573 """Save the general private binary value in database | 629 """Save the general private binary value in database |
630 | |
574 @param category: category of the privateeter | 631 @param category: category of the privateeter |
575 @param key: key of the private value | 632 @param key: key of the private value |
576 @param value: value to set | 633 @param value: value to set |
577 @return: deferred""" | 634 @return: deferred |
635 """ | |
578 d = self.dbpool.runQuery("REPLACE INTO private_gen_bin(namespace,key,value) VALUES (?,?,?)", (namespace, key, sqlite3.Binary(pickle.dumps(value, 0)))) | 636 d = self.dbpool.runQuery("REPLACE INTO private_gen_bin(namespace,key,value) VALUES (?,?,?)", (namespace, key, sqlite3.Binary(pickle.dumps(value, 0)))) |
579 d.addErrback(lambda ignore: log.error(_(u"Can't set general private binary value (%(key)s) [namespace:%(namespace)s] in database" % | 637 d.addErrback(lambda ignore: log.error(_(u"Can't set general private binary value (%(key)s) [namespace:%(namespace)s] in database" % |
580 {"namespace": namespace, "key": key}))) | 638 {"namespace": namespace, "key": key}))) |
581 return d | 639 return d |
582 | 640 |
583 def setIndPrivateBinary(self, namespace, key, value, profile): | 641 def setIndPrivateBinary(self, namespace, key, value, profile): |
584 """Save the individual private binary value in database | 642 """Save the individual private binary value in database |
643 | |
585 @param namespace: namespace of the value | 644 @param namespace: namespace of the value |
586 @param key: key of the private value | 645 @param key: key of the private value |
587 @param value: value to set | 646 @param value: value to set |
588 @param profile: a profile which *must* exist | 647 @param profile: a profile which *must* exist |
589 @return: deferred""" | 648 @return: deferred |
649 """ | |
590 d = self.dbpool.runQuery("REPLACE INTO private_ind_bin(namespace,key,profile_id,value) VALUES (?,?,?,?)", (namespace, key, self.profiles[profile], sqlite3.Binary(pickle.dumps(value, 0)))) | 650 d = self.dbpool.runQuery("REPLACE INTO private_ind_bin(namespace,key,profile_id,value) VALUES (?,?,?,?)", (namespace, key, self.profiles[profile], sqlite3.Binary(pickle.dumps(value, 0)))) |
591 d.addErrback(lambda ignore: log.error(_(u"Can't set individual binary private value (%(key)s) [namespace: %(namespace)s] for [%(profile)s] in database" % | 651 d.addErrback(lambda ignore: log.error(_(u"Can't set individual binary private value (%(key)s) [namespace: %(namespace)s] for [%(profile)s] in database" % |
592 {"namespace": namespace, "key": key, "profile": profile}))) | 652 {"namespace": namespace, "key": key, "profile": profile}))) |
593 return d | 653 return d |
594 | 654 |
595 def delGenPrivateBinary(self, namespace, key): | 655 def delGenPrivateBinary(self, namespace, key): |
596 """Delete the general private binary value from database | 656 """Delete the general private binary value from database |
657 | |
597 @param category: category of the privateeter | 658 @param category: category of the privateeter |
598 @param key: key of the private value | 659 @param key: key of the private value |
599 @return: deferred""" | 660 @return: deferred |
661 """ | |
600 d = self.dbpool.runQuery("DELETE FROM private_gen_bin WHERE namespace=? AND key=?", (namespace, key)) | 662 d = self.dbpool.runQuery("DELETE FROM private_gen_bin WHERE namespace=? AND key=?", (namespace, key)) |
601 d.addErrback(lambda ignore: log.error(_(u"Can't delete general private binary value (%(key)s) [namespace:%(namespace)s] in database" % | 663 d.addErrback(lambda ignore: log.error(_(u"Can't delete general private binary value (%(key)s) [namespace:%(namespace)s] in database" % |
602 {"namespace": namespace, "key": key}))) | 664 {"namespace": namespace, "key": key}))) |
603 return d | 665 return d |
604 | 666 |
605 def delIndPrivateBinary(self, namespace, key, profile): | 667 def delIndPrivateBinary(self, namespace, key, profile): |
606 """Delete the individual private binary value from database | 668 """Delete the individual private binary value from database |
669 | |
607 @param namespace: namespace of the value | 670 @param namespace: namespace of the value |
608 @param key: key of the private value | 671 @param key: key of the private value |
609 @param profile: a profile which *must* exist | 672 @param profile: a profile which *must* exist |
610 @return: deferred""" | 673 @return: deferred |
674 """ | |
611 d = self.dbpool.runQuery("DELETE FROM private_ind_bin WHERE namespace=? AND key=? AND profile=?)", (namespace, key, self.profiles[profile])) | 675 d = self.dbpool.runQuery("DELETE FROM private_ind_bin WHERE namespace=? AND key=? AND profile=?)", (namespace, key, self.profiles[profile])) |
612 d.addErrback(lambda ignore: log.error(_(u"Can't delete individual private binary value (%(key)s) [namespace: %(namespace)s] for [%(profile)s] in database" % | 676 d.addErrback(lambda ignore: log.error(_(u"Can't delete individual private binary value (%(key)s) [namespace: %(namespace)s] for [%(profile)s] in database" % |
613 {"namespace": namespace, "key": key, "profile": profile}))) | 677 {"namespace": namespace, "key": key, "profile": profile}))) |
614 return d | 678 return d |
615 ##Helper methods## | 679 ##Helper methods## |
636 self._sat_version = sat_version | 700 self._sat_version = sat_version |
637 self.dbpool = dbpool | 701 self.dbpool = dbpool |
638 | 702 |
639 def getLocalVersion(self): | 703 def getLocalVersion(self): |
640 """ Get local database version | 704 """ Get local database version |
705 | |
641 @return: version (int) | 706 @return: version (int) |
642 | |
643 """ | 707 """ |
644 return self.dbpool.runQuery("PRAGMA user_version").addCallback(lambda ret: int(ret[0][0])) | 708 return self.dbpool.runQuery("PRAGMA user_version").addCallback(lambda ret: int(ret[0][0])) |
645 | 709 |
646 def _setLocalVersion(self, version): | 710 def _setLocalVersion(self, version): |
647 """ Set local database version | 711 """ Set local database version |
712 | |
648 @param version: version (int) | 713 @param version: version (int) |
649 @return: deferred | 714 @return: deferred |
650 | |
651 """ | 715 """ |
652 return self.dbpool.runOperation("PRAGMA user_version=%d" % version) | 716 return self.dbpool.runOperation("PRAGMA user_version=%d" % version) |
653 | 717 |
654 def getLocalSchema(self): | 718 def getLocalSchema(self): |
655 """ return raw local schema | 719 """ return raw local schema |
720 | |
656 @return: list of strings with CREATE sql statements for local database | 721 @return: list of strings with CREATE sql statements for local database |
657 | |
658 """ | 722 """ |
659 d = self.dbpool.runQuery("select sql from sqlite_master where type = 'table'") | 723 d = self.dbpool.runQuery("select sql from sqlite_master where type = 'table'") |
660 d.addCallback(lambda result: [row[0] for row in result]) | 724 d.addCallback(lambda result: [row[0] for row in result]) |
661 return d | 725 return d |
662 | 726 |
663 @defer.inlineCallbacks | 727 @defer.inlineCallbacks |
664 def checkUpdates(self): | 728 def checkUpdates(self): |
665 """ Check if a database schema/content update is needed, according to DATABASE_SCHEMAS | 729 """ Check if a database schema/content update is needed, according to DATABASE_SCHEMAS |
730 | |
666 @return: deferred which fire a list of SQL update statements, or None if no update is needed | 731 @return: deferred which fire a list of SQL update statements, or None if no update is needed |
667 | |
668 """ | 732 """ |
669 local_version = yield self.getLocalVersion() | 733 local_version = yield self.getLocalVersion() |
670 raw_local_sch = yield self.getLocalSchema() | 734 raw_local_sch = yield self.getLocalSchema() |
671 local_sch = self.rawStatements2data(raw_local_sch) | 735 local_sch = self.rawStatements2data(raw_local_sch) |
672 current_sch = DATABASE_SCHEMAS['current']['CREATE'] | 736 current_sch = DATABASE_SCHEMAS['current']['CREATE'] |
716 defer.returnValue(update_raw) | 780 defer.returnValue(update_raw) |
717 | 781 |
718 @staticmethod | 782 @staticmethod |
719 def createData2Raw(data): | 783 def createData2Raw(data): |
720 """ Generate SQL statements from statements data | 784 """ Generate SQL statements from statements data |
785 | |
721 @param data: dictionary with table as key, and statements data in tuples as value | 786 @param data: dictionary with table as key, and statements data in tuples as value |
722 @return: list of strings with raw statements | 787 @return: list of strings with raw statements |
723 | |
724 """ | 788 """ |
725 ret = [] | 789 ret = [] |
726 for table in data: | 790 for table in data: |
727 defs, constraints = data[table] | 791 defs, constraints = data[table] |
728 assert isinstance(defs, tuple) | 792 assert isinstance(defs, tuple) |
731 return ret | 795 return ret |
732 | 796 |
733 @staticmethod | 797 @staticmethod |
734 def insertData2Raw(data): | 798 def insertData2Raw(data): |
735 """ Generate SQL statements from statements data | 799 """ Generate SQL statements from statements data |
800 | |
736 @param data: dictionary with table as key, and statements data in tuples as value | 801 @param data: dictionary with table as key, and statements data in tuples as value |
737 @return: list of strings with raw statements | 802 @return: list of strings with raw statements |
738 | |
739 """ | 803 """ |
740 ret = [] | 804 ret = [] |
741 for table in data: | 805 for table in data: |
742 values_tuple = data[table] | 806 values_tuple = data[table] |
743 assert isinstance(values_tuple, tuple) | 807 assert isinstance(values_tuple, tuple) |
746 ret.append(Updater.INSERT_SQL % (table, ', '.join(values))) | 810 ret.append(Updater.INSERT_SQL % (table, ', '.join(values))) |
747 return ret | 811 return ret |
748 | 812 |
749 def statementHash(self, data): | 813 def statementHash(self, data): |
750 """ Generate hash of template data | 814 """ Generate hash of template data |
815 | |
751 useful to compare schemas | 816 useful to compare schemas |
752 | |
753 @param data: dictionary of "CREATE" statement, with tables names as key, | 817 @param data: dictionary of "CREATE" statement, with tables names as key, |
754 and tuples of (col_defs, constraints) as values | 818 and tuples of (col_defs, constraints) as values |
755 @return: hash as string | 819 @return: hash as string |
756 """ | 820 """ |
757 hash_ = hashlib.sha1() | 821 hash_ = hashlib.sha1() |
766 hash_.update("%s:%s:%s" % (table, stmnts2str(col_defs), stmnts2str(col_constr))) | 830 hash_.update("%s:%s:%s" % (table, stmnts2str(col_defs), stmnts2str(col_constr))) |
767 return hash_.digest() | 831 return hash_.digest() |
768 | 832 |
769 def rawStatements2data(self, raw_statements): | 833 def rawStatements2data(self, raw_statements): |
770 """ separate "CREATE" statements into dictionary/tuples data | 834 """ separate "CREATE" statements into dictionary/tuples data |
835 | |
771 @param raw_statements: list of CREATE statements as strings | 836 @param raw_statements: list of CREATE statements as strings |
772 @return: dictionary with table names as key, and a (col_defs, constraints) tuple | 837 @return: dictionary with table names as key, and a (col_defs, constraints) tuple |
773 | |
774 """ | 838 """ |
775 schema_dict = {} | 839 schema_dict = {} |
776 for create_statement in raw_statements: | 840 for create_statement in raw_statements: |
777 if not create_statement.startswith("CREATE TABLE "): | 841 if not create_statement.startswith("CREATE TABLE "): |
778 log.warning("Unexpected statement, ignoring it") | 842 log.warning("Unexpected statement, ignoring it") |
804 - 'create': dictionary of tables to create | 868 - 'create': dictionary of tables to create |
805 - 'delete': tuple of tables to delete | 869 - 'delete': tuple of tables to delete |
806 - 'cols create': dictionary of columns to create (table as key, tuple of columns to create as value) | 870 - 'cols create': dictionary of columns to create (table as key, tuple of columns to create as value) |
807 - 'cols delete': dictionary of columns to delete (table as key, tuple of columns to delete as value) | 871 - 'cols delete': dictionary of columns to delete (table as key, tuple of columns to delete as value) |
808 - 'cols modify': dictionary of columns to modify (table as key, tuple of old columns to transfert as value). With this table, a new table will be created, and content from the old table will be transfered to it, only cols specified in the tuple will be transfered. | 872 - 'cols modify': dictionary of columns to modify (table as key, tuple of old columns to transfert as value). With this table, a new table will be created, and content from the old table will be transfered to it, only cols specified in the tuple will be transfered. |
809 | |
810 """ | 873 """ |
811 | 874 |
812 create_tables_data = {} | 875 create_tables_data = {} |
813 create_cols_data = {} | 876 create_cols_data = {} |
814 modify_cols_data = {} | 877 modify_cols_data = {} |