@cap-js/db-service 2.8.2 → 2.10.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/CHANGELOG.md CHANGED
@@ -4,6 +4,41 @@
4
4
  - The format is based on [Keep a Changelog](http://keepachangelog.com/).
5
5
  - This project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
+ ## [2.10.0](https://github.com/cap-js/cds-dbs/compare/db-service-v2.9.0...db-service-v2.10.0) (2026-04-22)
8
+
9
+
10
+ ### Added
11
+
12
+ * `cds.features.count_as_string` ([#1556](https://github.com/cap-js/cds-dbs/issues/1556)) ([00e0e60](https://github.com/cap-js/cds-dbs/commit/00e0e60d68edf0d42c1fce2fae3bb1286aca131e))
13
+ * **cqn4sql:** support for enums ([#1527](https://github.com/cap-js/cds-dbs/issues/1527)) ([27c4279](https://github.com/cap-js/cds-dbs/commit/27c4279c495fce8344c785e4489e3116d1a52c55))
14
+ * pql ([#1532](https://github.com/cap-js/cds-dbs/issues/1532)) ([943f76a](https://github.com/cap-js/cds-dbs/commit/943f76a3e4405eb91f0f4b929590212500c49c30))
15
+
16
+
17
+ ### Fixed
18
+
19
+ * `$self` reference to func column in `having` ([#1539](https://github.com/cap-js/cds-dbs/issues/1539)) ([9eac576](https://github.com/cap-js/cds-dbs/commit/9eac5762fc4d254a1bc54bded1dd6a492299f576)), closes [#1528](https://github.com/cap-js/cds-dbs/issues/1528)
20
+ * foreign key not included in wildcard select from subquery ([#1540](https://github.com/cap-js/cds-dbs/issues/1540)) ([0fde4ed](https://github.com/cap-js/cds-dbs/commit/0fde4eda21a389c68982f348e9e7c3680c00dcb3)), closes [#1127](https://github.com/cap-js/cds-dbs/issues/1127)
21
+ * sqlite generated key is named lastInsertRowid ([#1501](https://github.com/cap-js/cds-dbs/issues/1501)) ([a4d3437](https://github.com/cap-js/cds-dbs/commit/a4d34378297c8afdb13abb7e664165012c36eb8f))
22
+
23
+ ## [2.9.0](https://github.com/cap-js/cds-dbs/compare/db-service-v2.8.2...db-service-v2.9.0) (2026-03-09)
24
+
25
+
26
+ ### Added
27
+
28
+ * runtime views ([#1410](https://github.com/cap-js/cds-dbs/issues/1410)) ([5242675](https://github.com/cap-js/cds-dbs/commit/5242675c97472b86b81b3dc5fe0906141d276b02))
29
+ * support calculated elements in hierarchies ([#1456](https://github.com/cap-js/cds-dbs/issues/1456)) ([97c6f66](https://github.com/cap-js/cds-dbs/commit/97c6f6661f0ac4043245e021f2bf182f4e5d406f))
30
+
31
+
32
+ ### Fixed
33
+
34
+ * **`exists`:** detect join relevant path after exists ([#1412](https://github.com/cap-js/cds-dbs/issues/1412)) ([c5bad06](https://github.com/cap-js/cds-dbs/commit/c5bad06724ce6761379f91748490c6caac84153a)), closes [#1407](https://github.com/cap-js/cds-dbs/issues/1407)
35
+ * **cqn2sql:** Relied on inconstistent behavior of cds.ql.cloned queries ([#1500](https://github.com/cap-js/cds-dbs/issues/1500)) ([f9cb201](https://github.com/cap-js/cds-dbs/commit/f9cb2011219a86ae22f22fcc105e597b23209adf))
36
+ * enable expressions for `inline` ([#1512](https://github.com/cap-js/cds-dbs/issues/1512)) ([65f78e1](https://github.com/cap-js/cds-dbs/commit/65f78e1f3af83188462e9d44db67daa5d743ceb0))
37
+ * path expressions for scoped queries ([#1507](https://github.com/cap-js/cds-dbs/issues/1507)) ([0f1e234](https://github.com/cap-js/cds-dbs/commit/0f1e234b373f26a6244c715c9ca9d4a207a0faed))
38
+ * reject duplicated wildcards ([#1511](https://github.com/cap-js/cds-dbs/issues/1511)) ([b483062](https://github.com/cap-js/cds-dbs/commit/b483062e2ff5a8d0960dc2e7b71880af87ee8f78))
39
+ * the combination of `iterator` and `SELECT.one` ([#1514](https://github.com/cap-js/cds-dbs/issues/1514)) ([4b28579](https://github.com/cap-js/cds-dbs/commit/4b2857920a7a57bcfc09a9b5fb765283cf8bd70b))
40
+ * wildcard on inlined assoc ([#1513](https://github.com/cap-js/cds-dbs/issues/1513)) ([e520b97](https://github.com/cap-js/cds-dbs/commit/e520b97fd30394825b937b3613370c32c36c24a4))
41
+
7
42
  ## [2.8.2](https://github.com/cap-js/cds-dbs/compare/db-service-v2.8.1...db-service-v2.8.2) (2026-02-03)
8
43
 
9
44
 
@@ -69,9 +69,12 @@ module.exports = class InsertResult {
69
69
  }
70
70
 
71
71
  // If no generated keys in entries/rows/values we might have database-generated keys
72
- const rows = this.results.slice(0, this.affectedRows) // only up to # of root entries
73
72
  return (super[iterator] = function* () {
74
- for (const each of rows) yield { [k1]: this.insertedRowId4(each) } // REVISIT: sqlite only returns a single lastID per row -> how is that with others?
73
+ for (const row of this.results) {
74
+ const affectedRows = this.affectedRows4(row) - 1
75
+ const lastInsertRowid = this.insertedRowId4(row)
76
+ for (let i = lastInsertRowid - affectedRows; i<=lastInsertRowid;i++) yield { [k1]: i }
77
+ }
75
78
  })
76
79
  }
77
80
 
@@ -99,7 +102,7 @@ module.exports = class InsertResult {
99
102
  * @returns {number}
100
103
  */
101
104
  insertedRowId4(result) {
102
- return result.lastID
105
+ return result.lastInsertRowid
103
106
  }
104
107
 
105
108
  /**
package/lib/SQLService.js CHANGED
@@ -1,10 +1,14 @@
1
- const cds = require('@sap/cds'),
2
- DEBUG = cds.debug('sql|db')
1
+ const cds = require('@sap/cds')
2
+ const DEBUG = cds.log('sql|db')
3
3
  const { Readable, Transform } = require('stream')
4
4
  const { pipeline } = require('stream/promises')
5
- const { resolveView, getDBTable, getTransition } = require('@sap/cds/libx/_runtime/common/utils/resolveView')
6
5
  const DatabaseService = require('./common/DatabaseService')
7
6
  const cqn4sql = require('./cqn4sql')
7
+ const { resolveTable } = require('./utils')
8
+
9
+ // REVISIT: make string the default in next major
10
+ const _count_as_string = cds.env.features.count_as_string
11
+ const _count = _count_as_string ? { func: 'count', cast: { type: 'cds.String' } } : { func: 'count' }
8
12
 
9
13
  const BINARY_TYPES = {
10
14
  'cds.Binary': 1,
@@ -17,7 +21,7 @@ const BINARY_TYPES = {
17
21
  * @param {*} obj
18
22
  * @returns Boolean
19
23
  */
20
- const _hasProps = (obj) => {
24
+ const _hasProps = (obj) => {
21
25
  if (!obj) return false
22
26
  for (const p in obj) {
23
27
  return true
@@ -74,7 +78,7 @@ class SQLService extends DatabaseService {
74
78
  _changeToStreams(columns, rows, one) {
75
79
  if (!rows || !columns) return
76
80
  if (!Array.isArray(rows)) rows = [rows]
77
- if (!rows.length || !Object.keys(rows[0]).length) return
81
+ if (!rows.length || !Object.keys(rows[0]).length) return
78
82
 
79
83
  let changes = false
80
84
  for (let col of columns) {
@@ -149,7 +153,7 @@ class SQLService extends DatabaseService {
149
153
  if (expand) rows = rows.map(r => (typeof r._json_ === 'string' ? JSON.parse(r._json_) : r._json_ || r))
150
154
 
151
155
  if (!iterator) {
152
- this._changeToStreams(cqn.SELECT.columns, rows, query.SELECT.one)
156
+ this._changeToStreams(cqn.SELECT.columns, rows, query.SELECT.one)
153
157
  } else if (objectMode) {
154
158
  const converter = (row) => this._changeToStreams(cqn.SELECT.columns, row, true)
155
159
  const changeToStreams = new Transform({
@@ -168,7 +172,7 @@ class SQLService extends DatabaseService {
168
172
  return SQLService._arrayWithCount(rows, await this.count(query, rows))
169
173
  }
170
174
 
171
- return iterator !== false && isOne ? rows[0] : rows
175
+ return !iterator && isOne ? rows[0] : rows
172
176
  } catch (err) {
173
177
  // Ensure that iterators receive pre stream errors
174
178
  if (iterator) rows.emit('error', err)
@@ -198,7 +202,7 @@ class SQLService extends DatabaseService {
198
202
  const ps = await this.prepare(sql)
199
203
  const results = entries ? await Promise.all(entries.map(e => ps.run(e))) : await ps.run()
200
204
  // REVISIT: results isn't an array, when no entries -> how could that work? when do we have no entries?
201
- return results.reduce((total, affectedRows) => (total += affectedRows.changes), 0)
205
+ return results.reduce((total, affectedRows) => total + affectedRows.changes, 0)
202
206
  }
203
207
 
204
208
  /**
@@ -230,7 +234,8 @@ class SQLService extends DatabaseService {
230
234
  // REVISIT: It's not yet 100 % clear under which circumstances we can rely on db constraints
231
235
  return (super.onDELETE = /* cds.env.features.assert_integrity === 'db' ? this.onSIMPLE : */ deep_delete)
232
236
  async function deep_delete(/** @type {Request} */ req) {
233
- const transitions = getTransition(req.target, this, false, req.query.cmd || 'DELETE')
237
+ const resolve = this.resolve
238
+ const transitions = resolve.transitions(req.query)
234
239
  if (transitions.target !== transitions.queryTarget) {
235
240
  const keys = []
236
241
  const transitionsTarget = transitions.queryTarget.keys || transitions.queryTarget.elements
@@ -253,7 +258,7 @@ class SQLService extends DatabaseService {
253
258
  })
254
259
  return this.onDELETE({ query, target: transitions.target })
255
260
  }
256
- const table = getDBTable(req.target)
261
+ const table = resolveTable(req.target)
257
262
  const { compositions } = table
258
263
  if (compositions) {
259
264
  // Transform CQL`DELETE from Foo[p1] WHERE p2` into CQL`DELETE from Foo[p1 and p2]`
@@ -295,7 +300,7 @@ class SQLService extends DatabaseService {
295
300
  * @type {Handler}
296
301
  */
297
302
  async onEVENT({ event }) {
298
- DEBUG?.(event) // in the other cases above DEBUG happens in cqn2sql
303
+ if(DEBUG._debug) DEBUG.debug(event) // in the other cases above DEBUG happens in cqn2sql
299
304
  return await this.exec(event)
300
305
  }
301
306
 
@@ -305,7 +310,7 @@ class SQLService extends DatabaseService {
305
310
  */
306
311
  async onPlainSQL({ query, data }, next) {
307
312
  if (typeof query === 'string') {
308
- DEBUG?.(query, data)
313
+ if(DEBUG._debug) DEBUG.debug(query, data)
309
314
  const ps = await this.prepare(query)
310
315
  const exec = this.hasResults(query) ? d => ps.all(d) : d => ps.run(d)
311
316
  if (Array.isArray(data) && Array.isArray(data[0])) return await Promise.all(data.map(exec))
@@ -325,24 +330,24 @@ class SQLService extends DatabaseService {
325
330
  * Derives and executes a query to fill in `$count` for given query
326
331
  * @param {import('@sap/cds/apis/cqn').SELECT} query - SELECT CQN
327
332
  * @param {unknown[]} ret - Results of the original query
328
- * @returns {Promise<number>}
333
+ * @returns {Promise<number|string>}
329
334
  */
330
335
  async count(query, ret) {
331
336
  if (ret?.length) {
332
337
  const { one, limit: _ } = query.SELECT,
333
338
  n = ret.length
334
339
  const [max, offset = 0] = one ? [1] : _ ? [_.rows?.val, _.offset?.val] : []
335
- if (max === undefined || (n < max && (n || !offset))) return n + offset
340
+ if (max === undefined || (n < max && (n || !offset))) return _count_as_string ? `${n + offset}` : n + offset
336
341
  }
337
342
 
338
343
  // Keep original query columns when potentially used insde conditions
339
344
  const { having, groupBy } = query.SELECT
340
345
  let columns = []
341
- if((having?.length || groupBy?.length)) {
346
+ if (having?.length || groupBy?.length) {
342
347
  columns = query.SELECT.columns.filter(c => !c.expand)
343
348
  }
344
349
  if (columns.length === 0) columns.push({ val: 1 })
345
- const cq = SELECT.one([{ func: 'count' }]).from(
350
+ const cq = SELECT.one([_count]).from(
346
351
  cds.ql.clone(query, {
347
352
  columns,
348
353
  localized: false,
@@ -360,7 +365,7 @@ class SQLService extends DatabaseService {
360
365
  * @param {import('@sap/cds/apis/cqn').SELECT} query - SELECT CQN
361
366
  * @param {function} callback - Function to be invoked for each row
362
367
  */
363
- foreach (query, callback) {
368
+ foreach(query, callback) {
364
369
  return query.foreach(callback)
365
370
  }
366
371
 
@@ -390,40 +395,34 @@ class SQLService extends DatabaseService {
390
395
  })
391
396
  }
392
397
 
393
- /** @param {unknown[]} args */
394
398
  constructor(...args) {
395
399
  super(...args)
396
- /** @type {unknown} */
397
400
  this.class = new.target // for IntelliSense
398
401
  }
399
402
 
400
403
  /**
401
404
  * @param {import('@sap/cds/apis/cqn').Query} query
402
- * @param {unknown} values
403
405
  * @returns {typeof SQLService.CQN2SQL}
404
406
  */
405
407
  cqn2sql(query, values) {
406
- let q = this.cqn4sql(query)
407
- let kind = q.kind || Object.keys(q)[0]
408
- if (kind in { INSERT: 1, DELETE: 1, UPSERT: 1, UPDATE: 1 }) {
409
- q = resolveView(q, this.model, this) // REVISIT: before resolveView was called on flat cqn obtained from cqn4sql -> is it correct to call on original q instead?
410
- }
411
- let cqn2sql = new this.class.CQN2SQL(this)
412
- return cqn2sql.render(q, values)
408
+ const cqn2sql = new this.class.CQN2SQL(this)
409
+ const q = this.cqn4sql(query)
410
+ const sql = cqn2sql.render(q, values)
411
+ return sql
413
412
  }
414
413
 
415
414
  /**
416
415
  * @param {import('@sap/cds/apis/cqn').Query} q
417
416
  * @returns {import('./infer/cqn').Query}
418
417
  */
419
- cqn4sql(q) {
418
+ cqn4sql(q, useTechnicalAlias=true) {
420
419
  if (
421
420
  !cds.env.features.db_strict &&
422
421
  !q.SELECT?.from?.join &&
423
422
  !q.SELECT?.from?.SELECT &&
424
423
  !this.model?.definitions[_target_name4(q)]
425
424
  ) return q
426
- else return cqn4sql(q, this.model)
425
+ else return cqn4sql(q, this.model, useTechnicalAlias)
427
426
  }
428
427
 
429
428
  /**
@@ -512,31 +511,70 @@ const _target_name4 = q => {
512
511
  return first.id || first
513
512
  }
514
513
 
515
- const sqls = new (class extends SQLService {
516
- get factory() {
517
- return null
518
- }
519
514
 
520
- get model() {
521
- return cds.model
515
+ // Add support for cqn2pql if debug logging for pql is enabled, or if running in the REPL.
516
+ const DEBUG_PQL = cds.log('pql')
517
+ if (DEBUG_PQL._debug || cds.repl) {
518
+
519
+ // Add helper method to convert CQN to PQL, used below...
520
+ SQLService.prototype.cqn2pql = function cqn2pql (query, values) {
521
+ const CQN2PQL = cqn2pql.renderer ??= require('./cqn2pql')
522
+ return new CQN2PQL(this).render(query, values)
522
523
  }
523
- })()
524
- cds.extend(cds.ql.Query).with(
525
- class {
526
- forSQL() {
527
- let cqn = (cds.db || sqls).cqn4sql(this)
528
- return this.flat(cqn)
529
- }
530
- toSQL() {
531
- if (this.SELECT) this.SELECT.expand = 'root' // Enforces using json functions always for top-level SELECTS
532
- let { sql, values } = (cds.db || sqls).cqn2sql(this)
533
- return { sql, values } // skipping .cqn property
524
+
525
+ // Add support for logging generated PQL if debug logging for pql is enabled.
526
+ if (DEBUG_PQL._debug) {
527
+ const $super = SQLService.prototype.cqn2sql
528
+ SQLService.prototype.cqn2sql = function (query, values) {
529
+ const q2 = this.cqn4sql(query, false) // FIXME: calling cqn4sql twice per query is utterly expensive, isn't it ?!?
530
+ const pql = this.cqn2pql(q2, values)
531
+ DEBUG_PQL.debug(pql.sql, pql.values ?? '')
532
+ return $super.call(this, query, values)
534
533
  }
535
- toSql() {
536
- return this.toSQL().sql
534
+ }
535
+
536
+ // If running in the REPL, extend cds.ql.Query with helpers to inspect queries.
537
+ if (cds.repl) {
538
+
539
+ cds.extend(cds.ql.Query).with(
540
+ class {
541
+ forSQL() {
542
+ const cqn = db.srv.cqn4sql(this)
543
+ return this.flat(cqn)
544
+ }
545
+ forSql() { return this.forSQL() }
546
+ toSQL() {
547
+ if (this.SELECT) this.SELECT.expand = 'root' // Enforces using json functions always for top-level SELECTS
548
+ const { sql, values } = db.srv.cqn2sql(this)
549
+ return { sql, values } // skipping .cqn property
550
+ }
551
+ toSql() {
552
+ const { sql } = this.toSQL()
553
+ return sql
554
+ }
555
+ toPQL() {
556
+ const { sql, values } = db.srv.cqn2pql(this)
557
+ return { sql, values } // skipping .cqn property
558
+ }
559
+ toPql() {
560
+ const { sql } = this.toPQL()
561
+ return sql
562
+ }
563
+ }
564
+ )
565
+
566
+ /**
567
+ * Dummy SQL service used in extensions to cds.ql above,
568
+ * if no real SQL service is available yet through cds.db.
569
+ */
570
+ class db extends SQLService {
571
+ /** @returns {SQLService} */
572
+ static get srv() { return cds.db || (this.singleton ??= new this) }
573
+ get factory() { return null }
574
+ get model() { return cds.model }
537
575
  }
538
- },
539
- )
576
+ }
577
+ }
540
578
 
541
579
  Object.assign(SQLService, { _target_name4 })
542
580
  module.exports = SQLService
@@ -293,7 +293,7 @@ SELECT
293
293
  (SELECT MAX(HIERARCHY_RANK) + 1 FROM ${ranked})
294
294
  ) - Source.HIERARCHY_RANK AS HIERARCHY_TREE_SIZE
295
295
  FROM ${ranked} AS Source`)
296
- Hierarchy.as = 'H' + (uniqueCounter++)
296
+ Hierarchy.as = `H${uniqueCounter}`
297
297
  Hierarchy.SELECT.columns = [...Hierarchy.SELECT.columns, ...passThroughColumns]
298
298
  Hierarchy = this.expr(this.with(Hierarchy))
299
299
 
package/lib/cqn2pql.js ADDED
@@ -0,0 +1,116 @@
1
+ const cds = require('@sap/cds')
2
+
3
+ const CQN2SQL = require('./cqn2sql.js').class
4
+
5
+ class CQN2PQLRenderer extends CQN2SQL {
6
+
7
+ SELECT(q) {
8
+ this.values = undefined // inline all values
9
+ return (this.sql = super.SELECT(q)
10
+ .replaceAll('\n FROM', '\nFROM')
11
+ .replaceAll(/([^ ]) (FROM|WHERE|GROUP BY|HAVING|ORDER BY|LIMIT) /g, (a, b, c) => `${b}\n${c} `)
12
+ )
13
+ }
14
+
15
+ SELECT_columns(q) {
16
+ return super.SELECT_columns(q).map((c, i) => `${(i % 5 === 0) ? '\n ' : ' '}${c}${/ as /i.test(c) ? '\n' : ''}`).join(',')
17
+ }
18
+
19
+ column_expr(x, q) {
20
+ // omit alias when target is a single source
21
+ if (q.SELECT.from.ref && x?.ref) x.ref = x.ref.slice(-1)
22
+ return super.column_expr(x, q)
23
+ }
24
+
25
+ SELECT_expand(q, sql) { return sql }
26
+
27
+ INSERT_entries(q) {
28
+ super.INSERT_entries(q)
29
+ this.sql = this.sql
30
+ .replaceAll(/AS (.*?)([, ])(?=[^\n])/ig, (a, b, c) => `AS ${b}${c}\n${c === ',' ? ' ' : ''}`)
31
+ .replaceAll(/ *= */ig, ' = ')
32
+ .replaceAll('value AS "$$value$$"', 'value')
33
+ .replaceAll(' WHERE ', '\nWHERE ')
34
+ .replaceAll(' SELECT ', '\nSELECT')
35
+ .replaceAll('(SELECT ', '(SELECT\n ')
36
+ .replaceAll('))', ')\n)')
37
+ }
38
+
39
+ INSERT_rows(q) {
40
+ super.INSERT_rows(q)
41
+ this.sql = this.sql.replaceAll('SELECT', '\nSELECT')
42
+ }
43
+
44
+ UPSERT(q) {
45
+ super.UPSERT(q)
46
+ this.sql = this.sql
47
+ .replaceAll('INSERT', 'UPSERT')
48
+ .replaceAll(/AS (.*?)([, ])(?=[^\n])/ig, (a, b, c) => `AS ${b}${c}\n${c === ',' ? ' ' : ''}`)
49
+ .replaceAll(/ *= */ig, ' = ')
50
+ .replaceAll('value AS "$$value$$"', 'value')
51
+ .replaceAll(' WHERE ', '\nWHERE ')
52
+ .replaceAll(' SELECT ', '\nSELECT')
53
+ .replaceAll('(SELECT ', '(SELECT\n ')
54
+ .replaceAll('))', ')\n)')
55
+ }
56
+
57
+ expr(x) {
58
+ const wrap = x.cast ? sql => `cast(${sql} as ${this.type4(x.cast)})` : sql => sql
59
+ if (typeof x === 'string') throw cds.error`Unsupported expr: ${x}`
60
+ if (x.param) return wrap(this.param(x))
61
+ if ('ref' in x) return wrap(this.ref(x))
62
+ if ('val' in x) return wrap(this.val(x))
63
+ if ('func' in x) return wrap(this.func(x))
64
+ if ('xpr' in x) return wrap(this.xpr(x))
65
+ if ('list' in x) return wrap(this.list(x))
66
+ if ('SELECT' in x) return wrap(`(\n ${this.SELECT(x).replaceAll('\n', '\n ')}\n )`)
67
+ else throw cds.error`Unsupported expr: ${x}`
68
+ }
69
+
70
+ quote(s) { return s }
71
+
72
+ managed(columns, elements) {
73
+ const keys = ObjectKeys(elements).filter(e => elements[e].key && !elements[e].isAssociation)
74
+ const keyZero = keys[0]
75
+
76
+ const ret = super.managed(columns, elements)
77
+
78
+ ret.forEach(c => {
79
+ const { name, insert, update, onInsert, onUpdate } = c
80
+ const element = elements?.[name]
81
+ c.upsert = keyZero && (
82
+ // upsert requires the keys to be provided for the existance join (default values optional)
83
+ element?.key
84
+ // If both insert and update have the same managed definition exclude the old value check
85
+ || (onInsert && onUpdate && insert === update)
86
+ ? `${insert} as ${name}`
87
+ : `!OLD.${keyZero} ? ${
88
+ // If key of old is null execute insert
89
+ insert
90
+ } : ${
91
+ // Else execute managed update or keep old if no new data if provided
92
+ onUpdate ? update : `(${this.managed_default(name, `OLD.${name}`, update)})`
93
+ } as ${name}`
94
+ )
95
+ if (c.upsert) c.upsert = '\n ' + c.upsert
96
+ })
97
+ return ret
98
+ }
99
+
100
+ managed_default(name, managed, src) {
101
+ return `!${src} ? ${managed} : ${src}`
102
+ }
103
+
104
+ managed_extract(name) {
105
+ const { UPSERT, INSERT } = this.cqn
106
+ const extract = !(INSERT?.entries || UPSERT?.entries) && (INSERT?.rows || UPSERT?.rows)
107
+ ? `value[${this.columns.indexOf(name)}]`
108
+ : `value[${JSON.stringify(name)}]`
109
+ const sql = extract
110
+ return { extract, sql }
111
+ }
112
+ }
113
+
114
+ const ObjectKeys = o => (o && [...ObjectKeys(o.__proto__), ...Object.keys(o)]) || []
115
+
116
+ module.exports = CQN2PQLRenderer