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
30 changes: 20 additions & 10 deletions db-service/lib/cqn2sql.js
Original file line number Diff line number Diff line change
Expand Up @@ -886,13 +886,16 @@ class CQN2SQLRenderer {
return // REVISIT: mtx sends an insert statement without entries and no reference entity
}
const transitions = this.srv.resolve.transitions(q)
const columns = elements
? ObjectKeys(elements).filter(c => this.physical_column(elements, c)
const transitionsTargetElements = transitions.target.elements

const filteredElements = !elements ? {} :
ObjectKeys(elements).filter(c => this.physical_column(elements, c)
&& (c = transitions.mapping.get(c)?.ref?.[0] || c)
&& c in transitions.target.elements
&& this.physical_column(transitions.target.elements, c)
)
: ObjectKeys(INSERT.entries[0])
&& c in transitionsTargetElements
&& this.physical_column(transitionsTargetElements, c)
).reduce( (res, key) => (res[key] = elements[key], res), {} )

const columns = elements ? ObjectKeys(filteredElements) : ObjectKeys(INSERT.entries[0])

/** @type {string[]} */
this.columns = columns
Expand All @@ -919,9 +922,16 @@ class CQN2SQLRenderer {
this.entries = [[...this.values, stream]]
}

const extractions = this._managed = this.managed(columns.map(c => ({ name: c })), elements)
return (this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${this.columns.map(c => this.quote(transitions.mapping.get(c)?.ref?.[0] || c))
}) SELECT ${extractions.slice(0, columns.length).map(c => c.insert)} FROM json_each(?)`)
const mappedTransitionsTargets = new Set(columns.map(c => transitions.mapping.get(c)?.ref?.[0]).filter(Boolean))
const targetElementsFiltered = Object.fromEntries(
ObjectKeys(transitionsTargetElements)
.filter(key => !mappedTransitionsTargets.has(key))
.map(key => [key, transitionsTargetElements[key]]),
)

const extractions = this._managed = this.managed(columns.map(c => ({ name: c })), { ...targetElementsFiltered, ...filteredElements })
return (this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${extractions.map(c => this.quote(transitions.mapping.get(c.name)?.ref?.[0] || c.name))
}) SELECT ${extractions.map(c => c.insert)} FROM json_each(?)`)
}

async *INSERT_entries_stream(entries, binaryEncoding = 'base64') {
Expand Down Expand Up @@ -1511,7 +1521,7 @@ class CQN2SQLRenderer {
if (columns.find(c => c.name === e)) return false
return true
})
.map(name => ({ name, sql: 'NULL' }))
.map(name => ({ name }))

const keys = ObjectKeys(elements).filter(e => elements[e].key && !elements[e].isAssociation)
const keyZero = keys[0] && this.quote(keys[0])
Expand Down
26 changes: 17 additions & 9 deletions hana/lib/HANAService.js
Original file line number Diff line number Diff line change
Expand Up @@ -792,18 +792,26 @@ class HANAService extends SQLService {

const entity = q._target ? this.table_name(q) : INSERT.into.ref[0]
const transitions = this.srv.resolve.transitions(q)
const transitionsTargetElements = transitions.target.elements

const columns = elements
? ObjectKeys(elements).filter(c => this.physical_column(elements, c)
const filteredElements = !elements ? {} :
ObjectKeys(elements).filter(c => this.physical_column(elements, c)
&& (c = transitions.mapping.get(c)?.ref?.[0] || c)
&& c in transitions.target.elements
&& this.physical_column(transitions.target.elements, c)
&& c in transitionsTargetElements
&& this.physical_column(transitionsTargetElements, c)
&& !elements[c]?.[SYSTEM_VERSIONED]
)
: ObjectKeys(INSERT.entries[0])
this.columns = columns
).reduce( (res, key) => (res[key] = elements[key], res), {} )

const columns = this.columns = elements ? ObjectKeys(filteredElements) : ObjectKeys(INSERT.entries[0])

const mappedTransitionsTargets = new Set(columns.map(c => transitions.mapping.get(c)?.ref?.[0]).filter(Boolean))
const targetElementsFiltered = Object.fromEntries(
ObjectKeys(transitionsTargetElements)
.filter(key => !mappedTransitionsTargets.has(key))
.map(key => [key, transitionsTargetElements[key]]),
)

const extractions = this._managed = this.managed(columns.map(c => ({ name: c })), elements).slice(0, columns.length)
const extractions = this._managed = this.managed(columns.map(c => ({ name: c })), { ...targetElementsFiltered, ...filteredElements })

// REVISIT: @cds.extension required
const extraction = extractions.map(c => c.extract)
Expand Down Expand Up @@ -843,7 +851,7 @@ class HANAService extends SQLService {
// With the buffer table approach the data is processed in chunks of a configurable size
// Which allows even smaller HANA systems to process large datasets
// But the chunk size determines the maximum size of a single row
return (this.sql = `INSERT INTO ${this.quote(entity)} (${this.columns.map(c => this.quote(transitions.mapping.get(c)?.ref?.[0] || c))
return (this.sql = `INSERT INTO ${this.quote(entity)} (${extractions.map(c => this.quote(transitions.mapping.get(c.name)?.ref?.[0] || c.name))
}) WITH SRC AS (SELECT ? AS JSON FROM DUMMY UNION ALL SELECT TO_NCLOB(NULL) AS JSON FROM DUMMY)
SELECT ${converter} FROM JSON_TABLE(SRC.JSON, '$' COLUMNS(${extraction}) ERROR ON ERROR) AS NEW`)
}
Expand Down
9 changes: 9 additions & 0 deletions test/scenarios/bookshop/insert.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,13 @@ describe('Bookshop - Insert', () => {
const res = await SELECT.from(Books, {ID: 344})
expect(res.genre_ID).to.be.eq(10)
})

test('insert with default fields excluded from projection', async () => {
const { RenameKeys, Books } = cds.entities('AdminService')
await cds.run(INSERT({ foo: 345 }).into(RenameKeys))
const res = await SELECT.from(Books, { ID: 345 })
expect(res).to.containSubset({ genre_ID: 10, createdBy: 'anonymous', modifiedBy: 'anonymous' })
expect(res.createdAt).to.not.be.null
expect(res.modifiedAt).to.not.be.null
})
})