sqlite-utils.nut 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071
  1. class SQLiteUtils {
  2. static __db_cache = {};
  3. static function getDbFor(dbname)
  4. {
  5. local db = table_rawget(__db_cache, dbname, false);
  6. if(!db)
  7. {
  8. db = SQLite3(dbname);
  9. //db.exec_dml("PRAGMA synchronous = 0;");
  10. //db.exec_dml("PRAGMA journal_mode = WAL");
  11. table_rawset(__db_cache, dbname, db);
  12. }
  13. return db;
  14. }
  15. static __query_cache = {};
  16. static function getCachedQuery(qfn, db, sql)
  17. {
  18. return qfn(db, sql);
  19. /*
  20. There is a problem here we need to cache by db also
  21. local result = table_rawget(__query_cache, sql, false);
  22. if(!result)
  23. {
  24. result = qfn(db, sql);
  25. __query_cache[sql] <- result;
  26. }
  27. return result;
  28. */
  29. }
  30. static function escapeRE(str)
  31. {
  32. return str.gsub("(%-)", "%%%1");
  33. }
  34. static function escape_sql_like_search_str(str){
  35. if (str && (str.len() > 0)){
  36. str = str.gsub("%%", "%%%%");
  37. if (str.find(" ") == 0)
  38. str = str.gsub("^%s*(.+)%s*$","%1%%");
  39. else
  40. str = str.gsub("^%s*(.+)%s*$","%%%1%%");
  41. }
  42. //print("escape_sql_like_search_str +" + str + "+")
  43. return str;
  44. }
  45. static function sanitizeDBName(dbname)
  46. {
  47. return dbname.gsub("([^_%-a-zA-Z0-9])","");
  48. }
  49. static function create_stmt_bind(db, sql, bind_values=null)
  50. {
  51. local result = false;
  52. local stmt = db.prepare(sql);
  53. if(bind_values)
  54. {
  55. foreach(k,v in bind_values)
  56. {
  57. stmt.bind(k+1, v);
  58. }
  59. }
  60. return stmt;
  61. }
  62. static function exec_get_all(db, sql, bind_values=null)
  63. {
  64. local stmt = create_stmt_bind(db, sql, bind_values);
  65. local result = stmt.asArrayOfTables();
  66. stmt.finalize();
  67. return result;
  68. }
  69. static function exec_get_one(db, sql, bind_values=null)
  70. {
  71. local result = null;
  72. local stmt = create_stmt_bind(db, sql, bind_values);
  73. if(stmt.next_row())
  74. {
  75. result = stmt.col(0);
  76. }
  77. stmt.finalize();
  78. return result;
  79. }
  80. static function exec_dml(db, sql, bind_values=null)
  81. {
  82. local stmt = create_stmt_bind(db, sql, bind_values);
  83. //debug_print("exec_dml: ", sql, "\n");
  84. local result = stmt.step() == stmt.SQLITE_DONE;
  85. stmt.finalize();
  86. return result;
  87. }
  88. static function getReadOnlyFields(db, attached_db="")
  89. {
  90. local read_only_fields = {};
  91. local ro = exec_get_all(db, format("SELECT * FROM %s__fields_metadata_sys_names_view", attached_db));
  92. if(ro && ro.len())
  93. {
  94. foreach(k,v in ro[0])
  95. {
  96. table_rawset(read_only_fields, v, true);
  97. }
  98. }
  99. return read_only_fields;
  100. }
  101. static function getAcceptFieldsFields(db, tbl_name, attached_db="")
  102. {
  103. local af = {};
  104. local fld_name_key = "field_id_name";
  105. 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]);
  106. if(all_af && all_af.len())
  107. {
  108. foreach(k,v in all_af)
  109. {
  110. table_rawset(af, table_rawget(v, fld_name_key), true);
  111. }
  112. }
  113. return af;
  114. }
  115. static function getTableListFields(db, tbl_name, attached_db="")
  116. {
  117. local fld_name_key = "field_id_name";
  118. 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]);
  119. return fields;
  120. }
  121. static function getEditLinkFields(db, table_name, attached_db="")
  122. {
  123. local rows = exec_get_all(db, format("SELECT * FROM %s__table_metadata_edit_links_view WHERE table_id_name=?", attached_db), [table_name]);
  124. local result = {};
  125. foreach(k,row in rows)
  126. {
  127. result[row.field_id_name] <- row;
  128. }
  129. return result;
  130. }
  131. static function getTableListInstead(db, the_table_name, attached_db="")
  132. {
  133. local list_instead = exec_get_one(db,
  134. format("SELECT list_table_id_name FROM %s__tables_metadata_view WHERE name=?", attached_db),
  135. [the_table_name]);
  136. if(::type(list_instead) == "string")
  137. {
  138. return list_instead;
  139. }
  140. return the_table_name;
  141. }
  142. static function getTableEditInstead(db, the_table_name, attached_db="")
  143. {
  144. local edit_instead = exec_get_one(db,
  145. format("SELECT edit_table_id_name FROM %s__tables_metadata_view WHERE name=?", attached_db),
  146. [the_table_name]);
  147. if(::type(edit_instead) == "string")
  148. {
  149. return edit_instead;
  150. }
  151. return the_table_name;
  152. }
  153. static function getListLimitForTableName(db, the_table_name, attached_db="")
  154. {
  155. the_table_name = getTableEditInstead(db, the_table_name, attached_db);
  156. local limit = exec_get_one(db, format("SELECT default_list_limit FROM %s__tables_metadata WHERE name =?", attached_db), [the_table_name]);
  157. return limit ? limit : 0;
  158. }
  159. static function getIsReadOnlyForTableName(db, the_table_name, attached_db="")
  160. {
  161. local is_read_only = exec_get_one(db, format("SELECT is_read_only FROM %s__tables_metadata WHERE name=?", attached_db), [the_table_name]);
  162. return is_read_only != null ? is_read_only : 1; //if not found it's read only
  163. }
  164. static function getFilterTables(db, table_name, attached_db="")
  165. {
  166. local rows = exec_get_all(db, format("SELECT * FROM %s__table_metadata_filter_tables_list_view WHERE table_id_name=?", attached_db), [table_name]);
  167. return rows;
  168. }
  169. static function getByIdFieldAsBlob(db, table_name, id, field_name, attached_db="")
  170. {
  171. local result = exec_get_one(db, format("SELECT \"%s\" FROM %s\"%s\" WHERE \"id\"=?",
  172. sanitizeDBName(field_name), attached_db, table_name), [id]);
  173. return result;
  174. }
  175. static function getSchemaSqlFor(tbl, attached_db="")
  176. {
  177. local str = format("SELECT sql FROM %ssqlite_master WHERE (type='table' OR type='view') AND tbl_name='%s'", attached_db, tbl);
  178. return str;
  179. }
  180. static function getSchemaFor(db, tbl, attached_db="")
  181. {
  182. local str = db.exec_get_one(getSchemaSqlFor(tbl, attached_db));
  183. return str;
  184. }
  185. static function getIndexesSqlFor(tbl, attached_db="")
  186. {
  187. local str = format("SELECT name, sql FROM %ssqlite_master WHERE type='index' AND tbl_name='%s' ORDER BY name;", attached_db, tbl);
  188. return str;
  189. }
  190. static function getTriggersSqlFor(tbl, attached_db="")
  191. {
  192. local str = format("SELECT name, sql FROM %ssqlite_master WHERE type='trigger' AND tbl_name='%s' ORDER BY name;", attached_db, tbl);
  193. return str;
  194. }
  195. static function getTableInfoSqlFor(tbl, attached_db="")
  196. {
  197. local str = format("PRAGMA %stable_info(\"%s\");", attached_db, tbl);
  198. return str;
  199. }
  200. static function getTableFieldsInfoFor(db, tbl, attached_db="")
  201. {
  202. local stmt = db.prepare(getTableInfoSqlFor(tbl, attached_db));
  203. local fields = stmt.colsAsArray();
  204. stmt.finalize();
  205. return fields;
  206. }
  207. static function getFieldsFor(db, tbl, attached_db="")
  208. {
  209. local stmt = db.prepare(format("select * from %s\"%s\"", attached_db, tbl));
  210. local fields = stmt.colsAsArray();
  211. stmt.finalize();
  212. return fields;
  213. }
  214. static function getFieldsAsCSVFor(db, tbl, attached_db="")
  215. {
  216. local fields = getFieldsFor(db, tbl, attached_db);
  217. local str_fields = "\"" + fields.concat("\", \"") + "\"";
  218. return str_fields;
  219. }
  220. static function getIndexesAndTriggersFor(db, tbl, result, attached_db="")
  221. {
  222. local stmt = db.prepare(getIndexesSqlFor(tbl, attached_db));
  223. while(stmt.next_row())
  224. {
  225. local str = stmt.col(1);
  226. if(::type(str) == "string") result.write("\n\n", str, ";");
  227. }
  228. stmt.finalize();
  229. stmt = db.prepare(getTriggersSqlFor(tbl, attached_db));
  230. while(stmt.next_row())
  231. {
  232. local str = stmt.col(1);
  233. if(::type(str) == "string") result.write("\n\n", str, ";");
  234. }
  235. stmt.finalize();
  236. }
  237. static function createQuery(db, tbl, query_type, max_rows, attached_db=""){
  238. if(db){
  239. local sql;
  240. local fields = getFieldsFor(db, tbl, attached_db);
  241. local fields_csv = "\"" + fields.concat("\", \"") + "\"";
  242. local genSchemaUpgrade = function(db, with_references)
  243. {
  244. local new_suffix = "___new";
  245. local old_suffix = "";
  246. local result = blob(0, 8000);
  247. result.write("PRAGMA foreign_keys=OFF;\n\nBEGIN;\n\n");
  248. local str_schema = getSchemaFor(db, tbl, attached_db);
  249. local tbl_name_suffixed = tbl + new_suffix;
  250. str_schema = str_schema.gsub("(" + escapeRE(tbl) + ")", tbl_name_suffixed, 1);
  251. str_schema = str_schema.gsub("(\"?" + escapeRE(tbl_name_suffixed) + "\"?)", attached_db + "%1", 1);
  252. str_schema = str_schema.gsub("\n%s+", "\n\t");
  253. result.write(str_schema, ";");
  254. fields_csv = fields_csv.gsub(", ", ",\n\t");
  255. result.write(format("\n\nINSERT INTO %s\"%s%s\"(\n\t%s\n\t)\nSELECT\n\t%s\nFROM %s\"%s%s\";",
  256. attached_db, tbl, new_suffix, fields_csv, fields_csv, attached_db, tbl, old_suffix));
  257. result.write("\n\nDROP TABLE ", attached_db,"\"", tbl, old_suffix, "\";");
  258. result.write("\n\nALTER TABLE ", attached_db, "\"", tbl, new_suffix, "\" RENAME TO \"", tbl, old_suffix, "\";");
  259. getIndexesAndTriggersFor(db, tbl, result, attached_db);
  260. if(with_references)
  261. {
  262. result.write("\n\nDROP VIEW \"view_name\";\n\n");
  263. result.write(getReferencesOnDBSchema(db, tbl, attached_db));
  264. result.write("\n\nCREATE VIEW \"view_name\" AS \"db_table_name\";");
  265. }
  266. result.write("\n\nPRAGMA foreign_key_check;\n\nCOMMIT;\n\nPRAGMA foreign_keys=ON;");
  267. return result.tostring();
  268. };
  269. if(query_type == "select")
  270. {
  271. local alias_letter = 'a';
  272. local myjoins = "";
  273. local fields_last_idx = fields.len()-1;
  274. local stmt = db.prepare(format("PRAGMA %sforeign_key_list(\"%s\")", attached_db, tbl));
  275. local last_fk_id = -1;
  276. while(stmt.next_row())
  277. {
  278. local fk_id = stmt.col(0);
  279. local ftable = stmt.col(2);
  280. local ffrom = stmt.col(3);
  281. local fto = stmt.col(4);
  282. local field_idx = fields.find(ffrom);
  283. local is_new_join = last_fk_id != fk_id;
  284. if(is_new_join) ++alias_letter;
  285. if(field_idx)
  286. {
  287. fields[field_idx] += format("\"%s --%c.\"%s", (fields_last_idx == field_idx ? "" : ","), alias_letter, fto);
  288. }
  289. if(is_new_join)
  290. {
  291. myjoins += format("\n--LEFT JOIN %s\"%s\" AS %c ON a.\"%s\" = %c.\"%s\"", attached_db, ftable, alias_letter, ffrom, alias_letter, fto);
  292. }
  293. else
  294. {
  295. myjoins += format(" AND a.\"%s\" = %c.\"%s\"", ffrom, alias_letter, fto);
  296. }
  297. last_fk_id = fk_id;
  298. }
  299. stmt.finalize();
  300. fields_csv = "a.\"" + fields.concat("\",\n\ta.\"") + "\"";
  301. sql = format("--CREATE VIEW %s\"%s_list_view\" AS\nSELECT\n\t%s\nFROM %s\"%s\" AS a\nLIMIT %d",
  302. attached_db, tbl, fields_csv, attached_db, tbl, max_rows.tointeger());
  303. sql += myjoins;
  304. }
  305. else if(query_type == "insert") sql = format("INSERT INTO %s\"%s\"(%s)\nVALUES(%s)", attached_db, tbl, fields_csv, fields_csv);
  306. else if(query_type == "update") {
  307. fields_csv = "\"" + fields.concat("\"=?, \"") + "\"";
  308. sql = format("UPDATE %s\"%s\" SET %s=?\nWHERE \"id\"=?", attached_db, tbl, fields_csv);
  309. }
  310. else if(query_type == "delete") sql = format("DELETE FROM %s\"%s\" WHERE \"id\"=?", attached_db, tbl);
  311. 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);
  312. 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;",
  313. attached_db, tbl, attached_db, tbl);
  314. else if(query_type == "drop table")
  315. {
  316. local table_type = db.exec_get_one(format("SELECT \"type\" FROM %ssqlite_master WHERE name='%s'", attached_db, tbl));
  317. sql = format("DROP %s %s\"%s\"", table_type, attached_db, tbl);
  318. }
  319. else if(query_type == "dump table")
  320. {
  321. local result = blob(0, 8000);
  322. local str_schema = getSchemaFor(db, tbl, attached_db);
  323. str_schema = str_schema.gsub("\n%s+", "\n\t");
  324. result.write("BEGIN;\n\n", str_schema, ";");
  325. getIndexesAndTriggersFor(db, tbl, result, attached_db);
  326. fields_csv = fields_csv.gsub(", ", ",\n\t");
  327. result.write(format("\n\nINSERT INTO %s\"%s\" (\n\t%s\n\t) VALUES", attached_db, tbl, fields_csv));
  328. local result_size = result.len();
  329. local stmt = db.prepare(format("SELECT * FROM %s\"%s\"", attached_db, tbl));
  330. local col_count = stmt.col_count();
  331. local SQLITE_INTEGER = stmt.SQLITE_INTEGER;
  332. local SQLITE_FLOAT = stmt.SQLITE_FLOAT;
  333. local SQLITE_NULL = stmt.SQLITE_NULL;
  334. local SQLITE_TEXT = stmt.SQLITE_TEXT;
  335. local SQLITE_BLOB = stmt.SQLITE_BLOB;
  336. while(stmt.next_row())
  337. {
  338. result.write("\n(");
  339. for(local i=0; i < col_count; ++i)
  340. {
  341. local value = stmt.col(i);
  342. if(i) result.write(",");
  343. local ctype = stmt.col_type(i);
  344. if(ctype == SQLITE_INTEGER || ctype == SQLITE_FLOAT)
  345. result.write(value.tostring());
  346. else if(ctype == SQLITE_NULL) result.write("NULL");
  347. else if(ctype == SQLITE_TEXT) result.write("'", value.gsub("'", "''") ,"'");
  348. else if(ctype == SQLITE_BLOB) result.write(format("%q", value));
  349. else result.write("??");
  350. }
  351. result.write("),");
  352. }
  353. stmt.finalize();
  354. if(result_size < result.len())
  355. {
  356. result.resize(result.len()-1); //delete last comma
  357. result.write(";");
  358. }
  359. result.write("\n\nCOMMIT;");
  360. sql = result.tostring();
  361. }
  362. else if(query_type == "references") sql = tbl;
  363. else if(query_type == "schema update") {
  364. sql = genSchemaUpgrade(db, true);
  365. }
  366. else if(query_type == "schema update norefs") {
  367. sql = genSchemaUpgrade(db, false);
  368. }
  369. else if(query_type == "sqlite_master update") {
  370. local result = blob(0, 8000);
  371. local schema_version = getSchemaVersion(db, attached_db);
  372. result.write("BEGIN;\n--PRAGMA ", attached_db, "schema_version; --> ", schema_version.tostring(), "\n\n");
  373. result.write("PRAGMA ", attached_db, "writable_schema=ON;\n\n");
  374. local stmt = db.prepare("select rowid, sql from sqlite_master where tbl_name = '" + tbl + "';");
  375. while(stmt.next_row())
  376. {
  377. local str_schema = stmt.col(1);
  378. if(::type(str_schema) == "string")
  379. {
  380. str_schema = str_schema.gsub("\n%s+", "\n\t");
  381. str_schema = str_schema.gsub("'", "''");
  382. result.write("UPDATE ", attached_db, "sqlite_master\nSET sql='", str_schema, "'\nWHERE rowid=", stmt.col(0), ";\n\n");
  383. }
  384. }
  385. stmt.finalize();
  386. result.write("\n\nDROP VIEW \"view_name\";\n\n");
  387. result.write(getReferencesOnDBSchema(db, tbl, attached_db));
  388. result.write("\n\nCREATE VIEW \"view_name\" AS \"db_table_name\";");
  389. result.write("\n\nPRAGMA ", attached_db, "schema_version=", (schema_version.tointeger() + 1).tostring(),
  390. ";\n\nPRAGMA ", attached_db, "writable_schema=OFF;\n\nPRAGMA ", attached_db, "integrit_check;\n\nCOMMIT;");
  391. sql = result.tostring();
  392. }
  393. return sql;
  394. }
  395. }
  396. static function executeQuery(db, query_type, sql, attached_db=""){
  397. local result = {query_type=query_type, result=""};
  398. if(sql && sql.len()) {
  399. local start_time;
  400. local foreign_keys_saved = null;
  401. switch(query_type)
  402. {
  403. case "references":
  404. local references = getReferencesOnDBSchema(db, sql, attached_db);
  405. table_rawset(result, "value", references);
  406. table_rawset(result, "result", "OK");
  407. break;
  408. case "search all tables":
  409. local references = searchOnAllTables(db, sql, iMaxRows->value().tointeger(), attached_db);
  410. table_rawset(result, "value", references);
  411. table_rawset(result, "result", "OK");
  412. break;
  413. case "schema update":
  414. case "schema update norefs":
  415. foreign_keys_saved = db.exec_get_one(format("PRAGMA %sforeign_keys", attached_db));
  416. case "insert":
  417. case "update":
  418. case "delete":
  419. case "create index":
  420. case "create trigger":
  421. case "sqlite_master update":
  422. case "drop table":
  423. try
  424. {
  425. start_time = os.clock();
  426. db.exec_dml(sql);
  427. table_rawset(result, "time_spent", os.clock() - start_time);
  428. table_rawset(result, "result", "OK");
  429. switch(query_type)
  430. {
  431. case "create index":
  432. case "create trigger":
  433. case "schema update":
  434. case "schema update norefs":
  435. case "sqlite_master update":
  436. case "drop table":
  437. table_rawset(result, "schema_version", getSchemaVersion(db, attached_db));
  438. break;
  439. }
  440. }
  441. catch(e)
  442. {
  443. if(!db.IsAutoCommitOn() || query_type.startswith("schema update")) db.exec_dml("ROLLBACK;");
  444. if(foreign_keys_saved != null) db.exec_dml(format("PRAGMA %sforeign_keys=" + foreign_keys_saved, attached_db));
  445. table_rawset(result, "result", e);
  446. }
  447. break;
  448. case "dump table":
  449. break;
  450. default:
  451. }
  452. }
  453. return result;
  454. }
  455. static function getSchemaVersion(db, attached_db="")
  456. {
  457. return db.exec_get_one(format("PRAGMA %sschema_version", attached_db));
  458. }
  459. static function get_tables(db, attached_db=""){
  460. 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);
  461. get_records_by_sql(grid_tables, sql, true);
  462. //_the_schema_version = getSchemaVersion();
  463. }
  464. static function get_records_by_sql(db, sql , named=false){
  465. local stmt = db.prepare(sql);
  466. local rec_list = stmt.asArrayOfArrays(SQLite3Stmt.WITH_COL_NAMES |
  467. SQLite3Stmt.AS_STRING_ALWAYS | SQLite3Stmt.NULL_AS_EMPTY_STR);
  468. stmt.finalize();
  469. }
  470. static function doUpdateTablesMetadataFor(db, all_tables, attached_db="")
  471. {
  472. local stmt_insert_table = db.prepare(format("INSERT INTO %s__tables_metadata(name, is_view) VALUES(?,?)", attached_db));
  473. local stmt_insert_fields = db.prepare(format("INSERT INTO %s__fields_metadata(name, type_id, length) VALUES(?,?,?)", attached_db));
  474. foreach(idx, tbl in all_tables)
  475. {
  476. local is_view = tbl.type == "view" ? 1 : 0;
  477. stmt_insert_table.bind(1, tbl.name);
  478. stmt_insert_table.bind(2, is_view);
  479. stmt_insert_table.step();
  480. stmt_insert_table.reset();
  481. //if(!is_view)
  482. {
  483. local all_fields = exec_get_all(db, format("PRAGMA %stable_info(\"%s\");", attached_db, tbl.name));
  484. foreach(fld in all_fields)
  485. {
  486. stmt_insert_fields.bind(1, fld.name);
  487. local field_type = fld.type.gsub("%(.+$", "");
  488. if(!field_type.len()) field_type = "VARCHAR";
  489. local type_id = exec_get_one(db, format("SELECT id FROM %s__field_types WHERE name=?", attached_db), [field_type]);
  490. stmt_insert_fields.bind(2, type_id);
  491. local field_length = fld.type.match("%((%d+)%)");
  492. stmt_insert_fields.bind(3, field_length);
  493. stmt_insert_fields.step();
  494. stmt_insert_fields.reset();
  495. }
  496. }
  497. }
  498. stmt_insert_table.finalize();
  499. stmt_insert_fields.finalize();
  500. }
  501. static function doUpdateTableMetadataFieldsFor(db, all_tables, attached_db="")
  502. {
  503. local stmt_insert_fields = db.prepare(format("INSERT INTO %s__table_metadata_fields(table_id, field_id) VALUES(?,?)", attached_db));
  504. local stmt_insert_accept_fields = db.prepare(format("INSERT INTO %s__table_metadata_accept_fields(table_id, field_id) VALUES(?,?)", attached_db));
  505. 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));
  506. foreach(tbl in all_tables)
  507. {
  508. local all_fields = exec_get_all(db, format("PRAGMA %stable_info(\"%s\");", attached_db, tbl.name));
  509. foreach(fld in all_fields)
  510. {
  511. stmt_insert_fields.bind(1, tbl.id);
  512. local field_id = exec_get_one(db, format("SELECT id FROM %s__fields_metadata WHERE name=?", attached_db), [fld.name]);
  513. stmt_insert_fields.bind(2, field_id);
  514. stmt_insert_fields.step();
  515. stmt_insert_fields.reset();
  516. local ro = getReadOnlyFields(db, attached_db);
  517. if(!table_rawin(ro, fld.name))
  518. {
  519. stmt_insert_accept_fields.bind(1, tbl.id);
  520. stmt_insert_accept_fields.bind(2, field_id);
  521. stmt_insert_accept_fields.step();
  522. stmt_insert_accept_fields.reset();
  523. }
  524. if(fld.name.endswith("_id"))
  525. {
  526. field_id = db.last_row_id();
  527. local table_name = fld.name.gsub("_id$", "s");
  528. local link_table_id = exec_get_one(db, format("SELECT id FROM %s__tables_metadata WHERE name=?", attached_db), [table_name]);
  529. 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]);
  530. //print(table_name, link_table_id, link_field_id);
  531. if(link_table_id && link_field_id)
  532. {
  533. stmt_insert_edit_links.bind(1, tbl.id);
  534. stmt_insert_edit_links.bind(2,field_id);
  535. stmt_insert_edit_links.bind(3, link_table_id);
  536. stmt_insert_edit_links.bind(4, link_field_id);
  537. stmt_insert_edit_links.bind(5, link_field_id);
  538. stmt_insert_edit_links.bind(6, link_field_id);
  539. stmt_insert_edit_links.bind(7, link_field_id);
  540. stmt_insert_edit_links.step();
  541. stmt_insert_edit_links.reset();
  542. }
  543. }
  544. }
  545. }
  546. stmt_insert_fields.finalize();
  547. stmt_insert_accept_fields.finalize();
  548. stmt_insert_edit_links.finalize();
  549. }
  550. static function doCleanTableMetadataFieldsFor(db, all_tables, attached_db="")
  551. {
  552. //cleanup references for inexisting tables/views at global level
  553. local updateTablesMetadata = function()
  554. {
  555. local sql_exists = format("SELECT 1 FROM %ssqlite_master sm, %s__tables_metadata tm WHERE tm.id=a.%%s and sm.name=tm.name",
  556. attached_db, attached_db);
  557. 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"));
  558. db.exec_dml(sql);
  559. sql = format("UPDATE %s__tables_metadata a set edit_table_id=NULL WHERE NOT EXISTS(%s)", attached_db, format(sql_exists, "edit_table_id"));
  560. db.exec_dml(sql);
  561. sql = format("UPDATE %s__app_menu a set table_view_id=NULL WHERE NOT EXISTS(%s)", attached_db, format(sql_exists, "table_view_id"));
  562. db.exec_dml(sql);
  563. }
  564. updateTablesMetadata();
  565. //now for each table clean references to inexistent fields/tables/views
  566. foreach(tbl in all_tables)
  567. {
  568. local all_fields = exec_get_all(db, format("PRAGMA %stable_info(\"%s\");", attached_db, tbl.name));
  569. local existing_fields = [];
  570. foreach(fld in all_fields)
  571. {
  572. existing_fields.push(fld.name);
  573. }
  574. //if(!existing_fields.len()) continue;
  575. existing_fields = "'" + existing_fields.concat("','") + "'";
  576. local deleteNotIn = function(tbl_to_delete, tbl_id_name, field_names_to_keep, table_id_fld="table_id", field_id_fld="field_id")
  577. {
  578. 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))",
  579. attached_db, tbl_to_delete, attached_db, tbl_id_name, attached_db);
  580. db.exec_dml(sql);
  581. sql = format("DELETE FROM %s\"" + tbl_to_delete + "\" WHERE " + table_id_fld +
  582. "=(SELECT id FROM %s__tables_metadata WHERE name='" +
  583. tbl_id_name + "') AND " + field_id_fld + " NOT IN(SELECT id FROM %s__fields_metadata WHERE name IN(" + field_names_to_keep + "))",
  584. attached_db, attached_db, attached_db);
  585. //debug_print(sql, "\n");
  586. db.exec_dml(sql);
  587. }
  588. //order is important here first any of __table_metadata_list_fields, __table_metadata_accept_fields, __table_metadata_edit_links,
  589. //then __table_metadata_filter_tables and __table_metadata_fields
  590. deleteNotIn("__table_metadata_list_fields", tbl.name, existing_fields);
  591. deleteNotIn("__table_metadata_accept_fields", tbl.name, existing_fields);
  592. //__table_metadata_edit_links references tables/views in multiple fields
  593. deleteNotIn("__table_metadata_edit_links", tbl.name, existing_fields, "show_table_id", "show_field_id");
  594. deleteNotIn("__table_metadata_edit_links", tbl.name, existing_fields, "link_table_id", "link_field_id");
  595. deleteNotIn("__table_metadata_edit_links", tbl.name, existing_fields);
  596. deleteNotIn("__table_metadata_filter_tables", tbl.name, existing_fields, "table_filtered_id", "table_filtered_field_id");
  597. deleteNotIn("__table_metadata_fields", tbl.name, existing_fields);
  598. }
  599. //finally remove inexistent tables/views
  600. local sql = format("DELETE FROM %s__tables_metadata a WHERE NOT EXISTS(SELECT 1 FROM %ssqlite_master sm WHERE sm.name=a.name)",
  601. attached_db, attached_db);
  602. db.exec_dml(sql);
  603. }
  604. static function doUpdateMetadata(db, attached_db="")
  605. {
  606. db.exec_dml("BEGIN");
  607. local all_tables = exec_get_all(db, format("SELECT type, name FROM %ssqlite_master WHERE type IN('view','table')", attached_db));
  608. doUpdateTablesMetadataFor(db, all_tables);
  609. all_tables = exec_get_all(db, format("SELECT id, name FROM %s__tables_metadata", attached_db)); // WHERE is_view = 0");
  610. doUpdateTableMetadataFieldsFor(db, all_tables);
  611. doCleanTableMetadataFieldsFor(db, all_tables);
  612. db.exec_dml("COMMIT");
  613. }
  614. static function getReferencesOnDBSchema(db, name, attached_db="")
  615. {
  616. local reference_name = escapeRE(sanitizeDBName(name));
  617. local prefix_suffix_re = "[%s%(%),%.<>!=%-%+%*/\"']";
  618. local reference_name_re = prefix_suffix_re + "(" + reference_name + ")" + prefix_suffix_re;
  619. local reference_re = "()" + reference_name + "()";
  620. //print(reference_re);
  621. local checkValidRefenceName = function(whole_str, start_idx, end_idx)
  622. {
  623. //!!!this assume that the name searched is not at the begning or end of whole_str
  624. local context = whole_str.slice( (start_idx ? start_idx-1 : start_idx) ,
  625. (end_idx < whole_str.len() ? end_idx+1 : end_idx) );
  626. if(context.match(reference_name_re))
  627. {
  628. if( (context[0] == '\'') && (context[context.len()-1] != '\'') ) return false;
  629. else if( (context[0] == '"') && (context[context.len()-1] != '"') ) return false;
  630. return true;
  631. }
  632. return false;
  633. }
  634. local result = blob(0, 8000);
  635. local stmt = db.prepare(format("SELECT type, name, sql FROM %ssqlite_master;", attached_db));
  636. while(stmt.next_row())
  637. {
  638. local sql = stmt.col(2);
  639. if((::type(sql) == "string"))
  640. {
  641. sql = sql.tolower();
  642. sql.gmatch(reference_re, function(start_idx, end_idx){
  643. //print(start_idx, end_idx, sql.slice(start_idx, end_idx));
  644. if(checkValidRefenceName(sql, start_idx, end_idx))
  645. {
  646. //print("idx", start_idx, end_idx, sql.slice(start_idx, end_idx));
  647. //print(sql);
  648. result.write("-------------\n");
  649. result.write(stmt.col(0), "\t", stmt.col(1), "\n");
  650. result.write(stmt.col(2), "\n");
  651. return false; //one match is enough
  652. }
  653. return true;
  654. });
  655. }
  656. }
  657. stmt.finalize();
  658. return result.tostring();
  659. }
  660. static function searchOnAllTables(db, search_str, search_limit, attached_db="")
  661. {
  662. local embedded_limit = search_str.match("^(%d+):");
  663. if(embedded_limit)
  664. {
  665. search_limit = embedded_limit.tointeger();
  666. search_str = search_str.match("^%d+:(.+)");
  667. }
  668. local the_search_str;
  669. if( search_str.match("^_re_:") )
  670. {
  671. the_search_str = search_str.match("^_re_:(.+)");
  672. }
  673. else the_search_str = escapeRE(search_str);
  674. the_search_str = the_search_str.tolower();
  675. local search_count = 0;
  676. local result = blob(0, 8000);
  677. local stmt = db.prepare(format("SELECT name FROM %ssqlite_master WHERE type='table';", attached_db));
  678. while(stmt.next_row())
  679. {
  680. local tbl_name = stmt.col(0);
  681. local tbl_stmt = db.prepare(format("SELECT * FROM %s\"%s\"", attached_db, tbl_name));
  682. local col_count = tbl_stmt.col_count();
  683. local text_cols = [];
  684. for(local i=0; i < col_count; ++i)
  685. {
  686. local dtype = tbl_stmt.col_declared_type(i).tolower();
  687. if( (dtype.indexOf("varchar") >= 0) || (dtype.indexOf("text") >= 0) ) text_cols.push(i);
  688. }
  689. if(text_cols.len())
  690. {
  691. local text_cols_len = text_cols.len();
  692. local tbl_done = false;
  693. while(tbl_stmt.next_row() && !tbl_done)
  694. {
  695. for(local i=0; i < text_cols_len; ++i)
  696. {
  697. local col_idx = text_cols[i];
  698. local str = tbl_stmt.col(col_idx);
  699. if((::type(str) == "string"))
  700. {
  701. if(str.tolower().match(the_search_str))
  702. {
  703. result.write(tbl_name, ":", tbl_stmt.col_name(col_idx), "\n");
  704. ++search_count;
  705. tbl_done = true;
  706. break;
  707. }
  708. }
  709. }
  710. }
  711. }
  712. tbl_stmt.finalize();
  713. if(search_count >= search_limit) break;
  714. }
  715. stmt.finalize();
  716. return result.tostring();
  717. }
  718. }
  719. class SqlPreprocessor
  720. {
  721. stmt_get = null;
  722. stmt_get_by_name = null;
  723. stmt_set = null;
  724. stmt_insert_params = null;
  725. stmt_clear_params = null;
  726. function createSqlPreprocessorMainTable(db)
  727. {
  728. local sql = [==[
  729. CREATE TABLE IF NOT EXISTS __app_sql (
  730. id INTEGER PRIMARY KEY NOT NULL,
  731. _version_ INTEGER NOT NULL DEFAULT 0,
  732. _cdate_ DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  733. _mdate_ DATETIME,
  734. name VARCHAR NOT NULL COLLATE NOCASE
  735. CONSTRAINT __app_sql_name_unique UNIQUE,
  736. base TEXT COLLATE NOCASE NOT NULL,
  737. sql TEXT COLLATE NOCASE,
  738. notes TEXT COLLATE NOCASE
  739. );
  740. CREATE VIEW IF NOT EXISTS __app_sql_list_view AS
  741. SELECT
  742. a.id,
  743. a._mdate_,
  744. a.name
  745. FROM __app_sql AS a;
  746. CREATE TABLE IF NOT EXISTS __app_sql_parameters (
  747. id INTEGER PRIMARY KEY NOT NULL,
  748. _version_ INTEGER NOT NULL DEFAULT 0,
  749. _cdate_ DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  750. _mdate_ DATETIME,
  751. app_sql_id integer not null references __app_sql(id),
  752. name VARCHAR NOT NULL COLLATE NOCASE,
  753. default_value VARCHAR COLLATE NOCASE,
  754. field_type VARCHAR COLLATE NOCASE,
  755. notes TEXT COLLATE NOCASE,
  756. CONSTRAINT app_sql_id_name_unique UNIQUE(app_sql_id, name)
  757. );
  758. ]==];
  759. db.exec_dml(sql);
  760. }
  761. function doPrepare(db)
  762. {
  763. if(!stmt_get)
  764. {
  765. stmt_get = db.prepare("SELECT base FROM __app_sql WHERE id=?");
  766. stmt_get_by_name = db.prepare("SELECT base FROM __app_sql WHERE name=?");
  767. stmt_set = db.prepare("UPDATE __app_sql SET sql=? WHERE id=?");
  768. stmt_clear_params = db.prepare("DELETE FROM __app_sql_parameters WHERE app_sql_id=?");
  769. stmt_insert_params = db.prepare("INSERT INTO __app_sql_parameters(app_sql_id, name, default_value, field_type, notes) VALUES(?,?,?,?,?)");
  770. }
  771. }
  772. function doFinalize(db)
  773. {
  774. if(stmt_get)
  775. {
  776. stmt_get.finalize();
  777. stmt_get = null;
  778. stmt_set.finalize();
  779. stmt_get_by_name.finalize();
  780. stmt_clear_params.finalize();
  781. stmt_insert_params.finalize();
  782. }
  783. }
  784. function saveSqlParameters(sql_id, sql)
  785. {
  786. stmt_clear_params.bind(1, sql_id);
  787. stmt_clear_params.step();
  788. stmt_clear_params.reset();
  789. local self = this;
  790. //{$field_name:default_value:field_type:field_notes}
  791. sql.gmatch("(%b{})", function(m){
  792. if(m[1] == '$')
  793. {
  794. local field_name, default_value, field_type, field_notes;
  795. local ary = m.slice(2,-1).split(':');
  796. if(ary.len() > 0)
  797. {
  798. foreach(idx, val in ary)
  799. {
  800. val = val.trim();
  801. switch(idx)
  802. {
  803. case 0: field_name = val; break;
  804. case 1: default_value = val; break;
  805. case 2: field_type = val; break;
  806. case 4: field_notes = val; break;
  807. }
  808. }
  809. local stmt_insert_params = self.stmt_insert_params;
  810. stmt_insert_params.bind(1, sql_id);
  811. stmt_insert_params.bind(2, field_name);
  812. stmt_insert_params.bind_empty_null(3, default_value);
  813. stmt_insert_params.bind_empty_null(4, field_type);
  814. stmt_insert_params.bind_empty_null(5, field_notes);
  815. stmt_insert_params.step();
  816. stmt_insert_params.reset();
  817. }
  818. }
  819. return true;
  820. });
  821. }
  822. //the macros for params are {$param_name:param_value}
  823. static function preprocessSqlQueryParams(sql_seed, params)
  824. {
  825. local found;
  826. do {
  827. found = false;
  828. sql_seed = sql_seed.gsub("(%b{})", function(m){
  829. if(m[1] == '$')
  830. {
  831. found = true;
  832. local ary = m.slice(2,-1).split(':');
  833. local val = table_get(params, ary[0], null);
  834. if(val == null && (ary.len() > 1))
  835. {
  836. //we'll add the default parameter to params
  837. val = params[ary[0]] <- ary[1];
  838. }
  839. return val;
  840. }
  841. return m;
  842. });
  843. //print("loop", found);
  844. } while(found);
  845. return sql_seed;
  846. }
  847. function preprocessSqlQuery(db, sql_seed, id)
  848. {
  849. local need_prepare = stmt_get == null;
  850. if(need_prepare) doPrepare(db);
  851. if(sql_seed)
  852. {
  853. local found;
  854. local self = this;
  855. do {
  856. found = false;
  857. sql_seed = sql_seed.gsub("(%b{})", function(m){
  858. if(m[1] == ':')
  859. {
  860. local vt = m.slice(2,-1);
  861. self.stmt_get_by_name.bind(1, vt);
  862. if(self.stmt_get_by_name.next_row())
  863. {
  864. //need check to prevent infinite recursion
  865. found = true;
  866. local val = self.stmt_get_by_name.col(0);
  867. self.stmt_get_by_name.reset();
  868. return val;
  869. }
  870. }
  871. return m;
  872. });
  873. //print("loop", found);
  874. } while(found);
  875. stmt_set.bind(1, sql_seed);
  876. stmt_set.bind(2, id);
  877. stmt_set.step();
  878. stmt_set.reset();
  879. saveSqlParameters(id, sql_seed);
  880. }
  881. if(need_prepare) doFinalize(db);
  882. return sql_seed;
  883. }
  884. //the macros for params are {:query_name_to_include}
  885. function preprocessSqlQueries(db)
  886. {
  887. local stmt = db.prepare("SELECT id FROM __app_sql");
  888. local all_ids = [];
  889. while(stmt.next_row())
  890. {
  891. all_ids.push(stmt.col(0));
  892. }
  893. stmt.finalize();
  894. doPrepare(db);
  895. try
  896. {
  897. db.exec_dml("BEGIN");
  898. foreach(id in all_ids)
  899. {
  900. local found;
  901. local sql_seed = false;
  902. stmt_get.bind(1, id);
  903. if(stmt_get.next_row()) sql_seed = stmt_get.col(0);
  904. stmt_get.reset();
  905. if(sql_seed)
  906. {
  907. preprocessSqlQuery(db, sql_seed, id);
  908. }
  909. }
  910. db.exec_dml("COMMIT");
  911. }
  912. catch(e)
  913. {
  914. db.exec_dml("ROLLBACK");
  915. }
  916. doFinalize(db);
  917. }
  918. function getPreprocessorQueryName(db, sql)
  919. {
  920. createSqlPreprocessorMainTable(db);
  921. return sql.match("^%-%-name=(%S+)");
  922. }
  923. function getPreprocessorQuery(db, sql, field="ifnull(sql, base) as sql")
  924. {
  925. local name = getPreprocessorQueryName(db, sql);
  926. if(name)
  927. {
  928. local stmt = db.prepare(format("SELECT %s FROM __app_sql WHERE name=?", field));
  929. stmt.bind(1, name);
  930. if(stmt.next_row())
  931. {
  932. name = stmt.col(0);
  933. }
  934. stmt.finalize();
  935. }
  936. return name;
  937. }
  938. function insertPreprocessorQuery(db, sql)
  939. {
  940. local name = getPreprocessorQueryName(db, sql);
  941. if(name)
  942. {
  943. local stmt = db.prepare("insert into __app_sql(name, base) values(?,?)");
  944. stmt.bind(1, name);
  945. stmt.bind(2, sql);
  946. stmt.step();
  947. print(db.errmsg());
  948. stmt.finalize();
  949. preprocessSqlQueries(db);
  950. }
  951. return name;
  952. }
  953. function updatePreprocessorQuery(db, sql)
  954. {
  955. local name = getPreprocessorQueryName(db, sql);
  956. if(name)
  957. {
  958. local stmt = db.prepare("update __app_sql set base = ? where name = ?");
  959. stmt.bind(1, sql);
  960. stmt.bind(2, name);
  961. stmt.step();
  962. stmt.finalize();
  963. preprocessSqlQueries(db);
  964. }
  965. return name;
  966. }
  967. }