Package advene :: Package model :: Package backends :: Module sqlite
[hide private]
[frames] | no frames]

Source Code for Module advene.model.backends.sqlite

   1  """ 
   2   
   3  I am the reference implementation for advene backends modules. 
   4   
   5  A backend module can be registered by invoking 
   6  `advene.model.backends.register` with the module as its sole argument. It must 
   7  implement 4 functions: `claims_for_create` , `claims_for_bind` , create_ and 
   8  bind_ . The two latters return a *backend instance* with a standard API, for 
   9  which `_SqliteBackend` provides a reference implementation. 
  10  """ 
  11   
  12  #TODO: the backend does not ensure an exclusive access to the sqlite file. 
  13  #      this is contrary to the backend specification. 
  14  #      We should explore sqlite locking and use it (seems that multiple 
  15  #      read-only access is possible), but also the possibility to lock 
  16  #      differently each package in the database. 
  17   
  18  from sqlite3 import dbapi2 as sqlite 
  19  from os        import unlink 
  20  from os.path   import exists 
  21  from urllib    import url2pathname, pathname2url 
  22  from weakref   import WeakKeyDictionary, WeakValueDictionary 
  23  import re 
  24   
  25  from advene.model.backends.exceptions \ 
  26    import ClaimFailure, NoSuchPackage, InternalError, PackageInUse, WrongFormat 
  27  import advene.model.backends.sqlite_init as sqlite_init 
  28  from advene.model.core.element \ 
  29    import MEDIA, ANNOTATION, RELATION, VIEW, RESOURCE, TAG, LIST, QUERY, IMPORT 
  30  from advene.model.exceptions import ModelError 
  31  from advene.util.reftools import WeakValueDictWithCallback 
  32   
  33   
  34  BACKEND_VERSION = "1.2" 
  35   
  36  IN_MEMORY_URL = "sqlite:%3Amemory%3A" 
  37   
  38  _DF = False # means that assert will succeed, i.e. *no* debug 
  39  # this flag exists so that assertion do not impair efficiency *even* in 
  40  # non-optimized mode (because advene is bound to be in non-optimized mode 
  41  # for quite a long time ;-) ) 
  42   
