Merge lp:~jameinel/u1db/uuid_databases into lp:u1db

Proposed by John A Meinel
Status: Merged
Merged at revision: 135
Proposed branch: lp:~jameinel/u1db/uuid_databases
Merge into: lp:u1db
Diff against target: 183 lines (+68/-11) (has conflicts)
4 files modified
u1db/backends/sqlite_backend.py (+19/-5)
u1db/commandline/client.py (+7/-1)
u1db/tests/commandline/test_client.py (+17/-2)
u1db/tests/test_sqlite_backend.py (+25/-3)
Text conflict in u1db/commandline/client.py
To merge this branch: bzr merge lp:~jameinel/u1db/uuid_databases
Reviewer Review Type Date Requested Status
Samuele Pedroni Approve
Review via email: mp+83945@code.launchpad.net

Description of the change

This changes SQLiteDatabase so that it defaults to creating a unique replica_uid when it initializes the database.

Some comments:

1) We never set a uniqueness constraint on the u1db_config table, so we could call _set_replica_uid as much as we wanted, but it wouldn't actually change it, since it just added more data to the db.

2) I went with: uuid.uuid4().hex. Which is 32 bytes long (vs the default 36 bytes with the extra '-' chars).
This should make it reasonably short, since it will end up in all the doc revs, etc.
We've talked about including something like hostname, to help in debugging issues. For now, this was simple and works, we can extend it later, I think.

3) We use 'index_storage' as a flag to indicate the db has been set up, move it to after _extra_schema_init and _set_replica_uid.

To post a comment you must log in.
Revision history for this message
Samuele Pedroni (pedronis) wrote :

from my understarding while with sqlite3

with conn:
   A
   with conn:
     B
   C

is permitted, it doesn't do what one would expect, it will result in a commit after B and one after C (or more depending on the isolation level)

this means _initialize shouldn't call a helper with a with conn: of its own (_set_replica_uid), so things need to be factored a bit differently

review: Needs Fixing
Revision history for this message
Samuele Pedroni (pedronis) wrote :

with conn just emits a commit or rollback on __exit__. BEGINĀ is emitted by sqlite3 in a lazy, opportunistic way.

Revision history for this message
Samuele Pedroni (pedronis) wrote :

this kind of test shows the problem, a similar test but hooking on _extra_schema_init passes on trunk:

=== modified file 'u1db/tests/test_sqlite_backend.py'
--- u1db/tests/test_sqlite_backend.py 2011-11-30 14:05:19 +0000
+++ u1db/tests/test_sqlite_backend.py 2011-11-30 17:09:40 +0000
@@ -214,6 +214,25 @@
                           (doc1.doc_id, "sub.doc", "underneath"),
                          ], c.fetchall())

+ def test__ensure_schema_rollback(self):
+ temp_dir = self.createTempDir(prefix='u1db-test-')
+ path = temp_dir + '/rollback.db'
+ class SQLitePartialExpandDbTesting(
+ sqlite_backend.SQLitePartialExpandDatabase):
+ def _set_replica_uid(self, uid):
+ super(SQLitePartialExpandDbTesting, self)._set_replica_uid(uid)
+ if fail:
+ raise Exception()
+ db = SQLitePartialExpandDbTesting.__new__(SQLitePartialExpandDbTesting)
+ db._db_handle = dbapi2.connect(path) # db is there but not yet init-ed
+ fail = True
+ self.assertRaises(Exception, db._ensure_schema)
+ fail = False
+ db._ensure_schema()
+ x, err = db._which_index_storage(db._db_handle.cursor())
+ self.assertIsNotNone(x)
+ self.assertIsNone(err)
+
     def test__open_database(self):
         temp_dir = self.createTempDir(prefix='u1db-test-')
         path = temp_dir + '/test.sqlite'

Revision history for this message
Samuele Pedroni (pedronis) wrote :

this variant is simpler, has a clearer failure mode:

+ def test__ensure_schema_rollback(self):
+ temp_dir = self.createTempDir(prefix='u1db-test-')
+ path = temp_dir + '/rollback.db'
+ class SQLitePartialExpandDbTesting(
+ sqlite_backend.SQLitePartialExpandDatabase):
+ def _set_replica_uid(self, uid):
+ super(SQLitePartialExpandDbTesting, self)._set_replica_uid(uid)
+ if fail:
+ raise Exception()
+ db = SQLitePartialExpandDbTesting.__new__(SQLitePartialExpandDbTesting)
+ db._db_handle = dbapi2.connect(path) # db is there but not yet init-ed
+ fail = True
+ self.assertRaises(Exception, db._ensure_schema)
+ fail = False
+ db._initialize(db._db_handle.cursor())
+

