Skip to content

Commit c5652ef

Browse files
committedDec 9, 2024·
Add support for odbc hana driver
1 parent bbe7be0 commit c5652ef

File tree

4 files changed

+170
-3
lines changed

4 files changed

+170
-3
lines changed
 

‎hana/lib/HANAService.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ class HANAService extends SQLService {
333333
}
334334

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

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

12371237
const stmt = await this.dbc.prepare(createContainerDatabase)
12381238
const res = this.ensureDBC() && await stmt.run([creds.user, creds.password, creds.containerGroup, !clean])
1239-
res && DEBUG?.(res.changes.map(r => r.MESSAGE).join('\n'))
1239+
res && DEBUG?.(res.changes?.map?.(r => r.MESSAGE).join('\n'))
12401240
} finally {
12411241
if (this.dbc) {
12421242
// Release table lock
@@ -1282,7 +1282,7 @@ SELECT ${mixing} FROM JSON_TABLE(SRC.JSON, '$' COLUMNS(${extraction})) AS NEW LE
12821282
try {
12831283
const stmt = await this.dbc.prepare(createContainerTenant.replaceAll('{{{GROUP}}}', creds.containerGroup))
12841284
const res = this.ensureDBC() && await stmt.run([creds.user, creds.password, creds.schema, !clean])
1285-
res && DEBUG?.(res.changes.map?.(r => r.MESSAGE).join('\n'))
1285+
res && DEBUG?.(res.changes?.map?.(r => r.MESSAGE).join('\n'))
12861286
break
12871287
} catch (e) {
12881288
err = e

‎hana/lib/drivers/bin/extract.sh

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## TODO: use following find command to identify the source of the ABAP odbc linux driver and download it out of the HANA binaries
2+
## docker exec hce-hana-1 find /hana/shared/ -name "libodbcHDB.so"
3+
docker exec hce-hana-1 cat /hana/shared/H00/exe/linuxx86_64/HDB_4.00.000.00.1728375763_870b57d282c6ae9aedc90ae2f41f20321f3e060a/libsapcrypto.so > libsapcrypto.so
4+
docker exec hce-hana-1 cat /hana/shared/H00/exe/linuxx86_64/HDB_4.00.000.00.1728375763_870b57d282c6ae9aedc90ae2f41f20321f3e060a/libodbcHDB.so > libodbcHDB.so

‎hana/lib/drivers/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const cds = require('@sap/cds')
33
Object.defineProperties(module.exports, {
44
hdb: { get: () => require('./hdb') },
55
'hana-client': { get: () => require('./hana-client') },
6+
'odbc': { get: () => require('./odbc') },
67
default: {
78
get() {
89
try {

‎hana/lib/drivers/odbc.js

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
const path = require('path')
2+
const { Readable, Stream } = require('stream')
3+
const { text } = require('stream/consumers')
4+
5+
const odbc = require('odbc')
6+
7+
const { driver, prom, handleLevel } = require('./base')
8+
9+
const credentialMappings = [
10+
{ old: 'certificate', new: 'ca' },
11+
{ old: 'encrypt', new: 'useTLS' },
12+
{ old: 'sslValidateCertificate', new: 'rejectUnauthorized' },
13+
{ old: 'validate_certificate', new: 'rejectUnauthorized' },
14+
]
15+
16+
class HDBDriver extends driver {
17+
/**
18+
* Instantiates the HDBDriver class
19+
* @param {import('./base').Credentials} creds The credentials for the HDBDriver instance
20+
*/
21+
constructor(creds) {
22+
creds = {
23+
fetchSize: 1 << 16, // V8 default memory page size
24+
...creds,
25+
}
26+
27+
// Retain hana credential mappings to hdb / node credential mapping
28+
for (const m of credentialMappings) {
29+
if (m.old in creds && !(m.new in creds)) creds[m.new] = creds[m.old]
30+
}
31+
32+
creds.connectionString = [
33+
// src: https://community.sap.com/t5/technology-blogs-by-sap/using-the-odbc-driver-for-abap-on-linux/ba-p/13513705
34+
`driver=${path.resolve(__dirname, 'bin/libodbcHDB.so')}`,
35+
'client=100', // TODO: see what this means
36+
'trustall=true', // supersecure
37+
`cryptolibrary=${path.resolve(__dirname, 'bin/libsapcrypto.so')}`,
38+
`encrypt=${creds.encrypt}`,
39+
`sslValidateCertificate=${creds.sslValidateCertificate}`,
40+
`disableCloudRedirect=true`,
41+
42+
`servernode=${creds.host}:${creds.port}`,
43+
`database=${creds.schema}`,
44+
`uid=${creds.user}`,
45+
`pwd=${creds.password}`,
46+
47+
'uidtype=alias',
48+
'typemap=semantic', // semantic or native or ...
49+
].join(';')
50+
51+
super(creds)
52+
this.connected = odbc.connect(creds)
53+
this.connected.then(dbc => this._native = dbc)
54+
this.connected.catch(() => this.destroy?.())
55+
}
56+
57+
set(variables) {
58+
// TODO:
59+
}
60+
61+
async validate() {
62+
// TODO:
63+
return true
64+
}
65+
66+
/**
67+
* Connects the driver using the provided credentials
68+
* @returns {Promise<any>}
69+
*/
70+
async connect() {
71+
return this.connected.then(async () => {
72+
const [version] = await Promise.all([
73+
this._native.query('SELECT VERSION FROM "SYS"."M_DATABASE"'),
74+
this._creds.schema && this._native.query(`SET SCHEMA ${this._creds.schema}`),
75+
])
76+
const split = version[0].VERSION.split('.')
77+
this.server = {
78+
major: split[0],
79+
minor: split[2],
80+
patch: split[3],
81+
}
82+
})
83+
}
84+
85+
async disconnect() {
86+
return this._native.close()
87+
}
88+
89+
// TODO: find out how to do this with odbc driver
90+
async begin() {
91+
return this._native.beginTransaction()
92+
}
93+
async commit() {
94+
return this._native.commit()
95+
}
96+
async rollback() {
97+
return this._native.rollback()
98+
}
99+
100+
async exec(sql) {
101+
await this.connected
102+
return this._native.query(sql)
103+
}
104+
105+
async prepare(sql, hasBlobs) {
106+
try {
107+
const stmt = await this._native.createStatement()
108+
await stmt.prepare(sql)
109+
const run = async (args) => {
110+
try {
111+
await stmt.bind(await this._extractStreams(args).values)
112+
return await stmt.execute()
113+
} catch (e) {
114+
throw e.odbcErrors[0]
115+
}
116+
}
117+
return {
118+
run,
119+
get: (..._) => run(..._).then(r => r[0]),
120+
all: run,
121+
proc: async (data, outParameters) => {
122+
// this._native.callProcedure(null,null,)
123+
const rows = await run(data)
124+
return rows // TODO: see what this driver returns for procedures
125+
},
126+
127+
stream: (..._) => stmt.bind(..._).then(async () => Readable.from(this._iterator(await stmt.execute({ cursor: true })))),
128+
}
129+
} catch (e) {
130+
e.message += ' in:\n' + (e.query = sql)
131+
throw e
132+
}
133+
}
134+
135+
_extractStreams(values) {
136+
// Removes all streams from the values and replaces them with a placeholder
137+
if (!Array.isArray(values)) return { values: [], streams: [] }
138+
const streams = []
139+
values = values.map((v, i) => {
140+
if (v instanceof Stream) {
141+
return text(v)
142+
}
143+
return v
144+
})
145+
return {
146+
values: Promise.all(values),
147+
streams,
148+
}
149+
}
150+
151+
// TODO: implement proper raw stream
152+
async *_iterator(cursor) {
153+
while (!cursor.noData) {
154+
for (const row of await cursor.fetch()) {
155+
yield row
156+
}
157+
}
158+
await cursor.close()
159+
}
160+
}
161+
162+
module.exports.driver = HDBDriver

0 commit comments

Comments
 (0)