@balena/abstract-sql-compiler
Advanced tools
Comparing version 7.8.0-model-based-optimizations-6a74c34e2b10cf4f4adaeea62db26f1d1606413b to 7.8.0-non-null-optimizations-682e84dea2088fb0c336b05be83183e627c20d4f
@@ -9,4 +9,3 @@ # Change Log | ||
* Add support for model based optimizing IsDistinctFrom/IsNotDistinctFrom [Pagan Gazzard] | ||
* Add support for optimizing Exists/NotExists using model info [Pagan Gazzard] | ||
* Optimize Is[Not]DistinctFrom and [Not]Exists for non-null values [Pagan Gazzard] | ||
@@ -13,0 +12,0 @@ ## 7.7.0 - 2021-01-01 |
@@ -233,12 +233,12 @@ export declare const enum Engines { | ||
export declare const isResourceNode: (n: AbstractSqlType) => n is ResourceNode; | ||
export declare function compileRule(abstractSQL: UpsertQueryNode, engine: Engines, noBinds: true, abstractSqlModel?: AbstractSqlModel): [string, string]; | ||
export declare function compileRule(abstractSQL: AbstractSqlQuery, engine: Engines, noBinds: true, abstractSqlModel?: AbstractSqlModel): string; | ||
export declare function compileRule(abstractSQL: UpsertQueryNode, engine: Engines, noBinds?: false, abstractSqlModel?: AbstractSqlModel): [SqlResult, SqlResult]; | ||
export declare function compileRule(abstractSQL: SelectQueryNode | UnionQueryNode | InsertQueryNode | UpdateQueryNode | DeleteQueryNode, engine: Engines, noBinds?: false, abstractSqlModel?: AbstractSqlModel): SqlResult; | ||
export declare function compileRule(abstractSQL: AbstractSqlQuery, engine: Engines, noBinds?: false, abstractSqlModel?: AbstractSqlModel): SqlResult | [SqlResult, SqlResult]; | ||
export declare function compileRule(abstractSQL: AbstractSqlQuery, engine: Engines, noBinds?: boolean, abstractSqlModel?: AbstractSqlModel): SqlResult | [SqlResult, SqlResult] | string; | ||
export declare function compileRule(abstractSQL: UpsertQueryNode, engine: Engines, noBinds: true): [string, string]; | ||
export declare function compileRule(abstractSQL: AbstractSqlQuery, engine: Engines, noBinds: true): string; | ||
export declare function compileRule(abstractSQL: UpsertQueryNode, engine: Engines, noBinds?: false): [SqlResult, SqlResult]; | ||
export declare function compileRule(abstractSQL: SelectQueryNode | UnionQueryNode | InsertQueryNode | UpdateQueryNode | DeleteQueryNode, engine: Engines, noBinds?: false): SqlResult; | ||
export declare function compileRule(abstractSQL: AbstractSqlQuery, engine: Engines, noBinds?: false): SqlResult | [SqlResult, SqlResult]; | ||
export declare function compileRule(abstractSQL: AbstractSqlQuery, engine: Engines, noBinds?: boolean): SqlResult | [SqlResult, SqlResult] | string; | ||
export declare const postgres: { | ||
optimizeSchema: (abstractSqlModel: AbstractSqlModel) => AbstractSqlModel; | ||
compileSchema: _.Function1<AbstractSqlModel, SqlModel>; | ||
compileRule: (abstractSQL: AbstractSqlQuery, abstractSqlModel?: AbstractSqlModel | undefined) => SqlResult | [SqlResult, SqlResult]; | ||
compileRule: (abstractSQL: AbstractSqlQuery) => SqlResult | [SqlResult, SqlResult]; | ||
dataTypeValidate: (value: any, field: Pick<AbstractSqlField, 'dataType' | 'required'>) => Promise<any>; | ||
@@ -251,3 +251,3 @@ getReferencedFields: (ruleBody: AbstractSqlQuery) => ReferencedFields; | ||
compileSchema: _.Function1<AbstractSqlModel, SqlModel>; | ||
compileRule: (abstractSQL: AbstractSqlQuery, abstractSqlModel?: AbstractSqlModel | undefined) => SqlResult | [SqlResult, SqlResult]; | ||
compileRule: (abstractSQL: AbstractSqlQuery) => SqlResult | [SqlResult, SqlResult]; | ||
dataTypeValidate: (value: any, field: Pick<AbstractSqlField, 'dataType' | 'required'>) => Promise<any>; | ||
@@ -260,3 +260,3 @@ getReferencedFields: (ruleBody: AbstractSqlQuery) => ReferencedFields; | ||
compileSchema: _.Function1<AbstractSqlModel, SqlModel>; | ||
compileRule: (abstractSQL: AbstractSqlQuery, abstractSqlModel?: AbstractSqlModel | undefined) => SqlResult | [SqlResult, SqlResult]; | ||
compileRule: (abstractSQL: AbstractSqlQuery) => SqlResult | [SqlResult, SqlResult]; | ||
dataTypeValidate: (value: any, field: Pick<AbstractSqlField, 'dataType' | 'required'>) => Promise<any>; | ||
@@ -263,0 +263,0 @@ getReferencedFields: (ruleBody: AbstractSqlQuery) => ReferencedFields; |
@@ -190,4 +190,4 @@ "use strict"; | ||
}; | ||
function compileRule(abstractSQL, engine, noBinds = false, abstractSqlModel) { | ||
abstractSQL = AbstractSQLOptimiser_1.AbstractSQLOptimiser(abstractSQL, noBinds, abstractSqlModel); | ||
function compileRule(abstractSQL, engine, noBinds = false) { | ||
abstractSQL = AbstractSQLOptimiser_1.AbstractSQLOptimiser(abstractSQL, noBinds); | ||
return AbstractSQLRules2SQL_1.AbstractSQLRules2SQL(abstractSQL, engine, noBinds); | ||
@@ -263,3 +263,3 @@ } | ||
CREATE ${orReplaceStr}VIEW "${table.name}" AS ( | ||
${compileRule(definitionAbstractSql, engine, true, abstractSqlModel).replace(/^/gm, ' ')} | ||
${compileRule(definitionAbstractSql, engine, true).replace(/^/gm, ' ')} | ||
);`, | ||
@@ -329,3 +329,3 @@ ], | ||
const constraintName = check.name ? `CONSTRAINT "${check.name}" ` : ''; | ||
const sql = compileRule(check.abstractSql, engine, true, abstractSqlModel); | ||
const sql = compileRule(check.abstractSql, engine, true); | ||
createSqlElements.push(`\ | ||
@@ -427,3 +427,3 @@ ${comment}${constraintName}CHECK (${sql})`); | ||
} | ||
const { query: ruleSQL, bindings: ruleBindings } = compileRule(ruleBody, engine, undefined, abstractSqlModel); | ||
const { query: ruleSQL, bindings: ruleBindings } = compileRule(ruleBody, engine); | ||
let referencedFields; | ||
@@ -456,3 +456,3 @@ try { | ||
compileSchema: _.partial(compileSchema, _, engine, ifNotExists), | ||
compileRule: (abstractSQL, abstractSqlModel) => compileRule(abstractSQL, engine, false, abstractSqlModel), | ||
compileRule: (abstractSQL) => compileRule(abstractSQL, engine, false), | ||
dataTypeValidate, | ||
@@ -459,0 +459,0 @@ getReferencedFields, |
@@ -1,2 +0,2 @@ | ||
import { AbstractSqlModel, AbstractSqlQuery } from './AbstractSQLCompiler'; | ||
export declare const AbstractSQLOptimiser: (abstractSQL: AbstractSqlQuery, $noBinds?: boolean, $abstractSqlModel?: AbstractSqlModel | undefined) => AbstractSqlQuery; | ||
import { AbstractSqlQuery } from './AbstractSQLCompiler'; | ||
export declare const AbstractSQLOptimiser: (abstractSQL: AbstractSqlQuery, $noBinds?: boolean) => AbstractSqlQuery; |
@@ -6,3 +6,3 @@ "use strict"; | ||
const AbstractSQLRules2SQL = require("./AbstractSQLRules2SQL"); | ||
const { isAbstractSqlQuery, getAbstractSqlQuery, checkArgs, checkMinArgs, } = AbstractSQLRules2SQL; | ||
const { isAbstractSqlQuery, getAbstractSqlQuery, checkArgs, checkMinArgs, isNotNullable, } = AbstractSQLRules2SQL; | ||
const escapeForLike = (str) => [ | ||
@@ -20,5 +20,3 @@ 'Replace', | ||
let helped = false; | ||
let aliases = {}; | ||
let noBinds = false; | ||
let abstractSqlModel; | ||
const Helper = (fn) => { | ||
@@ -50,18 +48,2 @@ return (...args) => { | ||
}; | ||
const isNotNullable = (node) => { | ||
var _a; | ||
switch (node[0]) { | ||
case 'ReferencedField': | ||
if (abstractSqlModel != null) { | ||
const [, aliasName, fieldName] = node; | ||
const tableName = (_a = aliases[aliasName]) !== null && _a !== void 0 ? _a : aliasName; | ||
const table = abstractSqlModel.tables[tableName]; | ||
const field = table === null || table === void 0 ? void 0 : table.fields.find((f) => f.fieldName === fieldName); | ||
if ((field === null || field === void 0 ? void 0 : field.required) === true) { | ||
return true; | ||
} | ||
} | ||
} | ||
return false; | ||
}; | ||
const rewriteMatch = (name, matchers, rewriteFn) => (args) => { | ||
@@ -397,6 +379,2 @@ checkArgs(name, args, matchers.length); | ||
checkArgs('From', args, 1); | ||
const maybeAlias = args[0]; | ||
if (maybeAlias[0] === 'Alias' && maybeAlias[1][0] === 'Table') { | ||
aliases[maybeAlias[2]] = maybeAlias[1][1]; | ||
} | ||
return ['From', MaybeAlias(args[0], FromMatch)]; | ||
@@ -850,5 +828,2 @@ }, | ||
Exists: tryMatches(Helper((args) => { | ||
if (abstractSqlModel == null) { | ||
return false; | ||
} | ||
checkArgs('Exists', args, 1); | ||
@@ -880,5 +855,2 @@ const arg = getAbstractSqlQuery(args, 0); | ||
NotExists: tryMatches(Helper((args) => { | ||
if (abstractSqlModel == null) { | ||
return false; | ||
} | ||
checkArgs('Exists', args, 1); | ||
@@ -1147,8 +1119,6 @@ const arg = getAbstractSqlQuery(args, 0); | ||
}; | ||
const AbstractSQLOptimiser = (abstractSQL, $noBinds = false, $abstractSqlModel) => { | ||
const AbstractSQLOptimiser = (abstractSQL, $noBinds = false) => { | ||
noBinds = $noBinds; | ||
abstractSqlModel = $abstractSqlModel; | ||
do { | ||
helped = false; | ||
aliases = {}; | ||
const [type, ...rest] = abstractSQL; | ||
@@ -1155,0 +1125,0 @@ switch (type) { |
@@ -23,2 +23,3 @@ import { AbstractSqlQuery, AbstractSqlType, Engines, InsertQueryNode, SelectQueryNode, UnionQueryNode, UpdateQueryNode, DeleteQueryNode, UpsertQueryNode } from './AbstractSQLCompiler'; | ||
export declare const isFieldValue: (type: string | AbstractSqlQuery) => type is "Field" | "ReferencedField"; | ||
export declare const isNotNullable: (node: AbstractSqlType) => boolean; | ||
export declare const isAbstractSqlQuery: (x: AbstractSqlType) => x is AbstractSqlQuery; | ||
@@ -25,0 +26,0 @@ export declare const getAbstractSqlQuery: (args: AbstractSqlType[], index: number) => AbstractSqlQuery; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.AbstractSQLRules2SQL = exports.checkMinArgs = exports.checkArgs = exports.getAbstractSqlQuery = exports.isAbstractSqlQuery = exports.isFieldValue = exports.isDurationValue = exports.isJSONValue = exports.isDateValue = exports.isBooleanValue = exports.isNumericValue = exports.isTextValue = exports.comparisons = void 0; | ||
exports.AbstractSQLRules2SQL = exports.checkMinArgs = exports.checkArgs = exports.getAbstractSqlQuery = exports.isAbstractSqlQuery = exports.isNotNullable = exports.isFieldValue = exports.isDurationValue = exports.isJSONValue = exports.isDateValue = exports.isBooleanValue = exports.isNumericValue = exports.isTextValue = exports.comparisons = void 0; | ||
const _ = require("lodash"); | ||
@@ -158,5 +158,26 @@ const sbvrTypes = require("@balena/sbvr-types"); | ||
}; | ||
const isBindValue = (type) => { | ||
return (type === 'Bind' || type === 'Text' || type === 'Value' || type === 'Date'); | ||
const isNotNullable = (node) => { | ||
switch (node[0]) { | ||
case 'EmbeddedText': | ||
case 'Boolean': | ||
case 'Bind': | ||
case 'Value': | ||
case 'Text': | ||
case 'Date': | ||
case 'Number': | ||
case 'Real': | ||
case 'Integer': | ||
case 'IsDistinctFrom': | ||
case 'IsNotDistinctFrom': | ||
case 'Exists': | ||
case 'NotExists': | ||
return true; | ||
case 'Coalesce': | ||
return node.slice(1).some((n) => exports.isNotNullable(n)); | ||
case 'Not': | ||
return exports.isNotNullable(node[1]); | ||
} | ||
return false; | ||
}; | ||
exports.isNotNullable = isNotNullable; | ||
const isNotDistinctFrom = (args, indent) => { | ||
@@ -168,15 +189,17 @@ const a = exports.getAbstractSqlQuery(args, 0); | ||
if (engine === "postgres") { | ||
const aIsBind = isBindValue(a[0]); | ||
const bIsBind = isBindValue(b[0]); | ||
if (aIsBind && bIsBind) { | ||
const aIsNotNullable = exports.isNotNullable(a); | ||
const bIsNotNullable = exports.isNotNullable(b); | ||
if (aIsNotNullable && bIsNotNullable) { | ||
return `${aSql} = ${bSql}`; | ||
} | ||
const isNotNullChecks = []; | ||
if (!aIsBind) { | ||
if (!aIsNotNullable) { | ||
isNotNullChecks.push(`(${aSql}) IS NOT NULL`); | ||
} | ||
if (!bIsBind) { | ||
if (!bIsNotNullable) { | ||
isNotNullChecks.push(`(${bSql}) IS NOT NULL`); | ||
} | ||
const orBothNull = !aIsBind && !bIsBind ? ` OR (${aSql}) IS NULL AND (${bSql}) IS NULL` : ''; | ||
const orBothNull = !aIsNotNullable && !bIsNotNullable | ||
? ` OR (${aSql}) IS NULL AND (${bSql}) IS NULL` | ||
: ''; | ||
return `${isNotNullChecks.join(' AND ')} AND (${aSql}) = (${bSql})${orBothNull}`; | ||
@@ -183,0 +206,0 @@ } |
@@ -48,3 +48,3 @@ "use strict"; | ||
} | ||
ruleBody = AbstractSQLOptimiser_1.AbstractSQLOptimiser(ruleBody, true, abstractSqlModel); | ||
ruleBody = AbstractSQLOptimiser_1.AbstractSQLOptimiser(ruleBody, true); | ||
const count = countFroms(ruleBody); | ||
@@ -51,0 +51,0 @@ if (count === 1 && |
{ | ||
"name": "@balena/abstract-sql-compiler", | ||
"version": "7.8.0-model-based-optimizations-6a74c34e2b10cf4f4adaeea62db26f1d1606413b", | ||
"version": "7.8.0-non-null-optimizations-682e84dea2088fb0c336b05be83183e627c20d4f", | ||
"description": "A translator for abstract sql into sql.", | ||
@@ -5,0 +5,0 @@ "main": "out/AbstractSQLCompiler.js", |
@@ -574,3 +574,2 @@ export const enum Engines { | ||
noBinds: true, | ||
abstractSqlModel?: AbstractSqlModel, | ||
): [string, string]; | ||
@@ -581,3 +580,2 @@ export function compileRule( | ||
noBinds: true, | ||
abstractSqlModel?: AbstractSqlModel, | ||
): string; | ||
@@ -588,3 +586,2 @@ export function compileRule( | ||
noBinds?: false, | ||
abstractSqlModel?: AbstractSqlModel, | ||
): [SqlResult, SqlResult]; | ||
@@ -600,3 +597,2 @@ export function compileRule( | ||
noBinds?: false, | ||
abstractSqlModel?: AbstractSqlModel, | ||
): SqlResult; | ||
@@ -607,3 +603,2 @@ export function compileRule( | ||
noBinds?: false, | ||
abstractSqlModel?: AbstractSqlModel, | ||
): SqlResult | [SqlResult, SqlResult]; | ||
@@ -614,3 +609,2 @@ export function compileRule( | ||
noBinds?: boolean, | ||
abstractSqlModel?: AbstractSqlModel, | ||
): SqlResult | [SqlResult, SqlResult] | string; | ||
@@ -621,5 +615,4 @@ export function compileRule( | ||
noBinds = false, | ||
abstractSqlModel?: AbstractSqlModel, | ||
): SqlResult | [SqlResult, SqlResult] | string | [string, string] { | ||
abstractSQL = AbstractSQLOptimiser(abstractSQL, noBinds, abstractSqlModel); | ||
abstractSQL = AbstractSQLOptimiser(abstractSQL, noBinds); | ||
return AbstractSQLRules2SQL(abstractSQL, engine, noBinds); | ||
@@ -717,8 +710,6 @@ } | ||
CREATE ${orReplaceStr}VIEW "${table.name}" AS ( | ||
${compileRule( | ||
definitionAbstractSql as AbstractSqlQuery, | ||
engine, | ||
true, | ||
abstractSqlModel, | ||
).replace(/^/gm, ' ')} | ||
${compileRule(definitionAbstractSql as AbstractSqlQuery, engine, true).replace( | ||
/^/gm, | ||
' ', | ||
)} | ||
);`, | ||
@@ -805,3 +796,2 @@ ], | ||
true, | ||
abstractSqlModel, | ||
); | ||
@@ -932,4 +922,2 @@ createSqlElements.push(`\ | ||
engine, | ||
undefined, | ||
abstractSqlModel, | ||
) as SqlResult; | ||
@@ -966,6 +954,4 @@ let referencedFields: ReferencedFields | undefined; | ||
compileSchema: _.partial(compileSchema, _, engine, ifNotExists), | ||
compileRule: ( | ||
abstractSQL: AbstractSqlQuery, | ||
abstractSqlModel?: AbstractSqlModel, | ||
) => compileRule(abstractSQL, engine, false, abstractSqlModel), | ||
compileRule: (abstractSQL: AbstractSqlQuery) => | ||
compileRule(abstractSQL, engine, false), | ||
dataTypeValidate, | ||
@@ -972,0 +958,0 @@ getReferencedFields, |
@@ -5,10 +5,6 @@ import * as _ from 'lodash'; | ||
import { | ||
AbstractSqlModel, | ||
AbstractSqlQuery, | ||
AbstractSqlType, | ||
AliasNode, | ||
DurationNode, | ||
ReferencedFieldNode, | ||
ReplaceNode, | ||
TableNode, | ||
} from './AbstractSQLCompiler'; | ||
@@ -22,2 +18,3 @@ import * as AbstractSQLRules2SQL from './AbstractSQLRules2SQL'; | ||
checkMinArgs, | ||
isNotNullable, | ||
} = AbstractSQLRules2SQL; | ||
@@ -44,5 +41,3 @@ | ||
let helped = false; | ||
let aliases: { [alias: string]: string } = {}; | ||
let noBinds = false; | ||
let abstractSqlModel: AbstractSqlModel | undefined; | ||
const Helper = <F extends (...args: any[]) => any>(fn: F) => { | ||
@@ -76,18 +71,2 @@ return (...args: Parameters<F>): ReturnType<F> => { | ||
const isNotNullable = (node: AbstractSqlQuery): boolean => { | ||
switch (node[0]) { | ||
case 'ReferencedField': | ||
if (abstractSqlModel != null) { | ||
const [, aliasName, fieldName] = node as ReferencedFieldNode; | ||
const tableName = aliases[aliasName] ?? aliasName; | ||
const table = abstractSqlModel.tables[tableName]; | ||
const field = table?.fields.find((f) => f.fieldName === fieldName); | ||
if (field?.required === true) { | ||
return true; | ||
} | ||
} | ||
} | ||
return false; | ||
}; | ||
const rewriteMatch = ( | ||
@@ -483,6 +462,2 @@ name: string, | ||
checkArgs('From', args, 1); | ||
const maybeAlias = args[0] as AliasNode<TableNode>; | ||
if (maybeAlias[0] === 'Alias' && maybeAlias[1][0] === 'Table') { | ||
aliases[maybeAlias[2]] = maybeAlias[1][1]; | ||
} | ||
return ['From', MaybeAlias(args[0] as AbstractSqlQuery, FromMatch)]; | ||
@@ -988,5 +963,2 @@ }, | ||
Helper<OptimisationMatchFn>((args) => { | ||
if (abstractSqlModel == null) { | ||
return false; | ||
} | ||
checkArgs('Exists', args, 1); | ||
@@ -1022,5 +994,2 @@ const arg = getAbstractSqlQuery(args, 0); | ||
Helper<OptimisationMatchFn>((args) => { | ||
if (abstractSqlModel == null) { | ||
return false; | ||
} | ||
checkArgs('Exists', args, 1); | ||
@@ -1350,9 +1319,6 @@ const arg = getAbstractSqlQuery(args, 0); | ||
$noBinds = false, | ||
$abstractSqlModel?: AbstractSqlModel, | ||
): AbstractSqlQuery => { | ||
noBinds = $noBinds; | ||
abstractSqlModel = $abstractSqlModel; | ||
do { | ||
helped = false; | ||
aliases = {}; | ||
const [type, ...rest] = abstractSQL; | ||
@@ -1359,0 +1325,0 @@ switch (type) { |
@@ -16,2 +16,3 @@ import * as _ from 'lodash'; | ||
UpsertQueryNode, | ||
CoalesceNode, | ||
} from './AbstractSQLCompiler'; | ||
@@ -209,9 +210,28 @@ | ||
const isBindValue = ( | ||
type: string | AbstractSqlQuery, | ||
): type is 'Bind' | 'Text' | 'Value' | 'Date' => { | ||
return ( | ||
type === 'Bind' || type === 'Text' || type === 'Value' || type === 'Date' | ||
); | ||
export const isNotNullable = (node: AbstractSqlType): boolean => { | ||
switch (node[0]) { | ||
case 'EmbeddedText': | ||
case 'Boolean': | ||
// We don't support null binds so we can avoid checking them for null-ness | ||
// and avoid issues with postgres type inference | ||
case 'Bind': | ||
case 'Value': | ||
case 'Text': | ||
case 'Date': | ||
case 'Number': | ||
case 'Real': | ||
case 'Integer': | ||
case 'IsDistinctFrom': | ||
case 'IsNotDistinctFrom': | ||
case 'Exists': | ||
case 'NotExists': | ||
return true; | ||
case 'Coalesce': | ||
return (node as CoalesceNode).slice(1).some((n) => isNotNullable(n)); | ||
case 'Not': | ||
return isNotNullable(node[1]); | ||
} | ||
return false; | ||
}; | ||
const isNotDistinctFrom: MatchFn = (args, indent) => { | ||
@@ -225,18 +245,18 @@ const a = getAbstractSqlQuery(args, 0); | ||
if (engine === Engines.postgres) { | ||
// We don't support null binds so we can avoid checking them for null-ness | ||
// and avoid issues with postgres type inference | ||
const aIsBind = isBindValue(a[0]); | ||
const bIsBind = isBindValue(b[0]); | ||
if (aIsBind && bIsBind) { | ||
const aIsNotNullable = isNotNullable(a); | ||
const bIsNotNullable = isNotNullable(b); | ||
if (aIsNotNullable && bIsNotNullable) { | ||
return `${aSql} = ${bSql}`; | ||
} | ||
const isNotNullChecks: string[] = []; | ||
if (!aIsBind) { | ||
if (!aIsNotNullable) { | ||
isNotNullChecks.push(`(${aSql}) IS NOT NULL`); | ||
} | ||
if (!bIsBind) { | ||
if (!bIsNotNullable) { | ||
isNotNullChecks.push(`(${bSql}) IS NOT NULL`); | ||
} | ||
const orBothNull = | ||
!aIsBind && !bIsBind ? ` OR (${aSql}) IS NULL AND (${bSql}) IS NULL` : ''; | ||
!aIsNotNullable && !bIsNotNullable | ||
? ` OR (${aSql}) IS NULL AND (${bSql}) IS NULL` | ||
: ''; | ||
return `${isNotNullChecks.join( | ||
@@ -243,0 +263,0 @@ ' AND ', |
@@ -63,3 +63,3 @@ export const enum Engines { | ||
// Optimize the rule body, this also normalizes it making the check constraint check easier | ||
ruleBody = AbstractSQLOptimiser(ruleBody, true, abstractSqlModel); | ||
ruleBody = AbstractSQLOptimiser(ruleBody, true); | ||
@@ -66,0 +66,0 @@ const count = countFroms(ruleBody); |
@@ -18,3 +18,3 @@ import { AbstractSqlQuery } from '../../src/AbstractSQLCompiler'; | ||
'SelectQuery', | ||
['Select', [['IsDistinctFrom', ['Number', 1], ['Number', 2]]]], | ||
['Select', [['IsDistinctFrom', ['Field', 'a'], ['Field', 'b']]]], | ||
], | ||
@@ -25,3 +25,3 @@ (result, sqlEquals) => { | ||
result.query, | ||
'SELECT NOT((1) IS NOT NULL AND (2) IS NOT NULL AND (1) = (2) OR (1) IS NULL AND (2) IS NULL)', | ||
'SELECT NOT(("a") IS NOT NULL AND ("b") IS NOT NULL AND ("a") = ("b") OR ("a") IS NULL AND ("b") IS NULL)', | ||
); | ||
@@ -34,8 +34,22 @@ }); | ||
'SelectQuery', | ||
['Select', [['IsDistinctFrom', ['Number', 1], ['Text', '2']]]], | ||
['Select', [['IsDistinctFrom', ['Number', 1], ['Number', 2]]]], | ||
], | ||
(result, sqlEquals) => { | ||
it('should optimize down to a !=', () => { | ||
sqlEquals(result.query, 'SELECT 1 != 2'); | ||
}); | ||
}, | ||
); | ||
test( | ||
[ | ||
'SelectQuery', | ||
['Select', [['IsDistinctFrom', ['Field', 'a'], ['Text', '2']]]], | ||
], | ||
[['Text', '2']], | ||
(result, sqlEquals) => { | ||
it('should produce a valid is distinct from statement', () => { | ||
sqlEquals(result.query, 'SELECT NOT((1) IS NOT NULL AND (1) = ($1))'); | ||
sqlEquals( | ||
result.query, | ||
'SELECT NOT(("a") IS NOT NULL AND ("a") = ($1))', | ||
); | ||
}); | ||
@@ -46,6 +60,6 @@ }, | ||
test( | ||
['SelectQuery', ['Select', [['IsDistinctFrom', ['Number', 1], ['Null']]]]], | ||
['SelectQuery', ['Select', [['IsDistinctFrom', ['Field', 'a'], ['Null']]]]], | ||
(result, sqlEquals) => { | ||
it('should produce an is not null statement', () => { | ||
sqlEquals(result.query, 'SELECT 1 IS NOT NULL'); | ||
sqlEquals(result.query, 'SELECT "a" IS NOT NULL'); | ||
}); | ||
@@ -60,3 +74,3 @@ }, | ||
'SelectQuery', | ||
['Select', [['IsNotDistinctFrom', ['Number', 1], ['Number', 2]]]], | ||
['Select', [['IsNotDistinctFrom', ['Field', 'a'], ['Field', 'b']]]], | ||
], | ||
@@ -67,3 +81,3 @@ (result, sqlEquals) => { | ||
result.query, | ||
'SELECT (1) IS NOT NULL AND (2) IS NOT NULL AND (1) = (2) OR (1) IS NULL AND (2) IS NULL', | ||
'SELECT ("a") IS NOT NULL AND ("b") IS NOT NULL AND ("a") = ("b") OR ("a") IS NULL AND ("b") IS NULL', | ||
); | ||
@@ -76,8 +90,19 @@ }); | ||
'SelectQuery', | ||
['Select', [['IsNotDistinctFrom', ['Number', 1], ['Text', '2']]]], | ||
['Select', [['IsNotDistinctFrom', ['Number', 1], ['Number', 2]]]], | ||
], | ||
(result, sqlEquals) => { | ||
it('should optimize down to an =', () => { | ||
sqlEquals(result.query, 'SELECT 1 = 2'); | ||
}); | ||
}, | ||
); | ||
test( | ||
[ | ||
'SelectQuery', | ||
['Select', [['IsNotDistinctFrom', ['Field', 'a'], ['Text', '2']]]], | ||
], | ||
[['Text', '2']], | ||
(result, sqlEquals) => { | ||
it('should produce a valid is not distinct from statement', () => { | ||
sqlEquals(result.query, 'SELECT (1) IS NOT NULL AND (1) = ($1)'); | ||
sqlEquals(result.query, 'SELECT ("a") IS NOT NULL AND ("a") = ($1)'); | ||
}); | ||
@@ -90,7 +115,7 @@ }, | ||
'SelectQuery', | ||
['Select', [['IsNotDistinctFrom', ['Number', 1], ['Null']]]], | ||
['Select', [['IsNotDistinctFrom', ['Field', 'a'], ['Null']]]], | ||
], | ||
(result, sqlEquals) => { | ||
it('should produce a valid is not distinct from statement', () => { | ||
sqlEquals(result.query, 'SELECT 1 IS NULL'); | ||
sqlEquals(result.query, 'SELECT "a" IS NULL'); | ||
}); | ||
@@ -97,0 +122,0 @@ }, |
@@ -79,3 +79,3 @@ import * as AbstractSQLCompiler from '../..'; | ||
CONSTRAINT "test$hkEwz3pzAqalNu6crijhhdWJ0ffUvqRGK8rMkQbViPg=" CHECK (0 < "id" | ||
AND \"id\" IS NOT NULL) | ||
AND "id" IS NOT NULL) | ||
);`, | ||
@@ -85,75 +85,2 @@ ]); | ||
it('should optimize null checks for a required field', () => { | ||
expect( | ||
generateSchema({ | ||
synonyms: {}, | ||
relationships: {}, | ||
tables: { | ||
test: { | ||
name: 'test', | ||
resourceName: 'test', | ||
idField: 'id', | ||
fields: [ | ||
{ | ||
fieldName: 'id', | ||
dataType: 'Integer', | ||
required: true, | ||
index: 'PRIMARY KEY', | ||
}, | ||
], | ||
indexes: [], | ||
primitive: false, | ||
}, | ||
}, | ||
rules: [ | ||
[ | ||
'Rule', | ||
[ | ||
'Body', | ||
[ | ||
'Not', | ||
[ | ||
'Exists', | ||
[ | ||
'SelectQuery', | ||
['Select', []], | ||
['From', ['test', 'test.0']], | ||
[ | ||
'Where', | ||
[ | ||
'Not', | ||
[ | ||
'And', | ||
[ | ||
'LessThan', | ||
['Integer', 0], | ||
['ReferencedField', 'test.0', 'id'], | ||
], | ||
['Exists', ['ReferencedField', 'test.0', 'id']], | ||
], | ||
], | ||
], | ||
], | ||
], | ||
], | ||
] as AbstractSQLCompiler.AbstractSqlQuery, | ||
[ | ||
'StructuredEnglish', | ||
'It is necessary that each test has an id that is greater than 0.', | ||
], | ||
], | ||
], | ||
}), | ||
) | ||
.to.have.property('createSchema') | ||
.that.deep.equals([ | ||
`\ | ||
CREATE TABLE IF NOT EXISTS "test" ( | ||
"id" INTEGER NOT NULL PRIMARY KEY | ||
, -- It is necessary that each test has an id that is greater than 0. | ||
CONSTRAINT "test$TIITyGYLwuTGGJjwAk8awbiE/hnw6y8rue+hQ8Pp7as=" CHECK (0 < "id") | ||
);`, | ||
]); | ||
}); | ||
it('should correctly shorten a converted check rule with a long name', () => { | ||
@@ -173,3 +100,2 @@ expect( | ||
dataType: 'Integer', | ||
required: true, | ||
index: 'PRIMARY KEY', | ||
@@ -243,7 +169,8 @@ }, | ||
CREATE TABLE IF NOT EXISTS "test_table_with_very_very_long_name" ( | ||
"id" INTEGER NOT NULL PRIMARY KEY | ||
"id" INTEGER NULL PRIMARY KEY | ||
, -- It is necessary that each test_table_with_very_very_long_name has an id that is greater than 0. | ||
CONSTRAINT "test_table_with_very_very_long$/rDs8gDAB2Zoc7woBPozVMLKpx9jNTNa" CHECK (0 < "id") | ||
CONSTRAINT "test_table_with_very_very_long$9z+XEkP4EI1mhDQ8SiLulo2NLmenGY1C" CHECK (0 < "id" | ||
AND "id" IS NOT NULL) | ||
);`, | ||
]); | ||
}); |
@@ -157,2 +157,3 @@ /* | ||
fn(); | ||
parseOperand = null; | ||
running = false; | ||
@@ -324,7 +325,9 @@ } else { | ||
const operandTest = (lhs, op, rhs) => { | ||
const operandTest = (lhs, op, rhs, override) => { | ||
run(function () { | ||
let from; | ||
let where; | ||
const { odata, sql, bindings } = createExpression(lhs, op, rhs); | ||
let { odata, sql, bindings } = createExpression(lhs, op, rhs); | ||
bindings = override?.bindings ?? bindings; | ||
sql = override?.sql ?? sql; | ||
if (_.includes(odata, '/')) { | ||
@@ -409,3 +412,3 @@ from = `\ | ||
const operations = ['eq', 'ne', 'gt', 'ge', 'lt', 'le']; | ||
const operands = [ | ||
const nonNullableOperands = [ | ||
2, | ||
@@ -416,15 +419,17 @@ -2, | ||
"'bar'", | ||
new Date(), | ||
true, | ||
false, | ||
]; | ||
const nullableOperands = [ | ||
'name', | ||
'trained__pilot/name', | ||
new Date(), | ||
{ negative: true, day: 3, hour: 4, minute: 5, second: 6.7 }, | ||
true, | ||
false, | ||
// null is quoted as otherwise we hit issues with coffeescript defaulting values | ||
'null', | ||
// 'null', | ||
]; | ||
operations.forEach((op) => { | ||
describe(op, () => { | ||
operands.forEach((lhs) => { | ||
operands.forEach((rhs) => { | ||
nonNullableOperands.forEach((lhs) => { | ||
[...nonNullableOperands, ...nullableOperands].forEach((rhs) => { | ||
run(() => { | ||
@@ -434,3 +439,29 @@ operandTest(lhs, op, rhs); | ||
}); | ||
run(() => { | ||
switch (op) { | ||
case 'eq': | ||
case 'ne': | ||
// eq/ne of non-nullable to null are automatically optimized away | ||
operandTest(lhs, op, 'null', { | ||
bindings: [], | ||
sql: op === 'eq' ? 'false' : 'true', | ||
}); | ||
break; | ||
default: | ||
operandTest(lhs, op, 'null'); | ||
break; | ||
} | ||
}); | ||
}); | ||
nullableOperands.forEach((lhs) => { | ||
[...nonNullableOperands, ...nullableOperands].forEach((rhs) => { | ||
run(() => { | ||
operandTest(lhs, op, rhs); | ||
}); | ||
}); | ||
run(() => { | ||
operandTest(lhs, op, 'null'); | ||
}); | ||
}); | ||
}); | ||
@@ -440,3 +471,3 @@ }); | ||
(function () { | ||
run(function () { | ||
const left = createExpression('age', 'gt', 2); | ||
@@ -449,3 +480,3 @@ const right = createExpression('age', 'lt', 10); | ||
operandTest('not', left); | ||
})(); | ||
}); | ||
@@ -452,0 +483,0 @@ (function () { |
@@ -29,4 +29,2 @@ const typeVocab = require('fs').readFileSync( | ||
Concept Type: Short Text (Type) | ||
Term: nickname | ||
Concept Type: Short Text (Type) | ||
Term: years of experience | ||
@@ -42,4 +40,2 @@ Concept Type: Integer (Type) | ||
Necessity: each pilot has exactly one name | ||
Fact Type: pilot has nickname | ||
Necessity: each pilot has at most one nickname | ||
Fact Type: pilot has years of experience | ||
@@ -50,4 +46,2 @@ Necessity: each pilot has exactly one years of experience | ||
Definition: "planeA" or "planeB" or "planeC" | ||
Fact Type: plane has nickname | ||
Necessity: each plane has at most one nickname | ||
Fact Type: pilot can fly plane | ||
@@ -93,3 +87,2 @@ Synonymous Form: plane can be flown by pilot | ||
, "name" VARCHAR(255) NOT NULL CHECK ("name" IN ('planeA', 'planeB', 'planeC')) | ||
, "nickname" VARCHAR(255) NULL | ||
);`, | ||
@@ -119,3 +112,2 @@ modifiedAtTrigger('plane'), | ||
, "name" VARCHAR(255) NOT NULL | ||
, "nickname" VARCHAR(255) NULL | ||
, "years of experience" INTEGER NOT NULL | ||
@@ -231,8 +223,2 @@ , "is experienced" INTEGER DEFAULT 0 NOT NULL | ||
`\ | ||
SELECT 1 AS "result";`, | ||
); | ||
test.rule( | ||
'It is necessary that each plane that at least 3 pilots can fly, has a nickname', | ||
`\ | ||
SELECT NOT EXISTS ( | ||
@@ -246,3 +232,3 @@ SELECT 1 | ||
) >= 3 | ||
AND "plane.0"."nickname" IS NULL | ||
AND "plane.0"."name" IS NULL | ||
) AS "result";`, | ||
@@ -252,3 +238,3 @@ ); | ||
test.rule( | ||
'It is necessary that each plane that at least 3 pilots that are experienced can fly, has a nickname', | ||
'It is necessary that each plane that at least 3 pilots that are experienced can fly, has a name', | ||
`\ | ||
@@ -266,3 +252,3 @@ SELECT NOT EXISTS ( | ||
) >= 3 | ||
AND "plane.0"."nickname" IS NULL | ||
AND "plane.0"."name" IS NULL | ||
) AS "result";`, | ||
@@ -284,10 +270,10 @@ ); | ||
) >= 3 | ||
AND "plane.0"."nickname" IS NULL | ||
AND "plane.0"."name" IS NULL | ||
) AS "result";`; | ||
test.rule( | ||
'It is necessary that each plane that at least 3 pilots that are not experienced can fly, has a nickname', | ||
'It is necessary that each plane that at least 3 pilots that are not experienced can fly, has a name', | ||
sql, | ||
); | ||
test.rule( | ||
"It is necessary that each plane that at least 3 pilots that aren't experienced can fly, has a nickname", | ||
"It is necessary that each plane that at least 3 pilots that aren't experienced can fly, has a name", | ||
sql, | ||
@@ -298,3 +284,3 @@ ); | ||
test.rule( | ||
'It is necessary that each plane that at least 3 pilot that is experienced, can fly, has a nickname.', | ||
'It is necessary that each plane that at least 3 pilot that is experienced, can fly, has a name.', | ||
`\ | ||
@@ -312,3 +298,3 @@ SELECT NOT EXISTS ( | ||
) >= 3 | ||
AND "plane.0"."nickname" IS NULL | ||
AND "plane.0"."name" IS NULL | ||
) AS "result";`, | ||
@@ -318,3 +304,3 @@ ); | ||
test.rule( | ||
'It is necessary that each plane that at least 3 pilots that a name is of can fly, has a nickname', | ||
'It is necessary that each plane that at least 3 pilots that a name is of can fly, has a name', | ||
`\ | ||
@@ -328,6 +314,7 @@ SELECT NOT EXISTS ( | ||
"pilot-can fly-plane" AS "pilot.1-can fly-plane.0" | ||
WHERE "pilot.1-can fly-plane.0"."pilot" = "pilot.1"."id" | ||
WHERE "pilot.1"."name" IS NOT NULL | ||
AND "pilot.1-can fly-plane.0"."pilot" = "pilot.1"."id" | ||
AND "pilot.1-can fly-plane.0"."can fly-plane" = "plane.0"."id" | ||
) >= 3 | ||
AND "plane.0"."nickname" IS NULL | ||
AND "plane.0"."name" IS NULL | ||
) AS "result";`, | ||
@@ -344,2 +331,3 @@ ); | ||
0 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL | ||
) | ||
@@ -377,3 +365,4 @@ ) AS "result";`, | ||
) >= 2 | ||
OR 5 < "pilot.0"."years of experience") | ||
OR 5 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL) | ||
) | ||
@@ -397,2 +386,3 @@ ) AS "result";`, | ||
5 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL | ||
) | ||
@@ -421,2 +411,3 @@ ) AS "result";`, | ||
5 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL | ||
) | ||
@@ -492,3 +483,3 @@ ) AS "result";`, | ||
test.rule( | ||
'It is necessary that each plane that at least 3 pilots can fly or exactly one pilot can fly, has a nickname', | ||
'It is necessary that each plane that at least 3 pilots can fly or exactly one pilot can fly, has a name', | ||
`\ | ||
@@ -508,3 +499,3 @@ SELECT NOT EXISTS ( | ||
) = 1) | ||
AND "plane.0"."nickname" IS NULL | ||
AND "plane.0"."name" IS NULL | ||
) AS "result";`, | ||
@@ -528,2 +519,3 @@ ); | ||
AND 5 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL | ||
) | ||
@@ -547,2 +539,3 @@ ) AS "result";`, | ||
5 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL | ||
) | ||
@@ -618,3 +611,3 @@ ) AS "result";`, | ||
test.rule( | ||
'It is necessary that each plane that at least 3 pilots can fly and exactly one pilot can fly, has a nickname', | ||
'It is necessary that each plane that at least 3 pilots can fly and exactly one pilot can fly, has a name', | ||
`\ | ||
@@ -634,3 +627,3 @@ SELECT NOT EXISTS ( | ||
) = 1 | ||
AND "plane.0"."nickname" IS NULL | ||
AND "plane.0"."name" IS NULL | ||
) AS "result";`, | ||
@@ -659,2 +652,3 @@ ); | ||
5 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL | ||
) | ||
@@ -683,2 +677,3 @@ ) AS "result";`, | ||
5 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL | ||
) | ||
@@ -710,2 +705,3 @@ ) AS "result";`, | ||
5 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL | ||
) | ||
@@ -734,2 +730,3 @@ ) AS "result";`, | ||
5 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL | ||
) | ||
@@ -759,5 +756,7 @@ ) AS "result";`, | ||
OR 10 < LENGTH("pilot.0"."name") | ||
AND LENGTH("pilot.0"."name") IS NOT NULL) | ||
AND LENGTH("pilot.0"."name") IS NOT NULL | ||
AND "pilot.0"."name" IS NOT NULL) | ||
AND NOT ( | ||
5 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL | ||
) | ||
@@ -785,5 +784,7 @@ ) AS "result";`, | ||
AND 10 < LENGTH("pilot.0"."name") | ||
AND LENGTH("pilot.0"."name") IS NOT NULL) | ||
AND LENGTH("pilot.0"."name") IS NOT NULL | ||
AND "pilot.0"."name" IS NOT NULL) | ||
AND NOT ( | ||
5 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL | ||
) | ||
@@ -811,5 +812,7 @@ ) AS "result";`, | ||
AND 10 < LENGTH("pilot.0"."name") | ||
AND LENGTH("pilot.0"."name") IS NOT NULL) | ||
AND LENGTH("pilot.0"."name") IS NOT NULL | ||
AND "pilot.0"."name" IS NOT NULL) | ||
AND NOT ( | ||
5 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL | ||
) | ||
@@ -837,5 +840,7 @@ ) AS "result";`, | ||
AND 10 < LENGTH("pilot.0"."name") | ||
AND LENGTH("pilot.0"."name") IS NOT NULL) | ||
AND LENGTH("pilot.0"."name") IS NOT NULL | ||
AND "pilot.0"."name" IS NOT NULL) | ||
AND NOT ( | ||
5 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL | ||
) | ||
@@ -869,2 +874,3 @@ ) AS "result";`, | ||
5 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL | ||
) | ||
@@ -900,2 +906,3 @@ ) AS "result";`, | ||
5 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL | ||
) | ||
@@ -920,3 +927,4 @@ ) AS "result";`, | ||
OR 10 < LENGTH("pilot.0"."name") | ||
AND LENGTH("pilot.0"."name") IS NOT NULL) | ||
AND LENGTH("pilot.0"."name") IS NOT NULL | ||
AND "pilot.0"."name" IS NOT NULL) | ||
AND ( | ||
@@ -929,2 +937,3 @@ SELECT COUNT(*) | ||
5 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL | ||
) | ||
@@ -948,2 +957,3 @@ ) AS "result";`, | ||
AND LENGTH("pilot.0"."name") IS NOT NULL | ||
AND "pilot.0"."name" IS NOT NULL | ||
OR ( | ||
@@ -956,2 +966,3 @@ SELECT COUNT(*) | ||
5 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL | ||
) | ||
@@ -987,2 +998,3 @@ ) AS "result";`, | ||
5 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL | ||
) | ||
@@ -1018,2 +1030,3 @@ ) AS "result";`, | ||
5 < "pilot.0"."years of experience" | ||
AND "pilot.0"."years of experience" IS NOT NULL | ||
) | ||
@@ -1020,0 +1033,0 @@ ) AS "result";`, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
580241
52
12803