@balena/abstract-sql-compiler
Advanced tools
Comparing version 7.10.0 to 7.10.1-separate-referenced-fields-7ca7b5cfad4e8833293426235667cc59245f427a
@@ -7,2 +7,6 @@ # Change Log | ||
## 7.10.1 - 2021-01-29 | ||
* Separate code relating to referenced fields out of the main file [Pagan Gazzard] | ||
## 7.10.0 - 2021-01-29 | ||
@@ -9,0 +13,0 @@ |
@@ -9,2 +9,4 @@ export declare const enum Engines { | ||
import * as _ from 'lodash'; | ||
import { ReferencedFields, RuleReferencedFields, ModifiedFields } from './referenced-fields'; | ||
export type { ReferencedFields, RuleReferencedFields, ModifiedFields }; | ||
export declare type NullNode = ['Null']; | ||
@@ -173,5 +175,2 @@ export declare type DateNode = ['Date', Date]; | ||
} | ||
export interface ReferencedFields { | ||
[alias: string]: string[]; | ||
} | ||
export interface SqlRule { | ||
@@ -223,7 +222,2 @@ sql: string; | ||
} | ||
export interface ModifiedFields { | ||
table: string; | ||
action: keyof RuleReferencedFields[string]; | ||
fields?: string[]; | ||
} | ||
export interface EngineInstance { | ||
@@ -240,9 +234,2 @@ compileSchema: (abstractSqlModel: AbstractSqlModel) => SqlModel; | ||
export declare const isResourceNode: (n: AbstractSqlType) => n is ResourceNode; | ||
export interface RuleReferencedFields { | ||
[alias: string]: { | ||
create: string[]; | ||
update: string[]; | ||
delete: string[]; | ||
}; | ||
} | ||
export declare function compileRule(abstractSQL: UpsertQueryNode, engine: Engines, noBinds: true): [string, string]; | ||
@@ -249,0 +236,0 @@ export declare function compileRule(abstractSQL: AbstractSqlQuery, engine: Engines, noBinds: true): string; |
@@ -15,2 +15,3 @@ "use strict"; | ||
const AbstractSQLSchemaOptimiser_1 = require("./AbstractSQLSchemaOptimiser"); | ||
const referenced_fields_1 = require("./referenced-fields"); | ||
const validateTypes = _.mapValues(sbvrTypes, ({ validate }) => validate); | ||
@@ -85,170 +86,2 @@ const dataTypeValidate = async (value, field) => { | ||
}; | ||
const getScope = (rulePart, scope) => { | ||
scope = { ...scope }; | ||
const fromNodes = rulePart.filter(exports.isFromNode); | ||
fromNodes.forEach((node) => { | ||
const nested = node[1]; | ||
if (nested[0] === 'Alias') { | ||
const [, from, alias] = nested; | ||
if (typeof alias !== 'string') { | ||
throw new Error('Cannot handle non-string aliases'); | ||
} | ||
switch (from[0]) { | ||
case 'Table': | ||
scope[alias] = from[1]; | ||
break; | ||
case 'SelectQuery': | ||
scope[alias] = ''; | ||
break; | ||
default: | ||
throw new Error(`Cannot handle aliased ${from[0]} nodes`); | ||
} | ||
} | ||
else if (nested[0] === 'Table') { | ||
scope[nested[1]] = nested[1]; | ||
} | ||
else { | ||
throw Error(`Unsupported FromNode for scoping: ${nested[0]}`); | ||
} | ||
}); | ||
return scope; | ||
}; | ||
const $getReferencedFields = (referencedFields, rulePart, scope = {}) => { | ||
if (!Array.isArray(rulePart)) { | ||
return; | ||
} | ||
switch (rulePart[0]) { | ||
case 'SelectQuery': | ||
scope = getScope(rulePart, scope); | ||
rulePart.forEach((node) => { | ||
$getReferencedFields(referencedFields, node, scope); | ||
}); | ||
break; | ||
case 'ReferencedField': | ||
let tableName = rulePart[1]; | ||
const fieldName = rulePart[2]; | ||
if (typeof tableName !== 'string' || typeof fieldName !== 'string') { | ||
throw new Error(`Invalid ReferencedField: ${rulePart}`); | ||
} | ||
tableName = scope[tableName]; | ||
if (tableName !== '') { | ||
if (referencedFields[tableName] == null) { | ||
referencedFields[tableName] = []; | ||
} | ||
referencedFields[tableName].push(fieldName); | ||
} | ||
return; | ||
case 'Field': | ||
throw new Error('Cannot find queried fields for unreferenced fields'); | ||
default: | ||
rulePart.forEach((node) => { | ||
$getReferencedFields(referencedFields, node, scope); | ||
}); | ||
} | ||
}; | ||
const getReferencedFields = (ruleBody) => { | ||
ruleBody = AbstractSQLOptimiser_1.AbstractSQLOptimiser(ruleBody); | ||
const referencedFields = {}; | ||
$getReferencedFields(referencedFields, ruleBody); | ||
return _.mapValues(referencedFields, _.uniq); | ||
}; | ||
const dealiasTableNode = (n) => { | ||
if (exports.isTableNode(n)) { | ||
return n; | ||
} | ||
if (n[0] === 'Alias' && exports.isTableNode(n[1])) { | ||
return n[1]; | ||
} | ||
}; | ||
const getRuleReferencedFields = (ruleBody) => { | ||
ruleBody = AbstractSQLOptimiser_1.AbstractSQLOptimiser(ruleBody); | ||
let referencedFields = {}; | ||
const deletable = new Set(); | ||
if (ruleBody[0] === 'NotExists') { | ||
const s = ruleBody[1]; | ||
if (s[0] === 'SelectQuery') { | ||
s.forEach((m) => { | ||
if (!exports.isFromNode(m)) { | ||
return; | ||
} | ||
const table = dealiasTableNode(m[1]); | ||
if (table == null) { | ||
return; | ||
} | ||
deletable.add(table[1]); | ||
}); | ||
} | ||
} | ||
$getReferencedFields(referencedFields, ruleBody); | ||
referencedFields = _.mapValues(referencedFields, _.uniq); | ||
const refFields = {}; | ||
for (const f of Object.keys(referencedFields)) { | ||
refFields[f] = { | ||
create: referencedFields[f], | ||
update: referencedFields[f], | ||
delete: referencedFields[f], | ||
}; | ||
if (deletable.has(f)) { | ||
const countFroms = (n) => { | ||
let count = 0; | ||
n.forEach((p) => { | ||
var _a; | ||
if (Array.isArray(p)) { | ||
if (exports.isFromNode(p) && ((_a = dealiasTableNode(p[1])) === null || _a === void 0 ? void 0 : _a[1]) === f) { | ||
count++; | ||
} | ||
else { | ||
count += countFroms(p); | ||
} | ||
} | ||
}); | ||
return count; | ||
}; | ||
if (countFroms(ruleBody) === 1) { | ||
refFields[f].delete = []; | ||
} | ||
} | ||
} | ||
return refFields; | ||
}; | ||
const checkQuery = (query) => { | ||
const queryType = query[0]; | ||
if (!['InsertQuery', 'UpdateQuery', 'DeleteQuery'].includes(queryType)) { | ||
return; | ||
} | ||
const froms = query.filter(exports.isFromNode); | ||
if (froms.length !== 1) { | ||
return; | ||
} | ||
const table = froms[0][1]; | ||
let tableName; | ||
if (table[0] === 'Table') { | ||
tableName = table[1]; | ||
} | ||
else if (typeof table === 'string') { | ||
tableName = table; | ||
} | ||
else { | ||
return; | ||
} | ||
if (queryType === 'InsertQuery') { | ||
return { table: tableName, action: 'create' }; | ||
} | ||
if (queryType === 'DeleteQuery') { | ||
return { table: tableName, action: 'delete' }; | ||
} | ||
const fields = _(query) | ||
.filter((v) => v != null && v[0] === 'Fields') | ||
.flatMap((v) => v[1]) | ||
.value(); | ||
return { table: tableName, action: 'update', fields }; | ||
}; | ||
const getModifiedFields = (abstractSqlQuery) => { | ||
if (Array.isArray(abstractSqlQuery[0])) { | ||
return abstractSqlQuery.map(checkQuery); | ||
} | ||
else { | ||
return checkQuery(abstractSqlQuery); | ||
} | ||
}; | ||
function compileRule(abstractSQL, engine, noBinds = false) { | ||
@@ -493,3 +326,3 @@ abstractSQL = AbstractSQLOptimiser_1.AbstractSQLOptimiser(abstractSQL, noBinds); | ||
try { | ||
referencedFields = getReferencedFields(ruleBody); | ||
referencedFields = referenced_fields_1.getReferencedFields(ruleBody); | ||
} | ||
@@ -501,3 +334,3 @@ catch (e) { | ||
try { | ||
ruleReferencedFields = getRuleReferencedFields(ruleBody); | ||
ruleReferencedFields = referenced_fields_1.getRuleReferencedFields(ruleBody); | ||
} | ||
@@ -530,5 +363,5 @@ catch (e) { | ||
dataTypeValidate, | ||
getReferencedFields, | ||
getRuleReferencedFields, | ||
getModifiedFields, | ||
getReferencedFields: referenced_fields_1.getReferencedFields, | ||
getRuleReferencedFields: referenced_fields_1.getRuleReferencedFields, | ||
getModifiedFields: referenced_fields_1.getModifiedFields, | ||
}; | ||
@@ -535,0 +368,0 @@ }; |
{ | ||
"name": "@balena/abstract-sql-compiler", | ||
"version": "7.10.0", | ||
"version": "7.10.1-separate-referenced-fields-7ca7b5cfad4e8833293426235667cc59245f427a", | ||
"description": "A translator for abstract sql into sql.", | ||
@@ -5,0 +5,0 @@ "main": "out/AbstractSQLCompiler.js", |
@@ -17,3 +17,13 @@ export const enum Engines { | ||
import { optimizeSchema } from './AbstractSQLSchemaOptimiser'; | ||
import { | ||
getReferencedFields, | ||
getRuleReferencedFields, | ||
getModifiedFields, | ||
ReferencedFields, | ||
RuleReferencedFields, | ||
ModifiedFields, | ||
} from './referenced-fields'; | ||
export type { ReferencedFields, RuleReferencedFields, ModifiedFields }; | ||
export type NullNode = ['Null']; | ||
@@ -277,5 +287,2 @@ export type DateNode = ['Date', Date]; | ||
} | ||
export interface ReferencedFields { | ||
[alias: string]: string[]; | ||
} | ||
export interface SqlRule { | ||
@@ -341,8 +348,2 @@ sql: string; | ||
export interface ModifiedFields { | ||
table: string; | ||
action: keyof RuleReferencedFields[string]; | ||
fields?: string[]; | ||
} | ||
export interface EngineInstance { | ||
@@ -456,202 +457,2 @@ compileSchema: (abstractSqlModel: AbstractSqlModel) => SqlModel; | ||
type Scope = _.Dictionary<string>; | ||
const getScope = (rulePart: AbstractSqlQuery, scope: Scope): Scope => { | ||
scope = { ...scope }; | ||
const fromNodes = rulePart.filter(isFromNode); | ||
fromNodes.forEach((node) => { | ||
const nested = node[1]; | ||
if (nested[0] === 'Alias') { | ||
const [, from, alias] = nested; | ||
if (typeof alias !== 'string') { | ||
throw new Error('Cannot handle non-string aliases'); | ||
} | ||
switch (from[0]) { | ||
case 'Table': | ||
scope[alias] = from[1]; | ||
break; | ||
case 'SelectQuery': | ||
// Ignore SelectQuery in the From as we'll handle any fields it selects | ||
// when we recurse in. With true scope handling however we could prune | ||
// fields that don't affect the end result and avoid false positives | ||
scope[alias] = ''; | ||
break; | ||
default: | ||
throw new Error(`Cannot handle aliased ${from[0]} nodes`); | ||
} | ||
} else if (nested[0] === 'Table') { | ||
scope[nested[1]] = nested[1]; | ||
} else { | ||
throw Error(`Unsupported FromNode for scoping: ${nested[0]}`); | ||
} | ||
}); | ||
return scope; | ||
}; | ||
const $getReferencedFields = ( | ||
referencedFields: ReferencedFields, | ||
rulePart: AbstractSqlQuery, | ||
scope: Scope = {}, | ||
) => { | ||
if (!Array.isArray(rulePart)) { | ||
return; | ||
} | ||
switch (rulePart[0]) { | ||
case 'SelectQuery': | ||
// Update the current scope before trying to resolve field references | ||
scope = getScope(rulePart, scope); | ||
rulePart.forEach((node: AbstractSqlQuery) => { | ||
$getReferencedFields(referencedFields, node, scope); | ||
}); | ||
break; | ||
case 'ReferencedField': | ||
let tableName = rulePart[1]; | ||
const fieldName = rulePart[2]; | ||
if (typeof tableName !== 'string' || typeof fieldName !== 'string') { | ||
throw new Error(`Invalid ReferencedField: ${rulePart}`); | ||
} | ||
tableName = scope[tableName]; | ||
// The scoped tableName is empty in the case of an aliased from query | ||
// and those fields will be covered when we recurse into them | ||
if (tableName !== '') { | ||
if (referencedFields[tableName] == null) { | ||
referencedFields[tableName] = []; | ||
} | ||
referencedFields[tableName].push(fieldName); | ||
} | ||
return; | ||
case 'Field': | ||
throw new Error('Cannot find queried fields for unreferenced fields'); | ||
default: | ||
rulePart.forEach((node: AbstractSqlQuery) => { | ||
$getReferencedFields(referencedFields, node, scope); | ||
}); | ||
} | ||
}; | ||
const getReferencedFields: EngineInstance['getReferencedFields'] = ( | ||
ruleBody, | ||
) => { | ||
ruleBody = AbstractSQLOptimiser(ruleBody); | ||
const referencedFields: ReferencedFields = {}; | ||
$getReferencedFields(referencedFields, ruleBody); | ||
return _.mapValues(referencedFields, _.uniq); | ||
}; | ||
const dealiasTableNode = (n: AbstractSqlQuery): TableNode | undefined => { | ||
if (isTableNode(n)) { | ||
return n; | ||
} | ||
if (n[0] === 'Alias' && isTableNode(n[1])) { | ||
return n[1]; | ||
} | ||
}; | ||
export interface RuleReferencedFields { | ||
[alias: string]: { | ||
create: string[]; | ||
update: string[]; | ||
delete: string[]; | ||
}; | ||
} | ||
const getRuleReferencedFields: EngineInstance['getRuleReferencedFields'] = ( | ||
ruleBody, | ||
) => { | ||
ruleBody = AbstractSQLOptimiser(ruleBody); | ||
let referencedFields: ReferencedFields = {}; | ||
const deletable = new Set<string>(); | ||
if (ruleBody[0] === 'NotExists') { | ||
const s = ruleBody[1] as SelectQueryNode; | ||
if (s[0] === 'SelectQuery') { | ||
s.forEach((m) => { | ||
if (!isFromNode(m)) { | ||
return; | ||
} | ||
const table = dealiasTableNode(m[1]); | ||
if (table == null) { | ||
// keep this from node for later checking if we didn't optimize out | ||
return; | ||
} | ||
deletable.add(table[1]); | ||
}); | ||
} | ||
} | ||
$getReferencedFields(referencedFields, ruleBody); | ||
referencedFields = _.mapValues(referencedFields, _.uniq); | ||
const refFields: RuleReferencedFields = {}; | ||
for (const f of Object.keys(referencedFields)) { | ||
refFields[f] = { | ||
create: referencedFields[f], | ||
update: referencedFields[f], | ||
delete: referencedFields[f], | ||
}; | ||
if (deletable.has(f)) { | ||
const countFroms = (n: AbstractSqlType[]) => { | ||
let count = 0; | ||
n.forEach((p) => { | ||
if (Array.isArray(p)) { | ||
if (isFromNode(p) && dealiasTableNode(p[1])?.[1] === f) { | ||
count++; | ||
} else { | ||
count += countFroms(p as AbstractSqlType[]); | ||
} | ||
} | ||
}); | ||
return count; | ||
}; | ||
// It's only deletable if there's just a single ref | ||
if (countFroms(ruleBody) === 1) { | ||
refFields[f].delete = []; | ||
} | ||
} | ||
} | ||
return refFields; | ||
}; | ||
const checkQuery = (query: AbstractSqlQuery): ModifiedFields | undefined => { | ||
const queryType = query[0]; | ||
if (!['InsertQuery', 'UpdateQuery', 'DeleteQuery'].includes(queryType)) { | ||
return; | ||
} | ||
const froms = query.filter(isFromNode); | ||
if (froms.length !== 1) { | ||
return; | ||
} | ||
const table = froms[0][1]; | ||
let tableName: string; | ||
if (table[0] === 'Table') { | ||
tableName = table[1]; | ||
} else if (typeof table === 'string') { | ||
// Deprecated: Remove this when we drop implicit tables | ||
tableName = table; | ||
} else { | ||
return; | ||
} | ||
if (queryType === 'InsertQuery') { | ||
return { table: tableName, action: 'create' }; | ||
} | ||
if (queryType === 'DeleteQuery') { | ||
return { table: tableName, action: 'delete' }; | ||
} | ||
const fields = _<FieldsNode | AbstractSqlType>(query) | ||
.filter((v): v is FieldsNode => v != null && v[0] === 'Fields') | ||
.flatMap((v) => v[1]) | ||
.value(); | ||
return { table: tableName, action: 'update', fields }; | ||
}; | ||
const getModifiedFields: EngineInstance['getModifiedFields'] = ( | ||
abstractSqlQuery: AbstractSqlQuery, | ||
) => { | ||
if (Array.isArray(abstractSqlQuery[0])) { | ||
return abstractSqlQuery.map(checkQuery); | ||
} else { | ||
return checkQuery(abstractSqlQuery); | ||
} | ||
}; | ||
export function compileRule( | ||
@@ -658,0 +459,0 @@ abstractSQL: UpsertQueryNode, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
599590
58
13259
2