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

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
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)

Subscribers

People subscribed via source and target branches