Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for odbc driver #940

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
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
Loading