@cap-js/postgres 1.4.1 → 1.5.1
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 +21 -0
- package/lib/PostgresService.js +79 -25
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,27 @@
|
|
|
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.1](https://github.com/cap-js/cds-dbs/compare/postgres-v1.5.0...postgres-v1.5.1) (2024-02-16)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
* **`sqlite`:** Retain Error object for unique constraint violation ([#446](https://github.com/cap-js/cds-dbs/issues/446)) ([d27ee79](https://github.com/cap-js/cds-dbs/commit/d27ee79b4c4eea8522bf5dd2a288638f54029567))
|
|
13
|
+
|
|
14
|
+
## [1.5.0](https://github.com/cap-js/cds-dbs/compare/postgres-v1.4.1...postgres-v1.5.0) (2024-02-02)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
* 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))
|
|
20
|
+
* 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))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
* 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))
|
|
26
|
+
* 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))
|
|
27
|
+
|
|
7
28
|
## [1.4.1](https://github.com/cap-js/cds-dbs/compare/postgres-v1.4.0...postgres-v1.4.1) (2023-11-24)
|
|
8
29
|
|
|
9
30
|
|
package/lib/PostgresService.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,14 +283,55 @@ 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
|
+
|
|
311
|
+
async onINSERT(req) {
|
|
312
|
+
try {
|
|
313
|
+
return await super.onINSERT(req)
|
|
314
|
+
} catch (err) {
|
|
315
|
+
throw _not_unique(err, 'ENTITY_ALREADY_EXISTS')
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async onUPDATE(req) {
|
|
320
|
+
try {
|
|
321
|
+
return await super.onUPDATE(req)
|
|
322
|
+
} catch (err) {
|
|
323
|
+
throw _not_unique(err, 'UNIQUE_CONSTRAINT_VIOLATION')
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
284
327
|
static CQN2SQL = class CQN2Postgres extends SQLService.CQN2SQL {
|
|
285
328
|
_orderBy(orderBy, localized, locale) {
|
|
286
329
|
return orderBy.map(
|
|
287
330
|
localized
|
|
288
331
|
? c =>
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
332
|
+
this.expr(c) +
|
|
333
|
+
(c.element?.[this.class._localized] ? ` COLLATE "${locale}"` : '') +
|
|
334
|
+
(c.sort === 'desc' || c.sort === -1 ? ' DESC' : ' ASC')
|
|
292
335
|
: c => this.expr(c) + (c.sort === 'desc' || c.sort === -1 ? ' DESC' : ' ASC'),
|
|
293
336
|
)
|
|
294
337
|
}
|
|
@@ -337,10 +380,9 @@ GROUP BY k
|
|
|
337
380
|
return col
|
|
338
381
|
})
|
|
339
382
|
// REVISIT: Remove SELECT ${cols} by adjusting SELECT_columns
|
|
340
|
-
let obj = `
|
|
341
|
-
return `SELECT ${
|
|
342
|
-
|
|
343
|
-
} as _json_ FROM (SELECT ${cols} FROM (${sql}) as ${queryAlias}) as ${queryAlias}`
|
|
383
|
+
let obj = `to_jsonb(${queryAlias}.*)`
|
|
384
|
+
return `SELECT ${SELECT.one || SELECT.expand === 'root' ? obj : `coalesce(jsonb_agg (${obj}),'[]'::jsonb)`
|
|
385
|
+
} as _json_ FROM (SELECT ${cols} FROM (${sql}) as ${queryAlias}) as ${queryAlias}`
|
|
344
386
|
}
|
|
345
387
|
|
|
346
388
|
doubleQuote(name) {
|
|
@@ -355,8 +397,8 @@ GROUP BY k
|
|
|
355
397
|
// Adjusts json path expressions to be postgres specific
|
|
356
398
|
.replace(/->>'\$(?:(?:\."(.*?)")|(?:\[(\d*)\]))'/g, (a, b, c) => (b ? `->>'${b}'` : `->>${c}`))
|
|
357
399
|
// Adjusts json function to be postgres specific
|
|
358
|
-
.replace('json_each(?)', '
|
|
359
|
-
.replace(/json_type\((\w+),'\$\."(\w+)"'\)/g, (_a, b, c) => `
|
|
400
|
+
.replace('json_each(?)', 'jsonb_array_elements($1::jsonb)')
|
|
401
|
+
.replace(/json_type\((\w+),'\$\."(\w+)"'\)/g, (_a, b, c) => `jsonb_typeof(${b}->'${c}')`))
|
|
360
402
|
}
|
|
361
403
|
|
|
362
404
|
param({ ref }) {
|
|
@@ -428,8 +470,9 @@ GROUP BY k
|
|
|
428
470
|
Timestamp: e => `to_char(${e}, 'YYYY-MM-DD"T"HH24:MI:SS.FF3"Z"')`,
|
|
429
471
|
UTCDateTime: e => `to_char(${e}, 'YYYY-MM-DD"T"HH24:MI:SS"Z"')`,
|
|
430
472
|
UTCTimestamp: e => `to_char(${e}, 'YYYY-MM-DD"T"HH24:MI:SS.FF3"Z"')`,
|
|
431
|
-
|
|
432
|
-
|
|
473
|
+
Association: e => `jsonb(${e})`,
|
|
474
|
+
struct: e => `jsonb(${e})`,
|
|
475
|
+
array: e => `jsonb(${e})`,
|
|
433
476
|
}
|
|
434
477
|
}
|
|
435
478
|
|
|
@@ -457,6 +500,7 @@ GROUP BY k
|
|
|
457
500
|
DROP USER IF EXISTS "${creds.user}";
|
|
458
501
|
CREATE GROUP "${creds.usergroup}";
|
|
459
502
|
CREATE USER "${creds.user}" WITH CREATEROLE IN GROUP "${creds.usergroup}" PASSWORD '${creds.user}';
|
|
503
|
+
GRANT "${creds.usergroup}" TO "${creds.user}" WITH ADMIN OPTION;
|
|
460
504
|
`)
|
|
461
505
|
await this.exec(`CREATE DATABASE "${creds.database}" OWNER="${creds.user}" TEMPLATE=template0`)
|
|
462
506
|
} catch (e) {
|
|
@@ -523,14 +567,14 @@ class QueryStream extends Query {
|
|
|
523
567
|
this.stream = new Readable({
|
|
524
568
|
read: this.rows
|
|
525
569
|
? () => {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
570
|
+
this.stream.pause()
|
|
571
|
+
// Request more rows
|
|
572
|
+
this.connection.execute({
|
|
573
|
+
portal: this.portal,
|
|
574
|
+
rows: this.rows,
|
|
575
|
+
})
|
|
576
|
+
this.connection.flush()
|
|
577
|
+
}
|
|
534
578
|
: () => {},
|
|
535
579
|
})
|
|
536
580
|
this.push = this.stream.push.bind(this.stream)
|
|
@@ -711,4 +755,14 @@ class ParameterStream extends Writable {
|
|
|
711
755
|
}
|
|
712
756
|
}
|
|
713
757
|
|
|
758
|
+
function _not_unique(err, code) {
|
|
759
|
+
if (err.code === '23505')
|
|
760
|
+
return Object.assign(err, {
|
|
761
|
+
originalMessage: err.message, // FIXME: required because of next line
|
|
762
|
+
message: code, // FIXME: misusing message as code
|
|
763
|
+
code: 400, // FIXME: misusing code as (http) status
|
|
764
|
+
})
|
|
765
|
+
return err
|
|
766
|
+
}
|
|
767
|
+
|
|
714
768
|
module.exports = PostgresService
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/postgres",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
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.
|
|
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": {
|