@axinom/mosaic-db-common 0.10.0 → 0.11.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/mosaic-db.js +3 -0
- package/dist/cli/commands/generate-vscode-sql-snippets.d.ts +14 -0
- package/dist/cli/commands/generate-vscode-sql-snippets.d.ts.map +1 -0
- package/dist/cli/commands/generate-vscode-sql-snippets.js +206 -0
- package/dist/cli/commands/generate-vscode-sql-snippets.js.map +1 -0
- package/dist/cli/commands/index.d.ts +2 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/index.js +14 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +15 -0
- package/dist/cli/index.js.map +1 -0
- package/migrations/README.md +230 -0
- package/migrations/before-migration/002-error-functions.sql +19 -1
- package/migrations/before-migration/003-validation-functions.sql +267 -43
- package/migrations/before-migration/006-authorization.sql +82 -25
- package/migrations/define/define-functions.sql +469 -73
- package/migrations/define/define-multitenancy-functions.sql +109 -26
- package/migrations/snippets/custom.code-snippets +250 -0
- package/migrations/snippets/multitenancy-custom.code-snippets +122 -0
- package/package.json +7 -2
package/bin/mosaic-db.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CommandModule } from 'yargs';
|
|
2
|
+
/**
|
|
3
|
+
* CLI command options object.
|
|
4
|
+
*/
|
|
5
|
+
interface GenerateSnippetsOptions {
|
|
6
|
+
includeMultitenancySnippets: boolean;
|
|
7
|
+
snippetsFileNameWithoutExtension: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Yargs command module definition for `generate-vscode-sql-snippets` CLI script.
|
|
11
|
+
*/
|
|
12
|
+
export declare const generateVscodeSqlSnippets: CommandModule<unknown, GenerateSnippetsOptions>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=generate-vscode-sql-snippets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-vscode-sql-snippets.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/generate-vscode-sql-snippets.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEtC;;GAEG;AACH,UAAU,uBAAuB;IAC/B,2BAA2B,EAAE,OAAO,CAAC;IACrC,gCAAgC,EAAE,MAAM,CAAC;CAC1C;AAqMD;;GAEG;AACH,eAAO,MAAM,yBAAyB,EAAE,aAAa,CACnD,OAAO,EACP,uBAAuB,CAsBxB,CAAC"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
3
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
4
|
+
var m = o[Symbol.asyncIterator], i;
|
|
5
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
6
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
7
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.generateVscodeSqlSnippets = void 0;
|
|
11
|
+
/* eslint-disable no-console */
|
|
12
|
+
const fs_1 = require("fs");
|
|
13
|
+
const path_1 = require("path");
|
|
14
|
+
const readdir = require("readdirp");
|
|
15
|
+
/**
|
|
16
|
+
* Colors the CLI text in green.
|
|
17
|
+
*/
|
|
18
|
+
const green = (text) => `\u001b[32m${text}\u001b[39m`;
|
|
19
|
+
/**
|
|
20
|
+
* Colors the CLI text in red.
|
|
21
|
+
*/
|
|
22
|
+
const red = (text) => `\u001b[31m${text}\u001b[39m`;
|
|
23
|
+
/**
|
|
24
|
+
* Returns a readable timestamp in current locale time, e.g. `[2021-05-13 13:02:12.685]`
|
|
25
|
+
*/
|
|
26
|
+
const getTimestamp = () => {
|
|
27
|
+
const offset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds
|
|
28
|
+
const localISOTime = new Date(Date.now() - offset)
|
|
29
|
+
.toISOString()
|
|
30
|
+
.slice(0, -1)
|
|
31
|
+
.replace('T', ' ');
|
|
32
|
+
return `[${localISOTime}]`;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Writes generated snippets object to .vscode folder. Creates a folder if it does not exist.
|
|
36
|
+
* Returns a success message.
|
|
37
|
+
*/
|
|
38
|
+
const writeSourceFile = (snippetsFileName, contents) => {
|
|
39
|
+
const outDir = path_1.join(process.env.PROJECT_CWD || process.cwd(), '.vscode');
|
|
40
|
+
if (!fs_1.existsSync(outDir)) {
|
|
41
|
+
fs_1.mkdirSync(outDir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
const filePath = path_1.join(outDir, `${snippetsFileName}.code-snippets`);
|
|
44
|
+
fs_1.writeFileSync(filePath, JSON.stringify(contents, null, 2), 'utf-8');
|
|
45
|
+
return `Success! Snippets file path: ${filePath}`;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Counter for potential unnamed snippets to generate temporary unique names.
|
|
49
|
+
*/
|
|
50
|
+
let unparsedSnippetIndex = 1;
|
|
51
|
+
/**
|
|
52
|
+
* Extracts snippet name and prefix from a line that defines a function.
|
|
53
|
+
* In case it's impossible to extract - placeholder values are used and logged (which is expected to be fixed).
|
|
54
|
+
* @example `CREATE OR REPLACE FUNCTION ax_utils.raise_error(` -> `{"prefix": "ax-raise-error", "snippetName": "Raise Error (Ax Utils)"}
|
|
55
|
+
*/
|
|
56
|
+
const parseFunctionName = (line, snippetJson) => {
|
|
57
|
+
var _a;
|
|
58
|
+
const match = (_a = new RegExp(/CREATE OR REPLACE FUNCTION (.*)\(/i).exec(line)) === null || _a === void 0 ? void 0 : _a[1];
|
|
59
|
+
let prefix = `unnamed-snippet-${unparsedSnippetIndex}`;
|
|
60
|
+
let snippetName = `Unnamed Snippet ${unparsedSnippetIndex}`;
|
|
61
|
+
if (!match) {
|
|
62
|
+
console.log(red(`Unable to parse the name for the following snippet. Naming the snippet ${prefix} instead.`));
|
|
63
|
+
console.log(snippetJson);
|
|
64
|
+
unparsedSnippetIndex++;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
prefix = match;
|
|
68
|
+
const [schemaName, functionName] = match.split('.');
|
|
69
|
+
const format = (text) => text
|
|
70
|
+
.replace(/_/g, ' ')
|
|
71
|
+
.replace(/(^\w{1})|(\s+\w{1})/g, (letter) => letter.toUpperCase());
|
|
72
|
+
snippetName = `${format(functionName)} (${format(schemaName)})`;
|
|
73
|
+
prefix = `ax-${functionName.replace(/_/g, '-')}`;
|
|
74
|
+
}
|
|
75
|
+
return { prefix, snippetName };
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Reads a file to memory as an array, where each element is a new line.
|
|
79
|
+
* Goes through the array and generates sql snippet objects based on file contents.
|
|
80
|
+
* Depends on comment conventions to work properly.
|
|
81
|
+
*/
|
|
82
|
+
const populateSnippets = async (filePath, snippetsObject) => {
|
|
83
|
+
const contents = fs_1.readFileSync(filePath, 'utf-8').split('\n').filter(Boolean);
|
|
84
|
+
const startTag = '/*-snippet';
|
|
85
|
+
const endTag = 'snippet-*/';
|
|
86
|
+
const funcLine = 'create or replace function';
|
|
87
|
+
let readingSnippet = false;
|
|
88
|
+
let snippetStringContents = '';
|
|
89
|
+
let snippetJson = undefined;
|
|
90
|
+
for (const line of contents) {
|
|
91
|
+
if (line.startsWith(startTag)) {
|
|
92
|
+
readingSnippet = true;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (line.startsWith(endTag)) {
|
|
96
|
+
snippetJson = JSON.parse(snippetStringContents);
|
|
97
|
+
snippetStringContents = '';
|
|
98
|
+
readingSnippet = false;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (snippetJson && line.toLowerCase().startsWith(funcLine)) {
|
|
102
|
+
const { prefix, snippetName } = parseFunctionName(line, snippetJson);
|
|
103
|
+
snippetJson = Object.assign({ prefix }, snippetJson); // Adds a property to the first position
|
|
104
|
+
snippetJson['scope'] = 'sql';
|
|
105
|
+
snippetsObject[snippetName] = snippetJson;
|
|
106
|
+
snippetJson = undefined;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (readingSnippet) {
|
|
110
|
+
snippetStringContents += line;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return snippetsObject;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Reads all `.sql` files in `migrations` folder (and sub-folders), extracts snippet json objects from them, and returns a combined object with all snippets.
|
|
117
|
+
*/
|
|
118
|
+
const getParsedSnippets = async (dirPath, includeMultitenancySnippets) => {
|
|
119
|
+
var e_1, _a;
|
|
120
|
+
const parsedSnippets = {};
|
|
121
|
+
try {
|
|
122
|
+
for (var _b = __asyncValues(readdir(dirPath, {
|
|
123
|
+
fileFilter: '*.sql',
|
|
124
|
+
})), _c; _c = await _b.next(), !_c.done;) {
|
|
125
|
+
const { fullPath, path } = _c.value;
|
|
126
|
+
if (!includeMultitenancySnippets && path.includes('multitenancy')) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
console.log(getTimestamp(), `Parsing ${path}`);
|
|
130
|
+
await populateSnippets(fullPath, parsedSnippets);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
134
|
+
finally {
|
|
135
|
+
try {
|
|
136
|
+
if (_c && !_c.done && (_a = _b.return)) await _a.call(_b);
|
|
137
|
+
}
|
|
138
|
+
finally { if (e_1) throw e_1.error; }
|
|
139
|
+
}
|
|
140
|
+
return parsedSnippets;
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* Reads all `.code-snippets` files in `migrations` folder (and sub-folders) and returns a combined object with all snippets.
|
|
144
|
+
* These files contain custom snippets that are not tied to any one utils or define sql function.
|
|
145
|
+
*/
|
|
146
|
+
const getCustomSnippets = async (dirPath, includeMultitenancySnippets) => {
|
|
147
|
+
var e_2, _a;
|
|
148
|
+
let customSnippets = {};
|
|
149
|
+
try {
|
|
150
|
+
for (var _b = __asyncValues(readdir(dirPath, {
|
|
151
|
+
fileFilter: '*.code-snippets',
|
|
152
|
+
})), _c; _c = await _b.next(), !_c.done;) {
|
|
153
|
+
const { fullPath, path } = _c.value;
|
|
154
|
+
if (!includeMultitenancySnippets && path.includes('multitenancy')) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
console.log(getTimestamp(), `Reading ${path}`);
|
|
158
|
+
const fileContents = JSON.parse(fs_1.readFileSync(fullPath, 'utf-8'));
|
|
159
|
+
customSnippets = Object.assign(Object.assign({}, customSnippets), fileContents);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
163
|
+
finally {
|
|
164
|
+
try {
|
|
165
|
+
if (_c && !_c.done && (_a = _b.return)) await _a.call(_b);
|
|
166
|
+
}
|
|
167
|
+
finally { if (e_2) throw e_2.error; }
|
|
168
|
+
}
|
|
169
|
+
return customSnippets;
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* Iterates over all .sql files inside of the `migrations` folder.
|
|
173
|
+
* Parses each file based on comments and function definition conventions.
|
|
174
|
+
* Reads all .code-snippets files inside of the `migrations` folder and adds these snippets to parsed snippets.
|
|
175
|
+
* Generates or updates a VScode snippets file.
|
|
176
|
+
*/
|
|
177
|
+
const generate = async ({ includeMultitenancySnippets, snippetsFileNameWithoutExtension, }) => {
|
|
178
|
+
console.log(getTimestamp(), green(`Starting snippets generation!`));
|
|
179
|
+
const dirPath = path_1.resolve(__dirname, '../../../migrations');
|
|
180
|
+
const parsedSnippets = await getParsedSnippets(dirPath, includeMultitenancySnippets);
|
|
181
|
+
const customSnippets = await getCustomSnippets(dirPath, includeMultitenancySnippets);
|
|
182
|
+
const writeResult = writeSourceFile(snippetsFileNameWithoutExtension, Object.assign(Object.assign({}, parsedSnippets), customSnippets));
|
|
183
|
+
console.log(getTimestamp(), green(writeResult));
|
|
184
|
+
};
|
|
185
|
+
/**
|
|
186
|
+
* Yargs command module definition for `generate-vscode-sql-snippets` CLI script.
|
|
187
|
+
*/
|
|
188
|
+
exports.generateVscodeSqlSnippets = {
|
|
189
|
+
command: 'generate-vscode-sql-snippets',
|
|
190
|
+
describe: 'Creates or updates a mosaic sql snippets file, providing an easier way to generate database migrations.',
|
|
191
|
+
builder: (yargs) => yargs
|
|
192
|
+
.option('includeMultitenancySnippets', {
|
|
193
|
+
alias: 'm',
|
|
194
|
+
describe: 'If set to true, would also include snippets for multitenancy define functions.',
|
|
195
|
+
default: false,
|
|
196
|
+
boolean: true,
|
|
197
|
+
})
|
|
198
|
+
.option('snippetsFileNameWithoutExtension', {
|
|
199
|
+
alias: 'f',
|
|
200
|
+
describe: 'Overrides default snippets file name with a custom value. Extension `code-snippets` will be attached to this value.',
|
|
201
|
+
default: 'mosaic-sql-migrations',
|
|
202
|
+
string: true,
|
|
203
|
+
}),
|
|
204
|
+
handler: generate,
|
|
205
|
+
};
|
|
206
|
+
//# sourceMappingURL=generate-vscode-sql-snippets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-vscode-sql-snippets.js","sourceRoot":"","sources":["../../../src/cli/commands/generate-vscode-sql-snippets.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/index.ts"],"names":[],"mappings":"AAAA,cAAc,gCAAgC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
10
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
__exportStar(require("./generate-vscode-sql-snippets"), exports);
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cli/commands/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,iEAA+C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,GAAG,QAAO,IAOtB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.run = void 0;
|
|
4
|
+
const yargs = require("yargs");
|
|
5
|
+
const commands_1 = require("./commands");
|
|
6
|
+
const run = () => {
|
|
7
|
+
yargs
|
|
8
|
+
.scriptName('mosaic-db')
|
|
9
|
+
.command(commands_1.generateVscodeSqlSnippets)
|
|
10
|
+
.demandCommand()
|
|
11
|
+
.help()
|
|
12
|
+
.epilog(`For more information, visit https://portal.axinom.com.`).argv;
|
|
13
|
+
};
|
|
14
|
+
exports.run = run;
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";;;AAAA,+BAA+B;AAC/B,yCAAuD;AAEhD,MAAM,GAAG,GAAG,GAAS,EAAE;IAC5B,KAAK;SACF,UAAU,CAAC,WAAW,CAAC;SACvB,OAAO,CAAC,oCAAyB,CAAC;SAClC,aAAa,EAAE;SACf,IAAI,EAAE;SACN,MAAM,CAAC,wDAAwD,CAAC,CAAC,IAAI,CAAC;AAC3E,CAAC,CAAC;AAPW,QAAA,GAAG,OAOd"}
|
package/migrations/README.md
CHANGED
|
@@ -43,3 +43,233 @@ They reside in their own SQL schema to which the GraphQL role does not have
|
|
|
43
43
|
access.
|
|
44
44
|
|
|
45
45
|
The define SQL functions depend on the `ax_utils` functions.
|
|
46
|
+
|
|
47
|
+
## VSCode Snippets, Documentation, and Maintainability
|
|
48
|
+
|
|
49
|
+
Both Utility and Define SQL functions are there to simplify creation of database
|
|
50
|
+
migrations. But because they are provided as part of the library and there is no
|
|
51
|
+
intellisense support in sql files - we need to also make these functions
|
|
52
|
+
discoverable and provide documentation and examples to make it easier to use
|
|
53
|
+
them.
|
|
54
|
+
|
|
55
|
+
To help with that - we provide a way to generate a file that contains VSCode
|
|
56
|
+
snippets for all relevant functions, which adds a possibility to scaffold code
|
|
57
|
+
pieces in migration files and easily fill them with relevant data. There are 2
|
|
58
|
+
perspectives that should be kept in mind to make this work: Library Consumer and
|
|
59
|
+
Library Maintainer.
|
|
60
|
+
|
|
61
|
+
For more info on custom VSCode snippets - check here:
|
|
62
|
+
https://code.visualstudio.com/docs/editor/userdefinedsnippets
|
|
63
|
+
|
|
64
|
+
### Library Consumer Perspective
|
|
65
|
+
|
|
66
|
+
From this perspective, the following steps can be performed by the developer:
|
|
67
|
+
|
|
68
|
+
- install or update `@axinom/mosaic-db-common`
|
|
69
|
+
- run the cli command: `yarn mosaic-db generate-vscode-sql-snippets`
|
|
70
|
+
- if it's a multitenant service - run
|
|
71
|
+
`yarn mosaic-db generate-vscode-sql-snippets -m` to include multitenancy
|
|
72
|
+
snippets
|
|
73
|
+
- This generates or updates a `.vscode/mosaic-sql-migrations.code-snippets`
|
|
74
|
+
file, which contains all sql migration code snippets that are supported by the
|
|
75
|
+
currently installed `@axinom/mosaic-db-common` version.
|
|
76
|
+
- This file shall be kept under source control.
|
|
77
|
+
- There is no need to look into the contents of this file unless you are just
|
|
78
|
+
interested.
|
|
79
|
+
- go to `migrations/current.sql` or any other `.sql` file.
|
|
80
|
+
- Start typing `ax-` and intellisense will show a dropdown of all available
|
|
81
|
+
snippets.
|
|
82
|
+
- Use the Arrow Up and Down keys to select different options.
|
|
83
|
+
- Documentation window will be visible with text explanation and preview of
|
|
84
|
+
generated code.
|
|
85
|
+
- If this window is not able to be viewed fully and some content are
|
|
86
|
+
hidden - its size can be changed by dragging the right-bottom corner of
|
|
87
|
+
the window.
|
|
88
|
+
- typing after `ax-` would narrow down the selection. e.g. `ax-auth` would
|
|
89
|
+
find multiple authentication-related snippets, e.g.
|
|
90
|
+
`ax-define-authentication`
|
|
91
|
+
- When fitting snippet is selected - clicking `Enter` would expand it into the
|
|
92
|
+
actual migration code and highlight properties for easier editing
|
|
93
|
+
- It is recommended to edit highlighted values and clicking `Tab` to switch
|
|
94
|
+
to the next highlighted value. Some snippets are constructed in a way to
|
|
95
|
+
modify multiple values at once in such a way, making creating the
|
|
96
|
+
resulting migrations code easier.
|
|
97
|
+
|
|
98
|
+
With this, user is provided with an option to browse available snippets,
|
|
99
|
+
familiarize with them using IDE intellisense capabilities, and simplify the
|
|
100
|
+
process of writing migrations.
|
|
101
|
+
|
|
102
|
+
### Library Maintainer Perspective
|
|
103
|
+
|
|
104
|
+
To provide the user experience from the section above - files in this
|
|
105
|
+
`migrations` folder must be maintained in a certain way and it's good to know
|
|
106
|
+
how things work.
|
|
107
|
+
|
|
108
|
+
The resulting `.vscode/mosaic-sql-migrations.code-snippets` file is generated
|
|
109
|
+
based on 2 different sources (combining contents of both):
|
|
110
|
+
|
|
111
|
+
- from all `.sql` files inside of the `migrations` folders and sub-folders
|
|
112
|
+
(including `before-migration` and `define`)
|
|
113
|
+
- from all `.code-snippets` files inside of the `migrations` folders and
|
|
114
|
+
sub-folders, which contains ready-to-use snippets that are not related to any
|
|
115
|
+
single `ax_utils` or `ax_define` sql function. (can be related to no functions
|
|
116
|
+
or to multiple functions)
|
|
117
|
+
|
|
118
|
+
To be able to generate SQL snippets from sql files - certain conventions must be
|
|
119
|
+
followed when creating/modifying sql functions.
|
|
120
|
+
|
|
121
|
+
In it's simplified form, the function example can look like this:
|
|
122
|
+
|
|
123
|
+
```SQL
|
|
124
|
+
/*-snippet
|
|
125
|
+
{
|
|
126
|
+
"body": [
|
|
127
|
+
"SELECT ax_define.function_name('${1:column_name}');"
|
|
128
|
+
],
|
|
129
|
+
"description": [
|
|
130
|
+
"Description of function that will be visible in SQL snippet"
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
snippet-*/
|
|
134
|
+
CREATE OR REPLACE FUNCTION ax_define.function_name(column_name text)
|
|
135
|
+
RETURNS void LANGUAGE plpgsql AS $$
|
|
136
|
+
BEGIN
|
|
137
|
+
-- Function logic which is not important in this particular case
|
|
138
|
+
END;
|
|
139
|
+
$$;
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The logic that generates the SQL snippet performs the following steps:
|
|
143
|
+
|
|
144
|
+
- Parses the SQL file into memory and read it line by line
|
|
145
|
+
- if line starts with `/*-snippet` - starts recording of lines after it
|
|
146
|
+
- if line starts with `snippet-*/` - recording of lines is stopped.
|
|
147
|
+
- Content that is recorder is passed to `JSON.Parse` and converted from string
|
|
148
|
+
to json object
|
|
149
|
+
- Reading of lines continues, until a line is found that starts with
|
|
150
|
+
`CREATE OR REPLACE FUNCTION`
|
|
151
|
+
- Logic reads the function schema and name and generates a json object name
|
|
152
|
+
and snippet name. For the example above, it would be
|
|
153
|
+
`Function Name (Ax Define)` and `ax-function-name`
|
|
154
|
+
- Because it's theoretically possible to have a function with same name in
|
|
155
|
+
multiple database schemas, (and we can potentially have
|
|
156
|
+
`ax_utils.function_name` - we preserve schema name in form of `(Ax Define)`
|
|
157
|
+
suffix, since json function name is the one that must be unique, snippet
|
|
158
|
+
names can have duplicates)
|
|
159
|
+
- Once these steps are performed - a json snippet object is saved for this
|
|
160
|
+
function and parsing continues anew. The resulting snippet would look like
|
|
161
|
+
this:
|
|
162
|
+
- scope is always `sql` and is attached at the end of the object
|
|
163
|
+
|
|
164
|
+
```JSON
|
|
165
|
+
"Function Name (Ax Define)": {
|
|
166
|
+
"prefix": "ax-function-name",
|
|
167
|
+
"body": [
|
|
168
|
+
"SELECT ax_define.function_name('${1:column_name}');"
|
|
169
|
+
],
|
|
170
|
+
"description": [
|
|
171
|
+
"Description of function that will be visible in SQL snippet"
|
|
172
|
+
],
|
|
173
|
+
"scope": "sql"
|
|
174
|
+
},
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
As for `migrations/snippets` files - objects like the one above must be defined
|
|
178
|
+
there and files are read as is and merged into resulting
|
|
179
|
+
`.vscode/mosaic-sql-migrations.code-snippets` file.
|
|
180
|
+
|
|
181
|
+
### Development Tips
|
|
182
|
+
|
|
183
|
+
The way to develop and test the snippets code would look like this:
|
|
184
|
+
|
|
185
|
+
- If you modify typescript parts of `db-common` - run `yarn dev` for continuous
|
|
186
|
+
updates that will trigger on changes.
|
|
187
|
+
- If only sql or code-snippets files are modified - no need to keep the lib
|
|
188
|
+
running.
|
|
189
|
+
- Once adjustments are made - save them and run
|
|
190
|
+
`yarn mosaic-db generate-vscode-sql-snippets -m` in some backend service in
|
|
191
|
+
Navy repository (that has `@axinom/mosaic-db-common` dependency) or from Navy
|
|
192
|
+
repository root.
|
|
193
|
+
- `.vscode/mosaic-sql-migrations.code-snippets` will be updated
|
|
194
|
+
- go to any `.sql` file in monorepo (preferably `migrations/current.sql` in one
|
|
195
|
+
of the backend services) and test the added/updated snippet there.
|
|
196
|
+
|
|
197
|
+
When maintaining comment snippet sections of sql functions, it's good to keep in
|
|
198
|
+
mind, that they are fed into `JSON.Parse` as is, and therefore must be valid
|
|
199
|
+
json objects. This means that if
|
|
200
|
+
`yarn mosaic-db generate-vscode-sql-snippets -m` fails - chances are - json
|
|
201
|
+
object is not valid.
|
|
202
|
+
|
|
203
|
+
For example:
|
|
204
|
+
|
|
205
|
+
- `SyntaxError: Unexpected token ] in JSON at position 1035` might signify that
|
|
206
|
+
there is an extra `,` added at the end of the last string of the array which
|
|
207
|
+
must be removed.
|
|
208
|
+
- Similar error with other token value could signify that some character, for
|
|
209
|
+
example `"` must be escaped like `\"`.
|
|
210
|
+
- You might have a regex inside of the snippet that end with `*/`, which will be
|
|
211
|
+
recognized as a closing tag for the comment.
|
|
212
|
+
- In such situation - either modify the regex or move the whole snippet into
|
|
213
|
+
one of the `migrations/snippets` files.
|
|
214
|
+
|
|
215
|
+
Another thing to keep in mind, is that since `ax_utils` are updated on the fly
|
|
216
|
+
in projects whenever a new migration is applied - extra care must be done when
|
|
217
|
+
doing changes in `before-migration` files.
|
|
218
|
+
|
|
219
|
+
- Do not rename parameters of `ax_utils` functions, even if function name stays
|
|
220
|
+
the same. PostgreSQL recognizes that as a breaking change.
|
|
221
|
+
- Acceptable is to rename the column and to add something like
|
|
222
|
+
`DROP FUNCTION IF EXISTS ax_utils.function_name(TEXT, TEXT, TEXT);` before
|
|
223
|
+
definition of affected function. This way, old function will be dropped and
|
|
224
|
+
new property name would be applied. (but keep the number and order of
|
|
225
|
+
parameters the same)
|
|
226
|
+
- Do not just delete `ax_utils` functions. Because they can already be used by
|
|
227
|
+
some projects, they must stay, even if they are not used.
|
|
228
|
+
- Removing the snippet comment would make it harder to discover them, so that
|
|
229
|
+
might be a valid option.
|
|
230
|
+
- But modifying the snippet to indicate that ax_utils function is
|
|
231
|
+
deprecated/obsolete is a better option.
|
|
232
|
+
- The only exception to this is early stages of development, where we do not
|
|
233
|
+
have non-axinom projects and are 100% that `ax_utils` function is not used
|
|
234
|
+
in any project.
|
|
235
|
+
- And even in this case, if dropped function was already released - an
|
|
236
|
+
explicit drop statement must be added, e.g.
|
|
237
|
+
|
|
238
|
+
```SQL
|
|
239
|
+
-- TODO: Remove these after 01.01.2023
|
|
240
|
+
DROP FUNCTION IF EXISTS ax_utils.function_name(TEXT, TEXT, TEXT);
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The TODO comment is added to signify when this drop statement can be removed.
|
|
244
|
+
Assumption is that one year is enough for for a library to be updated at least
|
|
245
|
+
once for this statement to be executed in consuming services.
|
|
246
|
+
|
|
247
|
+
Worse case scenario:
|
|
248
|
+
|
|
249
|
+
- Someone uses the function that will be dropped in the future.
|
|
250
|
+
- That someone updates the library to the latest version where this function no
|
|
251
|
+
longer exists and drop statement is present.
|
|
252
|
+
- A bug is introduced and (hopefully) reported to us.
|
|
253
|
+
- We provide the code version of dropped function, which can be be explicitly
|
|
254
|
+
added as a script to run before each migration is applied in affected project.
|
|
255
|
+
- example -
|
|
256
|
+
`services/video/service/migrations/after-reset/initial-define-functions.sql`,
|
|
257
|
+
but instead - use it before migrations.
|
|
258
|
+
|
|
259
|
+
Also, some notes about the contents of snippets comment section:
|
|
260
|
+
|
|
261
|
+
- Both body and description are string arrays to make it possible to expand both
|
|
262
|
+
sections with more text, while keeping it all on the screen.
|
|
263
|
+
- If text contents are short - both can be changed from arrays of strings to
|
|
264
|
+
just strings.
|
|
265
|
+
- Snippets will be displayed differently depending on existance of `\n` inside
|
|
266
|
+
of the `description`.
|
|
267
|
+
- If it exists - line break will be added, but it is possible that text might
|
|
268
|
+
not be visible, curring it at the border of the window with `...`. In this
|
|
269
|
+
case - window must be manually resized for contents to be fully read.
|
|
270
|
+
- If `\n` is not specified - intellisense window will try to wrap the text no
|
|
271
|
+
matter the size and always keep it readable, but it might not look very
|
|
272
|
+
good.
|
|
273
|
+
- Formatting capabilities of snippets documentation window is quite limited, so
|
|
274
|
+
it makes sense to always preview it after changes to make sure it is
|
|
275
|
+
acceptable. (perhaps adding a few `\n` line breaks would make it better)
|
|
@@ -1,6 +1,24 @@
|
|
|
1
|
+
/*-snippet
|
|
2
|
+
{
|
|
3
|
+
"body": [
|
|
4
|
+
"PERFORM ax_utils.raise_error('${1:Enter your error message here.}', '${2:UNDEF}');"
|
|
5
|
+
],
|
|
6
|
+
"description": [
|
|
7
|
+
"Raises an error with specified message, code, and optional placeholder parameter(s) for the message.",
|
|
8
|
+
"Code must be an exactly 5-character value (letters or numbers), as per PostgreSQL convention.",
|
|
9
|
+
"If you want to use custom code values, consult PostgreSQL documentation for a list of existing error codes to prevent collisions:",
|
|
10
|
+
"https://www.postgresql.org/docs/13/errcodes-appendix.html\n",
|
|
11
|
+
"'PERFORM' is used because usually, you would want to throw an error inside of the function or condition block.",
|
|
12
|
+
"If you just want to throw an error in the migration file (e.g. to test this snippet) - replace 'PERFORM' with 'SELECT'.\n",
|
|
13
|
+
"Examples:",
|
|
14
|
+
"ax_utils.raise_error('Sample error message without parameters', 'NPERR');",
|
|
15
|
+
"ax_utils.raise_error('Sample error %s with one parameters', 'OPERR', 'message');",
|
|
16
|
+
"ax_utils.raise_error('Sample error %s with %s parameters', 'TPERR', 'message', 'two');"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
snippet-*/
|
|
1
20
|
CREATE OR REPLACE FUNCTION ax_utils.raise_error(
|
|
2
21
|
error_message text,
|
|
3
|
-
-- error code must be exact 5 characters long (letters or numbers)
|
|
4
22
|
error_code text,
|
|
5
23
|
VARIADIC placeholder_values text[] DEFAULT '{}'
|
|
6
24
|
) RETURNS void
|