|
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