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: | Superseded | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~thisfred/u1db/and-the-wild-rocky-mountains-to-roam | ||||
Merge into: | lp:u1db | ||||
Diff against target: |
1327 lines (+850/-92) 10 files modified
include/u1db/u1db.h (+49/-7) include/u1db/u1db_internal.h (+29/-11) src/u1db.c (+55/-11) src/u1db_query.c (+247/-16) u1db/__init__.py (+29/-6) u1db/backends/inmemory.py (+50/-2) u1db/backends/sqlite_backend.py (+98/-2) u1db/tests/c_backend_wrapper.pyx (+115/-37) u1db/tests/test_backends.py (+160/-0) u1db/tests/test_c_backend.py (+18/-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 |
---|---|---|---|
Ubuntu One hackers | Pending | ||
Review via email: mp+108996@code.launchpad.net |
This proposal has been superseded by 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
- 329. By Eric Casteleijn
-
merged trunk, resolved conflicts
- 330. By Eric Casteleijn
-
more conflicts resolved
Unmerged revisions
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-05-29 14:32:27 +0000 |
3 | +++ include/u1db/u1db.h 2012-06-06 17:13:17 +0000 |
4 | @@ -127,7 +127,6 @@ |
5 | */ |
6 | int u1db_put_doc(u1database *db, u1db_document *doc); |
7 | |
8 | - |
9 | /** |
10 | * Mark conflicts as having been resolved. |
11 | * @param doc (IN/OUT) The new content. doc->doc_rev will be updated with the |
12 | @@ -255,10 +254,22 @@ |
13 | * |
14 | * @param index_name An identifier for this index. |
15 | * @param n_expressions The number of index expressions. |
16 | + * @param exp0... The values to match in the index, all of these should be char* |
17 | + */ |
18 | +int u1db_create_index(u1database *db, const char *index_name, int n_expressions, |
19 | + ...); |
20 | + |
21 | + |
22 | +/** |
23 | + * Create an index that you can query for matching documents. |
24 | + * |
25 | + * @param index_name An identifier for this index. |
26 | + * @param n_expressions The number of index expressions. |
27 | * @param expressions An array of expressions. |
28 | */ |
29 | -int u1db_create_index(u1database *db, const char *index_name, |
30 | - int n_expressions, const char **expressions); |
31 | +int u1db_create_indexl(u1database *db, const char *index_name, |
32 | + int n_expressions, const char **expressions); |
33 | + |
34 | |
35 | /** |
36 | * Delete a defined index. |
37 | @@ -292,6 +303,7 @@ |
38 | */ |
39 | int u1db_query_init(u1database *db, const char *index_name, u1query **query); |
40 | |
41 | + |
42 | /** |
43 | * Free the memory pointed to by query and all associated buffers. |
44 | * |
45 | @@ -299,6 +311,20 @@ |
46 | */ |
47 | void u1db_free_query(u1query **query); |
48 | |
49 | + |
50 | +/** |
51 | + * Get documents which match a given index. |
52 | + * |
53 | + * @param query A u1query object, as created by u1db_query_init. |
54 | + * @param context Will be returned via the document callback |
55 | + * @param n_values The number of parameters being passed, must be >= 1 |
56 | + * @param values The values to match in the index. |
57 | + */ |
58 | +int u1db_get_from_indexl(u1database *db, u1query *query, void *context, |
59 | + u1db_doc_callback cb, int n_values, |
60 | + const char **values); |
61 | + |
62 | + |
63 | /** |
64 | * Get documents which match a given index. |
65 | * |
66 | @@ -307,10 +333,24 @@ |
67 | * @param n_values The number of parameters being passed, must be >= 1 |
68 | * @param val0... The values to match in the index, all of these should be char* |
69 | */ |
70 | -int u1db_get_from_index(u1database *db, u1query *query, |
71 | - void *context, u1db_doc_callback cb, |
72 | - int n_values, ...); |
73 | - |
74 | +int u1db_get_from_index(u1database *db, u1query *query, void *context, |
75 | + u1db_doc_callback cb, int n_values, ...); |
76 | + |
77 | + |
78 | + |
79 | +/** |
80 | + * Get documents with key values in the specified range |
81 | + * |
82 | + * @param query A u1query object, as created by u1db_query_init. |
83 | + * @param context Will be returned via the document callback |
84 | + * @param n_values The number of values. |
85 | + * @param start_values An array of values. If NULL, assume an open ended query. |
86 | + * @param end_values An array of values. If NULL, assume an open ended query. |
87 | + */ |
88 | +int 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 | * Get keys under which documents are indexed. |
94 | * |
95 | @@ -319,6 +359,8 @@ |
96 | */ |
97 | int u1db_get_index_keys(u1database *db, char *index_name, void *context, |
98 | u1db_key_callback cb); |
99 | + |
100 | + |
101 | /** |
102 | * Get documents matching a single column index. |
103 | */ |
104 | |
105 | === modified file 'include/u1db/u1db_internal.h' |
106 | --- include/u1db/u1db_internal.h 2012-05-18 10:52:37 +0000 |
107 | +++ include/u1db/u1db_internal.h 2012-06-06 17:13:17 +0000 |
108 | @@ -331,19 +331,37 @@ |
109 | /** |
110 | * Format a given query. |
111 | * |
112 | - * @param n_fields The number of fields being passed in, (the number of args |
113 | - * in argp) |
114 | - * @param argp Arguments being passed. It is assumed that all arguments |
115 | - * will be of type "char*". |
116 | + * @param n_fields The number of fields being passed in. (The number of values |
117 | + * in values) |
118 | + * @param values Array of values being passed. |
119 | * @param buf (OUT) The character array. This will be dynamically allocated, |
120 | - * and callers must free() it. |
121 | + * and callers must free() it. |
122 | * @param wildcard (IN/OUT) Any array indicating a wildcard type for this |
123 | - * argument. A 0 indicates this is an exact match, a 1 |
124 | - * indicates this is a pure wildcard (eg, "*") and a 2 |
125 | - * indicates this is a glob (eg "f*"). |
126 | - * This must point to an array at least n_fields wide. |
127 | - */ |
128 | -int u1db__format_query(int n_fields, va_list argp, char **buf, int *wildcard); |
129 | + * argument. A 0 indicates this is an exact match, a 1 |
130 | + * indicates this is a pure wildcard (eg, "*") and a 2 |
131 | + * indicates this is a glob (eg "f*"). |
132 | + * This must point to an array at least n_fields wide. |
133 | + */ |
134 | +int u1db__format_query(int n_fields, const char **values, char **buf, |
135 | + int *wildcard); |
136 | + |
137 | + |
138 | +/** |
139 | + * Format a given range query. |
140 | + * |
141 | + * @param n_fields The number of fields being passed in. (The number of values |
142 | + * in start_values and end_values) |
143 | + * @param start_values Array of values used to define the lower bound of the |
144 | + * query. |
145 | + * @param end_values Array of values used to define the upper bound of the |
146 | + * query. |
147 | + * @param buf (OUT) The character array. This will be dynamically allocated, |
148 | + * and callers must free() it. |
149 | + */ |
150 | +int u1db__format_range_query(int n_fields, const char **start_values, |
151 | + const char **end_values, char **buf, |
152 | + int *start_wildcard, int *end_wildcard); |
153 | + |
154 | |
155 | /** |
156 | * Given this document content, update the indexed fields in the db. |
157 | |
158 | === modified file 'src/u1db.c' |
159 | --- src/u1db.c 2012-05-29 15:49:21 +0000 |
160 | +++ src/u1db.c 2012-06-06 17:13:17 +0000 |
161 | @@ -1694,12 +1694,13 @@ |
162 | return U1DB_OK; |
163 | } |
164 | |
165 | + |
166 | int |
167 | -u1db_create_index(u1database *db, const char *index_name, int n_expressions, |
168 | - const char **expressions) |
169 | +u1db_create_indexl(u1database *db, const char *index_name, int n_expressions, |
170 | + const char **expressions) |
171 | { |
172 | int status = U1DB_OK, i = 0; |
173 | - sqlite3_stmt *statement; |
174 | + sqlite3_stmt *statement = NULL; |
175 | const char **unique_expressions; |
176 | int n_unique; |
177 | |
178 | @@ -1717,14 +1718,20 @@ |
179 | } |
180 | status = u1db__find_unique_expressions(db, n_expressions, expressions, |
181 | &n_unique, &unique_expressions); |
182 | - if (status != U1DB_OK) { goto finish; } |
183 | + if (status != U1DB_OK) { |
184 | + goto finish; |
185 | + } |
186 | status = sqlite3_prepare_v2(db->sql_handle, |
187 | "SELECT field FROM index_definitions" |
188 | " WHERE name = ? ORDER BY offset DESC", |
189 | -1, &statement, NULL); |
190 | - if (status != SQLITE_OK) { goto finish; } |
191 | + if (status != SQLITE_OK) { |
192 | + goto finish; |
193 | + } |
194 | status = sqlite3_bind_text(statement, 1, index_name, -1, SQLITE_TRANSIENT); |
195 | - if (status != SQLITE_OK) { goto finish; } |
196 | + if (status != SQLITE_OK) { |
197 | + goto finish; |
198 | + } |
199 | status = sqlite3_step(statement); |
200 | i=0; |
201 | while (status == SQLITE_ROW) { |
202 | @@ -1736,7 +1743,9 @@ |
203 | status = sqlite3_step(statement); |
204 | i++; |
205 | } |
206 | - if (status != SQLITE_DONE) { goto finish; } |
207 | + if (status != SQLITE_DONE) { |
208 | + goto finish; |
209 | + } |
210 | if (i>0) { |
211 | status = SQLITE_OK; |
212 | goto finish; |
213 | @@ -1749,13 +1758,19 @@ |
214 | goto finish; |
215 | } |
216 | status = sqlite3_bind_text(statement, 1, index_name, -1, SQLITE_TRANSIENT); |
217 | - if (status != SQLITE_OK) { goto finish; } |
218 | + if (status != SQLITE_OK) { |
219 | + goto finish; |
220 | + } |
221 | for (i = 0; i < n_expressions; ++i) { |
222 | status = sqlite3_bind_int(statement, 2, i); |
223 | - if (status != SQLITE_OK) { goto finish; } |
224 | + if (status != SQLITE_OK) { |
225 | + goto finish; |
226 | + } |
227 | status = sqlite3_bind_text(statement, 3, expressions[i], -1, |
228 | SQLITE_TRANSIENT); |
229 | - if (status != SQLITE_OK) { goto finish; } |
230 | + if (status != SQLITE_OK) { |
231 | + goto finish; |
232 | + } |
233 | status = sqlite3_step(statement); |
234 | if (status != SQLITE_DONE) { |
235 | if (status == SQLITE_CONSTRAINT) { |
236 | @@ -1765,7 +1780,9 @@ |
237 | goto finish; |
238 | } |
239 | status = sqlite3_reset(statement); |
240 | - if (status != SQLITE_OK) { goto finish; } |
241 | + if (status != SQLITE_OK) { |
242 | + goto finish; |
243 | + } |
244 | } |
245 | status = u1db__index_all_docs(db, n_unique, unique_expressions); |
246 | finish: |
247 | @@ -1783,6 +1800,33 @@ |
248 | |
249 | |
250 | int |
251 | +u1db_create_index(u1database *db, const char *index_name, int n_expressions, |
252 | + ...) |
253 | +{ |
254 | + int i, status = U1DB_OK; |
255 | + va_list argp; |
256 | + const char **expressions = NULL; |
257 | + |
258 | + expressions = (const char **)calloc(n_expressions, sizeof(char*)); |
259 | + if (expressions == NULL) { |
260 | + status = U1DB_NOMEM; |
261 | + goto finish; |
262 | + } |
263 | + va_start(argp, n_expressions); |
264 | + for (i = 0; i < n_expressions; ++i) { |
265 | + expressions[i] = va_arg(argp, char *); |
266 | + } |
267 | + status = u1db_create_indexl(db, index_name, n_expressions, expressions); |
268 | +finish: |
269 | + if (expressions != NULL) |
270 | + free(expressions); |
271 | + va_end(argp); |
272 | + return status; |
273 | +} |
274 | + |
275 | + |
276 | + |
277 | +int |
278 | u1db_delete_index(u1database *db, const char *index_name) |
279 | { |
280 | int status = U1DB_OK; |
281 | |
282 | === modified file 'src/u1db_query.c' |
283 | --- src/u1db_query.c 2012-05-30 19:42:52 +0000 |
284 | +++ src/u1db_query.c 2012-06-06 17:13:17 +0000 |
285 | @@ -535,17 +535,14 @@ |
286 | |
287 | |
288 | int |
289 | -u1db_get_from_index(u1database *db, u1query *query, |
290 | - void *context, u1db_doc_callback cb, |
291 | - int n_values, ...) |
292 | +u1db_get_from_indexl(u1database *db, u1query *query, void *context, |
293 | + u1db_doc_callback cb, int n_values, const char **values) |
294 | { |
295 | int status = U1DB_OK; |
296 | sqlite3_stmt *statement = NULL; |
297 | char *doc_id = NULL; |
298 | char *query_str = NULL; |
299 | int i, bind_arg; |
300 | - va_list argp; |
301 | - const char *valN = NULL; |
302 | int wildcard[20] = {0}; |
303 | |
304 | if (db == NULL || query == NULL || cb == NULL || n_values < 0) |
305 | @@ -558,29 +555,26 @@ |
306 | if (n_values > 20) { |
307 | return U1DB_NOT_IMPLEMENTED; |
308 | } |
309 | - va_start(argp, n_values); |
310 | - status = u1db__format_query(query->num_fields, argp, &query_str, wildcard); |
311 | - va_end(argp); |
312 | + status = u1db__format_query( |
313 | + query->num_fields, values, &query_str, wildcard); |
314 | if (status != U1DB_OK) { goto finish; } |
315 | status = sqlite3_prepare_v2(db->sql_handle, query_str, -1, |
316 | &statement, NULL); |
317 | if (status != SQLITE_OK) { goto finish; } |
318 | // Bind all of the 'field_name' parameters. sqlite_bind starts at 1 |
319 | bind_arg = 1; |
320 | - va_start(argp, n_values); |
321 | for (i = 0; i < query->num_fields; ++i) { |
322 | status = sqlite3_bind_text(statement, bind_arg, query->fields[i], -1, |
323 | SQLITE_TRANSIENT); |
324 | bind_arg++; |
325 | if (status != SQLITE_OK) { goto finish; } |
326 | - valN = va_arg(argp, char *); |
327 | if (wildcard[i] == 0) { |
328 | // Not a wildcard, so add the argument |
329 | - status = sqlite3_bind_text(statement, bind_arg, valN, -1, |
330 | + status = sqlite3_bind_text(statement, bind_arg, values[i], -1, |
331 | SQLITE_TRANSIENT); |
332 | bind_arg++; |
333 | } else if (wildcard[i] == 2) { |
334 | - status = sqlite3_bind_text(statement, bind_arg, valN, -1, |
335 | + status = sqlite3_bind_text(statement, bind_arg, values[i], -1, |
336 | SQLITE_TRANSIENT); |
337 | bind_arg++; |
338 | } |
339 | @@ -600,11 +594,134 @@ |
340 | status = U1DB_OK; |
341 | } |
342 | finish: |
343 | + sqlite3_finalize(statement); |
344 | + if (query_str != NULL) { |
345 | + free(query_str); |
346 | + } |
347 | + return status; |
348 | +} |
349 | + |
350 | + |
351 | +int |
352 | +u1db_get_from_index(u1database *db, u1query *query, void *context, |
353 | + u1db_doc_callback cb, int n_values, ...) |
354 | +{ |
355 | + int i, status = U1DB_OK; |
356 | + va_list argp; |
357 | + const char **values = NULL; |
358 | + |
359 | + values = (const char **)calloc(n_values, sizeof(char*)); |
360 | + if (values == NULL) { |
361 | + status = U1DB_NOMEM; |
362 | + goto finish; |
363 | + } |
364 | + va_start(argp, n_values); |
365 | + for (i = 0; i < n_values; ++i) { |
366 | + values[i] = va_arg(argp, char *); |
367 | + } |
368 | + status = u1db_get_from_indexl(db, query, context, cb, n_values, values); |
369 | +finish: |
370 | + if (values != NULL) |
371 | + free(values); |
372 | va_end(argp); |
373 | + return status; |
374 | +} |
375 | + |
376 | + |
377 | +int |
378 | +u1db_get_range_from_index(u1database *db, u1query *query, |
379 | + void *context, u1db_doc_callback cb, |
380 | + int n_values, const char **start_values, |
381 | + const char **end_values) |
382 | +{ |
383 | + int i, bind_arg, status = U1DB_OK; |
384 | + char *query_str = NULL; |
385 | + sqlite3_stmt *statement = NULL; |
386 | + char *doc_id = NULL; |
387 | + char *value_copy = NULL; |
388 | + int start_wildcard[20] = {0}; |
389 | + int end_wildcard[20] = {0}; |
390 | + |
391 | + if (db == NULL || query == NULL || cb == NULL || n_values < 0) { |
392 | + return U1DB_INVALID_PARAMETER; |
393 | + } |
394 | + if (n_values != query->num_fields) { |
395 | + return U1DB_INVALID_VALUE_FOR_INDEX; |
396 | + } |
397 | + status = u1db__format_range_query( |
398 | + query->num_fields, start_values, end_values, &query_str, |
399 | + start_wildcard, end_wildcard); |
400 | + |
401 | + if (status != U1DB_OK) { goto finish; } |
402 | + status = sqlite3_prepare_v2(db->sql_handle, query_str, -1, |
403 | + &statement, NULL); |
404 | + if (status != SQLITE_OK) { goto finish; } |
405 | + // Bind all of the 'field_name' parameters. sqlite_bind starts at 1 |
406 | + bind_arg = 1; |
407 | + |
408 | + for (i = 0; i < query->num_fields; ++i) { |
409 | + status = sqlite3_bind_text( |
410 | + statement, bind_arg, query->fields[i], -1, SQLITE_TRANSIENT); |
411 | + if (status != SQLITE_OK) { goto finish; } |
412 | + bind_arg++; |
413 | + if (start_values != NULL) { |
414 | + if (start_wildcard[i] == 0) { |
415 | + status = sqlite3_bind_text( |
416 | + statement, bind_arg, start_values[i], -1, |
417 | + SQLITE_TRANSIENT); |
418 | + bind_arg++; |
419 | + } else if (start_wildcard[i] == 2) { |
420 | + if (value_copy != NULL) |
421 | + free(value_copy); |
422 | + value_copy = strdup(start_values[i]); |
423 | + value_copy[strlen(value_copy) - 1] = '\0'; |
424 | + status = sqlite3_bind_text( |
425 | + statement, bind_arg, value_copy, -1, SQLITE_TRANSIENT); |
426 | + bind_arg++; |
427 | + } |
428 | + if (status != SQLITE_OK) { goto finish; } |
429 | + } |
430 | + if (end_values != NULL) { |
431 | + if (end_wildcard[i] == 0) { |
432 | + status = sqlite3_bind_text( |
433 | + statement, bind_arg, end_values[i], -1, |
434 | + SQLITE_TRANSIENT); |
435 | + bind_arg++; |
436 | + } else if (end_wildcard[i] == 2) { |
437 | + if (value_copy != NULL) |
438 | + free(value_copy); |
439 | + value_copy = strdup(end_values[i]); |
440 | + value_copy[strlen(value_copy) - 1] = '\0'; |
441 | + status = sqlite3_bind_text( |
442 | + statement, bind_arg, value_copy, -1, SQLITE_TRANSIENT); |
443 | + bind_arg++; |
444 | + status = sqlite3_bind_text( |
445 | + statement, bind_arg, end_values[i], -1, SQLITE_TRANSIENT); |
446 | + bind_arg++; |
447 | + } |
448 | + if (status != SQLITE_OK) { goto finish; } |
449 | + } |
450 | + } |
451 | + status = sqlite3_step(statement); |
452 | + while (status == SQLITE_ROW) { |
453 | + doc_id = (char*)sqlite3_column_text(statement, 0); |
454 | + // We use u1db_get_docs so we can pass check_for_conflicts=0, which is |
455 | + // currently expected by the test suite. |
456 | + status = u1db_get_docs( |
457 | + db, 1, (const char**)&doc_id, 0, 0, context, cb); |
458 | + if (status != U1DB_OK) { goto finish; } |
459 | + status = sqlite3_step(statement); |
460 | + } |
461 | + if (status == SQLITE_DONE) { |
462 | + status = U1DB_OK; |
463 | + } |
464 | +finish: |
465 | sqlite3_finalize(statement); |
466 | if (query_str != NULL) { |
467 | free(query_str); |
468 | } |
469 | + if (value_copy != NULL) |
470 | + free(value_copy); |
471 | return status; |
472 | } |
473 | |
474 | @@ -681,7 +798,8 @@ |
475 | |
476 | |
477 | int |
478 | -u1db__format_query(int n_fields, va_list argp, char **buf, int *wildcard) |
479 | +u1db__format_query(int n_fields, const char **values, char **buf, |
480 | + int *wildcard) |
481 | { |
482 | int status = U1DB_OK; |
483 | int buf_size, i; |
484 | @@ -712,7 +830,7 @@ |
485 | " AND d%d.field_name = ?", |
486 | i, i); |
487 | } |
488 | - val = va_arg(argp, char *); |
489 | + val = values[i]; |
490 | if (val == NULL) { |
491 | status = U1DB_INVALID_VALUE_FOR_INDEX; |
492 | goto finish; |
493 | @@ -756,6 +874,120 @@ |
494 | return status; |
495 | } |
496 | |
497 | +int |
498 | +u1db__format_range_query(int n_fields, const char **start_values, |
499 | + const char **end_values, char **buf, |
500 | + int *start_wildcard, int *end_wildcard) |
501 | +{ |
502 | + int status = U1DB_OK; |
503 | + int buf_size, i; |
504 | + char *cur = NULL; |
505 | + const char *val = NULL; |
506 | + int have_start_wildcard = 0; |
507 | + int have_end_wildcard = 0; |
508 | + |
509 | + if (n_fields < 1) { |
510 | + return U1DB_INVALID_PARAMETER; |
511 | + } |
512 | + // 81 for 1 doc, 166 for 2, 251 for 3 |
513 | + buf_size = (1 + n_fields) * 100; |
514 | + // The first field is treated specially |
515 | + cur = (char*)calloc(buf_size, 1); |
516 | + if (cur == NULL) { |
517 | + return U1DB_NOMEM; |
518 | + } |
519 | + *buf = cur; |
520 | + add_to_buf(&cur, &buf_size, "SELECT d0.doc_id FROM document_fields d0"); |
521 | + for (i = 1; i < n_fields; ++i) { |
522 | + add_to_buf(&cur, &buf_size, ", document_fields d%d", i); |
523 | + } |
524 | + add_to_buf(&cur, &buf_size, " WHERE d0.field_name = ?"); |
525 | + for (i = 0; i < n_fields; ++i) { |
526 | + if (i != 0) { |
527 | + add_to_buf(&cur, &buf_size, |
528 | + " AND d0.doc_id = d%d.doc_id" |
529 | + " AND d%d.field_name = ?", |
530 | + i, i); |
531 | + } |
532 | + if (start_values != NULL) { |
533 | + val = start_values[i]; |
534 | + if (val == NULL) { |
535 | + status = U1DB_INVALID_VALUE_FOR_INDEX; |
536 | + goto finish; |
537 | + } |
538 | + if (val[0] == '*') { |
539 | + start_wildcard[i] = 1; |
540 | + have_start_wildcard= 1; |
541 | + add_to_buf(&cur, &buf_size, " and d%d.value not null", i); |
542 | + } else if (val[0] != '\0' && val[strlen(val)-1] == '*') { |
543 | + // glob |
544 | + start_wildcard[i] = 2; |
545 | + if (have_start_wildcard) { |
546 | + //globs not allowed after another wildcard |
547 | + status = U1DB_INVALID_GLOBBING; |
548 | + goto finish; |
549 | + } |
550 | + have_start_wildcard = 1; |
551 | + add_to_buf(&cur, &buf_size, " and d%d.value >= ?", i); |
552 | + } else { |
553 | + start_wildcard[i] = 0; |
554 | + if (have_start_wildcard) { |
555 | + // can't have a non-wildcard after a wildcard |
556 | + status = U1DB_INVALID_GLOBBING; |
557 | + goto finish; |
558 | + } |
559 | + add_to_buf(&cur, &buf_size, " and d%d.value >= ?", i); |
560 | + } |
561 | + } |
562 | + if (end_values != NULL) { |
563 | + val = end_values[i]; |
564 | + if (val == NULL) { |
565 | + status = U1DB_INVALID_VALUE_FOR_INDEX; |
566 | + goto finish; |
567 | + } |
568 | + if (val[0] == '*') { |
569 | + end_wildcard[i] = 1; |
570 | + have_end_wildcard = 1; |
571 | + add_to_buf(&cur, &buf_size, " AND d%d.value NOT NULL", i); |
572 | + } else if (val[0] != '\0' && val[strlen(val)-1] == '*') { |
573 | + // glob |
574 | + end_wildcard[i] = 2; |
575 | + if (have_end_wildcard) { |
576 | + //globs not allowed after another wildcard |
577 | + status = U1DB_INVALID_GLOBBING; |
578 | + goto finish; |
579 | + } |
580 | + have_end_wildcard = 1; |
581 | + add_to_buf( |
582 | + &cur, &buf_size, |
583 | + " AND (d%d.value < ? OR d%d.value GLOB ?)", i, i); |
584 | + } else { |
585 | + end_wildcard[i] = 0; |
586 | + if (have_end_wildcard) { |
587 | + // Can't have a non-wildcard after a wildcard |
588 | + status = U1DB_INVALID_GLOBBING; |
589 | + goto finish; |
590 | + } |
591 | + add_to_buf(&cur, &buf_size, " AND d%d.value <= ?", i); |
592 | + } |
593 | + } |
594 | + |
595 | + } |
596 | + add_to_buf(&cur, &buf_size, " ORDER BY "); |
597 | + for (i = 0; i < n_fields; ++i) { |
598 | + if (i != 0) { |
599 | + add_to_buf(&cur, &buf_size, ", "); |
600 | + } |
601 | + add_to_buf(&cur, &buf_size, "d%d.value", i); |
602 | + } |
603 | +finish: |
604 | + if (status != U1DB_OK && *buf != NULL) { |
605 | + free(*buf); |
606 | + *buf = NULL; |
607 | + } |
608 | + return status; |
609 | +} |
610 | + |
611 | struct sqlcb_to_field_cb { |
612 | void *user_context; |
613 | int (*user_cb)(void *, const char*, transformation *tr); |
614 | @@ -787,7 +1019,6 @@ |
615 | } |
616 | } |
617 | else { |
618 | - // TODO: unicode fieldnames ? |
619 | size = (end - field_copy); |
620 | word = (char *)calloc(size + 1, 1); |
621 | strncpy(word, field_copy, size); |
622 | @@ -969,7 +1200,7 @@ |
623 | } |
624 | |
625 | static int |
626 | -evaluate_index_and_insert_into_db(void *context, const char *expression, |
627 | +evaluate_index_and_insert_into_db(void *context, const char *expression, |
628 | transformation *tr) |
629 | { |
630 | struct evaluate_index_context *ctx; |
631 | |
632 | === modified file 'u1db/__init__.py' |
633 | --- u1db/__init__.py 2012-05-31 18:51:20 +0000 |
634 | +++ u1db/__init__.py 2012-06-06 17:13:17 +0000 |
635 | @@ -180,22 +180,45 @@ |
636 | def get_from_index(self, index_name, *key_values): |
637 | """Return documents that match the keys supplied. |
638 | |
639 | - You must supply exactly the same number of values as has been defined |
640 | + You must supply exactly the same number of values as have been defined |
641 | in the index. It is possible to do a prefix match by using '*' to |
642 | indicate a wildcard match. You can only supply '*' to trailing entries, |
643 | - (eg [('val', '*', '*')] is allowed, but [('*', 'val', 'val')] is not.) |
644 | + (eg 'val', '*', '*' is allowed, but '*', 'val', 'val' is not.) |
645 | It is also possible to append a '*' to the last supplied value (eg |
646 | - [('val*', '*', '*')] or [('val', 'val*', '*')], but not [('val*', |
647 | - 'val', '*')]) |
648 | + 'val*', '*', '*' or 'val', 'val*', '*', but not 'val*', 'val', '*') |
649 | |
650 | :return: List of [Document] |
651 | :param index_name: The index to query |
652 | - :param key_values: tuples of values to match. eg, if you have |
653 | + :param key_values: values to match. eg, if you have |
654 | an index with 3 fields then you would have: |
655 | - (x-val1, x-val2, x-val3), (y-val1, y-val2, y-val3), ... |
656 | + get_from_index(index_name, val1, val2, val3) |
657 | """ |
658 | raise NotImplementedError(self.get_from_index) |
659 | |
660 | + def get_range_from_index(self, index_name, start_value, end_value): |
661 | + """Return documents that fall within the specified range. |
662 | + |
663 | + Both ends of the range are inclusive. For both start_value and |
664 | + end_value, one must supply exactly the same number of values as have |
665 | + been defined in the index, or pass None. In case of a single column |
666 | + index, a string is accepted as an alternative for a tuple with a single |
667 | + value. It is possible to do a prefix match by using '*' to indicate |
668 | + a wildcard match. You can only supply '*' to trailing entries, (eg |
669 | + 'val', '*', '*' is allowed, but '*', 'val', 'val' is not.) It is also |
670 | + possible to append a '*' to the last supplied value (eg 'val*', '*', |
671 | + '*' or 'val', 'val*', '*', but not 'val*', 'val', '*') |
672 | + |
673 | + :return: List of [Document] |
674 | + :param index_name: The index to query |
675 | + :param start_values: tuples of values that define the lower bound of |
676 | + the range. eg, if you have an index with 3 fields then you would |
677 | + have: (val1, val2, val3) |
678 | + :param end_values: tuples of values that define the upper bound of the |
679 | + range. eg, if you have an index with 3 fields then you would have: |
680 | + (val1, val2, val3) |
681 | + """ |
682 | + raise NotImplementedError(self.get_range_from_index) |
683 | + |
684 | def get_index_keys(self, index_name): |
685 | """Return all keys under which documents are indexed in this index. |
686 | |
687 | |
688 | === modified file 'u1db/backends/inmemory.py' |
689 | --- u1db/backends/inmemory.py 2012-05-31 18:51:20 +0000 |
690 | +++ u1db/backends/inmemory.py 2012-06-06 17:13:17 +0000 |
691 | @@ -27,6 +27,11 @@ |
692 | from u1db.backends import CommonBackend, CommonSyncTarget |
693 | |
694 | |
695 | +def get_prefix(value): |
696 | + key_prefix = '\x01'.join(value) |
697 | + return key_prefix.rstrip('*') |
698 | + |
699 | + |
700 | class InMemoryDatabase(CommonBackend): |
701 | """A database that only stores the data internally.""" |
702 | |
703 | @@ -222,6 +227,24 @@ |
704 | result.append(self._factory(doc_id, doc_rev, doc)) |
705 | return result |
706 | |
707 | + def get_range_from_index(self, index_name, start_value=None, |
708 | + end_value=None): |
709 | + """Return all documents with key values in the specified range.""" |
710 | + try: |
711 | + index = self._indexes[index_name] |
712 | + except KeyError: |
713 | + raise errors.IndexDoesNotExist |
714 | + if isinstance(start_value, basestring): |
715 | + start_value = (start_value,) |
716 | + if isinstance(end_value, basestring): |
717 | + end_value = (end_value,) |
718 | + doc_ids = index.lookup_range(start_value, end_value) |
719 | + result = [] |
720 | + for doc_id in doc_ids: |
721 | + doc_rev, doc = self._docs[doc_id] |
722 | + result.append(self._factory(doc_id, doc_rev, doc)) |
723 | + return result |
724 | + |
725 | def get_index_keys(self, index_name): |
726 | try: |
727 | index = self._indexes[index_name] |
728 | @@ -345,6 +368,32 @@ |
729 | else: |
730 | return self._lookup_prefix(values[:last]) |
731 | |
732 | + def lookup_range(self, start_values, end_values): |
733 | + """Find docs within the range.""" |
734 | + # TODO: Wildly inefficient, which is unlikely to be a problem for the |
735 | + # inmemory implementation. |
736 | + if start_values: |
737 | + self._find_non_wildcards(start_values) |
738 | + start_values = get_prefix(start_values) |
739 | + if end_values: |
740 | + if self._find_non_wildcards(end_values) == -1: |
741 | + exact = True |
742 | + else: |
743 | + exact = False |
744 | + end_values = get_prefix(end_values) |
745 | + found = [] |
746 | + for key, doc_ids in sorted(self._values.iteritems()): |
747 | + if start_values and start_values > key: |
748 | + continue |
749 | + if end_values and end_values < key: |
750 | + if exact: |
751 | + break |
752 | + else: |
753 | + if not key.startswith(end_values): |
754 | + break |
755 | + found.extend(doc_ids) |
756 | + return found |
757 | + |
758 | def keys(self): |
759 | """Find the indexed keys.""" |
760 | return self._values.keys() |
761 | @@ -353,8 +402,7 @@ |
762 | """Find docs that match the prefix string in values.""" |
763 | # TODO: We need a different data structure to make prefix style fast, |
764 | # some sort of sorted list would work, but a plain dict doesn't. |
765 | - key_prefix = '\x01'.join(value) |
766 | - key_prefix = key_prefix.rstrip('*') |
767 | + key_prefix = get_prefix(value) |
768 | all_doc_ids = [] |
769 | for key, doc_ids in sorted(self._values.iteritems()): |
770 | if key.startswith(key_prefix): |
771 | |
772 | === modified file 'u1db/backends/sqlite_backend.py' |
773 | --- u1db/backends/sqlite_backend.py 2012-05-31 18:51:20 +0000 |
774 | +++ u1db/backends/sqlite_backend.py 2012-06-06 17:13:17 +0000 |
775 | @@ -568,14 +568,19 @@ |
776 | |
777 | @staticmethod |
778 | def _transform_glob(value, escape_char='.'): |
779 | - """Transform the given glob value into a valid LIKE statement. |
780 | - """ |
781 | + """Transform the given glob value into a valid LIKE statement.""" |
782 | to_escape = [escape_char, '%', '_'] |
783 | for esc in to_escape: |
784 | value = value.replace(esc, escape_char + esc) |
785 | assert value[-1] == '*' |
786 | return value[:-1] + '%' |
787 | |
788 | + @staticmethod |
789 | + def _strip_glob(value): |
790 | + """Remove the trailing * from a value.""" |
791 | + assert value[-1] == '*' |
792 | + return value[:-1] |
793 | + |
794 | def get_from_index(self, index_name, *key_values): |
795 | definition = self._get_index_definition(index_name) |
796 | # First, build the definition. We join the document_fields table |
797 | @@ -638,6 +643,97 @@ |
798 | res = c.fetchall() |
799 | return [self._factory(r[0], r[1], r[2]) for r in res] |
800 | |
801 | + def get_range_from_index(self, index_name, start_value=None, |
802 | + end_value=None): |
803 | + """Return all documents with key values in the specified range.""" |
804 | + definition = self._get_index_definition(index_name) |
805 | + tables = ["document_fields d%d" % i for i in range(len(definition))] |
806 | + novalue_where = [ |
807 | + "d.doc_id = d%d.doc_id AND d%d.field_name = ?" % (i, i) for i in |
808 | + range(len(definition))] |
809 | + wildcard_where = [ |
810 | + novalue_where[i] + (" AND d%d.value NOT NULL" % (i,)) for i in |
811 | + range(len(definition))] |
812 | + like_where = [ |
813 | + novalue_where[i] + ( |
814 | + " AND (d%d.value < ? OR d%d.value LIKE ? ESCAPE '.')" % |
815 | + (i, i)) |
816 | + for i in range(len(definition))] |
817 | + range_where_lower = [ |
818 | + novalue_where[i] + (" AND d%d.value >= ?" % (i,)) for i in |
819 | + range(len(definition))] |
820 | + range_where_upper = [ |
821 | + novalue_where[i] + (" AND d%d.value <= ?" % (i,)) for i in |
822 | + range(len(definition))] |
823 | + args = [] |
824 | + where = [] |
825 | + if start_value: |
826 | + if isinstance(start_value, basestring): |
827 | + start_value = (start_value,) |
828 | + if len(start_value) != len(definition): |
829 | + raise errors.InvalidValueForIndex() |
830 | + is_wildcard = False |
831 | + for idx, (field, value) in enumerate(zip(definition, start_value)): |
832 | + args.append(field) |
833 | + if value.endswith('*'): |
834 | + if value == '*': |
835 | + where.append(wildcard_where[idx]) |
836 | + else: |
837 | + # This is a glob match |
838 | + if is_wildcard: |
839 | + # We can't have a partial wildcard following |
840 | + # another wildcard |
841 | + raise errors.InvalidGlobbing |
842 | + where.append(range_where_lower[idx]) |
843 | + args.append(self._strip_glob(value)) |
844 | + is_wildcard = True |
845 | + else: |
846 | + if is_wildcard: |
847 | + raise errors.InvalidGlobbing |
848 | + where.append(range_where_lower[idx]) |
849 | + args.append(value) |
850 | + if end_value: |
851 | + if isinstance(end_value, basestring): |
852 | + end_value = (end_value,) |
853 | + if len(end_value) != len(definition): |
854 | + raise errors.InvalidValueForIndex() |
855 | + is_wildcard = False |
856 | + for idx, (field, value) in enumerate(zip(definition, end_value)): |
857 | + args.append(field) |
858 | + if value.endswith('*'): |
859 | + if value == '*': |
860 | + where.append(wildcard_where[idx]) |
861 | + else: |
862 | + # This is a glob match |
863 | + if is_wildcard: |
864 | + # We can't have a partial wildcard following |
865 | + # another wildcard |
866 | + raise errors.InvalidGlobbing |
867 | + where.append(like_where[idx]) |
868 | + args.append(self._strip_glob(value)) |
869 | + args.append(self._transform_glob(value)) |
870 | + is_wildcard = True |
871 | + else: |
872 | + if is_wildcard: |
873 | + raise errors.InvalidGlobbing |
874 | + where.append(range_where_upper[idx]) |
875 | + args.append(value) |
876 | + statement = ( |
877 | + "SELECT d.doc_id, d.doc_rev, d.content FROM document d, %s " |
878 | + "WHERE %s ORDER BY %s;" % ( |
879 | + ', '.join(tables), |
880 | + ' AND '.join(where), |
881 | + ', '.join( |
882 | + ['d%d.value' % i for i in range(len(definition))]))) |
883 | + c = self._db_handle.cursor() |
884 | + try: |
885 | + c.execute(statement, tuple(args)) |
886 | + except dbapi2.OperationalError, e: |
887 | + raise dbapi2.OperationalError(str(e) + |
888 | + '\nstatement: %s\nargs: %s\n' % (statement, args)) |
889 | + res = c.fetchall() |
890 | + return [self._factory(r[0], r[1], r[2]) for r in res] |
891 | + |
892 | def get_index_keys(self, index): |
893 | c = self._db_handle.cursor() |
894 | try: |
895 | |
896 | === modified file 'u1db/tests/c_backend_wrapper.pyx' |
897 | --- u1db/tests/c_backend_wrapper.pyx 2012-05-31 18:51:20 +0000 |
898 | +++ u1db/tests/c_backend_wrapper.pyx 2012-06-06 17:13:17 +0000 |
899 | @@ -92,21 +92,28 @@ |
900 | u1db_trans_info_callback cb) |
901 | int u1db_get_doc_conflicts(u1database *db, char *doc_id, void *context, |
902 | u1db_doc_callback cb) |
903 | - |
904 | - int u1db_create_index(u1database *db, char *index_name, |
905 | - int n_expressions, const_char_ptr *expressions) |
906 | + int u1db_create_indexl(u1database *db, char *index_name, int n_expressions, |
907 | + const_char_ptr *expressions) |
908 | + int u1db_create_index(u1database *db, char *index_name, int n_expressions, |
909 | + ...) |
910 | + int u1db_get_from_indexl(u1database *db, u1query *query, void *context, |
911 | + u1db_doc_callback cb, int n_values, |
912 | + const_char_ptr *values) |
913 | + int u1db_get_from_index(u1database *db, u1query *query, void *context, |
914 | + u1db_doc_callback cb, int n_values, char *val0, |
915 | + ...) |
916 | + int u1db_get_range_from_index(u1database *db, u1query *query, |
917 | + void *context, u1db_doc_callback cb, |
918 | + int n_values, const_char_ptr *start_values, |
919 | + const_char_ptr *end_values) |
920 | int u1db_delete_index(u1database *db, char *index_name) |
921 | - |
922 | int u1db_list_indexes(u1database *db, void *context, |
923 | int (*cb)(void *context, const_char_ptr index_name, |
924 | int n_expressions, const_char_ptr *expressions)) |
925 | - int u1db_get_from_index(u1database *db, u1query *query, void *context, |
926 | - u1db_doc_callback cb, int n_values, char *val0, ...) |
927 | int u1db_get_index_keys(u1database *db, char *index_name, void *context, |
928 | u1db_key_callback cb) |
929 | int u1db_simple_lookup1(u1database *db, char *index_name, char *val1, |
930 | void *context, u1db_doc_callback cb) |
931 | - |
932 | int u1db_query_init(u1database *db, char *index_name, u1query **query) |
933 | void u1db_free_query(u1query **query) |
934 | |
935 | @@ -212,7 +219,8 @@ |
936 | u1db_record **conflict_records) |
937 | int u1db__sync_exchange_seen_ids(u1db_sync_exchange *se, int *n_ids, |
938 | const_char_ptr **doc_ids) |
939 | - int u1db__format_query(int n_fields, va_list argp, char **buf, int *wildcard) |
940 | + int u1db__format_query(int n_fields, const_char_ptr *values, char **buf, |
941 | + int *wildcard) |
942 | int u1db__get_sync_target(u1database *db, u1db_sync_target **sync_target) |
943 | int u1db__free_sync_target(u1db_sync_target **sync_target) |
944 | int u1db__sync_db_to_target(u1database *db, u1db_sync_target *target, |
945 | @@ -316,16 +324,6 @@ |
946 | return 0 |
947 | |
948 | |
949 | -cdef int _format_query_dotted(char **buf, int *wildcard, int n_fields, ...): |
950 | - cdef va_list argp |
951 | - cdef int status |
952 | - |
953 | - va_start_int(argp, n_fields) |
954 | - status = u1db__format_query(n_fields, argp, buf, wildcard) |
955 | - va_end(argp) |
956 | - return status |
957 | - |
958 | - |
959 | cdef int return_doc_cb_wrapper(void *context, u1db_document *doc, |
960 | int gen) with gil: |
961 | cdef CDocument pydoc |
962 | @@ -374,22 +372,16 @@ |
963 | cdef int status |
964 | cdef char *buf |
965 | cdef int wildcard[10] |
966 | + cdef const_char_ptr *values |
967 | + cdef int n_values |
968 | |
969 | - if len(fields) == 0: |
970 | - status = _format_query_dotted(&buf, wildcard, 0) |
971 | - elif len(fields) == 1: |
972 | - status = _format_query_dotted(&buf, wildcard, 1, <char*>fields[0]) |
973 | - elif len(fields) == 2: |
974 | - status = _format_query_dotted(&buf, wildcard, 2, <char*>fields[0], |
975 | - <char*>fields[1]) |
976 | - elif len(fields) == 3: |
977 | - status = _format_query_dotted(&buf, wildcard, 3, <char*>fields[0], |
978 | - <char*>fields[1], <char*>fields[2]) |
979 | - elif len(fields) == 4: |
980 | - status = _format_query_dotted(&buf, wildcard, 4, <char*>fields[0], |
981 | - <char*>fields[1], <char*>fields[2], <char *>fields[3]) |
982 | - else: |
983 | - status = U1DB_NOT_IMPLEMENTED |
984 | + # keep a reference to new_objs so that the pointers in expressions |
985 | + # remain valid. |
986 | + new_objs = _list_to_str_array(fields, &values, &n_values) |
987 | + try: |
988 | + status = u1db__format_query(n_values, values, &buf, wildcard) |
989 | + finally: |
990 | + free(<void*>values) |
991 | handle_status("format_query", status) |
992 | if buf == NULL: |
993 | res = None |
994 | @@ -1036,7 +1028,7 @@ |
995 | if status != U1DB_OK: |
996 | raise RuntimeError("Failed to _sync_exchange: %d" % (status,)) |
997 | |
998 | - def create_index(self, index_name, *index_expressions): |
999 | + def create_indexl(self, index_name, index_expressions): |
1000 | cdef const_char_ptr *expressions |
1001 | cdef int n_expressions |
1002 | |
1003 | @@ -1045,11 +1037,41 @@ |
1004 | new_objs = _list_to_str_array( |
1005 | index_expressions, &expressions, &n_expressions) |
1006 | try: |
1007 | - handle_status("create_index", |
1008 | - u1db_create_index( |
1009 | - self._db, index_name, n_expressions, expressions)) |
1010 | + status = u1db_create_indexl( |
1011 | + self._db, index_name, n_expressions, expressions) |
1012 | finally: |
1013 | free(<void*>expressions) |
1014 | + handle_status("create_index", status) |
1015 | + |
1016 | + def create_index(self, index_name, *index_expressions): |
1017 | + extra = [] |
1018 | + if len(index_expressions) == 0: |
1019 | + status = u1db_create_index(self._db, index_name, 0, NULL) |
1020 | + elif len(index_expressions) == 1: |
1021 | + status = u1db_create_index( |
1022 | + self._db, index_name, 1, |
1023 | + _ensure_str(index_expressions[0], extra)) |
1024 | + elif len(index_expressions) == 2: |
1025 | + status = u1db_create_index( |
1026 | + self._db, index_name, 2, |
1027 | + _ensure_str(index_expressions[0], extra), |
1028 | + _ensure_str(index_expressions[1], extra)) |
1029 | + elif len(index_expressions) == 3: |
1030 | + status = u1db_create_index( |
1031 | + self._db, index_name, 3, |
1032 | + _ensure_str(index_expressions[0], extra), |
1033 | + _ensure_str(index_expressions[1], extra), |
1034 | + _ensure_str(index_expressions[2], extra)) |
1035 | + elif len(index_expressions) == 4: |
1036 | + status = u1db_create_index( |
1037 | + self._db, index_name, 4, |
1038 | + _ensure_str(index_expressions[0], extra), |
1039 | + _ensure_str(index_expressions[1], extra), |
1040 | + _ensure_str(index_expressions[2], extra), |
1041 | + _ensure_str(index_expressions[3], extra)) |
1042 | + else: |
1043 | + status = U1DB_NOT_IMPLEMENTED |
1044 | + handle_status("create_index", status) |
1045 | |
1046 | def list_indexes(self): |
1047 | a_list = [] |
1048 | @@ -1062,9 +1084,29 @@ |
1049 | handle_status("delete_index", |
1050 | u1db_delete_index(self._db, index_name)) |
1051 | |
1052 | + def get_from_indexl(self, index_name, key_values): |
1053 | + cdef const_char_ptr *values |
1054 | + cdef int n_values |
1055 | + cdef CQuery query |
1056 | + |
1057 | + query = self._query_init(index_name) |
1058 | + res = [] |
1059 | + # keep a reference to new_objs so that the pointers in expressions |
1060 | + # remain valid. |
1061 | + new_objs = _list_to_str_array(key_values, &values, &n_values) |
1062 | + try: |
1063 | + handle_status( |
1064 | + "get_from_index", u1db_get_from_indexl( |
1065 | + self._db, query._query, <void*>res, _append_doc_to_list, |
1066 | + n_values, values)) |
1067 | + finally: |
1068 | + free(<void*>values) |
1069 | + return res |
1070 | + |
1071 | def get_from_index(self, index_name, *key_values): |
1072 | cdef CQuery query |
1073 | cdef int status |
1074 | + |
1075 | extra = [] |
1076 | query = self._query_init(index_name) |
1077 | res = [] |
1078 | @@ -1099,6 +1141,42 @@ |
1079 | handle_status("get_from_index", status) |
1080 | return res |
1081 | |
1082 | + def get_range_from_index(self, index_name, start_value=None, |
1083 | + end_value=None): |
1084 | + cdef CQuery query |
1085 | + cdef const_char_ptr *start_values |
1086 | + cdef int n_values |
1087 | + cdef const_char_ptr *end_values |
1088 | + |
1089 | + if start_value is not None: |
1090 | + if isinstance(start_value, basestring): |
1091 | + start_value = (start_value,) |
1092 | + new_objs_1 = _list_to_str_array( |
1093 | + start_value, &start_values, &n_values) |
1094 | + else: |
1095 | + n_values = 0 |
1096 | + start_values = NULL |
1097 | + if end_value is not None: |
1098 | + if isinstance(end_value, basestring): |
1099 | + end_value = (end_value,) |
1100 | + new_objs_2 = _list_to_str_array( |
1101 | + end_value, &end_values, &n_values) |
1102 | + else: |
1103 | + end_values = NULL |
1104 | + query = self._query_init(index_name) |
1105 | + res = [] |
1106 | + try: |
1107 | + handle_status("get_range_from_index", |
1108 | + u1db_get_range_from_index( |
1109 | + self._db, query._query, <void*>res, _append_doc_to_list, |
1110 | + n_values, start_values, end_values)) |
1111 | + finally: |
1112 | + if start_values != NULL: |
1113 | + free(<void*>start_values) |
1114 | + if end_values != NULL: |
1115 | + free(<void*>end_values) |
1116 | + return res |
1117 | + |
1118 | def get_index_keys(self, index_name): |
1119 | cdef int status |
1120 | result = [] |
1121 | |
1122 | === modified file 'u1db/tests/test_backends.py' |
1123 | --- u1db/tests/test_backends.py 2012-05-31 18:51:20 +0000 |
1124 | +++ u1db/tests/test_backends.py 2012-06-06 17:13:17 +0000 |
1125 | @@ -814,6 +814,166 @@ |
1126 | [doc4, doc3, doc2, doc1], |
1127 | self.db.get_from_index('test-idx', 'v*', '*')) |
1128 | |
1129 | + def test_get_range_from_index_start_end(self): |
1130 | + doc1 = self.db.create_doc('{"key": "value3"}') |
1131 | + doc2 = self.db.create_doc('{"key": "value2"}') |
1132 | + self.db.create_doc('{"key": "value4"}') |
1133 | + self.db.create_doc('{"key": "value1"}') |
1134 | + self.db.create_index('test-idx', 'key') |
1135 | + self.assertEqual( |
1136 | + [doc2, doc1], |
1137 | + self.db.get_range_from_index('test-idx', 'value2', 'value3')) |
1138 | + |
1139 | + def test_get_range_from_index_start(self): |
1140 | + doc1 = self.db.create_doc('{"key": "value3"}') |
1141 | + doc2 = self.db.create_doc('{"key": "value2"}') |
1142 | + doc3 = self.db.create_doc('{"key": "value4"}') |
1143 | + self.db.create_doc('{"key": "value1"}') |
1144 | + self.db.create_index('test-idx', 'key') |
1145 | + self.assertEqual( |
1146 | + [doc2, doc1, doc3], |
1147 | + self.db.get_range_from_index('test-idx', 'value2')) |
1148 | + |
1149 | + def test_get_range_from_index_end(self): |
1150 | + self.db.create_doc('{"key": "value3"}') |
1151 | + doc2 = self.db.create_doc('{"key": "value2"}') |
1152 | + self.db.create_doc('{"key": "value4"}') |
1153 | + doc4 = self.db.create_doc('{"key": "value1"}') |
1154 | + self.db.create_index('test-idx', 'key') |
1155 | + self.assertEqual( |
1156 | + [doc4, doc2], |
1157 | + self.db.get_range_from_index('test-idx', None, 'value2')) |
1158 | + |
1159 | + def test_get_wildcard_range_from_index_start(self): |
1160 | + doc1 = self.db.create_doc('{"key": "value4"}') |
1161 | + doc2 = self.db.create_doc('{"key": "value23"}') |
1162 | + doc3 = self.db.create_doc('{"key": "value2"}') |
1163 | + doc4 = self.db.create_doc('{"key": "value22"}') |
1164 | + self.db.create_doc('{"key": "value1"}') |
1165 | + self.db.create_index('test-idx', 'key') |
1166 | + self.assertEqual( |
1167 | + [doc3, doc4, doc2, doc1], |
1168 | + self.db.get_range_from_index('test-idx', 'value2*')) |
1169 | + |
1170 | + def test_get_wildcard_range_from_index_end(self): |
1171 | + self.db.create_doc('{"key": "value4"}') |
1172 | + doc2 = self.db.create_doc('{"key": "value23"}') |
1173 | + doc3 = self.db.create_doc('{"key": "value2"}') |
1174 | + doc4 = self.db.create_doc('{"key": "value22"}') |
1175 | + doc5 = self.db.create_doc('{"key": "value1"}') |
1176 | + self.db.create_index('test-idx', 'key') |
1177 | + self.assertEqual( |
1178 | + [doc5, doc3, doc4, doc2], |
1179 | + self.db.get_range_from_index('test-idx', None, 'value2*')) |
1180 | + |
1181 | + def test_get_wildcard_range_from_index_start_end(self): |
1182 | + self.db.create_doc('{"key": "a"}') |
1183 | + doc2 = self.db.create_doc('{"key": "boo3"}') |
1184 | + doc3 = self.db.create_doc('{"key": "catalyst"}') |
1185 | + doc4 = self.db.create_doc('{"key": "whaever"}') |
1186 | + doc5 = self.db.create_doc('{"key": "zerg"}') |
1187 | + self.db.create_index('test-idx', 'key') |
1188 | + self.assertEqual( |
1189 | + [doc3, doc4], |
1190 | + self.db.get_range_from_index('test-idx', 'cat*', 'zap*')) |
1191 | + |
1192 | + def test_get_range_from_index_multi_column_start_end(self): |
1193 | + self.db.create_doc('{"key": "value3", "key2": "value4"}') |
1194 | + doc2 = self.db.create_doc('{"key": "value2", "key2": "value3"}') |
1195 | + doc3 = self.db.create_doc('{"key": "value2", "key2": "value2"}') |
1196 | + self.db.create_doc('{"key": "value1", "key2": "value1"}') |
1197 | + self.db.create_index('test-idx', 'key', 'key2') |
1198 | + self.assertEqual( |
1199 | + [doc3, doc2], |
1200 | + self.db.get_range_from_index( |
1201 | + 'test-idx', ('value2', 'value2'), ('value2', 'value3'))) |
1202 | + |
1203 | + def test_get_range_from_index_multi_column_start(self): |
1204 | + doc1 = self.db.create_doc('{"key": "value3", "key2": "value4"}') |
1205 | + doc2 = self.db.create_doc('{"key": "value2", "key2": "value3"}') |
1206 | + self.db.create_doc('{"key": "value2", "key2": "value2"}') |
1207 | + self.db.create_doc('{"key": "value1", "key2": "value1"}') |
1208 | + self.db.create_index('test-idx', 'key', 'key2') |
1209 | + self.assertEqual( |
1210 | + [doc2, doc1], |
1211 | + self.db.get_range_from_index('test-idx', ('value2', 'value3'))) |
1212 | + |
1213 | + def test_get_range_from_index_multi_column_end(self): |
1214 | + self.db.create_doc('{"key": "value3", "key2": "value4"}') |
1215 | + doc2 = self.db.create_doc('{"key": "value2", "key2": "value3"}') |
1216 | + doc3 = self.db.create_doc('{"key": "value2", "key2": "value2"}') |
1217 | + doc4 = self.db.create_doc('{"key": "value1", "key2": "value1"}') |
1218 | + self.db.create_index('test-idx', 'key', 'key2') |
1219 | + self.assertEqual( |
1220 | + [doc4, doc3, doc2], |
1221 | + self.db.get_range_from_index( |
1222 | + 'test-idx', None, ('value2', 'value3'))) |
1223 | + |
1224 | + def test_get_wildcard_range_from_index_multi_column_start(self): |
1225 | + doc1 = self.db.create_doc('{"key": "value3", "key2": "value4"}') |
1226 | + doc2 = self.db.create_doc('{"key": "value2", "key2": "value23"}') |
1227 | + doc3 = self.db.create_doc('{"key": "value2", "key2": "value2"}') |
1228 | + self.db.create_doc('{"key": "value1", "key2": "value1"}') |
1229 | + self.db.create_index('test-idx', 'key', 'key2') |
1230 | + self.assertEqual( |
1231 | + [doc3, doc2, doc1], |
1232 | + self.db.get_range_from_index('test-idx', ('value2', 'value2*'))) |
1233 | + |
1234 | + def test_get_wildcard_range_from_index_multi_column_end(self): |
1235 | + self.db.create_doc('{"key": "value3", "key2": "value4"}') |
1236 | + doc2 = self.db.create_doc('{"key": "value2", "key2": "value23"}') |
1237 | + doc3 = self.db.create_doc('{"key": "value2", "key2": "value2"}') |
1238 | + doc4 = self.db.create_doc('{"key": "value1", "key2": "value1"}') |
1239 | + self.db.create_index('test-idx', 'key', 'key2') |
1240 | + self.assertEqual( |
1241 | + [doc4, doc3, doc2], |
1242 | + self.db.get_range_from_index( |
1243 | + 'test-idx', None, ('value2', 'value2*'))) |
1244 | + |
1245 | + def test_get_glob_range_from_index_multi_column_start(self): |
1246 | + doc1 = self.db.create_doc('{"key": "value3", "key2": "value4"}') |
1247 | + doc2 = self.db.create_doc('{"key": "value2", "key2": "value23"}') |
1248 | + self.db.create_doc('{"key": "value1", "key2": "value2"}') |
1249 | + self.db.create_doc('{"key": "value1", "key2": "value1"}') |
1250 | + self.db.create_index('test-idx', 'key', 'key2') |
1251 | + self.assertEqual( |
1252 | + [doc2, doc1], |
1253 | + self.db.get_range_from_index('test-idx', ('value2', '*'))) |
1254 | + |
1255 | + def test_get_glob_range_from_index_multi_column_end(self): |
1256 | + self.db.create_doc('{"key": "value3", "key2": "value4"}') |
1257 | + doc2 = self.db.create_doc('{"key": "value2", "key2": "value23"}') |
1258 | + doc3 = self.db.create_doc('{"key": "value1", "key2": "value2"}') |
1259 | + doc4 = self.db.create_doc('{"key": "value1", "key2": "value1"}') |
1260 | + self.db.create_index('test-idx', 'key', 'key2') |
1261 | + self.assertEqual( |
1262 | + [doc4, doc3, doc2], |
1263 | + self.db.get_range_from_index('test-idx', None, ('value2', '*'))) |
1264 | + |
1265 | + def test_get_range_from_index_illegal_wildcard_order(self): |
1266 | + self.db.create_index('test-idx', 'k1', 'k2') |
1267 | + self.assertRaises( |
1268 | + errors.InvalidGlobbing, |
1269 | + self.db.get_range_from_index, 'test-idx', ('*', 'v2')) |
1270 | + |
1271 | + def test_get_range_from_index_illegal_glob_after_wildcard(self): |
1272 | + self.db.create_index('test-idx', 'k1', 'k2') |
1273 | + self.assertRaises( |
1274 | + errors.InvalidGlobbing, |
1275 | + self.db.get_range_from_index, 'test-idx', ('*', 'v*')) |
1276 | + |
1277 | + def test_get_range_from_index_illegal_wildcard_order_end(self): |
1278 | + self.db.create_index('test-idx', 'k1', 'k2') |
1279 | + self.assertRaises( |
1280 | + errors.InvalidGlobbing, |
1281 | + self.db.get_range_from_index, 'test-idx', None, ('*', 'v2')) |
1282 | + |
1283 | + def test_get_range_from_index_illegal_glob_after_wildcard_end(self): |
1284 | + self.db.create_index('test-idx', 'k1', 'k2') |
1285 | + self.assertRaises( |
1286 | + errors.InvalidGlobbing, |
1287 | + self.db.get_range_from_index, 'test-idx', None, ('*', 'v*')) |
1288 | + |
1289 | def test_get_from_index_fails_if_no_index(self): |
1290 | self.assertRaises( |
1291 | errors.IndexDoesNotExist, self.db.get_from_index, 'foo') |
1292 | |
1293 | === modified file 'u1db/tests/test_c_backend.py' |
1294 | --- u1db/tests/test_c_backend.py 2012-05-31 18:51:20 +0000 |
1295 | +++ u1db/tests/test_c_backend.py 2012-06-06 17:13:17 +0000 |
1296 | @@ -93,6 +93,15 @@ |
1297 | " VALUES ('doc-id', 'doc-rev', '{}')") |
1298 | self.assertRaises(Exception, self.db.get_doc_conflicts, 'doc-id') |
1299 | |
1300 | + def test_create_indexl(self): |
1301 | + # We manually poke data into the DB, so that we test just the "get_doc" |
1302 | + # code, rather than also testing the index management code. |
1303 | + self.db = c_backend_wrapper.CDatabase(':memory:') |
1304 | + doc = self.db.create_doc(tests.simple_doc) |
1305 | + self.db.create_indexl("key-idx", ["key"]) |
1306 | + docs = self.db.get_from_index('key-idx', 'value') |
1307 | + self.assertEqual([doc], docs) |
1308 | + |
1309 | def test_get_from_index(self): |
1310 | # We manually poke data into the DB, so that we test just the "get_doc" |
1311 | # code, rather than also testing the index management code. |
1312 | @@ -102,6 +111,15 @@ |
1313 | docs = self.db.get_from_index('key-idx', 'value') |
1314 | self.assertEqual([doc], docs) |
1315 | |
1316 | + def test_get_from_indexl(self): |
1317 | + # We manually poke data into the DB, so that we test just the "get_doc" |
1318 | + # code, rather than also testing the index management code. |
1319 | + self.db = c_backend_wrapper.CDatabase(':memory:') |
1320 | + doc = self.db.create_doc(tests.simple_doc) |
1321 | + self.db.create_index("key-idx", "key") |
1322 | + docs = self.db.get_from_indexl('key-idx', ['value']) |
1323 | + self.assertEqual([doc], docs) |
1324 | + |
1325 | def test_get_from_index_2(self): |
1326 | self.db = c_backend_wrapper.CDatabase(':memory:') |
1327 | doc = self.db.create_doc(tests.nested_doc) |