@cap-js/postgres 1.13.0 → 2.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/CHANGELOG.md CHANGED
@@ -4,6 +4,34 @@
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.0.0](https://github.com/cap-js/cds-dbs/compare/postgres-v1.14.0...postgres-v2.0.0) (2025-05-07)
8
+
9
+
10
+ ### ⚠ BREAKING CHANGES
11
+
12
+ * update peer dependency to @sap/cds@9 ([#1178](https://github.com/cap-js/cds-dbs/issues/1178))
13
+ * update dependency to @cap-js/db-service@2 ([#1178](https://github.com/cap-js/cds-dbs/issues/1178))
14
+ * Unfiltered db constraint errors ([#1165](https://github.com/cap-js/cds-dbs/issues/1165))
15
+
16
+
17
+ ### Added
18
+
19
+ * Support for hierarchical queries ([#1093](https://github.com/cap-js/cds-dbs/issues/1093)) ([246e0b3](https://github.com/cap-js/cds-dbs/commit/246e0b38840f7e132ea49cae335b6be7a55354b3))
20
+
21
+
22
+ ### Changed
23
+
24
+ * Unfiltered db constraint errors ([#1165](https://github.com/cap-js/cds-dbs/issues/1165)) ([ff39e22](https://github.com/cap-js/cds-dbs/commit/ff39e22ac6cd3f20c98bc31c1a6bb828aa009796))
25
+ * update peer dependency to @sap/cds@9 ([#1178](https://github.com/cap-js/cds-dbs/issues/1178)) ([#1178](https://github.com/cap-js/cds-dbs/issues/1178)) ([0507edd](https://github.com/cap-js/cds-dbs/commit/0507edd4e1dcb98983b1fb65ade1344d978b7524))
26
+ * update dependency to @cap-js/db-service@2 ([#1178](https://github.com/cap-js/cds-dbs/issues/1178)) ([#1178](https://github.com/cap-js/cds-dbs/issues/1178)) ([0507edd](https://github.com/cap-js/cds-dbs/commit/0507edd4e1dcb98983b1fb65ade1344d978b7524))
27
+
28
+ ## [1.14.0](https://github.com/cap-js/cds-dbs/compare/postgres-v1.13.0...postgres-v1.14.0) (2025-04-17)
29
+
30
+
31
+ ### Added
32
+
33
+ * Result set streaming ([#702](https://github.com/cap-js/cds-dbs/issues/702)) ([2fe02ea](https://github.com/cap-js/cds-dbs/commit/2fe02eafd02993e5697efbdab90ad997fb2c9e00))
34
+
7
35
  ## [1.13.0](https://github.com/cap-js/cds-dbs/compare/postgres-v1.12.0...postgres-v1.13.0) (2025-03-31)
8
36
 
9
37
 
@@ -4,7 +4,6 @@ const cds = require('@sap/cds')
4
4
  const crypto = require('crypto')
5
5
  const { Writable, Readable } = require('stream')
6
6
  const sessionVariableMap = require('./session.json')
7
- const SANITIZE_VALUES = process.env.NODE_ENV === 'production' && cds.env.log.sanitize_values !== false
8
7
 
9
8
  class PostgresService extends SQLService {
10
9
  init() {
@@ -178,9 +177,9 @@ GROUP BY k
178
177
  throw enhanceError(e, sql)
179
178
  }
180
179
  },
181
- stream: async (values, one) => {
180
+ stream: async (values, one, objectMode) => {
182
181
  try {
183
- const streamQuery = new QueryStream({ ...query, values: this._getValues(values) }, one)
182
+ const streamQuery = new QueryStream({ ...query, values: this._getValues(values) }, one, objectMode)
184
183
  return await this.dbc.query(streamQuery)
185
184
  } catch (e) {
186
185
  throw enhanceError(e, sql)
@@ -305,7 +304,8 @@ GROUP BY k
305
304
  }
306
305
  }
307
306
 
308
- async onSELECT({ query, data }) {
307
+ async onSELECT(req) {
308
+ const { query, data } = req
309
309
  // workaround for chunking odata streaming
310
310
  if (query.SELECT?.columns?.find(col => col.as === '$mediaContentType')) {
311
311
  const columns = query.SELECT.columns
@@ -323,26 +323,50 @@ GROUP BY k
323
323
  res[this.class.CQN2SQL.prototype.column_name(binary[0])] = stream
324
324
  return res
325
325
  }
326
- return super.onSELECT({ query, data })
326
+ return super.onSELECT(req)
327
327
  }
328
328
 
329
- async onINSERT(req) {
330
- try {
331
- return await super.onINSERT(req)
332
- } catch (err) {
333
- throw _not_unique(err, 'ENTITY_ALREADY_EXISTS', req.data)
334
- }
335
- }
336
329
 
337
- async onUPDATE(req) {
338
- try {
339
- return await super.onUPDATE(req)
340
- } catch (err) {
341
- throw _not_unique(err, 'UNIQUE_CONSTRAINT_VIOLATION', req.data)
330
+ static CQN2SQL = class CQN2Postgres extends SQLService.CQN2SQL {
331
+
332
+ render_with() {
333
+ const sql = this.sql
334
+ let recursive = false
335
+ const prefix = this._with.map(q => {
336
+ let sql
337
+ if ('SELECT' in q) sql = `${this.quote(q.as)} AS (${this.SELECT(q)})`
338
+ else if ('SET' in q) {
339
+ recursive = true
340
+ const { SET } = q
341
+ const isDepthFirst = SET.orderBy?.length && (SET.orderBy[0].sort?.toLowerCase() === 'desc' || SET.orderBy[0].sort === -1)
342
+ let alias = q.as
343
+ if (isDepthFirst) {
344
+ alias = alias + '_depth_first'
345
+ SET.args[1].SELECT.from.args.forEach(r => { if (r.ref[0] === q.as) r.ref[0] = alias })
346
+ }
347
+
348
+ sql = `${this.quote(alias)}(${SET.args[0].SELECT.columns?.map(c => this.quote(this.column_name(c))) || ''}) AS (${
349
+ // Root select
350
+ this.SELECT(SET.args[0])} ${
351
+ // Union clause
352
+ SET.op?.toUpperCase() || 'UNION'} ${SET.all ? 'ALL' : ''} ${
353
+ // Repeated join query
354
+ this.SELECT(SET.args[1])
355
+ })${
356
+ // Leverage Postgres specific depth first syntax
357
+ SET.orderBy?.length
358
+ ? ` SEARCH DEPTH FIRST BY ${SET.orderBy.map(r => this.ref(r))} SET "$DEPTH$"`
359
+ : ''
360
+ }`
361
+
362
+ // Enforce depth sorting for consuming queries
363
+ if (isDepthFirst) sql += `,${this.quote(q.as)} AS (SELECT * FROM ${this.quote(q.as + '_depth_first')} ORDER BY "$DEPTH$")`
364
+ }
365
+ return { sql }
366
+ })
367
+ this.sql = `WITH${recursive ? ' RECURSIVE' : ''} ${prefix.map(p => p.sql)} ${sql}`
342
368
  }
343
- }
344
369
 
345
- static CQN2SQL = class CQN2Postgres extends SQLService.CQN2SQL {
346
370
  _orderBy(orderBy, localized, locale) {
347
371
  return orderBy.map(c => {
348
372
  const nulls = c.nulls || (c.sort?.toLowerCase() === 'desc' || c.sort === -1 ? 'LAST' : 'FIRST')
@@ -574,7 +598,6 @@ GROUP BY k
574
598
 
575
599
  // Convert ST types back to WKT format
576
600
  'cds.hana.ST_POINT': expr => `ST_AsText(${expr})`,
577
- 'cds.hana.ST_POINT': expr => `ST_AsText(${expr})`,
578
601
  }
579
602
  }
580
603
 
@@ -667,7 +690,7 @@ GROUP BY k
667
690
  }
668
691
 
669
692
  class QueryStream extends Query {
670
- constructor(config, one) {
693
+ constructor(config, one, objectMode) {
671
694
  // REVISIT: currently when setting the row chunk size
672
695
  // it results in an inconsistent connection state
673
696
  // if (!one) config.rows = 1000
@@ -676,6 +699,7 @@ class QueryStream extends Query {
676
699
  this._one = one || config.one
677
700
 
678
701
  this.stream = new Readable({
702
+ objectMode,
679
703
  read: this.rows
680
704
  ? () => {
681
705
  this.stream.pause()
@@ -693,7 +717,7 @@ class QueryStream extends Query {
693
717
  this._prom = new Promise((resolve, reject) => {
694
718
  this.once('error', reject)
695
719
  this.once('end', () => {
696
- if (!this._one) this.push(this.constructor.close)
720
+ if (!objectMode && !this._one) this.push(this.constructor.close)
697
721
  this.push(null)
698
722
  if (this.stream.isPaused()) this.stream.resume()
699
723
  resolve(null)
@@ -736,10 +760,15 @@ class QueryStream extends Query {
736
760
  } else {
737
761
  this.handleDataRow = msg => {
738
762
  const val = msg.fields[0]
739
- if (!this._one && val !== null) this.push(this.constructor.open)
763
+ const objectMode = this.stream.readableObjectMode
764
+ if (!objectMode && !this._one && val !== null) this.push(this.constructor.open)
740
765
  this.emit('row', val)
741
- this.push(val)
766
+ this.push(objectMode ? JSON.parse(val) : val)
767
+
742
768
  delete this.handleDataRow
769
+ if (objectMode) {
770
+ this.handleDataRow = this.handleDataRowObjectMode
771
+ }
743
772
  }
744
773
  }
745
774
  return super.handleRowDescription(msg)
@@ -751,6 +780,11 @@ class QueryStream extends Query {
751
780
  this.push(msg.fields[0])
752
781
  }
753
782
 
783
+ // Called when a new row is received
784
+ handleDataRowObjectMode(msg) {
785
+ this.push(JSON.parse(msg.fields[0]))
786
+ }
787
+
754
788
  // Called when a new binary row is received
755
789
  handleBinaryRow(msg) {
756
790
  const val = msg.fields[0] === null ? null : this._result._parsers[0](msg.fields[0])
@@ -866,15 +900,4 @@ class ParameterStream extends Writable {
866
900
  }
867
901
  }
868
902
 
869
- function _not_unique(err, code, data) {
870
- if (err.code === '23505')
871
- return Object.assign(err, {
872
- originalMessage: err.message, // FIXME: required because of next line
873
- message: code, // FIXME: misusing message as code
874
- code: 400, // FIXME: misusing code as (http) status
875
- })
876
- if (data) err.values = SANITIZE_VALUES ? ['***'] : data
877
- return err
878
- }
879
-
880
903
  module.exports = PostgresService
@@ -189,7 +189,7 @@ const HANAFunctions = {
189
189
  * @returns {string} - SQL statement
190
190
  */
191
191
  years_between(x, y) {
192
- return `TRUNC(${this.months_between(x, y)} / 12,0)`
192
+ return `TRUNC(${this.expr({ func: 'months_between', args: [x, y] })} / 12,0)`
193
193
  },
194
194
  }
195
195
 
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@cap-js/postgres",
3
- "version": "1.13.0",
3
+ "version": "2.0.0",
4
4
  "description": "CDS database service for Postgres",
5
5
  "homepage": "https://github.com/cap-js/cds-dbs/tree/main/postgres#cds-database-service-for-postgres",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "https://github.com/cap-js/cds-dbs"
8
+ "url": "git+https://github.com/cap-js/cds-dbs.git"
9
9
  },
10
10
  "bugs": {
11
11
  "url": "https://github.com/cap-js/cds-dbs/issues"
@@ -27,12 +27,12 @@
27
27
  "start": "docker compose -f pg-stack.yml up -d"
28
28
  },
29
29
  "dependencies": {
30
- "@cap-js/db-service": "^1.19.0",
30
+ "@cap-js/db-service": "^2",
31
31
  "pg": "^8"
32
32
  },
33
33
  "peerDependencies": {
34
- "@sap/cds": ">=7.6",
35
- "@sap/cds-dk": ">=7.5"
34
+ "@sap/cds": ">=9",
35
+ "@sap/cds-dk": ">=9"
36
36
  },
37
37
  "peerDependenciesMeta": {
38
38
  "@sap/cds-dk": {
@@ -76,5 +76,5 @@
76
76
  }
77
77
  }
78
78
  },
79
- "license": "SEE LICENSE"
79
+ "license": "Apache-2.0"
80
80
  }