Merge lp:~free.ekanayaka/storm/infoheritance into lp:storm

Proposed by Free Ekanayaka
Status: Rejected
Rejected by: Free Ekanayaka
Proposed branch: lp:~free.ekanayaka/storm/infoheritance
Merge into: lp:storm
Diff against target: 575 lines (+402/-18)
6 files modified
NEWS (+45/-0)
storm/references.py (+159/-16)
tests/store/base.py (+187/-2)
tests/store/mysql.py (+4/-0)
tests/store/postgres.py (+3/-0)
tests/store/sqlite.py (+4/-0)
To merge this branch: bzr merge lp:~free.ekanayaka/storm/infoheritance
Reviewer Review Type Date Requested Status
Gustavo Niemeyer Disapprove
Storm Developers Pending
Review via email: mp+80315@code.launchpad.net

Description of the change

This branch adds polymorphism support to References. See the NEWS file and the Reference docstring for an overview.

In a nutshell: if poly_type is passed to Reference.__init__, it will be treated as a column on the local object that holds an integer value identifying the type of the remote object. A Reference instance built with a non-None poly_type keeps a (lazily built) map of those integer values to the specific Relation objects to use for them.

To post a comment you must log in.
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

FWIW, we had something like this in the initial versions of Storm, before the
project was made public.

We actually used this pattern in Landscape itself for some time, and it created
code that is more involved and thus harder to understand, and we were able to
map the same model to a simpler and much cleaner version that was more explicit
without using that kind of trick, and decided to kill the feature because it
clearly encouraged unnecessary complexity.

I'd resist adding something like that, and would instead attempt to simplify
the object model.

I'll abstain from voting it, though, and will relay to others, but I have a
strong feeling you'll regret having invented this after you use it for some
time in real code.

Revision history for this message
Free Ekanayaka (free.ekanayaka) wrote :

Just to clarify, what do you mean by simplifying the object model? Not using the infoheritance pattern at all?

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

I mean making things straightforward, non-fancy, no magic.

Revision history for this message
Free Ekanayaka (free.ekanayaka) wrote :

I still don't understand your view. Specifically, I don't get whether you are suggesting to not use the infoheritance pattern at all by arranging your model objects differently, OR to use it but implement it with the non-fancy no-magic way described in the Storm website in the infohertiance page.

What I think is that if we are fine with the infoheritance pattern, or we don't have really sound alternatives (as it's the case with Landscape atm, since we are still using that pattern), then I believe there's nothing too bad in implementing support for it in Storm, which at the end of the day just move the very same non-fancy no-magic user code/logic into Storm, for convenience and sake of code re-use.

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Yes, I mean making your model match the database content, which is what the "infoheritance" page
demonstrates. The model documented there is the outcome of precisely getting _away_ from internal
support in Storm for what you're adding here, since it was confusing and unnecessarily complex.

You're going backwards in history. Check out old revisions of Landscape and Storm and you'll
notice we've been there, and regretted.

Revision history for this message
Gustavo Niemeyer (niemeyer) :
review: Disapprove
Revision history for this message
Free Ekanayaka (free.ekanayaka) wrote :

Okay, thanks for explaining, I'm heading off for the day but will check the old revisions tomorrow. Not sure if it makes any difference, and maybe it goes without saying, but note that the branch implements exactly the infoheritance approach, where you have a separate "child" class referenced by the parent and holding extra information, and does *not* introduce polymorphism at the class-hierarchy level (the OOP-style), which is for instance the way this implemented in SQLAlchemy. So in practice I'd say it doesn't introduce further abstractions with respect to the infohertiance approach.

Revision history for this message
Thomas Herve (therve) wrote :

I think there may be some value to extract the infoheritance pattern into some piece of code, for reusability/testability. But, I don't think modifying Reference is the way to go, as it's already a fairly complicated class (and poorly understood, at least by me). Maybe using composition would make more sense.

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Free, the beauty of the suggested pattern and the reason we have a page explaining it is precisely
that there's nothing special about it in terms of Storm itself. Storm offers simple building
blocks and it's up to people to put the pattern in place in a way that suits them, possibly with
variations that were not foreseen by us. Hacking that into Storm itself indeed feels like a bad idea.

Revision history for this message
Free Ekanayaka (free.ekanayaka) wrote :

