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
13
14
15
16
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
39
40
41
42
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
57
59 """Set the module's debug flaf. See _get_module_debug`."""
60 global _DF
61 _DF = not b
62
63
64
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
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
92 if path == ":memory:":
93 return True
94
95
96 if not exists(path):
97
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
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
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
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
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
206 if path == ":memory:" or not exists(path):
207 return ClaimFailure(NoSuchPackage(url))
208
209
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
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
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
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
296 c = cx.execute("SELECT id FROM Packages WHERE id = ?", (pkgid,))
297 for i in c:
298 return True
299 return False
300
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
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
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
372
373
374
376 """Return the backend-URL the given package is bound to."""
377 return "sqlite:%s%s" % (pathname2url(self._path), package_id)
378
380
381
382 q = "SELECT url FROM Packages WHERE id = ?"
383 return self._curs.execute(q, (package_id,)).fetchone()[0]
384
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
394 q = "SELECT uri FROM Packages WHERE id = ?"
395 return self._curs.execute(q, (package_id,)).fetchone()[0]
396
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)
414 if m is not None:
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)
430 if m is not None:
431 execute = self._curs.execute
432 args = [package_id,]
433
434
435
436
437
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
462
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)
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)
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
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)
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)
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
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)
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
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
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)
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
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
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
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
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
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
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
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
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
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
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
1097
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
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
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
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
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
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
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
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 ):
1317
1318
1319
1329
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)
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
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
1369
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
1384
1385
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
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
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
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
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
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
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
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
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:
1545 execute("ROLLBACK")
1546 raise InternalError("could not update", e)
1547 except:
1548 execute("ROLLBACK")
1549 raise
1550 execute("COMMIT")
1551
1552
1553
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
1565
1566
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
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)
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
1708
1726
1747
1795
1828
1829
1830
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)
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
1859
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
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)
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
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
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
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
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)
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
1991
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
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)
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
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
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
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
2096
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)
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)
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
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)
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)
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
2160
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
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
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
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
2239
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
2245
2246
2247 self._bound = WeakValueDictWithCallback(self._check_unused)
2248
2249
2250
2251 self._iterators = WeakKeyDictionary()
2252
2253
2254
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
2269 conn = self._conn
2270 if conn is not None and len(self._bound) == 0:
2271
2272
2273 try:
2274 self._curs.execute("UPDATE Packages SET url = ?", ("",))
2275 finally:
2276 conn.close()
2277 self._conn = None
2278 self._curs = None
2279
2280
2281
2282 self._bound = None
2283
2284 if self._path in _cache: del _cache[self._path]
2285
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
2297 """Perform controls and insertions common to all elements.
2298
2299 NB: This starts a transaction that must be commited by caller.
2300 """
2301
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
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__",]
2337 self._cursor = cursor
2338 backend._iterators[self] = True
2346
2347
2348
2349
2350
2351
2352
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
2369 self.w += " AND %(pid)s = ?" % self.__dict__
2370 self.a.append(pid)
2371
2373 self.w += " AND %(pid)s IN (%%s)" % self.__dict__
2374 self.w %= ",".join( "?" for i in pids )
2375 self.a.extend(pids)
2376
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
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
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
2414
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
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
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
2497 q,_ = self.exe()
2498 self.s = "SELECT COUNT(*)"
2499 self.f = "FROM (%s)" % q
2500 self.w = "WHERE 1"
2501
2503 """
2504 Return a list of arguments suitable to the ``execute`` methods.
2505 """
2506
2507 return " ".join((self.s, self.f, self.w)), self.a
2508
2509
2510