@oino-ts/db-mssql 0.0.16

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.
@@ -0,0 +1,373 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+ import { OINODb, OINODbDataSet, OINOBooleanDataField, OINONumberDataField, OINOStringDataField, OINO_ERROR_PREFIX, OINOBenchmark, OINODatetimeDataField, OINOBlobDataField, OINO_INFO_PREFIX, OINODB_EMPTY_ROW, OINODB_EMPTY_ROWS, OINOLog } from "@oino-ts/db";
7
+ import { ConnectionPool } from "mssql";
8
+ /**
9
+ * Implmentation of OINODbDataSet for MariaDb.
10
+ *
11
+ */
12
+ class OINOMsSqlData extends OINODbDataSet {
13
+ _recordsets = [OINODB_EMPTY_ROWS];
14
+ _rows = OINODB_EMPTY_ROWS;
15
+ _currentRecordset;
16
+ _currentRow;
17
+ _eof;
18
+ /**
19
+ * OINOMsSqlData constructor
20
+ * @param params database parameters
21
+ */
22
+ constructor(data, messages = []) {
23
+ super(data, messages);
24
+ if (data == null) {
25
+ this.messages.push(OINO_INFO_PREFIX + "SQL result is empty");
26
+ }
27
+ else if (!(Array.isArray(data) && (data.length > 0) && Array.isArray(data[0]))) {
28
+ throw new Error(OINO_ERROR_PREFIX + ": OINOMsSqlData constructor: invalid data!");
29
+ }
30
+ else {
31
+ this._recordsets = data;
32
+ this._rows = this._recordsets[0];
33
+ }
34
+ // OINOLog.debug("OINOMsSqlData.constructor", {_rows:this._rows})
35
+ if (this.isEmpty()) {
36
+ this._currentRecordset = -1;
37
+ this._currentRow = -1;
38
+ this._eof = true;
39
+ }
40
+ else {
41
+ this._currentRecordset = 0;
42
+ this._currentRow = 0;
43
+ this._eof = false;
44
+ }
45
+ }
46
+ /**
47
+ * Is data set empty.
48
+ *
49
+ */
50
+ isEmpty() {
51
+ return (this._rows.length == 0);
52
+ }
53
+ /**
54
+ * Is there no more content, i.e. either dataset is empty or we have moved beyond last line
55
+ *
56
+ */
57
+ isEof() {
58
+ return (this._eof);
59
+ }
60
+ /**
61
+ * Attempts to moves dataset to the next row, possibly waiting for more data to become available. Returns !isEof().
62
+ *
63
+ */
64
+ async next() {
65
+ // OINOLog.debug("OINODbDataSet.next", {currentRow:this._currentRow, length:this.sqlResult.data.length})
66
+ if (this._currentRow < this._rows.length - 1) {
67
+ this._currentRow = this._currentRow + 1;
68
+ }
69
+ else if (this._currentRecordset < this._recordsets.length - 1) {
70
+ this._currentRecordset = this._currentRecordset + 1;
71
+ this._rows = this._recordsets[this._currentRecordset];
72
+ this._currentRow = 0;
73
+ }
74
+ else {
75
+ this._eof = true;
76
+ }
77
+ return Promise.resolve(!this._eof);
78
+ }
79
+ /**
80
+ * Gets current row of data.
81
+ *
82
+ */
83
+ getRow() {
84
+ if ((this._currentRow >= 0) && (this._currentRow < this._rows.length)) {
85
+ return this._rows[this._currentRow];
86
+ }
87
+ else {
88
+ return OINODB_EMPTY_ROW;
89
+ }
90
+ }
91
+ }
92
+ /**
93
+ * Implementation of MariaDb/MySql-database.
94
+ *
95
+ */
96
+ export class OINODbMsSql extends OINODb {
97
+ _pool;
98
+ /**
99
+ * Constructor of `OINODbMsSql`
100
+ * @param params database parameters
101
+ */
102
+ constructor(params) {
103
+ super(params);
104
+ // OINOLog.debug("OINODbMsSql.constructor", {params:params})
105
+ if (this._params.type !== "OINODbMsSql") {
106
+ throw new Error(OINO_ERROR_PREFIX + ": Not OINODbMsSql-type: " + this._params.type);
107
+ }
108
+ this._pool = new ConnectionPool({
109
+ user: params.user,
110
+ password: params.password,
111
+ server: params.url,
112
+ port: params.port,
113
+ database: params.database,
114
+ arrayRowMode: true,
115
+ options: {
116
+ encrypt: true, // Use encryption for Azure SQL Database
117
+ rowCollectionOnRequestCompletion: false,
118
+ rowCollectionOnDone: false,
119
+ trustServerCertificate: true // Change to false for production
120
+ }
121
+ });
122
+ //this._pool = new ConnectionPool({connectionString: "Server=localhost,1433;Database=database;User Id=username;Password=" + params.password + ";Encrypt=true"})
123
+ this._pool.on("error", (conn) => {
124
+ // console.log("OINODbMsSql error", conn)
125
+ });
126
+ this._pool.on("debug", (event) => {
127
+ // console.log("OINODbMsSql debug",event)
128
+ });
129
+ }
130
+ async _query(sql) {
131
+ // OINOLog.debug("OINODbMsSql._query", {sql:sql})
132
+ try {
133
+ const sql_res = await this._pool.request().query(sql);
134
+ // console.log("OINODbMsSql._query result:" + JSON.stringify(sql_res.recordsets))
135
+ const result = new OINOMsSqlData(sql_res.recordsets);
136
+ return Promise.resolve(result);
137
+ }
138
+ catch (err) {
139
+ OINOLog.error("OINODbMsSql._query exception", { err: err });
140
+ throw err;
141
+ }
142
+ finally {
143
+ // console.log("OINODbMsSql._query finally");
144
+ }
145
+ // OINOLog.debug("OINODbMsSql._query", {result:query_result})
146
+ }
147
+ async _exec(sql) {
148
+ // OINOLog.debug("OINODbMsSql._exec", {sql:sql})
149
+ try {
150
+ const sql_res = await this._pool.request().query(sql);
151
+ // console.log("OINODbMsSql._exec result", sql_res);
152
+ return Promise.resolve(new OINOMsSqlData([[]]));
153
+ }
154
+ catch (err) {
155
+ OINOLog.error("OINODbMsSql._exec exception", { err: err });
156
+ throw err;
157
+ }
158
+ finally {
159
+ }
160
+ // OINOLog.debug("OINODbMsSql._exec", {result:query_result})
161
+ }
162
+ /**
163
+ * Print a table name using database specific SQL escaping.
164
+ *
165
+ * @param sqlTable name of the table
166
+ *
167
+ */
168
+ printSqlTablename(sqlTable) {
169
+ return "[" + sqlTable + "]";
170
+ }
171
+ /**
172
+ * Print a column name with correct SQL escaping.
173
+ *
174
+ * @param sqlColumn name of the column
175
+ *
176
+ */
177
+ printSqlColumnname(sqlColumn) {
178
+ return "[" + sqlColumn + "]";
179
+ }
180
+ /**
181
+ * Print a single data value from serialization using the context of the native data
182
+ * type with the correct SQL escaping.
183
+ *
184
+ * @param cellValue data from sql results
185
+ * @param sqlType native type name for table column
186
+ *
187
+ */
188
+ printCellAsSqlValue(cellValue, sqlType) {
189
+ // OINOLog.debug("OINODbMsSql.printCellAsSqlValue", {cellValue:cellValue, sqlType:sqlType})
190
+ if (cellValue === null) {
191
+ return "NULL";
192
+ }
193
+ else if (cellValue === undefined) {
194
+ return "UNDEFINED";
195
+ }
196
+ else if ((sqlType == "int") || (sqlType == "smallint") || (sqlType == "float")) {
197
+ return cellValue.toString();
198
+ }
199
+ else if ((sqlType == "longblob") || (sqlType == "binary") || (sqlType == "varbinary")) {
200
+ if (cellValue instanceof Buffer) {
201
+ return "0x" + cellValue.toString("hex") + "";
202
+ }
203
+ else if (cellValue instanceof Uint8Array) {
204
+ return "0x" + Buffer.from(cellValue).toString("hex") + "";
205
+ }
206
+ else {
207
+ return "'" + cellValue?.toString() + "'";
208
+ }
209
+ }
210
+ else if (((sqlType == "date") || (sqlType == "datetime") || (sqlType == "timestamp")) && (cellValue instanceof Date)) {
211
+ return "'" + cellValue.toISOString().substring(0, 23) + "'";
212
+ }
213
+ else {
214
+ // return "'" + cellValue?.toString().replaceAll("\\", "\\\\").replaceAll("\'", "''").replaceAll("\r", "\\r").replaceAll("\n", "\\n").replaceAll("\t", "\\t") + "'"
215
+ return "'" + cellValue?.toString().replaceAll("\'", "''") + "'";
216
+ }
217
+ }
218
+ /**
219
+ * Parse a single SQL result value for serialization using the context of the native data
220
+ * type.
221
+ *
222
+ * @param sqlValue data from serialization
223
+ * @param sqlType native type name for table column
224
+ *
225
+ */
226
+ parseSqlValueAsCell(sqlValue, sqlType) {
227
+ // OINOLog.debug("OINODbMsSql.parseSqlValueAsCell", {sqlValue:sqlValue, sqlType:sqlType})
228
+ if ((sqlValue === null) || (sqlValue == "NULL")) {
229
+ return null;
230
+ }
231
+ else if (sqlValue === undefined) {
232
+ return undefined;
233
+ }
234
+ else if (((sqlType == "date")) && (typeof (sqlValue) == "string")) {
235
+ return new Date(sqlValue);
236
+ }
237
+ else {
238
+ return sqlValue;
239
+ }
240
+ }
241
+ /**
242
+ * Connect to database.
243
+ *
244
+ */
245
+ async connect() {
246
+ try {
247
+ // make sure that any items are correctly URL encoded in the connection string
248
+ await this._pool.connect();
249
+ // OINOLog.info("OINODbMsSql.connect: Connected to database server.")
250
+ await this._pool.request().query("SELECT 1 as test");
251
+ return Promise.resolve(true);
252
+ }
253
+ catch (err) {
254
+ // ... error checks
255
+ throw new Error(OINO_ERROR_PREFIX + ": Error connecting to OINODbMsSql server: " + err);
256
+ }
257
+ }
258
+ /**
259
+ * Execute a select operation.
260
+ *
261
+ * @param sql SQL statement.
262
+ *
263
+ */
264
+ async sqlSelect(sql) {
265
+ OINOBenchmark.start("OINODb", "sqlSelect");
266
+ let result;
267
+ try {
268
+ // OINOLog.debug("OINODbMsSql.sqlSelect", {sql_rows:sql_rows})
269
+ result = await this._query(sql);
270
+ }
271
+ catch (e) {
272
+ result = new OINOMsSqlData([[]], [OINO_ERROR_PREFIX + " (sqlSelect): OINODbMsSql.sqlSelect exception in _db.query: " + e.message]);
273
+ }
274
+ OINOBenchmark.end("OINODb", "sqlSelect");
275
+ return result;
276
+ }
277
+ /**
278
+ * Execute other sql operations.
279
+ *
280
+ * @param sql SQL statement.
281
+ *
282
+ */
283
+ async sqlExec(sql) {
284
+ OINOBenchmark.start("OINODb", "sqlExec");
285
+ let result;
286
+ try {
287
+ result = await this._exec(sql);
288
+ }
289
+ catch (e) {
290
+ result = new OINOMsSqlData([[]], [OINO_ERROR_PREFIX + " (sqlExec): exception in _db.exec [" + e.message + "]"]);
291
+ }
292
+ OINOBenchmark.end("OINODb", "sqlExec");
293
+ return result;
294
+ }
295
+ _getSchemaSql(dbName, tableName) {
296
+ const sql =
297
+ // 0 1 2 3 4 5 6 7 8
298
+ `SELECT C.COLUMN_NAME, C.IS_NULLABLE, C.DATA_TYPE, C.CHARACTER_MAXIMUM_LENGTH, C.NUMERIC_PRECISION, C.NUMERIC_PRECISION_RADIX, CONST.CONSTRAINT_TYPES, COLUMNPROPERTY(OBJECT_ID(C.TABLE_SCHEMA + '.' + C.TABLE_NAME), C.COLUMN_NAME, 'IsIdentity') AS IS_AUTO_INCREMENT, COLUMNPROPERTY(OBJECT_ID(C.TABLE_SCHEMA + '.' + C.TABLE_NAME), C.COLUMN_NAME, 'IsComputed') AS IS_COMPUTED
299
+ FROM
300
+ INFORMATION_SCHEMA.COLUMNS as C LEFT JOIN
301
+ (
302
+ SELECT TC.TABLE_NAME, KU.COLUMN_NAME, STRING_AGG(TC.CONSTRAINT_TYPE, ', ') as CONSTRAINT_TYPES
303
+ FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
304
+ INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KU ON TC.CONSTRAINT_NAME = KU.CONSTRAINT_NAME
305
+ GROUP BY TC.TABLE_NAME, KU.COLUMN_NAME
306
+ ) as CONST
307
+ ON C.TABLE_NAME = CONST.TABLE_NAME AND C.COLUMN_NAME = CONST.COLUMN_NAME
308
+ WHERE C.TABLE_CATALOG = '${dbName}' AND C.TABLE_NAME = '${tableName}';`;
309
+ return sql;
310
+ }
311
+ /**
312
+ * Initialize a data model by getting the SQL schema and populating OINODbDataFields of
313
+ * the model.
314
+ *
315
+ * @param api api which data model to initialize.
316
+ *
317
+ */
318
+ async initializeApiDatamodel(api) {
319
+ //"SELECT COLUMN_NAME, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX
320
+ const res = await this.sqlSelect(this._getSchemaSql(this._params.database, api.params.tableName));
321
+ while (!res.isEof()) {
322
+ const row = res.getRow();
323
+ // OINOLog.debug("OINODbMsSql.initializeApiDatamodel", { description:row })
324
+ const field_name = row[0]?.toString() || "";
325
+ const sql_type = row[2] || "";
326
+ const char_field_length = row[3] || 0;
327
+ const numeric_field_length1 = row[4] || 0;
328
+ const numeric_field_length2 = row[5] || 0;
329
+ const constraint_types = row[6] || "";
330
+ const field_params = {
331
+ isPrimaryKey: constraint_types.indexOf("PRIMARY KEY") >= 0,
332
+ isAutoInc: row[7] == 1,
333
+ isNotNull: row[1] == "NO"
334
+ };
335
+ if (((api.params.excludeFieldPrefix) && field_name.startsWith(api.params.excludeFieldPrefix)) || ((api.params.excludeFields) && (api.params.excludeFields.indexOf(field_name) < 0))) {
336
+ OINOLog.info("OINODbMsSql.initializeApiDatamodel: field excluded in API parameters.", { field: field_name });
337
+ }
338
+ else {
339
+ // OINOLog.debug("OINODbMsSql.initializeApiDatamodel: next field ", {field_name: field_name, sql_type:sql_type, char_field_length:char_field_length, numeric_field_length1:numeric_field_length1, numeric_field_length2:numeric_field_length2, field_params:field_params })
340
+ if ((sql_type == "tinyint") || (sql_type == "smallint") || (sql_type == "int") || (sql_type == "bigint") || (sql_type == "float") || (sql_type == "real")) {
341
+ api.datamodel.addField(new OINONumberDataField(this, field_name, sql_type, field_params));
342
+ }
343
+ else if ((sql_type == "date") || (sql_type == "datetime") || (sql_type == "datetime2")) {
344
+ if (api.params.useDatesAsString) {
345
+ api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0));
346
+ }
347
+ else {
348
+ api.datamodel.addField(new OINODatetimeDataField(this, field_name, sql_type, field_params));
349
+ }
350
+ }
351
+ else if ((sql_type == "ntext") || (sql_type == "nchar") || (sql_type == "nvarchar") || (sql_type == "text") || (sql_type == "char") || (sql_type == "varchar")) {
352
+ api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, char_field_length));
353
+ }
354
+ else if ((sql_type == "binary") || (sql_type == "varbinary") || (sql_type == "image")) {
355
+ api.datamodel.addField(new OINOBlobDataField(this, field_name, sql_type, field_params, char_field_length));
356
+ }
357
+ else if ((sql_type == "numeric") || (sql_type == "decimal") || (sql_type == "money")) {
358
+ api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, numeric_field_length1 + numeric_field_length2 + 1));
359
+ }
360
+ else if ((sql_type == "bit")) {
361
+ api.datamodel.addField(new OINOBooleanDataField(this, field_name, sql_type, field_params));
362
+ }
363
+ else {
364
+ OINOLog.info("OINODbMsSql.initializeApiDatamodel: unrecognized field type treated as string", { field_name: field_name, sql_type: sql_type, char_length: char_field_length, numeric_field_length1: numeric_field_length1, numeric_field_length2: numeric_field_length2, field_params: field_params });
365
+ api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0));
366
+ }
367
+ }
368
+ await res.next();
369
+ }
370
+ OINOLog.debug("OINODbMsSql.initializeDatasetModel:\n" + api.datamodel.printDebug("\n"));
371
+ return Promise.resolve();
372
+ }
373
+ }
@@ -0,0 +1 @@
1
+ export { OINODbMsSql } from "./OINODbMsSql.js";
@@ -0,0 +1,75 @@
1
+ import { OINODb, OINODbParams, OINODbDataSet, OINODbApi, OINODataCell } from "@oino-ts/db";
2
+ /**
3
+ * Implementation of MariaDb/MySql-database.
4
+ *
5
+ */
6
+ export declare class OINODbMsSql extends OINODb {
7
+ private _pool;
8
+ /**
9
+ * Constructor of `OINODbMsSql`
10
+ * @param params database parameters
11
+ */
12
+ constructor(params: OINODbParams);
13
+ private _query;
14
+ private _exec;
15
+ /**
16
+ * Print a table name using database specific SQL escaping.
17
+ *
18
+ * @param sqlTable name of the table
19
+ *
20
+ */
21
+ printSqlTablename(sqlTable: string): string;
22
+ /**
23
+ * Print a column name with correct SQL escaping.
24
+ *
25
+ * @param sqlColumn name of the column
26
+ *
27
+ */
28
+ printSqlColumnname(sqlColumn: string): string;
29
+ /**
30
+ * Print a single data value from serialization using the context of the native data
31
+ * type with the correct SQL escaping.
32
+ *
33
+ * @param cellValue data from sql results
34
+ * @param sqlType native type name for table column
35
+ *
36
+ */
37
+ printCellAsSqlValue(cellValue: OINODataCell, sqlType: string): string;
38
+ /**
39
+ * Parse a single SQL result value for serialization using the context of the native data
40
+ * type.
41
+ *
42
+ * @param sqlValue data from serialization
43
+ * @param sqlType native type name for table column
44
+ *
45
+ */
46
+ parseSqlValueAsCell(sqlValue: OINODataCell, sqlType: string): OINODataCell;
47
+ /**
48
+ * Connect to database.
49
+ *
50
+ */
51
+ connect(): Promise<boolean>;
52
+ /**
53
+ * Execute a select operation.
54
+ *
55
+ * @param sql SQL statement.
56
+ *
57
+ */
58
+ sqlSelect(sql: string): Promise<OINODbDataSet>;
59
+ /**
60
+ * Execute other sql operations.
61
+ *
62
+ * @param sql SQL statement.
63
+ *
64
+ */
65
+ sqlExec(sql: string): Promise<OINODbDataSet>;
66
+ private _getSchemaSql;
67
+ /**
68
+ * Initialize a data model by getting the SQL schema and populating OINODbDataFields of
69
+ * the model.
70
+ *
71
+ * @param api api which data model to initialize.
72
+ *
73
+ */
74
+ initializeApiDatamodel(api: OINODbApi): Promise<void>;
75
+ }
@@ -0,0 +1 @@
1
+ export { OINODbMsSql } from "./OINODbMsSql.js";
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@oino-ts/db-mssql",
3
+ "version": "0.0.16",
4
+ "description": "OINO TS package for using Microsoft Sql databases.",
5
+ "author": "Matias Kiviniemi (pragmatta)",
6
+ "license": "MPL-2.0",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/pragmatta/oino-ts.git"
10
+ },
11
+ "keywords": [
12
+ "sql",
13
+ "database",
14
+ "rest-api",
15
+ "typescript",
16
+ "library",
17
+ "mssql",
18
+ "sql-server",
19
+ "azure-sql"
20
+ ],
21
+ "main": "./dist/cjs/index.js",
22
+ "module": "./dist/esm/index.js",
23
+ "types": "./dist/types/index.d.ts",
24
+ "dependencies": {
25
+ "@oino-ts/db": "latest",
26
+ "mssql": "^11.0.1"
27
+ },
28
+ "devDependencies": {
29
+ "@types/bun": "latest",
30
+ "@types/mssql": "^9.1.5",
31
+ "@types/node": "^20.12.7"
32
+ },
33
+ "files": [
34
+ "src/*.ts",
35
+ "dist/cjs/*.js",
36
+ "dist/esm/*.js",
37
+ "dist/types/*.d.ts"
38
+ ]
39
+ }