Skip to content
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
19084fb
feat: at least make a naive solution work
PDT42 Aug 21, 2025
e9db0da
Merge branch 'main' into feat/order-selected-columns
PDT42 Aug 21, 2025
a01ddf9
test: add & update tests for column sorting
PDT42 Aug 25, 2025
908c032
feat: consider more column syntax options in sort
PDT42 Aug 25, 2025
2ebc4c7
Merge branch 'main' into feat/order-selected-columns
PDT42 Aug 25, 2025
81d4f6f
test: add tests for expression columns
PDT42 Aug 26, 2025
4bc3cbd
test: add test for non string value
PDT42 Aug 26, 2025
3950cca
feat: stringify values before comparison
PDT42 Aug 26, 2025
4eed3f6
fix: handle plain string columns
PDT42 Aug 27, 2025
0d27acf
Merge branch 'main' into feat/order-selected-columns
PDT42 Aug 27, 2025
9ca0ab2
feat: integrate column sorting in hana insert_select
PDT42 Aug 27, 2025
952284d
Merge branch 'main' into feat/order-selected-columns
PDT42 Aug 27, 2025
e516b56
feat: move column sorting related test out of symlinked tests
PDT42 Aug 27, 2025
8dc5b36
fix: test app root dir
PDT42 Aug 27, 2025
3a6a0f8
chore: rename test
PDT42 Aug 28, 2025
7a3eefb
feat: just use column_name
PDT42 Aug 28, 2025
edc88eb
Merge branch 'main' into feat/order-selected-columns
PDT42 Aug 28, 2025
72c5f07
fix: prevent column_name from being called for *
PDT42 Aug 28, 2025
90223ec
fix: array identity
PDT42 Sep 1, 2025
0ee17a5
feat: get rid of tuples and type check
PDT42 Sep 1, 2025
6e74a15
feat: check for asterisk explicitly
PDT42 Sep 1, 2025
05dd28f
feat: go back to checking type
PDT42 Sep 2, 2025
5c7e3f0
Merge branch 'main' into feat/order-selected-columns
PDT42 Sep 4, 2025
24e2038
fix: consider insert.as
PDT42 Sep 5, 2025
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
33 changes: 33 additions & 0 deletions hana/lib/HANAService.js
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,12 @@ class HANAService extends SQLService {
SELECT_columns(q) {
const { SELECT, src } = q
if (!SELECT.columns) return '*'

// Sort selected columns to avoid creating redundant execution plans (column names can't be equal)
SELECT.columns = SELECT.columns.sort((a, b) => {
return (typeof a == 'string' ? a : this.column_name(a)) > (typeof b == 'string' ? b : this.column_name(b)) ? 1 : -1
})

if (SELECT.expand !== 'root') {
const ret = []
for (const x of q.SELECT.columns) {
Expand Down Expand Up @@ -837,6 +843,33 @@ class HANAService extends SQLService {
return this.INSERT_entries(q)
}

INSERT_select(q) {
const { INSERT } = q
const entity = this.name(q._target.name, q)
const alias = INSERT.into.as
const elements = q.elements || q._target?.elements || {}
const columns = (INSERT.columns || ObjectKeys(elements)).filter(
c => c in elements && !elements[c].virtual && !elements[c].isAssociation,
)

if (columns.length !== INSERT.from.SELECT.columns.length)
throw new Error('The number of specified columns does not match the number of selected columns')
this.columns = []
INSERT.from.SELECT.columns = INSERT.from.SELECT.columns
.map((col, originalIdx) => [typeof col == 'string' ? col : this.column_name(col), originalIdx])
.sort(([colNameA], [colNameB]) => colNameA > colNameB ? 1 : -1)
.map(([, originalIdx], i) => {
this.columns[i] = columns[originalIdx]
return INSERT.from.SELECT.columns[originalIdx]
})

this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${this.columns.map(c =>
this.quote(c),
)}) ${this.SELECT(this.cqn4sql(INSERT.from || INSERT.as))}`
this.entries = [this.values]
return this.sql
}

UPSERT(q) {
const { UPSERT } = q
// REVISIT: should @cds.persistence.name be considered ?
Expand Down
123 changes: 123 additions & 0 deletions hana/test/column-order.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
const cds = require('../../test/cds.js')
const bookshop = cds.utils.path.resolve(__dirname, '../../test/bookshop')

describe('column order', () => {
const { expect } = cds.test(bookshop)

describe('regardless of column order specifed in query', () => {
let hanaService

beforeAll(async () => {
hanaService = await cds.connect.to('db')
})

const expectSqlScriptToBeEqual = (query1, query2) => {
const sql1 = hanaService.cqn2sql(query1)
const sql2 = hanaService.cqn2sql(query2)
expect(sql1.sql).to.equal(sql2.sql)

const sqlScript1 = hanaService.wrapTemporary(sql1.temporary, sql1.withclause, sql1.blobs)
const sqlScript2 = hanaService.wrapTemporary(sql2.temporary, sql2.withclause, sql2.blobs)
expect(sqlScript1).to.equal(sqlScript2)
}

test('should select columns in the same order', async () => {
const query1 = SELECT.from('sap.capire.bookshop.Books').columns(['ID', 'title', 'descr', 'stock', 'price'])
const query2 = SELECT.from('sap.capire.bookshop.Books').columns(['stock', 'title', 'price', 'ID', 'descr'])

expectSqlScriptToBeEqual(query1, query2)
})

test('should select expands in the same order', async () => {
const query1 = SELECT.from('sap.capire.bookshop.Books').columns([
{ ref: ['author'], expand: [{ ref: ['name'] }] },
{ ref: ['genre'], expand: [{ ref: ['ID'] }] },
])
const query2 = SELECT.from('sap.capire.bookshop.Books').columns([
{ ref: ['genre'], expand: [{ ref: ['ID'] }] },
{ ref: ['author'], expand: [{ ref: ['name'] }] },
])

expectSqlScriptToBeEqual(query1, query2)
})

test('should select flat expands in the same order', async () => {
const query1 = SELECT.from('sap.capire.bookshop.Books').columns([
'ID',
{ ref: ['author', 'ID'] },
{ ref: ['genre', 'ID'] },
{ ref: ['author', 'name'] },
])
const query2 = SELECT.from('sap.capire.bookshop.Books').columns([
{ ref: ['genre', 'ID'] },
{ ref: ['author', 'name'] },
{ ref: ['author', 'ID'] },
'ID',
])

expectSqlScriptToBeEqual(query1, query2)
})

test('should select columns and expands in the same order', async () => {
const query1 = SELECT.from('sap.capire.bookshop.Books').columns([
'ID',
{ ref: ['author'], expand: [{ ref: ['ID'] }, { ref: ['name'] }] },
{ ref: ['genre', 'ID'] },
])
const query2 = SELECT.from('sap.capire.bookshop.Books').columns([
{ ref: ['genre', 'ID'] },
{ ref: ['author'], expand: [{ ref: ['ID'] }, { ref: ['name'] }] },
'ID',
])

expectSqlScriptToBeEqual(query1, query2)
})

test('should select columns from expands in the same order', async () => {
const query1 = SELECT.from('sap.capire.bookshop.Books').columns([
'ID',
{ ref: ['author'], expand: [{ ref: ['ID'] }, { ref: ['name'] }] },
])
const query2 = SELECT.from('sap.capire.bookshop.Books').columns([
{ ref: ['author'], expand: [{ ref: ['name'] }, { ref: ['ID'] }] },
'ID',
])

expectSqlScriptToBeEqual(query1, query2)
})

test('should select functions in the same order', async () => {
const query1 = SELECT.from('sap.capire.bookshop.Books').columns(['ID', { xpr: ['1=1'], as: 'always_true' }])
const query2 = SELECT.from('sap.capire.bookshop.Books').columns([{ xpr: ['1=1'], as: 'always_true' }, 'ID'])

expectSqlScriptToBeEqual(query1, query2)
})

test('should select expressions in the same order', async () => {
const query1 = SELECT.from('sap.capire.bookshop.Books').columns([
'ID',
{ func: 'max', args: [{ ref: ['price'] }] },
])
const query2 = SELECT.from('sap.capire.bookshop.Books').columns([
{ func: 'max', args: [{ ref: ['price'] }] },
'ID',
])

expectSqlScriptToBeEqual(query1, query2)
})

test('should select values in the same order', async () => {
const query1 = SELECT.from('sap.capire.bookshop.Books').columns(['ID', { val: 'some-static-value' }])
const query2 = SELECT.from('sap.capire.bookshop.Books').columns([{ val: 'some-static-value' }, 'ID'])

expectSqlScriptToBeEqual(query1, query2)
})

test('should select numeric values in the same order', async () => {
const query1 = SELECT.from('sap.capire.bookshop.Books').columns(['ID', { val: 1 }])
const query2 = SELECT.from('sap.capire.bookshop.Books').columns([{ val: 1 }, 'ID'])

expectSqlScriptToBeEqual(query1, query2)
})
})
})
Loading