Skip to content

Commit

Permalink
Merge pull request #237 from balena-io-modules/partial-unique-index
Browse files Browse the repository at this point in the history
Add support for partial unique indexes
  • Loading branch information
flowzone-app[bot] authored Mar 26, 2024
2 parents 1b4199f + 6d00b7d commit 9d857c2
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 13 deletions.
50 changes: 43 additions & 7 deletions src/AbstractSQLCompiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export { Binding, SqlResult } from './AbstractSQLRules2SQL';
import type { SbvrType } from '@balena/sbvr-types';
import sbvrTypes from '@balena/sbvr-types';
import * as _ from 'lodash';
import { optimizeSchema } from './AbstractSQLSchemaOptimiser';
import { optimizeSchema, generateRuleSlug } from './AbstractSQLSchemaOptimiser';
import {
getReferencedFields,
getRuleReferencedFields,
Expand Down Expand Up @@ -449,6 +449,15 @@ export interface Trigger {
level: 'ROW' | 'STATEMENT';
when: 'BEFORE' | 'AFTER' | 'INSTEAD OF';
}
export interface Index {
type: string;
fields: string[];
name?: string;
/** For rules converted to partial unique indexes this holds the actual rule expression */
description?: string;
distinctNulls?: boolean;
predicate?: BooleanTypeNodes;
}
export interface Check {
description?: string;
name?: string;
Expand All @@ -466,10 +475,7 @@ export interface AbstractSqlTable {
resourceName: string;
idField: string;
fields: AbstractSqlField[];
indexes: Array<{
type: string;
fields: string[];
}>;
indexes: Index[];
primitive: false | string;
triggers?: Trigger[];
checks?: Check[];
Expand Down Expand Up @@ -815,6 +821,7 @@ ${compileRule(definitionAbstractSql as AbstractSqlQuery, engine, true).replace(
const foreignKeys: string[] = [];
const depends: string[] = [];
const createSqlElements: string[] = [];
const createIndexes: string[] = [];

for (const field of table.fields) {
const { fieldName, references, dataType, computed } = field;
Expand Down Expand Up @@ -873,9 +880,36 @@ $$;`);

createSqlElements.push(...foreignKeys);
for (const index of table.indexes) {
createSqlElements.push(
index.type + '("' + index.fields.join('", "') + '")',
let nullsSql = '';
if (index.distinctNulls != null) {
nullsSql =
index.distinctNulls === false
? ` NULLS NOT DISTINCT`
: ` NULLS DISTINCT`;
}
// Non-partial indexes are added directly to the CREATE TABLE statement
if (index.predicate == null) {
createSqlElements.push(
index.type + nullsSql + '("' + index.fields.join('", "') + '")',
);
continue;
}
if (index.name == null) {
throw new Error('No name provided for partial index');
}
const comment = index.description
? `-- ${index.description.replaceAll(/\r?\n/g, '\n-- ')}\n`
: '';
const whereSql = compileRule(
index.predicate as AbstractSqlQuery,
engine,
true,
);
createIndexes.push(`\
${comment}\
CREATE ${index.type} INDEX IF NOT EXISTS "${index.name}"
ON "${table.name}" ("${index.fields.join('", "')}")${nullsSql}
WHERE (${whereSql});`);
}

if (table.checks) {
Expand Down Expand Up @@ -932,6 +966,7 @@ $$`);
CREATE TABLE ${ifNotExistsStr}"${table.name}" (
${createSqlElements.join('\n,\t')}
);`,
...createIndexes,
...createTriggers,
],
dropSQL: [...dropTriggers, `DROP TABLE "${table.name}";`],
Expand Down Expand Up @@ -1051,6 +1086,7 @@ CREATE TABLE ${ifNotExistsStr}"${table.name}" (
const generateExport = (engine: Engines, ifNotExists: boolean) => {
return {
optimizeSchema,
generateRuleSlug,
compileSchema: (abstractSqlModel: AbstractSqlModel) =>
compileSchema(abstractSqlModel, engine, ifNotExists),
compileRule: (abstractSQL: AbstractSqlQuery) =>
Expand Down
18 changes: 12 additions & 6 deletions src/AbstractSQLSchemaOptimiser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ const countFroms = (n: AbstractSqlType[]) => {
return count;
};

export const generateRuleSlug = (
tableName: string,
ruleBody: AbstractSqlType,
) => {
const sha = sbvrTypes.SHA.validateSync(
`${tableName}$${JSON.stringify(ruleBody)}`,
).replace(/^\$sha256\$/, '');
// Trim the trigger to a max of 63 characters, reserving at least 32 characters for the hash
return `${tableName.slice(0, 30)}$${sha}`.slice(0, 63);
};

export const optimizeSchema = (
abstractSqlModel: AbstractSqlModel,
createCheckConstraints: boolean = true,
Expand Down Expand Up @@ -110,10 +121,6 @@ export const optimizeSchema = (
convertReferencedFieldsToFields(whereNode);

const tableName = fromNode[1];
const sha = sbvrTypes.SHA.validateSync(
`${tableName}$${JSON.stringify(ruleBody)}`,
).replace(/^\$sha256\$/, '');

const table = _.find(
abstractSqlModel.tables,
(t) => t.name === tableName,
Expand All @@ -122,8 +129,7 @@ export const optimizeSchema = (
table.checks ??= [];
table.checks!.push({
description: ruleSE,
// Trim the trigger to a max of 63 characters, reserving at least 32 characters for the hash
name: `${tableName.slice(0, 30)}$${sha}`.slice(0, 63),
name: generateRuleSlug(tableName, ruleBody),
abstractSql: whereNode,
});
return;
Expand Down

0 comments on commit 9d857c2

Please sign in to comment.