Skip to content
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,7 @@ jobs:
FORCE_COLOR: true
TAG: ${{ steps.hxe.outputs.TAG }}
IMAGE_ID: ${{ steps.hxe.outputs.IMAGE_ID }}
- run: npm install sql.js && rm -rf node_modules/better-sqlite3/ && npm test -w sqlite
env:
cds_features_pool: true
FORCE_COLOR: true
16 changes: 13 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 14 additions & 7 deletions sqlite/lib/SQLiteService.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
const { SQLService } = require('@cap-js/db-service')
const cds = require('@sap/cds')
const sqlite = require('better-sqlite3')
const cds = require('@sap/cds/lib')
let sqlite
try {
sqlite = require('better-sqlite3')
} catch (err) {

Check warning on line 6 in sqlite/lib/SQLiteService.js

View workflow job for this annotation

GitHub Actions / Tests (22)

'err' is defined but never used
// When failing to load better-sqlite3 it fallsback to sql.js (wasm version of sqlite)
sqlite = require('./sql.js.js')
}

const $session = Symbol('dbc.session')
const sessionVariableMap = require('./session.json') // Adjust the path as necessary for your project
const convStrm = require('stream/consumers')
Expand Down Expand Up @@ -28,9 +35,11 @@
get factory() {
return {
options: { max: 1, ...this.options.pool },
create: tenant => {
create: async tenant => {
const database = this.url4(tenant)
const dbc = new sqlite(database, this.options.client)
await dbc.ready

const deterministic = { deterministic: true }
dbc.function('session_context', key => dbc[$session][key])
dbc.function('regexp', deterministic, (re, x) => (RegExp(re).test(x) ? 1 : 0))
Expand Down Expand Up @@ -134,10 +143,8 @@
}

async _allStream(stmt, binding_params, one, objectMode) {
stmt = stmt.constructor.name === 'Statement' ? stmt : stmt.__proto__
stmt.raw(true)
const get = stmt.get(binding_params)
if (!get) return []
stmt = stmt.iterate ? stmt : stmt.__proto__
stmt.raw?.(true)
const rs = stmt.iterate(binding_params)
const stream = Readable.from(objectMode ? this._iteratorObjectMode(rs) : this._iteratorRaw(rs, one), { objectMode })
const close = () => rs.return() // finish result set when closed early
Expand Down
90 changes: 90 additions & 0 deletions sqlite/lib/sql.js.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
const initSqlJs = require('sql.js');

const init = initSqlJs({})

class WasmSqlite {
constructor(/*database*/) {
// TODO: load / store database file contents
this.ready = init
.then(SQL => { this.db = new SQL.Database() })

this.memory = true
this.gc = new FinalizationRegistry(stmt => { stmt.free() })
}

prepare(sql) {
const stmt = this.db.prepare(sql)
const ret = {
run(params) {
try {
stmt.bind(params)
stmt.step()
return { changes: stmt.db.getRowsModified(stmt) }
} catch (err) {
if (err.message.indexOf('NOT NULL constraint failed:') === 0) {
err.code = 'SQLITE_CONSTRAINT_NOTNULL'
}
throw err
}
},
get(params) {
const columns = stmt.getColumnNames()
stmt.bind(params)
stmt.step()
const row = stmt.get()
const ret = {}
for (let i = 0; i < columns.length; i++) {
ret[columns[i]] = row[i]
}
return ret
},
all(params) {
const columns = stmt.getColumnNames()
const ret = []
stmt.bind(params)
while (stmt.step()) {
const row = stmt.get()
const obj = {}
for (let i = 0; i < columns.length; i++) {
obj[columns[i]] = row[i]
}
ret.push(obj)
}
return ret
},
*iterate(params) {
stmt.bind(params)
while (stmt.step()) {
yield stmt.get()
}
}
}
this.gc.register(ret, stmt)
return ret
}

exec(sql) {
try {
const { columns, values } = this.db.exec(sql)
return !Array.isArray(values) ? values : values.map(val => {
const ret = {}
for (let i = 0; i < columns.length; i++) {
ret[columns[i]] = val[i]
}
return ret
})
} catch (err) {
// REVISIT: address transaction errors
if (sql === 'BEGIN' || sql === 'ROLLBACK') { return }
throw err
}
}

function(name, config, func) {
this.db.create_function(name, func || config)
}

close() { this.db.close() }
}

module.exports = WasmSqlite
8 changes: 7 additions & 1 deletion sqlite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@
"better-sqlite3": "^12.0.0"
},
"peerDependencies": {
"@sap/cds": ">=9"
"@sap/cds": ">=9",
"sql.js": "^1.13.0"
},
"peerDependenciesMeta": {
"sql.js": {
"optional": true
}
},
"cds": {
"requires": {
Expand Down
Loading