@oino-ts/db-mssql 1.0.8 → 1.1.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 CHANGED
@@ -9,47 +9,69 @@ return new Response(result.modelset.writeString(OINOContentType.json))
9
9
 
10
10
  # GETTING STARTED
11
11
 
12
- ### Setup
13
- Install the `@oino-ts/db` npm package and necessary database packages and import them in your code.
12
+ ## Create Datasources
13
+
14
+ ### Create an SQL DB
15
+
16
+ First install the `@oino-ts/db` npm package and necessary database packages and import them in your code.
14
17
  ```
15
18
  bun install @oino-ts/db
16
19
  bun install @oino-ts/db-bunsqlite
17
20
  ```
18
21
 
19
22
  ```
20
- import { OINODb, OINOApi, OINOFactory } from "@oino-ts/db";
23
+ import { OINODb, OINODbFactory } from "@oino-ts/db";
24
+ import { OINOApi } from "@oino-ts/db";
21
25
  import { OINODbBunSqlite } from "@oino-ts/db-bunsqlite"
22
26
  ```
23
27
 
24
- ### Register database and logger
25
- Register your database implementation and logger (see [`OINOConsoleLog`](https://pragmatta.github.io/oino-ts/classes/common_src.OINOConsoleLog.html) how to implement your own)
28
+ Next register your database implementation and logger (see [`OINOConsoleLog`](https://pragmatta.github.io/oino-ts/classes/common_src.OINOConsoleLog.html) how to implement your own)
26
29
 
27
30
  ```
28
31
  OINOLog.setLogger(new OINOConsoleLog())
29
- OINOFactory.registerDb("OINODbBunSqlite", OINODbBunSqlite)
32
+ OINODbFactory.registerDb("OINODbBunSqlite", OINODbBunSqlite)
33
+ ```
34
+
35
+ Finally creating a database connection [`OINODb`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODb.html) is done by passing [`OINODbParams`](https://pragmatta.github.io/oino-ts/types/db_src.OINODbParams.html) to the factory method. For [`OINODbBunSqlite`](https://pragmatta.github.io/oino-ts/classes/db_bunsqlite_src.OINODbBunSqlite.html) that means a file url for the database file, for others network host, port, credentials etc.
36
+ ```
37
+ const db:OINODb = await OINODbFactory.createDb( { type: "OINODbBunSqlite", url: "file://../localDb/northwind.sqlite" } )
30
38
  ```
31
39
 
32
- ### Create a database
33
- Creating a database connection [`OINODb`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODb.html) is done by passing [`OINODbParams`](https://pragmatta.github.io/oino-ts/types/db_src.OINODbParams.html) to the factory method. For [`OINODbBunSqlite`](https://pragmatta.github.io/oino-ts/classes/db_bunsqlite_src.OINODbBunSqlite.html) that means a file url for the database file, for others network host, port, credentials etc.
40
+ ### Create NoSQL datasources
41
+ Creating NoSQL datasources works similarly by importing the [`OINONoSql`](https://pragmatta.github.io/oino-ts/modules/nosql_src.html) package and either the [`OINONoSqlAws`](https://pragmatta.github.io/oino-ts/modules/nosql-aws_src.html) or [`OINONoSqlAzure`](https://pragmatta.github.io/oino-ts/modules/nosql-azure_src.html), registering the implementation with the factory
42
+ ```
43
+ OINONoSqlFactory.registerNoSql("OINONoSqlAzureTable", OINONoSqlAzureTable)
44
+ const nosql_azure_params = { type: "OINONoSqlAzureTable", table: "NorthwindOrders", credentials: { connectionStr: process.env.OINOCLOUD_TEST_BLOB_AZURE_CONSTR } }
45
+ const nosql_azure = await OINONoSqlFactory.createNoSql(nosql_azure_params)
46
+ ```
47
+
48
+ NOTE! Format of the credentials varies by platform and might require extra authorization.
49
+
50
+ ### Create Blob datasources
51
+ Creating Blob datasources works similarly by importing the [`OINOBlob`](https://pragmatta.github.io/oino-ts/classes/blob_src.OINOBlob.html) package and either the [`OINOBlobAws`](https://pragmatta.github.io/oino-ts/modules/blob-aws_src.html) or [`OINOBlobAzure`](https://pragmatta.github.io/oino-ts/modules/blob-azure_src.html), registering the implementation with the factory
34
52
  ```
35
- const db:OINODb = await OINOFactory.createDb( { type: "OINODbBunSqlite", url: "file://../localDb/northwind.sqlite" } )
53
+ OINOBlobFactory.registerBlob("OINOBlobAzureTable", OINOBlobAzureTable)
54
+ const Blob_azure_params = { type: "OINOBlobAzureTable", table: "NorthwindOrders", credentials: { connectionStr: process.env.OINOCLOUD_TEST_BLOB_AZURE_CONSTR } }
55
+ const Blob_azure = await OINOBlobFactory.createBlob(nosql_azure_params)
36
56
  ```
37
57
 
38
- ### Create an API
39
- From a database you can create an [`OINOApi`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbApi.html) by passing [`OINOApiParams`](https://pragmatta.github.io/oino-ts/types/db_src.OINODbApiParams.html) with table name and preferences to the factory method.
58
+ NOTE! Format of the credentials varies by platform and might require extra authorization.
59
+
60
+ ## Create an API
61
+ From a datasource you can create an [`OINOApi`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbApi.html) by passing [`OINOApiParams`](https://pragmatta.github.io/oino-ts/types/db_src.OINODbApiParams.html) with table name and preferences to the factory method.
40
62
  ```
41
63
  const api_employees:OINOApi = await OINOFactory.createApi(db, { tableName: "Employees", excludeFields:["BirthDate"] })
42
64
  ```
43
65
 
44
- ### Pass HTTP requests to API
66
+ ## Pass HTTP requests to API
45
67
  When you receive a HTTP request, just pass the method, URL ID, body and params to the correct API, which will parse and validate input and return results.
46
68
 
47
69
  ```
48
70
  const result:OINOApiResult = await api_orderdetails.doRequest("GET", id, body, params)
49
71
  ```
50
72
 
51
- ### Write results back to HTTP Response
52
- The results for a GET request will contain [`OINOModelSet`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbModelSet.html) data that can be written out as JSON or CSV as needed. For other requests result is just success or error with messages.
73
+ ## Write results back to HTTP Response
74
+ The results for a GET request will contain [`OINOModelSet`](https://pragmatta.github.io/oino-ts/classes/common_src.OINOModelSet.html) data that can be written out as JSON or CSV as needed. For other requests result is just success or error with messages.
53
75
  ```
54
76
  return new Response(result.data.writeString(OINOContentType.json))
55
77
  ```
@@ -90,20 +112,29 @@ OINO handles serialization of data to JSON/CSV/etc. and back based on the data m
90
112
  - Multiple lines could be used to post multiple rows.
91
113
 
92
114
 
93
- ## Database Abstraction
94
- OINO functions as a database abstraction, providing a consistent interface for working with different databases. It abstracts out different conventions in connecting, making queries and formatting data.
115
+ ## Datasource Abstraction
116
+ OINO functions as a datasource abstraction for SQL, NoSQL and Blob storages, providing a consistent interface for working with different datasources. It abstracts out different conventions in connecting, making queries and formatting data.
95
117
 
96
- Currently supported databases:
97
- - Bun Sqlite through Bun native implementation
98
- - Postgresql through [pg](https://www.npmjs.com/package/pg)-package
99
- - Mariadb / Mysql-support through [mariadb](https://www.npmjs.com/package/mariadb)-package
100
- - Sql Server through [mssql](https://www.npmjs.com/package/mssql)-package
118
+ Currently supported datasources:
119
+ - SQL
120
+ - Bun Sqlite through Bun native implementation
121
+ - Postgresql through [pg](https://www.npmjs.com/package/pg)-package
122
+ - Mariadb / Mysql-support through [mariadb](https://www.npmjs.com/package/mariadb)-package
123
+ - Sql Server through [mssql](https://www.npmjs.com/package/mssql)-package
124
+ - NoSQL
125
+ - AWS DynamoDb through [@aws-sdk/client-dynamodb](https://www.npmjs.com/package/@aws-sdk/client-dynamodb)-package
126
+ - Azure Tables through [@azure/data-tables](https://www.npmjs.com/package/@azure/data-tables)-package
127
+ - Blob
128
+ - AWS S3 through [@aws-sdk/client-s3](https://www.npmjs.com/package/@aws-sdk/client-s3)-package
129
+ - Azure Blobs through [@azure/storage-blob](https://www.npmjs.com/package/@azure/storage-blob)-package
101
130
 
102
131
  ## Composite Keys
103
132
  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`.
104
133
 
105
134
  ## Power Of SQL
106
- Since OINO is just generating SQL, WHERE-conditions can be defined with [`OINODbSqlFilter`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbSqlFilter.html), order with [`OINODbSqlOrder`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbSqlOrder.html), limits/paging with [`OINODbSqlLimit`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbSqlLimit.html) and aggregation with [`OINODbSqlAggregate`](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbSqlAggregate.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.
135
+ Since OINO is just generating SQL, WHERE-conditions can be defined with [`OINOQueryFilter`](https://pragmatta.github.io/oino-ts/classes/common_src.OINOQueryFilter.html), order with [`OINOQueryOrder`](https://pragmatta.github.io/oino-ts/classes/common_src.OINOQueryOrder.html), limits/paging with [`OINOQueryLimit`](https://pragmatta.github.io/oino-ts/classes/common_src.OINOQueryLimit.html) and aggregation with [`OINOQueryAggregate`](https://pragmatta.github.io/oino-ts/classes/common_src.OINOQueryAggregate.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.
136
+
137
+ Most of the filtering also works with NoSQL and Blob datasources but might less performant depending if the service supports it or if we result in software filtering the results.
107
138
 
108
139
  ## Swagger Support
109
140
  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.
@@ -118,28 +149,26 @@ if (url.pathname == "/swagger.json") {
118
149
  OINO is developped Typescript first but compiles to standard CommonJS and the NPM packages should work on either ESM / CommonJS. Checkout sample apps `readmeApp` (ESM) and `nodeApp` (CommonJS).
119
150
 
120
151
  ## HTMX support
121
- 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)).
152
+ 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)).
122
153
 
123
154
  ## Hashids
124
155
  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.
125
156
 
157
+ ### Batch updates
158
+ Batch updates slight bend the RESTfull principles but there are separate `doBatchUpdate` endpoints (e.g. [OINODbApi.dobatchupdate](https://pragmatta.github.io/oino-ts/classes/db_src.OINODbApi.html#dobatchupdate)).
126
159
 
127
- # STATUS
128
- OINO is currently a side project which should considered in beta status. That means that semantic versioning is the goal but backwards incompatible changes can still happen in point releases if serious architectual issues are discovered.
160
+ ## Schema Management
161
+ OINO has endpoints for reading, creating and deleting table and column schemas.
129
162
 
130
- ## Roadmap
131
- Major features that are considered in future releases ()
132
163
 
133
- ### Support for views
134
- Simple cases of views probably work already in some databases but edge cases might get complicated.
164
+ # STATUS
165
+ OINO v1.1 is the first release considered production status. Architecture has now survived introduction NoSQL and Blov datasources and we feel comfortable saying it's stable now. Also we have been using it in [oino.cloud](https://oino.cloud) for a while without issues.
135
166
 
136
- For example issues could be
137
- - How to handle a view which does not have a complete private key?
138
- - What edge cases exist in updating views?
139
- - Can views be handled transparently to the DBMS or is there some fundamentally platform specific behavior?
167
+ ## Roadmap
168
+ Major features that are considered in future releases
140
169
 
141
- ### Batch updates
142
- Supporting batch updates similar to batch inserts is slightly bending the RESTfull principles but would still be a useful optional feature in some situations.
170
+ ### Views
171
+ It would be interesting to combine multiple datasources as OINO-views like multiple NoSQL-tables.
143
172
 
144
173
  ### Streaming
145
174
  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.
@@ -147,9 +176,6 @@ One core idea is to be efficient in not making unnecessary copies of the data an
147
176
  ### SQL generation callbacks
148
177
  It would be useful to allow developer to validate / override SQL generation to cover cases OINO does not support or even workaround issues.
149
178
 
150
- ### Transactions
151
- Even though the basic case for OINO is executing SQL operations on individual rows, having an option to use SQL transactions could make sense at least for batch operations.
152
-
153
179
 
154
180
  # HELP
155
181
 
@@ -159,8 +185,6 @@ Fixing bugs is a priority and getting good quality bug reports helps. It's recom
159
185
  ## Feedback
160
186
  Understanding and prioritizing the use cases for OINO is also important and feedback about how you'd use OINO is interesting. Feel free to raise issues and feature requests in Github, but understand that short term most of the effort goes towards reaching the beta stage.
161
187
 
162
- ## Typescript / Javascript architecture
163
- Typescript building with different targets and module-systemts and a ton of configuration is a complex domain and something I have little experience un so help in fixing problems and how thing ought to be done is appreciated.
164
188
 
165
189
  # LINKS
166
190
  - [Github repository](https://github.com/pragmatta/oino-ts)
@@ -171,10 +195,12 @@ Typescript building with different targets and module-systemts and a ton of conf
171
195
 
172
196
  ## Libraries
173
197
  OINO uses the following open source libraries and npm packages and I would like to thank everyone for their contributions:
174
- - Postgresql [node-postgres package](https://www.npmjs.com/package/pg)
175
- - Mariadb / Mysql [mariadb package](https://www.npmjs.com/package/mariadb)
176
- - Sql Server [mssql package](https://www.npmjs.com/package/mssql)
177
- - Custom base encoding [base-x package](https://www.npmjs.com/package/base-x)
198
+ - Postgresql [node-postgres package](https://github.com/brianc/node-postgres)
199
+ - Mariadb / Mysql [mariadb package](https://github.com/mariadb-corporation/mariadb-connector-nodejs)
200
+ - Sql Server [mssql package](https://github.com/tediousjs/node-mssql)
201
+ - Custom base encoding [base-x package](https://github.com/cryptocoinjs/base-x)
202
+ - AWS JS SDK [aws-sdk](https://github.com/aws/aws-sdk-js-v3)
203
+ - Azure JS SDK [azure](https://github.com/Azure/azure-sdk-for-js)
178
204
 
179
205
  ## Bun
180
206
  OINO has been developed using the Bun runtime, not because of the speed improvements but for the first class Typescript support and integrated developper experience. Kudos on the bun team for making Typescript work more exiting again.
@@ -443,16 +443,14 @@ WHERE C.TABLE_CATALOG = '${dbName}';`;
443
443
  return sql;
444
444
  }
445
445
  /**
446
- * Initialize a data model by getting the SQL schema and populating OINODataFields of
447
- * the model.
446
+ * Get the schema fields of a table as `OINODataField`s (without any API-level field filtering).
448
447
  *
449
- * @param api api which data model to initialize.
448
+ * @param tableName name of the table
450
449
  *
451
450
  */
452
- async initializeApiDatamodel(api) {
453
- api.initializeDatamodel(new db_1.OINODbDataModel(api));
454
- //"SELECT COLUMN_NAME, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX
455
- const schema_res = await this.sqlSelect(this._getSchemaSql(this.dbParams.database, api.params.tableName));
451
+ async getSchemaFields(tableName) {
452
+ const fields = [];
453
+ const schema_res = await this.sqlSelect(this._getSchemaSql(this.dbParams.database, tableName));
456
454
  while (!schema_res.isEof()) {
457
455
  const row = schema_res.getRow();
458
456
  const field_name = row[0]?.toString() || "";
@@ -467,45 +465,104 @@ WHERE C.TABLE_CATALOG = '${dbName}';`;
467
465
  isAutoInc: row[7] == 1,
468
466
  isNotNull: row[1] == "NO"
469
467
  };
470
- if (api.isFieldIncluded(field_name) == false) {
471
- common_1.OINOLog.info("@oino-ts/db-mssql", "OINODbMsSql", "initializeApiDatamodel", "Field excluded in API parameters.", { field: field_name });
472
- if (field_params.isPrimaryKey) {
473
- throw new Error(common_1.OINO_ERROR_PREFIX + "Primary key field excluded in API parameters: " + field_name);
474
- }
468
+ if ((sql_type == "tinyint") || (sql_type == "smallint") || (sql_type == "int") || (sql_type == "bigint") || (sql_type == "float") || (sql_type == "real")) {
469
+ fields.push(new common_1.OINONumberDataField(this, field_name, sql_type, field_params));
470
+ }
471
+ else if ((sql_type == "date") || (sql_type == "datetime") || (sql_type == "datetime2")) {
472
+ fields.push(new common_1.OINODatetimeDataField(this, field_name, sql_type, field_params));
473
+ }
474
+ else if ((sql_type == "ntext") || (sql_type == "nchar") || (sql_type == "nvarchar") || (sql_type == "text") || (sql_type == "char") || (sql_type == "varchar")) {
475
+ fields.push(new common_1.OINOStringDataField(this, field_name, sql_type, field_params, char_field_length));
476
+ }
477
+ else if ((sql_type == "binary") || (sql_type == "varbinary") || (sql_type == "image")) {
478
+ fields.push(new common_1.OINOBlobDataField(this, field_name, sql_type, field_params, char_field_length));
479
+ }
480
+ else if ((sql_type == "numeric") || (sql_type == "decimal") || (sql_type == "money")) {
481
+ fields.push(new common_1.OINOStringDataField(this, field_name, sql_type, field_params, numeric_field_length1 + numeric_field_length2 + 1));
482
+ }
483
+ else if (sql_type == "bit") {
484
+ fields.push(new common_1.OINOBooleanDataField(this, field_name, sql_type, field_params));
475
485
  }
476
486
  else {
477
- if ((sql_type == "tinyint") || (sql_type == "smallint") || (sql_type == "int") || (sql_type == "bigint") || (sql_type == "float") || (sql_type == "real")) {
478
- api.datamodel.addField(new common_1.OINONumberDataField(this, field_name, sql_type, field_params));
479
- }
480
- else if ((sql_type == "date") || (sql_type == "datetime") || (sql_type == "datetime2")) {
481
- if (api.params.useDatesAsString) {
482
- api.datamodel.addField(new common_1.OINOStringDataField(this, field_name, sql_type, field_params, 0));
483
- }
484
- else {
485
- api.datamodel.addField(new common_1.OINODatetimeDataField(this, field_name, sql_type, field_params));
486
- }
487
- }
488
- else if ((sql_type == "ntext") || (sql_type == "nchar") || (sql_type == "nvarchar") || (sql_type == "text") || (sql_type == "char") || (sql_type == "varchar")) {
489
- api.datamodel.addField(new common_1.OINOStringDataField(this, field_name, sql_type, field_params, char_field_length));
490
- }
491
- else if ((sql_type == "binary") || (sql_type == "varbinary") || (sql_type == "image")) {
492
- api.datamodel.addField(new common_1.OINOBlobDataField(this, field_name, sql_type, field_params, char_field_length));
493
- }
494
- else if ((sql_type == "numeric") || (sql_type == "decimal") || (sql_type == "money")) {
495
- api.datamodel.addField(new common_1.OINOStringDataField(this, field_name, sql_type, field_params, numeric_field_length1 + numeric_field_length2 + 1));
496
- }
497
- else if ((sql_type == "bit")) {
498
- api.datamodel.addField(new common_1.OINOBooleanDataField(this, field_name, sql_type, field_params));
499
- }
500
- else {
501
- common_1.OINOLog.info("@oino-ts/db-mssql", "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 });
502
- api.datamodel.addField(new common_1.OINOStringDataField(this, field_name, sql_type, field_params, 0));
503
- }
487
+ fields.push(new common_1.OINOStringDataField(this, field_name, sql_type, field_params, 0));
504
488
  }
505
489
  await schema_res.next();
506
490
  }
507
- common_1.OINOLog.info("@oino-ts/db-mssql", "OINODbMsSql", "initializeApiDatamodel", "\n" + api.datamodel.printDebug("\n"));
508
- return Promise.resolve();
491
+ return fields;
492
+ }
493
+ /**
494
+ * Resolve the optimal native (SQL) type for a serialized field schema.
495
+ *
496
+ * @param schema serialized field schema
497
+ *
498
+ */
499
+ getNativeDataType(schema) {
500
+ switch (schema.type) {
501
+ case "string":
502
+ return (schema.maxLength || 0) > 0 ? "nvarchar(" + schema.maxLength + ")" : "nvarchar(max)";
503
+ case "number":
504
+ return "float";
505
+ case "boolean":
506
+ return "bit";
507
+ case "datetime":
508
+ return "datetime2";
509
+ case "blob":
510
+ return "varbinary(max)";
511
+ default:
512
+ throw new Error(common_1.OINO_ERROR_PREFIX + ": OINODbMsSql.getNativeDataType - unsupported field type '" + schema.type + "'");
513
+ }
514
+ }
515
+ /**
516
+ * Print SQL CREATE TABLE statement.
517
+ *
518
+ * @param tableName name of the table
519
+ * @param fields fields of the table
520
+ *
521
+ */
522
+ printSqlCreateTable(tableName, fields) {
523
+ const columns = [];
524
+ const primary_keys = [];
525
+ for (const field of fields) {
526
+ columns.push(this._printColumnDefinition(field));
527
+ if (field.fieldParams.isPrimaryKey) {
528
+ primary_keys.push(this.printColumnName(field.name));
529
+ }
530
+ }
531
+ let result = "CREATE TABLE dbo." + this.printTableName(tableName) + " (" + columns.join(", ");
532
+ if (primary_keys.length > 0) {
533
+ result += ", PRIMARY KEY (" + primary_keys.join(", ") + ")";
534
+ }
535
+ result += ");";
536
+ return result;
537
+ }
538
+ /**
539
+ * Print SQL ADD COLUMN statement.
540
+ *
541
+ * @param tableName name of the table
542
+ * @param field field to add
543
+ *
544
+ */
545
+ printSqlCreateColumn(tableName, field) {
546
+ return "ALTER TABLE dbo." + this.printTableName(tableName) + " ADD " + this._printColumnDefinition(field) + ";";
547
+ }
548
+ /**
549
+ * Print SQL DROP TABLE statement.
550
+ *
551
+ * @param tableName name of the table
552
+ *
553
+ */
554
+ printSqlDropTable(tableName) {
555
+ return "DROP TABLE dbo." + this.printTableName(tableName) + ";";
556
+ }
557
+ /**
558
+ * Print SQL DROP COLUMN statement.
559
+ *
560
+ * @param tableName name of the table
561
+ * @param columnName name of the column
562
+ *
563
+ */
564
+ printSqlDropColumn(tableName, columnName) {
565
+ return "ALTER TABLE dbo." + this.printTableName(tableName) + " DROP COLUMN " + this.printColumnName(columnName) + ";";
509
566
  }
510
567
  }
511
568
  exports.OINODbMsSql = OINODbMsSql;
@@ -4,7 +4,7 @@
4
4
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
5
  */
6
6
  import { OINO_ERROR_PREFIX, OINOBenchmark, OINO_INFO_PREFIX, OINOLog, OINOResult, OINODataSet, OINOBooleanDataField, OINONumberDataField, OINOStringDataField, OINODatetimeDataField, OINOBlobDataField, OINO_EMPTY_ROW, OINO_EMPTY_ROWS } from "@oino-ts/common";
7
- import { OINODb, OINODbDataModel } from "@oino-ts/db";
7
+ import { OINODb } from "@oino-ts/db";
8
8
  import { ConnectionPool } from "mssql";
9
9
  /**
10
10
  * Implmentation of OINODataSet for MsSql.
@@ -440,16 +440,14 @@ WHERE C.TABLE_CATALOG = '${dbName}';`;
440
440
  return sql;
441
441
  }
442
442
  /**
443
- * Initialize a data model by getting the SQL schema and populating OINODataFields of
444
- * the model.
443
+ * Get the schema fields of a table as `OINODataField`s (without any API-level field filtering).
445
444
  *
446
- * @param api api which data model to initialize.
445
+ * @param tableName name of the table
447
446
  *
448
447
  */
449
- async initializeApiDatamodel(api) {
450
- api.initializeDatamodel(new OINODbDataModel(api));
451
- //"SELECT COLUMN_NAME, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX
452
- const schema_res = await this.sqlSelect(this._getSchemaSql(this.dbParams.database, api.params.tableName));
448
+ async getSchemaFields(tableName) {
449
+ const fields = [];
450
+ const schema_res = await this.sqlSelect(this._getSchemaSql(this.dbParams.database, tableName));
453
451
  while (!schema_res.isEof()) {
454
452
  const row = schema_res.getRow();
455
453
  const field_name = row[0]?.toString() || "";
@@ -464,44 +462,103 @@ WHERE C.TABLE_CATALOG = '${dbName}';`;
464
462
  isAutoInc: row[7] == 1,
465
463
  isNotNull: row[1] == "NO"
466
464
  };
467
- if (api.isFieldIncluded(field_name) == false) {
468
- OINOLog.info("@oino-ts/db-mssql", "OINODbMsSql", "initializeApiDatamodel", "Field excluded in API parameters.", { field: field_name });
469
- if (field_params.isPrimaryKey) {
470
- throw new Error(OINO_ERROR_PREFIX + "Primary key field excluded in API parameters: " + field_name);
471
- }
465
+ if ((sql_type == "tinyint") || (sql_type == "smallint") || (sql_type == "int") || (sql_type == "bigint") || (sql_type == "float") || (sql_type == "real")) {
466
+ fields.push(new OINONumberDataField(this, field_name, sql_type, field_params));
467
+ }
468
+ else if ((sql_type == "date") || (sql_type == "datetime") || (sql_type == "datetime2")) {
469
+ fields.push(new OINODatetimeDataField(this, field_name, sql_type, field_params));
470
+ }
471
+ else if ((sql_type == "ntext") || (sql_type == "nchar") || (sql_type == "nvarchar") || (sql_type == "text") || (sql_type == "char") || (sql_type == "varchar")) {
472
+ fields.push(new OINOStringDataField(this, field_name, sql_type, field_params, char_field_length));
473
+ }
474
+ else if ((sql_type == "binary") || (sql_type == "varbinary") || (sql_type == "image")) {
475
+ fields.push(new OINOBlobDataField(this, field_name, sql_type, field_params, char_field_length));
476
+ }
477
+ else if ((sql_type == "numeric") || (sql_type == "decimal") || (sql_type == "money")) {
478
+ fields.push(new OINOStringDataField(this, field_name, sql_type, field_params, numeric_field_length1 + numeric_field_length2 + 1));
479
+ }
480
+ else if (sql_type == "bit") {
481
+ fields.push(new OINOBooleanDataField(this, field_name, sql_type, field_params));
472
482
  }
473
483
  else {
474
- if ((sql_type == "tinyint") || (sql_type == "smallint") || (sql_type == "int") || (sql_type == "bigint") || (sql_type == "float") || (sql_type == "real")) {
475
- api.datamodel.addField(new OINONumberDataField(this, field_name, sql_type, field_params));
476
- }
477
- else if ((sql_type == "date") || (sql_type == "datetime") || (sql_type == "datetime2")) {
478
- if (api.params.useDatesAsString) {
479
- api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0));
480
- }
481
- else {
482
- api.datamodel.addField(new OINODatetimeDataField(this, field_name, sql_type, field_params));
483
- }
484
- }
485
- else if ((sql_type == "ntext") || (sql_type == "nchar") || (sql_type == "nvarchar") || (sql_type == "text") || (sql_type == "char") || (sql_type == "varchar")) {
486
- api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, char_field_length));
487
- }
488
- else if ((sql_type == "binary") || (sql_type == "varbinary") || (sql_type == "image")) {
489
- api.datamodel.addField(new OINOBlobDataField(this, field_name, sql_type, field_params, char_field_length));
490
- }
491
- else if ((sql_type == "numeric") || (sql_type == "decimal") || (sql_type == "money")) {
492
- api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, numeric_field_length1 + numeric_field_length2 + 1));
493
- }
494
- else if ((sql_type == "bit")) {
495
- api.datamodel.addField(new OINOBooleanDataField(this, field_name, sql_type, field_params));
496
- }
497
- else {
498
- OINOLog.info("@oino-ts/db-mssql", "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 });
499
- api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0));
500
- }
484
+ fields.push(new OINOStringDataField(this, field_name, sql_type, field_params, 0));
501
485
  }
502
486
  await schema_res.next();
503
487
  }
504
- OINOLog.info("@oino-ts/db-mssql", "OINODbMsSql", "initializeApiDatamodel", "\n" + api.datamodel.printDebug("\n"));
505
- return Promise.resolve();
488
+ return fields;
489
+ }
490
+ /**
491
+ * Resolve the optimal native (SQL) type for a serialized field schema.
492
+ *
493
+ * @param schema serialized field schema
494
+ *
495
+ */
496
+ getNativeDataType(schema) {
497
+ switch (schema.type) {
498
+ case "string":
499
+ return (schema.maxLength || 0) > 0 ? "nvarchar(" + schema.maxLength + ")" : "nvarchar(max)";
500
+ case "number":
501
+ return "float";
502
+ case "boolean":
503
+ return "bit";
504
+ case "datetime":
505
+ return "datetime2";
506
+ case "blob":
507
+ return "varbinary(max)";
508
+ default:
509
+ throw new Error(OINO_ERROR_PREFIX + ": OINODbMsSql.getNativeDataType - unsupported field type '" + schema.type + "'");
510
+ }
511
+ }
512
+ /**
513
+ * Print SQL CREATE TABLE statement.
514
+ *
515
+ * @param tableName name of the table
516
+ * @param fields fields of the table
517
+ *
518
+ */
519
+ printSqlCreateTable(tableName, fields) {
520
+ const columns = [];
521
+ const primary_keys = [];
522
+ for (const field of fields) {
523
+ columns.push(this._printColumnDefinition(field));
524
+ if (field.fieldParams.isPrimaryKey) {
525
+ primary_keys.push(this.printColumnName(field.name));
526
+ }
527
+ }
528
+ let result = "CREATE TABLE dbo." + this.printTableName(tableName) + " (" + columns.join(", ");
529
+ if (primary_keys.length > 0) {
530
+ result += ", PRIMARY KEY (" + primary_keys.join(", ") + ")";
531
+ }
532
+ result += ");";
533
+ return result;
534
+ }
535
+ /**
536
+ * Print SQL ADD COLUMN statement.
537
+ *
538
+ * @param tableName name of the table
539
+ * @param field field to add
540
+ *
541
+ */
542
+ printSqlCreateColumn(tableName, field) {
543
+ return "ALTER TABLE dbo." + this.printTableName(tableName) + " ADD " + this._printColumnDefinition(field) + ";";
544
+ }
545
+ /**
546
+ * Print SQL DROP TABLE statement.
547
+ *
548
+ * @param tableName name of the table
549
+ *
550
+ */
551
+ printSqlDropTable(tableName) {
552
+ return "DROP TABLE dbo." + this.printTableName(tableName) + ";";
553
+ }
554
+ /**
555
+ * Print SQL DROP COLUMN statement.
556
+ *
557
+ * @param tableName name of the table
558
+ * @param columnName name of the column
559
+ *
560
+ */
561
+ printSqlDropColumn(tableName, columnName) {
562
+ return "ALTER TABLE dbo." + this.printTableName(tableName) + " DROP COLUMN " + this.printColumnName(columnName) + ";";
506
563
  }
507
564
  }
@@ -1,5 +1,5 @@
1
- import { OINOResult, OINODataSet, OINODataCell } from "@oino-ts/common";
2
- import { OINODb, OINODbApi, OINODbParams } from "@oino-ts/db";
1
+ import { OINOResult, OINODataSet, OINODataField, OINODataFieldSchema, OINODataCell } from "@oino-ts/common";
2
+ import { OINODb, OINODbParams } from "@oino-ts/db";
3
3
  /**
4
4
  * Implementation of MsSql-database.
5
5
  *
@@ -106,11 +106,48 @@ export declare class OINODbMsSql extends OINODb {
106
106
  private _getSchemaSql;
107
107
  private _getValidateSql;
108
108
  /**
109
- * Initialize a data model by getting the SQL schema and populating OINODataFields of
110
- * the model.
109
+ * Get the schema fields of a table as `OINODataField`s (without any API-level field filtering).
111
110
  *
112
- * @param api api which data model to initialize.
111
+ * @param tableName name of the table
113
112
  *
114
113
  */
115
- initializeApiDatamodel(api: OINODbApi): Promise<void>;
114
+ getSchemaFields(tableName: string): Promise<OINODataField[]>;
115
+ /**
116
+ * Resolve the optimal native (SQL) type for a serialized field schema.
117
+ *
118
+ * @param schema serialized field schema
119
+ *
120
+ */
121
+ getNativeDataType(schema: OINODataFieldSchema): string;
122
+ /**
123
+ * Print SQL CREATE TABLE statement.
124
+ *
125
+ * @param tableName name of the table
126
+ * @param fields fields of the table
127
+ *
128
+ */
129
+ printSqlCreateTable(tableName: string, fields: OINODataField[]): string;
130
+ /**
131
+ * Print SQL ADD COLUMN statement.
132
+ *
133
+ * @param tableName name of the table
134
+ * @param field field to add
135
+ *
136
+ */
137
+ printSqlCreateColumn(tableName: string, field: OINODataField): string;
138
+ /**
139
+ * Print SQL DROP TABLE statement.
140
+ *
141
+ * @param tableName name of the table
142
+ *
143
+ */
144
+ printSqlDropTable(tableName: string): string;
145
+ /**
146
+ * Print SQL DROP COLUMN statement.
147
+ *
148
+ * @param tableName name of the table
149
+ * @param columnName name of the column
150
+ *
151
+ */
152
+ printSqlDropColumn(tableName: string, columnName: string): string;
116
153
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oino-ts/db-mssql",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
4
4
  "description": "OINO TS package for using Microsoft Sql databases.",
5
5
  "author": "Matias Kiviniemi (pragmatta)",
6
6
  "license": "MPL-2.0",
@@ -22,7 +22,7 @@
22
22
  "module": "./dist/esm/index.js",
23
23
  "types": "./dist/types/index.d.ts",
24
24
  "dependencies": {
25
- "@oino-ts/db": "1.0.8",
25
+ "@oino-ts/db": "1.1.0",
26
26
  "mssql": "^12.0.0"
27
27
  },
28
28
  "devDependencies": {
@@ -4,9 +4,9 @@
4
4
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
5
  */
6
6
 
7
- import { OINO_ERROR_PREFIX, OINOBenchmark, OINO_INFO_PREFIX, OINOLog, OINOResult, OINODataSet, OINOBooleanDataField, OINONumberDataField, OINOStringDataField, OINODataFieldParams, OINODataRow, OINODataCell, OINODatetimeDataField, OINOBlobDataField, OINO_EMPTY_ROW, OINO_EMPTY_ROWS } from "@oino-ts/common";
7
+ import { OINO_ERROR_PREFIX, OINOBenchmark, OINO_INFO_PREFIX, OINOLog, OINOResult, OINODataSet, OINOBooleanDataField, OINONumberDataField, OINOStringDataField, OINODataField, OINODataFieldSchema, OINODataFieldParams, OINODataRow, OINODataCell, OINODatetimeDataField, OINOBlobDataField, OINO_EMPTY_ROW, OINO_EMPTY_ROWS } from "@oino-ts/common";
8
8
 
9
- import { OINODb, OINODbApi, OINODbParams, OINODbDataModel } from "@oino-ts/db";
9
+ import { OINODb, OINODbParams } from "@oino-ts/db";
10
10
 
11
11
  import { ConnectionPool } from "mssql";
12
12
 
@@ -471,16 +471,14 @@ WHERE C.TABLE_CATALOG = '${dbName}';`
471
471
  }
472
472
 
473
473
  /**
474
- * Initialize a data model by getting the SQL schema and populating OINODataFields of
475
- * the model.
474
+ * Get the schema fields of a table as `OINODataField`s (without any API-level field filtering).
476
475
  *
477
- * @param api api which data model to initialize.
476
+ * @param tableName name of the table
478
477
  *
479
478
  */
480
- async initializeApiDatamodel(api:OINODbApi): Promise<void> {
481
- api.initializeDatamodel(new OINODbDataModel(api))
482
- //"SELECT COLUMN_NAME, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX
483
- const schema_res:OINODataSet = await this.sqlSelect(this._getSchemaSql(this.dbParams.database, api.params.tableName))
479
+ async getSchemaFields(tableName:string): Promise<OINODataField[]> {
480
+ const fields:OINODataField[] = []
481
+ const schema_res:OINODataSet = await this.sqlSelect(this._getSchemaSql(this.dbParams.database, tableName))
484
482
  while (!schema_res.isEof()) {
485
483
  const row:OINODataRow = schema_res.getRow()
486
484
  const field_name:string = row[0]?.toString() || ""
@@ -494,45 +492,104 @@ WHERE C.TABLE_CATALOG = '${dbName}';`
494
492
  isForeignKey: constraint_types.indexOf("FOREIGN KEY") >= 0,
495
493
  isAutoInc: row[7] == 1,
496
494
  isNotNull: row[1] == "NO"
497
- }
498
- if (api.isFieldIncluded(field_name) == false) {
499
- OINOLog.info("@oino-ts/db-mssql", "OINODbMsSql", "initializeApiDatamodel", "Field excluded in API parameters.", {field:field_name})
500
- if (field_params.isPrimaryKey) {
501
- throw new Error(OINO_ERROR_PREFIX + "Primary key field excluded in API parameters: " + field_name)
502
- }
503
-
495
+ }
496
+ if ((sql_type == "tinyint") || (sql_type == "smallint") || (sql_type == "int") || (sql_type == "bigint") || (sql_type == "float") || (sql_type == "real")) {
497
+ fields.push(new OINONumberDataField(this, field_name, sql_type, field_params))
498
+ } else if ((sql_type == "date") || (sql_type == "datetime") || (sql_type == "datetime2")) {
499
+ fields.push(new OINODatetimeDataField(this, field_name, sql_type, field_params))
500
+ } else if ((sql_type == "ntext") || (sql_type == "nchar") || (sql_type == "nvarchar") || (sql_type == "text") || (sql_type == "char") || (sql_type == "varchar")) {
501
+ fields.push(new OINOStringDataField(this, field_name, sql_type, field_params, char_field_length))
502
+ } else if ((sql_type == "binary") || (sql_type == "varbinary") || (sql_type == "image")) {
503
+ fields.push(new OINOBlobDataField(this, field_name, sql_type, field_params, char_field_length))
504
+ } else if ((sql_type == "numeric") || (sql_type == "decimal") || (sql_type == "money")) {
505
+ fields.push(new OINOStringDataField(this, field_name, sql_type, field_params, numeric_field_length1 + numeric_field_length2 + 1))
506
+ } else if (sql_type == "bit") {
507
+ fields.push(new OINOBooleanDataField(this, field_name, sql_type, field_params))
504
508
  } else {
505
- if ((sql_type == "tinyint") || (sql_type == "smallint") || (sql_type == "int") || (sql_type == "bigint") || (sql_type == "float") || (sql_type == "real")) {
506
- api.datamodel!.addField(new OINONumberDataField(this, field_name, sql_type, field_params ))
507
-
508
- } else if ((sql_type == "date") || (sql_type == "datetime") || (sql_type == "datetime2")) {
509
- if (api.params.useDatesAsString) {
510
- api.datamodel!.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0))
511
- } else {
512
- api.datamodel!.addField(new OINODatetimeDataField(this, field_name, sql_type, field_params))
513
- }
509
+ fields.push(new OINOStringDataField(this, field_name, sql_type, field_params, 0))
510
+ }
511
+ await schema_res.next()
512
+ }
513
+ return fields
514
+ }
514
515
 
515
- } else if ((sql_type == "ntext") || (sql_type == "nchar") || (sql_type == "nvarchar") || (sql_type == "text") || (sql_type == "char") || (sql_type == "varchar")) {
516
- api.datamodel!.addField(new OINOStringDataField(this, field_name, sql_type, field_params, char_field_length))
516
+ /**
517
+ * Resolve the optimal native (SQL) type for a serialized field schema.
518
+ *
519
+ * @param schema serialized field schema
520
+ *
521
+ */
522
+ getNativeDataType(schema:OINODataFieldSchema): string {
523
+ switch (schema.type) {
524
+ case "string":
525
+ return (schema.maxLength || 0) > 0 ? "nvarchar(" + schema.maxLength + ")" : "nvarchar(max)"
526
+ case "number":
527
+ return "float"
528
+ case "boolean":
529
+ return "bit"
530
+ case "datetime":
531
+ return "datetime2"
532
+ case "blob":
533
+ return "varbinary(max)"
534
+ default:
535
+ throw new Error(OINO_ERROR_PREFIX + ": OINODbMsSql.getNativeDataType - unsupported field type '" + schema.type + "'")
536
+ }
537
+ }
517
538
 
518
- } else if ((sql_type == "binary") || (sql_type == "varbinary") || (sql_type == "image")) {
519
- api.datamodel!.addField(new OINOBlobDataField(this, field_name, sql_type, field_params, char_field_length))
539
+ /**
540
+ * Print SQL CREATE TABLE statement.
541
+ *
542
+ * @param tableName name of the table
543
+ * @param fields fields of the table
544
+ *
545
+ */
546
+ printSqlCreateTable(tableName:string, fields:OINODataField[]): string {
547
+ const columns:string[] = []
548
+ const primary_keys:string[] = []
549
+ for (const field of fields) {
550
+ columns.push(this._printColumnDefinition(field))
551
+ if (field.fieldParams.isPrimaryKey) {
552
+ primary_keys.push(this.printColumnName(field.name))
553
+ }
554
+ }
555
+ let result:string = "CREATE TABLE dbo." + this.printTableName(tableName) + " (" + columns.join(", ")
556
+ if (primary_keys.length > 0) {
557
+ result += ", PRIMARY KEY (" + primary_keys.join(", ") + ")"
558
+ }
559
+ result += ");"
560
+ return result
561
+ }
520
562
 
521
- } else if ((sql_type == "numeric") || (sql_type == "decimal") || (sql_type == "money")) {
522
- api.datamodel!.addField(new OINOStringDataField(this, field_name, sql_type, field_params, numeric_field_length1 + numeric_field_length2 + 1))
563
+ /**
564
+ * Print SQL ADD COLUMN statement.
565
+ *
566
+ * @param tableName name of the table
567
+ * @param field field to add
568
+ *
569
+ */
570
+ printSqlCreateColumn(tableName:string, field:OINODataField): string {
571
+ return "ALTER TABLE dbo." + this.printTableName(tableName) + " ADD " + this._printColumnDefinition(field) + ";"
572
+ }
523
573
 
524
- } else if ((sql_type == "bit")) {
525
- api.datamodel!.addField(new OINOBooleanDataField(this, field_name, sql_type, field_params))
574
+ /**
575
+ * Print SQL DROP TABLE statement.
576
+ *
577
+ * @param tableName name of the table
578
+ *
579
+ */
580
+ printSqlDropTable(tableName:string): string {
581
+ return "DROP TABLE dbo." + this.printTableName(tableName) + ";"
582
+ }
526
583
 
527
- } else {
528
- OINOLog.info("@oino-ts/db-mssql", "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 })
529
- api.datamodel!.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0))
530
- }
531
- }
532
- await schema_res.next()
533
- }
534
- OINOLog.info("@oino-ts/db-mssql", "OINODbMsSql", "initializeApiDatamodel", "\n" + api.datamodel!.printDebug("\n"))
535
- return Promise.resolve()
584
+ /**
585
+ * Print SQL DROP COLUMN statement.
586
+ *
587
+ * @param tableName name of the table
588
+ * @param columnName name of the column
589
+ *
590
+ */
591
+ printSqlDropColumn(tableName:string, columnName:string): string {
592
+ return "ALTER TABLE dbo." + this.printTableName(tableName) + " DROP COLUMN " + this.printColumnName(columnName) + ";"
536
593
  }
537
594
 
538
595
  }