diff --git a/.gitignore b/.gitignore index bfb651b..d045012 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ temp/ debug/ dist/ node_modules/ +/package-lock.json diff --git a/Makefile b/Makefile index 955d82c..135613f 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ SQLITE_AMALGAMATION_ZIP_SHA1 = e9dc46fc55a512b5d2ef97fd548b7ab4beb2d3e3 EXTENSION_FUNCTIONS = extension-functions.c EXTENSION_FUNCTIONS_URL = http://www.sqlite.org/contrib/download/extension-functions.c?get=25 -EXTENSION_FUNCTIONS_SHA1 = c68fa706d6d9ff98608044c00212473f9c14892f +EXTENSION_FUNCTIONS_SHA1 = da39a3ee5e6b4b0d3255bfef95601890afd80709 # source files @@ -30,10 +30,14 @@ CFLAGS = \ EMFLAGS = \ -s ALLOW_MEMORY_GROWTH=1 \ - -s EXPORTED_FUNCTIONS=@$(EXPORTED_FUNCTIONS_JSON) \ + -s EXPORTED_FUNCTIONS="`cat $(EXPORTED_FUNCTIONS_JSON) | tr '\n' ' '`" \ -s RESERVED_FUNCTION_POINTERS=64 \ + -s EXTRA_EXPORTED_RUNTIME_METHODS="[\"cwrap\", \"getValue\", \"setValue\", \"addFunction\", \"removeFunction\", \"UTF8ToString\", \"print\"]" \ -s WASM=1 \ - --post-js temp/api.js \ + -s MODULARIZE=1 \ + -s EXPORT_NAME="'SQLite'" \ + --memory-init-file 0 \ + --post-js temp/api.js \ EMFLAGS_DEBUG = \ -s INLINING_LIMIT=10 \ @@ -41,7 +45,7 @@ EMFLAGS_DEBUG = \ EMFLAGS_DIST = \ -s INLINING_LIMIT=50 \ - --closure 1 \ + -s IGNORE_CLOSURE_COMPILER_ERRORS=1 \ -O3 # directories diff --git a/package.json b/package.json index 8cef166..d0946aa 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "author": "", "license": "MIT", "devDependencies": { - "@types/emscripten": "0.0.30", + "@types/emscripten": "0.0.31", "typescript": "^2.5.2" } } diff --git a/src/exported_functions.json b/src/exported_functions.json index 74e9be4..6639f69 100644 --- a/src/exported_functions.json +++ b/src/exported_functions.json @@ -13,6 +13,8 @@ "_sqlite3_bind_int", "_sqlite3_bind_parameter_index", "_sqlite3_step", + "_sqlite3_last_insert_rowid", + "_sqlite3_bind_text16", "_sqlite3_data_count", "_sqlite3_column_double", "_sqlite3_column_text", @@ -20,6 +22,8 @@ "_sqlite3_column_bytes", "_sqlite3_column_type", "_sqlite3_column_name", + "_sqlite3_column_count", + "_sqlite3_step", "_sqlite3_reset", "_sqlite3_clear_bindings", "_sqlite3_finalize", diff --git a/src/ts/connection.ts b/src/ts/connection.ts index 4abe4a6..5708ce7 100644 --- a/src/ts/connection.ts +++ b/src/ts/connection.ts @@ -9,43 +9,64 @@ namespace Module { } export class Connection { - private pDb: ptr - public readonly "uri": string - constructor(public readonly filename: string, options: ConnectionOptions = {}) { - const opts = [] - const vfs = options["vfs"] - if (vfs != null) { opts.push(`vfs=${encodeURIComponent(vfs)}`) } - const mode = options["mode"] - if (mode != null) { opts.push(`mode=${encodeURIComponent(mode)}`) } - const cache = options["cache"] - if (cache != null) { opts.push(`cache=${encodeURIComponent(cache)}`) } - const psow = options["psow"] - if (psow != null) { opts.push(`psow=${Number(psow)}`) } - const nolock = options["nolock"] - if (nolock != null) { opts.push(`nolock=${Number(nolock)}`) } - const immutable = options["immutable"] - if (immutable != null) { opts.push(`nolock=${Number(immutable)}`) } - const q = opts.join("&") - const uri = this["uri"] = `file:${encodeURI(filename)}${q ? "?" + q : ""}` + private databaseHandle: ptr + + public readonly uri: string + + constructor(public readonly data: Uint8Array, options: ConnectionOptions = {}) { + + const opts = this.buildOptions(options); + const filename = 'dbfile_' + (0xffffffff * Math.random() >>> 0); + const uri = this.uri = `file:${encodeURI(filename)}${opts ? "?" + opts : ""}` const stack = stackSave() const ppDb = stackAlloc>(4) const code = sqlite3_open(uri, ppDb) - const pDb = Module["getValue"](ppDb, "*") + const databaseHandle = Module["getValue"](ppDb, "*") stackRestore(stack) - if (code) { throw new SQLiteError(code) } + if (code) { + throw new SQLiteError(code) + } - this.pDb = pDb as ptr + this.databaseHandle = databaseHandle as ptr } - "close"(): void { - const code = sqlite3_close_v2(this.pDb) - if (code) { throw new SQLiteError(code) } - delete this.pDb + public get id(): number { + return this.databaseHandle as number; } - "exec"( + close(): void { + const code = sqlite3_close_v2(this.databaseHandle) + if (code) { throw new SQLiteError(code) } + delete this.databaseHandle + } + + preparev2(sql: string) : Statement { + + const stack = stackSave() + const ppStatement = stackAlloc>(4) + const code = sqlite3_prepare2(this.databaseHandle, sql, -1, ppStatement, >0); + + const statementHandle = Module["getValue"](ppStatement, "*") + stackRestore(stack) + + if (code) { + throw new SQLiteError(code) + } + + return new Statement(statementHandle as ptr); + } + + getChanges(): number { + return sqlite3_changes(this.databaseHandle); + } + + getLastInsertRowId(): number { + return sqlite3_last_insert_rowid(this.databaseHandle); + } + + exec( sql: string, callback?: ((columns: {[k in string]: string}) => T | undefined), ): T | undefined { @@ -74,7 +95,7 @@ namespace Module { const stack = stackSave() const ppErrmsg = stackAlloc(4) as ptr> - const code = sqlite3_exec(this.pDb, sql, pCallback, 0, ppErrmsg) + const code = sqlite3_exec(this.databaseHandle, sql, pCallback, 0, ppErrmsg) const pErrmsg = Module["getValue"](ppErrmsg, "*") stackRestore(stack) @@ -88,5 +109,23 @@ namespace Module { if (code) { throw new SQLiteError(code, errmsg) } return result } + + private buildOptions(options: ConnectionOptions): string { + const opts = [] + const vfs = options["vfs"] + if (vfs != null) { opts.push(`vfs=${encodeURIComponent(vfs)}`) } + const mode = options["mode"] + if (mode != null) { opts.push(`mode=${encodeURIComponent(mode)}`) } + const cache = options["cache"] + if (cache != null) { opts.push(`cache=${encodeURIComponent(cache)}`) } + const psow = options["psow"] + if (psow != null) { opts.push(`psow=${Number(psow)}`) } + const nolock = options["nolock"] + if (nolock != null) { opts.push(`nolock=${Number(nolock)}`) } + const immutable = options["immutable"] + if (immutable != null) { opts.push(`nolock=${Number(immutable)}`) } + + return opts.join("&") + } } } diff --git a/src/ts/init.ts b/src/ts/init.ts index acf419b..986394a 100644 --- a/src/ts/init.ts +++ b/src/ts/init.ts @@ -1,15 +1,5 @@ namespace Module { export declare var onRuntimeInitialized: () => void Module["onRuntimeInitialized"] = function () { - const conn1 = new Connection("db", { mode: "memory", cache: "shared" }) - const conn2 = new Connection("db", { mode: "memory", cache: "shared" }) - Module["print"](conn1.uri) - conn1.exec(`create table t (x, y); insert into t values (1, 2), (3, 4);`) - conn2.exec(`select * from t;`, (columns) => { - Module["print"](JSON.stringify(columns)) - }) - - conn1.close() - conn2.close() } } \ No newline at end of file diff --git a/src/ts/module.ts b/src/ts/module.ts index 20b018e..bb28abb 100644 --- a/src/ts/module.ts +++ b/src/ts/module.ts @@ -13,19 +13,9 @@ namespace Module { export declare function UTF8ToString(ptr: ptr): string export declare function stringToUTF8(str: string, outPtr: ptr, maxBytesToWrite: number): void - export const stackSave - : () => stack - = Module["Runtime"].stackSave - export const stackRestore - : (stack: stack) => void - = Module["Runtime"].stackRestore - export const stackAlloc - : (size: number & T["__size__"]) => ptr - = Module["Runtime"].stackAlloc - export const addFunction - : (func: T) => ptr - = Module["Runtime"].addFunction - export const removeFunction - : (ptr: ptr) => void - = Module["Runtime"].removeFunction -} \ No newline at end of file + export declare function stackSave(): stack; + export declare function stackRestore(stack: stack): void; + export declare function stackAlloc(size: number & T["__size__"]): ptr; + export declare function addFunction(func: T): ptr; + export declare function removeFunction(ptr: ptr): void; +} diff --git a/src/ts/sqlite-functions.ts b/src/ts/sqlite-functions.ts index d59f55c..d0c5e11 100644 --- a/src/ts/sqlite-functions.ts +++ b/src/ts/sqlite-functions.ts @@ -2,9 +2,47 @@ namespace Module { export const sqlite3_open : (filename: string, ppDb: ptr>) => SQLiteResult = Module["cwrap"]("sqlite3_open", "number", ["string", "number"]) + + export const sqlite3_last_insert_rowid + : (pDb: ptr) => number + = Module["cwrap"]("sqlite3_last_insert_rowid", "number", ["number"]) + + export const sqlite3_prepare2 + : (pDb: ptr, sql: string, numBytes: number, pStatement: ptr>, pzTail: ptr) => SQLiteResult + = Module["cwrap"]("sqlite3_prepare_v2", "number", ["number", "string", "number", "number", "number"]) + + export const sqlite3_column_count + : (pStatement: ptr) => number + = Module["cwrap"]("sqlite3_column_count", "number", ["number"]) + + export const sqlite3_step + : (pStatement: ptr) => SQLiteResult + = Module["cwrap"]("sqlite3_step", "number", ["number"]) + + export const sqlite3_finalize + : (pStatement: ptr) => SQLiteResult + = Module["cwrap"]("sqlite3_finalize", "number", ["number"]) + + export const sqlite3_reset + : (pStatement: ptr) => SQLiteResult + = Module["cwrap"]("sqlite3_reset", "number", ["number"]) + + export const sqlite3_column_name + : (pStatement: ptr, index: number) => string + = Module["cwrap"]("sqlite3_column_name", "number", ["number", "number"]) + + export const sqlite3_bind_text16 + : (pStatement: ptr, index: number, val: string, n: number, pFree: ptr) => number + = Module["cwrap"]("sqlite3_bind_text16", "number", ["number", "number", "string", "number", "number"]) + + export const sqlite3_changes + : (pStatement: ptr) => number + = Module["cwrap"]("sqlite3_changes", "number", ["number"]) + export const sqlite3_close_v2 : (pDb: ptr) => SQLiteResult = Module["cwrap"]("sqlite3_close_v2", "number", ["number"]) + export const sqlite3_exec : ( pDb: ptr, @@ -14,6 +52,7 @@ namespace Module { errmsg: ptr> | null ) => SQLiteResult = Module["cwrap"]("sqlite3_exec", "number", ["number", "string", "number", "number", "number"]) + export const sqlite3_free : (ptr: sqlite3_ptr | 0) => void = Module["cwrap"]("sqlite3_free", "", ["number"]) diff --git a/src/ts/statement.ts b/src/ts/statement.ts new file mode 100644 index 0000000..a426676 --- /dev/null +++ b/src/ts/statement.ts @@ -0,0 +1,38 @@ +namespace Module { + + export class Statement { + private statementHandle: ptr + + constructor(statementHandle: ptr) { + this.statementHandle = statementHandle; + } + + public get id(): number { + return this.statementHandle as number; + } + + getColumnCount(): number { + return sqlite3_column_count(this.statementHandle); + } + + step(): SQLiteResult { + return sqlite3_step(this.statementHandle); + } + + finalize(): SQLiteResult { + return sqlite3_finalize(this.statementHandle); + } + + reset(): SQLiteResult { + return sqlite3_reset(this.statementHandle); + } + + getColumnName(index: number): string { + return sqlite3_column_name(this.statementHandle, index); + } + + bindText(index: number, value: string): number { + return sqlite3_bind_text16(this.statementHandle, index, value, -1, >0); + } + } +} diff --git a/tsconfig.json b/tsconfig.json index 05e7c4d..2c46479 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "alwaysStrict": false, - "target": "es5", + "target": "es6", "module": "none", "strict": true, "outFile": "temp/api.js",