43 -def _get_module_debug():
44 """Return the state of the module's debug flag. 45 46 The debug flag enables a bunch of ``assert`` statements. 47 See also `_set_module_debug`. 48 49 NB: The benefit of disabling debug is not highly significant with sqlite, 50 but this would be different with a distant DBMS (because asserts often 51 invoke backend methods, resulting on extra queries to the database). Since 52 it is expected that a backend implementation over such a DBMS will be done 53 by copying and adapting this module, it seems like a good idea to have it. 54 """ 55 global _DF 56 return not _DF # _DF == True means "no debug"
57
58 -def _set_module_debug(b):
59 """Set the module's debug flaf. See _get_module_debug`.""" 60 global _DF 61 _DF = not b # _DF == True means "no debug"
62 63 64
65 -def claims_for_create(url):
66 """Is this backend able to create a package to the given URL ? 67 68 Checks whether the URL is recognized, and whether the requested package 69 does not already exist at that URL. 70 71 When the result of that method is False, it must be a `ClaimFailure` rather 72 than a `bool`. If it has no exception, then the URL is not recognized at 73 all. If it has an exception, then the URL is recognized, but attempting 74 to create the package will raise that exception. 75 """ 76 if not url.startswith("sqlite:"): return ClaimFailure() 77 78 path, pkgid = _strip_url(url) 79 80 # if already loaded 81 bi = _cache.get(path) 82 if bi is not None: 83 p = bi._bound.get(pkgid) 84 if p is not None: 85 return ClaimFailure(PackageInUse(p)) 86 elif _contains_package(bi._conn, pkgid): 87 return ClaimFailure(PackageInUse(url)) 88 else: 89 return True 90 91 # in new memory database 92 if path == ":memory:": 93 return True 94 95 # new connexion to persistent (file) database 96 if not exists(path): 97 # check that file can be created 98 try: 99 open(path, "w").close() 100 except IOError, e: 101 return ClaimFailure(e) 102 unlink(path) 103 return True 104 else: 105 try: 106 cx = _get_connection(path) 107 except Exception, e: 108 return ClaimFailure(e) 109 if cx is None: return ClaimFailure(WrongFormat(path)) 110 r = not _contains_package(cx, pkgid) 111 cx.close() 112 return r or ClaimFailure(PackageInUse(url))
113
114 -def create(package, force=False, url=None):
115 """Creates a new package and return backend instance and package id. 116 117 Parameters 118 ---------- 119 package 120 an object with attribute ``url``, which will be used as the backend URL 121 unless parameter `url` is also provided. 122 force 123 should the package be created (i.e. re-initialized) even if it exists? 124 url 125 URL to be used if ``package.url`` is not adapted to this backend (useful 126 for parsed-into-backend packages) 127 """ 128 url = url or package.url 129 if force: 130 raise NotImplementedError("This backend can not force creation of " 131 "an existing package") 132 r = claims_for_create(url) 133 if not r: 134 raise r.exception or RuntimeError("Unrecognized URL") 135 136 path, pkgid = _strip_url(url) 137 b = _cache.get(path) 138 if b is not None: 139 conn = b._conn 140 curs = b._curs 141 b._begin_transaction("EXCLUSIVE") 142 else: 143 # check the following *before* sqlite.connect creates the file! 144 must_init = (path == ":memory:" or not exists(path)) 145 conn = sqlite.connect(path, isolation_level=None, check_same_thread=False) 146 curs = conn.cursor() 147 curs.execute("BEGIN EXCLUSIVE") 148 if must_init: 149 # initialize database 150 try: 151 for sql in sqlite_init.statements: 152 curs.execute(sql) 153 curs.execute("INSERT INTO Version VALUES (?)", 154 (BACKEND_VERSION,)) 155 curs.execute("INSERT INTO Packages VALUES (?,?,?)", 156 (_DEFAULT_PKGID, "", "",)) 157 except sqlite.OperationalError, e: 158 curs.execute("ROLLBACK") 159 raise InternalError("could not initialize model", e) 160 except: 161 curs.execute("ROLLBACK") 162 raise 163 164 b = _SqliteBackend(path, conn, force) 165 _cache[path] = b 166 try: 167 if pkgid != _DEFAULT_PKGID: 168 conn.execute("INSERT INTO Packages VALUES (?,?,?)", 169 (pkgid, "", "",)) 170 b._bind(pkgid, package) 171 except sqlite.Error, e: 172 curs.execute("ROLLBACK") 173 raise InternalError("could not update", e) 174 except: 175 curs.execute("ROLLBACK") 176 raise 177 curs.execute("COMMIT") 178 return b, pkgid
179
180 -def claims_for_bind(url):
181 """Is this backend able to bind to the given URL ? 182 183 Checks whether the URL is recognized, and whether the requested package 184 does already exist in the database. 185 186 When the result of that method is False, it must be a `ClaimFailure` rather 187 than a `bool`. If it has no exception, then the URL is not recognized at 188 all. If it has an exception, then the URL is recognized, but attempting 189 to create the package will raise that exception. 190 """ 191 if not url.startswith("sqlite:"): return ClaimFailure() 192 193 path, pkgid = _strip_url(url) 194 195 # if already loaded 196 be = _cache.get(path) 197 if be is not None: 198 p = be._bound.get(pkgid) 199 if p: 200 return ClaimFailure(PackageInUse(p)) 201 else: 202 return _contains_package(be._conn, pkgid) \ 203 or ClaimFailure(NoSuchPackage(pkgid)) 204 205 # new memory or persistent (file) database 206 if path == ":memory:" or not exists(path): 207 return ClaimFailure(NoSuchPackage(url)) 208 209 # check that file is a correct database (created by this backend) 210 try: 211 cx = _get_connection(path) 212 except Exception, e: 213 return ClaimFailure(e) 214 if cx is None: 215 return ClaimFailure(WrongFormat(path)) 216 # check that file does contains the required pkg 217 r = _contains_package(cx, pkgid) 218 cx.close() 219 return r or ClaimFailure(NoSuchPackage(url))
220
221 -def bind(package, force=False, url=None):
222 """Bind to an existing package at the given URL. 223 224 Return the backend an the package id. 225 226 Parameters 227 ---------- 228 package 229 an object with attributes ``readonly`` and ``url``, which will be used as 230 the backend URL unless parameter `url` is also provided. 231 force 232 should the package be opened even if it is being used? 233 url 234 URL to be used if ``package.url`` is not adapted to this backend (useful 235 for parsed-into-backend packages) 236 """ 237 url = url or package.url 238 if force: 239 raise NotImplementedError("This backend can not force access to " 240 "locked package") 241 r = claims_for_bind(url) 242 if not r: 243 raise r.exception or RuntimeError("Unrecognized URL") 244 245 path, pkgid = _strip_url(url) 246 b = _cache.get(path) 247 if b is None: 248 conn = sqlite.connect(path, isolation_level=None) 249 b = _SqliteBackend(path, conn, force) 250 _cache[path] = b 251 b._begin_transaction("EXCLUSIVE") 252 try: 253 b._bind(pkgid, package) 254 except InternalError: 255 b._curs.execute("ROLLBACK") 256 raise 257 except: 258 b._curs.execute("ROLLBACK") 259 raise 260 b._curs.execute("COMMIT") 261 return b, pkgid
262 263 264 _cache = WeakValueDictionary() 265 266 _DEFAULT_PKGID = "" 267
268 -def _strip_url(url):
269 """ 270 Strip URL from its scheme ("sqlite:") and separate path and 271 fragment. Also convert path from url to OS-specific pathname expression. 272 """ 273 scheme = 7 274 semicolon = url.find(';') 275 if semicolon != -1: 276 path, pkgid = url[scheme:semicolon], url[semicolon:] 277 else: 278 path, pkgid = url[scheme:], _DEFAULT_PKGID 279 path = url2pathname(path) 280 return path, pkgid
281
282 -def _get_connection(path):
283 try: 284 cx = sqlite.connect(path) 285 c = cx.execute("SELECT version FROM Version") 286 for v in c: 287 if v[0] != BACKEND_VERSION: return None 288 return cx 289 290 except sqlite.DatabaseError: 291 return None 292 except sqlite.OperationalError: 293 return None
294
295 -def _contains_package(cx, pkgid):
296 c = cx.execute("SELECT id FROM Packages WHERE id = ?", (pkgid,)) 297 for i in c: 298 return True 299 return False
300
301 -def _split_id_ref(id_ref):
302 """ 303 Split an id_ref into a prefix and a suffix. 304 Return None prefix if id_ref is a plain id. 305 Raise an AssertionError if id_ref has length > 2. 306 """ 307 colon = id_ref.find(":") 308 if colon <= 0: 309 return "", id_ref 310 prefix, suffix = id_ref[:colon], id_ref[colon+1:] 311 assert _DF or suffix.find(":") <= 0, "id-path has length > 2" 312 return prefix, suffix
313
314 -def _split_uri_ref(uri_ref):
315 """ 316 Split a uri_ref into a URI and a fragment. 317 """ 318 sharp = uri_ref.find("#") 319 return uri_ref[:sharp], uri_ref[sharp+1:]
320 321
322 -class _SqliteBackend(object):
323 """I am the reference implementation of advene backend instances. 324 325 A number of conventions are used in all methods. 326 327 Method naming rationale 328 ======================= 329 330 When the parameters *or* return type of methods depend on the element type 331 they handle, distinct methods with the element type in their name are 332 defined (e.g. `create_annotation` vs. `create_view`). On the other hand, if 333 neither the parameters nor the return value change (type-wise) w.r.t. the 334 element type, a single method is defined, with the element type as a 335 parameter (e.g. `delete_element`). 336 337 Note that, as far as the rule above is concerned, tuples of different size 338 have different types, as well as iterators yielding objects of different 339 types. 340 341 Note also there is a notable exception to that rule: `get_element`, which 342 does not expect the type of an element, but returns a tuple with all the 343 elements attributes. A protocol forcing to first get the element type, 344 then call the appropriate method, whould have been more regular but 345 inconvenient, and probably less efficient. 346 347 Parameter names and semantics 348 ============================= 349 350 package_id 351 the package id as returned in second position by `create` or `bind`. The 352 operation will apply to that particular package. 353 354 package_ids 355 an iterable of package ids as described above. The operation will apply 356 in one shot on all these packages. 357 358 id 359 an element id. This is always used in conjunction with ``package_id``. 360 361 element_type 362 one of the constants defined in `advene.model.core.element`. It is always 363 used in conjunction with ``package_id`` and ``id`` and *must* be 364 consistent with them (i.e. be the type of the identified element if it 365 exists). The behaviour of the method is *unspecified* if ``element_type`` 366 is not consistent. As a consequence, the fact that this particular 367 implementation ignores an inconsistent ``element_type`` and works anyway 368 must *not* be relied on. 369 """ 370 371 # begin of the backend interface 372 373 # package related methods 374
375 - def get_bound_url(self, package_id):
376 """Return the backend-URL the given package is bound to.""" 377 return "sqlite:%s%s" % (pathname2url(self._path), package_id)
378
379 - def get_url(self, package_id):
380 # for the sake of completeness only: this should not be useful, since 381 # the URL is told to the backend by the package itself 382 q = "SELECT url FROM Packages WHERE id = ?" 383 return self._curs.execute(q, (package_id,)).fetchone()[0]
384
385 - def update_url(self, package_id, uri):
386 q = "UPDATE Packages SET url = ? WHERE id = ?" 387 execute = self._curs.execute 388 try: 389 execute(q, (uri, package_id,)) 390 except sqlite.Error, e: 391 raise InternalError("could not update", e)
392
393 - def get_uri(self, package_id):
394 q = "SELECT uri FROM Packages WHERE id = ?" 395 return self._curs.execute(q, (package_id,)).fetchone()[0]
396
397 - def update_uri(self, package_id, uri):
398 q = "UPDATE Packages SET uri = ? WHERE id = ?" 399 execute = self._curs.execute 400 try: 401 execute(q, (uri, package_id,)) 402 except sqlite.Error, e: 403 raise InternalError("could not update", e)
404
405 - def close(self, package_id):
406 """Inform the backend that a given package will no longer be used. 407 408 NB: this implementation is robust to packages forgetting to close 409 themselves, i.e. when packages are garbage collected, this is detected 410 and they are automatically unbound. 411 """ 412 d = self._bound 413 m = d.get(package_id) # keeping a ref on it prevents it to disappear 414 if m is not None: # in the meantime... 415 try: 416 self._curs.execute("UPDATE Packages SET url = ? WHERE id = ?", 417 ("", package_id,)) 418 except sqlite.Error, e: 419 raise InternalError("could not update", e) 420 del d[package_id] 421 self._check_unused(package_id)
422
423 - def delete(self, package_id):
424 """Delete from the backend all the data about a bound package. 425 426 Obviously, a deleted package does not need to be closed. 427 """ 428 d = self._bound 429 m = d.get(package_id) # keeping a ref on it prevents it to disappear 430 if m is not None: # in the meantime... 431 execute = self._curs.execute 432 args = [package_id,] 433 434 # NB: all the queries after the first one (and hence the 435 # transaction) are only required because sqlite does not implement 436 # foreign keys; with an "ON DELETE CASCADE", the deletion in 437 # Packages would suffice 438 439 self._begin_transaction("IMMEDIATE") 440 try: 441 execute("DELETE FROM Packages WHERE id = ?", args) 442 execute("DELETE FROM Elements WHERE package = ?", args) 443 execute("DELETE FROM Meta WHERE package = ?", args) 444 execute("DELETE FROM Contents WHERE package = ?", args) 445 execute("DELETE FROM Medias WHERE package = ?", args) 446 execute("DELETE FROM Annotations WHERE package = ?", args) 447 execute("DELETE FROM RelationMembers WHERE package = ?", args) 448 execute("DELETE FROM ListItems WHERE package = ?", args) 449 execute("DELETE FROM Imports WHERE package = ?", args) 450 execute("DELETE FROM Tagged WHERE package = ?", args) 451 except sqlite.Error, e: 452 execute("ROLLBACK") 453 raise InternalError("could not delete", e) 454 except: 455 execute("ROLLBACK") 456 raise 457 execute("COMMIT") 458 del d[package_id] 459 self._check_unused(package_id)
460 461 # element creation 462
463 - def create_media(self, package_id, id, url, frame_of_reference):
464 """Create a new media. 465 466 Raise a ModelException if the identifier already exists in the package. 467 """ 468 c = self._curs 469 _create_element = self._create_element 470 execute = self._curs.execute 471 try: 472 _create_element(execute, package_id, id, MEDIA) 473 execute("INSERT INTO Medias VALUES (?,?,?,?)", 474 (package_id, id, url, frame_of_reference)) 475 except sqlite.Error, e: 476 execute("ROLLBACK") 477 raise InternalError("could not insert", e) 478 except: 479 execute("ROLLBACK") 480 raise 481 execute("COMMIT")
482
483 - def create_annotation(self, package_id, id, media, begin, end, 484 mimetype, model, url):
485 """Create a new annotation and its associated content. 486 487 Specific parameters 488 ------------------- 489 media 490 id-ref of the media this annotation refs to 491 begin, end 492 int boundaries of the annotated temporal fragment 493 mimetype 494 the mimetype of the annotation content 495 model 496 the id-ref of the content model for the annotation (can be empty) 497 url 498 if non empty, the annotation content will be not be stored, and will 499 be fetched on demand from that URL 500 501 Raise a ModelException if the identifier already exists in the package. 502 """ 503 assert _DF or (isinstance(begin, (int, long)) and begin >= 0), repr(begin) 504 mp,ms = _split_id_ref(media) # also assert that media has depth < 2 505 assert _DF or mp == "" or self.has_element(package_id, mp, IMPORT), mp 506 assert _DF or mp != "" or self.has_element(package_id, ms, MEDIA), ms 507 sp,ss = _split_id_ref(model) # also assert that media has depth < 2 508 assert _DF or sp == "" or self.has_element(package_id, sp, IMPORT), sp 509 assert _DF or sp != "" or ss == "" or \ 510 self.has_element(package_id, ss, RESOURCE), ss 511 512 _create_element = self._create_element 513 execute = self._curs.execute 514 try: 515 _create_element(execute, package_id, id, ANNOTATION) 516 execute("INSERT INTO Annotations VALUES (?,?,?,?,?,?)", 517 (package_id, id, mp, ms, begin, end)) 518 execute("INSERT INTO Contents VALUES (?,?,?,?,?,?,?)", 519 (package_id, id, mimetype, sp, ss, url, "",)) 520 except sqlite.Error, e: 521 execute("ROLLBACK") 522 raise InternalError("could not insert", e) 523 except: 524 execute("ROLLBACK") 525 raise 526 execute("COMMIT")
527
528 - def create_relation(self, package_id, id, mimetype, model, url):
529 """Create a new empty relation and its associated content. 530 531 Specific parameters 532 ------------------- 533 mimetype 534 the mimetype of the annotation content 535 model 536 the id-ref of the content model for the annotation (can be empty) 537 url 538 if non empty, the annotation content will be not be stored, and will 539 be fetched on demand from that URL 540 541 Raise a ModelException if the identifier already exists in the package. 542 """ 543 sp,ss = _split_id_ref(model) # also assert that media has depth < 2 544 assert _DF or sp == "" or self.has_element(package_id, sp, IMPORT), sp 545 assert _DF or sp != "" or ss == "" or \ 546 self.has_element(package_id, ss, RESOURCE), ss 547 548 _create_element = self._create_element 549 execute = self._curs.execute 550 try: 551 _create_element(execute, package_id, id, RELATION) 552 execute("INSERT INTO Contents VALUES (?,?,?,?,?,?,?)", 553 (package_id, id, mimetype, sp, ss, url, "")) 554 except sqlite.Error, e: 555 execute("ROLLBACK") 556 raise InternalError("error in creating", e) 557 except: 558 execute("ROLLBACK") 559 raise 560 execute("COMMIT")
561
562 - def create_view(self, package_id, id, mimetype, model, url):
563 """Create a new view and its associated content. 564 565 Specific parameters 566 ------------------- 567 mimetype 568 the mimetype of the view content 569 model 570 the id-ref of the content model the view (can be empty) 571 url 572 if non empty, the view content will be not be stored, and will 573 be fetched on demand from that URL 574 575 Raise a ModelException if the identifier already exists in the package. 576 """ 577 sp,ss = _split_id_ref(model) # also assert that media has depth < 2 578 assert _DF or sp == "" or self.has_element(package_id, sp, IMPORT), sp 579 assert _DF or sp != "" or ss == "" or \ 580 self.has_element(package_id, ss, RESOURCE), ss 581 582 _create_element = self._create_element 583 execute = self._curs.execute 584 try: 585 _create_element(execute, package_id, id, VIEW) 586 execute("INSERT INTO Contents VALUES (?,?,?,?,?,?,?)", 587 (package_id, id, mimetype, sp, ss, url, "",)) 588 except sqlite.Error, e: 589 execute("ROLLBACK") 590 raise InternalError("error in creating", e) 591 except: 592 execute("ROLLBACK") 593 raise 594 execute("COMMIT")
595
596 - def create_resource(self, package_id, id, mimetype, model, url):
597 """Create a new resource and its associated content. 598 599 Specific parameters 600 ------------------- 601 mimetype 602 the mimetype of the resource content 603 model 604 the id-ref of the content model for the resource (can be empty) 605 url 606 if non empty, the resource content will be not be stored, and will 607 be fetched on demand from that URL 608 609 Raise a ModelException if the identifier already exists in the package. 610 """ 611 sp,ss = _split_id_ref(model) # also assert that media has depth < 2 612 assert _DF or sp == "" or self.has_element(package_id, sp, IMPORT), sp 613 assert _DF or sp != "" or ss == "" or \ 614 self.has_element(package_id, ss, RESOURCE), ss 615 616 _create_element = self._create_element 617 execute = self._curs.execute 618 try: 619 _create_element(execute, package_id, id, RESOURCE) 620 execute("INSERT INTO Contents VALUES (?,?,?,?,?,?,?)", 621 (package_id, id, mimetype, sp, ss, url, "",)) 622 except sqlite.Error, e: 623 execute("ROLLBACK") 624 raise InternalError("error in creating", e) 625 except: 626 execute("ROLLBACK") 627 raise 628 execute("COMMIT")
629
630 - def create_tag(self, package_id, id):
631 """Create a new tag. 632 633 Raise a ModelException if the identifier already exists in the package. 634 """ 635 _create_element = self._create_element 636 execute = self._curs.execute 637 try: 638 _create_element(execute, package_id, id, TAG) 639 except sqlite.Error, e: 640 execute("ROLLBACK") 641 raise InternalError("error in creating", e) 642 except: 643 execute("ROLLBACK") 644 raise 645 execute("COMMIT")
646
647 - def create_list(self, package_id, id):
648 """Create a new empty list. 649 650 Raise a ModelException if the identifier already exists in the package. 651 """ 652 _create_element = self._create_element 653 execute = self._curs.execute 654 try: 655 _create_element(execute, package_id, id, LIST) 656 except sqlite.Error, e: 657 execute("ROLLBACK") 658 raise InternalError("error in creating", e) 659 except: 660 execute("ROLLBACK") 661 raise 662 execute("COMMIT")
663
664 - def create_query(self, package_id, id, mimetype, model, url):
665 """Create a new query and its associated content. 666 667 Specific parameters 668 ------------------- 669 mimetype 670 the mimetype of the query content 671 model 672 the id-ref of the content model for the query (can be empty) 673 url 674 if non empty, the query content will be not be stored, and will 675 be fetched on demand from that URL 676 677 Raise a ModelException if the identifier already exists in the package. 678 """ 679 sp,ss = _split_id_ref(model) # also assert that media has depth < 2 680 assert _DF or sp == "" or self.has_element(package_id, sp, IMPORT), sp 681 assert _DF or sp != "" or ss == "" or \ 682 self.has_element(package_id, ss, RESOURCE), ss 683 684 _create_element = self._create_element 685 execute = self._curs.execute 686 try: 687 _create_element(execute, package_id, id, QUERY) 688 execute("INSERT INTO Contents VALUES (?,?,?,?,?,?,?)", 689 (package_id, id, mimetype, sp, ss, url, "",)) 690 except sqlite.Error, e: 691 execute("ROLLBACK") 692 raise InternalError("error in creating",e) 693 except: 694 execute("ROLLBACK") 695 raise 696 execute("COMMIT")
697
698 - def create_import(self, package_id, id, url, uri):
699 """Create a new import. 700 701 Raise a ModelException if the identifier already exists in the package. 702 """ 703 _create_element = self._create_element 704 execute = self._curs.execute 705 try: 706 _create_element(execute, package_id, id, IMPORT) 707 execute("INSERT INTO Imports VALUES (?,?,?,?)", 708 (package_id, id, url, uri)) 709 except sqlite.Error, e: 710 execute("ROLLBACK") 711 raise InternalError("error in creating", e) 712 except: 713 execute("ROLLBACK") 714 raise 715 execute("COMMIT")
716 717 # element retrieval 718
719 - def has_element(self, package_id, id, element_type=None):
720 """ 721 Return True if the given package has an element with the given id. 722 If element_type is provided, only return true if the element has the 723 the given type. 724 """ 725 q = "SELECT typ FROM Elements WHERE package = ? and id = ?" 726 for i in self._curs.execute(q, (package_id, id,)): 727 return element_type is None or i[0] == element_type 728 return False
729
730 - def get_element(self, package_id, id):
731 """Return the tuple describing a given element. 732 733 If the element does not exist, None is returned. 734 """ 735 736 q = "SELECT e.typ, m.url, m.foref, " \ 737 "join_id_ref(a.media_p, a.media_i), " \ 738 "a.fbegin, a.fend, i.url, i.uri, c.mimetype, " \ 739 "join_id_ref(c.model_p, c.model_i), c.url " \ 740 "FROM Elements e " \ 741 "LEFT JOIN Medias m ON e.package = m.package AND e.id = m.id " \ 742 "LEFT JOIN Annotations a " \ 743 "ON e.package = a.package AND e.id = a.id " \ 744 "LEFT JOIN Imports i ON e.package = i.package AND e.id = i.id " \ 745 "LEFT JOIN Contents c " \ 746 "ON e.package = c.package AND e.id = c.element " \ 747 "WHERE e.package = ? AND e.id = ?" 748 r = self._curs.execute(q, (package_id, id,)).fetchone() 749 if r is None: 750 return None 751 t = r[0] 752 if t == MEDIA: 753 return(t, package_id, id,) + r[1:3] 754 elif t == ANNOTATION: 755 return(t, package_id, id,) + r[3:6] + r[8:11] 756 elif t in (RELATION, VIEW, RESOURCE, QUERY): 757 return(t, package_id, id,) + r[8:11] 758 elif t == IMPORT: 759 return(t, package_id, id,) + r[6:8] 760 else: 761 return(t, package_id, id)
762
763 - def iter_references(self, package_ids, element):
764 """ 765 Iter over all the elements relying on the identified element 766 (where `element` is a uri-ref). 767 768 Yields 3-tuples where the first item one of the given package_ids, 769 the second element is either an element id-ref or an empty string to 770 identify the package itself. The thirs item describes the relation 771 between the second item and the identified element. It can be either: 772 an attribute name with an element as its value 773 in that case, the identified element is the value of the attribute 774 for the element or package identified by the first item. 775 the string ":item %s" where %s is an int i 776 the first item identifies a list, and its i'th item is the 777 identified element 778 the string ":member %s" where %s is an int i 779 the first item identifies a relation, and its i'th item is the 780 identified element 781 the string ":meta %s" where %s is a metadata key 782 in that case, the identified element is the value of that metadata 783 for the element or package identified by the first item. 784 the string ":tag %s" where %s is an id-ref 785 the identified element is a tag, to which this package identified 786 by the first parameter associates the element identified by the 787 id-ref. 788 the string ":tagged %s" where %s is an id-ref 789 this package associates the identified element the tag identified 790 by the id-ref. 791 792 The attribute names that may be returned are ``media`` and 793 ``content_model``. 794 """ 795 assert _DF or not isinstance(package_ids, basestring) 796 elt_u, elt_i = _split_uri_ref(element) 797 qmarks = "(" + ",".join("?" for i in package_ids) + ")" 798 q = """SELECT a.package, id, 'media' 799 FROM Annotations a 800 JOIN UriBases u ON a.package = u.package 801 AND media_p = prefix 802 WHERE a.package IN %(pid_list)s 803 AND uri_base = ? AND media_i = ? 804 UNION 805 SELECT c.package, element, 'content_model' 806 FROM Contents c 807 JOIN UriBases u ON c.package = u.package 808 AND model_p = prefix 809 WHERE c.package IN %(pid_list)s 810 AND uri_base = ? AND model_i = ? 811 UNION 812 SELECT r.package, relation, ':member '||ord 813 FROM RelationMembers r 814 JOIN UriBases u ON r.package = u.package 815 AND member_p = prefix 816 WHERE r.package IN %(pid_list)s 817 AND uri_base = ? AND member_i = ? 818 UNION 819 SELECT l.package, list, ':item '||ord 820 FROM ListItems l 821 JOIN UriBases u ON l.package = u.package 822 AND item_p = prefix 823 WHERE l.package IN %(pid_list)s 824 AND uri_base = ? AND item_i = ? 825 UNION 826 SELECT t.package, '', ':tag '||join_id_ref(element_p, element_i) 827 FROM Tagged t 828 JOIN UriBases u ON t.package = u.package 829 AND tag_p = prefix 830 WHERE t.package IN %(pid_list)s 831 AND uri_base = ? AND tag_i = ? 832 UNION 833 SELECT t.package, '', ':tagged '||join_id_ref(tag_p, tag_i) 834 FROM Tagged t 835 JOIN UriBases u ON t.package = u.package 836 AND element_p = prefix 837 WHERE t.package IN %(pid_list)s 838 AND uri_base = ? AND element_i = ? 839 UNION 840 SELECT m.package, element, ':meta '||key 841 FROM Meta m 842 JOIN UriBases u ON m.package = u.package 843 AND value_p = prefix 844 WHERE m.package IN %(pid_list)s 845 AND uri_base = ? AND value_i = ? 846 """ % {"pid_list": qmarks} 847 args = (list(package_ids) + [elt_u, elt_i]) * 7 848 c = self._conn.execute(q, args) 849 r = ( (i[0], i[1], i[2]) for i in c ) 850 return _FlushableIterator(r, self)
851
852 - def iter_references_with_import(self, package_id, id):
853 """Iter over all the elements relying on the identified import. 854 855 Yields 3-tuples where the first item is either an element id-ref 856 or an empty string to identify the package itself and the third item is 857 the id (without the importe prefix) of an element imported through the 858 import in question. The second item describes the relation between the 859 first and third ones. It can be either: 860 an attribute name with an element as its value 861 in that case, the imported element is the value of the attribute 862 for the element or package identified by the first item. 863 the string ":item %s" where %s is an int i 864 the first item identifies a list, and its i'th item is the 865 imported element 866 the string ":member %s" where %s is an int i 867 the first item identifies a relation, and its i'th item is the 868 imported element 869 the string ":meta %s" where %s is a metadata key 870 in that case, the imported element is the value of that metadata 871 for the element or package identified by the first item. 872 the string ":tag %s" where %s is an id-ref 873 the identified element is a tag, to which this package identified 874 by the first parameter associates the element identified by the 875 id-ref. 876 the string ":tagged %s" where %s is an id-ref 877 this package associates the identified element the tag identified 878 by the id-ref. 879 880 The attribute names that may be returned are ``media`` and 881 ``content_model``. 882 """ 883 q = """SELECT id, 'media', media_i FROM Annotations 884 WHERE package = ? AND media_p = ? 885 UNION 886 SELECT element, 'content_model', model_i FROM Contents 887 WHERE package = ? AND model_p = ? 888 UNION 889 SELECT relation, ':member '||ord, member_i FROM RelationMembers 890 WHERE package = ? AND member_p = ? 891 UNION 892 SELECT list, ':item '||ord, item_i FROM ListItems 893 WHERE package = ? AND item_p = ? 894 UNION 895 SELECT '', ':tag '||join_id_ref(element_p, element_i), tag_i 896 FROM Tagged 897 WHERE package = ? AND tag_p = ? 898 UNION 899 SELECT '', ':tagged '||join_id_ref(tag_p, tag_i), element_i 900 FROM Tagged 901 WHERE package = ? AND element_p = ? 902 UNION 903 SELECT element, ':meta '||key, value_i FROM Meta 904 WHERE package = ? AND value_p = ? 905 """ 906 args = [package_id, id, ] * 7 907 c = self._conn.execute(q, args) 908 return _FlushableIterator(c, self)
909
910 - def iter_medias(self, package_ids, 911 id=None, 912 url=None, 913 foref=None, 914 ):
915 """ 916 Yield tuples of the form(MEDIA, package_id, id, url,). 917 """ 918 assert _DF or not isinstance(package_ids, basestring) 919 q = _Query( 920 "SELECT ?, e.package, e.id, e.url, e.foref", 921 "FROM Medias e", 922 args = [MEDIA,] 923 ) 924 q.add_packages_filter(package_ids) 925 if id: q.add_id_filter(id) 926 if url: q.add_column_filter("e.url", url) 927 if foref: q.add_column_filter("e.foref", foref) 928 929 r = self._conn.execute(*q.exe()) 930 return _FlushableIterator(r, self)
931
932 - def iter_annotations(self, package_ids, 933 id=None, 934 media=None, 935 begin=None, begin_min=None, begin_max=None, 936 end=None, end_min=None, end_max=None, 937 ):
938 """ 939 Yield tuples of the form 940 (ANNOTATION, package_id, id, media, begin, end, mimetype, model, url), 941 ordered by begin, end and media id-ref. 942 943 ``media`` is the uri-ref of a media or an iterable of uri-refs. 944 """ 945 assert _DF or not isinstance(package_ids, basestring) 946 q = _Query( 947 "SELECT ?, e.package, e.id, join_id_ref(e.media_p, e.media_i)," 948 " e.fbegin, e.fend", 949 "FROM Annotations e", 950 args = [ANNOTATION,] 951 ) 952 q.add_content_columns() 953 q.add_packages_filter(package_ids) 954 if id: q.add_id_filter(id) 955 if media: q.add_media_filter(media) 956 if begin: q.append(" AND e.fbegin = ?", begin) 957 if begin_min: q.append(" AND e.fbegin >= ?", begin_min) 958 if begin_max: q.append(" AND e.fbegin <= ?", begin_max) 959 if end: q.append(" AND e.fend = ?", end) 960 if end_min: q.append(" AND e.fend >= ?", end_min) 961 if end_max: q.append(" AND e.fend <= ?", end_max) 962 q.append(" ORDER BY fbegin, fend, media_p, media_i") 963 r = self._conn.execute(*q.exe()) 964 return _FlushableIterator(r, self)
965
966 - def iter_relations(self, package_ids, id=None, member=None, pos=None):
967 """ 968 Yield tuples of the form (RELATION, package_id, id, mimetype, model, 969 url). 970 """ 971 assert _DF or not isinstance(package_ids, basestring) 972 assert _DF or pos is None or member is not None 973 q = _Query( 974 "SELECT e.typ, e.package, e.id", 975 "FROM Elements e", 976 "WHERE e.typ = ?", 977 args = [RELATION,] 978 ) 979 q.add_content_columns() 980 q.add_packages_filter(package_ids) 981 if id: q.add_id_filter(id) 982 if member: q.add_member_filter(member, pos) 983 r = self._conn.execute(*q.exe()) 984 return _FlushableIterator(r, self)
985
986 - def iter_views(self, package_ids, id=None):
987 """ 988 Yield tuples of the form (VIEW, package_id, id, mimetype, model, url). 989 """ 990 assert _DF or not isinstance(package_ids, basestring) 991 q = _Query( 992 "SELECT e.typ, e.package, e.id", 993 "FROM Elements e", 994 "WHERE e.typ = ?", 995 args = [VIEW,] 996 ) 997 q.add_content_columns() 998 q.add_packages_filter(package_ids) 999 if id: q.add_id_filter(id) 1000 r = self._conn.execute(*q.exe()) 1001 return _FlushableIterator(r, self)
1002
1003 - def iter_resources(self, package_ids, id=None):
1004 """ 1005 Yield tuples of the form (RESOURCE, package_id, id, mimetype, model, 1006 url). 1007 """ 1008 assert _DF or not isinstance(package_ids, basestring) 1009 q = _Query( 1010 "SELECT e.typ, e.package, e.id", 1011 "FROM Elements e", 1012 "WHERE e.typ = ?", 1013 args = [RESOURCE,] 1014 ) 1015 q.add_content_columns() 1016 q.add_packages_filter(package_ids) 1017 if id: q.add_id_filter(id) 1018 r = self._conn.execute(*q.exe()) 1019 return _FlushableIterator(r, self)
1020
1021 - def iter_tags(self, package_ids, id=None, meta=None):
1022 """ 1023 Yield tuples of the form (TAG, package_id, id,). 1024 """ 1025 assert _DF or not isinstance(package_ids, basestring) 1026 q = _Query( 1027 "SELECT e.typ, e.package, e.id", 1028 "FROM Elements e", 1029 "WHERE e.typ = ?", 1030 args = [TAG,] 1031 ) 1032 q.add_packages_filter(package_ids) 1033 if id: q.add_id_filter(id) 1034 if meta: q.add_meta_filter(meta) 1035 r = self._conn.execute(*q.exe()) 1036 return _FlushableIterator(r, self)
1037
1038 - def iter_lists(self, package_ids, id=None, item=None, pos=None, meta=None):
1039 """ 1040 Yield tuples of the form (LIST, package_id, id,). 1041 """ 1042 assert _DF or not isinstance(package_ids, basestring) 1043 assert _DF or pos is None or item is not None 1044 q = _Query( 1045 "SELECT e.typ, e.package, e.id", 1046 "FROM Elements e", 1047 "WHERE e.typ = ?", 1048 args = [LIST,] 1049 ) 1050 q.add_packages_filter(package_ids) 1051 if id: q.add_id_filter(id) 1052 if item: q.add_item_filter(item, pos) 1053 if meta: q.add_meta_filter(meta) 1054 r = self._conn.execute(*q.exe()) 1055 return _FlushableIterator(r, self)
1056
1057 - def iter_queries(self, package_ids, id=None):
1058 """ 1059 Yield tuples of the form (QUERY, package_id, id, mimetype, model, 1060 url). 1061 """ 1062 assert _DF or not isinstance(package_ids, basestring) 1063 q = _Query( 1064 "SELECT e.typ, e.package, e.id", 1065 "FROM Elements e", 1066 "WHERE e.typ = ?", 1067 args = [QUERY,] 1068 ) 1069 q.add_content_columns() 1070 q.add_packages_filter(package_ids) 1071 if id: q.add_id_filter(id) 1072 r = self._conn.execute(*q.exe()) 1073 return _FlushableIterator(r, self)
1074
1075 - def iter_imports(self, package_ids, 1076 id=None, 1077 url=None, 1078 uri=None, 1079 ):
1080 """ 1081 Yield tuples of the form (IMPORT, package_id, id, url, uri). 1082 """ 1083 assert _DF or not isinstance(package_ids, basestring) 1084 q = _Query( 1085 "SELECT ?, e.package, e.id, e.url, e.uri", 1086 "FROM Imports e", 1087 args = [IMPORT,] 1088 ) 1089 q.add_packages_filter(package_ids) 1090 if id: q.add_id_filter(id) 1091 if url: q.add_column_filter("e.url", url) 1092 if uri: q.add_column_filter("e.uri", uri) 1093 r = self._conn.execute(*q.exe()) 1094 return _FlushableIterator(r, self)
1095 1096 # element counting 1097
1098 - def count_medias(self, package_ids, 1099 id=None, 1100 url=None, 1101 foref=None, 1102 ):
1103 """ 1104 Count the medias matching the criteria. 1105 """ 1106 assert _DF or not isinstance(package_ids, basestring) 1107 q = _Query( 1108 "SELECT e.package, e.id", 1109 "FROM Medias e", 1110 ) 1111 q.add_packages_filter(package_ids) 1112 if id: q.add_id_filter(id) 1113 if url: q.add_column_filter("e.url", url) 1114 if foref: q.add_column_filter("e.foref", foref) 1115 q.wrap_in_count() 1116 1117 r = self._conn.execute(*q.exe()) 1118 return r.next()[0]
1119
1120 - def count_annotations(self, package_ids, 1121 id=None, 1122 media=None, 1123 begin=None, begin_min=None, begin_max=None, 1124 end=None, end_min=None, end_max=None, 1125 ):
1126 """ 1127 Return the number of annotations matching the criteria. 1128 1129 ``media`` is the uri-ref of a media or an iterable of uri-refs. 1130 """ 1131 assert _DF or not isinstance(package_ids, basestring) 1132 q = _Query( 1133 "SELECT e.package, e.id", 1134 "FROM Annotations e", 1135 ) 1136 q.add_packages_filter(package_ids) 1137 if id: q.add_id_filter(id) 1138 if media: q.add_media_filter(media) 1139 if begin: q.append(" AND e.fbegin = ?", begin) 1140 if begin_min: q.append(" AND e.fbegin >= ?", begin_min) 1141 if begin_max: q.append(" AND e.fbegin <= ?", begin_max) 1142 if end: q.append(" AND e.fend = ?", end) 1143 if end_min: q.append(" AND e.fend >= ?", end_min) 1144 if end_max: q.append(" AND e.fend <= ?", end_max) 1145 q.wrap_in_count() 1146 r = self._conn.execute(*q.exe()) 1147 return r.next()[0]
1148
1149 - def count_relations(self, package_ids, id=None, member=None, pos=None):
1150 """ 1151 Return the number of relations matching the criteria. 1152 """ 1153 assert _DF or not isinstance(package_ids, basestring) 1154 assert _DF or pos is None or member is not None 1155 if member is None: 1156 q = _Query( 1157 "SELECT e.package, e.id", 1158 "FROM Elements e", 1159 "WHERE e.typ = ?", 1160 args = [RELATION,] 1161 ) 1162 else: 1163 m_u, m_i = _split_uri_ref(member) 1164 1165 #q = _Query( 1166 # "SELECT DISTINCT rm.package, rm.relation", 1167 # "FROM RelationMembers rm " 1168 # "JOIN UriBases u ON u.package = rm.package " 1169 # "AND u.prefix = rm.member_p", 1170 # "WHERE u.uri_base = ? AND rm.member_i = ?", 1171 # [m_u, m_i], 1172 # pid = "rm.package", eid = "rm.relation" 1173 #) 1174 1175 # since UriBases (above) hinders efficiency, replace with: 1176 q = _Query( 1177 "SELECT DISTINCT rm.package, rm.relation", 1178 "FROM RelationMembers rm " 1179 "JOIN Packages p ON p.id = rm.package " 1180 "LEFT JOIN Imports i ON i.package = rm.package " 1181 "AND i.id = rm.member_p", 1182 "WHERE rm.member_i = ? " 1183 "AND (" 1184 "(member_p = '' AND (p.uri = '' AND ? = p.url OR ? = p.uri))" 1185 " OR " 1186 "(member_p = i.id AND (i.uri = '' AND ? = i.url OR ? = i.uri))" 1187 ")", 1188 [m_i, m_u, m_u, m_u, m_u], 1189 pid = "rm.package", eid = "rm.relation" 1190 ) 1191 if pos is not None: q.append(" AND rm.ord = ?", pos) 1192 q.add_packages_filter(package_ids) 1193 if id: q.add_id_filter(id) 1194 q.wrap_in_count() 1195 r = self._conn.execute(*q.exe()) 1196 return r.next()[0]
1197
1198 - def count_views(self, package_ids, id=None):
1199 """ 1200 Return the number of views matching the criteria. 1201 """ 1202 assert _DF or not isinstance(package_ids, basestring) 1203 q = _Query( 1204 "SELECT e.package, e.id", 1205 "FROM Elements e", 1206 "WHERE e.typ = ?", 1207 args = [VIEW,] 1208 ) 1209 q.add_packages_filter(package_ids) 1210 if id: q.add_id_filter(id) 1211 q.wrap_in_count() 1212 r = self._conn.execute(*q.exe()) 1213 return r.next()[0]
1214
1215 - def count_resources(self, package_ids, id=None):
1216 """ 1217 Return the number of resources matching the criteria. 1218 """ 1219 assert _DF or not isinstance(package_ids, basestring) 1220 q = _Query( 1221 "SELECT e.package, e.id", 1222 "FROM Elements e", 1223 "WHERE e.typ = ?", 1224 args = [RESOURCE,] 1225 ) 1226 q.add_packages_filter(package_ids) 1227 if id: q.add_id_filter(id) 1228 q.wrap_in_count() 1229 r = self._conn.execute(*q.exe()) 1230 return r.next()[0]
1231
1232 - def count_tags(self, package_ids, id=None, meta=None):
1233 """ 1234 Return the number of tags matching the criteria. 1235 """ 1236 assert _DF or not isinstance(package_ids, basestring) 1237 q = _Query( 1238 "SELECT e.package, e.id", 1239 "FROM Elements e", 1240 "WHERE e.typ = ?", 1241 args = [TAG,] 1242 ) 1243 q.add_packages_filter(package_ids) 1244 if id: q.add_id_filter(id) 1245 if meta: q.add_meta_filter(meta) 1246 q.wrap_in_count() 1247 r = self._conn.execute(*q.exe()) 1248 return r.next()[0]
1249
1250 - def count_lists(self, package_ids, id=None, item=None, pos=None, meta=None):
1251 """ 1252 Return the number of lists matching the criteria. 1253 """ 1254 assert _DF or not isinstance(package_ids, basestring) 1255 assert _DF or pos is None or item is not None 1256 if item is None: 1257 q = _Query( 1258 "SELECT e.package, e.id", 1259 "FROM Elements e", 1260 "WHERE e.typ = ?", 1261 args = [LIST,] 1262 ) 1263 else: 1264 i_u, i_i = _split_uri_ref(item) 1265 q = _Query( 1266 "SELECT DISTINCT li.package, li.list", 1267 "FROM ListItems li " 1268 "JOIN UriBases u ON u.package = li.package " 1269 "AND u.prefix = li.item_p", 1270 "WHERE u.uri_base = ? AND li.item_i = ?", 1271 [i_u, i_i], 1272 pid = "li.package", eid = "li.list" 1273 ) 1274 if pos is not None: q.append(" AND li.ord = ?", pos) 1275 q.add_packages_filter(package_ids) 1276 if id: q.add_id_filter(id) 1277 if meta: q.add_meta_filter(meta) 1278 q.wrap_in_count() 1279 r = self._conn.execute(*q.exe()) 1280 return r.next()[0]
1281
1282 - def count_queries(self, package_ids, id=None):
1283 """ 1284 Return the number of querties matching the criteria. 1285 """ 1286 q = _Query( 1287 "SELECT e.package, e.id", 1288 "FROM Elements e", 1289 "WHERE e.typ = ?", 1290 args = [QUERY,] 1291 ) 1292 q.add_packages_filter(package_ids) 1293 if id: q.add_id_filter(id) 1294 q.wrap_in_count() 1295 r = self._conn.execute(*q.exe()) 1296 return r.next()[0]
1297
1298 - def count_imports(self, package_ids, 1299 id=None, 1300 url=None, 1301 uri=None 1302 ):
1303 """ 1304 Return the number of imports matching the criteria. 1305 """ 1306 q = _Query( 1307 "SELECT e.package, e.id", 1308 "FROM Imports e", 1309 ) 1310 q.add_packages_filter(package_ids) 1311 if id: q.add_id_filter(id) 1312 if url: q.add_column_filter("e.url", url) 1313 if uri: q.add_column_filter("e.uri", uri) 1314 q.wrap_in_count() 1315 r = self._conn.execute(*q.exe()) 1316 return r.next()[0]
1317 1318 # element updating 1319
1320 - def update_media(self, package_id, id, url, frame_of_reference):
1321 assert _DF or self.has_element(package_id, id, MEDIA) 1322 execute = self._curs.execute 1323 try: 1324 execute("UPDATE Medias SET url = ?, foref = ? " 1325 "WHERE package = ? AND id = ?", 1326 (url, frame_of_reference, package_id, id,)) 1327 except sqlite.Error, e: 1328 raise InternalError("could not update", e)
1329
1330 - def update_annotation(self, package_id, id, media, begin, end):
1331 """ 1332 ``media`` is the id-ref of an own or directly imported media. 1333 """ 1334 assert _DF or self.has_element(package_id, id, ANNOTATION) 1335 assert _DF or isinstance(begin, (int, long)) and begin >= 0, begin 1336 1337 p,s = _split_id_ref(media) # also assert that media has depth < 2 1338 assert _DF or p == "" or self.has_element(package_id, p, IMPORT), p 1339 assert _DF or p != "" or self.has_element(package_id, s, MEDIA), s 1340 1341 execute = self._curs.execute 1342 try: 1343 execute("UPDATE Annotations SET media_p = ?, " 1344 "media_i = ?, fbegin = ?, fend = ? " 1345 "WHERE package = ? and id = ?", 1346 (p, s, begin, end, package_id, id,)) 1347 except sqlite.Error, e: 1348 self._conn.rollback() 1349 raise InternalError("could not update", e) 1350 except: 1351 self._conn.rollback() 1352 raise
1353
1354 - def update_import(self, package_id, id, url, uri):
1355 assert _DF or self.has_element(package_id, id, IMPORT) 1356 execute = self._curs.execute 1357 try: 1358 execute("UPDATE Imports SET url = ?, uri = ? " 1359 "WHERE package = ? and id = ?", 1360 (url, uri, package_id, id,)) 1361 except sqlite.Error, e: 1362 self._conn.rollback() 1363 raise InternalError("error in updating", e) 1364 except: 1365 self._conn.rollback() 1366 raise
1367 1368 # element renaming 1369
1370 - def rename_element(self, package_id, old_id, element_type, new_id):
1371 """Rename an own elemenent of package_id. 1372 1373 NB: element_type must be provided and must be the type constant of the 1374 identified element, or the behaviour of this method is unspecified. 1375 1376 NB: This does not update references to that element. For that, you must 1377 also use `rename_references`. This however does update the id-ref of 1378 imported elements if the renamed element is an import. 1379 """ 1380 assert _DF or self.has_element(package_id, old_id, element_type) 1381 assert _DF or not self.has_element(package_id, new_id) 1382 1383 # NB: all the queries after the first one (and hence the transaction) 1384 # are only required because sqlite does not implement foreign keys; 1385 # with an "ON UPDATE CASCADE", the renaming in Elements would suffice. 1386 1387 self._begin_transaction("IMMEDIATE") 1388 execute = self._curs.execute 1389 args = (new_id, package_id, old_id) 1390 try: 1391 execute("UPDATE Elements SET id = ? WHERE package = ? AND id = ?", 1392 args) 1393 if element_type in (ANNOTATION, RELATION, VIEW, RESOURCE, QUERY): 1394 execute("UPDATE Contents SET element = ? " \ 1395 "WHERE package = ? AND element = ?", 1396 args) 1397 1398 if element_type == MEDIA: 1399 execute("UPDATE Medias SET id = ? "\ 1400 "WHERE package = ? AND id = ?", 1401 args) 1402 elif element_type == ANNOTATION: 1403 execute("UPDATE Annotations SET id = ? "\ 1404 "WHERE package = ? AND id = ?", 1405 args) 1406 elif element_type == RELATION: 1407 execute("UPDATE RelationMembers SET relation = ? " \ 1408 "WHERE package = ? AND relation = ?", 1409 args) 1410 elif element_type == LIST: 1411 execute("UPDATE ListItems SET list = ? " \ 1412 "WHERE package = ? AND list = ?", 1413 args) 1414 elif element_type == IMPORT: 1415 execute("UPDATE Imports SET id = ? "\ 1416 "WHERE package = ? AND id = ?", 1417 args) 1418 execute("UPDATE Contents SET model_p = ? " \ 1419 "WHERE package = ? AND model_p = ?", 1420 args) 1421 execute("UPDATE Annotations SET media_p = ? " \ 1422 "WHERE package = ? AND media_p = ?", 1423 args) 1424 execute("UPDATE RelationMembers SET member_p = ? " \ 1425 "WHERE package = ? AND member_p = ?", 1426 args) 1427 execute("UPDATE ListItems SET item_p = ? " \ 1428 "WHERE package = ? AND item_p = ?", 1429 args) 1430 execute("UPDATE Tagged SET element_p = ? " \ 1431 "WHERE package = ? AND element_p = ?", 1432 args) 1433 execute("UPDATE Tagged SET tag_p = ? " \ 1434 "WHERE package = ? AND tag_p = ?", 1435 args) 1436 execute("UPDATE Meta SET value_p = ? " \ 1437 "WHERE package = ? AND value_p = ?", 1438 args) 1439 except sqlite.Error, e: 1440 execute("ROLLBACK") 1441 raise InternalError("could not update", e) 1442 except: 1443 execute("ROLLBACK") 1444 raise 1445 execute("COMMIT")
1446
1447 - def rename_references(self, package_ids, old_uriref, new_id):
1448 """Reflect the renaming of an element in several packages. 1449 1450 Apply the change of id of an element (formerly known as old_uriref) 1451 in all references to that element in package_ids. 1452 """ 1453 assert _DF or not isinstance(package_ids, basestring) 1454 element_u, element_i = _split_uri_ref(old_uriref) 1455 args = [new_id,] + list(package_ids) + [element_i, element_u,] 1456 qmarks = "(" + ",".join( "?" for i in package_ids ) + ")" 1457 execute = self._curs.execute 1458 self._begin_transaction("IMMEDIATE") 1459 try: 1460 # media references 1461 q2 = """UPDATE Annotations SET media_i = ? 1462 WHERE package IN %s 1463 AND media_i = ? 1464 AND EXISTS ( 1465 SELECT * FROM UriBases 1466 WHERE UriBases.package = Annotations.package 1467 AND prefix = media_p 1468 AND uri_base = ? 1469 ) 1470 """ % qmarks 1471 execute(q2, args) 1472 # model references 1473 q2 = """UPDATE Contents SET model_i = ? 1474 WHERE package IN %s 1475 AND model_i = ? 1476 AND EXISTS ( 1477 SELECT * FROM UriBases 1478 WHERE UriBases.package = Contents.package 1479 AND prefix = model_p 1480 AND uri_base = ? 1481 ) 1482 """ % qmarks 1483 execute(q2, args) 1484 # member references 1485 q2 = """UPDATE RelationMembers SET member_i = ? 1486 WHERE package IN %s 1487 AND member_i = ? 1488 AND EXISTS ( 1489 SELECT * FROM UriBases 1490 WHERE UriBases.package = RelationMembers.package 1491 AND prefix = member_p 1492 AND uri_base = ? 1493 ) 1494 """ % qmarks 1495 execute(q2, args) 1496 # item references 1497 q2 = """UPDATE ListItems SET item_i = ? 1498 WHERE package IN %s 1499 AND item_i = ? 1500 AND EXISTS ( 1501 SELECT * FROM UriBases 1502 WHERE UriBases.package = ListItems.package 1503 AND prefix = item_p 1504 AND uri_base = ? 1505 ) 1506 """ % qmarks 1507 execute(q2, args) 1508 # tags references 1509 q2 = """UPDATE Tagged SET tag_i = ? 1510 WHERE package IN %s 1511 AND tag_i = ? 1512 AND EXISTS ( 1513 SELECT * FROM UriBases 1514 WHERE UriBases.package = Tagged.package 1515 AND prefix = tag_p 1516 AND uri_base = ? 1517 ) 1518 """ % qmarks 1519 execute(q2, args) 1520 # tagged element references 1521 q2 = """UPDATE Tagged SET element_i = ? 1522 WHERE package IN %s 1523 AND element_i = ? 1524 AND EXISTS ( 1525 SELECT * FROM UriBases 1526 WHERE UriBases.package = Tagged.package 1527 AND prefix = element_p 1528 AND uri_base = ? 1529 ) 1530 """ % qmarks 1531 execute(q2, args) 1532 # metadata references 1533 q2 = """UPDATE Meta SET value_i = ? 1534 WHERE package IN %s 1535 AND value_i = ? 1536 AND EXISTS ( 1537 SELECT * FROM UriBases 1538 WHERE UriBases.package = Meta.package 1539 AND prefix = value_p 1540 AND uri_base = ? 1541 ) 1542 """ % qmarks 1543 execute(q2, args) 1544 except InternalError, e:#sqlite.Error, e: 1545 execute("ROLLBACK") 1546 raise InternalError("could not update", e) 1547 except: 1548 execute("ROLLBACK") 1549 raise 1550 execute("COMMIT")
1551 1552 # element deletion 1553
1554 - def delete_element(self, package_id, id, element_type):
1555 """Delete the identified element. 1556 1557 NB: This does not delete references to that element, *even* in the same 1558 package. The appropriate methods (`iter_references`, 1559 `iter_references_with_import`) must be used to detect and delete 1560 those references prior to deletion. 1561 """ 1562 assert _DF or self.has_element(package_id, id, element_type) 1563 1564 # NB: all the queries after the first one (and hence the transaction) 1565 # are only required because sqlite does not implement foreign keys; 1566 # with an "ON DELETE CASCADE", the deletion in Elements would suffice. 1567 1568 self._begin_transaction("IMMEDIATE") 1569 execute = self._curs.execute 1570 args = (package_id, id) 1571 try: 1572 execute("DELETE FROM Elements WHERE package = ? AND id = ?", args) 1573 if element_type in (ANNOTATION, RELATION, VIEW, RESOURCE, QUERY): 1574 execute("DELETE FROM Contents " \ 1575 "WHERE package = ? AND element = ?", 1576 args) 1577 1578 if element_type == MEDIA: 1579 execute("DELETE FROM Medias WHERE package = ? AND id = ?", 1580 args) 1581 elif element_type == ANNOTATION: 1582 execute("DELETE FROM Annotations WHERE package = ? AND id = ?", 1583 args) 1584 elif element_type == RELATION: 1585 execute("DELETE FROM RelationMembers " \ 1586 "WHERE package = ? AND relation = ?", 1587 args) 1588 elif element_type == LIST: 1589 execute("DELETE FROM ListItems WHERE package = ? AND list = ?", 1590 args) 1591 except sqlite.Error, e: 1592 execute("ROLLBACK") 1593 raise InternalError("could not delete", e) 1594 except: 1595 execute("ROLLBACK") 1596 raise 1597 execute("COMMIT")
1598 1599 # content management 1600
1601 - def get_content_info(self, package_id, id, element_type):
1602 """Return information about the content of an element, or None. 1603 1604 The information is a tuple of the form (mimetype, model_id, url), 1605 where ``model_id`` and ``url`` can be empty strings. 1606 1607 None is returned if the element does not exist or has no content. 1608 1609 Note that this method will not be used often since this information 1610 is provided by get_element for all elements having a content. 1611 """ 1612 q = "SELECT mimetype, join_id_ref(model_p,model_i) as model, url " \ 1613 "FROM Contents " \ 1614 "WHERE package = ? AND element = ?" 1615 return self._curs.execute(q, (package_id, id,)).fetchone() or None
1616
1617 - def update_content_info(self, package_id, id, element_type, 1618 mimetype, model, url):
1619 """Update the content information of the identified element. 1620 1621 ``model`` is the id of an own or directly imported resource, 1622 or an empty string to specify no model (not None). 1623 1624 If ``url`` is not an empty string, any data stored in the backend for 1625 this content will be discarded. 1626 """ 1627 assert _DF or self.has_element(package_id, id, element_type) 1628 assert _DF or element_type in (ANNOTATION,RELATION,VIEW,QUERY,RESOURCE) 1629 if model: 1630 p,s = _split_id_ref(model) # also assert that model has depth < 2 1631 assert _DF or p == "" or self.has_element(package_id,p,IMPORT), p 1632 assert _DF or p != "" or s == "" or \ 1633 self.has_element(package_id, s, RESOURCE), s 1634 else: 1635 p,s = "","" 1636 1637 q = "UPDATE Contents "\ 1638 "SET mimetype = ?, model_p = ?, model_i = ?, url = ?%s "\ 1639 "WHERE package = ? AND element = ?" 1640 args = [mimetype, p, s, url, package_id, id,] 1641 if url: 1642 q %= ", data = ?" 1643 args[4:4] = ["",] 1644 else: 1645 q %= "" 1646 execute = self._curs.execute 1647 try: 1648 execute(q, args) 1649 except sqlite.Error, e: 1650 raise InternalError("could not update", e)
1651
1652 - def get_content_data(self, package_id, id, element_type):
1653 """Return the stored data, as a string, of the content of an element. 1654 1655 This method will return an empty string if the content is externally 1656 stored (non-empty ``url`` attribute). 1657 """ 1658 assert _DF or self.has_element(package_id, id, element_type) 1659 assert _DF or element_type in (ANNOTATION,RELATION,VIEW,QUERY,RESOURCE) 1660 q = "SELECT data FROM Contents WHERE package = ? AND element = ?" 1661 return self._curs.execute(q, (package_id, id,)).fetchone()[0]
1662
1663 - def update_content_data(self, package_id, id, element_type, data):
1664 """Update the content data of the identified element. 1665 1666 If `data` is not an empty string, the ``url`` attribute of the content 1667 will be cleared. 1668 """ 1669 assert _DF or self.has_element(package_id, id, element_type) 1670 assert _DF or element_type in (ANNOTATION,RELATION,VIEW,QUERY,RESOURCE) 1671 q = "UPDATE Contents SET data = ?%s WHERE package = ? AND element = ?" 1672 args = [data, package_id, id,] 1673 if data: 1674 q %= ", url = ?" 1675 args[1:1] = ["",] 1676 else: 1677 q %= "" 1678 execute = self._curs.execute 1679 try: 1680 execute(q, args) 1681 except sqlite.Error, e: 1682 raise InternalError("could not update", e)
1683
1684 - def iter_contents_with_model(self, package_ids, model):
1685 """ 1686 Return tuples of the form (package_id, id) of all the 1687 elements with a content having the given model. 1688 1689 @param model the uri-ref of a resource 1690 """ 1691 assert _DF or not isinstance(package_ids, basestring) 1692 1693 model_u, model_i = _split_uri_ref(model) 1694 1695 q = "SELECT c.package, c.element FROM Contents c " \ 1696 "JOIN Packages p ON c.package = p.id "\ 1697 "LEFT JOIN Imports i ON c.model_p = i.id " \ 1698 "WHERE c.package IN (" \ 1699 + ",".join( "?" for i in package_ids ) + ")" \ 1700 " AND model_i = ? AND ("\ 1701 " (model_p = '' AND ? IN (p.uri, p.url)) OR " \ 1702 " (model_p = i.id AND ? IN (i.uri, i.url)))" 1703 args = list(package_ids) + [model_i, model_u, model_u,] 1704 r = self._conn.execute(q, args) 1705 return _FlushableIterator(r, self)
1706 1707 # meta-data management 1708
1709 - def iter_meta(self, package_id, id, element_type):
1710 """Iter over the metadata, sorting keys in alphabetical order. 1711 1712 Yield tuples of the form (key, val, val_is_id) (cf. `get_meta` and 1713 `set_meta`). 1714 1715 If package metadata is targeted, id should be an empty string (in 1716 that case, element_type will be ignored). 1717 """ 1718 assert _DF or id == "" or self.has_element(package_id, id, element_type) 1719 q = """SELECT key, value, value_p, value_i FROM Meta 1720 WHERE package = ? AND element = ? ORDER BY key""" 1721 r = ( (d[0], 1722 d[2] and "%s:%s" % (d[2], d[3]) or d[3] or d[1], 1723 d[3] != "", 1724 ) for d in self._conn.execute(q, (package_id, id)) ) 1725 return _FlushableIterator(r, self)
1726
1727 - def get_meta(self, package_id, id, element_type, key):
1728 """Return the given metadata of the identified element. 1729 1730 Return a tuple of the form (val, val_is_id) where the second item is 1731 a boolean, indicating whether the first item must be interpreted as an 1732 id-ref, or None if the element has no metadata with that key. 1733 1734 If package metadata is targeted, id should be an empty string (in 1735 that case, element_type will be ignored). 1736 """ 1737 assert _DF or id == "" or self.has_element(package_id, id, element_type) 1738 q = """SELECT value, value_p, value_i FROM Meta 1739 WHERE package = ? AND element = ? AND KEY = ?""" 1740 d = self._curs.execute(q, (package_id, id, key,)).fetchone() 1741 if d is None: 1742 return None 1743 elif d[2]: 1744 return (d[1] and "%s:%s" % (d[1], d[2]) or d[2], True) 1745 else: 1746 return (d[0], False)
1747
1748 - def set_meta(self, package_id, id, element_type, key, val, val_is_id):
1749 """Set the given metadata of the identified element. 1750 1751 Parameter ``val_is_id`` indicates whether parameter ``val`` must be 1752 interpreted as an id-ref rather than a plain string. Note that ``val`` 1753 can also be None to unset the corresponding metadata; in that case, 1754 val_is_id is ignored. 1755 1756 If package metadata is targeted, id should be an empty string (in 1757 that case, element_type will be ignored). 1758 """ 1759 assert _DF or id == "" or self.has_element(package_id, id, element_type) 1760 1761 if val is not None and val_is_id: 1762 val_p, val_i = _split_id_ref(val) 1763 val = "" 1764 else: 1765 val_p = "" 1766 val_i = "" 1767 1768 q = """SELECT value FROM Meta 1769 WHERE package = ? AND element = ? and key = ?""" 1770 c = self._curs.execute(q, (package_id, id, key)) 1771 d = c.fetchone() 1772 1773 if d is None: 1774 if val is not None: 1775 q = """INSERT INTO Meta 1776 VALUES (?,?,?,?,?,?)""" 1777 args = (package_id, id, key, val, val_p, val_i) 1778 else: 1779 q = "" 1780 else: 1781 if val is not None: 1782 q = """UPDATE Meta SET value = ?, value_p = ?, value_i = ? 1783 WHERE package = ? AND element = ? AND key = ?""" 1784 args = (val, val_p, val_i, package_id, id, key) 1785 else: 1786 q = """DELETE FROM Meta 1787 WHERE package = ? AND element = ? AND key = ?""" 1788 args = (package_id, id, key) 1789 if q: 1790 execute = self._curs.execute 1791 try: 1792 execute(q, args) 1793 except sqlite.Error, e: 1794 raise InternalError("could not %s" % q[:6], e)
1795
1796 - def iter_meta_refs(self, package_ids, uriref, element_type):
1797 """ 1798 Iter over the metadata whose value is a reference to the element 1799 identified by uriref. 1800 1801 The returned iterator yields triples of the form 1802 (package_id, element_id, metadata_key) where element_id is the empty 1803 string for package metadata.d 1804 """ 1805 # TODO may be deprecated 1806 assert _DF or not isinstance(package_ids, basestring) 1807 element_u, element_i = _split_uri_ref(uriref) 1808 q1 = "SELECT p.id || ' ' || i.id " \ 1809 "FROM Packages p JOIN Imports i ON p.id = i.package " \ 1810 "WHERE ? IN (i.uri, i.url) " \ 1811 "UNION " \ 1812 "SELECT p.id || ' ' FROM Packages p " \ 1813 "WHERE ? IN (p.uri, p.url)" 1814 # The query above selects all pairs package_id/import_id (where the 1815 # second can be "") matching the URI prefix of old_uriref. 1816 # It can then be used to know detect id-refs to update. 1817 # (see also rename_references) 1818 q2 = "SELECT package, element, key " \ 1819 "FROM Meta " \ 1820 "WHERE package IN (" \ 1821 + ",".join( "?" for i in package_ids ) + ")" \ 1822 "AND value_i = ? " \ 1823 "AND package || ' ' || value_p IN (%s)" % q1 1824 args = list(package_ids) \ 1825 + [element_i, element_u, element_u,] 1826 r = self._conn.execute(q2, args) 1827 return _FlushableIterator(r, self)
1828 1829 # relation members management 1830
1831 - def insert_member(self, package_id, id, member, pos, n=-1):
1832 """ 1833 Insert a member at the given position. 1834 ``member`` is the id-ref of an own or directly imported member. 1835 ``pos`` may be any value between -1 and n (inclusive), where n is the 1836 current number of members. 1837 If -1, the member will be appended at the end (**note** that this is 1838 not the same behaviour as ``list.insert`` in python2.5). 1839 If non-negative, the member will be inserted at that position. 1840 1841 NB: the total number of members, n, if known, may be provided, as an 1842 optimization. 1843 """ 1844 assert _DF or self.has_element(package_id, id, RELATION) 1845 if n < 0: 1846 n = self.count_members(package_id, id) 1847 assert _DF or -1 <= pos <= n, pos 1848 p,s = _split_id_ref(member) # also assert that member has depth < 2 1849 assert _DF or p == "" or self.has_element(package_id, p, IMPORT), p 1850 assert _DF or p != "" or self.has_element(package_id, s, ANNOTATION), s 1851 if pos == -1: 1852 pos = n 1853 execute = self._curs.execute 1854 executemany = self._curs.executemany 1855 updates = ((package_id, id, i) for i in xrange(n, pos-1, -1)) 1856 self._begin_transaction() 1857 try: 1858 # sqlite does not seem to be able to do the following updates in 1859 # one query (unicity constraint breaks), so... 1860 executemany("UPDATE RelationMembers SET ord=ord+1 " 1861 "WHERE package = ? AND relation = ? AND ord = ?", 1862 updates) 1863 execute("INSERT INTO RelationMembers VALUES (?,?,?,?,?)", 1864 (package_id, id, pos, p, s)) 1865 except sqlite.Error, e: 1866 execute("ROLLBACK") 1867 raise InternalError("could not update or insert", e) 1868 except: 1869 execute("ROLLBACK") 1870 raise 1871 execute("COMMIT")
1872
1873 - def update_member(self, package_id, id, member, pos):
1874 """ 1875 Remobv the member at the given position in the identified relation. 1876 ``member`` is the id-ref of an own or directly imported member. 1877 """ 1878 assert _DF or self.has_element(package_id, id, RELATION) 1879 assert _DF or 0 <= pos < self.count_members(package_id, id), pos 1880 1881 p,s = _split_id_ref(member) # also assert that member has depth < 2 1882 assert _DF or p == "" or self.has_element(package_id, p, IMPORT), p 1883 assert _DF or p != "" or self.has_element(package_id, s, ANNOTATION), s 1884 1885 execute = self._curs.execute 1886 try: 1887 execute("UPDATE RelationMembers SET member_p = ?, member_i = ? " 1888 "WHERE package = ? AND relation = ? AND ord = ?", 1889 (p, s, package_id, id, pos)) 1890 except sqlite.Error, e: 1891 raise InternalError("could not update", e)
1892
1893 - def count_members(self, package_id, id):
1894 """ 1895 Count the members of the identified relations. 1896 1897 This should return 0 if the relation does not exist. 1898 """ 1899 q = "SELECT count(ord) FROM RelationMembers "\ 1900 "WHERE package = ? AND relation = ?" 1901 return self._curs.execute(q, (package_id, id)).fetchone()[0]
1902
1903 - def get_member(self, package_id, id, pos, n=-1):
1904 """ 1905 Return the id-ref of the member at the given position in the identified 1906 relation. 1907 1908 NB: the total number of members, n, if known, may be provided, as an 1909 optimization. 1910 """ 1911 assert _DF or self.has_element(package_id, id, RELATION) 1912 if __debug__: 1913 n = self.count_members(package_id, id) 1914 assert _DF or -n <= pos < n, pos 1915 1916 if pos < 0: 1917 if n < 0: 1918 n = self.count_members(package_id, id) 1919 pos += n 1920 1921 q = "SELECT join_id_ref(member_p,member_i) AS member " \ 1922 "FROM RelationMembers "\ 1923 "WHERE package = ? AND relation = ? AND ord = ?" 1924 return self._curs.execute(q, (package_id, id, pos)).fetchone()[0]
1925
1926 - def iter_members(self, package_id, id):
1927 """ 1928 Iter over all the members of the identified relation. 1929 """ 1930 assert _DF or self.has_element(package_id, id, RELATION) 1931 q = "SELECT join_id_ref(member_p,member_i) AS member " \ 1932 "FROM RelationMembers " \ 1933 "WHERE package = ? AND relation = ? ORDER BY ord" 1934 r = ( i[0] for i in self._conn.execute(q, (package_id, id)) ) 1935 return _FlushableIterator(r, self)
1936
1937 - def remove_member(self, package_id, id, pos):
1938 """ 1939 Remove the member at the given position in the identified relation. 1940 """ 1941 assert _DF or self.has_element(package_id, id, RELATION) 1942 assert _DF or 0 <= pos < self.count_members(package_id, id), pos 1943 1944 execute = self._curs.execute 1945 self._begin_transaction() 1946 try: 1947 execute("DELETE FROM RelationMembers " 1948 "WHERE package = ? AND relation = ? AND ord = ?", 1949 (package_id, id, pos)) 1950 execute("UPDATE RelationMembers SET ord=ord-1 " 1951 "WHERE package = ? AND relation = ? AND ord > ?", 1952 (package_id, id, pos)) 1953 except sqlite.Error, e: 1954 execute("ROLLBACK") 1955 raise InternalError("could not delete or update", e) 1956 except: 1957 execute("ROLLBACK") 1958 raise 1959 execute("COMMIT")
1960 1961 # list items management 1962
1963 - def insert_item(self, package_id, id, item, pos, n=-1):
1964 """ 1965 Insert an item at the given position. 1966 ``item`` is the id-ref of an own or directly imported item. 1967 ``pos`` may be any value between -1 and n (inclusive), where n is the 1968 current number of items. 1969 If -1, the item will be appended at the end (**note** that this is 1970 not the same behaviour as ``list.insert`` in python2.5). 1971 If non-negative, the item will be inserted at that position. 1972 1973 NB: the total number of members, n, if known, may be provided, as an 1974 optimization. 1975 """ 1976 assert _DF or self.has_element(package_id, id, LIST) 1977 if n < 0: 1978 n = self.count_items(package_id, id) 1979 assert _DF or -1 <= pos <= n, pos 1980 p,s = _split_id_ref(item) # also assert that item has depth < 2 1981 assert _DF or p == "" or self.has_element(package_id, p, IMPORT), p 1982 assert _DF or p != "" or self.has_element(package_id, s), item 1983 if pos == -1: 1984 pos = n 1985 execute = self._curs.execute 1986 executemany = self._curs.executemany 1987 updates = ((package_id, id, i) for i in xrange(n, pos-1, -1)) 1988 self._begin_transaction() 1989 try: 1990 # sqlite does not seem to be able to do the following updates in 1991 # one query (unicity constraint breaks), so... 1992 executemany("UPDATE ListItems SET ord=ord+1 " 1993 "WHERE package = ? AND list = ? AND ord = ?", 1994 updates) 1995 execute("INSERT INTO ListItems VALUES (?,?,?,?,?)", 1996 (package_id, id, pos, p, s)) 1997 except sqlite.Error, e: 1998 execute("ROLLBACK") 1999 raise InternalError("could not update or insert", e) 2000 except: 2001 execute("ROLLBACK") 2002 raise 2003 execute("COMMIT")
2004
2005 - def update_item(self, package_id, id, item, pos):
2006 """ 2007 Remobv the item at the given position in the identified list. 2008 ``item`` is the id-ref of an own or directly imported item. 2009 """ 2010 assert _DF or self.has_element(package_id, id, LIST) 2011 assert _DF or 0 <= pos < self.count_items(package_id, id), pos 2012 2013 p,s = _split_id_ref(item) # also assert that item has depth < 2 2014 assert _DF or p == "" or self.has_element(package_id, p, IMPORT), p 2015 assert _DF or p != "" or self.has_element(package_id, s), s 2016 2017 execute = self._curs.execute 2018 try: 2019 execute("UPDATE ListItems SET item_p = ?, item_i = ? " 2020 "WHERE package = ? AND list = ? AND ord = ?", 2021 (p, s, package_id, id, pos)) 2022 except sqlite.Error, e: 2023 raise InternalError("could not update", e)
2024
2025 - def count_items(self, package_id, id):
2026 """ 2027 Count the items of the identified lists. 2028 2029 This should return 0 if the list does not exist. 2030 """ 2031 q = "SELECT count(ord) FROM ListItems "\ 2032 "WHERE package = ? AND list = ?" 2033 return self._curs.execute(q, (package_id, id)).fetchone()[0]
2034
2035 - def get_item(self, package_id, id, pos, n=-1):
2036 """ 2037 Return the id-ref of the item at the given position in the identified 2038 list. 2039 2040 NB: the total number of members, n, if known, may be provided, as an 2041 optimization. 2042 """ 2043 assert _DF or self.has_element(package_id, id, LIST) 2044 if __debug__: 2045 n = self.count_items(package_id, id) 2046 assert _DF or -n <= pos < n, pos 2047 2048 if pos < 0: 2049 if n < 0: 2050 n = self.count_items(package_id, id) 2051 pos += n 2052 2053 q = "SELECT join_id_ref(item_p,item_i) AS item " \ 2054 "FROM ListItems "\ 2055 "WHERE package = ? AND list = ? AND ord = ?" 2056 return self._curs.execute(q, (package_id, id, pos)).fetchone()[0]
2057
2058 - def iter_items(self, package_id, id):
2059 """ 2060 Iter over all the items of the identified list. 2061 """ 2062 assert _DF or self.has_element(package_id, id, LIST) 2063 q = "SELECT join_id_ref(item_p,item_i) AS item " \ 2064 "FROM ListItems " \ 2065 "WHERE package = ? AND list = ? ORDER BY ord" 2066 r = ( i[0] for i in self._conn.execute(q, (package_id, id)) ) 2067 return _FlushableIterator(r, self)
2068
2069 - def remove_item(self, package_id, id, pos):
2070 """ 2071 Remove the item at the given position in the identified list. 2072 """ 2073 assert _DF or self.has_element(package_id, id, LIST) 2074 assert _DF or 0 <= pos < self.count_items(package_id, id), pos 2075 2076 execute = self._curs.execute 2077 self._begin_transaction() 2078 try: 2079 execute("DELETE FROM ListItems " 2080 "WHERE package = ? AND list = ? AND ord = ?", 2081 (package_id, id, pos)) 2082 execute("UPDATE ListItems SET ord=ord-1 " 2083 "WHERE package = ? AND list = ? AND ord > ?", 2084 (package_id, id, pos)) 2085 except sqlite.Error, e: 2086 execute("ROLLBACK") 2087 self._conn.rollback() 2088 raise InternalError("could not delete or update", e) 2089 except: 2090 execute("ROLLBACK") 2091 self._conn.rollback() 2092 raise 2093 execute("COMMIT")
2094 2095 # tagged elements management 2096
2097 - def associate_tag(self, package_id, element, tag):
2098 """Associate a tag to an element. 2099 2100 @param element the id-ref of an own or directly imported element 2101 @param tag the id-ref of an own or directly imported tag 2102 """ 2103 ep, es = _split_id_ref(element) # also assert that it has depth < 2 2104 assert _DF or ep == "" or self.has_element(package_id, ep, IMPORT), ep 2105 assert _DF or ep != "" or self.has_element(package_id, es), es 2106 tp, ts = _split_id_ref(tag) # also assert that tag has depth < 2 2107 assert _DF or tp == "" or self.has_element(package_id, tp, IMPORT), tp 2108 assert _DF or tp != "" or self.has_element(package_id, ts, TAG), ts 2109 2110 execute = self._curs.execute 2111 try: 2112 execute("INSERT OR IGNORE INTO Tagged VALUES (?,?,?,?,?)", 2113 (package_id, ep, es, tp, ts)) 2114 except sqlite.Error, e: 2115 raise InternalError("could not insert", e)
2116
2117 - def dissociate_tag(self, package_id, element, tag):
2118 """Dissociate a tag from an element. 2119 2120 @param element the id-ref of an own or directly imported element 2121 @param tag the id-ref of an own or directly imported tag 2122 """ 2123 ep, es = _split_id_ref(element) # also assert that it has depth < 2 2124 assert _DF or ep == "" or self.has_element(package_id, ep, IMPORT), ep 2125 assert _DF or ep != "" or self.has_element(package_id, es), es 2126 tp, ts = _split_id_ref(tag) # also assert that tag has depth < 2 2127 assert _DF or tp == "" or self.has_element(package_id, tp, IMPORT), tp 2128 assert _DF or tp != "" or self.has_element(package_id, ts, TAG), ts 2129 2130 execute = self._curs.execute 2131 try: 2132 execute("DELETE FROM Tagged WHERE package = ? " 2133 "AND element_p = ? AND element_i = ? " 2134 "AND tag_p = ? AND tag_i = ?", 2135 (package_id, ep, es, tp, ts)) 2136 except sqlite.Error, e: 2137 raise InternalError("could not delete", e)
2138
2139 - def iter_tags_with_element(self, package_ids, element):
2140 """Iter over all the tags associated to element in the given packages. 2141 2142 @param element the uri-ref of an element 2143 """ 2144 assert _DF or not isinstance(package_ids, basestring) 2145 2146 element_u, element_i = _split_uri_ref(element) 2147 q = "SELECT t.package, join_id_ref(tag_p, tag_i) " \ 2148 "FROM Tagged t " \ 2149 "JOIN Packages p ON t.package = p.id " \ 2150 "LEFT JOIN Imports i ON t.element_p = i.id " \ 2151 "WHERE t.package IN (" \ 2152 + ",".join( "?" for i in package_ids ) + ")" \ 2153 " AND element_i = ? AND ("\ 2154 " (element_p = '' AND ? IN (p.uri, p.url)) OR " \ 2155 " (element_p = i.id AND ? IN (i.uri, i.url)))" 2156 args = list(package_ids) + [element_i, element_u, element_u] 2157 2158 r = self._conn.execute(q, args) 2159 return _FlushableIterator(r, self)
2160
2161 - def iter_elements_with_tag(self, package_ids, tag):
2162 """Iter over all the elements associated to tag in the given packages. 2163 2164 @param tag the uri-ref of a tag 2165 """ 2166 assert _DF or not isinstance(package_ids, basestring) 2167 2168 tag_u, tag_i = _split_uri_ref(tag) 2169 q = "SELECT t.package, join_id_ref(element_p, element_i) " \ 2170 "FROM Tagged t " \ 2171 "JOIN Packages p ON t.package = p.id " \ 2172 "LEFT JOIN Imports i ON t.tag_p = i.id " \ 2173 "WHERE t.package IN (" \ 2174 + ",".join( "?" for i in package_ids ) + ")" \ 2175 " AND tag_i = ? AND ("\ 2176 " (tag_p = '' AND ? IN (p.uri, p.url)) OR " \ 2177 " (tag_p = i.id AND ? IN (i.uri, i.url)))" 2178 args = list(package_ids) + [tag_i, tag_u, tag_u] 2179 2180 r = self._conn.execute(q, args) 2181 return _FlushableIterator(r, self)
2182
2183 - def iter_taggers(self, package_ids, element, tag):
2184 """Iter over all the packages associating element to tag. 2185 2186 @param element the uri-ref of an element 2187 @param tag the uri-ref of a tag 2188 """ 2189 assert _DF or not isinstance(package_ids, basestring) 2190 2191 element_u, element_i = _split_uri_ref(element) 2192 tag_u, tag_i = _split_uri_ref(tag) 2193 q = "SELECT t.package " \ 2194 "FROM Tagged t " \ 2195 "JOIN Packages p ON t.package = p.id " \ 2196 "LEFT JOIN Imports ie ON t.element_p = ie.id " \ 2197 "LEFT JOIN Imports it ON t.tag_p = it.id " \ 2198 "WHERE t.package IN (" \ 2199 + ",".join( "?" for i in package_ids ) + ")" \ 2200 " AND element_i = ? AND ("\ 2201 " (element_p = '' AND ? IN (p.uri, p.url)) OR " \ 2202 " (element_p = ie.id AND ? IN (ie.uri, ie.url)))" \ 2203 " AND tag_i = ? AND ("\ 2204 " (tag_p = '' AND ? IN (p.uri, p.url)) OR " \ 2205 " (tag_p = it.id AND ? IN (it.uri, it.url)))" 2206 args = list(package_ids) \ 2207 + [element_i, element_u, element_u, tag_i, tag_u, tag_u,] 2208 2209 r = ( i[0] for i in self._conn.execute(q, args) ) 2210 return _FlushableIterator(r, self)
2211
2212 - def iter_external_tagging(self, package_id):
2213 """Iter over all tagging involving two imported elements. 2214 2215 This is useful for serialization. 2216 """ 2217 q = "SELECT join_id_ref(element_p, element_i), " \ 2218 "join_id_ref(tag_p, tag_i) " \ 2219 "FROM Tagged t " \ 2220 "WHERE t.package = ? AND element_p > '' AND tag_p > ''" 2221 r = self._conn.execute(q, (package_id,)) 2222 return _FlushableIterator(r, self)
2223 2224 2225 # end of the backend interface 2226
2227 - def __init__(self, path, conn, force):
2228 """ 2229 Is not part of the interface. Instances must be created either with 2230 the L{create} or the L{bind} module functions. 2231 2232 Create a backend, and bind it to the given URL. 2233 """ 2234 2235 self._path = path 2236 self._conn = conn 2237 self._curs = conn.cursor() 2238 # NB: self._curs is to be used for any *internal* operations 2239 # Iterators intended for *external* use must be based on a new cursor. 2240 conn.create_function("join_id_ref", 2, 2241 lambda p,s: p and "%s:%s" % (p,s) or s) 2242 conn.create_function("regexp", 2, 2243 lambda r,l: re.search(r,l) is not None ) 2244 # NB: for a reason I don't know, the defined function regexp 2245 # receives the righthand operand first, then the lefthand operand... 2246 # hence the lambda function above 2247 self._bound = WeakValueDictWithCallback(self._check_unused) 2248 # NB: the callback ensures that even if packages "forget" to close 2249 # themselves, once they are garbage collected, we check if the 2250 # connexion to sqlite can be closed. 2251 self._iterators = WeakKeyDictionary()
2252 # _iterators is used to store all the iterators returned by iter_* 2253 # methods, and force them to flush their underlying cursor anytime 2254 # an modification of the database is about to happen 2255
2256 - def _bind(self, package_id, package):
2257 d = self._bound 2258 old = d.get(package_id) 2259 if old is not None: 2260 raise PackageInUse(old) 2261 try: 2262 self._curs.execute("UPDATE Packages SET url = ? WHERE id = ?", 2263 (package.url, package_id,)) 2264 except sqlite.Error, e: 2265 raise InternalError("could not update", e) 2266 d[package_id] = package
2267
2268 - def _check_unused(self, package_id):
2269 conn = self._conn 2270 if conn is not None and len(self._bound) == 0: 2271 #print "DEBUG:", __file__, \ 2272 # "about to close SqliteBackend", self._path 2273 try: 2274 self._curs.execute("UPDATE Packages SET url = ?", ("",)) 2275 finally: 2276 conn.close() 2277 self._conn = None 2278 self._curs = None 2279 # the following is necessary to break a cyclic reference: 2280 # self._bound references self._check_unused, which, as a bound 2281 # method, references self 2282 self._bound = None 2283 # the following is not stricly necessary, but does no harm ;) 2284 if self._path in _cache: del _cache[self._path]
2285
2286 - def _begin_transaction(self, mode=""):
2287 """Begin a transaction. 2288 2289 This method must *always* be used to begin a transaction (do *not* use 2290 `self._curs.execute("BEGIN")` directly. See `_FlushableIterator` . 2291 """ 2292 for i in self._iterators.iterkeys(): 2293 i.flush() 2294 self._curs.execute("BEGIN %s" % mode)
2295
2296 - def _create_element(self, execute, package_id, id, element_type):
2297 """Perform controls and insertions common to all elements. 2298 2299 NB: This starts a transaction that must be commited by caller. 2300 """ 2301 # check that the id is not in use 2302 self._begin_transaction("IMMEDIATE") 2303 c = execute("SELECT id FROM Elements WHERE package = ? AND id = ?", 2304 (package_id, id,)) 2305 if c.fetchone() is not None: 2306 raise ModelError("id in use: %s" % id) 2307 execute("INSERT INTO Elements VALUES (?,?,?)", 2308 (package_id, id, element_type))
2309 2310
2311 -class _FlushableIterator(object):
2312 """Cursor based iterator that may flush the cursor whenever needed. 2313 2314 Transactions to the database cannot be commited while a cursor is 2315 being used. So it is unsafe for _SqliteBackend to return cursors direcly 2316 because it may hinder further execution of methods using transactions. 2317 2318 On the other hand, it may be inefficient to flush all cursors into lists 2319 before returning them. This class provides an efficient solution. 2320 2321 All cursors (or cursor based iterators) are wrapped info a 2322 _FlushableIterator before being returned, and the latter is weakly 2323 referenced by the backend instance. Whenever a transaction is started, 2324 all known _FlushableIterators are flushed, i.e. they internally change 2325 their underlying iterator into a list, so that the transaction can be 2326 committed, but users can continue to transparently use them. 2327 2328 Note that this implementation uses the iterable interface of sqlite 2329 cursors rather than the DB-API cursor interface. This is less portable, 2330 but allows to wrap iterators that are not cursors but relying on a 2331 cursor, e.g.::: 2332 2333 return _FlusableIterator(( r[1] for r in conn.execute(query) ), be) 2334 """ 2335 __slots__ = ["_cursor", "__weakref__",]
2336 - def __init__ (self, cursor, backend):
2337 self._cursor = cursor 2338 backend._iterators[self] = True
2339 - def __iter__ (self):
2340 return self
2341 - def flush(self):
2342 """Flush the underlying cursor.""" 2343 self._cursor = iter(list(self._cursor))
2344 - def next(self):
2345 return self._cursor.next()
2346 2347 # NB: the wrapping of cursors into _FlushableIterators could be implemented 2348 # as a decorator on all iter_* functions. However 2349 # - "classical" (i.e. wrapping) decorators have a high overhead 2350 # - "smart" (i.e. code-modifying) decorators are hard to write 2351 # so for the moment, we opt for old-school copy/paste... 2352
2353 -class _Query(object):
2354 """ 2355 I provide useful functionalities for building SQL queries to the backend 2356 schema. 2357 """ 2358
2359 - def __init__(self, select, from_, where="WHERE 1", args=(), 2360 pid="e.package", eid="e.id"):
2361 self.s = select 2362 self.f = from_ 2363 self.w = where 2364 self.a = list(args) 2365 self.pid = pid 2366 self.eid = eid
2367
2368 - def add_package_filter(self, pid):
2369 self.w += " AND %(pid)s = ?" % self.__dict__ 2370 self.a.append(pid)
2371
2372 - def add_packages_filter(self, pids):
2373 self.w += " AND %(pid)s IN (%%s)" % self.__dict__ 2374 self.w %= ",".join( "?" for i in pids ) 2375 self.a.extend(pids)
2376
2377 - def add_id_filter(self, id):
2378 assert id is not None 2379 if isinstance(id, basestring): 2380 self.w += " AND %(eid)s = ?" % self.__dict__ 2381 self.a.append(id) 2382 else: 2383 self.w += " AND %(eid)s IN (%%s)" % self.__dict__ 2384 self.w %= ",".join( "?" for i in id ) 2385 self.a.extend(id)
2386
2387 - def add_column_filter(self, column, value):
2388 assert value is not None 2389 if isinstance(value, basestring): 2390 self.w += " AND %s = ?" % (column,) 2391 self.a.append(value) 2392 else: 2393 self.w += " AND %s IN (%%s)" % (column,) 2394 self.w %= ",".join( "?" for i in value ) 2395 self.a.extend(value)
2396
2397 - def add_content_columns(self):
2398 self.s += ", c.mimetype, join_id_ref(c.model_p, c.model_i), c.url" 2399 self.f += " JOIN Contents c"\ 2400 " ON %(pid)s = c.package AND %(eid)s = c.element"\ 2401 % self.__dict__
2402
2403 - def add_media_filter(self, media):
2404 if isinstance(media, basestring): 2405 media = (media,) 2406 media = [ _split_uri_ref(m) for m in media ] 2407 n = len(media) 2408 self.f += " JOIN UriBases mu ON mu.package = %(pid)s "\ 2409 "AND mu.prefix = media_p"\ 2410 % self.__dict__ 2411 self.w += " AND (%s)"\ 2412 % " OR ".join(n*["mu.uri_base = ? AND media_i = ?"]) 2413 self.a.extend(sum(media, ()))
2414
2415 - def add_member_filter(self, member, ord=None):
2416 m_u, m_i = _split_uri_ref(member) 2417 self.w += " AND EXISTS ("\ 2418 "SELECT m.relation FROM RelationMembers m "\ 2419 "JOIN UriBases u ON m.package = u.package "\ 2420 "AND m.member_p = u.prefix "\ 2421 "WHERE m.package = %(pid)s "\ 2422 "AND m.relation = %(eid)s "\ 2423 "AND u.uri_base = ? AND m.member_i = ?"\ 2424 ")" % self.__dict__ 2425 self.a.extend([m_u, m_i]) 2426 if ord is not None: 2427 self.w = self.w[:-1] + " AND m.ord = ?)" 2428 self.a.append(ord)
2429
2430 - def add_item_filter(self, item, ord=None):
2431 i_u, i_i = _split_uri_ref(item) 2432 self.w += " AND EXISTS ("\ 2433 "SELECT i.list FROM ListItems i "\ 2434 "JOIN UriBases u ON i.package = u.package "\ 2435 "AND i.item_p = u.prefix "\ 2436 "WHERE i.package = %(pid)s "\ 2437 "AND i.list = %(eid)s "\ 2438 "AND u.uri_base = ? AND i.item_i = ?"\ 2439 ")" % self.__dict__ 2440 self.a.extend([i_u, i_i]) 2441 if ord is not None: 2442 self.w = self.w[:-1] + " AND i.ord = ?)" 2443 self.a.append(ord)
2444
2445 - def add_meta_filter(self, meta):
2446 """ 2447 The meta parameter, is an iterable of triples of the form 2448 (key, value, uriref_flag). If uriref_flag is false, the value is 2449 interpreted as a string; if it is true, the value is interpreted as the 2450 uri-ref of an element. Note that value can also be None. This parameter 2451 is used to filter elements that have the given (key, value) pair in 2452 their metadata (value set to None filtering elements having no value 2453 for the given key). 2454 """ 2455 for k,v,u in meta: 2456 if v is None: 2457 self.w += " AND NOT EXISTS ("\ 2458 "SELECT m.element FROM Meta m "\ 2459 "WHERE m.package = %(pid)s "\ 2460 "AND m.element = %(eid)s "\ 2461 "AND m.key = ?"\ 2462 ")" % self.__dict__ 2463 self.a.append(k) 2464 elif not u: 2465 self.w += " AND EXISTS ("\ 2466 "SELECT m.element FROM Meta m "\ 2467 "WHERE m.package = %(pid)s "\ 2468 "AND m.element = %(eid)s "\ 2469 "AND m.key = ? AND m.value = ?"\ 2470 ")" % self.__dict__ 2471 self.a.extend([k, v]) 2472 else: 2473 v_u, v_i = _split_uri_ref(v) 2474 self.w += " AND EXISTS ("\ 2475 "SELECT m.element FROM Meta m "\ 2476 "JOIN UriBases u ON m.package = u.package "\ 2477 "AND m.value_p = u.prefix "\ 2478 "WHERE m.package = %(pid)s "\ 2479 "AND m.element = %(eid)s "\ 2480 "AND m.key = ? AND u.uri_base = ? "\ 2481 "AND m.value_i = ?"\ 2482 ")" % self.__dict__ 2483 self.a.extend([k, v_u, v_i,])
2484
2485 - def append(self, txt, *args):
2486 """ 2487 Append something at the end of the query. 2488 2489 This should be used with care, and preferably just before call to exe, 2490 since it may break other methods (e.g. appending an ORDER BY 2491 statement). 2492 """ 2493 self.w += txt 2494 self.a.extend(args)
2495
2496 - def wrap_in_count(self):
2497 q,_ = self.exe() 2498 self.s = "SELECT COUNT(*)" 2499 self.f = "FROM (%s)" % q 2500 self.w = "WHERE 1"
2501
2502 - def exe(self):
2503 """ 2504 Return a list of arguments suitable to the ``execute`` methods. 2505 """ 2506 #print "===", self.s, self.f, self.w, self.a 2507 return " ".join((self.s, self.f, self.w)), self.a
2508 2509 # 2510