@sap/cds-dk 10.0.3 → 10.0.5

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.
Files changed (82) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/bin/debug.js +6 -2
  3. package/bin/up.js +1 -1
  4. package/bin/watch.js +1 -0
  5. package/lib/build/plugins/helm/files/templates/_deployment_helpers.tpl +2 -0
  6. package/lib/build/plugins/nodejs/index.js +6 -3
  7. package/lib/build/util.js +1 -1
  8. package/lib/init/template/ams/files/containerize.yaml.hbs +1 -0
  9. package/lib/init/template/approuter/files/values.yaml.hbs +9 -1
  10. package/lib/init/template/data/as-csv.js +4 -2
  11. package/lib/init/template/data/as-json.js +4 -1
  12. package/lib/init/template/github-actions/files/workflows/cf.yaml.hbs +6 -6
  13. package/lib/init/template/ias/files/values.yaml.hbs +4 -4
  14. package/lib/init/template/portal/files/values.yaml.hbs +12 -0
  15. package/lib/init/template/sample/files/app/browse/fiori-service.cds +2 -0
  16. package/lib/init/template/typer/files/xsconfig.json +1 -1
  17. package/lib/init/template/typescript/files/package.json +1 -1
  18. package/lib/lint/eslintHelp.json +1 -1
  19. package/lib/watch/esm-resolve-registration.js +27 -0
  20. package/lib/watch/vite-watch.js +1 -1
  21. package/node_modules/@cap-js/db-service/CHANGELOG.md +768 -0
  22. package/node_modules/@cap-js/db-service/LICENSE +201 -0
  23. package/node_modules/@cap-js/db-service/README.md +26 -0
  24. package/node_modules/@cap-js/db-service/index.js +19 -0
  25. package/node_modules/@cap-js/db-service/lib/InsertResults.js +122 -0
  26. package/node_modules/@cap-js/db-service/lib/SQLService.js +581 -0
  27. package/node_modules/@cap-js/db-service/lib/common/DatabaseService.js +179 -0
  28. package/node_modules/@cap-js/db-service/lib/common/factory.d.ts +5 -0
  29. package/node_modules/@cap-js/db-service/lib/common/generic-pool.js +310 -0
  30. package/node_modules/@cap-js/db-service/lib/common/session-context.js +46 -0
  31. package/node_modules/@cap-js/db-service/lib/converters.d.ts +24 -0
  32. package/node_modules/@cap-js/db-service/lib/cql-functions.js +464 -0
  33. package/node_modules/@cap-js/db-service/lib/cqn2pql.js +116 -0
  34. package/node_modules/@cap-js/db-service/lib/cqn2sql.js +1612 -0
  35. package/node_modules/@cap-js/db-service/lib/cqn4sql.js +2895 -0
  36. package/node_modules/@cap-js/db-service/lib/deep-queries.js +290 -0
  37. package/node_modules/@cap-js/db-service/lib/fill-in-keys.js +75 -0
  38. package/node_modules/@cap-js/db-service/lib/infer/cqn.d.ts +42 -0
  39. package/node_modules/@cap-js/db-service/lib/infer/index.js +1290 -0
  40. package/node_modules/@cap-js/db-service/lib/infer/join-tree.js +278 -0
  41. package/node_modules/@cap-js/db-service/lib/infer/pseudos.js +24 -0
  42. package/node_modules/@cap-js/db-service/lib/search.js +220 -0
  43. package/node_modules/@cap-js/db-service/lib/utils.js +177 -0
  44. package/node_modules/@cap-js/db-service/package.json +36 -0
  45. package/node_modules/@cap-js/sqlite/CHANGELOG.md +404 -0
  46. package/node_modules/@cap-js/sqlite/LICENSE +201 -0
  47. package/node_modules/@cap-js/sqlite/README.md +34 -0
  48. package/node_modules/@cap-js/sqlite/cds-plugin.js +0 -0
  49. package/node_modules/@cap-js/sqlite/index.js +2 -0
  50. package/node_modules/@cap-js/sqlite/lib/SQLiteService.js +324 -0
  51. package/node_modules/@cap-js/sqlite/lib/cql-functions.js +160 -0
  52. package/node_modules/@cap-js/sqlite/lib/node-sqlite.js +36 -0
  53. package/node_modules/@cap-js/sqlite/lib/session.json +3 -0
  54. package/node_modules/@cap-js/sqlite/lib/sql.js.js +99 -0
  55. package/node_modules/@cap-js/sqlite/package.json +68 -0
  56. package/node_modules/@sap/cds-compiler/CHANGELOG.md +14 -0
  57. package/node_modules/@sap/cds-compiler/bin/cdsse.js +1 -1
  58. package/node_modules/@sap/cds-compiler/bin/cdsv2m.js +1 -1
  59. package/node_modules/@sap/cds-compiler/doc/IncompatibleChanges_v7.md +20 -13
  60. package/node_modules/@sap/cds-compiler/lib/base/builtins.js +4 -1
  61. package/node_modules/@sap/cds-compiler/lib/base/message-registry.js +15 -2
  62. package/node_modules/@sap/cds-compiler/lib/checks/defaultValues.js +0 -1
  63. package/node_modules/@sap/cds-compiler/lib/compiler/resolve.js +6 -5
  64. package/node_modules/@sap/cds-compiler/lib/compiler/tweak-assocs.js +27 -6
  65. package/node_modules/@sap/cds-compiler/lib/edm/annotations/edmJson.js +5 -0
  66. package/node_modules/@sap/cds-compiler/lib/json/from-csn.js +3 -3
  67. package/node_modules/@sap/cds-compiler/lib/json/to-csn.js +5 -4
  68. package/node_modules/@sap/cds-compiler/lib/parsers/AstBuildingParser.js +14 -3
  69. package/node_modules/@sap/cds-compiler/lib/tool-lib/enrichCsn.js +1 -1
  70. package/node_modules/@sap/cds-compiler/lib/transform/db/cdsPersistence.js +3 -2
  71. package/node_modules/@sap/cds-compiler/lib/transform/db/views.js +2 -2
  72. package/node_modules/@sap/cds-compiler/lib/transform/transformUtils.js +1 -2
  73. package/node_modules/@sap/cds-compiler/package.json +1 -1
  74. package/node_modules/iconv-lite/encodings/sbcs-data.js +2 -0
  75. package/node_modules/iconv-lite/encodings/utf32.js +10 -3
  76. package/node_modules/iconv-lite/package.json +2 -2
  77. package/node_modules/iconv-lite/types/encodings.d.ts +2 -0
  78. package/node_modules/ws/lib/receiver.js +15 -32
  79. package/node_modules/ws/lib/websocket-server.js +4 -4
  80. package/node_modules/ws/lib/websocket.js +4 -4
  81. package/node_modules/ws/package.json +5 -1
  82. package/package.json +5 -3
