Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 73 additions & 8 deletions postgres/cds-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,80 @@ cds.build?.register?.('postgres', class PostgresBuildPlugin extends cds.build.Pl

promises.push(this.write(cds.compile.to.json(model)).to(path.join('db', 'csn.json')))

let data
if (fs.existsSync(path.join(this.task.src, 'data'))) {
data = 'data'
} else if (fs.existsSync(path.join(this.task.src, 'csv'))) {
data = 'csv'
}
if (data) {
promises.push(this.copy(data).to(path.join('db', 'data')))
// csvFileDetection (default on, for parity with the HANA build task) collects initial
// data from all model sources - app-local db/data AND every reuse module's db/data - so
// the deployer artifact matches `cds deploy` from source. When disabled, only the
// app-local data/csv folder is copied (legacy behaviour).
const csvFileDetection = this.task.options?.csvFileDetection ?? true
if (csvFileDetection && typeof cds.deploy?.resources === 'function') {
for (const [dest, sources] of await collectInitialData(model)) {
const to = path.join('db', 'data', dest)
if (sources.length === 1) {
promises.push(this.copy(sources[0]).to(to))
} else {
promises.push(this.write(mergeCsvFiles(sources)).to(to))
}
}
} else {
let data
if (fs.existsSync(path.join(this.task.src, 'data'))) {
data = 'data'
} else if (fs.existsSync(path.join(this.task.src, 'csv'))) {
data = 'csv'
}
if (data) {
promises.push(this.copy(data).to(path.join('db', 'data')))
}
}
return Promise.all(promises)
}
})

// Discover initial-data resources across app + reuse modules, grouped by their
// destination file name. Files that resolve to the same destination (e.g. a code list
// extended by a reuse module and the consumer) are merged; init.js/ts is skipped as it
// cannot be reproduced from the artifact's db/data folder.
async function collectInitialData (model) {
const resources = await cds.deploy.resources(model)
const byDest = new Map()
for (const [file, entity] of Object.entries(resources)) {
if (entity === '*') continue // init.js/ts
const dest = path.basename(file)
const group = byDest.get(dest)
if (group) group.push(file)
else byDest.set(dest, [file])
}
return byDest
}

// Merge several CSV files for the same entity into the union of their rows under a unified
// header. Files are discovered reuse-module-first, so base rows precede consumer rows.
function mergeCsvFiles (files) {
const columns = []
const seen = new Set()
const records = []
for (const file of files) {
const [header, ...rows] = cds.parse.csv(fs.readFileSync(file, 'utf8'))
if (!header) continue
for (const column of header) if (!seen.has(column)) { seen.add(column); columns.push(column) }
for (const row of rows) {
const record = {}
header.forEach((column, i) => { record[column] = row[i] })
records.push(record)
}
}
const lines = [columns.map(csvEscape).join(',')]
const emitted = new Set()
for (const record of records) {
const line = columns.map(column => csvEscape(record[column] ?? '')).join(',')
if (emitted.has(line)) continue
emitted.add(line)
lines.push(line)
}
return lines.join('\n') + '\n'
}

function csvEscape (value) {
value = String(value)
return /[",\n\r]/.test(value) ? '"' + value.replace(/"/g, '""') + '"' : value
}
12 changes: 12 additions & 0 deletions postgres/test/cds-build.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ const genDir = path.join(workDir, 'gen')
const pgDest = path.join(genDir, 'pg')
const dbDest = path.join(pgDest, 'db')

const reuseWorkDir = path.join(__dirname, 'reuse-sample')
const reuseGenDir = path.join(reuseWorkDir, 'gen')

// delete the generated folder after each test
afterEach(() => {
if (fs.existsSync(genDir)) fs.rmSync(genDir, { recursive: true })
if (fs.existsSync(reuseGenDir)) fs.rmSync(reuseGenDir, { recursive: true })
})

describe('cds build plugin', () => {
Expand Down Expand Up @@ -61,4 +65,12 @@ describe('cds build plugin', () => {
const pgAdapterVersion = require(path.join(__dirname,'..','package.json')).version;
expect(packageJson.dependencies?.['@cap-js/postgres']).to.equal(pgAdapterVersion)
})

test('should merge initial data of reuse modules into gen/pg/db/data', () => {
execSync('npx cds build --for postgres', { cwd: reuseWorkDir })
const csv = fs.readFileSync(path.join(reuseGenDir, 'pg', 'db', 'data', 'my.reuse-CodeList.csv'), 'utf8')
const codes = csv.trim().split(/\r?\n/).slice(1).map(line => line.split(',')[0])
// union of the reuse module's rows (A, B, C) and the consumer's own row (D)
expect(codes.sort()).to.eql(['A', 'B', 'C', 'D'])
})
})
2 changes: 2 additions & 0 deletions postgres/test/reuse-sample/db/data/my.reuse-CodeList.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
code,name
D,Delta
3 changes: 3 additions & 0 deletions postgres/test/reuse-sample/db/index.cds
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// The reuse module is referenced via a relative path so the fixture needs no
// node_modules; resource discovery treats its db/data folder like any dependency's.
using from '../reuse/db/schema';
15 changes: 15 additions & 0 deletions postgres/test/reuse-sample/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "reuse-sample",
"version": "1.0.0",
"description": "A CAP project consuming a reuse module, to test the build plugin",
"dependencies": {
"@cap-js/postgres": "../../."
},
"cds": {
"requires": {
"db": {
"kind": "postgres"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
code,name
A,Alpha
B,Beta
C,Gamma
6 changes: 6 additions & 0 deletions postgres/test/reuse-sample/reuse/db/schema.cds
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace my.reuse;

entity CodeList {
key code : String;
name : String;
}
5 changes: 5 additions & 0 deletions postgres/test/reuse-sample/reuse/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "@cap-js-test/reuse-code-list",
"version": "1.0.0",
"description": "A reuse module shipping initial data, to test the build plugin"
}