From 1c3234426278bdc0d33709d5550fbd86ab1f9409 Mon Sep 17 00:00:00 2001 From: caseybrookes Date: Mon, 1 Sep 2025 18:48:14 -0500 Subject: [PATCH] feat(schemas): support cross schema control --- package-lock.json | 252 ++++++++++++------ package.json | 2 +- .../mysql.multischema/connection.config.ts | 8 + .../mysql.multischema/control.yml | 10 + .../definitions/functions.yml | 2 + .../functions/find_message_hash_by_text.sql | 7 + .../mysql.multischema/definitions/init.yml | 9 + .../definitions/init/databases.sql | 2 + .../definitions/init/service_user.sql | 4 + .../definitions/procedures.yml | 2 + .../definitions/procedures/upsert_message.sql | 17 ++ .../mysql.multischema/definitions/tables.yml | 10 + .../definitions/tables/notification.sql | 18 ++ .../tables/notification_version.sql | 13 + .../definitions/tables/spaceship.sql | 4 + .../definitions/tables/spaceship_cargo.sql | 8 + .../mysql.multischema/definitions/views.yml | 2 + .../views/view_spaceship_with_cargo.sql | 8 + .../apply.integration.test.ts.snap | 92 ++++++- .../commands/apply.integration.test.ts | 244 ++++++++++++----- .../extractResourceTypeAndNameFromDDL.test.ts | 14 +- .../extractResourceTypeAndNameFromDDL.ts | 9 +- 22 files changed, 579 insertions(+), 158 deletions(-) create mode 100644 src/contract/__test_assets__/mysql.multischema/connection.config.ts create mode 100644 src/contract/__test_assets__/mysql.multischema/control.yml create mode 100644 src/contract/__test_assets__/mysql.multischema/definitions/functions.yml create mode 100644 src/contract/__test_assets__/mysql.multischema/definitions/functions/find_message_hash_by_text.sql create mode 100644 src/contract/__test_assets__/mysql.multischema/definitions/init.yml create mode 100644 src/contract/__test_assets__/mysql.multischema/definitions/init/databases.sql create mode 100644 src/contract/__test_assets__/mysql.multischema/definitions/init/service_user.sql create mode 100644 src/contract/__test_assets__/mysql.multischema/definitions/procedures.yml create mode 100644 src/contract/__test_assets__/mysql.multischema/definitions/procedures/upsert_message.sql create mode 100644 src/contract/__test_assets__/mysql.multischema/definitions/tables.yml create mode 100644 src/contract/__test_assets__/mysql.multischema/definitions/tables/notification.sql create mode 100644 src/contract/__test_assets__/mysql.multischema/definitions/tables/notification_version.sql create mode 100644 src/contract/__test_assets__/mysql.multischema/definitions/tables/spaceship.sql create mode 100644 src/contract/__test_assets__/mysql.multischema/definitions/tables/spaceship_cargo.sql create mode 100644 src/contract/__test_assets__/mysql.multischema/definitions/views.yml create mode 100644 src/contract/__test_assets__/mysql.multischema/definitions/views/view_spaceship_with_cargo.sql diff --git a/package-lock.json b/package-lock.json index 64d75ad..b26ed41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@oclif/plugin-help": "^3.1.0", "chalk": "^2.4.2", "domain-objects": "0.22.1", - "helpful-errors": "^1.3.9", + "helpful-errors": "^1.3.10", "indent-string": "^4.0.0", "jest-diff": "^25.3.0", "joi": "17.4.0", @@ -8004,6 +8004,42 @@ "node": ">=8.0.0" } }, + "node_modules/declapract-typescript-ehmpathy/node_modules/type-fns": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-1.17.0.tgz", + "integrity": "sha512-zscRD2lGbnD2Ktyvb79cZ7R1kGm0OBnfisG3g3LdFS9C9pR8YKdXtUojDStvgQtaqyb/a77E5gYTsBY3jrZMsA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@ehmpathy/error-fns": "1.0.2", + "type-fns": "1.16.0", + "uuid": "9.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/declapract-typescript-ehmpathy/node_modules/type-fns/node_modules/type-fns": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-1.16.0.tgz", + "integrity": "sha512-4erPa91wW979ZHH/zVI340b3s4HQGFYUMH5rBUUP0tyqPGzpZJddUpZXBliu8KF/oXEMOq3Elydb7OgdgKpung==", + "dev": true, + "dependencies": { + "@ehmpathy/error-fns": "1.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/declapract-typescript-ehmpathy/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/declapract/node_modules/@ehmpathy/error-fns": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@ehmpathy/error-fns/-/error-fns-1.3.2.tgz", @@ -8417,6 +8453,55 @@ "typescript": ">=2.7" } }, + "node_modules/declapract/node_modules/type-fns": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-1.17.0.tgz", + "integrity": "sha512-zscRD2lGbnD2Ktyvb79cZ7R1kGm0OBnfisG3g3LdFS9C9pR8YKdXtUojDStvgQtaqyb/a77E5gYTsBY3jrZMsA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@ehmpathy/error-fns": "1.0.2", + "type-fns": "1.16.0", + "uuid": "9.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/declapract/node_modules/type-fns/node_modules/@ehmpathy/error-fns": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@ehmpathy/error-fns/-/error-fns-1.0.2.tgz", + "integrity": "sha512-v3aJIqUvD9a3drx1pyS8La+9u9WTTvNE35NksiD4Oo3VanNe8Rmue/atRHPg4nNYQ/xPv4+RoqC+OBj6cAY8VA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "type-fns": "0.9.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/declapract/node_modules/type-fns/node_modules/@ehmpathy/error-fns/node_modules/type-fns": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-0.9.0.tgz", + "integrity": "sha512-ndhY4JBIbKix0LuGA5smh/XhFFnbeudnih++WxVoGTfdrITsZe/s3qje9GZNdWwsO+YWGyQkNXwAjnWyM/dipw==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/declapract/node_modules/type-fns/node_modules/type-fns": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-1.16.0.tgz", + "integrity": "sha512-4erPa91wW979ZHH/zVI340b3s4HQGFYUMH5rBUUP0tyqPGzpZJddUpZXBliu8KF/oXEMOq3Elydb7OgdgKpung==", + "dev": true, + "dependencies": { + "@ehmpathy/error-fns": "1.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/declapract/node_modules/uuid": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", @@ -10262,24 +10347,12 @@ } }, "node_modules/helpful-errors": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/helpful-errors/-/helpful-errors-1.3.9.tgz", - "integrity": "sha512-aP2aF/r7N1W90rRr1OP6IviqxnxAs5WQl4XTnvgwUK3rcnxMgfZk9VppCFWBaFaDISu/CZq6AiEE2m2rLtQ9OQ==", + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/helpful-errors/-/helpful-errors-1.3.10.tgz", + "integrity": "sha512-OEuVXjPjquyLB2dVFpqum1KK4bHVqbQ/ZJBO7wQnWaJwNUPFqQ8+yFQzmj9E+ZdXHshkqiCgRv475dBGrqNOrg==", "hasInstallScript": true, "dependencies": { - "type-fns": "^1.20.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/helpful-errors/node_modules/type-fns": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-1.20.1.tgz", - "integrity": "sha512-IUD1gkExjmGrJq67wsE4NrOH1/7sr08XWoWQ5rSEVXXyL/jHRdPAcDKxt/R3i9t7P4DSU1ZXZ86o2eUdKSGV3A==", - "hasInstallScript": true, - "dependencies": { - "helpful-errors": "^1.3.8" + "type-fns": "1.20.2" }, "engines": { "node": ">=8.0.0" @@ -15517,41 +15590,17 @@ } }, "node_modules/type-fns": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-1.17.0.tgz", - "integrity": "sha512-zscRD2lGbnD2Ktyvb79cZ7R1kGm0OBnfisG3g3LdFS9C9pR8YKdXtUojDStvgQtaqyb/a77E5gYTsBY3jrZMsA==", - "dev": true, + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-1.20.2.tgz", + "integrity": "sha512-quQzMYawzNvVChc9tGcJEp4onbhiNoNBX/yD82/h0n1voizuMfovpUsK2oPDn0bP+PRFEvuLV6o5ApOaHuA37w==", "hasInstallScript": true, "dependencies": { - "@ehmpathy/error-fns": "1.0.2", - "type-fns": "1.16.0", - "uuid": "9.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/type-fns/node_modules/type-fns": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-1.16.0.tgz", - "integrity": "sha512-4erPa91wW979ZHH/zVI340b3s4HQGFYUMH5rBUUP0tyqPGzpZJddUpZXBliu8KF/oXEMOq3Elydb7OgdgKpung==", - "dev": true, - "dependencies": { - "@ehmpathy/error-fns": "1.0.2" + "helpful-errors": "^1.3.8" }, "engines": { "node": ">=8.0.0" } }, - "node_modules/type-fns/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/typed-array-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", @@ -22110,6 +22159,45 @@ "yn": "3.1.1" } }, + "type-fns": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-1.17.0.tgz", + "integrity": "sha512-zscRD2lGbnD2Ktyvb79cZ7R1kGm0OBnfisG3g3LdFS9C9pR8YKdXtUojDStvgQtaqyb/a77E5gYTsBY3jrZMsA==", + "dev": true, + "requires": { + "@ehmpathy/error-fns": "1.0.2", + "type-fns": "1.16.0", + "uuid": "9.0.0" + }, + "dependencies": { + "@ehmpathy/error-fns": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@ehmpathy/error-fns/-/error-fns-1.0.2.tgz", + "integrity": "sha512-v3aJIqUvD9a3drx1pyS8La+9u9WTTvNE35NksiD4Oo3VanNe8Rmue/atRHPg4nNYQ/xPv4+RoqC+OBj6cAY8VA==", + "dev": true, + "requires": { + "type-fns": "0.9.0" + }, + "dependencies": { + "type-fns": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-0.9.0.tgz", + "integrity": "sha512-ndhY4JBIbKix0LuGA5smh/XhFFnbeudnih++WxVoGTfdrITsZe/s3qje9GZNdWwsO+YWGyQkNXwAjnWyM/dipw==", + "dev": true + } + } + }, + "type-fns": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-1.16.0.tgz", + "integrity": "sha512-4erPa91wW979ZHH/zVI340b3s4HQGFYUMH5rBUUP0tyqPGzpZJddUpZXBliu8KF/oXEMOq3Elydb7OgdgKpung==", + "dev": true, + "requires": { + "@ehmpathy/error-fns": "1.0.2" + } + } + } + }, "uuid": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", @@ -22139,6 +22227,34 @@ "requires": { "type-fns": "1.17.0" } + }, + "type-fns": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-1.17.0.tgz", + "integrity": "sha512-zscRD2lGbnD2Ktyvb79cZ7R1kGm0OBnfisG3g3LdFS9C9pR8YKdXtUojDStvgQtaqyb/a77E5gYTsBY3jrZMsA==", + "dev": true, + "requires": { + "@ehmpathy/error-fns": "1.0.2", + "type-fns": "1.16.0", + "uuid": "9.0.0" + }, + "dependencies": { + "type-fns": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-1.16.0.tgz", + "integrity": "sha512-4erPa91wW979ZHH/zVI340b3s4HQGFYUMH5rBUUP0tyqPGzpZJddUpZXBliu8KF/oXEMOq3Elydb7OgdgKpung==", + "dev": true, + "requires": { + "@ehmpathy/error-fns": "1.0.2" + } + } + } + }, + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true } } }, @@ -23490,21 +23606,11 @@ } }, "helpful-errors": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/helpful-errors/-/helpful-errors-1.3.9.tgz", - "integrity": "sha512-aP2aF/r7N1W90rRr1OP6IviqxnxAs5WQl4XTnvgwUK3rcnxMgfZk9VppCFWBaFaDISu/CZq6AiEE2m2rLtQ9OQ==", + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/helpful-errors/-/helpful-errors-1.3.10.tgz", + "integrity": "sha512-OEuVXjPjquyLB2dVFpqum1KK4bHVqbQ/ZJBO7wQnWaJwNUPFqQ8+yFQzmj9E+ZdXHshkqiCgRv475dBGrqNOrg==", "requires": { - "type-fns": "^1.20.1" - }, - "dependencies": { - "type-fns": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-1.20.1.tgz", - "integrity": "sha512-IUD1gkExjmGrJq67wsE4NrOH1/7sr08XWoWQ5rSEVXXyL/jHRdPAcDKxt/R3i9t7P4DSU1ZXZ86o2eUdKSGV3A==", - "requires": { - "helpful-errors": "^1.3.8" - } - } + "type-fns": "1.20.2" } }, "hosted-git-info": { @@ -27349,31 +27455,11 @@ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==" }, "type-fns": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-1.17.0.tgz", - "integrity": "sha512-zscRD2lGbnD2Ktyvb79cZ7R1kGm0OBnfisG3g3LdFS9C9pR8YKdXtUojDStvgQtaqyb/a77E5gYTsBY3jrZMsA==", - "dev": true, + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-1.20.2.tgz", + "integrity": "sha512-quQzMYawzNvVChc9tGcJEp4onbhiNoNBX/yD82/h0n1voizuMfovpUsK2oPDn0bP+PRFEvuLV6o5ApOaHuA37w==", "requires": { - "@ehmpathy/error-fns": "1.0.2", - "type-fns": "1.16.0", - "uuid": "9.0.0" - }, - "dependencies": { - "type-fns": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-1.16.0.tgz", - "integrity": "sha512-4erPa91wW979ZHH/zVI340b3s4HQGFYUMH5rBUUP0tyqPGzpZJddUpZXBliu8KF/oXEMOq3Elydb7OgdgKpung==", - "dev": true, - "requires": { - "@ehmpathy/error-fns": "1.0.2" - } - }, - "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "dev": true - } + "helpful-errors": "^1.3.8" } }, "typed-array-length": { diff --git a/package.json b/package.json index 86eaa42..d3082fa 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "@oclif/plugin-help": "^3.1.0", "chalk": "^2.4.2", "domain-objects": "0.22.1", - "helpful-errors": "^1.3.9", + "helpful-errors": "^1.3.10", "indent-string": "^4.0.0", "jest-diff": "^25.3.0", "joi": "17.4.0", diff --git a/src/contract/__test_assets__/mysql.multischema/connection.config.ts b/src/contract/__test_assets__/mysql.multischema/connection.config.ts new file mode 100644 index 0000000..5e1deb5 --- /dev/null +++ b/src/contract/__test_assets__/mysql.multischema/connection.config.ts @@ -0,0 +1,8 @@ +// note: this connection config is defined in /provision/integration_test_db/docker-compose +export const promiseConfig = async () => ({ + host: 'localhost', + port: 12821, + database: 'superimportantdb', + username: 'root', + password: 'a-secure-password', +}); diff --git a/src/contract/__test_assets__/mysql.multischema/control.yml b/src/contract/__test_assets__/mysql.multischema/control.yml new file mode 100644 index 0000000..e010e7d --- /dev/null +++ b/src/contract/__test_assets__/mysql.multischema/control.yml @@ -0,0 +1,10 @@ +language: mysql +dialect: 5.7 +connection: ./connection.config.ts +strict: false # strict = true -> display uncontrolled resources +definitions: + - ./definitions/init.yml + - ./definitions/tables.yml + - ./definitions/views.yml + - ./definitions/functions.yml + - ./definitions/procedures.yml diff --git a/src/contract/__test_assets__/mysql.multischema/definitions/functions.yml b/src/contract/__test_assets__/mysql.multischema/definitions/functions.yml new file mode 100644 index 0000000..7885f49 --- /dev/null +++ b/src/contract/__test_assets__/mysql.multischema/definitions/functions.yml @@ -0,0 +1,2 @@ +- type: resource + path: './functions/find_message_hash_by_text.sql' diff --git a/src/contract/__test_assets__/mysql.multischema/definitions/functions/find_message_hash_by_text.sql b/src/contract/__test_assets__/mysql.multischema/definitions/functions/find_message_hash_by_text.sql new file mode 100644 index 0000000..5a8d577 --- /dev/null +++ b/src/contract/__test_assets__/mysql.multischema/definitions/functions/find_message_hash_by_text.sql @@ -0,0 +1,7 @@ +CREATE FUNCTION commsdb.find_message_hash_by_text( + in_message TEXT +) +RETURNS BINARY(32) +BEGIN + RETURN UNHEX(SHA(in_message)); +END; diff --git a/src/contract/__test_assets__/mysql.multischema/definitions/init.yml b/src/contract/__test_assets__/mysql.multischema/definitions/init.yml new file mode 100644 index 0000000..7b5aff5 --- /dev/null +++ b/src/contract/__test_assets__/mysql.multischema/definitions/init.yml @@ -0,0 +1,9 @@ +- type: change + path: './init/databases.sql' + id: 'init_databases' + reappliable: false + +- type: change + id: 'init_service_user' + path: './init/service_user.sql' + reappliable: true diff --git a/src/contract/__test_assets__/mysql.multischema/definitions/init/databases.sql b/src/contract/__test_assets__/mysql.multischema/definitions/init/databases.sql new file mode 100644 index 0000000..c238b38 --- /dev/null +++ b/src/contract/__test_assets__/mysql.multischema/definitions/init/databases.sql @@ -0,0 +1,2 @@ +CREATE DATABASE commsdb; +CREATE DATABASE spacedb; diff --git a/src/contract/__test_assets__/mysql.multischema/definitions/init/service_user.sql b/src/contract/__test_assets__/mysql.multischema/definitions/init/service_user.sql new file mode 100644 index 0000000..175ffae --- /dev/null +++ b/src/contract/__test_assets__/mysql.multischema/definitions/init/service_user.sql @@ -0,0 +1,4 @@ +CREATE USER 'user_name'@'%' IDENTIFIED BY '__CHANGE_M3__'; +GRANT ALL PRIVILEGES ON `awesomedb`.* TO 'user_name'@'%'; +GRANT ALL PRIVILEGES ON `commsdb`.* TO 'user_name'@'%'; +GRANT ALL PRIVILEGES ON `spacedb`.* TO 'user_name'@'%'; diff --git a/src/contract/__test_assets__/mysql.multischema/definitions/procedures.yml b/src/contract/__test_assets__/mysql.multischema/definitions/procedures.yml new file mode 100644 index 0000000..1099f37 --- /dev/null +++ b/src/contract/__test_assets__/mysql.multischema/definitions/procedures.yml @@ -0,0 +1,2 @@ +- type: resource + path: './procedures/upsert_message.sql' diff --git a/src/contract/__test_assets__/mysql.multischema/definitions/procedures/upsert_message.sql b/src/contract/__test_assets__/mysql.multischema/definitions/procedures/upsert_message.sql new file mode 100644 index 0000000..3dde9fb --- /dev/null +++ b/src/contract/__test_assets__/mysql.multischema/definitions/procedures/upsert_message.sql @@ -0,0 +1,17 @@ +CREATE PROCEDURE commsdb.upsert_message( + IN in_message TEXT +) +BEGIN + DECLARE v_message_hash BINARY(32); + DECLARE v_message_id BIGINT; + + -- assert the message exists + SET v_message_id = find_message_id_by_text(in_message); + IF (v_message_id IS null) THEN + SET v_message_hash = find_message_hash_by_text(in_message); + INSERT INTO messages + (text, hash) + VALUES + (in_message, v_message_hash); + END IF; +END; diff --git a/src/contract/__test_assets__/mysql.multischema/definitions/tables.yml b/src/contract/__test_assets__/mysql.multischema/definitions/tables.yml new file mode 100644 index 0000000..b107616 --- /dev/null +++ b/src/contract/__test_assets__/mysql.multischema/definitions/tables.yml @@ -0,0 +1,10 @@ +- type: resource + path: './tables/notification.sql' + +- type: resource + path: './tables/notification_version.sql' + +- type: resource + path: ./tables/spaceship.sql +- type: resource + path: ./tables/spaceship_cargo.sql diff --git a/src/contract/__test_assets__/mysql.multischema/definitions/tables/notification.sql b/src/contract/__test_assets__/mysql.multischema/definitions/tables/notification.sql new file mode 100644 index 0000000..9a2aaa1 --- /dev/null +++ b/src/contract/__test_assets__/mysql.multischema/definitions/tables/notification.sql @@ -0,0 +1,18 @@ +CREATE TABLE commsdb.notification ( + -- meta + `id` BIGINT NOT NULL AUTO_INCREMENT, -- pk + `uuid` CHAR(36) NOT NULL, -- uuid + `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, + + -- static data + `user_uuid` CHAR(36) NOT NULL, -- uuid + `method` ENUM('APP', 'SMS') NOT NULL, + `address` VARCHAR(255) NOT NULL, -- e.g, a phone number + `message_id` BIGINT NOT NULL, -- pointer to message to send; one:many + `wait_until` DATETIME(3) NOT NULL, -- when can we send the notification + + -- meta meta + PRIMARY KEY (`id`), + UNIQUE INDEX notifications_ux1 (`user_uuid`, `method`, `address`, `message_id`, `wait_until`) + -- CONSTRAINT notification_fk1 FOREIGN KEY (`message_id`) REFERENCES message(id) +) ENGINE = InnoDB; diff --git a/src/contract/__test_assets__/mysql.multischema/definitions/tables/notification_version.sql b/src/contract/__test_assets__/mysql.multischema/definitions/tables/notification_version.sql new file mode 100644 index 0000000..b0747d0 --- /dev/null +++ b/src/contract/__test_assets__/mysql.multischema/definitions/tables/notification_version.sql @@ -0,0 +1,13 @@ +CREATE TABLE commsdb.notification_version ( + -- meta + `notification_id` BIGINT NOT NULL, -- fk pointing to static entity + `effective_at` DATETIME(6) NOT NULL, -- the user should define the effective_at timestamp + + -- mutatable data + `status` ENUM('WAITING', 'QUEUED', 'SENT') NOT NULL, + + -- meta meta + PRIMARY KEY (`notification_id`, `effective_at`), + INDEX notification_version_ix1 (`status`), + CONSTRAINT notification_version_fk1 FOREIGN KEY (`notification_id`) REFERENCES notification(id) +) ENGINE = InnoDB; diff --git a/src/contract/__test_assets__/mysql.multischema/definitions/tables/spaceship.sql b/src/contract/__test_assets__/mysql.multischema/definitions/tables/spaceship.sql new file mode 100644 index 0000000..2be7750 --- /dev/null +++ b/src/contract/__test_assets__/mysql.multischema/definitions/tables/spaceship.sql @@ -0,0 +1,4 @@ +CREATE TABLE spacedb.spaceship ( + `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `name` VARCHAR(191) NOT NULL +) ENGINE = InnoDB; diff --git a/src/contract/__test_assets__/mysql.multischema/definitions/tables/spaceship_cargo.sql b/src/contract/__test_assets__/mysql.multischema/definitions/tables/spaceship_cargo.sql new file mode 100644 index 0000000..5cb2299 --- /dev/null +++ b/src/contract/__test_assets__/mysql.multischema/definitions/tables/spaceship_cargo.sql @@ -0,0 +1,8 @@ +CREATE TABLE spacedb.spaceship_cargo ( + `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `spaceship_id` BIGINT NOT NULL, + `owner` VARCHAR(191) NOT NULL, + `purpose` VARCHAR(191) NOT NULL, + `weight` VARCHAR(191) NOT NULL, + CONSTRAINT spaceship_cargo_fk1 FOREIGN KEY (`spaceship_id`) REFERENCES spaceship(id) +) ENGINE = InnoDB; diff --git a/src/contract/__test_assets__/mysql.multischema/definitions/views.yml b/src/contract/__test_assets__/mysql.multischema/definitions/views.yml new file mode 100644 index 0000000..d3ef661 --- /dev/null +++ b/src/contract/__test_assets__/mysql.multischema/definitions/views.yml @@ -0,0 +1,2 @@ +- type: resource + path: './views/view_spaceship_with_cargo.sql' diff --git a/src/contract/__test_assets__/mysql.multischema/definitions/views/view_spaceship_with_cargo.sql b/src/contract/__test_assets__/mysql.multischema/definitions/views/view_spaceship_with_cargo.sql new file mode 100644 index 0000000..0ca24ee --- /dev/null +++ b/src/contract/__test_assets__/mysql.multischema/definitions/views/view_spaceship_with_cargo.sql @@ -0,0 +1,8 @@ +CREATE VIEW `spacedb`.`view_spaceship_with_cargo` AS + SELECT + s.id as id, + s.name as name, + SUM(sc.weight) as total_cargo_weight, + GROUP_CONCAT(DISTINCT sc.purpose) as all_cargo_purpose + FROM spacedb.spaceship s + JOIN spacedb.spaceship_cargo sc on sc.spaceship_id = s.id; diff --git a/src/contract/commands/__snapshots__/apply.integration.test.ts.snap b/src/contract/commands/__snapshots__/apply.integration.test.ts.snap index 4bc205c..e6faf4a 100644 --- a/src/contract/commands/__snapshots__/apply.integration.test.ts.snap +++ b/src/contract/commands/__snapshots__/apply.integration.test.ts.snap @@ -1,6 +1,94 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`apply mysql should have an expected appearance when all changes need to be applied 1`] = ` +exports[`apply mysql multiple schemas should have an expected appearance when all changes need to be applied 1`] = ` +" Applying required actions... + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:23:11) + + + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:24:11) + + ✔ [APPLY] src/contract/__test_assets__/mysql.multischema/definitions/init/databases.sql (change:init_databases) + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:45:15) + + ✔ [APPLY] src/contract/__test_assets__/mysql.multischema/definitions/init/service_user.sql (change:init_service_user) + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:45:15) + + ✔ [APPLY] src/contract/__test_assets__/mysql.multischema/definitions/tables/notification.sql (resource:table:commsdb.notification) + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:45:15) + + ✔ [APPLY] src/contract/__test_assets__/mysql.multischema/definitions/tables/notification_version.sql (resource:table:commsdb.notification_version) + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:45:15) + + ✔ [APPLY] src/contract/__test_assets__/mysql.multischema/definitions/tables/spaceship.sql (resource:table:spacedb.spaceship) + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:45:15) + + ✔ [APPLY] src/contract/__test_assets__/mysql.multischema/definitions/tables/spaceship_cargo.sql (resource:table:spacedb.spaceship_cargo) + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:45:15) + + ✔ [APPLY] src/contract/__test_assets__/mysql.multischema/definitions/views/view_spaceship_with_cargo.sql (resource:view:spacedb.view_spaceship_with_cargo) + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:45:15) + + ✔ [APPLY] src/contract/__test_assets__/mysql.multischema/definitions/functions/find_message_hash_by_text.sql (resource:function:commsdb.find_message_hash_by_text) + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:45:15) + + ✔ [APPLY] src/contract/__test_assets__/mysql.multischema/definitions/procedures/upsert_message.sql (resource:procedure:commsdb.upsert_message) + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:45:15) + +" +`; + +exports[`apply mysql multiple schemas should have an expected appearance when all changes need to be reapplied, if possible 1`] = ` +" Applying required actions... + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:23:11) + + + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:24:11) + + ↓ [MANUAL_MIGRATION] src/contract/__test_assets__/mysql.multischema/definitions/tables/notification.sql (resource:table:commsdb.notification) + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:36:15) + + ↓ [MANUAL_MIGRATION] src/contract/__test_assets__/mysql.multischema/definitions/tables/notification_version.sql (resource:table:commsdb.notification_version) + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:36:15) + + ↓ [MANUAL_MIGRATION] src/contract/__test_assets__/mysql.multischema/definitions/tables/spaceship.sql (resource:table:spacedb.spaceship) + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:36:15) + + ↓ [MANUAL_MIGRATION] src/contract/__test_assets__/mysql.multischema/definitions/tables/spaceship_cargo.sql (resource:table:spacedb.spaceship_cargo) + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:36:15) + + ✔ [REAPPLY] src/contract/__test_assets__/mysql.multischema/definitions/views/view_spaceship_with_cargo.sql (resource:view:spacedb.view_spaceship_with_cargo) + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:45:15) + + ✔ [REAPPLY] src/contract/__test_assets__/mysql.multischema/definitions/functions/find_message_hash_by_text.sql (resource:function:commsdb.find_message_hash_by_text) + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:45:15) + + ✔ [REAPPLY] src/contract/__test_assets__/mysql.multischema/definitions/procedures/upsert_message.sql (resource:procedure:commsdb.upsert_message) + + at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:45:15) + +" +`; + +exports[`apply mysql single schema should have an expected appearance when all changes need to be applied 1`] = ` " Applying required actions...   at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:23:11) @@ -52,7 +140,7 @@ exports[`apply mysql should have an expected appearance when all changes need to " `; -exports[`apply mysql should have an expected appearance when all changes need to be reapplied, if possible 1`] = ` +exports[`apply mysql single schema should have an expected appearance when all changes need to be reapplied, if possible 1`] = ` " Applying required actions...   at applyPlans (src/logic/commands/applyPlans/applyPlans.ts:23:11) diff --git a/src/contract/commands/apply.integration.test.ts b/src/contract/commands/apply.integration.test.ts index fe37427..a728575 100644 --- a/src/contract/commands/apply.integration.test.ts +++ b/src/contract/commands/apply.integration.test.ts @@ -26,80 +26,188 @@ describe('apply', () => { afterAll(async () => { await connection.end(); }); - it('should have an expected appearance when all changes need to be applied', async () => { - // ensure previous runs dont break this test - await connection.query({ sql: 'DROP USER IF EXISTS user_name' }); - await connection.query({ sql: 'DROP TABLE IF EXISTS data_source' }); - await connection.query({ - sql: 'DROP TABLE IF EXISTS notification_version', - }); - await connection.query({ sql: 'DROP TABLE IF EXISTS notification' }); - await connection.query({ sql: 'DROP TABLE IF EXISTS spaceship_cargo' }); - await connection.query({ sql: 'DROP TABLE IF EXISTS spaceship' }); - await connection.query({ - sql: 'DROP VIEW IF EXISTS view_spaceship_with_cargo', - }); - await connection.query({ - sql: 'DROP FUNCTION IF EXISTS find_message_hash_by_text', - }); - await connection.query({ - sql: 'DROP PROCEDURE IF EXISTS upsert_message', - }); - await connection.query({ sql: 'DELETE FROM schema_control_change_log' }); + describe('single schema', () => { + it('should have an expected appearance when all changes need to be applied', async () => { + // ensure previous runs dont break this test + await connection.query({ sql: 'DROP USER IF EXISTS user_name' }); + await connection.query({ sql: 'DROP TABLE IF EXISTS data_source' }); + await connection.query({ + sql: 'DROP TABLE IF EXISTS notification_version', + }); + await connection.query({ sql: 'DROP TABLE IF EXISTS notification' }); + await connection.query({ sql: 'DROP TABLE IF EXISTS spaceship_cargo' }); + await connection.query({ sql: 'DROP TABLE IF EXISTS spaceship' }); + await connection.query({ + sql: 'DROP VIEW IF EXISTS view_spaceship_with_cargo', + }); + await connection.query({ + sql: 'DROP FUNCTION IF EXISTS find_message_hash_by_text', + }); + await connection.query({ + sql: 'DROP PROCEDURE IF EXISTS upsert_message', + }); + await connection.query({ + sql: 'DELETE FROM schema_control_change_log', + }); - // run the test - stdout.stripColor = false; // dont strip color - // stdout.print = true; - stdout.start(); - await Apply.run([ - '-c', - `${__dirname}/../__test_assets__/mysql/control.yml`, - ]); - stdout.stop(); - const output = stdout.output - .split('\n') - .filter((line) => !line.includes('console.log')) - .join('\n') // strip the console log portion - .replace(/\[\d\d:\d\d:\d\d\]/g, ''); // remove all timestamps, since they change over time... - expect(output).toMatchSnapshot(); - }); - it('should have an expected appearance when all changes need to be reapplied, if possible', async () => { - // ensure previous runs dont break this test - await connection.query({ sql: 'DROP USER IF EXISTS user_name' }); - await connection.query({ sql: 'DROP TABLE IF EXISTS data_source' }); - await connection.query({ - sql: 'DROP TABLE IF EXISTS notification_version', + // run the test + stdout.stripColor = false; // dont strip color + // stdout.print = true; + stdout.start(); + await Apply.run([ + '-c', + `${__dirname}/../__test_assets__/mysql/control.yml`, + ]); + stdout.stop(); + const output = stdout.output + .split('\n') + .filter((line) => !line.includes('console.log')) + .join('\n') // strip the console log portion + .replace(/\[\d\d:\d\d:\d\d\]/g, ''); // remove all timestamps, since they change over time... + expect(output).toMatchSnapshot(); }); - await connection.query({ sql: 'DROP TABLE IF EXISTS notification' }); - await connection.query({ - sql: 'DROP FUNCTION IF EXISTS find_message_hash_by_text', + it('should have an expected appearance when all changes need to be reapplied, if possible', async () => { + // ensure previous runs dont break this test + await connection.query({ sql: 'DROP USER IF EXISTS user_name' }); + await connection.query({ sql: 'DROP TABLE IF EXISTS data_source' }); + await connection.query({ + sql: 'DROP TABLE IF EXISTS notification_version', + }); + await connection.query({ sql: 'DROP TABLE IF EXISTS notification' }); + await connection.query({ + sql: 'DROP FUNCTION IF EXISTS find_message_hash_by_text', + }); + await connection.query({ + sql: 'DROP PROCEDURE IF EXISTS upsert_message', + }); + await connection.query({ + sql: 'DELETE FROM schema_control_change_log', + }); + + // apply the definitions the first time + await Apply.run([ + '-c', + `${__dirname}/../__test_assets__/mysql/control.yml`, + ]); + + // reapply the definitions + stdout.stripColor = false; // dont strip color + // stdout.print = true; + stdout.start(); + await Apply.run([ + '-c', + `${__dirname}/../__test_assets__/mysql/control.yml`, + ]); + stdout.stop(); + const output = stdout.output + .split('\n') + .filter((line) => !line.includes('console.log')) + .join('\n') // strip the console log portion + .replace(/\[\d\d:\d\d:\d\d\]/g, ''); // remove all timestamps, since they change over time... + expect(output).toMatchSnapshot(); }); - await connection.query({ - sql: 'DROP PROCEDURE IF EXISTS upsert_message', + }); + describe('multiple schemas', () => { + it('should have an expected appearance when all changes need to be applied', async () => { + // ensure previous runs dont break this test + await connection.query({ sql: 'DROP DATABASE IF EXISTS commsdb' }); + await connection.query({ sql: 'DROP DATABASE IF EXISTS spacedb' }); + await connection.query({ sql: 'DROP USER IF EXISTS user_name' }); + await connection.query({ + sql: 'DROP TABLE IF EXISTS commsdb.notification_version', + }); + await connection.query({ + sql: 'DROP TABLE IF EXISTS commsdb.notification', + }); + await connection.query({ + sql: 'DROP TABLE IF EXISTS spacedb.spaceship_cargo', + }); + await connection.query({ + sql: 'DROP TABLE IF EXISTS spacedb.spaceship', + }); + await connection.query({ + sql: 'DROP VIEW IF EXISTS spacedb.view_spaceship_with_cargo', + }); + await connection.query({ + sql: 'DROP FUNCTION IF EXISTS commsdb.find_message_hash_by_text', + }); + await connection.query({ + sql: 'DROP PROCEDURE IF EXISTS commsdb.upsert_message', + }); + await connection.query({ + sql: 'DELETE FROM schema_control_change_log', + }); + + // run the test + stdout.stripColor = false; // dont strip color + stdout.print = true; + stdout.start(); + await Apply.run([ + '-c', + `${__dirname}/../__test_assets__/mysql.multischema/control.yml`, + ]); + stdout.stop(); + console.log('post stdout.stop'); + + const output = stdout.output + .split('\n') + .filter((line) => !line.includes('console.log')) + .join('\n') // strip the console log portion + .replace(/\[\d\d:\d\d:\d\d\]/g, ''); // remove all timestamps, since they change over time... + expect(output).toMatchSnapshot(); }); - await connection.query({ sql: 'DELETE FROM schema_control_change_log' }); + it('should have an expected appearance when all changes need to be reapplied, if possible', async () => { + // ensure previous runs dont break this test + await connection.query({ sql: 'DROP DATABASE IF EXISTS commsdb' }); + await connection.query({ sql: 'DROP DATABASE IF EXISTS spacedb' }); + await connection.query({ sql: 'DROP USER IF EXISTS user_name' }); + await connection.query({ + sql: 'DROP TABLE IF EXISTS commsdb.notification_version', + }); + await connection.query({ + sql: 'DROP TABLE IF EXISTS commsdb.notification', + }); + await connection.query({ + sql: 'DROP FUNCTION IF EXISTS commsdb.find_message_hash_by_text', + }); + await connection.query({ + sql: 'DROP PROCEDURE IF EXISTS commsdb.upsert_message', + }); + await connection.query({ + sql: 'DROP VIEW IF EXISTS spacedb.view_spaceship_with_cargo', + }); + await connection.query({ + sql: 'DROP TABLE IF EXISTS spacedb.spaceship_cargo', + }); + await connection.query({ + sql: 'DROP TABLE IF EXISTS spacedb.spaceship', + }); + await connection.query({ + sql: 'DELETE FROM schema_control_change_log', + }); - // apply the definitions the first time - await Apply.run([ - '-c', - `${__dirname}/../__test_assets__/mysql/control.yml`, - ]); + // apply the definitions the first time + await Apply.run([ + '-c', + `${__dirname}/../__test_assets__/mysql.multischema/control.yml`, + ]); - // reapply the definitions - stdout.stripColor = false; // dont strip color - // stdout.print = true; - stdout.start(); - await Apply.run([ - '-c', - `${__dirname}/../__test_assets__/mysql/control.yml`, - ]); - stdout.stop(); - const output = stdout.output - .split('\n') - .filter((line) => !line.includes('console.log')) - .join('\n') // strip the console log portion - .replace(/\[\d\d:\d\d:\d\d\]/g, ''); // remove all timestamps, since they change over time... - expect(output).toMatchSnapshot(); + // reapply the definitions + stdout.stripColor = false; // dont strip color + // stdout.print = true; + stdout.start(); + await Apply.run([ + '-c', + `${__dirname}/../__test_assets__/mysql.multischema/control.yml`, + ]); + stdout.stop(); + const output = stdout.output + .split('\n') + .filter((line) => !line.includes('console.log')) + .join('\n') // strip the console log portion + .replace(/\[\d\d:\d\d:\d\d\]/g, ''); // remove all timestamps, since they change over time... + expect(output).toMatchSnapshot(); + }); }); }); describe('postgres', () => { diff --git a/src/logic/config/getConfig/readConfig/validateAndHydrateDefinitionsYmlContents/hydrateResourceDefinitionContent/extractResourceTypeAndNameFromDDL.test.ts b/src/logic/config/getConfig/readConfig/validateAndHydrateDefinitionsYmlContents/hydrateResourceDefinitionContent/extractResourceTypeAndNameFromDDL.test.ts index b12ee4f..5098ea0 100644 --- a/src/logic/config/getConfig/readConfig/validateAndHydrateDefinitionsYmlContents/hydrateResourceDefinitionContent/extractResourceTypeAndNameFromDDL.test.ts +++ b/src/logic/config/getConfig/readConfig/validateAndHydrateDefinitionsYmlContents/hydrateResourceDefinitionContent/extractResourceTypeAndNameFromDDL.test.ts @@ -7,7 +7,7 @@ describe('extractResourceTypeAndNameFromDDL', () => { try { extractResourceTypeAndNameFromDDL({ ddl }); } catch (error) { - expect(error.message).toEqual( + expect(error.message).toContain( 'resource creation type and name could not be found in ddl', ); } @@ -72,6 +72,18 @@ describe('extractResourceTypeAndNameFromDDL', () => { expect(name).toEqual('super_cool_table'); expect(type).toEqual(ResourceType.TABLE); }); + it('should be able to find a resource name even if scoped', () => { + const ddl = 'CREATE TABLE cooldb.super_cool_table ( ... )'; + const { name, type } = extractResourceTypeAndNameFromDDL({ ddl }); + expect(name).toEqual('cooldb.super_cool_table'); + expect(type).toEqual(ResourceType.TABLE); + }); + it('should be able to find a resource name even if scoped and backticked', () => { + const ddl = 'CREATE TABLE `cooldb`.`super_cool_table` ( ... )'; + const { name, type } = extractResourceTypeAndNameFromDDL({ ddl }); + expect(name).toEqual('cooldb.super_cool_table'); + expect(type).toEqual(ResourceType.TABLE); + }); it('should be able to find the resource name even if there is no whitespace between name and definition start', () => { const ddl = 'CREATE TABLE super_cool_table( ... )'; const { name, type } = extractResourceTypeAndNameFromDDL({ ddl }); diff --git a/src/logic/config/getConfig/readConfig/validateAndHydrateDefinitionsYmlContents/hydrateResourceDefinitionContent/extractResourceTypeAndNameFromDDL.ts b/src/logic/config/getConfig/readConfig/validateAndHydrateDefinitionsYmlContents/hydrateResourceDefinitionContent/extractResourceTypeAndNameFromDDL.ts index 15d0966..3f3061f 100644 --- a/src/logic/config/getConfig/readConfig/validateAndHydrateDefinitionsYmlContents/hydrateResourceDefinitionContent/extractResourceTypeAndNameFromDDL.ts +++ b/src/logic/config/getConfig/readConfig/validateAndHydrateDefinitionsYmlContents/hydrateResourceDefinitionContent/extractResourceTypeAndNameFromDDL.ts @@ -1,8 +1,10 @@ +import { HelpfulError } from 'helpful-errors'; + import { ResourceType } from '../../../../../../domain'; // TODO: generalize to other databases with adapter pattern const SQL_TYPE_NAME_CAPTURE_REGEX = - /(?:CREATE|create)(?:\s+)(?:DEFINER=`[a-zA-Z0-9_]+`@`[a-zA-Z0-9_%]+`)?(?:\s*)(?:OR REPLACE )?(PROCEDURE|procedure|FUNCTION|function|TABLE|table|VIEW|view)(?:\s+)(?:`?)(\w+)(?:`?)(?:\s*)(?:\(|AS\s|as\s)/g; // captures type and name from create statements of resources + /(?:CREATE|create)(?:\s+)(?:DEFINER=`[a-zA-Z0-9_]+`@`[a-zA-Z0-9_%]+`)?(?:\s*)(?:OR REPLACE )?(PROCEDURE|procedure|FUNCTION|function|TABLE|table|VIEW|view)(?:\s+)(`?[\w]+`?(?:\.`?[\w]+`?)?)(?:\s*)(?:\(|AS\s|as\s)/g; // captures type and name from create statements of resources const regexTypeMatchToTypeEnum = { PROCEDURE: ResourceType.PROCEDURE, @@ -30,8 +32,9 @@ export const extractResourceTypeAndNameFromDDL = ({ ddl }: { ddl: string }) => { // if no matches, throw error if (!extractionMatches) - throw new Error( + throw new HelpfulError( 'resource creation type and name could not be found in ddl', + { ddl }, ); // format the type @@ -39,7 +42,7 @@ export const extractResourceTypeAndNameFromDDL = ({ ddl }: { ddl: string }) => { const type = regexTypeMatchToTypeEnum[regexTypeMatch]; // extract the name - const name = extractionMatches[2]!; + const name = extractionMatches[2]!.replace(/`/g, ''); // the second capture group is the name, remove any backticks // return type and name return {