LCOV - code coverage report
Current view: top level - core/db/sqlite - SPSqliteHandleInit.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 684 768 89.1 %
Date: 2024-05-12 00:16:13 Functions: 21 23 91.3 %

          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             : }

Generated by: LCOV version 1.14