diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..468ceea01 --- /dev/null +++ b/.clang-format @@ -0,0 +1 @@ +BasedOnStyle: LLVM \ No newline at end of file diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 000000000..03303c533 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,24 @@ +{ + "permissions": { + "allow": [ + "WebFetch(domain:www.lazycplusplus.com)", + "Bash(grep:*)", + "Bash(ls:*)", + "Bash(find:*)", + "Bash(lzz:*)", + "Bash(cp:*)", + "Bash(npm run:*)", + "Bash(chmod:*)", + "Bash(python3:*)", + "Bash(npm test:*)", + "Bash(timeout:*)", + "Bash(awk:*)", + "Bash(git fetch:*)", + "Bash(git merge-base:*)", + "Bash(git stash:*)", + "Bash(rg:*)", + "Bash(clang-format:*)" + ] + }, + "enableAllProjectMcpServers": false +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index b1351adda..a4652a808 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1 @@ *.lzz linguist-language=C++ -*.cpp -diff -*.hpp -diff -*.c -diff -*.h -diff diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..e954f8cec --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,78 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +**better-sqlite3** is a Node.js native addon providing synchronous SQLite database access. It's architected as a hybrid C++/JavaScript library where C++ handles direct SQLite operations and JavaScript provides the high-level API. + +## Build & Development Commands + +### Essential Commands +- **Build**: `npm run build-release` (production build) +- **Debug Build**: `npm run build-debug` +- **Test**: `npm test` (mocha with 5s timeout) +- **Single Test**: `npx mocha test/[test-file].js` +- **Benchmark**: `npm run benchmark` +- **Download SQLite**: `npm run download` + +## Architecture + +### Code Organization +``` +lib/ # JavaScript API layer +├── index.js # Main export (Database constructor) +├── database.js # Database class with native binding +└── methods/ # Method implementations + +src/ # C++ source +├── better_sqlite3.cpp # Main implementation file +├── better_sqlite3.hpp # Main header file +├── objects/ # Core classes (Database, Statement, etc.) +└── util/ # Utilities and type conversion +``` + +### C++ Architecture +- Standard C++ source files (.cpp/.hpp) +- Modular header structure in `src/objects/` and `src/util/` +- Single compilation unit architecture maintained +- Uses standard C++ #include directives + +## Native Addon Structure + +### Data Flow +``` +JavaScript API → C++ Native Methods → SQLite C API → Results → Type Conversion → JavaScript +``` + +### Key Classes +- `Database`: Main database connection and operations +- `Statement`: Prepared SQL statements +- `StatementIterator`: Row iteration +- `Backup`: Database backup functionality + +### Type Conversion +The C++ layer handles conversion between SQLite types and V8/JavaScript types, including support for 64-bit integers, Buffers, and BigInts. + +## Testing + +Tests are organized by functionality in `test/`: +- `00.setup.js`: Global test setup +- `10-19.*`: Database-level operations +- `20-29.*`: Statement operations +- `30-39.*`: Advanced features (transactions, custom functions) +- `40-50.*`: Special cases (BigInts, worker threads, unsafe mode) + +## Development Workflow + +1. **For C++ changes**: Edit `.cpp/.hpp` files → `npm run build-release` → `npm test` +2. **For JavaScript changes**: Edit files in `lib/` → `npm test` +3. **Performance testing**: Use `npm run benchmark` after changes + +## Important Notes + +- This is a **synchronous** SQLite library (no async/await) +- Single-threaded SQLite access for performance +- Uses Node.js N-API for forward compatibility +- Embeds SQLite source in `deps/` directory +- Supports Node.js 20.x, 22.x, 23.x, 24.x \ No newline at end of file diff --git a/binding.gyp b/binding.gyp index 81a1238c4..4744b2aea 100644 --- a/binding.gyp +++ b/binding.gyp @@ -8,7 +8,22 @@ { 'target_name': 'better_sqlite3', 'dependencies': ['deps/sqlite3.gyp:sqlite3'], - 'sources': ['src/better_sqlite3.cpp'], + 'sources': [ + 'src/better_sqlite3.cpp', + 'src/objects/database.cpp', + 'src/objects/statement.cpp', + 'src/objects/statement-iterator.cpp', + 'src/objects/backup.cpp', + 'src/util/macros.cpp', + 'src/util/constants.cpp', + 'src/util/bind-map.cpp', + 'src/util/binder.cpp', + 'src/util/data-converter.cpp', + 'src/util/custom-function.cpp', + 'src/util/custom-aggregate.cpp', + 'src/util/custom-table.cpp', + 'src/util/data.cpp' + ], 'cflags_cc': ['-std=c++20'], 'xcode_settings': { 'OTHER_CPLUSPLUSFLAGS': ['-std=c++20', '-stdlib=libc++'], diff --git a/docs/contribution.md b/docs/contribution.md index 13cb7079e..fc9e8ed3e 100644 --- a/docs/contribution.md +++ b/docs/contribution.md @@ -52,11 +52,13 @@ If you've never written a native addon for Node.js before, you should start by r #### C++ -The C++ code in `better-sqlite3` is written using a tool called [`lzz`](https://github.com/WiseLibs/lzz), which alleviates the programmer from needing to write header files. If you plan on changing any C++ code, you'll need to edit `*.lzz` files and then re-compile them into `*.cpp` and `*.hpp` by running `npm run lzz` (while the `lzz` executable is in your PATH). You can learn how to download and install `lzz` [here](https://github.com/WiseLibs/lzz). +The C++ code in `better-sqlite3` uses standard C++ source files (`.cpp`) and header files (`.hpp`). The codebase is organized into modular components under `src/objects/` (core classes like Database, Statement) and `src/util/` (utilities and type conversion). When making C++ changes, edit the appropriate `.cpp` and `.hpp` files directly, then build with `npm run build-release`. #### Style guide -There is currently no linter or style guide associated with `better-sqlite3` (this may change in the future). For now, just try to match the style of existing code as much as possible. Code owners will reject your PR or rewrite your changes if they feel that you've used a coding style that doesn't match the existing code. Although the rules aren't layed out formally, you are expected to adhere to them by using your eyeballs. +There is currently no linter or style guide associated with `better-sqlite3` (this may change in the future). For now, just try to match the style of existing code as much as possible. Code owners will reject your PR or rewrite your changes if they feel that you've used a coding style that doesn't match the existing code. Although the rules aren't laid out formally, you are expected to adhere to them by using your eyeballs. + +For C++ code formatting, you can use `npm run fmt` to run `clang-format` on all C++ source files, which will automatically format your code according to the project's formatting standards. #### Testing diff --git a/package.json b/package.json index 2bcb45f69..55e6b629b 100644 --- a/package.json +++ b/package.json @@ -36,12 +36,10 @@ "install": "prebuild-install || node-gyp rebuild --release", "build-release": "node-gyp rebuild --release", "build-debug": "node-gyp rebuild --debug", - "rebuild-release": "npm run lzz && npm run build-release", - "rebuild-debug": "npm run lzz && npm run build-debug", "test": "mocha --exit --slow=75 --timeout=5000", "benchmark": "node benchmark", "download": "bash ./deps/download.sh", - "lzz": "lzz -hx hpp -sx cpp -k BETTER_SQLITE3 -d -hl -sl -e ./src/better_sqlite3.lzz" + "fmt": "find src -name '*.cpp' -o -name '*.hpp' | grep -v withlines | xargs clang-format -i" }, "license": "MIT", "keywords": [ diff --git a/src/better_sqlite3.cpp b/src/better_sqlite3.cpp index ea66d5522..e2a988afa 100644 --- a/src/better_sqlite3.cpp +++ b/src/better_sqlite3.cpp @@ -1,2189 +1,76 @@ -// better_sqlite3.cpp -// - +// Main implementation file #include "better_sqlite3.hpp" -#line 154 "./src/util/macros.lzz" -void SetPrototypeGetter( - v8::Isolate* isolate, - v8::Local data, - v8::Local recv, - const char* name, - v8::AccessorNameGetterCallback func -) { - v8::HandleScope scope(isolate); - - #if defined NODE_MODULE_VERSION && NODE_MODULE_VERSION < 121 - recv->InstanceTemplate()->SetAccessor( - InternalizedFromLatin1(isolate, name), - func, - 0, - data, - v8::AccessControl::DEFAULT, - v8::PropertyAttribute::None - ); - #else - recv->InstanceTemplate()->SetNativeDataProperty( - InternalizedFromLatin1(isolate, name), - func, - 0, - data - ); - #endif -} -#line 183 "./src/util/macros.lzz" -#if defined(V8_ENABLE_SANDBOX) -// When V8 Sandbox is enabled (in newer Electron versions), we need to use Buffer::Copy -// instead of Buffer::New to ensure the ArrayBuffer backing store is allocated inside the sandbox -static inline v8::MaybeLocal BufferSandboxNew(v8::Isolate* isolate, char* data, size_t length, void (*finalizeCallback)(char*, void*), void* finalizeHint) { - v8::MaybeLocal buffer = node::Buffer::Copy(isolate, data, length); - finalizeCallback(data, finalizeHint); - return buffer; -} -#define SAFE_NEW_BUFFER(env, data, length, finalizeCallback, finalizeHint) BufferSandboxNew(env, data, length, finalizeCallback, finalizeHint) -#else -// When V8 Sandbox is not enabled, we can use the more efficient Buffer::New -#define SAFE_NEW_BUFFER(env, data, length, finalizeCallback, finalizeHint) node::Buffer::New(env, data, length, finalizeCallback, finalizeHint) -#endif -#line 39 "./src/util/binder.lzz" - static bool IsPlainObject(v8::Isolate* isolate, v8::Local obj) { - v8::Local proto = obj->GetPrototype(); - - #if defined NODE_MODULE_VERSION && NODE_MODULE_VERSION < 93 - v8::Local ctx = obj->CreationContext(); - #else - v8::Local ctx = obj->GetCreationContext().ToLocalChecked(); - #endif - - ctx->Enter(); - v8::Local baseProto = v8::Object::New(isolate)->GetPrototype(); - ctx->Exit(); - return proto->StrictEquals(baseProto) || proto->StrictEquals(v8::Null(isolate)); - } -#line 67 "./src/better_sqlite3.lzz" -NODE_MODULE_INIT(/* exports, context */) { - v8::Isolate* isolate = context->GetIsolate(); - v8::HandleScope scope(isolate); - - // Initialize addon instance. - Addon* addon = new Addon(isolate); - v8::Local data = v8::External::New(isolate, addon); - node::AddEnvironmentCleanupHook(isolate, Addon::Cleanup, addon); - - // Create and export native-backed classes and functions. - exports->Set(context, InternalizedFromLatin1(isolate, "Database"), Database::Init(isolate, data)).FromJust(); - exports->Set(context, InternalizedFromLatin1(isolate, "Statement"), Statement::Init(isolate, data)).FromJust(); - exports->Set(context, InternalizedFromLatin1(isolate, "StatementIterator"), StatementIterator::Init(isolate, data)).FromJust(); - exports->Set(context, InternalizedFromLatin1(isolate, "Backup"), Backup::Init(isolate, data)).FromJust(); - exports->Set(context, InternalizedFromLatin1(isolate, "setErrorConstructor"), v8::FunctionTemplate::New(isolate, Addon::JS_setErrorConstructor, data)->GetFunction(context).ToLocalChecked()).FromJust(); - - // Store addon instance data. - addon->Statement.Reset(isolate, exports->Get(context, InternalizedFromLatin1(isolate, "Statement")).ToLocalChecked().As()); - addon->StatementIterator.Reset(isolate, exports->Get(context, InternalizedFromLatin1(isolate, "StatementIterator")).ToLocalChecked().As()); - addon->Backup.Reset(isolate, exports->Get(context, InternalizedFromLatin1(isolate, "Backup")).ToLocalChecked().As()); -} -#define LZZ_INLINE inline -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 67 "./src/util/data.lzz" - static char const FLAT = 0; -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 68 "./src/util/data.lzz" - static char const PLUCK = 1; -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 69 "./src/util/data.lzz" - static char const EXPAND = 2; -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 70 "./src/util/data.lzz" - static char const RAW = 3; -} -#line 38 "./src/util/macros.lzz" -void ThrowError (char const * message) -#line 38 "./src/util/macros.lzz" - { v8 :: Isolate * isolate = v8 :: Isolate :: GetCurrent ( ) ; isolate->ThrowException(v8::Exception::Error(StringFromUtf8(isolate, message, -1))); -} -#line 39 "./src/util/macros.lzz" -void ThrowTypeError (char const * message) -#line 39 "./src/util/macros.lzz" - { v8 :: Isolate * isolate = v8 :: Isolate :: GetCurrent ( ) ; isolate->ThrowException(v8::Exception::TypeError(StringFromUtf8(isolate, message, -1))); -} -#line 40 "./src/util/macros.lzz" -void ThrowRangeError (char const * message) -#line 40 "./src/util/macros.lzz" - { v8 :: Isolate * isolate = v8 :: Isolate :: GetCurrent ( ) ; isolate->ThrowException(v8::Exception::RangeError(StringFromUtf8(isolate, message, -1))); -} -#line 106 "./src/util/macros.lzz" -v8::Local NewConstructorTemplate (v8::Isolate * isolate, v8::Local data, v8::FunctionCallback func, char const * name) -#line 111 "./src/util/macros.lzz" - { - v8::Local t = v8::FunctionTemplate::New(isolate, func, data); - t->InstanceTemplate()->SetInternalFieldCount(1); - t->SetClassName(InternalizedFromLatin1(isolate, name)); - return t; -} -#line 117 "./src/util/macros.lzz" -void SetPrototypeMethod (v8::Isolate * isolate, v8::Local data, v8::Local recv, char const * name, v8::FunctionCallback func) -#line 123 "./src/util/macros.lzz" - { - v8::HandleScope scope(isolate); - recv->PrototypeTemplate()->Set( - InternalizedFromLatin1(isolate, name), - v8::FunctionTemplate::New(isolate, func, data, v8::Signature::New(isolate, recv)) - ); -} -#line 130 "./src/util/macros.lzz" -void SetPrototypeSymbolMethod (v8::Isolate * isolate, v8::Local data, v8::Local recv, v8::Local symbol, v8::FunctionCallback func) -#line 136 "./src/util/macros.lzz" - { - v8::HandleScope scope(isolate); - recv->PrototypeTemplate()->Set( - symbol, - v8::FunctionTemplate::New(isolate, func, data, v8::Signature::New(isolate, recv)) - ); -} -#line 4 "./src/util/constants.lzz" -v8::Local CS::Code (v8::Isolate * isolate, int code) -#line 4 "./src/util/constants.lzz" - { - auto element = codes.find(code); - if (element != codes.end()) return element->second.Get(isolate); - return StringFromUtf8(isolate, (std::string("UNKNOWN_SQLITE_ERROR_") + std::to_string(code)).c_str(), -1); -} -#line 10 "./src/util/constants.lzz" -CS::CS (v8::Isolate * isolate) -#line 10 "./src/util/constants.lzz" - { - SetString(isolate, database, "database"); - SetString(isolate, reader, "reader"); - SetString(isolate, source, "source"); - SetString(isolate, memory, "memory"); - SetString(isolate, readonly, "readonly"); - SetString(isolate, name, "name"); - SetString(isolate, next, "next"); - SetString(isolate, length, "length"); - SetString(isolate, done, "done"); - SetString(isolate, value, "value"); - SetString(isolate, changes, "changes"); - SetString(isolate, lastInsertRowid, "lastInsertRowid"); - SetString(isolate, statement, "statement"); - SetString(isolate, column, "column"); - SetString(isolate, table, "table"); - SetString(isolate, type, "type"); - SetString(isolate, totalPages, "totalPages"); - SetString(isolate, remainingPages, "remainingPages"); - - SetCode(isolate, SQLITE_OK, "SQLITE_OK"); - SetCode(isolate, SQLITE_ERROR, "SQLITE_ERROR"); - SetCode(isolate, SQLITE_INTERNAL, "SQLITE_INTERNAL"); - SetCode(isolate, SQLITE_PERM, "SQLITE_PERM"); - SetCode(isolate, SQLITE_ABORT, "SQLITE_ABORT"); - SetCode(isolate, SQLITE_BUSY, "SQLITE_BUSY"); - SetCode(isolate, SQLITE_LOCKED, "SQLITE_LOCKED"); - SetCode(isolate, SQLITE_NOMEM, "SQLITE_NOMEM"); - SetCode(isolate, SQLITE_READONLY, "SQLITE_READONLY"); - SetCode(isolate, SQLITE_INTERRUPT, "SQLITE_INTERRUPT"); - SetCode(isolate, SQLITE_IOERR, "SQLITE_IOERR"); - SetCode(isolate, SQLITE_CORRUPT, "SQLITE_CORRUPT"); - SetCode(isolate, SQLITE_NOTFOUND, "SQLITE_NOTFOUND"); - SetCode(isolate, SQLITE_FULL, "SQLITE_FULL"); - SetCode(isolate, SQLITE_CANTOPEN, "SQLITE_CANTOPEN"); - SetCode(isolate, SQLITE_PROTOCOL, "SQLITE_PROTOCOL"); - SetCode(isolate, SQLITE_EMPTY, "SQLITE_EMPTY"); - SetCode(isolate, SQLITE_SCHEMA, "SQLITE_SCHEMA"); - SetCode(isolate, SQLITE_TOOBIG, "SQLITE_TOOBIG"); - SetCode(isolate, SQLITE_CONSTRAINT, "SQLITE_CONSTRAINT"); - SetCode(isolate, SQLITE_MISMATCH, "SQLITE_MISMATCH"); - SetCode(isolate, SQLITE_MISUSE, "SQLITE_MISUSE"); - SetCode(isolate, SQLITE_NOLFS, "SQLITE_NOLFS"); - SetCode(isolate, SQLITE_AUTH, "SQLITE_AUTH"); - SetCode(isolate, SQLITE_FORMAT, "SQLITE_FORMAT"); - SetCode(isolate, SQLITE_RANGE, "SQLITE_RANGE"); - SetCode(isolate, SQLITE_NOTADB, "SQLITE_NOTADB"); - SetCode(isolate, SQLITE_NOTICE, "SQLITE_NOTICE"); - SetCode(isolate, SQLITE_WARNING, "SQLITE_WARNING"); - SetCode(isolate, SQLITE_ROW, "SQLITE_ROW"); - SetCode(isolate, SQLITE_DONE, "SQLITE_DONE"); - - SetCode(isolate, SQLITE_ERROR_MISSING_COLLSEQ, "SQLITE_ERROR_MISSING_COLLSEQ"); - SetCode(isolate, SQLITE_ERROR_RETRY, "SQLITE_ERROR_RETRY"); - SetCode(isolate, SQLITE_ERROR_SNAPSHOT, "SQLITE_ERROR_SNAPSHOT"); - SetCode(isolate, SQLITE_IOERR_READ, "SQLITE_IOERR_READ"); - SetCode(isolate, SQLITE_IOERR_SHORT_READ, "SQLITE_IOERR_SHORT_READ"); - SetCode(isolate, SQLITE_IOERR_WRITE, "SQLITE_IOERR_WRITE"); - SetCode(isolate, SQLITE_IOERR_FSYNC, "SQLITE_IOERR_FSYNC"); - SetCode(isolate, SQLITE_IOERR_DIR_FSYNC, "SQLITE_IOERR_DIR_FSYNC"); - SetCode(isolate, SQLITE_IOERR_TRUNCATE, "SQLITE_IOERR_TRUNCATE"); - SetCode(isolate, SQLITE_IOERR_FSTAT, "SQLITE_IOERR_FSTAT"); - SetCode(isolate, SQLITE_IOERR_UNLOCK, "SQLITE_IOERR_UNLOCK"); - SetCode(isolate, SQLITE_IOERR_RDLOCK, "SQLITE_IOERR_RDLOCK"); - SetCode(isolate, SQLITE_IOERR_DELETE, "SQLITE_IOERR_DELETE"); - SetCode(isolate, SQLITE_IOERR_BLOCKED, "SQLITE_IOERR_BLOCKED"); - SetCode(isolate, SQLITE_IOERR_NOMEM, "SQLITE_IOERR_NOMEM"); - SetCode(isolate, SQLITE_IOERR_ACCESS, "SQLITE_IOERR_ACCESS"); - SetCode(isolate, SQLITE_IOERR_CHECKRESERVEDLOCK, "SQLITE_IOERR_CHECKRESERVEDLOCK"); - SetCode(isolate, SQLITE_IOERR_LOCK, "SQLITE_IOERR_LOCK"); - SetCode(isolate, SQLITE_IOERR_CLOSE, "SQLITE_IOERR_CLOSE"); - SetCode(isolate, SQLITE_IOERR_DIR_CLOSE, "SQLITE_IOERR_DIR_CLOSE"); - SetCode(isolate, SQLITE_IOERR_SHMOPEN, "SQLITE_IOERR_SHMOPEN"); - SetCode(isolate, SQLITE_IOERR_SHMSIZE, "SQLITE_IOERR_SHMSIZE"); - SetCode(isolate, SQLITE_IOERR_SHMLOCK, "SQLITE_IOERR_SHMLOCK"); - SetCode(isolate, SQLITE_IOERR_SHMMAP, "SQLITE_IOERR_SHMMAP"); - SetCode(isolate, SQLITE_IOERR_SEEK, "SQLITE_IOERR_SEEK"); - SetCode(isolate, SQLITE_IOERR_DELETE_NOENT, "SQLITE_IOERR_DELETE_NOENT"); - SetCode(isolate, SQLITE_IOERR_MMAP, "SQLITE_IOERR_MMAP"); - SetCode(isolate, SQLITE_IOERR_GETTEMPPATH, "SQLITE_IOERR_GETTEMPPATH"); - SetCode(isolate, SQLITE_IOERR_CONVPATH, "SQLITE_IOERR_CONVPATH"); - SetCode(isolate, SQLITE_IOERR_VNODE, "SQLITE_IOERR_VNODE"); - SetCode(isolate, SQLITE_IOERR_AUTH, "SQLITE_IOERR_AUTH"); - SetCode(isolate, SQLITE_IOERR_BEGIN_ATOMIC, "SQLITE_IOERR_BEGIN_ATOMIC"); - SetCode(isolate, SQLITE_IOERR_COMMIT_ATOMIC, "SQLITE_IOERR_COMMIT_ATOMIC"); - SetCode(isolate, SQLITE_IOERR_ROLLBACK_ATOMIC, "SQLITE_IOERR_ROLLBACK_ATOMIC"); - SetCode(isolate, SQLITE_IOERR_DATA, "SQLITE_IOERR_DATA"); - SetCode(isolate, SQLITE_IOERR_CORRUPTFS, "SQLITE_IOERR_CORRUPTFS"); - SetCode(isolate, SQLITE_IOERR_IN_PAGE, "SQLITE_IOERR_IN_PAGE"); - SetCode(isolate, SQLITE_LOCKED_SHAREDCACHE, "SQLITE_LOCKED_SHAREDCACHE"); - SetCode(isolate, SQLITE_LOCKED_VTAB, "SQLITE_LOCKED_VTAB"); - SetCode(isolate, SQLITE_BUSY_RECOVERY, "SQLITE_BUSY_RECOVERY"); - SetCode(isolate, SQLITE_BUSY_SNAPSHOT, "SQLITE_BUSY_SNAPSHOT"); - SetCode(isolate, SQLITE_CANTOPEN_NOTEMPDIR, "SQLITE_CANTOPEN_NOTEMPDIR"); - SetCode(isolate, SQLITE_CANTOPEN_ISDIR, "SQLITE_CANTOPEN_ISDIR"); - SetCode(isolate, SQLITE_CANTOPEN_FULLPATH, "SQLITE_CANTOPEN_FULLPATH"); - SetCode(isolate, SQLITE_CANTOPEN_CONVPATH, "SQLITE_CANTOPEN_CONVPATH"); - SetCode(isolate, SQLITE_CANTOPEN_DIRTYWAL, "SQLITE_CANTOPEN_DIRTYWAL"); - SetCode(isolate, SQLITE_CANTOPEN_SYMLINK, "SQLITE_CANTOPEN_SYMLINK"); - SetCode(isolate, SQLITE_CORRUPT_VTAB, "SQLITE_CORRUPT_VTAB"); - SetCode(isolate, SQLITE_CORRUPT_SEQUENCE, "SQLITE_CORRUPT_SEQUENCE"); - SetCode(isolate, SQLITE_CORRUPT_INDEX, "SQLITE_CORRUPT_INDEX"); - SetCode(isolate, SQLITE_READONLY_RECOVERY, "SQLITE_READONLY_RECOVERY"); - SetCode(isolate, SQLITE_READONLY_CANTLOCK, "SQLITE_READONLY_CANTLOCK"); - SetCode(isolate, SQLITE_READONLY_ROLLBACK, "SQLITE_READONLY_ROLLBACK"); - SetCode(isolate, SQLITE_READONLY_DBMOVED, "SQLITE_READONLY_DBMOVED"); - SetCode(isolate, SQLITE_READONLY_CANTINIT, "SQLITE_READONLY_CANTINIT"); - SetCode(isolate, SQLITE_READONLY_DIRECTORY, "SQLITE_READONLY_DIRECTORY"); - SetCode(isolate, SQLITE_ABORT_ROLLBACK, "SQLITE_ABORT_ROLLBACK"); - SetCode(isolate, SQLITE_CONSTRAINT_CHECK, "SQLITE_CONSTRAINT_CHECK"); - SetCode(isolate, SQLITE_CONSTRAINT_COMMITHOOK, "SQLITE_CONSTRAINT_COMMITHOOK"); - SetCode(isolate, SQLITE_CONSTRAINT_FOREIGNKEY, "SQLITE_CONSTRAINT_FOREIGNKEY"); - SetCode(isolate, SQLITE_CONSTRAINT_FUNCTION, "SQLITE_CONSTRAINT_FUNCTION"); - SetCode(isolate, SQLITE_CONSTRAINT_NOTNULL, "SQLITE_CONSTRAINT_NOTNULL"); - SetCode(isolate, SQLITE_CONSTRAINT_PRIMARYKEY, "SQLITE_CONSTRAINT_PRIMARYKEY"); - SetCode(isolate, SQLITE_CONSTRAINT_TRIGGER, "SQLITE_CONSTRAINT_TRIGGER"); - SetCode(isolate, SQLITE_CONSTRAINT_UNIQUE, "SQLITE_CONSTRAINT_UNIQUE"); - SetCode(isolate, SQLITE_CONSTRAINT_VTAB, "SQLITE_CONSTRAINT_VTAB"); - SetCode(isolate, SQLITE_CONSTRAINT_ROWID, "SQLITE_CONSTRAINT_ROWID"); - SetCode(isolate, SQLITE_CONSTRAINT_PINNED, "SQLITE_CONSTRAINT_PINNED"); - SetCode(isolate, SQLITE_CONSTRAINT_DATATYPE, "SQLITE_CONSTRAINT_DATATYPE"); - SetCode(isolate, SQLITE_NOTICE_RECOVER_WAL, "SQLITE_NOTICE_RECOVER_WAL"); - SetCode(isolate, SQLITE_NOTICE_RECOVER_ROLLBACK, "SQLITE_NOTICE_RECOVER_ROLLBACK"); - SetCode(isolate, SQLITE_NOTICE_RBU, "SQLITE_NOTICE_RBU"); - SetCode(isolate, SQLITE_WARNING_AUTOINDEX, "SQLITE_WARNING_AUTOINDEX"); - SetCode(isolate, SQLITE_AUTH_USER, "SQLITE_AUTH_USER"); - SetCode(isolate, SQLITE_OK_LOAD_PERMANENTLY, "SQLITE_OK_LOAD_PERMANENTLY"); - SetCode(isolate, SQLITE_OK_SYMLINK, "SQLITE_OK_SYMLINK"); -} -#line 161 "./src/util/constants.lzz" -void CS::SetString (v8::Isolate * isolate, v8::Global & constant, char const * str) -#line 161 "./src/util/constants.lzz" - { - constant.Reset(isolate, InternalizedFromLatin1(isolate, str)); -} -#line 165 "./src/util/constants.lzz" -void CS::SetCode (v8::Isolate * isolate, int code, char const * str) -#line 165 "./src/util/constants.lzz" - { - codes.emplace(std::piecewise_construct, - std::forward_as_tuple(code), - std::forward_as_tuple(isolate, InternalizedFromLatin1(isolate, str))); -} -#line 19 "./src/util/bind-map.lzz" -BindMap::Pair::Pair (v8::Isolate * isolate, char const * name, int index) -#line 20 "./src/util/bind-map.lzz" - : name (isolate, InternalizedFromUtf8(isolate, name, -1)), index (index) -#line 20 "./src/util/bind-map.lzz" - {} -#line 22 "./src/util/bind-map.lzz" -BindMap::Pair::Pair (v8::Isolate * isolate, Pair * pair) -#line 23 "./src/util/bind-map.lzz" - : name (isolate, pair->name), index (pair->index) -#line 23 "./src/util/bind-map.lzz" - {} -#line 29 "./src/util/bind-map.lzz" -BindMap::BindMap (char _) -#line 29 "./src/util/bind-map.lzz" - { - assert(_ == 0); - pairs = NULL; - capacity = 0; - length = 0; -} -#line 36 "./src/util/bind-map.lzz" -BindMap::~ BindMap () -#line 36 "./src/util/bind-map.lzz" - { - while (length) pairs[--length].~Pair(); - FREE_ARRAY(pairs); -} -#line 50 "./src/util/bind-map.lzz" -void BindMap::Add (v8::Isolate * isolate, char const * name, int index) -#line 50 "./src/util/bind-map.lzz" - { - assert(name != NULL); - if (capacity == length) Grow(isolate); - new (pairs + length++) Pair(isolate, name, index); -} -#line 58 "./src/util/bind-map.lzz" -void BindMap::Grow (v8::Isolate * isolate) -#line 58 "./src/util/bind-map.lzz" - { - assert(capacity == length); - capacity = (capacity << 1) | 2; - Pair* new_pairs = ALLOC_ARRAY(capacity); - for (int i = 0; i < length; ++i) { - new (new_pairs + i) Pair(isolate, pairs + i); - pairs[i].~Pair(); - } - FREE_ARRAY(pairs); - pairs = new_pairs; -} -#line 4 "./src/objects/database.lzz" -v8::Local Database::Init (v8::Isolate * isolate, v8::Local data) -#line 4 "./src/objects/database.lzz" - { - v8::Local t = NewConstructorTemplate(isolate, data, JS_new, "Database"); - SetPrototypeMethod(isolate, data, t, "prepare", JS_prepare); - SetPrototypeMethod(isolate, data, t, "exec", JS_exec); - SetPrototypeMethod(isolate, data, t, "backup", JS_backup); - SetPrototypeMethod(isolate, data, t, "serialize", JS_serialize); - SetPrototypeMethod(isolate, data, t, "function", JS_function); - SetPrototypeMethod(isolate, data, t, "aggregate", JS_aggregate); - SetPrototypeMethod(isolate, data, t, "table", JS_table); - SetPrototypeMethod(isolate, data, t, "loadExtension", JS_loadExtension); - SetPrototypeMethod(isolate, data, t, "close", JS_close); - SetPrototypeMethod(isolate, data, t, "defaultSafeIntegers", JS_defaultSafeIntegers); - SetPrototypeMethod(isolate, data, t, "unsafeMode", JS_unsafeMode); - SetPrototypeGetter(isolate, data, t, "open", JS_open); - SetPrototypeGetter(isolate, data, t, "inTransaction", JS_inTransaction); - return t->GetFunction( isolate -> GetCurrentContext ( ) ).ToLocalChecked(); -} -#line 24 "./src/objects/database.lzz" -bool Database::CompareDatabase::operator () (Database const * const a, Database const * const b) const -#line 24 "./src/objects/database.lzz" - { - return a < b; -} -#line 29 "./src/objects/database.lzz" -bool Database::CompareStatement::operator () (Statement const * const a, Statement const * const b) const -#line 29 "./src/objects/database.lzz" - { - return Statement::Compare(a, b); -} -#line 34 "./src/objects/database.lzz" -bool Database::CompareBackup::operator () (Backup const * const a, Backup const * const b) const -#line 34 "./src/objects/database.lzz" - { - return Backup::Compare(a, b); -} -#line 40 "./src/objects/database.lzz" -void Database::ThrowDatabaseError () -#line 40 "./src/objects/database.lzz" - { - if (was_js_error) was_js_error = false; - else ThrowSqliteError(addon, db_handle); -} -#line 44 "./src/objects/database.lzz" -void Database::ThrowSqliteError (Addon * addon, sqlite3 * db_handle) -#line 44 "./src/objects/database.lzz" - { - assert(db_handle != NULL); - ThrowSqliteError(addon, sqlite3_errmsg(db_handle), sqlite3_extended_errcode(db_handle)); -} -#line 48 "./src/objects/database.lzz" -void Database::ThrowSqliteError (Addon * addon, char const * message, int code) -#line 48 "./src/objects/database.lzz" - { - assert(message != NULL); - assert((code & 0xff) != SQLITE_OK); - assert((code & 0xff) != SQLITE_ROW); - assert((code & 0xff) != SQLITE_DONE); - v8 :: Isolate * isolate = v8 :: Isolate :: GetCurrent ( ) ; - v8::Local args[2] = { - StringFromUtf8(isolate, message, -1), - addon->cs.Code(isolate, code) - }; - isolate->ThrowException(addon->SqliteError.Get(isolate) - ->NewInstance( isolate -> GetCurrentContext ( ) , 2, args) - .ToLocalChecked()); -} -#line 64 "./src/objects/database.lzz" -bool Database::Log (v8::Isolate * isolate, sqlite3_stmt * handle) -#line 64 "./src/objects/database.lzz" - { - assert(was_js_error == false); - if (!has_logger) return false; - char* expanded = sqlite3_expanded_sql(handle); - v8::Local arg = StringFromUtf8(isolate, expanded ? expanded : sqlite3_sql(handle), -1); - was_js_error = logger.Get(isolate).As() - ->Call( isolate -> GetCurrentContext ( ) , v8::Undefined(isolate), 1, &arg) - .IsEmpty(); - if (expanded) sqlite3_free(expanded); - return was_js_error; -} -#line 107 "./src/objects/database.lzz" -void Database::CloseHandles () -#line 107 "./src/objects/database.lzz" - { - if (open) { - open = false; - for (Statement* stmt : stmts) stmt->CloseHandles(); - for (Backup* backup : backups) backup->CloseHandles(); - stmts.clear(); - backups.clear(); - int status = sqlite3_close(db_handle); - assert(status == SQLITE_OK); ((void)status); - } -} -#line 119 "./src/objects/database.lzz" -Database::~ Database () -#line 119 "./src/objects/database.lzz" - { - if (open) addon->dbs.erase(this); - CloseHandles(); -} -#line 126 "./src/objects/database.lzz" -Database::Database (v8::Isolate * isolate, Addon * addon, sqlite3 * db_handle, v8::Local logger) -#line 131 "./src/objects/database.lzz" - : node::ObjectWrap (), db_handle (db_handle), open (true), busy (false), safe_ints (false), unsafe_mode (false), was_js_error (false), has_logger (logger->IsFunction()), iterators (0), addon (addon), logger (isolate, logger), stmts (), backups () -#line 144 "./src/objects/database.lzz" - { - assert(db_handle != NULL); - addon->dbs.insert(this); -} -#line 149 "./src/objects/database.lzz" -void Database::JS_new (v8::FunctionCallbackInfo const & info) -#line 149 "./src/objects/database.lzz" - { - assert(info.IsConstructCall()); - if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > filename = ( info [ 0 ] . As < v8 :: String > ( ) ) ; - if ( info . Length ( ) <= ( 1 ) || ! info [ 1 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "second" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > filenameGiven = ( info [ 1 ] . As < v8 :: String > ( ) ) ; - if ( info . Length ( ) <= ( 2 ) || ! info [ 2 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "third" " argument to be " "a boolean" ) ; bool in_memory = ( info [ 2 ] . As < v8 :: Boolean > ( ) ) -> Value ( ) ; - if ( info . Length ( ) <= ( 3 ) || ! info [ 3 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "fourth" " argument to be " "a boolean" ) ; bool readonly = ( info [ 3 ] . As < v8 :: Boolean > ( ) ) -> Value ( ) ; - if ( info . Length ( ) <= ( 4 ) || ! info [ 4 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "fifth" " argument to be " "a boolean" ) ; bool must_exist = ( info [ 4 ] . As < v8 :: Boolean > ( ) ) -> Value ( ) ; - if ( info . Length ( ) <= ( 5 ) || ! info [ 5 ] -> IsInt32 ( ) ) return ThrowTypeError ( "Expected " "sixth" " argument to be " "a 32-bit signed integer" ) ; int timeout = ( info [ 5 ] . As < v8 :: Int32 > ( ) ) -> Value ( ) ; - if ( info . Length ( ) <= ( 6 ) ) return ThrowTypeError ( "Expected a " "seventh" " argument" ) ; v8 :: Local < v8 :: Value > logger = info [ 6 ] ; - if ( info . Length ( ) <= ( 7 ) ) return ThrowTypeError ( "Expected a " "eighth" " argument" ) ; v8 :: Local < v8 :: Value > buffer = info [ 7 ] ; - - Addon * addon = static_cast < Addon * > ( info . Data ( ) . As < v8 :: External > ( ) -> Value ( ) ) ; - v8 :: Isolate * isolate = info . GetIsolate ( ) ; - sqlite3* db_handle; - v8::String::Utf8Value utf8(isolate, filename); - int mask = readonly ? SQLITE_OPEN_READONLY - : must_exist ? SQLITE_OPEN_READWRITE - : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); - - if (sqlite3_open_v2(*utf8, &db_handle, mask, NULL) != SQLITE_OK) { - ThrowSqliteError(addon, db_handle); - int status = sqlite3_close(db_handle); - assert(status == SQLITE_OK); ((void)status); - return; - } - - assert(sqlite3_db_mutex(db_handle) == NULL); - sqlite3_extended_result_codes(db_handle, 1); - sqlite3_busy_timeout(db_handle, timeout); - sqlite3_limit(db_handle, SQLITE_LIMIT_LENGTH, MAX_BUFFER_SIZE < MAX_STRING_SIZE ? MAX_BUFFER_SIZE : MAX_STRING_SIZE); - sqlite3_limit(db_handle, SQLITE_LIMIT_SQL_LENGTH, MAX_STRING_SIZE); - int status = sqlite3_db_config(db_handle, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, NULL); - assert(status == SQLITE_OK); - status = sqlite3_db_config(db_handle, SQLITE_DBCONFIG_DEFENSIVE, 1, NULL); - assert(status == SQLITE_OK); - - if (node::Buffer::HasInstance(buffer) && !Deserialize(buffer.As(), addon, db_handle, readonly)) { - int status = sqlite3_close(db_handle); - assert(status == SQLITE_OK); ((void)status); - return; - } - - v8 :: Local < v8 :: Context > ctx = isolate -> GetCurrentContext ( ) ; - Database* db = new Database(isolate, addon, db_handle, logger); - db->Wrap(info.This()); - SetFrozen(isolate, ctx, info.This(), addon->cs.memory, v8::Boolean::New(isolate, in_memory)); - SetFrozen(isolate, ctx, info.This(), addon->cs.readonly, v8::Boolean::New(isolate, readonly)); - SetFrozen(isolate, ctx, info.This(), addon->cs.name, filenameGiven); - - info.GetReturnValue().Set(info.This()); -} -#line 201 "./src/objects/database.lzz" -void Database::JS_prepare (v8::FunctionCallbackInfo const & info) -#line 201 "./src/objects/database.lzz" - { - if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > source = ( info [ 0 ] . As < v8 :: String > ( ) ) ; - if ( info . Length ( ) <= ( 1 ) || ! info [ 1 ] -> IsObject ( ) ) return ThrowTypeError ( "Expected " "second" " argument to be " "an object" ) ; v8 :: Local < v8 :: Object > database = ( info [ 1 ] . As < v8 :: Object > ( ) ) ; - if ( info . Length ( ) <= ( 2 ) || ! info [ 2 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "third" " argument to be " "a boolean" ) ; bool pragmaMode = ( info [ 2 ] . As < v8 :: Boolean > ( ) ) -> Value ( ) ; - (void)source; - (void)database; - (void)pragmaMode; - Addon * addon = static_cast < Addon * > ( info . Data ( ) . As < v8 :: External > ( ) -> Value ( ) ) ; - v8 :: Isolate * isolate = info . GetIsolate ( ) ; - v8::Local c = addon->Statement.Get(isolate); - addon->privileged_info = &info; - v8::MaybeLocal maybeStatement = c->NewInstance( isolate -> GetCurrentContext ( ) , 0, NULL); - addon->privileged_info = NULL; - if (!maybeStatement.IsEmpty()) info.GetReturnValue().Set(maybeStatement.ToLocalChecked()); -} -#line 217 "./src/objects/database.lzz" -void Database::JS_exec (v8::FunctionCallbackInfo const & info) -#line 217 "./src/objects/database.lzz" - { - Database* db = node :: ObjectWrap :: Unwrap (info.This()); - if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > source = ( info [ 0 ] . As < v8 :: String > ( ) ) ; - if ( ! db -> open ) return ThrowTypeError ( "The database connection is not open" ) ; - if ( db -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if ( ! db -> unsafe_mode ) { if ( db -> iterators ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; } ( ( void ) 0 ) ; - db->busy = true; - - v8 :: Isolate * isolate = info . GetIsolate ( ) ; - v8::String::Utf8Value utf8(isolate, source); - const char* sql = *utf8; - const char* tail; - - int status; - const bool has_logger = db->has_logger; - sqlite3* const db_handle = db->db_handle; - sqlite3_stmt* handle; - - for (;;) { - while (IS_SKIPPED(*sql)) ++sql; - status = sqlite3_prepare_v2(db_handle, sql, -1, &handle, &tail); - sql = tail; - if (!handle) break; - if (has_logger && db->Log(isolate, handle)) { - sqlite3_finalize(handle); - status = -1; - break; - } - do status = sqlite3_step(handle); - while (status == SQLITE_ROW); - status = sqlite3_finalize(handle); - if (status != SQLITE_OK) break; - } - - db->busy = false; - if (status != SQLITE_OK) { - db->ThrowDatabaseError(); - } -} -#line 257 "./src/objects/database.lzz" -void Database::JS_backup (v8::FunctionCallbackInfo const & info) -#line 257 "./src/objects/database.lzz" - { - if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsObject ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "an object" ) ; v8 :: Local < v8 :: Object > database = ( info [ 0 ] . As < v8 :: Object > ( ) ) ; - if ( info . Length ( ) <= ( 1 ) || ! info [ 1 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "second" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > attachedName = ( info [ 1 ] . As < v8 :: String > ( ) ) ; - if ( info . Length ( ) <= ( 2 ) || ! info [ 2 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "third" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > destFile = ( info [ 2 ] . As < v8 :: String > ( ) ) ; - if ( info . Length ( ) <= ( 3 ) || ! info [ 3 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "fourth" " argument to be " "a boolean" ) ; bool unlink = ( info [ 3 ] . As < v8 :: Boolean > ( ) ) -> Value ( ) ; - (void)database; - (void)attachedName; - (void)destFile; - (void)unlink; - Addon * addon = static_cast < Addon * > ( info . Data ( ) . As < v8 :: External > ( ) -> Value ( ) ) ; - v8 :: Isolate * isolate = info . GetIsolate ( ) ; - v8::Local c = addon->Backup.Get(isolate); - addon->privileged_info = &info; - v8::MaybeLocal maybeBackup = c->NewInstance( isolate -> GetCurrentContext ( ) , 0, NULL); - addon->privileged_info = NULL; - if (!maybeBackup.IsEmpty()) info.GetReturnValue().Set(maybeBackup.ToLocalChecked()); -} -#line 275 "./src/objects/database.lzz" -void Database::JS_serialize (v8::FunctionCallbackInfo const & info) -#line 275 "./src/objects/database.lzz" - { - Database* db = node :: ObjectWrap :: Unwrap (info.This()); - if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > attachedName = ( info [ 0 ] . As < v8 :: String > ( ) ) ; - if ( ! db -> open ) return ThrowTypeError ( "The database connection is not open" ) ; - if ( db -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if ( db -> iterators ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - - v8 :: Isolate * isolate = info . GetIsolate ( ) ; - v8::String::Utf8Value attached_name(isolate, attachedName); - sqlite3_int64 length = -1; - unsigned char* data = sqlite3_serialize(db->db_handle, *attached_name, &length, 0); - - if (!data && length) { - ThrowError("Out of memory"); - return; - } - - info.GetReturnValue().Set( - SAFE_NEW_BUFFER(isolate, reinterpret_cast(data), length, FreeSerialization, NULL).ToLocalChecked() - ); -} -#line 297 "./src/objects/database.lzz" -void Database::JS_function (v8::FunctionCallbackInfo const & info) -#line 297 "./src/objects/database.lzz" - { - Database* db = node :: ObjectWrap :: Unwrap (info.This()); - if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsFunction ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a function" ) ; v8 :: Local < v8 :: Function > fn = ( info [ 0 ] . As < v8 :: Function > ( ) ) ; - if ( info . Length ( ) <= ( 1 ) || ! info [ 1 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "second" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > nameString = ( info [ 1 ] . As < v8 :: String > ( ) ) ; - if ( info . Length ( ) <= ( 2 ) || ! info [ 2 ] -> IsInt32 ( ) ) return ThrowTypeError ( "Expected " "third" " argument to be " "a 32-bit signed integer" ) ; int argc = ( info [ 2 ] . As < v8 :: Int32 > ( ) ) -> Value ( ) ; - if ( info . Length ( ) <= ( 3 ) || ! info [ 3 ] -> IsInt32 ( ) ) return ThrowTypeError ( "Expected " "fourth" " argument to be " "a 32-bit signed integer" ) ; int safe_ints = ( info [ 3 ] . As < v8 :: Int32 > ( ) ) -> Value ( ) ; - if ( info . Length ( ) <= ( 4 ) || ! info [ 4 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "fifth" " argument to be " "a boolean" ) ; bool deterministic = ( info [ 4 ] . As < v8 :: Boolean > ( ) ) -> Value ( ) ; - if ( info . Length ( ) <= ( 5 ) || ! info [ 5 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "sixth" " argument to be " "a boolean" ) ; bool direct_only = ( info [ 5 ] . As < v8 :: Boolean > ( ) ) -> Value ( ) ; - if ( ! db -> open ) return ThrowTypeError ( "The database connection is not open" ) ; - if ( db -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if ( db -> iterators ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - - v8 :: Isolate * isolate = info . GetIsolate ( ) ; - v8::String::Utf8Value name(isolate, nameString); - int mask = SQLITE_UTF8; - if (deterministic) mask |= SQLITE_DETERMINISTIC; - if (direct_only) mask |= SQLITE_DIRECTONLY; - safe_ints = safe_ints < 2 ? safe_ints : static_cast(db->safe_ints); - - if (sqlite3_create_function_v2(db->db_handle, *name, argc, mask, new CustomFunction(isolate, db, *name, fn, safe_ints), CustomFunction::xFunc, NULL, NULL, CustomFunction::xDestroy) != SQLITE_OK) { - db->ThrowDatabaseError(); - } -} -#line 321 "./src/objects/database.lzz" -void Database::JS_aggregate (v8::FunctionCallbackInfo const & info) -#line 321 "./src/objects/database.lzz" - { - Database* db = node :: ObjectWrap :: Unwrap (info.This()); - if ( info . Length ( ) <= ( 0 ) ) return ThrowTypeError ( "Expected a " "first" " argument" ) ; v8 :: Local < v8 :: Value > start = info [ 0 ] ; - if ( info . Length ( ) <= ( 1 ) || ! info [ 1 ] -> IsFunction ( ) ) return ThrowTypeError ( "Expected " "second" " argument to be " "a function" ) ; v8 :: Local < v8 :: Function > step = ( info [ 1 ] . As < v8 :: Function > ( ) ) ; - if ( info . Length ( ) <= ( 2 ) ) return ThrowTypeError ( "Expected a " "third" " argument" ) ; v8 :: Local < v8 :: Value > inverse = info [ 2 ] ; - if ( info . Length ( ) <= ( 3 ) ) return ThrowTypeError ( "Expected a " "fourth" " argument" ) ; v8 :: Local < v8 :: Value > result = info [ 3 ] ; - if ( info . Length ( ) <= ( 4 ) || ! info [ 4 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "fifth" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > nameString = ( info [ 4 ] . As < v8 :: String > ( ) ) ; - if ( info . Length ( ) <= ( 5 ) || ! info [ 5 ] -> IsInt32 ( ) ) return ThrowTypeError ( "Expected " "sixth" " argument to be " "a 32-bit signed integer" ) ; int argc = ( info [ 5 ] . As < v8 :: Int32 > ( ) ) -> Value ( ) ; - if ( info . Length ( ) <= ( 6 ) || ! info [ 6 ] -> IsInt32 ( ) ) return ThrowTypeError ( "Expected " "seventh" " argument to be " "a 32-bit signed integer" ) ; int safe_ints = ( info [ 6 ] . As < v8 :: Int32 > ( ) ) -> Value ( ) ; - if ( info . Length ( ) <= ( 7 ) || ! info [ 7 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "eighth" " argument to be " "a boolean" ) ; bool deterministic = ( info [ 7 ] . As < v8 :: Boolean > ( ) ) -> Value ( ) ; - if ( info . Length ( ) <= ( 8 ) || ! info [ 8 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "ninth" " argument to be " "a boolean" ) ; bool direct_only = ( info [ 8 ] . As < v8 :: Boolean > ( ) ) -> Value ( ) ; - if ( ! db -> open ) return ThrowTypeError ( "The database connection is not open" ) ; - if ( db -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if ( db -> iterators ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - - v8 :: Isolate * isolate = info . GetIsolate ( ) ; - v8::String::Utf8Value name(isolate, nameString); - auto xInverse = inverse->IsFunction() ? CustomAggregate::xInverse : NULL; - auto xValue = xInverse ? CustomAggregate::xValue : NULL; - int mask = SQLITE_UTF8; - if (deterministic) mask |= SQLITE_DETERMINISTIC; - if (direct_only) mask |= SQLITE_DIRECTONLY; - safe_ints = safe_ints < 2 ? safe_ints : static_cast(db->safe_ints); - - if (sqlite3_create_window_function(db->db_handle, *name, argc, mask, new CustomAggregate(isolate, db, *name, start, step, inverse, result, safe_ints), CustomAggregate::xStep, CustomAggregate::xFinal, xValue, xInverse, CustomAggregate::xDestroy) != SQLITE_OK) { - db->ThrowDatabaseError(); - } -} -#line 350 "./src/objects/database.lzz" -void Database::JS_table (v8::FunctionCallbackInfo const & info) -#line 350 "./src/objects/database.lzz" - { - Database* db = node :: ObjectWrap :: Unwrap (info.This()); - if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsFunction ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a function" ) ; v8 :: Local < v8 :: Function > factory = ( info [ 0 ] . As < v8 :: Function > ( ) ) ; - if ( info . Length ( ) <= ( 1 ) || ! info [ 1 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "second" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > nameString = ( info [ 1 ] . As < v8 :: String > ( ) ) ; - if ( info . Length ( ) <= ( 2 ) || ! info [ 2 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "third" " argument to be " "a boolean" ) ; bool eponymous = ( info [ 2 ] . As < v8 :: Boolean > ( ) ) -> Value ( ) ; - if ( ! db -> open ) return ThrowTypeError ( "The database connection is not open" ) ; - if ( db -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if ( db -> iterators ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - - v8 :: Isolate * isolate = info . GetIsolate ( ) ; - v8::String::Utf8Value name(isolate, nameString); - sqlite3_module* module = eponymous ? &CustomTable::EPONYMOUS_MODULE : &CustomTable::MODULE; - - db->busy = true; - if (sqlite3_create_module_v2(db->db_handle, *name, module, new CustomTable(isolate, db, *name, factory), CustomTable::Destructor) != SQLITE_OK) { - db->ThrowDatabaseError(); - } - db->busy = false; -} -#line 370 "./src/objects/database.lzz" -void Database::JS_loadExtension (v8::FunctionCallbackInfo const & info) -#line 370 "./src/objects/database.lzz" - { - Database* db = node :: ObjectWrap :: Unwrap (info.This()); - v8::Local entryPoint; - if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > filename = ( info [ 0 ] . As < v8 :: String > ( ) ) ; - if (info.Length() > 1) { if ( info . Length ( ) <= ( 1 ) || ! info [ 1 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "second" " argument to be " "a string" ) ; entryPoint = ( info [ 1 ] . As < v8 :: String > ( ) ) ; } - if ( ! db -> open ) return ThrowTypeError ( "The database connection is not open" ) ; - if ( db -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if ( db -> iterators ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - v8 :: Isolate * isolate = info . GetIsolate ( ) ; - char* error; - int status = sqlite3_load_extension( - db->db_handle, - *v8::String::Utf8Value(isolate, filename), - entryPoint.IsEmpty() ? NULL : *v8::String::Utf8Value(isolate, entryPoint), - &error - ); - if (status != SQLITE_OK) { - ThrowSqliteError(db->addon, error, status); - } - sqlite3_free(error); -} -#line 392 "./src/objects/database.lzz" -void Database::JS_close (v8::FunctionCallbackInfo const & info) -#line 392 "./src/objects/database.lzz" - { - Database* db = node :: ObjectWrap :: Unwrap (info.This()); - if (db->open) { - if ( db -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if ( db -> iterators ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - db->addon->dbs.erase(db); - db->CloseHandles(); - } -} -#line 402 "./src/objects/database.lzz" -void Database::JS_defaultSafeIntegers (v8::FunctionCallbackInfo const & info) -#line 402 "./src/objects/database.lzz" - { - Database* db = node :: ObjectWrap :: Unwrap (info.This()); - if (info.Length() == 0) db->safe_ints = true; - else { if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a boolean" ) ; db -> safe_ints = ( info [ 0 ] . As < v8 :: Boolean > ( ) ) -> Value ( ) ; } -} -#line 408 "./src/objects/database.lzz" -void Database::JS_unsafeMode (v8::FunctionCallbackInfo const & info) -#line 408 "./src/objects/database.lzz" - { - Database* db = node :: ObjectWrap :: Unwrap (info.This()); - if (info.Length() == 0) db->unsafe_mode = true; - else { if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a boolean" ) ; db -> unsafe_mode = ( info [ 0 ] . As < v8 :: Boolean > ( ) ) -> Value ( ) ; } - sqlite3_db_config(db->db_handle, SQLITE_DBCONFIG_DEFENSIVE, static_cast(!db->unsafe_mode), NULL); -} -#line 415 "./src/objects/database.lzz" -void Database::JS_open (v8::Local _, v8::PropertyCallbackInfo const & info) -#line 415 "./src/objects/database.lzz" - { - info.GetReturnValue().Set( node :: ObjectWrap :: Unwrap (info.This())->open); -} -#line 419 "./src/objects/database.lzz" -void Database::JS_inTransaction (v8::Local _, v8::PropertyCallbackInfo const & info) -#line 419 "./src/objects/database.lzz" - { - Database* db = node :: ObjectWrap :: Unwrap (info.This()); - info.GetReturnValue().Set(db->open && !static_cast(sqlite3_get_autocommit(db->db_handle))); -} -#line 424 "./src/objects/database.lzz" -bool Database::Deserialize (v8::Local buffer, Addon * addon, sqlite3 * db_handle, bool readonly) -#line 424 "./src/objects/database.lzz" - { - size_t length = node::Buffer::Length(buffer); - unsigned char* data = (unsigned char*)sqlite3_malloc64(length); - unsigned int flags = SQLITE_DESERIALIZE_FREEONCLOSE | SQLITE_DESERIALIZE_RESIZEABLE; - - if (readonly) { - flags |= SQLITE_DESERIALIZE_READONLY; - } - if (length) { - if (!data) { - ThrowError("Out of memory"); - return false; - } - memcpy(data, node::Buffer::Data(buffer), length); - } - - int status = sqlite3_deserialize(db_handle, "main", data, length, length, flags); - if (status != SQLITE_OK) { - ThrowSqliteError(addon, status == SQLITE_ERROR ? "unable to deserialize database" : sqlite3_errstr(status), status); - return false; - } - - return true; -} -#line 449 "./src/objects/database.lzz" -void Database::FreeSerialization (char * data, void * _) -#line 449 "./src/objects/database.lzz" - { - sqlite3_free(data); -} -#line 453 "./src/objects/database.lzz" -int const Database::MAX_BUFFER_SIZE; -#line 454 "./src/objects/database.lzz" -int const Database::MAX_STRING_SIZE; -#line 4 "./src/objects/statement.lzz" -v8::Local Statement::Init (v8::Isolate * isolate, v8::Local data) -#line 4 "./src/objects/statement.lzz" - { - v8::Local t = NewConstructorTemplate(isolate, data, JS_new, "Statement"); - SetPrototypeMethod(isolate, data, t, "run", JS_run); - SetPrototypeMethod(isolate, data, t, "get", JS_get); - SetPrototypeMethod(isolate, data, t, "all", JS_all); - SetPrototypeMethod(isolate, data, t, "iterate", JS_iterate); - SetPrototypeMethod(isolate, data, t, "bind", JS_bind); - SetPrototypeMethod(isolate, data, t, "pluck", JS_pluck); - SetPrototypeMethod(isolate, data, t, "expand", JS_expand); - SetPrototypeMethod(isolate, data, t, "raw", JS_raw); - SetPrototypeMethod(isolate, data, t, "safeIntegers", JS_safeIntegers); - SetPrototypeMethod(isolate, data, t, "columns", JS_columns); - SetPrototypeGetter(isolate, data, t, "busy", JS_busy); - return t->GetFunction( isolate -> GetCurrentContext ( ) ).ToLocalChecked(); -} -#line 26 "./src/objects/statement.lzz" -BindMap * Statement::GetBindMap (v8::Isolate * isolate) -#line 26 "./src/objects/statement.lzz" - { - if (has_bind_map) return &extras->bind_map; - BindMap* bind_map = &extras->bind_map; - int param_count = sqlite3_bind_parameter_count(handle); - for (int i = 1; i <= param_count; ++i) { - const char* name = sqlite3_bind_parameter_name(handle, i); - if (name != NULL) bind_map->Add(isolate, name + 1, i); - } - has_bind_map = true; - return bind_map; -} -#line 39 "./src/objects/statement.lzz" -void Statement::CloseHandles () -#line 39 "./src/objects/statement.lzz" - { - if (alive) { - alive = false; - sqlite3_finalize(handle); - } -} -#line 46 "./src/objects/statement.lzz" -Statement::~ Statement () -#line 46 "./src/objects/statement.lzz" - { - if (alive) db->RemoveStatement(this); - CloseHandles(); - delete extras; -} -#line 56 "./src/objects/statement.lzz" -Statement::Extras::Extras (sqlite3_uint64 id) -#line 56 "./src/objects/statement.lzz" - : bind_map (0), id (id) -#line 56 "./src/objects/statement.lzz" - {} -#line 61 "./src/objects/statement.lzz" -Statement::Statement (Database * db, sqlite3_stmt * handle, sqlite3_uint64 id, bool returns_data) -#line 66 "./src/objects/statement.lzz" - : node::ObjectWrap (), db (db), handle (handle), extras (new Extras(id)), alive (true), locked (false), bound (false), has_bind_map (false), safe_ints (db->GetState()->safe_ints), mode (Data::FLAT), returns_data (returns_data) -#line 77 "./src/objects/statement.lzz" - { - assert(db != NULL); - assert(handle != NULL); - assert(db->GetState()->open); - assert(!db->GetState()->busy); - db->AddStatement(this); -} -#line 85 "./src/objects/statement.lzz" -void Statement::JS_new (v8::FunctionCallbackInfo const & info) -#line 85 "./src/objects/statement.lzz" - { - Addon * addon = static_cast < Addon * > ( info . Data ( ) . As < v8 :: External > ( ) -> Value ( ) ) ; - if (!addon->privileged_info) { - return ThrowTypeError("Statements can only be constructed by the db.prepare() method"); - } - assert(info.IsConstructCall()); - Database* db = node :: ObjectWrap :: Unwrap (addon->privileged_info->This()); - if ( ! db -> GetState ( ) -> open ) return ThrowTypeError ( "The database connection is not open" ) ; - if ( db -> GetState ( ) -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - - v8::Local source = (*addon->privileged_info)[0].As(); - v8::Local database = (*addon->privileged_info)[1].As(); - bool pragmaMode = (*addon->privileged_info)[2].As()->Value(); - int flags = SQLITE_PREPARE_PERSISTENT; - - if (pragmaMode) { - if ( ! db -> GetState ( ) -> unsafe_mode ) { if ( db -> GetState ( ) -> iterators ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; } ( ( void ) 0 ) ; - flags = 0; - } - - v8 :: Isolate * isolate = info . GetIsolate ( ) ; - v8::String::Utf8Value utf8(isolate, source); - sqlite3_stmt* handle; - const char* tail; - - if (sqlite3_prepare_v3(db->GetHandle(), *utf8, utf8.length() + 1, flags, &handle, &tail) != SQLITE_OK) { - return db->ThrowDatabaseError(); - } - if (handle == NULL) { - return ThrowRangeError("The supplied SQL string contains no statements"); - } - - for (char c; (c = *tail); ) { - if (IS_SKIPPED(c)) { - ++tail; - continue; - } - if (c == '/' && tail[1] == '*') { - tail += 2; - for (char c; (c = *tail); ++tail) { - if (c == '*' && tail[1] == '/') { - tail += 2; - break; - } - } - } else if (c == '-' && tail[1] == '-') { - tail += 2; - for (char c; (c = *tail); ++tail) { - if (c == '\n') { - ++tail; - break; - } - } - } else { - sqlite3_finalize(handle); - return ThrowRangeError("The supplied SQL string contains more than one statement"); - } - } - - v8 :: Local < v8 :: Context > ctx = isolate -> GetCurrentContext ( ) ; - bool returns_data = sqlite3_column_count(handle) >= 1 || pragmaMode; - Statement* stmt = new Statement(db, handle, addon->NextId(), returns_data); - stmt->Wrap(info.This()); - SetFrozen(isolate, ctx, info.This(), addon->cs.reader, v8::Boolean::New(isolate, returns_data)); - SetFrozen(isolate, ctx, info.This(), addon->cs.readonly, v8::Boolean::New(isolate, sqlite3_stmt_readonly(handle) != 0)); - SetFrozen(isolate, ctx, info.This(), addon->cs.source, source); - SetFrozen(isolate, ctx, info.This(), addon->cs.database, database); - - info.GetReturnValue().Set(info.This()); -} -#line 156 "./src/objects/statement.lzz" -void Statement::JS_run (v8::FunctionCallbackInfo const & info) -#line 156 "./src/objects/statement.lzz" - { - Statement * stmt = node :: ObjectWrap :: Unwrap < Statement > ( info . This ( ) ) ; ( ( void ) 0 ) ; sqlite3_stmt * handle = stmt -> handle ; Database * db = stmt -> db ; if ( ! db -> GetState ( ) -> open ) return ThrowTypeError ( "The database connection is not open" ) ; if ( db -> GetState ( ) -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; if ( stmt -> locked ) return ThrowTypeError ( "This statement is busy executing a query" ) ; if ( ! db -> GetState ( ) -> unsafe_mode ) { if ( db -> GetState ( ) -> iterators ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; } ( ( void ) 0 ) ; const bool bound = stmt -> bound ; if ( ! bound ) { Binder binder ( handle ) ; if ( ! binder . Bind ( info , info . Length ( ) , stmt ) ) { sqlite3_clear_bindings ( handle ) ; return ; } ( ( void ) 0 ) ; } else if ( info . Length ( ) > 0 ) { return ThrowTypeError ( "This statement already has bound parameters" ) ; } ( ( void ) 0 ) ; db -> GetState ( ) -> busy = true ; v8 :: Isolate * isolate = info . GetIsolate ( ) ; if ( db -> Log ( isolate , handle ) ) { db -> GetState ( ) -> busy = false ; db -> ThrowDatabaseError ( ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ; } ( ( void ) 0 ) ; - sqlite3* db_handle = db->GetHandle(); - int total_changes_before = sqlite3_total_changes(db_handle); - - sqlite3_step(handle); - if (sqlite3_reset(handle) == SQLITE_OK) { - int changes = sqlite3_total_changes(db_handle) == total_changes_before ? 0 : sqlite3_changes(db_handle); - sqlite3_int64 id = sqlite3_last_insert_rowid(db_handle); - Addon* addon = db->GetAddon(); - v8 :: Local < v8 :: Context > ctx = isolate -> GetCurrentContext ( ) ; - v8::Local result = v8::Object::New(isolate); - result->Set(ctx, addon->cs.changes.Get(isolate), v8::Int32::New(isolate, changes)).FromJust(); - result->Set(ctx, addon->cs.lastInsertRowid.Get(isolate), - stmt->safe_ints - ? v8::BigInt::New(isolate, id).As() - : v8::Number::New(isolate, (double)id).As() - ).FromJust(); - db -> GetState ( ) -> busy = false ; info . GetReturnValue ( ) . Set ( result ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ; - } - db -> GetState ( ) -> busy = false ; db -> ThrowDatabaseError ( ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ; -} -#line 179 "./src/objects/statement.lzz" -void Statement::JS_get (v8::FunctionCallbackInfo const & info) -#line 179 "./src/objects/statement.lzz" - { - Statement * stmt = node :: ObjectWrap :: Unwrap < Statement > ( info . This ( ) ) ; if ( ! stmt -> returns_data ) return ThrowTypeError ( "This statement does not return data. Use run() instead" ) ; sqlite3_stmt * handle = stmt -> handle ; Database * db = stmt -> db ; if ( ! db -> GetState ( ) -> open ) return ThrowTypeError ( "The database connection is not open" ) ; if ( db -> GetState ( ) -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; if ( stmt -> locked ) return ThrowTypeError ( "This statement is busy executing a query" ) ; const bool bound = stmt -> bound ; if ( ! bound ) { Binder binder ( handle ) ; if ( ! binder . Bind ( info , info . Length ( ) , stmt ) ) { sqlite3_clear_bindings ( handle ) ; return ; } ( ( void ) 0 ) ; } else if ( info . Length ( ) > 0 ) { return ThrowTypeError ( "This statement already has bound parameters" ) ; } ( ( void ) 0 ) ; db -> GetState ( ) -> busy = true ; v8 :: Isolate * isolate = info . GetIsolate ( ) ; if ( db -> Log ( isolate , handle ) ) { db -> GetState ( ) -> busy = false ; db -> ThrowDatabaseError ( ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ; } ( ( void ) 0 ) ; - int status = sqlite3_step(handle); - if (status == SQLITE_ROW) { - v8::Local result = Data::GetRowJS(isolate, isolate -> GetCurrentContext ( ) , handle, stmt->safe_ints, stmt->mode); - sqlite3_reset(handle); - db -> GetState ( ) -> busy = false ; info . GetReturnValue ( ) . Set ( result ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ; - } else if (status == SQLITE_DONE) { - sqlite3_reset(handle); - db -> GetState ( ) -> busy = false ; info . GetReturnValue ( ) . Set ( v8 :: Undefined ( isolate ) ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ; - } - sqlite3_reset(handle); - db -> GetState ( ) -> busy = false ; db -> ThrowDatabaseError ( ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ; -} -#line 194 "./src/objects/statement.lzz" -void Statement::JS_all (v8::FunctionCallbackInfo const & info) -#line 194 "./src/objects/statement.lzz" - { - Statement * stmt = node :: ObjectWrap :: Unwrap < Statement > ( info . This ( ) ) ; if ( ! stmt -> returns_data ) return ThrowTypeError ( "This statement does not return data. Use run() instead" ) ; sqlite3_stmt * handle = stmt -> handle ; Database * db = stmt -> db ; if ( ! db -> GetState ( ) -> open ) return ThrowTypeError ( "The database connection is not open" ) ; if ( db -> GetState ( ) -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; if ( stmt -> locked ) return ThrowTypeError ( "This statement is busy executing a query" ) ; const bool bound = stmt -> bound ; if ( ! bound ) { Binder binder ( handle ) ; if ( ! binder . Bind ( info , info . Length ( ) , stmt ) ) { sqlite3_clear_bindings ( handle ) ; return ; } ( ( void ) 0 ) ; } else if ( info . Length ( ) > 0 ) { return ThrowTypeError ( "This statement already has bound parameters" ) ; } ( ( void ) 0 ) ; db -> GetState ( ) -> busy = true ; v8 :: Isolate * isolate = info . GetIsolate ( ) ; if ( db -> Log ( isolate , handle ) ) { db -> GetState ( ) -> busy = false ; db -> ThrowDatabaseError ( ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ; } ( ( void ) 0 ) ; - v8 :: Local < v8 :: Context > ctx = isolate -> GetCurrentContext ( ) ; - v8::Local result = v8::Array::New(isolate, 0); - uint32_t row_count = 0; - const bool safe_ints = stmt->safe_ints; - const char mode = stmt->mode; - bool js_error = false; - - while (sqlite3_step(handle) == SQLITE_ROW) { - if (row_count == 0xffffffff) { ThrowRangeError("Array overflow (too many rows returned)"); js_error = true; break; } - result->Set(ctx, row_count++, Data::GetRowJS(isolate, ctx, handle, safe_ints, mode)).FromJust(); - } - - if (sqlite3_reset(handle) == SQLITE_OK && !js_error) { - db -> GetState ( ) -> busy = false ; info . GetReturnValue ( ) . Set ( result ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ; - } - if (js_error) db->GetState()->was_js_error = true; - db -> GetState ( ) -> busy = false ; db -> ThrowDatabaseError ( ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ; -} -#line 215 "./src/objects/statement.lzz" -void Statement::JS_iterate (v8::FunctionCallbackInfo const & info) -#line 215 "./src/objects/statement.lzz" - { - Addon * addon = static_cast < Addon * > ( info . Data ( ) . As < v8 :: External > ( ) -> Value ( ) ) ; - v8 :: Isolate * isolate = info . GetIsolate ( ) ; - v8::Local c = addon->StatementIterator.Get(isolate); - addon->privileged_info = &info; - v8::MaybeLocal maybeIterator = c->NewInstance( isolate -> GetCurrentContext ( ) , 0, NULL); - addon->privileged_info = NULL; - if (!maybeIterator.IsEmpty()) info.GetReturnValue().Set(maybeIterator.ToLocalChecked()); -} -#line 225 "./src/objects/statement.lzz" -void Statement::JS_bind (v8::FunctionCallbackInfo const & info) -#line 225 "./src/objects/statement.lzz" - { - Statement* stmt = node :: ObjectWrap :: Unwrap (info.This()); - if (stmt->bound) return ThrowTypeError("The bind() method can only be invoked once per statement object"); - if ( ! stmt -> db -> GetState ( ) -> open ) return ThrowTypeError ( "The database connection is not open" ) ; - if ( stmt -> db -> GetState ( ) -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if ( stmt -> locked ) return ThrowTypeError ( "This statement is busy executing a query" ) ; - Binder binder ( stmt -> handle ) ; if ( ! binder . Bind ( info , info . Length ( ) , stmt ) ) { sqlite3_clear_bindings ( stmt -> handle ) ; return ; } ( ( void ) 0 ) ; - stmt->bound = true; - info.GetReturnValue().Set(info.This()); -} -#line 236 "./src/objects/statement.lzz" -void Statement::JS_pluck (v8::FunctionCallbackInfo const & info) -#line 236 "./src/objects/statement.lzz" - { - Statement* stmt = node :: ObjectWrap :: Unwrap (info.This()); - if (!stmt->returns_data) return ThrowTypeError("The pluck() method is only for statements that return data"); - if ( stmt -> db -> GetState ( ) -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if ( stmt -> locked ) return ThrowTypeError ( "This statement is busy executing a query" ) ; - bool use = true; - if (info.Length() != 0) { if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a boolean" ) ; use = ( info [ 0 ] . As < v8 :: Boolean > ( ) ) -> Value ( ) ; } - stmt->mode = use ? Data::PLUCK : stmt->mode == Data::PLUCK ? Data::FLAT : stmt->mode; - info.GetReturnValue().Set(info.This()); -} -#line 247 "./src/objects/statement.lzz" -void Statement::JS_expand (v8::FunctionCallbackInfo const & info) -#line 247 "./src/objects/statement.lzz" - { - Statement* stmt = node :: ObjectWrap :: Unwrap (info.This()); - if (!stmt->returns_data) return ThrowTypeError("The expand() method is only for statements that return data"); - if ( stmt -> db -> GetState ( ) -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if ( stmt -> locked ) return ThrowTypeError ( "This statement is busy executing a query" ) ; - bool use = true; - if (info.Length() != 0) { if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a boolean" ) ; use = ( info [ 0 ] . As < v8 :: Boolean > ( ) ) -> Value ( ) ; } - stmt->mode = use ? Data::EXPAND : stmt->mode == Data::EXPAND ? Data::FLAT : stmt->mode; - info.GetReturnValue().Set(info.This()); -} -#line 258 "./src/objects/statement.lzz" -void Statement::JS_raw (v8::FunctionCallbackInfo const & info) -#line 258 "./src/objects/statement.lzz" - { - Statement* stmt = node :: ObjectWrap :: Unwrap (info.This()); - if (!stmt->returns_data) return ThrowTypeError("The raw() method is only for statements that return data"); - if ( stmt -> db -> GetState ( ) -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if ( stmt -> locked ) return ThrowTypeError ( "This statement is busy executing a query" ) ; - bool use = true; - if (info.Length() != 0) { if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a boolean" ) ; use = ( info [ 0 ] . As < v8 :: Boolean > ( ) ) -> Value ( ) ; } - stmt->mode = use ? Data::RAW : stmt->mode == Data::RAW ? Data::FLAT : stmt->mode; - info.GetReturnValue().Set(info.This()); -} -#line 269 "./src/objects/statement.lzz" -void Statement::JS_safeIntegers (v8::FunctionCallbackInfo const & info) -#line 269 "./src/objects/statement.lzz" - { - Statement* stmt = node :: ObjectWrap :: Unwrap (info.This()); - if ( stmt -> db -> GetState ( ) -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if ( stmt -> locked ) return ThrowTypeError ( "This statement is busy executing a query" ) ; - if (info.Length() == 0) stmt->safe_ints = true; - else { if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a boolean" ) ; stmt -> safe_ints = ( info [ 0 ] . As < v8 :: Boolean > ( ) ) -> Value ( ) ; } - info.GetReturnValue().Set(info.This()); -} -#line 278 "./src/objects/statement.lzz" -void Statement::JS_columns (v8::FunctionCallbackInfo const & info) -#line 278 "./src/objects/statement.lzz" - { - Statement* stmt = node :: ObjectWrap :: Unwrap (info.This()); - if (!stmt->returns_data) return ThrowTypeError("The columns() method is only for statements that return data"); - if ( ! stmt -> db -> GetState ( ) -> open ) return ThrowTypeError ( "The database connection is not open" ) ; - if ( stmt -> db -> GetState ( ) -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - Addon* addon = stmt->db->GetAddon(); - v8 :: Isolate * isolate = info . GetIsolate ( ) ; - v8 :: Local < v8 :: Context > ctx = isolate -> GetCurrentContext ( ) ; - - int column_count = sqlite3_column_count(stmt->handle); - v8::Local columns = v8::Array::New(isolate); - - v8::Local name = addon->cs.name.Get(isolate); - v8::Local columnName = addon->cs.column.Get(isolate); - v8::Local tableName = addon->cs.table.Get(isolate); - v8::Local databaseName = addon->cs.database.Get(isolate); - v8::Local typeName = addon->cs.type.Get(isolate); - - for (int i = 0; i < column_count; ++i) { - v8::Local column = v8::Object::New(isolate); - - column->Set(ctx, name, - InternalizedFromUtf8OrNull(isolate, sqlite3_column_name(stmt->handle, i), -1) - ).FromJust(); - column->Set(ctx, columnName, - InternalizedFromUtf8OrNull(isolate, sqlite3_column_origin_name(stmt->handle, i), -1) - ).FromJust(); - column->Set(ctx, tableName, - InternalizedFromUtf8OrNull(isolate, sqlite3_column_table_name(stmt->handle, i), -1) - ).FromJust(); - column->Set(ctx, databaseName, - InternalizedFromUtf8OrNull(isolate, sqlite3_column_database_name(stmt->handle, i), -1) - ).FromJust(); - column->Set(ctx, typeName, - InternalizedFromUtf8OrNull(isolate, sqlite3_column_decltype(stmt->handle, i), -1) - ).FromJust(); - - columns->Set(ctx, i, column).FromJust(); - } - - info.GetReturnValue().Set(columns); -} -#line 321 "./src/objects/statement.lzz" -void Statement::JS_busy (v8::Local _, v8::PropertyCallbackInfo const & info) -#line 321 "./src/objects/statement.lzz" - { - Statement* stmt = node :: ObjectWrap :: Unwrap (info.This()); - info.GetReturnValue().Set(stmt->alive && stmt->locked); -} -#line 4 "./src/objects/statement-iterator.lzz" -v8::Local StatementIterator::Init (v8::Isolate * isolate, v8::Local data) -#line 4 "./src/objects/statement-iterator.lzz" - { - v8::Local t = NewConstructorTemplate(isolate, data, JS_new, "StatementIterator"); - SetPrototypeMethod(isolate, data, t, "next", JS_next); - SetPrototypeMethod(isolate, data, t, "return", JS_return); - SetPrototypeSymbolMethod(isolate, data, t, v8::Symbol::GetIterator(isolate), JS_symbolIterator); - return t->GetFunction( isolate -> GetCurrentContext ( ) ).ToLocalChecked(); -} -#line 15 "./src/objects/statement-iterator.lzz" -StatementIterator::~ StatementIterator () -#line 15 "./src/objects/statement-iterator.lzz" - {} -#line 19 "./src/objects/statement-iterator.lzz" -StatementIterator::StatementIterator (Statement * stmt, bool bound) -#line 19 "./src/objects/statement-iterator.lzz" - : node::ObjectWrap (), stmt (stmt), handle (stmt->handle), db_state (stmt->db->GetState()), bound (bound), safe_ints (stmt->safe_ints), mode (stmt->mode), alive (true), logged (!db_state->has_logger) -#line 27 "./src/objects/statement-iterator.lzz" - { - assert(stmt != NULL); - assert(handle != NULL); - assert(stmt->bound == bound); - assert(stmt->alive == true); - assert(stmt->locked == false); - assert(db_state->iterators < USHRT_MAX); - stmt->locked = true; - db_state->iterators += 1; -} -#line 38 "./src/objects/statement-iterator.lzz" -void StatementIterator::JS_new (v8::FunctionCallbackInfo const & info) -#line 38 "./src/objects/statement-iterator.lzz" - { - Addon * addon = static_cast < Addon * > ( info . Data ( ) . As < v8 :: External > ( ) -> Value ( ) ) ; - if (!addon->privileged_info) return ThrowTypeError("Disabled constructor"); - assert(info.IsConstructCall()); - - StatementIterator* iter; - { - const v8 :: FunctionCallbackInfo < v8 :: Value > & info = *addon->privileged_info; - Statement * stmt = node :: ObjectWrap :: Unwrap < Statement > ( info . This ( ) ) ; if ( ! stmt -> returns_data ) return ThrowTypeError ( "This statement does not return data. Use run() instead" ) ; sqlite3_stmt * handle = stmt -> handle ; Database * db = stmt -> db ; if ( ! db -> GetState ( ) -> open ) return ThrowTypeError ( "The database connection is not open" ) ; if ( db -> GetState ( ) -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; if ( stmt -> locked ) return ThrowTypeError ( "This statement is busy executing a query" ) ; if ( db -> GetState ( ) -> iterators == USHRT_MAX ) return ThrowRangeError ( "Too many active database iterators" ) ; const bool bound = stmt -> bound ; if ( ! bound ) { Binder binder ( handle ) ; if ( ! binder . Bind ( info , info . Length ( ) , stmt ) ) { sqlite3_clear_bindings ( handle ) ; return ; } ( ( void ) 0 ) ; } else if ( info . Length ( ) > 0 ) { return ThrowTypeError ( "This statement already has bound parameters" ) ; } ( ( void ) 0 ) ; - iter = new StatementIterator(stmt, bound); - } - v8 :: Isolate * isolate = info . GetIsolate ( ) ; - v8 :: Local < v8 :: Context > ctx = isolate -> GetCurrentContext ( ) ; - iter->Wrap(info.This()); - SetFrozen(isolate, ctx, info.This(), addon->cs.statement, addon->privileged_info->This()); - - info.GetReturnValue().Set(info.This()); -} -#line 57 "./src/objects/statement-iterator.lzz" -void StatementIterator::JS_next (v8::FunctionCallbackInfo const & info) -#line 57 "./src/objects/statement-iterator.lzz" - { - StatementIterator* iter = node :: ObjectWrap :: Unwrap (info.This()); - if ( iter -> db_state -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if (iter->alive) iter->Next(info); - else info.GetReturnValue().Set(DoneRecord( info . GetIsolate ( ) , iter->db_state->addon)); -} -#line 64 "./src/objects/statement-iterator.lzz" -void StatementIterator::JS_return (v8::FunctionCallbackInfo const & info) -#line 64 "./src/objects/statement-iterator.lzz" - { - StatementIterator* iter = node :: ObjectWrap :: Unwrap (info.This()); - if ( iter -> db_state -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if (iter->alive) iter->Return(info); - else info.GetReturnValue().Set(DoneRecord( info . GetIsolate ( ) , iter->db_state->addon)); -} -#line 71 "./src/objects/statement-iterator.lzz" -void StatementIterator::JS_symbolIterator (v8::FunctionCallbackInfo const & info) -#line 71 "./src/objects/statement-iterator.lzz" - { - info.GetReturnValue().Set(info.This()); -} -#line 75 "./src/objects/statement-iterator.lzz" -void StatementIterator::Next (v8::FunctionCallbackInfo const & info) -#line 75 "./src/objects/statement-iterator.lzz" - { - assert(alive == true); - db_state->busy = true; - if (!logged) { - logged = true; - if (stmt->db->Log( info . GetIsolate ( ) , handle)) { - db_state->busy = false; - Throw(); - return; - } - } - int status = sqlite3_step(handle); - db_state->busy = false; - if (status == SQLITE_ROW) { - v8 :: Isolate * isolate = info . GetIsolate ( ) ; - v8 :: Local < v8 :: Context > ctx = isolate -> GetCurrentContext ( ) ; - info.GetReturnValue().Set( - NewRecord(isolate, ctx, Data::GetRowJS(isolate, ctx, handle, safe_ints, mode), db_state->addon, false) - ); - } else { - if (status == SQLITE_DONE) Return(info); - else Throw(); - } -} -#line 100 "./src/objects/statement-iterator.lzz" -void StatementIterator::Return (v8::FunctionCallbackInfo const & info) -#line 100 "./src/objects/statement-iterator.lzz" - { - Cleanup(); - info . GetReturnValue ( ) . Set ( DoneRecord ( info . GetIsolate ( ) , db_state -> addon ) ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ; -} -#line 105 "./src/objects/statement-iterator.lzz" -void StatementIterator::Throw () -#line 105 "./src/objects/statement-iterator.lzz" - { - Cleanup(); - Database* db = stmt->db; - db -> ThrowDatabaseError ( ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ; -} -#line 111 "./src/objects/statement-iterator.lzz" -void StatementIterator::Cleanup () -#line 111 "./src/objects/statement-iterator.lzz" - { - assert(alive == true); - alive = false; - stmt->locked = false; - db_state->iterators -= 1; - sqlite3_reset(handle); -} -#line 4 "./src/objects/backup.lzz" -v8::Local Backup::Init (v8::Isolate * isolate, v8::Local data) -#line 4 "./src/objects/backup.lzz" - { - v8::Local t = NewConstructorTemplate(isolate, data, JS_new, "Backup"); - SetPrototypeMethod(isolate, data, t, "transfer", JS_transfer); - SetPrototypeMethod(isolate, data, t, "close", JS_close); - return t->GetFunction( isolate -> GetCurrentContext ( ) ).ToLocalChecked(); -} -#line 17 "./src/objects/backup.lzz" -void Backup::CloseHandles () -#line 17 "./src/objects/backup.lzz" - { - if (alive) { - alive = false; - std::string filename(sqlite3_db_filename(dest_handle, "main")); - sqlite3_backup_finish(backup_handle); - int status = sqlite3_close(dest_handle); - assert(status == SQLITE_OK); ((void)status); - if (unlink) remove(filename.c_str()); - } -} -#line 28 "./src/objects/backup.lzz" -Backup::~ Backup () -#line 28 "./src/objects/backup.lzz" - { - if (alive) db->RemoveBackup(this); - CloseHandles(); -} -#line 35 "./src/objects/backup.lzz" -Backup::Backup (Database * db, sqlite3 * dest_handle, sqlite3_backup * backup_handle, sqlite3_uint64 id, bool unlink) -#line 41 "./src/objects/backup.lzz" - : node::ObjectWrap (), db (db), dest_handle (dest_handle), backup_handle (backup_handle), id (id), alive (true), unlink (unlink) -#line 48 "./src/objects/backup.lzz" - { - assert(db != NULL); - assert(dest_handle != NULL); - assert(backup_handle != NULL); - db->AddBackup(this); -} -#line 55 "./src/objects/backup.lzz" -void Backup::JS_new (v8::FunctionCallbackInfo const & info) -#line 55 "./src/objects/backup.lzz" - { - Addon * addon = static_cast < Addon * > ( info . Data ( ) . As < v8 :: External > ( ) -> Value ( ) ) ; - if (!addon->privileged_info) return ThrowTypeError("Disabled constructor"); - assert(info.IsConstructCall()); - Database* db = node :: ObjectWrap :: Unwrap (addon->privileged_info->This()); - if ( ! db -> GetState ( ) -> open ) return ThrowTypeError ( "The database connection is not open" ) ; - if ( db -> GetState ( ) -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; +#include "better_sqlite3_impl.hpp" - v8::Local database = (*addon->privileged_info)[0].As(); - v8::Local attachedName = (*addon->privileged_info)[1].As(); - v8::Local destFile = (*addon->privileged_info)[2].As(); - bool unlink = (*addon->privileged_info)[3].As()->Value(); - - v8 :: Isolate * isolate = info . GetIsolate ( ) ; - sqlite3* dest_handle; - v8::String::Utf8Value dest_file(isolate, destFile); - v8::String::Utf8Value attached_name(isolate, attachedName); - int mask = (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); - - if (sqlite3_open_v2(*dest_file, &dest_handle, mask, NULL) != SQLITE_OK) { - Database::ThrowSqliteError(addon, dest_handle); - int status = sqlite3_close(dest_handle); - assert(status == SQLITE_OK); ((void)status); - return; - } - - sqlite3_extended_result_codes(dest_handle, 1); - sqlite3_limit(dest_handle, SQLITE_LIMIT_LENGTH, INT_MAX); - sqlite3_backup* backup_handle = sqlite3_backup_init(dest_handle, "main", db->GetHandle(), *attached_name); - if (backup_handle == NULL) { - Database::ThrowSqliteError(addon, dest_handle); - int status = sqlite3_close(dest_handle); - assert(status == SQLITE_OK); ((void)status); - return; - } - - Backup* backup = new Backup(db, dest_handle, backup_handle, addon->NextId(), unlink); - backup->Wrap(info.This()); - SetFrozen(isolate, isolate -> GetCurrentContext ( ) , info.This(), addon->cs.database, database); - - info.GetReturnValue().Set(info.This()); +// Addon implementation +void Addon::Cleanup(void *ptr) { + Addon *addon = static_cast(ptr); + for (Database *db : addon->dbs) + db->CloseHandles(); + addon->dbs.clear(); + delete addon; } -#line 98 "./src/objects/backup.lzz" -void Backup::JS_transfer (v8::FunctionCallbackInfo const & info) -#line 98 "./src/objects/backup.lzz" - { - Backup* backup = node :: ObjectWrap :: Unwrap (info.This()); - if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsInt32 ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a 32-bit signed integer" ) ; int pages = ( info [ 0 ] . As < v8 :: Int32 > ( ) ) -> Value ( ) ; - if ( ! backup -> db -> GetState ( ) -> open ) return ThrowTypeError ( "The database connection is not open" ) ; - assert(backup->db->GetState()->busy == false); - assert(backup->alive == true); - sqlite3_backup* backup_handle = backup->backup_handle; - int status = sqlite3_backup_step(backup_handle, pages) & 0xff; +Addon::Addon(v8::Isolate *isolate) + : privileged_info(NULL), next_id(0), cs(isolate) {} - Addon* addon = backup->db->GetAddon(); - if (status == SQLITE_OK || status == SQLITE_DONE || status == SQLITE_BUSY) { - int total_pages = sqlite3_backup_pagecount(backup_handle); - int remaining_pages = sqlite3_backup_remaining(backup_handle); - v8 :: Isolate * isolate = info . GetIsolate ( ) ; - v8 :: Local < v8 :: Context > ctx = isolate -> GetCurrentContext ( ) ; - v8::Local result = v8::Object::New(isolate); - result->Set(ctx, addon->cs.totalPages.Get(isolate), v8::Int32::New(isolate, total_pages)).FromJust(); - result->Set(ctx, addon->cs.remainingPages.Get(isolate), v8::Int32::New(isolate, remaining_pages)).FromJust(); - info.GetReturnValue().Set(result); - if (status == SQLITE_DONE) backup->unlink = false; - } else { - Database::ThrowSqliteError(addon, sqlite3_errstr(status), status); - } -} -#line 124 "./src/objects/backup.lzz" -void Backup::JS_close (v8::FunctionCallbackInfo const & info) -#line 124 "./src/objects/backup.lzz" - { - Backup* backup = node :: ObjectWrap :: Unwrap (info.This()); - assert(backup->db->GetState()->busy == false); - if (backup->alive) backup->db->RemoveBackup(backup); - backup->CloseHandles(); - info.GetReturnValue().Set(info.This()); -} -#line 4 "./src/util/data-converter.lzz" -void DataConverter::ThrowDataConversionError (sqlite3_context * invocation, bool isBigInt) -#line 4 "./src/util/data-converter.lzz" - { - if (isBigInt) { - ThrowRangeError((GetDataErrorPrefix() + " a bigint that was too big").c_str()); - } else { - ThrowTypeError((GetDataErrorPrefix() + " an invalid value").c_str()); - } - PropagateJSError(invocation); -} -#line 4 "./src/util/custom-function.lzz" -CustomFunction::CustomFunction (v8::Isolate * isolate, Database * db, char const * name, v8::Local fn, bool safe_ints) -#line 10 "./src/util/custom-function.lzz" - : name (name), db (db), isolate (isolate), fn (isolate, fn), safe_ints (safe_ints) -#line 15 "./src/util/custom-function.lzz" - {} -#line 17 "./src/util/custom-function.lzz" -CustomFunction::~ CustomFunction () -#line 17 "./src/util/custom-function.lzz" - {} -#line 19 "./src/util/custom-function.lzz" -void CustomFunction::xDestroy (void * self) -#line 19 "./src/util/custom-function.lzz" - { - delete static_cast(self); -} -#line 23 "./src/util/custom-function.lzz" -void CustomFunction::xFunc (sqlite3_context * invocation, int argc, sqlite3_value * * argv) -#line 23 "./src/util/custom-function.lzz" - { - CustomFunction * self = static_cast < CustomFunction * > ( sqlite3_user_data ( invocation ) ) ; v8 :: Isolate * isolate = self -> isolate ; v8 :: HandleScope scope ( isolate ) ; - - v8::Local args_fast[4]; - v8::Local* args = NULL; - if (argc != 0) { - args = argc <= 4 ? args_fast : ALLOC_ARRAY>(argc); - Data::GetArgumentsJS(isolate, args, argv, argc, self->safe_ints); - } - - v8::MaybeLocal maybeReturnValue = self->fn.Get(isolate)->Call( isolate -> GetCurrentContext ( ) , v8::Undefined(isolate), argc, args); - if (args != args_fast) delete[] args; - - if (maybeReturnValue.IsEmpty()) self->PropagateJSError(invocation); - else Data::ResultValueFromJS(isolate, invocation, maybeReturnValue.ToLocalChecked(), self); -} -#line 42 "./src/util/custom-function.lzz" -void CustomFunction::PropagateJSError (sqlite3_context * invocation) -#line 42 "./src/util/custom-function.lzz" - { - assert(db->GetState()->was_js_error == false); - db->GetState()->was_js_error = true; - sqlite3_result_error(invocation, "", 0); -} -#line 48 "./src/util/custom-function.lzz" -std::string CustomFunction::GetDataErrorPrefix () -#line 48 "./src/util/custom-function.lzz" - { - return std::string("User-defined function ") + name + "() returned"; -} -#line 4 "./src/util/custom-aggregate.lzz" -CustomAggregate::CustomAggregate (v8::Isolate * isolate, Database * db, char const * name, v8::Local start, v8::Local step, v8::Local inverse, v8::Local result, bool safe_ints) -#line 13 "./src/util/custom-aggregate.lzz" - : CustomFunction (isolate, db, name, step, safe_ints), invoke_result (result->IsFunction()), invoke_start (start->IsFunction()), inverse (isolate, inverse->IsFunction() ? inverse.As() : v8::Local()), result (isolate, result->IsFunction() ? result.As() : v8::Local()), start (isolate, start) -#line 19 "./src/util/custom-aggregate.lzz" - {} -#line 21 "./src/util/custom-aggregate.lzz" -void CustomAggregate::xStep (sqlite3_context * invocation, int argc, sqlite3_value * * argv) -#line 21 "./src/util/custom-aggregate.lzz" - { - xStepBase(invocation, argc, argv, &CustomAggregate::fn); -} -#line 25 "./src/util/custom-aggregate.lzz" -void CustomAggregate::xInverse (sqlite3_context * invocation, int argc, sqlite3_value * * argv) -#line 25 "./src/util/custom-aggregate.lzz" - { - xStepBase(invocation, argc, argv, &CustomAggregate::inverse); -} -#line 29 "./src/util/custom-aggregate.lzz" -void CustomAggregate::xValue (sqlite3_context * invocation) -#line 29 "./src/util/custom-aggregate.lzz" - { - xValueBase(invocation, false); -} -#line 33 "./src/util/custom-aggregate.lzz" -void CustomAggregate::xFinal (sqlite3_context * invocation) -#line 33 "./src/util/custom-aggregate.lzz" - { - xValueBase(invocation, true); -} -#line 88 "./src/util/custom-aggregate.lzz" -CustomAggregate::Accumulator * CustomAggregate::GetAccumulator (sqlite3_context * invocation) -#line 88 "./src/util/custom-aggregate.lzz" - { - Accumulator* acc = static_cast(sqlite3_aggregate_context(invocation, sizeof(Accumulator))); - if (!acc->initialized) { - assert(acc->value.IsEmpty()); - acc->initialized = true; - if (invoke_start) { - v8::MaybeLocal maybeSeed = start.Get(isolate).As()->Call( isolate -> GetCurrentContext ( ) , v8::Undefined(isolate), 0, NULL); - if (maybeSeed.IsEmpty()) PropagateJSError(invocation); - else acc->value.Reset(isolate, maybeSeed.ToLocalChecked()); - } else { - assert(!start.IsEmpty()); - acc->value.Reset(isolate, start); - } - } - return acc; -} -#line 105 "./src/util/custom-aggregate.lzz" -void CustomAggregate::DestroyAccumulator (sqlite3_context * invocation) -#line 105 "./src/util/custom-aggregate.lzz" - { - Accumulator* acc = static_cast(sqlite3_aggregate_context(invocation, sizeof(Accumulator))); - assert(acc->initialized); - acc->value.Reset(); -} -#line 111 "./src/util/custom-aggregate.lzz" -void CustomAggregate::PropagateJSError (sqlite3_context * invocation) -#line 111 "./src/util/custom-aggregate.lzz" - { - DestroyAccumulator(invocation); - CustomFunction::PropagateJSError(invocation); -} -#line 4 "./src/util/custom-table.lzz" -CustomTable::CustomTable (v8::Isolate * isolate, Database * db, char const * name, v8::Local factory) -#line 9 "./src/util/custom-table.lzz" - : addon (db->GetAddon()), isolate (isolate), db (db), name (name), factory (isolate, factory) -#line 14 "./src/util/custom-table.lzz" - {} -#line 16 "./src/util/custom-table.lzz" -void CustomTable::Destructor (void * self) -#line 16 "./src/util/custom-table.lzz" - { - delete static_cast(self); -} -#line 20 "./src/util/custom-table.lzz" -sqlite3_module CustomTable::MODULE = { - 0, - xCreate, - xConnect, - xBestIndex, - xDisconnect, - xDisconnect, - xOpen, - xClose, - xFilter, - xNext, - xEof, - xColumn, - xRowid, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL - }; -#line 47 "./src/util/custom-table.lzz" -sqlite3_module CustomTable::EPONYMOUS_MODULE = { - 0, - NULL, - xConnect, - xBestIndex, - xDisconnect, - xDisconnect, - xOpen, - xClose, - xFilter, - xNext, - xEof, - xColumn, - xRowid, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL - }; -#line 78 "./src/util/custom-table.lzz" -CustomTable::VTab::VTab (CustomTable * parent, v8::Local generator, std::vector parameter_names, bool safe_ints) -#line 83 "./src/util/custom-table.lzz" - : parent (parent), parameter_count (parameter_names.size()), safe_ints (safe_ints), generator (parent->isolate, generator), parameter_names (parameter_names) -#line 88 "./src/util/custom-table.lzz" - { - ((void)base); +void Addon::JS_setErrorConstructor( + v8::FunctionCallbackInfo const &info) { + v8::Local e = v8::Local::Cast(info[0]); + assert(info[0]->IsFunction()); + Addon *addon = + static_cast(v8::Local::Cast(info.Data())->Value()); + addon->SqliteError.Reset(info.GetIsolate(), e); } -#line 132 "./src/util/custom-table.lzz" -CustomTable::TempDataConverter::TempDataConverter (CustomTable * parent) -#line 132 "./src/util/custom-table.lzz" - : parent (parent), status (SQLITE_OK) -#line 134 "./src/util/custom-table.lzz" - {} -#line 136 "./src/util/custom-table.lzz" -void CustomTable::TempDataConverter::PropagateJSError (sqlite3_context * invocation) -#line 136 "./src/util/custom-table.lzz" - { - status = SQLITE_ERROR; - parent->PropagateJSError(); -} -#line 141 "./src/util/custom-table.lzz" -std::string CustomTable::TempDataConverter::GetDataErrorPrefix () -#line 141 "./src/util/custom-table.lzz" - { - return std::string("Virtual table module \"") + parent->name + "\" yielded"; -} -#line 151 "./src/util/custom-table.lzz" -int CustomTable::xCreate (sqlite3 * db_handle, void * _self, int argc, char const * const * argv, sqlite3_vtab * * output, char * * errOutput) -#line 151 "./src/util/custom-table.lzz" - { - return xConnect(db_handle, _self, argc, argv, output, errOutput); -} -#line 156 "./src/util/custom-table.lzz" -int CustomTable::xConnect (sqlite3 * db_handle, void * _self, int argc, char const * const * argv, sqlite3_vtab * * output, char * * errOutput) -#line 156 "./src/util/custom-table.lzz" - { - CustomTable* self = static_cast(_self); - v8::Isolate* isolate = self->isolate; - v8::HandleScope scope(isolate); - v8 :: Local < v8 :: Context > ctx = isolate -> GetCurrentContext ( ) ; - - v8::Local* args = ALLOC_ARRAY>(argc); - for (int i = 0; i < argc; ++i) { - args[i] = StringFromUtf8(isolate, argv[i], -1); - } - - - v8::MaybeLocal maybeReturnValue = self->factory.Get(isolate)->Call(ctx, v8::Undefined(isolate), argc, args); - delete[] args; - - if (maybeReturnValue.IsEmpty()) { - self->PropagateJSError(); - return SQLITE_ERROR; - } - - - v8::Local returnValue = maybeReturnValue.ToLocalChecked().As(); - v8::Local sqlString = returnValue->Get(ctx, 0).ToLocalChecked().As(); - v8::Local generator = returnValue->Get(ctx, 1).ToLocalChecked().As(); - v8::Local parameterNames = returnValue->Get(ctx, 2).ToLocalChecked().As(); - int safe_ints = returnValue->Get(ctx, 3).ToLocalChecked().As()->Value(); - bool direct_only = returnValue->Get(ctx, 4).ToLocalChecked().As()->Value(); - - v8::String::Utf8Value sql(isolate, sqlString); - safe_ints = safe_ints < 2 ? safe_ints : static_cast(self->db->GetState()->safe_ints); - - - std::vector parameter_names; - for (int i = 0, len = parameterNames->Length(); i < len; ++i) { - v8::Local parameterName = parameterNames->Get(ctx, i).ToLocalChecked().As(); - v8::String::Utf8Value parameter_name(isolate, parameterName); - parameter_names.emplace_back(*parameter_name); - } - - - if (sqlite3_declare_vtab(db_handle, *sql) != SQLITE_OK) { - *errOutput = sqlite3_mprintf("failed to declare virtual table \"%s\"", argv[2]); - return SQLITE_ERROR; - } - if (direct_only && sqlite3_vtab_config(db_handle, SQLITE_VTAB_DIRECTONLY) != SQLITE_OK) { - *errOutput = sqlite3_mprintf("failed to configure virtual table \"%s\"", argv[2]); - return SQLITE_ERROR; - } - - - *output = (new VTab(self, generator, parameter_names, safe_ints))->Downcast(); - return SQLITE_OK; -} -#line 210 "./src/util/custom-table.lzz" -int CustomTable::xDisconnect (sqlite3_vtab * vtab) -#line 210 "./src/util/custom-table.lzz" - { - delete VTab::Upcast(vtab); - return SQLITE_OK; -} -#line 215 "./src/util/custom-table.lzz" -int CustomTable::xOpen (sqlite3_vtab * vtab, sqlite3_vtab_cursor * * output) -#line 215 "./src/util/custom-table.lzz" - { - *output = (new Cursor())->Downcast(); - return SQLITE_OK; -} -#line 220 "./src/util/custom-table.lzz" -int CustomTable::xClose (sqlite3_vtab_cursor * cursor) -#line 220 "./src/util/custom-table.lzz" - { - delete Cursor::Upcast(cursor); - return SQLITE_OK; -} -#line 228 "./src/util/custom-table.lzz" -int CustomTable::xFilter (sqlite3_vtab_cursor * _cursor, int idxNum, char const * idxStr, int argc, sqlite3_value * * argv) -#line 228 "./src/util/custom-table.lzz" - { - Cursor* cursor = Cursor::Upcast(_cursor); - VTab* vtab = cursor->GetVTab(); - CustomTable* self = vtab->parent; - Addon* addon = self->addon; - v8::Isolate* isolate = self->isolate; - v8::HandleScope scope(isolate); - v8 :: Local < v8 :: Context > ctx = isolate -> GetCurrentContext ( ) ; - - - - v8::Local args_fast[4]; - v8::Local* args = NULL; - int parameter_count = vtab->parameter_count; - if (parameter_count != 0) { - args = parameter_count <= 4 ? args_fast : ALLOC_ARRAY>(parameter_count); - int argn = 0; - bool safe_ints = vtab->safe_ints; - for (int i = 0; i < parameter_count; ++i) { - if (idxNum & 1 << i) { - args[i] = Data::GetValueJS(isolate, argv[argn++], safe_ints); - - - if (args[i]->IsNull()) { - if (args != args_fast) delete[] args; - cursor->done = true; - return SQLITE_OK; - } - } else { - args[i] = v8::Undefined(isolate); - } - } - } - - - v8::MaybeLocal maybeIterator = vtab->generator.Get(isolate)->Call(ctx, v8::Undefined(isolate), parameter_count, args); - if (args != args_fast) delete[] args; - - if (maybeIterator.IsEmpty()) { - self->PropagateJSError(); - return SQLITE_ERROR; - } - - - v8::Local iterator = maybeIterator.ToLocalChecked().As(); - v8::Local next = iterator->Get(ctx, addon->cs.next.Get(isolate)).ToLocalChecked().As(); - cursor->iterator.Reset(isolate, iterator); - cursor->next.Reset(isolate, next); - cursor->rowid = 0; - - return xNext(cursor->Downcast()); -} -#line 284 "./src/util/custom-table.lzz" -int CustomTable::xNext (sqlite3_vtab_cursor * _cursor) -#line 284 "./src/util/custom-table.lzz" - { - Cursor* cursor = Cursor::Upcast(_cursor); - CustomTable* self = cursor->GetVTab()->parent; - Addon* addon = self->addon; - v8::Isolate* isolate = self->isolate; - v8::HandleScope scope(isolate); - v8 :: Local < v8 :: Context > ctx = isolate -> GetCurrentContext ( ) ; - - v8::Local iterator = cursor->iterator.Get(isolate); - v8::Local next = cursor->next.Get(isolate); - - v8::MaybeLocal maybeRecord = next->Call(ctx, iterator, 0, NULL); - if (maybeRecord.IsEmpty()) { - self->PropagateJSError(); - return SQLITE_ERROR; - } - - v8::Local record = maybeRecord.ToLocalChecked().As(); - bool done = record->Get(ctx, addon->cs.done.Get(isolate)).ToLocalChecked().As()->Value(); - if (!done) { - cursor->row.Reset(isolate, record->Get(ctx, addon->cs.value.Get(isolate)).ToLocalChecked().As()); - } - cursor->done = done; - cursor->rowid += 1; - - return SQLITE_OK; -} -#line 313 "./src/util/custom-table.lzz" -int CustomTable::xEof (sqlite3_vtab_cursor * cursor) -#line 313 "./src/util/custom-table.lzz" - { - return Cursor::Upcast(cursor)->done; -} -#line 318 "./src/util/custom-table.lzz" -int CustomTable::xColumn (sqlite3_vtab_cursor * _cursor, sqlite3_context * invocation, int column) -#line 318 "./src/util/custom-table.lzz" - { - Cursor* cursor = Cursor::Upcast(_cursor); - CustomTable* self = cursor->GetVTab()->parent; - TempDataConverter temp_data_converter(self); - v8::Isolate* isolate = self->isolate; - v8::HandleScope scope(isolate); - - v8::Local row = cursor->row.Get(isolate); - v8::MaybeLocal maybeColumnValue = row->Get( isolate -> GetCurrentContext ( ) , column); - if (maybeColumnValue.IsEmpty()) { - temp_data_converter.PropagateJSError(NULL); - } else { - Data::ResultValueFromJS(isolate, invocation, maybeColumnValue.ToLocalChecked(), &temp_data_converter); - } - return temp_data_converter.status; -} -#line 336 "./src/util/custom-table.lzz" -int CustomTable::xRowid (sqlite3_vtab_cursor * cursor, sqlite_int64 * output) -#line 336 "./src/util/custom-table.lzz" - { - *output = Cursor::Upcast(cursor)->rowid; - return SQLITE_OK; -} -#line 343 "./src/util/custom-table.lzz" -int CustomTable::xBestIndex (sqlite3_vtab * vtab, sqlite3_index_info * output) -#line 343 "./src/util/custom-table.lzz" - { - int parameter_count = VTab::Upcast(vtab)->parameter_count; - int argument_count = 0; - std::vector> forwarded; - - for (int i = 0, len = output->nConstraint; i < len; ++i) { - auto item = output->aConstraint[i]; - - - - - - if (item.op == SQLITE_INDEX_CONSTRAINT_LIMIT || item.op == SQLITE_INDEX_CONSTRAINT_OFFSET) { - continue; - } - - if (item.iColumn >= 0 && item.iColumn < parameter_count) { - if (item.op != SQLITE_INDEX_CONSTRAINT_EQ) { - sqlite3_free(vtab->zErrMsg); - vtab->zErrMsg = sqlite3_mprintf( - "virtual table parameter \"%s\" can only be constrained by the '=' operator", - VTab::Upcast(vtab)->parameter_names.at(item.iColumn).c_str()); - return SQLITE_ERROR; - } - if (!item.usable) { - - - - return SQLITE_CONSTRAINT; - } - forwarded.emplace_back(item.iColumn, i); - } - } - - - std::sort(forwarded.begin(), forwarded.end()); - for (std::pair pair : forwarded) { - int bit = 1 << pair.first; - if (!(output->idxNum & bit)) { - output->idxNum |= bit; - output->aConstraintUsage[pair.second].argvIndex = ++argument_count; - output->aConstraintUsage[pair.second].omit = 1; - } - } - - - - output->estimatedCost = output->estimatedRows = 1000000000 / (argument_count + 1); - return SQLITE_OK; -} -#line 394 "./src/util/custom-table.lzz" -void CustomTable::PropagateJSError () -#line 394 "./src/util/custom-table.lzz" - { - assert(db->GetState()->was_js_error == false); - db->GetState()->was_js_error = true; -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 72 "./src/util/data.lzz" - v8::Local GetValueJS (v8::Isolate * isolate, sqlite3_stmt * handle, int column, bool safe_ints) -#line 72 "./src/util/data.lzz" - { - switch ( sqlite3_column_type ( handle , column ) ) { case SQLITE_INTEGER : if ( safe_ints ) { return v8 :: BigInt :: New ( isolate , sqlite3_column_int64 ( handle , column ) ) ; } case SQLITE_FLOAT : return v8 :: Number :: New ( isolate , sqlite3_column_double ( handle , column ) ) ; case SQLITE_TEXT : return StringFromUtf8 ( isolate , reinterpret_cast < const char * > ( sqlite3_column_text ( handle , column ) ) , sqlite3_column_bytes ( handle , column ) ) ; case SQLITE_BLOB : return node :: Buffer :: Copy ( isolate , static_cast < const char * > ( sqlite3_column_blob ( handle , column ) ) , sqlite3_column_bytes ( handle , column ) ) . ToLocalChecked ( ) ; default : assert ( sqlite3_column_type ( handle , column ) == SQLITE_NULL ) ; return v8 :: Null ( isolate ) ; } assert ( false ) ; ; - } -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 76 "./src/util/data.lzz" - v8::Local GetValueJS (v8::Isolate * isolate, sqlite3_value * value, bool safe_ints) -#line 76 "./src/util/data.lzz" - { - switch ( sqlite3_value_type ( value ) ) { case SQLITE_INTEGER : if ( safe_ints ) { return v8 :: BigInt :: New ( isolate , sqlite3_value_int64 ( value ) ) ; } case SQLITE_FLOAT : return v8 :: Number :: New ( isolate , sqlite3_value_double ( value ) ) ; case SQLITE_TEXT : return StringFromUtf8 ( isolate , reinterpret_cast < const char * > ( sqlite3_value_text ( value ) ) , sqlite3_value_bytes ( value ) ) ; case SQLITE_BLOB : return node :: Buffer :: Copy ( isolate , static_cast < const char * > ( sqlite3_value_blob ( value ) ) , sqlite3_value_bytes ( value ) ) . ToLocalChecked ( ) ; default : assert ( sqlite3_value_type ( value ) == SQLITE_NULL ) ; return v8 :: Null ( isolate ) ; } assert ( false ) ; ; - } -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 80 "./src/util/data.lzz" - v8::Local GetFlatRowJS (v8::Isolate * isolate, v8::Local ctx, sqlite3_stmt * handle, bool safe_ints) -#line 80 "./src/util/data.lzz" - { - v8::Local row = v8::Object::New(isolate); - int column_count = sqlite3_column_count(handle); - for (int i = 0; i < column_count; ++i) { - row->Set(ctx, - InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1), - Data::GetValueJS(isolate, handle, i, safe_ints)).FromJust(); - } - return row; - } -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 91 "./src/util/data.lzz" - v8::Local GetExpandedRowJS (v8::Isolate * isolate, v8::Local ctx, sqlite3_stmt * handle, bool safe_ints) -#line 91 "./src/util/data.lzz" - { - v8::Local row = v8::Object::New(isolate); - int column_count = sqlite3_column_count(handle); - for (int i = 0; i < column_count; ++i) { - const char* table_raw = sqlite3_column_table_name(handle, i); - v8::Local table = InternalizedFromUtf8(isolate, table_raw == NULL ? "$" : table_raw, -1); - v8::Local column = InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1); - v8::Local value = Data::GetValueJS(isolate, handle, i, safe_ints); - if (row->HasOwnProperty(ctx, table).FromJust()) { - row->Get(ctx, table).ToLocalChecked().As()->Set(ctx, column, value).FromJust(); - } else { - v8::Local nested = v8::Object::New(isolate); - row->Set(ctx, table, nested).FromJust(); - nested->Set(ctx, column, value).FromJust(); - } - } - return row; - } -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 110 "./src/util/data.lzz" - v8::Local GetRawRowJS (v8::Isolate * isolate, v8::Local ctx, sqlite3_stmt * handle, bool safe_ints) -#line 110 "./src/util/data.lzz" - { - v8::Local row = v8::Array::New(isolate); - int column_count = sqlite3_column_count(handle); - for (int i = 0; i < column_count; ++i) { - row->Set(ctx, i, Data::GetValueJS(isolate, handle, i, safe_ints)).FromJust(); - } - return row; - } -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 119 "./src/util/data.lzz" - v8::Local GetRowJS (v8::Isolate * isolate, v8::Local ctx, sqlite3_stmt * handle, bool safe_ints, char mode) -#line 119 "./src/util/data.lzz" - { - if (mode == FLAT) return GetFlatRowJS(isolate, ctx, handle, safe_ints); - if (mode == PLUCK) return GetValueJS(isolate, handle, 0, safe_ints); - if (mode == EXPAND) return GetExpandedRowJS(isolate, ctx, handle, safe_ints); - if (mode == RAW) return GetRawRowJS(isolate, ctx, handle, safe_ints); - assert(false); - return v8::Local(); - } -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 128 "./src/util/data.lzz" - void GetArgumentsJS (v8::Isolate * isolate, v8::Local * out, sqlite3_value * * values, int argument_count, bool safe_ints) -#line 128 "./src/util/data.lzz" - { - assert(argument_count > 0); - for (int i = 0; i < argument_count; ++i) { - out[i] = Data::GetValueJS(isolate, values[i], safe_ints); - } - } -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 135 "./src/util/data.lzz" - int BindValueFromJS (v8::Isolate * isolate, sqlite3_stmt * handle, int index, v8::Local value) -#line 135 "./src/util/data.lzz" - { - if ( value -> IsNumber ( ) ) { return sqlite3_bind_double ( handle , index , value . As < v8 :: Number > ( ) -> Value ( ) ) ; } else if ( value -> IsBigInt ( ) ) { bool lossless ; int64_t v = value . As < v8 :: BigInt > ( ) -> Int64Value ( & lossless ) ; if ( lossless ) { return sqlite3_bind_int64 ( handle , index , v ) ; } } else if ( value -> IsString ( ) ) { v8 :: String :: Utf8Value utf8 ( isolate , value . As < v8 :: String > ( ) ) ; return sqlite3_bind_text ( handle , index , * utf8 , utf8 . length ( ) , SQLITE_TRANSIENT ) ; } else if ( node :: Buffer :: HasInstance ( value ) ) { const char * data = node :: Buffer :: Data ( value ) ; return sqlite3_bind_blob ( handle , index , data ? data : "" , node :: Buffer :: Length ( value ) , SQLITE_TRANSIENT ) ; } else if ( value -> IsNull ( ) || value -> IsUndefined ( ) ) { return sqlite3_bind_null ( handle , index ) ; } ; - return value->IsBigInt() ? SQLITE_TOOBIG : -1; - } -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 140 "./src/util/data.lzz" - void ResultValueFromJS (v8::Isolate * isolate, sqlite3_context * invocation, v8::Local value, DataConverter * converter) -#line 140 "./src/util/data.lzz" - { - if ( value -> IsNumber ( ) ) { return sqlite3_result_double ( invocation , value . As < v8 :: Number > ( ) -> Value ( ) ) ; } else if ( value -> IsBigInt ( ) ) { bool lossless ; int64_t v = value . As < v8 :: BigInt > ( ) -> Int64Value ( & lossless ) ; if ( lossless ) { return sqlite3_result_int64 ( invocation , v ) ; } } else if ( value -> IsString ( ) ) { v8 :: String :: Utf8Value utf8 ( isolate , value . As < v8 :: String > ( ) ) ; return sqlite3_result_text ( invocation , * utf8 , utf8 . length ( ) , SQLITE_TRANSIENT ) ; } else if ( node :: Buffer :: HasInstance ( value ) ) { const char * data = node :: Buffer :: Data ( value ) ; return sqlite3_result_blob ( invocation , data ? data : "" , node :: Buffer :: Length ( value ) , SQLITE_TRANSIENT ) ; } else if ( value -> IsNull ( ) || value -> IsUndefined ( ) ) { return sqlite3_result_null ( invocation ) ; } ; - converter->ThrowDataConversionError(invocation, value->IsBigInt()); - } -} -#line 4 "./src/util/binder.lzz" -Binder::Binder (sqlite3_stmt * _handle) -#line 4 "./src/util/binder.lzz" - { - handle = _handle; - param_count = sqlite3_bind_parameter_count(_handle); - anon_index = 0; - success = true; -} -#line 11 "./src/util/binder.lzz" -bool Binder::Bind (v8::FunctionCallbackInfo const & info, int argc, Statement * stmt) -#line 11 "./src/util/binder.lzz" - { - assert(anon_index == 0); - Result result = BindArgs(info, argc, stmt); - if (success && result.count != param_count) { - if (result.count < param_count) { - if (!result.bound_object && stmt->GetBindMap( info . GetIsolate ( ) )->GetSize()) { - Fail(ThrowTypeError, "Missing named parameters"); - } else { - Fail(ThrowRangeError, "Too few parameter values were provided"); - } - } else { - Fail(ThrowRangeError, "Too many parameter values were provided"); - } - } - return success; -} -#line 55 "./src/util/binder.lzz" -void Binder::Fail (void (* Throw) (char const *), char const * message) -#line 55 "./src/util/binder.lzz" - { - assert(success == true); - assert((Throw == NULL) == (message == NULL)); - assert(Throw == ThrowError || Throw == ThrowTypeError || Throw == ThrowRangeError || Throw == NULL); - if (Throw) Throw(message); - success = false; -} -#line 63 "./src/util/binder.lzz" -int Binder::NextAnonIndex () -#line 63 "./src/util/binder.lzz" - { - while (sqlite3_bind_parameter_name(handle, ++anon_index) != NULL) {} - return anon_index; -} -#line 69 "./src/util/binder.lzz" -void Binder::BindValue (v8::Isolate * isolate, v8::Local value, int index) -#line 69 "./src/util/binder.lzz" - { - int status = Data::BindValueFromJS(isolate, handle, index, value); - if (status != SQLITE_OK) { - switch (status) { - case -1: - return Fail(ThrowTypeError, "SQLite3 can only bind numbers, strings, bigints, buffers, and null"); - case SQLITE_TOOBIG: - return Fail(ThrowRangeError, "The bound string, buffer, or bigint is too big"); - case SQLITE_RANGE: - return Fail(ThrowRangeError, "Too many parameter values were provided"); - case SQLITE_NOMEM: - return Fail(ThrowError, "Out of memory"); - default: - return Fail(ThrowError, "An unexpected error occured while trying to bind parameters"); - } - assert(false); - } -} -#line 90 "./src/util/binder.lzz" -int Binder::BindArray (v8::Isolate * isolate, v8::Local arr) -#line 90 "./src/util/binder.lzz" - { - v8 :: Local < v8 :: Context > ctx = isolate -> GetCurrentContext ( ) ; - uint32_t length = arr->Length(); - if (length > INT_MAX) { - Fail(ThrowRangeError, "Too many parameter values were provided"); - return 0; - } - int len = static_cast(length); - for (int i = 0; i < len; ++i) { - v8::MaybeLocal maybeValue = arr->Get(ctx, i); - if (maybeValue.IsEmpty()) { - Fail(NULL, NULL); - return i; - } - BindValue(isolate, maybeValue.ToLocalChecked(), NextAnonIndex()); - if (!success) { - return i; - } - } - return len; -} -#line 116 "./src/util/binder.lzz" -int Binder::BindObject (v8::Isolate * isolate, v8::Local obj, Statement * stmt) -#line 116 "./src/util/binder.lzz" - { - v8 :: Local < v8 :: Context > ctx = isolate -> GetCurrentContext ( ) ; - BindMap* bind_map = stmt->GetBindMap(isolate); - BindMap::Pair* pairs = bind_map->GetPairs(); - int len = bind_map->GetSize(); - - for (int i = 0; i < len; ++i) { - v8::Local key = pairs[i].GetName(isolate); - - - v8::Maybe has_property = obj->HasOwnProperty(ctx, key); - if (has_property.IsNothing()) { - Fail(NULL, NULL); - return i; - } - if (!has_property.FromJust()) { - v8::String::Utf8Value param_name(isolate, key); - Fail(ThrowRangeError, (std::string("Missing named parameter \"") + *param_name + "\"").c_str()); - return i; - } - - - v8::MaybeLocal maybeValue = obj->Get(ctx, key); - if (maybeValue.IsEmpty()) { - Fail(NULL, NULL); - return i; - } - - BindValue(isolate, maybeValue.ToLocalChecked(), pairs[i].GetIndex()); - if (!success) { - return i; - } - } - - return len; -} -#line 160 "./src/util/binder.lzz" -Binder::Result Binder::BindArgs (v8::FunctionCallbackInfo const & info, int argc, Statement * stmt) -#line 160 "./src/util/binder.lzz" - { - v8 :: Isolate * isolate = info . GetIsolate ( ) ; - int count = 0; - bool bound_object = false; - - for (int i = 0; i < argc; ++i) { - v8::Local arg = info[i]; - - if (arg->IsArray()) { - count += BindArray(isolate, arg.As()); - if (!success) break; - continue; - } - - if (arg->IsObject() && !node::Buffer::HasInstance(arg)) { - v8::Local obj = arg.As(); - if (IsPlainObject(isolate, obj)) { - if (bound_object) { - Fail(ThrowTypeError, "You cannot specify named parameters in two different objects"); - break; - } - bound_object = true; - - count += BindObject(isolate, obj, stmt); - if (!success) break; - continue; - } else if (stmt->GetBindMap(isolate)->GetSize()) { - Fail(ThrowTypeError, "Named parameters can only be passed within plain objects"); - break; - } - } - - BindValue(isolate, arg, NextAnonIndex()); - if (!success) break; - count += 1; - } - - return { count, bound_object }; -} -#line 35 "./src/better_sqlite3.lzz" -void Addon::JS_setErrorConstructor (v8::FunctionCallbackInfo const & info) -#line 35 "./src/better_sqlite3.lzz" - { - if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsFunction ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a function" ) ; v8 :: Local < v8 :: Function > SqliteError = ( info [ 0 ] . As < v8 :: Function > ( ) ) ; - static_cast < Addon * > ( info . Data ( ) . As < v8 :: External > ( ) -> Value ( ) ) ->SqliteError.Reset( info . GetIsolate ( ) , SqliteError); -} -#line 40 "./src/better_sqlite3.lzz" -void Addon::Cleanup (void * ptr) -#line 40 "./src/better_sqlite3.lzz" - { - Addon* addon = static_cast(ptr); - for (Database* db : addon->dbs) db->CloseHandles(); - addon->dbs.clear(); - delete addon; +NODE_MODULE_INIT(/* exports, context */) { + v8::Isolate *isolate = context->GetIsolate(); + v8::HandleScope scope(isolate); + + // Initialize addon instance. + Addon *addon = new Addon(isolate); + v8::Local data = v8::External::New(isolate, addon); + node::AddEnvironmentCleanupHook(isolate, Addon::Cleanup, addon); + + // Create and export native-backed classes and functions. + exports + ->Set(context, InternalizedFromLatin1(isolate, "Database"), + Database::Init(isolate, data)) + .FromJust(); + exports + ->Set(context, InternalizedFromLatin1(isolate, "Statement"), + Statement::Init(isolate, data)) + .FromJust(); + exports + ->Set(context, InternalizedFromLatin1(isolate, "StatementIterator"), + StatementIterator::Init(isolate, data)) + .FromJust(); + exports + ->Set(context, InternalizedFromLatin1(isolate, "Backup"), + Backup::Init(isolate, data)) + .FromJust(); + exports + ->Set(context, InternalizedFromLatin1(isolate, "setErrorConstructor"), + v8::FunctionTemplate::New(isolate, Addon::JS_setErrorConstructor, + data) + ->GetFunction(context) + .ToLocalChecked()) + .FromJust(); + + // Store addon instance data. + addon->Statement.Reset( + isolate, + exports->Get(context, InternalizedFromLatin1(isolate, "Statement")) + .ToLocalChecked() + .As()); + addon->StatementIterator.Reset( + isolate, + exports + ->Get(context, InternalizedFromLatin1(isolate, "StatementIterator")) + .ToLocalChecked() + .As()); + addon->Backup.Reset( + isolate, exports->Get(context, InternalizedFromLatin1(isolate, "Backup")) + .ToLocalChecked() + .As()); } -#line 47 "./src/better_sqlite3.lzz" -Addon::Addon (v8::Isolate * isolate) -#line 47 "./src/better_sqlite3.lzz" - : privileged_info (NULL), next_id (0), cs (isolate) -#line 50 "./src/better_sqlite3.lzz" - {} -#undef LZZ_INLINE diff --git a/src/better_sqlite3.hpp b/src/better_sqlite3.hpp index 27ae83b0e..18ab73b1c 100644 --- a/src/better_sqlite3.hpp +++ b/src/better_sqlite3.hpp @@ -1,1036 +1,49 @@ -// better_sqlite3.hpp -// +#ifndef BETTER_SQLITE3_HPP +#define BETTER_SQLITE3_HPP -#ifndef LZZ_BETTER_SQLITE3_better_sqlite3_hpp -#define LZZ_BETTER_SQLITE3_better_sqlite3_hpp -#line 2 "./src/better_sqlite3.lzz" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#line 145 "./src/util/macros.lzz" -void SetPrototypeGetter( - v8::Isolate* isolate, - v8::Local data, - v8::Local recv, - const char* name, - v8::AccessorNameGetterCallback func -); -#line 36 "./src/util/binder.lzz" - static bool IsPlainObject(v8::Isolate* isolate, v8::Local obj); -#define LZZ_INLINE inline -#line 20 "./src/util/macros.lzz" -v8::Local StringFromUtf8 (v8::Isolate * isolate, char const * data, int length); -#line 23 "./src/util/macros.lzz" -v8::Local InternalizedFromUtf8 (v8::Isolate * isolate, char const * data, int length); -#line 26 "./src/util/macros.lzz" -v8::Local InternalizedFromUtf8OrNull (v8::Isolate * isolate, char const * data, int length); -#line 30 "./src/util/macros.lzz" -v8::Local InternalizedFromLatin1 (v8::Isolate * isolate, char const * str); -#line 34 "./src/util/macros.lzz" -void SetFrozen (v8::Isolate * isolate, v8::Local ctx, v8::Local obj, v8::Global & key, v8::Local value); -#line 38 "./src/util/macros.lzz" -void ThrowError (char const * message); -#line 39 "./src/util/macros.lzz" -void ThrowTypeError (char const * message); -#line 40 "./src/util/macros.lzz" -void ThrowRangeError (char const * message); -#line 92 "./src/util/macros.lzz" -bool IS_SKIPPED (char c); -#line 97 "./src/util/macros.lzz" -template -#line 97 "./src/util/macros.lzz" -T * ALLOC_ARRAY (size_t count); -#line 102 "./src/util/macros.lzz" -template -#line 102 "./src/util/macros.lzz" -void FREE_ARRAY (T * array_pointer); -#line 106 "./src/util/macros.lzz" -v8::Local NewConstructorTemplate (v8::Isolate * isolate, v8::Local data, v8::FunctionCallback func, char const * name); -#line 117 "./src/util/macros.lzz" -void SetPrototypeMethod (v8::Isolate * isolate, v8::Local data, v8::Local recv, char const * name, v8::FunctionCallback func); -#line 130 "./src/util/macros.lzz" -void SetPrototypeSymbolMethod (v8::Isolate * isolate, v8::Local data, v8::Local recv, v8::Local symbol, v8::FunctionCallback func); -#line 1 "./src/util/constants.lzz" -class CS -{ -#line 2 "./src/util/constants.lzz" -public: -#line 4 "./src/util/constants.lzz" - v8::Local Code (v8::Isolate * isolate, int code); -#line 10 "./src/util/constants.lzz" - explicit CS (v8::Isolate * isolate); -#line 140 "./src/util/constants.lzz" - v8::Global database; -#line 141 "./src/util/constants.lzz" - v8::Global reader; -#line 142 "./src/util/constants.lzz" - v8::Global source; -#line 143 "./src/util/constants.lzz" - v8::Global memory; -#line 144 "./src/util/constants.lzz" - v8::Global readonly; -#line 145 "./src/util/constants.lzz" - v8::Global name; -#line 146 "./src/util/constants.lzz" - v8::Global next; -#line 147 "./src/util/constants.lzz" - v8::Global length; -#line 148 "./src/util/constants.lzz" - v8::Global done; -#line 149 "./src/util/constants.lzz" - v8::Global value; -#line 150 "./src/util/constants.lzz" - v8::Global changes; -#line 151 "./src/util/constants.lzz" - v8::Global lastInsertRowid; -#line 152 "./src/util/constants.lzz" - v8::Global statement; -#line 153 "./src/util/constants.lzz" - v8::Global column; -#line 154 "./src/util/constants.lzz" - v8::Global table; -#line 155 "./src/util/constants.lzz" - v8::Global type; -#line 156 "./src/util/constants.lzz" - v8::Global totalPages; -#line 157 "./src/util/constants.lzz" - v8::Global remainingPages; -#line 159 "./src/util/constants.lzz" -private: -#line 161 "./src/util/constants.lzz" - static void SetString (v8::Isolate * isolate, v8::Global & constant, char const * str); -#line 165 "./src/util/constants.lzz" - void SetCode (v8::Isolate * isolate, int code, char const * str); -#line 171 "./src/util/constants.lzz" - std::unordered_map > codes; -}; -#line 1 "./src/util/bind-map.lzz" -class BindMap -{ -#line 2 "./src/util/bind-map.lzz" -public: -#line 6 "./src/util/bind-map.lzz" - class Pair - { -#line 6 "./src/util/bind-map.lzz" - friend class BindMap; -#line 7 "./src/util/bind-map.lzz" - public: -#line 9 "./src/util/bind-map.lzz" - int GetIndex (); -#line 13 "./src/util/bind-map.lzz" - v8::Local GetName (v8::Isolate * isolate); -#line 17 "./src/util/bind-map.lzz" - private: -#line 19 "./src/util/bind-map.lzz" - explicit Pair (v8::Isolate * isolate, char const * name, int index); -#line 22 "./src/util/bind-map.lzz" - explicit Pair (v8::Isolate * isolate, Pair * pair); -#line 25 "./src/util/bind-map.lzz" - v8::Global const name; -#line 26 "./src/util/bind-map.lzz" - int const index; - }; -#line 29 "./src/util/bind-map.lzz" - explicit BindMap (char _); -#line 36 "./src/util/bind-map.lzz" - ~ BindMap (); -#line 41 "./src/util/bind-map.lzz" - Pair * GetPairs (); -#line 45 "./src/util/bind-map.lzz" - int GetSize (); -#line 50 "./src/util/bind-map.lzz" - void Add (v8::Isolate * isolate, char const * name, int index); -#line 56 "./src/util/bind-map.lzz" -private: -#line 58 "./src/util/bind-map.lzz" - void Grow (v8::Isolate * isolate); -#line 70 "./src/util/bind-map.lzz" - Pair * pairs; -#line 71 "./src/util/bind-map.lzz" - int capacity; -#line 72 "./src/util/bind-map.lzz" - int length; -}; -#line 20 "./src/better_sqlite3.lzz" +#include "better_sqlite3_deps.hpp" + +// Forward declarations struct Addon; -#line 21 "./src/better_sqlite3.lzz" class Statement; -#line 22 "./src/better_sqlite3.lzz" class Backup; -#line 1 "./src/objects/database.lzz" -class Database : public node::ObjectWrap -{ -#line 2 "./src/objects/database.lzz" -public: -#line 4 "./src/objects/database.lzz" - static v8::Local Init (v8::Isolate * isolate, v8::Local data); -#line 23 "./src/objects/database.lzz" - class CompareDatabase - { -#line 23 "./src/objects/database.lzz" - public: -#line 24 "./src/objects/database.lzz" - bool operator () (Database const * const a, Database const * const b) const; - }; -#line 28 "./src/objects/database.lzz" - class CompareStatement - { -#line 28 "./src/objects/database.lzz" - public: -#line 29 "./src/objects/database.lzz" - bool operator () (Statement const * const a, Statement const * const b) const; - }; -#line 33 "./src/objects/database.lzz" - class CompareBackup - { -#line 33 "./src/objects/database.lzz" - public: -#line 34 "./src/objects/database.lzz" - bool operator () (Backup const * const a, Backup const * const b) const; - }; -#line 40 "./src/objects/database.lzz" - void ThrowDatabaseError (); -#line 44 "./src/objects/database.lzz" - static void ThrowSqliteError (Addon * addon, sqlite3 * db_handle); -#line 48 "./src/objects/database.lzz" - static void ThrowSqliteError (Addon * addon, char const * message, int code); -#line 64 "./src/objects/database.lzz" - bool Log (v8::Isolate * isolate, sqlite3_stmt * handle); -#line 77 "./src/objects/database.lzz" - void AddStatement (Statement * stmt); -#line 78 "./src/objects/database.lzz" - void RemoveStatement (Statement * stmt); -#line 81 "./src/objects/database.lzz" - void AddBackup (Backup * backup); -#line 82 "./src/objects/database.lzz" - void RemoveBackup (Backup * backup); -#line 86 "./src/objects/database.lzz" - struct State - { -#line 87 "./src/objects/database.lzz" - bool const open; -#line 88 "./src/objects/database.lzz" - bool busy; -#line 89 "./src/objects/database.lzz" - bool const safe_ints; -#line 90 "./src/objects/database.lzz" - bool const unsafe_mode; -#line 91 "./src/objects/database.lzz" - bool was_js_error; -#line 92 "./src/objects/database.lzz" - bool const has_logger; -#line 93 "./src/objects/database.lzz" - unsigned short int iterators; -#line 94 "./src/objects/database.lzz" - Addon * const addon; - }; -#line 96 "./src/objects/database.lzz" - State * GetState (); -#line 99 "./src/objects/database.lzz" - sqlite3 * GetHandle (); -#line 102 "./src/objects/database.lzz" - Addon * GetAddon (); -#line 107 "./src/objects/database.lzz" - void CloseHandles (); -#line 119 "./src/objects/database.lzz" - ~ Database (); -#line 124 "./src/objects/database.lzz" -private: -#line 126 "./src/objects/database.lzz" - explicit Database (v8::Isolate * isolate, Addon * addon, sqlite3 * db_handle, v8::Local logger); -#line 149 "./src/objects/database.lzz" - static void JS_new (v8::FunctionCallbackInfo const & info); -#line 201 "./src/objects/database.lzz" - static void JS_prepare (v8::FunctionCallbackInfo const & info); -#line 217 "./src/objects/database.lzz" - static void JS_exec (v8::FunctionCallbackInfo const & info); -#line 257 "./src/objects/database.lzz" - static void JS_backup (v8::FunctionCallbackInfo const & info); -#line 275 "./src/objects/database.lzz" - static void JS_serialize (v8::FunctionCallbackInfo const & info); -#line 297 "./src/objects/database.lzz" - static void JS_function (v8::FunctionCallbackInfo const & info); -#line 321 "./src/objects/database.lzz" - static void JS_aggregate (v8::FunctionCallbackInfo const & info); -#line 350 "./src/objects/database.lzz" - static void JS_table (v8::FunctionCallbackInfo const & info); -#line 370 "./src/objects/database.lzz" - static void JS_loadExtension (v8::FunctionCallbackInfo const & info); -#line 392 "./src/objects/database.lzz" - static void JS_close (v8::FunctionCallbackInfo const & info); -#line 402 "./src/objects/database.lzz" - static void JS_defaultSafeIntegers (v8::FunctionCallbackInfo const & info); -#line 408 "./src/objects/database.lzz" - static void JS_unsafeMode (v8::FunctionCallbackInfo const & info); -#line 415 "./src/objects/database.lzz" - static void JS_open (v8::Local _, v8::PropertyCallbackInfo const & info); -#line 419 "./src/objects/database.lzz" - static void JS_inTransaction (v8::Local _, v8::PropertyCallbackInfo const & info); -#line 424 "./src/objects/database.lzz" - static bool Deserialize (v8::Local buffer, Addon * addon, sqlite3 * db_handle, bool readonly); -#line 449 "./src/objects/database.lzz" - static void FreeSerialization (char * data, void * _); -#line 453 "./src/objects/database.lzz" - static int const MAX_BUFFER_SIZE = node::Buffer::kMaxLength > INT_MAX ? INT_MAX : static_cast(node::Buffer::kMaxLength); -#line 454 "./src/objects/database.lzz" - static int const MAX_STRING_SIZE = v8::String::kMaxLength > INT_MAX ? INT_MAX : static_cast(v8::String::kMaxLength); -#line 456 "./src/objects/database.lzz" - sqlite3 * const db_handle; -#line 457 "./src/objects/database.lzz" - bool open; -#line 458 "./src/objects/database.lzz" - bool busy; -#line 459 "./src/objects/database.lzz" - bool safe_ints; -#line 460 "./src/objects/database.lzz" - bool unsafe_mode; -#line 461 "./src/objects/database.lzz" - bool was_js_error; -#line 462 "./src/objects/database.lzz" - bool const has_logger; -#line 463 "./src/objects/database.lzz" - unsigned short int iterators; -#line 464 "./src/objects/database.lzz" - Addon * const addon; -#line 465 "./src/objects/database.lzz" - v8::Global const logger; -#line 466 "./src/objects/database.lzz" - std::set stmts; -#line 467 "./src/objects/database.lzz" - std::set backups; -}; -#line 1 "./src/objects/statement.lzz" -class Statement : public node::ObjectWrap -{ -#line 1 "./src/objects/statement.lzz" - friend class StatementIterator; -#line 2 "./src/objects/statement.lzz" -public: -#line 4 "./src/objects/statement.lzz" - static v8::Local Init (v8::Isolate * isolate, v8::Local data); -#line 21 "./src/objects/statement.lzz" - static bool Compare (Statement const * const a, Statement const * const b); -#line 26 "./src/objects/statement.lzz" - BindMap * GetBindMap (v8::Isolate * isolate); -#line 39 "./src/objects/statement.lzz" - void CloseHandles (); -#line 46 "./src/objects/statement.lzz" - ~ Statement (); -#line 52 "./src/objects/statement.lzz" -private: -#line 55 "./src/objects/statement.lzz" - class Extras - { -#line 55 "./src/objects/statement.lzz" - friend class Statement; -#line 56 "./src/objects/statement.lzz" - explicit Extras (sqlite3_uint64 id); -#line 57 "./src/objects/statement.lzz" - BindMap bind_map; -#line 58 "./src/objects/statement.lzz" - sqlite3_uint64 const id; - }; -#line 61 "./src/objects/statement.lzz" - explicit Statement (Database * db, sqlite3_stmt * handle, sqlite3_uint64 id, bool returns_data); -#line 85 "./src/objects/statement.lzz" - static void JS_new (v8::FunctionCallbackInfo const & info); -#line 156 "./src/objects/statement.lzz" - static void JS_run (v8::FunctionCallbackInfo const & info); -#line 179 "./src/objects/statement.lzz" - static void JS_get (v8::FunctionCallbackInfo const & info); -#line 194 "./src/objects/statement.lzz" - static void JS_all (v8::FunctionCallbackInfo const & info); -#line 215 "./src/objects/statement.lzz" - static void JS_iterate (v8::FunctionCallbackInfo const & info); -#line 225 "./src/objects/statement.lzz" - static void JS_bind (v8::FunctionCallbackInfo const & info); -#line 236 "./src/objects/statement.lzz" - static void JS_pluck (v8::FunctionCallbackInfo const & info); -#line 247 "./src/objects/statement.lzz" - static void JS_expand (v8::FunctionCallbackInfo const & info); -#line 258 "./src/objects/statement.lzz" - static void JS_raw (v8::FunctionCallbackInfo const & info); -#line 269 "./src/objects/statement.lzz" - static void JS_safeIntegers (v8::FunctionCallbackInfo const & info); -#line 278 "./src/objects/statement.lzz" - static void JS_columns (v8::FunctionCallbackInfo const & info); -#line 321 "./src/objects/statement.lzz" - static void JS_busy (v8::Local _, v8::PropertyCallbackInfo const & info); -#line 326 "./src/objects/statement.lzz" - Database * const db; -#line 327 "./src/objects/statement.lzz" - sqlite3_stmt * const handle; -#line 328 "./src/objects/statement.lzz" - Extras * const extras; -#line 329 "./src/objects/statement.lzz" - bool alive; -#line 330 "./src/objects/statement.lzz" - bool locked; -#line 331 "./src/objects/statement.lzz" - bool bound; -#line 332 "./src/objects/statement.lzz" - bool has_bind_map; -#line 333 "./src/objects/statement.lzz" - bool safe_ints; -#line 334 "./src/objects/statement.lzz" - char mode; -#line 335 "./src/objects/statement.lzz" - bool const returns_data; -}; -#line 1 "./src/objects/statement-iterator.lzz" -class StatementIterator : public node::ObjectWrap -{ -#line 2 "./src/objects/statement-iterator.lzz" -public: -#line 4 "./src/objects/statement-iterator.lzz" - static v8::Local Init (v8::Isolate * isolate, v8::Local data); -#line 15 "./src/objects/statement-iterator.lzz" - ~ StatementIterator (); -#line 17 "./src/objects/statement-iterator.lzz" -private: -#line 19 "./src/objects/statement-iterator.lzz" - explicit StatementIterator (Statement * stmt, bool bound); -#line 38 "./src/objects/statement-iterator.lzz" - static void JS_new (v8::FunctionCallbackInfo const & info); -#line 57 "./src/objects/statement-iterator.lzz" - static void JS_next (v8::FunctionCallbackInfo const & info); -#line 64 "./src/objects/statement-iterator.lzz" - static void JS_return (v8::FunctionCallbackInfo const & info); -#line 71 "./src/objects/statement-iterator.lzz" - static void JS_symbolIterator (v8::FunctionCallbackInfo const & info); -#line 75 "./src/objects/statement-iterator.lzz" - void Next (v8::FunctionCallbackInfo const & info); -#line 100 "./src/objects/statement-iterator.lzz" - void Return (v8::FunctionCallbackInfo const & info); -#line 105 "./src/objects/statement-iterator.lzz" - void Throw (); -#line 111 "./src/objects/statement-iterator.lzz" - void Cleanup (); -#line 119 "./src/objects/statement-iterator.lzz" - static v8::Local NewRecord (v8::Isolate * isolate, v8::Local ctx, v8::Local value, Addon * addon, bool done); -#line 126 "./src/objects/statement-iterator.lzz" - static v8::Local DoneRecord (v8::Isolate * isolate, Addon * addon); -#line 130 "./src/objects/statement-iterator.lzz" - Statement * const stmt; -#line 131 "./src/objects/statement-iterator.lzz" - sqlite3_stmt * const handle; -#line 132 "./src/objects/statement-iterator.lzz" - Database::State * const db_state; -#line 133 "./src/objects/statement-iterator.lzz" - bool const bound; -#line 134 "./src/objects/statement-iterator.lzz" - bool const safe_ints; -#line 135 "./src/objects/statement-iterator.lzz" - char const mode; -#line 136 "./src/objects/statement-iterator.lzz" - bool alive; -#line 137 "./src/objects/statement-iterator.lzz" - bool logged; -}; -#line 1 "./src/objects/backup.lzz" -class Backup : public node::ObjectWrap -{ -#line 2 "./src/objects/backup.lzz" -public: -#line 4 "./src/objects/backup.lzz" - static v8::Local Init (v8::Isolate * isolate, v8::Local data); -#line 12 "./src/objects/backup.lzz" - static bool Compare (Backup const * const a, Backup const * const b); -#line 17 "./src/objects/backup.lzz" - void CloseHandles (); -#line 28 "./src/objects/backup.lzz" - ~ Backup (); -#line 33 "./src/objects/backup.lzz" -private: -#line 35 "./src/objects/backup.lzz" - explicit Backup (Database * db, sqlite3 * dest_handle, sqlite3_backup * backup_handle, sqlite3_uint64 id, bool unlink); -#line 55 "./src/objects/backup.lzz" - static void JS_new (v8::FunctionCallbackInfo const & info); -#line 98 "./src/objects/backup.lzz" - static void JS_transfer (v8::FunctionCallbackInfo const & info); -#line 124 "./src/objects/backup.lzz" - static void JS_close (v8::FunctionCallbackInfo const & info); -#line 132 "./src/objects/backup.lzz" - Database * const db; -#line 133 "./src/objects/backup.lzz" - sqlite3 * const dest_handle; -#line 134 "./src/objects/backup.lzz" - sqlite3_backup * const backup_handle; -#line 135 "./src/objects/backup.lzz" - sqlite3_uint64 const id; -#line 136 "./src/objects/backup.lzz" - bool alive; -#line 137 "./src/objects/backup.lzz" - bool unlink; -}; -#line 1 "./src/util/data-converter.lzz" -class DataConverter -{ -#line 2 "./src/util/data-converter.lzz" -public: -#line 4 "./src/util/data-converter.lzz" - void ThrowDataConversionError (sqlite3_context * invocation, bool isBigInt); -#line 13 "./src/util/data-converter.lzz" -protected: -#line 15 "./src/util/data-converter.lzz" - virtual void PropagateJSError (sqlite3_context * invocation) = 0; -#line 16 "./src/util/data-converter.lzz" - virtual std::string GetDataErrorPrefix () = 0; -}; -#line 1 "./src/util/custom-function.lzz" -class CustomFunction : protected DataConverter -{ -#line 2 "./src/util/custom-function.lzz" -public: -#line 4 "./src/util/custom-function.lzz" - explicit CustomFunction (v8::Isolate * isolate, Database * db, char const * name, v8::Local fn, bool safe_ints); -#line 17 "./src/util/custom-function.lzz" - virtual ~ CustomFunction (); -#line 19 "./src/util/custom-function.lzz" - static void xDestroy (void * self); -#line 23 "./src/util/custom-function.lzz" - static void xFunc (sqlite3_context * invocation, int argc, sqlite3_value * * argv); -#line 40 "./src/util/custom-function.lzz" -protected: -#line 42 "./src/util/custom-function.lzz" - void PropagateJSError (sqlite3_context * invocation); -#line 48 "./src/util/custom-function.lzz" - std::string GetDataErrorPrefix (); -#line 52 "./src/util/custom-function.lzz" -private: -#line 53 "./src/util/custom-function.lzz" - std::string const name; -#line 54 "./src/util/custom-function.lzz" - Database * const db; -#line 55 "./src/util/custom-function.lzz" -protected: -#line 56 "./src/util/custom-function.lzz" - v8::Isolate * const isolate; -#line 57 "./src/util/custom-function.lzz" - v8::Global const fn; -#line 58 "./src/util/custom-function.lzz" - bool const safe_ints; -}; -#line 1 "./src/util/custom-aggregate.lzz" -class CustomAggregate : public CustomFunction -{ -#line 2 "./src/util/custom-aggregate.lzz" -public: -#line 4 "./src/util/custom-aggregate.lzz" - explicit CustomAggregate (v8::Isolate * isolate, Database * db, char const * name, v8::Local start, v8::Local step, v8::Local inverse, v8::Local result, bool safe_ints); -#line 21 "./src/util/custom-aggregate.lzz" - static void xStep (sqlite3_context * invocation, int argc, sqlite3_value * * argv); -#line 25 "./src/util/custom-aggregate.lzz" - static void xInverse (sqlite3_context * invocation, int argc, sqlite3_value * * argv); -#line 29 "./src/util/custom-aggregate.lzz" - static void xValue (sqlite3_context * invocation); -#line 33 "./src/util/custom-aggregate.lzz" - static void xFinal (sqlite3_context * invocation); -#line 37 "./src/util/custom-aggregate.lzz" -private: -#line 39 "./src/util/custom-aggregate.lzz" - static void xStepBase (sqlite3_context * invocation, int argc, sqlite3_value * * argv, v8::Global const CustomAggregate::* ptrtm); -#line 58 "./src/util/custom-aggregate.lzz" - static void xValueBase (sqlite3_context * invocation, bool is_final); -#line 82 "./src/util/custom-aggregate.lzz" - struct Accumulator - { -#line 82 "./src/util/custom-aggregate.lzz" - public: -#line 83 "./src/util/custom-aggregate.lzz" - v8::Global value; -#line 84 "./src/util/custom-aggregate.lzz" - bool initialized; -#line 85 "./src/util/custom-aggregate.lzz" - bool is_window; - }; -#line 88 "./src/util/custom-aggregate.lzz" - Accumulator * GetAccumulator (sqlite3_context * invocation); -#line 105 "./src/util/custom-aggregate.lzz" - static void DestroyAccumulator (sqlite3_context * invocation); -#line 111 "./src/util/custom-aggregate.lzz" - void PropagateJSError (sqlite3_context * invocation); -#line 116 "./src/util/custom-aggregate.lzz" - bool const invoke_result; -#line 117 "./src/util/custom-aggregate.lzz" - bool const invoke_start; -#line 118 "./src/util/custom-aggregate.lzz" - v8::Global const inverse; -#line 119 "./src/util/custom-aggregate.lzz" - v8::Global const result; -#line 120 "./src/util/custom-aggregate.lzz" - v8::Global const start; -}; -#line 1 "./src/util/custom-table.lzz" -class CustomTable -{ -#line 2 "./src/util/custom-table.lzz" -public: -#line 4 "./src/util/custom-table.lzz" - explicit CustomTable (v8::Isolate * isolate, Database * db, char const * name, v8::Local factory); -#line 16 "./src/util/custom-table.lzz" - static void Destructor (void * self); -#line 20 "./src/util/custom-table.lzz" - static sqlite3_module MODULE; -#line 47 "./src/util/custom-table.lzz" - static sqlite3_module EPONYMOUS_MODULE; -#line 74 "./src/util/custom-table.lzz" -private: -#line 77 "./src/util/custom-table.lzz" - class VTab - { -#line 77 "./src/util/custom-table.lzz" - friend class CustomTable; -#line 78 "./src/util/custom-table.lzz" - explicit VTab (CustomTable * parent, v8::Local generator, std::vector parameter_names, bool safe_ints); -#line 92 "./src/util/custom-table.lzz" - static CustomTable::VTab * Upcast (sqlite3_vtab * vtab); -#line 96 "./src/util/custom-table.lzz" - sqlite3_vtab * Downcast (); -#line 100 "./src/util/custom-table.lzz" - sqlite3_vtab base; -#line 101 "./src/util/custom-table.lzz" - CustomTable * const parent; -#line 102 "./src/util/custom-table.lzz" - int const parameter_count; -#line 103 "./src/util/custom-table.lzz" - bool const safe_ints; -#line 104 "./src/util/custom-table.lzz" - v8::Global const generator; -#line 105 "./src/util/custom-table.lzz" - std::vector const parameter_names; - }; -#line 109 "./src/util/custom-table.lzz" - class Cursor - { -#line 109 "./src/util/custom-table.lzz" - friend class CustomTable; -#line 110 "./src/util/custom-table.lzz" - static CustomTable::Cursor * Upcast (sqlite3_vtab_cursor * cursor); -#line 114 "./src/util/custom-table.lzz" - sqlite3_vtab_cursor * Downcast (); -#line 118 "./src/util/custom-table.lzz" - CustomTable::VTab * GetVTab (); -#line 122 "./src/util/custom-table.lzz" - sqlite3_vtab_cursor base; -#line 123 "./src/util/custom-table.lzz" - v8::Global iterator; -#line 124 "./src/util/custom-table.lzz" - v8::Global next; -#line 125 "./src/util/custom-table.lzz" - v8::Global row; -#line 126 "./src/util/custom-table.lzz" - bool done; -#line 127 "./src/util/custom-table.lzz" - sqlite_int64 rowid; - }; -#line 131 "./src/util/custom-table.lzz" - class TempDataConverter : DataConverter - { -#line 131 "./src/util/custom-table.lzz" - friend class CustomTable; -#line 132 "./src/util/custom-table.lzz" - explicit TempDataConverter (CustomTable * parent); -#line 136 "./src/util/custom-table.lzz" - void PropagateJSError (sqlite3_context * invocation); -#line 141 "./src/util/custom-table.lzz" - std::string GetDataErrorPrefix (); -#line 145 "./src/util/custom-table.lzz" - CustomTable * const parent; -#line 146 "./src/util/custom-table.lzz" - int status; - }; -#line 151 "./src/util/custom-table.lzz" - static int xCreate (sqlite3 * db_handle, void * _self, int argc, char const * const * argv, sqlite3_vtab * * output, char * * errOutput); -#line 156 "./src/util/custom-table.lzz" - static int xConnect (sqlite3 * db_handle, void * _self, int argc, char const * const * argv, sqlite3_vtab * * output, char * * errOutput); -#line 210 "./src/util/custom-table.lzz" - static int xDisconnect (sqlite3_vtab * vtab); -#line 215 "./src/util/custom-table.lzz" - static int xOpen (sqlite3_vtab * vtab, sqlite3_vtab_cursor * * output); -#line 220 "./src/util/custom-table.lzz" - static int xClose (sqlite3_vtab_cursor * cursor); -#line 228 "./src/util/custom-table.lzz" - static int xFilter (sqlite3_vtab_cursor * _cursor, int idxNum, char const * idxStr, int argc, sqlite3_value * * argv); -#line 284 "./src/util/custom-table.lzz" - static int xNext (sqlite3_vtab_cursor * _cursor); -#line 313 "./src/util/custom-table.lzz" - static int xEof (sqlite3_vtab_cursor * cursor); -#line 318 "./src/util/custom-table.lzz" - static int xColumn (sqlite3_vtab_cursor * _cursor, sqlite3_context * invocation, int column); -#line 336 "./src/util/custom-table.lzz" - static int xRowid (sqlite3_vtab_cursor * cursor, sqlite_int64 * output); -#line 343 "./src/util/custom-table.lzz" - static int xBestIndex (sqlite3_vtab * vtab, sqlite3_index_info * output); -#line 394 "./src/util/custom-table.lzz" - void PropagateJSError (); -#line 399 "./src/util/custom-table.lzz" - Addon * const addon; -#line 400 "./src/util/custom-table.lzz" - v8::Isolate * const isolate; -#line 401 "./src/util/custom-table.lzz" - Database * const db; -#line 402 "./src/util/custom-table.lzz" - std::string const name; -#line 403 "./src/util/custom-table.lzz" - v8::Global const factory; -}; -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 72 "./src/util/data.lzz" - v8::Local GetValueJS (v8::Isolate * isolate, sqlite3_stmt * handle, int column, bool safe_ints); -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 76 "./src/util/data.lzz" - v8::Local GetValueJS (v8::Isolate * isolate, sqlite3_value * value, bool safe_ints); -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 80 "./src/util/data.lzz" - v8::Local GetFlatRowJS (v8::Isolate * isolate, v8::Local ctx, sqlite3_stmt * handle, bool safe_ints); -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 91 "./src/util/data.lzz" - v8::Local GetExpandedRowJS (v8::Isolate * isolate, v8::Local ctx, sqlite3_stmt * handle, bool safe_ints); -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 110 "./src/util/data.lzz" - v8::Local GetRawRowJS (v8::Isolate * isolate, v8::Local ctx, sqlite3_stmt * handle, bool safe_ints); -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 119 "./src/util/data.lzz" - v8::Local GetRowJS (v8::Isolate * isolate, v8::Local ctx, sqlite3_stmt * handle, bool safe_ints, char mode); -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 128 "./src/util/data.lzz" - void GetArgumentsJS (v8::Isolate * isolate, v8::Local * out, sqlite3_value * * values, int argument_count, bool safe_ints); -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 135 "./src/util/data.lzz" - int BindValueFromJS (v8::Isolate * isolate, sqlite3_stmt * handle, int index, v8::Local value); -} -#line 65 "./src/util/data.lzz" -namespace Data -{ -#line 140 "./src/util/data.lzz" - void ResultValueFromJS (v8::Isolate * isolate, sqlite3_context * invocation, v8::Local value, DataConverter * converter); -} -#line 1 "./src/util/binder.lzz" -class Binder -{ -#line 2 "./src/util/binder.lzz" -public: -#line 4 "./src/util/binder.lzz" - explicit Binder (sqlite3_stmt * _handle); -#line 11 "./src/util/binder.lzz" - bool Bind (v8::FunctionCallbackInfo const & info, int argc, Statement * stmt); -#line 28 "./src/util/binder.lzz" -private: -#line 30 "./src/util/binder.lzz" - struct Result - { -#line 31 "./src/util/binder.lzz" - int count; -#line 32 "./src/util/binder.lzz" - bool bound_object; - }; -#line 55 "./src/util/binder.lzz" - void Fail (void (* Throw) (char const *), char const * message); -#line 63 "./src/util/binder.lzz" - int NextAnonIndex (); -#line 69 "./src/util/binder.lzz" - void BindValue (v8::Isolate * isolate, v8::Local value, int index); -#line 90 "./src/util/binder.lzz" - int BindArray (v8::Isolate * isolate, v8::Local arr); -#line 116 "./src/util/binder.lzz" - int BindObject (v8::Isolate * isolate, v8::Local obj, Statement * stmt); -#line 160 "./src/util/binder.lzz" - Result BindArgs (v8::FunctionCallbackInfo const & info, int argc, Statement * stmt); -#line 200 "./src/util/binder.lzz" - sqlite3_stmt * handle; -#line 201 "./src/util/binder.lzz" - int param_count; -#line 202 "./src/util/binder.lzz" - int anon_index; -#line 203 "./src/util/binder.lzz" - bool success; -}; -#line 34 "./src/better_sqlite3.lzz" -struct Addon -{ -#line 35 "./src/better_sqlite3.lzz" - static void JS_setErrorConstructor (v8::FunctionCallbackInfo const & info); -#line 40 "./src/better_sqlite3.lzz" - static void Cleanup (void * ptr); -#line 47 "./src/better_sqlite3.lzz" - explicit Addon (v8::Isolate * isolate); -#line 52 "./src/better_sqlite3.lzz" - sqlite3_uint64 NextId (); -#line 56 "./src/better_sqlite3.lzz" - v8::Global Statement; -#line 57 "./src/better_sqlite3.lzz" - v8::Global StatementIterator; -#line 58 "./src/better_sqlite3.lzz" - v8::Global Backup; -#line 59 "./src/better_sqlite3.lzz" - v8::Global SqliteError; -#line 60 "./src/better_sqlite3.lzz" - v8::FunctionCallbackInfo const * privileged_info; -#line 61 "./src/better_sqlite3.lzz" +class Database; + +// Include all component headers +#include "objects/backup.hpp" +#include "objects/database.hpp" +#include "objects/statement-iterator.hpp" +#include "objects/statement.hpp" +#include "util/bind-map.hpp" +#include "util/binder.hpp" +#include "util/constants.hpp" +#include "util/custom-aggregate.hpp" +#include "util/custom-function.hpp" +#include "util/custom-table.hpp" +#include "util/data-converter.hpp" +#include "util/data.hpp" +#include "util/macros.hpp" + +// Addon struct definition +struct Addon { + static void + JS_setErrorConstructor(v8::FunctionCallbackInfo const &info); + static void Cleanup(void *ptr); + explicit Addon(v8::Isolate *isolate); + sqlite3_uint64 NextId(); + v8::Global Statement; + v8::Global StatementIterator; + v8::Global Backup; + v8::Global SqliteError; + v8::FunctionCallbackInfo const *privileged_info; sqlite3_uint64 next_id; -#line 62 "./src/better_sqlite3.lzz" CS cs; -#line 63 "./src/better_sqlite3.lzz" - std::set dbs; + std::set dbs; }; -#line 20 "./src/util/macros.lzz" -LZZ_INLINE v8::Local StringFromUtf8 (v8::Isolate * isolate, char const * data, int length) -#line 20 "./src/util/macros.lzz" - { - return v8::String::NewFromUtf8(isolate, data, v8::NewStringType::kNormal, length).ToLocalChecked(); -} -#line 23 "./src/util/macros.lzz" -LZZ_INLINE v8::Local InternalizedFromUtf8 (v8::Isolate * isolate, char const * data, int length) -#line 23 "./src/util/macros.lzz" - { - return v8::String::NewFromUtf8(isolate, data, v8::NewStringType::kInternalized, length).ToLocalChecked(); -} -#line 26 "./src/util/macros.lzz" -LZZ_INLINE v8::Local InternalizedFromUtf8OrNull (v8::Isolate * isolate, char const * data, int length) -#line 26 "./src/util/macros.lzz" - { - if (data == NULL) return v8::Null(isolate); - return InternalizedFromUtf8(isolate, data, length); -} -#line 30 "./src/util/macros.lzz" -LZZ_INLINE v8::Local InternalizedFromLatin1 (v8::Isolate * isolate, char const * str) -#line 30 "./src/util/macros.lzz" - { - return v8::String::NewFromOneByte(isolate, reinterpret_cast(str), v8::NewStringType::kInternalized).ToLocalChecked(); -} -#line 34 "./src/util/macros.lzz" -LZZ_INLINE void SetFrozen (v8::Isolate * isolate, v8::Local ctx, v8::Local obj, v8::Global & key, v8::Local value) -#line 34 "./src/util/macros.lzz" - { - obj->DefineOwnProperty(ctx, key.Get(isolate), value, static_cast(v8::DontDelete | v8::ReadOnly)).FromJust(); -} -#line 92 "./src/util/macros.lzz" -LZZ_INLINE bool IS_SKIPPED (char c) -#line 92 "./src/util/macros.lzz" - { - return c == ' ' || c == ';' || (c >= '\t' && c <= '\r'); -} -#line 97 "./src/util/macros.lzz" -template -#line 97 "./src/util/macros.lzz" -LZZ_INLINE T * ALLOC_ARRAY (size_t count) -#line 97 "./src/util/macros.lzz" - { - return static_cast(::operator new[](count * sizeof(T))); -} -#line 102 "./src/util/macros.lzz" -template -#line 102 "./src/util/macros.lzz" -LZZ_INLINE void FREE_ARRAY (T * array_pointer) -#line 102 "./src/util/macros.lzz" - { - ::operator delete[](array_pointer); -} -#line 9 "./src/util/bind-map.lzz" -LZZ_INLINE int BindMap::Pair::GetIndex () -#line 9 "./src/util/bind-map.lzz" - { - return index; -} -#line 13 "./src/util/bind-map.lzz" -LZZ_INLINE v8::Local BindMap::Pair::GetName (v8::Isolate * isolate) -#line 13 "./src/util/bind-map.lzz" - { - return name.Get(isolate); -} -#line 41 "./src/util/bind-map.lzz" -LZZ_INLINE BindMap::Pair * BindMap::GetPairs () -#line 41 "./src/util/bind-map.lzz" - { - return pairs; -} -#line 45 "./src/util/bind-map.lzz" -LZZ_INLINE int BindMap::GetSize () -#line 45 "./src/util/bind-map.lzz" - { - return length; -} -#line 77 "./src/objects/database.lzz" -LZZ_INLINE void Database::AddStatement (Statement * stmt) -#line 77 "./src/objects/database.lzz" - { stmts.insert(stmts.end(), stmt); -} -#line 78 "./src/objects/database.lzz" -LZZ_INLINE void Database::RemoveStatement (Statement * stmt) -#line 78 "./src/objects/database.lzz" - { stmts.erase(stmt); -} -#line 81 "./src/objects/database.lzz" -LZZ_INLINE void Database::AddBackup (Backup * backup) -#line 81 "./src/objects/database.lzz" - { backups.insert(backups.end(), backup); -} -#line 82 "./src/objects/database.lzz" -LZZ_INLINE void Database::RemoveBackup (Backup * backup) -#line 82 "./src/objects/database.lzz" - { backups.erase(backup); -} -#line 96 "./src/objects/database.lzz" -LZZ_INLINE Database::State * Database::GetState () -#line 96 "./src/objects/database.lzz" - { - return reinterpret_cast(&open); -} -#line 99 "./src/objects/database.lzz" -LZZ_INLINE sqlite3 * Database::GetHandle () -#line 99 "./src/objects/database.lzz" - { - return db_handle; -} -#line 102 "./src/objects/database.lzz" -LZZ_INLINE Addon * Database::GetAddon () -#line 102 "./src/objects/database.lzz" - { - return addon; -} -#line 21 "./src/objects/statement.lzz" -LZZ_INLINE bool Statement::Compare (Statement const * const a, Statement const * const b) -#line 21 "./src/objects/statement.lzz" - { - return a->extras->id < b->extras->id; -} -#line 119 "./src/objects/statement-iterator.lzz" -LZZ_INLINE v8::Local StatementIterator::NewRecord (v8::Isolate * isolate, v8::Local ctx, v8::Local value, Addon * addon, bool done) -#line 119 "./src/objects/statement-iterator.lzz" - { - v8::Local record = v8::Object::New(isolate); - record->Set(ctx, addon->cs.value.Get(isolate), value).FromJust(); - record->Set(ctx, addon->cs.done.Get(isolate), v8::Boolean::New(isolate, done)).FromJust(); - return record; -} -#line 126 "./src/objects/statement-iterator.lzz" -LZZ_INLINE v8::Local StatementIterator::DoneRecord (v8::Isolate * isolate, Addon * addon) -#line 126 "./src/objects/statement-iterator.lzz" - { - return NewRecord(isolate, isolate -> GetCurrentContext ( ) , v8::Undefined(isolate), addon, true); -} -#line 12 "./src/objects/backup.lzz" -LZZ_INLINE bool Backup::Compare (Backup const * const a, Backup const * const b) -#line 12 "./src/objects/backup.lzz" - { - return a->id < b->id; -} -#line 39 "./src/util/custom-aggregate.lzz" -LZZ_INLINE void CustomAggregate::xStepBase (sqlite3_context * invocation, int argc, sqlite3_value * * argv, v8::Global const CustomAggregate::* ptrtm) -#line 39 "./src/util/custom-aggregate.lzz" - { - CustomAggregate * self = static_cast < CustomAggregate * > ( sqlite3_user_data ( invocation ) ) ; v8 :: Isolate * isolate = self -> isolate ; v8 :: HandleScope scope ( isolate ) ; Accumulator * acc = self -> GetAccumulator ( invocation ) ; if ( acc -> value . IsEmpty ( ) ) return ; - - v8::Local args_fast[5]; - v8::Local* args = argc <= 4 ? args_fast : ALLOC_ARRAY>(argc + 1); - args[0] = acc->value.Get(isolate); - if (argc != 0) Data::GetArgumentsJS(isolate, args + 1, argv, argc, self->safe_ints); - v8::MaybeLocal maybeReturnValue = (self->*ptrtm).Get(isolate)->Call( isolate -> GetCurrentContext ( ) , v8::Undefined(isolate), argc + 1, args); - if (args != args_fast) delete[] args; +// Addon inline implementation +LZZ_INLINE sqlite3_uint64 Addon::NextId() { return next_id++; } - if (maybeReturnValue.IsEmpty()) { - self->PropagateJSError(invocation); - } else { - v8::Local returnValue = maybeReturnValue.ToLocalChecked(); - if (!returnValue->IsUndefined()) acc->value.Reset(isolate, returnValue); - } -} -#line 58 "./src/util/custom-aggregate.lzz" -LZZ_INLINE void CustomAggregate::xValueBase (sqlite3_context * invocation, bool is_final) -#line 58 "./src/util/custom-aggregate.lzz" - { - CustomAggregate * self = static_cast < CustomAggregate * > ( sqlite3_user_data ( invocation ) ) ; v8 :: Isolate * isolate = self -> isolate ; v8 :: HandleScope scope ( isolate ) ; Accumulator * acc = self -> GetAccumulator ( invocation ) ; if ( acc -> value . IsEmpty ( ) ) return ; - - if (!is_final) { - acc->is_window = true; - } else if (acc->is_window) { - DestroyAccumulator(invocation); - return; - } - - v8::Local result = acc->value.Get(isolate); - if (self->invoke_result) { - v8::MaybeLocal maybeResult = self->result.Get(isolate)->Call( isolate -> GetCurrentContext ( ) , v8::Undefined(isolate), 1, &result); - if (maybeResult.IsEmpty()) { - self->PropagateJSError(invocation); - return; - } - result = maybeResult.ToLocalChecked(); - } - - Data::ResultValueFromJS(isolate, invocation, result, self); - if (is_final) DestroyAccumulator(invocation); -} -#line 92 "./src/util/custom-table.lzz" -LZZ_INLINE CustomTable::VTab * CustomTable::VTab::Upcast (sqlite3_vtab * vtab) -#line 92 "./src/util/custom-table.lzz" - { - return reinterpret_cast(vtab); -} -#line 96 "./src/util/custom-table.lzz" -LZZ_INLINE sqlite3_vtab * CustomTable::VTab::Downcast () -#line 96 "./src/util/custom-table.lzz" - { - return reinterpret_cast(this); -} -#line 110 "./src/util/custom-table.lzz" -LZZ_INLINE CustomTable::Cursor * CustomTable::Cursor::Upcast (sqlite3_vtab_cursor * cursor) -#line 110 "./src/util/custom-table.lzz" - { - return reinterpret_cast(cursor); -} -#line 114 "./src/util/custom-table.lzz" -LZZ_INLINE sqlite3_vtab_cursor * CustomTable::Cursor::Downcast () -#line 114 "./src/util/custom-table.lzz" - { - return reinterpret_cast(this); -} -#line 118 "./src/util/custom-table.lzz" -LZZ_INLINE CustomTable::VTab * CustomTable::Cursor::GetVTab () -#line 118 "./src/util/custom-table.lzz" - { - return VTab::Upcast(base.pVtab); -} -#line 52 "./src/better_sqlite3.lzz" -LZZ_INLINE sqlite3_uint64 Addon::NextId () -#line 52 "./src/better_sqlite3.lzz" - { - return next_id++; -} #undef LZZ_INLINE -#endif + +#endif // BETTER_SQLITE3_HPP diff --git a/src/better_sqlite3.lzz b/src/better_sqlite3.lzz deleted file mode 100644 index 430991fc5..000000000 --- a/src/better_sqlite3.lzz +++ /dev/null @@ -1,88 +0,0 @@ -#hdr -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#end - -#insert "util/macros.lzz" -#insert "util/query-macros.lzz" -#insert "util/constants.lzz" -#insert "util/bind-map.lzz" -struct Addon; -class Statement; -class Backup; -#insert "objects/database.lzz" -#insert "objects/statement.lzz" -#insert "objects/statement-iterator.lzz" -#insert "objects/backup.lzz" -#insert "util/data-converter.lzz" -#insert "util/custom-function.lzz" -#insert "util/custom-aggregate.lzz" -#insert "util/custom-table.lzz" -#insert "util/data.lzz" -#insert "util/binder.lzz" - -struct Addon { - NODE_METHOD(JS_setErrorConstructor) { - REQUIRE_ARGUMENT_FUNCTION(first, v8::Local SqliteError); - OnlyAddon->SqliteError.Reset(OnlyIsolate, SqliteError); - } - - static void Cleanup(void* ptr) { - Addon* addon = static_cast(ptr); - for (Database* db : addon->dbs) db->CloseHandles(); - addon->dbs.clear(); - delete addon; - } - - explicit Addon(v8::Isolate* isolate) : - privileged_info(NULL), - next_id(0), - cs(isolate) {} - - inline sqlite3_uint64 NextId() { - return next_id++; - } - - v8::Global Statement; - v8::Global StatementIterator; - v8::Global Backup; - v8::Global SqliteError; - NODE_ARGUMENTS_POINTER privileged_info; - sqlite3_uint64 next_id; - CS cs; - std::set dbs; -}; - -#src -NODE_MODULE_INIT(/* exports, context */) { - v8::Isolate* isolate = context->GetIsolate(); - v8::HandleScope scope(isolate); - - // Initialize addon instance. - Addon* addon = new Addon(isolate); - v8::Local data = v8::External::New(isolate, addon); - node::AddEnvironmentCleanupHook(isolate, Addon::Cleanup, addon); - - // Create and export native-backed classes and functions. - exports->Set(context, InternalizedFromLatin1(isolate, "Database"), Database::Init(isolate, data)).FromJust(); - exports->Set(context, InternalizedFromLatin1(isolate, "Statement"), Statement::Init(isolate, data)).FromJust(); - exports->Set(context, InternalizedFromLatin1(isolate, "StatementIterator"), StatementIterator::Init(isolate, data)).FromJust(); - exports->Set(context, InternalizedFromLatin1(isolate, "Backup"), Backup::Init(isolate, data)).FromJust(); - exports->Set(context, InternalizedFromLatin1(isolate, "setErrorConstructor"), v8::FunctionTemplate::New(isolate, Addon::JS_setErrorConstructor, data)->GetFunction(context).ToLocalChecked()).FromJust(); - - // Store addon instance data. - addon->Statement.Reset(isolate, exports->Get(context, InternalizedFromLatin1(isolate, "Statement")).ToLocalChecked().As()); - addon->StatementIterator.Reset(isolate, exports->Get(context, InternalizedFromLatin1(isolate, "StatementIterator")).ToLocalChecked().As()); - addon->Backup.Reset(isolate, exports->Get(context, InternalizedFromLatin1(isolate, "Backup")).ToLocalChecked().As()); -} -#end diff --git a/src/better_sqlite3_deps.hpp b/src/better_sqlite3_deps.hpp new file mode 100644 index 000000000..f4cafe976 --- /dev/null +++ b/src/better_sqlite3_deps.hpp @@ -0,0 +1,21 @@ +#ifndef BETTER_SQLITE3_DEPS_HPP +#define BETTER_SQLITE3_DEPS_HPP + +// Common system includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// LZZ_INLINE macro +#define LZZ_INLINE inline + +#endif // BETTER_SQLITE3_DEPS_HPP diff --git a/src/better_sqlite3_impl.hpp b/src/better_sqlite3_impl.hpp new file mode 100644 index 000000000..3550cfd24 --- /dev/null +++ b/src/better_sqlite3_impl.hpp @@ -0,0 +1,13 @@ +#ifndef BETTER_SQLITE3_IMPL_HPP +#define BETTER_SQLITE3_IMPL_HPP + +#include "better_sqlite3.hpp" + +// Include all headers needed by implementation files +#include "util/bind-map.hpp" +#include "util/binder.hpp" +#include "util/constants.hpp" +#include "util/data.hpp" +#include "util/macros.hpp" + +#endif // BETTER_SQLITE3_IMPL_HPP diff --git a/src/better_sqlite3_split.hpp b/src/better_sqlite3_split.hpp new file mode 100644 index 000000000..05a562183 --- /dev/null +++ b/src/better_sqlite3_split.hpp @@ -0,0 +1,21 @@ +#ifndef BETTER_SQLITE3_SPLIT_HPP +#define BETTER_SQLITE3_SPLIT_HPP + +// better_sqlite3_split.hpp +// + +#include "objects/backup.hpp" +#include "objects/database.hpp" +#include "objects/statement-iterator.hpp" +#include "objects/statement.hpp" +#include "util/bind-map.hpp" +#include "util/binder.hpp" +#include "util/constants.hpp" +#include "util/custom-aggregate.hpp" +#include "util/custom-function.hpp" +#include "util/custom-table.hpp" +#include "util/data-converter.hpp" +#include "util/data.hpp" +#include "util/macros.hpp" + +#endif // BETTER_SQLITE3_SPLIT_HPP diff --git a/src/objects/backup.cpp b/src/objects/backup.cpp new file mode 100644 index 000000000..fd02f3561 --- /dev/null +++ b/src/objects/backup.cpp @@ -0,0 +1,137 @@ +#include "backup.hpp" +#include "../better_sqlite3_impl.hpp" + +v8::Local Backup::Init(v8::Isolate *isolate, + v8::Local data) { + v8::Local t = + NewConstructorTemplate(isolate, data, JS_new, "Backup"); + SetPrototypeMethod(isolate, data, t, "transfer", JS_transfer); + SetPrototypeMethod(isolate, data, t, "close", JS_close); + return t->GetFunction(isolate->GetCurrentContext()).ToLocalChecked(); +} +void Backup::CloseHandles() { + if (alive) { + alive = false; + std::string filename(sqlite3_db_filename(dest_handle, "main")); + sqlite3_backup_finish(backup_handle); + int status = sqlite3_close(dest_handle); + assert(status == SQLITE_OK); + ((void)status); + if (unlink) + remove(filename.c_str()); + } +} +Backup::~Backup() { + if (alive) + db->RemoveBackup(this); + CloseHandles(); +} +Backup::Backup(Database *db, sqlite3 *dest_handle, + sqlite3_backup *backup_handle, sqlite3_uint64 id, bool unlink) + : node::ObjectWrap(), db(db), dest_handle(dest_handle), + backup_handle(backup_handle), id(id), alive(true), unlink(unlink) { + assert(db != NULL); + assert(dest_handle != NULL); + assert(backup_handle != NULL); + db->AddBackup(this); +} +void Backup::JS_new(v8::FunctionCallbackInfo const &info) { + Addon *addon = static_cast(info.Data().As()->Value()); + if (!addon->privileged_info) + return ThrowTypeError("Disabled constructor"); + assert(info.IsConstructCall()); + Database *db = + node ::ObjectWrap ::Unwrap(addon->privileged_info->This()); + if (!db->GetState()->open) + return ThrowTypeError("The database connection is not open"); + if (db->GetState()->busy) + return ThrowTypeError("This database connection is busy executing a query"); + + v8::Local database = + (*addon->privileged_info)[0].As(); + v8::Local attachedName = + (*addon->privileged_info)[1].As(); + v8::Local destFile = + (*addon->privileged_info)[2].As(); + bool unlink = (*addon->privileged_info)[3].As()->Value(); + + v8::Isolate *isolate = info.GetIsolate(); + sqlite3 *dest_handle; + v8::String::Utf8Value dest_file(isolate, destFile); + v8::String::Utf8Value attached_name(isolate, attachedName); + int mask = (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + + if (sqlite3_open_v2(*dest_file, &dest_handle, mask, NULL) != SQLITE_OK) { + Database::ThrowSqliteError(addon, dest_handle); + int status = sqlite3_close(dest_handle); + assert(status == SQLITE_OK); + ((void)status); + return; + } + + sqlite3_extended_result_codes(dest_handle, 1); + sqlite3_limit(dest_handle, SQLITE_LIMIT_LENGTH, INT_MAX); + sqlite3_backup *backup_handle = + sqlite3_backup_init(dest_handle, "main", db->GetHandle(), *attached_name); + if (backup_handle == NULL) { + Database::ThrowSqliteError(addon, dest_handle); + int status = sqlite3_close(dest_handle); + assert(status == SQLITE_OK); + ((void)status); + return; + } + + Backup *backup = + new Backup(db, dest_handle, backup_handle, addon->NextId(), unlink); + backup->Wrap(info.This()); + SetFrozen(isolate, isolate->GetCurrentContext(), info.This(), + addon->cs.database, database); + + info.GetReturnValue().Set(info.This()); +} +void Backup::JS_transfer(v8::FunctionCallbackInfo const &info) { + Backup *backup = node ::ObjectWrap ::Unwrap(info.This()); + if (info.Length() <= (0) || !info[0]->IsInt32()) + return ThrowTypeError("Expected " + "first" + " argument to be " + "a 32-bit signed integer"); + int pages = (info[0].As())->Value(); + if (!backup->db->GetState()->open) + return ThrowTypeError("The database connection is not open"); + assert(backup->db->GetState()->busy == false); + assert(backup->alive == true); + + sqlite3_backup *backup_handle = backup->backup_handle; + int status = sqlite3_backup_step(backup_handle, pages) & 0xff; + + Addon *addon = backup->db->GetAddon(); + if (status == SQLITE_OK || status == SQLITE_DONE || status == SQLITE_BUSY) { + int total_pages = sqlite3_backup_pagecount(backup_handle); + int remaining_pages = sqlite3_backup_remaining(backup_handle); + v8::Isolate *isolate = info.GetIsolate(); + v8::Local ctx = isolate->GetCurrentContext(); + v8::Local result = v8::Object::New(isolate); + result + ->Set(ctx, addon->cs.totalPages.Get(isolate), + v8::Int32::New(isolate, total_pages)) + .FromJust(); + result + ->Set(ctx, addon->cs.remainingPages.Get(isolate), + v8::Int32::New(isolate, remaining_pages)) + .FromJust(); + info.GetReturnValue().Set(result); + if (status == SQLITE_DONE) + backup->unlink = false; + } else { + Database::ThrowSqliteError(addon, sqlite3_errstr(status), status); + } +} +void Backup::JS_close(v8::FunctionCallbackInfo const &info) { + Backup *backup = node ::ObjectWrap ::Unwrap(info.This()); + assert(backup->db->GetState()->busy == false); + if (backup->alive) + backup->db->RemoveBackup(backup); + backup->CloseHandles(); + info.GetReturnValue().Set(info.This()); +} diff --git a/src/objects/backup.hpp b/src/objects/backup.hpp new file mode 100644 index 000000000..c9806f795 --- /dev/null +++ b/src/objects/backup.hpp @@ -0,0 +1,35 @@ +#ifndef BETTER_SQLITE3_OBJECTS_BACKUP_HPP +#define BETTER_SQLITE3_OBJECTS_BACKUP_HPP + +#include "../better_sqlite3_deps.hpp" + +// Forward declarations +class Database; + +class Backup : public node::ObjectWrap { +public: + static v8::Local Init(v8::Isolate *isolate, + v8::Local data); + static bool Compare(Backup const *const a, Backup const *const b); + void CloseHandles(); + ~Backup(); + +private: + explicit Backup(Database *db, sqlite3 *dest_handle, + sqlite3_backup *backup_handle, sqlite3_uint64 id, + bool unlink); + static void JS_new(v8::FunctionCallbackInfo const &info); + static void JS_transfer(v8::FunctionCallbackInfo const &info); + static void JS_close(v8::FunctionCallbackInfo const &info); + Database *const db; + sqlite3 *const dest_handle; + sqlite3_backup *const backup_handle; + sqlite3_uint64 const id; + bool alive; + bool unlink; +}; +LZZ_INLINE bool Backup::Compare(Backup const *const a, Backup const *const b) { + return a->id < b->id; +} + +#endif // BETTER_SQLITE3_OBJECTS_BACKUP_HPP diff --git a/src/objects/backup.lzz b/src/objects/backup.lzz deleted file mode 100644 index da200f853..000000000 --- a/src/objects/backup.lzz +++ /dev/null @@ -1,138 +0,0 @@ -class Backup : public node::ObjectWrap { -public: - - INIT(Init) { - v8::Local t = NewConstructorTemplate(isolate, data, JS_new, "Backup"); - SetPrototypeMethod(isolate, data, t, "transfer", JS_transfer); - SetPrototypeMethod(isolate, data, t, "close", JS_close); - return t->GetFunction(OnlyContext).ToLocalChecked(); - } - - // Used to support ordered containers. - static inline bool Compare(Backup const * const a, Backup const * const b) { - return a->id < b->id; - } - - // Whenever this is used, db->RemoveBackup must be invoked beforehand. - void CloseHandles() { - if (alive) { - alive = false; - std::string filename(sqlite3_db_filename(dest_handle, "main")); - sqlite3_backup_finish(backup_handle); - int status = sqlite3_close(dest_handle); - assert(status == SQLITE_OK); ((void)status); - if (unlink) remove(filename.c_str()); - } - } - - ~Backup() { - if (alive) db->RemoveBackup(this); - CloseHandles(); - } - -private: - - explicit Backup( - Database* db, - sqlite3* dest_handle, - sqlite3_backup* backup_handle, - sqlite3_uint64 id, - bool unlink - ) : - node::ObjectWrap(), - db(db), - dest_handle(dest_handle), - backup_handle(backup_handle), - id(id), - alive(true), - unlink(unlink) { - assert(db != NULL); - assert(dest_handle != NULL); - assert(backup_handle != NULL); - db->AddBackup(this); - } - - NODE_METHOD(JS_new) { - UseAddon; - if (!addon->privileged_info) return ThrowTypeError("Disabled constructor"); - assert(info.IsConstructCall()); - Database* db = Unwrap(addon->privileged_info->This()); - REQUIRE_DATABASE_OPEN(db->GetState()); - REQUIRE_DATABASE_NOT_BUSY(db->GetState()); - - v8::Local database = (*addon->privileged_info)[0].As(); - v8::Local attachedName = (*addon->privileged_info)[1].As(); - v8::Local destFile = (*addon->privileged_info)[2].As(); - bool unlink = (*addon->privileged_info)[3].As()->Value(); - - UseIsolate; - sqlite3* dest_handle; - v8::String::Utf8Value dest_file(isolate, destFile); - v8::String::Utf8Value attached_name(isolate, attachedName); - int mask = (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); - - if (sqlite3_open_v2(*dest_file, &dest_handle, mask, NULL) != SQLITE_OK) { - Database::ThrowSqliteError(addon, dest_handle); - int status = sqlite3_close(dest_handle); - assert(status == SQLITE_OK); ((void)status); - return; - } - - sqlite3_extended_result_codes(dest_handle, 1); - sqlite3_limit(dest_handle, SQLITE_LIMIT_LENGTH, INT_MAX); - sqlite3_backup* backup_handle = sqlite3_backup_init(dest_handle, "main", db->GetHandle(), *attached_name); - if (backup_handle == NULL) { - Database::ThrowSqliteError(addon, dest_handle); - int status = sqlite3_close(dest_handle); - assert(status == SQLITE_OK); ((void)status); - return; - } - - Backup* backup = new Backup(db, dest_handle, backup_handle, addon->NextId(), unlink); - backup->Wrap(info.This()); - SetFrozen(isolate, OnlyContext, info.This(), addon->cs.database, database); - - info.GetReturnValue().Set(info.This()); - } - - NODE_METHOD(JS_transfer) { - Backup* backup = Unwrap(info.This()); - REQUIRE_ARGUMENT_INT32(first, int pages); - REQUIRE_DATABASE_OPEN(backup->db->GetState()); - assert(backup->db->GetState()->busy == false); - assert(backup->alive == true); - - sqlite3_backup* backup_handle = backup->backup_handle; - int status = sqlite3_backup_step(backup_handle, pages) & 0xff; - - Addon* addon = backup->db->GetAddon(); - if (status == SQLITE_OK || status == SQLITE_DONE || status == SQLITE_BUSY) { - int total_pages = sqlite3_backup_pagecount(backup_handle); - int remaining_pages = sqlite3_backup_remaining(backup_handle); - UseIsolate; - UseContext; - v8::Local result = v8::Object::New(isolate); - result->Set(ctx, addon->cs.totalPages.Get(isolate), v8::Int32::New(isolate, total_pages)).FromJust(); - result->Set(ctx, addon->cs.remainingPages.Get(isolate), v8::Int32::New(isolate, remaining_pages)).FromJust(); - info.GetReturnValue().Set(result); - if (status == SQLITE_DONE) backup->unlink = false; - } else { - Database::ThrowSqliteError(addon, sqlite3_errstr(status), status); - } - } - - NODE_METHOD(JS_close) { - Backup* backup = Unwrap(info.This()); - assert(backup->db->GetState()->busy == false); - if (backup->alive) backup->db->RemoveBackup(backup); - backup->CloseHandles(); - info.GetReturnValue().Set(info.This()); - } - - Database* const db; - sqlite3* const dest_handle; - sqlite3_backup* const backup_handle; - const sqlite3_uint64 id; - bool alive; - bool unlink; -}; diff --git a/src/objects/database.cpp b/src/objects/database.cpp new file mode 100644 index 000000000..0c59ede21 --- /dev/null +++ b/src/objects/database.cpp @@ -0,0 +1,660 @@ +#include "database.hpp" +#include "../better_sqlite3_impl.hpp" + +v8::Local Database::Init(v8::Isolate *isolate, + v8::Local data) { + v8::Local t = + NewConstructorTemplate(isolate, data, JS_new, "Database"); + SetPrototypeMethod(isolate, data, t, "prepare", JS_prepare); + SetPrototypeMethod(isolate, data, t, "exec", JS_exec); + SetPrototypeMethod(isolate, data, t, "backup", JS_backup); + SetPrototypeMethod(isolate, data, t, "serialize", JS_serialize); + SetPrototypeMethod(isolate, data, t, "function", JS_function); + SetPrototypeMethod(isolate, data, t, "aggregate", JS_aggregate); + SetPrototypeMethod(isolate, data, t, "table", JS_table); + SetPrototypeMethod(isolate, data, t, "loadExtension", JS_loadExtension); + SetPrototypeMethod(isolate, data, t, "close", JS_close); + SetPrototypeMethod(isolate, data, t, "defaultSafeIntegers", + JS_defaultSafeIntegers); + SetPrototypeMethod(isolate, data, t, "unsafeMode", JS_unsafeMode); + SetPrototypeGetter(isolate, data, t, "open", JS_open); + SetPrototypeGetter(isolate, data, t, "inTransaction", JS_inTransaction); + return t->GetFunction(isolate->GetCurrentContext()).ToLocalChecked(); +} +bool Database::CompareDatabase::operator()(Database const *const a, + Database const *const b) const { + return a < b; +} +bool Database::CompareStatement::operator()(Statement const *const a, + Statement const *const b) const { + return Statement::Compare(a, b); +} +bool Database::CompareBackup::operator()(Backup const *const a, + Backup const *const b) const { + return Backup::Compare(a, b); +} +void Database::ThrowDatabaseError() { + if (was_js_error) + was_js_error = false; + else + ThrowSqliteError(addon, db_handle); +} +void Database::ThrowSqliteError(Addon *addon, sqlite3 *db_handle) { + assert(db_handle != NULL); + ThrowSqliteError(addon, sqlite3_errmsg(db_handle), + sqlite3_extended_errcode(db_handle)); +} +void Database::ThrowSqliteError(Addon *addon, char const *message, int code) { + assert(message != NULL); + assert((code & 0xff) != SQLITE_OK); + assert((code & 0xff) != SQLITE_ROW); + assert((code & 0xff) != SQLITE_DONE); + v8::Isolate *isolate = v8::Isolate ::GetCurrent(); + v8::Local args[2] = {StringFromUtf8(isolate, message, -1), + addon->cs.Code(isolate, code)}; + isolate->ThrowException( + addon->SqliteError.Get(isolate) + ->NewInstance(isolate->GetCurrentContext(), 2, args) + .ToLocalChecked()); +} +bool Database::Log(v8::Isolate *isolate, sqlite3_stmt *handle) { + assert(was_js_error == false); + if (!has_logger) + return false; + char *expanded = sqlite3_expanded_sql(handle); + v8::Local arg = + StringFromUtf8(isolate, expanded ? expanded : sqlite3_sql(handle), -1); + was_js_error = + logger.Get(isolate) + .As() + ->Call(isolate->GetCurrentContext(), v8::Undefined(isolate), 1, &arg) + .IsEmpty(); + if (expanded) + sqlite3_free(expanded); + return was_js_error; +} +void Database::CloseHandles() { + if (open) { + open = false; + for (Statement *stmt : stmts) + stmt->CloseHandles(); + for (Backup *backup : backups) + backup->CloseHandles(); + stmts.clear(); + backups.clear(); + int status = sqlite3_close(db_handle); + assert(status == SQLITE_OK); + ((void)status); + } +} +Database::~Database() { + if (open) + addon->dbs.erase(this); + CloseHandles(); +} +Database::Database(v8::Isolate *isolate, Addon *addon, sqlite3 *db_handle, + v8::Local logger) + : node::ObjectWrap(), db_handle(db_handle), open(true), busy(false), + safe_ints(false), unsafe_mode(false), was_js_error(false), + has_logger(logger->IsFunction()), iterators(0), addon(addon), + logger(isolate, logger), stmts(), backups() { + assert(db_handle != NULL); + addon->dbs.insert(this); +} +void Database::JS_new(v8::FunctionCallbackInfo const &info) { + assert(info.IsConstructCall()); + if (info.Length() <= (0) || !info[0]->IsString()) + return ThrowTypeError("Expected " + "first" + " argument to be " + "a string"); + v8::Local filename = (info[0].As()); + if (info.Length() <= (1) || !info[1]->IsString()) + return ThrowTypeError("Expected " + "second" + " argument to be " + "a string"); + v8::Local filenameGiven = (info[1].As()); + if (info.Length() <= (2) || !info[2]->IsBoolean()) + return ThrowTypeError("Expected " + "third" + " argument to be " + "a boolean"); + bool in_memory = (info[2].As())->Value(); + if (info.Length() <= (3) || !info[3]->IsBoolean()) + return ThrowTypeError("Expected " + "fourth" + " argument to be " + "a boolean"); + bool readonly = (info[3].As())->Value(); + if (info.Length() <= (4) || !info[4]->IsBoolean()) + return ThrowTypeError("Expected " + "fifth" + " argument to be " + "a boolean"); + bool must_exist = (info[4].As())->Value(); + if (info.Length() <= (5) || !info[5]->IsInt32()) + return ThrowTypeError("Expected " + "sixth" + " argument to be " + "a 32-bit signed integer"); + int timeout = (info[5].As())->Value(); + if (info.Length() <= (6)) + return ThrowTypeError("Expected a " + "seventh" + " argument"); + v8::Local logger = info[6]; + if (info.Length() <= (7)) + return ThrowTypeError("Expected a " + "eighth" + " argument"); + v8::Local buffer = info[7]; + + Addon *addon = static_cast(info.Data().As()->Value()); + v8::Isolate *isolate = info.GetIsolate(); + sqlite3 *db_handle; + v8::String::Utf8Value utf8(isolate, filename); + int mask = readonly ? SQLITE_OPEN_READONLY + : must_exist ? SQLITE_OPEN_READWRITE + : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + + if (sqlite3_open_v2(*utf8, &db_handle, mask, NULL) != SQLITE_OK) { + ThrowSqliteError(addon, db_handle); + int status = sqlite3_close(db_handle); + assert(status == SQLITE_OK); + ((void)status); + return; + } + + assert(sqlite3_db_mutex(db_handle) == NULL); + sqlite3_extended_result_codes(db_handle, 1); + sqlite3_busy_timeout(db_handle, timeout); + sqlite3_limit(db_handle, SQLITE_LIMIT_LENGTH, + MAX_BUFFER_SIZE < MAX_STRING_SIZE ? MAX_BUFFER_SIZE + : MAX_STRING_SIZE); + sqlite3_limit(db_handle, SQLITE_LIMIT_SQL_LENGTH, MAX_STRING_SIZE); + int status = sqlite3_db_config( + db_handle, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, NULL); + assert(status == SQLITE_OK); + status = sqlite3_db_config(db_handle, SQLITE_DBCONFIG_DEFENSIVE, 1, NULL); + assert(status == SQLITE_OK); + + if (node::Buffer::HasInstance(buffer) && + !Deserialize(buffer.As(), addon, db_handle, readonly)) { + int status = sqlite3_close(db_handle); + assert(status == SQLITE_OK); + ((void)status); + return; + } + + v8::Local ctx = isolate->GetCurrentContext(); + Database *db = new Database(isolate, addon, db_handle, logger); + db->Wrap(info.This()); + SetFrozen(isolate, ctx, info.This(), addon->cs.memory, + v8::Boolean::New(isolate, in_memory)); + SetFrozen(isolate, ctx, info.This(), addon->cs.readonly, + v8::Boolean::New(isolate, readonly)); + SetFrozen(isolate, ctx, info.This(), addon->cs.name, filenameGiven); + + info.GetReturnValue().Set(info.This()); +} +void Database::JS_prepare(v8::FunctionCallbackInfo const &info) { + if (info.Length() <= (0) || !info[0]->IsString()) + return ThrowTypeError("Expected " + "first" + " argument to be " + "a string"); + v8::Local source = (info[0].As()); + if (info.Length() <= (1) || !info[1]->IsObject()) + return ThrowTypeError("Expected " + "second" + " argument to be " + "an object"); + v8::Local database = (info[1].As()); + if (info.Length() <= (2) || !info[2]->IsBoolean()) + return ThrowTypeError("Expected " + "third" + " argument to be " + "a boolean"); + bool pragmaMode = (info[2].As())->Value(); + (void)source; + (void)database; + (void)pragmaMode; + Addon *addon = static_cast(info.Data().As()->Value()); + v8::Isolate *isolate = info.GetIsolate(); + v8::Local c = addon->Statement.Get(isolate); + addon->privileged_info = &info; + v8::MaybeLocal maybeStatement = + c->NewInstance(isolate->GetCurrentContext(), 0, NULL); + addon->privileged_info = NULL; + if (!maybeStatement.IsEmpty()) + info.GetReturnValue().Set(maybeStatement.ToLocalChecked()); +} +void Database::JS_exec(v8::FunctionCallbackInfo const &info) { + Database *db = node ::ObjectWrap ::Unwrap(info.This()); + if (info.Length() <= (0) || !info[0]->IsString()) + return ThrowTypeError("Expected " + "first" + " argument to be " + "a string"); + v8::Local source = (info[0].As()); + if (!db->open) + return ThrowTypeError("The database connection is not open"); + if (db->busy) + return ThrowTypeError("This database connection is busy executing a query"); + if (!db->unsafe_mode) { + if (db->iterators) + return ThrowTypeError( + "This database connection is busy executing a query"); + } + ((void)0); + db->busy = true; + + v8::Isolate *isolate = info.GetIsolate(); + v8::String::Utf8Value utf8(isolate, source); + const char *sql = *utf8; + const char *tail; + + int status; + const bool has_logger = db->has_logger; + sqlite3 *const db_handle = db->db_handle; + sqlite3_stmt *handle; + + for (;;) { + while (IS_SKIPPED(*sql)) + ++sql; + status = sqlite3_prepare_v2(db_handle, sql, -1, &handle, &tail); + sql = tail; + if (!handle) + break; + if (has_logger && db->Log(isolate, handle)) { + sqlite3_finalize(handle); + status = -1; + break; + } + do + status = sqlite3_step(handle); + while (status == SQLITE_ROW); + status = sqlite3_finalize(handle); + if (status != SQLITE_OK) + break; + } + + db->busy = false; + if (status != SQLITE_OK) { + db->ThrowDatabaseError(); + } +} +void Database::JS_backup(v8::FunctionCallbackInfo const &info) { + if (info.Length() <= (0) || !info[0]->IsObject()) + return ThrowTypeError("Expected " + "first" + " argument to be " + "an object"); + v8::Local database = (info[0].As()); + if (info.Length() <= (1) || !info[1]->IsString()) + return ThrowTypeError("Expected " + "second" + " argument to be " + "a string"); + v8::Local attachedName = (info[1].As()); + if (info.Length() <= (2) || !info[2]->IsString()) + return ThrowTypeError("Expected " + "third" + " argument to be " + "a string"); + v8::Local destFile = (info[2].As()); + if (info.Length() <= (3) || !info[3]->IsBoolean()) + return ThrowTypeError("Expected " + "fourth" + " argument to be " + "a boolean"); + bool unlink = (info[3].As())->Value(); + (void)database; + (void)attachedName; + (void)destFile; + (void)unlink; + Addon *addon = static_cast(info.Data().As()->Value()); + v8::Isolate *isolate = info.GetIsolate(); + v8::Local c = addon->Backup.Get(isolate); + addon->privileged_info = &info; + v8::MaybeLocal maybeBackup = + c->NewInstance(isolate->GetCurrentContext(), 0, NULL); + addon->privileged_info = NULL; + if (!maybeBackup.IsEmpty()) + info.GetReturnValue().Set(maybeBackup.ToLocalChecked()); +} +void Database::JS_serialize(v8::FunctionCallbackInfo const &info) { + Database *db = node ::ObjectWrap ::Unwrap(info.This()); + if (info.Length() <= (0) || !info[0]->IsString()) + return ThrowTypeError("Expected " + "first" + " argument to be " + "a string"); + v8::Local attachedName = (info[0].As()); + if (!db->open) + return ThrowTypeError("The database connection is not open"); + if (db->busy) + return ThrowTypeError("This database connection is busy executing a query"); + if (db->iterators) + return ThrowTypeError("This database connection is busy executing a query"); + + v8::Isolate *isolate = info.GetIsolate(); + v8::String::Utf8Value attached_name(isolate, attachedName); + sqlite3_int64 length = -1; + unsigned char *data = + sqlite3_serialize(db->db_handle, *attached_name, &length, 0); + + if (!data && length) { + ThrowError("Out of memory"); + return; + } + + info.GetReturnValue().Set(SAFE_NEW_BUFFER(isolate, + reinterpret_cast(data), + length, FreeSerialization, NULL) + .ToLocalChecked()); +} +void Database::JS_function(v8::FunctionCallbackInfo const &info) { + Database *db = node ::ObjectWrap ::Unwrap(info.This()); + if (info.Length() <= (0) || !info[0]->IsFunction()) + return ThrowTypeError("Expected " + "first" + " argument to be " + "a function"); + v8::Local fn = (info[0].As()); + if (info.Length() <= (1) || !info[1]->IsString()) + return ThrowTypeError("Expected " + "second" + " argument to be " + "a string"); + v8::Local nameString = (info[1].As()); + if (info.Length() <= (2) || !info[2]->IsInt32()) + return ThrowTypeError("Expected " + "third" + " argument to be " + "a 32-bit signed integer"); + int argc = (info[2].As())->Value(); + if (info.Length() <= (3) || !info[3]->IsInt32()) + return ThrowTypeError("Expected " + "fourth" + " argument to be " + "a 32-bit signed integer"); + int safe_ints = (info[3].As())->Value(); + if (info.Length() <= (4) || !info[4]->IsBoolean()) + return ThrowTypeError("Expected " + "fifth" + " argument to be " + "a boolean"); + bool deterministic = (info[4].As())->Value(); + if (info.Length() <= (5) || !info[5]->IsBoolean()) + return ThrowTypeError("Expected " + "sixth" + " argument to be " + "a boolean"); + bool direct_only = (info[5].As())->Value(); + if (!db->open) + return ThrowTypeError("The database connection is not open"); + if (db->busy) + return ThrowTypeError("This database connection is busy executing a query"); + if (db->iterators) + return ThrowTypeError("This database connection is busy executing a query"); + + v8::Isolate *isolate = info.GetIsolate(); + v8::String::Utf8Value name(isolate, nameString); + int mask = SQLITE_UTF8; + if (deterministic) + mask |= SQLITE_DETERMINISTIC; + if (direct_only) + mask |= SQLITE_DIRECTONLY; + safe_ints = safe_ints < 2 ? safe_ints : static_cast(db->safe_ints); + + if (sqlite3_create_function_v2( + db->db_handle, *name, argc, mask, + new CustomFunction(isolate, db, *name, fn, safe_ints), + CustomFunction::xFunc, NULL, NULL, + CustomFunction::xDestroy) != SQLITE_OK) { + db->ThrowDatabaseError(); + } +} +void Database::JS_aggregate(v8::FunctionCallbackInfo const &info) { + Database *db = node ::ObjectWrap ::Unwrap(info.This()); + if (info.Length() <= (0)) + return ThrowTypeError("Expected a " + "first" + " argument"); + v8::Local start = info[0]; + if (info.Length() <= (1) || !info[1]->IsFunction()) + return ThrowTypeError("Expected " + "second" + " argument to be " + "a function"); + v8::Local step = (info[1].As()); + if (info.Length() <= (2)) + return ThrowTypeError("Expected a " + "third" + " argument"); + v8::Local inverse = info[2]; + if (info.Length() <= (3)) + return ThrowTypeError("Expected a " + "fourth" + " argument"); + v8::Local result = info[3]; + if (info.Length() <= (4) || !info[4]->IsString()) + return ThrowTypeError("Expected " + "fifth" + " argument to be " + "a string"); + v8::Local nameString = (info[4].As()); + if (info.Length() <= (5) || !info[5]->IsInt32()) + return ThrowTypeError("Expected " + "sixth" + " argument to be " + "a 32-bit signed integer"); + int argc = (info[5].As())->Value(); + if (info.Length() <= (6) || !info[6]->IsInt32()) + return ThrowTypeError("Expected " + "seventh" + " argument to be " + "a 32-bit signed integer"); + int safe_ints = (info[6].As())->Value(); + if (info.Length() <= (7) || !info[7]->IsBoolean()) + return ThrowTypeError("Expected " + "eighth" + " argument to be " + "a boolean"); + bool deterministic = (info[7].As())->Value(); + if (info.Length() <= (8) || !info[8]->IsBoolean()) + return ThrowTypeError("Expected " + "ninth" + " argument to be " + "a boolean"); + bool direct_only = (info[8].As())->Value(); + if (!db->open) + return ThrowTypeError("The database connection is not open"); + if (db->busy) + return ThrowTypeError("This database connection is busy executing a query"); + if (db->iterators) + return ThrowTypeError("This database connection is busy executing a query"); + + v8::Isolate *isolate = info.GetIsolate(); + v8::String::Utf8Value name(isolate, nameString); + auto xInverse = inverse->IsFunction() ? CustomAggregate::xInverse : NULL; + auto xValue = xInverse ? CustomAggregate::xValue : NULL; + int mask = SQLITE_UTF8; + if (deterministic) + mask |= SQLITE_DETERMINISTIC; + if (direct_only) + mask |= SQLITE_DIRECTONLY; + safe_ints = safe_ints < 2 ? safe_ints : static_cast(db->safe_ints); + + if (sqlite3_create_window_function( + db->db_handle, *name, argc, mask, + new CustomAggregate(isolate, db, *name, start, step, inverse, result, + safe_ints), + CustomAggregate::xStep, CustomAggregate::xFinal, xValue, xInverse, + CustomAggregate::xDestroy) != SQLITE_OK) { + db->ThrowDatabaseError(); + } +} +void Database::JS_table(v8::FunctionCallbackInfo const &info) { + Database *db = node ::ObjectWrap ::Unwrap(info.This()); + if (info.Length() <= (0) || !info[0]->IsFunction()) + return ThrowTypeError("Expected " + "first" + " argument to be " + "a function"); + v8::Local factory = (info[0].As()); + if (info.Length() <= (1) || !info[1]->IsString()) + return ThrowTypeError("Expected " + "second" + " argument to be " + "a string"); + v8::Local nameString = (info[1].As()); + if (info.Length() <= (2) || !info[2]->IsBoolean()) + return ThrowTypeError("Expected " + "third" + " argument to be " + "a boolean"); + bool eponymous = (info[2].As())->Value(); + if (!db->open) + return ThrowTypeError("The database connection is not open"); + if (db->busy) + return ThrowTypeError("This database connection is busy executing a query"); + if (db->iterators) + return ThrowTypeError("This database connection is busy executing a query"); + + v8::Isolate *isolate = info.GetIsolate(); + v8::String::Utf8Value name(isolate, nameString); + sqlite3_module *module = + eponymous ? &CustomTable::EPONYMOUS_MODULE : &CustomTable::MODULE; + + db->busy = true; + if (sqlite3_create_module_v2(db->db_handle, *name, module, + new CustomTable(isolate, db, *name, factory), + CustomTable::Destructor) != SQLITE_OK) { + db->ThrowDatabaseError(); + } + db->busy = false; +} +void Database::JS_loadExtension( + v8::FunctionCallbackInfo const &info) { + Database *db = node ::ObjectWrap ::Unwrap(info.This()); + v8::Local entryPoint; + if (info.Length() <= (0) || !info[0]->IsString()) + return ThrowTypeError("Expected " + "first" + " argument to be " + "a string"); + v8::Local filename = (info[0].As()); + if (info.Length() > 1) { + if (info.Length() <= (1) || !info[1]->IsString()) + return ThrowTypeError("Expected " + "second" + " argument to be " + "a string"); + entryPoint = (info[1].As()); + } + if (!db->open) + return ThrowTypeError("The database connection is not open"); + if (db->busy) + return ThrowTypeError("This database connection is busy executing a query"); + if (db->iterators) + return ThrowTypeError("This database connection is busy executing a query"); + v8::Isolate *isolate = info.GetIsolate(); + char *error; + int status = sqlite3_load_extension( + db->db_handle, *v8::String::Utf8Value(isolate, filename), + entryPoint.IsEmpty() ? NULL : *v8::String::Utf8Value(isolate, entryPoint), + &error); + if (status != SQLITE_OK) { + ThrowSqliteError(db->addon, error, status); + } + sqlite3_free(error); +} +void Database::JS_close(v8::FunctionCallbackInfo const &info) { + Database *db = node ::ObjectWrap ::Unwrap(info.This()); + if (db->open) { + if (db->busy) + return ThrowTypeError( + "This database connection is busy executing a query"); + if (db->iterators) + return ThrowTypeError( + "This database connection is busy executing a query"); + db->addon->dbs.erase(db); + db->CloseHandles(); + } +} +void Database::JS_defaultSafeIntegers( + v8::FunctionCallbackInfo const &info) { + Database *db = node ::ObjectWrap ::Unwrap(info.This()); + if (info.Length() == 0) + db->safe_ints = true; + else { + if (info.Length() <= (0) || !info[0]->IsBoolean()) + return ThrowTypeError("Expected " + "first" + " argument to be " + "a boolean"); + db->safe_ints = (info[0].As())->Value(); + } +} +void Database::JS_unsafeMode(v8::FunctionCallbackInfo const &info) { + Database *db = node ::ObjectWrap ::Unwrap(info.This()); + if (info.Length() == 0) + db->unsafe_mode = true; + else { + if (info.Length() <= (0) || !info[0]->IsBoolean()) + return ThrowTypeError("Expected " + "first" + " argument to be " + "a boolean"); + db->unsafe_mode = (info[0].As())->Value(); + } + sqlite3_db_config(db->db_handle, SQLITE_DBCONFIG_DEFENSIVE, + static_cast(!db->unsafe_mode), NULL); +} +void Database::JS_open(v8::Local _, + v8::PropertyCallbackInfo const &info) { + info.GetReturnValue().Set( + node ::ObjectWrap ::Unwrap(info.This())->open); +} +void Database::JS_inTransaction( + v8::Local _, v8::PropertyCallbackInfo const &info) { + Database *db = node ::ObjectWrap ::Unwrap(info.This()); + info.GetReturnValue().Set( + db->open && !static_cast(sqlite3_get_autocommit(db->db_handle))); +} +bool Database::Deserialize(v8::Local buffer, Addon *addon, + sqlite3 *db_handle, bool readonly) { + size_t length = node::Buffer::Length(buffer); + unsigned char *data = (unsigned char *)sqlite3_malloc64(length); + unsigned int flags = + SQLITE_DESERIALIZE_FREEONCLOSE | SQLITE_DESERIALIZE_RESIZEABLE; + + if (readonly) { + flags |= SQLITE_DESERIALIZE_READONLY; + } + if (length) { + if (!data) { + ThrowError("Out of memory"); + return false; + } + memcpy(data, node::Buffer::Data(buffer), length); + } + + int status = + sqlite3_deserialize(db_handle, "main", data, length, length, flags); + if (status != SQLITE_OK) { + ThrowSqliteError(addon, + status == SQLITE_ERROR ? "unable to deserialize database" + : sqlite3_errstr(status), + status); + return false; + } + + return true; +} +void Database::FreeSerialization(char *data, void *_) { sqlite3_free(data); } +int const Database::MAX_BUFFER_SIZE; +int const Database::MAX_STRING_SIZE; diff --git a/src/objects/database.hpp b/src/objects/database.hpp new file mode 100644 index 000000000..ab5b317dc --- /dev/null +++ b/src/objects/database.hpp @@ -0,0 +1,119 @@ +#ifndef BETTER_SQLITE3_OBJECTS_DATABASE_HPP +#define BETTER_SQLITE3_OBJECTS_DATABASE_HPP + +#include "../better_sqlite3_deps.hpp" + +// Forward declarations +struct Addon; +class Statement; +class Backup; + +class Database : public node::ObjectWrap { +public: + static v8::Local Init(v8::Isolate *isolate, + v8::Local data); + class CompareDatabase { + public: + bool operator()(Database const *const a, Database const *const b) const; + }; + class CompareStatement { + public: + bool operator()(Statement const *const a, Statement const *const b) const; + }; + class CompareBackup { + public: + bool operator()(Backup const *const a, Backup const *const b) const; + }; + void ThrowDatabaseError(); + static void ThrowSqliteError(Addon *addon, sqlite3 *db_handle); + static void ThrowSqliteError(Addon *addon, char const *message, int code); + bool Log(v8::Isolate *isolate, sqlite3_stmt *handle); + void AddStatement(Statement *stmt); + void RemoveStatement(Statement *stmt); + void AddBackup(Backup *backup); + void RemoveBackup(Backup *backup); + struct State { + bool const open; + bool busy; + bool const safe_ints; + bool const unsafe_mode; + bool was_js_error; + bool const has_logger; + unsigned short int iterators; + Addon *const addon; + }; + State *GetState(); + sqlite3 *GetHandle(); + Addon *GetAddon(); + void CloseHandles(); + ~Database(); + +private: + explicit Database(v8::Isolate *isolate, Addon *addon, sqlite3 *db_handle, + v8::Local logger); + static void JS_new(v8::FunctionCallbackInfo const &info); + static void JS_prepare(v8::FunctionCallbackInfo const &info); + static void JS_exec(v8::FunctionCallbackInfo const &info); + static void JS_backup(v8::FunctionCallbackInfo const &info); + static void JS_serialize(v8::FunctionCallbackInfo const &info); + static void JS_function(v8::FunctionCallbackInfo const &info); + static void JS_aggregate(v8::FunctionCallbackInfo const &info); + static void JS_table(v8::FunctionCallbackInfo const &info); + static void JS_loadExtension(v8::FunctionCallbackInfo const &info); + static void JS_close(v8::FunctionCallbackInfo const &info); + static void + JS_defaultSafeIntegers(v8::FunctionCallbackInfo const &info); + static void JS_unsafeMode(v8::FunctionCallbackInfo const &info); + static void JS_open(v8::Local _, + v8::PropertyCallbackInfo const &info); + static void JS_inTransaction(v8::Local _, + v8::PropertyCallbackInfo const &info); + static bool Deserialize(v8::Local buffer, Addon *addon, + sqlite3 *db_handle, bool readonly); + static void FreeSerialization(char *data, void *_); + static int const + MAX_BUFFER_SIZE = node::Buffer::kMaxLength > INT_MAX + ? INT_MAX + : static_cast(node::Buffer::kMaxLength); + static int const + MAX_STRING_SIZE = v8::String::kMaxLength > INT_MAX + ? INT_MAX + : static_cast(v8::String::kMaxLength); + sqlite3 *const db_handle; + bool open; + bool busy; + bool safe_ints; + bool unsafe_mode; + bool was_js_error; + bool const has_logger; + unsigned short int iterators; + Addon *const addon; + v8::Global const logger; + std::set stmts; + std::set backups; +}; +LZZ_INLINE void Database::AddStatement(Statement *stmt) { + stmts.insert(stmts.end(), stmt); +} +LZZ_INLINE void Database::RemoveStatement(Statement *stmt) { + stmts.erase(stmt); +} +LZZ_INLINE void Database::AddBackup(Backup *backup) { + backups.insert(backups.end(), backup); +} +LZZ_INLINE void Database::RemoveBackup(Backup *backup) { + backups.erase(backup); +} +LZZ_INLINE Database::State *Database::GetState() { + // Cast Database members to State struct - members layout must match exactly + // State: {open, busy, safe_ints, unsafe_mode, was_js_error, has_logger, + // iterators, addon} This relies on the fact that Database members from 'open' + // onwards have identical layout to State + static_assert(std::is_standard_layout_v, + "State must have standard layout for safe casting"); + return reinterpret_cast(&open); +} +LZZ_INLINE sqlite3 *Database::GetHandle() { return db_handle; } +LZZ_INLINE Addon *Database::GetAddon() { return addon; } + +#endif // BETTER_SQLITE3_OBJECTS_DATABASE_HPP diff --git a/src/objects/database.lzz b/src/objects/database.lzz deleted file mode 100644 index 28294fa3b..000000000 --- a/src/objects/database.lzz +++ /dev/null @@ -1,468 +0,0 @@ -class Database : public node::ObjectWrap { -public: - - INIT(Init) { - v8::Local t = NewConstructorTemplate(isolate, data, JS_new, "Database"); - SetPrototypeMethod(isolate, data, t, "prepare", JS_prepare); - SetPrototypeMethod(isolate, data, t, "exec", JS_exec); - SetPrototypeMethod(isolate, data, t, "backup", JS_backup); - SetPrototypeMethod(isolate, data, t, "serialize", JS_serialize); - SetPrototypeMethod(isolate, data, t, "function", JS_function); - SetPrototypeMethod(isolate, data, t, "aggregate", JS_aggregate); - SetPrototypeMethod(isolate, data, t, "table", JS_table); - SetPrototypeMethod(isolate, data, t, "loadExtension", JS_loadExtension); - SetPrototypeMethod(isolate, data, t, "close", JS_close); - SetPrototypeMethod(isolate, data, t, "defaultSafeIntegers", JS_defaultSafeIntegers); - SetPrototypeMethod(isolate, data, t, "unsafeMode", JS_unsafeMode); - SetPrototypeGetter(isolate, data, t, "open", JS_open); - SetPrototypeGetter(isolate, data, t, "inTransaction", JS_inTransaction); - return t->GetFunction(OnlyContext).ToLocalChecked(); - } - - // Used to support ordered containers. - class CompareDatabase { public: - bool operator() (Database const * const a, Database const * const b) const { - return a < b; - } - }; - class CompareStatement { public: - bool operator() (Statement const * const a, Statement const * const b) const { - return Statement::Compare(a, b); - } - }; - class CompareBackup { public: - bool operator() (Backup const * const a, Backup const * const b) const { - return Backup::Compare(a, b); - } - }; - - // Proper error handling logic for when an sqlite3 operation fails. - void ThrowDatabaseError() { - if (was_js_error) was_js_error = false; - else ThrowSqliteError(addon, db_handle); - } - static void ThrowSqliteError(Addon* addon, sqlite3* db_handle) { - assert(db_handle != NULL); - ThrowSqliteError(addon, sqlite3_errmsg(db_handle), sqlite3_extended_errcode(db_handle)); - } - static void ThrowSqliteError(Addon* addon, const char* message, int code) { - assert(message != NULL); - assert((code & 0xff) != SQLITE_OK); - assert((code & 0xff) != SQLITE_ROW); - assert((code & 0xff) != SQLITE_DONE); - EasyIsolate; - v8::Local args[2] = { - StringFromUtf8(isolate, message, -1), - addon->cs.Code(isolate, code) - }; - isolate->ThrowException(addon->SqliteError.Get(isolate) - ->NewInstance(OnlyContext, 2, args) - .ToLocalChecked()); - } - - // Allows Statements to log their executed SQL. - bool Log(v8::Isolate* isolate, sqlite3_stmt* handle) { - assert(was_js_error == false); - if (!has_logger) return false; - char* expanded = sqlite3_expanded_sql(handle); - v8::Local arg = StringFromUtf8(isolate, expanded ? expanded : sqlite3_sql(handle), -1); - was_js_error = logger.Get(isolate).As() - ->Call(OnlyContext, v8::Undefined(isolate), 1, &arg) - .IsEmpty(); - if (expanded) sqlite3_free(expanded); - return was_js_error; - } - - // Allow Statements to manage themselves when created and garbage collected. - inline void AddStatement(Statement* stmt) { stmts.insert(stmts.end(), stmt); } - inline void RemoveStatement(Statement* stmt) { stmts.erase(stmt); } - - // Allow Backups to manage themselves when created and garbage collected. - inline void AddBackup(Backup* backup) { backups.insert(backups.end(), backup); } - inline void RemoveBackup(Backup* backup) { backups.erase(backup); } - - // A view for Statements to see and modify Database state. - // The order of these fields must exactly match their actual order. - struct State { - const bool open; - bool busy; - const bool safe_ints; - const bool unsafe_mode; - bool was_js_error; - const bool has_logger; - unsigned short iterators; - Addon* const addon; - }; - inline State* GetState() { - return reinterpret_cast(&open); - } - inline sqlite3* GetHandle() { - return db_handle; - } - inline Addon* GetAddon() { - return addon; - } - - // Whenever this is used, addon->dbs.erase() must be invoked beforehand. - void CloseHandles() { - if (open) { - open = false; - for (Statement* stmt : stmts) stmt->CloseHandles(); - for (Backup* backup : backups) backup->CloseHandles(); - stmts.clear(); - backups.clear(); - int status = sqlite3_close(db_handle); - assert(status == SQLITE_OK); ((void)status); - } - } - - ~Database() { - if (open) addon->dbs.erase(this); - CloseHandles(); - } - -private: - - explicit Database( - v8::Isolate* isolate, - Addon* addon, - sqlite3* db_handle, - v8::Local logger - ) : - node::ObjectWrap(), - db_handle(db_handle), - open(true), - busy(false), - safe_ints(false), - unsafe_mode(false), - was_js_error(false), - has_logger(logger->IsFunction()), - iterators(0), - addon(addon), - logger(isolate, logger), - stmts(), - backups() { - assert(db_handle != NULL); - addon->dbs.insert(this); - } - - NODE_METHOD(JS_new) { - assert(info.IsConstructCall()); - REQUIRE_ARGUMENT_STRING(first, v8::Local filename); - REQUIRE_ARGUMENT_STRING(second, v8::Local filenameGiven); - REQUIRE_ARGUMENT_BOOLEAN(third, bool in_memory); - REQUIRE_ARGUMENT_BOOLEAN(fourth, bool readonly); - REQUIRE_ARGUMENT_BOOLEAN(fifth, bool must_exist); - REQUIRE_ARGUMENT_INT32(sixth, int timeout); - REQUIRE_ARGUMENT_ANY(seventh, v8::Local logger); - REQUIRE_ARGUMENT_ANY(eighth, v8::Local buffer); - - UseAddon; - UseIsolate; - sqlite3* db_handle; - v8::String::Utf8Value utf8(isolate, filename); - int mask = readonly ? SQLITE_OPEN_READONLY - : must_exist ? SQLITE_OPEN_READWRITE - : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); - - if (sqlite3_open_v2(*utf8, &db_handle, mask, NULL) != SQLITE_OK) { - ThrowSqliteError(addon, db_handle); - int status = sqlite3_close(db_handle); - assert(status == SQLITE_OK); ((void)status); - return; - } - - assert(sqlite3_db_mutex(db_handle) == NULL); - sqlite3_extended_result_codes(db_handle, 1); - sqlite3_busy_timeout(db_handle, timeout); - sqlite3_limit(db_handle, SQLITE_LIMIT_LENGTH, MAX_BUFFER_SIZE < MAX_STRING_SIZE ? MAX_BUFFER_SIZE : MAX_STRING_SIZE); - sqlite3_limit(db_handle, SQLITE_LIMIT_SQL_LENGTH, MAX_STRING_SIZE); - int status = sqlite3_db_config(db_handle, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, NULL); - assert(status == SQLITE_OK); - status = sqlite3_db_config(db_handle, SQLITE_DBCONFIG_DEFENSIVE, 1, NULL); - assert(status == SQLITE_OK); - - if (node::Buffer::HasInstance(buffer) && !Deserialize(buffer.As(), addon, db_handle, readonly)) { - int status = sqlite3_close(db_handle); - assert(status == SQLITE_OK); ((void)status); - return; - } - - UseContext; - Database* db = new Database(isolate, addon, db_handle, logger); - db->Wrap(info.This()); - SetFrozen(isolate, ctx, info.This(), addon->cs.memory, v8::Boolean::New(isolate, in_memory)); - SetFrozen(isolate, ctx, info.This(), addon->cs.readonly, v8::Boolean::New(isolate, readonly)); - SetFrozen(isolate, ctx, info.This(), addon->cs.name, filenameGiven); - - info.GetReturnValue().Set(info.This()); - } - - NODE_METHOD(JS_prepare) { - REQUIRE_ARGUMENT_STRING(first, v8::Local source); - REQUIRE_ARGUMENT_OBJECT(second, v8::Local database); - REQUIRE_ARGUMENT_BOOLEAN(third, bool pragmaMode); - (void)source; - (void)database; - (void)pragmaMode; - UseAddon; - UseIsolate; - v8::Local c = addon->Statement.Get(isolate); - addon->privileged_info = &info; - v8::MaybeLocal maybeStatement = c->NewInstance(OnlyContext, 0, NULL); - addon->privileged_info = NULL; - if (!maybeStatement.IsEmpty()) info.GetReturnValue().Set(maybeStatement.ToLocalChecked()); - } - - NODE_METHOD(JS_exec) { - Database* db = Unwrap(info.This()); - REQUIRE_ARGUMENT_STRING(first, v8::Local source); - REQUIRE_DATABASE_OPEN(db); - REQUIRE_DATABASE_NOT_BUSY(db); - REQUIRE_DATABASE_NO_ITERATORS_UNLESS_UNSAFE(db); - db->busy = true; - - UseIsolate; - v8::String::Utf8Value utf8(isolate, source); - const char* sql = *utf8; - const char* tail; - - int status; - const bool has_logger = db->has_logger; - sqlite3* const db_handle = db->db_handle; - sqlite3_stmt* handle; - - for (;;) { - while (IS_SKIPPED(*sql)) ++sql; - status = sqlite3_prepare_v2(db_handle, sql, -1, &handle, &tail); - sql = tail; - if (!handle) break; - if (has_logger && db->Log(isolate, handle)) { - sqlite3_finalize(handle); - status = -1; - break; - } - do status = sqlite3_step(handle); - while (status == SQLITE_ROW); - status = sqlite3_finalize(handle); - if (status != SQLITE_OK) break; - } - - db->busy = false; - if (status != SQLITE_OK) { - db->ThrowDatabaseError(); - } - } - - NODE_METHOD(JS_backup) { - REQUIRE_ARGUMENT_OBJECT(first, v8::Local database); - REQUIRE_ARGUMENT_STRING(second, v8::Local attachedName); - REQUIRE_ARGUMENT_STRING(third, v8::Local destFile); - REQUIRE_ARGUMENT_BOOLEAN(fourth, bool unlink); - (void)database; - (void)attachedName; - (void)destFile; - (void)unlink; - UseAddon; - UseIsolate; - v8::Local c = addon->Backup.Get(isolate); - addon->privileged_info = &info; - v8::MaybeLocal maybeBackup = c->NewInstance(OnlyContext, 0, NULL); - addon->privileged_info = NULL; - if (!maybeBackup.IsEmpty()) info.GetReturnValue().Set(maybeBackup.ToLocalChecked()); - } - - NODE_METHOD(JS_serialize) { - Database* db = Unwrap(info.This()); - REQUIRE_ARGUMENT_STRING(first, v8::Local attachedName); - REQUIRE_DATABASE_OPEN(db); - REQUIRE_DATABASE_NOT_BUSY(db); - REQUIRE_DATABASE_NO_ITERATORS(db); - - UseIsolate; - v8::String::Utf8Value attached_name(isolate, attachedName); - sqlite3_int64 length = -1; - unsigned char* data = sqlite3_serialize(db->db_handle, *attached_name, &length, 0); - - if (!data && length) { - ThrowError("Out of memory"); - return; - } - - info.GetReturnValue().Set( - SAFE_NEW_BUFFER(isolate, reinterpret_cast(data), length, FreeSerialization, NULL).ToLocalChecked() - ); - } - - NODE_METHOD(JS_function) { - Database* db = Unwrap(info.This()); - REQUIRE_ARGUMENT_FUNCTION(first, v8::Local fn); - REQUIRE_ARGUMENT_STRING(second, v8::Local nameString); - REQUIRE_ARGUMENT_INT32(third, int argc); - REQUIRE_ARGUMENT_INT32(fourth, int safe_ints); - REQUIRE_ARGUMENT_BOOLEAN(fifth, bool deterministic); - REQUIRE_ARGUMENT_BOOLEAN(sixth, bool direct_only); - REQUIRE_DATABASE_OPEN(db); - REQUIRE_DATABASE_NOT_BUSY(db); - REQUIRE_DATABASE_NO_ITERATORS(db); - - UseIsolate; - v8::String::Utf8Value name(isolate, nameString); - int mask = SQLITE_UTF8; - if (deterministic) mask |= SQLITE_DETERMINISTIC; - if (direct_only) mask |= SQLITE_DIRECTONLY; - safe_ints = safe_ints < 2 ? safe_ints : static_cast(db->safe_ints); - - if (sqlite3_create_function_v2(db->db_handle, *name, argc, mask, new CustomFunction(isolate, db, *name, fn, safe_ints), CustomFunction::xFunc, NULL, NULL, CustomFunction::xDestroy) != SQLITE_OK) { - db->ThrowDatabaseError(); - } - } - - NODE_METHOD(JS_aggregate) { - Database* db = Unwrap(info.This()); - REQUIRE_ARGUMENT_ANY(first, v8::Local start); - REQUIRE_ARGUMENT_FUNCTION(second, v8::Local step); - REQUIRE_ARGUMENT_ANY(third, v8::Local inverse); - REQUIRE_ARGUMENT_ANY(fourth, v8::Local result); - REQUIRE_ARGUMENT_STRING(fifth, v8::Local nameString); - REQUIRE_ARGUMENT_INT32(sixth, int argc); - REQUIRE_ARGUMENT_INT32(seventh, int safe_ints); - REQUIRE_ARGUMENT_BOOLEAN(eighth, bool deterministic); - REQUIRE_ARGUMENT_BOOLEAN(ninth, bool direct_only); - REQUIRE_DATABASE_OPEN(db); - REQUIRE_DATABASE_NOT_BUSY(db); - REQUIRE_DATABASE_NO_ITERATORS(db); - - UseIsolate; - v8::String::Utf8Value name(isolate, nameString); - auto xInverse = inverse->IsFunction() ? CustomAggregate::xInverse : NULL; - auto xValue = xInverse ? CustomAggregate::xValue : NULL; - int mask = SQLITE_UTF8; - if (deterministic) mask |= SQLITE_DETERMINISTIC; - if (direct_only) mask |= SQLITE_DIRECTONLY; - safe_ints = safe_ints < 2 ? safe_ints : static_cast(db->safe_ints); - - if (sqlite3_create_window_function(db->db_handle, *name, argc, mask, new CustomAggregate(isolate, db, *name, start, step, inverse, result, safe_ints), CustomAggregate::xStep, CustomAggregate::xFinal, xValue, xInverse, CustomAggregate::xDestroy) != SQLITE_OK) { - db->ThrowDatabaseError(); - } - } - - NODE_METHOD(JS_table) { - Database* db = Unwrap(info.This()); - REQUIRE_ARGUMENT_FUNCTION(first, v8::Local factory); - REQUIRE_ARGUMENT_STRING(second, v8::Local nameString); - REQUIRE_ARGUMENT_BOOLEAN(third, bool eponymous); - REQUIRE_DATABASE_OPEN(db); - REQUIRE_DATABASE_NOT_BUSY(db); - REQUIRE_DATABASE_NO_ITERATORS(db); - - UseIsolate; - v8::String::Utf8Value name(isolate, nameString); - sqlite3_module* module = eponymous ? &CustomTable::EPONYMOUS_MODULE : &CustomTable::MODULE; - - db->busy = true; - if (sqlite3_create_module_v2(db->db_handle, *name, module, new CustomTable(isolate, db, *name, factory), CustomTable::Destructor) != SQLITE_OK) { - db->ThrowDatabaseError(); - } - db->busy = false; - } - - NODE_METHOD(JS_loadExtension) { - Database* db = Unwrap(info.This()); - v8::Local entryPoint; - REQUIRE_ARGUMENT_STRING(first, v8::Local filename); - if (info.Length() > 1) { REQUIRE_ARGUMENT_STRING(second, entryPoint); } - REQUIRE_DATABASE_OPEN(db); - REQUIRE_DATABASE_NOT_BUSY(db); - REQUIRE_DATABASE_NO_ITERATORS(db); - UseIsolate; - char* error; - int status = sqlite3_load_extension( - db->db_handle, - *v8::String::Utf8Value(isolate, filename), - entryPoint.IsEmpty() ? NULL : *v8::String::Utf8Value(isolate, entryPoint), - &error - ); - if (status != SQLITE_OK) { - ThrowSqliteError(db->addon, error, status); - } - sqlite3_free(error); - } - - NODE_METHOD(JS_close) { - Database* db = Unwrap(info.This()); - if (db->open) { - REQUIRE_DATABASE_NOT_BUSY(db); - REQUIRE_DATABASE_NO_ITERATORS(db); - db->addon->dbs.erase(db); - db->CloseHandles(); - } - } - - NODE_METHOD(JS_defaultSafeIntegers) { - Database* db = Unwrap(info.This()); - if (info.Length() == 0) db->safe_ints = true; - else { REQUIRE_ARGUMENT_BOOLEAN(first, db->safe_ints); } - } - - NODE_METHOD(JS_unsafeMode) { - Database* db = Unwrap(info.This()); - if (info.Length() == 0) db->unsafe_mode = true; - else { REQUIRE_ARGUMENT_BOOLEAN(first, db->unsafe_mode); } - sqlite3_db_config(db->db_handle, SQLITE_DBCONFIG_DEFENSIVE, static_cast(!db->unsafe_mode), NULL); - } - - NODE_GETTER(JS_open) { - info.GetReturnValue().Set(Unwrap(info.This())->open); - } - - NODE_GETTER(JS_inTransaction) { - Database* db = Unwrap(info.This()); - info.GetReturnValue().Set(db->open && !static_cast(sqlite3_get_autocommit(db->db_handle))); - } - - static bool Deserialize(v8::Local buffer, Addon* addon, sqlite3* db_handle, bool readonly) { - size_t length = node::Buffer::Length(buffer); - unsigned char* data = (unsigned char*)sqlite3_malloc64(length); - unsigned int flags = SQLITE_DESERIALIZE_FREEONCLOSE | SQLITE_DESERIALIZE_RESIZEABLE; - - if (readonly) { - flags |= SQLITE_DESERIALIZE_READONLY; - } - if (length) { - if (!data) { - ThrowError("Out of memory"); - return false; - } - memcpy(data, node::Buffer::Data(buffer), length); - } - - int status = sqlite3_deserialize(db_handle, "main", data, length, length, flags); - if (status != SQLITE_OK) { - ThrowSqliteError(addon, status == SQLITE_ERROR ? "unable to deserialize database" : sqlite3_errstr(status), status); - return false; - } - - return true; - } - - static void FreeSerialization(char* data, void* _) { - sqlite3_free(data); - } - - static const int MAX_BUFFER_SIZE = node::Buffer::kMaxLength > INT_MAX ? INT_MAX : static_cast(node::Buffer::kMaxLength); - static const int MAX_STRING_SIZE = v8::String::kMaxLength > INT_MAX ? INT_MAX : static_cast(v8::String::kMaxLength); - - sqlite3* const db_handle; - bool open; - bool busy; - bool safe_ints; - bool unsafe_mode; - bool was_js_error; - const bool has_logger; - unsigned short iterators; - Addon* const addon; - const v8::Global logger; - std::set stmts; - std::set backups; -}; diff --git a/src/objects/statement-iterator.cpp b/src/objects/statement-iterator.cpp new file mode 100644 index 000000000..612d2e563 --- /dev/null +++ b/src/objects/statement-iterator.cpp @@ -0,0 +1,171 @@ +#include "statement-iterator.hpp" +#include "../better_sqlite3_impl.hpp" + +// Inline implementations that depend on Addon +v8::Local StatementIterator::NewRecord(v8::Isolate *isolate, + v8::Local ctx, + v8::Local value, + Addon *addon, bool done) { + v8::Local record = v8::Object::New(isolate); + record->Set(ctx, addon->cs.value.Get(isolate), value).FromJust(); + record->Set(ctx, addon->cs.done.Get(isolate), v8::Boolean::New(isolate, done)) + .FromJust(); + return record; +} + +v8::Local StatementIterator::DoneRecord(v8::Isolate *isolate, + Addon *addon) { + return NewRecord(isolate, isolate->GetCurrentContext(), + v8::Undefined(isolate), addon, true); +} + +v8::Local StatementIterator::Init(v8::Isolate *isolate, + v8::Local data) { + v8::Local t = + NewConstructorTemplate(isolate, data, JS_new, "StatementIterator"); + SetPrototypeMethod(isolate, data, t, "next", JS_next); + SetPrototypeMethod(isolate, data, t, "return", JS_return); + SetPrototypeSymbolMethod(isolate, data, t, v8::Symbol::GetIterator(isolate), + JS_symbolIterator); + return t->GetFunction(isolate->GetCurrentContext()).ToLocalChecked(); +} +StatementIterator::~StatementIterator() {} +StatementIterator::StatementIterator(Statement *stmt, bool bound) + : node::ObjectWrap(), stmt(stmt), handle(stmt->handle), + db_state(stmt->db->GetState()), bound(bound), safe_ints(stmt->safe_ints), + mode(stmt->mode), alive(true), logged(!db_state->has_logger) { + assert(stmt != NULL); + assert(handle != NULL); + assert(stmt->bound == bound); + assert(stmt->alive == true); + assert(stmt->locked == false); + assert(db_state->iterators < USHRT_MAX); + stmt->locked = true; + db_state->iterators += 1; +} +void StatementIterator::JS_new( + v8::FunctionCallbackInfo const &info) { + Addon *addon = static_cast(info.Data().As()->Value()); + if (!addon->privileged_info) + return ThrowTypeError("Disabled constructor"); + assert(info.IsConstructCall()); + + StatementIterator *iter; + { + const v8::FunctionCallbackInfo &info = *addon->privileged_info; + Statement *stmt = node ::ObjectWrap ::Unwrap(info.This()); + if (!stmt->returns_data) + return ThrowTypeError( + "This statement does not return data. Use run() instead"); + sqlite3_stmt *handle = stmt->handle; + Database *db = stmt->db; + if (!db->GetState()->open) + return ThrowTypeError("The database connection is not open"); + if (db->GetState()->busy) + return ThrowTypeError( + "This database connection is busy executing a query"); + if (stmt->locked) + return ThrowTypeError("This statement is busy executing a query"); + if (db->GetState()->iterators == USHRT_MAX) + return ThrowRangeError("Too many active database iterators"); + const bool bound = stmt->bound; + if (!bound) { + Binder binder(handle); + if (!binder.Bind(info, info.Length(), stmt)) { + sqlite3_clear_bindings(handle); + return; + } + ((void)0); + } else if (info.Length() > 0) { + return ThrowTypeError("This statement already has bound parameters"); + } + ((void)0); + iter = new StatementIterator(stmt, bound); + } + v8::Isolate *isolate = info.GetIsolate(); + v8::Local ctx = isolate->GetCurrentContext(); + iter->Wrap(info.This()); + SetFrozen(isolate, ctx, info.This(), addon->cs.statement, + addon->privileged_info->This()); + + info.GetReturnValue().Set(info.This()); +} +void StatementIterator::JS_next( + v8::FunctionCallbackInfo const &info) { + StatementIterator *iter = + node ::ObjectWrap ::Unwrap(info.This()); + if (iter->db_state->busy) + return ThrowTypeError("This database connection is busy executing a query"); + if (iter->alive) + iter->Next(info); + else + info.GetReturnValue().Set( + DoneRecord(info.GetIsolate(), iter->db_state->addon)); +} +void StatementIterator::JS_return( + v8::FunctionCallbackInfo const &info) { + StatementIterator *iter = + node ::ObjectWrap ::Unwrap(info.This()); + if (iter->db_state->busy) + return ThrowTypeError("This database connection is busy executing a query"); + if (iter->alive) + iter->Return(info); + else + info.GetReturnValue().Set( + DoneRecord(info.GetIsolate(), iter->db_state->addon)); +} +void StatementIterator::JS_symbolIterator( + v8::FunctionCallbackInfo const &info) { + info.GetReturnValue().Set(info.This()); +} +void StatementIterator::Next(v8::FunctionCallbackInfo const &info) { + assert(alive == true); + db_state->busy = true; + if (!logged) { + logged = true; + if (stmt->db->Log(info.GetIsolate(), handle)) { + db_state->busy = false; + Throw(); + return; + } + } + int status = sqlite3_step(handle); + db_state->busy = false; + if (status == SQLITE_ROW) { + v8::Isolate *isolate = info.GetIsolate(); + v8::Local ctx = isolate->GetCurrentContext(); + info.GetReturnValue().Set(NewRecord( + isolate, ctx, Data::GetRowJS(isolate, ctx, handle, safe_ints, mode), + db_state->addon, false)); + } else { + if (status == SQLITE_DONE) + Return(info); + else + Throw(); + } +} +void StatementIterator::Return( + v8::FunctionCallbackInfo const &info) { + Cleanup(); + info.GetReturnValue().Set(DoneRecord(info.GetIsolate(), db_state->addon)); + if (!bound) { + sqlite3_clear_bindings(handle); + } + return; +} +void StatementIterator::Throw() { + Cleanup(); + Database *db = stmt->db; + db->ThrowDatabaseError(); + if (!bound) { + sqlite3_clear_bindings(handle); + } + return; +} +void StatementIterator::Cleanup() { + assert(alive == true); + alive = false; + stmt->locked = false; + db_state->iterators -= 1; + sqlite3_reset(handle); +} diff --git a/src/objects/statement-iterator.hpp b/src/objects/statement-iterator.hpp new file mode 100644 index 000000000..290609517 --- /dev/null +++ b/src/objects/statement-iterator.hpp @@ -0,0 +1,40 @@ +#ifndef BETTER_SQLITE3_OBJECTS_STATEMENT_ITERATOR_HPP +#define BETTER_SQLITE3_OBJECTS_STATEMENT_ITERATOR_HPP + +#include "../better_sqlite3_deps.hpp" +#include "database.hpp" + +class StatementIterator : public node::ObjectWrap { +public: + static v8::Local Init(v8::Isolate *isolate, + v8::Local data); + ~StatementIterator(); + +private: + explicit StatementIterator(Statement *stmt, bool bound); + static void JS_new(v8::FunctionCallbackInfo const &info); + static void JS_next(v8::FunctionCallbackInfo const &info); + static void JS_return(v8::FunctionCallbackInfo const &info); + static void + JS_symbolIterator(v8::FunctionCallbackInfo const &info); + void Next(v8::FunctionCallbackInfo const &info); + void Return(v8::FunctionCallbackInfo const &info); + void Throw(); + void Cleanup(); + static v8::Local NewRecord(v8::Isolate *isolate, + v8::Local ctx, + v8::Local value, + Addon *addon, bool done); + static v8::Local DoneRecord(v8::Isolate *isolate, Addon *addon); + Statement *const stmt; + sqlite3_stmt *const handle; + Database::State *const db_state; + bool const bound; + bool const safe_ints; + char const mode; + bool alive; + bool logged; +}; +// Implementation moved to cpp file due to Addon dependency + +#endif // BETTER_SQLITE3_OBJECTS_STATEMENT_ITERATOR_HPP diff --git a/src/objects/statement-iterator.lzz b/src/objects/statement-iterator.lzz deleted file mode 100644 index 0936645b9..000000000 --- a/src/objects/statement-iterator.lzz +++ /dev/null @@ -1,138 +0,0 @@ -class StatementIterator : public node::ObjectWrap { -public: - - INIT(Init) { - v8::Local t = NewConstructorTemplate(isolate, data, JS_new, "StatementIterator"); - SetPrototypeMethod(isolate, data, t, "next", JS_next); - SetPrototypeMethod(isolate, data, t, "return", JS_return); - SetPrototypeSymbolMethod(isolate, data, t, v8::Symbol::GetIterator(isolate), JS_symbolIterator); - return t->GetFunction(OnlyContext).ToLocalChecked(); - } - - // The ~Statement destructor currently covers any state this object creates. - // Additionally, we actually DON'T want to revert stmt->locked or db_state - // ->iterators in this destructor, to ensure deterministic database access. - ~StatementIterator() {} - -private: - - explicit StatementIterator(Statement* stmt, bool bound) : node::ObjectWrap(), - stmt(stmt), - handle(stmt->handle), - db_state(stmt->db->GetState()), - bound(bound), - safe_ints(stmt->safe_ints), - mode(stmt->mode), - alive(true), - logged(!db_state->has_logger) { - assert(stmt != NULL); - assert(handle != NULL); - assert(stmt->bound == bound); - assert(stmt->alive == true); - assert(stmt->locked == false); - assert(db_state->iterators < USHRT_MAX); - stmt->locked = true; - db_state->iterators += 1; - } - - NODE_METHOD(JS_new) { - UseAddon; - if (!addon->privileged_info) return ThrowTypeError("Disabled constructor"); - assert(info.IsConstructCall()); - - StatementIterator* iter; - { - NODE_ARGUMENTS info = *addon->privileged_info; - STATEMENT_START_LOGIC(REQUIRE_STATEMENT_RETURNS_DATA, DOES_ADD_ITERATOR); - iter = new StatementIterator(stmt, bound); - } - UseIsolate; - UseContext; - iter->Wrap(info.This()); - SetFrozen(isolate, ctx, info.This(), addon->cs.statement, addon->privileged_info->This()); - - info.GetReturnValue().Set(info.This()); - } - - NODE_METHOD(JS_next) { - StatementIterator* iter = Unwrap(info.This()); - REQUIRE_DATABASE_NOT_BUSY(iter->db_state); - if (iter->alive) iter->Next(info); - else info.GetReturnValue().Set(DoneRecord(OnlyIsolate, iter->db_state->addon)); - } - - NODE_METHOD(JS_return) { - StatementIterator* iter = Unwrap(info.This()); - REQUIRE_DATABASE_NOT_BUSY(iter->db_state); - if (iter->alive) iter->Return(info); - else info.GetReturnValue().Set(DoneRecord(OnlyIsolate, iter->db_state->addon)); - } - - NODE_METHOD(JS_symbolIterator) { - info.GetReturnValue().Set(info.This()); - } - - void Next(NODE_ARGUMENTS info) { - assert(alive == true); - db_state->busy = true; - if (!logged) { - logged = true; - if (stmt->db->Log(OnlyIsolate, handle)) { - db_state->busy = false; - Throw(); - return; - } - } - int status = sqlite3_step(handle); - db_state->busy = false; - if (status == SQLITE_ROW) { - UseIsolate; - UseContext; - info.GetReturnValue().Set( - NewRecord(isolate, ctx, Data::GetRowJS(isolate, ctx, handle, safe_ints, mode), db_state->addon, false) - ); - } else { - if (status == SQLITE_DONE) Return(info); - else Throw(); - } - } - - void Return(NODE_ARGUMENTS info) { - Cleanup(); - STATEMENT_RETURN_LOGIC(DoneRecord(OnlyIsolate, db_state->addon)); - } - - void Throw() { - Cleanup(); - Database* db = stmt->db; - STATEMENT_THROW_LOGIC(); - } - - void Cleanup() { - assert(alive == true); - alive = false; - stmt->locked = false; - db_state->iterators -= 1; - sqlite3_reset(handle); - } - - static inline v8::Local NewRecord(v8::Isolate* isolate, v8::Local ctx, v8::Local value, Addon* addon, bool done) { - v8::Local record = v8::Object::New(isolate); - record->Set(ctx, addon->cs.value.Get(isolate), value).FromJust(); - record->Set(ctx, addon->cs.done.Get(isolate), v8::Boolean::New(isolate, done)).FromJust(); - return record; - } - - static inline v8::Local DoneRecord(v8::Isolate* isolate, Addon* addon) { - return NewRecord(isolate, OnlyContext, v8::Undefined(isolate), addon, true); - } - - Statement* const stmt; - sqlite3_stmt* const handle; - Database::State* const db_state; - const bool bound; - const bool safe_ints; - const char mode; - bool alive; - bool logged; -}; diff --git a/src/objects/statement.cpp b/src/objects/statement.cpp new file mode 100644 index 000000000..a33c19626 --- /dev/null +++ b/src/objects/statement.cpp @@ -0,0 +1,535 @@ +#include "statement.hpp" +#include "../better_sqlite3_impl.hpp" + +v8::Local Statement::Init(v8::Isolate *isolate, + v8::Local data) { + v8::Local t = + NewConstructorTemplate(isolate, data, JS_new, "Statement"); + SetPrototypeMethod(isolate, data, t, "run", JS_run); + SetPrototypeMethod(isolate, data, t, "get", JS_get); + SetPrototypeMethod(isolate, data, t, "all", JS_all); + SetPrototypeMethod(isolate, data, t, "iterate", JS_iterate); + SetPrototypeMethod(isolate, data, t, "bind", JS_bind); + SetPrototypeMethod(isolate, data, t, "pluck", JS_pluck); + SetPrototypeMethod(isolate, data, t, "expand", JS_expand); + SetPrototypeMethod(isolate, data, t, "raw", JS_raw); + SetPrototypeMethod(isolate, data, t, "safeIntegers", JS_safeIntegers); + SetPrototypeMethod(isolate, data, t, "columns", JS_columns); + SetPrototypeGetter(isolate, data, t, "busy", JS_busy); + return t->GetFunction(isolate->GetCurrentContext()).ToLocalChecked(); +} +BindMap *Statement::GetBindMap(v8::Isolate *isolate) { + if (has_bind_map) + return &extras->bind_map; + BindMap *bind_map = &extras->bind_map; + int param_count = sqlite3_bind_parameter_count(handle); + for (int i = 1; i <= param_count; ++i) { + const char *name = sqlite3_bind_parameter_name(handle, i); + if (name != NULL) + bind_map->Add(isolate, name + 1, i); + } + has_bind_map = true; + return bind_map; +} +void Statement::CloseHandles() { + if (alive) { + alive = false; + sqlite3_finalize(handle); + } +} +Statement::~Statement() { + if (alive) + db->RemoveStatement(this); + CloseHandles(); + delete extras; +} +Statement::Extras::Extras(sqlite3_uint64 id) : bind_map(0), id(id) {} +Statement::Statement(Database *db, sqlite3_stmt *handle, sqlite3_uint64 id, + bool returns_data) + : node::ObjectWrap(), db(db), handle(handle), extras(new Extras(id)), + alive(true), locked(false), bound(false), has_bind_map(false), + safe_ints(db->GetState()->safe_ints), mode(Data::FLAT), + returns_data(returns_data) { + assert(db != NULL); + assert(handle != NULL); + assert(db->GetState()->open); + assert(!db->GetState()->busy); + db->AddStatement(this); +} +void Statement::JS_new(v8::FunctionCallbackInfo const &info) { + Addon *addon = static_cast(info.Data().As()->Value()); + if (!addon->privileged_info) { + return ThrowTypeError( + "Statements can only be constructed by the db.prepare() method"); + } + assert(info.IsConstructCall()); + Database *db = + node ::ObjectWrap ::Unwrap(addon->privileged_info->This()); + if (!db->GetState()->open) + return ThrowTypeError("The database connection is not open"); + if (db->GetState()->busy) + return ThrowTypeError("This database connection is busy executing a query"); + + v8::Local source = (*addon->privileged_info)[0].As(); + v8::Local database = + (*addon->privileged_info)[1].As(); + bool pragmaMode = (*addon->privileged_info)[2].As()->Value(); + int flags = SQLITE_PREPARE_PERSISTENT; + + if (pragmaMode) { + if (!db->GetState()->unsafe_mode) { + if (db->GetState()->iterators) + return ThrowTypeError( + "This database connection is busy executing a query"); + } + ((void)0); + flags = 0; + } + + v8::Isolate *isolate = info.GetIsolate(); + v8::String::Utf8Value utf8(isolate, source); + sqlite3_stmt *handle; + const char *tail; + + if (sqlite3_prepare_v3(db->GetHandle(), *utf8, utf8.length() + 1, flags, + &handle, &tail) != SQLITE_OK) { + return db->ThrowDatabaseError(); + } + if (handle == NULL) { + return ThrowRangeError("The supplied SQL string contains no statements"); + } + + for (char c; (c = *tail);) { + if (IS_SKIPPED(c)) { + ++tail; + continue; + } + if (c == '/' && tail[1] == '*') { + tail += 2; + for (char c; (c = *tail); ++tail) { + if (c == '*' && tail[1] == '/') { + tail += 2; + break; + } + } + } else if (c == '-' && tail[1] == '-') { + tail += 2; + for (char c; (c = *tail); ++tail) { + if (c == '\n') { + ++tail; + break; + } + } + } else { + sqlite3_finalize(handle); + return ThrowRangeError( + "The supplied SQL string contains more than one statement"); + } + } + + v8::Local ctx = isolate->GetCurrentContext(); + bool returns_data = sqlite3_column_count(handle) >= 1 || pragmaMode; + Statement *stmt = new Statement(db, handle, addon->NextId(), returns_data); + stmt->Wrap(info.This()); + SetFrozen(isolate, ctx, info.This(), addon->cs.reader, + v8::Boolean::New(isolate, returns_data)); + SetFrozen(isolate, ctx, info.This(), addon->cs.readonly, + v8::Boolean::New(isolate, sqlite3_stmt_readonly(handle) != 0)); + SetFrozen(isolate, ctx, info.This(), addon->cs.source, source); + SetFrozen(isolate, ctx, info.This(), addon->cs.database, database); + + info.GetReturnValue().Set(info.This()); +} +void Statement::JS_run(v8::FunctionCallbackInfo const &info) { + Statement *stmt = node ::ObjectWrap ::Unwrap(info.This()); + ((void)0); + sqlite3_stmt *handle = stmt->handle; + Database *db = stmt->db; + if (!db->GetState()->open) + return ThrowTypeError("The database connection is not open"); + if (db->GetState()->busy) + return ThrowTypeError("This database connection is busy executing a query"); + if (stmt->locked) + return ThrowTypeError("This statement is busy executing a query"); + if (!db->GetState()->unsafe_mode) { + if (db->GetState()->iterators) + return ThrowTypeError( + "This database connection is busy executing a query"); + } + ((void)0); + const bool bound = stmt->bound; + if (!bound) { + Binder binder(handle); + if (!binder.Bind(info, info.Length(), stmt)) { + sqlite3_clear_bindings(handle); + return; + } + ((void)0); + } else if (info.Length() > 0) { + return ThrowTypeError("This statement already has bound parameters"); + } + ((void)0); + db->GetState()->busy = true; + v8::Isolate *isolate = info.GetIsolate(); + if (db->Log(isolate, handle)) { + db->GetState()->busy = false; + db->ThrowDatabaseError(); + if (!bound) { + sqlite3_clear_bindings(handle); + } + return; + } + ((void)0); + sqlite3 *db_handle = db->GetHandle(); + int total_changes_before = sqlite3_total_changes(db_handle); + + sqlite3_step(handle); + if (sqlite3_reset(handle) == SQLITE_OK) { + int changes = sqlite3_total_changes(db_handle) == total_changes_before + ? 0 + : sqlite3_changes(db_handle); + sqlite3_int64 id = sqlite3_last_insert_rowid(db_handle); + Addon *addon = db->GetAddon(); + v8::Local ctx = isolate->GetCurrentContext(); + v8::Local result = v8::Object::New(isolate); + result + ->Set(ctx, addon->cs.changes.Get(isolate), + v8::Int32::New(isolate, changes)) + .FromJust(); + result + ->Set(ctx, addon->cs.lastInsertRowid.Get(isolate), + stmt->safe_ints + ? v8::BigInt::New(isolate, id).As() + : v8::Number::New(isolate, (double)id).As()) + .FromJust(); + db->GetState()->busy = false; + info.GetReturnValue().Set(result); + if (!bound) { + sqlite3_clear_bindings(handle); + } + return; + } + db->GetState()->busy = false; + db->ThrowDatabaseError(); + if (!bound) { + sqlite3_clear_bindings(handle); + } + return; +} +void Statement::JS_get(v8::FunctionCallbackInfo const &info) { + Statement *stmt = node ::ObjectWrap ::Unwrap(info.This()); + if (!stmt->returns_data) + return ThrowTypeError( + "This statement does not return data. Use run() instead"); + sqlite3_stmt *handle = stmt->handle; + Database *db = stmt->db; + if (!db->GetState()->open) + return ThrowTypeError("The database connection is not open"); + if (db->GetState()->busy) + return ThrowTypeError("This database connection is busy executing a query"); + if (stmt->locked) + return ThrowTypeError("This statement is busy executing a query"); + const bool bound = stmt->bound; + if (!bound) { + Binder binder(handle); + if (!binder.Bind(info, info.Length(), stmt)) { + sqlite3_clear_bindings(handle); + return; + } + ((void)0); + } else if (info.Length() > 0) { + return ThrowTypeError("This statement already has bound parameters"); + } + ((void)0); + db->GetState()->busy = true; + v8::Isolate *isolate = info.GetIsolate(); + if (db->Log(isolate, handle)) { + db->GetState()->busy = false; + db->ThrowDatabaseError(); + if (!bound) { + sqlite3_clear_bindings(handle); + } + return; + } + ((void)0); + int status = sqlite3_step(handle); + if (status == SQLITE_ROW) { + v8::Local result = + Data::GetRowJS(isolate, isolate->GetCurrentContext(), handle, + stmt->safe_ints, stmt->mode); + sqlite3_reset(handle); + db->GetState()->busy = false; + info.GetReturnValue().Set(result); + if (!bound) { + sqlite3_clear_bindings(handle); + } + return; + } else if (status == SQLITE_DONE) { + sqlite3_reset(handle); + db->GetState()->busy = false; + info.GetReturnValue().Set(v8::Undefined(isolate)); + if (!bound) { + sqlite3_clear_bindings(handle); + } + return; + } + sqlite3_reset(handle); + db->GetState()->busy = false; + db->ThrowDatabaseError(); + if (!bound) { + sqlite3_clear_bindings(handle); + } + return; +} +void Statement::JS_all(v8::FunctionCallbackInfo const &info) { + Statement *stmt = node ::ObjectWrap ::Unwrap(info.This()); + if (!stmt->returns_data) + return ThrowTypeError( + "This statement does not return data. Use run() instead"); + sqlite3_stmt *handle = stmt->handle; + Database *db = stmt->db; + if (!db->GetState()->open) + return ThrowTypeError("The database connection is not open"); + if (db->GetState()->busy) + return ThrowTypeError("This database connection is busy executing a query"); + if (stmt->locked) + return ThrowTypeError("This statement is busy executing a query"); + const bool bound = stmt->bound; + if (!bound) { + Binder binder(handle); + if (!binder.Bind(info, info.Length(), stmt)) { + sqlite3_clear_bindings(handle); + return; + } + ((void)0); + } else if (info.Length() > 0) { + return ThrowTypeError("This statement already has bound parameters"); + } + ((void)0); + db->GetState()->busy = true; + v8::Isolate *isolate = info.GetIsolate(); + if (db->Log(isolate, handle)) { + db->GetState()->busy = false; + db->ThrowDatabaseError(); + if (!bound) { + sqlite3_clear_bindings(handle); + } + return; + } + ((void)0); + v8::Local ctx = isolate->GetCurrentContext(); + v8::Local result = v8::Array::New(isolate, 0); + uint32_t row_count = 0; + const bool safe_ints = stmt->safe_ints; + const char mode = stmt->mode; + bool js_error = false; + + while (sqlite3_step(handle) == SQLITE_ROW) { + if (row_count == 0xffffffff) { + ThrowRangeError("Array overflow (too many rows returned)"); + js_error = true; + break; + } + result + ->Set(ctx, row_count++, + Data::GetRowJS(isolate, ctx, handle, safe_ints, mode)) + .FromJust(); + } + + if (sqlite3_reset(handle) == SQLITE_OK && !js_error) { + db->GetState()->busy = false; + info.GetReturnValue().Set(result); + if (!bound) { + sqlite3_clear_bindings(handle); + } + return; + } + if (js_error) + db->GetState()->was_js_error = true; + db->GetState()->busy = false; + db->ThrowDatabaseError(); + if (!bound) { + sqlite3_clear_bindings(handle); + } + return; +} +void Statement::JS_iterate(v8::FunctionCallbackInfo const &info) { + Addon *addon = static_cast(info.Data().As()->Value()); + v8::Isolate *isolate = info.GetIsolate(); + v8::Local c = addon->StatementIterator.Get(isolate); + addon->privileged_info = &info; + v8::MaybeLocal maybeIterator = + c->NewInstance(isolate->GetCurrentContext(), 0, NULL); + addon->privileged_info = NULL; + if (!maybeIterator.IsEmpty()) + info.GetReturnValue().Set(maybeIterator.ToLocalChecked()); +} +void Statement::JS_bind(v8::FunctionCallbackInfo const &info) { + Statement *stmt = node ::ObjectWrap ::Unwrap(info.This()); + if (stmt->bound) + return ThrowTypeError( + "The bind() method can only be invoked once per statement object"); + if (!stmt->db->GetState()->open) + return ThrowTypeError("The database connection is not open"); + if (stmt->db->GetState()->busy) + return ThrowTypeError("This database connection is busy executing a query"); + if (stmt->locked) + return ThrowTypeError("This statement is busy executing a query"); + Binder binder(stmt->handle); + if (!binder.Bind(info, info.Length(), stmt)) { + sqlite3_clear_bindings(stmt->handle); + return; + } + ((void)0); + stmt->bound = true; + info.GetReturnValue().Set(info.This()); +} +void Statement::JS_pluck(v8::FunctionCallbackInfo const &info) { + Statement *stmt = node ::ObjectWrap ::Unwrap(info.This()); + if (!stmt->returns_data) + return ThrowTypeError( + "The pluck() method is only for statements that return data"); + if (stmt->db->GetState()->busy) + return ThrowTypeError("This database connection is busy executing a query"); + if (stmt->locked) + return ThrowTypeError("This statement is busy executing a query"); + bool use = true; + if (info.Length() != 0) { + if (info.Length() <= (0) || !info[0]->IsBoolean()) + return ThrowTypeError("Expected " + "first" + " argument to be " + "a boolean"); + use = (info[0].As())->Value(); + } + stmt->mode = use ? Data::PLUCK + : stmt->mode == Data::PLUCK ? Data::FLAT + : stmt->mode; + info.GetReturnValue().Set(info.This()); +} +void Statement::JS_expand(v8::FunctionCallbackInfo const &info) { + Statement *stmt = node ::ObjectWrap ::Unwrap(info.This()); + if (!stmt->returns_data) + return ThrowTypeError( + "The expand() method is only for statements that return data"); + if (stmt->db->GetState()->busy) + return ThrowTypeError("This database connection is busy executing a query"); + if (stmt->locked) + return ThrowTypeError("This statement is busy executing a query"); + bool use = true; + if (info.Length() != 0) { + if (info.Length() <= (0) || !info[0]->IsBoolean()) + return ThrowTypeError("Expected " + "first" + " argument to be " + "a boolean"); + use = (info[0].As())->Value(); + } + stmt->mode = use ? Data::EXPAND + : stmt->mode == Data::EXPAND ? Data::FLAT + : stmt->mode; + info.GetReturnValue().Set(info.This()); +} +void Statement::JS_raw(v8::FunctionCallbackInfo const &info) { + Statement *stmt = node ::ObjectWrap ::Unwrap(info.This()); + if (!stmt->returns_data) + return ThrowTypeError( + "The raw() method is only for statements that return data"); + if (stmt->db->GetState()->busy) + return ThrowTypeError("This database connection is busy executing a query"); + if (stmt->locked) + return ThrowTypeError("This statement is busy executing a query"); + bool use = true; + if (info.Length() != 0) { + if (info.Length() <= (0) || !info[0]->IsBoolean()) + return ThrowTypeError("Expected " + "first" + " argument to be " + "a boolean"); + use = (info[0].As())->Value(); + } + stmt->mode = use ? Data::RAW + : stmt->mode == Data::RAW ? Data::FLAT + : stmt->mode; + info.GetReturnValue().Set(info.This()); +} +void Statement::JS_safeIntegers( + v8::FunctionCallbackInfo const &info) { + Statement *stmt = node ::ObjectWrap ::Unwrap(info.This()); + if (stmt->db->GetState()->busy) + return ThrowTypeError("This database connection is busy executing a query"); + if (stmt->locked) + return ThrowTypeError("This statement is busy executing a query"); + if (info.Length() == 0) + stmt->safe_ints = true; + else { + if (info.Length() <= (0) || !info[0]->IsBoolean()) + return ThrowTypeError("Expected " + "first" + " argument to be " + "a boolean"); + stmt->safe_ints = (info[0].As())->Value(); + } + info.GetReturnValue().Set(info.This()); +} +void Statement::JS_columns(v8::FunctionCallbackInfo const &info) { + Statement *stmt = node ::ObjectWrap ::Unwrap(info.This()); + if (!stmt->returns_data) + return ThrowTypeError( + "The columns() method is only for statements that return data"); + if (!stmt->db->GetState()->open) + return ThrowTypeError("The database connection is not open"); + if (stmt->db->GetState()->busy) + return ThrowTypeError("This database connection is busy executing a query"); + Addon *addon = stmt->db->GetAddon(); + v8::Isolate *isolate = info.GetIsolate(); + v8::Local ctx = isolate->GetCurrentContext(); + + int column_count = sqlite3_column_count(stmt->handle); + v8::Local columns = v8::Array::New(isolate); + + v8::Local name = addon->cs.name.Get(isolate); + v8::Local columnName = addon->cs.column.Get(isolate); + v8::Local tableName = addon->cs.table.Get(isolate); + v8::Local databaseName = addon->cs.database.Get(isolate); + v8::Local typeName = addon->cs.type.Get(isolate); + + for (int i = 0; i < column_count; ++i) { + v8::Local column = v8::Object::New(isolate); + + column + ->Set(ctx, name, + InternalizedFromUtf8OrNull( + isolate, sqlite3_column_name(stmt->handle, i), -1)) + .FromJust(); + column + ->Set(ctx, columnName, + InternalizedFromUtf8OrNull( + isolate, sqlite3_column_origin_name(stmt->handle, i), -1)) + .FromJust(); + column + ->Set(ctx, tableName, + InternalizedFromUtf8OrNull( + isolate, sqlite3_column_table_name(stmt->handle, i), -1)) + .FromJust(); + column + ->Set(ctx, databaseName, + InternalizedFromUtf8OrNull( + isolate, sqlite3_column_database_name(stmt->handle, i), -1)) + .FromJust(); + column + ->Set(ctx, typeName, + InternalizedFromUtf8OrNull( + isolate, sqlite3_column_decltype(stmt->handle, i), -1)) + .FromJust(); + + columns->Set(ctx, i, column).FromJust(); + } + + info.GetReturnValue().Set(columns); +} +void Statement::JS_busy(v8::Local _, + v8::PropertyCallbackInfo const &info) { + Statement *stmt = node ::ObjectWrap ::Unwrap(info.This()); + info.GetReturnValue().Set(stmt->alive && stmt->locked); +} diff --git a/src/objects/statement.hpp b/src/objects/statement.hpp new file mode 100644 index 000000000..9e55a06bd --- /dev/null +++ b/src/objects/statement.hpp @@ -0,0 +1,58 @@ +#ifndef BETTER_SQLITE3_OBJECTS_STATEMENT_HPP +#define BETTER_SQLITE3_OBJECTS_STATEMENT_HPP + +#include "../better_sqlite3_deps.hpp" +#include "../util/bind-map.hpp" +#include "../util/data.hpp" +#include "database.hpp" + +class Statement : public node::ObjectWrap { + friend class StatementIterator; + +public: + static v8::Local Init(v8::Isolate *isolate, + v8::Local data); + static bool Compare(Statement const *const a, Statement const *const b); + BindMap *GetBindMap(v8::Isolate *isolate); + void CloseHandles(); + ~Statement(); + +private: + class Extras { + friend class Statement; + explicit Extras(sqlite3_uint64 id); + BindMap bind_map; + sqlite3_uint64 const id; + }; + explicit Statement(Database *db, sqlite3_stmt *handle, sqlite3_uint64 id, + bool returns_data); + static void JS_new(v8::FunctionCallbackInfo const &info); + static void JS_run(v8::FunctionCallbackInfo const &info); + static void JS_get(v8::FunctionCallbackInfo const &info); + static void JS_all(v8::FunctionCallbackInfo const &info); + static void JS_iterate(v8::FunctionCallbackInfo const &info); + static void JS_bind(v8::FunctionCallbackInfo const &info); + static void JS_pluck(v8::FunctionCallbackInfo const &info); + static void JS_expand(v8::FunctionCallbackInfo const &info); + static void JS_raw(v8::FunctionCallbackInfo const &info); + static void JS_safeIntegers(v8::FunctionCallbackInfo const &info); + static void JS_columns(v8::FunctionCallbackInfo const &info); + static void JS_busy(v8::Local _, + v8::PropertyCallbackInfo const &info); + Database *const db; + sqlite3_stmt *const handle; + Extras *const extras; + bool alive; + bool locked; + bool bound; + bool has_bind_map; + bool safe_ints; + char mode; + bool const returns_data; +}; +LZZ_INLINE bool Statement::Compare(Statement const *const a, + Statement const *const b) { + return a->extras->id < b->extras->id; +} + +#endif // BETTER_SQLITE3_OBJECTS_STATEMENT_HPP diff --git a/src/objects/statement.lzz b/src/objects/statement.lzz deleted file mode 100644 index 5e2c80570..000000000 --- a/src/objects/statement.lzz +++ /dev/null @@ -1,336 +0,0 @@ -class Statement : public node::ObjectWrap { friend class StatementIterator; -public: - - INIT(Init) { - v8::Local t = NewConstructorTemplate(isolate, data, JS_new, "Statement"); - SetPrototypeMethod(isolate, data, t, "run", JS_run); - SetPrototypeMethod(isolate, data, t, "get", JS_get); - SetPrototypeMethod(isolate, data, t, "all", JS_all); - SetPrototypeMethod(isolate, data, t, "iterate", JS_iterate); - SetPrototypeMethod(isolate, data, t, "bind", JS_bind); - SetPrototypeMethod(isolate, data, t, "pluck", JS_pluck); - SetPrototypeMethod(isolate, data, t, "expand", JS_expand); - SetPrototypeMethod(isolate, data, t, "raw", JS_raw); - SetPrototypeMethod(isolate, data, t, "safeIntegers", JS_safeIntegers); - SetPrototypeMethod(isolate, data, t, "columns", JS_columns); - SetPrototypeGetter(isolate, data, t, "busy", JS_busy); - return t->GetFunction(OnlyContext).ToLocalChecked(); - } - - // Used to support ordered containers. - static inline bool Compare(Statement const * const a, Statement const * const b) { - return a->extras->id < b->extras->id; - } - - // Returns the Statement's bind map (creates it upon first execution). - BindMap* GetBindMap(v8::Isolate* isolate) { - if (has_bind_map) return &extras->bind_map; - BindMap* bind_map = &extras->bind_map; - int param_count = sqlite3_bind_parameter_count(handle); - for (int i = 1; i <= param_count; ++i) { - const char* name = sqlite3_bind_parameter_name(handle, i); - if (name != NULL) bind_map->Add(isolate, name + 1, i); - } - has_bind_map = true; - return bind_map; - } - - // Whenever this is used, db->RemoveStatement must be invoked beforehand. - void CloseHandles() { - if (alive) { - alive = false; - sqlite3_finalize(handle); - } - } - - ~Statement() { - if (alive) db->RemoveStatement(this); - CloseHandles(); - delete extras; - } - -private: - - // A class for holding values that are less often used. - class Extras { friend class Statement; - explicit Extras(sqlite3_uint64 id) : bind_map(0), id(id) {} - BindMap bind_map; - const sqlite3_uint64 id; - }; - - explicit Statement( - Database* db, - sqlite3_stmt* handle, - sqlite3_uint64 id, - bool returns_data - ) : - node::ObjectWrap(), - db(db), - handle(handle), - extras(new Extras(id)), - alive(true), - locked(false), - bound(false), - has_bind_map(false), - safe_ints(db->GetState()->safe_ints), - mode(Data::FLAT), - returns_data(returns_data) { - assert(db != NULL); - assert(handle != NULL); - assert(db->GetState()->open); - assert(!db->GetState()->busy); - db->AddStatement(this); - } - - NODE_METHOD(JS_new) { - UseAddon; - if (!addon->privileged_info) { - return ThrowTypeError("Statements can only be constructed by the db.prepare() method"); - } - assert(info.IsConstructCall()); - Database* db = Unwrap(addon->privileged_info->This()); - REQUIRE_DATABASE_OPEN(db->GetState()); - REQUIRE_DATABASE_NOT_BUSY(db->GetState()); - - v8::Local source = (*addon->privileged_info)[0].As(); - v8::Local database = (*addon->privileged_info)[1].As(); - bool pragmaMode = (*addon->privileged_info)[2].As()->Value(); - int flags = SQLITE_PREPARE_PERSISTENT; - - if (pragmaMode) { - REQUIRE_DATABASE_NO_ITERATORS_UNLESS_UNSAFE(db->GetState()); - flags = 0; - } - - UseIsolate; - v8::String::Utf8Value utf8(isolate, source); - sqlite3_stmt* handle; - const char* tail; - - if (sqlite3_prepare_v3(db->GetHandle(), *utf8, utf8.length() + 1, flags, &handle, &tail) != SQLITE_OK) { - return db->ThrowDatabaseError(); - } - if (handle == NULL) { - return ThrowRangeError("The supplied SQL string contains no statements"); - } - // https://github.com/WiseLibs/better-sqlite3/issues/975#issuecomment-1520934678 - for (char c; (c = *tail); ) { - if (IS_SKIPPED(c)) { - ++tail; - continue; - } - if (c == '/' && tail[1] == '*') { - tail += 2; - for (char c; (c = *tail); ++tail) { - if (c == '*' && tail[1] == '/') { - tail += 2; - break; - } - } - } else if (c == '-' && tail[1] == '-') { - tail += 2; - for (char c; (c = *tail); ++tail) { - if (c == '\n') { - ++tail; - break; - } - } - } else { - sqlite3_finalize(handle); - return ThrowRangeError("The supplied SQL string contains more than one statement"); - } - } - - UseContext; - bool returns_data = sqlite3_column_count(handle) >= 1 || pragmaMode; - Statement* stmt = new Statement(db, handle, addon->NextId(), returns_data); - stmt->Wrap(info.This()); - SetFrozen(isolate, ctx, info.This(), addon->cs.reader, v8::Boolean::New(isolate, returns_data)); - SetFrozen(isolate, ctx, info.This(), addon->cs.readonly, v8::Boolean::New(isolate, sqlite3_stmt_readonly(handle) != 0)); - SetFrozen(isolate, ctx, info.This(), addon->cs.source, source); - SetFrozen(isolate, ctx, info.This(), addon->cs.database, database); - - info.GetReturnValue().Set(info.This()); - } - - NODE_METHOD(JS_run) { - STATEMENT_START(ALLOW_ANY_STATEMENT, DOES_MUTATE); - sqlite3* db_handle = db->GetHandle(); - int total_changes_before = sqlite3_total_changes(db_handle); - - sqlite3_step(handle); - if (sqlite3_reset(handle) == SQLITE_OK) { - int changes = sqlite3_total_changes(db_handle) == total_changes_before ? 0 : sqlite3_changes(db_handle); - sqlite3_int64 id = sqlite3_last_insert_rowid(db_handle); - Addon* addon = db->GetAddon(); - UseContext; - v8::Local result = v8::Object::New(isolate); - result->Set(ctx, addon->cs.changes.Get(isolate), v8::Int32::New(isolate, changes)).FromJust(); - result->Set(ctx, addon->cs.lastInsertRowid.Get(isolate), - stmt->safe_ints - ? v8::BigInt::New(isolate, id).As() - : v8::Number::New(isolate, (double)id).As() - ).FromJust(); - STATEMENT_RETURN(result); - } - STATEMENT_THROW(); - } - - NODE_METHOD(JS_get) { - STATEMENT_START(REQUIRE_STATEMENT_RETURNS_DATA, DOES_NOT_MUTATE); - int status = sqlite3_step(handle); - if (status == SQLITE_ROW) { - v8::Local result = Data::GetRowJS(isolate, OnlyContext, handle, stmt->safe_ints, stmt->mode); - sqlite3_reset(handle); - STATEMENT_RETURN(result); - } else if (status == SQLITE_DONE) { - sqlite3_reset(handle); - STATEMENT_RETURN(v8::Undefined(isolate)); - } - sqlite3_reset(handle); - STATEMENT_THROW(); - } - - NODE_METHOD(JS_all) { - STATEMENT_START(REQUIRE_STATEMENT_RETURNS_DATA, DOES_NOT_MUTATE); - UseContext; - v8::Local result = v8::Array::New(isolate, 0); - uint32_t row_count = 0; - const bool safe_ints = stmt->safe_ints; - const char mode = stmt->mode; - bool js_error = false; - - while (sqlite3_step(handle) == SQLITE_ROW) { - if (row_count == 0xffffffff) { ThrowRangeError("Array overflow (too many rows returned)"); js_error = true; break; } - result->Set(ctx, row_count++, Data::GetRowJS(isolate, ctx, handle, safe_ints, mode)).FromJust(); - } - - if (sqlite3_reset(handle) == SQLITE_OK && !js_error) { - STATEMENT_RETURN(result); - } - if (js_error) db->GetState()->was_js_error = true; - STATEMENT_THROW(); - } - - NODE_METHOD(JS_iterate) { - UseAddon; - UseIsolate; - v8::Local c = addon->StatementIterator.Get(isolate); - addon->privileged_info = &info; - v8::MaybeLocal maybeIterator = c->NewInstance(OnlyContext, 0, NULL); - addon->privileged_info = NULL; - if (!maybeIterator.IsEmpty()) info.GetReturnValue().Set(maybeIterator.ToLocalChecked()); - } - - NODE_METHOD(JS_bind) { - Statement* stmt = Unwrap(info.This()); - if (stmt->bound) return ThrowTypeError("The bind() method can only be invoked once per statement object"); - REQUIRE_DATABASE_OPEN(stmt->db->GetState()); - REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); - REQUIRE_STATEMENT_NOT_LOCKED(stmt); - STATEMENT_BIND(stmt->handle); - stmt->bound = true; - info.GetReturnValue().Set(info.This()); - } - - NODE_METHOD(JS_pluck) { - Statement* stmt = Unwrap(info.This()); - if (!stmt->returns_data) return ThrowTypeError("The pluck() method is only for statements that return data"); - REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); - REQUIRE_STATEMENT_NOT_LOCKED(stmt); - bool use = true; - if (info.Length() != 0) { REQUIRE_ARGUMENT_BOOLEAN(first, use); } - stmt->mode = use ? Data::PLUCK : stmt->mode == Data::PLUCK ? Data::FLAT : stmt->mode; - info.GetReturnValue().Set(info.This()); - } - - NODE_METHOD(JS_expand) { - Statement* stmt = Unwrap(info.This()); - if (!stmt->returns_data) return ThrowTypeError("The expand() method is only for statements that return data"); - REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); - REQUIRE_STATEMENT_NOT_LOCKED(stmt); - bool use = true; - if (info.Length() != 0) { REQUIRE_ARGUMENT_BOOLEAN(first, use); } - stmt->mode = use ? Data::EXPAND : stmt->mode == Data::EXPAND ? Data::FLAT : stmt->mode; - info.GetReturnValue().Set(info.This()); - } - - NODE_METHOD(JS_raw) { - Statement* stmt = Unwrap(info.This()); - if (!stmt->returns_data) return ThrowTypeError("The raw() method is only for statements that return data"); - REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); - REQUIRE_STATEMENT_NOT_LOCKED(stmt); - bool use = true; - if (info.Length() != 0) { REQUIRE_ARGUMENT_BOOLEAN(first, use); } - stmt->mode = use ? Data::RAW : stmt->mode == Data::RAW ? Data::FLAT : stmt->mode; - info.GetReturnValue().Set(info.This()); - } - - NODE_METHOD(JS_safeIntegers) { - Statement* stmt = Unwrap(info.This()); - REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); - REQUIRE_STATEMENT_NOT_LOCKED(stmt); - if (info.Length() == 0) stmt->safe_ints = true; - else { REQUIRE_ARGUMENT_BOOLEAN(first, stmt->safe_ints); } - info.GetReturnValue().Set(info.This()); - } - - NODE_METHOD(JS_columns) { - Statement* stmt = Unwrap(info.This()); - if (!stmt->returns_data) return ThrowTypeError("The columns() method is only for statements that return data"); - REQUIRE_DATABASE_OPEN(stmt->db->GetState()); - REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); - Addon* addon = stmt->db->GetAddon(); - UseIsolate; - UseContext; - - int column_count = sqlite3_column_count(stmt->handle); - v8::Local columns = v8::Array::New(isolate); - - v8::Local name = addon->cs.name.Get(isolate); - v8::Local columnName = addon->cs.column.Get(isolate); - v8::Local tableName = addon->cs.table.Get(isolate); - v8::Local databaseName = addon->cs.database.Get(isolate); - v8::Local typeName = addon->cs.type.Get(isolate); - - for (int i = 0; i < column_count; ++i) { - v8::Local column = v8::Object::New(isolate); - - column->Set(ctx, name, - InternalizedFromUtf8OrNull(isolate, sqlite3_column_name(stmt->handle, i), -1) - ).FromJust(); - column->Set(ctx, columnName, - InternalizedFromUtf8OrNull(isolate, sqlite3_column_origin_name(stmt->handle, i), -1) - ).FromJust(); - column->Set(ctx, tableName, - InternalizedFromUtf8OrNull(isolate, sqlite3_column_table_name(stmt->handle, i), -1) - ).FromJust(); - column->Set(ctx, databaseName, - InternalizedFromUtf8OrNull(isolate, sqlite3_column_database_name(stmt->handle, i), -1) - ).FromJust(); - column->Set(ctx, typeName, - InternalizedFromUtf8OrNull(isolate, sqlite3_column_decltype(stmt->handle, i), -1) - ).FromJust(); - - columns->Set(ctx, i, column).FromJust(); - } - - info.GetReturnValue().Set(columns); - } - - NODE_GETTER(JS_busy) { - Statement* stmt = Unwrap(info.This()); - info.GetReturnValue().Set(stmt->alive && stmt->locked); - } - - Database* const db; - sqlite3_stmt* const handle; - Extras* const extras; - bool alive; - bool locked; - bool bound; - bool has_bind_map; - bool safe_ints; - char mode; - const bool returns_data; -}; diff --git a/src/util/bind-map.cpp b/src/util/bind-map.cpp new file mode 100644 index 000000000..331c09761 --- /dev/null +++ b/src/util/bind-map.cpp @@ -0,0 +1,35 @@ +#include "bind-map.hpp" +#include "../better_sqlite3_impl.hpp" + +BindMap::Pair::Pair(v8::Isolate *isolate, char const *name, int index) + : name(isolate, InternalizedFromUtf8(isolate, name, -1)), index(index) {} +BindMap::Pair::Pair(v8::Isolate *isolate, Pair *pair) + : name(isolate, pair->name), index(pair->index) {} +BindMap::BindMap(char _) { + assert(_ == 0); + pairs = NULL; + capacity = 0; + length = 0; +} +BindMap::~BindMap() { + while (length) + pairs[--length].~Pair(); + FREE_ARRAY(pairs); +} +void BindMap::Add(v8::Isolate *isolate, char const *name, int index) { + assert(name != NULL); + if (capacity == length) + Grow(isolate); + new (pairs + length++) Pair(isolate, name, index); +} +void BindMap::Grow(v8::Isolate *isolate) { + assert(capacity == length); + capacity = capacity * 2 + 2; + Pair *new_pairs = ALLOC_ARRAY(capacity); + for (int i = 0; i < length; ++i) { + new (new_pairs + i) Pair(isolate, pairs + i); + pairs[i].~Pair(); + } + FREE_ARRAY(pairs); + pairs = new_pairs; +} diff --git a/src/util/bind-map.hpp b/src/util/bind-map.hpp new file mode 100644 index 000000000..d25f7b8b4 --- /dev/null +++ b/src/util/bind-map.hpp @@ -0,0 +1,40 @@ +#ifndef BETTER_SQLITE3_UTIL_BIND_MAP_HPP +#define BETTER_SQLITE3_UTIL_BIND_MAP_HPP + +#include "../better_sqlite3_deps.hpp" + +class BindMap { +public: + class Pair { + friend class BindMap; + + public: + int GetIndex(); + v8::Local GetName(v8::Isolate *isolate); + + private: + explicit Pair(v8::Isolate *isolate, char const *name, int index); + explicit Pair(v8::Isolate *isolate, Pair *pair); + v8::Global const name; + int const index; + }; + explicit BindMap(char _); + ~BindMap(); + Pair *GetPairs(); + int GetSize(); + void Add(v8::Isolate *isolate, char const *name, int index); + +private: + void Grow(v8::Isolate *isolate); + Pair *pairs; + int capacity; + int length; +}; +LZZ_INLINE int BindMap::Pair::GetIndex() { return index; } +LZZ_INLINE v8::Local BindMap::Pair::GetName(v8::Isolate *isolate) { + return name.Get(isolate); +} +LZZ_INLINE BindMap::Pair *BindMap::GetPairs() { return pairs; } +LZZ_INLINE int BindMap::GetSize() { return length; } + +#endif // BETTER_SQLITE3_UTIL_BIND_MAP_HPP diff --git a/src/util/bind-map.lzz b/src/util/bind-map.lzz deleted file mode 100644 index 766d0f942..000000000 --- a/src/util/bind-map.lzz +++ /dev/null @@ -1,73 +0,0 @@ -class BindMap { -public: - - // This nested class represents a single mapping between a parameter name - // and its associated parameter index in a prepared statement. - class Pair { friend class BindMap; - public: - - inline int GetIndex() { - return index; - } - - inline v8::Local GetName(v8::Isolate* isolate) { - return name.Get(isolate); - } - - private: - - explicit Pair(v8::Isolate* isolate, const char* name, int index) - : name(isolate, InternalizedFromUtf8(isolate, name, -1)), index(index) {} - - explicit Pair(v8::Isolate* isolate, Pair* pair) - : name(isolate, pair->name), index(pair->index) {} - - const v8::Global name; - const int index; - }; - - explicit BindMap(char _) { - assert(_ == 0); - pairs = NULL; - capacity = 0; - length = 0; - } - - ~BindMap() { - while (length) pairs[--length].~Pair(); - FREE_ARRAY(pairs); - } - - inline Pair* GetPairs() { - return pairs; - } - - inline int GetSize() { - return length; - } - - // Adds a pair to the bind map, expanding the capacity if necessary. - void Add(v8::Isolate* isolate, const char* name, int index) { - assert(name != NULL); - if (capacity == length) Grow(isolate); - new (pairs + length++) Pair(isolate, name, index); - } - -private: - - void Grow(v8::Isolate* isolate) { - assert(capacity == length); - capacity = (capacity << 1) | 2; - Pair* new_pairs = ALLOC_ARRAY(capacity); - for (int i = 0; i < length; ++i) { - new (new_pairs + i) Pair(isolate, pairs + i); - pairs[i].~Pair(); - } - FREE_ARRAY(pairs); - pairs = new_pairs; - } - - Pair* pairs; - int capacity; - int length; -}; diff --git a/src/util/binder.cpp b/src/util/binder.cpp new file mode 100644 index 000000000..d3f1ab3c3 --- /dev/null +++ b/src/util/binder.cpp @@ -0,0 +1,183 @@ +#include "binder.hpp" +#include "../better_sqlite3_impl.hpp" + +bool IsPlainObject(v8::Isolate *isolate, v8::Local obj) { + v8::Local proto = obj->GetPrototype(); + +#if defined NODE_MODULE_VERSION && NODE_MODULE_VERSION < 93 + v8::Local ctx = obj->CreationContext(); +#else + v8::Local ctx = obj->GetCreationContext().ToLocalChecked(); +#endif + + ctx->Enter(); + v8::Local baseProto = v8::Object::New(isolate)->GetPrototype(); + ctx->Exit(); + return proto->StrictEquals(baseProto) || + proto->StrictEquals(v8::Null(isolate)); +} +Binder::Binder(sqlite3_stmt *_handle) { + handle = _handle; + param_count = sqlite3_bind_parameter_count(_handle); + anon_index = 0; + success = true; +} +bool Binder::Bind(v8::FunctionCallbackInfo const &info, int argc, + Statement *stmt) { + assert(anon_index == 0); + Result result = BindArgs(info, argc, stmt); + if (success && result.count != param_count) { + if (result.count < param_count) { + if (!result.bound_object && + stmt->GetBindMap(info.GetIsolate())->GetSize()) { + Fail(ThrowTypeError, "Missing named parameters"); + } else { + Fail(ThrowRangeError, "Too few parameter values were provided"); + } + } else { + Fail(ThrowRangeError, "Too many parameter values were provided"); + } + } + return success; +} +void Binder::Fail(void (*Throw)(char const *), char const *message) { + assert(success == true); + assert((Throw == NULL) == (message == NULL)); + assert(Throw == ThrowError || Throw == ThrowTypeError || + Throw == ThrowRangeError || Throw == NULL); + if (Throw) + Throw(message); + success = false; +} +int Binder::NextAnonIndex() { + while (sqlite3_bind_parameter_name(handle, ++anon_index) != NULL) { + } + return anon_index; +} +void Binder::BindValue(v8::Isolate *isolate, v8::Local value, + int index) { + int status = Data::BindValueFromJS(isolate, handle, index, value); + if (status != SQLITE_OK) { + switch (status) { + case -1: + return Fail( + ThrowTypeError, + "SQLite3 can only bind numbers, strings, bigints, buffers, and null"); + case SQLITE_TOOBIG: + return Fail(ThrowRangeError, + "The bound string, buffer, or bigint is too big"); + case SQLITE_RANGE: + return Fail(ThrowRangeError, "Too many parameter values were provided"); + case SQLITE_NOMEM: + return Fail(ThrowError, "Out of memory"); + default: + return Fail( + ThrowError, + "An unexpected error occured while trying to bind parameters"); + } + assert(false); + } +} +int Binder::BindArray(v8::Isolate *isolate, v8::Local arr) { + v8::Local ctx = isolate->GetCurrentContext(); + uint32_t length = arr->Length(); + if (length > INT_MAX) { + Fail(ThrowRangeError, "Too many parameter values were provided"); + return 0; + } + int len = static_cast(length); + for (int i = 0; i < len; ++i) { + v8::MaybeLocal maybeValue = arr->Get(ctx, i); + if (maybeValue.IsEmpty()) { + Fail(NULL, NULL); + return i; + } + BindValue(isolate, maybeValue.ToLocalChecked(), NextAnonIndex()); + if (!success) { + return i; + } + } + return len; +} +int Binder::BindObject(v8::Isolate *isolate, v8::Local obj, + Statement *stmt) { + v8::Local ctx = isolate->GetCurrentContext(); + BindMap *bind_map = stmt->GetBindMap(isolate); + BindMap::Pair *pairs = bind_map->GetPairs(); + int len = bind_map->GetSize(); + + for (int i = 0; i < len; ++i) { + v8::Local key = pairs[i].GetName(isolate); + + v8::Maybe has_property = obj->HasOwnProperty(ctx, key); + if (has_property.IsNothing()) { + Fail(NULL, NULL); + return i; + } + if (!has_property.FromJust()) { + v8::String::Utf8Value param_name(isolate, key); + Fail(ThrowRangeError, + (std::string("Missing named parameter \"") + *param_name + "\"") + .c_str()); + return i; + } + + v8::MaybeLocal maybeValue = obj->Get(ctx, key); + if (maybeValue.IsEmpty()) { + Fail(NULL, NULL); + return i; + } + + BindValue(isolate, maybeValue.ToLocalChecked(), pairs[i].GetIndex()); + if (!success) { + return i; + } + } + + return len; +} +Binder::Result Binder::BindArgs(v8::FunctionCallbackInfo const &info, + int argc, Statement *stmt) { + v8::Isolate *isolate = info.GetIsolate(); + int count = 0; + bool bound_object = false; + + for (int i = 0; i < argc; ++i) { + v8::Local arg = info[i]; + + if (arg->IsArray()) { + count += BindArray(isolate, arg.As()); + if (!success) + break; + continue; + } + + if (arg->IsObject() && !node::Buffer::HasInstance(arg)) { + v8::Local obj = arg.As(); + if (IsPlainObject(isolate, obj)) { + if (bound_object) { + Fail(ThrowTypeError, + "You cannot specify named parameters in two different objects"); + break; + } + bound_object = true; + + count += BindObject(isolate, obj, stmt); + if (!success) + break; + continue; + } else if (stmt->GetBindMap(isolate)->GetSize()) { + Fail(ThrowTypeError, + "Named parameters can only be passed within plain objects"); + break; + } + } + + BindValue(isolate, arg, NextAnonIndex()); + if (!success) + break; + count += 1; + } + + return {count, bound_object}; +} diff --git a/src/util/binder.hpp b/src/util/binder.hpp new file mode 100644 index 000000000..8cc57eab6 --- /dev/null +++ b/src/util/binder.hpp @@ -0,0 +1,35 @@ +#ifndef BETTER_SQLITE3_UTIL_BINDER_HPP +#define BETTER_SQLITE3_UTIL_BINDER_HPP + +#include "../better_sqlite3_deps.hpp" + +// Forward declarations +class Statement; + +bool IsPlainObject(v8::Isolate *isolate, v8::Local obj); +class Binder { +public: + explicit Binder(sqlite3_stmt *_handle); + bool Bind(v8::FunctionCallbackInfo const &info, int argc, + Statement *stmt); + +private: + struct Result { + int count; + bool bound_object; + }; + void Fail(void (*Throw)(char const *), char const *message); + int NextAnonIndex(); + void BindValue(v8::Isolate *isolate, v8::Local value, int index); + int BindArray(v8::Isolate *isolate, v8::Local arr); + int BindObject(v8::Isolate *isolate, v8::Local obj, + Statement *stmt); + Result BindArgs(v8::FunctionCallbackInfo const &info, int argc, + Statement *stmt); + sqlite3_stmt *handle; + int param_count; + int anon_index; + bool success; +}; + +#endif // BETTER_SQLITE3_UTIL_BINDER_HPP diff --git a/src/util/binder.lzz b/src/util/binder.lzz deleted file mode 100644 index 9ccb5e332..000000000 --- a/src/util/binder.lzz +++ /dev/null @@ -1,204 +0,0 @@ -class Binder { -public: - - explicit Binder(sqlite3_stmt* _handle) { - handle = _handle; - param_count = sqlite3_bind_parameter_count(_handle); - anon_index = 0; - success = true; - } - - bool Bind(NODE_ARGUMENTS info, int argc, Statement* stmt) { - assert(anon_index == 0); - Result result = BindArgs(info, argc, stmt); - if (success && result.count != param_count) { - if (result.count < param_count) { - if (!result.bound_object && stmt->GetBindMap(OnlyIsolate)->GetSize()) { - Fail(ThrowTypeError, "Missing named parameters"); - } else { - Fail(ThrowRangeError, "Too few parameter values were provided"); - } - } else { - Fail(ThrowRangeError, "Too many parameter values were provided"); - } - } - return success; - } - -private: - - struct Result { - int count; - bool bound_object; - }; - -#hdr - static bool IsPlainObject(v8::Isolate* isolate, v8::Local obj); -#end -#src - static bool IsPlainObject(v8::Isolate* isolate, v8::Local obj) { - v8::Local proto = obj->GetPrototype(); - - #if defined NODE_MODULE_VERSION && NODE_MODULE_VERSION < 93 - v8::Local ctx = obj->CreationContext(); - #else - v8::Local ctx = obj->GetCreationContext().ToLocalChecked(); - #endif - - ctx->Enter(); - v8::Local baseProto = v8::Object::New(isolate)->GetPrototype(); - ctx->Exit(); - return proto->StrictEquals(baseProto) || proto->StrictEquals(v8::Null(isolate)); - } -#end - - void Fail(void (*Throw)(const char* _), const char* message) { - assert(success == true); - assert((Throw == NULL) == (message == NULL)); - assert(Throw == ThrowError || Throw == ThrowTypeError || Throw == ThrowRangeError || Throw == NULL); - if (Throw) Throw(message); - success = false; - } - - int NextAnonIndex() { - while (sqlite3_bind_parameter_name(handle, ++anon_index) != NULL) {} - return anon_index; - } - - // Binds the value at the given index or throws an appropriate error. - void BindValue(v8::Isolate* isolate, v8::Local value, int index) { - int status = Data::BindValueFromJS(isolate, handle, index, value); - if (status != SQLITE_OK) { - switch (status) { - case -1: - return Fail(ThrowTypeError, "SQLite3 can only bind numbers, strings, bigints, buffers, and null"); - case SQLITE_TOOBIG: - return Fail(ThrowRangeError, "The bound string, buffer, or bigint is too big"); - case SQLITE_RANGE: - return Fail(ThrowRangeError, "Too many parameter values were provided"); - case SQLITE_NOMEM: - return Fail(ThrowError, "Out of memory"); - default: - return Fail(ThrowError, "An unexpected error occured while trying to bind parameters"); - } - assert(false); - } - } - - // Binds each value in the array or throws an appropriate error. - // The number of successfully bound parameters is returned. - int BindArray(v8::Isolate* isolate, v8::Local arr) { - UseContext; - uint32_t length = arr->Length(); - if (length > INT_MAX) { - Fail(ThrowRangeError, "Too many parameter values were provided"); - return 0; - } - int len = static_cast(length); - for (int i = 0; i < len; ++i) { - v8::MaybeLocal maybeValue = arr->Get(ctx, i); - if (maybeValue.IsEmpty()) { - Fail(NULL, NULL); - return i; - } - BindValue(isolate, maybeValue.ToLocalChecked(), NextAnonIndex()); - if (!success) { - return i; - } - } - return len; - } - - // Binds all named parameters using the values found in the given object. - // The number of successfully bound parameters is returned. - // If a named parameter is missing from the object, an error is thrown. - // This should only be invoked once per instance. - int BindObject(v8::Isolate* isolate, v8::Local obj, Statement* stmt) { - UseContext; - BindMap* bind_map = stmt->GetBindMap(isolate); - BindMap::Pair* pairs = bind_map->GetPairs(); - int len = bind_map->GetSize(); - - for (int i = 0; i < len; ++i) { - v8::Local key = pairs[i].GetName(isolate); - - // Check if the named parameter was provided. - v8::Maybe has_property = obj->HasOwnProperty(ctx, key); - if (has_property.IsNothing()) { - Fail(NULL, NULL); - return i; - } - if (!has_property.FromJust()) { - v8::String::Utf8Value param_name(isolate, key); - Fail(ThrowRangeError, (std::string("Missing named parameter \"") + *param_name + "\"").c_str()); - return i; - } - - // Get the current property value. - v8::MaybeLocal maybeValue = obj->Get(ctx, key); - if (maybeValue.IsEmpty()) { - Fail(NULL, NULL); - return i; - } - - BindValue(isolate, maybeValue.ToLocalChecked(), pairs[i].GetIndex()); - if (!success) { - return i; - } - } - - return len; - } - - // Binds all parameters using the values found in the arguments object. - // Anonymous parameter values can be directly in the arguments object or in an Array. - // Named parameter values can be provided in a plain Object argument. - // Only one plain Object argument may be provided. - // If an error occurs, an appropriate error is thrown. - // The return value is a struct indicating how many parameters were successfully bound - // and whether or not it tried to bind an object. - Result BindArgs(NODE_ARGUMENTS info, int argc, Statement* stmt) { - UseIsolate; - int count = 0; - bool bound_object = false; - - for (int i = 0; i < argc; ++i) { - v8::Local arg = info[i]; - - if (arg->IsArray()) { - count += BindArray(isolate, arg.As()); - if (!success) break; - continue; - } - - if (arg->IsObject() && !node::Buffer::HasInstance(arg)) { - v8::Local obj = arg.As(); - if (IsPlainObject(isolate, obj)) { - if (bound_object) { - Fail(ThrowTypeError, "You cannot specify named parameters in two different objects"); - break; - } - bound_object = true; - - count += BindObject(isolate, obj, stmt); - if (!success) break; - continue; - } else if (stmt->GetBindMap(isolate)->GetSize()) { - Fail(ThrowTypeError, "Named parameters can only be passed within plain objects"); - break; - } - } - - BindValue(isolate, arg, NextAnonIndex()); - if (!success) break; - count += 1; - } - - return { count, bound_object }; - } - - sqlite3_stmt* handle; - int param_count; - int anon_index; // This value should only be used by NextAnonIndex() - bool success; // This value should only be set by Fail() -}; diff --git a/src/util/constants.cpp b/src/util/constants.cpp new file mode 100644 index 000000000..64a9f617b --- /dev/null +++ b/src/util/constants.cpp @@ -0,0 +1,157 @@ +#include "constants.hpp" +#include "../better_sqlite3_impl.hpp" + +v8::Local CS::Code(v8::Isolate *isolate, int code) { + auto element = codes.find(code); + if (element != codes.end()) + return element->second.Get(isolate); + return StringFromUtf8( + isolate, + (std::string("UNKNOWN_SQLITE_ERROR_") + std::to_string(code)).c_str(), + -1); +} +CS::CS(v8::Isolate *isolate) { + SetString(isolate, database, "database"); + SetString(isolate, reader, "reader"); + SetString(isolate, source, "source"); + SetString(isolate, memory, "memory"); + SetString(isolate, readonly, "readonly"); + SetString(isolate, name, "name"); + SetString(isolate, next, "next"); + SetString(isolate, length, "length"); + SetString(isolate, done, "done"); + SetString(isolate, value, "value"); + SetString(isolate, changes, "changes"); + SetString(isolate, lastInsertRowid, "lastInsertRowid"); + SetString(isolate, statement, "statement"); + SetString(isolate, column, "column"); + SetString(isolate, table, "table"); + SetString(isolate, type, "type"); + SetString(isolate, totalPages, "totalPages"); + SetString(isolate, remainingPages, "remainingPages"); + + SetCode(isolate, SQLITE_OK, "SQLITE_OK"); + SetCode(isolate, SQLITE_ERROR, "SQLITE_ERROR"); + SetCode(isolate, SQLITE_INTERNAL, "SQLITE_INTERNAL"); + SetCode(isolate, SQLITE_PERM, "SQLITE_PERM"); + SetCode(isolate, SQLITE_ABORT, "SQLITE_ABORT"); + SetCode(isolate, SQLITE_BUSY, "SQLITE_BUSY"); + SetCode(isolate, SQLITE_LOCKED, "SQLITE_LOCKED"); + SetCode(isolate, SQLITE_NOMEM, "SQLITE_NOMEM"); + SetCode(isolate, SQLITE_READONLY, "SQLITE_READONLY"); + SetCode(isolate, SQLITE_INTERRUPT, "SQLITE_INTERRUPT"); + SetCode(isolate, SQLITE_IOERR, "SQLITE_IOERR"); + SetCode(isolate, SQLITE_CORRUPT, "SQLITE_CORRUPT"); + SetCode(isolate, SQLITE_NOTFOUND, "SQLITE_NOTFOUND"); + SetCode(isolate, SQLITE_FULL, "SQLITE_FULL"); + SetCode(isolate, SQLITE_CANTOPEN, "SQLITE_CANTOPEN"); + SetCode(isolate, SQLITE_PROTOCOL, "SQLITE_PROTOCOL"); + SetCode(isolate, SQLITE_EMPTY, "SQLITE_EMPTY"); + SetCode(isolate, SQLITE_SCHEMA, "SQLITE_SCHEMA"); + SetCode(isolate, SQLITE_TOOBIG, "SQLITE_TOOBIG"); + SetCode(isolate, SQLITE_CONSTRAINT, "SQLITE_CONSTRAINT"); + SetCode(isolate, SQLITE_MISMATCH, "SQLITE_MISMATCH"); + SetCode(isolate, SQLITE_MISUSE, "SQLITE_MISUSE"); + SetCode(isolate, SQLITE_NOLFS, "SQLITE_NOLFS"); + SetCode(isolate, SQLITE_AUTH, "SQLITE_AUTH"); + SetCode(isolate, SQLITE_FORMAT, "SQLITE_FORMAT"); + SetCode(isolate, SQLITE_RANGE, "SQLITE_RANGE"); + SetCode(isolate, SQLITE_NOTADB, "SQLITE_NOTADB"); + SetCode(isolate, SQLITE_NOTICE, "SQLITE_NOTICE"); + SetCode(isolate, SQLITE_WARNING, "SQLITE_WARNING"); + SetCode(isolate, SQLITE_ROW, "SQLITE_ROW"); + SetCode(isolate, SQLITE_DONE, "SQLITE_DONE"); + + SetCode(isolate, SQLITE_ERROR_MISSING_COLLSEQ, + "SQLITE_ERROR_MISSING_COLLSEQ"); + SetCode(isolate, SQLITE_ERROR_RETRY, "SQLITE_ERROR_RETRY"); + SetCode(isolate, SQLITE_ERROR_SNAPSHOT, "SQLITE_ERROR_SNAPSHOT"); + SetCode(isolate, SQLITE_IOERR_READ, "SQLITE_IOERR_READ"); + SetCode(isolate, SQLITE_IOERR_SHORT_READ, "SQLITE_IOERR_SHORT_READ"); + SetCode(isolate, SQLITE_IOERR_WRITE, "SQLITE_IOERR_WRITE"); + SetCode(isolate, SQLITE_IOERR_FSYNC, "SQLITE_IOERR_FSYNC"); + SetCode(isolate, SQLITE_IOERR_DIR_FSYNC, "SQLITE_IOERR_DIR_FSYNC"); + SetCode(isolate, SQLITE_IOERR_TRUNCATE, "SQLITE_IOERR_TRUNCATE"); + SetCode(isolate, SQLITE_IOERR_FSTAT, "SQLITE_IOERR_FSTAT"); + SetCode(isolate, SQLITE_IOERR_UNLOCK, "SQLITE_IOERR_UNLOCK"); + SetCode(isolate, SQLITE_IOERR_RDLOCK, "SQLITE_IOERR_RDLOCK"); + SetCode(isolate, SQLITE_IOERR_DELETE, "SQLITE_IOERR_DELETE"); + SetCode(isolate, SQLITE_IOERR_BLOCKED, "SQLITE_IOERR_BLOCKED"); + SetCode(isolate, SQLITE_IOERR_NOMEM, "SQLITE_IOERR_NOMEM"); + SetCode(isolate, SQLITE_IOERR_ACCESS, "SQLITE_IOERR_ACCESS"); + SetCode(isolate, SQLITE_IOERR_CHECKRESERVEDLOCK, + "SQLITE_IOERR_CHECKRESERVEDLOCK"); + SetCode(isolate, SQLITE_IOERR_LOCK, "SQLITE_IOERR_LOCK"); + SetCode(isolate, SQLITE_IOERR_CLOSE, "SQLITE_IOERR_CLOSE"); + SetCode(isolate, SQLITE_IOERR_DIR_CLOSE, "SQLITE_IOERR_DIR_CLOSE"); + SetCode(isolate, SQLITE_IOERR_SHMOPEN, "SQLITE_IOERR_SHMOPEN"); + SetCode(isolate, SQLITE_IOERR_SHMSIZE, "SQLITE_IOERR_SHMSIZE"); + SetCode(isolate, SQLITE_IOERR_SHMLOCK, "SQLITE_IOERR_SHMLOCK"); + SetCode(isolate, SQLITE_IOERR_SHMMAP, "SQLITE_IOERR_SHMMAP"); + SetCode(isolate, SQLITE_IOERR_SEEK, "SQLITE_IOERR_SEEK"); + SetCode(isolate, SQLITE_IOERR_DELETE_NOENT, "SQLITE_IOERR_DELETE_NOENT"); + SetCode(isolate, SQLITE_IOERR_MMAP, "SQLITE_IOERR_MMAP"); + SetCode(isolate, SQLITE_IOERR_GETTEMPPATH, "SQLITE_IOERR_GETTEMPPATH"); + SetCode(isolate, SQLITE_IOERR_CONVPATH, "SQLITE_IOERR_CONVPATH"); + SetCode(isolate, SQLITE_IOERR_VNODE, "SQLITE_IOERR_VNODE"); + SetCode(isolate, SQLITE_IOERR_AUTH, "SQLITE_IOERR_AUTH"); + SetCode(isolate, SQLITE_IOERR_BEGIN_ATOMIC, "SQLITE_IOERR_BEGIN_ATOMIC"); + SetCode(isolate, SQLITE_IOERR_COMMIT_ATOMIC, "SQLITE_IOERR_COMMIT_ATOMIC"); + SetCode(isolate, SQLITE_IOERR_ROLLBACK_ATOMIC, + "SQLITE_IOERR_ROLLBACK_ATOMIC"); + SetCode(isolate, SQLITE_IOERR_DATA, "SQLITE_IOERR_DATA"); + SetCode(isolate, SQLITE_IOERR_CORRUPTFS, "SQLITE_IOERR_CORRUPTFS"); + SetCode(isolate, SQLITE_IOERR_IN_PAGE, "SQLITE_IOERR_IN_PAGE"); + SetCode(isolate, SQLITE_LOCKED_SHAREDCACHE, "SQLITE_LOCKED_SHAREDCACHE"); + SetCode(isolate, SQLITE_LOCKED_VTAB, "SQLITE_LOCKED_VTAB"); + SetCode(isolate, SQLITE_BUSY_RECOVERY, "SQLITE_BUSY_RECOVERY"); + SetCode(isolate, SQLITE_BUSY_SNAPSHOT, "SQLITE_BUSY_SNAPSHOT"); + SetCode(isolate, SQLITE_CANTOPEN_NOTEMPDIR, "SQLITE_CANTOPEN_NOTEMPDIR"); + SetCode(isolate, SQLITE_CANTOPEN_ISDIR, "SQLITE_CANTOPEN_ISDIR"); + SetCode(isolate, SQLITE_CANTOPEN_FULLPATH, "SQLITE_CANTOPEN_FULLPATH"); + SetCode(isolate, SQLITE_CANTOPEN_CONVPATH, "SQLITE_CANTOPEN_CONVPATH"); + SetCode(isolate, SQLITE_CANTOPEN_DIRTYWAL, "SQLITE_CANTOPEN_DIRTYWAL"); + SetCode(isolate, SQLITE_CANTOPEN_SYMLINK, "SQLITE_CANTOPEN_SYMLINK"); + SetCode(isolate, SQLITE_CORRUPT_VTAB, "SQLITE_CORRUPT_VTAB"); + SetCode(isolate, SQLITE_CORRUPT_SEQUENCE, "SQLITE_CORRUPT_SEQUENCE"); + SetCode(isolate, SQLITE_CORRUPT_INDEX, "SQLITE_CORRUPT_INDEX"); + SetCode(isolate, SQLITE_READONLY_RECOVERY, "SQLITE_READONLY_RECOVERY"); + SetCode(isolate, SQLITE_READONLY_CANTLOCK, "SQLITE_READONLY_CANTLOCK"); + SetCode(isolate, SQLITE_READONLY_ROLLBACK, "SQLITE_READONLY_ROLLBACK"); + SetCode(isolate, SQLITE_READONLY_DBMOVED, "SQLITE_READONLY_DBMOVED"); + SetCode(isolate, SQLITE_READONLY_CANTINIT, "SQLITE_READONLY_CANTINIT"); + SetCode(isolate, SQLITE_READONLY_DIRECTORY, "SQLITE_READONLY_DIRECTORY"); + SetCode(isolate, SQLITE_ABORT_ROLLBACK, "SQLITE_ABORT_ROLLBACK"); + SetCode(isolate, SQLITE_CONSTRAINT_CHECK, "SQLITE_CONSTRAINT_CHECK"); + SetCode(isolate, SQLITE_CONSTRAINT_COMMITHOOK, + "SQLITE_CONSTRAINT_COMMITHOOK"); + SetCode(isolate, SQLITE_CONSTRAINT_FOREIGNKEY, + "SQLITE_CONSTRAINT_FOREIGNKEY"); + SetCode(isolate, SQLITE_CONSTRAINT_FUNCTION, "SQLITE_CONSTRAINT_FUNCTION"); + SetCode(isolate, SQLITE_CONSTRAINT_NOTNULL, "SQLITE_CONSTRAINT_NOTNULL"); + SetCode(isolate, SQLITE_CONSTRAINT_PRIMARYKEY, + "SQLITE_CONSTRAINT_PRIMARYKEY"); + SetCode(isolate, SQLITE_CONSTRAINT_TRIGGER, "SQLITE_CONSTRAINT_TRIGGER"); + SetCode(isolate, SQLITE_CONSTRAINT_UNIQUE, "SQLITE_CONSTRAINT_UNIQUE"); + SetCode(isolate, SQLITE_CONSTRAINT_VTAB, "SQLITE_CONSTRAINT_VTAB"); + SetCode(isolate, SQLITE_CONSTRAINT_ROWID, "SQLITE_CONSTRAINT_ROWID"); + SetCode(isolate, SQLITE_CONSTRAINT_PINNED, "SQLITE_CONSTRAINT_PINNED"); + SetCode(isolate, SQLITE_CONSTRAINT_DATATYPE, "SQLITE_CONSTRAINT_DATATYPE"); + SetCode(isolate, SQLITE_NOTICE_RECOVER_WAL, "SQLITE_NOTICE_RECOVER_WAL"); + SetCode(isolate, SQLITE_NOTICE_RECOVER_ROLLBACK, + "SQLITE_NOTICE_RECOVER_ROLLBACK"); + SetCode(isolate, SQLITE_NOTICE_RBU, "SQLITE_NOTICE_RBU"); + SetCode(isolate, SQLITE_WARNING_AUTOINDEX, "SQLITE_WARNING_AUTOINDEX"); + SetCode(isolate, SQLITE_AUTH_USER, "SQLITE_AUTH_USER"); + SetCode(isolate, SQLITE_OK_LOAD_PERMANENTLY, "SQLITE_OK_LOAD_PERMANENTLY"); + SetCode(isolate, SQLITE_OK_SYMLINK, "SQLITE_OK_SYMLINK"); +} +void CS::SetString(v8::Isolate *isolate, v8::Global &constant, + char const *str) { + constant.Reset(isolate, InternalizedFromLatin1(isolate, str)); +} +void CS::SetCode(v8::Isolate *isolate, int code, char const *str) { + codes.emplace( + std::piecewise_construct, std::forward_as_tuple(code), + std::forward_as_tuple(isolate, InternalizedFromLatin1(isolate, str))); +} diff --git a/src/util/constants.hpp b/src/util/constants.hpp new file mode 100644 index 000000000..67760038e --- /dev/null +++ b/src/util/constants.hpp @@ -0,0 +1,36 @@ +#ifndef BETTER_SQLITE3_UTIL_CONSTANTS_HPP +#define BETTER_SQLITE3_UTIL_CONSTANTS_HPP + +#include "../better_sqlite3_deps.hpp" + +class CS { +public: + v8::Local Code(v8::Isolate *isolate, int code); + explicit CS(v8::Isolate *isolate); + v8::Global database; + v8::Global reader; + v8::Global source; + v8::Global memory; + v8::Global readonly; + v8::Global name; + v8::Global next; + v8::Global length; + v8::Global done; + v8::Global value; + v8::Global changes; + v8::Global lastInsertRowid; + v8::Global statement; + v8::Global column; + v8::Global table; + v8::Global type; + v8::Global totalPages; + v8::Global remainingPages; + +private: + static void SetString(v8::Isolate *isolate, v8::Global &constant, + char const *str); + void SetCode(v8::Isolate *isolate, int code, char const *str); + std::unordered_map> codes; +}; + +#endif // BETTER_SQLITE3_UTIL_CONSTANTS_HPP diff --git a/src/util/constants.lzz b/src/util/constants.lzz deleted file mode 100644 index d1b6e1376..000000000 --- a/src/util/constants.lzz +++ /dev/null @@ -1,172 +0,0 @@ -class CS { -public: - - v8::Local Code(v8::Isolate* isolate, int code) { - auto element = codes.find(code); - if (element != codes.end()) return element->second.Get(isolate); - return StringFromUtf8(isolate, (std::string("UNKNOWN_SQLITE_ERROR_") + std::to_string(code)).c_str(), -1); - } - - explicit CS(v8::Isolate* isolate) { - SetString(isolate, database, "database"); - SetString(isolate, reader, "reader"); - SetString(isolate, source, "source"); - SetString(isolate, memory, "memory"); - SetString(isolate, readonly, "readonly"); - SetString(isolate, name, "name"); - SetString(isolate, next, "next"); - SetString(isolate, length, "length"); - SetString(isolate, done, "done"); - SetString(isolate, value, "value"); - SetString(isolate, changes, "changes"); - SetString(isolate, lastInsertRowid, "lastInsertRowid"); - SetString(isolate, statement, "statement"); - SetString(isolate, column, "column"); - SetString(isolate, table, "table"); - SetString(isolate, type, "type"); - SetString(isolate, totalPages, "totalPages"); - SetString(isolate, remainingPages, "remainingPages"); - - SetCode(isolate, SQLITE_OK, "SQLITE_OK"); - SetCode(isolate, SQLITE_ERROR, "SQLITE_ERROR"); - SetCode(isolate, SQLITE_INTERNAL, "SQLITE_INTERNAL"); - SetCode(isolate, SQLITE_PERM, "SQLITE_PERM"); - SetCode(isolate, SQLITE_ABORT, "SQLITE_ABORT"); - SetCode(isolate, SQLITE_BUSY, "SQLITE_BUSY"); - SetCode(isolate, SQLITE_LOCKED, "SQLITE_LOCKED"); - SetCode(isolate, SQLITE_NOMEM, "SQLITE_NOMEM"); - SetCode(isolate, SQLITE_READONLY, "SQLITE_READONLY"); - SetCode(isolate, SQLITE_INTERRUPT, "SQLITE_INTERRUPT"); - SetCode(isolate, SQLITE_IOERR, "SQLITE_IOERR"); - SetCode(isolate, SQLITE_CORRUPT, "SQLITE_CORRUPT"); - SetCode(isolate, SQLITE_NOTFOUND, "SQLITE_NOTFOUND"); - SetCode(isolate, SQLITE_FULL, "SQLITE_FULL"); - SetCode(isolate, SQLITE_CANTOPEN, "SQLITE_CANTOPEN"); - SetCode(isolate, SQLITE_PROTOCOL, "SQLITE_PROTOCOL"); - SetCode(isolate, SQLITE_EMPTY, "SQLITE_EMPTY"); - SetCode(isolate, SQLITE_SCHEMA, "SQLITE_SCHEMA"); - SetCode(isolate, SQLITE_TOOBIG, "SQLITE_TOOBIG"); - SetCode(isolate, SQLITE_CONSTRAINT, "SQLITE_CONSTRAINT"); - SetCode(isolate, SQLITE_MISMATCH, "SQLITE_MISMATCH"); - SetCode(isolate, SQLITE_MISUSE, "SQLITE_MISUSE"); - SetCode(isolate, SQLITE_NOLFS, "SQLITE_NOLFS"); - SetCode(isolate, SQLITE_AUTH, "SQLITE_AUTH"); - SetCode(isolate, SQLITE_FORMAT, "SQLITE_FORMAT"); - SetCode(isolate, SQLITE_RANGE, "SQLITE_RANGE"); - SetCode(isolate, SQLITE_NOTADB, "SQLITE_NOTADB"); - SetCode(isolate, SQLITE_NOTICE, "SQLITE_NOTICE"); - SetCode(isolate, SQLITE_WARNING, "SQLITE_WARNING"); - SetCode(isolate, SQLITE_ROW, "SQLITE_ROW"); - SetCode(isolate, SQLITE_DONE, "SQLITE_DONE"); - - SetCode(isolate, SQLITE_ERROR_MISSING_COLLSEQ, "SQLITE_ERROR_MISSING_COLLSEQ"); - SetCode(isolate, SQLITE_ERROR_RETRY, "SQLITE_ERROR_RETRY"); - SetCode(isolate, SQLITE_ERROR_SNAPSHOT, "SQLITE_ERROR_SNAPSHOT"); - SetCode(isolate, SQLITE_IOERR_READ, "SQLITE_IOERR_READ"); - SetCode(isolate, SQLITE_IOERR_SHORT_READ, "SQLITE_IOERR_SHORT_READ"); - SetCode(isolate, SQLITE_IOERR_WRITE, "SQLITE_IOERR_WRITE"); - SetCode(isolate, SQLITE_IOERR_FSYNC, "SQLITE_IOERR_FSYNC"); - SetCode(isolate, SQLITE_IOERR_DIR_FSYNC, "SQLITE_IOERR_DIR_FSYNC"); - SetCode(isolate, SQLITE_IOERR_TRUNCATE, "SQLITE_IOERR_TRUNCATE"); - SetCode(isolate, SQLITE_IOERR_FSTAT, "SQLITE_IOERR_FSTAT"); - SetCode(isolate, SQLITE_IOERR_UNLOCK, "SQLITE_IOERR_UNLOCK"); - SetCode(isolate, SQLITE_IOERR_RDLOCK, "SQLITE_IOERR_RDLOCK"); - SetCode(isolate, SQLITE_IOERR_DELETE, "SQLITE_IOERR_DELETE"); - SetCode(isolate, SQLITE_IOERR_BLOCKED, "SQLITE_IOERR_BLOCKED"); - SetCode(isolate, SQLITE_IOERR_NOMEM, "SQLITE_IOERR_NOMEM"); - SetCode(isolate, SQLITE_IOERR_ACCESS, "SQLITE_IOERR_ACCESS"); - SetCode(isolate, SQLITE_IOERR_CHECKRESERVEDLOCK, "SQLITE_IOERR_CHECKRESERVEDLOCK"); - SetCode(isolate, SQLITE_IOERR_LOCK, "SQLITE_IOERR_LOCK"); - SetCode(isolate, SQLITE_IOERR_CLOSE, "SQLITE_IOERR_CLOSE"); - SetCode(isolate, SQLITE_IOERR_DIR_CLOSE, "SQLITE_IOERR_DIR_CLOSE"); - SetCode(isolate, SQLITE_IOERR_SHMOPEN, "SQLITE_IOERR_SHMOPEN"); - SetCode(isolate, SQLITE_IOERR_SHMSIZE, "SQLITE_IOERR_SHMSIZE"); - SetCode(isolate, SQLITE_IOERR_SHMLOCK, "SQLITE_IOERR_SHMLOCK"); - SetCode(isolate, SQLITE_IOERR_SHMMAP, "SQLITE_IOERR_SHMMAP"); - SetCode(isolate, SQLITE_IOERR_SEEK, "SQLITE_IOERR_SEEK"); - SetCode(isolate, SQLITE_IOERR_DELETE_NOENT, "SQLITE_IOERR_DELETE_NOENT"); - SetCode(isolate, SQLITE_IOERR_MMAP, "SQLITE_IOERR_MMAP"); - SetCode(isolate, SQLITE_IOERR_GETTEMPPATH, "SQLITE_IOERR_GETTEMPPATH"); - SetCode(isolate, SQLITE_IOERR_CONVPATH, "SQLITE_IOERR_CONVPATH"); - SetCode(isolate, SQLITE_IOERR_VNODE, "SQLITE_IOERR_VNODE"); - SetCode(isolate, SQLITE_IOERR_AUTH, "SQLITE_IOERR_AUTH"); - SetCode(isolate, SQLITE_IOERR_BEGIN_ATOMIC, "SQLITE_IOERR_BEGIN_ATOMIC"); - SetCode(isolate, SQLITE_IOERR_COMMIT_ATOMIC, "SQLITE_IOERR_COMMIT_ATOMIC"); - SetCode(isolate, SQLITE_IOERR_ROLLBACK_ATOMIC, "SQLITE_IOERR_ROLLBACK_ATOMIC"); - SetCode(isolate, SQLITE_IOERR_DATA, "SQLITE_IOERR_DATA"); - SetCode(isolate, SQLITE_IOERR_CORRUPTFS, "SQLITE_IOERR_CORRUPTFS"); - SetCode(isolate, SQLITE_IOERR_IN_PAGE, "SQLITE_IOERR_IN_PAGE"); - SetCode(isolate, SQLITE_LOCKED_SHAREDCACHE, "SQLITE_LOCKED_SHAREDCACHE"); - SetCode(isolate, SQLITE_LOCKED_VTAB, "SQLITE_LOCKED_VTAB"); - SetCode(isolate, SQLITE_BUSY_RECOVERY, "SQLITE_BUSY_RECOVERY"); - SetCode(isolate, SQLITE_BUSY_SNAPSHOT, "SQLITE_BUSY_SNAPSHOT"); - SetCode(isolate, SQLITE_CANTOPEN_NOTEMPDIR, "SQLITE_CANTOPEN_NOTEMPDIR"); - SetCode(isolate, SQLITE_CANTOPEN_ISDIR, "SQLITE_CANTOPEN_ISDIR"); - SetCode(isolate, SQLITE_CANTOPEN_FULLPATH, "SQLITE_CANTOPEN_FULLPATH"); - SetCode(isolate, SQLITE_CANTOPEN_CONVPATH, "SQLITE_CANTOPEN_CONVPATH"); - SetCode(isolate, SQLITE_CANTOPEN_DIRTYWAL, "SQLITE_CANTOPEN_DIRTYWAL"); - SetCode(isolate, SQLITE_CANTOPEN_SYMLINK, "SQLITE_CANTOPEN_SYMLINK"); - SetCode(isolate, SQLITE_CORRUPT_VTAB, "SQLITE_CORRUPT_VTAB"); - SetCode(isolate, SQLITE_CORRUPT_SEQUENCE, "SQLITE_CORRUPT_SEQUENCE"); - SetCode(isolate, SQLITE_CORRUPT_INDEX, "SQLITE_CORRUPT_INDEX"); - SetCode(isolate, SQLITE_READONLY_RECOVERY, "SQLITE_READONLY_RECOVERY"); - SetCode(isolate, SQLITE_READONLY_CANTLOCK, "SQLITE_READONLY_CANTLOCK"); - SetCode(isolate, SQLITE_READONLY_ROLLBACK, "SQLITE_READONLY_ROLLBACK"); - SetCode(isolate, SQLITE_READONLY_DBMOVED, "SQLITE_READONLY_DBMOVED"); - SetCode(isolate, SQLITE_READONLY_CANTINIT, "SQLITE_READONLY_CANTINIT"); - SetCode(isolate, SQLITE_READONLY_DIRECTORY, "SQLITE_READONLY_DIRECTORY"); - SetCode(isolate, SQLITE_ABORT_ROLLBACK, "SQLITE_ABORT_ROLLBACK"); - SetCode(isolate, SQLITE_CONSTRAINT_CHECK, "SQLITE_CONSTRAINT_CHECK"); - SetCode(isolate, SQLITE_CONSTRAINT_COMMITHOOK, "SQLITE_CONSTRAINT_COMMITHOOK"); - SetCode(isolate, SQLITE_CONSTRAINT_FOREIGNKEY, "SQLITE_CONSTRAINT_FOREIGNKEY"); - SetCode(isolate, SQLITE_CONSTRAINT_FUNCTION, "SQLITE_CONSTRAINT_FUNCTION"); - SetCode(isolate, SQLITE_CONSTRAINT_NOTNULL, "SQLITE_CONSTRAINT_NOTNULL"); - SetCode(isolate, SQLITE_CONSTRAINT_PRIMARYKEY, "SQLITE_CONSTRAINT_PRIMARYKEY"); - SetCode(isolate, SQLITE_CONSTRAINT_TRIGGER, "SQLITE_CONSTRAINT_TRIGGER"); - SetCode(isolate, SQLITE_CONSTRAINT_UNIQUE, "SQLITE_CONSTRAINT_UNIQUE"); - SetCode(isolate, SQLITE_CONSTRAINT_VTAB, "SQLITE_CONSTRAINT_VTAB"); - SetCode(isolate, SQLITE_CONSTRAINT_ROWID, "SQLITE_CONSTRAINT_ROWID"); - SetCode(isolate, SQLITE_CONSTRAINT_PINNED, "SQLITE_CONSTRAINT_PINNED"); - SetCode(isolate, SQLITE_CONSTRAINT_DATATYPE, "SQLITE_CONSTRAINT_DATATYPE"); - SetCode(isolate, SQLITE_NOTICE_RECOVER_WAL, "SQLITE_NOTICE_RECOVER_WAL"); - SetCode(isolate, SQLITE_NOTICE_RECOVER_ROLLBACK, "SQLITE_NOTICE_RECOVER_ROLLBACK"); - SetCode(isolate, SQLITE_NOTICE_RBU, "SQLITE_NOTICE_RBU"); - SetCode(isolate, SQLITE_WARNING_AUTOINDEX, "SQLITE_WARNING_AUTOINDEX"); - SetCode(isolate, SQLITE_AUTH_USER, "SQLITE_AUTH_USER"); - SetCode(isolate, SQLITE_OK_LOAD_PERMANENTLY, "SQLITE_OK_LOAD_PERMANENTLY"); - SetCode(isolate, SQLITE_OK_SYMLINK, "SQLITE_OK_SYMLINK"); - } - - v8::Global database; - v8::Global reader; - v8::Global source; - v8::Global memory; - v8::Global readonly; - v8::Global name; - v8::Global next; - v8::Global length; - v8::Global done; - v8::Global value; - v8::Global changes; - v8::Global lastInsertRowid; - v8::Global statement; - v8::Global column; - v8::Global table; - v8::Global type; - v8::Global totalPages; - v8::Global remainingPages; - -private: - - static void SetString(v8::Isolate* isolate, v8::Global& constant, const char* str) { - constant.Reset(isolate, InternalizedFromLatin1(isolate, str)); - } - - void SetCode(v8::Isolate* isolate, int code, const char* str) { - codes.emplace(std::piecewise_construct, - std::forward_as_tuple(code), - std::forward_as_tuple(isolate, InternalizedFromLatin1(isolate, str))); - } - - std::unordered_map > codes; -}; diff --git a/src/util/custom-aggregate.cpp b/src/util/custom-aggregate.cpp new file mode 100644 index 000000000..cb1680a6e --- /dev/null +++ b/src/util/custom-aggregate.cpp @@ -0,0 +1,61 @@ +#include "custom-aggregate.hpp" +#include "../better_sqlite3_impl.hpp" + +CustomAggregate::CustomAggregate(v8::Isolate *isolate, Database *db, + char const *name, v8::Local start, + v8::Local step, + v8::Local inverse, + v8::Local result, bool safe_ints) + : CustomFunction(isolate, db, name, step, safe_ints), + invoke_result(result->IsFunction()), invoke_start(start->IsFunction()), + inverse(isolate, inverse->IsFunction() ? inverse.As() + : v8::Local()), + result(isolate, result->IsFunction() ? result.As() + : v8::Local()), + start(isolate, start) {} +void CustomAggregate::xStep(sqlite3_context *invocation, int argc, + sqlite3_value **argv) { + xStepBase(invocation, argc, argv, &CustomAggregate::fn); +} +void CustomAggregate::xInverse(sqlite3_context *invocation, int argc, + sqlite3_value **argv) { + xStepBase(invocation, argc, argv, &CustomAggregate::inverse); +} +void CustomAggregate::xValue(sqlite3_context *invocation) { + xValueBase(invocation, false); +} +void CustomAggregate::xFinal(sqlite3_context *invocation) { + xValueBase(invocation, true); +} +CustomAggregate::Accumulator * +CustomAggregate::GetAccumulator(sqlite3_context *invocation) { + Accumulator *acc = static_cast( + sqlite3_aggregate_context(invocation, sizeof(Accumulator))); + if (!acc->initialized) { + assert(acc->value.IsEmpty()); + acc->initialized = true; + if (invoke_start) { + v8::MaybeLocal maybeSeed = + start.Get(isolate).As()->Call( + isolate->GetCurrentContext(), v8::Undefined(isolate), 0, NULL); + if (maybeSeed.IsEmpty()) + PropagateJSError(invocation); + else + acc->value.Reset(isolate, maybeSeed.ToLocalChecked()); + } else { + assert(!start.IsEmpty()); + acc->value.Reset(isolate, start); + } + } + return acc; +} +void CustomAggregate::DestroyAccumulator(sqlite3_context *invocation) { + Accumulator *acc = static_cast( + sqlite3_aggregate_context(invocation, sizeof(Accumulator))); + assert(acc->initialized); + acc->value.Reset(); +} +void CustomAggregate::PropagateJSError(sqlite3_context *invocation) { + DestroyAccumulator(invocation); + CustomFunction::PropagateJSError(invocation); +} diff --git a/src/util/custom-aggregate.hpp b/src/util/custom-aggregate.hpp new file mode 100644 index 000000000..fa8d8c6e4 --- /dev/null +++ b/src/util/custom-aggregate.hpp @@ -0,0 +1,110 @@ +#ifndef BETTER_SQLITE3_UTIL_CUSTOM_AGGREGATE_HPP +#define BETTER_SQLITE3_UTIL_CUSTOM_AGGREGATE_HPP + +#include "../better_sqlite3_deps.hpp" +#include "custom-function.hpp" +#include "data.hpp" +#include "macros.hpp" + +class CustomAggregate : public CustomFunction { +public: + explicit CustomAggregate(v8::Isolate *isolate, Database *db, char const *name, + v8::Local start, + v8::Local step, + v8::Local inverse, + v8::Local result, bool safe_ints); + static void xStep(sqlite3_context *invocation, int argc, + sqlite3_value **argv); + static void xInverse(sqlite3_context *invocation, int argc, + sqlite3_value **argv); + static void xValue(sqlite3_context *invocation); + static void xFinal(sqlite3_context *invocation); + +private: + static void xStepBase(sqlite3_context *invocation, int argc, + sqlite3_value **argv, + v8::Global const CustomAggregate::*ptrtm); + static void xValueBase(sqlite3_context *invocation, bool is_final); + struct Accumulator { + public: + v8::Global value; + bool initialized; + bool is_window; + }; + Accumulator *GetAccumulator(sqlite3_context *invocation); + static void DestroyAccumulator(sqlite3_context *invocation); + void PropagateJSError(sqlite3_context *invocation); + bool const invoke_result; + bool const invoke_start; + v8::Global const inverse; + v8::Global const result; + v8::Global const start; +}; +LZZ_INLINE void CustomAggregate::xStepBase( + sqlite3_context *invocation, int argc, sqlite3_value **argv, + v8::Global const CustomAggregate::*ptrtm) { + CustomAggregate *self = + static_cast(sqlite3_user_data(invocation)); + v8::Isolate *isolate = self->isolate; + v8::HandleScope scope(isolate); + Accumulator *acc = self->GetAccumulator(invocation); + if (acc->value.IsEmpty()) + return; + + v8::Local args_fast[5]; + v8::Local *args = + argc <= 4 ? args_fast : ALLOC_ARRAY>(argc + 1); + args[0] = acc->value.Get(isolate); + if (argc != 0) + Data::GetArgumentsJS(isolate, args + 1, argv, argc, self->safe_ints); + + v8::MaybeLocal maybeReturnValue = + (self->*ptrtm) + .Get(isolate) + ->Call(isolate->GetCurrentContext(), v8::Undefined(isolate), argc + 1, + args); + if (args != args_fast) + delete[] args; + + if (maybeReturnValue.IsEmpty()) { + self->PropagateJSError(invocation); + } else { + v8::Local returnValue = maybeReturnValue.ToLocalChecked(); + if (!returnValue->IsUndefined()) + acc->value.Reset(isolate, returnValue); + } +} +LZZ_INLINE void CustomAggregate::xValueBase(sqlite3_context *invocation, + bool is_final) { + CustomAggregate *self = + static_cast(sqlite3_user_data(invocation)); + v8::Isolate *isolate = self->isolate; + v8::HandleScope scope(isolate); + Accumulator *acc = self->GetAccumulator(invocation); + if (acc->value.IsEmpty()) + return; + + if (!is_final) { + acc->is_window = true; + } else if (acc->is_window) { + DestroyAccumulator(invocation); + return; + } + + v8::Local result = acc->value.Get(isolate); + if (self->invoke_result) { + v8::MaybeLocal maybeResult = self->result.Get(isolate)->Call( + isolate->GetCurrentContext(), v8::Undefined(isolate), 1, &result); + if (maybeResult.IsEmpty()) { + self->PropagateJSError(invocation); + return; + } + result = maybeResult.ToLocalChecked(); + } + + Data::ResultValueFromJS(isolate, invocation, result, self); + if (is_final) + DestroyAccumulator(invocation); +} + +#endif // BETTER_SQLITE3_UTIL_CUSTOM_AGGREGATE_HPP diff --git a/src/util/custom-aggregate.lzz b/src/util/custom-aggregate.lzz deleted file mode 100644 index ec52ea824..000000000 --- a/src/util/custom-aggregate.lzz +++ /dev/null @@ -1,121 +0,0 @@ -class CustomAggregate : public CustomFunction { -public: - - explicit CustomAggregate( - v8::Isolate* isolate, - Database* db, - const char* name, - v8::Local start, - v8::Local step, - v8::Local inverse, - v8::Local result, - bool safe_ints - ) : - CustomFunction(isolate, db, name, step, safe_ints), - invoke_result(result->IsFunction()), - invoke_start(start->IsFunction()), - inverse(isolate, inverse->IsFunction() ? inverse.As() : v8::Local()), - result(isolate, result->IsFunction() ? result.As() : v8::Local()), - start(isolate, start) {} - - static void xStep(sqlite3_context* invocation, int argc, sqlite3_value** argv) { - xStepBase(invocation, argc, argv, &CustomAggregate::fn); - } - - static void xInverse(sqlite3_context* invocation, int argc, sqlite3_value** argv) { - xStepBase(invocation, argc, argv, &CustomAggregate::inverse); - } - - static void xValue(sqlite3_context* invocation) { - xValueBase(invocation, false); - } - - static void xFinal(sqlite3_context* invocation) { - xValueBase(invocation, true); - } - -private: - - static inline void xStepBase(sqlite3_context* invocation, int argc, sqlite3_value** argv, const v8::Global CustomAggregate::*ptrtm) { - AGGREGATE_START(); - - v8::Local args_fast[5]; - v8::Local* args = argc <= 4 ? args_fast : ALLOC_ARRAY>(argc + 1); - args[0] = acc->value.Get(isolate); - if (argc != 0) Data::GetArgumentsJS(isolate, args + 1, argv, argc, self->safe_ints); - - v8::MaybeLocal maybeReturnValue = (self->*ptrtm).Get(isolate)->Call(OnlyContext, v8::Undefined(isolate), argc + 1, args); - if (args != args_fast) delete[] args; - - if (maybeReturnValue.IsEmpty()) { - self->PropagateJSError(invocation); - } else { - v8::Local returnValue = maybeReturnValue.ToLocalChecked(); - if (!returnValue->IsUndefined()) acc->value.Reset(isolate, returnValue); - } - } - - static inline void xValueBase(sqlite3_context* invocation, bool is_final) { - AGGREGATE_START(); - - if (!is_final) { - acc->is_window = true; - } else if (acc->is_window) { - DestroyAccumulator(invocation); - return; - } - - v8::Local result = acc->value.Get(isolate); - if (self->invoke_result) { - v8::MaybeLocal maybeResult = self->result.Get(isolate)->Call(OnlyContext, v8::Undefined(isolate), 1, &result); - if (maybeResult.IsEmpty()) { - self->PropagateJSError(invocation); - return; - } - result = maybeResult.ToLocalChecked(); - } - - Data::ResultValueFromJS(isolate, invocation, result, self); - if (is_final) DestroyAccumulator(invocation); - } - - struct Accumulator { public: - v8::Global value; - bool initialized; - bool is_window; - } - - Accumulator* GetAccumulator(sqlite3_context* invocation) { - Accumulator* acc = static_cast(sqlite3_aggregate_context(invocation, sizeof(Accumulator))); - if (!acc->initialized) { - assert(acc->value.IsEmpty()); - acc->initialized = true; - if (invoke_start) { - v8::MaybeLocal maybeSeed = start.Get(isolate).As()->Call(OnlyContext, v8::Undefined(isolate), 0, NULL); - if (maybeSeed.IsEmpty()) PropagateJSError(invocation); - else acc->value.Reset(isolate, maybeSeed.ToLocalChecked()); - } else { - assert(!start.IsEmpty()); - acc->value.Reset(isolate, start); - } - } - return acc; - } - - static void DestroyAccumulator(sqlite3_context* invocation) { - Accumulator* acc = static_cast(sqlite3_aggregate_context(invocation, sizeof(Accumulator))); - assert(acc->initialized); - acc->value.Reset(); - } - - void PropagateJSError(sqlite3_context* invocation) { - DestroyAccumulator(invocation); - CustomFunction::PropagateJSError(invocation); - } - - const bool invoke_result; - const bool invoke_start; - const v8::Global inverse; - const v8::Global result; - const v8::Global start; -}; diff --git a/src/util/custom-function.cpp b/src/util/custom-function.cpp new file mode 100644 index 000000000..ce7a5ce29 --- /dev/null +++ b/src/util/custom-function.cpp @@ -0,0 +1,45 @@ +#include "custom-function.hpp" +#include "../better_sqlite3_impl.hpp" + +CustomFunction::CustomFunction(v8::Isolate *isolate, Database *db, + char const *name, v8::Local fn, + bool safe_ints) + : name(name), db(db), isolate(isolate), fn(isolate, fn), + safe_ints(safe_ints) {} +CustomFunction::~CustomFunction() {} +void CustomFunction::xDestroy(void *self) { + delete static_cast(self); +} +void CustomFunction::xFunc(sqlite3_context *invocation, int argc, + sqlite3_value **argv) { + CustomFunction *self = + static_cast(sqlite3_user_data(invocation)); + v8::Isolate *isolate = self->isolate; + v8::HandleScope scope(isolate); + + v8::Local args_fast[4]; + v8::Local *args = NULL; + if (argc != 0) { + args = argc <= 4 ? args_fast : ALLOC_ARRAY>(argc); + Data::GetArgumentsJS(isolate, args, argv, argc, self->safe_ints); + } + + v8::MaybeLocal maybeReturnValue = self->fn.Get(isolate)->Call( + isolate->GetCurrentContext(), v8::Undefined(isolate), argc, args); + if (args != args_fast) + delete[] args; + + if (maybeReturnValue.IsEmpty()) + self->PropagateJSError(invocation); + else + Data::ResultValueFromJS(isolate, invocation, + maybeReturnValue.ToLocalChecked(), self); +} +void CustomFunction::PropagateJSError(sqlite3_context *invocation) { + assert(db->GetState()->was_js_error == false); + db->GetState()->was_js_error = true; + sqlite3_result_error(invocation, "", 0); +} +std::string CustomFunction::GetDataErrorPrefix() { + return std::string("User-defined function ") + name + "() returned"; +} diff --git a/src/util/custom-function.hpp b/src/util/custom-function.hpp new file mode 100644 index 000000000..a8bac810b --- /dev/null +++ b/src/util/custom-function.hpp @@ -0,0 +1,33 @@ +#ifndef BETTER_SQLITE3_UTIL_CUSTOM_FUNCTION_HPP +#define BETTER_SQLITE3_UTIL_CUSTOM_FUNCTION_HPP + +#include "../better_sqlite3_deps.hpp" +#include "data-converter.hpp" + +// Forward declarations +class Database; + +class CustomFunction : protected DataConverter { +public: + explicit CustomFunction(v8::Isolate *isolate, Database *db, char const *name, + v8::Local fn, bool safe_ints); + virtual ~CustomFunction(); + static void xDestroy(void *self); + static void xFunc(sqlite3_context *invocation, int argc, + sqlite3_value **argv); + +protected: + void PropagateJSError(sqlite3_context *invocation); + std::string GetDataErrorPrefix(); + +private: + std::string const name; + Database *const db; + +protected: + v8::Isolate *const isolate; + v8::Global const fn; + bool const safe_ints; +}; + +#endif // BETTER_SQLITE3_UTIL_CUSTOM_FUNCTION_HPP diff --git a/src/util/custom-function.lzz b/src/util/custom-function.lzz deleted file mode 100644 index e9972663e..000000000 --- a/src/util/custom-function.lzz +++ /dev/null @@ -1,59 +0,0 @@ -class CustomFunction : protected DataConverter { -public: - - explicit CustomFunction( - v8::Isolate* isolate, - Database* db, - const char* name, - v8::Local fn, - bool safe_ints - ) : - name(name), - db(db), - isolate(isolate), - fn(isolate, fn), - safe_ints(safe_ints) {} - - virtual ~CustomFunction() {} - - static void xDestroy(void* self) { - delete static_cast(self); - } - - static void xFunc(sqlite3_context* invocation, int argc, sqlite3_value** argv) { - FUNCTION_START(); - - v8::Local args_fast[4]; - v8::Local* args = NULL; - if (argc != 0) { - args = argc <= 4 ? args_fast : ALLOC_ARRAY>(argc); - Data::GetArgumentsJS(isolate, args, argv, argc, self->safe_ints); - } - - v8::MaybeLocal maybeReturnValue = self->fn.Get(isolate)->Call(OnlyContext, v8::Undefined(isolate), argc, args); - if (args != args_fast) delete[] args; - - if (maybeReturnValue.IsEmpty()) self->PropagateJSError(invocation); - else Data::ResultValueFromJS(isolate, invocation, maybeReturnValue.ToLocalChecked(), self); - } - -protected: - - void PropagateJSError(sqlite3_context* invocation) { - assert(db->GetState()->was_js_error == false); - db->GetState()->was_js_error = true; - sqlite3_result_error(invocation, "", 0); - } - - std::string GetDataErrorPrefix() { - return std::string("User-defined function ") + name + "() returned"; - } - -private: - const std::string name; - Database* const db; -protected: - v8::Isolate* const isolate; - const v8::Global fn; - const bool safe_ints; -}; diff --git a/src/util/custom-table.cpp b/src/util/custom-table.cpp new file mode 100644 index 000000000..a6e063566 --- /dev/null +++ b/src/util/custom-table.cpp @@ -0,0 +1,279 @@ +#include "custom-table.hpp" +#include "../better_sqlite3_impl.hpp" + +CustomTable::CustomTable(v8::Isolate *isolate, Database *db, char const *name, + v8::Local factory) + : addon(db->GetAddon()), isolate(isolate), db(db), name(name), + factory(isolate, factory) {} +void CustomTable::Destructor(void *self) { + delete static_cast(self); +} +sqlite3_module CustomTable::MODULE = { + 0, xCreate, xConnect, xBestIndex, xDisconnect, xDisconnect, + xOpen, xClose, xFilter, xNext, xEof, xColumn, + xRowid, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL}; +sqlite3_module CustomTable::EPONYMOUS_MODULE = { + 0, NULL, xConnect, xBestIndex, xDisconnect, xDisconnect, + xOpen, xClose, xFilter, xNext, xEof, xColumn, + xRowid, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL}; +CustomTable::VTab::VTab(CustomTable *parent, v8::Local generator, + std::vector parameter_names, + bool safe_ints) + : parent(parent), parameter_count(parameter_names.size()), + safe_ints(safe_ints), generator(parent->isolate, generator), + parameter_names(parameter_names) { + ((void)base); +} +CustomTable::TempDataConverter::TempDataConverter(CustomTable *parent) + : parent(parent), status(SQLITE_OK) {} +void CustomTable::TempDataConverter::PropagateJSError( + sqlite3_context *invocation) { + status = SQLITE_ERROR; + parent->PropagateJSError(); +} +std::string CustomTable::TempDataConverter::GetDataErrorPrefix() { + return std::string("Virtual table module \"") + parent->name + "\" yielded"; +} +int CustomTable::xCreate(sqlite3 *db_handle, void *_self, int argc, + char const *const *argv, sqlite3_vtab **output, + char **errOutput) { + return xConnect(db_handle, _self, argc, argv, output, errOutput); +} +int CustomTable::xConnect(sqlite3 *db_handle, void *_self, int argc, + char const *const *argv, sqlite3_vtab **output, + char **errOutput) { + CustomTable *self = static_cast(_self); + v8::Isolate *isolate = self->isolate; + v8::HandleScope scope(isolate); + v8::Local ctx = isolate->GetCurrentContext(); + + v8::Local *args = ALLOC_ARRAY>(argc); + for (int i = 0; i < argc; ++i) { + args[i] = StringFromUtf8(isolate, argv[i], -1); + } + + v8::MaybeLocal maybeReturnValue = + self->factory.Get(isolate)->Call(ctx, v8::Undefined(isolate), argc, args); + delete[] args; + + if (maybeReturnValue.IsEmpty()) { + self->PropagateJSError(); + return SQLITE_ERROR; + } + + v8::Local returnValue = + maybeReturnValue.ToLocalChecked().As(); + v8::Local sqlString = + returnValue->Get(ctx, 0).ToLocalChecked().As(); + v8::Local generator = + returnValue->Get(ctx, 1).ToLocalChecked().As(); + v8::Local parameterNames = + returnValue->Get(ctx, 2).ToLocalChecked().As(); + int safe_ints = + returnValue->Get(ctx, 3).ToLocalChecked().As()->Value(); + bool direct_only = + returnValue->Get(ctx, 4).ToLocalChecked().As()->Value(); + + v8::String::Utf8Value sql(isolate, sqlString); + safe_ints = safe_ints < 2 ? safe_ints + : static_cast(self->db->GetState()->safe_ints); + + std::vector parameter_names; + for (int i = 0, len = parameterNames->Length(); i < len; ++i) { + v8::Local parameterName = + parameterNames->Get(ctx, i).ToLocalChecked().As(); + v8::String::Utf8Value parameter_name(isolate, parameterName); + parameter_names.emplace_back(*parameter_name); + } + + if (sqlite3_declare_vtab(db_handle, *sql) != SQLITE_OK) { + *errOutput = + sqlite3_mprintf("failed to declare virtual table \"%s\"", argv[2]); + return SQLITE_ERROR; + } + if (direct_only && + sqlite3_vtab_config(db_handle, SQLITE_VTAB_DIRECTONLY) != SQLITE_OK) { + *errOutput = + sqlite3_mprintf("failed to configure virtual table \"%s\"", argv[2]); + return SQLITE_ERROR; + } + + *output = (new VTab(self, generator, parameter_names, safe_ints))->Downcast(); + return SQLITE_OK; +} +int CustomTable::xDisconnect(sqlite3_vtab *vtab) { + delete VTab::Upcast(vtab); + return SQLITE_OK; +} +int CustomTable::xOpen(sqlite3_vtab *vtab, sqlite3_vtab_cursor **output) { + *output = (new Cursor())->Downcast(); + return SQLITE_OK; +} +int CustomTable::xClose(sqlite3_vtab_cursor *cursor) { + delete Cursor::Upcast(cursor); + return SQLITE_OK; +} +int CustomTable::xFilter(sqlite3_vtab_cursor *_cursor, int idxNum, + char const *idxStr, int argc, sqlite3_value **argv) { + Cursor *cursor = Cursor::Upcast(_cursor); + VTab *vtab = cursor->GetVTab(); + CustomTable *self = vtab->parent; + Addon *addon = self->addon; + v8::Isolate *isolate = self->isolate; + v8::HandleScope scope(isolate); + v8::Local ctx = isolate->GetCurrentContext(); + + v8::Local args_fast[4]; + v8::Local *args = NULL; + int parameter_count = vtab->parameter_count; + if (parameter_count != 0) { + args = parameter_count <= 4 + ? args_fast + : ALLOC_ARRAY>(parameter_count); + int argn = 0; + bool safe_ints = vtab->safe_ints; + for (int i = 0; i < parameter_count; ++i) { + if (idxNum & 1 << i) { + args[i] = Data::GetValueJS(isolate, argv[argn++], safe_ints); + + if (args[i]->IsNull()) { + if (args != args_fast) + delete[] args; + cursor->done = true; + return SQLITE_OK; + } + } else { + args[i] = v8::Undefined(isolate); + } + } + } + + v8::MaybeLocal maybeIterator = vtab->generator.Get(isolate)->Call( + ctx, v8::Undefined(isolate), parameter_count, args); + if (args != args_fast) + delete[] args; + + if (maybeIterator.IsEmpty()) { + self->PropagateJSError(); + return SQLITE_ERROR; + } + + v8::Local iterator = + maybeIterator.ToLocalChecked().As(); + v8::Local next = iterator->Get(ctx, addon->cs.next.Get(isolate)) + .ToLocalChecked() + .As(); + cursor->iterator.Reset(isolate, iterator); + cursor->next.Reset(isolate, next); + cursor->rowid = 0; + + return xNext(cursor->Downcast()); +} +int CustomTable::xNext(sqlite3_vtab_cursor *_cursor) { + Cursor *cursor = Cursor::Upcast(_cursor); + CustomTable *self = cursor->GetVTab()->parent; + Addon *addon = self->addon; + v8::Isolate *isolate = self->isolate; + v8::HandleScope scope(isolate); + v8::Local ctx = isolate->GetCurrentContext(); + + v8::Local iterator = cursor->iterator.Get(isolate); + v8::Local next = cursor->next.Get(isolate); + + v8::MaybeLocal maybeRecord = next->Call(ctx, iterator, 0, NULL); + if (maybeRecord.IsEmpty()) { + self->PropagateJSError(); + return SQLITE_ERROR; + } + + v8::Local record = maybeRecord.ToLocalChecked().As(); + bool done = record->Get(ctx, addon->cs.done.Get(isolate)) + .ToLocalChecked() + .As() + ->Value(); + if (!done) { + cursor->row.Reset(isolate, record->Get(ctx, addon->cs.value.Get(isolate)) + .ToLocalChecked() + .As()); + } + cursor->done = done; + cursor->rowid += 1; + + return SQLITE_OK; +} +int CustomTable::xEof(sqlite3_vtab_cursor *cursor) { + return Cursor::Upcast(cursor)->done; +} +int CustomTable::xColumn(sqlite3_vtab_cursor *_cursor, + sqlite3_context *invocation, int column) { + Cursor *cursor = Cursor::Upcast(_cursor); + CustomTable *self = cursor->GetVTab()->parent; + TempDataConverter temp_data_converter(self); + v8::Isolate *isolate = self->isolate; + v8::HandleScope scope(isolate); + + v8::Local row = cursor->row.Get(isolate); + v8::MaybeLocal maybeColumnValue = + row->Get(isolate->GetCurrentContext(), column); + if (maybeColumnValue.IsEmpty()) { + temp_data_converter.PropagateJSError(NULL); + } else { + Data::ResultValueFromJS(isolate, invocation, + maybeColumnValue.ToLocalChecked(), + &temp_data_converter); + } + return temp_data_converter.status; +} +int CustomTable::xRowid(sqlite3_vtab_cursor *cursor, sqlite_int64 *output) { + *output = Cursor::Upcast(cursor)->rowid; + return SQLITE_OK; +} +int CustomTable::xBestIndex(sqlite3_vtab *vtab, sqlite3_index_info *output) { + int parameter_count = VTab::Upcast(vtab)->parameter_count; + int argument_count = 0; + std::vector> forwarded; + + for (int i = 0, len = output->nConstraint; i < len; ++i) { + auto item = output->aConstraint[i]; + + if (item.op == SQLITE_INDEX_CONSTRAINT_LIMIT || + item.op == SQLITE_INDEX_CONSTRAINT_OFFSET) { + continue; + } + + if (item.iColumn >= 0 && item.iColumn < parameter_count) { + if (item.op != SQLITE_INDEX_CONSTRAINT_EQ) { + sqlite3_free(vtab->zErrMsg); + vtab->zErrMsg = sqlite3_mprintf( + "virtual table parameter \"%s\" can only be constrained by the '=' " + "operator", + VTab::Upcast(vtab)->parameter_names.at(item.iColumn).c_str()); + return SQLITE_ERROR; + } + if (!item.usable) { + return SQLITE_CONSTRAINT; + } + forwarded.emplace_back(item.iColumn, i); + } + } + + std::sort(forwarded.begin(), forwarded.end()); + for (std::pair pair : forwarded) { + int bit = 1 << pair.first; + if (!(output->idxNum & bit)) { + output->idxNum |= bit; + output->aConstraintUsage[pair.second].argvIndex = ++argument_count; + output->aConstraintUsage[pair.second].omit = 1; + } + } + + output->estimatedCost = output->estimatedRows = + 1000000000 / (argument_count + 1); + return SQLITE_OK; +} +void CustomTable::PropagateJSError() { + assert(db->GetState()->was_js_error == false); + db->GetState()->was_js_error = true; +} diff --git a/src/util/custom-table.hpp b/src/util/custom-table.hpp new file mode 100644 index 000000000..11f24c989 --- /dev/null +++ b/src/util/custom-table.hpp @@ -0,0 +1,94 @@ +#ifndef BETTER_SQLITE3_UTIL_CUSTOM_TABLE_HPP +#define BETTER_SQLITE3_UTIL_CUSTOM_TABLE_HPP + +#include "../better_sqlite3_deps.hpp" +#include "data-converter.hpp" + +// Forward declarations +struct Addon; +class Database; + +class CustomTable { +public: + explicit CustomTable(v8::Isolate *isolate, Database *db, char const *name, + v8::Local factory); + static void Destructor(void *self); + static sqlite3_module MODULE; + static sqlite3_module EPONYMOUS_MODULE; + +private: + class VTab { + friend class CustomTable; + explicit VTab(CustomTable *parent, v8::Local generator, + std::vector parameter_names, bool safe_ints); + static CustomTable::VTab *Upcast(sqlite3_vtab *vtab); + sqlite3_vtab *Downcast(); + sqlite3_vtab base; + CustomTable *const parent; + int const parameter_count; + bool const safe_ints; + v8::Global const generator; + std::vector const parameter_names; + }; + class Cursor { + friend class CustomTable; + static CustomTable::Cursor *Upcast(sqlite3_vtab_cursor *cursor); + sqlite3_vtab_cursor *Downcast(); + CustomTable::VTab *GetVTab(); + sqlite3_vtab_cursor base; + v8::Global iterator; + v8::Global next; + v8::Global row; + bool done; + sqlite_int64 rowid; + }; + class TempDataConverter : DataConverter { + friend class CustomTable; + explicit TempDataConverter(CustomTable *parent); + void PropagateJSError(sqlite3_context *invocation); + std::string GetDataErrorPrefix(); + CustomTable *const parent; + int status; + }; + static int xCreate(sqlite3 *db_handle, void *_self, int argc, + char const *const *argv, sqlite3_vtab **output, + char **errOutput); + static int xConnect(sqlite3 *db_handle, void *_self, int argc, + char const *const *argv, sqlite3_vtab **output, + char **errOutput); + static int xDisconnect(sqlite3_vtab *vtab); + static int xOpen(sqlite3_vtab *vtab, sqlite3_vtab_cursor **output); + static int xClose(sqlite3_vtab_cursor *cursor); + static int xFilter(sqlite3_vtab_cursor *_cursor, int idxNum, + char const *idxStr, int argc, sqlite3_value **argv); + static int xNext(sqlite3_vtab_cursor *_cursor); + static int xEof(sqlite3_vtab_cursor *cursor); + static int xColumn(sqlite3_vtab_cursor *_cursor, sqlite3_context *invocation, + int column); + static int xRowid(sqlite3_vtab_cursor *cursor, sqlite_int64 *output); + static int xBestIndex(sqlite3_vtab *vtab, sqlite3_index_info *output); + void PropagateJSError(); + Addon *const addon; + v8::Isolate *const isolate; + Database *const db; + std::string const name; + v8::Global const factory; +}; +LZZ_INLINE CustomTable::VTab *CustomTable::VTab::Upcast(sqlite3_vtab *vtab) { + return reinterpret_cast(vtab); +} +LZZ_INLINE sqlite3_vtab *CustomTable::VTab::Downcast() { + return reinterpret_cast(this); +} +LZZ_INLINE CustomTable::Cursor * +CustomTable::Cursor::Upcast(sqlite3_vtab_cursor *cursor) { + return reinterpret_cast(cursor); +} +LZZ_INLINE sqlite3_vtab_cursor *CustomTable::Cursor::Downcast() { + return reinterpret_cast(this); +} +LZZ_INLINE CustomTable::VTab *CustomTable::Cursor::GetVTab() { + return VTab::Upcast(base.pVtab); +} + +#endif // BETTER_SQLITE3_UTIL_CUSTOM_TABLE_HPP diff --git a/src/util/custom-table.lzz b/src/util/custom-table.lzz deleted file mode 100644 index 89b7dff42..000000000 --- a/src/util/custom-table.lzz +++ /dev/null @@ -1,404 +0,0 @@ -class CustomTable { -public: - - explicit CustomTable( - v8::Isolate* isolate, - Database* db, - const char* name, - v8::Local factory - ) : - addon(db->GetAddon()), - isolate(isolate), - db(db), - name(name), - factory(isolate, factory) {} - - static void Destructor(void* self) { - delete static_cast(self); - } - - static sqlite3_module MODULE = { - 0, /* iVersion */ - xCreate, /* xCreate */ - xConnect, /* xConnect */ - xBestIndex, /* xBestIndex */ - xDisconnect, /* xDisconnect */ - xDisconnect, /* xDestroy */ - xOpen, /* xOpen */ - xClose, /* xClose */ - xFilter, /* xFilter */ - xNext, /* xNext */ - xEof, /* xEof */ - xColumn, /* xColumn */ - xRowid, /* xRowid */ - NULL, /* xUpdate */ - NULL, /* xBegin */ - NULL, /* xSync */ - NULL, /* xCommit */ - NULL, /* xRollback */ - NULL, /* xFindMethod */ - NULL, /* xRename */ - NULL, /* xSavepoint */ - NULL, /* xRelease */ - NULL, /* xRollbackTo */ - NULL /* xShadowName */ - }; - - static sqlite3_module EPONYMOUS_MODULE = { - 0, /* iVersion */ - NULL, /* xCreate */ - xConnect, /* xConnect */ - xBestIndex, /* xBestIndex */ - xDisconnect, /* xDisconnect */ - xDisconnect, /* xDestroy */ - xOpen, /* xOpen */ - xClose, /* xClose */ - xFilter, /* xFilter */ - xNext, /* xNext */ - xEof, /* xEof */ - xColumn, /* xColumn */ - xRowid, /* xRowid */ - NULL, /* xUpdate */ - NULL, /* xBegin */ - NULL, /* xSync */ - NULL, /* xCommit */ - NULL, /* xRollback */ - NULL, /* xFindMethod */ - NULL, /* xRename */ - NULL, /* xSavepoint */ - NULL, /* xRelease */ - NULL, /* xRollbackTo */ - NULL /* xShadowName */ - }; - -private: - - // This nested class is instantiated on each CREATE VIRTUAL TABLE statement. - class VTab { friend class CustomTable; - explicit VTab( - CustomTable* parent, - v8::Local generator, - std::vector parameter_names, - bool safe_ints - ) : - parent(parent), - parameter_count(parameter_names.size()), - safe_ints(safe_ints), - generator(parent->isolate, generator), - parameter_names(parameter_names) { - ((void)base); - } - - static inline CustomTable::VTab* Upcast(sqlite3_vtab* vtab) { - return reinterpret_cast(vtab); - } - - inline sqlite3_vtab* Downcast() { - return reinterpret_cast(this); - } - - sqlite3_vtab base; - CustomTable * const parent; - const int parameter_count; - const bool safe_ints; - const v8::Global generator; - const std::vector parameter_names; - }; - - // This nested class is instantiated each time a virtual table is scanned. - class Cursor { friend class CustomTable; - static inline CustomTable::Cursor* Upcast(sqlite3_vtab_cursor* cursor) { - return reinterpret_cast(cursor); - } - - inline sqlite3_vtab_cursor* Downcast() { - return reinterpret_cast(this); - } - - inline CustomTable::VTab* GetVTab() { - return VTab::Upcast(base.pVtab); - } - - sqlite3_vtab_cursor base; - v8::Global iterator; - v8::Global next; - v8::Global row; - bool done; - sqlite_int64 rowid; - }; - - // This nested class is used by Data::ResultValueFromJS to report errors. - class TempDataConverter : DataConverter { friend class CustomTable; - explicit TempDataConverter(CustomTable* parent) : - parent(parent), - status(SQLITE_OK) {} - - void PropagateJSError(sqlite3_context* invocation) { - status = SQLITE_ERROR; - parent->PropagateJSError(); - } - - std::string GetDataErrorPrefix() { - return std::string("Virtual table module \"") + parent->name + "\" yielded"; - } - - CustomTable * const parent; - int status; - }; - - // Although this function does nothing, we cannot use xConnect directly, - // because that would cause SQLite to register an eponymous virtual table. - static int xCreate(sqlite3* db_handle, void* _self, int argc, const char* const * argv, sqlite3_vtab** output, char** errOutput) { - return xConnect(db_handle, _self, argc, argv, output, errOutput); - } - - // This method uses the factory function to instantiate a new virtual table. - static int xConnect(sqlite3* db_handle, void* _self, int argc, const char* const * argv, sqlite3_vtab** output, char** errOutput) { - CustomTable* self = static_cast(_self); - v8::Isolate* isolate = self->isolate; - v8::HandleScope scope(isolate); - UseContext; - - v8::Local* args = ALLOC_ARRAY>(argc); - for (int i = 0; i < argc; ++i) { - args[i] = StringFromUtf8(isolate, argv[i], -1); - } - - // Run the factory function to receive a new virtual table definition. - v8::MaybeLocal maybeReturnValue = self->factory.Get(isolate)->Call(ctx, v8::Undefined(isolate), argc, args); - delete[] args; - - if (maybeReturnValue.IsEmpty()) { - self->PropagateJSError(); - return SQLITE_ERROR; - } - - // Extract each part of the virtual table definition. - v8::Local returnValue = maybeReturnValue.ToLocalChecked().As(); - v8::Local sqlString = returnValue->Get(ctx, 0).ToLocalChecked().As(); - v8::Local generator = returnValue->Get(ctx, 1).ToLocalChecked().As(); - v8::Local parameterNames = returnValue->Get(ctx, 2).ToLocalChecked().As(); - int safe_ints = returnValue->Get(ctx, 3).ToLocalChecked().As()->Value(); - bool direct_only = returnValue->Get(ctx, 4).ToLocalChecked().As()->Value(); - - v8::String::Utf8Value sql(isolate, sqlString); - safe_ints = safe_ints < 2 ? safe_ints : static_cast(self->db->GetState()->safe_ints); - - // Copy the parameter names into a std::vector. - std::vector parameter_names; - for (int i = 0, len = parameterNames->Length(); i < len; ++i) { - v8::Local parameterName = parameterNames->Get(ctx, i).ToLocalChecked().As(); - v8::String::Utf8Value parameter_name(isolate, parameterName); - parameter_names.emplace_back(*parameter_name); - } - - // Pass our SQL table definition to SQLite (this should never fail). - if (sqlite3_declare_vtab(db_handle, *sql) != SQLITE_OK) { - *errOutput = sqlite3_mprintf("failed to declare virtual table \"%s\"", argv[2]); - return SQLITE_ERROR; - } - if (direct_only && sqlite3_vtab_config(db_handle, SQLITE_VTAB_DIRECTONLY) != SQLITE_OK) { - *errOutput = sqlite3_mprintf("failed to configure virtual table \"%s\"", argv[2]); - return SQLITE_ERROR; - } - - // Return the successfully created virtual table. - *output = (new VTab(self, generator, parameter_names, safe_ints))->Downcast(); - return SQLITE_OK; - } - - static int xDisconnect(sqlite3_vtab* vtab) { - delete VTab::Upcast(vtab); - return SQLITE_OK; - } - - static int xOpen(sqlite3_vtab* vtab, sqlite3_vtab_cursor** output) { - *output = (new Cursor())->Downcast(); - return SQLITE_OK; - } - - static int xClose(sqlite3_vtab_cursor* cursor) { - delete Cursor::Upcast(cursor); - return SQLITE_OK; - } - - // This method uses a fresh cursor to start a new scan of a virtual table. - // The args and idxNum are provided by xBestIndex (idxStr is unused). - // idxNum is a bitmap that provides the proper indices of the received args. - static int xFilter(sqlite3_vtab_cursor* _cursor, int idxNum, const char* idxStr, int argc, sqlite3_value** argv) { - Cursor* cursor = Cursor::Upcast(_cursor); - VTab* vtab = cursor->GetVTab(); - CustomTable* self = vtab->parent; - Addon* addon = self->addon; - v8::Isolate* isolate = self->isolate; - v8::HandleScope scope(isolate); - UseContext; - - // Convert the SQLite arguments into JavaScript arguments. Note that - // the values in argv may be in the wrong order, so we fix that here. - v8::Local args_fast[4]; - v8::Local* args = NULL; - int parameter_count = vtab->parameter_count; - if (parameter_count != 0) { - args = parameter_count <= 4 ? args_fast : ALLOC_ARRAY>(parameter_count); - int argn = 0; - bool safe_ints = vtab->safe_ints; - for (int i = 0; i < parameter_count; ++i) { - if (idxNum & 1 << i) { - args[i] = Data::GetValueJS(isolate, argv[argn++], safe_ints); - // If any arguments are NULL, the result set is necessarily - // empty, so don't bother to run the generator function. - if (args[i]->IsNull()) { - if (args != args_fast) delete[] args; - cursor->done = true; - return SQLITE_OK; - } - } else { - args[i] = v8::Undefined(isolate); - } - } - } - - // Invoke the generator function to create a new iterator. - v8::MaybeLocal maybeIterator = vtab->generator.Get(isolate)->Call(ctx, v8::Undefined(isolate), parameter_count, args); - if (args != args_fast) delete[] args; - - if (maybeIterator.IsEmpty()) { - self->PropagateJSError(); - return SQLITE_ERROR; - } - - // Store the iterator and its next() method; we'll be using it a lot. - v8::Local iterator = maybeIterator.ToLocalChecked().As(); - v8::Local next = iterator->Get(ctx, addon->cs.next.Get(isolate)).ToLocalChecked().As(); - cursor->iterator.Reset(isolate, iterator); - cursor->next.Reset(isolate, next); - cursor->rowid = 0; - - // Advance the iterator/cursor to the first row. - return xNext(cursor->Downcast()); - } - - // This method advances a virtual table's cursor to the next row. - // SQLite will call this method repeatedly, driving the generator function. - static int xNext(sqlite3_vtab_cursor* _cursor) { - Cursor* cursor = Cursor::Upcast(_cursor); - CustomTable* self = cursor->GetVTab()->parent; - Addon* addon = self->addon; - v8::Isolate* isolate = self->isolate; - v8::HandleScope scope(isolate); - UseContext; - - v8::Local iterator = cursor->iterator.Get(isolate); - v8::Local next = cursor->next.Get(isolate); - - v8::MaybeLocal maybeRecord = next->Call(ctx, iterator, 0, NULL); - if (maybeRecord.IsEmpty()) { - self->PropagateJSError(); - return SQLITE_ERROR; - } - - v8::Local record = maybeRecord.ToLocalChecked().As(); - bool done = record->Get(ctx, addon->cs.done.Get(isolate)).ToLocalChecked().As()->Value(); - if (!done) { - cursor->row.Reset(isolate, record->Get(ctx, addon->cs.value.Get(isolate)).ToLocalChecked().As()); - } - cursor->done = done; - cursor->rowid += 1; - - return SQLITE_OK; - } - - // If this method returns 1, SQLite will stop scanning the virtual table. - static int xEof(sqlite3_vtab_cursor* cursor) { - return Cursor::Upcast(cursor)->done; - } - - // This method extracts some column from the cursor's current row. - static int xColumn(sqlite3_vtab_cursor* _cursor, sqlite3_context* invocation, int column) { - Cursor* cursor = Cursor::Upcast(_cursor); - CustomTable* self = cursor->GetVTab()->parent; - TempDataConverter temp_data_converter(self); - v8::Isolate* isolate = self->isolate; - v8::HandleScope scope(isolate); - - v8::Local row = cursor->row.Get(isolate); - v8::MaybeLocal maybeColumnValue = row->Get(OnlyContext, column); - if (maybeColumnValue.IsEmpty()) { - temp_data_converter.PropagateJSError(NULL); - } else { - Data::ResultValueFromJS(isolate, invocation, maybeColumnValue.ToLocalChecked(), &temp_data_converter); - } - return temp_data_converter.status; - } - - // This method outputs the rowid of the cursor's current row. - static int xRowid(sqlite3_vtab_cursor* cursor, sqlite_int64* output) { - *output = Cursor::Upcast(cursor)->rowid; - return SQLITE_OK; - } - - // This method tells SQLite how to *plan* queries on our virtual table. - // It gets invoked (typically multiple times) during db.prepare(). - static int xBestIndex(sqlite3_vtab* vtab, sqlite3_index_info* output) { - int parameter_count = VTab::Upcast(vtab)->parameter_count; - int argument_count = 0; - std::vector> forwarded; - - for (int i = 0, len = output->nConstraint; i < len; ++i) { - auto item = output->aConstraint[i]; - - // The SQLITE_INDEX_CONSTRAINT_LIMIT and SQLITE_INDEX_CONSTRAINT_OFFSET - // operators have no left-hand operand, and so for those operators the - // corresponding item.iColumn is meaningless. - // We don't care those constraints. - if (item.op == SQLITE_INDEX_CONSTRAINT_LIMIT || item.op == SQLITE_INDEX_CONSTRAINT_OFFSET) { - continue; - } - // We only care about constraints on parameters, not regular columns. - if (item.iColumn >= 0 && item.iColumn < parameter_count) { - if (item.op != SQLITE_INDEX_CONSTRAINT_EQ) { - sqlite3_free(vtab->zErrMsg); - vtab->zErrMsg = sqlite3_mprintf( - "virtual table parameter \"%s\" can only be constrained by the '=' operator", - VTab::Upcast(vtab)->parameter_names.at(item.iColumn).c_str()); - return SQLITE_ERROR; - } - if (!item.usable) { - // Don't allow SQLite to make plans that ignore arguments. - // Otherwise, a user could pass arguments, but then they - // could appear undefined in the generator function. - return SQLITE_CONSTRAINT; - } - forwarded.emplace_back(item.iColumn, i); - } - } - - // Tell SQLite to forward arguments to xFilter. - std::sort(forwarded.begin(), forwarded.end()); - for (std::pair pair : forwarded) { - int bit = 1 << pair.first; - if (!(output->idxNum & bit)) { - output->idxNum |= bit; - output->aConstraintUsage[pair.second].argvIndex = ++argument_count; - output->aConstraintUsage[pair.second].omit = 1; - } - } - - // Use a very high estimated cost so SQLite is not tempted to invoke the - // generator function within a loop, if it can be avoided. - output->estimatedCost = output->estimatedRows = 1000000000 / (argument_count + 1); - return SQLITE_OK; - } - - void PropagateJSError() { - assert(db->GetState()->was_js_error == false); - db->GetState()->was_js_error = true; - } - - Addon* const addon; - v8::Isolate* const isolate; - Database* const db; - const std::string name; - const v8::Global factory; -}; diff --git a/src/util/data-converter.cpp b/src/util/data-converter.cpp new file mode 100644 index 000000000..9221993be --- /dev/null +++ b/src/util/data-converter.cpp @@ -0,0 +1,13 @@ +#include "data-converter.hpp" +#include "../better_sqlite3_impl.hpp" + +void DataConverter::ThrowDataConversionError(sqlite3_context *invocation, + bool isBigInt) { + if (isBigInt) { + ThrowRangeError( + (GetDataErrorPrefix() + " a bigint that was too big").c_str()); + } else { + ThrowTypeError((GetDataErrorPrefix() + " an invalid value").c_str()); + } + PropagateJSError(invocation); +} diff --git a/src/util/data-converter.hpp b/src/util/data-converter.hpp new file mode 100644 index 000000000..c8aaa2762 --- /dev/null +++ b/src/util/data-converter.hpp @@ -0,0 +1,15 @@ +#ifndef BETTER_SQLITE3_UTIL_DATA_CONVERTER_HPP +#define BETTER_SQLITE3_UTIL_DATA_CONVERTER_HPP + +#include "../better_sqlite3_deps.hpp" + +class DataConverter { +public: + void ThrowDataConversionError(sqlite3_context *invocation, bool isBigInt); + +protected: + virtual void PropagateJSError(sqlite3_context *invocation) = 0; + virtual std::string GetDataErrorPrefix() = 0; +}; + +#endif // BETTER_SQLITE3_UTIL_DATA_CONVERTER_HPP diff --git a/src/util/data-converter.lzz b/src/util/data-converter.lzz deleted file mode 100644 index 2ccbeeaf4..000000000 --- a/src/util/data-converter.lzz +++ /dev/null @@ -1,17 +0,0 @@ -class DataConverter { -public: - - void ThrowDataConversionError(sqlite3_context* invocation, bool isBigInt) { - if (isBigInt) { - ThrowRangeError((GetDataErrorPrefix() + " a bigint that was too big").c_str()); - } else { - ThrowTypeError((GetDataErrorPrefix() + " an invalid value").c_str()); - } - PropagateJSError(invocation); - } - -protected: - - virtual void PropagateJSError(sqlite3_context* invocation) = 0; - virtual std::string GetDataErrorPrefix() = 0; -}; diff --git a/src/util/data.cpp b/src/util/data.cpp new file mode 100644 index 000000000..9472ebb37 --- /dev/null +++ b/src/util/data.cpp @@ -0,0 +1,192 @@ +#include "data.hpp" +#include "../better_sqlite3_impl.hpp" + +// Constants are now defined in data.hpp +namespace Data { +v8::Local GetValueJS(v8::Isolate *isolate, sqlite3_stmt *handle, + int column, bool safe_ints) { + switch (sqlite3_column_type(handle, column)) { + case SQLITE_INTEGER: + if (safe_ints) { + return v8::BigInt ::New(isolate, sqlite3_column_int64(handle, column)); + } + case SQLITE_FLOAT: + return v8::Number ::New(isolate, sqlite3_column_double(handle, column)); + case SQLITE_TEXT: + return StringFromUtf8( + isolate, + reinterpret_cast(sqlite3_column_text(handle, column)), + sqlite3_column_bytes(handle, column)); + case SQLITE_BLOB: + return node ::Buffer ::Copy( + isolate, + static_cast(sqlite3_column_blob(handle, column)), + sqlite3_column_bytes(handle, column)) + .ToLocalChecked(); + default: + assert(sqlite3_column_type(handle, column) == SQLITE_NULL); + return v8::Null(isolate); + } + assert(false); + ; +} +} // namespace Data +namespace Data { +v8::Local GetValueJS(v8::Isolate *isolate, sqlite3_value *value, + bool safe_ints) { + switch (sqlite3_value_type(value)) { + case SQLITE_INTEGER: + if (safe_ints) { + return v8::BigInt ::New(isolate, sqlite3_value_int64(value)); + } + case SQLITE_FLOAT: + return v8::Number ::New(isolate, sqlite3_value_double(value)); + case SQLITE_TEXT: + return StringFromUtf8( + isolate, reinterpret_cast(sqlite3_value_text(value)), + sqlite3_value_bytes(value)); + case SQLITE_BLOB: + return node ::Buffer ::Copy( + isolate, static_cast(sqlite3_value_blob(value)), + sqlite3_value_bytes(value)) + .ToLocalChecked(); + default: + assert(sqlite3_value_type(value) == SQLITE_NULL); + return v8::Null(isolate); + } + assert(false); + ; +} +} // namespace Data +namespace Data { +v8::Local GetFlatRowJS(v8::Isolate *isolate, + v8::Local ctx, + sqlite3_stmt *handle, bool safe_ints) { + v8::Local row = v8::Object::New(isolate); + int column_count = sqlite3_column_count(handle); + for (int i = 0; i < column_count; ++i) { + row->Set(ctx, + InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1), + Data::GetValueJS(isolate, handle, i, safe_ints)) + .FromJust(); + } + return row; +} +} // namespace Data +namespace Data { +v8::Local GetExpandedRowJS(v8::Isolate *isolate, + v8::Local ctx, + sqlite3_stmt *handle, bool safe_ints) { + v8::Local row = v8::Object::New(isolate); + int column_count = sqlite3_column_count(handle); + for (int i = 0; i < column_count; ++i) { + const char *table_raw = sqlite3_column_table_name(handle, i); + v8::Local table = + InternalizedFromUtf8(isolate, table_raw == NULL ? "$" : table_raw, -1); + v8::Local column = + InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1); + v8::Local value = + Data::GetValueJS(isolate, handle, i, safe_ints); + if (row->HasOwnProperty(ctx, table).FromJust()) { + row->Get(ctx, table) + .ToLocalChecked() + .As() + ->Set(ctx, column, value) + .FromJust(); + } else { + v8::Local nested = v8::Object::New(isolate); + row->Set(ctx, table, nested).FromJust(); + nested->Set(ctx, column, value).FromJust(); + } + } + return row; +} +} // namespace Data +namespace Data { +v8::Local GetRawRowJS(v8::Isolate *isolate, + v8::Local ctx, + sqlite3_stmt *handle, bool safe_ints) { + v8::Local row = v8::Array::New(isolate); + int column_count = sqlite3_column_count(handle); + for (int i = 0; i < column_count; ++i) { + row->Set(ctx, i, Data::GetValueJS(isolate, handle, i, safe_ints)) + .FromJust(); + } + return row; +} +} // namespace Data +namespace Data { +v8::Local GetRowJS(v8::Isolate *isolate, v8::Local ctx, + sqlite3_stmt *handle, bool safe_ints, char mode) { + if (mode == FLAT) + return GetFlatRowJS(isolate, ctx, handle, safe_ints); + if (mode == PLUCK) + return GetValueJS(isolate, handle, 0, safe_ints); + if (mode == EXPAND) + return GetExpandedRowJS(isolate, ctx, handle, safe_ints); + if (mode == RAW) + return GetRawRowJS(isolate, ctx, handle, safe_ints); + assert(false); + return v8::Local(); +} +} // namespace Data +namespace Data { +void GetArgumentsJS(v8::Isolate *isolate, v8::Local *out, + sqlite3_value **values, int argument_count, + bool safe_ints) { + assert(argument_count > 0); + for (int i = 0; i < argument_count; ++i) { + out[i] = Data::GetValueJS(isolate, values[i], safe_ints); + } +} +} // namespace Data +namespace Data { +int BindValueFromJS(v8::Isolate *isolate, sqlite3_stmt *handle, int index, + v8::Local value) { + if (value->IsNumber()) { + return sqlite3_bind_double(handle, index, value.As()->Value()); + } else if (value->IsBigInt()) { + bool lossless; + int64_t v = value.As()->Int64Value(&lossless); + if (lossless) { + return sqlite3_bind_int64(handle, index, v); + } + } else if (value->IsString()) { + v8::String ::Utf8Value utf8(isolate, value.As()); + return sqlite3_bind_text(handle, index, *utf8, utf8.length(), + SQLITE_TRANSIENT); + } else if (node ::Buffer ::HasInstance(value)) { + const char *data = node ::Buffer ::Data(value); + return sqlite3_bind_blob(handle, index, data ? data : "", + node ::Buffer ::Length(value), SQLITE_TRANSIENT); + } else if (value->IsNull() || value->IsUndefined()) { + return sqlite3_bind_null(handle, index); + }; + return value->IsBigInt() ? SQLITE_TOOBIG : -1; +} +} // namespace Data +namespace Data { +void ResultValueFromJS(v8::Isolate *isolate, sqlite3_context *invocation, + v8::Local value, DataConverter *converter) { + if (value->IsNumber()) { + return sqlite3_result_double(invocation, value.As()->Value()); + } else if (value->IsBigInt()) { + bool lossless; + int64_t v = value.As()->Int64Value(&lossless); + if (lossless) { + return sqlite3_result_int64(invocation, v); + } + } else if (value->IsString()) { + v8::String ::Utf8Value utf8(isolate, value.As()); + return sqlite3_result_text(invocation, *utf8, utf8.length(), + SQLITE_TRANSIENT); + } else if (node ::Buffer ::HasInstance(value)) { + const char *data = node ::Buffer ::Data(value); + return sqlite3_result_blob(invocation, data ? data : "", + node ::Buffer ::Length(value), SQLITE_TRANSIENT); + } else if (value->IsNull() || value->IsUndefined()) { + return sqlite3_result_null(invocation); + }; + converter->ThrowDataConversionError(invocation, value->IsBigInt()); +} +} // namespace Data diff --git a/src/util/data.hpp b/src/util/data.hpp new file mode 100644 index 000000000..1db91d5bf --- /dev/null +++ b/src/util/data.hpp @@ -0,0 +1,54 @@ +#ifndef BETTER_SQLITE3_UTIL_DATA_HPP +#define BETTER_SQLITE3_UTIL_DATA_HPP + +#include "../better_sqlite3_deps.hpp" + +// Forward declarations +class DataConverter; + +namespace Data { +// Mode constants for row retrieval +static const char FLAT = 0; +static const char PLUCK = 1; +static const char EXPAND = 2; +static const char RAW = 3; +v8::Local GetValueJS(v8::Isolate *isolate, sqlite3_stmt *handle, + int column, bool safe_ints); +} // namespace Data +namespace Data { +v8::Local GetValueJS(v8::Isolate *isolate, sqlite3_value *value, + bool safe_ints); +} +namespace Data { +v8::Local GetFlatRowJS(v8::Isolate *isolate, + v8::Local ctx, + sqlite3_stmt *handle, bool safe_ints); +} +namespace Data { +v8::Local GetExpandedRowJS(v8::Isolate *isolate, + v8::Local ctx, + sqlite3_stmt *handle, bool safe_ints); +} +namespace Data { +v8::Local GetRawRowJS(v8::Isolate *isolate, + v8::Local ctx, + sqlite3_stmt *handle, bool safe_ints); +} +namespace Data { +v8::Local GetRowJS(v8::Isolate *isolate, v8::Local ctx, + sqlite3_stmt *handle, bool safe_ints, char mode); +} +namespace Data { +void GetArgumentsJS(v8::Isolate *isolate, v8::Local *out, + sqlite3_value **values, int argument_count, bool safe_ints); +} +namespace Data { +int BindValueFromJS(v8::Isolate *isolate, sqlite3_stmt *handle, int index, + v8::Local value); +} +namespace Data { +void ResultValueFromJS(v8::Isolate *isolate, sqlite3_context *invocation, + v8::Local value, DataConverter *converter); +} + +#endif // BETTER_SQLITE3_UTIL_DATA_HPP diff --git a/src/util/data.lzz b/src/util/data.lzz deleted file mode 100644 index 5b3ce5daf..000000000 --- a/src/util/data.lzz +++ /dev/null @@ -1,145 +0,0 @@ -#define JS_VALUE_TO_SQLITE(to, value, isolate, ...) \ - if (value->IsNumber()) { \ - return sqlite3_##to##_double( \ - __VA_ARGS__, \ - value.As()->Value() \ - ); \ - } else if (value->IsBigInt()) { \ - bool lossless; \ - int64_t v = value.As()->Int64Value(&lossless); \ - if (lossless) { \ - return sqlite3_##to##_int64(__VA_ARGS__, v); \ - } \ - } else if (value->IsString()) { \ - v8::String::Utf8Value utf8(isolate, value.As()); \ - return sqlite3_##to##_text( \ - __VA_ARGS__, \ - *utf8, \ - utf8.length(), \ - SQLITE_TRANSIENT \ - ); \ - } else if (node::Buffer::HasInstance(value)) { \ - const char* data = node::Buffer::Data(value); \ - return sqlite3_##to##_blob( \ - __VA_ARGS__, \ - data ? data : "", \ - node::Buffer::Length(value), \ - SQLITE_TRANSIENT \ - ); \ - } else if (value->IsNull() || value->IsUndefined()) { \ - return sqlite3_##to##_null(__VA_ARGS__); \ - } - -#define SQLITE_VALUE_TO_JS(from, isolate, safe_ints, ...) \ - switch (sqlite3_##from##_type(__VA_ARGS__)) { \ - case SQLITE_INTEGER: \ - if (safe_ints) { \ - return v8::BigInt::New( \ - isolate, \ - sqlite3_##from##_int64(__VA_ARGS__) \ - ); \ - } \ - case SQLITE_FLOAT: \ - return v8::Number::New( \ - isolate, \ - sqlite3_##from##_double(__VA_ARGS__) \ - ); \ - case SQLITE_TEXT: \ - return StringFromUtf8( \ - isolate, \ - reinterpret_cast(sqlite3_##from##_text(__VA_ARGS__)), \ - sqlite3_##from##_bytes(__VA_ARGS__) \ - ); \ - case SQLITE_BLOB: \ - return node::Buffer::Copy( \ - isolate, \ - static_cast(sqlite3_##from##_blob(__VA_ARGS__)), \ - sqlite3_##from##_bytes(__VA_ARGS__) \ - ).ToLocalChecked(); \ - default: \ - assert(sqlite3_##from##_type(__VA_ARGS__) == SQLITE_NULL); \ - return v8::Null(isolate); \ - } \ - assert(false); - -namespace Data { - - static const char FLAT = 0; - static const char PLUCK = 1; - static const char EXPAND = 2; - static const char RAW = 3; - - v8::Local GetValueJS(v8::Isolate* isolate, sqlite3_stmt* handle, int column, bool safe_ints) { - SQLITE_VALUE_TO_JS(column, isolate, safe_ints, handle, column); - } - - v8::Local GetValueJS(v8::Isolate* isolate, sqlite3_value* value, bool safe_ints) { - SQLITE_VALUE_TO_JS(value, isolate, safe_ints, value); - } - - v8::Local GetFlatRowJS(v8::Isolate* isolate, v8::Local ctx, sqlite3_stmt* handle, bool safe_ints) { - v8::Local row = v8::Object::New(isolate); - int column_count = sqlite3_column_count(handle); - for (int i = 0; i < column_count; ++i) { - row->Set(ctx, - InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1), - Data::GetValueJS(isolate, handle, i, safe_ints)).FromJust(); - } - return row; - } - - v8::Local GetExpandedRowJS(v8::Isolate* isolate, v8::Local ctx, sqlite3_stmt* handle, bool safe_ints) { - v8::Local row = v8::Object::New(isolate); - int column_count = sqlite3_column_count(handle); - for (int i = 0; i < column_count; ++i) { - const char* table_raw = sqlite3_column_table_name(handle, i); - v8::Local table = InternalizedFromUtf8(isolate, table_raw == NULL ? "$" : table_raw, -1); - v8::Local column = InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1); - v8::Local value = Data::GetValueJS(isolate, handle, i, safe_ints); - if (row->HasOwnProperty(ctx, table).FromJust()) { - row->Get(ctx, table).ToLocalChecked().As()->Set(ctx, column, value).FromJust(); - } else { - v8::Local nested = v8::Object::New(isolate); - row->Set(ctx, table, nested).FromJust(); - nested->Set(ctx, column, value).FromJust(); - } - } - return row; - } - - v8::Local GetRawRowJS(v8::Isolate* isolate, v8::Local ctx, sqlite3_stmt* handle, bool safe_ints) { - v8::Local row = v8::Array::New(isolate); - int column_count = sqlite3_column_count(handle); - for (int i = 0; i < column_count; ++i) { - row->Set(ctx, i, Data::GetValueJS(isolate, handle, i, safe_ints)).FromJust(); - } - return row; - } - - v8::Local GetRowJS(v8::Isolate* isolate, v8::Local ctx, sqlite3_stmt* handle, bool safe_ints, char mode) { - if (mode == FLAT) return GetFlatRowJS(isolate, ctx, handle, safe_ints); - if (mode == PLUCK) return GetValueJS(isolate, handle, 0, safe_ints); - if (mode == EXPAND) return GetExpandedRowJS(isolate, ctx, handle, safe_ints); - if (mode == RAW) return GetRawRowJS(isolate, ctx, handle, safe_ints); - assert(false); - return v8::Local(); - } - - void GetArgumentsJS(v8::Isolate* isolate, v8::Local* out, sqlite3_value** values, int argument_count, bool safe_ints) { - assert(argument_count > 0); - for (int i = 0; i < argument_count; ++i) { - out[i] = Data::GetValueJS(isolate, values[i], safe_ints); - } - } - - int BindValueFromJS(v8::Isolate* isolate, sqlite3_stmt* handle, int index, v8::Local value) { - JS_VALUE_TO_SQLITE(bind, value, isolate, handle, index); - return value->IsBigInt() ? SQLITE_TOOBIG : -1; - } - - void ResultValueFromJS(v8::Isolate* isolate, sqlite3_context* invocation, v8::Local value, DataConverter* converter) { - JS_VALUE_TO_SQLITE(result, value, isolate, invocation); - converter->ThrowDataConversionError(invocation, value->IsBigInt()); - } - -} diff --git a/src/util/macros.cpp b/src/util/macros.cpp new file mode 100644 index 000000000..f5d099106 --- /dev/null +++ b/src/util/macros.cpp @@ -0,0 +1,61 @@ +#include "macros.hpp" +#include "../better_sqlite3_impl.hpp" + +void SetPrototypeGetter(v8::Isolate *isolate, v8::Local data, + v8::Local recv, const char *name, + v8::AccessorNameGetterCallback func) { + v8::HandleScope scope(isolate); + +#if defined NODE_MODULE_VERSION && NODE_MODULE_VERSION < 121 + recv->InstanceTemplate()->SetAccessor( + InternalizedFromLatin1(isolate, name), func, 0, data, + v8::AccessControl::DEFAULT, v8::PropertyAttribute::None); +#else + recv->InstanceTemplate()->SetNativeDataProperty( + InternalizedFromLatin1(isolate, name), func, 0, data); +#endif +} + +void ThrowError(char const *message) { + v8::Isolate *isolate = v8::Isolate::GetCurrent(); + isolate->ThrowException( + v8::Exception::Error(StringFromUtf8(isolate, message, -1))); +} +void ThrowTypeError(char const *message) { + v8::Isolate *isolate = v8::Isolate::GetCurrent(); + isolate->ThrowException( + v8::Exception::TypeError(StringFromUtf8(isolate, message, -1))); +} +void ThrowRangeError(char const *message) { + v8::Isolate *isolate = v8::Isolate::GetCurrent(); + isolate->ThrowException( + v8::Exception::RangeError(StringFromUtf8(isolate, message, -1))); +} +v8::Local +NewConstructorTemplate(v8::Isolate *isolate, v8::Local data, + v8::FunctionCallback func, char const *name) { + v8::Local t = + v8::FunctionTemplate::New(isolate, func, data); + t->InstanceTemplate()->SetInternalFieldCount(1); + t->SetClassName(InternalizedFromLatin1(isolate, name)); + return t; +} +void SetPrototypeMethod(v8::Isolate *isolate, v8::Local data, + v8::Local recv, char const *name, + v8::FunctionCallback func) { + v8::HandleScope scope(isolate); + recv->PrototypeTemplate()->Set( + InternalizedFromLatin1(isolate, name), + v8::FunctionTemplate::New(isolate, func, data, + v8::Signature::New(isolate, recv))); +} +void SetPrototypeSymbolMethod(v8::Isolate *isolate, + v8::Local data, + v8::Local recv, + v8::Local symbol, + v8::FunctionCallback func) { + v8::HandleScope scope(isolate); + recv->PrototypeTemplate()->Set( + symbol, v8::FunctionTemplate::New(isolate, func, data, + v8::Signature::New(isolate, recv))); +} diff --git a/src/util/macros.hpp b/src/util/macros.hpp new file mode 100644 index 000000000..cc14222c7 --- /dev/null +++ b/src/util/macros.hpp @@ -0,0 +1,101 @@ +#ifndef BETTER_SQLITE3_UTIL_MACROS_HPP +#define BETTER_SQLITE3_UTIL_MACROS_HPP + +#include "../better_sqlite3_deps.hpp" + +// Buffer creation macro for V8 Sandbox compatibility +#if defined(V8_ENABLE_SANDBOX) +// When V8 Sandbox is enabled (in newer Electron versions), we need to use +// Buffer::Copy instead of Buffer::New to ensure the ArrayBuffer backing store +// is allocated inside the sandbox +static inline v8::MaybeLocal +BufferSandboxNew(v8::Isolate *isolate, char *data, size_t length, + void (*finalizeCallback)(char *, void *), void *finalizeHint) { + v8::MaybeLocal buffer = node::Buffer::Copy(isolate, data, length); + finalizeCallback(data, finalizeHint); + return buffer; +} +#define SAFE_NEW_BUFFER(env, data, length, finalizeCallback, finalizeHint) \ + BufferSandboxNew(env, data, length, finalizeCallback, finalizeHint) +#else +// When V8 Sandbox is not enabled, we can use the more efficient Buffer::New +#define SAFE_NEW_BUFFER(env, data, length, finalizeCallback, finalizeHint) \ + node::Buffer::New(env, data, length, finalizeCallback, finalizeHint) +#endif + +void SetPrototypeGetter(v8::Isolate *isolate, v8::Local data, + v8::Local recv, const char *name, + v8::AccessorNameGetterCallback func); +v8::Local StringFromUtf8(v8::Isolate *isolate, char const *data, + int length); +v8::Local InternalizedFromUtf8(v8::Isolate *isolate, + char const *data, int length); +v8::Local InternalizedFromUtf8OrNull(v8::Isolate *isolate, + char const *data, int length); +v8::Local InternalizedFromLatin1(v8::Isolate *isolate, + char const *str); +void SetFrozen(v8::Isolate *isolate, v8::Local ctx, + v8::Local obj, v8::Global &key, + v8::Local value); +void ThrowError(char const *message); +void ThrowTypeError(char const *message); +void ThrowRangeError(char const *message); +bool IS_SKIPPED(char c); +template T *ALLOC_ARRAY(size_t count); +template void FREE_ARRAY(T *array_pointer); +v8::Local +NewConstructorTemplate(v8::Isolate *isolate, v8::Local data, + v8::FunctionCallback func, char const *name); +void SetPrototypeMethod(v8::Isolate *isolate, v8::Local data, + v8::Local recv, char const *name, + v8::FunctionCallback func); +void SetPrototypeSymbolMethod(v8::Isolate *isolate, + v8::Local data, + v8::Local recv, + v8::Local symbol, + v8::FunctionCallback func); +LZZ_INLINE v8::Local StringFromUtf8(v8::Isolate *isolate, + char const *data, int length) { + return v8::String::NewFromUtf8(isolate, data, v8::NewStringType::kNormal, + length) + .ToLocalChecked(); +} +LZZ_INLINE v8::Local +InternalizedFromUtf8(v8::Isolate *isolate, char const *data, int length) { + return v8::String::NewFromUtf8(isolate, data, + v8::NewStringType::kInternalized, length) + .ToLocalChecked(); +} +LZZ_INLINE v8::Local +InternalizedFromUtf8OrNull(v8::Isolate *isolate, char const *data, int length) { + if (data == NULL) + return v8::Null(isolate); + return InternalizedFromUtf8(isolate, data, length); +} +LZZ_INLINE v8::Local InternalizedFromLatin1(v8::Isolate *isolate, + char const *str) { + return v8::String::NewFromOneByte(isolate, + reinterpret_cast(str), + v8::NewStringType::kInternalized) + .ToLocalChecked(); +} +LZZ_INLINE void SetFrozen(v8::Isolate *isolate, v8::Local ctx, + v8::Local obj, + v8::Global &key, + v8::Local value) { + obj->DefineOwnProperty( + ctx, key.Get(isolate), value, + static_cast(v8::DontDelete | v8::ReadOnly)) + .FromJust(); +} +LZZ_INLINE bool IS_SKIPPED(char c) { + return c == ' ' || c == ';' || (c >= '\t' && c <= '\r'); +} +template LZZ_INLINE T *ALLOC_ARRAY(size_t count) { + return static_cast(::operator new[](count * sizeof(T))); +} +template LZZ_INLINE void FREE_ARRAY(T *array_pointer) { + ::operator delete[](array_pointer); +} + +#endif // BETTER_SQLITE3_UTIL_MACROS_HPP diff --git a/src/util/macros.lzz b/src/util/macros.lzz deleted file mode 100644 index 232d70cdd..000000000 --- a/src/util/macros.lzz +++ /dev/null @@ -1,196 +0,0 @@ -#define NODE_ARGUMENTS const v8::FunctionCallbackInfo& -#define NODE_ARGUMENTS_POINTER const v8::FunctionCallbackInfo* -#define NODE_METHOD(name) static void name(NODE_ARGUMENTS info) -#if defined NODE_MODULE_VERSION && NODE_MODULE_VERSION < 121 -#define NODE_GETTER(name) static void name(v8::Local _, const v8::PropertyCallbackInfo& info) -#else -#define NODE_GETTER(name) static void name(v8::Local _, const v8::PropertyCallbackInfo& info) -#endif -#define INIT(name) static v8::Local name(v8::Isolate* isolate, v8::Local data) - -#define EasyIsolate v8::Isolate* isolate = v8::Isolate::GetCurrent() -#define OnlyIsolate info.GetIsolate() -#define OnlyContext isolate->GetCurrentContext() -#define OnlyAddon static_cast(info.Data().As()->Value()) -#define UseIsolate v8::Isolate* isolate = OnlyIsolate -#define UseContext v8::Local ctx = OnlyContext -#define UseAddon Addon* addon = OnlyAddon -#define Unwrap node::ObjectWrap::Unwrap - -inline v8::Local StringFromUtf8(v8::Isolate* isolate, const char* data, int length) { - return v8::String::NewFromUtf8(isolate, data, v8::NewStringType::kNormal, length).ToLocalChecked(); -} -inline v8::Local InternalizedFromUtf8(v8::Isolate* isolate, const char* data, int length) { - return v8::String::NewFromUtf8(isolate, data, v8::NewStringType::kInternalized, length).ToLocalChecked(); -} -inline v8::Local InternalizedFromUtf8OrNull(v8::Isolate* isolate, const char* data, int length) { - if (data == NULL) return v8::Null(isolate); - return InternalizedFromUtf8(isolate, data, length); -} -inline v8::Local InternalizedFromLatin1(v8::Isolate* isolate, const char* str) { - return v8::String::NewFromOneByte(isolate, reinterpret_cast(str), v8::NewStringType::kInternalized).ToLocalChecked(); -} - -inline void SetFrozen(v8::Isolate* isolate, v8::Local ctx, v8::Local obj, v8::Global& key, v8::Local value) { - obj->DefineOwnProperty(ctx, key.Get(isolate), value, static_cast(v8::DontDelete | v8::ReadOnly)).FromJust(); -} - -void ThrowError(const char* message) { EasyIsolate; isolate->ThrowException(v8::Exception::Error(StringFromUtf8(isolate, message, -1))); } -void ThrowTypeError(const char* message) { EasyIsolate; isolate->ThrowException(v8::Exception::TypeError(StringFromUtf8(isolate, message, -1))); } -void ThrowRangeError(const char* message) { EasyIsolate; isolate->ThrowException(v8::Exception::RangeError(StringFromUtf8(isolate, message, -1))); } - -#define REQUIRE_ARGUMENT_ANY(at, var) \ - if (info.Length() <= (at())) \ - return ThrowTypeError("Expected a "#at" argument"); \ - var = info[at()] - -#define _REQUIRE_ARGUMENT(at, var, Type, message, ...) \ - if (info.Length() <= (at()) || !info[at()]->Is##Type()) \ - return ThrowTypeError("Expected "#at" argument to be "#message); \ - var = (info[at()].As())__VA_ARGS__ - -#define REQUIRE_ARGUMENT_INT32(at, var) \ - _REQUIRE_ARGUMENT(at, var, Int32, a 32-bit signed integer, ->Value()) -#define REQUIRE_ARGUMENT_BOOLEAN(at, var) \ - _REQUIRE_ARGUMENT(at, var, Boolean, a boolean, ->Value()) -#define REQUIRE_ARGUMENT_STRING(at, var) \ - _REQUIRE_ARGUMENT(at, var, String, a string) -#define REQUIRE_ARGUMENT_OBJECT(at, var) \ - _REQUIRE_ARGUMENT(at, var, Object, an object) -#define REQUIRE_ARGUMENT_FUNCTION(at, var) \ - _REQUIRE_ARGUMENT(at, var, Function, a function) - -#define REQUIRE_DATABASE_OPEN(db) \ - if (!db->open) \ - return ThrowTypeError("The database connection is not open") -#define REQUIRE_DATABASE_NOT_BUSY(db) \ - if (db->busy) \ - return ThrowTypeError("This database connection is busy executing a query") -#define REQUIRE_DATABASE_NO_ITERATORS(db) \ - if (db->iterators) \ - return ThrowTypeError("This database connection is busy executing a query") -#define REQUIRE_DATABASE_NO_ITERATORS_UNLESS_UNSAFE(db) \ - if (!db->unsafe_mode) { \ - REQUIRE_DATABASE_NO_ITERATORS(db); \ - } ((void)0) -#define REQUIRE_STATEMENT_NOT_LOCKED(stmt) \ - if (stmt->locked) \ - return ThrowTypeError("This statement is busy executing a query") - -#define first() 0 -#define second() 1 -#define third() 2 -#define fourth() 3 -#define fifth() 4 -#define sixth() 5 -#define seventh() 6 -#define eighth() 7 -#define ninth() 8 -#define tenth() 9 - -// Determines whether to skip the given character at the start of an SQL string. -inline bool IS_SKIPPED(char c) { - return c == ' ' || c == ';' || (c >= '\t' && c <= '\r'); -} - -// Allocates an empty array, without calling constructors/initializers. -template inline T* ALLOC_ARRAY(size_t count) { - return static_cast(::operator new[](count * sizeof(T))); -} - -// Deallocates an array, without calling destructors. -template inline void FREE_ARRAY(T* array_pointer) { - ::operator delete[](array_pointer); -} - -v8::Local NewConstructorTemplate( - v8::Isolate* isolate, - v8::Local data, - v8::FunctionCallback func, - const char* name -) { - v8::Local t = v8::FunctionTemplate::New(isolate, func, data); - t->InstanceTemplate()->SetInternalFieldCount(1); - t->SetClassName(InternalizedFromLatin1(isolate, name)); - return t; -} -void SetPrototypeMethod( - v8::Isolate* isolate, - v8::Local data, - v8::Local recv, - const char* name, - v8::FunctionCallback func -) { - v8::HandleScope scope(isolate); - recv->PrototypeTemplate()->Set( - InternalizedFromLatin1(isolate, name), - v8::FunctionTemplate::New(isolate, func, data, v8::Signature::New(isolate, recv)) - ); -} -void SetPrototypeSymbolMethod( - v8::Isolate* isolate, - v8::Local data, - v8::Local recv, - v8::Local symbol, - v8::FunctionCallback func -) { - v8::HandleScope scope(isolate); - recv->PrototypeTemplate()->Set( - symbol, - v8::FunctionTemplate::New(isolate, func, data, v8::Signature::New(isolate, recv)) - ); -} - -#hdr -void SetPrototypeGetter( - v8::Isolate* isolate, - v8::Local data, - v8::Local recv, - const char* name, - v8::AccessorNameGetterCallback func -); -#end -#src -void SetPrototypeGetter( - v8::Isolate* isolate, - v8::Local data, - v8::Local recv, - const char* name, - v8::AccessorNameGetterCallback func -) { - v8::HandleScope scope(isolate); - - #if defined NODE_MODULE_VERSION && NODE_MODULE_VERSION < 121 - recv->InstanceTemplate()->SetAccessor( - InternalizedFromLatin1(isolate, name), - func, - 0, - data, - v8::AccessControl::DEFAULT, - v8::PropertyAttribute::None - ); - #else - recv->InstanceTemplate()->SetNativeDataProperty( - InternalizedFromLatin1(isolate, name), - func, - 0, - data - ); - #endif -} -#end -#src -#if defined(V8_ENABLE_SANDBOX) -// When V8 Sandbox is enabled (in newer Electron versions), we need to use Buffer::Copy -// instead of Buffer::New to ensure the ArrayBuffer backing store is allocated inside the sandbox -static inline v8::MaybeLocal BufferSandboxNew(v8::Isolate* isolate, char* data, size_t length, void (*finalizeCallback)(char*, void*), void* finalizeHint) { - v8::MaybeLocal buffer = node::Buffer::Copy(isolate, data, length); - finalizeCallback(data, finalizeHint); - return buffer; -} -#define SAFE_NEW_BUFFER(env, data, length, finalizeCallback, finalizeHint) BufferSandboxNew(env, data, length, finalizeCallback, finalizeHint) -#else -// When V8 Sandbox is not enabled, we can use the more efficient Buffer::New -#define SAFE_NEW_BUFFER(env, data, length, finalizeCallback, finalizeHint) node::Buffer::New(env, data, length, finalizeCallback, finalizeHint) -#endif -#end diff --git a/src/util/query-macros.lzz b/src/util/query-macros.lzz deleted file mode 100644 index 2f8087573..000000000 --- a/src/util/query-macros.lzz +++ /dev/null @@ -1,71 +0,0 @@ -#define STATEMENT_BIND(handle) \ - Binder binder(handle); \ - if (!binder.Bind(info, info.Length(), stmt)) { \ - sqlite3_clear_bindings(handle); \ - return; \ - } ((void)0) - -#define STATEMENT_THROW_LOGIC() \ - db->ThrowDatabaseError(); \ - if (!bound) { sqlite3_clear_bindings(handle); } \ - return - -#define STATEMENT_RETURN_LOGIC(return_value) \ - info.GetReturnValue().Set(return_value); \ - if (!bound) { sqlite3_clear_bindings(handle); } \ - return - -#define STATEMENT_START_LOGIC(RETURNS_DATA_CHECK, MUTATE_CHECK) \ - Statement* stmt = Unwrap(info.This()); \ - RETURNS_DATA_CHECK(); \ - sqlite3_stmt* handle = stmt->handle; \ - Database* db = stmt->db; \ - REQUIRE_DATABASE_OPEN(db->GetState()); \ - REQUIRE_DATABASE_NOT_BUSY(db->GetState()); \ - MUTATE_CHECK(); \ - const bool bound = stmt->bound; \ - if (!bound) { \ - STATEMENT_BIND(handle); \ - } else if (info.Length() > 0) { \ - return ThrowTypeError("This statement already has bound parameters"); \ - } ((void)0) - - -#define STATEMENT_THROW() db->GetState()->busy = false; STATEMENT_THROW_LOGIC() -#define STATEMENT_RETURN(x) db->GetState()->busy = false; STATEMENT_RETURN_LOGIC(x) -#define STATEMENT_START(x, y) \ - STATEMENT_START_LOGIC(x, y); \ - db->GetState()->busy = true; \ - UseIsolate; \ - if (db->Log(isolate, handle)) { \ - STATEMENT_THROW(); \ - } ((void)0) - - -#define DOES_NOT_MUTATE() REQUIRE_STATEMENT_NOT_LOCKED(stmt) -#define DOES_MUTATE() \ - REQUIRE_STATEMENT_NOT_LOCKED(stmt); \ - REQUIRE_DATABASE_NO_ITERATORS_UNLESS_UNSAFE(db->GetState()) -#define DOES_ADD_ITERATOR() \ - DOES_NOT_MUTATE(); \ - if (db->GetState()->iterators == USHRT_MAX) \ - return ThrowRangeError("Too many active database iterators") -#define REQUIRE_STATEMENT_RETURNS_DATA() \ - if (!stmt->returns_data) \ - return ThrowTypeError("This statement does not return data. Use run() instead") -#define ALLOW_ANY_STATEMENT() \ - ((void)0) - - -#define _FUNCTION_START(type) \ - type* self = static_cast(sqlite3_user_data(invocation)); \ - v8::Isolate* isolate = self->isolate; \ - v8::HandleScope scope(isolate) - -#define FUNCTION_START() \ - _FUNCTION_START(CustomFunction) - -#define AGGREGATE_START() \ - _FUNCTION_START(CustomAggregate); \ - Accumulator* acc = self->GetAccumulator(invocation); \ - if (acc->value.IsEmpty()) return