@cap-js/postgres 1.4.1 → 1.5.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,20 @@
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
+ ## [1.5.0](https://github.com/cap-js/cds-dbs/compare/postgres-v1.4.1...postgres-v1.5.0) (2024-02-02)
8
+
9
+
10
+ ### Added
11
+
12
+ * SELECT returns LargeBinaries as streams unless feature flag "stream_compat" is set ([#251](https://github.com/cap-js/cds-dbs/issues/251)) ([8165a4a](https://github.com/cap-js/cds-dbs/commit/8165a4a3f6bb21c970668c8873f9d9c662b43780))
13
+ * Support Readable Streams inside INSERT.entries ([#343](https://github.com/cap-js/cds-dbs/issues/343)) ([f6faf89](https://github.com/cap-js/cds-dbs/commit/f6faf8955b7888479c66f1727ade65b382611c2f))
14
+
15
+
16
+ ### Fixed
17
+
18
+ * switch Postgres from json to jsonb ([#402](https://github.com/cap-js/cds-dbs/issues/402)) ([c98a964](https://github.com/cap-js/cds-dbs/commit/c98a964a34232267aece337dc6f6bedf03e9891a))
19
+ * UPSERT for @cap-js/hana for entities with multiple keys ([#418](https://github.com/cap-js/cds-dbs/issues/418)) ([9bbac6e](https://github.com/cap-js/cds-dbs/commit/9bbac6ebbbddfa2f620833ce195eedeb0a79f43e))
20
+
7
21
  ## [1.4.1](https://github.com/cap-js/cds-dbs/compare/postgres-v1.4.0...postgres-v1.4.1) (2023-11-24)
8
22
 
9
23
 
@@ -13,6 +13,7 @@ class PostgresService extends SQLService {
13
13
  cds.options.dialect = 'postgres'
14
14
  }
15
15
  this.kind = 'postgres'
16
+ this._queryCache = {}
16
17
  return super.init(...arguments)
17
18
  }
18
19
 
@@ -78,7 +79,7 @@ class PostgresService extends SQLService {
78
79
  }
79
80
 
80
81
  return Promise.all([
81
- (await this.prepare(`SELECT set_config(key::text,$1->>key,false) FROM json_each($1);`)).run([
82
+ (await this.prepare(`SELECT set_config(key::text,$1->>key,false) FROM jsonb_each($1);`)).run([
82
83
  JSON.stringify(env),
83
84
  ]),
84
85
  ...(this.options?.credentials?.schema
@@ -135,12 +136,13 @@ GROUP BY k
135
136
  }
136
137
 
137
138
  prepare(sql) {
138
- const query = {
139
+ // Track queries name for postgres referencing prepare statements
140
+ // sha1 as it needs to be less then 63 character
141
+ const sha = crypto.createHash('sha1').update(sql).digest('hex')
142
+ const query = this._queryCache[sha] = this._queryCache[sha] || {
139
143
  _streams: 0,
140
144
  text: sql,
141
- // Track queries name for postgres referencing prepare statements
142
- // sha1 as it needs to be less then 63 characters
143
- name: crypto.createHash('sha1').update(sql).digest('hex'),
145
+ name: sha,
144
146
  }
145
147
  return {
146
148
  run: async values => {
@@ -194,7 +196,7 @@ GROUP BY k
194
196
  values.forEach((value, i) => {
195
197
  if (value instanceof Readable) {
196
198
  const streamID = query._streams++
197
- const isBinary = value.type === 'binary'
199
+ const isBinary = value.type !== 'json'
198
200
  const paramStream = new ParameterStream(query.name, streamID)
199
201
  if (isBinary) value.setEncoding('base64')
200
202
  value.pipe(paramStream)
@@ -281,6 +283,31 @@ GROUP BY k
281
283
  return super.onPlainSQL(req, next)
282
284
  }
283
285
 
286
+ async onSELECT({ query, data }) {
287
+ // workaround for chunking odata streaming
288
+ if (query.SELECT?.columns?.find(col => col.as === '$mediaContentType')) {
289
+ const columns = query.SELECT.columns
290
+ const index = columns.findIndex(col => query.elements[col.ref?.[col.ref.length - 1]].type === 'cds.LargeBinary')
291
+ const binary = columns[index]
292
+ // SELECT without binary column
293
+ columns.splice(index, 1)
294
+ const { sql, values } = this.cqn2sql(query, data)
295
+ let ps = this.prepare(sql)
296
+ let res = await ps.all(values)
297
+ if (res.length === 0) return
298
+ res = res.map(r => (typeof r._json_ === 'string' ? JSON.parse(r._json_) : r._json_ || r))[0]
299
+ // SELECT only binary column
300
+ query.SELECT.columns = [binary]
301
+ const { sql: streamSql, values: valuesStream } = this.cqn2sql(query, data)
302
+ ps = this.prepare(streamSql)
303
+ const stream = await ps.stream(valuesStream, true)
304
+ // merge results
305
+ res[binary.as || binary.ref[binary.ref.length - 1]] = stream
306
+ return res
307
+ }
308
+ return super.onSELECT({ query, data })
309
+ }
310
+
284
311
  static CQN2SQL = class CQN2Postgres extends SQLService.CQN2SQL {
285
312
  _orderBy(orderBy, localized, locale) {
286
313
  return orderBy.map(
@@ -337,9 +364,9 @@ GROUP BY k
337
364
  return col
338
365
  })
339
366
  // REVISIT: Remove SELECT ${cols} by adjusting SELECT_columns
340
- let obj = `row_to_json(${queryAlias}.*)`
367
+ let obj = `to_jsonb(${queryAlias}.*)`
341
368
  return `SELECT ${
342
- SELECT.one || SELECT.expand === 'root' ? obj : `coalesce(json_agg(${obj}),'[]'::json)`
369
+ SELECT.one || SELECT.expand === 'root' ? obj : `coalesce(jsonb_agg (${obj}),'[]'::jsonb)`
343
370
  } as _json_ FROM (SELECT ${cols} FROM (${sql}) as ${queryAlias}) as ${queryAlias}`
344
371
  }
345
372
 
@@ -355,8 +382,8 @@ GROUP BY k
355
382
  // Adjusts json path expressions to be postgres specific
356
383
  .replace(/->>'\$(?:(?:\."(.*?)")|(?:\[(\d*)\]))'/g, (a, b, c) => (b ? `->>'${b}'` : `->>${c}`))
357
384
  // Adjusts json function to be postgres specific
358
- .replace('json_each(?)', 'json_array_elements($1::JSON)')
359
- .replace(/json_type\((\w+),'\$\."(\w+)"'\)/g, (_a, b, c) => `json_typeof(${b}->'${c}')`))
385
+ .replace('json_each(?)', 'jsonb_array_elements($1::jsonb)')
386
+ .replace(/json_type\((\w+),'\$\."(\w+)"'\)/g, (_a, b, c) => `jsonb_typeof(${b}->'${c}')`))
360
387
  }
361
388
 
362
389
  param({ ref }) {
@@ -428,8 +455,8 @@ GROUP BY k
428
455
  Timestamp: e => `to_char(${e}, 'YYYY-MM-DD"T"HH24:MI:SS.FF3"Z"')`,
429
456
  UTCDateTime: e => `to_char(${e}, 'YYYY-MM-DD"T"HH24:MI:SS"Z"')`,
430
457
  UTCTimestamp: e => `to_char(${e}, 'YYYY-MM-DD"T"HH24:MI:SS.FF3"Z"')`,
431
- struct: e => `json(${e})`,
432
- array: e => `json(${e})`,
458
+ struct: e => `jsonb(${e})`,
459
+ array: e => `jsonb(${e})`,
433
460
  }
434
461
  }
435
462
 
@@ -457,6 +484,7 @@ GROUP BY k
457
484
  DROP USER IF EXISTS "${creds.user}";
458
485
  CREATE GROUP "${creds.usergroup}";
459
486
  CREATE USER "${creds.user}" WITH CREATEROLE IN GROUP "${creds.usergroup}" PASSWORD '${creds.user}';
487
+ GRANT "${creds.usergroup}" TO "${creds.user}" WITH ADMIN OPTION;
460
488
  `)
461
489
  await this.exec(`CREATE DATABASE "${creds.database}" OWNER="${creds.user}" TEMPLATE=template0`)
462
490
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js/postgres",
3
- "version": "1.4.1",
3
+ "version": "1.5.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": {
@@ -31,11 +31,11 @@
31
31
  "start": "docker-compose -f pg-stack.yml up -d"
32
32
  },
33
33
  "dependencies": {
34
- "@cap-js/db-service": "^1.3.1",
34
+ "@cap-js/db-service": "^1.6.0",
35
35
  "pg": "^8"
36
36
  },
37
37
  "peerDependencies": {
38
- "@sap/cds": ">=7",
38
+ "@sap/cds": ">=7.6",
39
39
  "@sap/cds-dk": ">=7"
40
40
  },
41
41
  "peerDependenciesMeta": {