@cap-js/sqlite 0.1.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,101 +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
- // Setting session context variables
47
- await this.set({
48
- get '$user.id'(){ return _set (this, '$user.id', ctx.user?.id || 'anonymous') },
49
- get '$user.locale'(){ return _set (this, '$user.locale', ctx.locale || cds.env.i18n.default_language) },
50
- get '$valid.from'(){ return _set (this, '$valid.from', ctx._?.['VALID-FROM'] || ctx._?.['VALID-AT'] || '1970-01-01T00:00:00Z') },
51
- get '$valid.to'(){ return _set (this, '$valid.to', ctx._?.['VALID-TO'] || _validTo4(ctx._?.['VALID-AT']) || '9999-11-11T22:22:22Z') },
52
- })
53
- // Run BEGIN
54
- try { await this.send('BEGIN') }
55
- catch (e) { this._release(dbc); throw e }
56
- return this
57
- }
58
-
59
- async commit() {
60
- const dbc = this.dbc; if (!dbc) return
61
- await this.send('COMMIT')
62
- this._release(dbc) // only release on successful commit as otherwise released on rollback
63
- }
64
-
65
- async rollback() {
66
- const dbc = this.dbc; if (!dbc) return
67
- try { await this.send('ROLLBACK') }
68
- finally { this._release(dbc) }
69
- }
70
-
71
- // REVISIT: should happen automatically after a configurable time
72
- async disconnect (tenant) {
73
- const pool = this.pools[tenant]
74
- if (pool) delete this.pools[tenant]; else return
75
- await pool.drain()
76
- await pool.clear()
77
- }
78
-
79
- run (query, data, ...etc) {
80
- // Allow db.run('...',1,2,3,4)
81
- if (data !== undefined && typeof query === 'string' && typeof data !== 'object') data = [ data, ...etc ]
82
- return super.run (query, data)
83
- }
84
-
85
- url4 (tenant) { // eslint-disable-line no-unused-vars
86
- let { url } = this.options?.credentials || this.options || {}
87
- return url
88
- }
89
- getDbUrl (tenant) { return this.url4 (tenant) } // REVISIT: Remove after cds v6.7
90
- }
91
-
92
- const _set = (context, variable, value) => {
93
- Object.defineProperty (context, variable, { value, configurable:true })
94
- return value
95
- }
96
- const _validTo4 = validAt => {
97
- return validAt?.replace(/(\dZ?)$/,d=>parseInt(d[0])+1+d[1]||'')
98
- }
99
-
100
- DatabaseService.prototype.isDatabaseService = true
101
- 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,223 +0,0 @@
1
- const cds = require('../../../cds'), DEBUG = cds.debug('sql|db')
2
- const DatabaseService = require('../DatabaseService')
3
- const cqn4sql = require('./cqn4sql')
4
- const { target_name4 } = require('./utils')
5
- const { PassThrough, pipeline } = require('stream')
6
-
7
-
8
- class SQLService extends DatabaseService {
9
-
10
- init() {
11
- this.on([ 'INSERT', 'UPSERT', 'UPDATE', 'DELETE' ], require('./workarounds').input) // REVISIT should be replaced by correct input processing eventually
12
- this.on([ 'INSERT', 'UPSERT', 'UPDATE', 'DELETE' ], require('./deep').onDeep)
13
- this.on([ 'SELECT' ], this.onSELECT)
14
- this.on([ 'INSERT', 'UPSERT' ], this.onINSERT)
15
- this.on([ 'UPDATE' ], this.onUPDATE)
16
- this.on([ 'DELETE', 'CREATE ENTITY', 'DROP ENTITY' ], this.onSIMPLE)
17
- this.on([ 'BEGIN', 'COMMIT', 'ROLLBACK' ], this.onEVENT)
18
- this.on([ '*' ], this.onPlainSQL)
19
- return super.init()
20
- }
21
-
22
- /** Handler for SELECT */
23
- async onSELECT ({ query, data }) {
24
- // REVISIT: disable this for queries like (SELECT 1)
25
- // Will return multiple rows with objects inside
26
- // REVISIT: streaming: if we need custom app and db handlers with app stream and cds.stream
27
- if (query._streaming) return this.onStream(query) // TODO: implemented on HANA
28
- query.SELECT.expand = 'root'
29
- const { sql, values, cqn } = this.cqn2sql (query, data)
30
- let ps = await this.prepare(sql)
31
- let rows = await ps.all(values)
32
- if (rows.length)
33
- if (cqn.SELECT.expand) rows = rows.map(r => typeof r._json_ === 'string' ? JSON.parse(r._json_) : r._json_ || r)
34
- if (cqn.SELECT.count) rows.$count = await this.count(query, rows)
35
- return cqn.SELECT.one || query.SELECT.from.ref?.[0].cardinality?.max === 1 ? rows[0] || null : rows
36
- }
37
-
38
- /** Handler for INSERT & UPSERT, which support bulk queries */
39
- async onINSERT ({ query, data }) {
40
- const { sql, entries, cqn } = this.cqn2sql (query, data)
41
- if(!sql) return // Do nothing when there is nothing to be done
42
- const ps = await this.prepare(sql)
43
- const results = entries ? await Promise.all(entries.map(e => ps.run(e))) : await ps.run()
44
- return new this.class.InsertResults (cqn, results)
45
- }
46
-
47
- /** Handler for UPDATE */
48
- async onUPDATE (req) {
49
- return this.onSIMPLE(req)
50
- }
51
-
52
- /** Handler for CREATE, DROP, UPDATE, DELETE, with simple CQN */
53
- async onSIMPLE ({ query, data }) {
54
- const { sql, values } = this.cqn2sql (query, data)
55
- let ps = await this.prepare(sql)
56
- return (await ps.run(values)).changes
57
- }
58
-
59
- /** Handler for BEGIN, COMMIT, ROLLBACK, which don't have any CQN */
60
- async onEVENT ({ event }) {
61
- DEBUG?.(event) // in the other cases above DEBUG happens in cqn2sql
62
- return await this.exec(event)
63
- }
64
-
65
- /** Handler for SQL statements which don't have any CQN */
66
- async onPlainSQL ({ query, data }, next) {
67
- if (typeof query === 'string') {
68
- DEBUG?.(query)
69
- const ps = await this.prepare(query)
70
- const exec = this.hasResults(query) ? d => ps.all(d) : d => ps.run(d)
71
- if (Array.isArray(data) && typeof data[0] === 'object')
72
- return await Promise.all(data.map(exec))
73
- else return exec(data)
74
- }
75
- else return next()
76
- }
77
-
78
- /** Override in subclasses to detect more statements to be called with ps.all() */
79
- hasResults(sql) {
80
- return /^(SELECT|WITH|CALL|PRAGMA table_info)/.test(sql)
81
- }
82
-
83
-
84
- /** Derives and executes a query to fill in `$count` for given query */
85
- async count (query, ret) {
86
- if (ret) {
87
- const { one, limit:_ } = query.SELECT, n = ret.length
88
- const [ max, offset=0 ] = one ? [1] : _ ? [ _.rows?.val, _.offset?.val ] : []
89
- if (max === undefined || n < max && (n || !offset)) return n + offset
90
- }
91
- const cq = cds.ql.clone (query, {
92
- columns: [{func:'count'}],
93
- localized: false,
94
- expand: false,
95
- limit: 0,
96
- orderBy: 0
97
- })
98
- const { sql, values } = this.cqn2sql(cq)
99
- const ps = await this.prepare(sql)
100
- const { count } = await ps.get(values)
101
- return count
102
- }
103
-
104
- /**
105
- * Streaming
106
- * Returns either a readable stream for sync calls or a readable stream promise for async calls
107
- */
108
- stream(q) {
109
- return typeof q === 'object'
110
- // aynchronous API: cds.stream(query)
111
- ? this.run(Object.assign(q, { _streaming: true }))
112
- // synchronous API: cds.stream('column').from(entity).where(...)
113
- : new StreamCQN(q, this)
114
- }
115
-
116
-
117
- static InsertResults = require('./InsertResults')
118
-
119
- /**
120
- * Helper class implementing {@link SQLService#cqn2sql}.
121
- * Subclasses commonly override this.
122
- */
123
- static CQN2SQL = require('./cqn2sql').class
124
- constructor() {
125
- super(...arguments)
126
- this.class = new.target // for IntelliSense
127
- }
128
- cqn2sql(q,values) {
129
- const cqn = this.cqn4sql(q)
130
- return (new this.class.CQN2SQL) .render (cqn, values)
131
- }
132
- cqn4sql(q) {
133
- if (!this.model?.definitions[target_name4(q)]) return _unquirked(q)
134
- return cqn4sql (q, this.model)
135
- }
136
-
137
- /**
138
- * Returns a Promise which resolves to a prepared statement object with
139
- * `{run,get,all}` signature as specified in {@link PreparedStatement}.
140
- * @returns {PreparedStatement}
141
- */
142
- // eslint-disable-next-line no-unused-vars
143
- async prepare (sql) { throw '2b overridden by subclass' }
144
-
145
- /**
146
- * Used to execute simple SQL statement like BEGIN, COMMIT, ROLLBACK
147
- */
148
- // eslint-disable-next-line no-unused-vars
149
- async exec (sql) { throw '2b overridden by subclass' }
150
-
151
- }
152
-
153
-
154
- /** Interface of prepared statement objects as returned by {@link SQLService#prepare} */
155
- class PreparedStatement { // eslint-disable-line no-unused-vars
156
- /**
157
- * Executes a prepared DML query, i.e., INSERT, UPDATE, DELETE, CREATE, DROP
158
- * @param {[]|{}} binding_params
159
- */
160
- async run (binding_params) {} // eslint-disable-line no-unused-vars
161
- /**
162
- * Executes a prepared SELECT query and returns a single/first row only
163
- * @param {[]|{}} binding_params
164
- */
165
- async get (binding_params) { return {} } // eslint-disable-line no-unused-vars
166
- /**
167
- * Executes a prepared SELECT query and returns an array of all rows
168
- * @param {[]|{}} binding_params
169
- */
170
- async all (binding_params) { return [{}] } // eslint-disable-line no-unused-vars
171
- }
172
-
173
- /**
174
- * Class that builds and runs stream CQN
175
- */
176
- class StreamCQN {
177
- constructor (column, srv) {
178
- this.column = column
179
- this.srv = srv
180
- this.result = new PassThrough()
181
- }
182
- /** synchronous streaming API: returns readable stream or class instance for chaining */
183
- from (...args) {
184
- this.sq = SELECT.from(...args)
185
- this.sq._streaming = true
186
- if (this.column) this.sq.columns([this.column])
187
- const ref = this.sq.SELECT.from.ref
188
- if (!ref?.[ref.length - 1].where) return this
189
- this._runStream()
190
- return this.result
191
- }
192
- /** synchronous streaming API: returns readable stream */
193
- where (...args) {
194
- this.sq.where(...args)
195
- this._runStream()
196
- return this.result
197
- }
198
-
199
- async _runStream() {
200
- try {
201
- const stream = await this.srv.run(this.sq)
202
- // In case of streaming error while streaming from stream to this.result
203
- // the error is emitted to both streams. After this the output stream this.result is destroyed.
204
- // No explicit closing of this.result is needed.
205
- // In (theoretical) case if for some error this.result is not destroyed the code like below can be used
206
- // as callback: err => err && this.result.push(null)
207
- stream ? pipeline(stream, this.result, () => {}) : this.result.push(null)
208
- }
209
- catch (err) { this.result.emit('error', err); this.result.push(null) }
210
- }
211
- }
212
-
213
- const _unquirked = q => {
214
- if (typeof q.INSERT?.into === 'string') q.INSERT.into = {ref:[q.INSERT.into]}
215
- if (typeof q.UPSERT?.into === 'string') q.UPSERT.into = {ref:[q.UPSERT.into]}
216
- if (typeof q.UPDATE?.entity === 'string') q.UPDATE.entity = {ref:[q.UPDATE.entity]}
217
- if (typeof q.DELETE?.from === 'string') q.DELETE.from = {ref:[q.DELETE.from]}
218
- if (typeof q.CREATE?.entity === 'string') q.CREATE.entity = {ref:[q.CREATE.entity]}
219
- if (typeof q.DROP?.entity === 'string') q.DROP.entity = {ref:[q.DROP.entity]}
220
- return q
221
- }
222
-
223
- 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
- }