Thanks Gustavo and Thomas for the feedback. I understand your points they make sense to me. Maybe we could for composition like suggested by Thomas, something that doesn't modify the core of Storm at all, but simply plugs to it. It could be even an external package.

For now, I'm withdrawing the MP.

Unmerged revisions

420. By Free Ekanayaka

Add polymorphic support to Reference

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2011-10-17 18:30:27 +0000
3+++ NEWS 2011-10-25 10:34:20 +0000
4@@ -4,6 +4,51 @@
5 Improvements
6 ------------
7
8+- The Reference class now supports polymorphism, by linking to remote objects of
9+ different types and from different tables. Its new "poly_type" constructor
10+ parameter is used to indicate a column on the local object whose value
11+ identifies the type of remote object. This feature can be used to implement
12+ the infoheritance pattern, for example:
13+
14+ class Person(object):
15+
16+ __storm_table__ = "person"
17+ id = Int(allow_none=False, primary=True)
18+ name = Unicode(allow_none=False)
19+ info_type = Int(allow_none=False)
20+ info = Reference(id, "person_id", on_remote=True, poly_type=info_type)
21+
22+ def __init__(self, name, info_class, **kwargs):
23+ self.name = name
24+
25+ class PersonInfo(object):
26+
27+ person_id = Int(allow_none=False, primary=True)
28+ person = Reference(person_id, Person.id)
29+
30+ def __init__(self, person):
31+ self.person = person
32+
33+ @poly_type(1, on=Person.info)
34+ class SecretAgent(PersonInfo):
35+
36+ __storm_table__ = "secret_agent"
37+ passcode = Unicode(allow_none=False)
38+
39+ def __init__(self, person, passcode=None):
40+ super(SecretAgent, self).__init__(person)
41+ self.passcode = passcode
42+
43+ @poly_type(2, on=Person.info)
44+ class Teacher(PersonInfo):
45+
46+ __storm_table__ = "teacher"
47+ school = Unicode(allow_none=False)
48+
49+ def __init__(self, person, school=None):
50+ super(Teacher, self).__init__(person)
51+ self.school = school
52+
53 Bug fixes
54 ---------
55
56
57=== modified file 'storm/references.py'
58--- storm/references.py 2010-06-01 08:33:33 +0000
59+++ storm/references.py 2011-10-25 10:34:20 +0000
60@@ -111,7 +111,7 @@
61 # warnings!).
62 _relation = LazyAttribute("_relation", "_build_relation")
63
64- def __init__(self, local_key, remote_key, on_remote=False):
65+ def __init__(self, local_key, remote_key, on_remote=False, poly_type=None):
66 """
67 Create a Reference property.
68
69@@ -124,10 +124,15 @@
70 @param on_remote: If specified, then the reference is
71 backwards: It is the C{remote_key} which is a foreign key
72 onto C{local_key}.
73+ @param poly_type: If specified, the sibling column that is used
74+ to determine the type of the referred-to object. In this case,
75+ the C{remote_key} parameter must be a C{str} which will resolve
76+ to the matching column of the referred-to type.
77 """
78 self._local_key = local_key
79 self._remote_key = remote_key
80 self._on_remote = on_remote
81+ self._poly_type = poly_type
82 self._cls = None
83
84 def __get__(self, local, cls=None):
85@@ -141,27 +146,34 @@
86 if local is None:
87 return self
88
89- remote = self._relation.get_remote(local)
90+ if isinstance(self._relation, Relation):
91+ relation = self._relation
92+ else:
93+ # This means the reference is polymorfic and we have to select
94+ # the relation according to the poly type
95+ relation = self._select_relation(local)
96+
97+ remote = relation.get_remote(local)
98 if remote is not None:
99 return remote
100
101- if self._relation.local_variables_are_none(local):
102+ if relation.local_variables_are_none(local):
103 return None
104
105 store = Store.of(local)
106 if store is None:
107 return None
108
109- if self._relation.remote_key_is_primary:
110- remote = store.get(self._relation.remote_cls,
111- self._relation.get_local_variables(local))
112+ if relation.remote_key_is_primary:
113+ remote = store.get(relation.remote_cls,
114+ relation.get_local_variables(local))
115 else:
116- where = self._relation.get_where_for_remote(local)
117- result = store.find(self._relation.remote_cls, where)
118+ where = relation.get_where_for_remote(local)
119+ result = store.find(relation.remote_cls, where)
120 remote = result.one()
121
122 if remote is not None:
123- self._relation.link(local, remote)
124+ relation.link(local, remote)
125
126 return remote
127
128@@ -172,33 +184,97 @@
129 if self._cls is None:
130 self._cls = _find_descriptor_class(local.__class__, self)
131
132+ if isinstance(self._relation, Relation):
133+ relation = self._relation
134+ else:
135+ # This means the reference is polymorfic and we have to select
136+ # the relation according to the poly type
137+ relation = self._select_relation(local, remote)
138+
139 if remote is None:
140 if self._on_remote:
141 remote = self.__get__(local)
142 if remote is None:
143 return
144 else:
145- remote = self._relation.get_remote(local)
146+ remote = relation.get_remote(local)
147 if remote is None:
148 remote_info = None
149 else:
150 remote_info = get_obj_info(remote)
151- self._relation.unlink(get_obj_info(local), remote_info, True)
152+ if self._poly_type:
153+ # We also need to reset the poly type
154+ local_info = get_obj_info(local)
155+ local_info.variables[self._poly_column].set(None)
156+ relation.unlink(get_obj_info(local), remote_info, True)
157 else:
158 # Don't use remote here, as it might be
159 # security proxied or something.
160 try:
161 remote = get_obj_info(remote).get_obj()
162 except ClassInfoError:
163- pass # It might fail when remote is a tuple or a raw value.
164- self._relation.link(local, remote, True)
165+ pass # It might fail when remote is a tuple or a raw value.
166+ relation.link(local, remote, True)
167
168 def _build_relation(self):
169 resolver = PropertyResolver(self, self._cls)
170 self._local_key = resolver.resolve(self._local_key)
171- self._remote_key = resolver.resolve(self._remote_key)
172- self._relation = Relation(self._local_key, self._remote_key,
173- False, self._on_remote)
174+ if self._poly_type is None:
175+ self._remote_key = resolver.resolve(self._remote_key)
176+ self._relation = Relation(self._local_key, self._remote_key, False,
177+ self._on_remote)
178+ else:
179+ # The reference is polymorfic, we will build relations lazily,
180+ # saving them in a poly_type -> poly_class mapping
181+ self._relation = {}
182+ if type(self._remote_key) is not tuple:
183+ self._remote_key = tuple((self._remote_key,))
184+ self._poly_column = resolver.resolve_one(self._poly_type)
185+
186+ def _select_relation(self, local, remote=None):
187+ """
188+ Select the correct L{Relation} object for the polymorphic type at hand,
189+ building if it's the first time we hit this type.
190+ """
191+ local_info = get_obj_info(local)
192+ local_poly_type = local_info.variables[self._poly_column].get()
193+ if remote is None:
194+ # We're being called from __get__, as poly type we use the value of
195+ # the given poly_type column on the local object
196+ poly_type = local_poly_type
197+ else:
198+ # We're being called from __set__, as the poly type we use the
199+ # value stored in the remote obect's class by register_poly_type
200+ remote_poly_type = getattr(remote.__class__,
201+ self._poly_column.name)
202+ # Set the poly_type column of the local object to the poly
203+ # type value found in the remote object's class
204+ local_info.variables[self._poly_column].set(remote_poly_type)
205+ poly_type = remote_poly_type
206+
207+ relation = self._relation.get(poly_type)
208+
209+ if relation is None:
210+ # The relation for the given poly type is not in the map, build
211+ # it now
212+ if remote is None:
213+ remote_class = self._poly_lookup[poly_type]
214+ else:
215+ remote_class = remote.__class__
216+
217+ if hasattr(remote_class, "__storm_table__"):
218+ # The remote object is a Storm object, so build a regular
219+ # relation
220+ resolver = PropertyResolver(self, self._cls)
221+ remote_key = resolver.resolve(
222+ tuple(getattr(remote_class, k) for k in self._remote_key))
223+ relation = Relation(self._local_key, remote_key, False,
224+ self._on_remote)
225+ else:
226+ relation = RemoteFactory(remote_class, self._poly_column)
227+
228+ self._relation[poly_type] = relation
229+ return relation
230
231 def __eq__(self, other):
232 return self._relation.get_where_for_local(other)
233@@ -892,6 +968,26 @@
234 return self._r_to_l.setdefault(local_cls, map).get(remote_column)
235
236
237+class RemoteFactory(object):
238+ """Factory for in-memory remote object references."""
239+
240+ def __init__(self, remote_class, poly_column):
241+ self._remote_class = remote_class
242+ self._local_attr = "__storm_poly_remote_%s_" % poly_column.name
243+
244+ def get_remote(self, local):
245+ remote = getattr(local, self._local_attr, None)
246+ if remote is None:
247+ remote = self._remote_class.__new__(self._remote_class)
248+ self.link(local, remote, False)
249+ return remote
250+
251+ def link(self, local, remote, setting):
252+ setattr(local, self._local_attr, remote)
253+ if not setting and getattr(remote, "link", None) is not None:
254+ remote.link(local)
255+
256+
257 class PropertyResolver(object):
258 """Transform strings and pure properties (non-columns) into columns."""
259
260@@ -943,3 +1039,50 @@
261 if _descr is descr:
262 return getattr(cls, attr)
263 raise RuntimeError("Reference used in an unknown class")
264+
265+
266+def register_poly_type(cls, type, on):
267+ """Register a new type for a polymorfic reference.
268+
269+ @param cls: The class of the new type to register.
270+ @param type: A unique identifier for the class, usually an integer value.
271+ @param on: The polymorfic L{Reference} to associated the type with.
272+ """
273+ if getattr(on, "_poly_lookup", None) is None:
274+ on._poly_lookup = {}
275+ if type in on._poly_lookup:
276+ raise RuntimeError("poly type already registered")
277+ on._poly_lookup[type] = cls
278+ for attr, value in on._cls.__dict__.iteritems():
279+ if value is on._poly_type:
280+ setattr(cls, attr, type)
281+ break
282+ else:
283+ raise RuntimeError("parent attr not found")
284+
285+
286+def unregister_poly_type(cls, on):
287+ """Unregister a type of a polymorphic reference."""
288+ for attr, value in on._cls.__dict__.iteritems():
289+ if value is on._poly_type:
290+ del on._poly_lookup[getattr(cls, attr)]
291+ break
292+ else:
293+ raise RuntimeError("parent attr not found")
294+
295+
296+def unregister_poly_types(on):
297+ """Unregister all types on the given polymorfic reference."""
298+ on._poly_lookup.clear()
299+
300+
301+def poly_type(type, on):
302+ """
303+ Decorator used to register a class as a type for a polymorfic reference.
304+ """
305+
306+ def register(cls):
307+ register_poly_type(cls, type, on)
308+ return cls
309+
310+ return register
311
312=== modified file 'tests/store/base.py'
313--- tests/store/base.py 2011-08-01 12:54:36 +0000
314+++ tests/store/base.py 2011-10-25 10:34:20 +0000
315@@ -27,7 +27,9 @@
316 from uuid import uuid4
317 import weakref
318
319-from storm.references import Reference, ReferenceSet, Proxy
320+from storm.references import (
321+ Reference, ReferenceSet, Proxy, poly_type, register_poly_type,
322+ unregister_poly_type)
323 from storm.database import Result, STATE_DISCONNECTED
324 from storm.properties import (
325 Int, Float, RawStr, Unicode, Property, Pickle, UUID)
326@@ -125,6 +127,32 @@
327 foo = Reference(foo_id, Foo.id)
328 foo_title = Proxy(foo, Foo.title)
329
330+
331+class Egg(object):
332+ __storm_table__ = "egg"
333+ id = Int(primary=True, default=AutoReload)
334+ info_type = Int()
335+ info = Reference(id, "egg_id", on_remote=True, poly_type=info_type)
336+
337+ def __init__(self, info_class=None):
338+ if info_class is not None:
339+ self.info = info_class()
340+
341+@poly_type(1, on=Egg.info)
342+class StoredEggInfo(object):
343+ __storm_table__ = "stored_egg_info"
344+ egg_id = Int(primary=True)
345+ egg = Reference(egg_id, Egg.id)
346+
347+@poly_type(2, on=Egg.info)
348+class MemoryEggInfo(object):
349+ def __init__(self):
350+ self.created = True
351+
352+ def link(self, egg):
353+ self.created = False
354+ self.egg = egg
355+
356 class Money(object):
357 __storm_table__ = "money"
358 id = Int(primary=True)
359@@ -267,7 +295,7 @@
360
361 def drop_tables(self):
362 for table in ["foo", "bar", "bin", "link", "money", "selfref",
363- "foovalue", "unique_id"]:
364+ "foovalue", "unique_id", "egg", "stored_egg_info"]:
365 try:
366 self.connection.execute("DROP TABLE %s" % table)
367 self.connection.commit()
368@@ -5756,6 +5784,163 @@
369 self.assertEquals(bar.foo_id, 20)
370 self.assertEquals(link.foo_id, 20)
371
372+ def test_poly_register(self):
373+ """
374+ It's possible to register new polymorphic types using the C{poly_type}
375+ decorator.
376+ """
377+ self.assertEqual(1, StoredEggInfo.info_type)
378+ self.assertEqual(2, MemoryEggInfo.info_type)
379+
380+ def test_poly_register_with_duplicate_type(self):
381+ """
382+ An error is raised when trying to register the same info type twice
383+ for the same Reference.
384+ """
385+
386+ class OtherEggInfo(object):
387+ pass
388+
389+ self.assertRaises(RuntimeError, register_poly_type, OtherEggInfo,
390+ MemoryEggInfo.info_type, Egg.info)
391+
392+ def test_poly_unregister(self):
393+ """
394+ It's possible to unregister a poly type.
395+ """
396+
397+ class OtherEggInfo(object):
398+ pass
399+
400+ register_poly_type(OtherEggInfo, 3, Egg.info)
401+ self.assertEqual(3, OtherEggInfo.info_type)
402+ self.assertIs(OtherEggInfo, Egg.info._poly_lookup[3])
403+ unregister_poly_type(OtherEggInfo, Egg.info)
404+ self.assertIs(None, Egg.info._poly_lookup.get(3))
405+
406+
407+ def test_poly_in_memory_init(self):
408+ """
409+ In-memory remote objects are cached while the local object is alive,
410+ but when they're recreated, their C{__init__} should not be called
411+ again.
412+ """
413+ egg = Egg()
414+ egg.info = MemoryEggInfo()
415+ self.store.add(egg)
416+ self.store.flush()
417+ egg_id = egg.id
418+ self.assertTrue(egg.info.created)
419+ self.store.invalidate(egg)
420+ del egg
421+ gc.collect()
422+ egg = self.store.get(Egg, egg_id)
423+ self.assertFalse(egg.info.created)
424+ self.assertIs(egg, egg.info.egg)
425+
426+ def test_poly_wb_relations_are_built_lazily(self):
427+ """
428+ The relations for the registered poly types are built lazily.
429+ """
430+ Egg.info._relation.clear()
431+ egg = Egg()
432+ egg.info = StoredEggInfo()
433+ self.assertEqual([1], Egg.info._relation.keys())
434+ egg.info = MemoryEggInfo()
435+ self.assertEqual(sorted([1, 2]), sorted(Egg.info._relation.keys()))
436+
437+ def test_poly_with_type_override(self):
438+ """
439+ When the local and remote poly type values don't match, the local one
440+ is overriden when setting the reference.
441+ """
442+ egg = Egg()
443+ egg.info_type = 9999
444+ egg.info = StoredEggInfo()
445+ self.assertEqual(StoredEggInfo.info_type, egg.info_type)
446+
447+ def test_poly_create_in_memory_object(self):
448+ """
449+ When setting the local reference to a new in-memory remote object, the
450+ C{link} method of the remote object is not invoked.
451+ """
452+ egg = Egg(MemoryEggInfo)
453+ self.store.add(egg)
454+ self.store.flush()
455+ self.assertEqual(MemoryEggInfo.info_type, egg.info_type)
456+ self.assertTrue(egg.info.created)
457+
458+ def test_poly_link_in_memory_remote(self):
459+ """
460+ If the remote object is an in memory object, its C{link} method is
461+ invoked when it's linked to a Storm object loaded from the store.
462+ """
463+ egg = Egg()
464+ egg.info_type = MemoryEggInfo.info_type
465+ self.store.add(egg)
466+ self.store.flush()
467+ self.assertFalse(egg.info.created)
468+ self.assertIs(egg, egg.info.egg)
469+
470+ def test_poly_create_stored_remote(self):
471+ """
472+ When a setting the local reference to a remote object, the remote
473+ key is set to the local key.
474+ """
475+ egg = Egg()
476+ egg_info = StoredEggInfo()
477+ egg.info = egg_info
478+ self.store.add(egg)
479+ self.store.flush()
480+ self.assertEqual(StoredEggInfo.info_type, egg.info_type)
481+ self.assertEqual(egg.id, egg_info.egg_id)
482+ self.assertIs(egg, egg_info.egg)
483+ self.assertEqual(egg.id, egg_info.egg.id)
484+
485+ def test_poly_link_stored_remote(self):
486+ """
487+ When the remote object is a Storm object, the local key value is used
488+ to find the matching remote key.
489+ """
490+ egg = Egg()
491+ egg.id = 1234
492+ egg.info_type = StoredEggInfo.info_type
493+ egg_info1 = StoredEggInfo()
494+ egg_info1.egg_id = egg.id
495+ egg_info2 = StoredEggInfo()
496+ egg_info2.egg_id = 9999
497+ self.store.add(egg)
498+ self.store.add(egg_info1)
499+ self.store.add(egg_info2)
500+ self.store.flush()
501+ self.assertIs(egg_info1, egg.info)
502+
503+ def test_poly_link_with_change_type(self):
504+ """
505+ When changing the type of the linked remote object, the poly type
506+ column on the local object gets changed accordingly.
507+ """
508+ egg = Egg(StoredEggInfo)
509+ self.assertEqual(StoredEggInfo.info_type, egg.info_type)
510+
511+ egg.info = MemoryEggInfo()
512+ self.assertEqual(MemoryEggInfo.info_type, egg.info_type)
513+
514+ def test_poly_with_unlink_stored_remote(self):
515+ """
516+ If the reference is linked to a remote object, when setting it to
517+ C{None} the poly type on the local object gets reset too.
518+ """
519+ egg = Egg(StoredEggInfo)
520+ egg.id = 1234
521+ egg_info = egg.info
522+ self.assertEqual(StoredEggInfo.info_type, egg.info_type)
523+ self.assertEqual(1234, egg_info.egg_id)
524+
525+ egg.info = None
526+ self.assertEqual(None, egg.info_type)
527+ self.assertEqual(None, egg_info.egg_id)
528+
529 def test_get_decimal_property(self):
530 money = self.store.get(Money, 10)
531 self.assertEquals(money.value, decimal.Decimal("12.3455"))
532
533=== modified file 'tests/store/mysql.py'
534--- tests/store/mysql.py 2011-02-14 12:17:54 +0000
535+++ tests/store/mysql.py 2011-10-25 10:34:20 +0000
536@@ -79,6 +79,10 @@
537 connection.execute("CREATE TABLE unique_id "
538 "(id VARCHAR(36) PRIMARY KEY) "
539 "ENGINE=InnoDB")
540+ connection.execute("CREATE TABLE egg "
541+ "(id INT PRIMARY KEY AUTO_INCREMENT,"
542+ " info_type INT)")
543+ connection.execute("CREATE TABLE stored_egg_info (egg_id INT)")
544 connection.commit()
545
546
547
548=== modified file 'tests/store/postgres.py'
549--- tests/store/postgres.py 2011-02-14 12:17:54 +0000
550+++ tests/store/postgres.py 2011-10-25 10:34:20 +0000
551@@ -93,6 +93,9 @@
552 " value1 INTEGER, value2 INTEGER)")
553 connection.execute("CREATE TABLE unique_id "
554 "(id UUID PRIMARY KEY)")
555+ connection.execute("CREATE TABLE egg "
556+ "(id SERIAL PRIMARY KEY, info_type INTEGER)")
557+ connection.execute("CREATE TABLE stored_egg_info (egg_id INTEGER)")
558 connection.commit()
559
560 def drop_tables(self):
561
562=== modified file 'tests/store/sqlite.py'
563--- tests/store/sqlite.py 2011-02-14 12:17:54 +0000
564+++ tests/store/sqlite.py 2011-10-25 10:34:20 +0000
565@@ -65,6 +65,10 @@
566 " value1 INTEGER, value2 INTEGER)")
567 connection.execute("CREATE TABLE unique_id "
568 "(id VARCHAR PRIMARY KEY)")
569+ connection.execute("CREATE TABLE egg "
570+ "(id INTEGER PRIMARY KEY, info_type INTEGER)")
571+ connection.execute("CREATE TABLE stored_egg_info "
572+ "(egg_id INTEGER)")
573 connection.commit()
574
575 def drop_tables(self):

Subscribers

People subscribed via source and target branches

to status/vote changes: