@oino-ts/db 0.17.1 → 0.17.2

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.
@@ -1,291 +1,291 @@
1
- /*
2
- * This Source Code Form is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
- */
6
-
7
- import { OINODbDataField, OINODbApi, OINODataRow, OINO_ERROR_PREFIX, OINODbDataFieldFilter, OINODbConfig, OINODbSqlParams, OINONumberDataField, OINOLog, OINODbSqlSelect, OINODB_UNDEFINED } from "./index.js";
8
-
9
- /**
10
- * OINO Datamodel object for representing one database table and it's columns.
11
- *
12
- */
13
- export class OINODbDataModel {
14
- private _fieldIndexLookup:Record<string, number>;
15
-
16
- /** Database refererence of the table */
17
- readonly api:OINODbApi
18
-
19
- /** Field refererences of the API */
20
- readonly fields: OINODbDataField[]
21
-
22
- /**
23
- * Constructor of the data model.
24
- * NOTE! OINODbDataModel.initialize must be called after constructor to populate fields.
25
- *
26
- * @param api api of the data model
27
- *
28
- */
29
- constructor(api:OINODbApi) {
30
- this._fieldIndexLookup = {}
31
- this.api = api
32
- this.fields = []
33
- }
34
- /**
35
- * Initialize datamodel from SQL schema.
36
- *
37
- */
38
- async initialize() {
39
- await this.api.db.initializeApiDatamodel(this.api)
40
- }
41
-
42
- private _printSqlColumnNames(select?:OINODbSqlSelect): string {
43
- let result: string = "";
44
- for (let i=0; i < this.fields.length; i++) {
45
- const f:OINODbDataField = this.fields[i]
46
- if (select?.isSelected(f) === false) { // if a field is not selected, we include a constant and correct fieldname instead so that dimensions of the data don't change but no unnecessary data is fetched
47
- result += f.db.printSqlString(OINODB_UNDEFINED) + " as " + f.printSqlColumnName()+","
48
- } else {
49
- result += f.printSqlColumnName()+","
50
- }
51
- }
52
- return result.substring(0, result.length-1)
53
- }
54
-
55
- private _printSqlInsertColumnsAndValues(row: OINODataRow): string {
56
- let columns: string = "";
57
- let values: string = "";
58
- for (let i=0; i< this.fields.length; i++) {
59
- const val = row[i];
60
- // console.log("_printSqlInsertColumnsAndValues: row[" + i + "]=" + val)
61
- if (val !== undefined) {
62
- const f = this.fields[i]
63
- if (values != "") {
64
- columns += ",";
65
- values += ",";
66
- }
67
- columns += f.printSqlColumnName();
68
- values += f.printCellAsSqlValue(val);
69
- }
70
- }
71
- // console.log("_printSqlInsertColumnsAndValues: columns=" + columns + ", values=" + values)
72
- return "(" + columns + ") VALUES (" + values + ")";
73
- }
74
-
75
- private _printSqlUpdateValues(row: OINODataRow): string {
76
- let result: string = "";
77
- for (let i=0; i< this.fields.length; i++) {
78
- const f = this.fields[i]
79
- const val = row[i];
80
- if ((!f.fieldParams.isPrimaryKey) && (val !== undefined)) {
81
- if (result != "") {
82
- result += ",";
83
- }
84
- result += f.printSqlColumnName() + "=" + f.printCellAsSqlValue(val);
85
- }
86
- }
87
- return result;
88
- }
89
-
90
- private _printSqlPrimaryKeyCondition(id_value: string): string {
91
- let result: string = ""
92
- let i:number = 0
93
- const id_parts = id_value.split(OINODbConfig.OINODB_ID_SEPARATOR)
94
- for (let f of this.fields) {
95
- if (f.fieldParams.isPrimaryKey) {
96
- if (result != "") {
97
- result += " AND "
98
- }
99
- let value = decodeURIComponent(id_parts[i])
100
- if ((f instanceof OINONumberDataField) && (this.api.hashid)) {
101
- value = this.api.hashid.decode(value)
102
- }
103
- value = f.printCellAsSqlValue(value)
104
- if (value == "") { // ids are user input and could be specially crafted to be empty
105
- throw new Error(OINO_ERROR_PREFIX + ": empty condition for id '" + id_value + "' for table " + this.api.params.tableName)
106
- }
107
- result += f.printSqlColumnName() + "=" + value;
108
- i = i + 1
109
- }
110
- }
111
- if (i != id_parts.length) {
112
- throw new Error(OINO_ERROR_PREFIX + ": id '" + id_value + "' is not a valid key for table " + this.api.params.tableName)
113
- }
114
- return "(" + result + ")";
115
- }
116
-
117
- /**
118
- * Add a field to the datamodel.
119
- *
120
- * @param field dataset field
121
- *
122
- */
123
- addField(field:OINODbDataField) {
124
- this.fields.push(field)
125
- this._fieldIndexLookup[field.name] = this.fields.length-1
126
- }
127
-
128
- /**
129
- * Find a field of a given name if any.
130
- *
131
- * @param name name of the field to find
132
- *
133
- */
134
- findFieldByName(name:string):OINODbDataField|null {
135
- const i:number = this._fieldIndexLookup[name]
136
- if (i >= 0) {
137
- return this.fields[i]
138
- } else {
139
- return null
140
- }
141
- }
142
-
143
- /**
144
- * Find index of a field of a given name if any.
145
- *
146
- * @param name name of the field to find
147
- *
148
- */
149
- findFieldIndexByName(name:string):number {
150
- const i:number = this._fieldIndexLookup[name]
151
- if (i >= 0) {
152
- return i
153
- } else {
154
- return -1
155
- }
156
- }
157
-
158
- /**
159
- * Find all fields based of given filter callback criteria (e.g. fields of certain data type, primary keys etc.)
160
- *
161
- * @param filter callback called for each field to include or not
162
- *
163
- */
164
- filterFields(filter:OINODbDataFieldFilter):OINODbDataField[] {
165
- let result:OINODbDataField[] = []
166
- for (let f of this.fields) {
167
- if (filter(f)) {
168
- result.push(f)
169
- }
170
- }
171
- return result
172
- }
173
-
174
- /**
175
- * Return the primary key values of one row in order of the data model
176
- *
177
- * @param row data row
178
- * @param hashidValues apply hashid when applicable
179
- *
180
- */
181
- getRowPrimarykeyValues(row: OINODataRow, hashidValues:boolean = false): string[] {
182
- let values: string[] = [];
183
- for (let i=0; i< this.fields.length; i++) {
184
- const f = this.fields[i]
185
- if (f.fieldParams.isPrimaryKey) {
186
- const value:string = row[i]?.toString() || ""
187
- if (hashidValues && value && (f instanceof OINONumberDataField) && this.api.hashid) {
188
- values.push(this.api.hashid.encode(value))
189
- } else {
190
- values.push(value)
191
- }
192
- }
193
- }
194
- return values
195
- }
196
-
197
- /**
198
- * Print debug information about the fields.
199
- *
200
- * @param separator string to separate field prints
201
- *
202
- */
203
- printDebug(separator:string = ""): string {
204
- let result: string = this.api.params.tableName + ":" + separator;
205
- for (let f of this.fields) {
206
- result += f.printColumnDebug() + separator;
207
- }
208
- return result;
209
- }
210
-
211
- /**
212
- * Print all public properties (db, table name, fields) of the datamodel. Used
213
- * in automated testing validate schema has stayed the same.
214
- *
215
- */
216
- printFieldPublicPropertiesJson():string {
217
- const result:string = JSON.stringify(this.fields, (key:any, value:any) => {
218
- if (key.startsWith("_")) {
219
- return undefined
220
- } else {
221
- return value
222
- }
223
- })
224
- return result
225
- }
226
-
227
- /**
228
- * Print SQL select statement using optional id and filter.
229
- *
230
- * @param id OINO ID (i.e. combined primary key values)
231
- * @param params OINO reqest params
232
- *
233
- */
234
- printSqlSelect(id: string, params:OINODbSqlParams): string {
235
- let column_names = ""
236
- if (params.aggregate) {
237
- column_names = params.aggregate.printSqlColumnNames(this, params.select)
238
- } else {
239
- column_names = this._printSqlColumnNames(params.select)
240
- }
241
- const order_sql = params.order?.toSql(this) || ""
242
- const limit_sql = params.limit?.toSql(this) || ""
243
- const filter_sql = params.filter?.toSql(this) || ""
244
- const groupby_sql = params.aggregate?.toSql(this, params.select) || ""
245
-
246
- let where_sql = ""
247
- if ((id != null) && (id != "") && (filter_sql != "")) {
248
- where_sql = this._printSqlPrimaryKeyCondition(id) + " AND " + filter_sql
249
- } else if ((id != null) && (id != "")) {
250
- where_sql = this._printSqlPrimaryKeyCondition(id)
251
- } else if (filter_sql != "") {
252
- where_sql = filter_sql
253
- }
254
- const result = this.api.db.printSqlSelect(this.api.params.tableName, column_names, where_sql, order_sql, limit_sql, groupby_sql)
255
- return result;
256
- }
257
-
258
- /**
259
- * Print SQL insert statement from one data row.
260
- *
261
- * @param row one row of data in the data model
262
- *
263
- */
264
- printSqlInsert(row: OINODataRow): string {
265
- let result: string = "INSERT INTO " + this.api.db.printSqlTablename(this.api.params.tableName) + " " + this._printSqlInsertColumnsAndValues(row) + ";";
266
- return result;
267
- }
268
-
269
- /**
270
- * Print SQL insert statement from one data row.
271
- *
272
- * @param id OINO ID (i.e. combined primary key values)
273
- * @param row one row of data in the data model
274
- *
275
- */
276
- printSqlUpdate(id: string, row: OINODataRow): string {
277
- let result: string = "UPDATE " + this.api.db.printSqlTablename(this.api.params.tableName) + " SET " + this._printSqlUpdateValues(row) + " WHERE " + this._printSqlPrimaryKeyCondition(id) + ";";
278
- return result;
279
- }
280
-
281
- /**
282
- * Print SQL delete statement for id.
283
- *
284
- * @param id OINO ID (i.e. combined primary key values)
285
- *
286
- */
287
- printSqlDelete(id: string): string {
288
- let result: string = "DELETE FROM " + this.api.db.printSqlTablename(this.api.params.tableName) + " WHERE " + this._printSqlPrimaryKeyCondition(id) + ";";
289
- return result;
290
- }
291
- }
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+
7
+ import { OINODbDataField, OINODbApi, OINODataRow, OINO_ERROR_PREFIX, OINODbDataFieldFilter, OINODbConfig, OINODbSqlParams, OINONumberDataField, OINOLog, OINODbSqlSelect, OINODB_UNDEFINED } from "./index.js";
8
+
9
+ /**
10
+ * OINO Datamodel object for representing one database table and it's columns.
11
+ *
12
+ */
13
+ export class OINODbDataModel {
14
+ private _fieldIndexLookup:Record<string, number>;
15
+
16
+ /** Database refererence of the table */
17
+ readonly api:OINODbApi
18
+
19
+ /** Field refererences of the API */
20
+ readonly fields: OINODbDataField[]
21
+
22
+ /**
23
+ * Constructor of the data model.
24
+ * NOTE! OINODbDataModel.initialize must be called after constructor to populate fields.
25
+ *
26
+ * @param api api of the data model
27
+ *
28
+ */
29
+ constructor(api:OINODbApi) {
30
+ this._fieldIndexLookup = {}
31
+ this.api = api
32
+ this.fields = []
33
+ }
34
+ /**
35
+ * Initialize datamodel from SQL schema.
36
+ *
37
+ */
38
+ async initialize() {
39
+ await this.api.db.initializeApiDatamodel(this.api)
40
+ }
41
+
42
+ private _printSqlColumnNames(select?:OINODbSqlSelect): string {
43
+ let result: string = "";
44
+ for (let i=0; i < this.fields.length; i++) {
45
+ const f:OINODbDataField = this.fields[i]
46
+ if (select?.isSelected(f) === false) { // if a field is not selected, we include a constant and correct fieldname instead so that dimensions of the data don't change but no unnecessary data is fetched
47
+ result += f.db.printSqlString(OINODB_UNDEFINED) + " as " + f.printSqlColumnName()+","
48
+ } else {
49
+ result += f.printSqlColumnName()+","
50
+ }
51
+ }
52
+ return result.substring(0, result.length-1)
53
+ }
54
+
55
+ private _printSqlInsertColumnsAndValues(row: OINODataRow): string {
56
+ let columns: string = "";
57
+ let values: string = "";
58
+ for (let i=0; i< this.fields.length; i++) {
59
+ const val = row[i];
60
+ // console.log("_printSqlInsertColumnsAndValues: row[" + i + "]=" + val)
61
+ if (val !== undefined) {
62
+ const f = this.fields[i]
63
+ if (values != "") {
64
+ columns += ",";
65
+ values += ",";
66
+ }
67
+ columns += f.printSqlColumnName();
68
+ values += f.printCellAsSqlValue(val);
69
+ }
70
+ }
71
+ // console.log("_printSqlInsertColumnsAndValues: columns=" + columns + ", values=" + values)
72
+ return "(" + columns + ") VALUES (" + values + ")";
73
+ }
74
+
75
+ private _printSqlUpdateValues(row: OINODataRow): string {
76
+ let result: string = "";
77
+ for (let i=0; i< this.fields.length; i++) {
78
+ const f = this.fields[i]
79
+ const val = row[i];
80
+ if ((!f.fieldParams.isPrimaryKey) && (val !== undefined)) {
81
+ if (result != "") {
82
+ result += ",";
83
+ }
84
+ result += f.printSqlColumnName() + "=" + f.printCellAsSqlValue(val);
85
+ }
86
+ }
87
+ return result;
88
+ }
89
+
90
+ private _printSqlPrimaryKeyCondition(id_value: string): string {
91
+ let result: string = ""
92
+ let i:number = 0
93
+ const id_parts = id_value.split(OINODbConfig.OINODB_ID_SEPARATOR)
94
+ for (let f of this.fields) {
95
+ if (f.fieldParams.isPrimaryKey) {
96
+ if (result != "") {
97
+ result += " AND "
98
+ }
99
+ let value = decodeURIComponent(id_parts[i])
100
+ if ((f instanceof OINONumberDataField) && (this.api.hashid)) {
101
+ value = this.api.hashid.decode(value)
102
+ }
103
+ value = f.printCellAsSqlValue(value)
104
+ if (value == "") { // ids are user input and could be specially crafted to be empty
105
+ throw new Error(OINO_ERROR_PREFIX + ": empty condition for id '" + id_value + "' for table " + this.api.params.tableName)
106
+ }
107
+ result += f.printSqlColumnName() + "=" + value;
108
+ i = i + 1
109
+ }
110
+ }
111
+ if (i != id_parts.length) {
112
+ throw new Error(OINO_ERROR_PREFIX + ": id '" + id_value + "' is not a valid key for table " + this.api.params.tableName)
113
+ }
114
+ return "(" + result + ")";
115
+ }
116
+
117
+ /**
118
+ * Add a field to the datamodel.
119
+ *
120
+ * @param field dataset field
121
+ *
122
+ */
123
+ addField(field:OINODbDataField) {
124
+ this.fields.push(field)
125
+ this._fieldIndexLookup[field.name] = this.fields.length-1
126
+ }
127
+
128
+ /**
129
+ * Find a field of a given name if any.
130
+ *
131
+ * @param name name of the field to find
132
+ *
133
+ */
134
+ findFieldByName(name:string):OINODbDataField|null {
135
+ const i:number = this._fieldIndexLookup[name]
136
+ if (i >= 0) {
137
+ return this.fields[i]
138
+ } else {
139
+ return null
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Find index of a field of a given name if any.
145
+ *
146
+ * @param name name of the field to find
147
+ *
148
+ */
149
+ findFieldIndexByName(name:string):number {
150
+ const i:number = this._fieldIndexLookup[name]
151
+ if (i >= 0) {
152
+ return i
153
+ } else {
154
+ return -1
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Find all fields based of given filter callback criteria (e.g. fields of certain data type, primary keys etc.)
160
+ *
161
+ * @param filter callback called for each field to include or not
162
+ *
163
+ */
164
+ filterFields(filter:OINODbDataFieldFilter):OINODbDataField[] {
165
+ let result:OINODbDataField[] = []
166
+ for (let f of this.fields) {
167
+ if (filter(f)) {
168
+ result.push(f)
169
+ }
170
+ }
171
+ return result
172
+ }
173
+
174
+ /**
175
+ * Return the primary key values of one row in order of the data model
176
+ *
177
+ * @param row data row
178
+ * @param hashidValues apply hashid when applicable
179
+ *
180
+ */
181
+ getRowPrimarykeyValues(row: OINODataRow, hashidValues:boolean = false): string[] {
182
+ let values: string[] = [];
183
+ for (let i=0; i< this.fields.length; i++) {
184
+ const f = this.fields[i]
185
+ if (f.fieldParams.isPrimaryKey) {
186
+ const value:string = row[i]?.toString() || ""
187
+ if (hashidValues && value && (f instanceof OINONumberDataField) && this.api.hashid) {
188
+ values.push(this.api.hashid.encode(value))
189
+ } else {
190
+ values.push(value)
191
+ }
192
+ }
193
+ }
194
+ return values
195
+ }
196
+
197
+ /**
198
+ * Print debug information about the fields.
199
+ *
200
+ * @param separator string to separate field prints
201
+ *
202
+ */
203
+ printDebug(separator:string = ""): string {
204
+ let result: string = this.api.params.tableName + ":" + separator;
205
+ for (let f of this.fields) {
206
+ result += f.printColumnDebug() + separator;
207
+ }
208
+ return result;
209
+ }
210
+
211
+ /**
212
+ * Print all public properties (db, table name, fields) of the datamodel. Used
213
+ * in automated testing validate schema has stayed the same.
214
+ *
215
+ */
216
+ printFieldPublicPropertiesJson():string {
217
+ const result:string = JSON.stringify(this.fields, (key:any, value:any) => {
218
+ if (key.startsWith("_")) {
219
+ return undefined
220
+ } else {
221
+ return value
222
+ }
223
+ })
224
+ return result
225
+ }
226
+
227
+ /**
228
+ * Print SQL select statement using optional id and filter.
229
+ *
230
+ * @param id OINO ID (i.e. combined primary key values)
231
+ * @param params OINO reqest params
232
+ *
233
+ */
234
+ printSqlSelect(id: string, params:OINODbSqlParams): string {
235
+ let column_names = ""
236
+ if (params.aggregate) {
237
+ column_names = params.aggregate.printSqlColumnNames(this, params.select)
238
+ } else {
239
+ column_names = this._printSqlColumnNames(params.select)
240
+ }
241
+ const order_sql = params.order?.toSql(this) || ""
242
+ const limit_sql = params.limit?.toSql(this) || ""
243
+ const filter_sql = params.filter?.toSql(this) || ""
244
+ const groupby_sql = params.aggregate?.toSql(this, params.select) || ""
245
+
246
+ let where_sql = ""
247
+ if ((id != null) && (id != "") && (filter_sql != "")) {
248
+ where_sql = this._printSqlPrimaryKeyCondition(id) + " AND " + filter_sql
249
+ } else if ((id != null) && (id != "")) {
250
+ where_sql = this._printSqlPrimaryKeyCondition(id)
251
+ } else if (filter_sql != "") {
252
+ where_sql = filter_sql
253
+ }
254
+ const result = this.api.db.printSqlSelect(this.api.params.tableName, column_names, where_sql, order_sql, limit_sql, groupby_sql)
255
+ return result;
256
+ }
257
+
258
+ /**
259
+ * Print SQL insert statement from one data row.
260
+ *
261
+ * @param row one row of data in the data model
262
+ *
263
+ */
264
+ printSqlInsert(row: OINODataRow): string {
265
+ let result: string = "INSERT INTO " + this.api.db.printSqlTablename(this.api.params.tableName) + " " + this._printSqlInsertColumnsAndValues(row) + ";";
266
+ return result;
267
+ }
268
+
269
+ /**
270
+ * Print SQL insert statement from one data row.
271
+ *
272
+ * @param id OINO ID (i.e. combined primary key values)
273
+ * @param row one row of data in the data model
274
+ *
275
+ */
276
+ printSqlUpdate(id: string, row: OINODataRow): string {
277
+ let result: string = "UPDATE " + this.api.db.printSqlTablename(this.api.params.tableName) + " SET " + this._printSqlUpdateValues(row) + " WHERE " + this._printSqlPrimaryKeyCondition(id) + ";";
278
+ return result;
279
+ }
280
+
281
+ /**
282
+ * Print SQL delete statement for id.
283
+ *
284
+ * @param id OINO ID (i.e. combined primary key values)
285
+ *
286
+ */
287
+ printSqlDelete(id: string): string {
288
+ let result: string = "DELETE FROM " + this.api.db.printSqlTablename(this.api.params.tableName) + " WHERE " + this._printSqlPrimaryKeyCondition(id) + ";";
289
+ return result;
290
+ }
291
+ }