Merge lp:~thisfred/u1db/home-on-the-range into lp:u1db

Proposed by Eric Casteleijn on 2012-06-04
Status: Merged
Approved by: Eric Casteleijn on 2012-06-12
Approved revision: 332
Merged at revision: 325
Proposed branch: lp:~thisfred/u1db/home-on-the-range
Merge into: lp:u1db
Prerequisite: lp:~thisfred/u1db/c-api-improvements
Diff against target: 532 lines (+371/-13)
8 files modified
include/u1db/u1db.h (+13/-3)
include/u1db/u1db_internal.h (+16/-3)
src/u1db_query.c (+130/-1)
u1db/__init__.py (+25/-6)
u1db/backends/inmemory.py (+35/-0)
u1db/backends/sqlite_backend.py (+50/-0)
u1db/tests/c_backend_wrapper.pyx (+40/-0)
u1db/tests/test_backends.py (+62/-0)
To merge this branch: bzr merge lp:~thisfred/u1db/home-on-the-range
Reviewer Review Type Date Requested Status
John A Meinel (community) 2012-06-04 Approve on 2012-06-12
Review via email: mp+108606@code.launchpad.net

Commit Message

Implemented range queries on all backends

Description of the Change

Implemented range queries on all backends

To post a comment you must log in.
lp:~thisfred/u1db/home-on-the-range updated on 2012-06-04
328. By Eric Casteleijn on 2012-06-04

removed copy pasta

John A Meinel (jameinel) wrote :

Quick thing. Your doc is "n_start_values" but the variable is "n_values"

John
=:->

John A Meinel (jameinel) wrote :
Download full text (19.5 KiB)

You also didn't document the public API in u1db/__init__.py. and the
inmemory one could probably use a couple comments about efficiency. (You
sort the items, but don't bisect to find the start, nor stop early. It
would be significantly more efficient to filter then sort, etc.)

Otherwise seems good to me.
John
=:->
On Jun 4, 2012 8:29 PM, "Eric Casteleijn" <email address hidden>
wrote:

