|
14 | 14 | #include "SQLiteDataFile.hh"
|
15 | 15 | #include "QueryTranslator.hh"
|
16 | 16 | #include "SQLUtil.hh"
|
| 17 | +#include "SecureDigest.hh" |
17 | 18 | #include "StringUtil.hh"
|
18 | 19 | #include "Array.hh"
|
19 | 20 |
|
20 | 21 | using namespace std;
|
21 |
| -using namespace fleece; |
22 | 22 | using namespace fleece::impl;
|
23 | 23 |
|
24 | 24 | namespace litecore {
|
25 | 25 |
|
26 | 26 | bool SQLiteKeyStore::createArrayIndex(const IndexSpec& spec) {
|
| 27 | + auto currSpec = db().getIndex(spec.name); |
| 28 | + if ( currSpec ) { |
| 29 | + // If there is already index with the index name, |
| 30 | + // eiher delete the current one, or use it (return false) |
| 31 | + bool same = true; |
| 32 | + if ( currSpec->type != IndexSpec::kArray || !currSpec->arrayOptions() |
| 33 | + || currSpec->arrayOptions()->unnestPath != spec.arrayOptions()->unnestPath || !currSpec->what() |
| 34 | + || !spec.what() ) |
| 35 | + same = false; |
| 36 | + else { |
| 37 | + alloc_slice currWhat = FLValue_ToJSON(FLValue(currSpec->what())); |
| 38 | + alloc_slice specWhat = FLValue_ToJSON(FLValue(spec.what())); |
| 39 | + if ( currWhat != specWhat ) same = false; |
| 40 | + } |
| 41 | + |
| 42 | + if ( same ) return false; |
| 43 | + else |
| 44 | + db().deleteIndex(*currSpec); |
| 45 | + } |
| 46 | + |
| 47 | + string plainTableName, unnestTableName; |
| 48 | + // the following will throw if !spec.arrayOptions() || !spec.arrayOptions()->unnestPath |
| 49 | + for ( Array::iterator itPath((const Array*)spec.unnestPaths()); itPath; ++itPath ) { |
| 50 | + std::tie(plainTableName, unnestTableName) = |
| 51 | + createUnnestedTable(itPath.value(), plainTableName, unnestTableName); |
| 52 | + } |
27 | 53 | Array::iterator iExprs((const Array*)spec.what());
|
28 |
| - string arrayTableName = createUnnestedTable(iExprs.value()); |
29 |
| - return createIndex(spec, arrayTableName, ++iExprs); |
| 54 | + return createIndex(spec, plainTableName, iExprs); |
30 | 55 | }
|
31 | 56 |
|
32 |
| - string SQLiteKeyStore::createUnnestedTable(const Value* expression) { |
| 57 | + std::pair<string, string> SQLiteKeyStore::createUnnestedTable(const Value* expression, string plainParentTable, |
| 58 | + string parentTable) { |
33 | 59 | // Derive the table name from the expression it unnests:
|
34 |
| - string kvTableName = tableName(); |
35 |
| - QueryTranslator qp(db(), "", kvTableName); |
36 |
| - string unnestTableName = qp.unnestedTableName(FLValue(expression)); |
| 60 | + if ( plainParentTable.empty() ) plainParentTable = parentTable = tableName(); |
| 61 | + QueryTranslator qp(db(), "", plainParentTable); |
| 62 | + string plainTableName = qp.unnestedTableName(FLValue(expression)); |
| 63 | + string unnestTableName = hexName(plainTableName); |
| 64 | + string quotedParentTable = CONCAT(sqlIdentifier(parentTable)); |
37 | 65 |
|
38 | 66 | // Create the index table, unless an identical one already exists:
|
39 | 67 | string sql = CONCAT("CREATE TABLE " << sqlIdentifier(unnestTableName)
|
40 | 68 | << " "
|
41 | 69 | "(docid INTEGER NOT NULL REFERENCES "
|
42 |
| - << sqlIdentifier(kvTableName) |
| 70 | + << sqlIdentifier(parentTable) |
43 | 71 | << "(rowid), "
|
44 | 72 | " i INTEGER NOT NULL,"
|
45 | 73 | " body BLOB NOT NULL, "
|
46 |
| - " CONSTRAINT pk PRIMARY KEY (docid, i)) " |
47 |
| - "WITHOUT ROWID"); |
| 74 | + " CONSTRAINT pk PRIMARY KEY (docid, i))"); |
48 | 75 | if ( !db().schemaExistsWithSQL(unnestTableName, "table", unnestTableName, sql) ) {
|
49 | 76 | LogTo(QueryLog, "Creating UNNEST table '%s' on %s", unnestTableName.c_str(),
|
50 | 77 | expression->toJSON(true).asString().c_str());
|
51 | 78 | db().exec(sql);
|
52 | 79 |
|
53 | 80 | qp.setBodyColumnName("new.body");
|
54 | 81 | string eachExpr = qp.eachExpressionSQL(FLValue(expression));
|
| 82 | + bool nested = plainParentTable.find(KeyStore::kUnnestSeparator) != string::npos; |
55 | 83 |
|
56 | 84 | // Populate the index-table with data from existing documents:
|
57 |
| - db().exec(CONCAT("INSERT INTO " << sqlIdentifier(unnestTableName) |
58 |
| - << " (docid, i, body) " |
59 |
| - "SELECT new.rowid, _each.rowid, _each.value " |
60 |
| - << "FROM " << sqlIdentifier(kvTableName) << " as new, " << eachExpr |
61 |
| - << " AS _each " |
62 |
| - "WHERE (new.flags & 1) = 0")); |
| 85 | + if ( !nested ) { |
| 86 | + db().exec(CONCAT("INSERT INTO " << sqlIdentifier(unnestTableName) |
| 87 | + << " (docid, i, body) " |
| 88 | + "SELECT new.rowid, _each.rowid, _each.value " |
| 89 | + << "FROM " << sqlIdentifier(parentTable) << " as new, " << eachExpr |
| 90 | + << " AS _each " |
| 91 | + "WHERE (new.flags & 1) = 0")); |
| 92 | + } else { |
| 93 | + db().exec(CONCAT("INSERT INTO " << sqlIdentifier(unnestTableName) |
| 94 | + << " (docid, i, body) " |
| 95 | + "SELECT new.rowid, _each.rowid, _each.value " |
| 96 | + << "FROM " << sqlIdentifier(parentTable) << " as new, " << eachExpr |
| 97 | + << " AS _each")); |
| 98 | + } |
63 | 99 |
|
64 | 100 | // Set up triggers to keep the index-table up to date
|
65 | 101 | // ...on insertion:
|
66 | 102 | string insertTriggerExpr = CONCAT("INSERT INTO " << sqlIdentifier(unnestTableName)
|
67 | 103 | << " (docid, i, body) "
|
68 | 104 | "SELECT new.rowid, _each.rowid, _each.value "
|
69 | 105 | << "FROM " << eachExpr << " AS _each ");
|
70 |
| - createTrigger(unnestTableName, "ins", "AFTER INSERT", "WHEN (new.flags & 1) = 0", insertTriggerExpr); |
71 |
| - |
72 | 106 | // ...on delete:
|
73 | 107 | string deleteTriggerExpr = CONCAT("DELETE FROM " << sqlIdentifier(unnestTableName)
|
74 | 108 | << " "
|
75 | 109 | "WHERE docid = old.rowid");
|
76 |
| - createTrigger(unnestTableName, "del", "BEFORE DELETE", "WHEN (old.flags & 1) = 0", deleteTriggerExpr); |
| 110 | + if ( !nested ) { |
| 111 | + createTrigger(unnestTableName, "ins", "AFTER INSERT", "WHEN (new.flags & 1) = 0", insertTriggerExpr, |
| 112 | + quotedParentTable); |
| 113 | + createTrigger(unnestTableName, "del", "BEFORE DELETE", "WHEN (old.flags & 1) = 0", deleteTriggerExpr, |
| 114 | + quotedParentTable); |
77 | 115 |
|
78 |
| - // ...on update: |
79 |
| - createTrigger(unnestTableName, "preupdate", "BEFORE UPDATE OF body, flags", "WHEN (old.flags & 1) = 0", |
80 |
| - deleteTriggerExpr); |
81 |
| - createTrigger(unnestTableName, "postupdate", "AFTER UPDATE OF body, flags", "WHEN (new.flags & 1 = 0)", |
82 |
| - insertTriggerExpr); |
| 116 | + // ...on update: |
| 117 | + createTrigger(unnestTableName, "preupdate", "BEFORE UPDATE OF body, flags", "WHEN (old.flags & 1) = 0", |
| 118 | + deleteTriggerExpr, quotedParentTable); |
| 119 | + createTrigger(unnestTableName, "postupdate", "AFTER UPDATE OF body, flags", "WHEN (new.flags & 1 = 0)", |
| 120 | + insertTriggerExpr, quotedParentTable); |
| 121 | + } else { |
| 122 | + createTrigger(unnestTableName, "ins", "AFTER INSERT", "", insertTriggerExpr, quotedParentTable); |
| 123 | + createTrigger(unnestTableName, "del", "BEFORE DELETE", "", deleteTriggerExpr, quotedParentTable); |
| 124 | + } |
83 | 125 | }
|
84 |
| - return unnestTableName; |
| 126 | + return {plainTableName, unnestTableName}; |
85 | 127 | }
|
86 | 128 |
|
87 | 129 | } // namespace litecore
|
0 commit comments