sof-mssql 2.3.0 → 2.4.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/README.md +88 -0
- package/dist/fhirpath/jsonSafeEmptiness.test.d.ts +19 -0
- package/dist/fhirpath/jsonSafeEmptiness.test.d.ts.map +1 -0
- package/dist/fhirpath/jsonSafeEmptiness.test.js +80 -0
- package/dist/fhirpath/jsonSafeEmptiness.test.js.map +1 -0
- package/dist/fhirpath/visitor.d.ts.map +1 -1
- package/dist/fhirpath/visitor.js +20 -12
- package/dist/fhirpath/visitor.js.map +1 -1
- package/dist/load.d.ts +9 -0
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js +4 -0
- package/dist/load.js.map +1 -1
- package/dist/loader/feedback.integration.test.d.ts +14 -0
- package/dist/loader/feedback.integration.test.d.ts.map +1 -0
- package/dist/loader/feedback.integration.test.js +76 -0
- package/dist/loader/feedback.integration.test.js.map +1 -0
- package/dist/loader/index.d.ts.map +1 -1
- package/dist/loader/index.js +19 -6
- package/dist/loader/index.js.map +1 -1
- package/dist/loader/jsonColumn.integration.test.d.ts +13 -0
- package/dist/loader/jsonColumn.integration.test.d.ts.map +1 -0
- package/dist/loader/jsonColumn.integration.test.js +86 -0
- package/dist/loader/jsonColumn.integration.test.js.map +1 -0
- package/dist/loader/tables.d.ts +96 -3
- package/dist/loader/tables.d.ts.map +1 -1
- package/dist/loader/tables.js +182 -16
- package/dist/loader/tables.js.map +1 -1
- package/dist/loader/tables.test.d.ts +12 -0
- package/dist/loader/tables.test.d.ts.map +1 -0
- package/dist/loader/tables.test.js +130 -0
- package/dist/loader/tables.test.js.map +1 -0
- package/dist/loader/types.d.ts +9 -0
- package/dist/loader/types.d.ts.map +1 -1
- package/dist/tests/load.test.d.ts +11 -0
- package/dist/tests/load.test.d.ts.map +1 -0
- package/dist/tests/load.test.js +37 -0
- package/dist/tests/load.test.js.map +1 -0
- package/dist/tests/utils/database.d.ts.map +1 -1
- package/dist/tests/utils/database.js +51 -3
- package/dist/tests/utils/database.js.map +1 -1
- package/dist/tests/utils/loaderIntegration.d.ts +59 -0
- package/dist/tests/utils/loaderIntegration.d.ts.map +1 -0
- package/dist/tests/utils/loaderIntegration.js +137 -0
- package/dist/tests/utils/loaderIntegration.js.map +1 -0
- package/dist/tests/utils/serverCapabilities.d.ts +23 -0
- package/dist/tests/utils/serverCapabilities.d.ts.map +1 -0
- package/dist/tests/utils/serverCapabilities.js +35 -0
- package/dist/tests/utils/serverCapabilities.js.map +1 -0
- package/dist/tests/validation.test.d.ts +13 -0
- package/dist/tests/validation.test.d.ts.map +1 -0
- package/dist/tests/validation.test.js +81 -0
- package/dist/tests/validation.test.js.map +1 -0
- package/dist/validation.d.ts +40 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +56 -0
- package/dist/validation.js.map +1 -1
- package/package.json +1 -1
package/dist/loader/tables.js
CHANGED
|
@@ -9,12 +9,119 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
9
9
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.buildCreateTableStatements = buildCreateTableStatements;
|
|
13
|
+
exports.resolveColumnJsonDataType = resolveColumnJsonDataType;
|
|
14
|
+
exports.buildJsonTypeMismatchWarning = buildJsonTypeMismatchWarning;
|
|
12
15
|
exports.tableExists = tableExists;
|
|
16
|
+
exports.getExistingJsonColumnType = getExistingJsonColumnType;
|
|
17
|
+
exports.warnIfJsonTypeMismatch = warnIfJsonTypeMismatch;
|
|
13
18
|
exports.createTable = createTable;
|
|
14
19
|
exports.truncateTable = truncateTable;
|
|
15
20
|
exports.ensureTable = ensureTable;
|
|
16
21
|
const mssql_1 = __importDefault(require("mssql"));
|
|
17
22
|
const validation_js_1 = require("../validation.js");
|
|
23
|
+
/**
|
|
24
|
+
* Build the DDL statements for the resources table and its index.
|
|
25
|
+
*
|
|
26
|
+
* This is a pure function so the generated SQL can be unit-tested without a
|
|
27
|
+
* database. The `json` column is typed with the resolved {@link
|
|
28
|
+
* ResourceJsonDataType}; with the default `NVARCHAR(MAX)` the output is
|
|
29
|
+
* byte-for-byte identical to earlier releases (SC-001). Identifiers are assumed
|
|
30
|
+
* to have been validated by the caller (see {@link createTable}).
|
|
31
|
+
*
|
|
32
|
+
* @param schemaName - Schema name (already validated).
|
|
33
|
+
* @param tableName - Table name (already validated).
|
|
34
|
+
* @param jsonType - Resolved canonical storage type for the `json` column.
|
|
35
|
+
* @returns The `CREATE TABLE` and `CREATE INDEX` statements.
|
|
36
|
+
*/
|
|
37
|
+
function buildCreateTableStatements(schemaName, tableName, jsonType) {
|
|
38
|
+
// Only the json column's type varies; every other part of the DDL is held
|
|
39
|
+
// constant so the default path is unchanged from earlier releases.
|
|
40
|
+
const createTable = `
|
|
41
|
+
CREATE TABLE [${schemaName}].[${tableName}] (
|
|
42
|
+
[id] INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
|
|
43
|
+
[resource_type] NVARCHAR(64) NOT NULL,
|
|
44
|
+
[json] ${jsonType} NOT NULL
|
|
45
|
+
)
|
|
46
|
+
`;
|
|
47
|
+
const createIndex = `
|
|
48
|
+
CREATE INDEX [IX_${tableName}_resource_type]
|
|
49
|
+
ON [${schemaName}].[${tableName}] ([resource_type])
|
|
50
|
+
`;
|
|
51
|
+
return { createTable, createIndex };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Render an INFORMATION_SCHEMA column type for a diagnostic message.
|
|
55
|
+
*
|
|
56
|
+
* Length-bearing types are shown with their declared length, with the `-1`
|
|
57
|
+
* sentinel rendered as `MAX`; types reported without a length (such as the
|
|
58
|
+
* native `JSON` type) are shown as the bare type name.
|
|
59
|
+
*
|
|
60
|
+
* @param dataType - The INFORMATION_SCHEMA DATA_TYPE.
|
|
61
|
+
* @param characterMaximumLength - The INFORMATION_SCHEMA CHARACTER_MAXIMUM_LENGTH.
|
|
62
|
+
* @returns A readable type such as `VARCHAR(100)`, `NVARCHAR(MAX)` or `JSON`.
|
|
63
|
+
*/
|
|
64
|
+
function formatColumnType(dataType, characterMaximumLength) {
|
|
65
|
+
const baseType = dataType.trim().toUpperCase();
|
|
66
|
+
if (characterMaximumLength === null) {
|
|
67
|
+
return baseType;
|
|
68
|
+
}
|
|
69
|
+
const length = characterMaximumLength === -1 ? "MAX" : String(characterMaximumLength);
|
|
70
|
+
return `${baseType}(${length})`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Resolve an INFORMATION_SCHEMA column description to a canonical json type.
|
|
74
|
+
*
|
|
75
|
+
* Only two column shapes can faithfully hold a serialised FHIR resource: the
|
|
76
|
+
* native type (`data_type = 'json'`) and `NVARCHAR(MAX)` (`data_type =
|
|
77
|
+
* 'nvarchar'` with `character_maximum_length = -1`). Any other shape - a bounded
|
|
78
|
+
* `NVARCHAR(64)`, a non-Unicode `VARCHAR`, `TEXT`, and so on - is rejected here
|
|
79
|
+
* rather than silently coerced to `NVARCHAR(MAX)`. Coercion would let the
|
|
80
|
+
* mismatch check pass and the loader write into a column that cannot hold the
|
|
81
|
+
* data, surfacing later as a `String or binary data would be truncated` error
|
|
82
|
+
* or, under non-Unicode `VARCHAR`, silent character corruption. Failing fast
|
|
83
|
+
* turns that late, data-dependent failure into an early, actionable
|
|
84
|
+
* configuration error (Constitution Principle IV).
|
|
85
|
+
*
|
|
86
|
+
* @param dataType - The INFORMATION_SCHEMA DATA_TYPE.
|
|
87
|
+
* @param characterMaximumLength - The INFORMATION_SCHEMA CHARACTER_MAXIMUM_LENGTH.
|
|
88
|
+
* @returns The canonical resource json data type (`JSON` or `NVARCHAR(MAX)`).
|
|
89
|
+
* @throws Error if the column is neither native `JSON` nor `NVARCHAR(MAX)`. The
|
|
90
|
+
* message names the offending type and the two acceptable types.
|
|
91
|
+
*/
|
|
92
|
+
function resolveColumnJsonDataType(dataType, characterMaximumLength) {
|
|
93
|
+
const normalised = dataType.trim().toLowerCase();
|
|
94
|
+
if (normalised === "json") {
|
|
95
|
+
return "JSON";
|
|
96
|
+
}
|
|
97
|
+
// NVARCHAR(MAX) is the only nvarchar form that can hold an arbitrarily long
|
|
98
|
+
// resource; bounded lengths would truncate, so the length is required here.
|
|
99
|
+
if (normalised === "nvarchar" && characterMaximumLength === -1) {
|
|
100
|
+
return "NVARCHAR(MAX)";
|
|
101
|
+
}
|
|
102
|
+
throw new Error(`Existing [json] column is ` +
|
|
103
|
+
`${formatColumnType(dataType, characterMaximumLength)}, which cannot ` +
|
|
104
|
+
`safely hold serialised FHIR resources. Expected NVARCHAR(MAX) or JSON. ` +
|
|
105
|
+
`Alter or drop the column before loading.`);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Build a warning for an existing table whose json column type differs from the
|
|
109
|
+
* requested type.
|
|
110
|
+
*
|
|
111
|
+
* @param schemaName - Schema name.
|
|
112
|
+
* @param tableName - Table name.
|
|
113
|
+
* @param existingType - The table's current json column type.
|
|
114
|
+
* @param requestedType - The requested json column type.
|
|
115
|
+
* @returns A warning message naming both types, or null when they match.
|
|
116
|
+
*/
|
|
117
|
+
function buildJsonTypeMismatchWarning(schemaName, tableName, existingType, requestedType) {
|
|
118
|
+
if (existingType === requestedType) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
return (`Warning: table [${schemaName}].[${tableName}] already exists with a json ` +
|
|
122
|
+
`column of type ${existingType}, but ${requestedType} was requested. The ` +
|
|
123
|
+
`existing table is left unaltered and loading continues into it.`);
|
|
124
|
+
}
|
|
18
125
|
/**
|
|
19
126
|
* Check if a table exists in the database.
|
|
20
127
|
*
|
|
@@ -34,31 +141,82 @@ async function tableExists(pool, schemaName, tableName) {
|
|
|
34
141
|
`);
|
|
35
142
|
return result.recordset[0].count > 0;
|
|
36
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Read the effective json column type for an existing table.
|
|
146
|
+
*
|
|
147
|
+
* @param pool - Database connection pool.
|
|
148
|
+
* @param schemaName - Schema name.
|
|
149
|
+
* @param tableName - Name of the table.
|
|
150
|
+
* @returns The canonical json column type, or null if the table or its `json`
|
|
151
|
+
* column does not exist.
|
|
152
|
+
* @throws Error if the column exists but is neither native `JSON` nor
|
|
153
|
+
* `NVARCHAR(MAX)` (see {@link resolveColumnJsonDataType}).
|
|
154
|
+
*/
|
|
155
|
+
async function getExistingJsonColumnType(pool, schemaName, tableName) {
|
|
156
|
+
const result = await pool
|
|
157
|
+
.request()
|
|
158
|
+
.input("schemaName", mssql_1.default.NVarChar, schemaName)
|
|
159
|
+
.input("tableName", mssql_1.default.NVarChar, tableName).query(`
|
|
160
|
+
SELECT DATA_TYPE, CHARACTER_MAXIMUM_LENGTH
|
|
161
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
162
|
+
WHERE TABLE_SCHEMA = @schemaName
|
|
163
|
+
AND TABLE_NAME = @tableName
|
|
164
|
+
AND COLUMN_NAME = 'json'
|
|
165
|
+
`);
|
|
166
|
+
const row = result.recordset[0];
|
|
167
|
+
if (!row) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
return resolveColumnJsonDataType(row.DATA_TYPE, row.CHARACTER_MAXIMUM_LENGTH);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Emit a warning if an existing table's json column type differs from the
|
|
174
|
+
* requested type. The table is never altered; this only surfaces the mismatch
|
|
175
|
+
* so it is visible rather than silently ignored (FR-008, SC-005).
|
|
176
|
+
*
|
|
177
|
+
* An existing column that is neither native `JSON` nor `NVARCHAR(MAX)` cannot
|
|
178
|
+
* hold a serialised FHIR resource, so it is rejected outright rather than
|
|
179
|
+
* warned about: the error is raised here before any rows are loaded.
|
|
180
|
+
*
|
|
181
|
+
* @param pool - Database connection pool.
|
|
182
|
+
* @param schemaName - Schema name.
|
|
183
|
+
* @param tableName - Name of the table.
|
|
184
|
+
* @param requestedType - The requested json column type.
|
|
185
|
+
* @throws Error if the existing `json` column is neither native `JSON` nor
|
|
186
|
+
* `NVARCHAR(MAX)` (see {@link resolveColumnJsonDataType}).
|
|
187
|
+
*/
|
|
188
|
+
async function warnIfJsonTypeMismatch(pool, schemaName, tableName, requestedType) {
|
|
189
|
+
const existingType = await getExistingJsonColumnType(pool, schemaName, tableName);
|
|
190
|
+
if (existingType === null) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const warning = buildJsonTypeMismatchWarning(schemaName, tableName, existingType, requestedType);
|
|
194
|
+
if (warning !== null) {
|
|
195
|
+
// The warning is emitted regardless of quiet mode so the misconfiguration
|
|
196
|
+
// is always visible (SC-005).
|
|
197
|
+
console.warn(warning);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
37
200
|
/**
|
|
38
201
|
* Create the fhir_resources table with an index on resource_type.
|
|
39
|
-
* Table schema: id (INT IDENTITY PRIMARY KEY), resource_type (NVARCHAR(64)),
|
|
202
|
+
* Table schema: id (INT IDENTITY PRIMARY KEY), resource_type (NVARCHAR(64)),
|
|
203
|
+
* json (the configured storage type, NVARCHAR(MAX) by default).
|
|
40
204
|
*
|
|
41
205
|
* @param pool - Database connection pool.
|
|
42
206
|
* @param schemaName - Schema name.
|
|
43
207
|
* @param tableName - Name of the table to create.
|
|
208
|
+
* @param jsonType - Storage type for the `json` column (default `NVARCHAR(MAX)`).
|
|
44
209
|
*/
|
|
45
|
-
async function createTable(pool, schemaName, tableName) {
|
|
46
|
-
// Validate identifiers to prevent SQL injection
|
|
210
|
+
async function createTable(pool, schemaName, tableName, jsonType = "NVARCHAR(MAX)") {
|
|
211
|
+
// Validate identifiers to prevent SQL injection. The json type is already a
|
|
212
|
+
// canonical, allowlisted value, so it carries no injection risk.
|
|
47
213
|
(0, validation_js_1.validateSqlServerIdentifier)(schemaName, "Schema name");
|
|
48
214
|
(0, validation_js_1.validateSqlServerIdentifier)(tableName, "Table name");
|
|
215
|
+
const { createTable: createTableSql, createIndex: createIndexSql } = buildCreateTableStatements(schemaName, tableName, jsonType);
|
|
49
216
|
// Create the table.
|
|
50
|
-
await pool.request().query(
|
|
51
|
-
CREATE TABLE [${schemaName}].[${tableName}] (
|
|
52
|
-
[id] INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
|
|
53
|
-
[resource_type] NVARCHAR(64) NOT NULL,
|
|
54
|
-
[json] NVARCHAR(MAX) NOT NULL
|
|
55
|
-
)
|
|
56
|
-
`);
|
|
217
|
+
await pool.request().query(createTableSql);
|
|
57
218
|
// Create an index on resource_type for efficient filtering by resource type.
|
|
58
|
-
await pool.request().query(
|
|
59
|
-
CREATE INDEX [IX_${tableName}_resource_type]
|
|
60
|
-
ON [${schemaName}].[${tableName}] ([resource_type])
|
|
61
|
-
`);
|
|
219
|
+
await pool.request().query(createIndexSql);
|
|
62
220
|
}
|
|
63
221
|
/**
|
|
64
222
|
* Truncate a table (remove all rows).
|
|
@@ -76,20 +234,28 @@ async function truncateTable(pool, schemaName, tableName) {
|
|
|
76
234
|
/**
|
|
77
235
|
* Ensure the fhir_resources table exists, creating it if necessary.
|
|
78
236
|
*
|
|
237
|
+
* When the table already exists it is never altered; the requested `json`
|
|
238
|
+
* column type only governs creation of a new table.
|
|
239
|
+
*
|
|
79
240
|
* @param pool - Database connection pool.
|
|
80
241
|
* @param schemaName - Schema name.
|
|
81
242
|
* @param tableName - Name of the table.
|
|
82
243
|
* @param truncate - Whether to truncate the table if it exists.
|
|
244
|
+
* @param jsonType - Storage type for the `json` column when creating the table
|
|
245
|
+
* (default `NVARCHAR(MAX)`).
|
|
83
246
|
*/
|
|
84
|
-
async function ensureTable(pool, schemaName, tableName, truncate = false) {
|
|
247
|
+
async function ensureTable(pool, schemaName, tableName, truncate = false, jsonType = "NVARCHAR(MAX)") {
|
|
85
248
|
const exists = await tableExists(pool, schemaName, tableName);
|
|
86
249
|
if (exists) {
|
|
250
|
+
// The table already exists, so the requested type cannot take effect. Warn
|
|
251
|
+
// if it differs from the existing column type, then leave the table as is.
|
|
252
|
+
await warnIfJsonTypeMismatch(pool, schemaName, tableName, jsonType);
|
|
87
253
|
if (truncate) {
|
|
88
254
|
await truncateTable(pool, schemaName, tableName);
|
|
89
255
|
}
|
|
90
256
|
}
|
|
91
257
|
else {
|
|
92
|
-
await createTable(pool, schemaName, tableName);
|
|
258
|
+
await createTable(pool, schemaName, tableName, jsonType);
|
|
93
259
|
}
|
|
94
260
|
}
|
|
95
261
|
//# sourceMappingURL=tables.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tables.js","sourceRoot":"","sources":["../../src/loader/tables.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;
|
|
1
|
+
{"version":3,"file":"tables.js","sourceRoot":"","sources":["../../src/loader/tables.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for pure table DDL generation and existing-column-type resolution.
|
|
3
|
+
*
|
|
4
|
+
* These exercise the string-building and comparison logic without a database,
|
|
5
|
+
* so the byte-for-byte DDL contract (SC-001), the native JSON column type
|
|
6
|
+
* (FR-005) and the type-mismatch decision (FR-008) can be verified in
|
|
7
|
+
* isolation.
|
|
8
|
+
*
|
|
9
|
+
* @author John Grimes
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=tables.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tables.test.d.ts","sourceRoot":"","sources":["../../src/loader/tables.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Unit tests for pure table DDL generation and existing-column-type resolution.
|
|
4
|
+
*
|
|
5
|
+
* These exercise the string-building and comparison logic without a database,
|
|
6
|
+
* so the byte-for-byte DDL contract (SC-001), the native JSON column type
|
|
7
|
+
* (FR-005) and the type-mismatch decision (FR-008) can be verified in
|
|
8
|
+
* isolation.
|
|
9
|
+
*
|
|
10
|
+
* @author John Grimes
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
const vitest_1 = require("vitest");
|
|
14
|
+
const tables_1 = require("./tables");
|
|
15
|
+
(0, vitest_1.describe)("buildCreateTableStatements", () => {
|
|
16
|
+
(0, vitest_1.describe)("default NVARCHAR(MAX) json column", () => {
|
|
17
|
+
const statements = (0, tables_1.buildCreateTableStatements)("dbo", "fhir_resources", "NVARCHAR(MAX)");
|
|
18
|
+
(0, vitest_1.it)("types the json column as NVARCHAR(MAX) NOT NULL", () => {
|
|
19
|
+
(0, vitest_1.expect)(statements.createTable).toContain("[json] NVARCHAR(MAX) NOT NULL");
|
|
20
|
+
});
|
|
21
|
+
(0, vitest_1.it)("keeps the id column unchanged", () => {
|
|
22
|
+
(0, vitest_1.expect)(statements.createTable).toContain("[id] INT IDENTITY(1,1) NOT NULL PRIMARY KEY");
|
|
23
|
+
});
|
|
24
|
+
(0, vitest_1.it)("keeps the resource_type column unchanged", () => {
|
|
25
|
+
(0, vitest_1.expect)(statements.createTable).toContain("[resource_type] NVARCHAR(64) NOT NULL");
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.it)("brackets the schema and table identifiers", () => {
|
|
28
|
+
(0, vitest_1.expect)(statements.createTable).toContain("[dbo].[fhir_resources]");
|
|
29
|
+
});
|
|
30
|
+
(0, vitest_1.it)("builds the resource_type index", () => {
|
|
31
|
+
(0, vitest_1.expect)(statements.createIndex).toContain("[IX_fhir_resources_resource_type]");
|
|
32
|
+
(0, vitest_1.expect)(statements.createIndex).toContain("[dbo].[fhir_resources] ([resource_type])");
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
(0, vitest_1.describe)("native JSON json column", () => {
|
|
36
|
+
const statements = (0, tables_1.buildCreateTableStatements)("dbo", "fhir_resources", "JSON");
|
|
37
|
+
(0, vitest_1.it)("types the json column as JSON NOT NULL", () => {
|
|
38
|
+
(0, vitest_1.expect)(statements.createTable).toContain("[json] JSON NOT NULL");
|
|
39
|
+
});
|
|
40
|
+
(0, vitest_1.it)("does not emit NVARCHAR(MAX) for the json column", () => {
|
|
41
|
+
(0, vitest_1.expect)(statements.createTable).not.toContain("[json] NVARCHAR(MAX)");
|
|
42
|
+
});
|
|
43
|
+
(0, vitest_1.it)("keeps the resource_type column unchanged", () => {
|
|
44
|
+
(0, vitest_1.expect)(statements.createTable).toContain("[resource_type] NVARCHAR(64) NOT NULL");
|
|
45
|
+
});
|
|
46
|
+
(0, vitest_1.it)("leaves the index statement identical to the default", () => {
|
|
47
|
+
const defaultStatements = (0, tables_1.buildCreateTableStatements)("dbo", "fhir_resources", "NVARCHAR(MAX)");
|
|
48
|
+
(0, vitest_1.expect)(statements.createIndex).toBe(defaultStatements.createIndex);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
(0, vitest_1.describe)("identifier bracketing", () => {
|
|
52
|
+
(0, vitest_1.it)("brackets custom schema and table names", () => {
|
|
53
|
+
const statements = (0, tables_1.buildCreateTableStatements)("analytics", "resources", "JSON");
|
|
54
|
+
(0, vitest_1.expect)(statements.createTable).toContain("[analytics].[resources]");
|
|
55
|
+
(0, vitest_1.expect)(statements.createIndex).toContain("[IX_resources_resource_type]");
|
|
56
|
+
(0, vitest_1.expect)(statements.createIndex).toContain("[analytics].[resources] ([resource_type])");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
(0, vitest_1.describe)("resolveColumnJsonDataType", () => {
|
|
61
|
+
(0, vitest_1.it)("resolves nvarchar with MAX length to NVARCHAR(MAX)", () => {
|
|
62
|
+
// NVARCHAR(MAX) appears in INFORMATION_SCHEMA as data_type 'nvarchar' with a
|
|
63
|
+
// character_maximum_length of -1.
|
|
64
|
+
(0, vitest_1.expect)((0, tables_1.resolveColumnJsonDataType)("nvarchar", -1)).toBe("NVARCHAR(MAX)");
|
|
65
|
+
});
|
|
66
|
+
(0, vitest_1.it)("resolves json to JSON", () => {
|
|
67
|
+
// The native type appears as data_type 'json' with a null maximum length.
|
|
68
|
+
(0, vitest_1.expect)((0, tables_1.resolveColumnJsonDataType)("json", null)).toBe("JSON");
|
|
69
|
+
});
|
|
70
|
+
(0, vitest_1.it)("is case-insensitive on the data type name", () => {
|
|
71
|
+
(0, vitest_1.expect)((0, tables_1.resolveColumnJsonDataType)("JSON", null)).toBe("JSON");
|
|
72
|
+
(0, vitest_1.expect)((0, tables_1.resolveColumnJsonDataType)("NVARCHAR", -1)).toBe("NVARCHAR(MAX)");
|
|
73
|
+
});
|
|
74
|
+
(0, vitest_1.it)("tolerates surrounding whitespace on the data type name", () => {
|
|
75
|
+
(0, vitest_1.expect)((0, tables_1.resolveColumnJsonDataType)(" json ", null)).toBe("JSON");
|
|
76
|
+
(0, vitest_1.expect)((0, tables_1.resolveColumnJsonDataType)(" nvarchar ", -1)).toBe("NVARCHAR(MAX)");
|
|
77
|
+
});
|
|
78
|
+
// An existing column that is neither native JSON nor NVARCHAR(MAX) cannot
|
|
79
|
+
// faithfully hold a serialised FHIR resource. Such a column must be rejected
|
|
80
|
+
// at the boundary, naming the offending type, rather than silently coerced to
|
|
81
|
+
// NVARCHAR(MAX) - coercion would let the loader write into it and lose data
|
|
82
|
+
// through truncation or, under non-Unicode VARCHAR, character corruption
|
|
83
|
+
// (Constitution Principle IV).
|
|
84
|
+
(0, vitest_1.describe)("rejects column types that cannot hold a FHIR resource", () => {
|
|
85
|
+
(0, vitest_1.it)("throws for a bounded nvarchar, naming the offending type", () => {
|
|
86
|
+
(0, vitest_1.expect)(() => (0, tables_1.resolveColumnJsonDataType)("nvarchar", 64)).toThrow(/NVARCHAR\(64\)/);
|
|
87
|
+
});
|
|
88
|
+
(0, vitest_1.it)("throws for varchar, naming the offending type", () => {
|
|
89
|
+
(0, vitest_1.expect)(() => (0, tables_1.resolveColumnJsonDataType)("varchar", 100)).toThrow(/VARCHAR\(100\)/);
|
|
90
|
+
});
|
|
91
|
+
(0, vitest_1.it)("throws for a max-length varchar, which is still not Unicode-safe", () => {
|
|
92
|
+
// VARCHAR(MAX) reports a length of -1 but is non-Unicode, so it can
|
|
93
|
+
// silently corrupt multi-byte characters and must still be rejected.
|
|
94
|
+
(0, vitest_1.expect)(() => (0, tables_1.resolveColumnJsonDataType)("varchar", -1)).toThrow(/VARCHAR\(MAX\)/);
|
|
95
|
+
});
|
|
96
|
+
(0, vitest_1.it)("throws for text, naming the offending type", () => {
|
|
97
|
+
(0, vitest_1.expect)(() => (0, tables_1.resolveColumnJsonDataType)("text", 2147483647)).toThrow(/TEXT/);
|
|
98
|
+
});
|
|
99
|
+
(0, vitest_1.it)("names both acceptable types in the error message", () => {
|
|
100
|
+
let message = "";
|
|
101
|
+
try {
|
|
102
|
+
(0, tables_1.resolveColumnJsonDataType)("varchar", 100);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
message = error.message;
|
|
106
|
+
}
|
|
107
|
+
(0, vitest_1.expect)(message).toContain("NVARCHAR(MAX)");
|
|
108
|
+
(0, vitest_1.expect)(message).toContain("JSON");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
(0, vitest_1.describe)("buildJsonTypeMismatchWarning", () => {
|
|
113
|
+
(0, vitest_1.it)("returns null when the existing and requested types are equal", () => {
|
|
114
|
+
(0, vitest_1.expect)((0, tables_1.buildJsonTypeMismatchWarning)("dbo", "t", "NVARCHAR(MAX)", "NVARCHAR(MAX)")).toBeNull();
|
|
115
|
+
(0, vitest_1.expect)((0, tables_1.buildJsonTypeMismatchWarning)("dbo", "t", "JSON", "JSON")).toBeNull();
|
|
116
|
+
});
|
|
117
|
+
(0, vitest_1.it)("returns a warning naming both types and the table when they differ", () => {
|
|
118
|
+
const warning = (0, tables_1.buildJsonTypeMismatchWarning)("dbo", "fhir_resources", "NVARCHAR(MAX)", "JSON");
|
|
119
|
+
(0, vitest_1.expect)(warning).not.toBeNull();
|
|
120
|
+
(0, vitest_1.expect)(warning).toContain("NVARCHAR(MAX)");
|
|
121
|
+
(0, vitest_1.expect)(warning).toContain("JSON");
|
|
122
|
+
(0, vitest_1.expect)(warning).toContain("fhir_resources");
|
|
123
|
+
});
|
|
124
|
+
(0, vitest_1.it)("names both the existing and the requested type in either direction", () => {
|
|
125
|
+
const warning = (0, tables_1.buildJsonTypeMismatchWarning)("dbo", "t", "JSON", "NVARCHAR(MAX)");
|
|
126
|
+
(0, vitest_1.expect)(warning).toMatch(/JSON/);
|
|
127
|
+
(0, vitest_1.expect)(warning).toMatch(/NVARCHAR\(MAX\)/);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
//# sourceMappingURL=tables.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tables.test.js","sourceRoot":"","sources":["../../src/loader/tables.test.ts"],"names":[],"mappings":"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"}
|
package/dist/loader/types.d.ts
CHANGED
|
@@ -49,6 +49,15 @@ export interface LoaderOptions {
|
|
|
49
49
|
tableName?: string;
|
|
50
50
|
/** Schema name (default: dbo). */
|
|
51
51
|
schemaName?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Storage type for the resources table `json` column. One of `NVARCHAR(MAX)`
|
|
54
|
+
* (default) or `JSON`. Matching is case-insensitive and tolerant of
|
|
55
|
+
* surrounding whitespace. `JSON` selects SQL Server 2025's native JSON type
|
|
56
|
+
* and requires SQL Server 2025 or later; when omitted the column is created as
|
|
57
|
+
* `NVARCHAR(MAX)`, exactly as in earlier releases. The value is validated
|
|
58
|
+
* against the allowlist before any database connection is opened.
|
|
59
|
+
*/
|
|
60
|
+
resourceJsonDataType?: string;
|
|
52
61
|
/** Create table if it doesn't exist. */
|
|
53
62
|
createTable?: boolean;
|
|
54
63
|
/** Truncate table before loading. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/loader/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/loader/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the `load` command option mapping.
|
|
3
|
+
*
|
|
4
|
+
* Verifies that the `--resource-json-data-type` CLI flag is mapped onto
|
|
5
|
+
* `LoaderOptions.resourceJsonDataType` and passed through untouched, leaving
|
|
6
|
+
* validation and normalisation to the loader (FR-007, cli-load contract).
|
|
7
|
+
*
|
|
8
|
+
* @author John Grimes
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=load.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load.test.d.ts","sourceRoot":"","sources":["../../src/tests/load.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Unit tests for the `load` command option mapping.
|
|
4
|
+
*
|
|
5
|
+
* Verifies that the `--resource-json-data-type` CLI flag is mapped onto
|
|
6
|
+
* `LoaderOptions.resourceJsonDataType` and passed through untouched, leaving
|
|
7
|
+
* validation and normalisation to the loader (FR-007, cli-load contract).
|
|
8
|
+
*
|
|
9
|
+
* @author John Grimes
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const vitest_1 = require("vitest");
|
|
13
|
+
const load_1 = require("../load");
|
|
14
|
+
(0, vitest_1.describe)("buildLoaderOptions", () => {
|
|
15
|
+
(0, vitest_1.it)("maps --resource-json-data-type onto resourceJsonDataType", () => {
|
|
16
|
+
// Dry-run avoids any need for database environment variables.
|
|
17
|
+
const options = (0, load_1.buildLoaderOptions)("./data", {
|
|
18
|
+
dryRun: true,
|
|
19
|
+
resourceJsonDataType: "JSON",
|
|
20
|
+
});
|
|
21
|
+
(0, vitest_1.expect)(options.resourceJsonDataType).toBe("JSON");
|
|
22
|
+
});
|
|
23
|
+
(0, vitest_1.it)("leaves resourceJsonDataType undefined when the flag is absent", () => {
|
|
24
|
+
const options = (0, load_1.buildLoaderOptions)("./data", { dryRun: true });
|
|
25
|
+
(0, vitest_1.expect)(options.resourceJsonDataType).toBeUndefined();
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.it)("passes the raw value through without normalising", () => {
|
|
28
|
+
// Normalisation is the loader's responsibility; the CLI layer must not
|
|
29
|
+
// pre-empt it, so a lower-case value is forwarded verbatim.
|
|
30
|
+
const options = (0, load_1.buildLoaderOptions)("./data", {
|
|
31
|
+
dryRun: true,
|
|
32
|
+
resourceJsonDataType: "json",
|
|
33
|
+
});
|
|
34
|
+
(0, vitest_1.expect)(options.resourceJsonDataType).toBe("json");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=load.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load.test.js","sourceRoot":"","sources":["../../src/tests/load.test.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAEH,mCAA8C;AAC9C,kCAA6C;AAE7C,IAAA,iBAAQ,EAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,IAAA,WAAE,EAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,8DAA8D;QAC9D,MAAM,OAAO,GAAG,IAAA,yBAAkB,EAAC,QAAQ,EAAE;YAC3C,MAAM,EAAE,IAAI;YACZ,oBAAoB,EAAE,MAAM;SAC7B,CAAC,CAAC;QACH,IAAA,eAAM,EAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,OAAO,GAAG,IAAA,yBAAkB,EAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/D,IAAA,eAAM,EAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,aAAa,EAAE,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,uEAAuE;QACvE,4DAA4D;QAC5D,MAAM,OAAO,GAAG,IAAA,yBAAkB,EAAC,QAAQ,EAAE;YAC3C,MAAM,EAAE,IAAI;YACZ,oBAAoB,EAAE,MAAM;SAC7B,CAAC,CAAC;QACH,IAAA,eAAM,EAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../src/tests/utils/database.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,cAAc,EAAkC,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../src/tests/utils/database.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,cAAc,EAAkC,MAAM,OAAO,CAAC;AA4DvE;;;GAGG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAuBnD;AAED;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAWrD;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,GAAG,EAAE,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAoDf;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCnE;AAsID;;;GAGG;AACH,wBAAgB,eAAe,IAAI,cAAc,CAKhD"}
|
|
@@ -15,6 +15,7 @@ exports.cleanupTestData = cleanupTestData;
|
|
|
15
15
|
exports.getDatabasePool = getDatabasePool;
|
|
16
16
|
const lossless_json_1 = require("lossless-json");
|
|
17
17
|
const mssql_1 = require("mssql");
|
|
18
|
+
const validation_1 = require("../../validation");
|
|
18
19
|
// Global connection pool
|
|
19
20
|
let globalPool = null;
|
|
20
21
|
let isConnected = false;
|
|
@@ -44,12 +45,20 @@ const getDatabaseConfig = () => {
|
|
|
44
45
|
/**
|
|
45
46
|
* Get table configuration from environment variables.
|
|
46
47
|
* Uses a test-specific table name to avoid conflicts with production data.
|
|
48
|
+
*
|
|
49
|
+
* The `json` column's storage type is governed by MSSQL_RESOURCE_JSON_DATA_TYPE
|
|
50
|
+
* (default `NVARCHAR(MAX)`); setting it to `JSON` exercises the native JSON type
|
|
51
|
+
* and requires a SQL Server 2025 instance. This lets the full conformance suite
|
|
52
|
+
* run against both column types (FR-011).
|
|
47
53
|
*/
|
|
48
54
|
const getTableConfig = () => ({
|
|
49
55
|
tableName: process.env.MSSQL_TEST_TABLE ?? "fhir_resources_test",
|
|
50
56
|
schemaName: process.env.MSSQL_SCHEMA ?? "dbo",
|
|
51
57
|
resourceIdColumn: "id",
|
|
52
58
|
resourceJsonColumn: "json",
|
|
59
|
+
resourceJsonDataType: process.env.MSSQL_RESOURCE_JSON_DATA_TYPE
|
|
60
|
+
? (0, validation_1.normaliseResourceJsonDataType)(process.env.MSSQL_RESOURCE_JSON_DATA_TYPE)
|
|
61
|
+
: "NVARCHAR(MAX)",
|
|
53
62
|
});
|
|
54
63
|
/**
|
|
55
64
|
* Setup database connection and create the FHIR resources table.
|
|
@@ -215,6 +224,34 @@ async function clearTestTable() {
|
|
|
215
224
|
throw new Error(`Failed to clear test table: ${error instanceof Error ? error.message : String(error)}`);
|
|
216
225
|
}
|
|
217
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Determine the existing test table's json column type.
|
|
229
|
+
*
|
|
230
|
+
* Maps the INFORMATION_SCHEMA representation to a canonical type: `json` for the
|
|
231
|
+
* native type, otherwise `NVARCHAR(MAX)` (the test table only ever uses these
|
|
232
|
+
* two). Used to decide whether the persisted table needs recreating when the
|
|
233
|
+
* configured type changes.
|
|
234
|
+
*
|
|
235
|
+
* @param tableConfig - The resolved test table configuration.
|
|
236
|
+
* @returns The canonical existing json column type.
|
|
237
|
+
*/
|
|
238
|
+
async function getExistingTestTableJsonType(tableConfig) {
|
|
239
|
+
if (!globalPool) {
|
|
240
|
+
throw new Error("Database not connected");
|
|
241
|
+
}
|
|
242
|
+
const request = new mssql_1.Request(globalPool);
|
|
243
|
+
request.input("schemaName", tableConfig.schemaName);
|
|
244
|
+
request.input("tableName", tableConfig.tableName);
|
|
245
|
+
request.input("columnName", tableConfig.resourceJsonColumn);
|
|
246
|
+
const result = await request.query(`
|
|
247
|
+
SELECT DATA_TYPE
|
|
248
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
249
|
+
WHERE TABLE_SCHEMA = @schemaName
|
|
250
|
+
AND TABLE_NAME = @tableName
|
|
251
|
+
AND COLUMN_NAME = @columnName
|
|
252
|
+
`);
|
|
253
|
+
return result.recordset[0]?.DATA_TYPE === "json" ? "JSON" : "NVARCHAR(MAX)";
|
|
254
|
+
}
|
|
218
255
|
/**
|
|
219
256
|
* Create the FHIR resources table if it doesn't exist.
|
|
220
257
|
*/
|
|
@@ -236,14 +273,25 @@ async function createTableIfNotExists() {
|
|
|
236
273
|
const checkResult = await checkRequest.query(checkTableSql);
|
|
237
274
|
const tableExists = checkResult.recordset[0]?.table_count > 0;
|
|
238
275
|
if (tableExists) {
|
|
239
|
-
|
|
276
|
+
// The shared test table persists between runs. If its json column type no
|
|
277
|
+
// longer matches the configured type (e.g. switching
|
|
278
|
+
// MSSQL_RESOURCE_JSON_DATA_TYPE between NVARCHAR(MAX) and JSON), drop it so
|
|
279
|
+
// it is recreated with the right type; otherwise leave it as is.
|
|
280
|
+
const existingType = await getExistingTestTableJsonType(tableConfig);
|
|
281
|
+
if (existingType === tableConfig.resourceJsonDataType) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
await new mssql_1.Request(globalPool).query(`DROP TABLE ${tableName}`);
|
|
240
285
|
}
|
|
241
|
-
// Create test table with test_id column for concurrent test isolation
|
|
286
|
+
// Create test table with test_id column for concurrent test isolation. The
|
|
287
|
+
// json column type is configurable so the same suite can prove conformance
|
|
288
|
+
// over both NVARCHAR(MAX) and the native JSON type. The type is a canonical,
|
|
289
|
+
// allowlisted value (see getTableConfig), so it carries no injection risk.
|
|
242
290
|
const createTableSql = `
|
|
243
291
|
CREATE TABLE ${tableName} (
|
|
244
292
|
[${tableConfig.resourceIdColumn}] INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
|
|
245
293
|
[resource_type] NVARCHAR(64) NOT NULL,
|
|
246
|
-
[${tableConfig.resourceJsonColumn}]
|
|
294
|
+
[${tableConfig.resourceJsonColumn}] ${tableConfig.resourceJsonDataType} NOT NULL,
|
|
247
295
|
[test_id] NVARCHAR(255) NOT NULL
|
|
248
296
|
)
|
|
249
297
|
`;
|