@nymphjs/driver-sqlite3
Version:
Nymph.js - SQLite3 DB Driver
1,037 lines (1,032 loc) • 83 kB
JavaScript
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;
}