Merge lp:~thisfred/u1db/and-the-wild-rocky-mountains-to-roam into lp:u1db

Proposed by Eric Casteleijn
Status: Merged
Approved by: Eric Casteleijn
Approved revision: 330
Merged at revision: 326
Proposed branch: lp:~thisfred/u1db/and-the-wild-rocky-mountains-to-roam
Merge into: lp:u1db
Prerequisite: lp:~thisfred/u1db/home-on-the-range
Diff against target: 638 lines (+315/-46)
6 files modified
include/u1db/u1db_internal.h (+2/-1)
src/u1db_query.c (+130/-23)
u1db/__init__.py (+15/-11)
u1db/backends/inmemory.py (+18/-5)
u1db/backends/sqlite_backend.py (+52/-6)
u1db/tests/test_backends.py (+98/-0)
To merge this branch: bzr merge lp:~thisfred/u1db/and-the-wild-rocky-mountains-to-roam
Reviewer Review Type Date Requested Status
Samuele Pedroni Approve
Review via email: mp+108999@code.launchpad.net

This proposal supersedes a proposal from 2012-06-06.

Commit message

Made range queries work with wildcards in exactly the same way that regular queries do.

Description of the change

Made range queries work with wildcards in exactly the same way that regular queries do.

To post a comment you must log in.
327. By Eric Casteleijn

more out of memory checks. clearer variable names.

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

looks good,

I find the constant 0,1,2 used in start_wildcard[i] and end_wildcard[i] a bit cryptic, can they get names or something,

a bit worried about the size of/repetitions in get_from_index/get_range_from_index, suppose the bug about extracting a _format_query will possibly address that.

review: Approve
328. By Eric Casteleijn

added constants for glob cases

Revision history for this message
Eric Casteleijn (thisfred) wrote :

> looks good,
>
> I find the constant 0,1,2 used in start_wildcard[i] and end_wildcard[i] a bit
> cryptic, can they get names or something,

Done.

> a bit worried about the size of/repetitions in
> get_from_index/get_range_from_index, suppose the bug about extracting a
> _format_query will possibly address that.

That is my plan.

329. By Eric Casteleijn

merged trunk, resolved conflicts

330. By Eric Casteleijn