lp:~jameinel/u1db/uuid_databases updated
133. By John A Meinel

Bring in Samuele's test case and a fix for the transaction isolation logic.

Revision history for this message
John A Meinel (jameinel) wrote :

Updated to use a helper function, and now includes your test case (modified to override the new helper function that is being called.)

Revision history for this message
Samuele Pedroni (pedronis) wrote :

+1, looks good, notice that it seems there will be a conflict when merging with trunk

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'u1db/backends/sqlite_backend.py'
2--- u1db/backends/sqlite_backend.py 2011-11-30 13:09:21 +0000
3+++ u1db/backends/sqlite_backend.py 2011-12-01 14:02:25 +0000
4@@ -18,6 +18,7 @@
5 import simplejson
6 from sqlite3 import dbapi2
7 import time
8+import uuid
9
10 from u1db.backends import CommonBackend, CommonSyncTarget
11 from u1db import Document, errors
12@@ -158,11 +159,19 @@
13 " field TEXT,"
14 " CONSTRAINT index_definitions_pkey"
15 " PRIMARY KEY (name, offset))")
16- c.execute("CREATE TABLE u1db_config (name TEXT, value TEXT)")
17+ c.execute("CREATE TABLE u1db_config ("
18+ " name TEXT PRIMARY KEY,"
19+ " value TEXT)")
20 c.execute("INSERT INTO u1db_config VALUES ('sql_schema', '0')")
21+ self._extra_schema_init(c)
22+ # A unique identifier should be set for this replica. Implementations
23+ # don't have to strictly use uuid here, but we do want the uid to be
24+ # unique amongst all databases that will sync with each other.
25+ # We might extend this to using something with hostname for easier
26+ # debugging.
27+ self._set_replica_uid_in_transaction(uuid.uuid4().hex)
28 c.execute("INSERT INTO u1db_config VALUES" " ('index_storage', ?)",
29 (self._index_storage_value,))
30- self._extra_schema_init(c)
31
32 def _ensure_schema(self):
33 """Ensure that the database schema has been created."""
34@@ -188,9 +197,14 @@
35 def _set_replica_uid(self, replica_uid):
36 """Force the replica_uid to be set."""
37 with self._db_handle:
38- c = self._db_handle.cursor()
39- c.execute("INSERT INTO u1db_config VALUES ('replica_uid', ?)",
40- (replica_uid,))
41+ self._set_replica_uid_in_transaction(replica_uid)
42+
43+ def _set_replica_uid_in_transaction(self, replica_uid):
44+ """Set the replica_uid. A transaction should already be held."""
45+ c = self._db_handle.cursor()
46+ c.execute("INSERT OR REPLACE INTO u1db_config"
47+ " VALUES ('replica_uid', ?)",
48+ (replica_uid,))
49 self._real_replica_uid = replica_uid
50
51 def _get_replica_uid(self):
52
53=== modified file 'u1db/commandline/client.py'
54--- u1db/commandline/client.py 2011-11-30 13:09:38 +0000
55+++ u1db/commandline/client.py 2011-12-01 14:02:25 +0000
56@@ -92,12 +92,18 @@
57 @classmethod
58 def _populate_subparser(cls, parser):
59 parser.add_argument('database', help='The database to create')
60- parser.add_argument('replica_uid',
61+ parser.add_argument('--replica-uid', default=None,
62 help='The unique identifier for this database')
63
64 def run(self, database, replica_uid):
65+<<<<<<< TREE
66 db = u1db_open(database, create=True)
67 db._set_replica_uid(replica_uid)
68+=======
69+ db = sqlite_backend.SQLiteDatabase.open_database(database)
70+ if replica_uid is not None:
71+ db._set_replica_uid(replica_uid)
72+>>>>>>> MERGE-SOURCE
73
74 client_commands.register(CmdInitDB)
75
76
77=== modified file 'u1db/tests/commandline/test_client.py'
78--- u1db/tests/commandline/test_client.py 2011-11-23 12:39:27 +0000
79+++ u1db/tests/commandline/test_client.py 2011-12-01 14:02:25 +0000
80@@ -73,11 +73,18 @@
81 self.assertEqual(sys.stdout, args.outfile)
82
83 def test_init_db(self):
84- args = self.parse_args(['init-db', 'test.db', 'replica-uid'])
85+ args = self.parse_args(
86+ ['init-db', 'test.db', '--replica-uid=replica-uid'])
87 self.assertEqual(client.CmdInitDB, args.subcommand)
88 self.assertEqual('test.db', args.database)
89 self.assertEqual('replica-uid', args.replica_uid)
90
91+ def test_init_db_no_replica(self):
92+ args = self.parse_args(['init-db', 'test.db'])
93+ self.assertEqual(client.CmdInitDB, args.subcommand)
94+ self.assertEqual('test.db', args.database)
95+ self.assertIs(None, args.replica_uid)
96+
97 def test_put(self):
98 args = self.parse_args(['put', 'test.db', 'doc-id', 'old-doc-rev'])
99 self.assertEqual(client.CmdPut, args.subcommand)
100@@ -149,6 +156,14 @@
101 db = sqlite_backend.SQLiteDatabase.open_database(path, create=False)
102 self.assertEqual('test-uid', db._replica_uid)
103
104+ def test_init_no_uid(self):
105+ path = self.working_dir + '/test2.db'
106+ cmd = self.make_command(client.CmdInitDB)
107+ cmd.run(path, None)
108+ self.assertTrue(os.path.exists(path))
109+ db = sqlite_backend.SQLiteDatabase.open_database(path, create=False)
110+ self.assertIsNot(None, db._replica_uid)
111+
112
113 class TestCmdPut(TestCaseWithDB):
114
115@@ -261,7 +276,7 @@
116
117 def test_init_db(self):
118 path = self.working_dir + '/test2.db'
119- ret, stdout, stderr = self.run_main(['init-db', path, 'uid'])
120+ ret, stdout, stderr = self.run_main(['init-db', path])
121 db2 = sqlite_backend.SQLiteDatabase.open_database(path, create=False)
122
123 def test_put(self):
124
125=== modified file 'u1db/tests/test_sqlite_backend.py'
126--- u1db/tests/test_sqlite_backend.py 2011-11-30 14:19:48 +0000
127+++ u1db/tests/test_sqlite_backend.py 2011-12-01 14:02:25 +0000
128@@ -87,6 +87,12 @@
129 raw_db = self.db._get_sqlite_handle()
130 self.assertNotEqual(None, raw_db)
131
132+ def test_default_replica_uid(self):
133+ self.db = sqlite_backend.SQLitePartialExpandDatabase(':memory:')
134+ self.assertIsNot(None, self.db._replica_uid)
135+ self.assertEqual(32, len(self.db._replica_uid))
136+ val = int(self.db._replica_uid, 16)
137+
138 def test__close_sqlite_handle(self):
139 raw_db = self.db._get_sqlite_handle()
140 self.db._close_sqlite_handle()
141@@ -112,8 +118,8 @@
142 def test__set_replica_uid(self):
143 # Start from scratch, so that replica_uid isn't set.
144 self.db = sqlite_backend.SQLitePartialExpandDatabase(':memory:')
145- self.assertEqual(None, self.db._real_replica_uid)
146- self.assertEqual(None, self.db._replica_uid)
147+ self.assertIsNot(None, self.db._real_replica_uid)
148+ self.assertIsNot(None, self.db._replica_uid)
149 self.db._set_replica_uid('foo')
150 c = self.db._get_sqlite_handle().cursor()
151 c.execute("SELECT value FROM u1db_config WHERE name='replica_uid'")
152@@ -124,7 +130,6 @@
153 self.assertEqual('foo', self.db._replica_uid)
154
155 def test__get_generation(self):
156- self.db._set_replica_uid('foo')
157 self.assertEqual(0, self.db._get_generation())
158
159 def test__allocate_doc_id(self):
160@@ -209,6 +214,23 @@
161 (doc1.doc_id, "sub.doc", "underneath"),
162 ], c.fetchall())
163
164+ def test__ensure_schema_rollback(self):
165+ temp_dir = self.createTempDir(prefix='u1db-test-')
166+ path = temp_dir + '/rollback.db'
167+ class SQLitePartialExpandDbTesting(
168+ sqlite_backend.SQLitePartialExpandDatabase):
169+ def _set_replica_uid_in_transaction(self, uid):
170+ super(SQLitePartialExpandDbTesting,
171+ self)._set_replica_uid_in_transaction(uid)
172+ if fail:
173+ raise Exception()
174+ db = SQLitePartialExpandDbTesting.__new__(SQLitePartialExpandDbTesting)
175+ db._db_handle = dbapi2.connect(path) # db is there but not yet init-ed
176+ fail = True
177+ self.assertRaises(Exception, db._ensure_schema)
178+ fail = False
179+ db._initialize(db._db_handle.cursor())
180+
181 def test__open_database(self):
182 temp_dir = self.createTempDir(prefix='u1db-test-')
183 path = temp_dir + '/test.sqlite'

Subscribers

People subscribed via source and target branches