class SQLiteUtils { static __db_cache = {}; static function getDbFor(dbname) { local db = table_rawget(__db_cache, dbname, false); if(!db) { db = SQLite3(dbname); //db.exec_dml("PRAGMA synchronous = 0;"); //db.exec_dml("PRAGMA journal_mode = WAL"); table_rawset(__db_cache, dbname, db); } return db; } static __query_cache = {}; static function getCachedQuery(qfn, db, sql) { return qfn(db, sql); /* There is a problem here we need to cache by db also local result = table_rawget(__query_cache, sql, false); if(!result) { result = qfn(db, sql); __query_cache[sql] <- result; } return result; */ } static function escapeRE(str) { return str.gsub("(%-)", "%%%1"); } static function escape_sql_like_search_str(str){ if (str && (str.len() > 0)){ str = str.gsub("%%", "%%%%"); if (str.find(" ") == 0) str = str.gsub("^%s*(.+)%s*$","%1%%"); else str = str.gsub("^%s*(.+)%s*$","%%%1%%"); } //print("escape_sql_like_search_str +" + str + "+") return str; } static function sanitizeDBName(dbname) { return dbname.gsub("([^_%-a-zA-Z0-9])",""); } static function create_stmt_bind(db, sql, bind_values=null) { local result = false; local stmt = db.prepare(sql); if(bind_values) { foreach(k,v in bind_values) { stmt.bind(k+1, v); } } return stmt; } static function exec_get_all(db, sql, bind_values=null) { local stmt = create_stmt_bind(db, sql, bind_values); local result = stmt.asArrayOfTables(); stmt.finalize(); return result; } static function exec_get_one(db, sql, bind_values=null) { local result = null; local stmt = create_stmt_bind(db, sql, bind_values); if(stmt.next_row()) { result = stmt.col(0); } stmt.finalize(); return result; } static function exec_dml(db, sql, bind_values=null) { local stmt = create_stmt_bind(db, sql, bind_values); //debug_print("exec_dml: ", sql, "\n"); local result = stmt.step() == stmt.SQLITE_DONE; stmt.finalize(); return result; } static function getReadOnlyFields(db, attached_db="") { local read_only_fields = {}; local ro = exec_get_all(db, format("SELECT * FROM %s__fields_metadata_sys_names_view", attached_db)); if(ro && ro.len()) { foreach(k,v in ro[0]) { table_rawset(read_only_fields, v, true); } } return read_only_fields; } static function getAcceptFieldsFields(db, tbl_name, attached_db="") { local af = {}; local fld_name_key = "field_id_name"; local all_af = exec_get_all(db, format("SELECT \"%s\" FROM %s__table_metadata_accept_fields_view WHERE table_id_name=?", fld_name_key, attached_db), [tbl_name]); if(all_af && all_af.len()) { foreach(k,v in all_af) { table_rawset(af, table_rawget(v, fld_name_key), true); } } return af; } static function getTableListFields(db, tbl_name, attached_db="") { local fld_name_key = "field_id_name"; local fields = exec_get_one(db, format("SELECT group_concat(\"%s\", ',') AS fields FROM %s__table_metadata_fields_view0 WHERE table_id_name=? AND hide_on_list <> 1", fld_name_key, attached_db), [tbl_name]); return fields; } static function getEditLinkFields(db, table_name, attached_db="") { local rows = exec_get_all(db, format("SELECT * FROM %s__table_metadata_edit_links_view WHERE table_id_name=?", attached_db), [table_name]); local result = {}; foreach(k,row in rows) { result[row.field_id_name] <- row; } return result; } static function getTableListInstead(db, the_table_name, attached_db="") { local list_instead = exec_get_one(db, format("SELECT list_table_id_name FROM %s__tables_metadata_view WHERE name=?", attached_db), [the_table_name]); if(::type(list_instead) == "string") { return list_instead; } return the_table_name; } static function getTableEditInstead(db, the_table_name, attached_db="") { local edit_instead = exec_get_one(db, format("SELECT edit_table_id_name FROM %s__tables_metadata_view WHERE name=?", attached_db), [the_table_name]); if(::type(edit_instead) == "string") { return edit_instead; } return the_table_name; } static function getListLimitForTableName(db, the_table_name, attached_db="") { the_table_name = getTableEditInstead(db, the_table_name, attached_db); local limit = exec_get_one(db, format("SELECT default_list_limit FROM %s__tables_metadata WHERE name =?", attached_db), [the_table_name]); return limit ? limit : 0; } static function getIsReadOnlyForTableName(db, the_table_name, attached_db="") { local is_read_only = exec_get_one(db, format("SELECT is_read_only FROM %s__tables_metadata WHERE name=?", attached_db), [the_table_name]); return is_read_only != null ? is_read_only : 1; //if not found it's read only } static function getFilterTables(db, table_name, attached_db="") { local rows = exec_get_all(db, format("SELECT * FROM %s__table_metadata_filter_tables_list_view WHERE table_id_name=?", attached_db), [table_name]); return rows; } static function getByIdFieldAsBlob(db, table_name, id, field_name, attached_db="") { local result = exec_get_one(db, format("SELECT \"%s\" FROM %s\"%s\" WHERE \"id\"=?", sanitizeDBName(field_name), attached_db, table_name), [id]); return result; } static function getSchemaSqlFor(tbl, attached_db="") { local str = format("SELECT sql FROM %ssqlite_master WHERE (type='table' OR type='view') AND tbl_name='%s'", attached_db, tbl); return str; } static function getSchemaFor(db, tbl, attached_db="") { local str = db.exec_get_one(getSchemaSqlFor(tbl, attached_db)); return str; } static function getIndexesSqlFor(tbl, attached_db="") { local str = format("SELECT name, sql FROM %ssqlite_master WHERE type='index' AND tbl_name='%s' ORDER BY name;", attached_db, tbl); return str; } static function getTriggersSqlFor(tbl, attached_db="") { local str = format("SELECT name, sql FROM %ssqlite_master WHERE type='trigger' AND tbl_name='%s' ORDER BY name;", attached_db, tbl); return str; } static function getTableInfoSqlFor(tbl, attached_db="") { local str = format("PRAGMA %stable_info(\"%s\");", attached_db, tbl); return str; } static function getTableFieldsInfoFor(db, tbl, attached_db="") { local stmt = db.prepare(getTableInfoSqlFor(tbl, attached_db)); local fields = stmt.colsAsArray(); stmt.finalize(); return fields; } static function getFieldsFor(db, tbl, attached_db="") { local stmt = db.prepare(format("select * from %s\"%s\"", attached_db, tbl)); local fields = stmt.colsAsArray(); stmt.finalize(); return fields; } static function getFieldsAsCSVFor(db, tbl, attached_db="") { local fields = getFieldsFor(db, tbl, attached_db); local str_fields = "\"" + fields.concat("\", \"") + "\""; return str_fields; } static function getIndexesAndTriggersFor(db, tbl, result, attached_db="") { local stmt = db.prepare(getIndexesSqlFor(tbl, attached_db)); while(stmt.next_row()) { local str = stmt.col(1); if(::type(str) == "string") result.write("\n\n", str, ";"); } stmt.finalize(); stmt = db.prepare(getTriggersSqlFor(tbl, attached_db)); while(stmt.next_row()) { local str = stmt.col(1); if(::type(str) == "string") result.write("\n\n", str, ";"); } stmt.finalize(); } static function createQuery(db, tbl, query_type, max_rows, attached_db=""){ if(db){ local sql; local fields = getFieldsFor(db, tbl, attached_db); local fields_csv = "\"" + fields.concat("\", \"") + "\""; local genSchemaUpgrade = function(db, with_references) { local new_suffix = "___new"; local old_suffix = ""; local result = blob(0, 8000); result.write("PRAGMA foreign_keys=OFF;\n\nBEGIN;\n\n"); local str_schema = getSchemaFor(db, tbl, attached_db); local tbl_name_suffixed = tbl + new_suffix; str_schema = str_schema.gsub("(" + escapeRE(tbl) + ")", tbl_name_suffixed, 1); str_schema = str_schema.gsub("(\"?" + escapeRE(tbl_name_suffixed) + "\"?)", attached_db + "%1", 1); str_schema = str_schema.gsub("\n%s+", "\n\t"); result.write(str_schema, ";"); fields_csv = fields_csv.gsub(", ", ",\n\t"); result.write(format("\n\nINSERT INTO %s\"%s%s\"(\n\t%s\n\t)\nSELECT\n\t%s\nFROM %s\"%s%s\";", attached_db, tbl, new_suffix, fields_csv, fields_csv, attached_db, tbl, old_suffix)); result.write("\n\nDROP TABLE ", attached_db,"\"", tbl, old_suffix, "\";"); result.write("\n\nALTER TABLE ", attached_db, "\"", tbl, new_suffix, "\" RENAME TO \"", tbl, old_suffix, "\";"); getIndexesAndTriggersFor(db, tbl, result, attached_db); if(with_references) { result.write("\n\nDROP VIEW \"view_name\";\n\n"); result.write(getReferencesOnDBSchema(db, tbl, attached_db)); result.write("\n\nCREATE VIEW \"view_name\" AS \"db_table_name\";"); } result.write("\n\nPRAGMA foreign_key_check;\n\nCOMMIT;\n\nPRAGMA foreign_keys=ON;"); return result.tostring(); }; if(query_type == "select") { local alias_letter = 'a'; local myjoins = ""; local fields_last_idx = fields.len()-1; local stmt = db.prepare(format("PRAGMA %sforeign_key_list(\"%s\")", attached_db, tbl)); local last_fk_id = -1; while(stmt.next_row()) { local fk_id = stmt.col(0); local ftable = stmt.col(2); local ffrom = stmt.col(3); local fto = stmt.col(4); local field_idx = fields.find(ffrom); local is_new_join = last_fk_id != fk_id; if(is_new_join) ++alias_letter; if(field_idx) { fields[field_idx] += format("\"%s --%c.\"%s", (fields_last_idx == field_idx ? "" : ","), alias_letter, fto); } if(is_new_join) { myjoins += format("\n--LEFT JOIN %s\"%s\" AS %c ON a.\"%s\" = %c.\"%s\"", attached_db, ftable, alias_letter, ffrom, alias_letter, fto); } else { myjoins += format(" AND a.\"%s\" = %c.\"%s\"", ffrom, alias_letter, fto); } last_fk_id = fk_id; } stmt.finalize(); fields_csv = "a.\"" + fields.concat("\",\n\ta.\"") + "\""; sql = format("--CREATE VIEW %s\"%s_list_view\" AS\nSELECT\n\t%s\nFROM %s\"%s\" AS a\nLIMIT %d", attached_db, tbl, fields_csv, attached_db, tbl, max_rows.tointeger()); sql += myjoins; } else if(query_type == "insert") sql = format("INSERT INTO %s\"%s\"(%s)\nVALUES(%s)", attached_db, tbl, fields_csv, fields_csv); else if(query_type == "update") { fields_csv = "\"" + fields.concat("\"=?, \"") + "\""; sql = format("UPDATE %s\"%s\" SET %s=?\nWHERE \"id\"=?", attached_db, tbl, fields_csv); } else if(query_type == "delete") sql = format("DELETE FROM %s\"%s\" WHERE \"id\"=?", attached_db, tbl); else if(query_type == "create index") sql = format("CREATE INDEX %s\"%s_idx\" ON %s\"%s\"(\"field\" COLLATE NOCASE)", attached_db, tbl, attached_db, tbl); else if(query_type == "create trigger") sql = format("CREATE TRIGGER %s\"%s_trigger\"\nBEFORE/AFTER/INSTEAD OF INSERT, UPDATE, DELETE OF col_name ON %s\"%s\"\nFOR EACH ROW WHEN expr\nBEGIN\nEND;", attached_db, tbl, attached_db, tbl); else if(query_type == "drop table") { local table_type = db.exec_get_one(format("SELECT \"type\" FROM %ssqlite_master WHERE name='%s'", attached_db, tbl)); sql = format("DROP %s %s\"%s\"", table_type, attached_db, tbl); } else if(query_type == "dump table") { local result = blob(0, 8000); local str_schema = getSchemaFor(db, tbl, attached_db); str_schema = str_schema.gsub("\n%s+", "\n\t"); result.write("BEGIN;\n\n", str_schema, ";"); getIndexesAndTriggersFor(db, tbl, result, attached_db); fields_csv = fields_csv.gsub(", ", ",\n\t"); result.write(format("\n\nINSERT INTO %s\"%s\" (\n\t%s\n\t) VALUES", attached_db, tbl, fields_csv)); local result_size = result.len(); local stmt = db.prepare(format("SELECT * FROM %s\"%s\"", attached_db, tbl)); local col_count = stmt.col_count(); local SQLITE_INTEGER = stmt.SQLITE_INTEGER; local SQLITE_FLOAT = stmt.SQLITE_FLOAT; local SQLITE_NULL = stmt.SQLITE_NULL; local SQLITE_TEXT = stmt.SQLITE_TEXT; local SQLITE_BLOB = stmt.SQLITE_BLOB; while(stmt.next_row()) { result.write("\n("); for(local i=0; i < col_count; ++i) { local value = stmt.col(i); if(i) result.write(","); local ctype = stmt.col_type(i); if(ctype == SQLITE_INTEGER || ctype == SQLITE_FLOAT) result.write(value.tostring()); else if(ctype == SQLITE_NULL) result.write("NULL"); else if(ctype == SQLITE_TEXT) result.write("'", value.gsub("'", "''") ,"'"); else if(ctype == SQLITE_BLOB) result.write(format("%q", value)); else result.write("??"); } result.write("),"); } stmt.finalize(); if(result_size < result.len()) { result.resize(result.len()-1); //delete last comma result.write(";"); } result.write("\n\nCOMMIT;"); sql = result.tostring(); } else if(query_type == "references") sql = tbl; else if(query_type == "schema update") { sql = genSchemaUpgrade(db, true); } else if(query_type == "schema update norefs") { sql = genSchemaUpgrade(db, false); } else if(query_type == "sqlite_master update") { local result = blob(0, 8000); local schema_version = getSchemaVersion(db, attached_db); result.write("BEGIN;\n--PRAGMA ", attached_db, "schema_version; --> ", schema_version.tostring(), "\n\n"); result.write("PRAGMA ", attached_db, "writable_schema=ON;\n\n"); local stmt = db.prepare("select rowid, sql from sqlite_master where tbl_name = '" + tbl + "';"); while(stmt.next_row()) { local str_schema = stmt.col(1); if(::type(str_schema) == "string") { str_schema = str_schema.gsub("\n%s+", "\n\t"); str_schema = str_schema.gsub("'", "''"); result.write("UPDATE ", attached_db, "sqlite_master\nSET sql='", str_schema, "'\nWHERE rowid=", stmt.col(0), ";\n\n"); } } stmt.finalize(); result.write("\n\nDROP VIEW \"view_name\";\n\n"); result.write(getReferencesOnDBSchema(db, tbl, attached_db)); result.write("\n\nCREATE VIEW \"view_name\" AS \"db_table_name\";"); result.write("\n\nPRAGMA ", attached_db, "schema_version=", (schema_version.tointeger() + 1).tostring(), ";\n\nPRAGMA ", attached_db, "writable_schema=OFF;\n\nPRAGMA ", attached_db, "integrit_check;\n\nCOMMIT;"); sql = result.tostring(); } return sql; } } static function executeQuery(db, query_type, sql, attached_db=""){ local result = {query_type=query_type, result=""}; if(sql && sql.len()) { local start_time; local foreign_keys_saved = null; switch(query_type) { case "references": local references = getReferencesOnDBSchema(db, sql, attached_db); table_rawset(result, "value", references); table_rawset(result, "result", "OK"); break; case "search all tables": local references = searchOnAllTables(db, sql, iMaxRows->value().tointeger(), attached_db); table_rawset(result, "value", references); table_rawset(result, "result", "OK"); break; case "schema update": case "schema update norefs": foreign_keys_saved = db.exec_get_one(format("PRAGMA %sforeign_keys", attached_db)); case "insert": case "update": case "delete": case "create index": case "create trigger": case "sqlite_master update": case "drop table": try { start_time = os.clock(); db.exec_dml(sql); table_rawset(result, "time_spent", os.clock() - start_time); table_rawset(result, "result", "OK"); switch(query_type) { case "create index": case "create trigger": case "schema update": case "schema update norefs": case "sqlite_master update": case "drop table": table_rawset(result, "schema_version", getSchemaVersion(db, attached_db)); break; } } catch(e) { if(!db.IsAutoCommitOn() || query_type.startswith("schema update")) db.exec_dml("ROLLBACK;"); if(foreign_keys_saved != null) db.exec_dml(format("PRAGMA %sforeign_keys=" + foreign_keys_saved, attached_db)); table_rawset(result, "result", e); } break; case "dump table": break; default: } } return result; } static function getSchemaVersion(db, attached_db="") { return db.exec_get_one(format("PRAGMA %sschema_version", attached_db)); } static function get_tables(db, attached_db=""){ local sql = format("select rowid as 'rowid|ID|0', name as 'name|Name|-1', type as 'type|Type|4' from %ssqlite_master where (type='table' OR type='view') order by name", attached_db); get_records_by_sql(grid_tables, sql, true); //_the_schema_version = getSchemaVersion(); } static function get_records_by_sql(db, sql , named=false){ local stmt = db.prepare(sql); local rec_list = stmt.asArrayOfArrays(SQLite3Stmt.WITH_COL_NAMES | SQLite3Stmt.AS_STRING_ALWAYS | SQLite3Stmt.NULL_AS_EMPTY_STR); stmt.finalize(); } static function doUpdateTablesMetadataFor(db, all_tables, attached_db="") { local stmt_insert_table = db.prepare(format("INSERT INTO %s__tables_metadata(name, is_view) VALUES(?,?)", attached_db)); local stmt_insert_fields = db.prepare(format("INSERT INTO %s__fields_metadata(name, type_id, length) VALUES(?,?,?)", attached_db)); foreach(idx, tbl in all_tables) { local is_view = tbl.type == "view" ? 1 : 0; stmt_insert_table.bind(1, tbl.name); stmt_insert_table.bind(2, is_view); stmt_insert_table.step(); stmt_insert_table.reset(); //if(!is_view) { local all_fields = exec_get_all(db, format("PRAGMA %stable_info(\"%s\");", attached_db, tbl.name)); foreach(fld in all_fields) { stmt_insert_fields.bind(1, fld.name); local field_type = fld.type.gsub("%(.+$", ""); if(!field_type.len()) field_type = "VARCHAR"; local type_id = exec_get_one(db, format("SELECT id FROM %s__field_types WHERE name=?", attached_db), [field_type]); stmt_insert_fields.bind(2, type_id); local field_length = fld.type.match("%((%d+)%)"); stmt_insert_fields.bind(3, field_length); stmt_insert_fields.step(); stmt_insert_fields.reset(); } } } stmt_insert_table.finalize(); stmt_insert_fields.finalize(); } static function doUpdateTableMetadataFieldsFor(db, all_tables, attached_db="") { local stmt_insert_fields = db.prepare(format("INSERT INTO %s__table_metadata_fields(table_id, field_id) VALUES(?,?)", attached_db)); local stmt_insert_accept_fields = db.prepare(format("INSERT INTO %s__table_metadata_accept_fields(table_id, field_id) VALUES(?,?)", attached_db)); local stmt_insert_edit_links = db.prepare(format("INSERT INTO %s__table_metadata_edit_links(table_id, field_id, link_table_id, link_field_id, show_table_id, show_field_id, show_text_id) VALUES(?,?,?,?,?,?,?)",attached_db)); foreach(tbl in all_tables) { local all_fields = exec_get_all(db, format("PRAGMA %stable_info(\"%s\");", attached_db, tbl.name)); foreach(fld in all_fields) { stmt_insert_fields.bind(1, tbl.id); local field_id = exec_get_one(db, format("SELECT id FROM %s__fields_metadata WHERE name=?", attached_db), [fld.name]); stmt_insert_fields.bind(2, field_id); stmt_insert_fields.step(); stmt_insert_fields.reset(); local ro = getReadOnlyFields(db, attached_db); if(!table_rawin(ro, fld.name)) { stmt_insert_accept_fields.bind(1, tbl.id); stmt_insert_accept_fields.bind(2, field_id); stmt_insert_accept_fields.step(); stmt_insert_accept_fields.reset(); } if(fld.name.endswith("_id")) { field_id = db.last_row_id(); local table_name = fld.name.gsub("_id$", "s"); local link_table_id = exec_get_one(db, format("SELECT id FROM %s__tables_metadata WHERE name=?", attached_db), [table_name]); local link_field_id = exec_get_one(db, format("SELECT id FROM %s__table_metadata_fields_view WHERE table_id_name=? AND field_id_name='id'", attached_db), [table_name]); //print(table_name, link_table_id, link_field_id); if(link_table_id && link_field_id) { stmt_insert_edit_links.bind(1, tbl.id); stmt_insert_edit_links.bind(2,field_id); stmt_insert_edit_links.bind(3, link_table_id); stmt_insert_edit_links.bind(4, link_field_id); stmt_insert_edit_links.bind(5, link_field_id); stmt_insert_edit_links.bind(6, link_field_id); stmt_insert_edit_links.bind(7, link_field_id); stmt_insert_edit_links.step(); stmt_insert_edit_links.reset(); } } } } stmt_insert_fields.finalize(); stmt_insert_accept_fields.finalize(); stmt_insert_edit_links.finalize(); } static function doCleanTableMetadataFieldsFor(db, all_tables, attached_db="") { //cleanup references for inexisting tables/views at global level local updateTablesMetadata = function() { local sql_exists = format("SELECT 1 FROM %ssqlite_master sm, %s__tables_metadata tm WHERE tm.id=a.%%s and sm.name=tm.name", attached_db, attached_db); local sql = format("UPDATE %s__tables_metadata a set list_table_id=NULL WHERE NOT EXISTS(%s)", attached_db, format(sql_exists, "list_table_id")); db.exec_dml(sql); sql = format("UPDATE %s__tables_metadata a set edit_table_id=NULL WHERE NOT EXISTS(%s)", attached_db, format(sql_exists, "edit_table_id")); db.exec_dml(sql); sql = format("UPDATE %s__app_menu a set table_view_id=NULL WHERE NOT EXISTS(%s)", attached_db, format(sql_exists, "table_view_id")); db.exec_dml(sql); } updateTablesMetadata(); //now for each table clean references to inexistent fields/tables/views foreach(tbl in all_tables) { local all_fields = exec_get_all(db, format("PRAGMA %stable_info(\"%s\");", attached_db, tbl.name)); local existing_fields = []; foreach(fld in all_fields) { existing_fields.push(fld.name); } //if(!existing_fields.len()) continue; existing_fields = "'" + existing_fields.concat("','") + "'"; local deleteNotIn = function(tbl_to_delete, tbl_id_name, field_names_to_keep, table_id_fld="table_id", field_id_fld="field_id") { local sql = format("DELETE FROM %s%s WHERE table_id IN (SELECT tm.id FROM %s__tables_metadata tm WHERE tm.name = '%s' AND NOT EXISTS(SELECT 1 FROM %ssqlite_master sm WHERE sm.name=tm.name))", attached_db, tbl_to_delete, attached_db, tbl_id_name, attached_db); db.exec_dml(sql); sql = format("DELETE FROM %s\"" + tbl_to_delete + "\" WHERE " + table_id_fld + "=(SELECT id FROM %s__tables_metadata WHERE name='" + tbl_id_name + "') AND " + field_id_fld + " NOT IN(SELECT id FROM %s__fields_metadata WHERE name IN(" + field_names_to_keep + "))", attached_db, attached_db, attached_db); //debug_print(sql, "\n"); db.exec_dml(sql); } //order is important here first any of __table_metadata_list_fields, __table_metadata_accept_fields, __table_metadata_edit_links, //then __table_metadata_filter_tables and __table_metadata_fields deleteNotIn("__table_metadata_list_fields", tbl.name, existing_fields); deleteNotIn("__table_metadata_accept_fields", tbl.name, existing_fields); //__table_metadata_edit_links references tables/views in multiple fields deleteNotIn("__table_metadata_edit_links", tbl.name, existing_fields, "show_table_id", "show_field_id"); deleteNotIn("__table_metadata_edit_links", tbl.name, existing_fields, "link_table_id", "link_field_id"); deleteNotIn("__table_metadata_edit_links", tbl.name, existing_fields); deleteNotIn("__table_metadata_filter_tables", tbl.name, existing_fields, "table_filtered_id", "table_filtered_field_id"); deleteNotIn("__table_metadata_fields", tbl.name, existing_fields); } //finally remove inexistent tables/views local sql = format("DELETE FROM %s__tables_metadata a WHERE NOT EXISTS(SELECT 1 FROM %ssqlite_master sm WHERE sm.name=a.name)", attached_db, attached_db); db.exec_dml(sql); } static function doUpdateMetadata(db, attached_db="") { db.exec_dml("BEGIN"); local all_tables = exec_get_all(db, format("SELECT type, name FROM %ssqlite_master WHERE type IN('view','table')", attached_db)); doUpdateTablesMetadataFor(db, all_tables); all_tables = exec_get_all(db, format("SELECT id, name FROM %s__tables_metadata", attached_db)); // WHERE is_view = 0"); doUpdateTableMetadataFieldsFor(db, all_tables); doCleanTableMetadataFieldsFor(db, all_tables); db.exec_dml("COMMIT"); } static function getReferencesOnDBSchema(db, name, attached_db="") { local reference_name = escapeRE(sanitizeDBName(name)); local prefix_suffix_re = "[%s%(%),%.<>!=%-%+%*/\"']"; local reference_name_re = prefix_suffix_re + "(" + reference_name + ")" + prefix_suffix_re; local reference_re = "()" + reference_name + "()"; //print(reference_re); local checkValidRefenceName = function(whole_str, start_idx, end_idx) { //!!!this assume that the name searched is not at the begning or end of whole_str local context = whole_str.slice( (start_idx ? start_idx-1 : start_idx) , (end_idx < whole_str.len() ? end_idx+1 : end_idx) ); if(context.match(reference_name_re)) { if( (context[0] == '\'') && (context[context.len()-1] != '\'') ) return false; else if( (context[0] == '"') && (context[context.len()-1] != '"') ) return false; return true; } return false; } local result = blob(0, 8000); local stmt = db.prepare(format("SELECT type, name, sql FROM %ssqlite_master;", attached_db)); while(stmt.next_row()) { local sql = stmt.col(2); if((::type(sql) == "string")) { sql = sql.tolower(); sql.gmatch(reference_re, function(start_idx, end_idx){ //print(start_idx, end_idx, sql.slice(start_idx, end_idx)); if(checkValidRefenceName(sql, start_idx, end_idx)) { //print("idx", start_idx, end_idx, sql.slice(start_idx, end_idx)); //print(sql); result.write("-------------\n"); result.write(stmt.col(0), "\t", stmt.col(1), "\n"); result.write(stmt.col(2), "\n"); return false; //one match is enough } return true; }); } } stmt.finalize(); return result.tostring(); } static function searchOnAllTables(db, search_str, search_limit, attached_db="") { local embedded_limit = search_str.match("^(%d+):"); if(embedded_limit) { search_limit = embedded_limit.tointeger(); search_str = search_str.match("^%d+:(.+)"); } local the_search_str; if( search_str.match("^_re_:") ) { the_search_str = search_str.match("^_re_:(.+)"); } else the_search_str = escapeRE(search_str); the_search_str = the_search_str.tolower(); local search_count = 0; local result = blob(0, 8000); local stmt = db.prepare(format("SELECT name FROM %ssqlite_master WHERE type='table';", attached_db)); while(stmt.next_row()) { local tbl_name = stmt.col(0); local tbl_stmt = db.prepare(format("SELECT * FROM %s\"%s\"", attached_db, tbl_name)); local col_count = tbl_stmt.col_count(); local text_cols = []; for(local i=0; i < col_count; ++i) { local dtype = tbl_stmt.col_declared_type(i).tolower(); if( (dtype.indexOf("varchar") >= 0) || (dtype.indexOf("text") >= 0) ) text_cols.push(i); } if(text_cols.len()) { local text_cols_len = text_cols.len(); local tbl_done = false; while(tbl_stmt.next_row() && !tbl_done) { for(local i=0; i < text_cols_len; ++i) { local col_idx = text_cols[i]; local str = tbl_stmt.col(col_idx); if((::type(str) == "string")) { if(str.tolower().match(the_search_str)) { result.write(tbl_name, ":", tbl_stmt.col_name(col_idx), "\n"); ++search_count; tbl_done = true; break; } } } } } tbl_stmt.finalize(); if(search_count >= search_limit) break; } stmt.finalize(); return result.tostring(); } } class SqlPreprocessor { stmt_get = null; stmt_get_by_name = null; stmt_set = null; stmt_insert_params = null; stmt_clear_params = null; function createSqlPreprocessorMainTable(db) { local sql = [==[ CREATE TABLE IF NOT EXISTS __app_sql ( id INTEGER PRIMARY KEY NOT NULL, _version_ INTEGER NOT NULL DEFAULT 0, _cdate_ DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, _mdate_ DATETIME, name VARCHAR NOT NULL COLLATE NOCASE CONSTRAINT __app_sql_name_unique UNIQUE, base TEXT COLLATE NOCASE NOT NULL, sql TEXT COLLATE NOCASE, notes TEXT COLLATE NOCASE ); CREATE VIEW IF NOT EXISTS __app_sql_list_view AS SELECT a.id, a._mdate_, a.name FROM __app_sql AS a; CREATE TABLE IF NOT EXISTS __app_sql_parameters ( id INTEGER PRIMARY KEY NOT NULL, _version_ INTEGER NOT NULL DEFAULT 0, _cdate_ DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, _mdate_ DATETIME, app_sql_id integer not null references __app_sql(id), name VARCHAR NOT NULL COLLATE NOCASE, default_value VARCHAR COLLATE NOCASE, field_type VARCHAR COLLATE NOCASE, notes TEXT COLLATE NOCASE, CONSTRAINT app_sql_id_name_unique UNIQUE(app_sql_id, name) ); ]==]; db.exec_dml(sql); } function doPrepare(db) { if(!stmt_get) { stmt_get = db.prepare("SELECT base FROM __app_sql WHERE id=?"); stmt_get_by_name = db.prepare("SELECT base FROM __app_sql WHERE name=?"); stmt_set = db.prepare("UPDATE __app_sql SET sql=? WHERE id=?"); stmt_clear_params = db.prepare("DELETE FROM __app_sql_parameters WHERE app_sql_id=?"); stmt_insert_params = db.prepare("INSERT INTO __app_sql_parameters(app_sql_id, name, default_value, field_type, notes) VALUES(?,?,?,?,?)"); } } function doFinalize(db) { if(stmt_get) { stmt_get.finalize(); stmt_get = null; stmt_set.finalize(); stmt_get_by_name.finalize(); stmt_clear_params.finalize(); stmt_insert_params.finalize(); } } function saveSqlParameters(sql_id, sql) { stmt_clear_params.bind(1, sql_id); stmt_clear_params.step(); stmt_clear_params.reset(); local self = this; //{$field_name:default_value:field_type:field_notes} sql.gmatch("(%b{})", function(m){ if(m[1] == '$') { local field_name, default_value, field_type, field_notes; local ary = m.slice(2,-1).split(':'); if(ary.len() > 0) { foreach(idx, val in ary) { val = val.trim(); switch(idx) { case 0: field_name = val; break; case 1: default_value = val; break; case 2: field_type = val; break; case 4: field_notes = val; break; } } local stmt_insert_params = self.stmt_insert_params; stmt_insert_params.bind(1, sql_id); stmt_insert_params.bind(2, field_name); stmt_insert_params.bind_empty_null(3, default_value); stmt_insert_params.bind_empty_null(4, field_type); stmt_insert_params.bind_empty_null(5, field_notes); stmt_insert_params.step(); stmt_insert_params.reset(); } } return true; }); } //the macros for params are {$param_name:param_value} static function preprocessSqlQueryParams(sql_seed, params) { local found; do { found = false; sql_seed = sql_seed.gsub("(%b{})", function(m){ if(m[1] == '$') { found = true; local ary = m.slice(2,-1).split(':'); local val = table_get(params, ary[0], null); if(val == null && (ary.len() > 1)) { //we'll add the default parameter to params val = params[ary[0]] <- ary[1]; } return val; } return m; }); //print("loop", found); } while(found); return sql_seed; } function preprocessSqlQuery(db, sql_seed, id) { local need_prepare = stmt_get == null; if(need_prepare) doPrepare(db); if(sql_seed) { local found; local self = this; do { found = false; sql_seed = sql_seed.gsub("(%b{})", function(m){ if(m[1] == ':') { local vt = m.slice(2,-1); self.stmt_get_by_name.bind(1, vt); if(self.stmt_get_by_name.next_row()) { //need check to prevent infinite recursion found = true; local val = self.stmt_get_by_name.col(0); self.stmt_get_by_name.reset(); return val; } } return m; }); //print("loop", found); } while(found); stmt_set.bind(1, sql_seed); stmt_set.bind(2, id); stmt_set.step(); stmt_set.reset(); saveSqlParameters(id, sql_seed); } if(need_prepare) doFinalize(db); return sql_seed; } //the macros for params are {:query_name_to_include} function preprocessSqlQueries(db) { local stmt = db.prepare("SELECT id FROM __app_sql"); local all_ids = []; while(stmt.next_row()) { all_ids.push(stmt.col(0)); } stmt.finalize(); doPrepare(db); try { db.exec_dml("BEGIN"); foreach(id in all_ids) { local found; local sql_seed = false; stmt_get.bind(1, id); if(stmt_get.next_row()) sql_seed = stmt_get.col(0); stmt_get.reset(); if(sql_seed) { preprocessSqlQuery(db, sql_seed, id); } } db.exec_dml("COMMIT"); } catch(e) { db.exec_dml("ROLLBACK"); } doFinalize(db); } function getPreprocessorQueryName(db, sql) { createSqlPreprocessorMainTable(db); return sql.match("^%-%-name=(%S+)"); } function getPreprocessorQuery(db, sql, field="ifnull(sql, base) as sql") { local name = getPreprocessorQueryName(db, sql); if(name) { local stmt = db.prepare(format("SELECT %s FROM __app_sql WHERE name=?", field)); stmt.bind(1, name); if(stmt.next_row()) { name = stmt.col(0); } stmt.finalize(); } return name; } function insertPreprocessorQuery(db, sql) { local name = getPreprocessorQueryName(db, sql); if(name) { local stmt = db.prepare("insert into __app_sql(name, base) values(?,?)"); stmt.bind(1, name); stmt.bind(2, sql); stmt.step(); print(db.errmsg()); stmt.finalize(); preprocessSqlQueries(db); } return name; } function updatePreprocessorQuery(db, sql) { local name = getPreprocessorQueryName(db, sql); if(name) { local stmt = db.prepare("update __app_sql set base = ? where name = ?"); stmt.bind(1, sql); stmt.bind(2, name); stmt.step(); stmt.finalize(); preprocessSqlQueries(db); } return name; } }