New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@balena/abstract-sql-compiler

Package Overview
Dependencies
Maintainers
3
Versions
478
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@balena/abstract-sql-compiler - npm Package Compare versions

Comparing version 7.10.2 to 7.11.0-generalize-rule-referenced-fields-3e77ac1aed9f3fb596f6a01a08a1800fead1e860

4

CHANGELOG.md

@@ -7,2 +7,6 @@ # Change Log

## 7.11.0 - 2021-03-01
* Generalize/share the referenced fields code and cover more cases [Pagan Gazzard]
## 7.10.2 - 2021-02-12

@@ -9,0 +13,0 @@

135

out/referenced-fields.js

@@ -7,3 +7,14 @@ "use strict";

const AbstractSQLOptimiser_1 = require("./AbstractSQLOptimiser");
const getScope = (rulePart, scope) => {
const getReferencedFields = (ruleBody) => {
const referencedFields = exports.getRuleReferencedFields(ruleBody);
return _.mapValues(referencedFields, ({ update }) => _.uniq(update));
};
exports.getReferencedFields = getReferencedFields;
var IsSafe;
(function (IsSafe) {
IsSafe["Insert"] = "ins";
IsSafe["Delete"] = "del";
IsSafe["Unknown"] = "";
})(IsSafe || (IsSafe = {}));
const getRuleReferencedScope = (rulePart, scope, isSafe) => {
scope = { ...scope };

@@ -20,6 +31,6 @@ const fromNodes = rulePart.filter(AbstractSQLCompiler_1.isFromNode);

case 'Table':
scope[alias] = from[1];
scope[alias] = { tableName: from[1], isSafe };
break;
case 'SelectQuery':
scope[alias] = '';
scope[alias] = { tableName: '', isSafe };
break;

@@ -31,3 +42,3 @@ default:

else if (nested[0] === 'Table') {
scope[nested[1]] = nested[1];
scope[nested[1]] = { tableName: nested[1], isSafe };
}

@@ -40,3 +51,3 @@ else {

};
const $getReferencedFields = (referencedFields, rulePart, scope = {}) => {
const $getRuleReferencedFields = (referencedFields, rulePart, isSafe, scope = {}) => {
if (!Array.isArray(rulePart)) {

@@ -47,19 +58,29 @@ return;

case 'SelectQuery':
scope = getScope(rulePart, scope);
scope = getRuleReferencedScope(rulePart, scope, isSafe);
rulePart.forEach((node) => {
$getReferencedFields(referencedFields, node, scope);
$getRuleReferencedFields(referencedFields, node, isSafe, scope);
});
break;
return;
case 'ReferencedField':
let tableName = rulePart[1];
const aliasName = rulePart[1];
const fieldName = rulePart[2];
if (typeof tableName !== 'string' || typeof fieldName !== 'string') {
if (typeof aliasName !== 'string' || typeof fieldName !== 'string') {
throw new Error(`Invalid ReferencedField: ${rulePart}`);
}
tableName = scope[tableName];
if (tableName !== '') {
if (referencedFields[tableName] == null) {
referencedFields[tableName] = [];
const a = scope[aliasName];
if (a.tableName !== '') {
if (referencedFields[a.tableName] == null) {
referencedFields[a.tableName] = {
create: [],
update: [],
delete: [],
};
}
referencedFields[tableName].push(fieldName);
if (a.isSafe !== IsSafe.Insert) {
referencedFields[a.tableName].create.push(fieldName);
}
if (a.isSafe !== IsSafe.Delete) {
referencedFields[a.tableName].delete.push(fieldName);
}
referencedFields[a.tableName].update.push(fieldName);
}

@@ -69,73 +90,33 @@ return;

throw new Error('Cannot find queried fields for unreferenced fields');
case 'Not':
case 'NotExists':
if (isSafe === IsSafe.Insert) {
isSafe = IsSafe.Delete;
}
else if (isSafe === IsSafe.Delete) {
isSafe = IsSafe.Insert;
}
case 'And':
case 'Exists':
rulePart.forEach((node) => {
$getRuleReferencedFields(referencedFields, node, isSafe, scope);
});
return;
default:
rulePart.forEach((node) => {
$getReferencedFields(referencedFields, node, scope);
$getRuleReferencedFields(referencedFields, node, IsSafe.Unknown, scope);
});
}
};
const getReferencedFields = (ruleBody) => {
const getRuleReferencedFields = (ruleBody) => {
ruleBody = AbstractSQLOptimiser_1.AbstractSQLOptimiser(ruleBody);
const referencedFields = {};
$getReferencedFields(referencedFields, ruleBody);
return _.mapValues(referencedFields, _.uniq);
};
exports.getReferencedFields = getReferencedFields;
const dealiasTableNode = (n) => {
if (AbstractSQLCompiler_1.isTableNode(n)) {
return n;
}
if (n[0] === 'Alias' && AbstractSQLCompiler_1.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 (!AbstractSQLCompiler_1.isFromNode(m)) {
return;
}
const table = dealiasTableNode(m[1]);
if (table == null) {
return;
}
deletable.add(table[1]);
});
$getRuleReferencedFields(referencedFields, ruleBody, IsSafe.Insert);
for (const tableName of Object.keys(referencedFields)) {
const tableRefs = referencedFields[tableName];
for (const method of Object.keys(tableRefs)) {
tableRefs[method] = _.uniq(tableRefs[method]);
}
}
$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 (AbstractSQLCompiler_1.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;
return referencedFields;
};

@@ -142,0 +123,0 @@ exports.getRuleReferencedFields = getRuleReferencedFields;

{
"name": "@balena/abstract-sql-compiler",
"version": "7.10.2",
"version": "7.11.0-generalize-rule-referenced-fields-3e77ac1aed9f3fb596f6a01a08a1800fead1e860",
"description": "A translator for abstract sql into sql.",

@@ -5,0 +5,0 @@ "main": "out/AbstractSQLCompiler.js",

@@ -8,5 +8,2 @@ import * as _ from 'lodash';

isFromNode,
isTableNode,
SelectQueryNode,
TableNode,
} from './AbstractSQLCompiler';

@@ -25,5 +22,33 @@ import { AbstractSQLOptimiser } from './AbstractSQLOptimiser';

type Scope = _.Dictionary<string>;
export const getReferencedFields: EngineInstance['getReferencedFields'] = (
ruleBody,
) => {
const referencedFields = getRuleReferencedFields(ruleBody);
const getScope = (rulePart: AbstractSqlQuery, scope: Scope): Scope => {
return _.mapValues(referencedFields, ({ update }) => _.uniq(update));
};
export interface RuleReferencedFields {
[alias: string]: {
create: string[];
update: string[];
delete: string[];
};
}
enum IsSafe {
Insert = 'ins',
Delete = 'del',
Unknown = '',
}
type RuleReferencedScope = {
[aliasName: string]: {
tableName: string;
isSafe: IsSafe;
};
};
const getRuleReferencedScope = (
rulePart: AbstractSqlQuery,
scope: RuleReferencedScope,
isSafe: IsSafe,
): RuleReferencedScope => {
scope = { ...scope };

@@ -40,3 +65,3 @@ const fromNodes = rulePart.filter(isFromNode);

case 'Table':
scope[alias] = from[1];
scope[alias] = { tableName: from[1], isSafe };
break;

@@ -47,3 +72,3 @@ case 'SelectQuery':

// fields that don't affect the end result and avoid false positives
scope[alias] = '';
scope[alias] = { tableName: '', isSafe };
break;

@@ -54,3 +79,3 @@ default:

} else if (nested[0] === 'Table') {
scope[nested[1]] = nested[1];
scope[nested[1]] = { tableName: nested[1], isSafe };
} else {

@@ -62,6 +87,7 @@ throw Error(`Unsupported FromNode for scoping: ${nested[0]}`);

};
const $getReferencedFields = (
referencedFields: ReferencedFields,
const $getRuleReferencedFields = (
referencedFields: RuleReferencedFields,
rulePart: AbstractSqlQuery,
scope: Scope = {},
isSafe: IsSafe,
scope: RuleReferencedScope = {},
) => {

@@ -74,21 +100,31 @@ if (!Array.isArray(rulePart)) {

// Update the current scope before trying to resolve field references
scope = getScope(rulePart, scope);
scope = getRuleReferencedScope(rulePart, scope, isSafe);
rulePart.forEach((node: AbstractSqlQuery) => {
$getReferencedFields(referencedFields, node, scope);
$getRuleReferencedFields(referencedFields, node, isSafe, scope);
});
break;
return;
case 'ReferencedField':
let tableName = rulePart[1];
const aliasName = rulePart[1];
const fieldName = rulePart[2];
if (typeof tableName !== 'string' || typeof fieldName !== 'string') {
if (typeof aliasName !== 'string' || typeof fieldName !== 'string') {
throw new Error(`Invalid ReferencedField: ${rulePart}`);
}
tableName = scope[tableName];
const a = scope[aliasName];
// 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] = [];
if (a.tableName !== '') {
if (referencedFields[a.tableName] == null) {
referencedFields[a.tableName] = {
create: [],
update: [],
delete: [],
};
}
referencedFields[tableName].push(fieldName);
if (a.isSafe !== IsSafe.Insert) {
referencedFields[a.tableName].create.push(fieldName);
}
if (a.isSafe !== IsSafe.Delete) {
referencedFields[a.tableName].delete.push(fieldName);
}
referencedFields[a.tableName].update.push(fieldName);
}

@@ -98,33 +134,23 @@ return;

throw new Error('Cannot find queried fields for unreferenced fields');
case 'Not':
case 'NotExists':
// When hitting a `Not` we invert the safety rule
if (isSafe === IsSafe.Insert) {
isSafe = IsSafe.Delete;
} else if (isSafe === IsSafe.Delete) {
isSafe = IsSafe.Insert;
}
// Fallthrough
case 'And':
case 'Exists':
rulePart.forEach((node: AbstractSqlQuery) => {
$getRuleReferencedFields(referencedFields, node, isSafe, scope);
});
return;
default:
rulePart.forEach((node: AbstractSqlQuery) => {
$getReferencedFields(referencedFields, node, scope);
$getRuleReferencedFields(referencedFields, node, IsSafe.Unknown, scope);
});
}
};
export 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[];
};
}
export const getRuleReferencedFields: EngineInstance['getRuleReferencedFields'] = (

@@ -134,53 +160,14 @@ 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]);
});
const referencedFields: RuleReferencedFields = {};
$getRuleReferencedFields(referencedFields, ruleBody, IsSafe.Insert);
for (const tableName of Object.keys(referencedFields)) {
const tableRefs = referencedFields[tableName];
for (const method of Object.keys(tableRefs) as Array<
keyof typeof tableRefs
>) {
tableRefs[method] = _.uniq(tableRefs[method]);
}
}
$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;
return referencedFields;
};

@@ -187,0 +174,0 @@

import { expect } from 'chai';
import * as AbstractSqlCompiler from '../../src/AbstractSQLCompiler';
describe('getReferencedFields', () => {
it('should work with selected fields', () => {
describe('getRuleReferencedFields', () => {
it('should work with single table SELECT NOT EXISTS', () => {
expect(

@@ -41,2 +41,74 @@ AbstractSqlCompiler.postgres.getRuleReferencedFields([

});
it('should work with multi table SELECT NOT EXISTS', () => {
expect(
AbstractSqlCompiler.postgres.getRuleReferencedFields([
'Not',
[
'Exists',
[
'SelectQuery',
['Select', []],
['From', ['test', 'test.0']],
['From', ['test', 'test.1']],
[
'Where',
[
'Not',
[
'And',
[
'LessThan',
['ReferencedField', 'test.1', 'id'],
['ReferencedField', 'test.0', 'id'],
],
['Exists', ['ReferencedField', 'test.0', 'id']],
],
],
],
],
],
] as AbstractSqlCompiler.AbstractSqlQuery),
).to.deep.equal({
test: {
create: ['id'],
update: ['id'],
delete: [],
},
});
});
it('should work with single table SELECT EXISTS', () => {
expect(
AbstractSqlCompiler.postgres.getRuleReferencedFields([
'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),
).to.deep.equal({
test: {
create: [],
update: ['id'],
delete: ['id'],
},
});
});
});

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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc