Merge lp:~pedronis/u1db/open-vs-ensure into lp:u1db

Proposed by Samuele Pedroni
Status: Superseded
Proposed branch: lp:~pedronis/u1db/open-vs-ensure
Merge into: lp:u1db
Diff against target: 324 lines (+139/-28)
7 files modified
u1db/backends/sqlite_backend.py (+40/-10)
u1db/commandline/client.py (+6/-5)
u1db/errors.py (+4/-0)
u1db/remote/server_state.py (+7/-0)
u1db/tests/commandline/test_client.py (+5/-5)
u1db/tests/test_server_state.py (+11/-0)
u1db/tests/test_sqlite_backend.py (+66/-8)
To merge this branch: bzr merge lp:~pedronis/u1db/open-vs-ensure
Reviewer Review Type Date Requested Status
Ubuntu One hackers Pending
Review via email: mp+83134@code.launchpad.net

Description of the change

Try to in down the workings/semantics of open_database:

- fail with DatabasesDoesNotExist if the databases file is not there
- try to be robust in case the database is being opened while another process is setting it up

add an ensure_database to create a database, expose it on server state (for use by later the http app)

To post a comment you must log in.
lp:~pedronis/u1db/open-vs-ensure updated
126. By Samuele Pedroni

fold SQLiteDatabase.ensure_database into open_database(create=True) which is also the default

127. By Samuele Pedroni

use open_database(create=True) in test_client

