@oino-ts/db-mariadb 1.1.3 → 1.2.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.
@@ -104,7 +104,7 @@ class OINODbMariadb extends db_1.OINODb {
104
104
  if (this.dbParams.type !== "OINODbMariadb") {
105
105
  throw new Error(common_1.OINO_ERROR_PREFIX + ": Not OINODbMariadb-type: " + this.dbParams.type);
106
106
  }
107
- this._pool = mariadb_1.default.createPool({ host: this.dbParams.url, database: this.dbParams.database, port: this.dbParams.port, user: this.dbParams.user, password: this.dbParams.password, acquireTimeout: 2000, debug: false, rowsAsArray: true, multipleStatements: true });
107
+ this._pool = mariadb_1.default.createPool({ host: this.dbParams.url, database: this.dbParams.database, port: this.dbParams.port, user: this.dbParams.user, password: this.dbParams.password, acquireTimeout: 2000, debug: false, rowsAsArray: true, multipleStatements: false }); // statements are now executed individually with bind parameters, so stacked/multi-statement execution is disabled
108
108
  delete this.dbParams.password; // do not store password in db object
109
109
  }
110
110
  _parseFieldLength(fieldLengthStr) {
@@ -114,12 +114,12 @@ class OINODbMariadb extends db_1.OINODb {
114
114
  }
115
115
  return result;
116
116
  }