more conflicts resolved

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'include/u1db/u1db_internal.h'
2--- include/u1db/u1db_internal.h 2012-06-12 14:28:50 +0000
3+++ include/u1db/u1db_internal.h 2012-06-12 15:20:23 +0000
4@@ -364,7 +364,8 @@
5 * and callers must free() it.
6 */
7 int u1db__format_range_query(int n_fields, const char **start_values,
8- const char **end_values, char **buf);
9+ const char **end_values, char **buf,
10+ int *start_wildcard, int *end_wildcard);
11
12 /**
13 * Given this document content, update the indexed fields in the db.
14
15=== modified file 'src/u1db_query.c'
16--- src/u1db_query.c 2012-06-12 14:28:50 +0000
17+++ src/u1db_query.c 2012-06-12 15:20:23 +0000
18@@ -23,6 +23,10 @@
19 #include <ctype.h>
20 #include <json/json.h>
21
22+#define NO_GLOB 0
23+#define IS_GLOB 1
24+#define ENDS_IN_GLOB 2
25+
26 #define OPS 4
27 #define MAX_INT_STR_LEN 21
28 #ifndef max
29@@ -93,6 +97,8 @@
30 if (new_item == NULL)
31 return U1DB_NOMEM;
32 new_item->data = strdup(data);
33+ if (new_item->data == NULL)
34+ return U1DB_NOMEM;
35 if (list->head == NULL)
36 {
37 list->head = new_item;
38@@ -381,6 +387,8 @@
39 for (item = values->head; item != NULL; item = item->next)
40 {
41 intermediate = strdup(item->data);
42+ if (intermediate == NULL)
43+ return U1DB_NOMEM;
44 intermediate_ptr = intermediate;
45 while (intermediate_ptr != NULL) {
46 space_chr = strchr(intermediate_ptr, ' ');
47@@ -459,6 +467,10 @@
48 goto finish;
49 }
50 query->fields[offset] = strdup(field);
51+ if (query->fields[offset] == NULL) {
52+ status = U1DB_NOMEM;
53+ goto finish;
54+ }
55 status = sqlite3_step(statement);
56 }
57 if (status == SQLITE_DONE) {
58@@ -533,7 +545,6 @@
59 return status;
60 }
61
62-
63 int
64 u1db_get_from_index_list(u1database *db, u1query *query, void *context,
65 u1db_doc_callback cb, int n_values,
66@@ -569,12 +580,12 @@
67 SQLITE_TRANSIENT);
68 bind_arg++;
69 if (status != SQLITE_OK) { goto finish; }
70- if (wildcard[i] == 0) {
71+ if (wildcard[i] == NO_GLOB) {
72 // Not a wildcard, so add the argument
73 status = sqlite3_bind_text(statement, bind_arg, values[i], -1,
74 SQLITE_TRANSIENT);
75 bind_arg++;
76- } else if (wildcard[i] == 2) {
77+ } else if (wildcard[i] == ENDS_IN_GLOB) {
78 status = sqlite3_bind_text(statement, bind_arg, values[i], -1,
79 SQLITE_TRANSIENT);
80 bind_arg++;
81@@ -602,7 +613,6 @@
82 return status;
83 }
84
85-
86 int
87 u1db_get_range_from_index(u1database *db, u1query *query,
88 void *context, u1db_doc_callback cb,
89@@ -613,6 +623,9 @@
90 char *query_str = NULL;
91 sqlite3_stmt *statement = NULL;
92 char *doc_id = NULL;
93+ char *stripped = NULL;
94+ int start_wildcard[20] = {0};
95+ int end_wildcard[20] = {0};
96
97 if (db == NULL || query == NULL || cb == NULL || n_values < 0) {
98 return U1DB_INVALID_PARAMETER;
99@@ -621,7 +634,8 @@
100 return U1DB_INVALID_VALUE_FOR_INDEX;
101 }
102 status = u1db__format_range_query(
103- query->num_fields, start_values, end_values, &query_str);
104+ query->num_fields, start_values, end_values, &query_str,
105+ start_wildcard, end_wildcard);
106 if (status != U1DB_OK) { goto finish; }
107 status = sqlite3_prepare_v2(db->sql_handle, query_str, -1,
108 &statement, NULL);
109@@ -634,16 +648,49 @@
110 if (status != SQLITE_OK) { goto finish; }
111 bind_arg++;
112 if (start_values != NULL) {
113- status = sqlite3_bind_text(
114- statement, bind_arg, start_values[i], -1, SQLITE_TRANSIENT);
115- if (status != SQLITE_OK) { goto finish; }
116- bind_arg++;
117+ if (start_wildcard[i] == NO_GLOB) {
118+ status = sqlite3_bind_text(
119+ statement, bind_arg, start_values[i], -1,
120+ SQLITE_TRANSIENT);
121+ bind_arg++;
122+ } else if (start_wildcard[i] == ENDS_IN_GLOB) {
123+ if (stripped != NULL)
124+ free(stripped);
125+ stripped = strdup(start_values[i]);
126+ if (stripped == NULL) {
127+ status = U1DB_NOMEM;
128+ goto finish;
129+ }
130+ stripped[strlen(stripped) - 1] = '\0';
131+ status = sqlite3_bind_text(
132+ statement, bind_arg, stripped, -1, SQLITE_TRANSIENT);
133+ bind_arg++;
134+ }
135+ if (status != SQLITE_OK) { goto finish; }
136 }
137 if (end_values != NULL) {
138- status = sqlite3_bind_text(
139- statement, bind_arg, end_values[i], -1, SQLITE_TRANSIENT);
140+ if (end_wildcard[i] == NO_GLOB) {
141+ status = sqlite3_bind_text(
142+ statement, bind_arg, end_values[i], -1,
143+ SQLITE_TRANSIENT);
144+ bind_arg++;
145+ } else if (end_wildcard[i] == ENDS_IN_GLOB) {
146+ if (stripped != NULL)
147+ free(stripped);
148+ stripped = strdup(end_values[i]);
149+ if (stripped == NULL) {
150+ status = U1DB_NOMEM;
151+ goto finish;
152+ }
153+ stripped[strlen(stripped) - 1] = '\0';
154+ status = sqlite3_bind_text(
155+ statement, bind_arg, stripped, -1, SQLITE_TRANSIENT);
156+ bind_arg++;
157+ status = sqlite3_bind_text(
158+ statement, bind_arg, end_values[i], -1, SQLITE_TRANSIENT);
159+ bind_arg++;
160+ }
161 if (status != SQLITE_OK) { goto finish; }
162- bind_arg++;
163 }
164 }
165 status = sqlite3_step(statement);
166@@ -664,6 +711,8 @@
167 if (query_str != NULL) {
168 free(query_str);
169 }
170+ if (stripped != NULL)
171+ free(stripped);
172 return status;
173 }
174
175@@ -806,12 +855,12 @@
176 goto finish;
177 }
178 if (val[0] == '*') {
179- wildcard[i] = 1;
180+ wildcard[i] = IS_GLOB;
181 have_wildcard = 1;
182 add_to_buf(&cur, &buf_size, " AND d%d.value NOT NULL", i);
183 } else if (val[0] != '\0' && val[strlen(val)-1] == '*') {
184 // glob
185- wildcard[i] = 2;
186+ wildcard[i] = ENDS_IN_GLOB;
187 if (have_wildcard) {
188 //globs not allowed after another wildcard
189 status = U1DB_INVALID_GLOBBING;
190@@ -820,7 +869,7 @@
191 have_wildcard = 1;
192 add_to_buf(&cur, &buf_size, " AND d%d.value GLOB ?", i);
193 } else {
194- wildcard[i] = 0;
195+ wildcard[i] = NO_GLOB;
196 if (have_wildcard) {
197 // Can't have a non-wildcard after a wildcard
198 status = U1DB_INVALID_GLOBBING;
199@@ -846,12 +895,15 @@
200
201 int
202 u1db__format_range_query(int n_fields, const char **start_values,
203- const char **end_values, char **buf)
204+ const char **end_values, char **buf,
205+ int *start_wildcard, int *end_wildcard)
206 {
207 int status = U1DB_OK;
208 int buf_size, i;
209 char *cur = NULL;
210 const char *val = NULL;
211+ int have_start_wildcard = 0;
212+ int have_end_wildcard = 0;
213
214 if (n_fields < 1) {
215 return U1DB_INVALID_PARAMETER;
216@@ -882,7 +934,29 @@
217 status = U1DB_INVALID_VALUE_FOR_INDEX;
218 goto finish;
219 }
220- add_to_buf(&cur, &buf_size, " AND d%d.value >= ?", i);
221+ if (val[0] == '*') {
222+ start_wildcard[i] = IS_GLOB;
223+ have_start_wildcard= 1;
224+ add_to_buf(&cur, &buf_size, " and d%d.value not null", i);
225+ } else if (val[0] != '\0' && val[strlen(val)-1] == '*') {
226+ // glob
227+ start_wildcard[i] = ENDS_IN_GLOB;
228+ if (have_start_wildcard) {
229+ //globs not allowed after another wildcard
230+ status = U1DB_INVALID_GLOBBING;
231+ goto finish;
232+ }
233+ have_start_wildcard = 1;
234+ add_to_buf(&cur, &buf_size, " and d%d.value >= ?", i);
235+ } else {
236+ start_wildcard[i] = NO_GLOB;
237+ if (have_start_wildcard) {
238+ // can't have a non-wildcard after a wildcard
239+ status = U1DB_INVALID_GLOBBING;
240+ goto finish;
241+ }
242+ add_to_buf(&cur, &buf_size, " and d%d.value >= ?", i);
243+ }
244 }
245 if (end_values != NULL) {
246 val = end_values[i];
247@@ -890,7 +964,31 @@
248 status = U1DB_INVALID_VALUE_FOR_INDEX;
249 goto finish;
250 }
251- add_to_buf(&cur, &buf_size, " AND d%d.value <= ?", i);
252+ if (val[0] == '*') {
253+ end_wildcard[i] = IS_GLOB;
254+ have_end_wildcard = 1;
255+ add_to_buf(&cur, &buf_size, " AND d%d.value NOT NULL", i);
256+ } else if (val[0] != '\0' && val[strlen(val)-1] == '*') {
257+ // glob
258+ end_wildcard[i] = ENDS_IN_GLOB;
259+ if (have_end_wildcard) {
260+ //globs not allowed after another wildcard
261+ status = U1DB_INVALID_GLOBBING;
262+ goto finish;
263+ }
264+ have_end_wildcard = 1;
265+ add_to_buf(
266+ &cur, &buf_size,
267+ " AND (d%d.value < ? OR d%d.value GLOB ?)", i, i);
268+ } else {
269+ end_wildcard[i] = NO_GLOB;
270+ if (have_end_wildcard) {
271+ // Can't have a non-wildcard after a wildcard
272+ status = U1DB_INVALID_GLOBBING;
273+ goto finish;
274+ }
275+ add_to_buf(&cur, &buf_size, " AND d%d.value <= ?", i);
276+ }
277 }
278
279 }
280@@ -919,12 +1017,16 @@
281 {
282 transformation *inner = NULL;
283 char *new_field = NULL, *new_ptr, *argptr, *argend;
284- char *word, *first_comma;
285+ char *word = NULL, *first_comma;
286 int status = U1DB_OK;
287 int i, size;
288 int new_value_type = json_type_string;
289 char *field_copy, *end = NULL;
290 field_copy = strdup(field);
291+ if (field_copy == NULL) {
292+ status = U1DB_NOMEM;
293+ goto finish;
294+ }
295 end = field_copy;
296 while (*end != '(' && *end != ')' && *end != '\0')
297 {
298@@ -940,12 +1042,15 @@
299 }
300 }
301 else {
302- // TODO: unicode fieldnames ?
303 size = (end - field_copy);
304 word = (char *)calloc(size + 1, 1);
305 strncpy(word, field_copy, size);
306 }
307 new_field = strdup(end);
308+ if (new_field == NULL) {
309+ status = U1DB_NOMEM;
310+ goto finish;
311+ }
312 new_ptr = new_field;
313 if (status != U1DB_OK)
314 goto finish;
315@@ -1035,10 +1140,12 @@
316 result->value_type = value_type;
317 }
318 finish:
319- free(word);
320- free(field_copy);
321+ if (word != NULL)
322+ free(word);
323+ if (field_copy != NULL)
324+ free(field_copy);
325 if (new_field != NULL)
326- free(new_field);
327+ free(new_field);
328 return status;
329 }
330
331
332=== modified file 'u1db/__init__.py'
333--- u1db/__init__.py 2012-06-12 14:28:50 +0000
334+++ u1db/__init__.py 2012-06-12 15:20:23 +0000
335@@ -198,20 +198,24 @@
336 def get_range_from_index(self, index_name, start_value, end_value):
337 """Return documents that fall within the specified range.
338
339- For both start_value and end_value, one must supply exactly the same
340- number of values as have been defined in the index, or pass None. In
341- case of a single column index, a string is accepted as an alternative
342- for a tuple with a single value. No wildcards of any kind are
343- supported.
344+ Both ends of the range are inclusive. For both start_value and
345+ end_value, one must supply exactly the same number of values as have
346+ been defined in the index, or pass None. In case of a single column
347+ index, a string is accepted as an alternative for a tuple with a single
348+ value. It is possible to do a prefix match by using '*' to indicate
349+ a wildcard match. You can only supply '*' to trailing entries, (eg
350+ 'val', '*', '*' is allowed, but '*', 'val', 'val' is not.) It is also
351+ possible to append a '*' to the last supplied value (eg 'val*', '*',
352+ '*' or 'val', 'val*', '*', but not 'val*', 'val', '*')
353
354 :return: List of [Document]
355 :param index_name: The index to query
356- :param start_values: tuples of values that define the upperbound of the
357- range. eg, if you have an index with 3 fields then you would have:
358- (x-val1, x-val2, x-val3), (y-val1, y-val2, y-val3), ...
359- :param end_values: tuples of values that define the upperbound of the
360- range. eg, if you have an index with 3 fields then you would have:
361- (x-val1, x-val2, x-val3), (y-val1, y-val2, y-val3), ...
362+ :param start_values: tuples of values that define the lower bound of
363+ the range. eg, if you have an index with 3 fields then you would
364+ have: (val1, val2, val3)
365+ :param end_values: tuples of values that define the upper bound of the
366+ range. eg, if you have an index with 3 fields then you would have:
367+ (val1, val2, val3)
368 """
369 raise NotImplementedError(self.get_range_from_index)
370
371
372=== modified file 'u1db/backends/inmemory.py'
373--- u1db/backends/inmemory.py 2012-06-04 21:01:26 +0000
374+++ u1db/backends/inmemory.py 2012-06-12 15:20:23 +0000
375@@ -27,6 +27,11 @@
376 from u1db.backends import CommonBackend, CommonSyncTarget
377
378
379+def get_prefix(value):
380+ key_prefix = '\x01'.join(value)
381+ return key_prefix.rstrip('*')
382+
383+
384 class InMemoryDatabase(CommonBackend):
385 """A database that only stores the data internally."""
386
387@@ -368,15 +373,24 @@
388 # TODO: Wildly inefficient, which is unlikely to be a problem for the
389 # inmemory implementation.
390 if start_values:
391- start_values = '\x01'.join(start_values)
392+ self._find_non_wildcards(start_values)
393+ start_values = get_prefix(start_values)
394 if end_values:
395- end_values = '\x01'.join(end_values)
396+ if self._find_non_wildcards(end_values) == -1:
397+ exact = True
398+ else:
399+ exact = False
400+ end_values = get_prefix(end_values)
401 found = []
402 for key, doc_ids in sorted(self._values.iteritems()):
403 if start_values and start_values > key:
404 continue
405 if end_values and end_values < key:
406- break
407+ if exact:
408+ break
409+ else:
410+ if not key.startswith(end_values):
411+ break
412 found.extend(doc_ids)
413 return found
414
415@@ -388,8 +402,7 @@
416 """Find docs that match the prefix string in values."""
417 # TODO: We need a different data structure to make prefix style fast,
418 # some sort of sorted list would work, but a plain dict doesn't.
419- key_prefix = '\x01'.join(value)
420- key_prefix = key_prefix.rstrip('*')
421+ key_prefix = get_prefix(value)
422 all_doc_ids = []
423 for key, doc_ids in sorted(self._values.iteritems()):
424 if key.startswith(key_prefix):
425
426=== modified file 'u1db/backends/sqlite_backend.py'
427--- u1db/backends/sqlite_backend.py 2012-06-04 18:36:31 +0000
428+++ u1db/backends/sqlite_backend.py 2012-06-12 15:20:23 +0000
429@@ -568,14 +568,19 @@
430
431 @staticmethod
432 def _transform_glob(value, escape_char='.'):
433- """Transform the given glob value into a valid LIKE statement.
434- """
435+ """Transform the given glob value into a valid LIKE statement."""
436 to_escape = [escape_char, '%', '_']
437 for esc in to_escape:
438 value = value.replace(esc, escape_char + esc)
439 assert value[-1] == '*'
440 return value[:-1] + '%'
441
442+ @staticmethod
443+ def _strip_glob(value):
444+ """Remove the trailing * from a value."""
445+ assert value[-1] == '*'
446+ return value[:-1]
447+
448 def get_from_index(self, index_name, *key_values):
449 definition = self._get_index_definition(index_name)
450 # First, build the definition. We join the document_fields table
451@@ -646,6 +651,14 @@
452 novalue_where = [
453 "d.doc_id = d%d.doc_id AND d%d.field_name = ?" % (i, i) for i in
454 range(len(definition))]
455+ wildcard_where = [
456+ novalue_where[i] + (" AND d%d.value NOT NULL" % (i,)) for i in
457+ range(len(definition))]
458+ like_where = [
459+ novalue_where[i] + (
460+ " AND (d%d.value < ? OR d%d.value LIKE ? ESCAPE '.')" %
461+ (i, i))
462+ for i in range(len(definition))]
463 range_where_lower = [
464 novalue_where[i] + (" AND d%d.value >= ?" % (i,)) for i in
465 range(len(definition))]
466@@ -659,19 +672,52 @@
467 start_value = (start_value,)
468 if len(start_value) != len(definition):
469 raise errors.InvalidValueForIndex()
470+ is_wildcard = False
471 for idx, (field, value) in enumerate(zip(definition, start_value)):
472 args.append(field)
473- where.append(range_where_lower[idx])
474- args.append(value)
475+ if value.endswith('*'):
476+ if value == '*':
477+ where.append(wildcard_where[idx])
478+ else:
479+ # This is a glob match
480+ if is_wildcard:
481+ # We can't have a partial wildcard following
482+ # another wildcard
483+ raise errors.InvalidGlobbing
484+ where.append(range_where_lower[idx])
485+ args.append(self._strip_glob(value))
486+ is_wildcard = True
487+ else:
488+ if is_wildcard:
489+ raise errors.InvalidGlobbing
490+ where.append(range_where_lower[idx])
491+ args.append(value)
492 if end_value:
493 if isinstance(end_value, basestring):
494 end_value = (end_value,)
495 if len(end_value) != len(definition):
496 raise errors.InvalidValueForIndex()
497+ is_wildcard = False
498 for idx, (field, value) in enumerate(zip(definition, end_value)):
499 args.append(field)
500- where.append(range_where_upper[idx])
501- args.append(value)
502+ if value.endswith('*'):
503+ if value == '*':
504+ where.append(wildcard_where[idx])
505+ else:
506+ # This is a glob match
507+ if is_wildcard:
508+ # We can't have a partial wildcard following
509+ # another wildcard
510+ raise errors.InvalidGlobbing
511+ where.append(like_where[idx])
512+ args.append(self._strip_glob(value))
513+ args.append(self._transform_glob(value))
514+ is_wildcard = True
515+ else:
516+ if is_wildcard:
517+ raise errors.InvalidGlobbing
518+ where.append(range_where_upper[idx])
519+ args.append(value)
520 statement = (
521 "SELECT d.doc_id, d.doc_rev, d.content FROM document d, %s "
522 "WHERE %s ORDER BY %s;" % (
523
524=== modified file 'u1db/tests/test_backends.py'
525--- u1db/tests/test_backends.py 2012-06-04 14:52:57 +0000
526+++ u1db/tests/test_backends.py 2012-06-12 15:20:23 +0000
527@@ -844,6 +844,39 @@
528 [doc4, doc2],
529 self.db.get_range_from_index('test-idx', None, 'value2'))
530
531+ def test_get_wildcard_range_from_index_start(self):
532+ doc1 = self.db.create_doc('{"key": "value4"}')
533+ doc2 = self.db.create_doc('{"key": "value23"}')
534+ doc3 = self.db.create_doc('{"key": "value2"}')
535+ doc4 = self.db.create_doc('{"key": "value22"}')
536+ self.db.create_doc('{"key": "value1"}')
537+ self.db.create_index('test-idx', 'key')
538+ self.assertEqual(
539+ [doc3, doc4, doc2, doc1],
540+ self.db.get_range_from_index('test-idx', 'value2*'))
541+
542+ def test_get_wildcard_range_from_index_end(self):
543+ self.db.create_doc('{"key": "value4"}')
544+ doc2 = self.db.create_doc('{"key": "value23"}')
545+ doc3 = self.db.create_doc('{"key": "value2"}')
546+ doc4 = self.db.create_doc('{"key": "value22"}')
547+ doc5 = self.db.create_doc('{"key": "value1"}')
548+ self.db.create_index('test-idx', 'key')
549+ self.assertEqual(
550+ [doc5, doc3, doc4, doc2],
551+ self.db.get_range_from_index('test-idx', None, 'value2*'))
552+
553+ def test_get_wildcard_range_from_index_start_end(self):
554+ self.db.create_doc('{"key": "a"}')
555+ doc2 = self.db.create_doc('{"key": "boo3"}')
556+ doc3 = self.db.create_doc('{"key": "catalyst"}')
557+ doc4 = self.db.create_doc('{"key": "whaever"}')
558+ doc5 = self.db.create_doc('{"key": "zerg"}')
559+ self.db.create_index('test-idx', 'key')
560+ self.assertEqual(
561+ [doc3, doc4],
562+ self.db.get_range_from_index('test-idx', 'cat*', 'zap*'))
563+
564 def test_get_range_from_index_multi_column_start_end(self):
565 self.db.create_doc('{"key": "value3", "key2": "value4"}')
566 doc2 = self.db.create_doc('{"key": "value2", "key2": "value3"}')
567@@ -876,6 +909,71 @@
568 self.db.get_range_from_index(
569 'test-idx', None, ('value2', 'value3')))
570
571+ def test_get_wildcard_range_from_index_multi_column_start(self):
572+ doc1 = self.db.create_doc('{"key": "value3", "key2": "value4"}')
573+ doc2 = self.db.create_doc('{"key": "value2", "key2": "value23"}')
574+ doc3 = self.db.create_doc('{"key": "value2", "key2": "value2"}')
575+ self.db.create_doc('{"key": "value1", "key2": "value1"}')
576+ self.db.create_index('test-idx', 'key', 'key2')
577+ self.assertEqual(
578+ [doc3, doc2, doc1],
579+ self.db.get_range_from_index('test-idx', ('value2', 'value2*')))
580+
581+ def test_get_wildcard_range_from_index_multi_column_end(self):
582+ self.db.create_doc('{"key": "value3", "key2": "value4"}')
583+ doc2 = self.db.create_doc('{"key": "value2", "key2": "value23"}')
584+ doc3 = self.db.create_doc('{"key": "value2", "key2": "value2"}')
585+ doc4 = self.db.create_doc('{"key": "value1", "key2": "value1"}')
586+ self.db.create_index('test-idx', 'key', 'key2')
587+ self.assertEqual(
588+ [doc4, doc3, doc2],
589+ self.db.get_range_from_index(
590+ 'test-idx', None, ('value2', 'value2*')))
591+
592+ def test_get_glob_range_from_index_multi_column_start(self):
593+ doc1 = self.db.create_doc('{"key": "value3", "key2": "value4"}')
594+ doc2 = self.db.create_doc('{"key": "value2", "key2": "value23"}')
595+ self.db.create_doc('{"key": "value1", "key2": "value2"}')
596+ self.db.create_doc('{"key": "value1", "key2": "value1"}')
597+ self.db.create_index('test-idx', 'key', 'key2')
598+ self.assertEqual(
599+ [doc2, doc1],
600+ self.db.get_range_from_index('test-idx', ('value2', '*')))
601+
602+ def test_get_glob_range_from_index_multi_column_end(self):
603+ self.db.create_doc('{"key": "value3", "key2": "value4"}')
604+ doc2 = self.db.create_doc('{"key": "value2", "key2": "value23"}')
605+ doc3 = self.db.create_doc('{"key": "value1", "key2": "value2"}')
606+ doc4 = self.db.create_doc('{"key": "value1", "key2": "value1"}')
607+ self.db.create_index('test-idx', 'key', 'key2')
608+ self.assertEqual(
609+ [doc4, doc3, doc2],
610+ self.db.get_range_from_index('test-idx', None, ('value2', '*')))
611+
612+ def test_get_range_from_index_illegal_wildcard_order(self):
613+ self.db.create_index('test-idx', 'k1', 'k2')
614+ self.assertRaises(
615+ errors.InvalidGlobbing,
616+ self.db.get_range_from_index, 'test-idx', ('*', 'v2'))
617+
618+ def test_get_range_from_index_illegal_glob_after_wildcard(self):
619+ self.db.create_index('test-idx', 'k1', 'k2')
620+ self.assertRaises(
621+ errors.InvalidGlobbing,
622+ self.db.get_range_from_index, 'test-idx', ('*', 'v*'))
623+
624+ def test_get_range_from_index_illegal_wildcard_order_end(self):
625+ self.db.create_index('test-idx', 'k1', 'k2')
626+ self.assertRaises(
627+ errors.InvalidGlobbing,
628+ self.db.get_range_from_index, 'test-idx', None, ('*', 'v2'))
629+
630+ def test_get_range_from_index_illegal_glob_after_wildcard_end(self):
631+ self.db.create_index('test-idx', 'k1', 'k2')
632+ self.assertRaises(
633+ errors.InvalidGlobbing,
634+ self.db.get_range_from_index, 'test-idx', None, ('*', 'v*'))
635+
636 def test_get_from_index_fails_if_no_index(self):
637 self.assertRaises(
638 errors.IndexDoesNotExist, self.db.get_from_index, 'foo')

Subscribers

People subscribed via source and target branches