@balena/pinejs 20.0.2-build-rollbackable-migrations-guide-192216f3f07fa8637bc024bb634227d1b09b26d8-1 → 20.0.2-build-compile-in-as-eq-any-cdd3d0c80b63550d7316739dc6050ed1382f8009-1
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.
@@ -1,15 +1,15 @@
|
|
1
1
|
- commits:
|
2
|
-
- subject:
|
3
|
-
hash:
|
2
|
+
- subject: Compile in as (= ANY($singleListBinding))
|
3
|
+
hash: cdd3d0c80b63550d7316739dc6050ed1382f8009
|
4
4
|
body: ""
|
5
5
|
footer:
|
6
6
|
Change-type: patch
|
7
7
|
change-type: patch
|
8
|
-
author:
|
8
|
+
author: Otavio Jacobi
|
9
9
|
nested: []
|
10
10
|
version: 20.0.2
|
11
11
|
title: ""
|
12
|
-
date: 2025-02-
|
12
|
+
date: 2025-02-04T16:16:17.491Z
|
13
13
|
- commits:
|
14
14
|
- subject: Update postgres Docker tag to v17
|
15
15
|
hash: cfd7adeb986bad2fa6bfdf266ffd36c50c30a9f9
|
package/CHANGELOG.md
CHANGED
@@ -7,7 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
7
7
|
# v20.0.2
|
8
8
|
## (2025-02-04)
|
9
9
|
|
10
|
-
*
|
10
|
+
* Compile in as (= ANY($singleListBinding)) [Otavio Jacobi]
|
11
11
|
|
12
12
|
# v20.0.1
|
13
13
|
## (2025-01-21)
|
package/docs/Migrations.md
CHANGED
@@ -65,6 +65,23 @@ Async migrations are stored in the migrations folder, alongside synchronous migr
|
|
65
65
|
The async migration query must have a `LIMIT` statement to limit the maximum number of affected rows per batch.
|
66
66
|
|
67
67
|
|
68
|
+
### Async migration procedure
|
69
|
+
* Deployment 1
|
70
|
+
- Add new column (with independent sync migration) to contain new data and add code accessing the new column.
|
71
|
+
- Update the service's implementation to set both the old & new column on each write.
|
72
|
+
- The service's implementation should only read the old column since the async migration still migrates data from old column to new column.
|
73
|
+
- Async migrator runs forever.
|
74
|
+
* Deployment 2
|
75
|
+
- Finalize async migration => only sync migration part gets executed.
|
76
|
+
- Sync migration migrates all left over data from old column to new column.
|
77
|
+
- Update the service's implementation to only read the new column, but still write the old one as well.
|
78
|
+
- Mark the old field as optional in the sbvr if it isn't, or set a default value for it.
|
79
|
+
* Deployment 3
|
80
|
+
- Update the service's implementation to stop settings the old column and remove it from the sbvr.
|
81
|
+
- Make the old field NULLable if it isn't.
|
82
|
+
* Deployment 4
|
83
|
+
- Delete the old column with a sync migration.
|
84
|
+
|
68
85
|
### TS migration file format with SQL query string
|
69
86
|
|
70
87
|
The placeholder `%%ASYNC_BATCH_SIZE%%` will be replaced with the value specified by asyncBatchSize parameter
|
@@ -130,35 +147,4 @@ export = {
|
|
130
147
|
|
131
148
|
```
|
132
149
|
### SQL query file (plain text)
|
133
|
-
Plain SQL files are not supported as they cannot bundle async and sync migration statements in one file. Moreover they cannot carry migration metadata.
|
134
|
-
|
135
|
-
## Rollbackable no-down-time suggested migration procedure
|
136
|
-
|
137
|
-
* Deployment 1
|
138
|
-
- Add the new column with an independent sync migration.
|
139
|
-
- Add an Async migrator that starts migrating data from the old column to new column (optional optimization for big tables).
|
140
|
-
- Update the service's implementation to set both the old & new column on each write.
|
141
|
-
The service's implementation should only read the old column, since not all data has been migrated to the new column yet.
|
142
|
-
* Deployment 2
|
143
|
-
- Complete the data migration from the old to the new column:
|
144
|
-
- Either finalize the async migration from deployment 1 if you are using one.
|
145
|
-
This causes the sync part of the migration to execute.
|
146
|
-
- Or write a sync migration otherwise.
|
147
|
-
- Mark the new column as non-NULL if applicable as part of the above migration.
|
148
|
-
- Update the service's implementation to only read the new column, but still write to the old one as well.
|
149
|
-
- Test that the implementation works even if we keep the old column as NOT NULLable.
|
150
|
-
This makes sure that older service instances can still work during the deployment.
|
151
|
-
- Only then mark the old field as NULLable if it isn't.
|
152
|
-
* Deployment 3
|
153
|
-
- Update the service's implementation to stop writing to the old column.
|
154
|
-
- Remove the old column from the SBVR, and add it in the `initSql`/`initSqlPath` of the model
|
155
|
-
(so that the SBVR & DB stay in sync) with a TODO to DROP it from the DB in a follow-up.
|
156
|
-
The old column needs to stay in the DB so that older service instances can still work during the deployment.
|
157
|
-
* Deployment 4
|
158
|
-
- DROP the old column with a sync migration.
|
159
|
-
|
160
|
-
### Testing rollbacks
|
161
|
-
For each step, be sure and utilize testing to ensure you can safely rollback to previous versions of your service even with the updated database schemas (with data) deployed.
|
162
|
-
Before merging each step, try rolling back your development environment to the previous deployment and make sure that everything still works without issues.
|
163
|
-
The old deployment should be able to operate with DB records created by the new deployment and leave the DB in a valid state.
|
164
|
-
Eg: While testing the PR for deployment 2 on your development environment, make sure that if you push deplpoyment 1 everything still works without issues.
|
150
|
+
Plain SQL files are not supported as they cannot bundle async and sync migration statements in one file. Moreover they cannot carry migration metadata.
|
@@ -46,7 +46,7 @@ export const getAndCheckBindValues = async (request, bindings) => {
|
|
46
46
|
const sqlModelTables = sbvrUtils.getAbstractSqlModel(request).tables;
|
47
47
|
return await Promise.all(bindings.map(async (binding) => {
|
48
48
|
let fieldName = '';
|
49
|
-
let
|
49
|
+
let dataType;
|
50
50
|
let value;
|
51
51
|
if (binding[0] === 'Bind') {
|
52
52
|
const bindValue = binding[1];
|
@@ -66,46 +66,56 @@ export const getAndCheckBindValues = async (request, bindings) => {
|
|
66
66
|
if (maybeField == null) {
|
67
67
|
throw new Error(`Could not find field '${fieldName}'`);
|
68
68
|
}
|
69
|
-
|
69
|
+
dataType = maybeField.dataType;
|
70
70
|
}
|
71
71
|
else if (Number.isInteger(bindValue)) {
|
72
72
|
if (bindValue >= odataBinds.length) {
|
73
73
|
console.error(`Invalid binding number '${bindValue}' for binds: `, odataBinds);
|
74
74
|
throw new Error('Invalid binding');
|
75
75
|
}
|
76
|
-
let dataType;
|
77
76
|
[dataType, value] = odataBinds[bindValue];
|
78
|
-
field = { dataType };
|
79
77
|
}
|
80
78
|
else if (typeof bindValue === 'string') {
|
81
79
|
if (!Object.hasOwn(odataBinds, bindValue)) {
|
82
80
|
console.error(`Invalid binding '${bindValue}' for binds: `, odataBinds);
|
83
81
|
throw new Error('Invalid binding');
|
84
82
|
}
|
85
|
-
let dataType;
|
86
83
|
[dataType, value] = odataBinds[bindValue];
|
87
|
-
field = { dataType };
|
88
84
|
}
|
89
85
|
else {
|
90
86
|
throw new Error(`Unknown binding: ${binding}`);
|
91
87
|
}
|
92
88
|
}
|
93
89
|
else {
|
94
|
-
let dataType;
|
95
90
|
[dataType, value] = binding;
|
96
|
-
field = { dataType };
|
97
91
|
}
|
98
92
|
if (value === undefined) {
|
99
93
|
throw new Error(`Bind value cannot be undefined: ${binding}`);
|
100
94
|
}
|
101
95
|
try {
|
102
|
-
return await
|
96
|
+
return await validateBindingType(engine, value, dataType);
|
103
97
|
}
|
104
98
|
catch (err) {
|
105
99
|
throw new BadRequestError(`"${fieldName}" ${err.message}`);
|
106
100
|
}
|
107
101
|
}));
|
108
102
|
};
|
103
|
+
const validateBindingType = async (engine, $value, $dataType) => {
|
104
|
+
if ($dataType === 'List') {
|
105
|
+
if (!Array.isArray($value)) {
|
106
|
+
throw new Error('List value binding must be an array');
|
107
|
+
}
|
108
|
+
return await Promise.all($value.map(async ([dataType, value]) => {
|
109
|
+
if (dataType === 'Null') {
|
110
|
+
return null;
|
111
|
+
}
|
112
|
+
return await validateBindingType(engine, value, dataType);
|
113
|
+
}));
|
114
|
+
}
|
115
|
+
return await AbstractSQLCompiler[engine].dataTypeValidate($value, {
|
116
|
+
dataType: $dataType,
|
117
|
+
});
|
118
|
+
};
|
109
119
|
const checkModifiedFields = (ruleReferencedFields, modifiedFields) => {
|
110
120
|
const refs = ruleReferencedFields[modifiedFields.table];
|
111
121
|
if (refs == null) {
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"abstract-sql.js","sourceRoot":"","sources":["../../src/sbvr-api/abstract-sql.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,QAAQ,CAAC;
|
1
|
+
{"version":3,"file":"abstract-sql.js","sourceRoot":"","sources":["../../src/sbvr-api/abstract-sql.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@balena/pinejs",
|
3
|
-
"version": "20.0.2-build-
|
3
|
+
"version": "20.0.2-build-compile-in-as-eq-any-cdd3d0c80b63550d7316739dc6050ed1382f8009-1",
|
4
4
|
"main": "out/server-glue/module.js",
|
5
5
|
"type": "module",
|
6
6
|
"repository": "git@github.com:balena-io/pinejs.git",
|
@@ -26,12 +26,12 @@
|
|
26
26
|
"generate-types": "node ./bin/sbvr-compiler.js generate-types ./src/sbvr-api/user.sbvr ./src/sbvr-api/user.ts && node ./bin/sbvr-compiler.js generate-types ./src/migrator/migrations.sbvr ./src/migrator/migrations.ts && node ./bin/sbvr-compiler.js generate-types ./src/sbvr-api/dev.sbvr ./src/sbvr-api/dev.ts && node ./bin/sbvr-compiler.js generate-types ./src/tasks/tasks.sbvr ./src/tasks/tasks.ts && balena-lint -t tsconfig.dev.json --fix ./src/sbvr-api/user.ts ./src/migrator/migrations.ts ./src/sbvr-api/dev.ts"
|
27
27
|
},
|
28
28
|
"dependencies": {
|
29
|
-
"@balena/abstract-sql-compiler": "^10.
|
29
|
+
"@balena/abstract-sql-compiler": "^10.2.0",
|
30
30
|
"@balena/abstract-sql-to-typescript": "^5.1.0",
|
31
31
|
"@balena/env-parsing": "^1.2.0",
|
32
32
|
"@balena/lf-to-abstract-sql": "^5.0.3",
|
33
|
-
"@balena/odata-parser": "
|
34
|
-
"@balena/odata-to-abstract-sql": "
|
33
|
+
"@balena/odata-parser": "4.1.0-build-bind-null-be4c53827082b1917a7de0174635d2c91cefb64e-1",
|
34
|
+
"@balena/odata-to-abstract-sql": "7.1.0-build-parse-in-as-eq-any-6b7559a8fc632e7614f4a1959546a3cbbf80c25f-1",
|
35
35
|
"@balena/sbvr-parser": "^1.4.6",
|
36
36
|
"@balena/sbvr-types": "^9.1.0",
|
37
37
|
"@types/body-parser": "^1.19.5",
|
@@ -148,6 +148,6 @@
|
|
148
148
|
"recursive": true
|
149
149
|
},
|
150
150
|
"versionist": {
|
151
|
-
"publishedAt": "2025-02-
|
151
|
+
"publishedAt": "2025-02-04T16:16:18.419Z"
|
152
152
|
}
|
153
153
|
}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import _ from 'lodash';
|
2
2
|
|
3
|
+
import type { Engines } from '@balena/abstract-sql-compiler';
|
3
4
|
import AbstractSQLCompiler from '@balena/abstract-sql-compiler';
|
4
5
|
import type { BindKey } from '@balena/odata-parser';
|
5
6
|
import {
|
@@ -78,7 +79,7 @@ export const getAndCheckBindValues = async (
|
|
78
79
|
return await Promise.all(
|
79
80
|
bindings.map(async (binding) => {
|
80
81
|
let fieldName = '';
|
81
|
-
let
|
82
|
+
let dataType: string;
|
82
83
|
let value: any;
|
83
84
|
if (binding[0] === 'Bind') {
|
84
85
|
const bindValue = binding[1];
|
@@ -103,7 +104,7 @@ export const getAndCheckBindValues = async (
|
|
103
104
|
if (maybeField == null) {
|
104
105
|
throw new Error(`Could not find field '${fieldName}'`);
|
105
106
|
}
|
106
|
-
|
107
|
+
dataType = maybeField.dataType;
|
107
108
|
} else if (Number.isInteger(bindValue)) {
|
108
109
|
if (bindValue >= odataBinds.length) {
|
109
110
|
console.error(
|
@@ -112,9 +113,7 @@ export const getAndCheckBindValues = async (
|
|
112
113
|
);
|
113
114
|
throw new Error('Invalid binding');
|
114
115
|
}
|
115
|
-
let dataType;
|
116
116
|
[dataType, value] = odataBinds[bindValue];
|
117
|
-
field = { dataType };
|
118
117
|
} else if (typeof bindValue === 'string') {
|
119
118
|
if (!Object.hasOwn(odataBinds, bindValue)) {
|
120
119
|
console.error(
|
@@ -123,16 +122,12 @@ export const getAndCheckBindValues = async (
|
|
123
122
|
);
|
124
123
|
throw new Error('Invalid binding');
|
125
124
|
}
|
126
|
-
let dataType;
|
127
125
|
[dataType, value] = odataBinds[bindValue as BindKey];
|
128
|
-
field = { dataType };
|
129
126
|
} else {
|
130
127
|
throw new Error(`Unknown binding: ${binding}`);
|
131
128
|
}
|
132
129
|
} else {
|
133
|
-
let dataType;
|
134
130
|
[dataType, value] = binding;
|
135
|
-
field = { dataType };
|
136
131
|
}
|
137
132
|
|
138
133
|
if (value === undefined) {
|
@@ -140,7 +135,7 @@ export const getAndCheckBindValues = async (
|
|
140
135
|
}
|
141
136
|
|
142
137
|
try {
|
143
|
-
return await
|
138
|
+
return await validateBindingType(engine, value, dataType);
|
144
139
|
} catch (err: any) {
|
145
140
|
throw new BadRequestError(`"${fieldName}" ${err.message}`);
|
146
141
|
}
|
@@ -148,6 +143,30 @@ export const getAndCheckBindValues = async (
|
|
148
143
|
);
|
149
144
|
};
|
150
145
|
|
146
|
+
const validateBindingType = async (
|
147
|
+
engine: Engines,
|
148
|
+
$value: any,
|
149
|
+
$dataType: string,
|
150
|
+
): Promise<any> => {
|
151
|
+
if ($dataType === 'List') {
|
152
|
+
if (!Array.isArray($value)) {
|
153
|
+
throw new Error('List value binding must be an array');
|
154
|
+
}
|
155
|
+
return await Promise.all(
|
156
|
+
$value.map(async ([dataType, value]: [string, any]) => {
|
157
|
+
// Null is a special case for list values
|
158
|
+
if (dataType === 'Null') {
|
159
|
+
return null;
|
160
|
+
}
|
161
|
+
return await validateBindingType(engine, value, dataType);
|
162
|
+
}),
|
163
|
+
);
|
164
|
+
}
|
165
|
+
return await AbstractSQLCompiler[engine].dataTypeValidate($value, {
|
166
|
+
dataType: $dataType,
|
167
|
+
});
|
168
|
+
};
|
169
|
+
|
151
170
|
const checkModifiedFields = (
|
152
171
|
ruleReferencedFields: AbstractSQLCompiler.RuleReferencedFields,
|
153
172
|
modifiedFields: AbstractSQLCompiler.ModifiedFields,
|