Line data Source code
1 : /**
2 : Copyright (c) 2021-2022 Roman Katuntsev <sbkarr@stappler.org>
3 : Copyright (c) 2023-2024 Stappler LLC <admin@stappler.dev>
4 :
5 : Permission is hereby granted, free of charge, to any person obtaining a copy
6 : of this software and associated documentation files (the "Software"), to deal
7 : in the Software without restriction, including without limitation the rights
8 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 : copies of the Software, and to permit persons to whom the Software is
10 : furnished to do so, subject to the following conditions:
11 :
12 : The above copyright notice and this permission notice shall be included in
13 : all copies or substantial portions of the Software.
14 :
15 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 : THE SOFTWARE.
22 : **/
23 :
24 : #include "SPSqliteHandle.h"
25 :
26 : namespace STAPPLER_VERSIONIZED stappler::db::sqlite {
27 :
28 : constexpr static uint32_t getDefaultFunctionVersion() { return 10; }
29 :
30 4323 : static BackendInterface::StorageType getStorageType(StringView type) {
31 4323 : if (type == "BIGINT") {
32 1325 : return BackendInterface::StorageType::Int8;
33 2997 : } else if (type == "INT" || type == "INTEGER") {
34 474 : return BackendInterface::StorageType::Int4;
35 2525 : } else if (type == "NUMERIC") {
36 0 : return BackendInterface::StorageType::Numeric;
37 2525 : } else if (type == "BOOLEAN") {
38 150 : return BackendInterface::StorageType::Bool;
39 2375 : } else if (type == "BLOB") {
40 925 : return BackendInterface::StorageType::Bytes;
41 1450 : } else if (type == "TEXT") {
42 825 : return BackendInterface::StorageType::Text;
43 625 : } else if (type == "REAL" || type == "DOUBLE") {
44 25 : return BackendInterface::StorageType::Float8;
45 : }
46 600 : return BackendInterface::StorageType::Unknown;
47 : }
48 :
49 : struct ColRec {
50 : using Type = BackendInterface::StorageType;
51 :
52 : enum Flags {
53 : None,
54 : IsNotNull,
55 : PrimaryKey
56 : };
57 :
58 : Type type = Type::Unknown;
59 : String custom;
60 : Flags flags = Flags::None;
61 :
62 : bool isNotNull() const;
63 :
64 10275 : ColRec(Type t, Flags flags = Flags::None) : type(t), flags(flags) { }
65 550 : ColRec(const StringView &t, Flags flags = Flags::None) : custom(t.str<Interface>()), flags(flags) {
66 550 : type = getStorageType(custom);
67 550 : }
68 : };
69 :
70 : SP_DEFINE_ENUM_AS_MASK(ColRec::Flags)
71 :
72 : struct IndexRec {
73 : Vector<String> fields;
74 : bool unique = false;
75 :
76 7350 : IndexRec(StringView str, bool unique = false) : fields(Vector<String>({str.str<Interface>()})), unique(unique) { }
77 772 : IndexRec(Vector<String> &&fields, bool unique = false) : fields(std::move(fields)), unique(unique) { }
78 : };
79 :
80 : struct TriggerRec {
81 : enum Type {
82 : Delete,
83 : Update,
84 : Insert
85 : };
86 :
87 : enum Bind {
88 : Before,
89 : After
90 : };
91 :
92 : Type type = Type::Delete;
93 : Bind bind = Bind::Before;
94 : const Field *rootField = nullptr;
95 : const Scheme *rootScheme = nullptr;
96 : String sourceTable;
97 : String sourceField;
98 : String targetTable;
99 : String targetField;
100 : String tagField;
101 : RemovePolicy onRemove = RemovePolicy::Null;
102 :
103 75 : TriggerRec(StringView def) {
104 75 : uint32_t valueIdx = 0;
105 375 : while (!def.empty()) {
106 300 : auto value = def.readUntil<StringView::Chars<':'>>();
107 300 : switch (valueIdx) {
108 75 : case 0:
109 75 : if (value == "BEFORE") {
110 0 : bind = Before;
111 75 : } else if (value == "AFTER") {
112 75 : bind = After;
113 : } else {
114 0 : return;
115 : }
116 75 : break;
117 75 : case 1:
118 75 : if (value == "DELETE") {
119 25 : type = Delete;
120 50 : } else if (value == "UPDATE") {
121 25 : type = Update;
122 25 : } else if (value == "INSERT") {
123 25 : type = Insert;
124 : } else {
125 0 : return;
126 : }
127 75 : break;
128 75 : case 2: {
129 75 : auto table = value.readUntil<StringView::Chars<'@'>>();
130 75 : if (value.is('@')) {
131 75 : ++ value;
132 75 : sourceTable = table.str<Interface>();
133 75 : sourceField = value.str<Interface>();
134 : } else {
135 0 : return;
136 : }
137 75 : break;
138 : }
139 75 : case 3: {
140 75 : auto table = value.readUntil<StringView::Chars<'@'>>();
141 75 : if (value.is('@')) {
142 75 : ++ value;
143 75 : targetTable = table.str<Interface>();
144 75 : targetField = value.str<Interface>();
145 : } else {
146 0 : return;
147 : }
148 75 : break;
149 : }
150 0 : case 4:
151 0 : if (value == "CASCADE") {
152 0 : onRemove = RemovePolicy::Cascade;
153 0 : } else if (value == "RESTRICT") {
154 0 : onRemove = RemovePolicy::Restrict;
155 0 : } else if (value == "REF") {
156 0 : onRemove = RemovePolicy::Reference;
157 0 : } else if (value == "SREF") {
158 0 : onRemove = RemovePolicy::StrongReference;
159 : } else {
160 0 : return;
161 : }
162 0 : break;
163 0 : default:
164 0 : break;
165 : }
166 300 : if (def.is(':')) {
167 225 : ++ def;
168 225 : ++ valueIdx;
169 : }
170 : }
171 0 : }
172 :
173 1650 : TriggerRec(Type t, Bind b, StringView sourceTable, StringView sourceField,
174 : StringView targetTable, StringView targetField, const Field *f = nullptr)
175 1650 : : type(t), bind(b), rootField(f)
176 1650 : , sourceTable(sourceTable.str<Interface>())
177 1650 : , sourceField(sourceField.str<Interface>())
178 1650 : , targetTable(targetTable.str<Interface>())
179 3300 : , targetField(targetField.str<Interface>()) { }
180 :
181 1650 : String makeName() const {
182 1650 : StringStream stream;
183 1650 : switch (bind) {
184 500 : case Bind::Before: stream << "ST_TRIGGER:BEFORE:"; break;
185 1150 : case Bind::After: stream << "ST_TRIGGER:AFTER:"; break;
186 : }
187 1650 : switch (type) {
188 1025 : case Type::Delete: stream << "DELETE:"; break;
189 450 : case Type::Update: stream << "UPDATE:"; break;
190 175 : case Type::Insert: stream << "INSERT:"; break;
191 : }
192 1650 : stream << sourceTable << "@" << sourceField << ":";
193 1650 : stream << targetTable << "@" << targetField;
194 1650 : switch (onRemove) {
195 1525 : case RemovePolicy::Null: break;
196 50 : case RemovePolicy::Cascade: stream << ":CASCADE"; break;
197 0 : case RemovePolicy::Restrict: stream << ":RESTRICT"; break;
198 25 : case RemovePolicy::Reference: stream << ":REF"; break;
199 50 : case RemovePolicy::StrongReference: stream << ":SREF"; break;
200 : }
201 3300 : return stream.str();
202 1650 : }
203 : };
204 :
205 : struct TableRec {
206 : using Scheme = db::Scheme;
207 :
208 : static Map<StringView, TableRec> parse(const Driver *driver, const BackendInterface::Config &cfg,
209 : const Map<StringView, const Scheme *> &s);
210 : static Map<StringView, TableRec> get(Handle &h, StringStream &stream);
211 :
212 : static void writeCompareResult(Handle &h, StringStream &stream,
213 : Map<StringView, TableRec> &required, Map<StringView, TableRec> &existed,
214 : const Map<StringView, const db::Scheme *> &s);
215 :
216 : TableRec();
217 : TableRec(const Driver *driver, const BackendInterface::Config &cfg, const db::Scheme *scheme);
218 :
219 : Map<String, ColRec> cols;
220 : Map<String, IndexRec> indexes;
221 : Map<String, TriggerRec> triggers;
222 : bool exists = false;
223 : bool valid = false;
224 : bool withOids = false;
225 : bool detached = false;
226 :
227 : const db::Scheme *viewScheme = nullptr;
228 : const db::FieldView *viewField = nullptr;
229 : };
230 :
231 3650 : static StringView getStorageTypeName(BackendInterface::StorageType type, StringView custom) {
232 3650 : switch (type) {
233 0 : case ColRec::Type::Unknown: return custom; break;
234 200 : case ColRec::Type::Bool: return "BOOLEAN"; break;
235 0 : case ColRec::Type::Float4: return "DOUBLE"; break;
236 50 : case ColRec::Type::Float8: return "DOUBLE"; break;
237 0 : case ColRec::Type::Int2: return "INT"; break;
238 0 : case ColRec::Type::Int4: return "INT"; break;
239 1950 : case ColRec::Type::Int8: return "BIGINT"; break;
240 650 : case ColRec::Type::Text: return "TEXT"; break;
241 0 : case ColRec::Type::VarChar: return "TEXT"; break;
242 0 : case ColRec::Type::Numeric: return "NUMERIC"; break;
243 800 : case ColRec::Type::Bytes: return "BLOB"; break;
244 0 : default: break;
245 : }
246 0 : return StringView();
247 : }
248 :
249 0 : bool ColRec::isNotNull() const { return (flags & Flags::IsNotNull) != Flags::None; }
250 :
251 150 : void TableRec::writeCompareResult(Handle &h, StringStream &outstream,
252 : Map<StringView, TableRec> &required, Map<StringView, TableRec> &existed,
253 : const Map<StringView, const db::Scheme *> &s) {
254 :
255 1575 : auto writeTriggerHeader = [&] (StringView name, TriggerRec &t, StringView updateField) {
256 1575 : outstream << "CREATE TRIGGER IF NOT EXISTS \"" << name << "\"";
257 1575 : switch (t.bind) {
258 500 : case TriggerRec::Before: outstream << " BEFORE"; break;
259 1075 : case TriggerRec::After: outstream << " AFTER"; break;
260 : }
261 1575 : switch (t.type) {
262 1000 : case TriggerRec::Delete: outstream << " DELETE"; break;
263 425 : case TriggerRec::Update: outstream << " UPDATE";
264 425 : if (!updateField.empty()) {
265 75 : outstream << " OF \"" << updateField << "\"";
266 : }
267 425 : break;
268 150 : case TriggerRec::Insert: outstream << " INSERT"; break;
269 : }
270 1575 : outstream << " ON \"" << t.sourceTable << "\" FOR EACH ROW";
271 1725 : };
272 :
273 1575 : auto writeTrigger = [&] (StringView name, TriggerRec &t) {
274 1575 : if (t.rootField) {
275 1275 : switch (t.rootField->getType()) {
276 75 : case Type::Array:
277 75 : writeTriggerHeader(name, t,StringView());
278 1650 : outstream << " BEGIN\n\tDELETE FROM \"" << t.targetTable << "\""
279 75 : " WHERE \"" << t.targetTable << "\".\"" << t.targetField << "\"=OLD.__oid;\nEND;\n";
280 75 : break;
281 500 : case Type::File:
282 : case Type::Image:
283 500 : writeTriggerHeader(name, t,StringView());
284 500 : switch (t.type) {
285 250 : case TriggerRec::Delete:
286 250 : outstream << " WHEN OLD.\"" << t.sourceField << "\" IS NOT NULL BEGIN"
287 250 : "\n\tINSERT OR IGNORE INTO __removed (__oid) VALUES (OLD.\"" << t.sourceField << "\");\nEND;\n";
288 250 : break;
289 250 : case TriggerRec::Update:
290 250 : outstream << " WHEN OLD.\"" << t.sourceField << "\" IS NOT NULL BEGIN"
291 250 : "\n\tINSERT OR IGNORE INTO __removed (__oid) VALUES (OLD.\"" << t.sourceField << "\");\nEND;\n";
292 250 : break;
293 0 : default:
294 0 : break;
295 : }
296 500 : break;
297 200 : case Type::Set:
298 200 : switch (static_cast<const db::FieldObject *>(t.rootField->getSlot())->onRemove) {
299 200 : case RemovePolicy::Reference:
300 : case RemovePolicy::StrongReference:
301 200 : writeTriggerHeader(name, t,StringView());
302 200 : outstream << " BEGIN\n\tDELETE FROM \"" << t.targetTable << "\" WHERE \""
303 200 : << t.targetTable << "\".\"" << t.targetField << "\"=OLD.\"" << t.sourceField << "\";\nEND;\n";
304 200 : break;
305 0 : default: break;
306 : }
307 200 : break;
308 100 : case Type::View:
309 100 : writeTriggerHeader(name, t,StringView());
310 100 : outstream << " BEGIN\n\tDELETE FROM \"" << t.targetTable << "\" WHERE \""
311 100 : << t.targetTable << "\".\"" << t.targetField << "\"=OLD.\"" << t.sourceField << "\";\nEND;\n";
312 100 : break;
313 250 : case Type::Object:
314 250 : switch (t.onRemove) {
315 50 : case RemovePolicy::Cascade:
316 50 : writeTriggerHeader(name, t,StringView());
317 50 : outstream << " BEGIN\n\tDELETE FROM \"" << t.targetTable << "\" WHERE \""
318 50 : << t.targetTable << "\".\"" << t.targetField << "\"=OLD.\"" << t.sourceField << "\";\nEND;\n";
319 50 : break;
320 0 : case RemovePolicy::Restrict:
321 0 : writeTriggerHeader(name, t,StringView());
322 : outstream << " BEGIN\n\tSELECT RAISE(ABORT, 'Restrict constraint failed on "
323 0 : << t.targetTable << "." << t.targetField << "' FROM \""<< t.targetTable << "\" WHERE \""
324 0 : << t.targetTable << "\".\"" << t.targetField << "\"=OLD.\"" << t.sourceField << "\";\nEND;\n";
325 0 : break;
326 150 : case RemovePolicy::Null:
327 : case RemovePolicy::Reference:
328 150 : writeTriggerHeader(name, t,StringView());
329 300 : outstream << " BEGIN\n\tUPDATE \"" << t.targetTable << "\" SET \"" << t.targetField << "\"=NULL WHERE \""
330 150 : << t.targetTable << "\".\"" << t.targetField << "\"=OLD.\"" << t.sourceField << "\";\nEND;\n";
331 150 : break;
332 50 : case RemovePolicy::StrongReference:
333 : // Reverse trigger
334 50 : switch (t.type) {
335 25 : case TriggerRec::Delete:
336 25 : writeTriggerHeader(name, t,StringView());
337 25 : outstream << " BEGIN\n\tDELETE FROM \"" << t.targetTable << "\" WHERE \""
338 25 : << t.targetTable << "\".\"" << t.targetField << "\"=OLD.\"" << t.sourceField << "\";\nEND;\n";
339 25 : break;
340 25 : case TriggerRec::Update:
341 25 : writeTriggerHeader(name, t, t.sourceField);
342 25 : outstream << " WHEN OLD.\"" << t.sourceField << "\" IS NOT NULL BEGIN"
343 25 : "\n\tDELETE FROM \"" << t.targetTable << "\" WHERE \""
344 25 : << t.targetTable << "\".\"" << t.targetField << "\"=OLD.\"" << t.sourceField << "\";\nEND;\n";
345 25 : break;
346 0 : default:
347 0 : break;
348 : }
349 50 : break;
350 : }
351 250 : break;
352 150 : case Type::FullTextView:
353 150 : writeTriggerHeader(name, t, t.sourceField);
354 150 : switch (t.type) {
355 50 : case TriggerRec::Delete:
356 50 : outstream << " WHEN OLD.\"" << t.sourceField << "\" IS NOT NULL BEGIN\n"
357 50 : "\tSELECT sp_ts_update(OLD.__oid, OLD.\"" << t.sourceField << "\", "
358 150 : "'" << t.sourceTable << "', '" << t.sourceField << "', '" << t.targetTable << "', 2);"
359 50 : "\nEND;\n";
360 50 : break;
361 50 : case TriggerRec::Update:
362 : outstream << " BEGIN\n"
363 50 : "\tSELECT sp_ts_update(OLD.__oid, NEW.\"" << t.sourceField << "\", "
364 150 : "'" << t.sourceTable << "', '" << t.sourceField << "', '" << t.targetTable << "', 1);"
365 50 : "\nEND;\n";
366 50 : break;
367 50 : case TriggerRec::Insert:
368 50 : outstream << " WHEN NEW.\"" << t.sourceField << "\" IS NOT NULL BEGIN\n"
369 50 : "\tSELECT sp_ts_update(NEW.__oid, NEW.\"" << t.sourceField << "\", "
370 150 : "'" << t.sourceTable << "', '" << t.sourceField << "', '" << t.targetTable << "', 0);"
371 50 : "\nEND;\n";
372 50 : break;
373 : }
374 150 : break;
375 0 : default:
376 0 : break;
377 : }
378 300 : } else if (t.rootScheme && t.rootScheme->hasDelta()) {
379 225 : switch (t.type) {
380 75 : case TriggerRec::Delete:
381 75 : writeTriggerHeader(name, t,StringView());
382 : outstream << " BEGIN"
383 75 : "\n\tINSERT INTO " << t.targetTable << "(\"object\",\"action\",\"time\",\"user\") VALUES"
384 75 : "(OLD.__oid," << stappler::toInt(DeltaAction::Delete) << ",sp_sqlite_now(),sp_sqlite_user());"
385 75 : "\nEND;\n";
386 75 : break;
387 75 : case TriggerRec::Update:
388 75 : writeTriggerHeader(name, t,StringView());
389 : outstream << " BEGIN"
390 75 : "\n\tINSERT INTO " << t.targetTable << "(\"object\",\"action\",\"time\",\"user\") VALUES"
391 75 : "(NEW.__oid," << stappler::toInt(DeltaAction::Update) << ",sp_sqlite_now(),sp_sqlite_user());"
392 75 : "\nEND;\n";
393 75 : break;
394 75 : case TriggerRec::Insert:
395 75 : writeTriggerHeader(name, t,StringView());
396 : outstream << " BEGIN"
397 75 : "\n\tINSERT INTO " << t.targetTable << "(\"object\",\"action\",\"time\",\"user\") VALUES"
398 75 : "(NEW.__oid," << stappler::toInt(DeltaAction::Create) << ",sp_sqlite_now(),sp_sqlite_user());"
399 75 : "\nEND;\n";
400 75 : break;
401 : }
402 75 : } else if (t.sourceField == "__delta") {
403 75 : switch (t.type) {
404 25 : case TriggerRec::Delete:
405 25 : writeTriggerHeader(name, t,StringView());
406 : outstream << " BEGIN"
407 25 : "\n\tINSERT INTO " << t.targetTable << "(\"tag\",\"object\",\"time\",\"user\") VALUES"
408 50 : "(OLD.\"" << t.tagField << "\",OLD.\"" << t.targetField << "\",sp_sqlite_now(),sp_sqlite_user());"
409 25 : "\nEND;\n";
410 25 : break;
411 25 : case TriggerRec::Update:
412 25 : writeTriggerHeader(name, t,StringView());
413 : outstream << " BEGIN"
414 25 : "\n\tINSERT INTO " << t.targetTable << "(\"tag\",\"object\",\"time\",\"user\") VALUES"
415 50 : "(NEW.\"" << t.tagField << "\",NEW.\"" << t.targetField << "\",sp_sqlite_now(),sp_sqlite_user());"
416 25 : "\nEND;\n";
417 25 : break;
418 25 : case TriggerRec::Insert:
419 25 : writeTriggerHeader(name, t,StringView());
420 : outstream << " BEGIN"
421 25 : "\n\tINSERT INTO " << t.targetTable << "(\"tag\",\"object\",\"time\",\"user\") VALUES"
422 50 : "(NEW.\"" << t.tagField << "\",NEW.\"" << t.targetField << "\",sp_sqlite_now(),sp_sqlite_user());"
423 25 : "\nEND;\n";
424 25 : break;
425 : }
426 : }
427 1575 : };
428 :
429 1241 : for (auto &ex_it : existed) {
430 1091 : auto req_it = required.find(ex_it.first);
431 1092 : if (req_it != required.end()) {
432 50 : auto &req_t = req_it->second;
433 50 : auto &ex_t = ex_it.second;
434 50 : req_t.exists = true;
435 :
436 225 : for (auto &ex_idx_it : ex_t.indexes) {
437 175 : auto req_idx_it = req_t.indexes.find(ex_idx_it.first);
438 175 : if (req_idx_it == req_t.indexes.end()) {
439 : // index is not required any more, drop it
440 0 : outstream << "DROP INDEX IF EXISTS \"" << ex_idx_it.first << "\";\n";
441 : } else {
442 175 : req_t.indexes.erase(req_idx_it);
443 : }
444 : }
445 :
446 375 : for (auto &ex_col_it : ex_t.cols) {
447 325 : if (ex_col_it.first == "__oid") {
448 25 : continue;
449 : }
450 :
451 300 : auto req_col_it = req_t.cols.find(ex_col_it.first);
452 300 : if (req_col_it == req_t.cols.end()) {
453 0 : outstream << "ALTER TABLE \"" << ex_it.first << "\" DROP COLUMN \"" << ex_col_it.first << "\";\n";
454 : } else {
455 300 : auto &req_col = req_col_it->second;
456 300 : auto &ex_col = ex_col_it.second;
457 :
458 300 : auto req_type = req_col.type;
459 :
460 300 : if (req_type != ex_col.type) {
461 0 : outstream << "ALTER TABLE \"" << ex_it.first << "\" DROP COLUMN \"" << ex_col_it.first << "\";\n";
462 300 : } else if (ex_col.type == ColRec::Type::Unknown && req_type == ColRec::Type::Unknown && ex_col.custom != req_col.custom) {
463 0 : outstream << "ALTER TABLE \"" << ex_it.first << "\" DROP COLUMN \"" << ex_col_it.first << "\";\n";
464 : } else {
465 300 : req_t.cols.erase(req_col_it);
466 : }
467 : }
468 : }
469 :
470 125 : for (auto &ex_tgr_it : ex_t.triggers) {
471 75 : auto req_tgr_it = req_t.triggers.find(ex_tgr_it.first);
472 75 : if (req_tgr_it == req_t.triggers.end()) {
473 0 : outstream << "DROP TRIGGER IF EXISTS \"" << ex_tgr_it.first << "\";\n";
474 : } else {
475 75 : req_t.triggers.erase(ex_tgr_it.first);
476 : }
477 : }
478 : }
479 : }
480 :
481 : // write table structs
482 923 : for (auto &it : required) {
483 775 : auto &t = it.second;
484 775 : if (!it.second.exists) {
485 725 : outstream << "CREATE TABLE IF NOT EXISTS \"" << it.first << "\" (\n";
486 :
487 725 : bool first = true;
488 725 : if (it.second.withOids) {
489 375 : first = false;
490 375 : if (it.second.detached) {
491 25 : outstream << "\t\"__oid\" INTEGER PRIMARY KEY AUTOINCREMENT";
492 : } else {
493 350 : outstream << "\t\"__oid\" INTEGER DEFAULT (sp_sqlite_next_oid())";
494 : }
495 : }
496 :
497 4375 : for (auto cit = t.cols.begin(); cit != t.cols.end(); cit ++) {
498 3650 : if (first) { first = false; } else { outstream << ",\n"; }
499 7300 : outstream << "\t\"" << cit->first << "\" "
500 3650 : << getStorageTypeName(cit->second.type, cit->second.custom);
501 :
502 3650 : if ((cit->second.flags & ColRec::IsNotNull) != ColRec::None) {
503 625 : outstream << " NOT NULL";
504 : }
505 :
506 3650 : if (!it.second.withOids && (cit->second.flags & ColRec::PrimaryKey) != ColRec::Flags::None) {
507 75 : outstream << " PRIMARY KEY";
508 : }
509 : }
510 :
511 725 : outstream << "\n);\n";
512 : } else {
513 50 : for (auto cit : t.cols) {
514 0 : if (cit.first != "__oid") {
515 0 : outstream << "ALTER TABLE \"" << it.first << "\" ADD COLUMN \"" << cit.first << "\" "
516 0 : << getStorageTypeName(cit.second.type, cit.second.custom);
517 0 : if ((cit.second.flags & ColRec::Flags::IsNotNull) != ColRec::Flags::None) {
518 0 : outstream << " NOT NULL";
519 : }
520 0 : outstream << ";\n";
521 : }
522 0 : }
523 : }
524 : }
525 :
526 : // indexes
527 925 : for (auto &it : required) {
528 2725 : for (auto & cit : it.second.indexes) {
529 1950 : outstream << "CREATE";
530 1950 : if (cit.second.unique) {
531 250 : outstream << " UNIQUE";
532 : }
533 1950 : outstream << " INDEX IF NOT EXISTS \"" << cit.first << "\" ON \"" << it.first << "\"";
534 1950 : if (cit.second.fields.size() == 1 && cit.second.fields.front().back() == ')') {
535 100 : outstream << " " << cit.second.fields.front() << ";\n";
536 : } else {
537 1850 : outstream << " (";
538 1850 : bool first = true;
539 3700 : for (auto &field : cit.second.fields) {
540 1850 : if (first) { first = false; } else { outstream << ","; }
541 1850 : outstream << "\"" << field << "\"";
542 : }
543 1850 : outstream << ");\n";
544 : }
545 : }
546 :
547 775 : if (!it.second.triggers.empty()) {
548 1900 : for (auto & tit : it.second.triggers) {
549 1575 : writeTrigger(tit.first, tit.second);
550 : }
551 :
552 : /*auto scheme_it = s.find(it.first);
553 : if (scheme_it != s.end()) {
554 : for (auto & tit : it.second.triggers) {
555 : if (StringView(tit).starts_with("_tr_a_")) {
556 : writeAfterTrigger(stream, scheme_it->second, tit);
557 : } else {
558 : writeBeforeTrigger(stream, scheme_it->second, tit);
559 : }
560 : }
561 : } else if (it.second.viewField) {
562 : for (auto & tit : it.second.triggers) {
563 : writeDeltaTrigger(stream, it.first, it.second, tit);
564 : }
565 : }*/
566 : }
567 : }
568 150 : }
569 :
570 148 : Map<StringView, TableRec> TableRec::parse(const Driver *driver, const BackendInterface::Config &cfg,
571 : const Map<StringView, const db::Scheme *> &s) {
572 148 : Map<StringView, TableRec> tables;
573 549 : for (auto &it : s) {
574 400 : auto scheme = it.second;
575 400 : tables.emplace(scheme->getName(), TableRec(driver, cfg, scheme));
576 : }
577 :
578 549 : for (auto &it : s) {
579 400 : auto scheme = it.second;
580 400 : auto tableIt = tables.find(scheme->getName());
581 400 : if (tableIt == tables.end()) {
582 0 : continue;
583 : }
584 400 : auto schemeTable = &tableIt->second;
585 :
586 : // check for extra tables
587 3625 : for (auto &fit : scheme->getFields()) {
588 3225 : auto &f = fit.second;
589 3225 : auto type = fit.second.getType();
590 :
591 3225 : switch (type) {
592 175 : case db::Type::Set: {
593 175 : auto ref = static_cast<const db::FieldObject *>(f.getSlot());
594 175 : if (ref->onRemove == db::RemovePolicy::Reference || ref->onRemove == db::RemovePolicy::StrongReference) {
595 : // create many-to-many table link
596 75 : String name = toString(it.first, "_f_", fit.first);
597 75 : auto & source = it.first;
598 75 : auto target = ref->scheme->getName();
599 75 : TableRec table;
600 75 : table.cols.emplace(toString(source, "_id"), ColRec(ColRec::Type::Int8, ColRec::IsNotNull));
601 75 : table.cols.emplace(toString(target, "_id"), ColRec(ColRec::Type::Int8, ColRec::IsNotNull));
602 :
603 75 : table.indexes.emplace(toString(name, "_idx_", source), toString(source, "_id"));
604 75 : table.indexes.emplace(toString(name, "_idx_", target), toString(target, "_id"));
605 :
606 75 : auto extraTable = &tables.emplace(StringView(name).pdup(), std::move(table)).first->second;
607 :
608 : do {
609 : TriggerRec trigger(TriggerRec::Delete, TriggerRec::Before, source, "__oid",
610 75 : name, toString(source, "_id"));
611 75 : trigger.rootField = &fit.second;
612 75 : auto triggerName = trigger.makeName();
613 75 : schemeTable->triggers.emplace(std::move(triggerName), std::move(trigger));
614 75 : } while (0);
615 :
616 : do {
617 75 : auto targetIt = tables.find(target);
618 75 : if (targetIt != tables.end()) {
619 : TriggerRec trigger(TriggerRec::Delete, TriggerRec::After, target, "__oid",
620 75 : name, toString(target, "_id"));
621 75 : trigger.rootField = &fit.second;
622 75 : trigger.rootScheme = scheme;
623 75 : auto triggerName = trigger.makeName();
624 75 : targetIt->second.triggers.emplace(std::move(triggerName), std::move(trigger));
625 75 : }
626 :
627 75 : if (ref->onRemove == db::RemovePolicy::StrongReference && targetIt != tables.end()) {
628 50 : TriggerRec trigger(TriggerRec::Delete, TriggerRec::Before, name, toString(target, "_id"),
629 100 : target, "__oid");
630 50 : trigger.rootField = &fit.second;
631 50 : trigger.rootScheme = scheme;
632 50 : auto triggerName = trigger.makeName();
633 50 : extraTable->triggers.emplace(std::move(triggerName), std::move(trigger));
634 50 : }
635 : } while (0);
636 75 : }
637 175 : break;
638 : }
639 200 : case db::Type::Object: {
640 200 : auto ref = static_cast<const db::FieldObject *>(f.getSlot());
641 200 : auto targetIt = tables.find(ref->scheme->getName());
642 200 : if (targetIt != tables.end()) {
643 200 : TriggerRec trigger(TriggerRec::Delete, TriggerRec::Before, ref->scheme->getName(), "__oid",
644 200 : scheme->getName(), fit.second.getName());
645 200 : trigger.rootField = &fit.second;
646 200 : trigger.rootScheme = scheme;
647 200 : trigger.onRemove = ref->onRemove;
648 200 : if (ref->onRemove == RemovePolicy::StrongReference) {
649 25 : trigger.onRemove = RemovePolicy::Reference; // make trigger to remove just reference
650 : }
651 :
652 200 : auto triggerName = trigger.makeName();
653 200 : targetIt->second.triggers.emplace(std::move(triggerName), std::move(trigger));
654 :
655 200 : if (ref->onRemove == RemovePolicy::StrongReference) {
656 : // make reverse-trigger to remove object with strong reference
657 : do {
658 : TriggerRec trigger(TriggerRec::Delete, TriggerRec::Before, scheme->getName(), fit.second.getName(),
659 25 : ref->scheme->getName(), "__oid");
660 25 : trigger.rootField = &fit.second;
661 25 : trigger.rootScheme = scheme;
662 25 : trigger.onRemove = ref->onRemove;
663 :
664 25 : auto triggerName = trigger.makeName();
665 25 : schemeTable->triggers.emplace(std::move(triggerName), std::move(trigger));
666 25 : } while (0);
667 :
668 : do {
669 : TriggerRec trigger(TriggerRec::Update, TriggerRec::Before, scheme->getName(), fit.second.getName(),
670 25 : ref->scheme->getName(), "__oid");
671 25 : trigger.rootField = &fit.second;
672 25 : trigger.rootScheme = scheme;
673 25 : trigger.onRemove = ref->onRemove;
674 :
675 25 : auto triggerName = trigger.makeName();
676 25 : schemeTable->triggers.emplace(std::move(triggerName), std::move(trigger));
677 25 : } while (0);
678 : }
679 200 : }
680 200 : break;
681 : }
682 75 : case db::Type::Array: {
683 75 : auto slot = static_cast<const db::FieldArray *>(f.getSlot());
684 75 : if (slot->tfield && slot->tfield.isSimpleLayout()) {
685 :
686 75 : String name = toString(it.first, "_f_", fit.first);
687 75 : auto & source = it.first;
688 :
689 75 : auto sourceFieldName = toString(source, "_id");
690 :
691 75 : TableRec table;
692 75 : table.cols.emplace(sourceFieldName, ColRec(ColRec::Type::Int8));
693 :
694 75 : auto type = slot->tfield.getType();
695 : switch (type) {
696 0 : case db::Type::Float: table.cols.emplace("data", ColRec(ColRec::Type::Float8)); break;
697 0 : case db::Type::Boolean: table.cols.emplace("data", ColRec(ColRec::Type::Bool)); break;
698 25 : case db::Type::Text: table.cols.emplace("data", ColRec(ColRec::Type::Text)); break;
699 25 : case db::Type::Integer: table.cols.emplace("data", ColRec(ColRec::Type::Int8)); break;
700 25 : case db::Type::Data:
701 : case db::Type::Bytes:
702 : case db::Type::Extra:
703 25 : table.cols.emplace("data", ColRec(ColRec::Type::Bytes));
704 25 : break;
705 0 : default:
706 0 : break;
707 : }
708 :
709 75 : table.indexes.emplace(toString(name, "_idx_", source), toString(source, "_id"));
710 75 : if (f.hasFlag(db::Flags::Unique)) {
711 25 : table.indexes.emplace(toString(name, "_uidx_data"), IndexRec(toString("data"), true));
712 : }
713 :
714 75 : tables.emplace(StringView(name).pdup(), std::move(table));
715 :
716 150 : TriggerRec trigger(TriggerRec::Delete, TriggerRec::Before, scheme->getName(), f.getName(), name, sourceFieldName);
717 75 : trigger.rootField = &f;
718 150 : auto triggerName = trigger.makeName();
719 :
720 75 : schemeTable->triggers.emplace(std::move(triggerName), std::move(trigger));
721 75 : }
722 75 : break;
723 : }
724 50 : case db::Type::View: {
725 50 : auto slot = static_cast<const db::FieldView *>(f.getSlot());
726 :
727 50 : String viewName = toString(it.first, "_f_", fit.first, "_view");
728 50 : auto & source = it.first;
729 50 : auto target = slot->scheme->getName();
730 :
731 50 : TableRec table;
732 50 : table.viewScheme = it.second;
733 50 : table.viewField = slot;
734 50 : table.cols.emplace("__vid", ColRec(ColRec::Type::Int8, ColRec::PrimaryKey));
735 50 : table.cols.emplace(toString(source, "_id"), ColRec(ColRec::Type::Int8, ColRec::IsNotNull));
736 50 : table.cols.emplace(toString(target, "_id"), ColRec(ColRec::Type::Int8, ColRec::IsNotNull));
737 :
738 : do {
739 : TriggerRec trigger(TriggerRec::Delete, TriggerRec::Before, source, "__oid",
740 50 : viewName, toString(source, "_id"));
741 50 : trigger.rootField = &fit.second;
742 50 : auto triggerName = trigger.makeName();
743 50 : schemeTable->triggers.emplace(std::move(triggerName), std::move(trigger));
744 50 : } while (0);
745 :
746 : do {
747 50 : auto targetIt = tables.find(target);
748 50 : if (targetIt != tables.end()) {
749 : TriggerRec trigger(TriggerRec::Delete, TriggerRec::After, target, "__oid",
750 50 : viewName, toString(target, "_id"));
751 50 : trigger.rootField = &fit.second;
752 50 : trigger.rootScheme = scheme;
753 50 : auto triggerName = trigger.makeName();
754 50 : targetIt->second.triggers.emplace(std::move(triggerName), std::move(trigger));
755 50 : }
756 : } while (0);
757 :
758 50 : table.indexes.emplace(toString(viewName, "_idx_", source), toString(source, "_id"));
759 50 : table.indexes.emplace(toString(viewName, "_idx_", target), toString(target, "_id"));
760 :
761 50 : auto tblIt = tables.emplace(StringView(viewName).pdup(), std::move(table)).first;
762 :
763 50 : if (slot->delta) {
764 25 : String deltaName = toString(it.first, "_f_", fit.first, "_delta");
765 25 : table.cols.emplace("id", ColRec(ColRec::Type::Int8, ColRec::PrimaryKey));
766 25 : table.cols.emplace("tag", ColRec(ColRec::Type::Int8, ColRec::IsNotNull));
767 25 : table.cols.emplace("object", ColRec(ColRec::Type::Int8, ColRec::IsNotNull));
768 25 : table.cols.emplace("time", ColRec(ColRec::Type::Int8, ColRec::IsNotNull));
769 25 : table.cols.emplace("user", ColRec(ColRec::Type::Int8));
770 :
771 25 : table.indexes.emplace(deltaName + "_idx_tag", "tag");
772 25 : table.indexes.emplace(deltaName + "_idx_object", "object");
773 25 : table.indexes.emplace(deltaName + "_idx_time", "time");
774 :
775 : do {
776 25 : TriggerRec trigger(TriggerRec::Insert, TriggerRec::After, deltaName, "__delta", deltaName, toString(target, "_id"));
777 25 : trigger.tagField = toString(source, "_id");
778 25 : auto triggerName = trigger.makeName();
779 25 : tblIt->second.triggers.emplace(std::move(triggerName), std::move(trigger));
780 25 : } while (0);
781 :
782 : do {
783 25 : TriggerRec trigger(TriggerRec::Update, TriggerRec::After, deltaName, "__delta", deltaName, toString(target, "_id"));
784 25 : trigger.tagField = toString(source, "_id");
785 25 : auto triggerName = trigger.makeName();
786 25 : tblIt->second.triggers.emplace(std::move(triggerName), std::move(trigger));
787 25 : } while (0);
788 :
789 : do {
790 25 : TriggerRec trigger(TriggerRec::Delete, TriggerRec::After, deltaName, "__delta", deltaName, toString(target, "_id"));
791 25 : trigger.tagField = toString(source, "_id");
792 25 : auto triggerName = trigger.makeName();
793 25 : tblIt->second.triggers.emplace(std::move(triggerName), std::move(trigger));
794 25 : } while (0);
795 :
796 25 : tables.emplace(StringView(deltaName).pdup(), std::move(table));
797 25 : }
798 50 : break;
799 50 : }
800 75 : case db::Type::FullTextView: {
801 75 : String name = toString(it.first, "_f_", fit.first);
802 :
803 75 : auto & source = it.first;
804 75 : auto sourceFieldName = toString(source, "_id");
805 :
806 75 : TableRec table;
807 75 : table.cols.emplace(sourceFieldName, ColRec(ColRec::Type::Int8));
808 75 : table.cols.emplace("word", ColRec(ColRec::Type::Int8));
809 :
810 75 : table.indexes.emplace(toString(name, "_idx_word"), "word");
811 75 : tables.emplace(StringView(name).pdup(), std::move(table));
812 :
813 : do {
814 75 : TriggerRec trigger(TriggerRec::Insert, TriggerRec::After, it.first, fit.first, name, sourceFieldName, &fit.second);
815 75 : trigger.tagField = toString(source, "_id");
816 75 : auto triggerName = trigger.makeName();
817 75 : schemeTable->triggers.emplace(std::move(triggerName), std::move(trigger));
818 75 : } while (0);
819 :
820 : do {
821 75 : TriggerRec trigger(TriggerRec::Update, TriggerRec::After, it.first, fit.first, name, sourceFieldName, &fit.second);
822 75 : trigger.tagField = toString(source, "_id");
823 75 : auto triggerName = trigger.makeName();
824 75 : schemeTable->triggers.emplace(std::move(triggerName), std::move(trigger));
825 75 : } while (0);
826 :
827 : do {
828 75 : TriggerRec trigger(TriggerRec::Delete, TriggerRec::After, it.first, fit.first, name, sourceFieldName, &fit.second);
829 75 : trigger.tagField = toString(source, "_id");
830 75 : auto triggerName = trigger.makeName();
831 75 : schemeTable->triggers.emplace(std::move(triggerName), std::move(trigger));
832 75 : } while (0);
833 :
834 75 : break;
835 75 : }
836 2650 : default:
837 2650 : break;
838 : }
839 :
840 3225 : if (scheme->hasDelta()) {
841 850 : auto name = Handle::getNameForDelta(*scheme);
842 850 : TableRec table;
843 850 : table.cols.emplace("object", ColRec(ColRec::Type::Int8, ColRec::IsNotNull));
844 850 : table.cols.emplace("time", ColRec(ColRec::Type::Int8, ColRec::IsNotNull));
845 850 : table.cols.emplace("action", ColRec(ColRec::Type::Int8, ColRec::IsNotNull));
846 850 : table.cols.emplace("user", ColRec(ColRec::Type::Int8));
847 :
848 850 : table.indexes.emplace(name + "_idx_object", "object");
849 850 : table.indexes.emplace(name + "_idx_time", "time");
850 850 : tables.emplace(StringView(name).pdup(), std::move(table));
851 850 : }
852 : }
853 : }
854 150 : return tables;
855 0 : }
856 :
857 148 : Map<StringView, TableRec> TableRec::get(Handle &h, StringStream &stream) {
858 148 : Map<StringView, TableRec> ret;
859 :
860 150 : h.performSimpleSelect("SELECT name FROM sqlite_schema WHERE type='table';",
861 149 : [&] (db::sql::Result &tables) {
862 1247 : for (auto it : tables) {
863 1096 : ret.emplace(it.at(0).pdup(), TableRec());
864 1098 : stream << "TABLE " << it.at(0) << "\n";
865 : // std::cout << "TABLE " << it.at(0) << "\n";
866 : }
867 150 : });
868 :
869 1250 : for (auto &it : ret) {
870 1100 : auto &table = it.second;
871 1100 : h.performSimpleSelect(toString("PRAGMA table_info('", it.first, "');"),
872 1100 : [&] (db::sql::Result &columns) {
873 4873 : for (auto col : columns) {
874 3774 : auto name = col.at(1);
875 3772 : auto t = getStorageType(col.at(2));
876 :
877 3773 : ColRec::Flags flags = ColRec::Flags::None;
878 3773 : if (col.toBool(3)) {
879 2100 : flags |= ColRec::Flags::IsNotNull;
880 : }
881 3775 : if (col.toBool(5)) {
882 750 : flags |= ColRec::Flags::PrimaryKey;
883 : }
884 :
885 3774 : if (t == BackendInterface::StorageType::Unknown) {
886 300 : table.cols.emplace(name.str<Interface>(), ColRec(col.at(2).str<Interface>(), flags));
887 : } else {
888 3474 : table.cols.emplace(name.str<Interface>(), ColRec(t, flags));
889 : }
890 : }
891 1100 : });
892 : }
893 :
894 150 : h.performSimpleSelect("SELECT tbl_name, name, sql FROM sqlite_schema WHERE type='index';",
895 150 : [&] (db::sql::Result &indexes) {
896 924 : for (auto it : indexes) {
897 775 : auto tname = it.at(0).str<Interface>();
898 774 : auto f = ret.find(tname);
899 774 : if (f != ret.end()) {
900 774 : auto &table = f->second;
901 774 : auto name = it.at(1);
902 774 : auto sql = it.at(2);
903 774 : if (!name.starts_with("sqlite_autoindex_")) {
904 774 : bool unique = false;
905 774 : if (sql.starts_with("CREATE UNIQUE")) {
906 150 : unique = true;
907 : }
908 :
909 773 : auto stream = toString("\"", it.at(1), "\" ON \"", tname, "\" ");
910 773 : sql.readUntilString(stream);
911 771 : sql += stream.size();
912 772 : sql.skipChars<StringView::WhiteSpace>();
913 773 : if (sql.is("(")) {
914 773 : ++ sql;
915 773 : Vector<String> fields;
916 1594 : while (!sql.empty() && !sql.is(')')) {
917 824 : sql.skipUntil<StringView::Chars<'"'>>();
918 823 : if (sql.is('"')) {
919 773 : ++ sql;
920 772 : auto field = sql.readUntil<StringView::Chars<'"'>>();
921 773 : if (sql.is('"')) {
922 771 : fields.emplace_back(field.str<Interface>());
923 772 : ++ sql;
924 : }
925 : }
926 : }
927 772 : table.indexes.emplace(it.at(1).str<Interface>(), IndexRec(std::move(fields), unique));
928 773 : }
929 775 : }
930 : }
931 774 : }
932 150 : });
933 :
934 150 : h.performSimpleSelect("SELECT tbl_name, name, sql FROM sqlite_schema WHERE type='trigger';",
935 150 : [&] (db::sql::Result &triggers) {
936 225 : for (auto it : triggers) {
937 75 : auto tableName = it.at(0);
938 75 : auto f = ret.find(tableName);
939 75 : if (f != ret.end()) {
940 75 : auto triggerName = it.at(1);
941 75 : if (!triggerName.starts_with("ST_TRIGGER:")) {
942 0 : continue;
943 : }
944 :
945 75 : triggerName += "ST_TRIGGER:"_len;
946 :
947 75 : TriggerRec trigger(triggerName);
948 75 : f->second.triggers.emplace(it.at(1).str<Interface>(), std::move(trigger));
949 75 : }
950 : }
951 150 : });
952 :
953 150 : return ret;
954 0 : }
955 :
956 2221 : TableRec::TableRec() { }
957 400 : TableRec::TableRec(const Driver *driver, const BackendInterface::Config &cfg, const db::Scheme *scheme) {
958 400 : withOids = true;
959 400 : if (scheme->isDetouched()) {
960 25 : detached = true;
961 : }
962 :
963 400 : auto name = scheme->getName();
964 :
965 3625 : for (auto &it : scheme->getFields()) {
966 3225 : bool emplaced = false;
967 3225 : auto &f = it.second;
968 3225 : auto type = it.second.getType();
969 :
970 3225 : ColRec::Flags flags = ColRec::None;
971 3225 : if (f.hasFlag(db::Flags::Required)) {
972 75 : flags |= ColRec::IsNotNull;
973 : }
974 :
975 3225 : switch (type) {
976 125 : case db::Type::None:
977 : case db::Type::Array:
978 : case db::Type::View:
979 : case db::Type::Virtual:
980 125 : break;
981 :
982 75 : case db::Type::Float:
983 75 : cols.emplace(it.first, ColRec(ColRec::Type::Float8, flags));
984 75 : emplaced = true;
985 75 : break;
986 :
987 200 : case db::Type::Boolean:
988 200 : cols.emplace(it.first, ColRec(ColRec::Type::Bool, flags));
989 200 : emplaced = true;
990 200 : break;
991 :
992 700 : case db::Type::Text:
993 700 : cols.emplace(it.first, ColRec(ColRec::Type::Text, flags));
994 700 : emplaced = true;
995 700 : break;
996 :
997 525 : case db::Type::Data:
998 : case db::Type::Bytes:
999 : case db::Type::Extra:
1000 525 : cols.emplace(it.first, ColRec(ColRec::Type::Bytes, flags));
1001 525 : emplaced = true;
1002 525 : break;
1003 :
1004 900 : case db::Type::Integer:
1005 : case db::Type::File:
1006 : case db::Type::Image:
1007 900 : cols.emplace(it.first, ColRec(ColRec::Type::Int8, flags));
1008 900 : emplaced = true;
1009 900 : break;
1010 :
1011 75 : case db::Type::FullTextView:
1012 75 : cols.emplace(it.first, ColRec(ColRec::Type::Bytes, flags));
1013 75 : emplaced = true;
1014 75 : break;
1015 :
1016 200 : case db::Type::Object:
1017 200 : cols.emplace(it.first, ColRec(ColRec::Type::Int8, flags));
1018 200 : emplaced = true;
1019 200 : break;
1020 :
1021 175 : case db::Type::Set:
1022 175 : if (f.isReference()) {
1023 : // set is filled with references
1024 : } else {
1025 : //
1026 : }
1027 175 : break;
1028 :
1029 250 : case db::Type::Custom:
1030 250 : if (auto objSlot = f.getSlot<db::FieldCustom>()) {
1031 250 : if (auto info = driver->getCustomFieldInfo(objSlot->getDriverTypeName())) {
1032 250 : cols.emplace(it.first, ColRec(StringView(info->typeName), flags));
1033 250 : emplaced = true;
1034 : }
1035 : }
1036 250 : break;
1037 : }
1038 :
1039 3225 : if (emplaced) {
1040 2925 : bool unique = f.hasFlag(db::Flags::Unique) || f.getTransform() == db::Transform::Alias;
1041 2925 : if (type == db::Type::Object) {
1042 200 : auto ref = static_cast<const db::FieldObject *>(f.getSlot());
1043 200 : auto target = ref->scheme->getName();
1044 200 : StringStream cname; cname << name << "_ref_" << it.first << "_" << target;
1045 :
1046 200 : switch (ref->onRemove) {
1047 50 : case RemovePolicy::Cascade: cname << "_csc"; break;
1048 0 : case RemovePolicy::Restrict: cname << "_rst"; break;
1049 0 : case RemovePolicy::Reference: cname << "_ref"; break;
1050 25 : case RemovePolicy::StrongReference: cname << "_sref"; break;
1051 125 : case RemovePolicy::Null: break;
1052 : }
1053 :
1054 200 : indexes.emplace(toString(name, (unique ? "_uidx_" : "_idx_"), it.first), IndexRec(it.first, unique));
1055 2925 : } else if (type == db::Type::File || type == db::Type::Image) {
1056 : TriggerRec updateTrigger(TriggerRec::Update, TriggerRec::After, name, it.second.getName(),
1057 250 : cfg.fileScheme->getName(), "__oid", &it.second);
1058 250 : auto updateTriggerName = updateTrigger.makeName();
1059 250 : triggers.emplace(std::move(updateTriggerName), std::move(updateTrigger));
1060 :
1061 : TriggerRec removeTrigger(TriggerRec::Delete, TriggerRec::After, name, it.second.getName(),
1062 250 : cfg.fileScheme->getName(), "__oid", &it.second);
1063 250 : auto removeTriggerName = removeTrigger.makeName();
1064 250 : triggers.emplace(std::move(removeTriggerName), std::move(removeTrigger));
1065 250 : }
1066 :
1067 2925 : if ((type == db::Type::Text && f.getTransform() == db::Transform::Alias) || (f.hasFlag(db::Flags::Indexed))) {
1068 800 : if (type == db::Type::Custom) {
1069 50 : auto c = f.getSlot<db::FieldCustom>();
1070 50 : if (auto info = driver->getCustomFieldInfo(c->getDriverTypeName())) {
1071 50 : if (info->isIndexable) {
1072 0 : indexes.emplace(toString(name, "_idx_", info->getIndexName(*c)), info->getIndexDefinition(*c));
1073 : }
1074 : }
1075 : } else {
1076 750 : indexes.emplace(toString(name, (unique ? "_uidx_" : "_idx_"), it.first), IndexRec(it.first, unique));
1077 : }
1078 : }
1079 :
1080 2925 : if (type == db::Type::Text) {
1081 700 : if (f.hasFlag(db::Flags::PatternIndexed)) {
1082 150 : indexes.emplace(toString(name, "_idx_", it.first, "_pattern"), toString("( \"", it.first, "\" COLLATE NOCASE)"));
1083 : }
1084 : /*if (f.hasFlag(db::Flags::TrigramIndexed)) {
1085 : indexes.emplace(toString(name, "_idx_", it.first, "_trgm"), toString("USING GIN ( \"", it.first, "\" gin_trgm_ops)"));
1086 : }*/
1087 : }
1088 :
1089 : if (type == db::Type::FullTextView) {
1090 :
1091 : }
1092 : }
1093 : }
1094 :
1095 400 : for (auto &it : scheme->getUnique()) {
1096 0 : StringStream nameStream;
1097 0 : nameStream << name << "_uidx";
1098 0 : Vector<String> values;
1099 0 : for (auto &f : it.fields) {
1100 0 : values.emplace_back(f->getName().str<Interface>());
1101 0 : nameStream << "_" << f->getName();
1102 : }
1103 0 : indexes.emplace(nameStream.str(), IndexRec(std::move(values), true));
1104 0 : }
1105 :
1106 400 : if (scheme->hasDelta()) {
1107 : do {
1108 : TriggerRec trigger(TriggerRec::Insert, TriggerRec::After, name, "__delta",
1109 75 : Handle::getNameForDelta(*scheme), "object");
1110 75 : trigger.rootScheme = scheme;
1111 75 : auto triggerName = trigger.makeName();
1112 75 : triggers.emplace(std::move(triggerName), std::move(trigger));
1113 75 : } while (0);
1114 :
1115 : do {
1116 : TriggerRec trigger(TriggerRec::Update, TriggerRec::After, name, "__delta",
1117 75 : Handle::getNameForDelta(*scheme), "object");
1118 75 : trigger.rootScheme = scheme;
1119 75 : auto triggerName = trigger.makeName();
1120 75 : triggers.emplace(std::move(triggerName), std::move(trigger));
1121 75 : } while (0);
1122 :
1123 : do {
1124 : TriggerRec trigger(TriggerRec::Delete, TriggerRec::After, name, "__delta",
1125 75 : Handle::getNameForDelta(*scheme), "object");
1126 75 : trigger.rootScheme = scheme;
1127 75 : auto triggerName = trigger.makeName();
1128 75 : triggers.emplace(std::move(triggerName), std::move(trigger));
1129 75 : } while (0);
1130 : }
1131 :
1132 400 : if (withOids && !detached) {
1133 375 : indexes.emplace(toString(name, "_idx___oid"), IndexRec("__oid"));
1134 : }
1135 400 : }
1136 :
1137 147 : bool Handle::init(const BackendInterface::Config &cfg, const Map<StringView, const Scheme *> &s) {
1138 147 : level = TransactionLevel::Exclusive;
1139 147 : beginTransaction();
1140 :
1141 150 : if (!performSimpleQuery(StringView(DATABASE_DEFAULTS))) {
1142 0 : endTransaction();
1143 0 : return false;
1144 : }
1145 :
1146 150 : StringStream tables;
1147 149 : tables << "Server: " << cfg.name << "\n";
1148 150 : auto existedTables = TableRec::get(*this, tables);
1149 150 : auto requiredTables = TableRec::parse(driver, cfg, s);
1150 :
1151 150 : StringStream stream;
1152 150 : TableRec::writeCompareResult(*this, stream, requiredTables, existedTables, s);
1153 :
1154 150 : if (!stream.empty()) {
1155 75 : bool success = true;
1156 75 : if (!performSimpleQuery(stream.weak(), [&] (const Value &errInfo) {
1157 0 : stream << "Server: " << cfg.name << "\n";
1158 0 : stream << "\nErrorInfo: " << EncodeFormat::Pretty << errInfo << "\n";
1159 0 : })) {
1160 0 : endTransaction();
1161 0 : success = false;
1162 : }
1163 :
1164 75 : tables << "\n" << stream;
1165 75 : if (_driver->getApplicationInterface()) {
1166 75 : _driver->getApplicationInterface()->reportDbUpdate(tables.weak(), success);
1167 : }
1168 75 : if (!success) {
1169 0 : return false;
1170 : }
1171 : }
1172 :
1173 150 : StringStream query;
1174 150 : query << "DELETE FROM __login WHERE \"date\" < " << Time::now().toSeconds() - config::STORAGE_DEFAULT_INTERNAL_INTERVAL.toSeconds() << ";";
1175 149 : performSimpleQuery(query.weak());
1176 150 : query.clear();
1177 :
1178 150 : auto iit = existedTables.find(StringView("__error"));
1179 149 : if (iit != existedTables.end()) {
1180 0 : query << "DELETE FROM __error WHERE \"time\" < " << Time::now().toMicros() - config::STORAGE_DEFAULT_INTERNAL_INTERVAL.toMicros() << ";";
1181 0 : performSimpleQuery(query.weak());
1182 0 : query.clear();
1183 : }
1184 :
1185 149 : endTransaction();
1186 150 : return true;
1187 150 : }
1188 :
1189 : }
|