@linked-db/linked-ql
Advanced tools
Comparing version 0.3.8 to 0.4.0
@@ -14,3 +14,3 @@ { | ||
"icon": "https://webqit.io/icon.svg", | ||
"version": "0.3.8", | ||
"version": "0.4.0", | ||
"license": "MIT", | ||
@@ -17,0 +17,0 @@ "repository": { |
@@ -257,3 +257,3 @@ | ||
version_tag: null, | ||
savepoint_date: new String('now()'), | ||
savepoint_date: new Date, | ||
}; | ||
@@ -277,5 +277,5 @@ // -- Find a match first | ||
// -- Create record | ||
const insertResult = await this.database(OBJ_INFOSCHEMA_DB).table('database_savepoints').add(savepointJson); | ||
return new Savepoint(this, { ...insertResult.toJson(), id_following: null }); | ||
const insertResult = await this.database(OBJ_INFOSCHEMA_DB).table('database_savepoints').insert(savepointJson); | ||
return new Savepoint(this, { ...insertResult[0], id_following: null }); | ||
} | ||
} |
import { _isTypeObject, _isNull, _isString, _isNumeric, _isUndefined, _isObject } from '@webqit/util/js/index.js'; | ||
import { _from as _arrFrom, _intersect } from '@webqit/util/arr/index.js'; | ||
import { _wrapped } from '@webqit/util/str/index.js'; | ||
import { _isObject } from '@webqit/util/js/index.js'; | ||
import Insert from '../../query/insert/Insert.js'; | ||
import Update from '../../query/update/Update.js'; | ||
import Delete from '../../query/delete/Delete.js'; | ||
import Select from '../../query/select/Select.js'; | ||
@@ -12,18 +14,14 @@ export default class AbstractTable { | ||
constructor(database, tblName, params = {}) { | ||
this.$ = { | ||
database, | ||
name: tblName, | ||
params | ||
}; | ||
this.$ = { database, name: tblName, params }; | ||
} | ||
/** | ||
* @property String | ||
* @property Database | ||
*/ | ||
get name() { return this.$.name; } | ||
get database() { return this.$.database; } | ||
/** | ||
* @property Database | ||
* @property String | ||
*/ | ||
get database() { return this.$.database; } | ||
get name() { return this.$.name; } | ||
@@ -35,223 +33,122 @@ /** | ||
/** | ||
* @property Bool | ||
*/ | ||
get dropped() { return this.$.schema.hiddenAs === 'dropped'; } | ||
/** | ||
* @returns Object | ||
*/ | ||
async schema() { return await this.database.describeTable(this.name); } | ||
/** | ||
* ---------- | ||
* SCHEMA UTILS | ||
* ---------- | ||
*/ | ||
/** | ||
* Get Primary Key columns from schema. | ||
* Counts records. | ||
* | ||
* @returns Array | ||
* @param String q | ||
*/ | ||
async primaryKeyColumns() { return (await this.columnsForConstraint('PRIMARY_KEY'))[0]; } | ||
async count(q = '*') { | ||
const result = await this.select([ q => q.fn('COUNT', q).as('c') ]); | ||
return (result.rows || result)[0].c; | ||
} | ||
/** | ||
* Get columns that have given constraintType. | ||
* Selects record(s). | ||
* | ||
* @param String constraintType | ||
* @param Array fields | ||
* | ||
* @returns Array | ||
* @param Number|Object|Function where | ||
* | ||
* @param Array fields | ||
* @param Number|Object|Function where | ||
*/ | ||
async columnsForConstraint(constraintType) { | ||
const schema = await this.database.describeTable(this.name); | ||
const inlineConstraintTypesMap = { 'PRIMARY_KEY': 'primaryKey', 'UNIQUE_KEY': 'uniqueKey', 'CHECK': 'check', 'FOREIGN_KEY': 'references' }; | ||
let columns = !(constraintType in inlineConstraintTypesMap) ? [] : schema.columns.filter(col => col[inlineConstraintTypesMap[constraintType]]).map(col => [col.name]); | ||
if (schema.constraints.length) { columns = columns.concat(schema.constraints.filter(cnst => cnst.type === constraintType).reduce((cols, cnst) => cols.concat([cnst.columns]))); } | ||
return columns; | ||
async select(...args) { | ||
const query = new Select(this.database.client); | ||
// Where and fields | ||
if (Array.isArray(args[0])) { | ||
query.select(...args[0]); | ||
await this.resolveWhere(query, args[1]); | ||
} else await this.resolveWhere(query, args[0]); | ||
// Handle | ||
query.from([this.database.name, this.name]); | ||
return await this.database.client.query(query); | ||
} | ||
/** | ||
* Get columns that have given indexType. | ||
* Inserts record(s). | ||
* | ||
* @param String indexType | ||
* @param Object keyValsMap | ||
* @param Array|String returnList | ||
* | ||
* @returns Array | ||
* @param Array multilineKeyValsMap | ||
* @param Array|String returnList | ||
* | ||
* @param Array columns | ||
* @param Array multilineValues | ||
* @param Array|String returnList | ||
*/ | ||
async columnsForIndex(indexType) { | ||
const schema = await this.database.describeTable(this.name); | ||
if (schema.indexes.length) { return schema.indexes.filter(index => index.type === indexType).reduce((cols, index) => cols.concat([index.columns])); } | ||
return []; | ||
async insert(...args) { | ||
const query = new Insert(this.database.client); | ||
const [ columns = [], values = [], returnList = '*' ] = await this.resolvePayload(...args); | ||
// Payload | ||
if (columns.length) query.columns(...columns); | ||
for (const row of values) query.values(...row); | ||
// Handle | ||
query.into([this.database.name, this.name]); | ||
if (returnList) query.returning(returnList); | ||
return await this.database.client.query(query); | ||
} | ||
/** | ||
* ---------- | ||
* QUERY UTILS | ||
* ---------- | ||
*/ | ||
/** | ||
* Syncs a cursor. | ||
* Upserts record(s); with optional custom onConflict clause. | ||
* | ||
* @param Cursor cursor | ||
* @param Object keyValsMap | ||
* @param Array|String returnList | ||
* | ||
* @return Number | ||
* @param Array multilineKeyValsMap | ||
* @param Array|String returnList | ||
* | ||
* @param Array columns | ||
* @param Array multilineValues | ||
* @param Array|String returnList | ||
*/ | ||
async syncCursor(cursor) { return await this.putAll(cursor.cache); } | ||
/** | ||
* @inheritdoc | ||
*/ | ||
async match(rowObj) { | ||
// ----------- | ||
let primaryKey, existing; | ||
if (this.def.schema.primaryKey | ||
&& (primaryKey = readKeyPath(rowObj, this.def.schema.primaryKey)) | ||
&& (existing = await this.get(primaryKey))) { | ||
return { | ||
matchingKey: 'PRIMARY_KEY', | ||
primaryKey, | ||
row: existing, | ||
}; | ||
} | ||
// ----------- | ||
const primaryKeyColumns = await this.primaryKeyColumns(); | ||
const uniqueKeyColumns = await this.columnsForConstraint('UNIQUE_KEY'); | ||
primaryKeyColumns.concat(uniqueKeyColumns).map(columns => { | ||
return `(${ columns.map(col => `${ this.quote(obj[col]) } IN (${ columns.join(',') })`).join(' AND ') })`; | ||
}).join(' OR '); | ||
var match, uniqueKeys = Object.keys(this.def.schema.indexes).filter(alias => this.def.schema.indexes[alias].type === 'unique'); | ||
if (uniqueKeys.length) { | ||
(await this.getAll()).forEach((existingRow, i) => { | ||
if (match) return; | ||
uniqueKeys.forEach(constraintName => { | ||
var keyPath = this.def.schema.indexes[constraintName].keyPath; | ||
if (existingRow && readKeyPath(rowObj, keyPath) === readKeyPath(existingRow, keyPath)) { | ||
match = { | ||
matchingKey: constraintName, | ||
primaryKey: this.def.schema.primaryKey ? readKeyPath(existingRow, this.def.schema.primaryKey) : i, | ||
row: {...existingRow}, | ||
}; | ||
} | ||
}); | ||
}); | ||
} | ||
return match; | ||
async upsert(...args) { | ||
const query = new Insert(this.database.client); | ||
const [ columns = [], values = [], returnList = '*' ] = await this.resolvePayload(...args); | ||
// Payload | ||
if (columns.length) query.columns(...columns); | ||
for (const row of values) query.values(...row); | ||
// On-conflict | ||
query.onConflict({ entries: columns.map((col, i) => [col, values[i]])}); | ||
if (returnList) query.returning(returnList); | ||
// Handle | ||
query.into([this.database.name, this.name]); | ||
return await this.database.client.query(query); | ||
} | ||
/** | ||
* ------------------------------- | ||
* Updates record(s). | ||
* | ||
* @param Object|Object|Function where | ||
* @param Object payload | ||
* @param Array|String returnList | ||
*/ | ||
/** | ||
* @inheritdoc | ||
*/ | ||
async addAll(multiValues, columns = [], duplicateKeyCallback = null, forceAutoIncrement = false) { | ||
const inserts = [], forUpdates = []; | ||
for (const values of multiValues) { | ||
let rowObj = values; | ||
if (Array.isArray(values)) { | ||
const columnNames = columns.length ? columns : (await this.schema()).columns.map(col => col.name); | ||
if (columnNames.length && columnNames.length !== values.length) { | ||
throw new Error(`Column/values count mismatch at line ${ multiValues.indexOf(values) }.`); | ||
} | ||
rowObj = columnNames.reduce((rowObj, name, i) => ({ ...rowObj, [name]: values[i], }), {}); | ||
} | ||
// ------------- | ||
await this.handleInput(rowObj, true); | ||
// ------------- | ||
if (await this.shouldMatchInput(rowObj) || duplicateKeyCallback) { | ||
const match = await this.match(rowObj); | ||
if (match && duplicateKeyCallback) { | ||
const duplicateRow = { ...match.row }; | ||
if (duplicateKeyCallback(duplicateRow, rowObj)) { | ||
forUpdates.push(duplicateRow); | ||
} | ||
// The duplicate situation had been handled | ||
// ...positive or negative | ||
inserts.push('0'); | ||
continue; | ||
} | ||
// We're finally going to add! | ||
// We must not do this earlier... | ||
// as "onupdate" rows will erronously take on a new timestamp on this column | ||
await this.beforeAdd(rowObj, match); | ||
inserts.push(await this.add(rowObj)); | ||
continue; | ||
} | ||
await this.beforeAdd(rowObj); | ||
inserts.push(await this.add(rowObj)); | ||
} | ||
// OnDuplicateKey updates | ||
if (forUpdates.length) { inserts.push(...(await this.putAll(forUpdates))); } | ||
return inserts.filter((n, i) => n !== 0 && inserts.indexOf(n) === i); | ||
async update(...args) { | ||
if (args.length < 2) throw new Error(`A "where" match cannot be ommitted.`); | ||
const query = new Update(this.database.client); | ||
query.table([this.database.name, this.name]); | ||
// Where and payload | ||
const { where, payload, returnList = '*' } = args; | ||
await this.resolveWhere(query, where); | ||
for (const [key, value] of Object.entries(payload)) query.set(key, value); | ||
if (returnList) query.returning(returnList); | ||
// Handle | ||
return await this.database.client.query(query); | ||
} | ||
/** | ||
* @inheritdoc | ||
*/ | ||
async beforeAdd(rowObj, match) { | ||
const timestamp = (new Date).toISOString(); | ||
for (const column of (await this.schema()).columns) { | ||
const columnType = _isObject(column.type) ? column.type.name : column.type; | ||
if ((columnType === 'datetime' || columnType === 'timestamp') && column.default.expr === 'CURRENT_TIMESTAMP') { | ||
rowObj[column.name] = timestamp; | ||
} | ||
} | ||
} | ||
/** | ||
* @inheritdoc | ||
* Deletes record(s). | ||
* | ||
* @param Number|Object|Function where | ||
* @param Array|String returnList | ||
*/ | ||
async putAll(multiRows) { | ||
const updates = []; | ||
for (const rowObj of multiRows) { | ||
// ------------- | ||
await this.handleInput(rowObj); | ||
// ------------- | ||
if (await this.shouldMatchInput(rowObj)) { | ||
await this.beforePut(rowObj, await this.match(rowObj)); | ||
updates.push(await this.put(rowObj)); | ||
continue; | ||
} | ||
await this.beforePut(rowObj); | ||
updates.push(await this.put(rowObj)); | ||
} | ||
return updates; | ||
async delete(where, returnList = '*') { | ||
if (args.length < 1) throw new Error(`A "where" match cannot be ommitted.`); | ||
const query = new Delete(this.database.client); | ||
query.from([this.database.name, this.name]); | ||
// Where | ||
await this.resolveWhere(query, where); | ||
if (returnList) query.returning(returnList); | ||
// Handle | ||
return await this.database.client.query(query); | ||
} | ||
/** | ||
* @inheritdoc | ||
*/ | ||
async beforePut(rowObj, match) { | ||
if (match && !Object.keys(rowObj).every(key => rowObj[key] === match.row[key])) { | ||
const timestamp = (new Date).toISOString(); | ||
for (const column of (await this.schema()).columns) { | ||
const columnType = _isObject(column.type) ? column.type.name : column.type; | ||
if ((columnType === 'datetime' || columnType === 'timestamp') && column.onupdate === 'CURRENT_TIMESTAMP') { | ||
rowObj[column.name] = timestamp; | ||
} | ||
} | ||
} | ||
} | ||
/** | ||
* @inheritdoc | ||
*/ | ||
async deleteAll(multiIDs) { | ||
const deletes = []; | ||
for (const primaryKey of multiIDs) { | ||
deletes.push(this.delete(await this.beforeDelete(primaryKey))); | ||
} | ||
return deletes; | ||
} | ||
/** | ||
* @inheritdoc | ||
*/ | ||
async beforeDelete(primaryKey) { | ||
return primaryKey; | ||
} | ||
@@ -263,65 +160,54 @@ /** | ||
/** | ||
* @inheritdoc | ||
* Helps resolve specified where condition for the query. | ||
* | ||
* @param Query query | ||
* @param Number|Bool|Object|Function where | ||
*/ | ||
async handleInput(rowObj, applyDefaults = false) { | ||
const rowObjColumns = Object.keys(rowObj); | ||
const schema = await this.schema(); | ||
const schemaColumns = schema.columns.map(col => col.name); | ||
// ------------------ | ||
const unknownFields = rowObjColumns.filter(col => schemaColumns.indexOf(col) === -1); | ||
if (unknownFields.length) { throw new Error(`Unknown column: ${ unknownFields[0] }`); } | ||
// ------------------ | ||
for (const columnName of schemaColumns) { | ||
const value = rowObj[columnName]; | ||
const column = schema.columns.find(col => col.name === columnName) || {}; | ||
if (rowObjColumns.includes(columnName)) { | ||
const columnType = _isObject(column.type) ? column.type.name : column.type; | ||
// TODO: Validate supplied value | ||
if (columnType === 'json') { | ||
if (!_isTypeObject(value) && (!_isString(value) || (!_wrapped(value, '[', ']') && !_wrapped(value, '{', '}')))) { | ||
} | ||
} else if (['char', 'tinytext', 'smalltext', 'text', 'bigtext', 'varchar'].includes(columnType)) { | ||
if (!_isString(value)) { | ||
} | ||
} else if (['bit', 'tinyint', 'smallint', 'int', 'bigint', 'decimal', 'number', 'float', 'real'].includes(columnType)) { | ||
if (!_isNumeric(value)) { | ||
} | ||
} else if (['enum', 'set'].includes(columnType)) { | ||
if (!_isNumeric(value)) { | ||
} | ||
} else if (['date', 'datetime', 'timestamp'].includes(columnType)) { | ||
if (!_isString(value)) { | ||
} | ||
} | ||
} else if (applyDefaults && !_intersect([columnName], await this.primaryKeyColumns()).length) { | ||
// DONE: Apply defaults... | ||
rowObj[columnName] = ('default' in column) && !(['date', 'datetime', 'timestamp'].includes(columnType) && column.default.expr === 'CURRENT_TIMESTAMP') | ||
? column.default.value | ||
: null; | ||
} | ||
// Non-nullable | ||
if (column.notNull && (_isNull(rowObj[columnName]) || _isUndefined(rowObj[columnName]))) { | ||
throw new Error(`Inserting NULL on non-nullable column: ${ columnName }.`); | ||
} | ||
async resolveWhere(query, where) { | ||
if (where === true) return; | ||
if (/^\d+$/.test(where)) { | ||
const schema = await this.database.describeTable(this.name); | ||
const primaryKey = schema.columns?.find(col => col.primaryKey)?.name || schema.constraints.find(cons => cons.type === 'PRIMARY_KEY')?.targetColumns[0]; | ||
if (!primaryKey) throw new Error(`Cannot resolve primary key name for implied record.`); | ||
where = { [primaryKey]: where }; | ||
} | ||
if (_isObject(where)) { | ||
query.where(...Object.entries(where).map(([key, val]) => q => q.equals(key, val))); | ||
} else if (where) query.where(where); | ||
} | ||
/** | ||
* @inheritdoc | ||
* Resolves input arguments into columns and values array. | ||
* | ||
* @param Object keyValsMap | ||
* @param Array|String returnList | ||
* | ||
* @param Array multilineKeyValsMap | ||
* @param Array|String returnList | ||
* | ||
* @param Array columns | ||
* @param Array multilineValues | ||
* @param Array|String returnList | ||
*/ | ||
async shouldMatchInput(rowObj) { | ||
return (await this.schema()).columns.some(column => { | ||
const columnType = _isObject(column.type) ? column.type.name : column.type; | ||
return ['datetime', 'timestamp'].includes(columnType) && ( | ||
column.default.expr === 'CURRENT_TIMESTAMP' || column.onupdate === 'CURRENT_TIMESTAMP' | ||
); | ||
}); | ||
async resolvePayload(...args) { | ||
let columns = [], values = [], returnList; | ||
if (Array.isArray(args[0]) && /*important*/args[0].every(s => typeof s === 'string') && Array.isArray(args[1])) { | ||
if (!args[1].every(s => Array.isArray(s))) throw new TypeError(`Invalid payload format.`); | ||
[ columns, values, returnList ] = args.splice(0, 3); | ||
} else { | ||
const payload = [].concat(args.shift()); | ||
if (!_isObject(payload[0])) throw new TypeError(`Invalid payload format.`); | ||
columns = Object.keys(payload[0]); | ||
values = payload.map(row => Object.values(row)); | ||
returnList = args.shift(); | ||
} | ||
values = values.map(row => row.map(v => { | ||
if ([true,false,null].includes(v)) return q => q.literal(v); | ||
if (v instanceof Date) return q => q.value(v.toISOString().split('.')[0]); | ||
if (Array.isArray(v)) return q => q.array(v); | ||
if (_isObject(v)) return q => q.object(v); | ||
return q => q.value(v); | ||
})); | ||
return [columns, values, returnList]; | ||
} | ||
} | ||
/** | ||
* @AutoIncremen | ||
*/ | ||
const readKeyPath = (rowObj, keyPath) => { | ||
return _arrFrom(keyPath).map(key => rowObj[key]).filter(v => v).join('-'); | ||
}; | ||
} |
@@ -58,2 +58,8 @@ | ||
if (queryInstance.expandable) await queryInstance.expand(true); | ||
let myReturningList; | ||
if (this.params.dialect === 'mysql' && queryInstance.RETURNING_LIST?.length) { | ||
queryInstance = queryInstance.clone(); | ||
myReturningList = queryInstance.RETURNING_LIST.splice(0); | ||
// TODO: myReturningList | ||
} | ||
const result = await this.driver.query(queryInstance.toString(), params.params || []); | ||
@@ -60,0 +66,0 @@ return result.rows || result; |
import { _isObject, _isNull, _isNumeric } from '@webqit/util/js/index.js'; | ||
import SQLInsertQueryInspector from './SQLInsertQueryInspector.js'; | ||
import SQLDeleteQueryInspector from './SQLDeleteQueryInspector.js'; | ||
import Identifier from '../../query/select/Identifier.js'; | ||
import AbstractTable from '../abstracts/AbstractTable.js'; | ||
@@ -17,187 +13,2 @@ import SQLCursor from './SQLCursor.js'; | ||
getCursor() { return new SQLCursor(this); } | ||
/** | ||
* @inheritdoc | ||
*/ | ||
async getAll() { | ||
return new Promise((resolve, reject) => { | ||
this.database.client.driver.query(`SELECT * FROM ${ this.database.name }.${ this.name }`, (err, result) => { | ||
if (err) return reject(err); | ||
resolve((result.rows || result)); | ||
}); | ||
}); | ||
} | ||
/** | ||
* @inheritdoc | ||
*/ | ||
async get(primaryKey) { | ||
const primaryKeyColumns = await this.primaryKeyColumns(); | ||
if (!primaryKeyColumns.length) throw new Error(`Table has no primary key defined.`); | ||
return new Promise((resolve, reject) => { | ||
this.database.client.driver.query(`SELECT * FROM ${ this.database.name }.${ this.name } WHERE '${ primaryKey }' IN (${ primaryKeyColumns.join(',') })`, [], (err, result) => { | ||
if (err) return reject(err); | ||
resolve((result.rows || result)[0]); | ||
}); | ||
}); | ||
} | ||
/** | ||
* @inheritdoc | ||
*/ | ||
async count(query = '*') { | ||
return new Promise((resolve, reject) => { | ||
this.database.client.driver.query(`SELECT COUNT(${ query }) AS c FROM ${ this.database.name }.${ this.name }`, (err, result) => { | ||
if (err) return reject(err); | ||
resolve((result.rows || result)[0].c); | ||
}); | ||
}); | ||
} | ||
/** | ||
* @inheritdoc | ||
*/ | ||
async addAll(entries, columns = [], duplicateKeyCallback = null) { | ||
if (!entries.length) return; | ||
let duplicateKeyUpdateObj = {}; | ||
if (!columns.length) { | ||
if (_isObject(entries[0])) { | ||
columns = Object.keys(entries[0]); | ||
} else { | ||
const schema = await this.database.describeTable(this.name); | ||
columns = schema.columns.map(col => col.name); | ||
} | ||
} | ||
return new Promise((resolve, reject) => { | ||
let insertSql = `INSERT INTO ${ this.database.name }.${ this.name }\n\t${ columns.length ? `(${ columns.join(',') })\n\t` : `` }`; | ||
insertSql += `VALUES\n\t${ entries.map(row => formatAddRow(Object.values(row), this.database.client.params.dialect)).join(`,\n\t`) }`; | ||
if (duplicateKeyCallback) { | ||
duplicateKeyCallback(duplicateKeyUpdateObj); | ||
insertSql += ` ${ this.database.client.params.dialect === 'mysql' ? 'ON DUPLICATE KEY UPDATE' : /*postgres*/'ON CONFLICT DO UPDATE SET' } ${ formatAssignments(duplicateKeyUpdateObj, this.database.client.params.dialect) }`; | ||
} | ||
this.database.client.driver.query(insertSql, (err, result) => { | ||
if (err) return reject(err); | ||
resolve(new SQLInsertQueryInspector( | ||
this, | ||
result, | ||
columns, | ||
entries, | ||
duplicateKeyUpdateObj | ||
)); | ||
}); | ||
}); | ||
} | ||
/** | ||
* @inheritdoc | ||
*/ | ||
async add(rowObj) { | ||
return new Promise((resolve, reject) => { | ||
let insertSql = `INSERT INTO ${ this.database.name }.${ this.name }\n\t(${ Object.keys(rowObj).map(key => Identifier.fromJson(this, key)).join(', ') })\n\t`; | ||
insertSql += `VALUES\n\t${ formatAddRow(Object.values(rowObj), this.database.client.params.dialect) }\n\t`; | ||
insertSql += 'RETURNING *'; | ||
this.database.client.driver.query(insertSql, (err, result) => { | ||
if (err) return reject(err); | ||
resolve(new SQLInsertQueryInspector( | ||
this, | ||
result, | ||
Object.keys(rowObj), | ||
Object.values(rowObj), | ||
)); | ||
}); | ||
}); | ||
} | ||
/** | ||
* @inheritdoc | ||
*/ | ||
async putAll(rowObjs) { | ||
await Promise.all(rowObjs.map(rowObj => this.put(rowObj))); | ||
return new SQLInsertQueryInspector( | ||
this, | ||
{}, | ||
Object.keys(rowObjs[0]), | ||
rowObjs, | ||
); | ||
} | ||
/** | ||
* @inheritdoc | ||
*/ | ||
async put(rowObj) { | ||
return new Promise((resolve, reject) => { | ||
const putSql = `INSERT INTO ${ this.database.name }.${ this.name }\n\t${ formatPutRow(rowObj, this.database.client.params.dialect) }`; | ||
this.database.client.driver.query(putSql, (err, result) => { | ||
if (err) return reject(err); | ||
resolve(new SQLInsertQueryInspector( | ||
this, | ||
result, | ||
Object.keys(rowObj), | ||
Object.values(rowObj), | ||
)); | ||
}); | ||
}); | ||
} | ||
/** | ||
* @inheritdoc | ||
*/ | ||
async deleteAll(IDs = []) { | ||
const primaryKeyColumns = await this.primaryKeyColumns(); | ||
if (!primaryKeyColumns.length) throw new Error(`Table has no primary key defined.`); | ||
return new Promise((resolve, reject) => { | ||
const deleteSql = `DELETE FROM ${ this.database.name }.${ this.name }${ IDs.length ? ` WHERE ${ IDs.map(id => `'${ id }' in (${ primaryKeyColumns.join(',') })`).join(' OR ') }` : ''}`; | ||
this.database.client.driver.query(deleteSql, [], (err, result) => { | ||
if (err) return reject(err); | ||
resolve(new SQLDeleteQueryInspector( | ||
this, | ||
result | ||
)); | ||
}); | ||
}); | ||
} | ||
/** | ||
* @inheritdoc | ||
*/ | ||
async delete(primaryKey) { | ||
const primaryKeyColumns = await this.primaryKeyColumns(); | ||
if (!primaryKeyColumns.length) throw new Error(`Table has no primary key defined.`); | ||
return new Promise((resolve, reject) => { | ||
const deleteSql = `DELETE FROM ${ this.database.name }.${ this.name } WHERE ${ primaryKey } IN (${ primaryKeyColumns.join(',') })`; | ||
this.database.client.driver.query(deleteSql, [], (err, result) => { | ||
if (err) return reject(err); | ||
resolve(new SQLDeleteQueryInspector( | ||
this, | ||
result | ||
)); | ||
}); | ||
}); | ||
} | ||
} | ||
/** | ||
* -------- | ||
* HELPERS | ||
* -------- | ||
*/ | ||
const formatVal = (val, dialect) => { | ||
if (val instanceof Date) { | ||
try { return `'${ val.toISOString().split('.')[0] }'`; } | ||
catch(e) { return 'NULL'; } | ||
} | ||
if (val instanceof String) return val + ''; | ||
if (_isNumeric(val)) return val; | ||
if (_isNull(val)) return 'NULL'; | ||
if (Array.isArray(val) || _isObject(val)) (val = JSON.stringify(val)); | ||
if (typeof val === 'string') return `'${ val.replace(/'/g, `''`) }'`; | ||
throw new Error(`Couldn't serialize payload.`); | ||
}; | ||
const formatAddRow = (values, dialect) => '(' + values.map(val => formatVal(val, dialect)).join(',') + ')'; | ||
const formatAssignments = (rowObj, dialect) => Object.keys(rowObj).map(key => `${ Identifier.fromJson(this, key) } = ${ formatVal(rowObj[key], dialect) }`).join(','); | ||
const formatPutRow = (rowObj, dialect) => { | ||
const assignments = formatAssignments(rowObj, dialect); | ||
return `SET ${ assignments } ${ dialect === 'mysql' ? 'ON DUPLICATE KEY UPDATE' : /*postgres*/'ON CONFLICT DO UPDATE SET' } ${ assignments }`; | ||
}; | ||
} |
@@ -57,14 +57,2 @@ import Lexer from '../Lexer.js'; | ||
get quoteChars() { return this.constructor.getQuoteChars(this); } | ||
/** | ||
* A Quote helper | ||
* | ||
* @param String string | ||
* | ||
* @returns String | ||
*/ | ||
quote(string) { | ||
const quoteChar = this.quoteChars[0]; | ||
return `${ quoteChar }${ string.replace(new RegExp(quoteChar, 'g'), quoteChar.repeat(2)) }${ quoteChar }`; | ||
} | ||
@@ -249,3 +237,3 @@ /** | ||
const router = methodName => (...args) => { | ||
const instance = Types.reduce((prev, Type) => prev || (Type.factoryMethods ? (methodName in Type.factoryMethods && Type.factoryMethods[methodName](this, ...args)) : (methodName in Type.prototype && new Type(this))), null); | ||
const instance = Types.reduce((prev, Type) => prev || (Type.factoryMethods ? (typeof Type.factoryMethods[methodName] === 'function' && Type.factoryMethods[methodName](this, ...args)) : (typeof Type.prototype[methodName] === 'function' && new Type(this))), null); | ||
if (!instance) throw new Error(`Unknow method: ${ methodName }()`); | ||
@@ -252,0 +240,0 @@ set(instance); |
@@ -8,2 +8,10 @@ | ||
/** | ||
* @constructor | ||
*/ | ||
constructor(context) { | ||
super(context); | ||
this._vars = []; | ||
} | ||
/** | ||
* @returns String | ||
@@ -16,3 +24,3 @@ */ | ||
*/ | ||
get statementNode() { return this } | ||
get statementNode() { return this; } | ||
@@ -22,2 +30,7 @@ /** | ||
*/ | ||
get variables() { return this._vars; } | ||
/** | ||
* @inheritdoc | ||
*/ | ||
connectedNodeCallback(node) {} | ||
@@ -24,0 +37,0 @@ |
@@ -10,2 +10,3 @@ | ||
import Table from '../select/Table.js'; | ||
import Field from '../select/Field.js'; | ||
@@ -40,2 +41,3 @@ /* | ||
LIMIT_CLAUSE = null; | ||
RETURNING_LIST = []; | ||
@@ -172,2 +174,7 @@ /** | ||
} | ||
/** | ||
* @return Void | ||
*/ | ||
returning(...fields) { return this.build('RETURNING_LIST', fields, Field); } | ||
@@ -186,2 +193,3 @@ /** | ||
limit_clause: this.LIMIT_CLAUSE, | ||
returning_list: this.RETURNING_LIST, | ||
flags: this.FLAGS, | ||
@@ -204,2 +212,3 @@ }; | ||
if (json.limit_clause) instance.limit(json.limit_clause); | ||
if (json.returning_list) instance.returning(...json.returning_list); | ||
return instance; | ||
@@ -221,2 +230,3 @@ } | ||
if (this.LIMIT_CLAUSE) sql.push('LIMIT', this.LIMIT_CLAUSE); | ||
if (this.RETURNING_LIST.length) sql.push('RETURNING', this.RETURNING_LIST.join(', ')); | ||
return sql.join(' '); | ||
@@ -235,3 +245,3 @@ } | ||
const $body = this.mySubstitutePlaceholders(instance, body.trim()); | ||
const clausesMap = { from: { backtest: '^(?!.*\\s+DISTINCT\\s+$)', test: 'FROM' }, using: { backtest: '^(?!.*\\s+JOIN\\s+)', test: 'USING' }, join:JoinClause, where:'WHERE', orderBy:OrderByClause, limit:'LIMIT' }; | ||
const clausesMap = { from: { backtest: '^(?!.*\\s+DISTINCT\\s+$)', test: 'FROM' }, using: { backtest: '^(?!.*\\s+JOIN\\s+)', test: 'USING' }, join:JoinClause, where:'WHERE', orderBy:OrderByClause, limit:'LIMIT', returning:'RETURNING' }; | ||
const { tokens: [ maybeTablesSpec, ...tokens ], matches: clauses } = Lexer.lex($body, Object.values(clausesMap).map(x => typeof x === 'string' || x.test ? x : x.regex), { useRegex: 'i' }); | ||
@@ -265,2 +275,6 @@ // MAYBE_TABLES_SPEC (BEFORE A FROM CLAUSE) - MYSQL | ||
} | ||
// RETURNING | ||
else if (clauseKey === 'returning') { | ||
instance.returning(...Lexer.split(tokens.shift(), [',']).map(field => parseCallback(instance, field.trim(), [Field]))); | ||
} | ||
// JOIN|ORDER_BY | ||
@@ -267,0 +281,0 @@ else { |
import { _wrapped, _unwrap } from '@webqit/util/str/index.js'; | ||
import Lexer from '../Lexer.js'; | ||
import { _wrapped } from '@webqit/util/str/index.js'; | ||
import Node from '../abstracts/Node.js'; | ||
import Expr from '../select/abstracts/Expr.js'; | ||
import Identifier from '../select/Identifier.js'; | ||
import Node from '../abstracts/Node.js'; | ||
import Lexer from '../Lexer.js'; | ||
import ColumnsList from './ColumnsList.js'; | ||
import ValuesList from './ValuesList.js'; | ||
@@ -23,6 +25,10 @@ export default class AssignmentList extends Node { | ||
set(target_s, value_s) { | ||
if (Array.isArray(target_s)) target_s = target_s.map(t => t instanceof Node ? t : Identifier.fromJson(this, t)); | ||
else if (!(target_s instanceof Node)) target_s = Identifier.fromJson(this, target_s); | ||
if (Array.isArray(value_s)) value_s = value_s.map(v => v instanceof Node ? v : Expr.cast(this, v)); | ||
else if (!(value_s instanceof Node)) value_s = Expr.cast(this, value_s); | ||
if (Array.isArray(target_s)) { | ||
target_s = ColumnsList.fromJson(this, target_s); | ||
if (Array.isArray(value_s)) value_s = ValuesList.fromJson(this, value_s); | ||
else value_s = Expr.cast(this, value_s); | ||
} else if (!(target_s instanceof Node)) { | ||
target_s = Identifier.fromJson(this, target_s); | ||
value_s = Expr.cast(this, value_s); | ||
} | ||
this.ENTRIES.push([target_s, value_s]); | ||
@@ -36,11 +42,3 @@ return this; | ||
toJson() { | ||
return { | ||
entries: this.ENTRIES.map(([target_s, value_s]) => { | ||
if (Array.isArray(target_s)) target_s = target_s.map(t => t.toJson()); | ||
else target_s = target_s.toJson(); | ||
if (Array.isArray(value_s)) value_s = value_s.map(v => v.toJson()); | ||
else value_s = value_s.toJson(); | ||
return [target_s, value_s]; | ||
}), | ||
}; | ||
return { entries: this.ENTRIES.map(([target_s, value_s]) => [target_s.toJson(), value_s.toJson()]), }; | ||
} | ||
@@ -56,3 +54,3 @@ | ||
instance.set(target_s, value_s); | ||
}; | ||
} | ||
return instance; | ||
@@ -65,7 +63,3 @@ } | ||
stringify() { | ||
return `\n\t${ this.ENTRIES.map(([target_s, value_s]) => { | ||
if (Array.isArray(target_s)) target_s = `(${ target_s.join(', ') })`; | ||
if (Array.isArray(value_s)) value_s = `(${ value_s.join(', ') })`; | ||
return `${ target_s } = ${ value_s }`; | ||
}).join(',\n\t') }`; | ||
return `\n\t${ this.ENTRIES.map(([target_s, value_s]) => `${ target_s } = ${ value_s }`).join(',\n\t') }`; | ||
} | ||
@@ -82,7 +76,7 @@ | ||
if (_wrapped(target_s, '(', ')')) { | ||
const targets = Lexer.split(_unwrap(target_s, '(', ')'), [',']).map(expr => parseCallback(instance, expr.trim(), [Identifier])); | ||
const targets = parseCallback(instance, value_s.trim(), [ColumnsList]);; | ||
if (!_wrapped(value_s, '(', ')')) return; // Abort... for this isn't the kind of expression we handle here | ||
const values = /^\((\s+)?SELECT\s+/i.test(value_s) | ||
? parseCallback(instance, value_s) | ||
: Lexer.split(_unwrap(value_s, '(', ')'), [',']).map(expr => parseCallback(instance, expr.trim())); | ||
? parseCallback(instance, value_s.trim()) | ||
: parseCallback(instance, value_s.trim(), [ValuesList]); | ||
instance.set(targets, values); | ||
@@ -89,0 +83,0 @@ } else { |
@@ -7,5 +7,7 @@ | ||
import OnConflictClause from './OnConflictClause.js'; | ||
import Identifier from '../select/Identifier.js'; | ||
import Select from '../select/Select.js'; | ||
import Table from '../select/Table.js'; | ||
import ValuesList from './ValuesList.js'; | ||
import ColumnsList from './ColumnsList.js'; | ||
import Field from '../select/Field.js'; | ||
@@ -23,2 +25,3 @@ export default class Insert extends StatementNode { | ||
ON_CONFLICT_CLAUSE = null; | ||
RETURNING_LIST = []; | ||
@@ -48,3 +51,3 @@ /** | ||
*/ | ||
columns(...columns) { return this.build('COLUMNS_LIST', columns, Identifier); } | ||
columns(...columns) { return this.build('COLUMNS_LIST', columns, ColumnsList, 'list'); } | ||
@@ -58,3 +61,3 @@ /** | ||
*/ | ||
values(...values) { return this.VALUES_LIST.push(values); } | ||
values(...values) { return this.build('VALUES_LIST', values, ValuesList, 'list'); } | ||
@@ -96,2 +99,7 @@ /** | ||
onConflict(...onConflictSpecs) { return this.build('ON_CONFLICT_CLAUSE', onConflictSpecs, OnConflictClause); } | ||
/** | ||
* @return Void | ||
*/ | ||
returning(...fields) { return this.build('RETURNING_LIST', fields, Field); } | ||
@@ -104,7 +112,8 @@ /** | ||
table: this.TABLE.toJson(), | ||
columns_list: this.COLUMNS_LIST.map(c => c.toJson()), | ||
values_list: this.VALUES_LIST.map(v => v), | ||
columns_list: this.COLUMNS_LIST.toJson(), | ||
values_list: this.VALUES_LIST.toJson(), | ||
set_clause: this.SET_CLAUSE?.toJson(), | ||
select_clause: this.SELECT_CLAUSE?.toJson(), | ||
on_conflict_clause: this.ON_CONFLICT_CLAUSE?.toJson(), | ||
returning_list: this.RETURNING_LIST, | ||
flags: this.FLAGS, | ||
@@ -122,6 +131,7 @@ }; | ||
if (json.columns_list?.length) instance.columns(...json.columns_list); | ||
for (const values of json.values_list || []) instance.values(...values); | ||
if (json.values_list?.length) instance.values(...json.values_list); | ||
if (json.set_clause) instance.set(json.set_clause); | ||
if (json.select_clause) instance.select(json.select_clause); | ||
if (json.on_conflict_clause) instance.onConflict(json.on_conflict_clause); | ||
if (json.returning_list?.length) instance.returning(...json.returning_list); | ||
return instance; | ||
@@ -139,7 +149,8 @@ } | ||
else { | ||
if (this.COLUMNS_LIST.length) sql.push(`(${ this.COLUMNS_LIST.join(', ') })`); | ||
if (this.COLUMNS_LIST) sql.push(this.COLUMNS_LIST); | ||
if (this.SELECT_CLAUSE) sql.push(this.SELECT_CLAUSE); | ||
else sql.push('VALUES', `\n\t(${ this.VALUES_LIST.map(row => row.join(', ')).join(`),\n\t(`) })`); | ||
else sql.push('VALUES', this.VALUES_LIST); | ||
} | ||
if (this.ON_CONFLICT_CLAUSE) sql.push(this.ON_CONFLICT_CLAUSE); | ||
if (this.RETURNING_LIST.length) sql.push('RETURNING', this.RETURNING_LIST.join(', ')); | ||
return sql.join(' '); | ||
@@ -155,3 +166,3 @@ } | ||
const $body = this.mySubstitutePlaceholders(context, body.trim()); | ||
const { tokens: [ tableSpec, payloadSpec, onConflictSpec ], matches: [insertType, onConflictClause] } = Lexer.lex($body, ['(VALUES|VALUE|SET|SELECT)', 'ON\\s+(DUPLICATE\\s+KEY|CONFLICT)'], { useRegex:'i' }); | ||
const { tokens: [ tableSpec, payloadSpec, onConflictSpec, returnList ], matches: [insertType, onConflictClause] } = Lexer.lex($body, ['(VALUES|VALUE|SET|SELECT)', 'ON\\s+(DUPLICATE\\s+KEY|CONFLICT)', 'RETURNING'], { useRegex:'i' }); | ||
const instance = new this(context); | ||
@@ -168,4 +179,3 @@ if (withUac) instance.withFlag('WITH_UAC'); | ||
if (tableColumnSplit.length) { | ||
const columns = Lexer.split(_unwrap(tableColumnSplit.shift().trim(), '(', ')'), [',']).map(c => parseCallback(instance, c.trim(), [Identifier])); | ||
instance.columns(...columns); | ||
instance.columns(parseCallback(instance, tableColumnSplit.shift().trim(), [ColumnsList])); | ||
} | ||
@@ -178,4 +188,3 @@ if (/^SELECT$/i.test(insertType)) { | ||
for (const rowPayload of Lexer.split(payloadSpec, [','])) { | ||
const rowPayloadArray = Lexer.split(_unwrap(rowPayload.trim(), '(', ')'), [',']).map(valueExpr => parseCallback(instance, valueExpr.trim())); | ||
instance.values(...rowPayloadArray); | ||
instance.values(parseCallback(instance, rowPayload.trim(), [ValuesList])); | ||
} | ||
@@ -185,4 +194,5 @@ } | ||
if (onConflictClause) { instance.onConflict(parseCallback(instance, `${ onConflictClause } ${ onConflictSpec }`, [OnConflictClause])); } | ||
if (returnList) instance.returning(...Lexer.split(returnList, [',']).map(field => parseCallback(instance, field.trim(), [Field]))); | ||
return instance; | ||
} | ||
} |
@@ -31,8 +31,3 @@ | ||
*/ | ||
toJson() { | ||
return { | ||
...super.toJson(), | ||
where_clause: this.WHERE_CLAUSE?.toJson(), | ||
}; | ||
} | ||
toJson() { return { ...super.toJson(), where_clause: this.WHERE_CLAUSE?.toJson(), }; } | ||
@@ -39,0 +34,0 @@ /** |
@@ -17,3 +17,3 @@ | ||
import Num from '../Num.js'; | ||
import Prim from '../Prim.js'; | ||
import Literal from '../Literal.js'; | ||
import Placeholder from '../Placeholder.js'; | ||
@@ -35,4 +35,4 @@ | ||
const router = methodName => (...args) => { | ||
const $instance = Types.reduce((prev, Type) => prev || (Type.factoryMethods ? (methodName in Type.factoryMethods && Type.factoryMethods[methodName](context, ...args)) : (methodName in Type.prototype && new Type(context))), null); | ||
if (!$instance) throw new Error(`Unknow method: ${ methodName }()`); | ||
const $instance = Types.reduce((prev, Type) => prev || (Type.factoryMethods ? (typeof Type.factoryMethods[methodName] === 'function' && Type.factoryMethods[methodName](context, ...args)) : (typeof Type.prototype[methodName] === 'function' && new Type(context))), null); | ||
if (!$instance) throw new Error(`Unknown method: ${ methodName }()`); | ||
instance = $instance; | ||
@@ -79,5 +79,5 @@ if ($instance[methodName]) return $instance[methodName](...args); // Foward the call | ||
Json, | ||
Num, | ||
Str, | ||
Num, | ||
Prim, | ||
Literal, | ||
Placeholder, | ||
@@ -84,0 +84,0 @@ Identifier, |
@@ -18,3 +18,3 @@ | ||
*/ | ||
call(...args) { return (super.call(...args), this); } | ||
fn(...args) { return (super.fn(...args), this); } | ||
@@ -85,3 +85,3 @@ /** | ||
static factoryMethods = { call: (context, name, ...args) => this.names.flat().includes(name?.toUpperCase()) && new this(context) }; | ||
static factoryMethods = { fn: (context, name, ...args) => this.names.flat().includes(name?.toUpperCase()) && new this(context) }; | ||
@@ -88,0 +88,0 @@ static names = [ |
@@ -28,3 +28,3 @@ | ||
*/ | ||
call(name, ...args) { return (this.build('$EXPR', [name, ...args], Aggr.names.flat().includes(name.toUpperCase()) ? Aggr : Func, 'call'), this); } | ||
fn(name, ...args) { return (this.build('$EXPR', [name, ...args], Aggr.names.flat().includes(name.toUpperCase()) ? Aggr : Func, 'fn'), this); } | ||
@@ -31,0 +31,0 @@ /** |
@@ -17,3 +17,3 @@ | ||
*/ | ||
call(name, ...args) { | ||
fn(name, ...args) { | ||
this.NAME = name; | ||
@@ -40,3 +40,3 @@ return this.build('ARGS', args, Expr.Types); | ||
const instance = (new this(context)).withFlag(...(json.flags || [])); | ||
instance.call(json.name, ...json.args); | ||
instance.fn(json.name, ...json.args); | ||
return instance; | ||
@@ -57,5 +57,5 @@ } | ||
const instance = new this(context); | ||
instance.call(name, ...Lexer.split(args, [',']).map(arg => parseCallback(instance, arg.trim()))); | ||
instance.fn(name, ...Lexer.split(args, [',']).map(arg => parseCallback(instance, arg.trim()))); | ||
return instance; | ||
} | ||
} |
@@ -39,3 +39,3 @@ | ||
static fromJson(context, json) { | ||
if (typeof json === 'string' || Array.isArray(json)) json = { name: json }; | ||
if ((typeof json === 'string') || (Array.isArray(json) && json.every(s => typeof s === 'string'))) json = { name: json }; | ||
else if (typeof json?.name !== 'string' && !Array.isArray(json?.name)) return; | ||
@@ -42,0 +42,0 @@ const instance = (new this(context)).withFlag(...(json?.flags || [])); |
@@ -6,2 +6,3 @@ | ||
import { _wrapped } from '@webqit/util/str/index.js'; | ||
import { _isObject } from '@webqit/util/js/index.js'; | ||
import Lexer from '../../Lexer.js'; | ||
@@ -15,3 +16,3 @@ import Str from '../str/Str.js'; | ||
*/ | ||
TYPE = ''; | ||
TYPE; | ||
@@ -27,19 +28,19 @@ /** | ||
/** | ||
* Sets the value to an object | ||
* Sets the value to an array | ||
* | ||
* @param Object value | ||
*/ | ||
object(value) { | ||
this.VALUE = typeof value === 'object' && value ? JSON.stringify(value) : value; | ||
this.TYPE = 'OBJECT'; | ||
array(value) { | ||
this.VALUE = Array.isArray(value) ? JSON.stringify(value) : value; | ||
this.TYPE = 'ARRAY'; | ||
} | ||
/** | ||
* Sets the value to an array | ||
* Sets the value to an object | ||
* | ||
* @param Object value | ||
*/ | ||
array(value) { | ||
this.VALUE = Array.isArray(value) ? JSON.stringify(value) : value; | ||
this.TYPE = 'ARRAY'; | ||
object(value) { | ||
this.VALUE = _isObject(value) ? JSON.stringify(value) : value; | ||
this.TYPE = 'OBJECT'; | ||
} | ||
@@ -82,2 +83,4 @@ | ||
} | ||
static factoryMethods = { array: (context, value) => Array.isArray(value) && new this(context), object: (context, value) => _isObject(value) && new this(context) }; | ||
} |
@@ -20,2 +20,9 @@ | ||
/** | ||
* Sets the value | ||
* | ||
* @param String expr | ||
*/ | ||
value(value) { this.VALUE = value; } | ||
/** | ||
* @inheritdoc | ||
@@ -46,2 +53,4 @@ */ | ||
} | ||
static factoryMethods = { value: (context, value) => /^\d+$/.test(value) && new this(context) }; | ||
} |
@@ -163,3 +163,3 @@ | ||
const joinKeyAlias = `${ joinKey }:${ ( 0 | Math.random() * 9e6 ).toString( 36 ) }`; | ||
stmt.leftJoin( j => j.query( q => q.select( field => field.name( joinKey ).as( joinKeyAlias ) ), q => q.from([rhs.schema.basename,rhs.schema.name]) ) ) | ||
stmt.leftJoin( j => j.query( q => q.select( field => field.name( joinKey ).as( joinKeyAlias ) ), q => q.from([rhs.schema.basename,rhs.schema.name].filter(s => s)) ) ) | ||
.with({ IS_SMART_JOIN: true }).as(joinAlias) | ||
@@ -166,0 +166,0 @@ .on( on => on.equals([joinAlias,joinKeyAlias], [baseAlias,baseKey]) ); |
@@ -23,2 +23,13 @@ | ||
*/ | ||
$value(offset, value = undefined) { | ||
this.OFFSET = offset; | ||
if (arguments.length === 2) { | ||
this.statementNode.variables.push(value); | ||
if (this.OFFSET === 0) this.OFFSET = this.statementNode.variables.length; | ||
} | ||
} | ||
/** | ||
* @inheritdoc | ||
*/ | ||
toJson() { return { offset: this.OFFSET }; } | ||
@@ -25,0 +36,0 @@ |
@@ -28,3 +28,3 @@ | ||
*/ | ||
literal(expr) { this.VALUE = expr; } | ||
value(expr) { this.VALUE = expr; } | ||
@@ -36,3 +36,3 @@ /** | ||
const quote = this.QUOTE || this.quoteChars[0]; | ||
return `${ quote }${ this.VALUE.replace(new RegExp(quote, 'g'), quote.repeat(2)) }${ quote }`; | ||
return `${ quote }${ (this.VALUE + '').replace(new RegExp(quote, 'g'), quote.repeat(2)) }${ quote }`; | ||
} | ||
@@ -39,0 +39,0 @@ |
@@ -10,2 +10,3 @@ | ||
import Table from '../select/Table.js'; | ||
import Field from '../select/Field.js'; | ||
@@ -23,2 +24,3 @@ export default class Update extends StatementNode { | ||
LIMIT_CLAUSE = null; | ||
RETURNING_LIST = []; | ||
@@ -145,2 +147,7 @@ /** | ||
} | ||
/** | ||
* @return Void | ||
*/ | ||
returning(...fields) { return this.build('RETURNING_LIST', fields, Field); } | ||
@@ -158,2 +165,3 @@ /** | ||
limit_clause: this.LIMIT_CLAUSE, | ||
returning_list: this.RETURNING_LIST, | ||
flags: this.FLAGS, | ||
@@ -175,2 +183,3 @@ }; | ||
if (json.limit_clause) instance.limit(json.limit_clause); | ||
if (json.returning_list?.length) instance.returning(...json.returning_list); | ||
return instance; | ||
@@ -191,2 +200,3 @@ } | ||
if (this.LIMIT_CLAUSE) sql.push('LIMIT', this.LIMIT_CLAUSE); | ||
if (this.RETURNING_LIST.length) sql.push('RETURNING', this.RETURNING_LIST.join(', ')); | ||
return sql.join(' '); | ||
@@ -205,3 +215,3 @@ } | ||
const $body = this.mySubstitutePlaceholders(instance, body.trim()); | ||
const clausesMap = { join:JoinClause, set:'SET', where:'WHERE', orderBy:OrderByClause, limit:'LIMIT' }; | ||
const clausesMap = { join:JoinClause, set:'SET', where:'WHERE', orderBy:OrderByClause, limit:'LIMIT', returning:'RETURNING' }; | ||
const { tokens: [ tableSpec, ...tokens ], matches: clauses } = Lexer.lex($body, Object.values(clausesMap).map(x => typeof x === 'string' || x.test ? x : x.regex), { useRegex: 'i' }); | ||
@@ -231,2 +241,6 @@ // TABLE_LIST | ||
} | ||
// RETURNING | ||
else if (clauseKey === 'returning') { | ||
instance.returning(...Lexer.split(tokens.shift(), [',']).map(field => parseCallback(instance, field.trim(), [Field]))); | ||
} | ||
// JOIN|ORDER_BY | ||
@@ -233,0 +247,0 @@ else { |
@@ -24,3 +24,3 @@ | ||
//await lqlClient.alterDatabase('private', db => db.name('public'), { noCreateSavepoint: true }); | ||
console.log('DROP 4', await lqlClient.query('SELECT 2 + 3 as summm')); | ||
@@ -90,3 +90,3 @@ console.log('DROP 4', await lqlClient.query('DROP DATABASE if exists obj_information_schema CASCADE')); | ||
// Should see: 1,2,3 | ||
console.log('\n\n\n\n\n\nall savepoints-----', ...(await lqlClient.database('obj_information_schema').table('database_savepoints').getAll())); | ||
console.log('\n\n\n\n\n\nall savepoints-----', ...(await lqlClient.database('obj_information_schema').table('database_savepoints').select())); | ||
@@ -93,0 +93,0 @@ await lqlClient.query(`INSERT INTO roles (name, created_time) VALUES ('admin', now()), ('guest', now())`); |
@@ -99,5 +99,5 @@ | ||
).as('subValue', false), | ||
field => field.call('max', q => q.cast('col2', 'text', true)).as('MX'), | ||
field => field.fn('max', q => q.cast('col2', 'text', true)).as('MX'), | ||
field => field.expr( | ||
q => q.call('max', 'col2').over(), | ||
q => q.fn('max', 'col2').over(), | ||
).as('MX'), | ||
@@ -104,0 +104,0 @@ //field => field.path('author1', '~>', q => q.path('parent', '~>', 'fname')).as('path'), |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
125
1585663
12079