Merge lp:~thisfred/u1db/and-the-wild-rocky-mountains-to-roam into lp:u1db
- and-the-wild-rocky-mountains-to-roam
- Merge into trunk
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 | ||||
Related bugs: |
|
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.
- 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_
> _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') |
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.