@oino-ts/db-mssql 0.0.16 → 0.0.18

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 CHANGED
@@ -59,20 +59,8 @@
59
59
 
60
60
  ## RESTfull
61
61
  OINO maps HTTP methods GET/POST/PUT/DELETE to SQL operations SELECT/INSERT/UPDATE/DELETE. The GET/POST requests can be made without URL ID to get all rows or insert new ones and others target a single row using URL ID.
62
-
63
- ### HTTP GET
64
- ```
65
- Request and response:
66
- > curl.exe -X GET http://localhost:3001/orderdetails/11077:77
67
- [
68
- {"_OINOID_":"11077:77","OrderID":11077,"ProductID":77,"UnitPrice":13,"Quantity":2,"Discount":0}
69
- ]
70
-
71
- SQL:
72
- SELECT "OrderID","ProductID","UnitPrice","Quantity","Discount" FROM [OrderDetails] WHERE ("OrderID"=11077 AND "ProductID"=77);
73
- ```
74
62
 
75
- ### HTTP POST
63
+ For example HTTP POST
76
64
  ```
77
65
  Request and response:
78
66
  > curl.exe -X POST http://localhost:3001/orderdetails -H "Content-Type: application/json" --data '[{\"OrderID\":11077,\"ProductID\":99,\"UnitPrice\":19,\"Quantity\":1,\"Discount\":0}]'
@@ -82,31 +70,12 @@
82
70
  INSERT INTO [OrderDetails] ("OrderID","ProductID","UnitPrice","Quantity","Discount") VALUES (11077,99,19,1,0);
83
71
  ```
84
72
 
85
- ### HTTP PUT
86
- ```
87
- Request and response:
88
- > curl.exe -X PUT http://localhost:3001/orderdetails/11077:99 -H "Content-Type: application/json" --data '[{\"UnitPrice\":20}]'
89
- {"success":true,"statusCode":200,"statusMessage":"OK","messages":[]}
90
73
 
91
- SQL:
92
- UPDATE [OrderDetails] SET "UnitPrice"=20 WHERE ("OrderID"=11077 AND "ProductID"=99);
93
- ```
94
-
95
- ### HTTP DELETE
96
- ```
97
- Request and response:
98
- > curl.exe -X DELETE http://localhost:3001/orderdetails/11077:99
99
- {"success":true,"statusCode":200,"statusMessage":"OK","messages":[]}
100
-
101
- SQL:
102
- DELETE FROM [OrderDetails] WHERE ("OrderID"=11077 AND "ProductID"=99);
103
- ```
104
-
105
74
  ## Universal Serialization
106
75
  OINO handles serialization of data to JSON/CSV/etc. and back based on the data model. It knows what columns exist, what is their data type and how to convert each to JSON/CSV and back. This allows also partial data to be sent, i.e. you can send only columns that need updating or even send extra columns and have them ignored.
107
76
 
108
77
  ### Features
109
- - Files can be sent to BLOB fields using BASE64 encoding.
78
+ - Files can be sent to BLOB fields using BASE64 or MIME multipart encoding. Also supports standard HTML form file submission to blob fields and returning them data url images.
110
79
  - Datetimes are (optionally) normalized to ISO 8601 format.
111
80
  - Extended JSON-encoding
112
81
  - Unquoted literal `undefined` can be used to represent non-existent values (leaving property out works too but preserving structure might be easier e.g. when translating data).