Unmerged revisions

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-22 15:08:58 +0000
3+++ u1db/backends/sqlite_backend.py 2011-11-23 12:42:23 +0000
4@@ -14,8 +14,10 @@
5
6 """A U1DB implementation that uses SQLite as its persistence layer."""
7
8+import os
9 import simplejson
10 from sqlite3 import dbapi2
11+import time
12
13 from u1db.backends import CommonBackend, CommonSyncTarget
14 from u1db import Document, errors
15@@ -35,18 +37,46 @@
16 def get_sync_target(self):
17 return SQLiteSyncTarget(self)
18
19- @staticmethod
20- def open_database(sqlite_file):
21- # TODO: We really want a way to indicate that the database must already
22- # exist.
23+ @classmethod
24+ def _which_index_storage(cls, c):
25+ try:
26+ c.execute("SELECT value FROM u1db_config"
27+ " WHERE name = 'index_storage'")
28+ except dbapi2.OperationalError, e:
29+ # The table does not exist yet
30+ return None, e
31+ else:
32+ return c.fetchone()[0], None
33+
34+ @classmethod
35+ def _open_database(cls, sqlite_file):
36+ if not os.path.isfile(sqlite_file):
37+ raise errors.DatabaseDoesNotExist()
38+ tries = 2
39 db_handle = dbapi2.connect(sqlite_file)
40 c = db_handle.cursor()
41- c.execute("SELECT value FROM u1db_config WHERE name = 'index_storage'")
42- v = c.fetchone()
43- # if v is None:
44- # raise ValueError('No defined index_storage for database %s'
45- # % (sqlite_file,))
46- return SQLiteDatabase._sqlite_registry[v[0]](sqlite_file)
47+ while tries:
48+ v, err = cls._which_index_storage(c)
49+ if v is not None:
50+ break
51+ # possibly another process is initializing it, wait for it to be
52+ # done
53+ tries -= 1
54+ time.sleep(0.5)
55+ else:
56+ raise err # go for the richest error?
57+ return SQLiteDatabase._sqlite_registry[v](sqlite_file)
58+
59+ @classmethod
60+ def open_database(cls, sqlite_file, backend_cls=None, create=True):
61+ try:
62+ return cls._open_database(sqlite_file)
63+ except errors.DatabaseDoesNotExist:
64+ if not create:
65+ raise
66+ if backend_cls is None:
67+ backend_cls = SQLitePartialExpandDatabase # default
68+ return backend_cls(sqlite_file)
69
70 @staticmethod
71 def register_implementation(klass):
72
73=== modified file 'u1db/commandline/client.py'
74--- u1db/commandline/client.py 2011-11-21 14:31:33 +0000
75+++ u1db/commandline/client.py 2011-11-23 12:42:23 +0000
76@@ -48,7 +48,7 @@
77 def run(self, database, infile, doc_id):
78 if infile is None:
79 infile = self.stdin
80- db = sqlite_backend.SQLiteDatabase.open_database(database)
81+ db = sqlite_backend.SQLiteDatabase.open_database(database, create=False)
82 doc = db.create_doc(infile.read(), doc_id=doc_id)
83 self.stderr.write('id: %s\nrev: %s\n' % (doc.doc_id, doc.rev))
84
85@@ -71,7 +71,7 @@
86 def run(self, database, doc_id, outfile):
87 if outfile is None:
88 outfile = self.stdout
89- db = sqlite_backend.SQLiteDatabase.open_database(database)
90+ db = sqlite_backend.SQLiteDatabase.open_database(database, create=False)
91 doc = db.get_doc(doc_id)
92 outfile.write(doc.content)
93 self.stderr.write('rev: %s\n' % (doc.rev,))
94@@ -95,7 +95,7 @@
95 help='The unique identifier for this database')
96
97 def run(self, database, replica_uid):
98- db = sqlite_backend.SQLitePartialExpandDatabase(database)
99+ db = sqlite_backend.SQLiteDatabase.open_database(database)
100 db._set_replica_uid(replica_uid)
101
102 client_commands.register(CmdInitDB)
103@@ -119,7 +119,7 @@
104 def run(self, database, doc_id, doc_rev, infile):
105 if infile is None:
106 infile = self.stdin
107- db = sqlite_backend.SQLiteDatabase.open_database(database)
108+ db = sqlite_backend.SQLiteDatabase.open_database(database, create=False)
109 doc = Document(doc_id, doc_rev, infile.read())
110 doc_rev = db.put_doc(doc)
111 self.stderr.write('rev: %s\n' % (doc_rev,))
112@@ -147,7 +147,8 @@
113
114 def run(self, source, target):
115 """Start a Sync request."""
116- source_db = sqlite_backend.SQLiteDatabase.open_database(source)
117+ source_db = sqlite_backend.SQLiteDatabase.open_database(source,
118+ create=False)
119 st = self._open_target(target)
120 syncer = sync.Synchronizer(source_db, st)
121 syncer.sync()
122
123=== modified file 'u1db/errors.py'
124--- u1db/errors.py 2011-11-23 09:32:06 +0000
125+++ u1db/errors.py 2011-11-23 12:42:23 +0000
126@@ -37,3 +37,7 @@
127 Can also be raised if wildcard matches are not strictly at the tail of the
128 request.
129 """
130+
131+
132+class DatabaseDoesNotExist(U1DBError):
133+ """The database does not exist."""
134
135=== modified file 'u1db/remote/server_state.py'
136--- u1db/remote/server_state.py 2011-11-22 11:09:00 +0000
137+++ u1db/remote/server_state.py 2011-11-23 12:42:23 +0000
138@@ -42,4 +42,11 @@
139 """Open a database at the given location."""
140 from u1db.backends import sqlite_backend
141 full_path = self._relpath(path)
142+ return sqlite_backend.SQLiteDatabase.open_database(full_path,
143+ create=False)
144+
145+ def ensure_database(self, path):
146+ """Ensure database at the given location."""
147+ from u1db.backends import sqlite_backend
148+ full_path = self._relpath(path)
149 return sqlite_backend.SQLiteDatabase.open_database(full_path)
150
151=== modified file 'u1db/tests/commandline/test_client.py'
152--- u1db/tests/commandline/test_client.py 2011-11-23 09:34:59 +0000
153+++ u1db/tests/commandline/test_client.py 2011-11-23 12:42:23 +0000
154@@ -99,7 +99,7 @@
155 super(TestCaseWithDB, self).setUp()
156 self.working_dir = self.createTempDir()
157 self.db_path = self.working_dir + '/test.db'
158- self.db = sqlite_backend.SQLitePartialExpandDatabase(self.db_path)
159+ self.db = sqlite_backend.SQLiteDatabase.open_database(self.db_path)
160 self.db._set_replica_uid('test')
161 self.addCleanup(self.db.close)
162
163@@ -146,7 +146,7 @@
164 cmd = self.make_command(client.CmdInitDB)
165 cmd.run(path, 'test-uid')
166 self.assertTrue(os.path.exists(path))
167- db = sqlite_backend.SQLiteDatabase.open_database(path)
168+ db = sqlite_backend.SQLiteDatabase.open_database(path, create=False)
169 self.assertEqual('test-uid', db._replica_uid)
170
171
172@@ -174,7 +174,7 @@
173 def setUp(self):
174 super(TestCmdSync, self).setUp()
175 self.db2_path = self.working_dir + '/test2.db'
176- self.db2 = sqlite_backend.SQLitePartialExpandDatabase(self.db2_path)
177+ self.db2 = sqlite_backend.SQLiteDatabase.open_database(self.db2_path)
178 self.addCleanup(self.db2.close)
179 self.db2._set_replica_uid('test2')
180 self.doc = self.db.create_doc(tests.simple_doc, doc_id='test-id')
181@@ -262,7 +262,7 @@
182 def test_init_db(self):
183 path = self.working_dir + '/test2.db'
184 ret, stdout, stderr = self.run_main(['init-db', path, 'uid'])
185- db2 = sqlite_backend.SQLiteDatabase.open_database(path)
186+ db2 = sqlite_backend.SQLiteDatabase.open_database(path, create=False)
187
188 def test_put(self):
189 doc = self.db.create_doc(tests.simple_doc, doc_id='test-id')
190@@ -279,7 +279,7 @@
191 def test_sync(self):
192 doc = self.db.create_doc(tests.simple_doc, doc_id='test-id')
193 self.db2_path = self.working_dir + '/test2.db'
194- self.db2 = sqlite_backend.SQLitePartialExpandDatabase(self.db2_path)
195+ self.db2 = sqlite_backend.SQLiteDatabase.open_database(self.db2_path)
196 self.addCleanup(self.db2.close)
197 ret, stdout, stderr = self.run_main(
198 ['sync', self.db_path, self.db2_path])
199
200=== modified file 'u1db/tests/test_server_state.py'
201--- u1db/tests/test_server_state.py 2011-11-23 09:31:16 +0000
202+++ u1db/tests/test_server_state.py 2011-11-23 12:42:23 +0000
203@@ -46,3 +46,14 @@
204 sqlite_backend.SQLitePartialExpandDatabase(path)
205 db = self.state.open_database('test.db')
206 self.assertIsInstance(db, sqlite_backend.SQLitePartialExpandDatabase)
207+
208+ def test_ensure_database(self):
209+ tempdir = self.createTempDir()
210+ self.state.set_workingdir(tempdir)
211+ path = tempdir + '/test.db'
212+ self.assertFalse(os.path.exists(path))
213+ db = self.state.ensure_database('test.db')
214+ self.assertIsInstance(db, sqlite_backend.SQLitePartialExpandDatabase)
215+ self.assertTrue(os.path.exists(path))
216+ db2 = self.state.open_database('test.db')
217+ self.assertIsInstance(db2, sqlite_backend.SQLitePartialExpandDatabase)
218
219=== modified file 'u1db/tests/test_sqlite_backend.py'
220--- u1db/tests/test_sqlite_backend.py 2011-11-23 09:34:59 +0000
221+++ u1db/tests/test_sqlite_backend.py 2011-11-23 12:42:23 +0000
222@@ -15,14 +15,13 @@
223 """Test sqlite backend internals."""
224
225 import os
226-import tempfile
227 import time
228 import threading
229-import shutil
230
231 from sqlite3 import dbapi2
232
233 from u1db import (
234+ errors,
235 tests,
236 )
237 from u1db.backends import sqlite_backend
238@@ -72,7 +71,7 @@
239 db1 = SQLiteDatabaseTesting(dbname, 1)
240 t2.join()
241
242- self.assertTrue(isinstance(outcome2[0], SQLiteDatabaseTesting))
243+ self.assertIsInstance(outcome2[0], SQLiteDatabaseTesting)
244 db2 = outcome2[0]
245 self.assertTrue(db2._is_initialized(db1._get_sqlite_handle().cursor()))
246
247@@ -210,13 +209,72 @@
248 (doc1.doc_id, "sub.doc", "underneath"),
249 ], c.fetchall())
250
251- def test_open_database(self):
252- temp_dir = tempfile.mkdtemp(prefix='u1db-test-')
253- self.addCleanup(shutil.rmtree, temp_dir)
254+ def test__open_database(self):
255+ temp_dir = self.createTempDir(prefix='u1db-test-')
256 path = temp_dir + '/test.sqlite'
257 db = sqlite_backend.SQLitePartialExpandDatabase(path)
258- db2 = sqlite_backend.SQLiteDatabase.open_database(path)
259- self.assertIsInstance(db2, sqlite_backend.SQLitePartialExpandDatabase)
260+ db2 = sqlite_backend.SQLiteDatabase._open_database(path)
261+ self.assertIsInstance(db2, sqlite_backend.SQLitePartialExpandDatabase)
262+
263+ def test__open_database_non_existent(self):
264+ temp_dir = self.createTempDir(prefix='u1db-test-')
265+ path = temp_dir + '/non-existent.sqlite'
266+ self.assertRaises(errors.DatabaseDoesNotExist,
267+ sqlite_backend.SQLiteDatabase._open_database, path)
268+
269+ def test__open_database_during_init(self):
270+ temp_dir = self.createTempDir(prefix='u1db-test-')
271+ path = temp_dir + '/initialised.db'
272+ db = sqlite_backend.SQLitePartialExpandDatabase.__new__(
273+ sqlite_backend.SQLitePartialExpandDatabase)
274+ db._db_handle = dbapi2.connect(path) # db is there but not yet init-ed
275+ observed = []
276+ class SQLiteDatabaseTesting(sqlite_backend.SQLiteDatabase):
277+ @classmethod
278+ def _which_index_storage(cls, c):
279+ res = super(SQLiteDatabaseTesting, cls)._which_index_storage(c)
280+ db._ensure_schema() # init db
281+ observed.append(res[0])
282+ return res
283+ db2 = SQLiteDatabaseTesting._open_database(path)
284+ self.assertIsInstance(db2, sqlite_backend.SQLitePartialExpandDatabase)
285+ self.assertEqual([None,
286+ sqlite_backend.SQLitePartialExpandDatabase._index_storage_value],
287+ observed)
288+
289+ def test__open_database_invalid(self):
290+ temp_dir = self.createTempDir(prefix='u1db-test-')
291+ path1 = temp_dir + '/invalid1.db'
292+ with open(path1, 'wb') as f:
293+ f.write("")
294+ self.assertRaises(dbapi2.OperationalError,
295+ sqlite_backend.SQLiteDatabase._open_database, path1)
296+ path2 = temp_dir + '/invalid2.db'
297+ with open(path1, 'wb') as f:
298+ f.write("invalid")
299+ self.assertRaises(dbapi2.DatabaseError,
300+ sqlite_backend.SQLiteDatabase._open_database, path1)
301+
302+ def test_open_database_existing(self):
303+ temp_dir = self.createTempDir(prefix='u1db-test-')
304+ path = temp_dir + '/existing.sqlite'
305+ db = sqlite_backend.SQLitePartialExpandDatabase(path)
306+ db2 = sqlite_backend.SQLiteDatabase.open_database(path, create=False)
307+ self.assertIsInstance(db2, sqlite_backend.SQLitePartialExpandDatabase)
308+
309+ def test_open_database_create(self):
310+ temp_dir = self.createTempDir(prefix='u1db-test-')
311+ path = temp_dir + '/new.sqlite'
312+ db = sqlite_backend.SQLiteDatabase.open_database(path)
313+ db2 = sqlite_backend.SQLiteDatabase.open_database(path, create=False)
314+ self.assertIsInstance(db2, sqlite_backend.SQLitePartialExpandDatabase)
315+
316+ def test_open_database_non_existent(self):
317+ temp_dir = self.createTempDir(prefix='u1db-test-')
318+ path = temp_dir + '/non-existent.sqlite'
319+ self.assertRaises(errors.DatabaseDoesNotExist,
320+ sqlite_backend.SQLiteDatabase.open_database, path,
321+ create=False)
322
323 def assertTransform(self, sql_value, value):
324 transformed = sqlite_backend.SQLiteDatabase._transform_glob(value)

Subscribers

People subscribed via source and target branches