@@ -0,0 +1,34 @@
1
+ # CDS database service for SQLite
2
+
3
+ Welcome to the SQLite database service for [SAP Cloud Application Programming Model](https://cap.cloud.sap) Node.js, based on streamlined database architecture and [*better-sqlite* driver](https://www.npmjs.com/package/better-sqlite3).
4
+
5
+ ## Setup
6
+
7
+ If you want to use SQLite for development, all you need to do is to install the database package, as follows
8
+
9
+ ```sh
10
+ npm add @cap-js/sqlite -D
11
+ ```
12
+
13
+ Learn more about setup and usage in the [respective database guide](https://cap.cloud.sap/docs/guides/databases/sqlite).
14
+
15
+ ## Support
16
+
17
+ This project is open to feature requests/suggestions, bug reports etc. via [GitHub issues](https://github.com/cap-js/cds-dbs/issues).
18
+
19
+ ## Contribution
20
+
21
+ Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](../CONTRIBUTING.md).
22
+
23
+ ## Versioning
24
+
25
+ This library follows [Semantic Versioning](https://semver.org/).
26
+ All notable changes are documented in [CHANGELOG.md](CHANGELOG.md).
27
+
28
+ ## Code of Conduct
29
+
30
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone. By participating in this project, you agree to abide by its [Code of Conduct](../CODE_OF_CONDUCT.md) at all times.
31
+
32
+ ## Licensing
33
+
34
+ Copyright 2024 SAP SE or an SAP affiliate company and cds-dbs contributors. Please see our [LICENSE](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/cap-js/cds-dbs).
File without changes
@@ -0,0 +1,2 @@
1
+ module.exports = require('./lib/SQLiteService.js')
2
+
@@ -0,0 +1,324 @@
1
+ const { SQLService } = require('@cap-js/db-service')
2
+ const cds = require('@sap/cds/lib')
3
+ let sqlite // sqlite driver is loaded on connect
4
+
5
+ const $session = Symbol('dbc.session')
6
+ const sessionVariableMap = require('./session.json') // Adjust the path as necessary for your project
7
+ const convStrm = require('stream/consumers')
8
+ const { Readable } = require('stream')
9
+
10
+ const keywords = cds.compiler.to.sql.sqlite.keywords
11
+ // keywords come as array
12
+ const sqliteKeywords = keywords.reduce((prev, curr) => {
13
+ prev[curr] = 1
14
+ return prev
15
+ }, {})
16
+
17
+ // define date and time functions in js to allow for throwing errors
18
+ const isTime = /^\d{1,2}:\d{1,2}:\d{1,2}$/
19
+ const hasTimezone = /([+-]\d{1,2}:?\d{0,2}|Z)$/
20
+ const toDate = (d, allowTime = false) => {
21
+ const date = new Date(allowTime && isTime.test(d) ? `1970-01-01T${d}Z` : hasTimezone.test(d) ? d : d + 'Z')
22
+ if (Number.isNaN(date.getTime())) throw new Error(`Value does not contain a valid ${allowTime ? 'time' : 'date'} "${d}"`)
23
+ return date
24
+ }
25
+
26
+
27
+ class SQLiteService extends SQLService {
28
+
29
+ get factory() {
30
+ return {
31
+ options: this.options.pool || {},
32
+ create: async tenant => {
33
+ try {
34
+ if (!sqlite) loadSQLite(this.options.driver || this.options.credentials?.driver)
35
+ const database = this.url4(tenant)
36
+ const dbc = new sqlite(database, this.options.client || {})
37
+ await dbc.ready
38
+
39
+ const deterministic = { deterministic: true }
40
+ dbc.function('session_context', key => dbc[$session][key])
41
+ dbc.function('regexp', deterministic, (re, x) => (RegExp(re).test(x) ? 1 : 0))
42
+ dbc.function('ISO', deterministic, d => d && new Date(d).toISOString())
43
+ dbc.function('year', deterministic, d => d === null ? null : toDate(d).getUTCFullYear())
44
+ dbc.function('month', deterministic, d => d === null ? null : toDate(d).getUTCMonth() + 1)
45
+ dbc.function('day', deterministic, d => d === null ? null : toDate(d).getUTCDate())
46
+ dbc.function('hour', deterministic, d => d === null ? null : toDate(d, true).getUTCHours())
47
+ dbc.function('minute', deterministic, d => d === null ? null : toDate(d, true).getUTCMinutes())
48
+ dbc.function('second', deterministic, d => d === null ? null : toDate(d, true).getUTCSeconds())
49
+ if (database !== ':memory:') dbc.pragma?.('journal_mode = WAL') || dbc.exec('PRAGMA journal_mode = WAL')
50
+ return dbc
51
+ } catch (err) {
52
+ Promise.reject(err)
53
+ await new Promise(() => { })
54
+ }
55
+ },
56
+ destroy: dbc => dbc.close(),
57
+ validate: dbc => dbc.open,
58
+ }
59
+ }
60
+
61
+ url4(tenant) {
62
+ let { url, database: db = url } = this.options.credentials || this.options || {}
63
+ if (!db || db === ':memory:') return ':memory:'
64
+ if (tenant) db = db.replace(/\.(db|sqlite)$/, `-${tenant}.$1`)
65
+ return cds.utils.path.resolve(cds.root, db)
66
+ }
67
+
68
+ set(variables) {
69
+ const dbc = this.dbc || cds.error('Cannot set session context: No database connection')
70
+
71
+ // Enrich provided session context with aliases
72
+ for (const alias in sessionVariableMap) {
73
+ const name = sessionVariableMap[alias]
74
+ if (variables[name]) variables[alias] = variables[name]
75
+ }
76
+
77
+ if (!dbc[$session]) dbc[$session] = variables
78
+ else Object.assign(dbc[$session], variables)
79
+ }
80
+
81
+ release() {
82
+ this.dbc[$session] = undefined
83
+ return super.release()
84
+ }
85
+
86
+ prepare(sql) {
87
+ try {
88
+ const stmt = this.dbc.prepare(sql)
89
+ return {
90
+ run: (..._) => this._run(stmt, ..._),
91
+ get: (..._) => stmt.get(..._),
92
+ all: (..._) => stmt.all(..._),
93
+ stream: (..._) => this._allStream(stmt, ..._),
94
+ }
95
+ } catch (e) {
96
+ e.message += ' in:\n' + (e.query = sql)
97
+ throw e
98
+ }
99
+ }
100
+
101
+ async _run(stmt, binding_params) {
102
+ for (let i = 0; i < binding_params.length; i++) {
103
+ const val = binding_params[i]
104
+ if (val instanceof Readable) {
105
+ binding_params[i] = await convStrm[val.type === 'json' ? 'text' : 'buffer'](val)
106
+ }
107
+ if (Buffer.isBuffer(val)) {
108
+ binding_params[i] = Buffer.from(val.toString('base64'))
109
+ }
110
+ }
111
+ return stmt.run(binding_params)
112
+ }
113
+
114
+ async *_iteratorRaw(rs, one) {
115
+ const pageSize = (1 << 16)
116
+ // Allow for both array and iterator result sets
117
+ const first = Array.isArray(rs) ? { done: !rs[0], value: rs[0] } : rs.next()
118
+ if (first.done) {
119
+ yield one ? 'null' : '[]'
120
+ return
121
+ }
122
+ if (one) {
123
+ yield first.value[0]
124
+ // Close result set to release database connection
125
+ rs.return()
126
+ return
127
+ }
128
+
129
+ let buffer = '[' + first.value[0]
130
+ // Print first value as stand alone to prevent comma check inside the loop
131
+ for (const row of rs) {
132
+ buffer += `,${row[0]}`
133
+ if (buffer.length > pageSize) {
134
+ yield buffer
135
+ buffer = ''
136
+ }
137
+ }
138
+ buffer += ']'
139
+ yield buffer
140
+ }
141
+
142
+ async *_iteratorObjectMode(rs) {
143
+ for (const row of rs) {
144
+ yield JSON.parse(row[0])
145
+ }
146
+ }
147
+
148
+ async _allStream(stmt, binding_params, one, objectMode) {
149
+ stmt = stmt.iterate ? stmt : stmt.__proto__
150
+ stmt.raw?.(true)
151
+ const rs = stmt.iterate(binding_params)
152
+ const stream = Readable.from(objectMode ? this._iteratorObjectMode(rs) : this._iteratorRaw(rs, one), { objectMode })
153
+ const close = () => rs.return() // finish result set when closed early
154
+ stream.on('error', close)
155
+ stream.on('close', close)
156
+ return stream
157
+ }
158
+
159
+ pragma(pragma, options) {
160
+ if (!this.dbc) return this.begin('pragma').then(tx => {
161
+ try { return tx.pragma(pragma, options) }
162
+ finally { tx.release() }
163
+ })
164
+ return this.dbc.pragma(pragma, options)
165
+ }
166
+
167
+
168
+ exec(sql) {
169
+ return this.dbc.exec(sql)
170
+ }
171
+
172
+ _prepareStreams(values) {
173
+ let any
174
+ values.forEach((v, i) => {
175
+ if (v instanceof Readable) {
176
+ any = values[i] = convStrm.buffer(v)
177
+ }
178
+ })
179
+ return any ? Promise.all(values) : values
180
+ }
181
+
182
+ async onSIMPLE({ query, data }) {
183
+ const { sql, values } = this.cqn2sql(query, data)
184
+ let ps = await this.prepare(sql)
185
+ const vals = await this._prepareStreams(values)
186
+ const { changes } = await ps.run(vals)
187
+ return this._return_affected (changes)
188
+ }
189
+
190
+ onPlainSQL({ query, data }, next) {
191
+ if (typeof query === 'string') {
192
+ // REVISIT: this is a hack the target of $now might not be a timestamp or date time
193
+ // Add input converter to CURRENT_TIMESTAMP inside views using $now
194
+ if (/^CREATE VIEW.* CURRENT_TIMESTAMP[( ]/is.test(query)) {
195
+ query = query.replace(/CURRENT_TIMESTAMP/gi, "STRFTIME('%Y-%m-%dT%H:%M:%fZ','NOW')")
196
+ }
197
+ }
198
+ return super.onPlainSQL({ query, data }, next)
199
+ }
200
+
201
+ static CQN2SQL = class CQN2SQLite extends SQLService.CQN2SQL {
202
+ column_alias4(x, q) {
203
+ let alias = super.column_alias4(x, q)
204
+ if (alias) return alias
205
+ if (x.ref) {
206
+ let obm = q._orderByMap
207
+ if (!obm) {
208
+ Object.defineProperty(q, '_orderByMap', { value: (obm = {}) })
209
+ q.SELECT?.orderBy?.forEach(o => {
210
+ if (o.ref?.length === 1) obm[o.ref[0]] = o.ref[0]
211
+ })
212
+ }
213
+ return obm[x.ref.at(-1)]
214
+ }
215
+ }
216
+
217
+ val(v) {
218
+ if (typeof v.val === 'boolean') v.val = v.val ? 1 : 0
219
+ else if (Buffer.isBuffer(v.val)) v.val = v.val.toString('base64')
220
+ // intercept DateTime values and convert to Date objects to compare ISO Strings
221
+ else if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(.\d{1,9})?(Z|[+-]\d{2}(:?\d{2})?)$/.test(v.val)) {
222
+ const date = new Date(v.val)
223
+ if (!Number.isNaN(date.getTime())) {
224
+ v.val = date
225
+ }
226
+ }
227
+ return super.val(v)
228
+ }
229
+
230
+ forUpdate() {
231
+ return ''
232
+ }
233
+
234
+ forShareLock() {
235
+ return ''
236
+ }
237
+
238
+ // Used for INSERT statements
239
+ static InputConverters = {
240
+ ...super.InputConverters,
241
+ // The following allows passing in ISO strings with non-zulu
242
+ // timezones and converts them into zulu dates and times
243
+ Date: e => e === '?' ? e : `strftime('%Y-%m-%d',${e})`,
244
+ Time: e => e === '?' ? e : `strftime('%H:%M:%S',${e})`,
245
+ // Both, DateTimes and Timestamps are canonicalized to ISO strings with
246
+ // ms precision to allow safe comparisons, also to query {val}s in where clauses
247
+ DateTime: e => e === '?' ? e : `ISO(${e})`,
248
+ Timestamp: e => e === '?' ? e : `ISO(${e})`,
249
+ }
250
+
251
+ static OutputConverters = {
252
+ ...super.OutputConverters,
253
+ // Structs and arrays are stored as JSON strings; the ->'$' unwraps them.
254
+ // Otherwise they would be added as strings to json_objects.
255
+ Association: expr => `${expr}->'$'`,
256
+ struct: expr => `${expr}->'$'`,
257
+ array: expr => `${expr}->'$'`,
258
+ // SQLite has no booleans so we need to convert 0 and 1
259
+ boolean:
260
+ cds.env.features.sql_simple_queries === 2
261
+ ? undefined
262
+ : expr => `CASE ${expr} when 1 then 'true' when 0 then 'false' END ->'$'`,
263
+ // DateTimes are returned without ms added by InputConverters
264
+ DateTime: e => `substr(${e},0,20)||'Z'`,
265
+ // Timestamps are returned with ms, as written by InputConverters.
266
+ // And as cds.builtin.classes.Timestamp inherits from DateTime we need
267
+ // to override the DateTime converter above
268
+ Timestamp: undefined,
269
+ // int64 is stored as native int64 for best comparison
270
+ // Reading int64 as string to not loose precision
271
+ Int64: cds.env.features.ieee754compatible ? expr => `CAST(${expr} as TEXT)` : undefined,
272
+ // REVISIT: always cast to string in next major
273
+ // Reading decimal as string to not loose precision
274
+ Decimal: cds.env.features.ieee754compatible
275
+ ? (expr, elem) =>
276
+ elem?.scale
277
+ ? `CASE WHEN ${expr} IS NULL THEN NULL ELSE format('%.${elem.scale}f', ${expr}) END`
278
+ : `CASE WHEN ${expr} IS NULL THEN NULL ELSE rtrim(rtrim(format('%.999f', ${expr}), '0'), '.') END`
279
+ : undefined,
280
+ // Binary is not allowed in json objects
281
+ Binary: expr => `${expr} || ''`,
282
+ }
283
+
284
+ // Used for SQL function expressions
285
+ static Functions = { ...super.Functions, ...require('./cql-functions') }
286
+
287
+ // Used for CREATE TABLE statements
288
+ static TypeMap = {
289
+ ...super.TypeMap,
290
+ Binary: e => `BINARY_BLOB(${e.length || 5000})`,
291
+ Date: () => 'DATE_TEXT',
292
+ Time: () => 'TIME_TEXT',
293
+ DateTime: () => 'DATETIME_TEXT',
294
+ Timestamp: () => 'TIMESTAMP_TEXT',
295
+ Map: () => 'JSON_TEXT',
296
+ Decimal: cds.env.requires.db?.decimal_affinity?.match(/^real$/i) ? () => 'REAL_DECIMAL' : undefined,
297
+ }
298
+
299
+ get is_distinct_from_() {
300
+ return 'is not'
301
+ }
302
+ get is_not_distinct_from_() {
303
+ return 'is'
304
+ }
305
+
306
+ static ReservedWords = { ...super.ReservedWords, ...sqliteKeywords }
307
+ }
308
+ }
309
+
310
+ function loadSQLite(driver) {
311
+ const drivers = {
312
+ node: './node-sqlite.js',
313
+ 'better-sqlite3': 'better-sqlite3',
314
+ 'sql.js': './sql.js.js',
315
+ }
316
+
317
+ if (driver) {
318
+ sqlite = require(drivers[driver])
319
+ } else {
320
+ sqlite = require(drivers.node)
321
+ }
322
+ }
323
+
324
+ module.exports = SQLiteService
@@ -0,0 +1,160 @@
1
+ 'use strict'
2
+
3
+ const StandardFunctions = {
4
+ // ==============================
5
+ // Date and Time Functions
6
+ // ==============================
7
+
8
+ /**
9
+ * Ensures ISO strings are returned for current timestamp
10
+ * @returns {string} - SQL statement
11
+ */
12
+ current_timestamp: () => 'ISO(current_timestamp)',
13
+
14
+ /**
15
+ * SQLite doesn't support arguments for current_date
16
+ * @returns {string} - SQL statement
17
+ */
18
+ current_date: () => 'current_date',
19
+
20
+ /**
21
+ * SQLite doesn't support arguments for current_time
22
+ * @returns {string} - SQL statement
23
+ */
24
+ current_time: () => 'current_time',
25
+
26
+ /**
27
+ * Generates SQL statement that produces the fractional seconds of a given timestamp
28
+ * @param {string} x - The timestamp input
29
+ * @returns {string} - SQL statement
30
+ */
31
+ fractionalseconds: x => `cast(substr(strftime('%f', ${x}), length(strftime('%f', ${x})) - 3) as REAL)`,
32
+
33
+ // ==============================
34
+ // String Functions
35
+ // ==============================
36
+
37
+ /**
38
+ * Generates SQL statement that produces a boolean value indicating whether the first string contains the second string
39
+ * @param {...string} args - The strings to evaluate
40
+ * @returns {string} - SQL statement
41
+ */
42
+ contains: (...args) => `(ifnull(instr(${args}),0) > 0)`,
43
+
44
+ /**
45
+ * Generates SQL statement that produces the index of the first occurrence of the second string in the first string
46
+ * @param {string} x - The string to search
47
+ * @param {string} y - The substring to find
48
+ * @returns {string} - SQL statement
49
+ */
50
+ indexof: (x, y) => `instr(${x},${y}) - 1`,
51
+
52
+ /**
53
+ * Generates SQL statement that produces a boolean value indicating whether the first string starts with the second string
54
+ * @param {string} x - The string to evaluate
55
+ * @param {string} y - The prefix to check
56
+ * @returns {string} - SQL statement
57
+ */
58
+ startswith: (x, y) => `coalesce(instr(${x},${y}) = 1,false)`,
59
+
60
+ /**
61
+ * Generates SQL statement that produces a boolean value indicating whether the first string ends with the second string
62
+ * @param {string} x - The string to evaluate
63
+ * @param {string} y - The suffix to check
64
+ * @returns {string} - SQL statement
65
+ */
66
+ endswith: (x, y) => `coalesce(substr(${x}, length(${x}) + 1 - length(${y})) = ${y},false)`,
67
+
68
+ /**
69
+ * Generates SQL statement that matches the given string against a regular expression
70
+ * @param {string} x - The string to match
71
+ * @param {string} y - The regular expression
72
+ * @returns {string} - SQL statement
73
+ */
74
+ matchesPattern: (x, y) => `(${x} regexp ${y})`,
75
+
76
+ /**
77
+ * Alias for matchesPattern
78
+ * @param {string} x - The string to match
79
+ * @param {string} y - The regular expression
80
+ * @returns {string} - SQL statement
81
+ */
82
+ matchespattern: (x, y) => `(${x} regexp ${y})`,
83
+ }
84
+
85
+ const HANAFunctions = {
86
+ // ==============================
87
+ // Timestamp Difference Functions
88
+ // ==============================
89
+
90
+ /**
91
+ * Generates SQL statement that calculates the difference in 100-nanoseconds between two timestamps
92
+ * @param {string} x - Left timestamp
93
+ * @param {string} y - Right timestamp
94
+ * @returns {string} - SQL statement
95
+ */
96
+ nano100_between: (x, y) => `(julianday(${y}) - julianday(${x})) * 864000000000`,
97
+
98
+ /**
99
+ * Generates SQL statement that calculates the difference in seconds between two timestamps
100
+ * @param {string} x - Left timestamp
101
+ * @param {string} y - Right timestamp
102
+ * @returns {string} - SQL statement
103
+ */
104
+ seconds_between: (x, y) => `(julianday(${y}) - julianday(${x})) * 86400`,
105
+
106
+ /**
107
+ * Generates SQL statement that calculates the difference in days between two timestamps
108
+ * @param {string} x - Left timestamp
109
+ * @param {string} y - Right timestamp
110
+ * @returns {string} - SQL statement
111
+ */
112
+ days_between: (x, y) => `(
113
+ cast(julianday(${y}) as Integer) - cast(julianday(${x}) as Integer)
114
+ ) + (
115
+ case
116
+ when (julianday(${y}) < julianday(${x})) then
117
+ (cast(strftime('%H%M%S%f0000', ${y}) as Integer) < cast(strftime('%H%M%S%f0000', ${x}) as Integer))
118
+ else
119
+ (cast(strftime('%H%M%S%f0000', ${y}) as Integer) > cast(strftime('%H%M%S%f0000', ${x}) as Integer)) * -1
120
+ end
121
+ )`,
122
+
123
+ /**
124
+ * Generates SQL statement that calculates the difference in months between two timestamps
125
+ * @param {string} x - Left timestamp
126
+ * @param {string} y - Right timestamp
127
+ * @returns {string} - SQL statement
128
+ */
129
+ months_between: (x, y) => `
130
+ (
131
+ (
132
+ (cast(strftime('%Y', ${y}) as Integer) - cast(strftime('%Y', ${x}) as Integer)) * 12
133
+ ) + (
134
+ cast(strftime('%m', ${y}) as Integer) - cast(strftime('%m', ${x}) as Integer)
135
+ ) + (
136
+ (
137
+ case
138
+ when (cast(strftime('%Y%m', ${y}) as Integer) < cast(strftime('%Y%m', ${x}) as Integer)) then
139
+ (cast(strftime('%d%H%M%S%f0000', ${y}) as Integer) > cast(strftime('%d%H%M%S%f0000', ${x}) as Integer))
140
+ else
141
+ (cast(strftime('%d%H%M%S%f0000', ${y}) as Integer) < cast(strftime('%d%H%M%S%f0000', ${x}) as Integer)) * -1
142
+ end
143
+ )
144
+ )
145
+ )`,
146
+
147
+ /**
148
+ * Generates SQL statement that calculates the difference in years between two timestamps
149
+ * @param {string} x - Left timestamp
150
+ * @param {string} y - Right timestamp
151
+ * @returns {string} - SQL statement
152
+ */
153
+ years_between(x, y) {
154
+ return `floor(${this.expr({ func: 'months_between', args: [x, y] })} / 12)`
155
+ },
156
+ }
157
+
158
+ for (let each in HANAFunctions) HANAFunctions[each.toUpperCase()] = HANAFunctions[each]
159
+
160
+ module.exports = { ...StandardFunctions, ...HANAFunctions }
@@ -0,0 +1,36 @@
1
+ const { DatabaseSync } = require('node:sqlite');
2
+
3
+ class NodeSqlite extends DatabaseSync {
4
+ prepare(sql) {
5
+ const stmt = super.prepare(sql)
6
+ const ret = {
7
+ run(params) {
8
+ try {
9
+ params = Array.isArray(params) ? params : [params]
10
+ return stmt.run(...params)
11
+ } catch (err) {
12
+ if (err.message.indexOf('NOT NULL constraint failed:') === 0) {
13
+ err.code = 'SQLITE_CONSTRAINT_NOTNULL'
14
+ }
15
+ throw err
16
+ }
17
+ },
18
+ get(params) {
19
+ params = Array.isArray(params) ? params : [params]
20
+ return stmt.get(...params)
21
+ },
22
+ all(params) {
23
+ params = Array.isArray(params) ? params : [params]
24
+ return stmt.all(...params)
25
+ },
26
+ iterate(params) {
27
+ stmt.setReturnArrays(true)
28
+ params = Array.isArray(params) ? params : [params]
29
+ return stmt.iterate(...params)
30
+ }
31
+ }
32
+ return ret
33
+ }
34
+ }
35
+
36
+ module.exports = NodeSqlite
@@ -0,0 +1,3 @@
1
+ {
2
+ "$user": "$user.id"
3
+ }
@@ -0,0 +1,99 @@
1
+ const initSqlJs = require('sql.js');
2
+
3
+ const init = initSqlJs({})
4
+
5
+ class WasmSqlite {
6
+ constructor(/*database*/) {
7
+ // TODO: load / store database file contents
8
+ this.ready = init
9
+ .then(SQL => {
10
+ this.db = new SQL.Database()
11
+ // polyfill for missing or mismatched default sqlite3 math functions
12
+ this.db.create_function('ln', x => Math.log(x))
13
+ this.db.create_function('log', (x) => Math.log10(x))
14
+ this.db.create_function('log', (x, y) => Math.log(y) / Math.log(x))
15
+ this.db.create_function('mod', (x, y) => x % y)
16
+ })
17
+
18
+ this.memory = true
19
+ this.gc = new FinalizationRegistry(stmt => { stmt.free() })
20
+ }
21
+
22
+ prepare(sql) {
23
+ const stmt = this.db.prepare(sql)
24
+ const ret = {
25
+ run(params) {
26
+ try {
27
+ stmt.bind(params)
28
+ stmt.step()
29
+ const changes = stmt.db.getRowsModified(stmt)
30
+ const lastInsertRowid = stmt.db.exec('SELECT last_insert_rowid()')[0]?.values[0][0]
31
+ return { changes, lastInsertRowid }
32
+ } catch (err) {
33
+ if (err.message.indexOf('NOT NULL constraint failed:') === 0) {
34
+ err.code = 'SQLITE_CONSTRAINT_NOTNULL'
35
+ }
36
+ throw err
37
+ }
38
+ },
39
+ get(params) {
40
+ const columns = stmt.getColumnNames()
41
+ stmt.bind(params)
42
+ stmt.step()
43
+ const row = stmt.get()
44
+ const ret = {}
45
+ for (let i = 0; i < columns.length; i++) {
46
+ ret[columns[i]] = row[i]
47
+ }
48
+ return ret
49
+ },
50
+ all(params) {
51
+ const columns = stmt.getColumnNames()
52
+ const ret = []
53
+ stmt.bind(params)
54
+ while (stmt.step()) {
55
+ const row = stmt.get()
56
+ const obj = {}
57
+ for (let i = 0; i < columns.length; i++) {
58
+ obj[columns[i]] = row[i]
59
+ }
60
+ ret.push(obj)
61
+ }
62
+ return ret
63
+ },
64
+ *iterate(params) {
65
+ stmt.bind(params)
66
+ while (stmt.step()) {
67
+ yield stmt.get()
68
+ }
69
+ }
70
+ }
71
+ this.gc.register(ret, stmt)
72
+ return ret
73
+ }
74
+
75
+ exec(sql) {
76
+ try {
77
+ const { columns, values } = this.db.exec(sql)
78
+ return !Array.isArray(values) ? values : values.map(val => {
79
+ const ret = {}
80
+ for (let i = 0; i < columns.length; i++) {
81
+ ret[columns[i]] = val[i]
82
+ }
83
+ return ret
84
+ })
85
+ } catch (err) {
86
+ // REVISIT: address transaction errors
87
+ if (sql === 'BEGIN' || sql === 'ROLLBACK') { return }
88
+ throw err
89
+ }
90
+ }
91
+
92
+ function(name, config, func) {
93
+ this.db.create_function(name, func || config)
94
+ }
95
+
96
+ close() { this.db.close() }
97
+ }
98
+
99
+ module.exports = WasmSqlite