@oino-ts/db-mariadb 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/dist/cjs/OINODbMariadb.js +49 -44
- package/dist/esm/OINODbMariadb.js +50 -45
- package/dist/types/OINODbMariadb.d.ts +12 -6
- package/package.json +3 -3
- package/readme.md +209 -0
- package/src/OINODbMariadb.ts +48 -47
|
@@ -391,18 +391,16 @@ WHERE C.TABLE_SCHEMA = '${dbName}';`;
|
|
|
391
391
|
return sql;
|
|
392
392
|
}
|
|
393
393
|
/**
|
|
394
|
-
*
|
|
395
|
-
* the model.
|
|
394
|
+
* Get the schema fields of a table as `OINODataField`s (without any API-level field filtering).
|
|
396
395
|
*
|
|
397
|
-
* @param
|
|
396
|
+
* @param tableName name of the table
|
|
398
397
|
*
|
|
399
398
|
*/
|
|
400
|
-
async
|
|
401
|
-
|
|
402
|
-
const schema_res = await this._query(this._getSchemaSql(this.dbParams.database,
|
|
399
|
+
async getSchemaFields(tableName) {
|
|
400
|
+
const fields = [];
|
|
401
|
+
const schema_res = await this._query(this._getSchemaSql(this.dbParams.database, tableName));
|
|
403
402
|
while (!schema_res.isEof()) {
|
|
404
403
|
const row = schema_res.getRow();
|
|
405
|
-
// console.log("OINODbMariadb.initializeApiDatamodel row", row)
|
|
406
404
|
const field_name = row[0]?.toString() || "";
|
|
407
405
|
const field_matches = OINODbMariadb._fieldLengthRegex.exec(row[1]?.toString() || "") || [];
|
|
408
406
|
const sql_type = field_matches[1] || "";
|
|
@@ -415,50 +413,57 @@ WHERE C.TABLE_SCHEMA = '${dbName}';`;
|
|
|
415
413
|
isAutoInc: extra.indexOf('auto_increment') >= 0,
|
|
416
414
|
isNotNull: row[2] == "NO"
|
|
417
415
|
};
|
|
418
|
-
if (
|
|
419
|
-
|
|
420
|
-
if (field_params.isPrimaryKey) {
|
|
421
|
-
throw new Error(common_1.OINO_ERROR_PREFIX + "Primary key field excluded in API parameters: " + field_name);
|
|
422
|
-
}
|
|
416
|
+
if ((sql_type == "int") || (sql_type == "smallint") || (sql_type == "float") || (sql_type == "double")) {
|
|
417
|
+
fields.push(new common_2.OINONumberDataField(this, field_name, sql_type, field_params));
|
|
423
418
|
}
|
|
424
|
-
else {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
else if ((sql_type == "longblob") || (sql_type == "binary") || (sql_type == "varbinary")) {
|
|
440
|
-
api.datamodel.addField(new common_2.OINOBlobDataField(this, field_name, sql_type, field_params, field_length1));
|
|
441
|
-
}
|
|
442
|
-
else if ((sql_type == "decimal")) {
|
|
443
|
-
api.datamodel.addField(new common_2.OINOStringDataField(this, field_name, sql_type, field_params, field_length1 + field_length2 + 1));
|
|
444
|
-
}
|
|
445
|
-
else if ((sql_type == "bit")) {
|
|
446
|
-
if (field_length1 == 1) {
|
|
447
|
-
api.datamodel.addField(new common_2.OINOBooleanDataField(this, field_name, sql_type, field_params));
|
|
448
|
-
}
|
|
449
|
-
else {
|
|
450
|
-
api.datamodel.addField(new common_2.OINOStringDataField(this, field_name, sql_type, field_params, field_length1 * 8));
|
|
451
|
-
}
|
|
419
|
+
else if ((sql_type == "date") || (sql_type == "datetime") || (sql_type == "timestamp")) {
|
|
420
|
+
fields.push(new common_2.OINODatetimeDataField(this, field_name, sql_type, field_params));
|
|
421
|
+
}
|
|
422
|
+
else if ((sql_type == "char") || (sql_type == "varchar") || (sql_type == "tinytext") || (sql_type == "mediumtext") || (sql_type == "longtext")) {
|
|
423
|
+
fields.push(new common_2.OINOStringDataField(this, field_name, sql_type, field_params, field_length1));
|
|
424
|
+
}
|
|
425
|
+
else if ((sql_type == "longblob") || (sql_type == "binary") || (sql_type == "varbinary")) {
|
|
426
|
+
fields.push(new common_2.OINOBlobDataField(this, field_name, sql_type, field_params, field_length1));
|
|
427
|
+
}
|
|
428
|
+
else if (sql_type == "decimal") {
|
|
429
|
+
fields.push(new common_2.OINOStringDataField(this, field_name, sql_type, field_params, field_length1 + field_length2 + 1));
|
|
430
|
+
}
|
|
431
|
+
else if (sql_type == "bit") {
|
|
432
|
+
if (field_length1 == 1) {
|
|
433
|
+
fields.push(new common_2.OINOBooleanDataField(this, field_name, sql_type, field_params));
|
|
452
434
|
}
|
|
453
435
|
else {
|
|
454
|
-
|
|
455
|
-
api.datamodel.addField(new common_2.OINOStringDataField(this, field_name, sql_type, field_params, 0));
|
|
436
|
+
fields.push(new common_2.OINOStringDataField(this, field_name, sql_type, field_params, field_length1 * 8));
|
|
456
437
|
}
|
|
457
438
|
}
|
|
439
|
+
else {
|
|
440
|
+
fields.push(new common_2.OINOStringDataField(this, field_name, sql_type, field_params, 0));
|
|
441
|
+
}
|
|
458
442
|
await schema_res.next();
|
|
459
443
|
}
|
|
460
|
-
|
|
461
|
-
|
|
444
|
+
return fields;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Resolve the optimal native (SQL) type for a serialized field schema.
|
|
448
|
+
*
|
|
449
|
+
* @param schema serialized field schema
|
|
450
|
+
*
|
|
451
|
+
*/
|
|
452
|
+
getNativeDataType(schema) {
|
|
453
|
+
switch (schema.type) {
|
|
454
|
+
case "string":
|
|
455
|
+
return (schema.maxLength || 0) > 0 ? "varchar(" + schema.maxLength + ")" : "longtext";
|
|
456
|
+
case "number":
|
|
457
|
+
return "double";
|
|
458
|
+
case "boolean":
|
|
459
|
+
return "bit(1)";
|
|
460
|
+
case "datetime":
|
|
461
|
+
return "datetime";
|
|
462
|
+
case "blob":
|
|
463
|
+
return "longblob";
|
|
464
|
+
default:
|
|
465
|
+
throw new Error(common_1.OINO_ERROR_PREFIX + ": OINODbMariadb.getNativeDataType - unsupported field type '" + schema.type + "'");
|
|
466
|
+
}
|
|
462
467
|
}
|
|
463
468
|
}
|
|
464
469
|
exports.OINODbMariadb = OINODbMariadb;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { OINO_ERROR_PREFIX, OINOBenchmark, OINO_INFO_PREFIX, OINOLog, OINOResult } from "@oino-ts/common";
|
|
7
7
|
import { OINODataSet, OINOBooleanDataField, OINONumberDataField, OINOStringDataField, OINODatetimeDataField, OINOBlobDataField, OINO_EMPTY_ROW, OINO_EMPTY_ROWS } from "@oino-ts/common";
|
|
8
|
-
import { OINODb
|
|
8
|
+
import { OINODb } from "@oino-ts/db";
|
|
9
9
|
import mariadb from "mariadb";
|
|
10
10
|
/**
|
|
11
11
|
* Implmentation of OINODataSet for MariaDb.
|
|
@@ -388,18 +388,16 @@ WHERE C.TABLE_SCHEMA = '${dbName}';`;
|
|
|
388
388
|
return sql;
|
|
389
389
|
}
|
|
390
390
|
/**
|
|
391
|
-
*
|
|
392
|
-
* the model.
|
|
391
|
+
* Get the schema fields of a table as `OINODataField`s (without any API-level field filtering).
|
|
393
392
|
*
|
|
394
|
-
* @param
|
|
393
|
+
* @param tableName name of the table
|
|
395
394
|
*
|
|
396
395
|
*/
|
|
397
|
-
async
|
|
398
|
-
|
|
399
|
-
const schema_res = await this._query(this._getSchemaSql(this.dbParams.database,
|
|
396
|
+
async getSchemaFields(tableName) {
|
|
397
|
+
const fields = [];
|
|
398
|
+
const schema_res = await this._query(this._getSchemaSql(this.dbParams.database, tableName));
|
|
400
399
|
while (!schema_res.isEof()) {
|
|
401
400
|
const row = schema_res.getRow();
|
|
402
|
-
// console.log("OINODbMariadb.initializeApiDatamodel row", row)
|
|
403
401
|
const field_name = row[0]?.toString() || "";
|
|
404
402
|
const field_matches = OINODbMariadb._fieldLengthRegex.exec(row[1]?.toString() || "") || [];
|
|
405
403
|
const sql_type = field_matches[1] || "";
|
|
@@ -412,49 +410,56 @@ WHERE C.TABLE_SCHEMA = '${dbName}';`;
|
|
|
412
410
|
isAutoInc: extra.indexOf('auto_increment') >= 0,
|
|
413
411
|
isNotNull: row[2] == "NO"
|
|
414
412
|
};
|
|
415
|
-
if (
|
|
416
|
-
|
|
417
|
-
if (field_params.isPrimaryKey) {
|
|
418
|
-
throw new Error(OINO_ERROR_PREFIX + "Primary key field excluded in API parameters: " + field_name);
|
|
419
|
-
}
|
|
413
|
+
if ((sql_type == "int") || (sql_type == "smallint") || (sql_type == "float") || (sql_type == "double")) {
|
|
414
|
+
fields.push(new OINONumberDataField(this, field_name, sql_type, field_params));
|
|
420
415
|
}
|
|
421
|
-
else {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
else if ((sql_type == "longblob") || (sql_type == "binary") || (sql_type == "varbinary")) {
|
|
437
|
-
api.datamodel.addField(new OINOBlobDataField(this, field_name, sql_type, field_params, field_length1));
|
|
438
|
-
}
|
|
439
|
-
else if ((sql_type == "decimal")) {
|
|
440
|
-
api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1 + field_length2 + 1));
|
|
441
|
-
}
|
|
442
|
-
else if ((sql_type == "bit")) {
|
|
443
|
-
if (field_length1 == 1) {
|
|
444
|
-
api.datamodel.addField(new OINOBooleanDataField(this, field_name, sql_type, field_params));
|
|
445
|
-
}
|
|
446
|
-
else {
|
|
447
|
-
api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1 * 8));
|
|
448
|
-
}
|
|
416
|
+
else if ((sql_type == "date") || (sql_type == "datetime") || (sql_type == "timestamp")) {
|
|
417
|
+
fields.push(new OINODatetimeDataField(this, field_name, sql_type, field_params));
|
|
418
|
+
}
|
|
419
|
+
else if ((sql_type == "char") || (sql_type == "varchar") || (sql_type == "tinytext") || (sql_type == "mediumtext") || (sql_type == "longtext")) {
|
|
420
|
+
fields.push(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1));
|
|
421
|
+
}
|
|
422
|
+
else if ((sql_type == "longblob") || (sql_type == "binary") || (sql_type == "varbinary")) {
|
|
423
|
+
fields.push(new OINOBlobDataField(this, field_name, sql_type, field_params, field_length1));
|
|
424
|
+
}
|
|
425
|
+
else if (sql_type == "decimal") {
|
|
426
|
+
fields.push(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1 + field_length2 + 1));
|
|
427
|
+
}
|
|
428
|
+
else if (sql_type == "bit") {
|
|
429
|
+
if (field_length1 == 1) {
|
|
430
|
+
fields.push(new OINOBooleanDataField(this, field_name, sql_type, field_params));
|
|
449
431
|
}
|
|
450
432
|
else {
|
|
451
|
-
|
|
452
|
-
api.datamodel.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0));
|
|
433
|
+
fields.push(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1 * 8));
|
|
453
434
|
}
|
|
454
435
|
}
|
|
436
|
+
else {
|
|
437
|
+
fields.push(new OINOStringDataField(this, field_name, sql_type, field_params, 0));
|
|
438
|
+
}
|
|
455
439
|
await schema_res.next();
|
|
456
440
|
}
|
|
457
|
-
|
|
458
|
-
|
|
441
|
+
return fields;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Resolve the optimal native (SQL) type for a serialized field schema.
|
|
445
|
+
*
|
|
446
|
+
* @param schema serialized field schema
|
|
447
|
+
*
|
|
448
|
+
*/
|
|
449
|
+
getNativeDataType(schema) {
|
|
450
|
+
switch (schema.type) {
|
|
451
|
+
case "string":
|
|
452
|
+
return (schema.maxLength || 0) > 0 ? "varchar(" + schema.maxLength + ")" : "longtext";
|
|
453
|
+
case "number":
|
|
454
|
+
return "double";
|
|
455
|
+
case "boolean":
|
|
456
|
+
return "bit(1)";
|
|
457
|
+
case "datetime":
|
|
458
|
+
return "datetime";
|
|
459
|
+
case "blob":
|
|
460
|
+
return "longblob";
|
|
461
|
+
default:
|
|
462
|
+
throw new Error(OINO_ERROR_PREFIX + ": OINODbMariadb.getNativeDataType - unsupported field type '" + schema.type + "'");
|
|
463
|
+
}
|
|
459
464
|
}
|
|
460
465
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { OINOResult } from "@oino-ts/common";
|
|
2
|
-
import { OINODataSet, OINODataCell } from "@oino-ts/common";
|
|
3
|
-
import { OINODb,
|
|
2
|
+
import { OINODataSet, OINODataField, OINODataFieldSchema, OINODataCell } from "@oino-ts/common";
|
|
3
|
+
import { OINODb, OINODbParams } from "@oino-ts/db";
|
|
4
4
|
/**
|
|
5
5
|
* Implementation of MariaDb/MySql-database.
|
|
6
6
|
*
|
|
@@ -89,11 +89,17 @@ export declare class OINODbMariadb extends OINODb {
|
|
|
89
89
|
private _getSchemaSql;
|
|
90
90
|
private _getValidateSql;
|
|
91
91
|
/**
|
|
92
|
-
*
|
|
93
|
-
* the model.
|
|
92
|
+
* Get the schema fields of a table as `OINODataField`s (without any API-level field filtering).
|
|
94
93
|
*
|
|
95
|
-
* @param
|
|
94
|
+
* @param tableName name of the table
|
|
96
95
|
*
|
|
97
96
|
*/
|
|
98
|
-
|
|
97
|
+
getSchemaFields(tableName: string): Promise<OINODataField[]>;
|
|
98
|
+
/**
|
|
99
|
+
* Resolve the optimal native (SQL) type for a serialized field schema.
|
|
100
|
+
*
|
|
101
|
+
* @param schema serialized field schema
|
|
102
|
+
*
|
|
103
|
+
*/
|
|
104
|
+
getNativeDataType(schema: OINODataFieldSchema): string;
|
|
99
105
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oino-ts/db-mariadb",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "OINO TS package for using Mariadb databases.",
|
|
5
5
|
"author": "Matias Kiviniemi (pragmatta)",
|
|
6
6
|
"license": "MPL-2.0",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"module": "./dist/esm/index.js",
|
|
22
22
|
"types": "./dist/types/index.d.ts",
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@oino-ts/common": "^1.0
|
|
25
|
-
"@oino-ts/db": "^1.0
|
|
24
|
+
"@oino-ts/common": "^1.1.0",
|
|
25
|
+
"@oino-ts/db": "^1.1.0",
|
|
26
26
|
"mariadb": "^3.2.3"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
package/readme.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# OINO TS
|
|
2
|
+
OINO Is Not an ORM but it's trying to solve a similar problem for API development. Instead of mirroring your DB schema in code that needs manual updates, OINO will get the data schema from DBMS using SQL in real time. Every time your app starts, it has an updated data model which enables automatic (de)serialize SQL results to JSON/CSV and back. OINO works on the level below routing where you pass the method, URL ID, body and request parameters to the API-object. OINO will parse and validate the data against the data model and generate proper SQL for your DB. Because OINO knows how data is serialized (e.g. JSON), what column it belongs to (e.g. floating point number) and what the target database is, it knows how to parse, format and escape the value as valid SQL.
|
|
3
|
+
|
|
4
|
+
```
|
|
5
|
+
const result:OINOApiResult = await api_orderdetails.doRequest("GET", id, body, params)
|
|
6
|
+
return new Response(result.modelset.writeString(OINOContentType.json))
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# GETTING STARTED
|
|
11
|
+
|
|
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.
|
|
17
|
+
```
|
|
18
|
+
bun install @oino-ts/db
|
|
19
|
+
bun install @oino-ts/db-bunsqlite
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
import { OINODb, OINODbFactory } from "@oino-ts/db";
|
|
24
|
+
import { OINOApi } from "@oino-ts/db";
|
|
25
|
+
import { OINODbBunSqlite } from "@oino-ts/db-bunsqlite"
|
|
26
|
+
```
|
|
27
|
+
|
|
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)
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
OINOLog.setLogger(new OINOConsoleLog())
|
|
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" } )
|
|
38
|
+
```
|
|
39
|
+
|
|
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
|
|
52
|
+
```
|
|
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)
|
|
56
|
+
```
|
|
57
|
+
|
|
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.
|
|
62
|
+
```
|
|
63
|
+
const api_employees:OINOApi = await OINOFactory.createApi(db, { tableName: "Employees", excludeFields:["BirthDate"] })
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Pass HTTP requests to API
|
|
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.
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
const result:OINOApiResult = await api_orderdetails.doRequest("GET", id, body, params)
|
|
71
|
+
```
|
|
72
|
+
|
|
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.
|
|
75
|
+
```
|
|
76
|
+
return new Response(result.data.writeString(OINOContentType.json))
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# MAIN FEATURES
|
|
81
|
+
|
|
82
|
+
## RESTfull
|
|
83
|
+
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.
|
|
84
|
+
|
|
85
|
+
For example HTTP POST
|
|
86
|
+
```
|
|
87
|
+
Request and response:
|
|
88
|
+
> curl.exe -X POST http://localhost:3001/orderdetails -H "Content-Type: application/json" --data '[{\"OrderID\":11077,\"ProductID\":99,\"UnitPrice\":19,\"Quantity\":1,\"Discount\":0}]'
|
|
89
|
+
{"success":true,"statusCode":200,"statusMessage":"OK","messages":[]}
|
|
90
|
+
|
|
91
|
+
SQL:
|
|
92
|
+
INSERT INTO [OrderDetails] ("OrderID","ProductID","UnitPrice","Quantity","Discount") VALUES (11077,99,19,1,0);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
## Universal Serialization
|
|
97
|
+
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.
|
|
98
|
+
|
|
99
|
+
- 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.
|
|
100
|
+
- Datetimes are (optionally) normalized to ISO 8601 format.
|
|
101
|
+
- Extended JSON-encoding
|
|
102
|
+
- 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).
|
|
103
|
+
- CSV
|
|
104
|
+
- Comma-separated, doublequotes.
|
|
105
|
+
- Unquoted literal `null` represents null values.
|
|
106
|
+
- Unquoted empty string represents undefined values.
|
|
107
|
+
- Form data
|
|
108
|
+
- Multipart-mixed and binary files not supported.
|
|
109
|
+
- Non-existent value line (i.e. nothing after the empty line) treated as a null value.
|
|
110
|
+
- Url-encoded
|
|
111
|
+
- No null values, missing properties treated as undefined.
|
|
112
|
+
- Multiple lines could be used to post multiple rows.
|
|
113
|
+
|
|
114
|
+
|
|
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.
|
|
117
|
+
|
|
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
|
|
130
|
+
|
|
131
|
+
## Composite Keys
|
|
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`.
|
|
133
|
+
|
|
134
|
+
## Power Of SQL
|
|
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.
|
|
138
|
+
|
|
139
|
+
## Swagger Support
|
|
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.
|
|
141
|
+
```
|
|
142
|
+
if (url.pathname == "/swagger.json") {
|
|
143
|
+
return new Response(JSON.stringify(OINOSwagger.getApiDefinition(api_array)))
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+

|
|
147
|
+
|
|
148
|
+
## Node support
|
|
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).
|
|
150
|
+
|
|
151
|
+
## HTMX support
|
|
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)).
|
|
153
|
+
|
|
154
|
+
## Hashids
|
|
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.
|
|
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)).
|
|
159
|
+
|
|
160
|
+
## Schema Management
|
|
161
|
+
OINO has endpoints for reading, creating and deleting table and column schemas.
|
|
162
|
+
|
|
163
|
+
|
|
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.
|
|
166
|
+
|
|
167
|
+
## Roadmap
|
|
168
|
+
Major features that are considered in future releases
|
|
169
|
+
|
|
170
|
+
### Views
|
|
171
|
+
It would be interesting to combine multiple datasources as OINO-views like multiple NoSQL-tables.
|
|
172
|
+
|
|
173
|
+
### Streaming
|
|
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.
|
|
175
|
+
|
|
176
|
+
### SQL generation callbacks
|
|
177
|
+
It would be useful to allow developer to validate / override SQL generation to cover cases OINO does not support or even workaround issues.
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# HELP
|
|
181
|
+
|
|
182
|
+
## Bug reports
|
|
183
|
+
Fixing bugs is a priority and getting good quality bug reports helps. It's recommended to use the sample Northwind database included with project to replicate issues or make an SQL script export of the relevant table.
|
|
184
|
+
|
|
185
|
+
## Feedback
|
|
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.
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# LINKS
|
|
190
|
+
- [Github repository](https://github.com/pragmatta/oino-ts)
|
|
191
|
+
- [NPM repository](https://www.npmjs.com/org/oino-ts)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# ACKNOWLEDGEMENTS
|
|
195
|
+
|
|
196
|
+
## Libraries
|
|
197
|
+
OINO uses the following open source libraries and npm packages and I would like to thank everyone for their contributions:
|
|
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)
|
|
204
|
+
|
|
205
|
+
## Bun
|
|
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.
|
|
207
|
+
|
|
208
|
+
## SQL Scripts
|
|
209
|
+
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).
|
package/src/OINODbMariadb.ts
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
import { OINO_ERROR_PREFIX, OINOBenchmark, OINO_INFO_PREFIX, OINOLog, OINOResult } from "@oino-ts/common";
|
|
8
8
|
|
|
9
|
-
import { OINODataSet, OINOBooleanDataField, OINONumberDataField, OINOStringDataField, OINODataFieldParams, OINODataRow, OINODataCell, OINODatetimeDataField, OINOBlobDataField, OINO_EMPTY_ROW, OINO_EMPTY_ROWS } from "@oino-ts/common";
|
|
9
|
+
import { OINODataSet, OINOBooleanDataField, OINONumberDataField, OINOStringDataField, OINODataField, OINODataFieldSchema, OINODataFieldParams, OINODataRow, OINODataCell, OINODatetimeDataField, OINOBlobDataField, OINO_EMPTY_ROW, OINO_EMPTY_ROWS } from "@oino-ts/common";
|
|
10
10
|
|
|
11
|
-
import { OINODb,
|
|
11
|
+
import { OINODb, OINODbParams } from "@oino-ts/db";
|
|
12
12
|
|
|
13
13
|
import mariadb from "mariadb";
|
|
14
14
|
|
|
@@ -418,18 +418,16 @@ WHERE C.TABLE_SCHEMA = '${dbName}';`
|
|
|
418
418
|
|
|
419
419
|
|
|
420
420
|
/**
|
|
421
|
-
*
|
|
422
|
-
* the model.
|
|
421
|
+
* Get the schema fields of a table as `OINODataField`s (without any API-level field filtering).
|
|
423
422
|
*
|
|
424
|
-
* @param
|
|
423
|
+
* @param tableName name of the table
|
|
425
424
|
*
|
|
426
425
|
*/
|
|
427
|
-
async
|
|
428
|
-
|
|
429
|
-
const schema_res:OINODataSet = await this._query(this._getSchemaSql(this.dbParams.database,
|
|
426
|
+
async getSchemaFields(tableName:string): Promise<OINODataField[]> {
|
|
427
|
+
const fields:OINODataField[] = []
|
|
428
|
+
const schema_res:OINODataSet = await this._query(this._getSchemaSql(this.dbParams.database, tableName))
|
|
430
429
|
while (!schema_res.isEof()) {
|
|
431
430
|
const row:OINODataRow = schema_res.getRow()
|
|
432
|
-
// console.log("OINODbMariadb.initializeApiDatamodel row", row)
|
|
433
431
|
const field_name:string = row[0]?.toString() || ""
|
|
434
432
|
const field_matches = OINODbMariadb._fieldLengthRegex.exec(row[1]?.toString() || "") || []
|
|
435
433
|
const sql_type:string = field_matches[1] || ""
|
|
@@ -441,49 +439,52 @@ WHERE C.TABLE_SCHEMA = '${dbName}';`
|
|
|
441
439
|
isForeignKey: row[6] != null,
|
|
442
440
|
isAutoInc: extra.indexOf('auto_increment') >= 0,
|
|
443
441
|
isNotNull: row[2] == "NO"
|
|
444
|
-
}
|
|
445
|
-
if (
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
442
|
+
}
|
|
443
|
+
if ((sql_type == "int") || (sql_type == "smallint") || (sql_type == "float") || (sql_type == "double")) {
|
|
444
|
+
fields.push(new OINONumberDataField(this, field_name, sql_type, field_params))
|
|
445
|
+
} else if ((sql_type == "date") || (sql_type == "datetime") || (sql_type == "timestamp")) {
|
|
446
|
+
fields.push(new OINODatetimeDataField(this, field_name, sql_type, field_params))
|
|
447
|
+
} else if ((sql_type == "char") || (sql_type == "varchar") || (sql_type == "tinytext") || (sql_type == "mediumtext") || (sql_type == "longtext")) {
|
|
448
|
+
fields.push(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1))
|
|
449
|
+
} else if ((sql_type == "longblob") || (sql_type == "binary") || (sql_type == "varbinary")) {
|
|
450
|
+
fields.push(new OINOBlobDataField(this, field_name, sql_type, field_params, field_length1))
|
|
451
|
+
} else if (sql_type == "decimal") {
|
|
452
|
+
fields.push(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1 + field_length2 + 1))
|
|
453
|
+
} else if (sql_type == "bit") {
|
|
454
|
+
if (field_length1 == 1) {
|
|
455
|
+
fields.push(new OINOBooleanDataField(this, field_name, sql_type, field_params))
|
|
456
|
+
} else {
|
|
457
|
+
fields.push(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1 * 8))
|
|
449
458
|
}
|
|
450
|
-
|
|
451
459
|
} else {
|
|
452
|
-
|
|
453
|
-
api.datamodel!.addField(new OINONumberDataField(this, field_name, sql_type, field_params ))
|
|
454
|
-
|
|
455
|
-
} else if ((sql_type == "date") || (sql_type == "datetime") || (sql_type == "timestamp")) {
|
|
456
|
-
if (api.params.useDatesAsString) {
|
|
457
|
-
api.datamodel!.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0))
|
|
458
|
-
} else {
|
|
459
|
-
api.datamodel!.addField(new OINODatetimeDataField(this, field_name, sql_type, field_params))
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
} else if ((sql_type == "char") || (sql_type == "varchar") || (sql_type == "tinytext") || (sql_type == "tinytext") || (sql_type == "mediumtext") || (sql_type == "longtext")) {
|
|
463
|
-
api.datamodel!.addField(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1))
|
|
464
|
-
|
|
465
|
-
} else if ((sql_type == "longblob") || (sql_type == "binary") || (sql_type == "varbinary")) {
|
|
466
|
-
api.datamodel!.addField(new OINOBlobDataField(this, field_name, sql_type, field_params, field_length1))
|
|
467
|
-
|
|
468
|
-
} else if ((sql_type == "decimal")) {
|
|
469
|
-
api.datamodel!.addField(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1 + field_length2 + 1))
|
|
470
|
-
|
|
471
|
-
} else if ((sql_type == "bit")) {
|
|
472
|
-
if (field_length1 == 1) {
|
|
473
|
-
api.datamodel!.addField(new OINOBooleanDataField(this, field_name, sql_type, field_params))
|
|
474
|
-
} else {
|
|
475
|
-
api.datamodel!.addField(new OINOStringDataField(this, field_name, sql_type, field_params, field_length1*8))
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
} else {
|
|
479
|
-
OINOLog.info("@oino-ts/db-mariadb", "OINODbMariadb", "initializeApiDatamodel", "Unrecognized field type treated as string", {field_name: field_name, sql_type:sql_type, field_length1:field_length1, field_length2:field_length2, field_params:field_params })
|
|
480
|
-
api.datamodel!.addField(new OINOStringDataField(this, field_name, sql_type, field_params, 0))
|
|
481
|
-
}
|
|
460
|
+
fields.push(new OINOStringDataField(this, field_name, sql_type, field_params, 0))
|
|
482
461
|
}
|
|
483
462
|
await schema_res.next()
|
|
484
463
|
}
|
|
485
|
-
|
|
486
|
-
|
|
464
|
+
return fields
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Resolve the optimal native (SQL) type for a serialized field schema.
|
|
469
|
+
*
|
|
470
|
+
* @param schema serialized field schema
|
|
471
|
+
*
|
|
472
|
+
*/
|
|
473
|
+
getNativeDataType(schema:OINODataFieldSchema): string {
|
|
474
|
+
switch (schema.type) {
|
|
475
|
+
case "string":
|
|
476
|
+
return (schema.maxLength || 0) > 0 ? "varchar(" + schema.maxLength + ")" : "longtext"
|
|
477
|
+
case "number":
|
|
478
|
+
return "double"
|
|
479
|
+
case "boolean":
|
|
480
|
+
return "bit(1)"
|
|
481
|
+
case "datetime":
|
|
482
|
+
return "datetime"
|
|
483
|
+
case "blob":
|
|
484
|
+
return "longblob"
|
|
485
|
+
default:
|
|
486
|
+
throw new Error(OINO_ERROR_PREFIX + ": OINODbMariadb.getNativeDataType - unsupported field type '" + schema.type + "'")
|
|
487
|
+
}
|
|
487
488
|
}
|
|
488
489
|
}
|
|
489
490
|
|