117
- async _query(sql) {
117
+ async _query(sql, params) {
118
118
  let connection = null;
119
119
  let rows = common_2.OINO_EMPTY_ROWS;
120
120
  try {
121
121
  connection = await this._pool.getConnection();
122
- const sql_res = await connection.query(sql);
122
+ const sql_res = (params && params.length > 0) ? await connection.query(sql, params) : await connection.query(sql);
123
123
  // console.log("_query: sql=", sql, " result=", result)
124
124
  if (Array.isArray(sql_res)) {
125
125
  rows = sql_res.filter((r) => Array.isArray(r)); // filter out OkPacket results from multiple statements
@@ -136,12 +136,12 @@ class OINODbMariadb extends db_1.OINODb {
136
136
  }
137
137
  return new OINOMariadbData(rows, []);
138
138
  }
139
- async _exec(sql) {
139
+ async _exec(sql, params) {
140
140
  let connection = null;
141
141
  let rows = common_2.OINO_EMPTY_ROWS;
142
142
  try {
143
143
  connection = await this._pool.getConnection();
144
- const sql_res = await connection.query(sql);
144
+ const sql_res = (params && params.length > 0) ? await connection.query(sql, params) : await connection.query(sql);
145
145
  // console.log("OINODbMariadb._exec: result=", result)
146
146
  if (Array.isArray(sql_res)) {
147
147
  rows = sql_res.filter((r) => Array.isArray(r)); // filter out OkPacket results from multiple statements
@@ -166,7 +166,7 @@ class OINODbMariadb extends db_1.OINODb {
166
166
  *
167
167
  */
168
168
  printTableName(sqlTable) {
169
- return "`" + sqlTable + "`";
169
+ return "`" + sqlTable.replaceAll("`", "``") + "`";
170
170
  }
171
171
  /**
172
172
  * Print a column name with correct SQL escaping.
@@ -175,7 +175,33 @@ class OINODbMariadb extends db_1.OINODb {
175
175
  *
176
176
  */
177
177
  printColumnName(sqlColumn) {
178
- return "`" + sqlColumn + "`";
178
+ return "`" + sqlColumn.replaceAll("`", "``") + "`";
179
+ }
180
+ /**
181
+ * Print a bind-parameter placeholder for the given zero-based parameter index (MySQL/MariaDB `?`).
182
+ *
183
+ * @param index zero-based parameter index
184
+ *
185
+ */
186
+ printParameterName(index) {
187
+ return "?";
188
+ }
189
+ /**
190
+ * Coerce a data value into a MariaDB bind-parameter value. The connector binds numbers,
191
+ * strings, `Date` and `Buffer` natively; booleans are mapped to 1/0 for `bit`/numeric columns.
192
+ *
193
+ * @param cellValue data value to bind
194
+ * @param nativeType native type name for the table column
195
+ *
196
+ */
197
+ bindCellValue(cellValue, nativeType) {
198
+ if (cellValue === undefined) {
199
+ return null;
200
+ }
201
+ if (typeof cellValue === "boolean") {
202
+ return cellValue ? 1 : 0;
203
+ }
204
+ return cellValue;
179
205
  }
180
206
  /**
181
207
  * Print a single data value from serialization using the context of the native data
@@ -304,8 +330,8 @@ class OINODbMariadb extends db_1.OINODb {
304
330
  common_1.OINOBenchmark.startMetric("OINODb", "validate");
305
331
  let result = new common_1.OINOResult();
306
332
  try {
307
- const sql = this._getValidateSql(this.dbParams.database);
308
- const sql_res = await this._query(sql);
333
+ const sql = this._getValidateSql();
334
+ const sql_res = await this._query(sql, [this.dbParams.database]);
309
335
  if (sql_res.isEmpty()) {
310
336
  result.setError(400, "DB returned no rows for schema!", "OINODbMariadb.validate");
311
337
  }
@@ -361,7 +387,22 @@ class OINODbMariadb extends db_1.OINODb {
361
387
  common_1.OINOBenchmark.endMetric("OINODb", "sqlExec", result.status != 500);
362
388
  return result;
363
389
  }
364
- _getSchemaSql(dbName, tableName) {
390
+ /**
391
+ * Execute a parameterized statement, binding its values as positional `?` parameters.
392
+ *
393
+ * @param statement statement (SQL text + ordered bind values) to execute
394
+ *
395
+ */
396
+ async runStatement(statement) {
397
+ if (!this.isValidated) {
398
+ throw new Error(common_1.OINO_ERROR_PREFIX + ": Database connection not validated!");
399
+ }
400
+ common_1.OINOBenchmark.startMetric("OINODb", "runStatement");
401
+ let result = await this._exec(statement.sql, statement.values);
402
+ common_1.OINOBenchmark.endMetric("OINODb", "runStatement", result.status != 500);
403
+ return result;
404
+ }
405
+ _getSchemaSql() {
365
406
  const sql = `SELECT
366
407
  C.COLUMN_NAME,
367
408
  C.COLUMN_TYPE,
@@ -369,19 +410,19 @@ class OINODbMariadb extends db_1.OINODb {
369
410
  C.COLUMN_KEY,
370
411
  C.COLUMN_DEFAULT,
371
412
  C.EXTRA,
372
- KCU.CONSTRAINT_NAME AS ForeignKeyName
413
+ KCU.CONSTRAINT_NAME AS ForeignKeyName
373
414
  FROM information_schema.COLUMNS C
374
415
  LEFT JOIN information_schema.KEY_COLUMN_USAGE KCU ON KCU.TABLE_SCHEMA = C.TABLE_SCHEMA AND KCU.TABLE_NAME = C.TABLE_NAME AND C.COLUMN_NAME = KCU.COLUMN_NAME and KCU.REFERENCED_TABLE_NAME IS NOT NULL
375
- WHERE C.TABLE_SCHEMA = '${dbName}' AND C.TABLE_NAME = '${tableName}'
416
+ WHERE C.TABLE_SCHEMA = ? AND C.TABLE_NAME = ?
376
417
  ORDER BY C.ORDINAL_POSITION;`;
377
418
  return sql;
378
419
  }
379
- _getValidateSql(dbName) {
420
+ _getValidateSql() {
380
421
  const sql = `SELECT
381
422
  Count(C.COLUMN_NAME) AS COLUMN_COUNT
382
423
  FROM information_schema.COLUMNS C
383
424
  LEFT JOIN information_schema.KEY_COLUMN_USAGE KCU ON KCU.TABLE_SCHEMA = C.TABLE_SCHEMA AND KCU.TABLE_NAME = C.TABLE_NAME AND C.COLUMN_NAME = KCU.COLUMN_NAME and KCU.REFERENCED_TABLE_NAME IS NOT NULL
384
- WHERE C.TABLE_SCHEMA = '${dbName}';`;
425
+ WHERE C.TABLE_SCHEMA = ?;`;
385
426
  return sql;
386
427
  }
387
428
  /**
@@ -392,7 +433,7 @@ WHERE C.TABLE_SCHEMA = '${dbName}';`;
392
433
  */
393
434
  async getSchemaFields(tableName) {
394
435
  const fields = [];
395
- const schema_res = await this._query(this._getSchemaSql(this.dbParams.database, tableName));
436
+ const schema_res = await this._query(this._getSchemaSql(), [this.dbParams.database, tableName]);
396
437
  while (!schema_res.isEof()) {
397
438
  const row = schema_res.getRow();
398
439
  const field_name = row[0]?.toString() || "";
@@ -443,8 +484,8 @@ WHERE C.TABLE_SCHEMA = '${dbName}';`;
443
484
  */
444
485
  async getSchemaTables() {
445
486
  const tables = [];
446
- const sql = "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = '" + this.dbParams.database + "' AND TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME;";
447
- const tables_res = await this._query(sql);
487
+ const sql = "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME;";
488
+ const tables_res = await this._query(sql, [this.dbParams.database]);
448
489
  while (!tables_res.isEof()) {
449
490
  const row = tables_res.getRow();
450
491
  const table_name = row[0]?.toString() || "";
@@ -101,7 +101,7 @@ export class OINODbMariadb extends OINODb {
101
101
  if (this.dbParams.type !== "OINODbMariadb") {
102
102
  throw new Error(OINO_ERROR_PREFIX + ": Not OINODbMariadb-type: " + this.dbParams.type);
103
103
  }
104
- this._pool = mariadb.createPool({ host: this.dbParams.url, database: this.dbParams.database, port: this.dbParams.port, user: this.dbParams.user, password: this.dbParams.password, acquireTimeout: 2000, debug: false, rowsAsArray: true, multipleStatements: true });
104
+ this._pool = mariadb.createPool({ host: this.dbParams.url, database: this.dbParams.database, port: this.dbParams.port, user: this.dbParams.user, password: this.dbParams.password, acquireTimeout: 2000, debug: false, rowsAsArray: true, multipleStatements: false }); // statements are now executed individually with bind parameters, so stacked/multi-statement execution is disabled
105
105
  delete this.dbParams.password; // do not store password in db object
106
106
  }
107
107
  _parseFieldLength(fieldLengthStr) {
@@ -111,12 +111,12 @@ export class OINODbMariadb extends OINODb {
111
111
  }
112
112
  return result;
113
113
  }
114
- async _query(sql) {
114
+ async _query(sql, params) {
115
115
  let connection = null;
116
116
  let rows = OINO_EMPTY_ROWS;
117
117
  try {
118
118
  connection = await this._pool.getConnection();
119
- const sql_res = await connection.query(sql);
119
+ const sql_res = (params && params.length > 0) ? await connection.query(sql, params) : await connection.query(sql);
120
120
  // console.log("_query: sql=", sql, " result=", result)
121
121
  if (Array.isArray(sql_res)) {
122
122
  rows = sql_res.filter((r) => Array.isArray(r)); // filter out OkPacket results from multiple statements
@@ -133,12 +133,12 @@ export class OINODbMariadb extends OINODb {
133
133
  }
134
134
  return new OINOMariadbData(rows, []);
135
135
  }
136
- async _exec(sql) {
136
+ async _exec(sql, params) {
137
137
  let connection = null;
138
138
  let rows = OINO_EMPTY_ROWS;
139
139
  try {
140
140
  connection = await this._pool.getConnection();
141
- const sql_res = await connection.query(sql);
141
+ const sql_res = (params && params.length > 0) ? await connection.query(sql, params) : await connection.query(sql);
142
142
  // console.log("OINODbMariadb._exec: result=", result)
143
143
  if (Array.isArray(sql_res)) {
144
144
  rows = sql_res.filter((r) => Array.isArray(r)); // filter out OkPacket results from multiple statements
@@ -163,7 +163,7 @@ export class OINODbMariadb extends OINODb {
163
163
  *
164
164
  */
165
165
  printTableName(sqlTable) {
166
- return "`" + sqlTable + "`";
166
+ return "`" + sqlTable.replaceAll("`", "``") + "`";
167
167
  }
168
168
  /**
169
169
  * Print a column name with correct SQL escaping.
@@ -172,7 +172,33 @@ export class OINODbMariadb extends OINODb {
172
172
  *
173
173
  */
174
174
  printColumnName(sqlColumn) {
175
- return "`" + sqlColumn + "`";
175
+ return "`" + sqlColumn.replaceAll("`", "``") + "`";
176
+ }
177
+ /**
178
+ * Print a bind-parameter placeholder for the given zero-based parameter index (MySQL/MariaDB `?`).
179
+ *
180
+ * @param index zero-based parameter index
181
+ *
182
+ */
183
+ printParameterName(index) {
184
+ return "?";
185
+ }
186
+ /**
187
+ * Coerce a data value into a MariaDB bind-parameter value. The connector binds numbers,
188
+ * strings, `Date` and `Buffer` natively; booleans are mapped to 1/0 for `bit`/numeric columns.
189
+ *
190
+ * @param cellValue data value to bind
191
+ * @param nativeType native type name for the table column
192
+ *
193
+ */
194
+ bindCellValue(cellValue, nativeType) {
195
+ if (cellValue === undefined) {
196
+ return null;
197
+ }
198
+ if (typeof cellValue === "boolean") {
199
+ return cellValue ? 1 : 0;
200
+ }
201
+ return cellValue;
176
202
  }
177
203
  /**
178
204
  * Print a single data value from serialization using the context of the native data
@@ -301,8 +327,8 @@ export class OINODbMariadb extends OINODb {
301
327
  OINOBenchmark.startMetric("OINODb", "validate");
302
328
  let result = new OINOResult();
303
329
  try {
304
- const sql = this._getValidateSql(this.dbParams.database);
305
- const sql_res = await this._query(sql);
330
+ const sql = this._getValidateSql();
331
+ const sql_res = await this._query(sql, [this.dbParams.database]);
306
332
  if (sql_res.isEmpty()) {
307
333
  result.setError(400, "DB returned no rows for schema!", "OINODbMariadb.validate");
308
334
  }
@@ -358,7 +384,22 @@ export class OINODbMariadb extends OINODb {
358
384
  OINOBenchmark.endMetric("OINODb", "sqlExec", result.status != 500);
359
385
  return result;
360
386
  }
361
- _getSchemaSql(dbName, tableName) {
387
+ /**
388
+ * Execute a parameterized statement, binding its values as positional `?` parameters.
389
+ *
390
+ * @param statement statement (SQL text + ordered bind values) to execute
391
+ *
392
+ */
393
+ async runStatement(statement) {
394
+ if (!this.isValidated) {
395
+ throw new Error(OINO_ERROR_PREFIX + ": Database connection not validated!");
396
+ }
397
+ OINOBenchmark.startMetric("OINODb", "runStatement");
398
+ let result = await this._exec(statement.sql, statement.values);
399
+ OINOBenchmark.endMetric("OINODb", "runStatement", result.status != 500);
400
+ return result;
401
+ }
402
+ _getSchemaSql() {
362
403
  const sql = `SELECT
363
404
  C.COLUMN_NAME,
364
405
  C.COLUMN_TYPE,
@@ -366,19 +407,19 @@ export class OINODbMariadb extends OINODb {
366
407
  C.COLUMN_KEY,
367
408
  C.COLUMN_DEFAULT,
368
409
  C.EXTRA,
369
- KCU.CONSTRAINT_NAME AS ForeignKeyName
410
+ KCU.CONSTRAINT_NAME AS ForeignKeyName
370
411
  FROM information_schema.COLUMNS C
371
412
  LEFT JOIN information_schema.KEY_COLUMN_USAGE KCU ON KCU.TABLE_SCHEMA = C.TABLE_SCHEMA AND KCU.TABLE_NAME = C.TABLE_NAME AND C.COLUMN_NAME = KCU.COLUMN_NAME and KCU.REFERENCED_TABLE_NAME IS NOT NULL
372
- WHERE C.TABLE_SCHEMA = '${dbName}' AND C.TABLE_NAME = '${tableName}'
413
+ WHERE C.TABLE_SCHEMA = ? AND C.TABLE_NAME = ?
373
414
  ORDER BY C.ORDINAL_POSITION;`;
374
415
  return sql;
375
416
  }
376
- _getValidateSql(dbName) {
417
+ _getValidateSql() {
377
418
  const sql = `SELECT
378
419
  Count(C.COLUMN_NAME) AS COLUMN_COUNT
379
420
  FROM information_schema.COLUMNS C
380
421
  LEFT JOIN information_schema.KEY_COLUMN_USAGE KCU ON KCU.TABLE_SCHEMA = C.TABLE_SCHEMA AND KCU.TABLE_NAME = C.TABLE_NAME AND C.COLUMN_NAME = KCU.COLUMN_NAME and KCU.REFERENCED_TABLE_NAME IS NOT NULL
381
- WHERE C.TABLE_SCHEMA = '${dbName}';`;
422
+ WHERE C.TABLE_SCHEMA = ?;`;
382
423
  return sql;
383
424
  }
384
425
  /**
@@ -389,7 +430,7 @@ WHERE C.TABLE_SCHEMA = '${dbName}';`;
389
430
  */
390
431
  async getSchemaFields(tableName) {
391
432
  const fields = [];
392
- const schema_res = await this._query(this._getSchemaSql(this.dbParams.database, tableName));
433
+ const schema_res = await this._query(this._getSchemaSql(), [this.dbParams.database, tableName]);
393
434
  while (!schema_res.isEof()) {
394
435
  const row = schema_res.getRow();
395
436
  const field_name = row[0]?.toString() || "";
@@ -440,8 +481,8 @@ WHERE C.TABLE_SCHEMA = '${dbName}';`;
440
481
  */
441
482
  async getSchemaTables() {
442
483
  const tables = [];
443
- const sql = "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = '" + this.dbParams.database + "' AND TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME;";
444
- const tables_res = await this._query(sql);
484
+ const sql = "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME;";
485
+ const tables_res = await this._query(sql, [this.dbParams.database]);
445
486
  while (!tables_res.isEof()) {
446
487
  const row = tables_res.getRow();
447
488
  const table_name = row[0]?.toString() || "";
@@ -1,6 +1,6 @@
1
1
  import { OINOResult } from "@oino-ts/common";
2
2
  import { OINODataSet, OINODataField, OINODataFieldSchema, OINODataCell } from "@oino-ts/common";
3
- import { OINODb, OINODbParams } from "@oino-ts/db";
3
+ import { OINODb, OINODbParams, OINODbSqlStatement } from "@oino-ts/db";
4
4
  /**
5
5
  * Implementation of MariaDb/MySql-database.
6
6
  *
@@ -32,6 +32,22 @@ export declare class OINODbMariadb extends OINODb {
32
32
  *
33
33
  */
34
34
  printColumnName(sqlColumn: string): string;
35
+ /**
36
+ * Print a bind-parameter placeholder for the given zero-based parameter index (MySQL/MariaDB `?`).
37
+ *
38
+ * @param index zero-based parameter index
39
+ *
40
+ */
41
+ printParameterName(index: number): string;
42
+ /**
43
+ * Coerce a data value into a MariaDB bind-parameter value. The connector binds numbers,
44
+ * strings, `Date` and `Buffer` natively; booleans are mapped to 1/0 for `bit`/numeric columns.
45
+ *
46
+ * @param cellValue data value to bind
47
+ * @param nativeType native type name for the table column
48
+ *
49
+ */
50
+ bindCellValue(cellValue: OINODataCell, nativeType: string): OINODataCell;
35
51
  /**
36
52
  * Print a single data value from serialization using the context of the native data
37
53
  * type with the correct SQL escaping.
@@ -86,6 +102,13 @@ export declare class OINODbMariadb extends OINODb {
86
102
  *
87
103
  */
88
104
  sqlExec(sql: string): Promise<OINODataSet>;
105
+ /**
106
+ * Execute a parameterized statement, binding its values as positional `?` parameters.
107
+ *
108
+ * @param statement statement (SQL text + ordered bind values) to execute
109
+ *
110
+ */
111
+ runStatement(statement: OINODbSqlStatement): Promise<OINODataSet>;
89
112
  private _getSchemaSql;
90
113
  private _getValidateSql;
91
114
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oino-ts/db-mariadb",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "description": "OINO TS package for using Mariadb databases.",
5
5
  "author": "Matias Kiviniemi (pragmatta)",
6
6
  "license": "MPL-2.0",
@@ -21,12 +21,12 @@
21
21
  "module": "./dist/esm/index.js",
22
22
  "types": "./dist/types/index.d.ts",
23
23
  "dependencies": {
24
- "@oino-ts/common": "^1.1.3",
25
- "@oino-ts/db": "^1.1.3",
24
+ "@oino-ts/common": "^1.2.0",
25
+ "@oino-ts/db": "^1.2.0",
26
26
  "mariadb": "^3.2.3"
27
27
  },
28
28
  "devDependencies": {
29
- "@types/bun": "^1.1.34",
29
+ "@types/bun": "^1.2.04",
30
30
  "@types/node": "^20.12.7",
31
31
  "typescript": "~5.9.0"
32
32
  },
@@ -8,7 +8,7 @@ import { OINO_ERROR_PREFIX, OINOBenchmark, OINO_INFO_PREFIX, OINOLog, OINOResult
8
8
 
9
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, OINODbParams } from "@oino-ts/db";
11
+ import { OINODb, OINODbParams, OINODbSqlStatement } from "@oino-ts/db";
12
12
 
13
13
  import mariadb from "mariadb";
14
14
 
@@ -116,7 +116,7 @@ export class OINODbMariadb extends OINODb {
116
116
  if (this.dbParams.type !== "OINODbMariadb") {
117
117
  throw new Error(OINO_ERROR_PREFIX + ": Not OINODbMariadb-type: " + this.dbParams.type)
118
118
  }
119
- this._pool = mariadb.createPool({ host: this.dbParams.url, database: this.dbParams.database, port: this.dbParams.port, user: this.dbParams.user, password: this.dbParams.password, acquireTimeout: 2000, debug:false, rowsAsArray: true, multipleStatements: true })
119
+ this._pool = mariadb.createPool({ host: this.dbParams.url, database: this.dbParams.database, port: this.dbParams.port, user: this.dbParams.user, password: this.dbParams.password, acquireTimeout: 2000, debug:false, rowsAsArray: true, multipleStatements: false }) // statements are now executed individually with bind parameters, so stacked/multi-statement execution is disabled
120
120
  delete this.dbParams.password // do not store password in db object
121
121
  }
122
122
 
@@ -128,12 +128,12 @@ export class OINODbMariadb extends OINODb {
128
128
  return result
129
129
  }
130
130
 
131
- private async _query(sql:string):Promise<OINODataSet> {
131
+ private async _query(sql:string, params?:OINODataCell[]):Promise<OINODataSet> {
132
132
  let connection:mariadb.PoolConnection|null = null
133
133
  let rows:OINODataRow[] = OINO_EMPTY_ROWS
134
134
  try {
135
135
  connection = await this._pool.getConnection()
136
- const sql_res = await connection.query(sql)
136
+ const sql_res = (params && params.length > 0) ? await connection.query(sql, params) : await connection.query(sql)
137
137
  // console.log("_query: sql=", sql, " result=", result)
138
138
  if (Array.isArray(sql_res)) {
139
139
  rows = sql_res.filter((r) => Array.isArray(r)) as OINODataRow[] // filter out OkPacket results from multiple statements
@@ -150,12 +150,12 @@ export class OINODbMariadb extends OINODb {
150
150
  return new OINOMariadbData(rows, [])
151
151
  }
152
152
 
153
- private async _exec(sql:string):Promise<OINODataSet> {
153
+ private async _exec(sql:string, params?:OINODataCell[]):Promise<OINODataSet> {
154
154
  let connection:mariadb.PoolConnection|null = null
155
155
  let rows:OINODataRow[] = OINO_EMPTY_ROWS
156
156
  try {
157
157
  connection = await this._pool.getConnection()
158
- const sql_res = await connection.query(sql)
158
+ const sql_res = (params && params.length > 0) ? await connection.query(sql, params) : await connection.query(sql)
159
159
  // console.log("OINODbMariadb._exec: result=", result)
160
160
  if (Array.isArray(sql_res)) {
161
161
  rows = sql_res.filter((r) => Array.isArray(r)) // filter out OkPacket results from multiple statements
@@ -181,17 +181,45 @@ export class OINODbMariadb extends OINODb {
181
181
  *
182
182
  */
183
183
  printTableName(sqlTable:string): string {
184
- return "`"+sqlTable+"`"
184
+ return "`"+sqlTable.replaceAll("`", "``")+"`"
185
185
  }
186
186
 
187
187
  /**
188
188
  * Print a column name with correct SQL escaping.
189
- *
189
+ *
190
190
  * @param sqlColumn name of the column
191
191
  *
192
192
  */
193
193
  printColumnName(sqlColumn:string): string {
194
- return "`"+sqlColumn+"`"
194
+ return "`"+sqlColumn.replaceAll("`", "``")+"`"
195
+ }
196
+
197
+ /**
198
+ * Print a bind-parameter placeholder for the given zero-based parameter index (MySQL/MariaDB `?`).
199
+ *
200
+ * @param index zero-based parameter index
201
+ *
202
+ */
203
+ printParameterName(index:number): string {
204
+ return "?"
205
+ }
206
+
207
+ /**
208
+ * Coerce a data value into a MariaDB bind-parameter value. The connector binds numbers,
209
+ * strings, `Date` and `Buffer` natively; booleans are mapped to 1/0 for `bit`/numeric columns.
210
+ *
211
+ * @param cellValue data value to bind
212
+ * @param nativeType native type name for the table column
213
+ *
214
+ */
215
+ bindCellValue(cellValue:OINODataCell, nativeType: string): OINODataCell {
216
+ if (cellValue === undefined) {
217
+ return null
218
+ }
219
+ if (typeof cellValue === "boolean") {
220
+ return cellValue ? 1 : 0
221
+ }
222
+ return cellValue
195
223
  }
196
224
 
197
225
 
@@ -323,8 +351,8 @@ export class OINODbMariadb extends OINODb {
323
351
  OINOBenchmark.startMetric("OINODb", "validate")
324
352
  let result:OINOResult = new OINOResult()
325
353
  try {
326
- const sql = this._getValidateSql(this.dbParams.database)
327
- const sql_res:OINODataSet = await this._query(sql)
354
+ const sql = this._getValidateSql()
355
+ const sql_res:OINODataSet = await this._query(sql, [this.dbParams.database])
328
356
  if (sql_res.isEmpty()) {
329
357
  result.setError(400, "DB returned no rows for schema!", "OINODbMariadb.validate")
330
358
 
@@ -369,7 +397,7 @@ export class OINODbMariadb extends OINODb {
369
397
 
370
398
  /**
371
399
  * Execute other sql operations.
372
- *
400
+ *
373
401
  * @param sql SQL statement.
374
402
  *
375
403
  */
@@ -383,8 +411,24 @@ export class OINODbMariadb extends OINODb {
383
411
  return result
384
412
  }
385
413
 
386
- private _getSchemaSql(dbName:string, tableName:string):string {
387
- const sql =
414
+ /**
415
+ * Execute a parameterized statement, binding its values as positional `?` parameters.
416
+ *
417
+ * @param statement statement (SQL text + ordered bind values) to execute
418
+ *
419
+ */
420
+ async runStatement(statement:OINODbSqlStatement): Promise<OINODataSet> {
421
+ if (!this.isValidated) {
422
+ throw new Error(OINO_ERROR_PREFIX + ": Database connection not validated!")
423
+ }
424
+ OINOBenchmark.startMetric("OINODb", "runStatement")
425
+ let result:OINODataSet = await this._exec(statement.sql, statement.values)
426
+ OINOBenchmark.endMetric("OINODb", "runStatement", result.status != 500)
427
+ return result
428
+ }
429
+
430
+ private _getSchemaSql():string {
431
+ const sql =
388
432
  `SELECT
389
433
  C.COLUMN_NAME,
390
434
  C.COLUMN_TYPE,
@@ -392,21 +436,21 @@ export class OINODbMariadb extends OINODb {
392
436
  C.COLUMN_KEY,
393
437
  C.COLUMN_DEFAULT,
394
438
  C.EXTRA,
395
- KCU.CONSTRAINT_NAME AS ForeignKeyName
439
+ KCU.CONSTRAINT_NAME AS ForeignKeyName
396
440
  FROM information_schema.COLUMNS C
397
441
  LEFT JOIN information_schema.KEY_COLUMN_USAGE KCU ON KCU.TABLE_SCHEMA = C.TABLE_SCHEMA AND KCU.TABLE_NAME = C.TABLE_NAME AND C.COLUMN_NAME = KCU.COLUMN_NAME and KCU.REFERENCED_TABLE_NAME IS NOT NULL
398
- WHERE C.TABLE_SCHEMA = '${dbName}' AND C.TABLE_NAME = '${tableName}'
442
+ WHERE C.TABLE_SCHEMA = ? AND C.TABLE_NAME = ?
399
443
  ORDER BY C.ORDINAL_POSITION;`
400
444
  return sql
401
445
  }
402
446
 
403
- private _getValidateSql(dbName:string):string {
404
- const sql =
447
+ private _getValidateSql():string {
448
+ const sql =
405
449
  `SELECT
406
450
  Count(C.COLUMN_NAME) AS COLUMN_COUNT
407
451
  FROM information_schema.COLUMNS C
408
452
  LEFT JOIN information_schema.KEY_COLUMN_USAGE KCU ON KCU.TABLE_SCHEMA = C.TABLE_SCHEMA AND KCU.TABLE_NAME = C.TABLE_NAME AND C.COLUMN_NAME = KCU.COLUMN_NAME and KCU.REFERENCED_TABLE_NAME IS NOT NULL
409
- WHERE C.TABLE_SCHEMA = '${dbName}';`
453
+ WHERE C.TABLE_SCHEMA = ?;`
410
454
  return sql
411
455
  }
412
456
 
@@ -419,7 +463,7 @@ WHERE C.TABLE_SCHEMA = '${dbName}';`
419
463
  */
420
464
  async getSchemaFields(tableName:string): Promise<OINODataField[]> {
421
465
  const fields:OINODataField[] = []
422
- const schema_res:OINODataSet = await this._query(this._getSchemaSql(this.dbParams.database, tableName))
466
+ const schema_res:OINODataSet = await this._query(this._getSchemaSql(), [this.dbParams.database, tableName])
423
467
  while (!schema_res.isEof()) {
424
468
  const row:OINODataRow = schema_res.getRow()
425
469
  const field_name:string = row[0]?.toString() || ""
@@ -464,8 +508,8 @@ WHERE C.TABLE_SCHEMA = '${dbName}';`
464
508
  */
465
509
  async getSchemaTables(): Promise<string[]> {
466
510
  const tables:string[] = []
467
- const sql:string = "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = '" + this.dbParams.database + "' AND TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME;"
468
- const tables_res:OINODataSet = await this._query(sql)
511
+ const sql:string = "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME;"
512
+ const tables_res:OINODataSet = await this._query(sql, [this.dbParams.database])
469
513
  while (!tables_res.isEof()) {
470
514
  const row:OINODataRow = tables_res.getRow()
471
515
  const table_name:string = row[0]?.toString() || ""