> Eric Casteleijn has proposed merging lp:~thisfred/u1db/home-on-the-range
> into lp:u1db with lp:~thisfred/u1db/c-api-improvements as a prerequisite.
>
> Requested reviews:
> Ubuntu One hackers (ubuntuone-hackers)
> Related bugs:
> Bug #999585 in U1DB: "support range queries"
> https://bugs.launchpad.net/u1db/+bug/999585
>
> For more details, see:
> https://code.launchpad.net/~thisfred/u1db/home-on-the-range/+merge/108606
>
> Implemented range queries on all backends
> --
> https://code.launchpad.net/~thisfred/u1db/home-on-the-range/+merge/108606
> Your team Ubuntu One hackers is requested to review the proposed merge of
> lp:~thisfred/u1db/home-on-the-range into lp:u1db.
>
> === modified file 'include/u1db/u1db.h'
> --- include/u1db/u1db.h 2012-06-04 18:28:18 +0000
> +++ include/u1db/u1db.h 2012-06-04 18:28:18 +0000
> @@ -337,6 +337,20 @@
> u1db_doc_callback cb, int n_values, ...);
>
>
> +
> +/**
> + * Get documents with key values in the specified range
> + *
> + * @param query A u1query object, as created by u1db_query_init.
> + * @param context Will be returned via the document callback
> + * @param n_start_values The number of values.
> + * @param start_values An array of values. If NULL, assume an open ended
> query.
> + * @param end_values An array of values. If NULL, assume an open ended
> query.
> + */
> +int u1db_get_range_from_index(u1database *db, u1query *query,
> + void *context, u1db_doc_callback cb,
> + int n_values, const char **start_values,
> + const char **end_values);
> /**
> * Get keys under which documents are indexed.
> *
>
> === modified file 'include/u1db/u1db_internal.h'
> --- include/u1db/u1db_internal.h 2012-06-04 18:28:18 +0000
> +++ include/u1db/u1db_internal.h 2012-06-04 18:28:18 +0000
> @@ -331,11 +331,10 @@
> /**
> * Format a given query.
> *
> - * @param n_fields The number of fields being passed in, (the number of
> args
> - * in argp)
> + * @param n_fields The number of fields being passed in. (The number of
> values
> + * in values)
> * @param values Array of values being passed.
> * @param buf (OUT) The character array. This will be dynamically
> allocated,
> - * print key_values
> * and callers must free() it.
> * @param wildcard (IN/OUT) Any array indicating a wildcard type for this
> * argument. A 0 indicates this is an exact match, a 1
> @@ -348,6 +347,22 @@
>
>
> /**
> + * Format a given range query.
> + *
> + * @param n_fields The number of fields being passed in. (The number of
> values
> + * in start_values and end_values)
> + * @param start_values Array of values used to define the lower bound of
> the
> + * query.
> + * @param end_value...

lp:~thisfred/u1db/home-on-the-range updated on 2012-06-04
329. By Eric Casteleijn on 2012-06-04

fixed documentation

Eric Casteleijn (thisfred) wrote :

On 06/04/2012 04:28 PM, John A Meinel wrote:
> Quick thing. Your doc is "n_start_values" but the variable is "n_values"

Thanks, fix pushed (I started off with both n_start_values and
n_end_values, before realizing that was unnecessary.)

lp:~thisfred/u1db/home-on-the-range updated on 2012-06-04
330. By Eric Casteleijn on 2012-06-04

added get_range_from_index to API definition, fixed some documentation lag

331. By Eric Casteleijn on 2012-06-04

added docstring to inmemory implementation stating inefficiency.

Eric Casteleijn (thisfred) wrote :

On 06/04/2012 04:38 PM, John A Meinel wrote:
> You also didn't document the public API in u1db/__init__.py. and the
> inmemory one could probably use a couple comments about efficiency. (You
> sort the items, but don't bisect to find the start, nor stop early. It
> would be significantly more efficient to filter then sort, etc.)

Both fixed (that is, I commented, rather than made the inmem
implementation faster, aside from breaking from the loop early, since to
make it fast, we'd need something other than a dictionary anyway, now
that we have ordering.)

--
eric casteleijn
https://launchpad.net/~thisfred

John A Meinel (jameinel) wrote :

Well, you could easily get O(N + M log M) ~ O(N) rather than O(N log N + N) ~ O(N log N) (where N is all keys and M is just the subset). But meh, we can do it if it matters later.

review: Approve
lp:~thisfred/u1db/home-on-the-range updated on 2012-06-12
332. By Eric Casteleijn on 2012-06-12

merged trunk and fixed conflicts

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'include/u1db/u1db.h'
2--- include/u1db/u1db.h 2012-06-12 12:35:05 +0000
3+++ include/u1db/u1db.h 2012-06-12 14:34:24 +0000
4@@ -271,7 +271,6 @@
5 int u1db_create_index_list(u1database *db, const char *index_name,
6 int n_expressions, const char **expressions);
7
8-
9 /**
10 * Delete a defined index.
11 */
12@@ -325,7 +324,6 @@
13 u1db_doc_callback cb, int n_values,
14 const char **values);
15
16-
17 /**
18 * Get documents which match a given index.
19 *
20@@ -337,7 +335,19 @@
21 int u1db_get_from_index(u1database *db, u1query *query, void *context,
22 u1db_doc_callback cb, int n_values, ...);
23
24-
25+/**
26+ * Get documents with key values in the specified range
27+ *
28+ * @param query A u1query object, as created by u1db_query_init.
29+ * @param context Will be returned via the document callback
30+ * @param n_values The number of values.
31+ * @param start_values An array of values. If NULL, assume an open ended query.
32+ * @param end_values An array of values. If NULL, assume an open ended query.
33+ */
34+int u1db_get_range_from_index(u1database *db, u1query *query,
35+ void *context, u1db_doc_callback cb,
36+ int n_values, const char **start_values,
37+ const char **end_values);
38 /**
39 * Get keys under which documents are indexed.
40 *
41
42=== modified file 'include/u1db/u1db_internal.h'
43--- include/u1db/u1db_internal.h 2012-06-12 12:35:05 +0000
44+++ include/u1db/u1db_internal.h 2012-06-12 14:34:24 +0000
45@@ -337,11 +337,10 @@
46 /**
47 * Format a given query.
48 *
49- * @param n_fields The number of fields being passed in, (the number of args
50- * in argp)
51+ * @param n_fields The number of fields being passed in. (The number of values
52+ * in values)
53 * @param values Array of values being passed.
54 * @param buf (OUT) The character array. This will be dynamically allocated,
55- * print key_values
56 * and callers must free() it.
57 * @param wildcard (IN/OUT) Any array indicating a wildcard type for this
58 * argument. A 0 indicates this is an exact match, a 1
59@@ -352,6 +351,20 @@
60 int u1db__format_query(int n_fields, const char **values, char **buf,
61 int *wildcard);
62
63+/**
64+ * Format a given range query.
65+ *
66+ * @param n_fields The number of fields being passed in. (The number of values
67+ * in start_values and end_values)
68+ * @param start_values Array of values used to define the lower bound of the
69+ * query.
70+ * @param end_values Array of values used to define the upper bound of the
71+ * query.
72+ * @param buf (OUT) The character array. This will be dynamically allocated,
73+ * and callers must free() it.
74+ */
75+int u1db__format_range_query(int n_fields, const char **start_values,
76+ const char **end_values, char **buf);
77
78 /**
79 * Given this document content, update the indexed fields in the db.
80
81=== modified file 'src/u1db_query.c'
82--- src/u1db_query.c 2012-06-05 23:00:23 +0000
83+++ src/u1db_query.c 2012-06-12 14:34:24 +0000
84@@ -604,6 +604,71 @@
85
86
87 int
88+u1db_get_range_from_index(u1database *db, u1query *query,
89+ void *context, u1db_doc_callback cb,
90+ int n_values, const char **start_values,
91+ const char **end_values)
92+{
93+ int i, bind_arg, status = U1DB_OK;
94+ char *query_str = NULL;
95+ sqlite3_stmt *statement = NULL;
96+ char *doc_id = NULL;
97+
98+ if (db == NULL || query == NULL || cb == NULL || n_values < 0) {
99+ return U1DB_INVALID_PARAMETER;
100+ }
101+ if (n_values != query->num_fields) {
102+ return U1DB_INVALID_VALUE_FOR_INDEX;
103+ }
104+ status = u1db__format_range_query(
105+ query->num_fields, start_values, end_values, &query_str);
106+ if (status != U1DB_OK) { goto finish; }
107+ status = sqlite3_prepare_v2(db->sql_handle, query_str, -1,
108+ &statement, NULL);
109+ if (status != SQLITE_OK) { goto finish; }
110+ // Bind all of the 'field_name' parameters. sqlite_bind starts at 1
111+ bind_arg = 1;
112+ for (i = 0; i < query->num_fields; ++i) {
113+ status = sqlite3_bind_text(
114+ statement, bind_arg, query->fields[i], -1, SQLITE_TRANSIENT);
115+ if (status != SQLITE_OK) { goto finish; }
116+ bind_arg++;
117+ if (start_values != NULL) {
118+ status = sqlite3_bind_text(
119+ statement, bind_arg, start_values[i], -1, SQLITE_TRANSIENT);
120+ if (status != SQLITE_OK) { goto finish; }
121+ bind_arg++;
122+ }
123+ if (end_values != NULL) {
124+ status = sqlite3_bind_text(
125+ statement, bind_arg, end_values[i], -1, SQLITE_TRANSIENT);
126+ if (status != SQLITE_OK) { goto finish; }
127+ bind_arg++;
128+ }
129+ }
130+ status = sqlite3_step(statement);
131+ while (status == SQLITE_ROW) {
132+ doc_id = (char*)sqlite3_column_text(statement, 0);
133+ // We use u1db_get_docs so we can pass check_for_conflicts=0, which is
134+ // currently expected by the test suite.
135+ status = u1db_get_docs(
136+ db, 1, (const char**)&doc_id, 0, 0, context, cb);
137+ if (status != U1DB_OK) { goto finish; }
138+ status = sqlite3_step(statement);
139+ }
140+ if (status == SQLITE_DONE) {
141+ status = U1DB_OK;
142+ }
143+finish:
144+ sqlite3_finalize(statement);
145+ if (query_str != NULL) {
146+ free(query_str);
147+ }
148+ return status;
149+}
150+
151+
152+int
153 u1db_get_from_index(u1database *db, u1query *query, void *context,
154 u1db_doc_callback cb, int n_values, ...)
155 {
156@@ -704,7 +769,7 @@
157
158 int
159 u1db__format_query(int n_fields, const char **values, char **buf,
160- int *wildcard)
161+ int *wildcard)
162 {
163 int status = U1DB_OK;
164 int buf_size, i;
165@@ -779,6 +844,70 @@
166 return status;
167 }
168
169+int
170+u1db__format_range_query(int n_fields, const char **start_values,
171+ const char **end_values, char **buf)
172+{
173+ int status = U1DB_OK;
174+ int buf_size, i;
175+ char *cur = NULL;
176+ const char *val = NULL;
177+
178+ if (n_fields < 1) {
179+ return U1DB_INVALID_PARAMETER;
180+ }
181+ // 81 for 1 doc, 166 for 2, 251 for 3
182+ buf_size = (1 + n_fields) * 100;
183+ // The first field is treated specially
184+ cur = (char*)calloc(buf_size, 1);
185+ if (cur == NULL) {
186+ return U1DB_NOMEM;
187+ }
188+ *buf = cur;
189+ add_to_buf(&cur, &buf_size, "SELECT d0.doc_id FROM document_fields d0");
190+ for (i = 1; i < n_fields; ++i) {
191+ add_to_buf(&cur, &buf_size, ", document_fields d%d", i);
192+ }
193+ add_to_buf(&cur, &buf_size, " WHERE d0.field_name = ?");
194+ for (i = 0; i < n_fields; ++i) {
195+ if (i != 0) {
196+ add_to_buf(&cur, &buf_size,
197+ " AND d0.doc_id = d%d.doc_id"
198+ " AND d%d.field_name = ?",
199+ i, i);
200+ }
201+ if (start_values != NULL) {
202+ val = start_values[i];
203+ if (val == NULL) {
204+ status = U1DB_INVALID_VALUE_FOR_INDEX;
205+ goto finish;
206+ }
207+ add_to_buf(&cur, &buf_size, " AND d%d.value >= ?", i);
208+ }
209+ if (end_values != NULL) {
210+ val = end_values[i];
211+ if (val == NULL) {
212+ status = U1DB_INVALID_VALUE_FOR_INDEX;
213+ goto finish;
214+ }
215+ add_to_buf(&cur, &buf_size, " AND d%d.value <= ?", i);
216+ }
217+
218+ }
219+ add_to_buf(&cur, &buf_size, " ORDER BY ");
220+ for (i = 0; i < n_fields; ++i) {
221+ if (i != 0) {
222+ add_to_buf(&cur, &buf_size, ", ");
223+ }
224+ add_to_buf(&cur, &buf_size, "d%d.value", i);
225+ }
226+finish:
227+ if (status != U1DB_OK && *buf != NULL) {
228+ free(*buf);
229+ *buf = NULL;
230+ }
231+ return status;
232+}
233
234 struct sqlcb_to_field_cb {
235 void *user_context;
236
237=== modified file 'u1db/__init__.py'
238--- u1db/__init__.py 2012-06-08 07:39:35 +0000
239+++ u1db/__init__.py 2012-06-12 14:34:24 +0000
240@@ -180,22 +180,41 @@
241 def get_from_index(self, index_name, *key_values):
242 """Return documents that match the keys supplied.
243
244- You must supply exactly the same number of values as has been defined
245+ You must supply exactly the same number of values as have been defined
246 in the index. It is possible to do a prefix match by using '*' to
247 indicate a wildcard match. You can only supply '*' to trailing entries,
248- (eg [('val', '*', '*')] is allowed, but [('*', 'val', 'val')] is not.)
249+ (eg 'val', '*', '*' is allowed, but '*', 'val', 'val' is not.)
250 It is also possible to append a '*' to the last supplied value (eg
251- [('val*', '*', '*')] or [('val', 'val*', '*')], but not [('val*',
252- 'val', '*')])
253+ 'val*', '*', '*' or 'val', 'val*', '*', but not 'val*', 'val', '*')
254
255 :return: List of [Document]
256 :param index_name: The index to query
257- :param key_values: tuples of values to match. eg, if you have
258+ :param key_values: values to match. eg, if you have
259 an index with 3 fields then you would have:
260- (x-val1, x-val2, x-val3), (y-val1, y-val2, y-val3), ...
261+ get_from_index(index_name, val1, val2, val3)
262 """
263 raise NotImplementedError(self.get_from_index)
264
265+ def get_range_from_index(self, index_name, start_value, end_value):
266+ """Return documents that fall within the specified range.
267+
268+ For both start_value and end_value, one must supply exactly the same
269+ number of values as have been defined in the index, or pass None. In
270+ case of a single column index, a string is accepted as an alternative
271+ for a tuple with a single value. No wildcards of any kind are
272+ supported.
273+
274+ :return: List of [Document]
275+ :param index_name: The index to query
276+ :param start_values: tuples of values that define the upperbound of the
277+ range. eg, if you have an index with 3 fields then you would have:
278+ (x-val1, x-val2, x-val3), (y-val1, y-val2, y-val3), ...
279+ :param end_values: tuples of values that define the upperbound of the
280+ range. eg, if you have an index with 3 fields then you would have:
281+ (x-val1, x-val2, x-val3), (y-val1, y-val2, y-val3), ...
282+ """
283+ raise NotImplementedError(self.get_range_from_index)
284+
285 def get_index_keys(self, index_name):
286 """Return all keys under which documents are indexed in this index.
287
288
289=== modified file 'u1db/backends/inmemory.py'
290--- u1db/backends/inmemory.py 2012-05-31 18:51:20 +0000
291+++ u1db/backends/inmemory.py 2012-06-12 14:34:24 +0000
292@@ -222,6 +222,24 @@
293 result.append(self._factory(doc_id, doc_rev, doc))
294 return result
295
296+ def get_range_from_index(self, index_name, start_value=None,
297+ end_value=None):
298+ """Return all documents with key values in the specified range."""
299+ try:
300+ index = self._indexes[index_name]
301+ except KeyError:
302+ raise errors.IndexDoesNotExist
303+ if isinstance(start_value, basestring):
304+ start_value = (start_value,)
305+ if isinstance(end_value, basestring):
306+ end_value = (end_value,)
307+ doc_ids = index.lookup_range(start_value, end_value)
308+ result = []
309+ for doc_id in doc_ids:
310+ doc_rev, doc = self._docs[doc_id]
311+ result.append(self._factory(doc_id, doc_rev, doc))
312+ return result
313+
314 def get_index_keys(self, index_name):
315 try:
316 index = self._indexes[index_name]
317@@ -345,6 +363,23 @@
318 else:
319 return self._lookup_prefix(values[:last])
320
321+ def lookup_range(self, start_values, end_values):
322+ """Find docs within the range."""
323+ # TODO: Wildly inefficient, which is unlikely to be a problem for the
324+ # inmemory implementation.
325+ if start_values:
326+ start_values = '\x01'.join(start_values)
327+ if end_values:
328+ end_values = '\x01'.join(end_values)
329+ found = []
330+ for key, doc_ids in sorted(self._values.iteritems()):
331+ if start_values and start_values > key:
332+ continue
333+ if end_values and end_values < key:
334+ break
335+ found.extend(doc_ids)
336+ return found
337+
338 def keys(self):
339 """Find the indexed keys."""
340 return self._values.keys()
341
342=== modified file 'u1db/backends/sqlite_backend.py'
343--- u1db/backends/sqlite_backend.py 2012-05-31 18:51:20 +0000
344+++ u1db/backends/sqlite_backend.py 2012-06-12 14:34:24 +0000
345@@ -638,6 +638,56 @@
346 res = c.fetchall()
347 return [self._factory(r[0], r[1], r[2]) for r in res]
348
349+ def get_range_from_index(self, index_name, start_value=None,
350+ end_value=None):
351+ """Return all documents with key values in the specified range."""
352+ definition = self._get_index_definition(index_name)
353+ tables = ["document_fields d%d" % i for i in range(len(definition))]
354+ novalue_where = [
355+ "d.doc_id = d%d.doc_id AND d%d.field_name = ?" % (i, i) for i in
356+ range(len(definition))]
357+ range_where_lower = [
358+ novalue_where[i] + (" AND d%d.value >= ?" % (i,)) for i in
359+ range(len(definition))]
360+ range_where_upper = [
361+ novalue_where[i] + (" AND d%d.value <= ?" % (i,)) for i in
362+ range(len(definition))]
363+ args = []
364+ where = []
365+ if start_value:
366+ if isinstance(start_value, basestring):
367+ start_value = (start_value,)
368+ if len(start_value) != len(definition):
369+ raise errors.InvalidValueForIndex()
370+ for idx, (field, value) in enumerate(zip(definition, start_value)):
371+ args.append(field)
372+ where.append(range_where_lower[idx])
373+ args.append(value)
374+ if end_value:
375+ if isinstance(end_value, basestring):
376+ end_value = (end_value,)
377+ if len(end_value) != len(definition):
378+ raise errors.InvalidValueForIndex()
379+ for idx, (field, value) in enumerate(zip(definition, end_value)):
380+ args.append(field)
381+ where.append(range_where_upper[idx])
382+ args.append(value)
383+ statement = (
384+ "SELECT d.doc_id, d.doc_rev, d.content FROM document d, %s "
385+ "WHERE %s ORDER BY %s;" % (
386+ ', '.join(tables),
387+ ' AND '.join(where),
388+ ', '.join(
389+ ['d%d.value' % i for i in range(len(definition))])))
390+ c = self._db_handle.cursor()
391+ try:
392+ c.execute(statement, tuple(args))
393+ except dbapi2.OperationalError, e:
394+ raise dbapi2.OperationalError(str(e) +
395+ '\nstatement: %s\nargs: %s\n' % (statement, args))
396+ res = c.fetchall()
397+ return [self._factory(r[0], r[1], r[2]) for r in res]
398+
399 def get_index_keys(self, index):
400 c = self._db_handle.cursor()
401 try:
402
403=== modified file 'u1db/tests/c_backend_wrapper.pyx'
404--- u1db/tests/c_backend_wrapper.pyx 2012-06-12 12:35:05 +0000
405+++ u1db/tests/c_backend_wrapper.pyx 2012-06-12 14:34:24 +0000
406@@ -102,6 +102,10 @@
407 int u1db_get_from_index(u1database *db, u1query *query, void *context,
408 u1db_doc_callback cb, int n_values, char *val0,
409 ...)
410+ int u1db_get_range_from_index(u1database *db, u1query *query,
411+ void *context, u1db_doc_callback cb,
412+ int n_values, const_char_ptr *start_values,
413+ const_char_ptr *end_values)
414 int u1db_delete_index(u1database *db, char *index_name)
415 int u1db_list_indexes(u1database *db, void *context,
416 int (*cb)(void *context, const_char_ptr index_name,
417@@ -1148,6 +1152,42 @@
418 handle_status("get_from_index", status)
419 return res
420
421+ def get_range_from_index(self, index_name, start_value=None,
422+ end_value=None):
423+ cdef CQuery query
424+ cdef const_char_ptr *start_values
425+ cdef int n_values
426+ cdef const_char_ptr *end_values
427+
428+ if start_value is not None:
429+ if isinstance(start_value, basestring):
430+ start_value = (start_value,)
431+ new_objs_1 = _list_to_str_array(
432+ start_value, &start_values, &n_values)
433+ else:
434+ n_values = 0
435+ start_values = NULL
436+ if end_value is not None:
437+ if isinstance(end_value, basestring):
438+ end_value = (end_value,)
439+ new_objs_2 = _list_to_str_array(
440+ end_value, &end_values, &n_values)
441+ else:
442+ end_values = NULL
443+ query = self._query_init(index_name)
444+ res = []
445+ try:
446+ handle_status("get_range_from_index",
447+ u1db_get_range_from_index(
448+ self._db, query._query, <void*>res, _append_doc_to_list,
449+ n_values, start_values, end_values))
450+ finally:
451+ if start_values != NULL:
452+ free(<void*>start_values)
453+ if end_values != NULL:
454+ free(<void*>end_values)
455+ return res
456+
457 def get_index_keys(self, index_name):
458 cdef int status
459 result = []
460
461=== modified file 'u1db/tests/test_backends.py'
462--- u1db/tests/test_backends.py 2012-05-31 18:51:20 +0000
463+++ u1db/tests/test_backends.py 2012-06-12 14:34:24 +0000
464@@ -814,6 +814,68 @@
465 [doc4, doc3, doc2, doc1],
466 self.db.get_from_index('test-idx', 'v*', '*'))
467
468+ def test_get_range_from_index_start_end(self):
469+ doc1 = self.db.create_doc('{"key": "value3"}')
470+ doc2 = self.db.create_doc('{"key": "value2"}')
471+ self.db.create_doc('{"key": "value4"}')
472+ self.db.create_doc('{"key": "value1"}')
473+ self.db.create_index('test-idx', 'key')
474+ self.assertEqual(
475+ [doc2, doc1],
476+ self.db.get_range_from_index('test-idx', 'value2', 'value3'))
477+
478+ def test_get_range_from_index_start(self):
479+ doc1 = self.db.create_doc('{"key": "value3"}')
480+ doc2 = self.db.create_doc('{"key": "value2"}')
481+ doc3 = self.db.create_doc('{"key": "value4"}')
482+ self.db.create_doc('{"key": "value1"}')
483+ self.db.create_index('test-idx', 'key')
484+ self.assertEqual(
485+ [doc2, doc1, doc3],
486+ self.db.get_range_from_index('test-idx', 'value2'))
487+
488+ def test_get_range_from_index_end(self):
489+ self.db.create_doc('{"key": "value3"}')
490+ doc2 = self.db.create_doc('{"key": "value2"}')
491+ self.db.create_doc('{"key": "value4"}')
492+ doc4 = self.db.create_doc('{"key": "value1"}')
493+ self.db.create_index('test-idx', 'key')
494+ self.assertEqual(
495+ [doc4, doc2],
496+ self.db.get_range_from_index('test-idx', None, 'value2'))
497+
498+ def test_get_range_from_index_multi_column_start_end(self):
499+ self.db.create_doc('{"key": "value3", "key2": "value4"}')
500+ doc2 = self.db.create_doc('{"key": "value2", "key2": "value3"}')
501+ doc3 = self.db.create_doc('{"key": "value2", "key2": "value2"}')
502+ self.db.create_doc('{"key": "value1", "key2": "value1"}')
503+ self.db.create_index('test-idx', 'key', 'key2')
504+ self.assertEqual(
505+ [doc3, doc2],
506+ self.db.get_range_from_index(
507+ 'test-idx', ('value2', 'value2'), ('value2', 'value3')))
508+
509+ def test_get_range_from_index_multi_column_start(self):
510+ doc1 = self.db.create_doc('{"key": "value3", "key2": "value4"}')
511+ doc2 = self.db.create_doc('{"key": "value2", "key2": "value3"}')
512+ self.db.create_doc('{"key": "value2", "key2": "value2"}')
513+ self.db.create_doc('{"key": "value1", "key2": "value1"}')
514+ self.db.create_index('test-idx', 'key', 'key2')
515+ self.assertEqual(
516+ [doc2, doc1],
517+ self.db.get_range_from_index('test-idx', ('value2', 'value3')))
518+
519+ def test_get_range_from_index_multi_column_end(self):
520+ self.db.create_doc('{"key": "value3", "key2": "value4"}')
521+ doc2 = self.db.create_doc('{"key": "value2", "key2": "value3"}')
522+ doc3 = self.db.create_doc('{"key": "value2", "key2": "value2"}')
523+ doc4 = self.db.create_doc('{"key": "value1", "key2": "value1"}')
524+ self.db.create_index('test-idx', 'key', 'key2')
525+ self.assertEqual(
526+ [doc4, doc3, doc2],
527+ self.db.get_range_from_index(
528+ 'test-idx', None, ('value2', 'value3')))
529+
530 def test_get_from_index_fails_if_no_index(self):
531 self.assertRaises(
532 errors.IndexDoesNotExist, self.db.get_from_index, 'foo')

Subscribers

People subscribed via source and target branches