using System; using System.Text; using Microsoft.Extensions.Logging; using Microsoft.EntityFrameworkCore; using AyaNova.Models; namespace AyaNova.Util { //Key generator controller public static class AySchema { private static ILogger log; private static AyContext ct; ///////////////////////////////////////////////////////////////// /////////// CHANGE THIS ON NEW SCHEMA UPDATE //////////////////// //!!!!WARNING: BE SURE TO UPDATE THE DbUtil::PrepareDatabaseForSeeding WHEN NEW TABLES ADDED!!!! private const int DESIRED_SCHEMA_LEVEL = 9; internal const long EXPECTED_COLUMN_COUNT = 70; internal const long EXPECTED_INDEX_COUNT = 15; //!!!!WARNING: BE SURE TO UPDATE THE DbUtil::PrepareDatabaseForSeeding WHEN NEW TABLES ADDED!!!! ///////////////////////////////////////////////////////////////// static int startingSchema = -1; public static int currentSchema = -1; //check and update schema public static void CheckAndUpdate(AyContext context, ILogger logger) { ct = context; log = logger; //Check if ayschemaversion table exists bool aySchemaVersionExists = false; using (var command = ct.Database.GetDbConnection().CreateCommand()) { command.CommandText = "SELECT * FROM information_schema.tables WHERE table_name = 'aschemaversion'"; ct.Database.OpenConnection(); using (var result = command.ExecuteReader()) { if (result.HasRows) { aySchemaVersionExists = true; } ct.Database.CloseConnection(); } } //Create schema table (v1) if (!aySchemaVersionExists) { log.LogDebug("aschemaversion table not found, creating now"); //nope, no schema table, add it now and set to v1 using (var cm = ct.Database.GetDbConnection().CreateCommand()) { ct.Database.OpenConnection(); cm.CommandText = "CREATE TABLE aschemaversion (schema INTEGER NOT NULL);"; cm.ExecuteNonQuery(); cm.CommandText = "insert into aschemaversion (schema) values (1);"; cm.ExecuteNonQuery(); ct.Database.CloseConnection(); startingSchema = 1; currentSchema = 1; } } else { //get current schema level using (var cm = ct.Database.GetDbConnection().CreateCommand()) { log.LogDebug("Fetching current schema version"); cm.CommandText = "SELECT schema FROM aschemaversion;"; ct.Database.OpenConnection(); using (var result = cm.ExecuteReader()) { if (result.HasRows) { result.Read(); currentSchema = startingSchema = result.GetInt32(0); ct.Database.CloseConnection(); log.LogDebug("AyaNova schema version is " + currentSchema.ToString()); } else { ct.Database.CloseConnection(); throw new System.Exception("AyaNova->AySchema->CheckAndUpdate: Error reading schema version"); } } } } //Bail early no update? if (currentSchema == DESIRED_SCHEMA_LEVEL) { log.LogDebug("Current schema is at required schema version " + currentSchema.ToString()); return; } log.LogInformation("AyaNova database needs to be updated from schema version {0} to version {1}", currentSchema, DESIRED_SCHEMA_LEVEL); //************* SCHEMA UPDATES ****************** ////////////////////////////////////////////////// // USER table locale text and default data if (currentSchema < 2) { LogUpdateMessage(log); //create aevent biz event log table exec("CREATE TABLE aevent (id BIGSERIAL PRIMARY KEY, created timestamp not null, ownerid bigint not null," + "ayid bigint not null, aytype integer not null, ayevent integer not null, textra varchar(255))"); //TODO: do I *really* need these or do they bloat unnecessarily? Need to test with big dataset //index for quick searching // exec("CREATE INDEX ayid_idx ON aevent (ayid);"); // exec("CREATE INDEX aytype_idx ON aevent (aytype);"); //create locale text tables exec("CREATE TABLE alocale (id BIGSERIAL PRIMARY KEY, ownerid bigint not null, name varchar(255) not null, stock bool)"); exec("CREATE UNIQUE INDEX localename_idx ON alocale (name)"); exec("CREATE TABLE alocaleitem (id BIGSERIAL PRIMARY KEY, localeid bigint not null REFERENCES alocale (id), key text not null, display text not null)"); exec("CREATE INDEX localeitemlid_key_idx ON alocaleitem (localeid,key)"); //Load the default LOCALES AyaNova.Biz.PrimeData.PrimeLocales(ct); //Add user table exec("CREATE TABLE auser (id BIGSERIAL PRIMARY KEY, ownerid bigint not null, name varchar(255) not null, " + "login text not null, password text not null, salt text not null, roles integer not null, localeid bigint REFERENCES alocale (id), " + "dlkey text, dlkeyexpire timestamp)"); //Prime the db with the default MANAGER account AyaNova.Biz.PrimeData.PrimeManagerAccount(ct); setSchemaLevel(++currentSchema); } ////////////////////////////////////////////////// //LICENSE table if (currentSchema < 3) { LogUpdateMessage(log); //Add user table exec("CREATE TABLE alicense (id BIGSERIAL PRIMARY KEY, key text not null)"); setSchemaLevel(++currentSchema); } ////////////////////////////////////////////////// //WIDGET table for development testing if (currentSchema < 4) { LogUpdateMessage(log); //Add widget table //id, text, longtext, boolean, currency, exec("CREATE TABLE awidget (id BIGSERIAL PRIMARY KEY, ownerid bigint not null, name varchar(255) not null, " + "startdate timestamp, enddate timestamp, dollaramount money, active bool, roles int4)"); setSchemaLevel(++currentSchema); } ////////////////////////////////////////////////// // FileAttachment table if (currentSchema < 5) { LogUpdateMessage(log); exec("CREATE TABLE afileattachment (id BIGSERIAL PRIMARY KEY, ownerid bigint not null," + "attachtoobjectid bigint not null, attachtoobjecttype integer not null, " + "storedfilename text not null, displayfilename text not null, contenttype text, notes text)"); //index required for ops that need to check if file already in db (delete, count refs etc) exec("CREATE INDEX storedfilename_idx ON afileattachment (storedfilename);"); setSchemaLevel(++currentSchema); } ////////////////////////////////////////////////// //TAG tables if (currentSchema < 6) { LogUpdateMessage(log); exec("CREATE TABLE atag (id BIGSERIAL PRIMARY KEY, ownerid bigint not null, name varchar(35) not null)"); exec("CREATE UNIQUE INDEX tagname_idx ON atag (name);"); exec("CREATE TABLE atagmap (id BIGSERIAL PRIMARY KEY, ownerid bigint not null," + "tagid bigint not null REFERENCES atag (id), tagtoobjectid bigint not null, tagtoobjecttype integer not null)"); setSchemaLevel(++currentSchema); } ////////////////////////////////////////////////// // OPS LRO tables if (currentSchema < 7) { LogUpdateMessage(log); exec("CREATE TABLE aopsjob (gid uuid PRIMARY KEY, ownerid bigint not null, name text not null, created timestamp not null, exclusive bool not null, " + "startafter timestamp not null, jobtype integer not null, objectid bigint null, objecttype integer null, jobstatus integer not null, jobinfo text null)"); exec("CREATE TABLE aopsjoblog (gid uuid PRIMARY KEY, jobid uuid not null REFERENCES aopsjob (gid), created timestamp not null, statustext text not null)"); setSchemaLevel(++currentSchema); } ////////////////////////////////////////////////// //LICENSE table new columns //TODO: DO I need this anymore??? if (currentSchema < 8) { LogUpdateMessage(log); //Add license related stuff exec("ALTER TABLE alicense ADD COLUMN dbid uuid"); exec("ALTER TABLE alicense ADD COLUMN LastFetchStatus integer"); exec("ALTER TABLE alicense ADD COLUMN LastFetchMessage text"); setSchemaLevel(++currentSchema); } ////////////////////////////////////////////////// // FUTURE // if (currentSchema < 9) // { // LogUpdateMessage(log); // setSchemaLevel(++currentSchema); // } //!!!!WARNING: BE SURE TO UPDATE THE DbUtil::PrepareDatabaseForSeeding WHEN NEW TABLES ADDED!!!! log.LogInformation("Finished updating database schema to version {0}", currentSchema); //************************************************************************************* }//eofunction private static void setSchemaLevel(int nCurrentSchema) { exec("UPDATE aschemaversion SET schema=" + nCurrentSchema.ToString()); } //execute command query private static void exec(string q) { using (var cm = ct.Database.GetDbConnection().CreateCommand()) { ct.Database.OpenConnection(); cm.CommandText = q; cm.ExecuteNonQuery(); ct.Database.CloseConnection(); } } private static void LogUpdateMessage(ILogger log) { log.LogDebug($"Updating database to schema version {currentSchema + 1}"); } //eoclass } //eons }