UNPKG

@nymphjs/driver-sqlite3

Version:

Nymph.js - SQLite3 DB Driver

1,037 lines (1,032 loc) 83 kB
import SQLite3 from 'better-sqlite3'; import { NymphDriver, EntityUniqueConstraintError, InvalidParametersError, NotConfiguredError, QueryFailedError, UnableToConnectError, xor, } from '@nymphjs/nymph'; import { makeTableSuffix } from '@nymphjs/guid'; import { SQLite3DriverConfigDefaults as defaults, } from './conf/index.js'; class InternalStore { link; linkWrite; connected = false; transactionsStarted = 0; constructor(link) { this.link = link; } } /** * The SQLite3 Nymph database driver. */ export default class SQLite3Driver extends NymphDriver { config; prefix; // @ts-ignore: this is assigned in connect(), which is called by the constructor. store; static escape(input) { if (input.indexOf('\x00') !== -1) { throw new InvalidParametersError('SQLite3 identifiers (like entity ETYPE) cannot contain null characters.'); } return '"' + input.replace(/"/g, () => '""') + '"'; } constructor(config, store) { super(); this.config = { ...defaults, ...config }; if (this.config.filename === ':memory:') { this.config.explicitWrite = true; } this.prefix = this.config.prefix; if (store) { this.store = store; } else { this.connect(); } } /** * This is used internally by Nymph. Don't call it yourself. * * @returns A clone of this instance. */ clone() { return new SQLite3Driver(this.config, this.store); } /** * Connect to the SQLite3 database. * * @returns Whether this instance is connected to a SQLite3 database. */ connect() { if (this.store && this.store.connected) { return Promise.resolve(true); } // Connecting this._connect(false); return Promise.resolve(this.store.connected); } _connect(write) { const { filename, fileMustExist, timeout, explicitWrite, wal, verbose } = this.config; try { const setOptions = (link) => { // Set database and connection options. if (wal) { link.pragma('journal_mode = WAL;'); } link.pragma('encoding = "UTF-8";'); link.pragma('foreign_keys = 1;'); link.pragma('case_sensitive_like = 1;'); for (let pragma of this.config.pragmas) { link.pragma(pragma); } // Create the preg_match and regexp functions. link.function('regexp', { deterministic: true }, ((pattern, subject) => (this.posixRegexMatch(pattern, subject) ? 1 : 0))); }; let link; try { link = new SQLite3(filename, { readonly: !explicitWrite && !write, fileMustExist, timeout, verbose, }); } catch (e) { if (e.code === 'SQLITE_CANTOPEN' && !explicitWrite && !write && !this.config.fileMustExist) { // This happens when the file doesn't exist and we attempt to open it // readonly. // First open it in write mode. const writeLink = new SQLite3(filename, { readonly: false, fileMustExist, timeout, verbose, }); setOptions(writeLink); writeLink.close(); // Now open in readonly. link = new SQLite3(filename, { readonly: true, fileMustExist, timeout, verbose, }); } else { throw e; } } if (!this.store) { if (write) { throw new Error('Tried to open in write without opening in read first.'); } this.store = new InternalStore(link); } else if (write) { this.store.linkWrite = link; } else { this.store.link = link; } this.store.connected = true; setOptions(link); } catch (e) { if (this.store) { this.store.connected = false; } if (filename === ':memory:') { throw new NotConfiguredError("It seems the config hasn't been set up correctly. Could not connect: " + e?.message); } else { throw new UnableToConnectError('Could not connect: ' + e?.message); } } } /** * Disconnect from the SQLite3 database. * * @returns Whether this instance is connected to a SQLite3 database. */ async disconnect() { if (this.store.connected) { if (this.store.linkWrite && !this.config.explicitWrite) { this.store.linkWrite.exec('PRAGMA optimize;'); this.store.linkWrite.close(); this.store.linkWrite = undefined; } if (this.config.explicitWrite) { this.store.link.exec('PRAGMA optimize;'); } this.store.link.close(); this.store.transactionsStarted = 0; this.store.connected = false; } return this.store.connected; } async inTransaction() { return this.store.transactionsStarted > 0; } /** * Check connection status. * * @returns Whether this instance is connected to a SQLite3 database. */ isConnected() { return this.store.connected; } /** * Create entity tables in the database. * * @param etype The entity type to create a table for. If this is blank, the default tables are created. */ createTables(etype = null) { this.startTransaction('nymph-tablecreation'); try { if (etype != null) { // Create the entity table. this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid" CHARACTER(24) PRIMARY KEY, "tags" TEXT, "cdate" REAL NOT NULL, "mdate" REAL NOT NULL);`); this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_cdate`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("cdate");`); this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_mdate`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("mdate");`); this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_tags`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("tags");`); // Create the data table. this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid") ON DELETE CASCADE, "name" TEXT NOT NULL, "value" CHARACTER(1) NOT NULL, "json" BLOB, "string" TEXT, "number" REAL, "truthy" INTEGER, PRIMARY KEY("guid", "name"));`); this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid");`); this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_guid_name`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid", "name");`); this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_guid__name_user`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid") WHERE "name" = \'user\';`); this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_guid__name_group`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid") WHERE "name" = \'group\';`); this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_name`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("name");`); this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_name__truthy`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("name") WHERE "truthy" = 1;`); this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_name__falsy`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("name") WHERE "truthy" <> 1;`); this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_name_string`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("name", "string");`); this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_name_number`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("name", "number");`); // Create the references table. this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid") ON DELETE CASCADE, "name" TEXT NOT NULL, "reference" CHARACTER(24) NOT NULL, PRIMARY KEY("guid", "name", "reference"));`); this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}_id_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("guid");`); this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}_id_name`)} ON ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("name");`); this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}_id_name_reference`)} ON ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("name", "reference");`); // Create the unique strings table. this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid") ON DELETE CASCADE, "unique" TEXT NOT NULL UNIQUE, PRIMARY KEY("guid", "unique"));`); } else { // Create the UID table. this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}uids`)} ("name" TEXT PRIMARY KEY NOT NULL, "cur_uid" INTEGER NOT NULL);`); } } catch (e) { this.rollback('nymph-tablecreation'); throw e; } this.commit('nymph-tablecreation'); return true; } query(runQuery, query, etypes = []) { try { this.nymph.config.debugInfo('sqlite3:query', query); return runQuery(); } catch (e) { const errorCode = e?.code; const errorMsg = e?.message; if (errorCode === 'SQLITE_ERROR' && errorMsg.match(/^no such table: /) && this.createTables()) { for (let etype of etypes) { this.createTables(etype); } try { return runQuery(); } catch (e2) { throw new QueryFailedError('Query failed: ' + e2?.code + ' - ' + e2?.message, query); } } else if (errorCode === 'SQLITE_CONSTRAINT_UNIQUE' && errorMsg.match(/^UNIQUE constraint failed: /)) { throw new EntityUniqueConstraintError(`Unique constraint violation.`); } else { throw new QueryFailedError('Query failed: ' + e?.code + ' - ' + e?.message, query); } } } queryArray(query, { etypes = [], params = {}, } = {}) { return this.query(() => (this.store.linkWrite || this.store.link) .prepare(query) .iterate(params), `${query} -- ${JSON.stringify(params)}`, etypes); } queryGet(query, { etypes = [], params = {}, } = {}) { return this.query(() => (this.store.linkWrite || this.store.link).prepare(query).get(params), `${query} -- ${JSON.stringify(params)}`, etypes); } queryRun(query, { etypes = [], params = {}, } = {}) { return this.query(() => (this.store.linkWrite || this.store.link).prepare(query).run(params), `${query} -- ${JSON.stringify(params)}`, etypes); } async commit(name) { if (name == null || typeof name !== 'string' || name.length === 0) { throw new InvalidParametersError('Transaction commit attempted without a name.'); } if (this.store.transactionsStarted === 0) { return true; } this.queryRun(`RELEASE SAVEPOINT ${SQLite3Driver.escape(name)};`); this.store.transactionsStarted--; if (this.store.transactionsStarted === 0 && this.store.linkWrite && !this.config.explicitWrite) { this.store.linkWrite.exec('PRAGMA optimize;'); this.store.linkWrite.close(); this.store.linkWrite = undefined; } return true; } async deleteEntityByID(guid, className) { let EntityClass; if (typeof className === 'string' || className == null) { const GetEntityClass = this.nymph.getEntityClass(className ?? 'Entity'); EntityClass = GetEntityClass; } else { EntityClass = className; } const etype = EntityClass.ETYPE; await this.startTransaction('nymph-delete'); try { this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} WHERE "guid"=@guid;`, { etypes: [etype], params: { guid, }, }); this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} WHERE "guid"=@guid;`, { etypes: [etype], params: { guid, }, }); this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=@guid;`, { etypes: [etype], params: { guid, }, }); this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=@guid;`, { etypes: [etype], params: { guid, }, }); } catch (e) { this.nymph.config.debugError('sqlite3', `Delete entity error: "${e}"`); await this.rollback('nymph-delete'); throw e; } await this.commit('nymph-delete'); // Remove any cached versions of this entity. if (this.nymph.config.cache) { this.cleanCache(guid); } return true; } async deleteUID(name) { if (!name) { throw new InvalidParametersError('Name not given for UID'); } await this.startTransaction('nymph-delete-uid'); this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, { params: { name, }, }); await this.commit('nymph-delete-uid'); return true; } async *exportDataIterator() { if (yield { type: 'comment', content: `#nex2 # Nymph Entity Exchange v2 # http://nymph.io # # Generation Time: ${new Date().toLocaleString()} `, }) { return; } if (yield { type: 'comment', content: ` # # UIDs # `, }) { return; } // Export UIDs. let uids = this.queryArray(`SELECT * FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} ORDER BY "name";`); for (const uid of uids) { if (yield { type: 'uid', content: `<${uid.name}>[${uid.cur_uid}]\n` }) { return; } } if (yield { type: 'comment', content: ` # # Entities # `, }) { return; } // Get the etypes. const tables = this.queryArray("SELECT `name` FROM `sqlite_master` WHERE `type`='table' AND `name` LIKE @prefix;", { params: { prefix: this.prefix + 'entities_' + '%', }, }); const etypes = []; for (const table of tables) { etypes.push(table.name.substr((this.prefix + 'entities_').length)); } for (const etype of etypes) { // Export entities. const dataIterator = this.queryArray(`SELECT e.*, d."name", d."value", json(d."json") as "json", d."string", d."number" FROM ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} e LEFT JOIN ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} d USING ("guid") ORDER BY e."guid";`)[Symbol.iterator](); let datum = dataIterator.next(); while (!datum.done) { const guid = datum.value.guid; const tags = datum.value.tags.slice(1, -1); const cdate = datum.value.cdate; const mdate = datum.value.mdate; let currentEntityExport = []; currentEntityExport.push(`{${guid}}<${etype}>[${tags}]`); currentEntityExport.push(`\tcdate=${JSON.stringify(cdate)}`); currentEntityExport.push(`\tmdate=${JSON.stringify(mdate)}`); if (datum.value.name != null) { // This do will keep going and adding the data until the // next entity is reached. datum will end on the next entity. do { const value = datum.value.value === 'N' ? JSON.stringify(datum.value.number) : datum.value.value === 'S' ? JSON.stringify(datum.value.string) : datum.value.value === 'J' ? datum.value.json : datum.value.value; currentEntityExport.push(`\t${datum.value.name}=${value}`); datum = dataIterator.next(); } while (!datum.done && datum.value.guid === guid); } else { // Make sure that datum is incremented :) datum = dataIterator.next(); } currentEntityExport.push(''); if (yield { type: 'entity', content: currentEntityExport.join('\n') }) { return; } } } } /** * Generate the SQLite3 query. * @param options The options array. * @param formattedSelectors The formatted selector array. * @param etype * @param count Used to track internal params. * @param params Used to store internal params. * @param subquery Whether only a subquery should be returned. * @returns The SQL query. */ makeEntityQuery(options, formattedSelectors, etype, count = { i: 0 }, params = {}, subquery = false, tableSuffix = '', etypes = [], guidSelector = undefined) { if (typeof options.class?.alterOptions === 'function') { options = options.class.alterOptions(options); } const eTable = `e${tableSuffix}`; const dTable = `d${tableSuffix}`; const fTable = `f${tableSuffix}`; const ieTable = `ie${tableSuffix}`; const sTable = `s${tableSuffix}`; const sort = options.sort ?? 'cdate'; const queryParts = this.iterateSelectorsForQuery(formattedSelectors, ({ key, value, typeIsOr, typeIsNot }) => { const clauseNot = key.startsWith('!'); let curQuery = ''; for (const curValue of value) { switch (key) { case 'guid': case '!guid': for (const curGuid of curValue) { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const guid = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + ieTable + '."guid"=@' + guid; params[guid] = curGuid; } break; case 'tag': case '!tag': for (const curTag of curValue) { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const tag = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + ieTable + '."tags" LIKE @' + tag + " ESCAPE '\\'"; params[tag] = '%,' + curTag .replace('\\', '\\\\') .replace('%', '\\%') .replace('_', '\\_') + ',%'; } break; case 'defined': case '!defined': for (const curVar of curValue) { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const name = `param${++count.i}`; curQuery += ieTable + '."guid" ' + (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + 'IN (SELECT "guid" FROM ' + SQLite3Driver.escape(this.prefix + 'data_' + etype) + ' WHERE "name"=@' + name + ')'; params[name] = curVar; } break; case 'truthy': case '!truthy': for (const curVar of curValue) { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } if (curVar === 'cdate') { curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + '(' + ieTable + '."cdate" NOT NULL)'; break; } else if (curVar === 'mdate') { curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + '(' + ieTable + '."mdate" NOT NULL)'; break; } else { const name = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + 'EXISTS (SELECT "guid" FROM ' + SQLite3Driver.escape(this.prefix + 'data_' + etype) + ' WHERE "guid"=' + ieTable + '."guid" AND "name"=@' + name + ' AND "truthy"=1)'; params[name] = curVar; } } break; case 'equal': case '!equal': if (curValue[0] === 'cdate') { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const cdate = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + ieTable + '."cdate"=@' + cdate; params[cdate] = Number(curValue[1]); break; } else if (curValue[0] === 'mdate') { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const mdate = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + ieTable + '."mdate"=@' + mdate; params[mdate] = Number(curValue[1]); break; } else if (typeof curValue[1] === 'number') { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const name = `param${++count.i}`; const value = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + 'EXISTS (SELECT "guid" FROM ' + SQLite3Driver.escape(this.prefix + 'data_' + etype) + ' WHERE "guid"=' + ieTable + '."guid" AND "name"=@' + name + ' AND "number"=@' + value + ')'; params[name] = curValue[0]; params[value] = curValue[1]; } else if (typeof curValue[1] === 'string') { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const name = `param${++count.i}`; const value = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + 'EXISTS (SELECT "guid" FROM ' + SQLite3Driver.escape(this.prefix + 'data_' + etype) + ' WHERE "guid"=' + ieTable + '."guid" AND "name"=@' + name + ' AND "string"=@' + value + ')'; params[name] = curValue[0]; params[value] = curValue[1]; } else { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } let svalue; if (curValue[1] instanceof Object && typeof curValue[1].toReference === 'function') { svalue = JSON.stringify(curValue[1].toReference()); } else { svalue = JSON.stringify(curValue[1]); } const name = `param${++count.i}`; const value = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + 'EXISTS (SELECT "guid" FROM ' + SQLite3Driver.escape(this.prefix + 'data_' + etype) + ' WHERE "guid"=' + ieTable + '."guid" AND "name"=@' + name + ' AND "json"=jsonb(@' + value + '))'; params[name] = curValue[0]; params[value] = svalue; } break; case 'contain': case '!contain': if (curValue[0] === 'cdate') { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const cdate = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + ieTable + '."cdate"=@' + cdate; params[cdate] = Number(curValue[1]); break; } else if (curValue[0] === 'mdate') { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const mdate = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + ieTable + '."mdate"=@' + mdate; params[mdate] = Number(curValue[1]); break; } else { const containTableSuffix = makeTableSuffix(); if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } let svalue; if (curValue[1] instanceof Object && typeof curValue[1].toReference === 'function') { svalue = JSON.stringify(curValue[1].toReference()); } else { svalue = JSON.stringify(curValue[1]); } const name = `param${++count.i}`; const value = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + 'EXISTS (SELECT "guid" FROM ' + SQLite3Driver.escape(this.prefix + 'data_' + etype) + ' d' + containTableSuffix + ' WHERE "guid"=' + ieTable + '."guid" AND "name"=@' + name + ' AND json(@' + value + ') IN (SELECT json_quote("value") FROM json_each(d' + containTableSuffix + '."json")))'; params[name] = curValue[0]; params[value] = svalue; } break; case 'match': case '!match': if (curValue[0] === 'cdate') { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const cdate = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + '(' + ieTable + '."cdate" REGEXP @' + cdate + ')'; params[cdate] = curValue[1]; break; } else if (curValue[0] === 'mdate') { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const mdate = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + '(' + ieTable + '."mdate" REGEXP @' + mdate + ')'; params[mdate] = curValue[1]; break; } else { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const name = `param${++count.i}`; const value = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + 'EXISTS (SELECT "guid" FROM ' + SQLite3Driver.escape(this.prefix + 'data_' + etype) + ' WHERE "guid"=' + ieTable + '."guid" AND "name"=@' + name + ' AND "string" REGEXP @' + value + ')'; params[name] = curValue[0]; params[value] = curValue[1]; } break; case 'imatch': case '!imatch': if (curValue[0] === 'cdate') { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const cdate = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + '(' + ieTable + '."cdate" REGEXP @' + cdate + ')'; params[cdate] = curValue[1]; break; } else if (curValue[0] === 'mdate') { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const mdate = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + '(' + ieTable + '."mdate" REGEXP @' + mdate + ')'; params[mdate] = curValue[1]; break; } else { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const name = `param${++count.i}`; const value = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + 'EXISTS (SELECT "guid" FROM ' + SQLite3Driver.escape(this.prefix + 'data_' + etype) + ' WHERE "guid"=' + ieTable + '."guid" AND "name"=@' + name + ' AND lower("string") REGEXP lower(@' + value + '))'; params[name] = curValue[0]; params[value] = curValue[1]; } break; case 'like': case '!like': if (curValue[0] === 'cdate') { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const cdate = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + '(' + ieTable + '."cdate" LIKE @' + cdate + " ESCAPE '\\')"; params[cdate] = curValue[1]; break; } else if (curValue[0] === 'mdate') { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const mdate = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + '(' + ieTable + '."mdate" LIKE @' + mdate + " ESCAPE '\\')"; params[mdate] = curValue[1]; break; } else { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const name = `param${++count.i}`; const value = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + 'EXISTS (SELECT "guid" FROM ' + SQLite3Driver.escape(this.prefix + 'data_' + etype) + ' WHERE "guid"=' + ieTable + '."guid" AND "name"=@' + name + ' AND "string" LIKE @' + value + " ESCAPE '\\')"; params[name] = curValue[0]; params[value] = curValue[1]; } break; case 'ilike': case '!ilike': if (curValue[0] === 'cdate') { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const cdate = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + '(' + ieTable + '."cdate" LIKE @' + cdate + " ESCAPE '\\')"; params[cdate] = curValue[1]; break; } else if (curValue[0] === 'mdate') { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const mdate = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + '(' + ieTable + '."mdate" LIKE @' + mdate + " ESCAPE '\\')"; params[mdate] = curValue[1]; break; } else { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const name = `param${++count.i}`; const value = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + 'EXISTS (SELECT "guid" FROM ' + SQLite3Driver.escape(this.prefix + 'data_' + etype) + ' WHERE "guid"=' + ieTable + '."guid" AND "name"=@' + name + ' AND lower("string") LIKE lower(@' + value + ") ESCAPE '\\')"; params[name] = curValue[0]; params[value] = curValue[1]; } break; case 'gt': case '!gt': if (curValue[0] === 'cdate') { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const cdate = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + ieTable + '."cdate">@' + cdate; params[cdate] = Number(curValue[1]); break; } else if (curValue[0] === 'mdate') { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const mdate = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + ieTable + '."mdate">@' + mdate; params[mdate] = Number(curValue[1]); break; } else { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const name = `param${++count.i}`; const value = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + 'EXISTS (SELECT "guid" FROM ' + SQLite3Driver.escape(this.prefix + 'data_' + etype) + ' WHERE "guid"=' + ieTable + '."guid" AND "name"=@' + name + ' AND "number">@' + value + ')'; params[name] = curValue[0]; params[value] = Number(curValue[1]); } break; case 'gte': case '!gte': if (curValue[0] === 'cdate') { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const cdate = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + ieTable + '."cdate">=@' + cdate; params[cdate] = Number(curValue[1]); break; } else if (curValue[0] === 'mdate') { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const mdate = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + ieTable + '."mdate">=@' + mdate; params[mdate] = Number(curValue[1]); break; } else { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const name = `param${++count.i}`; const value = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + 'EXISTS (SELECT "guid" FROM ' + SQLite3Driver.escape(this.prefix + 'data_' + etype) + ' WHERE "guid"=' + ieTable + '."guid" AND "name"=@' + name + ' AND "number">=@' + value + ')'; params[name] = curValue[0]; params[value] = Number(curValue[1]); } break; case 'lt': case '!lt': if (curValue[0] === 'cdate') { if (curQuery) { curQuery += typeIsOr ? ' OR ' : ' AND '; } const cdate = `param${++count.i}`; curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + ieTable + '."cdate"<@' + cdate; params[cdate] = Number(curValue[1]); break; }