@linked-db/linked-ql
Advanced tools
Comparing version 0.30.6 to 0.30.7
@@ -14,3 +14,3 @@ { | ||
"icon": "https://webqit.io/icon.svg", | ||
"version": "0.30.6", | ||
"version": "0.30.7", | ||
"license": "MIT", | ||
@@ -17,0 +17,0 @@ "repository": { |
@@ -171,3 +171,3 @@ import { _from as _arrFrom, _difference, _intersect } from '@webqit/util/arr/index.js'; | ||
} | ||
const willNeedSchema = query.statementType === 'DDL' || query.hasPaths; | ||
const willNeedSchema = query.statementType === 'DDL' || query.hasPaths || query.isSugar/*upsert*/; | ||
return await this.withSchema(willNeedSchema, async (rootSchema) => { | ||
@@ -187,3 +187,3 @@ if (query.statementType === 'DDL') { | ||
// All non-DDL statements support bindings | ||
const queryBindings = query.normalizeBindings?.(true).map(b => b.value()) || []; | ||
const queryBindings = query.normalizeBindings?.(true).map((b) => b.value()) || []; | ||
const $queryBindings = queryBindings.map(value => Array.isArray(value) || typeof value === 'object' && value ? JSON.stringify(value) : value); | ||
@@ -190,0 +190,0 @@ // Visualize? Now! |
@@ -70,3 +70,3 @@ import { Lexer } from '../../lang/Lexer.js'; | ||
async execDDL(query, rootSchema, params = {}) { | ||
return super.execDDL(async (query) => { | ||
return await super.execDDL(async (query) => { | ||
return await this.driver.query(query.toString()); | ||
@@ -73,0 +73,0 @@ }, ...arguments); |
@@ -69,3 +69,4 @@ import { _fromCamel } from '@webqit/util/str/index.js'; | ||
if (instance) return instance; | ||
return Types.reduce((prev, Type) => prev || Type.fromJSON(this, arg), null); | ||
const contextNode = ['dependencies', 'dependents'].includes(slotName) ? this.baseClient : this; | ||
return Types.reduce((prev, Type) => prev || Type.fromJSON(contextNode, arg), null); | ||
}; | ||
@@ -72,0 +73,0 @@ const createFactoryMethodHandler = ({ returnPairs = false, autoThrow = false }) => { |
@@ -24,3 +24,3 @@ import { Lexer } from "./Lexer.js"; | ||
else this.#queryBindings.add(eventSource); | ||
return; // Don't bubble beyond this point. think dimensional queries | ||
// return; Don't bubble beyond this point. think dimensional queries | ||
} | ||
@@ -32,3 +32,5 @@ return super.$bubble(eventType, eventSource); | ||
const result = super.$capture(requestName, requestSource); | ||
if (requestName === 'ROOT_SCHEMA' && !result) return RootSchema.fromJSON(this, []); | ||
if (requestName === 'ROOT_SCHEMA' && !result) { | ||
return RootSchema.fromJSON(this, []); | ||
} | ||
return result; | ||
@@ -35,0 +37,0 @@ } |
@@ -15,3 +15,3 @@ export const AbstractStatementNode = Class => class extends Class { | ||
else this.#querySugars.add(eventSource); | ||
return; // Don't bubble beyond this point. think dimensional queries | ||
// return; // Don't bubble beyond this point. think dimensional queries | ||
} | ||
@@ -18,0 +18,0 @@ return super.$bubble(eventType, eventSource); |
@@ -103,16 +103,22 @@ import { Lexer } from '../../Lexer.js'; | ||
for (const cd of columnCDL) { | ||
let argumentJson, argumentJsonNew; | ||
if (['ADD', 'SET'].includes(cd.CLAUSE)) { | ||
argumentJson = cd.argument().jsonfy(options); | ||
argumentJsonNew = { ...argumentJson, ...(options.diff !== false ? { status: 'new' } : {}) }; | ||
} | ||
if (cd.CLAUSE === 'ADD') { | ||
if (cd.$KIND === 'IDENTITY') { | ||
if (json.identity) throw new Error(`IDENTITY constraint already exists.`); | ||
json = { ...json, identity: { ...cd.argument().jsonfy(options), ...(options.diff !== false ? { status: 'new' } : {}) } }; | ||
json = { ...json, identity: argumentJsonNew }; | ||
} | ||
} else if (cd.CLAUSE === 'SET') { | ||
if (cd.KIND === 'DATA_TYPE') { | ||
json = this.diffMergeJsons(json, { type: cd.argument().jsonfy(options) }); | ||
json = this.diffMergeJsons(json, { type: argumentJson }); | ||
} else if (cd.$KIND === 'IDENTITY') { | ||
json = { ...json, identity: this.diffMergeJsons(json.identity || {}, { always: cd.argument().always() }) }; | ||
if (!json.identity) throw new Error(`IDENTITY constraint not exists.`); | ||
json = { ...json, identity: this.diffMergeJsons(json.identity, { always: argumentJson.always }, options) }; | ||
} else if (cd.$KIND === 'EXPRESSION') { | ||
json = { ...json, expression: this.diffMergeJsons(json.expression || { stored: true }, { expr: cd.argument().expr().jsonfy(options) }) }; | ||
json = { ...json, expression: json.expression ? this.diffMergeJsons(json.expression, { expr: argumentJson.expr }, options) : argumentJsonNew }; | ||
} else if (cd.$KIND === 'DEFAULT') { | ||
json = { ...json, default: this.diffMergeJsons(json.default || {}, { expr: cd.argument().expr().jsonfy(options) }) }; | ||
json = { ...json, default: json.default ? this.diffMergeJsons(json.default, { expr: argumentJson.expr }, options) : argumentJsonNew }; | ||
} | ||
@@ -136,3 +142,3 @@ } else if (cd.CLAUSE === 'DROP') { | ||
columnCDL.add('DROP', 'CONSTRAINT', cons.TYPE); | ||
} else if (cons.status() === 'new') { | ||
} else if (cons.status() === 'new' && cons.TYPE === 'IDENTITY') { | ||
columnCDL.add('ADD', 'CONSTRAINT', cons.TYPE, (cd) => cd.argument(cons.jsonfy({ ...options, diff: false }))); | ||
@@ -139,0 +145,0 @@ } else { |
import { _unwrap, _wrapped } from '@webqit/util/str/index.js'; | ||
import { ColumnRef } from '../../../expr/refs/ColumnRef.js'; | ||
import { Exprs } from '../../../expr/grammar.js'; | ||
import { Literal } from '../../../expr/Literal.js'; | ||
@@ -19,11 +20,17 @@ export const AbstractExprMixin = Class => class extends Class { | ||
expr(expr) { | ||
if (!arguments.length) return this.#expr; | ||
expr(value) { | ||
if (!arguments.length || typeof value === 'boolean') { | ||
let expr = this.#expr; | ||
if (!expr && value === true && this.TYPE === 'DEFAULT') { | ||
expr = Literal.fromJSON(this, { value: null }); | ||
} | ||
return expr; | ||
} | ||
if (this.$diffTagHydrate()) { | ||
this.#$expr = this.$castInputs([expr], Exprs, this.#$expr, '$expr'); | ||
} else this.#expr = this.$castInputs([expr], Exprs, this.#expr, 'expr'); | ||
this.#$expr = this.$castInputs([value], Exprs, this.#$expr, '$expr'); | ||
} else this.#expr = this.$castInputs([value], Exprs, this.#expr, 'expr'); | ||
return this; | ||
} | ||
$expr() { return this.#$expr ?? this.#expr } | ||
$expr(...args) { return this.#$expr ?? this.expr(...args) } | ||
@@ -46,3 +53,3 @@ columns() { | ||
...super.generateDiff(nodeB, options), | ||
expr: this.$expr()?.jsonfy(options), | ||
expr: this.$expr(!!nodeB.$expr())?.jsonfy(options), | ||
}, { | ||
@@ -49,0 +56,0 @@ expr: nodeB.$expr()?.jsonfy(options), |
@@ -48,3 +48,3 @@ import { Lexer } from '../Lexer.js'; | ||
tables: this.#tables.map((t) => t.jsonfy(options)), | ||
...(this.#postgresFromList.length ? { postgresFromList: this.#postgresFromList.map(t => t.jsonfy(options)) } : {}), | ||
...(this.#postgresFromList.length ? { postgresFromList: this.#postgresFromList.map((t) => t.jsonfy(options)) } : {}), | ||
...jsonIn | ||
@@ -51,0 +51,0 @@ })); |
import { DatabaseSchema } from '../ddl/database/DatabaseSchema.js'; | ||
import { Assertion } from '../expr/logic/Assertion.js'; | ||
import { OrderByClause } from './clauses/OrderByClause.js'; | ||
@@ -9,2 +10,3 @@ import { LimitClause } from './clauses/LimitClause.js'; | ||
import { JsonAgg } from '../expr/json/JsonAgg.js'; | ||
import { Table } from './clauses/Table.js'; | ||
@@ -119,3 +121,3 @@ export const AbstractQueryStatement = Class => class extends Class { | ||
if (!(path instanceof PathRight)) throw new Error(`Can't desugar path: ${path}. Must be instance of PathRight.`); | ||
const [ keyLhs_ident, targetTableIdent, keyRhs_ident ] = path.plot(true/*fullyQualified*/); | ||
const [keyLhs_ident, targetTableIdent, keyRhs_ident] = path.plot(true/*fullyQualified*/); | ||
// Relationship details, to begin | ||
@@ -165,6 +167,82 @@ const relationID = `$relation::${[keyLhs_ident, targetTableIdent, keyRhs_ident].join(':')}${path.rhs() instanceof JsonAgg ? '/g' : ''}`; | ||
finalizeJSON(json, options) { | ||
if (!json.joinClauses) json.joinClauses = []; | ||
for (const [, join] of this.#generatedJoins) { | ||
json.joinClauses.push(join.jsonfy(options)); | ||
if (!this.#generatedJoins.size) { | ||
return super.finalizeJSON(json, options); | ||
} | ||
// Derived joins need special rewrite on postgre updates and deletes | ||
const rand = (0 | Math.random() * 9e6).toString(36); | ||
if (this.params.dialect === 'postgres' && this.NODE_NAME === 'UPDATE_STATEMENT') { | ||
const pgGeneratedFromEntries = new Map; | ||
const rederiveGeneratedJoinRefForPgUpdate = (columnRef) => { | ||
return json.tables.reduce((prev, tbl) => { | ||
if (prev) return prev; | ||
const tblRefOriginal = tbl.expr; | ||
const tblAliasOriginal = tbl.alias || tbl.expr.name; | ||
const colRefOriginal = columnRef.name; | ||
if (columnRef.prefix.name !== tblAliasOriginal) return; | ||
const tblAliasRewrite = `${tblAliasOriginal}:${rand}`; | ||
const colRefRewrite = `${colRefOriginal}:${rand}`; | ||
if (!pgGeneratedFromEntries.has(tblAliasOriginal)) { | ||
pgGeneratedFromEntries.set(tblAliasOriginal, { | ||
table: { expr: tblRefOriginal }, | ||
alias: tblAliasRewrite, | ||
fields: [] | ||
}); | ||
} | ||
pgGeneratedFromEntries.get(tblAliasOriginal).fields.push({ | ||
expr: { name: colRefOriginal }, alias: colRefRewrite | ||
}); | ||
return { | ||
name: colRefRewrite, | ||
prefix: { name: tblAliasRewrite } | ||
}; | ||
}, null) || columnRef; | ||
}; | ||
if (!json.joinClauses) json.joinClauses = []; | ||
for (const [, join] of this.#generatedJoins) { | ||
const joinJson = join.jsonfy(options); | ||
joinJson.onClause.entries = joinJson.onClause.entries.map((c) => ({ | ||
...c, rhs: rederiveGeneratedJoinRefForPgUpdate(c.rhs) | ||
})); | ||
json.joinClauses.push(joinJson); | ||
} | ||
if (pgGeneratedFromEntries.size) { | ||
if (!json.postgresFromList) json.postgresFromList = []; | ||
for (const [, derivation] of pgGeneratedFromEntries) { | ||
json.postgresFromList.push( | ||
Table.fromJSON(this, { | ||
expr: (q) => q.select(...derivation.fields).from(derivation.table), | ||
alias: derivation.alias | ||
}).jsonfy(options), | ||
); | ||
} | ||
} | ||
} else if (this.params.dialect === 'postgres' && this.NODE_NAME === 'DELETE_STATEMENT') { | ||
const tblRefOriginal = json.table.expr; | ||
const tblAliasOriginal = json.table.alias || json.table.expr.name; | ||
const tblAliasRewrite = `${tblAliasOriginal}:${rand}`; | ||
const whereClauseOriginal = json.whereClause; | ||
const pk = this.from().schema().primaryKey().columns()[0]; | ||
json.table = { | ||
expr: tblRefOriginal, | ||
alias: tblAliasRewrite | ||
}; | ||
json.whereClause = Assertion.fromJSON(this, { | ||
operator: 'IN', | ||
lhs: { | ||
name: pk, | ||
prefix: { name: tblAliasRewrite } | ||
}, | ||
rhs: (q) => { | ||
q.select({ name: pk, }) | ||
.from({ expr: tblRefOriginal, alias: tblAliasOriginal }) | ||
.where(whereClauseOriginal) | ||
.joins(...[...this.#generatedJoins].map(([, j]) => j.jsonfy(options))); | ||
} | ||
}).jsonfy(options); | ||
} else { | ||
if (!json.joinClauses) json.joinClauses = []; | ||
for (const [, join] of this.#generatedJoins) { | ||
json.joinClauses.push(join.jsonfy(options)); | ||
} | ||
} | ||
this.#generatedJoins.clear(); | ||
@@ -171,0 +249,0 @@ return super.finalizeJSON(json, options); |
@@ -35,2 +35,3 @@ import { _wrapped, _unwrap } from '@webqit/util/str/index.js'; | ||
static fromJSON(context, json, callback = null) { | ||
if (typeof json === 'string' && !json.trim()) json = { value: json }; // Empty string or whitespaces | ||
if (typeof json?.value !== 'string' || Object.keys(json).filter((k) => !['nodeName', 'value', 'quote'].includes(k)).length || (json.quote && !['"', "'"].includes(json.quote))) return; | ||
@@ -37,0 +38,0 @@ return super.fromJSON(context, json, (instance) => { |
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
4395657
12062