@cap-js/sqlite 0.2.0 → 1.0.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/cds.js DELETED
@@ -1,39 +0,0 @@
1
- const cds = module.exports = require('@sap/cds/lib')
2
- const { extend, lazified } = cds
3
-
4
- extend(cds).with(lazified({
5
- inferred: lazy => require('./lib/ql/cds.infer'),
6
- cqn2sql: lazy => require('./lib/db/sql/cqn2sql'),
7
- cqn4sql: lazy => require('./lib/db/sql/cqn4sql'),
8
- }))
9
-
10
- extend (cds.ql.Query) .with (class {
11
- forSQL (db = cds.db || cds) { return this.flat(db.cqn4sql(this)) }
12
- toSQL (db = cds.db || cds) { return _2sql(db.cqn2sql(this)) }
13
- toSql (db = cds.db || cds) { return this.toSQL(db).sql }
14
- })
15
-
16
- // Monkey-patch req.event to be undefined for plain sql query strings -> remove when @sap/cds 6.6 is released
17
- const $super = Reflect.getOwnPropertyDescriptor(cds.Request.prototype,'event')
18
- Reflect.defineProperty (cds.Request.prototype,'event', {...$super, get(){
19
- if (typeof this.query === 'string') return this._set ('event', undefined)
20
- else return $super.get.call(this)
21
- }})
22
-
23
- // skip .cqn property when in repl
24
- const _2sql = cds.repl ? ({sql,values}) => ({sql,values}) : x => x
25
-
26
- cds.ApplicationService // FIXME: somehow we need to invoke the getter, must be removed in the future
27
-
28
- // cds.requires.kinds.postgres = {
29
- // kind: 'postgres',
30
- // dialect: 'plain',
31
- // credentials: {
32
- // "host": "localhost",
33
- // "port": "5432",
34
- // "database": "beershop", //> override per test
35
- // "username": "postgres",
36
- // "password": "postgres"
37
- // },
38
- // impl: __dirname + '/lib/db/pg'
39
- // }
@@ -1,102 +0,0 @@
1
- const cds = require('../../cds')
2
-
3
- function Pool (factory, tenant) { return createPool (
4
- { __proto__:factory, create: factory.create.bind(undefined,tenant) },
5
- factory.options
6
- )}
7
- const { createPool } = require('@sap/cds-foss').pool
8
-
9
- class DatabaseService extends cds.Service {
10
-
11
- /**
12
- * Return a pool factory + options property as expected by
13
- * https://github.com/coopernurse/node-pool#createpool.
14
- */
15
- get factory(){ throw '2b overriden in subclass' }
16
- pools = { _factory: this.factory }
17
-
18
- get isMultitenant() { return (
19
- 'multiTenant' in this.options ? this.options.multiTenant
20
- : cds.env.requires.multitenancy
21
- )}
22
-
23
- /**
24
- * Set one or more session context variables like so:
25
- * ```js
26
- * const tx = cds.db.tx()
27
- * tx.set({
28
- * '$user.name': 'Alice',
29
- * '$user.role': 'admin'
30
- * })
31
- * ```
32
- */
33
- // eslint-disable-next-line no-unused-vars
34
- set (variables) { throw '2b overridden by subclass' }
35
-
36
- infer (q, m = this.model) {
37
- return cds.inferred(q,m)
38
- }
39
-
40
- async begin() {
41
- const ctx = this.context; if (!ctx) return this.tx().begin()
42
- const tenant = this.isMultitenant && ctx.tenant
43
- const pool = this.pools[tenant] ??= new Pool (this.pools._factory, tenant)
44
- const dbc = this.dbc = await pool.acquire()
45
- this._release = (dbc) => pool.release(dbc)
46
- try {
47
- // Setting session context variables
48
- await this.set({
49
- get '$user.id'(){ return _set (this, '$user.id', ctx.user?.id || 'anonymous') },
50
- get '$user.locale'(){ return _set (this, '$user.locale', ctx.locale || cds.env.i18n.default_language) },
51
- get '$valid.from'(){ return _set (this, '$valid.from', ctx._?.['VALID-FROM']?.toISOString() || ctx._?.['VALID-AT']?.toISOString() || '1970-01-01T00:00:00Z') },
52
- get '$valid.to'(){ return _set (this, '$valid.to', ctx._?.['VALID-TO']?.toISOString() || _validTo4(ctx._?.['VALID-AT'])?.toISOString() || '9999-11-11T22:22:22Z') },
53
- })
54
- // Run BEGIN
55
- await this.send('BEGIN')
56
- } catch (e) { this._release(dbc); throw e }
57
- return this
58
- }
59
-
60
- async commit() {
61
- const dbc = this.dbc; if (!dbc) return
62
- await this.send('COMMIT')
63
- this._release(dbc) // only release on successful commit as otherwise released on rollback
64
- }
65
-
66
- async rollback() {
67
- const dbc = this.dbc; if (!dbc) return
68
- try { await this.send('ROLLBACK') }
69
- finally { this._release(dbc) }
70
- }
71
-
72
- // REVISIT: should happen automatically after a configurable time
73
- async disconnect (tenant) {
74
- const pool = this.pools[tenant]
75
- if (pool) delete this.pools[tenant]; else return
76
- await pool.drain()
77
- await pool.clear()
78
- }
79
-
80
- run (query, data, ...etc) {
81
- // Allow db.run('...',1,2,3,4)
82
- if (data !== undefined && typeof query === 'string' && typeof data !== 'object') data = [ data, ...etc ]
83
- return super.run (query, data)
84
- }
85
-
86
- url4 (tenant) { // eslint-disable-line no-unused-vars
87
- let { url } = this.options?.credentials || this.options || {}
88
- return url
89
- }
90
- getDbUrl (tenant) { return this.url4 (tenant) } // REVISIT: Remove after cds v6.7
91
- }
92
-
93
- const _set = (context, variable, value) => {
94
- Object.defineProperty (context, variable, { value, configurable:true })
95
- return value
96
- }
97
- const _validTo4 = validAt => {
98
- return validAt?.replace(/(\dZ?)$/,d=>parseInt(d[0])+1+d[1]||'')
99
- }
100
-
101
- DatabaseService.prototype.isDatabaseService = true
102
- module.exports = DatabaseService
@@ -1,87 +0,0 @@
1
- const iterator = Symbol.iterator
2
-
3
- // eslint-disable-next-line no-unused-vars
4
- const USAGE_SAMPLE = async ()=>{ // from https://pages.github.tools.sap/cap/docs/node.js/services?q=Emily#srvrun--query--results
5
- const { Authors, Books } = {}
6
- const [ Emily, Charlotte ] = await INSERT.into (Authors, [
7
- { name: 'Emily Brontëe' },
8
- { name: 'Charlotte Brontëe' },
9
- ])
10
- await INSERT.into (Books, [
11
- { title: 'Wuthering Heights', author: Emily },
12
- { title: 'Jane Eyre', author: Charlotte },
13
- ])
14
- }
15
-
16
-
17
- module.exports = class InsertResult {
18
-
19
- constructor (query, results) {
20
- this.query = query
21
- this.results = results
22
- }
23
-
24
- /*
25
- * Lazy access to auto-generated keys.
26
- */
27
- get [iterator]() {
28
-
29
- // For INSERT.as(SELECT.from(...)) return a dummy iterator with correct length
30
- const { INSERT } = this.query
31
- if (INSERT.as) {
32
- return super[iterator] = function*(){
33
- for (let i = 0; i < this.affectedRows; i++) yield {}
34
- }
35
- }
36
-
37
- const { target } = this.query; if (!target?.keys) return (super[iterator] = this.results[iterator])
38
- const keys = Object.keys(target.keys), [k1] = keys
39
-
40
- // For INSERT.entries() with generated keys in there return these keys
41
- const { entries } = INSERT; if (entries && k1 in entries[0]) {
42
- return super[iterator] = function*(){
43
- for (const each of entries)
44
- yield keys.reduce ((p,k) => { p[k] = each[k]; return p },{})
45
- }
46
- }
47
-
48
- // For INSERT.rows/values() with generated keys in there return these keys
49
- const { columns } = INSERT; if (columns && columns.includes(k1)) {
50
- return super[iterator] = function*(){
51
- const indices = keys.reduce ((p,k) => {
52
- let i = columns.indexOf(k); if (i >= 0) p[k] = i
53
- return p
54
- },{})
55
- for (const each of INSERT.rows || [INSERT.values])
56
- yield keys.reduce ((p,k) => { p[k] = each[indices[k]]; return p },{})
57
- }
58
- }
59
-
60
- // If no generated keys in entries/rows/values we might have database-generated keys
61
- const rows = this.results.slice(0, this.affectedRows) // only up to # of root entries
62
- return super[iterator] = function*(){
63
- for (const each of rows)
64
- yield { [k1]: this.insertedRowId4(each) } // REVISIT: sqlite only returns a single lastID per row -> how is that with others?
65
- }
66
- }
67
-
68
- /*
69
- * the number of inserted (root) entries or the number of affectedRows in case of INSERT into SELECT
70
- */
71
- get affectedRows() {
72
- const { INSERT:_ } = this.query
73
- if (_.as) return super.affectedRows = this.affectedRows4(this.results[0]|| this.results)
74
- else return super.affectedRows = _.entries?.length || _.rows?.length || this.results.length || 1
75
- }
76
-
77
- /*
78
- * for checks such as res > 2
79
- */
80
- valueOf() {
81
- return this.affectedRows
82
- }
83
-
84
- insertedRowId4 (result) { return result.lastID }
85
- affectedRows4 (result) { return result.changes }
86
-
87
- }
@@ -1,243 +0,0 @@
1
- const cds = require('../../../cds'), DEBUG = cds.debug('sql|db')
2
- const { resolveView } = require('@sap/cds/libx/_runtime/common/utils/resolveView')
3
- const DatabaseService = require('../DatabaseService')
4
- const cqn4sql = require('./cqn4sql')
5
- const { target_name4 } = require('./utils')
6
- const { PassThrough, pipeline } = require('stream')
7
-
8
-
9
- class SQLService extends DatabaseService {
10
-
11
- init() {
12
- this.on([ 'INSERT', 'UPSERT', 'UPDATE', 'DELETE' ], require('./workarounds').input) // REVISIT should be replaced by correct input processing eventually
13
- this.on([ 'INSERT', 'UPSERT', 'UPDATE', 'DELETE' ], require('./deep').onDeep)
14
- this.on([ 'SELECT' ], this.onSELECT)
15
- this.on([ 'INSERT' ], this.onINSERT)
16
- this.on([ 'UPSERT' ], this.onUPSERT)
17
- this.on([ 'UPDATE' ], this.onUPDATE)
18
- this.on([ 'DELETE', 'CREATE ENTITY', 'DROP ENTITY' ], this.onSIMPLE)
19
- this.on([ 'BEGIN', 'COMMIT', 'ROLLBACK' ], this.onEVENT)
20
- this.on([ '*' ], this.onPlainSQL)
21
- return super.init()
22
- }
23
-
24
- /** Handler for SELECT */
25
- async onSELECT ({ query, data }) {
26
- // REVISIT: disable this for queries like (SELECT 1)
27
- // Will return multiple rows with objects inside
28
- // REVISIT: streaming: if we need custom app and db handlers with app stream and cds.stream
29
- if (query._streaming) return this.onStream(query) // TODO: implemented on HANA
30
- query.SELECT.expand = 'root'
31
- const { sql, values, cqn } = this.cqn2sql (query, data)
32
- let ps = await this.prepare(sql)
33
- let rows = await ps.all(values)
34
- if (rows.length)
35
- if (cqn.SELECT.expand) rows = rows.map(r => typeof r._json_ === 'string' ? JSON.parse(r._json_) : r._json_ || r)
36
- if (cqn.SELECT.count) rows.$count = await this.count(query, rows)
37
- return cqn.SELECT.one || query.SELECT.from.ref?.[0].cardinality?.max === 1 ? rows[0] || null : rows
38
- }
39
-
40
- async onINSERT ({ query, data }) {
41
- const { sql, entries, cqn } = this.cqn2sql (query, data)
42
- if(!sql) return // Do nothing when there is nothing to be done
43
- const ps = await this.prepare(sql)
44
- const results = entries ? await Promise.all(entries.map(e => ps.run(e))) : await ps.run()
45
- return new this.class.InsertResults (cqn, results)
46
- }
47
-
48
- async onUPSERT ({ query, data }) {
49
- const { sql, entries } = this.cqn2sql (query, data)
50
- if(!sql) return // Do nothing when there is nothing to be done
51
- const ps = await this.prepare(sql)
52
- const results = entries ? await Promise.all(entries.map(e => ps.run(e))) : await ps.run()
53
- return results.reduce((lastValue, currentValue) => lastValue += currentValue.changes, 0)
54
- }
55
-
56
- /** Handler for UPDATE */
57
- async onUPDATE (req) {
58
- return this.onSIMPLE(req)
59
- }
60
-
61
- /** Handler for CREATE, DROP, UPDATE, DELETE, with simple CQN */
62
- async onSIMPLE ({ query, data }) {
63
- const { sql, values } = this.cqn2sql (query, data)
64
- let ps = await this.prepare(sql)
65
- return (await ps.run(values)).changes
66
- }
67
-
68
- /** Handler for BEGIN, COMMIT, ROLLBACK, which don't have any CQN */
69
- async onEVENT ({ event }) {
70
- DEBUG?.(event) // in the other cases above DEBUG happens in cqn2sql
71
- return await this.exec(event)
72
- }
73
-
74
- /** Handler for SQL statements which don't have any CQN */
75
- async onPlainSQL ({ query, data }, next) {
76
- if (typeof query === 'string') {
77
- DEBUG?.(query)
78
- const ps = await this.prepare(query)
79
- const exec = this.hasResults(query) ? d => ps.all(d) : d => ps.run(d)
80
- if (Array.isArray(data) && typeof data[0] === 'object')
81
- return await Promise.all(data.map(exec))
82
- else return exec(data)
83
- }
84
- else return next()
85
- }
86
-
87
- /** Override in subclasses to detect more statements to be called with ps.all() */
88
- hasResults(sql) {
89
- return /^(SELECT|WITH|CALL|PRAGMA table_info)/.test(sql)
90
- }
91
-
92
-
93
- /** Derives and executes a query to fill in `$count` for given query */
94
- async count (query, ret) {
95
- if (ret) {
96
- const { one, limit:_ } = query.SELECT, n = ret.length
97
- const [ max, offset=0 ] = one ? [1] : _ ? [ _.rows?.val, _.offset?.val ] : []
98
- if (max === undefined || n < max && (n || !offset)) return n + offset
99
- }
100
- const cq = cds.ql.clone (query, {
101
- columns: [{func:'count'}],
102
- localized: false,
103
- expand: false,
104
- limit: 0,
105
- orderBy: 0
106
- })
107
- const { sql, values } = this.cqn2sql(cq)
108
- const ps = await this.prepare(sql)
109
- const { count } = await ps.get(values)
110
- return count
111
- }
112
-
113
- /**
114
- * Streaming
115
- * Returns either a readable stream for sync calls or a readable stream promise for async calls
116
- */
117
- stream(q) {
118
- return typeof q === 'object'
119
- // aynchronous API: cds.stream(query)
120
- ? this.run(Object.assign(q, { _streaming: true }))
121
- // synchronous API: cds.stream('column').from(entity).where(...)
122
- : new StreamCQN(q, this)
123
- }
124
-
125
-
126
- static InsertResults = require('./InsertResults')
127
-
128
- /**
129
- * Helper class implementing {@link SQLService#cqn2sql}.
130
- * Subclasses commonly override this.
131
- */
132
- static CQN2SQL = require('./cqn2sql').class
133
- constructor() {
134
- super(...arguments)
135
- this.class = new.target // for IntelliSense
136
- }
137
- cqn2sql(q,values) {
138
- const cqn = this.cqn4sql(q)
139
-
140
- const cmd = cqn.cmd || Object.keys(cqn)[0]
141
- if (cmd in { INSERT:1, DELETE: 1, UPSERT: 1, UPDATE: 1 }) {
142
- let resolvedCqn = resolveView(cqn, this.model, this)
143
- if (resolvedCqn && resolvedCqn[cmd]._transitions?.[0].target) {
144
- resolvedCqn = resolvedCqn || cqn
145
- resolvedCqn.target = resolvedCqn?.[cmd]._transitions[0].target || cqn.target
146
- }
147
- return (new this.class.CQN2SQL(this.context)) .render (resolvedCqn, values)
148
- }
149
- return (new this.class.CQN2SQL(this.context)) .render (cqn, values)
150
- }
151
- cqn4sql(q) {
152
- // REVISIT: move this check to cqn4sql?
153
- if (!q.SELECT?.from?.join && !this.model?.definitions[target_name4(q)]) return _unquirked(q)
154
- return cqn4sql (q, this.model)
155
- }
156
-
157
- /**
158
- * Returns a Promise which resolves to a prepared statement object with
159
- * `{run,get,all}` signature as specified in {@link PreparedStatement}.
160
- * @returns {PreparedStatement}
161
- */
162
- // eslint-disable-next-line no-unused-vars
163
- async prepare (sql) { throw '2b overridden by subclass' }
164
-
165
- /**
166
- * Used to execute simple SQL statement like BEGIN, COMMIT, ROLLBACK
167
- */
168
- // eslint-disable-next-line no-unused-vars
169
- async exec (sql) { throw '2b overridden by subclass' }
170
-
171
- }
172
-
173
-
174
- /** Interface of prepared statement objects as returned by {@link SQLService#prepare} */
175
- class PreparedStatement { // eslint-disable-line no-unused-vars
176
- /**
177
- * Executes a prepared DML query, i.e., INSERT, UPDATE, DELETE, CREATE, DROP
178
- * @param {[]|{}} binding_params
179
- */
180
- async run (binding_params) {} // eslint-disable-line no-unused-vars
181
- /**
182
- * Executes a prepared SELECT query and returns a single/first row only
183
- * @param {[]|{}} binding_params
184
- */
185
- async get (binding_params) { return {} } // eslint-disable-line no-unused-vars
186
- /**
187
- * Executes a prepared SELECT query and returns an array of all rows
188
- * @param {[]|{}} binding_params
189
- */
190
- async all (binding_params) { return [{}] } // eslint-disable-line no-unused-vars
191
- }
192
-
193
- /**
194
- * Class that builds and runs stream CQN
195
- */
196
- class StreamCQN {
197
- constructor (column, srv) {
198
- this.column = column
199
- this.srv = srv
200
- this.result = new PassThrough()
201
- }
202
- /** synchronous streaming API: returns readable stream or class instance for chaining */
203
- from (...args) {
204
- this.sq = SELECT.from(...args)
205
- this.sq._streaming = true
206
- if (this.column) this.sq.columns([this.column])
207
- const ref = this.sq.SELECT.from.ref
208
- if (!ref?.[ref.length - 1].where) return this
209
- this._runStream()
210
- return this.result
211
- }
212
- /** synchronous streaming API: returns readable stream */
213
- where (...args) {
214
- this.sq.where(...args)
215
- this._runStream()
216
- return this.result
217
- }
218
-
219
- async _runStream() {
220
- try {
221
- const stream = await this.srv.run(this.sq)
222
- // In case of streaming error while streaming from stream to this.result
223
- // the error is emitted to both streams. After this the output stream this.result is destroyed.
224
- // No explicit closing of this.result is needed.
225
- // In (theoretical) case if for some error this.result is not destroyed the code like below can be used
226
- // as callback: err => err && this.result.push(null)
227
- stream ? pipeline(stream, this.result, () => {}) : this.result.push(null)
228
- }
229
- catch (err) { this.result.emit('error', err); this.result.push(null) }
230
- }
231
- }
232
-
233
- const _unquirked = q => {
234
- if (typeof q.INSERT?.into === 'string') q.INSERT.into = {ref:[q.INSERT.into]}
235
- if (typeof q.UPSERT?.into === 'string') q.UPSERT.into = {ref:[q.UPSERT.into]}
236
- if (typeof q.UPDATE?.entity === 'string') q.UPDATE.entity = {ref:[q.UPDATE.entity]}
237
- if (typeof q.DELETE?.from === 'string') q.DELETE.from = {ref:[q.DELETE.from]}
238
- if (typeof q.CREATE?.entity === 'string') q.CREATE.entity = {ref:[q.CREATE.entity]}
239
- if (typeof q.DROP?.entity === 'string') q.DROP.entity = {ref:[q.DROP.entity]}
240
- return q
241
- }
242
-
243
- module.exports = SQLService
@@ -1,17 +0,0 @@
1
- module.exports.copy = function (obj) {
2
- const walk = function (par, prop) {
3
- const val = prop ? par[prop] : par
4
-
5
- // If value is native return
6
- if (typeof val !== 'object' || val == null || val instanceof RegExp || val instanceof Date || val instanceof Buffer)
7
- return val
8
-
9
- const ret = Array.isArray(val) ? [] : {}
10
- Object.keys(val).forEach(k => {
11
- ret[k] = walk(val, k)
12
- })
13
- return ret
14
- }
15
-
16
- return walk(obj)
17
- }