LCOV - code coverage report
Current view: top level - core/db/pq - SPPqHandleInit.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 496 647 76.7 %
Date: 2024-05-12 00:16:13 Functions: 26 32 81.2 %

          Line data    Source code
       1             : /**
       2             : Copyright (c) 2016-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 "SPPqHandle.h"
      25             : 
      26             : namespace STAPPLER_VERSIONIZED stappler::db::pq {
      27             : 
      28             : using RemovePolicy = db::RemovePolicy;
      29             : 
      30             : struct ConstraintRec {
      31             :         enum Type {
      32             :                 Unique,
      33             :                 Reference,
      34             :         };
      35             : 
      36             :         Type type;
      37             :         Vector<String> fields;
      38             :         String reference;
      39             :         db::RemovePolicy remove = db::RemovePolicy::Null;
      40             : 
      41           0 :         ConstraintRec(Type t) : type(t) { }
      42           0 :         ConstraintRec(Type t, std::initializer_list<String> il) : type(t), fields(il) { }
      43         825 :         ConstraintRec(Type t, const String &col, StringView ref = StringView(), db::RemovePolicy r = db::RemovePolicy::Null)
      44        1650 :         : type(t), fields{col}, reference(ref.str<Interface>()), remove(r) { }
      45             : };
      46             : 
      47             : struct ColRec {
      48             :         using Type = BackendInterface::StorageType;
      49             : 
      50             :         Type type = Type::Unknown;
      51             :         String custom;
      52             :         bool notNull = false;
      53             :         bool serial = false;
      54             :         int64_t oid = 0;
      55             : 
      56        2550 :         ColRec(Type t, bool notNull = false, bool serial = false) : type(t), notNull(notNull), serial(serial) { }
      57         375 :         ColRec(Type t, int64_t oid, bool notNull = false, bool serial = false) : type(t), notNull(notNull), serial(serial), oid(oid) { }
      58           0 :         ColRec(const StringView &t, bool notNull = false) : custom(t.str<Interface>()), notNull(notNull) { }
      59         150 :         ColRec(const StringView &t, int64_t oid, bool notNull = false) : custom(t.str<Interface>()), notNull(notNull), oid(oid) { }
      60             : };
      61             : 
      62             : struct TableRec {
      63             :         using Scheme = db::Scheme;
      64             : 
      65             :         static Map<StringView, TableRec> parse(const Driver *driver, const BackendInterface::Config &cfg,
      66             :                         const Map<StringView, const Scheme *> &s, const Vector<Pair<StringView, int64_t>> &);
      67             :         static Map<StringView, TableRec> get(Handle &h, StringStream &stream);
      68             : 
      69             :         static void writeCompareResult(StringStream &stream,
      70             :                         Map<StringView, TableRec> &required, Map<StringView, TableRec> &existed,
      71             :                         const Map<StringView, const db::Scheme *> &s);
      72             : 
      73             :         TableRec();
      74             :         TableRec(const Driver *d, const BackendInterface::Config &cfg, const db::Scheme *scheme,
      75             :                         const Vector<Pair<StringView, int64_t>> &customs);
      76             : 
      77             :         Map<String, ColRec> cols;
      78             :         Map<String, ConstraintRec> constraints;
      79             :         Map<String, String> indexes;
      80             :         Vector<String> pkey;
      81             :         Set<String> triggers;
      82             :         bool objects = true;
      83             : 
      84             :         bool exists = false;
      85             :         bool valid = false;
      86             : 
      87             :         const db::Scheme *viewScheme = nullptr;
      88             :         const db::FieldView *viewField = nullptr;
      89             : };
      90             : 
      91             : 
      92         775 : constexpr static uint32_t getDefaultFunctionVersion() { return 10; }
      93             : 
      94             : constexpr static const char * DATABASE_DEFAULTS = R"Sql(
      95             : CREATE TABLE IF NOT EXISTS __objects (
      96             :         __oid bigserial NOT NULL,
      97             :         CONSTRAINT __objects_pkey PRIMARY KEY (__oid)
      98             : ) WITH ( OIDS=FALSE );
      99             : 
     100             : CREATE TABLE IF NOT EXISTS __removed (
     101             :         __oid bigint NOT NULL,
     102             :         CONSTRAINT __removed_pkey PRIMARY KEY (__oid)
     103             : ) WITH ( OIDS=FALSE );
     104             : 
     105             : CREATE TABLE IF NOT EXISTS __sessions (
     106             :         name bytea NOT NULL,
     107             :         mtime bigint NOT NULL,
     108             :         maxage bigint NOT NULL,
     109             :         data bytea,
     110             :         CONSTRAINT __sessions_pkey PRIMARY KEY (name)
     111             : ) WITH ( OIDS=FALSE );
     112             : 
     113             : CREATE TABLE IF NOT EXISTS __broadcasts (
     114             :         id bigserial NOT NULL,
     115             :         date bigint NOT NULL,
     116             :         msg bytea,
     117             :         CONSTRAINT __broadcasts_pkey PRIMARY KEY (id)
     118             : ) WITH ( OIDS=FALSE );
     119             : CREATE INDEX IF NOT EXISTS __broadcasts_date ON __broadcasts ("date" DESC);
     120             : 
     121             : CREATE TABLE IF NOT EXISTS __login (
     122             :         id bigserial NOT NULL,
     123             :         "user" bigint NOT NULL,
     124             :         name text NOT NULL,
     125             :         password bytea NOT NULL,
     126             :         date bigint NOT NULL,
     127             :         success boolean NOT NULL,
     128             :         addr inet,
     129             :         host text,
     130             :         path text,
     131             :         CONSTRAINT __login_pkey PRIMARY KEY (id)
     132             : ) WITH ( OIDS=FALSE );
     133             : CREATE INDEX IF NOT EXISTS __login_user ON __login ("user");
     134             : CREATE INDEX IF NOT EXISTS __login_date ON __login (date);
     135             : 
     136             : CREATE EXTENSION IF NOT EXISTS intarray;
     137             : CREATE EXTENSION IF NOT EXISTS pg_trgm;
     138             : )Sql";
     139             : 
     140             : constexpr static const char * INDEX_QUERY = R"Sql(
     141             : WITH tables AS (SELECT table_name AS name FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE')
     142             : SELECT pg_class.relname as table_name, i.relname as index_name, array_to_string(array_agg(a.attname), ', ') as column_names
     143             : FROM pg_class INNER JOIN tables ON (tables.name = pg_class.relname), pg_class i, pg_index ix, pg_attribute a
     144             : WHERE pg_class.oid = ix.indrelid
     145             :         AND i.oid = ix.indexrelid
     146             :         AND a.attrelid = pg_class.oid
     147             :         AND a.attnum = ANY(ix.indkey)
     148             :         AND pg_class.relkind = 'r'
     149             : GROUP BY pg_class.relname, i.relname ORDER BY pg_class.relname, i.relname;)Sql";
     150             : 
     151             : constexpr static const char * COL_QUERY = R"Sql(
     152             : SELECT table_name, column_name, is_nullable::text, data_type, atttypid::integer as col_oid, pg_class.oid::integer as table_oid, attname
     153             : FROM information_schema.columns
     154             :         INNER JOIN pg_class ON (table_name = relname)
     155             :         INNER JOIN pg_attribute ON (attrelid = pg_class.oid AND pg_attribute.attname = column_name)
     156             :                         WHERE table_schema='public';)Sql";
     157             : 
     158         250 : static void writeFileUpdateTrigger(StringStream &stream, const db::Scheme *s, const db::Field &obj) {
     159           0 :         stream << "\t\tIF (NEW.\"" << obj.getName() << "\" IS NULL OR OLD.\"" << obj.getName() << "\" <> NEW.\"" << obj.getName() << "\") THEN\n"
     160           0 :                 << "\t\t\tIF (OLD.\"" << obj.getName() << "\" IS NOT NULL) THEN\n"
     161           0 :                 << "\t\t\t\tINSERT INTO __removed (__oid) VALUES (OLD.\"" << obj.getName() << "\");\n"
     162         250 :                 << "\t\t\tEND IF;\n\t\tEND IF;\n";
     163         250 : }
     164             : 
     165         250 : static void writeFileRemoveTrigger(StringStream &stream, const db::Scheme *s, const db::Field &obj) {
     166         500 :         stream << "\t\tIF (OLD.\"" << obj.getName() << "\" IS NOT NULL) THEN\n"
     167           0 :                 << "\t\t\tINSERT INTO __removed (__oid) VALUES (OLD.\"" << obj.getName() << "\");\n"
     168         250 :                 << "\t\tEND IF;\n";
     169         250 : }
     170             : 
     171          50 : static void writeObjectSetRemoveTrigger(StringStream &stream, const db::Scheme *s, const db::FieldObject *obj) {
     172          50 :         auto source = s->getName();
     173          50 :         auto target = obj->scheme->getName();
     174             : 
     175             :         stream << "\t\tDELETE FROM " << target << " WHERE __oid IN (SELECT " << target << "_id FROM "
     176          50 :                         << s->getName() << "_f_" << obj->name << " WHERE "<< source << "_id=OLD.__oid);\n";
     177          50 : }
     178             : 
     179           0 : static void writeObjectUpdateTrigger(StringStream &stream, const db::Scheme *s, const db::FieldObject *obj) {
     180           0 :         auto target = obj->scheme->getName();
     181             : 
     182           0 :         stream << "\t\tIF (NEW.\"" << obj->getName() << "\" IS NULL OR OLD.\"" << obj->getName() << "\" <> NEW.\"" << obj->getName() << "\") THEN\n"
     183           0 :                 << "\t\t\tIF (OLD.\"" << obj->getName() << "\" IS NOT NULL) THEN\n"
     184           0 :                 << "\t\t\t\tDELETE FROM " << target << " WHERE __oid=OLD." << obj->getName() << ";\n";
     185           0 :         stream << "\t\t\tEND IF;\n\t\tEND IF;\n";
     186           0 : }
     187             : 
     188           0 : static void writeObjectRemoveTrigger(StringStream &stream, const db::Scheme *s, const db::FieldObject *obj) {
     189           0 :         auto target = obj->scheme->getName();
     190             : 
     191           0 :         stream << "\t\tIF (OLD.\"" << obj->getName() << "\" IS NOT NULL) THEN\n"
     192           0 :                 << "\t\t\tDELETE FROM " << target << " WHERE __oid=OLD." << obj->getName() << ";\n";
     193           0 :         stream << "\t\tEND IF;\n";
     194           0 : }
     195             : 
     196         100 : static void writeAfterTrigger(StringStream &stream, const db::Scheme *s, const String &triggerName) {
     197         100 :         auto &fields = s->getFields();
     198             : 
     199          75 :         auto writeInsertDelta = [&] (DeltaAction a) {
     200          75 :                 if (a == DeltaAction::Create || a == DeltaAction::Update) {
     201          50 :                         stream << "\t\tINSERT INTO " << Handle::getNameForDelta(*s) << "(\"object\",\"action\",\"time\",\"user\")"
     202         100 :                                 "VALUES(NEW.__oid," << stappler::toInt(a) << ",current_setting('serenity.now')::bigint,current_setting('serenity.user')::bigint);\n";
     203             :                 } else {
     204          25 :                         stream << "\t\tINSERT INTO " << Handle::getNameForDelta(*s) << "(\"object\",\"action\",\"time\",\"user\")"
     205          25 :                                 "VALUES(OLD.__oid," << stappler::toInt(a) << ",current_setting('serenity.now')::bigint,current_setting('serenity.user')::bigint);\n";
     206             :                 }
     207          75 :         };
     208             : 
     209             :         stream << "CREATE OR REPLACE FUNCTION " << triggerName << "_func() RETURNS TRIGGER AS $" << triggerName
     210         100 :                         << "$ BEGIN\n\tIF (TG_OP = 'INSERT') THEN\n";
     211         100 :         if (s->hasDelta()) {
     212          25 :                 writeInsertDelta(DeltaAction::Create);
     213             :         }
     214         100 :         stream << "\tELSIF (TG_OP = 'UPDATE') THEN\n";
     215         875 :         for (auto &it : fields) {
     216         775 :                 if (it.second.isFile()) {
     217         250 :                         writeFileUpdateTrigger(stream, s, it.second);
     218         525 :                 } else if (it.second.getType() == db::Type::Object) {
     219          25 :                         const db::FieldObject *objSlot = static_cast<const db::FieldObject *>(it.second.getSlot());
     220          25 :                         if (objSlot->onRemove == db::RemovePolicy::StrongReference) {
     221           0 :                                 writeObjectUpdateTrigger(stream, s, objSlot);
     222             :                         }
     223             :                 }
     224             :         }
     225         100 :         if (s->hasDelta()) {
     226          25 :                 writeInsertDelta(DeltaAction::Update);
     227             :         }
     228         100 :         stream << "\tELSIF (TG_OP = 'DELETE') THEN\n";
     229         875 :         for (auto &it : fields) {
     230         775 :                 if (it.second.isFile()) {
     231         250 :                         writeFileRemoveTrigger(stream, s, it.second);
     232         525 :                 } else if (it.second.getType() == db::Type::Object) {
     233          25 :                         const db::FieldObject *objSlot = static_cast<const db::FieldObject *>(it.second.getSlot());
     234          25 :                         if (objSlot->onRemove == db::RemovePolicy::StrongReference) {
     235           0 :                                 writeObjectRemoveTrigger(stream, s, objSlot);
     236             :                         }
     237             :                 }
     238             :         }
     239         100 :         if (s->hasDelta()) {
     240          25 :                 writeInsertDelta(DeltaAction::Delete);
     241             :         }
     242         100 :         stream << "\tEND IF;\n\tRETURN NULL;\n";
     243         100 :         stream << "\nEND; $" << triggerName << "$ LANGUAGE plpgsql;\n";
     244             : 
     245           0 :         stream << "CREATE TRIGGER " << triggerName << " AFTER INSERT OR UPDATE OR DELETE ON \"" << s->getName()
     246         100 :                         << "\" FOR EACH ROW EXECUTE PROCEDURE " << triggerName << "_func();\n";
     247         100 : }
     248             : 
     249          50 : static void writeBeforeTrigger(StringStream &stream, const db::Scheme *s, const String &triggerName) {
     250          50 :         auto &fields = s->getFields();
     251             : 
     252             :         stream << "CREATE OR REPLACE FUNCTION " << triggerName << "_func() RETURNS TRIGGER AS $" << triggerName
     253          50 :                         << "$ BEGIN\n\tIF (TG_OP = 'DELETE') THEN\n";
     254             : 
     255         675 :         for (auto &it : fields) {
     256         625 :                 if (it.second.getType() == db::Type::Set) {
     257         100 :                         const db::FieldObject *objSlot = static_cast<const db::FieldObject *>(it.second.getSlot());
     258         100 :                         if (objSlot->onRemove == db::RemovePolicy::StrongReference) {
     259          50 :                                 writeObjectSetRemoveTrigger(stream, s, objSlot);
     260             :                         }
     261             :                 }
     262             :         }
     263             : 
     264          50 :         stream << "\tEND IF;\n\tRETURN OLD;\n";
     265          50 :         stream << "\nEND; $" << triggerName << "$ LANGUAGE plpgsql;\n";
     266             : 
     267         100 :         stream << "CREATE TRIGGER " << triggerName << " BEFORE DELETE ON \"" << s->getName()
     268          50 :                         << "\" FOR EACH ROW EXECUTE PROCEDURE " << triggerName << "_func();\n";
     269          50 : }
     270             : 
     271          25 : static void writeDeltaTrigger(StringStream &stream, const StringView &name, const TableRec &s, const StringView &triggerName) {
     272          25 :         String deltaName = toString(name.sub(0, name.size() - 5), "_delta");
     273          25 :         String tagField = toString(s.viewScheme->getName(), "_id");
     274          25 :         String objField = toString(s.viewField->scheme->getName(), "_id");
     275             : 
     276             :         stream << "CREATE OR REPLACE FUNCTION " << triggerName << "_func() RETURNS TRIGGER AS $" << triggerName
     277             :                         << "$ BEGIN\n"
     278          25 :                         "\tIF (TG_OP = 'INSERT') THEN\n";
     279             : 
     280             : 
     281             :         stream << "\tINSERT INTO " << deltaName << " (\"tag\", \"object\", \"time\", \"user\") VALUES("
     282             :                         "NEW.\"" << tagField << "\",NEW.\"" << objField << "\","
     283          25 :                         "current_setting('serenity.now')::bigint,current_setting('serenity.user')::bigint);\n";
     284             : 
     285             : 
     286          25 :         stream << "\tELSIF (TG_OP = 'UPDATE') THEN\n";
     287             : 
     288             : 
     289             :         stream << "\tINSERT INTO " << deltaName << " (\"tag\", \"object\", \"time\", \"user\") VALUES("
     290             :                         "OLD.\"" << tagField << "\",OLD.\"" << objField << "\","
     291          25 :                         "current_setting('serenity.now')::bigint,current_setting('serenity.user')::bigint);\n";
     292             : 
     293             : 
     294          25 :         stream << "\tELSIF (TG_OP = 'DELETE') THEN\n";
     295             : 
     296             : 
     297             :         stream << "\tINSERT INTO " << deltaName << " (\"tag\", \"object\", \"time\", \"user\") VALUES("
     298             :                         "OLD.\"" << tagField << "\",OLD.\"" << objField << "\","
     299          25 :                         "current_setting('serenity.now')::bigint,current_setting('serenity.user')::bigint);\n";
     300             : 
     301             : 
     302             :         stream << "\tEND IF;\n"
     303             :                         "\tRETURN NULL;\n"
     304          25 :                         "END; $" << triggerName << "$ LANGUAGE plpgsql;\n";
     305             :         stream << "CREATE TRIGGER " << triggerName << " AFTER INSERT OR UPDATE OR DELETE ON \"" << name
     306          25 :                         << "\" FOR EACH ROW EXECUTE PROCEDURE " << triggerName << "_func();\n";
     307          25 : }
     308             : 
     309          25 : void TableRec::writeCompareResult(StringStream &stream,
     310             :                 Map<StringView, TableRec> &required, Map<StringView, TableRec> &existed,
     311             :                 const Map<StringView, const db::Scheme *> &s) {
     312         150 :         for (auto &ex_it : existed) {
     313         125 :                 auto req_it = required.find(ex_it.first);
     314         125 :                 if (req_it != required.end()) {
     315           0 :                         auto &req_t = req_it->second;
     316           0 :                         auto &ex_t = ex_it.second;
     317           0 :                         req_t.exists = true;
     318             : 
     319           0 :                         for (auto &ex_idx_it : ex_t.indexes) {
     320           0 :                                 auto req_idx_it = req_t.indexes.find(ex_idx_it.first);
     321           0 :                                 if (req_idx_it == req_t.indexes.end()) {
     322             :                                         // index is not required any more, drop it
     323           0 :                                         stream << "DROP INDEX IF EXISTS \"" << ex_idx_it.first << "\";\n";
     324             :                                 } else {
     325           0 :                                         req_t.indexes.erase(req_idx_it);
     326             :                                 }
     327             :                         }
     328             : 
     329           0 :                         for (auto &ex_cst_it : ex_t.constraints) {
     330           0 :                                 auto req_cst_it = req_t.constraints.find(ex_cst_it.first);
     331           0 :                                 if (req_cst_it == req_t.constraints.end()) {
     332             :                                         // constraint is not required any more, drop it
     333           0 :                                         stream << "ALTER TABLE " << ex_it.first << " DROP CONSTRAINT IF EXISTS \"" << ex_cst_it.first << "\";\n";
     334             :                                 } else {
     335           0 :                                         req_t.constraints.erase(req_cst_it);
     336             :                                 }
     337             :                         }
     338             : 
     339           0 :                         for (auto &ex_col_it : ex_t.cols) {
     340           0 :                                 auto req_col_it = req_t.cols.find(ex_col_it.first);
     341           0 :                                 if (req_col_it == req_t.cols.end()) {
     342           0 :                                         stream << "ALTER TABLE " << ex_it.first << " DROP COLUMN IF EXISTS \"" << ex_col_it.first << "\";\n";
     343             :                                 } else {
     344           0 :                                         auto &req_col = req_col_it->second;
     345           0 :                                         auto &ex_col = ex_col_it.second;
     346             : 
     347           0 :                                         auto req_type = req_col.type;
     348             : 
     349           0 :                                         if (req_type != ex_col.type) {
     350           0 :                                                 stream << "ALTER TABLE " << ex_it.first << " DROP COLUMN IF EXISTS \"" << ex_col_it.first << "\";\n";
     351           0 :                                         } else if (ex_col.type == ColRec::Type::Unknown && req_type == ColRec::Type::Unknown
     352           0 :                                                         && ((req_col.oid && ex_col.oid != req_col.oid) || (!req_col.oid && ex_col.custom != req_col.custom))) {
     353           0 :                                                 stream << "ALTER TABLE " << ex_it.first << " DROP COLUMN IF EXISTS \"" << ex_col_it.first << "\";\n";
     354             :                                         } else {
     355           0 :                                                 if (ex_col.notNull != req_col.notNull) {
     356           0 :                                                         if (ex_col.notNull) {
     357           0 :                                                                 stream << "ALTER TABLE " << ex_it.first << " ALTER COLUMN \"" << ex_col_it.first << "\" DROP NOT NULL;\n";
     358             :                                                         } else {
     359           0 :                                                                 stream << "ALTER TABLE " << ex_it.first << " ALTER COLUMN \"" << ex_col_it.first << "\" SET NOT NULL;\n";
     360             :                                                         }
     361             :                                                 }
     362           0 :                                                 req_t.cols.erase(req_col_it);
     363             :                                         }
     364             :                                 }
     365             :                         }
     366             : 
     367           0 :                         for (auto &ex_tgr_it : ex_t.triggers) {
     368           0 :                                 auto req_tgr_it = req_t.triggers.find(ex_tgr_it);
     369           0 :                                 if (req_tgr_it == req_t.triggers.end()) {
     370           0 :                                         stream << "DROP TRIGGER IF EXISTS \"" << ex_tgr_it << "\" ON \"" << ex_it.first << "\";\n";
     371           0 :                                         stream << "DROP FUNCTION IF EXISTS \"" << ex_tgr_it << "_func\"();\n";
     372             :                                 } else {
     373           0 :                                         req_t.triggers.erase(ex_tgr_it);
     374             :                                 }
     375             :                         }
     376             :                 }
     377             :         }
     378             : 
     379             :         // write table structs
     380         600 :         for (auto &it : required) {
     381         575 :                 auto &t = it.second;
     382         575 :                 if (!it.second.exists) {
     383         575 :                         stream << "CREATE TABLE IF NOT EXISTS " << it.first << " (\n";
     384             : 
     385         575 :                         bool first = true;
     386        3250 :                         for (auto cit = t.cols.begin(); cit != t.cols.end(); cit ++) {
     387        2675 :                                 if (first) { first = false; } else { stream << ",\n"; }
     388        2675 :                                 stream << "\t\"" << cit->first << "\" ";
     389        2675 :                                 if (cit->second.serial) {
     390         125 :                                         stream << "bigserial";
     391             :                                 } else {
     392        2550 :                                         switch(cit->second.type) {
     393         125 :                                         case ColRec::Type::Unknown: stream << cit->second.custom; break;
     394         125 :                                         case ColRec::Type::Bool:        stream << "boolean"; break;
     395           0 :                                         case ColRec::Type::Char:        stream << "\"char\""; break;
     396           0 :                                         case ColRec::Type::Float4:      stream << "real"; break;
     397          25 :                                         case ColRec::Type::Float8:      stream << "double precision"; break;
     398           0 :                                         case ColRec::Type::Int2:        stream << "smallint"; break;
     399           0 :                                         case ColRec::Type::Int4:        stream << "integer"; break;
     400        1250 :                                         case ColRec::Type::Int8:        stream << "bigint"; break;
     401         675 :                                         case ColRec::Type::Text:        stream << "text"; break;
     402           0 :                                         case ColRec::Type::VarChar: stream << "varchar"; break;
     403           0 :                                         case ColRec::Type::Numeric: stream << "numeric"; break;
     404         325 :                                         case ColRec::Type::Bytes:       stream << "bytea"; break;
     405          25 :                                         case ColRec::Type::TsVector:stream << "tsvector"; break;
     406           0 :                                         default: break;
     407             :                                         }
     408             :                                 }
     409             : 
     410        2675 :                                 if (cit->second.notNull) {
     411         625 :                                         stream << " NOT NULL";
     412             :                                 }
     413             :                         }
     414             : 
     415         575 :                         first = true;
     416         575 :                         if (!t.pkey.empty()) {
     417         575 :                                 stream << ",\n\tPRIMARY KEY (";
     418        1225 :                                 for (auto &key : t.pkey) {
     419         650 :                                         if (first) { first = false; } else { stream << ", "; }
     420         650 :                                         stream << "\"" << key << "\"";
     421             :                                 }
     422         575 :                                 stream << ")";
     423             :                         }
     424             : 
     425         575 :                         stream << "\n)";
     426         575 :                         if (it.second.objects) {
     427         375 :                                 stream << " INHERITS (__objects)";
     428             :                         }
     429         575 :                         stream << " WITH ( OIDS=FALSE );\n\n";
     430             :                 } else {
     431           0 :                         for (auto cit : t.cols) {
     432           0 :                                 if (cit.first != "__oid") {
     433           0 :                                         stream << "ALTER TABLE " << it.first << " ADD COLUMN \"" << cit.first << "\" ";
     434           0 :                                         if (cit.second.serial) {
     435           0 :                                                 stream << "bigserial";
     436             :                                         } else {
     437           0 :                                                 switch(cit.second.type) {
     438           0 :                                                 case ColRec::Type::Unknown: stream << cit.second.custom; break;
     439           0 :                                                 case ColRec::Type::Bool:        stream << "boolean"; break;
     440           0 :                                                 case ColRec::Type::Char:        stream << "\"char\""; break;
     441           0 :                                                 case ColRec::Type::Float4:      stream << "real"; break;
     442           0 :                                                 case ColRec::Type::Float8:      stream << "double precision"; break;
     443           0 :                                                 case ColRec::Type::Int2:        stream << "smallint"; break;
     444           0 :                                                 case ColRec::Type::Int4:        stream << "integer"; break;
     445           0 :                                                 case ColRec::Type::Int8:        stream << "bigint"; break;
     446           0 :                                                 case ColRec::Type::Text:        stream << "text"; break;
     447           0 :                                                 case ColRec::Type::VarChar: stream << "varchar"; break;
     448           0 :                                                 case ColRec::Type::Numeric: stream << "numeric"; break;
     449           0 :                                                 case ColRec::Type::Bytes:       stream << "bytea"; break;
     450           0 :                                                 case ColRec::Type::TsVector:stream << "tsvector"; break;
     451           0 :                                                 default: break;
     452             :                                                 }
     453             :                                         }
     454           0 :                                         if (cit.second.notNull) {
     455           0 :                                                 stream << " NOT NULL";
     456             :                                         }
     457           0 :                                         stream << ";\n";
     458             :                                 }
     459           0 :                         }
     460             :                 }
     461             :         }
     462             : 
     463             :         // write constraints
     464         600 :         for (auto &it : required) {
     465        1400 :                 for (auto & cit : it.second.constraints) {
     466         825 :                         stream << "ALTER TABLE " << it.first << " ADD CONSTRAINT \"" << cit.first << "\" ";
     467             : 
     468         825 :                         bool first = true;
     469         825 :                         switch (cit.second.type) {
     470         200 :                         case ConstraintRec::Unique:
     471         200 :                                 stream << " UNIQUE ( ";
     472         400 :                                 for (auto &key : cit.second.fields) {
     473         200 :                                         if (first) { first = false; } else { stream << ", "; }
     474         200 :                                         stream << "\"" << key << "\"";
     475             :                                 }
     476         200 :                                 stream << " )";
     477         200 :                                 break;
     478         625 :                         case ConstraintRec::Reference:
     479         625 :                                 stream << " FOREIGN KEY (";
     480        1250 :                                 for (auto &key : cit.second.fields) {
     481         625 :                                         if (first) { first = false; } else { stream << ", "; }
     482         625 :                                         stream << "\"" << key << "\"";
     483             :                                 }
     484         625 :                                 stream << ") REFERENCES " << cit.second.reference << " ( \"__oid\" )";
     485         625 :                                 switch (cit.second.remove) {
     486         300 :                                 case db::RemovePolicy::Cascade:
     487         300 :                                         stream << " ON DELETE CASCADE";
     488         300 :                                         break;
     489           0 :                                 case db::RemovePolicy::Restrict:
     490           0 :                                         stream << " ON DELETE RESTRICT";
     491           0 :                                         break;
     492         325 :                                 case db::RemovePolicy::Null:
     493             :                                 case db::RemovePolicy::Reference:
     494             :                                 case db::RemovePolicy::StrongReference:
     495         325 :                                         stream << " ON DELETE SET NULL";
     496         325 :                                         break;
     497             :                                 }
     498         625 :                                 break;
     499             :                         }
     500             : 
     501         825 :                         stream << ";\n";
     502             :                 }
     503             :         }
     504             : 
     505             :         // indexes
     506         600 :         for (auto &it : required) {
     507        1600 :                 for (auto & cit : it.second.indexes) {
     508        1025 :                         if (cit.second.back() != ')') {
     509         400 :                                 stream << "CREATE INDEX IF NOT EXISTS \"" << cit.first
     510         400 :                                                 << "\" ON " << it.first << " ( \"" << cit.second << "\" );\n";
     511             :                         } else {
     512         625 :                                 stream << "CREATE INDEX IF NOT EXISTS \"" << cit.first
     513         625 :                                                 << "\" ON " << it.first << " " << cit.second << ";\n";
     514             :                         }
     515             :                 }
     516             : 
     517         575 :                 if (!it.second.triggers.empty()) {
     518         125 :                         auto scheme_it = s.find(it.first);
     519         125 :                         if (scheme_it != s.end()) {
     520         250 :                                 for (auto & tit : it.second.triggers) {
     521         150 :                                         if (StringView(tit).starts_with("_tr_a_")) {
     522         100 :                                                 writeAfterTrigger(stream, scheme_it->second, tit);
     523             :                                         } else {
     524          50 :                                                 writeBeforeTrigger(stream, scheme_it->second, tit);
     525             :                                         }
     526             :                                 }
     527          25 :                         } else if (it.second.viewField) {
     528          50 :                                 for (auto & tit : it.second.triggers) {
     529          25 :                                         writeDeltaTrigger(stream, it.first, it.second, tit);
     530             :                                 }
     531             :                         }
     532             :                 }
     533             :         }
     534          25 : }
     535             : 
     536          25 : Map<StringView, TableRec> TableRec::parse(const Driver *driver, const BackendInterface::Config &cfg,
     537             :                 const Map<StringView, const db::Scheme *> &s, const Vector<Pair<StringView, int64_t>> &customs) {
     538          25 :         Map<StringView, TableRec> tables;
     539         400 :         for (auto &it : s) {
     540         375 :                 auto scheme = it.second;
     541         375 :                 tables.emplace(scheme->getName(), TableRec(driver, cfg, scheme, customs));
     542             : 
     543             :                 // check for extra tables
     544        2700 :                 for (auto &fit : scheme->getFields()) {
     545        2325 :                         auto &f = fit.second;
     546        2325 :                         auto type = fit.second.getType();
     547             : 
     548        2325 :                         if (type == db::Type::Set) {
     549         150 :                                 auto ref = static_cast<const db::FieldObject *>(f.getSlot());
     550         150 :                                 if (ref->onRemove == db::RemovePolicy::Reference || ref->onRemove == db::RemovePolicy::StrongReference) {
     551          75 :                                         String name = toString(it.first, "_f_", fit.first);
     552          75 :                                         auto & source = it.first;
     553          75 :                                         auto target = ref->scheme->getName();
     554          75 :                                         TableRec table;
     555          75 :                                         table.cols.emplace(toString(source, "_id"), ColRec(ColRec::Type::Int8, true));
     556          75 :                                         table.cols.emplace(toString(target, "_id"), ColRec(ColRec::Type::Int8, true));
     557             : 
     558          75 :                                         table.constraints.emplace(toString(name, "_ref_", source), ConstraintRec(
     559         150 :                                                         ConstraintRec::Reference, toString(source, "_id"), source, db::RemovePolicy::Cascade));
     560         150 :                                         table.constraints.emplace(toString(name, "_ref_", ref->getName()), ConstraintRec(
     561         225 :                                                         ConstraintRec::Reference, toString(target, "_id"), target.str<Interface>(), db::RemovePolicy::Cascade));
     562             : 
     563          75 :                                         table.indexes.emplace(toString(name, "_idx_", source), toString(source, "_id"));
     564          75 :                                         table.indexes.emplace(toString(name, "_idx_", target), toString(target, "_id"));
     565             : 
     566          75 :                                         table.pkey.emplace_back(toString(source, "_id"));
     567          75 :                                         table.pkey.emplace_back(toString(target, "_id"));
     568          75 :                                         tables.emplace(StringView(stappler::string::tolower<Interface>(name)).pdup(), std::move(table));
     569          75 :                                 }
     570        2175 :                         } else if (type == db::Type::Array) {
     571          25 :                                 auto slot = static_cast<const db::FieldArray *>(f.getSlot());
     572          25 :                                 if (slot->tfield && slot->tfield.isSimpleLayout()) {
     573             : 
     574          25 :                                         String name = toString(it.first, "_f_", fit.first);
     575          25 :                                         string::apply_tolower_c(name);
     576          25 :                                         auto & source = it.first;
     577             : 
     578          25 :                                         TableRec table;
     579          25 :                                         table.cols.emplace("id", ColRec(ColRec::Type::Int8, true, true));
     580          25 :                                         table.cols.emplace(toString(source, "_id"), ColRec(ColRec::Type::Int8));
     581             : 
     582          25 :                                         auto type = slot->tfield.getType();
     583          25 :                                         switch (type) {
     584           0 :                                         case db::Type::Float:
     585           0 :                                                 table.cols.emplace("data", ColRec(ColRec::Type::Float8));
     586           0 :                                                 break;
     587           0 :                                         case db::Type::Boolean:
     588           0 :                                                 table.cols.emplace("data", ColRec(ColRec::Type::Bool));
     589           0 :                                                 break;
     590          25 :                                         case db::Type::Text:
     591          25 :                                                 table.cols.emplace("data", ColRec(ColRec::Type::Text));
     592          25 :                                                 break;
     593           0 :                                         case db::Type::Data:
     594             :                                         case db::Type::Bytes:
     595             :                                         case db::Type::Extra:
     596           0 :                                                 table.cols.emplace("data", ColRec(ColRec::Type::Bytes));
     597           0 :                                                 break;
     598           0 :                                         case db::Type::Integer:
     599           0 :                                                 table.cols.emplace("data", ColRec(ColRec::Type::Int8));
     600           0 :                                                 break;
     601           0 :                                         default:
     602           0 :                                                 break;
     603             :                                         }
     604             : 
     605          25 :                                         table.constraints.emplace(toString(name, "_ref_", source), ConstraintRec (
     606          50 :                                                         ConstraintRec::Reference, toString(source, "_id"), source, db::RemovePolicy::Cascade));
     607          25 :                                         table.pkey.emplace_back("id");
     608             : 
     609          25 :                                         if (f.hasFlag(db::Flags::Unique)) {
     610           0 :                                                 table.constraints.emplace(name + "_unique", ConstraintRec(ConstraintRec::Unique, {toString(source, "_id"), "data"}));
     611             :                                         }
     612             : 
     613          25 :                                         table.indexes.emplace(toString(name, "_idx_", source), toString(source, "_id"));
     614          25 :                                         tables.emplace(StringView(name).pdup(), std::move(table));
     615          25 :                                 }
     616        2150 :                         } else if (type == db::Type::View) {
     617          50 :                                 auto slot = static_cast<const db::FieldView *>(f.getSlot());
     618             : 
     619          50 :                                 String name = toString(it.first, "_f_", fit.first, "_view");
     620          50 :                                 auto & source = it.first;
     621          50 :                                 auto target = slot->scheme->getName();
     622             : 
     623          50 :                                 TableRec table;
     624          50 :                                 table.viewScheme = it.second;
     625          50 :                                 table.viewField = slot;
     626          50 :                                 table.cols.emplace("__vid", ColRec(ColRec::Type::Int8, true, true));
     627          50 :                                 table.cols.emplace(toString(source, "_id"), ColRec(ColRec::Type::Int8, true));
     628          50 :                                 table.cols.emplace(toString(target, "_id"), ColRec(ColRec::Type::Int8, true));
     629             : 
     630          50 :                                 table.constraints.emplace(toString(name, "_ref_", source), ConstraintRec(
     631         100 :                                                 ConstraintRec::Reference, toString(source, "_id"), source, db::RemovePolicy::Cascade));
     632         100 :                                 table.constraints.emplace(toString(name, "_ref_", slot->getName()), ConstraintRec(
     633         150 :                                                 ConstraintRec::Reference, toString(target, "_id"), target.str<Interface>(), db::RemovePolicy::Cascade));
     634             : 
     635          50 :                                 table.indexes.emplace(toString(name, "_idx_", source), toString(source, "_id"));
     636          50 :                                 table.indexes.emplace(toString(name, "_idx_", target), toString(target, "_id"));
     637             : 
     638          50 :                                 table.pkey.emplace_back("__vid");
     639          50 :                                 auto tblIt = tables.emplace(StringView(name).pdup(), std::move(table)).first;
     640             : 
     641          50 :                                 if (slot->delta) {
     642          25 :                                         StringStream hashStream;
     643          25 :                                         hashStream << getDefaultFunctionVersion() << tblIt->first << "_delta";
     644             : 
     645          25 :                                         size_t id = std::hash<String>{}(hashStream.weak());
     646          25 :                                         hashStream.clear();
     647          25 :                                         hashStream << "_trig_" << tblIt->first << "_" << id;
     648          25 :                                         tblIt->second.triggers.emplace(StringView(hashStream.weak()).sub(0, 56).str<Interface>());
     649             : 
     650          25 :                                         String name = toString(it.first, "_f_", fit.first, "_delta");
     651          25 :                                         table.cols.emplace("id", ColRec(ColRec::Type::Int8, true, true));
     652          25 :                                         table.cols.emplace("tag", ColRec(ColRec::Type::Int8, true));
     653          25 :                                         table.cols.emplace("object", ColRec(ColRec::Type::Int8, true));
     654          25 :                                         table.cols.emplace("time", ColRec(ColRec::Type::Int8, true));
     655          25 :                                         table.cols.emplace("user", ColRec(ColRec::Type::Int8));
     656             : 
     657          25 :                                         table.pkey.emplace_back("id");
     658          25 :                                         table.indexes.emplace(name + "_idx_tag", "tag");
     659          25 :                                         table.indexes.emplace(name + "_idx_object", "object");
     660          25 :                                         table.indexes.emplace(name + "_idx_time", "time");
     661          25 :                                         tables.emplace(StringView(name).pdup(), std::move(table));
     662          25 :                                 }
     663          50 :                         }
     664             : 
     665        2325 :                         if (scheme->hasDelta()) {
     666          25 :                                 auto name = Handle::getNameForDelta(*scheme);
     667          25 :                                 TableRec table;
     668          25 :                                 table.cols.emplace("id", ColRec(ColRec::Type::Int8, true, true));
     669          25 :                                 table.cols.emplace("object", ColRec(ColRec::Type::Int8, true));
     670          25 :                                 table.cols.emplace("time", ColRec(ColRec::Type::Int8, true));
     671          25 :                                 table.cols.emplace("action", ColRec(ColRec::Type::Int8, true));
     672          25 :                                 table.cols.emplace("user", ColRec(ColRec::Type::Int8));
     673             : 
     674          25 :                                 table.pkey.emplace_back("id");
     675          25 :                                 table.indexes.emplace(name + "_idx_object", "object");
     676          25 :                                 table.indexes.emplace(name + "_idx_time", "time");
     677          25 :                                 tables.emplace(StringView(name).pdup(), std::move(table));
     678          25 :                         }
     679             :                 }
     680             :         }
     681          25 :         return tables;
     682           0 : }
     683             : 
     684          25 : Map<StringView, TableRec> TableRec::get(Handle &h, StringStream &stream) {
     685          25 :         Map<StringView, TableRec> ret;
     686             : 
     687          25 :         h.performSimpleSelect("SELECT table_name FROM information_schema.tables "
     688             :                         "WHERE table_schema='public' AND table_type='BASE TABLE';",
     689          25 :                         [&] (db::sql::Result &tables) {
     690         150 :                 for (auto it : tables) {
     691         125 :                         ret.emplace(it.at(0).pdup(), TableRec());
     692         125 :                         stream << "TABLE " << it.at(0) << "\n";
     693             :                 }
     694          25 :                 tables.clear();
     695          25 :         });
     696             : 
     697          25 :         h.performSimpleSelect(COL_QUERY, [&] (db::sql::Result &columns) {
     698         475 :                 for (auto it : columns) {
     699         450 :                         auto tname = it.at(0).str<Interface>();
     700         450 :                         auto f = ret.find(tname);
     701         450 :                         if (f != ret.end()) {
     702         450 :                                 auto &table = f->second;
     703         450 :                                 bool isNullable = (it.at(2) == "YES");
     704         450 :                                 auto type = it.at(3);
     705         450 :                                 if (it.at(1) != "__oid") {
     706         400 :                                         auto storageType = h.getDriver()->getTypeById(it.toInteger(4));
     707         400 :                                         switch (storageType) {
     708          25 :                                         case BackendInterface::StorageType::Unknown:
     709          25 :                                                 table.cols.emplace(it.at(1).str<Interface>(), ColRec(type, it.toInteger(4), !isNullable));
     710          25 :                                                 break;
     711         375 :                                         default:
     712         375 :                                                 table.cols.emplace(it.at(1).str<Interface>(), ColRec(storageType, it.toInteger(4), !isNullable));
     713         375 :                                                 break;
     714             :                                         }
     715             :                                 }
     716         450 :                                 stream << "COLUMNS " << it.at(0) << " " << it.at(1) << " " << it.at(2) << " " << it.at(3) << " (" <<  it.toInteger(4) << ")\n";
     717             :                         }
     718         450 :                 }
     719          25 :                 columns.clear();
     720          25 :         });
     721             : 
     722          25 :         h.performSimpleSelect("SELECT table_name, constraint_name, constraint_type FROM information_schema.table_constraints "
     723             :                         "WHERE table_schema='public' AND constraint_schema='public';"_weak,
     724          25 :                         [&] (db::sql::Result &constraints) {
     725         475 :                 for (auto it : constraints) {
     726         450 :                         auto tname = it.at(0).str<Interface>();
     727         450 :                         auto f = ret.find(tname);
     728         450 :                         if (f != ret.end()) {
     729         450 :                                 auto &table = f->second;
     730         450 :                                 if (it.at(2) == "UNIQUE") {
     731           0 :                                         table.constraints.emplace(it.at(1).str<Interface>(), ConstraintRec(ConstraintRec::Unique));
     732           0 :                                         stream << "CONSTRAINT " << it.at(0) << " " << it.at(1) << " " << it.at(2) << "\n";
     733         450 :                                 } else if (it.at(2) == "FOREIGN KEY") {
     734           0 :                                         table.constraints.emplace(it.at(1).str<Interface>(), ConstraintRec(ConstraintRec::Reference));
     735           0 :                                         stream << "CONSTRAINT " << it.at(0) << " " << it.at(1) << " " << it.at(2) << "\n";
     736             :                                 }
     737             :                         }
     738         450 :                 }
     739          25 :                 constraints.clear();
     740          25 :         });
     741             : 
     742          25 :         h.performSimpleSelect(String::make_weak(INDEX_QUERY),
     743          25 :                         [&] (db::sql::Result &indexes) {
     744         225 :                 for (auto it : indexes) {
     745         200 :                         auto tname = it.at(0).str<Interface>();
     746         200 :                         auto f = ret.find(tname);
     747         200 :                         if (f != ret.end()) {
     748         200 :                                 auto &table = f->second;
     749         200 :                                 auto name = it.at(1);
     750         200 :                                 name.readUntilString("_idx_");
     751         200 :                                 if (name.is("_idx_")) {
     752           0 :                                         table.indexes.emplace(it.at(1).str<Interface>(), it.at(2).str<Interface>());
     753           0 :                                         stream << "INDEX " << it.at(0) << " " << it.at(1) << " " << it.at(2) << "\n";
     754             :                                 }
     755             :                         }
     756         200 :                 }
     757          25 :                 indexes.clear();
     758          25 :         });
     759             : 
     760          25 :         h.performSimpleSelect("SELECT event_object_table, trigger_name FROM information_schema.triggers "
     761             :                         "WHERE trigger_schema='public';"_weak,
     762          25 :                         [&] (db::sql::Result &triggers) {
     763          25 :                 for (auto it : triggers) {
     764           0 :                         auto tname = it.at(0).str<Interface>();
     765           0 :                         auto f = ret.find(tname);
     766           0 :                         if (f != ret.end()) {
     767           0 :                                 auto &table = f->second;
     768           0 :                                 table.triggers.emplace(it.at(1).str<Interface>());
     769           0 :                                 stream << "TRIGGER " << it.at(0) << " " << it.at(1) << "\n";
     770             :                         }
     771           0 :                 }
     772          25 :                 triggers.clear();
     773          25 :         });
     774             : 
     775          25 :         return ret;
     776           0 : }
     777             : 
     778         300 : TableRec::TableRec() : objects(false) { }
     779         375 : TableRec::TableRec(const Driver *driver, const BackendInterface::Config &cfg, const db::Scheme *scheme,
     780         375 :                 const Vector<Pair<StringView, int64_t>> &customs) {
     781         375 :         StringStream hashStreamAfter; hashStreamAfter << getDefaultFunctionVersion();
     782         375 :         StringStream hashStreamBefore; hashStreamBefore << getDefaultFunctionVersion();
     783             : 
     784         375 :         bool hasAfterTrigger = false;
     785         375 :         bool hasBeforeTrigger = false;
     786         375 :         auto name = scheme->getName();
     787         375 :         pkey.emplace_back("__oid");
     788             : 
     789         375 :         if (scheme->hasDelta()) {
     790          25 :                 hasAfterTrigger = true;
     791          25 :                 hashStreamAfter << ":delta:";
     792             :         }
     793             : 
     794        2700 :         for (auto &it : scheme->getFields()) {
     795        2325 :                 bool emplaced = false;
     796        2325 :                 auto &f = it.second;
     797        2325 :                 auto type = it.second.getType();
     798             : 
     799        2325 :                 if (type == db::Type::File || type == db::Type::Image) {
     800         250 :                         hasAfterTrigger = true;
     801         250 :                         hashStreamAfter << it.first << stappler::toInt(type);
     802             :                 }
     803             : 
     804        2325 :                 switch (type) {
     805         125 :                 case db::Type::None:
     806             :                 case db::Type::Array:
     807             :                 case db::Type::View:
     808             :                 case db::Type::Virtual:
     809         125 :                         break;
     810             : 
     811          25 :                 case db::Type::Float:
     812          25 :                         cols.emplace(it.first, ColRec(ColRec::Type::Float8, f.hasFlag(db::Flags::Required)));
     813          25 :                         emplaced = true;
     814          25 :                         break;
     815             : 
     816         125 :                 case db::Type::Boolean:
     817         125 :                         cols.emplace(it.first, ColRec(ColRec::Type::Bool, f.hasFlag(db::Flags::Required)));
     818         125 :                         emplaced = true;
     819         125 :                         break;
     820             : 
     821         650 :                 case db::Type::Text:
     822         650 :                         cols.emplace(it.first, ColRec(ColRec::Type::Text, f.hasFlag(db::Flags::Required)));
     823         650 :                         emplaced = true;
     824         650 :                         break;
     825             : 
     826         325 :                 case db::Type::Data:
     827             :                 case db::Type::Bytes:
     828             :                 case db::Type::Extra:
     829         325 :                         cols.emplace(it.first, ColRec(ColRec::Type::Bytes, f.hasFlag(db::Flags::Required)));
     830         325 :                         emplaced = true;
     831         325 :                         break;
     832             : 
     833         675 :                 case db::Type::Integer:
     834             :                 case db::Type::File:
     835             :                 case db::Type::Image:
     836         675 :                         cols.emplace(it.first, ColRec(ColRec::Type::Int8, f.hasFlag(db::Flags::Required)));
     837         675 :                         emplaced = true;
     838         675 :                         break;
     839             : 
     840          25 :                 case db::Type::FullTextView:
     841          25 :                         cols.emplace(it.first, ColRec(ColRec::Type::TsVector, f.hasFlag(db::Flags::Required)));
     842          25 :                         emplaced = true;
     843          25 :                         break;
     844             : 
     845         100 :                 case db::Type::Object:
     846         100 :                         cols.emplace(it.first, ColRec(ColRec::Type::Int8, f.hasFlag(db::Flags::Required)));
     847         100 :                         if (f.isReference()) {
     848           0 :                                 auto objSlot = static_cast<const db::FieldObject *>(f.getSlot());
     849           0 :                                 if (objSlot->onRemove == db::RemovePolicy::StrongReference) {
     850           0 :                                         hasAfterTrigger = true;
     851           0 :                                         hashStreamAfter << it.first << stappler::toInt(type);
     852             :                                 }
     853             :                         }
     854         100 :                         emplaced = true;
     855         100 :                         break;
     856             : 
     857         150 :                 case db::Type::Set:
     858         150 :                         if (f.isReference()) {
     859          75 :                                 auto objSlot = static_cast<const db::FieldObject *>(f.getSlot());
     860          75 :                                 if (objSlot->onRemove == db::RemovePolicy::StrongReference) {
     861          50 :                                         hasBeforeTrigger = true;
     862          50 :                                         hashStreamBefore << it.first << stappler::toInt(type);
     863             :                                 }
     864             :                         }
     865         150 :                         break;
     866             : 
     867         125 :                 case db::Type::Custom:
     868         125 :                         if (auto objSlot = f.getSlot<db::FieldCustom>()) {
     869         125 :                                 if (auto info = driver->getCustomFieldInfo(objSlot->getDriverTypeName())) {
     870         125 :                                         int64_t oid = 0;
     871         350 :                                         for (auto &it : customs) {
     872         350 :                                                 if (it.first == info->typeName) {
     873         125 :                                                         oid = it.second;
     874         125 :                                                         break;
     875             :                                                 }
     876             :                                         }
     877         125 :                                         if (oid) {
     878         125 :                                                 cols.emplace(it.first, ColRec(info->typeName, oid, f.hasFlag(db::Flags::Required)));
     879             :                                         } else {
     880           0 :                                                 cols.emplace(it.first, ColRec(info->typeName, f.hasFlag(db::Flags::Required)));
     881             :                                         }
     882         125 :                                         emplaced = true;
     883             :                                 }
     884             :                         }
     885         125 :                         break;
     886             :                 }
     887             : 
     888        2325 :                 if (emplaced) {
     889        2050 :                         if (type == db::Type::Object) {
     890         100 :                                 auto ref = static_cast<const db::FieldObject *>(f.getSlot());
     891         100 :                                 auto target = ref->scheme->getName();
     892         100 :                                 StringStream cname; cname << name << "_ref_" << it.first << "_" << target;
     893             : 
     894         100 :                                 switch (ref->onRemove) {
     895          25 :                                 case RemovePolicy::Cascade: cname << "_csc"; break;
     896           0 :                                 case RemovePolicy::Restrict: cname << "_rst"; break;
     897           0 :                                 case RemovePolicy::Reference: cname << "_ref"; break;
     898           0 :                                 case RemovePolicy::StrongReference: cname << "_sref"; break;
     899          75 :                                 case RemovePolicy::Null: break;
     900             :                                 }
     901             : 
     902         100 :                                 constraints.emplace(cname.str(), ConstraintRec(ConstraintRec::Reference, it.first, target.str<Interface>(), ref->onRemove));
     903         100 :                                 indexes.emplace(toString(name, "_idx_", it.first), toString("( \"", it.first, "\" )"));
     904        2050 :                         } else if (type == db::Type::File || type == db::Type::Image) {
     905         250 :                                 auto ref = cfg.fileScheme;
     906         250 :                                 auto cname = toString(name, "_ref_", it.first);
     907         250 :                                 auto target = ref->getName();
     908         250 :                                 constraints.emplace(cname, ConstraintRec(ConstraintRec::Reference, it.first, target.str<Interface>(), db::RemovePolicy::Null));
     909         250 :                         }
     910             : 
     911        3975 :                         if ((type == db::Type::Text && f.getTransform() == db::Transform::Alias) ||
     912        1925 :                                         f.hasFlag(db::Flags::Unique)) {
     913         200 :                                 constraints.emplace(toString(name, "_unique_", it.first), ConstraintRec(ConstraintRec::Unique, it.first));
     914             :                         }
     915             : 
     916         650 :                         if ((type == db::Type::Text && f.getTransform() == db::Transform::Alias)
     917        2700 :                                         || (f.hasFlag(db::Flags::Indexed) && !f.hasFlag(db::Flags::Unique))) {
     918         475 :                                 if (type == db::Type::Custom) {
     919          25 :                                         auto c = f.getSlot<db::FieldCustom>();
     920          25 :                                         if (auto info = driver->getCustomFieldInfo(c->getDriverTypeName())) {
     921          25 :                                                 if (info->isIndexable) {
     922          25 :                                                         indexes.emplace(toString(name, "_idx_", info->getIndexName(*c)), info->getIndexDefinition(*c));
     923             :                                                 }
     924             :                                         }
     925         450 :                                 } else if (type == db::Type::FullTextView) {
     926           0 :                                         indexes.emplace(toString(name, "_idx_", it.first), toString("USING GIN ( \"", it.first, "\" )"));
     927             :                                 } else {
     928         450 :                                         indexes.emplace(toString(name, "_idx_", it.first), toString("( \"", it.first, "\" )"));
     929             :                                 }
     930             :                         }
     931             : 
     932        2050 :                         if (type == db::Type::Text) {
     933         650 :                                 if (f.hasFlag(db::Flags::PatternIndexed)) {
     934          25 :                                         indexes.emplace(toString(name, "_idx_", it.first, "_pattern"), toString("USING btree ( \"", it.first, "\" text_pattern_ops)"));
     935             :                                 }
     936         650 :                                 if (f.hasFlag(db::Flags::TrigramIndexed)) {
     937          25 :                                         indexes.emplace(toString(name, "_idx_", it.first, "_trgm"), toString("USING GIN ( \"", it.first, "\" gin_trgm_ops)"));
     938             :                                 }
     939             :                         }
     940             :                 }
     941             :         }
     942             : 
     943         375 :         for (auto &it : scheme->getUnique()) {
     944           0 :                 auto &c = constraints.emplace(it.name.str<Interface>(), ConstraintRec(ConstraintRec::Unique)).first->second;
     945           0 :                 for (auto &f : it.fields) {
     946           0 :                         c.fields.emplace_back(f->getName().str<Interface>());
     947             :                 }
     948             :         }
     949             : 
     950         375 :         if (scheme->isDetouched()) {
     951           0 :                 cols.emplace("__oid", ColRec(ColRec::Type::Int8, true, true));
     952           0 :                 objects = false;
     953             :         }
     954             : 
     955         375 :         if (hasAfterTrigger) {
     956         100 :                 size_t id = std::hash<String>{}(hashStreamAfter.weak());
     957             : 
     958         100 :                 hashStreamAfter.clear();
     959         100 :                 hashStreamAfter << "_tr_a_" << scheme->getName() << "_" << id;
     960         100 :                 triggers.emplace(StringView(hashStreamAfter.weak()).sub(0, 56).str<Interface>());
     961             :         }
     962             : 
     963         375 :         if (hasBeforeTrigger) {
     964          50 :                 size_t id = std::hash<String>{}(hashStreamBefore.weak());
     965             : 
     966          50 :                 hashStreamBefore.clear();
     967          50 :                 hashStreamBefore << "_tr_b_" << scheme->getName() << "_" << id;
     968          50 :                 triggers.emplace(StringView(hashStreamBefore.weak()).sub(0, 56).str<Interface>());
     969             :         }
     970         375 : }
     971             : 
     972         125 : static void Handle_insert_sorted(Vector<Pair<StringView, int64_t>> & vec, StringView type) {
     973         125 :         auto it = std::upper_bound(vec.begin(), vec.end(), type, [] (const StringView &l, const Pair<StringView, int64_t> &r) -> bool {
     974         175 :                 return l < r.first;
     975         125 :         });
     976         125 :         vec.emplace(it, type, 0);
     977         125 : }
     978             : 
     979          25 : bool Handle::init(const BackendInterface::Config &cfg, const Map<StringView, const Scheme *> &s) {
     980          25 :         if (!performSimpleQuery(StringView(DATABASE_DEFAULTS))) {
     981           0 :                 return false;
     982             :         }
     983             : 
     984          25 :         if (!performSimpleQuery("START TRANSACTION; LOCK TABLE __objects;")) {
     985           0 :                 return false;
     986             :         }
     987             : 
     988          25 :         StringStream tables;
     989          25 :         tables << "Server: " << cfg.name << "\n";
     990             : 
     991          25 :         Vector<Pair<StringView, int64_t>> customFields;
     992             : 
     993         400 :         for (auto &it : s) {
     994        2700 :                 for (auto &f : it.second->getFields()) {
     995        2325 :                         if (f.second.getType() == Type::Custom) {
     996         125 :                                 auto objSlot = f.second.getSlot<db::FieldCustom>();
     997         125 :                                 if (auto info = driver->getCustomFieldInfo(objSlot->getDriverTypeName())) {
     998         125 :                                         Handle_insert_sorted(customFields, info->typeName);
     999             :                                 }
    1000             :                         }
    1001             :                 }
    1002             :         }
    1003             : 
    1004          25 :         if (!customFields.empty()) {
    1005          25 :                 StringStream tempTable;
    1006          25 :                 tempTable << "CREATE TEMPORARY TABLE custom_fields (\n\tid integer primary key";
    1007          25 :                 size_t idx = 0;
    1008         150 :                 for (auto &it : customFields) {
    1009         125 :                         tempTable << ",\n\tfield" << idx << " " << it.first;
    1010         125 :                         ++ idx;
    1011             :                 }
    1012          25 :                 tempTable << "\n);";
    1013             : 
    1014          25 :                 performSimpleQuery(tempTable.weak());
    1015             : 
    1016          25 :                 performSimpleSelect("SELECT attname, atttypid::integer FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'custom_fields');",
    1017          25 :                                 [&] (db::sql::Result &result) {
    1018         325 :                         for (auto it : result) {
    1019         300 :                                 auto n = it.at(0);
    1020         300 :                                 if (n.starts_with("field")) {
    1021         125 :                                         n += "field"_len;
    1022         125 :                                         auto idx = n.readInteger(10).get(0);
    1023         125 :                                         customFields[idx].second = it.toInteger(1);
    1024             :                                 }
    1025             :                         }
    1026          25 :                         tables.clear();
    1027          25 :                 });
    1028             : 
    1029          25 :                 performSimpleQuery("DROP TABLE custom_fields;");
    1030          25 :         }
    1031             : 
    1032          25 :         auto requiredTables = TableRec::parse(driver, cfg, s, customFields);
    1033          25 :         auto existedTables = TableRec::get(*this, tables);
    1034             : 
    1035          25 :         StringStream stream;
    1036          25 :         TableRec::writeCompareResult(stream, requiredTables, existedTables, s);
    1037             : 
    1038          25 :         if (stream.size() > 3) {
    1039          25 :                 bool success = true;
    1040          25 :                 if (performSimpleQuery(stream.weak(), [&] (const Value &errInfo) {
    1041           0 :                         stream << "Server: " << cfg.name << "\n";
    1042           0 :                         stream << "\nErrorInfo: " << EncodeFormat::Pretty << errInfo << "\n";
    1043           0 :                 })) {
    1044          25 :                         performSimpleQuery("COMMIT;"_weak);
    1045             :                 } else {
    1046           0 :                         log::error("Database", "Fail to perform database update");
    1047           0 :                         stream << "\nError: " << driver->getStatusMessage(lastError) << "\n";
    1048           0 :                         performSimpleQuery("ROLLBACK;"_weak);
    1049           0 :                         success = false;
    1050             :                 }
    1051             : 
    1052          25 :                 tables << "\n" << stream;
    1053          25 :                 _driver->getApplicationInterface()->reportDbUpdate(tables.weak(), success);
    1054          25 :                 if (!success) {
    1055           0 :                         return false;
    1056             :                 }
    1057             :         } else {
    1058           0 :                 performSimpleQuery("COMMIT;"_weak);
    1059             :         }
    1060             : 
    1061          25 :         beginTransaction_pg(TransactionLevel::ReadCommited);
    1062          25 :         StringStream query;
    1063          25 :         query << "DELETE FROM __login WHERE \"date\" < " << Time::now().toSeconds() - config::STORAGE_DEFAULT_INTERNAL_INTERVAL.toSeconds() << ";";
    1064          25 :         performSimpleQuery(query.weak());
    1065          25 :         query.clear();
    1066             : 
    1067          25 :         auto iit = existedTables.find(StringView("__error"));
    1068          25 :         if (iit != existedTables.end()) {
    1069           0 :                 query << "DELETE FROM __error WHERE \"time\" < " << Time::now().toMicros() - config::STORAGE_DEFAULT_INTERNAL_INTERVAL.toMicros() << ";";
    1070           0 :                 performSimpleQuery(query.weak());
    1071           0 :                 query.clear();
    1072             :         }
    1073             : 
    1074          25 :         endTransaction_pg();
    1075          25 :         return true;
    1076          25 : }
    1077             : 
    1078             : }

Generated by: LCOV version 1.14