Skip to content

Commit

Permalink
Drop Entities
Browse files Browse the repository at this point in the history
  • Loading branch information
cressie176 committed Jan 14, 2024
1 parent 4eec3ba commit ddedeb8
Show file tree
Hide file tree
Showing 12 changed files with 176 additions and 5 deletions.
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"max-classes-per-file": 0,
"max-len": 0,
"no-await-in-loop": 0,
"no-console": "error",
"no-dynamic-require": 0,
"no-empty-function": 0,
"no-plusplus": 0,
Expand Down
4 changes: 2 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const { Pool } = require('pg');
const parseDuration = require('parse-duration');

const driver = require('./lib/marv-filby-driver');
const { tableName } = require('./lib/helpers');
const { aggregateFunctionName } = require('./lib/helpers');

module.exports = class Filby extends EventEmitter {

Expand Down Expand Up @@ -104,7 +104,7 @@ LIMIT 1`, [projection.id]);

async getAggregates(changeSetId, name, version) {
return this.withTransaction(async (tx) => {
const functionName = `get_${tableName(name, version)}_aggregate`;
const functionName = aggregateFunctionName(name, version);
const { rowCount: exists } = await tx.query('SELECT 1 FROM pg_proc WHERE proname = $1', [functionName]);
if (!exists) throw new Error(`Function '${functionName}' does not exist`);

Expand Down
5 changes: 5 additions & 0 deletions lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ function tableName(name, version) {
return `${name.toLowerCase().replace(/\s/g, '_')}_v${version}`;
}

function aggregateFunctionName(name, version) {
return `get_${tableName(name, version)}_aggregate`;
}

function loadCsv(path, options) {
const { rows, errors } = parseCsv(path);
if (errors[0]) throw new Error(`Error parsing ${path}:${errors[0].row + 2} - ${errors[0].message}`);
Expand Down Expand Up @@ -53,6 +57,7 @@ function toString(result, item, index, items) {
module.exports = {
and,
tableName,
aggregateFunctionName,
loadCsv,
xkeys,
xvalues,
Expand Down
1 change: 1 addition & 0 deletions lib/marv-filby-driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const partials = {
addHooks: fs.readFileSync(path.join(__dirname, 'partials', 'add-hooks.hbs'), 'utf-8'),
addChangeSets: fs.readFileSync(path.join(__dirname, 'partials', 'add-change-sets.hbs'), 'utf-8'),
dropEnums: fs.readFileSync(path.join(__dirname, 'partials', 'drop-enums.hbs'), 'utf-8'),
dropEntities: fs.readFileSync(path.join(__dirname, 'partials', 'drop-entities.hbs'), 'utf-8'),
dropProjections: fs.readFileSync(path.join(__dirname, 'partials', 'drop-projections.hbs'), 'utf-8'),
dropHooks: fs.readFileSync(path.join(__dirname, 'partials', 'drop-hooks.hbs'), 'utf-8'),
};
Expand Down
2 changes: 1 addition & 1 deletion lib/partials/add-entities.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ CREATE TABLE {{tableName name version}} (
{{/each}}
);

CREATE FUNCTION get_{{tableName name version}}_aggregate(p_change_set_id INTEGER) RETURNS TABLE (
CREATE FUNCTION {{aggregateFunctionName name version}}(p_change_set_id INTEGER) RETURNS TABLE (
{{#fields}}
{{name}} {{type}}{{#unless @last}},{{/unless}}
{{/fields}}
Expand Down
11 changes: 11 additions & 0 deletions lib/partials/drop-entities.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{{#if entities}}-- Drop Entities{{/if}}
{{#entities}}
DELETE FROM fby_entity
WHERE name = '{{name}}'
AND version = {{version}};

DROP TABLE {{tableName name version}};

DROP FUNCTION {{aggregateFunctionName name version}};

{{/entities}}
26 changes: 26 additions & 0 deletions lib/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@
"drop_hooks": {
"$ref": "#/definitions/dropHooksType"
},
"drop entities": {
"$ref": "#/definitions/dropEntitiesType"
},
"drop_entities": {
"$ref": "#/definitions/dropEntitiesType"
},
"drop enums": {
"$ref": "#/definitions/dropEnumsType"
},
Expand Down Expand Up @@ -376,6 +382,26 @@
}
}
},
"dropEntitiesType": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "integer"
}
},
"additionalProperties": false,
"required": [
"name",
"version"
]
}
},
"dropEnumsType": {
"type": "array",
"minItems": 1,
Expand Down
1 change: 1 addition & 0 deletions lib/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ START TRANSACTION;
{{> addChangeSets changeSets=add_change_sets}}
{{> dropProjections projections=drop_projections}}
{{> dropHooks hooks=drop_hooks}}
{{> dropEntities entities=drop_entities}}
{{> dropEnums enums=drop_enums}}

END TRANSACTION;
2 changes: 1 addition & 1 deletion migrations/003.create-fby-projection-entity-relations.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ START TRANSACTION;

CREATE TABLE fby_projection_entity (
projection_id INTEGER REFERENCES fby_projection (id) ON DELETE CASCADE NOT NULL,
entity_id INTEGER REFERENCES fby_entity (id) ON DELETE CASCADE NOT NULL,
entity_id INTEGER REFERENCES fby_entity (id) NOT NULL,
PRIMARY KEY (projection_id, entity_id)
);

Expand Down
2 changes: 1 addition & 1 deletion migrations/007.create-fby-data-frame-relations.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ CREATE TYPE fby_action_type AS ENUM ('POST', 'DELETE');
CREATE TABLE fby_data_frame (
id SERIAL PRIMARY KEY,
change_set_id INTEGER REFERENCES fby_change_set (id) NOT NULL,
entity_id INTEGER REFERENCES fby_entity (id) NOT NULL,
entity_id INTEGER REFERENCES fby_entity (id) ON DELETE CASCADE NOT NULL,
action fby_action_type NOT NULL
);

Expand Down
27 changes: 27 additions & 0 deletions test/database-schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,33 @@ describe('Database Schema', () => {
});
});

describe('Entities', () => {

it('should prevent deletion when there are dependent projections', async () => {
await rejects(() => filby.withTransaction(async (tx) => {
await tx.query("INSERT INTO fby_projection (id, name, version) VALUES (1, 'Parks', 1)");
await tx.query("INSERT INTO fby_entity (id, name, version) VALUES (1, 'Park', 1)");
await tx.query('INSERT INTO fby_projection_entity (projection_id, entity_id) VALUES (1, 1)');
await tx.query('DELETE FROM fby_entity');
}), (err) => {
eq(err.code, '23503');
return true;
});
});

it('should cascade deletes to data frames', async () => {
await filby.withTransaction(async (tx) => {
await tx.query("INSERT INTO fby_entity (id, name, version) VALUES (1, 'Park', 1)");
await tx.query('INSERT INTO fby_change_set (id, effective) VALUES (1, now())');
await tx.query("INSERT INTO fby_data_frame (id, change_set_id, entity_id, action) VALUES (1, 1, 1, 'POST')");
await tx.query('DELETE FROM fby_entity');
});

const { rows: frames } = await filby.withTransaction((tx) => tx.query('SELECT * from fby_data_frame'));
eq(frames.length, 0);
});
});

describe('Hooks', () => {
it('should prevent duplicate projection hooks', async () => {

Expand Down
99 changes: 99 additions & 0 deletions test/dsl.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const path = require('node:path');
const { ok, strictEqual: eq, deepEqual: deq, rejects, match } = require('node:assert');
const { describe, it, before, beforeEach, after, afterEach } = require('zunit');

const { tableName, aggregateFunctionName } = require('../lib/helpers');
const TestFilby = require('./TestFilby');

const config = {
Expand Down Expand Up @@ -584,6 +585,104 @@ describe('DSL', () => {
});
});

describe('Drop Entities', () => {
it('should drop entities', async (t) => {
await applyYaml(t.name, `
add entities:
- name: VAT Rate
version: 1
fields:
- name: type
type: TEXT
- name: rate
type: NUMERIC
identified by:
- type
drop entities:
- name: VAT Rate
version: 1
`);

const { rows: entities } = await filby.withTransaction((tx) => tx.query('SELECT * FROM fby_entity'));
eq(entities.length, 0);

const { rows: tables } = await filby.withTransaction((tx) => tx.query('SELECT * FROM information_schema.tables WHERE table_name = $1', [tableName('vat_rate', 1)]));
eq(tables.length, 0);

const { rows: functions } = await filby.withTransaction((tx) => tx.query('SELECT * FROM pg_proc WHERE proname = $1', [aggregateFunctionName('vat_rate', 1)]));
eq(functions.length, 0);
});

it('should ignore other entities', async (t) => {
await applyYaml(t.name, `
add entities:
- name: VAT Rate
version: 1
fields:
- name: type
type: TEXT
- name: rate
type: NUMERIC
identified by:
- type
- name: VAT Rate
version: 2
fields:
- name: type
type: TEXT
- name: rate
type: NUMERIC
identified by:
- type
drop entities:
- name: VAT Rate
version: 1
`);

const { rows: entities } = await filby.withTransaction((tx) => tx.query('SELECT * FROM fby_entity'));
eq(entities.length, 1);

const { rows: tables } = await filby.withTransaction((tx) => tx.query('SELECT * FROM information_schema.tables WHERE table_name = $1', [tableName('vat_rate', 2)]));
eq(tables.length, 1);

const { rows: functions } = await filby.withTransaction((tx) => tx.query('SELECT * FROM pg_proc WHERE proname = $1', [aggregateFunctionName('vat_rate', 2)]));
eq(functions.length, 1);
});

it('should require a name', async (t) => {
await rejects(() => applyYaml(t.name, `
drop entities:
- version: 1
`), (err) => {
match(err.message, new RegExp("^001.should-require-a-name.yaml: /drop_entities/0 must have required property 'name'$"));
return true;
});
});

it('should require a version', async (t) => {
await rejects(() => applyYaml(t.name, `
drop entities:
- name: VAT Rate
`), (err) => {
match(err.message, new RegExp("^001.should-require-a-version.yaml: /drop_entities/0 must have required property 'version'$"));
return true;
});
});

it('should forbid additional properties', async (t) => {
await rejects(() => applyYaml(t.name, `
drop entities:
- name: VAT Rate
version: 1
wombat: Freddy
`), (err) => {
match(err.message, new RegExp('^001.should-forbid-additional-properties.yaml: /drop_entities/0 must NOT have additional properties$'));
return true;
});
});
});

describe('Add Change Sets', () => {
it('should require an effective date', async (t) => {
await rejects(() => applyYaml(t.name, `
Expand Down

0 comments on commit ddedeb8

Please sign in to comment.