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 = {}