Skip to content

Commit

Permalink
Add support for odbc hana driver
Browse files Browse the repository at this point in the history
  • Loading branch information
BobdenOs committed Dec 9, 2024
1 parent bbe7be0 commit c5652ef
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 3 deletions.
6 changes: 3 additions & 3 deletions hana/lib/HANAService.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ class HANAService extends SQLService {
}

let { limit, one, orderBy, expand, columns = ['*'], localized, count, parent } = q.SELECT


// When one of these is defined wrap the query in a sub query
if (expand || (parent && (limit || one || orderBy))) {
Expand Down Expand Up @@ -1236,7 +1236,7 @@ SELECT ${mixing} FROM JSON_TABLE(SRC.JSON, '$' COLUMNS(${extraction})) AS NEW LE

const stmt = await this.dbc.prepare(createContainerDatabase)
const res = this.ensureDBC() && await stmt.run([creds.user, creds.password, creds.containerGroup, !clean])
res && DEBUG?.(res.changes.map(r => r.MESSAGE).join('\n'))
res && DEBUG?.(res.changes?.map?.(r => r.MESSAGE).join('\n'))
} finally {
if (this.dbc) {
// Release table lock
Expand Down Expand Up @@ -1282,7 +1282,7 @@ SELECT ${mixing} FROM JSON_TABLE(SRC.JSON, '$' COLUMNS(${extraction})) AS NEW LE
try {
const stmt = await this.dbc.prepare(createContainerTenant.replaceAll('{{{GROUP}}}', creds.containerGroup))
const res = this.ensureDBC() && await stmt.run([creds.user, creds.password, creds.schema, !clean])
res && DEBUG?.(res.changes.map?.(r => r.MESSAGE).join('\n'))
res && DEBUG?.(res.changes?.map?.(r => r.MESSAGE).join('\n'))
break
} catch (e) {
err = e
Expand Down
4 changes: 4 additions & 0 deletions hana/lib/drivers/bin/extract.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## TODO: use following find command to identify the source of the ABAP odbc linux driver and download it out of the HANA binaries
## docker exec hce-hana-1 find /hana/shared/ -name "libodbcHDB.so"
docker exec hce-hana-1 cat /hana/shared/H00/exe/linuxx86_64/HDB_4.00.000.00.1728375763_870b57d282c6ae9aedc90ae2f41f20321f3e060a/libsapcrypto.so > libsapcrypto.so
docker exec hce-hana-1 cat /hana/shared/H00/exe/linuxx86_64/HDB_4.00.000.00.1728375763_870b57d282c6ae9aedc90ae2f41f20321f3e060a/libodbcHDB.so > libodbcHDB.so
1 change: 1 addition & 0 deletions hana/lib/drivers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const cds = require('@sap/cds')
Object.defineProperties(module.exports, {
hdb: { get: () => require('./hdb') },
'hana-client': { get: () => require('./hana-client') },
'odbc': { get: () => require('./odbc') },
default: {
get() {
try {
Expand Down
162 changes: 162 additions & 0 deletions hana/lib/drivers/odbc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
const path = require('path')
const { Readable, Stream } = require('stream')
const { text } = require('stream/consumers')

const odbc = require('odbc')

const { driver, prom, handleLevel } = require('./base')

Check warning on line 7 in hana/lib/drivers/odbc.js

View workflow job for this annotation

GitHub Actions / Tests (22)

'prom' is assigned a value but never used

Check warning on line 7 in hana/lib/drivers/odbc.js

View workflow job for this annotation

GitHub Actions / Tests (22)

'handleLevel' is assigned a value but never used

const credentialMappings = [
{ old: 'certificate', new: 'ca' },
{ old: 'encrypt', new: 'useTLS' },
{ old: 'sslValidateCertificate', new: 'rejectUnauthorized' },
{ old: 'validate_certificate', new: 'rejectUnauthorized' },
]

class HDBDriver extends driver {
/**
* Instantiates the HDBDriver class
* @param {import('./base').Credentials} creds The credentials for the HDBDriver instance
*/
constructor(creds) {
creds = {
fetchSize: 1 << 16, // V8 default memory page size
...creds,
}

// Retain hana credential mappings to hdb / node credential mapping
for (const m of credentialMappings) {
if (m.old in creds && !(m.new in creds)) creds[m.new] = creds[m.old]
}

creds.connectionString = [
// src: https://community.sap.com/t5/technology-blogs-by-sap/using-the-odbc-driver-for-abap-on-linux/ba-p/13513705
`driver=${path.resolve(__dirname, 'bin/libodbcHDB.so')}`,
'client=100', // TODO: see what this means
'trustall=true', // supersecure
`cryptolibrary=${path.resolve(__dirname, 'bin/libsapcrypto.so')}`,
`encrypt=${creds.encrypt}`,
`sslValidateCertificate=${creds.sslValidateCertificate}`,
`disableCloudRedirect=true`,

`servernode=${creds.host}:${creds.port}`,
`database=${creds.schema}`,
`uid=${creds.user}`,
`pwd=${creds.password}`,

'uidtype=alias',
'typemap=semantic', // semantic or native or ...
].join(';')

super(creds)
this.connected = odbc.connect(creds)
this.connected.then(dbc => this._native = dbc)
this.connected.catch(() => this.destroy?.())
}

set(variables) {

Check warning on line 57 in hana/lib/drivers/odbc.js

View workflow job for this annotation

GitHub Actions / Tests (22)

'variables' is defined but never used
// TODO:
}

async validate() {
// TODO:
return true
}

/**
* Connects the driver using the provided credentials
* @returns {Promise<any>}
*/
async connect() {
return this.connected.then(async () => {
const [version] = await Promise.all([
this._native.query('SELECT VERSION FROM "SYS"."M_DATABASE"'),
this._creds.schema && this._native.query(`SET SCHEMA ${this._creds.schema}`),
])
const split = version[0].VERSION.split('.')
this.server = {
major: split[0],
minor: split[2],
patch: split[3],
}
})
}

async disconnect() {
return this._native.close()
}

// TODO: find out how to do this with odbc driver
async begin() {
return this._native.beginTransaction()
}
async commit() {
return this._native.commit()
}
async rollback() {
return this._native.rollback()
}

async exec(sql) {
await this.connected
return this._native.query(sql)
}

async prepare(sql, hasBlobs) {

Check warning on line 105 in hana/lib/drivers/odbc.js

View workflow job for this annotation

GitHub Actions / Tests (22)

'hasBlobs' is defined but never used
try {
const stmt = await this._native.createStatement()
await stmt.prepare(sql)
const run = async (args) => {
try {
await stmt.bind(await this._extractStreams(args).values)
return await stmt.execute()
} catch (e) {
throw e.odbcErrors[0]
}
}
return {
run,
get: (..._) => run(..._).then(r => r[0]),
all: run,
proc: async (data, outParameters) => {

Check warning on line 121 in hana/lib/drivers/odbc.js

View workflow job for this annotation

GitHub Actions / Tests (22)

'outParameters' is defined but never used
// this._native.callProcedure(null,null,)
const rows = await run(data)
return rows // TODO: see what this driver returns for procedures
},

stream: (..._) => stmt.bind(..._).then(async () => Readable.from(this._iterator(await stmt.execute({ cursor: true })))),
}
} catch (e) {
e.message += ' in:\n' + (e.query = sql)
throw e
}
}

_extractStreams(values) {
// Removes all streams from the values and replaces them with a placeholder
if (!Array.isArray(values)) return { values: [], streams: [] }
const streams = []
values = values.map((v, i) => {

Check warning on line 139 in hana/lib/drivers/odbc.js

View workflow job for this annotation

GitHub Actions / Tests (22)

'i' is defined but never used
if (v instanceof Stream) {
return text(v)
}
return v
})
return {
values: Promise.all(values),
streams,
}
}

// TODO: implement proper raw stream
async *_iterator(cursor) {
while (!cursor.noData) {
for (const row of await cursor.fetch()) {
yield row
}
}
await cursor.close()
}
}

module.exports.driver = HDBDriver

0 comments on commit c5652ef

Please sign in to comment.