@@ -131,11 +100,11 @@
131
100
  - Mariadb / Mysql-support through [mariadb](https://www.npmjs.com/package/mariadb)-package
132
101
  - Sql Server through [mssql](https://www.npmjs.com/package/mssql)-package
133
102
 
134
- ## Complex Keys
103
+ ## Composite Keys
135
104
  To support tables with multipart primary keys OINO generates a composite key `_OINOID_` that is included in the result and can be used as the REST ID. For example in the example above table `OrderDetails` has two primary keys `OrderID` and `ProductID` making the `_OINOID_` of form `11077:99`.
136
105
 
137
106
  ## Power Of SQL
138
- Since OINO is just generating SQL, WHERE-conditions can be defined with [`OINOSqlFilter`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbSqlFilter.html) and order with [`OINOSqlOrder`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbSqlOrder.html) that are passed as HTTP request parameters. No more API development where you make unique API endpoints for each filter that fetch all data with original API and filter in backend code. Every API can be filtered when and as needed without unnessecary data tranfer and utilizing SQL indexing when available.
107
+ Since OINO is just generating SQL, WHERE-conditions can be defined with [`OINOSqlFilter`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbSqlFilter.html), order with [`OINOSqlOrder`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbSqlOrder.html) and limits/paging with [`OINOSqlLimit`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbSqlLimit.html) that are passed as HTTP request parameters. No more API development where you make unique API endpoints for each filter that fetch all data with original API and filter in backend code. Every API can be filtered when and as needed without unnessecary data tranfer and utilizing SQL indexing when available.
139
108
 
140
109
  ## Swagger Support
141
110
  Swagger is great as long as the definitions are updated and with OINO you can automatically get a Swagger definition including a data model schema.
@@ -152,7 +121,7 @@
152
121
  ## HTMX support
153
122
  OINO is [htmx.org](https://htmx.org) friendly, allowing easy translation of [`OINODataRow`](https://pragmatta.github.io/oino-ts/types/db_src.OINODataRow.html) to HTML output using templates (cf. the [htmx sample app](https://github.com/pragmatta/oino-ts/tree/main/samples/htmxApp)).
154
123
 
155
- ### Hashids
124
+ ## Hashids
156
125
  Autoinc numeric id's are very pragmatic and fit well with OINO (e.g. using a form without primary key fields to insert new rows with database assigned ids). However it's not always sensible to share information about the sequence. Hashids solve this by masking the original values by encrypting the ids using AES-128 and some randomness. Length of the hashid can be chosen from 12-32 characters where longer ids provide more security. However this should not be considereded a cryptographic solution for keeping ids secret but rather making it infeasible to iterate all ids.
157
126
 
158
127
 
@@ -164,9 +133,6 @@
164
133
 
165
134
  ### Realistic app
166
135
  There needs to be a realistic app built on top of OINO to get a better grasp of the edge cases.
167
-
168
- ### Security review
169
- Handling of SQL-injection attacks needs a thorough review, what are the relevant attack vectors are for OINO and what protections are still needed.
170
136
 
171
137
  ## Roadmap
172
138
  Things that need to happen in some order before beta-status are at least following:
@@ -179,8 +145,8 @@
179
145
  ### Batch updates
180
146
  Supporting batch updates similar to batch inserts is slightly bending the RESTfull principles but would still be a useful optional feature.
181
147
 
182
- ### Aggregation and limits
183
- Similar to filtering and ordering, aggregation and limits can be implemented as HTTP request parameters telling what column is aggregated or used for ordering or how many results to return.
148
+ ### Aggregation
149
+ Similar to filtering, ordering and limits, aggregation could be implemented as HTTP request parameters telling what column is aggregated or used for ordering or how many results to return.
184
150
 
185
151
  ### Streaming
186
152
  One core idea is to be efficient in not making unnecessary copies of the data and minimizing garbage collection debt. This can be taken further by implementing streaming, allowing large dataset to be written to HTTP response as SQL result rows are received.
@@ -222,4 +188,3 @@
222
188
 
223
189
  ## SQL Scripts
224
190
  The SQL scripts for creating the sample Northwind database are based on [Google Code archive](https://code.google.com/archive/p/northwindextended/downloads) and have been further customized to ensure they would have identical data (in the scope of the automated testing).
225
-
@@ -210,7 +210,7 @@ class OINODbMsSql extends db_1.OINODb {
210
210
  return "'" + cellValue?.toString() + "'";
211
211
  }
212
212
  }
213
- else if (((sqlType == "date") || (sqlType == "datetime") || (sqlType == "timestamp")) && (cellValue instanceof Date)) {
213
+ else if (((sqlType == "date") || (sqlType == "datetime") || (sqlType == "datetime2") || (sqlType == "timestamp")) && (cellValue instanceof Date)) {
214
214
  return "'" + cellValue.toISOString().substring(0, 23) + "'";
215
215
  }
216
216
  else {
@@ -234,13 +234,49 @@ class OINODbMsSql extends db_1.OINODb {
234
234
  else if (sqlValue === undefined) {
235
235
  return undefined;
236
236
  }
237
- else if (((sqlType == "date")) && (typeof (sqlValue) == "string")) {
237
+ else if (((sqlType == "date") || (sqlType == "datetime") || (sqlType == "datetime2")) && (typeof (sqlValue) == "string")) {
238
238
  return new Date(sqlValue);
239
239
  }
240
240
  else {
241
241
  return sqlValue;
242
242
  }
243
243
  }
244
+ /**
245
+ * Print SQL select statement with DB specific formatting.
246
+ *
247
+ * @param tableName - The name of the table to select from.
248
+ * @param columnNames - The columns to be selected.
249
+ * @param whereCondition - The WHERE clause to filter the results.
250
+ * @param orderCondition - The ORDER BY clause to sort the results.
251
+ * @param limitCondition - The LIMIT clause to limit the number of results.
252
+ *
253
+ */
254
+ printSqlSelect(tableName, columnNames, whereCondition, orderCondition, limitCondition) {
255
+ const limit_parts = limitCondition.split(" OFFSET ");
256
+ let result = "SELECT ";
257
+ if ((limitCondition != "") && (limit_parts.length == 1)) {
258
+ result += "TOP " + limit_parts[0] + " ";
259
+ }
260
+ result += columnNames + " FROM " + tableName;
261
+ // OINOLog.debug("OINODb.printSqlSelect", {tableName:tableName, columnNames:columnNames, whereCondition:whereCondition, orderCondition:orderCondition, limitCondition:limitCondition })
262
+ if (whereCondition != "") {
263
+ result += " WHERE " + whereCondition;
264
+ }
265
+ if (orderCondition != "") {
266
+ result += " ORDER BY " + orderCondition;
267
+ }
268
+ if ((limitCondition != "") && (limit_parts.length == 2)) {
269
+ if (orderCondition == "") {
270
+ db_1.OINOLog.error("OINODbMsSql.printSqlSelect: LIMIT without ORDER BY is not supported in MS SQL Server");
271
+ }
272
+ else {
273
+ result += " OFFSET " + limit_parts[1] + " ROWS FETCH NEXT " + limit_parts[0] + " ROWS ONLY";
274
+ }
275
+ }
276
+ result += ";";
277
+ // OINOLog.debug("OINODb.printSqlSelect", {result:result})
278
+ return result;
279
+ }
244
280
  /**
245
281
  * Connect to database.
246
282
  *
@@ -296,19 +332,27 @@ class OINODbMsSql extends db_1.OINODb {
296
332
  return result;
297
333
  }
298
334
  _getSchemaSql(dbName, tableName) {
299
- const sql =
300
- // 0 1 2 3 4 5 6 7 8
301
- `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
335
+ const sql = `SELECT
336
+ C.COLUMN_NAME,
337
+ C.IS_NULLABLE,
338
+ C.DATA_TYPE,
339
+ C.CHARACTER_MAXIMUM_LENGTH,
340
+ C.NUMERIC_PRECISION,
341
+ C.NUMERIC_PRECISION_RADIX,
342
+ CONST.CONSTRAINT_TYPES,
343
+ COLUMNPROPERTY(OBJECT_ID(C.TABLE_SCHEMA + '.' + C.TABLE_NAME), C.COLUMN_NAME, 'IsIdentity') AS IS_AUTO_INCREMENT,
344
+ COLUMNPROPERTY(OBJECT_ID(C.TABLE_SCHEMA + '.' + C.TABLE_NAME), C.COLUMN_NAME, 'IsComputed') AS IS_COMPUTED
302
345
  FROM
303
346
  INFORMATION_SCHEMA.COLUMNS as C LEFT JOIN
304
347
  (
305
- SELECT TC.TABLE_NAME, KU.COLUMN_NAME, STRING_AGG(TC.CONSTRAINT_TYPE, ', ') as CONSTRAINT_TYPES
348
+ SELECT TC.TABLE_NAME, KU.COLUMN_NAME, STRING_AGG(TC.CONSTRAINT_TYPE, ',') as CONSTRAINT_TYPES
306
349
  FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
307
350
  INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KU ON TC.CONSTRAINT_NAME = KU.CONSTRAINT_NAME
308
351
  GROUP BY TC.TABLE_NAME, KU.COLUMN_NAME
309
352
  ) as CONST
310
353
  ON C.TABLE_NAME = CONST.TABLE_NAME AND C.COLUMN_NAME = CONST.COLUMN_NAME
311
- WHERE C.TABLE_CATALOG = '${dbName}' AND C.TABLE_NAME = '${tableName}';`;
354
+ WHERE C.TABLE_CATALOG = '${dbName}' AND C.TABLE_NAME = '${tableName}'
355
+ ORDER BY C.ORDINAL_POSITION;`;
312
356
  return sql;
313
357
  }
314
358
  /**
@@ -332,6 +376,7 @@ WHERE C.TABLE_CATALOG = '${dbName}' AND C.TABLE_NAME = '${tableName}';`;
332
376
  const constraint_types = row[6] || "";
333
377
  const field_params = {
334
378
  isPrimaryKey: constraint_types.indexOf("PRIMARY KEY") >= 0,
379
+ isForeignKey: constraint_types.indexOf("FOREIGN KEY") >= 0,
335
380
  isAutoInc: row[7] == 1,
336
381
  isNotNull: row[1] == "NO"
337
382
  };
@@ -207,7 +207,7 @@ export class OINODbMsSql extends OINODb {
207
207
  return "'" + cellValue?.toString() + "'";
208
208
  }
209
209
  }
210
- else if (((sqlType == "date") || (sqlType == "datetime") || (sqlType == "timestamp")) && (cellValue instanceof Date)) {
210
+ else if (((sqlType == "date") || (sqlType == "datetime") || (sqlType == "datetime2") || (sqlType == "timestamp")) && (cellValue instanceof Date)) {
211
211
  return "'" + cellValue.toISOString().substring(0, 23) + "'";
212
212
  }
213
213
  else {
@@ -231,13 +231,49 @@ export class OINODbMsSql extends OINODb {
231
231
  else if (sqlValue === undefined) {
232
232
  return undefined;
233
233
  }
234
- else if (((sqlType == "date")) && (typeof (sqlValue) == "string")) {
234
+ else if (((sqlType == "date") || (sqlType == "datetime") || (sqlType == "datetime2")) && (typeof (sqlValue) == "string")) {
235
235
  return new Date(sqlValue);
236
236
  }
237
237
  else {
238
238
  return sqlValue;
239
239
  }
240
240
  }
241
+ /**
242
+ * Print SQL select statement with DB specific formatting.
243
+ *
244
+ * @param tableName - The name of the table to select from.
245
+ * @param columnNames - The columns to be selected.
246
+ * @param whereCondition - The WHERE clause to filter the results.
247
+ * @param orderCondition - The ORDER BY clause to sort the results.
248
+ * @param limitCondition - The LIMIT clause to limit the number of results.
249
+ *
250
+ */
251
+ printSqlSelect(tableName, columnNames, whereCondition, orderCondition, limitCondition) {
252
+ const limit_parts = limitCondition.split(" OFFSET ");
253
+ let result = "SELECT ";
254
+ if ((limitCondition != "") && (limit_parts.length == 1)) {
255
+ result += "TOP " + limit_parts[0] + " ";
256
+ }
257
+ result += columnNames + " FROM " + tableName;
258
+ // OINOLog.debug("OINODb.printSqlSelect", {tableName:tableName, columnNames:columnNames, whereCondition:whereCondition, orderCondition:orderCondition, limitCondition:limitCondition })
259
+ if (whereCondition != "") {
260
+ result += " WHERE " + whereCondition;
261
+ }
262
+ if (orderCondition != "") {
263
+ result += " ORDER BY " + orderCondition;
264
+ }
265
+ if ((limitCondition != "") && (limit_parts.length == 2)) {
266
+ if (orderCondition == "") {
267
+ OINOLog.error("OINODbMsSql.printSqlSelect: LIMIT without ORDER BY is not supported in MS SQL Server");
268
+ }
269
+ else {
270
+ result += " OFFSET " + limit_parts[1] + " ROWS FETCH NEXT " + limit_parts[0] + " ROWS ONLY";
271
+ }
272
+ }
273
+ result += ";";
274
+ // OINOLog.debug("OINODb.printSqlSelect", {result:result})
275
+ return result;
276
+ }
241
277
  /**
242
278
  * Connect to database.
243
279
  *
@@ -293,19 +329,27 @@ export class OINODbMsSql extends OINODb {
293
329
  return result;
294
330
  }
295
331
  _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
332
+ const sql = `SELECT
333
+ C.COLUMN_NAME,
334
+ C.IS_NULLABLE,
335
+ C.DATA_TYPE,
336
+ C.CHARACTER_MAXIMUM_LENGTH,
337
+ C.NUMERIC_PRECISION,
338
+ C.NUMERIC_PRECISION_RADIX,
339
+ CONST.CONSTRAINT_TYPES,
340
+ COLUMNPROPERTY(OBJECT_ID(C.TABLE_SCHEMA + '.' + C.TABLE_NAME), C.COLUMN_NAME, 'IsIdentity') AS IS_AUTO_INCREMENT,
341
+ COLUMNPROPERTY(OBJECT_ID(C.TABLE_SCHEMA + '.' + C.TABLE_NAME), C.COLUMN_NAME, 'IsComputed') AS IS_COMPUTED
299
342
  FROM
300
343
  INFORMATION_SCHEMA.COLUMNS as C LEFT JOIN
301
344
  (
302
- SELECT TC.TABLE_NAME, KU.COLUMN_NAME, STRING_AGG(TC.CONSTRAINT_TYPE, ', ') as CONSTRAINT_TYPES
345
+ SELECT TC.TABLE_NAME, KU.COLUMN_NAME, STRING_AGG(TC.CONSTRAINT_TYPE, ',') as CONSTRAINT_TYPES
303
346
  FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
304
347
  INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KU ON TC.CONSTRAINT_NAME = KU.CONSTRAINT_NAME
305
348
  GROUP BY TC.TABLE_NAME, KU.COLUMN_NAME
306
349
  ) as CONST
307
350
  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}';`;
351
+ WHERE C.TABLE_CATALOG = '${dbName}' AND C.TABLE_NAME = '${tableName}'
352
+ ORDER BY C.ORDINAL_POSITION;`;
309
353
  return sql;
310
354
  }
311
355
  /**
@@ -329,6 +373,7 @@ WHERE C.TABLE_CATALOG = '${dbName}' AND C.TABLE_NAME = '${tableName}';`;
329
373
  const constraint_types = row[6] || "";
330
374
  const field_params = {
331
375
  isPrimaryKey: constraint_types.indexOf("PRIMARY KEY") >= 0,
376
+ isForeignKey: constraint_types.indexOf("FOREIGN KEY") >= 0,
332
377
  isAutoInc: row[7] == 1,
333
378
  isNotNull: row[1] == "NO"
334
379
  };
@@ -44,6 +44,17 @@ export declare class OINODbMsSql extends OINODb {
44
44
  *
45
45
  */
46
46
  parseSqlValueAsCell(sqlValue: OINODataCell, sqlType: string): OINODataCell;
47
+ /**
48
+ * Print SQL select statement with DB specific formatting.
49
+ *
50
+ * @param tableName - The name of the table to select from.
51
+ * @param columnNames - The columns to be selected.
52
+ * @param whereCondition - The WHERE clause to filter the results.
53
+ * @param orderCondition - The ORDER BY clause to sort the results.
54
+ * @param limitCondition - The LIMIT clause to limit the number of results.
55
+ *
56
+ */
57
+ printSqlSelect(tableName: string, columnNames: string, whereCondition: string, orderCondition: string, limitCondition: string): string;
47
58
  /**
48
59
  * Connect to database.
49
60
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oino-ts/db-mssql",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "description": "OINO TS package for using Microsoft Sql databases.",
5
5
  "author": "Matias Kiviniemi (pragmatta)",
6
6
  "license": "MPL-2.0",
@@ -222,7 +222,7 @@ export class OINODbMsSql extends OINODb {
222
222
  return "'" + cellValue?.toString() + "'"
223
223
  }
224
224
 
225
- } else if (((sqlType == "date") || (sqlType == "datetime") || (sqlType == "timestamp")) && (cellValue instanceof Date)) {
225
+ } else if (((sqlType == "date") || (sqlType == "datetime") || (sqlType == "datetime2") || (sqlType == "timestamp")) && (cellValue instanceof Date)) {
226
226
  return "'" + cellValue.toISOString().substring(0, 23) + "'"
227
227
 
228
228
  } else {
@@ -247,7 +247,7 @@ export class OINODbMsSql extends OINODb {
247
247
  } else if (sqlValue === undefined) {
248
248
  return undefined
249
249
 
250
- } else if (((sqlType == "date")) && (typeof(sqlValue) == "string")) {
250
+ } else if (((sqlType == "date") || (sqlType == "datetime") || (sqlType == "datetime2")) && (typeof(sqlValue) == "string")) {
251
251
  return new Date(sqlValue)
252
252
 
253
253
  } else {
@@ -256,6 +256,42 @@ export class OINODbMsSql extends OINODb {
256
256
 
257
257
  }
258
258
 
259
+ /**
260
+ * Print SQL select statement with DB specific formatting.
261
+ *
262
+ * @param tableName - The name of the table to select from.
263
+ * @param columnNames - The columns to be selected.
264
+ * @param whereCondition - The WHERE clause to filter the results.
265
+ * @param orderCondition - The ORDER BY clause to sort the results.
266
+ * @param limitCondition - The LIMIT clause to limit the number of results.
267
+ *
268
+ */
269
+ printSqlSelect(tableName:string, columnNames:string, whereCondition:string, orderCondition:string, limitCondition:string): string {
270
+ const limit_parts = limitCondition.split(" OFFSET ")
271
+ let result:string = "SELECT "
272
+ if ((limitCondition != "") && (limit_parts.length == 1)) {
273
+ result += "TOP " + limit_parts[0] + " "
274
+ }
275
+ result += columnNames + " FROM " + tableName
276
+ // OINOLog.debug("OINODb.printSqlSelect", {tableName:tableName, columnNames:columnNames, whereCondition:whereCondition, orderCondition:orderCondition, limitCondition:limitCondition })
277
+ if (whereCondition != "") {
278
+ result += " WHERE " + whereCondition
279
+ }
280
+ if (orderCondition != "") {
281
+ result += " ORDER BY " + orderCondition
282
+ }
283
+ if ((limitCondition != "") && (limit_parts.length == 2)) {
284
+ if (orderCondition == "") {
285
+ OINOLog.error("OINODbMsSql.printSqlSelect: LIMIT without ORDER BY is not supported in MS SQL Server")
286
+ } else {
287
+ result += " OFFSET " + limit_parts[1] + " ROWS FETCH NEXT " + limit_parts[0] + " ROWS ONLY"
288
+ }
289
+ }
290
+ result += ";"
291
+ // OINOLog.debug("OINODb.printSqlSelect", {result:result})
292
+ return result;
293
+ }
294
+
259
295
  /**
260
296
  * Connect to database.
261
297
  *
@@ -314,18 +350,27 @@ export class OINODbMsSql extends OINODb {
314
350
 
315
351
  private _getSchemaSql(dbName:string, tableName:string):string {
316
352
  const sql =
317
- // 0 1 2 3 4 5 6 7 8
318
- `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
353
+ `SELECT
354
+ C.COLUMN_NAME,
355
+ C.IS_NULLABLE,
356
+ C.DATA_TYPE,
357
+ C.CHARACTER_MAXIMUM_LENGTH,
358
+ C.NUMERIC_PRECISION,
359
+ C.NUMERIC_PRECISION_RADIX,
360
+ CONST.CONSTRAINT_TYPES,
361
+ COLUMNPROPERTY(OBJECT_ID(C.TABLE_SCHEMA + '.' + C.TABLE_NAME), C.COLUMN_NAME, 'IsIdentity') AS IS_AUTO_INCREMENT,
362
+ COLUMNPROPERTY(OBJECT_ID(C.TABLE_SCHEMA + '.' + C.TABLE_NAME), C.COLUMN_NAME, 'IsComputed') AS IS_COMPUTED
319
363
  FROM
320
364
  INFORMATION_SCHEMA.COLUMNS as C LEFT JOIN
321
365
  (
322
- SELECT TC.TABLE_NAME, KU.COLUMN_NAME, STRING_AGG(TC.CONSTRAINT_TYPE, ', ') as CONSTRAINT_TYPES
366
+ SELECT TC.TABLE_NAME, KU.COLUMN_NAME, STRING_AGG(TC.CONSTRAINT_TYPE, ',') as CONSTRAINT_TYPES
323
367
  FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
324
368
  INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KU ON TC.CONSTRAINT_NAME = KU.CONSTRAINT_NAME
325
369
  GROUP BY TC.TABLE_NAME, KU.COLUMN_NAME
326
370
  ) as CONST
327
371
  ON C.TABLE_NAME = CONST.TABLE_NAME AND C.COLUMN_NAME = CONST.COLUMN_NAME
328
- WHERE C.TABLE_CATALOG = '${dbName}' AND C.TABLE_NAME = '${tableName}';`
372
+ WHERE C.TABLE_CATALOG = '${dbName}' AND C.TABLE_NAME = '${tableName}'
373
+ ORDER BY C.ORDINAL_POSITION;`
329
374
  return sql
330
375
  }
331
376
  /**
@@ -350,6 +395,7 @@ WHERE C.TABLE_CATALOG = '${dbName}' AND C.TABLE_NAME = '${tableName}';`
350
395
  const constraint_types:string = row[6] as string || ""
351
396
  const field_params:OINODbDataFieldParams = {
352
397
  isPrimaryKey: constraint_types.indexOf("PRIMARY KEY") >= 0,
398
+ isForeignKey: constraint_types.indexOf("FOREIGN KEY") >= 0,
353
399
  isAutoInc: row[7] == 1,
354
400
  isNotNull: row[1] == "NO"
355
401
  }
@@ -389,6 +435,7 @@ WHERE C.TABLE_CATALOG = '${dbName}' AND C.TABLE_NAME = '${tableName}';`
389
435
  OINOLog.debug("OINODbMsSql.initializeDatasetModel:\n" + api.datamodel.printDebug("\n"))
390
436
  return Promise.resolve()
391
437
  }
438
+
392
439
  }
393
440
 
394
441