Skip to content

Commit

Permalink
Merge branch 'main' into no-false-unique-constraint-errors
Browse files Browse the repository at this point in the history
  • Loading branch information
David-Kunz committed Sep 2, 2024
2 parents b552a8f + 147b88e commit 6b75734
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 4 deletions.
1 change: 1 addition & 0 deletions db-service/lib/cqn4sql.js
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,7 @@ function cqn4sql(originalQuery, model) {

if (expand.expand) {
const nested = _subqueryForGroupBy(expand, fullRef, expand.as || expand.ref.map(idOnly).join('_'))
setElementOnColumns(nested, expand.element)
elements[expand.as || expand.ref.map(idOnly).join('_')] = nested
return nested
}
Expand Down
15 changes: 11 additions & 4 deletions hana/lib/HANAService.js
Original file line number Diff line number Diff line change
Expand Up @@ -505,9 +505,18 @@ class HANAService extends SQLService {
if (col.ref?.length > 1) {
const colName = this.column_name(col)
if (col.ref[0] !== parent.as) {
// Inject foreign columns into parent select
// Inject foreign columns into parent selects (recursively)
const as = `$$${col.ref.join('.')}$$`
parent.SELECT.columns.push({ __proto__: col, ref: col.ref, as })
let curPar = parent
while (curPar) {
if (curPar.SELECT.from?.args?.some(a => a.as === col.ref[0])) {
curPar.SELECT.columns.push({ __proto__: col, ref: col.ref, as })
break
} else {
curPar.SELECT.columns.push({ __proto__: col, ref: [curPar.SELECT.parent.as, as], as })
curPar = curPar.SELECT.parent
}
}
col.as = colName
col.ref = [parent.as, as]
} else if (!parent.SELECT.columns.some(c => this.column_name(c) === colName)) {
Expand Down Expand Up @@ -557,8 +566,6 @@ class HANAService extends SQLService {
)
.filter(a => a)

if (sql.length === 0) sql = '*'

if (SELECT.expand === 'root') {
this._blobs = blobs
const blobColumns = Object.keys(blobs)
Expand Down
91 changes: 91 additions & 0 deletions hana/lib/drivers/dynatrace.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// COPIED AS IS (excluding unused code) FROM @sap/cds

const dynatrace = {}
try {
dynatrace.sdk = require('@dynatrace/oneagent-sdk')
dynatrace.api = dynatrace.sdk.createInstance()
} catch {
// If module was not required, do not do anything
}

const isDynatraceEnabled = () => {
return dynatrace.sdk !== undefined && !process.env.CDS_SKIP_DYNATRACE
}

const _dynatraceResultCallback = function (tracer, cb) {
return function (err, ...args) {
const results = args.shift()
if (err) {
tracer.error(err)
} else {
tracer.setResultData({
rowsReturned: (results && results.length) || results
})
}
tracer.end(cb, err, results, ...args)
}
}

const _execUsingDynatrace = (client, execFn, dbInfo) => {
// args = [sql, options, callback] --> options is optional
return function (...args) {
const cb = args[args.length - 1]

const tracer = dynatrace.api.traceSQLDatabaseRequest(dbInfo, {
statement: args[0]
})

tracer.startWithContext(execFn, client, ...args.slice(0, args.length - 1), _dynatraceResultCallback(tracer, cb))
}
}

const _preparedStmtUsingDynatrace = function (client, prepareFn, dbInfo) {
// args = [sql, options, callback] --> options is optional
return function (...args) {
const cb = args[args.length - 1]

const tracer = dynatrace.api.traceSQLDatabaseRequest(dbInfo, {
statement: args[0]
})

tracer.startWithContext(prepareFn, client, ...args.slice(0, args.length - 1), (err, stmt) => {
if (err) {
tracer.error(err)
tracer.end(cb, err)
} else {
// same here. hana-client does not like decorating
const originalExecFn = stmt.exec
stmt.exec = function (...args) {
const stmtCb = args[args.length - 1]
originalExecFn.call(stmt, ...args.slice(0, args.length - 1), _dynatraceResultCallback(tracer, stmtCb))
}
cb(null, stmt)
}
})
}
}

const dynatraceClient = (client, credentials, tenant) => {
const dbInfo = {
name: `SAPHANA${tenant ? `-${tenant}` : ''}`,
vendor: dynatrace.sdk.DatabaseVendor.HANADB,
host: credentials.host,
port: Number(credentials.port)
}

// hana-client does not like decorating.
// because of that, we need to override the fn and pass the original fn for execution
const originalExecFn = client.exec
client.exec = _execUsingDynatrace(client, originalExecFn, dbInfo)
const originalPrepareFn = client.prepare
if (client.name === '@sap/hana-client') {
// client.prepare = ... doesn't work for hana-client
Object.defineProperty(client, 'prepare', { value: _preparedStmtUsingDynatrace(client, originalPrepareFn, dbInfo) })
} else {
client.prepare = _preparedStmtUsingDynatrace(client, originalPrepareFn, dbInfo)
}

return client
}

module.exports = { dynatraceClient, isDynatraceEnabled }
2 changes: 2 additions & 0 deletions hana/lib/drivers/hana-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const { Readable, Stream } = require('stream')
const cds = require('@sap/cds')
const hdb = require('@sap/hana-client')
const { driver, prom, handleLevel } = require('./base')
const { isDynatraceEnabled: dt_sdk_is_present, dynatraceClient: wrap_client } = require('./dynatrace')
const LOG = cds.log('@sap/hana-client')
if (process.env.NODE_ENV === 'production' && !process.env.HDB_NODEJS_THREADPOOL_SIZE && !process.env.UV_THREADPOOL_SIZE) LOG.warn("When using @sap/hana-client, it's strongly recommended to adjust its thread pool size with environment variable `HDB_NODEJS_THREADPOOL_SIZE`, otherwise it might lead to performance issues.\nLearn more: https://help.sap.com/docs/SAP_HANA_CLIENT/f1b440ded6144a54ada97ff95dac7adf/31a8c93a574b4f8fb6a8366d2c758f21.html")

Expand Down Expand Up @@ -43,6 +44,7 @@ class HANAClientDriver extends driver {

super(creds)
this._native = hdb.createConnection(creds)
if (dt_sdk_is_present()) this._native = wrap_client(this._native, creds, creds.tenant)
this._native.setAutoCommit(false)
}

Expand Down
2 changes: 2 additions & 0 deletions hana/lib/drivers/hdb.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const hdb = require('hdb')
const iconv = require('iconv-lite')

const { driver, prom, handleLevel } = require('./base')
const { isDynatraceEnabled: dt_sdk_is_present, dynatraceClient: wrap_client } = require('./dynatrace')

const credentialMappings = [
{ old: 'certificate', new: 'ca' },
Expand Down Expand Up @@ -33,6 +34,7 @@ class HDBDriver extends driver {

super(creds)
this._native = hdb.createClient(creds)
if (dt_sdk_is_present()) this._native = wrap_client(this._native, creds, creds.tenant)
this._native.setAutoCommit(false)
this._native.on('close', () => this.destroy?.())

Expand Down
8 changes: 8 additions & 0 deletions test/scenarios/bookshop/read.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ describe('Bookshop - Read', () => {
).to.be.true
})

test('same as above, with more depth', async () => {
const res = await GET(
'/admin/Books?$apply=filter(title%20ne%20%27bar%27)/groupby((genre/parent/name),aggregate(price with sum as totalAmount))',
admin,
)
expect(res.data.value[0].genre.parent.name).to.be.eq('Fiction')
})

test('Path expression', async () => {
const q = CQL`SELECT title, author.name as author FROM sap.capire.bookshop.Books where author.name LIKE '%a%'`
const res = await cds.run(q)
Expand Down

0 comments on commit 6b75734

Please sign in to comment.