Forked SQLite deno WASM lib
This commit is contained in:
parent
e5a192b358
commit
e225c926f5
1
plugos/forked/deno-sqlite/.gitattributes
vendored
Normal file
1
plugos/forked/deno-sqlite/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
build/lib/** linguist-vendored
|
23
plugos/forked/deno-sqlite/.gitignore
vendored
Normal file
23
plugos/forked/deno-sqlite/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
# System files
|
||||
.DS_Store
|
||||
|
||||
# Debug builds
|
||||
debug.js
|
||||
|
||||
# Misc
|
||||
hack.*
|
||||
*.br
|
||||
*.db
|
||||
*.db-journal
|
||||
*.db-shm
|
||||
*.sqlite
|
||||
*.sqlite-journal
|
||||
*.sqlite-shm
|
||||
*.gz
|
||||
build/sqlite-src
|
||||
build/wasi-sdk
|
||||
deno
|
||||
|
||||
# NPM garbage
|
||||
node_modules
|
||||
package-lock.json
|
88
plugos/forked/deno-sqlite/CONTRIBUTING.md
Normal file
88
plugos/forked/deno-sqlite/CONTRIBUTING.md
Normal file
@ -0,0 +1,88 @@
|
||||
# Contribute to SQLite for Deno
|
||||
|
||||
> Note: this is a draft
|
||||
|
||||
Thank you for considering to contribute to the SQLite for Deno module! Below are
|
||||
a few guidelines on how to contribute.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To work on the JavaScript/ TypeScript wrapper module, all you need is a
|
||||
[deno](https://deno.land) runtime.
|
||||
|
||||
To change the compiled SQLite WASM binary, you will require to download the
|
||||
[WASI SDK][wasi-sdk]. This process should function fully automatically for most
|
||||
users.
|
||||
|
||||
**To install build dependencies** go to the `build` folder (`cd build`), then
|
||||
run `make setup`.
|
||||
|
||||
**To compile the binary** run `make release` (or `make debug` for a debug
|
||||
build). If you changed any build flags of SQLite, also run `make amalgamation`,
|
||||
before building.
|
||||
|
||||
If you are interested in more details regarding the compilation setup, also see
|
||||
[this blog post][compile-wasm-blog].
|
||||
|
||||
## Code Style, Review, and Dependencies
|
||||
|
||||
This project uses the `deno fmt` code style.
|
||||
|
||||
This project uses no external dependencies (with the exception of a copy of the
|
||||
SQLite C library).
|
||||
|
||||
For testing purposes, Deno standard library modules may be used.
|
||||
|
||||
## Documentation
|
||||
|
||||
Any user-facing interfaces should be documented. To document such interfaces,
|
||||
include a **JSDoc comment**, which should be formatted as follows:
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* A short but complete description, formatted
|
||||
* as markdown.
|
||||
*/
|
||||
functionName(arg1, arg2) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Comments with this format will be automatically parsed by `deno doc`.
|
||||
|
||||
These comments should not include examples unless they are essential to
|
||||
illustrating an important point.
|
||||
|
||||
## Tests and Benchmarks
|
||||
|
||||
Any important functionality should be tested. Tests are in the `test.ts` file.
|
||||
Changes will not be merged unless all tests pass.
|
||||
|
||||
Benchmarks are in the `bench.ts` file.
|
||||
|
||||
## Technical Direction
|
||||
|
||||
The goal of this module is to provide a **simple and predictable** interface to
|
||||
SQLite. The interface should feel like a JavaScript library, but also
|
||||
immediately make sense to someone who knows the SQLite C/C++ interface. Features
|
||||
and interfaces should generally be orthogonal.
|
||||
|
||||
This is a low-level library, which provides access to running SQL queries and
|
||||
retrieving the results of these queries. This library will only wrap SQLite C
|
||||
API functions, but never try to provide a higher level interface to the database
|
||||
than plain SQL. It is meant to serve as a building block for constructing higher
|
||||
level interfaces, or for people who need an easy way to execute SQL queries on
|
||||
their SQLite database.
|
||||
|
||||
The library should be easy to use and behave as any regular JavaScript library
|
||||
would in Deno. This means, it should only need the required permissions (e.g. if
|
||||
only in-memory databases are used, no permissions should be necessary. If a
|
||||
database is opened in read-only mode, `--allow-read` should be sufficient).
|
||||
|
||||
## License
|
||||
|
||||
By making contributions, you agree that anything you submit will be distributed
|
||||
under the projects license (see `LICENSE`).
|
||||
|
||||
[wasi-sdk]: https://github.com/CraneStation/wasi-sdk/releases
|
||||
[compile-wasm-blog]: https://tilman.xyz/blog/2019/12/building-webassembly-for-deno/
|
19
plugos/forked/deno-sqlite/LICENSE
Normal file
19
plugos/forked/deno-sqlite/LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2019 - 2022 Tilman Roeder and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
107
plugos/forked/deno-sqlite/README.md
Normal file
107
plugos/forked/deno-sqlite/README.md
Normal file
@ -0,0 +1,107 @@
|
||||
# Deno SQLite Module
|
||||
|
||||
[![test status](https://github.com/dyedgreen/deno-sqlite/workflows/tests/badge.svg?branch=master)](https://github.com/dyedgreen/deno-sqlite/actions)
|
||||
[![deno doc](https://doc.deno.land/badge.svg)](https://deno.land/x/sqlite/mod.ts)
|
||||
|
||||
This is an SQLite module for JavaScript and TypeScript. The wrapper is targeted
|
||||
at [Deno](https://deno.land) and uses a version of SQLite3 compiled to
|
||||
WebAssembly (WASM). This module focuses on correctness, ease of use and
|
||||
performance.
|
||||
|
||||
This module guarantees API compatibility according to
|
||||
[semantic versioning](https://semver.org). Please report any issues you
|
||||
encounter. Note that the `master` branch might contain new or breaking features.
|
||||
The versioning guarantee applies only to
|
||||
[tagged releases](https://github.com/dyedgreen/deno-sqlite/releases).
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation is available [Deno Docs](https://deno.land/x/sqlite). There is
|
||||
also a list of examples in the [`examples`](./examples) folder.
|
||||
|
||||
## Example
|
||||
|
||||
```javascript
|
||||
import { DB } from "https://deno.land/x/sqlite/mod.ts";
|
||||
|
||||
// Open a database
|
||||
const db = new DB("test.db");
|
||||
db.execute(`
|
||||
CREATE TABLE IF NOT EXISTS people (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT
|
||||
)
|
||||
`);
|
||||
|
||||
const names = ["Peter Parker", "Clark Kent", "Bruce Wayne"];
|
||||
|
||||
// Run a simple query
|
||||
for (const name of names) {
|
||||
db.query("INSERT INTO people (name) VALUES (?)", [name]);
|
||||
}
|
||||
|
||||
// Print out data in table
|
||||
for (const [name] of db.query("SELECT name FROM people")) {
|
||||
console.log(name);
|
||||
}
|
||||
|
||||
// Close connection
|
||||
db.close();
|
||||
```
|
||||
|
||||
## Comparison to Plugin based Modules
|
||||
|
||||
### TL;DR
|
||||
|
||||
If you want something that just works (and is fast), use this library.
|
||||
|
||||
Depending on your specific needs, there is also
|
||||
[sqlite3](https://github.com/denodrivers/sqlite3), however using this module
|
||||
requires the `--allow-ffi` and `--unstable` flags, which means the database
|
||||
connection may bypass e.g. file access permissions.
|
||||
|
||||
### Advantages
|
||||
|
||||
- Security: benefit from Denos security settings, without the need to trust a
|
||||
third party
|
||||
- Portability: runs everywhere Deno runs and can even run in the browser
|
||||
- Ease of Use: takes full advantage of Denos module cache and does not require
|
||||
any network access after initial download
|
||||
- Speed: thanks to WASM, the database performance is comparable to native
|
||||
bindings in most situations and the API is carefully designed to provide
|
||||
optimal performance
|
||||
|
||||
### Disadvantages
|
||||
|
||||
- Weaker Persistence Guarantees: due to limitations in Denos file system APIs,
|
||||
SQLite can't acquire file locks or memory map files (e.g. this module does not
|
||||
support WAL mode)
|
||||
|
||||
## Browser Version (Experimental)
|
||||
|
||||
There is **experimental** support for using `deno-sqlite` in the browser. You
|
||||
can generate a browser compatible module by running:
|
||||
|
||||
```bash
|
||||
deno bundle --import-map browser/import_map.json browser/mod.ts [output_bundle_path]
|
||||
```
|
||||
|
||||
The modules documentation can be seen by running
|
||||
|
||||
```bash
|
||||
deno doc browser/mod.ts
|
||||
```
|
||||
|
||||
Databases created in the browser are persisted using
|
||||
[indexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API).
|
||||
|
||||
## Users
|
||||
|
||||
- [cotton](https://github.com/rahmanfadhil/cotton)
|
||||
- [deno-nessie](https://github.com/halvardssm/deno-nessie)
|
||||
- [denodb](https://github.com/eveningkid/denodb)
|
||||
- [denolib/typeorm](https://github.com/denolib/typeorm)
|
||||
- [small-orm-sqlite](https://github.com/enimatek-nl/small-orm-sqlite)
|
||||
|
||||
_(listed in alphabetical order, please submit a PR if you are using this library
|
||||
and are not included)_
|
88
plugos/forked/deno-sqlite/bench.ts
Normal file
88
plugos/forked/deno-sqlite/bench.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import {
|
||||
bench,
|
||||
runBenchmarks,
|
||||
} from "https://deno.land/std@0.135.0/testing/bench.ts";
|
||||
import { DB } from "./mod.ts";
|
||||
|
||||
if (Deno.args[0]) {
|
||||
try {
|
||||
await Deno.remove(Deno.args[0]);
|
||||
} catch (_) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
const dbFile = Deno.args[0] || ":memory:";
|
||||
const db = new DB(dbFile);
|
||||
|
||||
db.query(
|
||||
"CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, balance INTEGER)",
|
||||
);
|
||||
|
||||
/** Performance of insert statements (1 insert). */
|
||||
const names = "Deno Land Peter Parker Clark Kent Robert Parr".split(" ");
|
||||
|
||||
bench({
|
||||
name: "insert 10 000 (named)",
|
||||
runs: 100,
|
||||
func: (b): void => {
|
||||
b.start();
|
||||
const query = db.prepareQuery(
|
||||
"INSERT INTO users (name, balance) VALUES (:name, :balance)",
|
||||
);
|
||||
db.query("begin");
|
||||
for (let i = 0; i < 10_000; i++) {
|
||||
query.execute({ name: names[i % names.length], balance: i });
|
||||
}
|
||||
db.query("commit");
|
||||
b.stop();
|
||||
},
|
||||
});
|
||||
|
||||
bench({
|
||||
name: "insert 10 000 (positional)",
|
||||
runs: 100,
|
||||
func: (b): void => {
|
||||
b.start();
|
||||
const query = db.prepareQuery(
|
||||
"INSERT INTO users (name, balance) VALUES (?, ?)",
|
||||
);
|
||||
db.query("begin");
|
||||
for (let i = 0; i < 10_000; i++) {
|
||||
query.execute([names[i % names.length], i]);
|
||||
}
|
||||
db.query("commit");
|
||||
b.stop();
|
||||
},
|
||||
});
|
||||
|
||||
/** Performance of select statements (select all; 10_000). */
|
||||
bench({
|
||||
name: "select 10 000 (select all)",
|
||||
runs: 100,
|
||||
func: (b): void => {
|
||||
b.start();
|
||||
db.query(
|
||||
"SELECT name, balance FROM users LIMIT 10000",
|
||||
);
|
||||
b.stop();
|
||||
},
|
||||
});
|
||||
|
||||
/** Performance of select statements (select individually; 10_000). */
|
||||
bench({
|
||||
name: "select 10 000 (select first)",
|
||||
runs: 100,
|
||||
func: (b): void => {
|
||||
b.start();
|
||||
const query = db.prepareQuery(
|
||||
"SELECT name, balance FROM users WHERE id = ?",
|
||||
);
|
||||
for (let id = 1; id <= 10_000; id++) {
|
||||
query.first([id]);
|
||||
}
|
||||
b.stop();
|
||||
},
|
||||
});
|
||||
|
||||
runBenchmarks();
|
5
plugos/forked/deno-sqlite/browser/import_map.json
Normal file
5
plugos/forked/deno-sqlite/browser/import_map.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"imports": {
|
||||
"../build/vfs.js": "../browser/vfs.js"
|
||||
}
|
||||
}
|
38
plugos/forked/deno-sqlite/browser/mod.ts
Normal file
38
plugos/forked/deno-sqlite/browser/mod.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { DB } from "../src/db.ts";
|
||||
import { loadFile, writeFile } from "./vfs.js";
|
||||
import { compile, instantiateBrowser } from "../build/sqlite.js";
|
||||
|
||||
export { SqliteError } from "../src/error.ts";
|
||||
export { Status } from "../src/constants.ts";
|
||||
|
||||
const hasCompiled = compile();
|
||||
|
||||
/**
|
||||
* Opens a database with the given name. If `file` is
|
||||
* not provided or `:memory:`, an in-memory database
|
||||
* is returned which will not persist after the database
|
||||
* is closed.
|
||||
*/
|
||||
export async function open(file?: string): Promise<DB> {
|
||||
if (file != null && file !== ":memory:") await loadFile(file);
|
||||
await hasCompiled;
|
||||
await instantiateBrowser();
|
||||
return new DB(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite a given file with arbitrary data. This can be used
|
||||
* to import a database which can later be opened.
|
||||
*/
|
||||
export async function write(file: string, data: Uint8Array): Promise<void> {
|
||||
await writeFile(file, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the data currently stored for a given file. This can be used
|
||||
* to export a database which has been created or modified.
|
||||
*/
|
||||
export async function read(file: string): Promise<Uint8Array | null> {
|
||||
const buffer = await loadFile(file);
|
||||
return buffer?.toUint8Array()?.slice();
|
||||
}
|
208
plugos/forked/deno-sqlite/browser/vfs.js
Normal file
208
plugos/forked/deno-sqlite/browser/vfs.js
Normal file
@ -0,0 +1,208 @@
|
||||
import { getStr } from "../src/wasm.ts";
|
||||
|
||||
const DB_NAME = "sqlitevfs";
|
||||
const LOADED_FILES = new Map();
|
||||
const OPEN_FILES = new Map();
|
||||
|
||||
function nextRid() {
|
||||
const rid = (nextRid?.LAST_RID ?? 0) + 1;
|
||||
nextRid.LAST_RID = rid;
|
||||
return rid;
|
||||
}
|
||||
|
||||
function getOpenFile(rid) {
|
||||
if (!OPEN_FILES.has(rid)) {
|
||||
throw new Error(`Resource ID ${rid} does not exist.`);
|
||||
}
|
||||
return OPEN_FILES.get(rid);
|
||||
}
|
||||
|
||||
const MIN_GROW_BYTES = 2048;
|
||||
const MAX_GROW_BYTES = 65536;
|
||||
|
||||
class Buffer {
|
||||
constructor(data) {
|
||||
this._data = data ?? new Uint8Array();
|
||||
this._size = this._data.length;
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this._size;
|
||||
}
|
||||
|
||||
read(offset, buffer) {
|
||||
if (offset >= this._size) return 0;
|
||||
const toCopy = this._data.subarray(
|
||||
offset,
|
||||
Math.min(this._size, offset + buffer.length),
|
||||
);
|
||||
buffer.set(toCopy);
|
||||
return toCopy.length;
|
||||
}
|
||||
|
||||
reserve(capacity) {
|
||||
if (this._data.length >= capacity) return;
|
||||
const neededBytes = capacity - this._data.length;
|
||||
const growBy = Math.min(
|
||||
MAX_GROW_BYTES,
|
||||
Math.max(MIN_GROW_BYTES, this._data.length),
|
||||
);
|
||||
const newArray = new Uint8Array(
|
||||
this._data.length + Math.max(growBy, neededBytes),
|
||||
);
|
||||
newArray.set(this._data);
|
||||
this._data = newArray;
|
||||
}
|
||||
|
||||
write(offset, buffer) {
|
||||
this.reserve(offset + buffer.length);
|
||||
this._data.set(buffer, offset);
|
||||
this._size = Math.max(this._size, offset + buffer.length);
|
||||
return buffer.length;
|
||||
}
|
||||
|
||||
truncate(size) {
|
||||
this._size = size;
|
||||
}
|
||||
|
||||
toUint8Array() {
|
||||
return this._data.subarray(0, this._size);
|
||||
}
|
||||
}
|
||||
|
||||
const indexedDB = window.indexedDB || window.mozIndexedDB ||
|
||||
window.webkitIndexedDB || window.msIndexedDB || window.shimIndexedDB;
|
||||
|
||||
// Web browser indexedDB database
|
||||
const database = new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(DB_NAME, 1);
|
||||
request.onupgradeneeded = () =>
|
||||
request.result.createObjectStore("files", { keyPath: "name" });
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
|
||||
export async function loadFile(fileName) {
|
||||
const db = await database;
|
||||
const file = await new Promise((resolve, reject) => {
|
||||
const store = db.transaction("files", "readonly").objectStore("files");
|
||||
const request = store.get(fileName);
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
if (file != null && !LOADED_FILES.has(fileName)) {
|
||||
const buffer = new Buffer(file.data);
|
||||
LOADED_FILES.set(fileName, buffer);
|
||||
return buffer;
|
||||
} else if (LOADED_FILES.has(fileName)) {
|
||||
return LOADED_FILES.get(fileName);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function syncFile(fileName, data) {
|
||||
const db = await database;
|
||||
await new Promise((resolve, reject) => {
|
||||
const store = db.transaction("files", "readwrite").objectStore("files");
|
||||
const request = store.put({ name: fileName, data });
|
||||
request.onsuccess = () => resolve();
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteFile(fileName) {
|
||||
const db = await database;
|
||||
await new Promise((resolve, reject) => {
|
||||
const store = db.transaction("files", "readwrite").objectStore("files");
|
||||
const request = store.delete(fileName);
|
||||
request.onsuccess = () => resolve();
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
export async function writeFile(fileName, data) {
|
||||
await syncFile(fileName, data);
|
||||
if (LOADED_FILES.has(fileName)) {
|
||||
const buffer = LOADED_FILES.get(fileName);
|
||||
buffer.truncate(0);
|
||||
buffer.write(0, data);
|
||||
}
|
||||
}
|
||||
|
||||
// Closure to return an environment that links
|
||||
// the current wasm context. This is a modified
|
||||
// version, suitable for use within browsers.
|
||||
export default function env(inst) {
|
||||
const env = {
|
||||
js_print: (str_ptr) => {
|
||||
const text = getStr(inst.exports, str_ptr);
|
||||
console.log(text[text.length - 1] === "\n" ? text.slice(0, -1) : text);
|
||||
},
|
||||
js_open: (path_ptr, mode, _flags) => {
|
||||
if (mode === 1 /* temp file */) {
|
||||
const rid = nextRid();
|
||||
OPEN_FILES.set(rid, { path: null, buffer: new Buffer() });
|
||||
return rid;
|
||||
} else if (mode === 0 /* regular file */) {
|
||||
const path = getStr(inst.exports, path_ptr);
|
||||
const buffer = LOADED_FILES.get(path) ?? new Buffer();
|
||||
if (!LOADED_FILES.has(path)) LOADED_FILES.set(path, buffer);
|
||||
const rid = nextRid();
|
||||
OPEN_FILES.set(rid, { path, buffer });
|
||||
return rid;
|
||||
}
|
||||
},
|
||||
js_close: (rid) => {
|
||||
OPEN_FILES.delete(rid);
|
||||
},
|
||||
js_delete: (path_ptr) => {
|
||||
const path = getStr(inst.exports, path_ptr);
|
||||
LOADED_FILES.delete(path);
|
||||
deleteFile(path);
|
||||
},
|
||||
js_read: (rid, buffer_ptr, offset, amount) => {
|
||||
const buffer = new Uint8Array(
|
||||
inst.exports.memory.buffer,
|
||||
buffer_ptr,
|
||||
amount,
|
||||
);
|
||||
const file = getOpenFile(rid);
|
||||
return file.buffer.read(offset, buffer);
|
||||
},
|
||||
js_write: (rid, buffer_ptr, offset, amount) => {
|
||||
const buffer = new Uint8Array(
|
||||
inst.exports.memory.buffer,
|
||||
buffer_ptr,
|
||||
amount,
|
||||
);
|
||||
const file = getOpenFile(rid);
|
||||
return file.buffer.write(offset, buffer);
|
||||
},
|
||||
js_truncate: (rid, size) => {
|
||||
getOpenFile(rid).buffer.truncate(size);
|
||||
},
|
||||
js_sync: (rid) => {
|
||||
const file = getOpenFile(rid);
|
||||
if (file.path != null) syncFile(file.path, file.buffer.toUint8Array());
|
||||
},
|
||||
js_size: (rid) => {
|
||||
return getOpenFile(rid).buffer.size;
|
||||
},
|
||||
js_lock: (_rid, _exclusive) => {},
|
||||
js_unlock: (_rid) => {},
|
||||
js_time: () => {
|
||||
return Date.now();
|
||||
},
|
||||
js_timezone: () => {
|
||||
return (new Date()).getTimezoneOffset();
|
||||
},
|
||||
js_exists: (path_ptr) => {
|
||||
const path = getStr(inst.exports, path_ptr);
|
||||
return LOADED_FILES.has(path) ? 1 : 0;
|
||||
},
|
||||
js_access: (_path_ptr) => 1,
|
||||
};
|
||||
|
||||
return { env };
|
||||
}
|
3
plugos/forked/deno-sqlite/build/.checksums
Normal file
3
plugos/forked/deno-sqlite/build/.checksums
Normal file
@ -0,0 +1,3 @@
|
||||
b9365f4aa1d3047a8d80d6bfe90e705c80e158c3 sqlite_dl.zip
|
||||
022ae45d50b124b9df68be065d65b9a607923362 wasi_dl_linux.tar.gz
|
||||
bc76d264214c21a603fc38adb405622a2fcda8b3 wasi_dl_darwin.tar.gz
|
131
plugos/forked/deno-sqlite/build/Makefile
Normal file
131
plugos/forked/deno-sqlite/build/Makefile
Normal file
@ -0,0 +1,131 @@
|
||||
DENO ?= deno
|
||||
WASI ?= ./wasi-sdk
|
||||
CC = $(WASI)/bin/clang
|
||||
|
||||
OUT_WA = "sqlite.wasm"
|
||||
OUT_BN = "sqlite.js"
|
||||
OUT_TY = "sqlite.d.ts"
|
||||
|
||||
SQLITE_DLD = "https://sqlite.org/2022/sqlite-src-3390200.zip"
|
||||
SQLITE_DIR = "sqlite-src-3390200"
|
||||
|
||||
WASI_DLD = "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-16/wasi-sdk-16.0-linux.tar.gz"
|
||||
WASI_TAR = wasi_dl_linux.tar.gz
|
||||
ifeq ($(shell uname), Darwin)
|
||||
WASI_DLD = "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-16/wasi-sdk-16.0-macos.tar.gz"
|
||||
WASI_TAR = wasi_dl_darwin.tar.gz
|
||||
endif
|
||||
|
||||
CSRC = ""
|
||||
CSRC += $(shell find ./src -name "*.c")
|
||||
CSRC += $(shell find ./lib -name "*.c")
|
||||
CSRC += $(shell find ./hask -name "*.c")
|
||||
FLGS = -Wall
|
||||
RFLG = -Os
|
||||
DFLG = -DDEBUG_BUILD
|
||||
WAFLG = --target=wasm32-unknown-wasi -Wl,--no-entry -nostartfiles --sysroot $(WASI)/share/wasi-sysroot\
|
||||
-DWASI_BUILD -Wl,--export,malloc -Wl,--export,free -Wl,--allow-undefined-file=vfs.syms
|
||||
INCS = -Ilib
|
||||
|
||||
# Location of wrapper library which contains all c-land export
|
||||
CWRP = "./src/wrapper.c"
|
||||
|
||||
# Configure sqlite for out use-case
|
||||
SQLFLG = -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS\
|
||||
-DSQLITE_DEFAULT_FOREIGN_KEYS=1 -DSQLITE_TEMP_STORE=2\
|
||||
-DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_UTF16 -DSQLITE_OMIT_SHARED_CACHE\
|
||||
-DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_TRACE\
|
||||
-DSQLITE_OS_OTHER=1 -DSQLITE_OMIT_COMPLETE -DSQLITE_OMIT_WAL\
|
||||
-DNDEBUG=1 -DSQLITE_ENABLE_COLUMN_METADATA -DHAVE_LOCALTIME_R\
|
||||
-DSQLITE_OMIT_DESERIALIZE -DSQLITE_ENABLE_FTS5
|
||||
# Rational:
|
||||
# SQLITE_DQS -> we do not need to have backwards comp
|
||||
# SQLITE_THREADSAFE -> we run single-threaded
|
||||
# SQLITE_LIKE_DOESNT_MATCH_BLOBS -> faster (is recommended if no backwards comp)
|
||||
# SQLITE_DEFAULT_FOREIGN_KEYS -> this should be the default
|
||||
# SQLITE_TEMP_STORE -> in memory is faster, so it's the better default
|
||||
# SQLITE_OMIT_DEPRECATED -> we do not need to have backwards comp
|
||||
# SQLITE_OMIT_UTF16 -> we only support utf-8 encoded strings
|
||||
# SQLITE_OMIT_SHARED_CACHE -> we only ever open one connection
|
||||
# SQLITE_OMIT_LOAD_EXTENSION -> we don't use it
|
||||
# SQLITE_OMIT_PROGRESS_CALLBACK -> we don't use it
|
||||
# SQLITE_OMIT_TRACE -> we make no use of these
|
||||
# SQLITE_OS_OTHER -> we provide our own vfs
|
||||
# SQLITE_OMIT_COMPLETE -> we don't need these
|
||||
# SQLITE_OMIT_WAL -> this is doggy, as we can not memory map files
|
||||
# DNDEBUG -> "use for maximum speed"
|
||||
# SQLITE_ENABLE_COLUMN_METADATA -> we depend on column metadata interfaces (`sqlite3_column_table_name` and `sqlite3_column_origin_name`)
|
||||
# SQLITE_OMIT_DESERIALIZE -> we don't use these interfaces
|
||||
|
||||
all: release
|
||||
|
||||
build:
|
||||
$(DENO) run --allow-read --allow-write hack/gen_syms.js vfs.syms
|
||||
$(CC) $(WAFLG) $(FLGS) $(INCS) $(CSRC) $(SQLFLG) -o $(OUT_WA)
|
||||
|
||||
bundle:
|
||||
$(DENO) run --allow-read --allow-write hack/bundle.js $(OUT_WA) $(OUT_BN)
|
||||
$(DENO) fmt $(OUT_BN)
|
||||
|
||||
types:
|
||||
$(DENO) run --allow-read --allow-write hack/gen_types.ts $(CWRP) $(OUT_TY)
|
||||
$(DENO) fmt $(OUT_TY)
|
||||
|
||||
size.txt: sqlite.js sqlite.wasm
|
||||
rm -f size.txt *.gz *.br
|
||||
gzip --best < sqlite.js > sqlite.js.gz
|
||||
gzip --best < sqlite.wasm > sqlite.wasm.gz
|
||||
brotli --best -o sqlite.js.br < sqlite.js && \
|
||||
brotli --best -o sqlite.wasm.br < sqlite.wasm || \
|
||||
echo "WARN: brotli size comparison unavailable"
|
||||
ls -l sqlite.* | awk '{printf "%-15s➜%7s bytes\n",$$9,$$5}' | tee size.txt
|
||||
|
||||
debug: FLGS += $(DFLG)
|
||||
debug: build
|
||||
debug: bundle
|
||||
debug: types
|
||||
|
||||
release: FLGS += $(RFLG)
|
||||
release: build
|
||||
release: bundle
|
||||
release: types
|
||||
release: size.txt
|
||||
|
||||
amalgamation:
|
||||
make -C sqlite-src clean
|
||||
make -C sqlite-src sqlite3.c SQLFLG="$(SQLFLG)"
|
||||
mv sqlite-src/sqlite3.c lib/sqlite3.c
|
||||
mv sqlite-src/sqlite3.h lib/sqlite3.h
|
||||
|
||||
dlsqlite:
|
||||
curl "$(SQLITE_DLD)" -o "sqlite_dl.zip"
|
||||
sed -n '/sqlite_dl.zip/p' ".checksums" | shasum -c -
|
||||
rm -rf "sqlite-src"
|
||||
unzip "sqlite_dl.zip"
|
||||
mv $(SQLITE_DIR) "sqlite-src"
|
||||
cp "Makefile.sqlite" "sqlite-src/Makefile"
|
||||
rm "sqlite_dl.zip"
|
||||
|
||||
dlwasi:
|
||||
curl -L "$(WASI_DLD)" -o "$(WASI_TAR)"
|
||||
sed -n '/${WASI_TAR}/p' ".checksums" | shasum -c -
|
||||
rm -rf "wasi-sdk"
|
||||
tar -xzvf "$(WASI_TAR)"
|
||||
mv "wasi-sdk-16.0" "wasi-sdk"
|
||||
rm "${WASI_TAR}"
|
||||
|
||||
testdb:
|
||||
gcc -Ilib lib/sqlite3.c hack/gen_test_db.c -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o gen_test_db
|
||||
rm -f 2GB_test.db
|
||||
./gen_test_db
|
||||
rm gen_test_db
|
||||
|
||||
setup: dlsqlite
|
||||
setup: dlwasi
|
||||
|
||||
clean:
|
||||
rm -rf sqlite-src
|
||||
rm -rf wasi-sdk
|
||||
rm -f 2GB_test.db
|
||||
|
||||
.PHONY: build amalgamation dlsqlite dlwasi setup clean
|
62
plugos/forked/deno-sqlite/build/hack/bundle.js
Normal file
62
plugos/forked/deno-sqlite/build/hack/bundle.js
Normal file
@ -0,0 +1,62 @@
|
||||
const [src, dest] = Deno.args;
|
||||
|
||||
const wasm = await Deno.readFile(src);
|
||||
|
||||
function encode(bytes) {
|
||||
let binary = "";
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return btoa(binary).replace(/\n/g, "");
|
||||
}
|
||||
|
||||
await Deno.writeFile(
|
||||
dest,
|
||||
new TextEncoder().encode(
|
||||
`/// <reference types="./sqlite.d.ts" />
|
||||
|
||||
/* This file is automatically generated. Do not edit directly. */
|
||||
import env from "./vfs.js";
|
||||
|
||||
const wasm =
|
||||
"${encode(wasm)}";
|
||||
|
||||
function decode(base64) {
|
||||
const bytesStr = atob(base64);
|
||||
const bytes = new Uint8Array(bytesStr.length);
|
||||
for (let i = 0, c = bytesStr.length; i < c; i++) {
|
||||
bytes[i] = bytesStr.charCodeAt(i);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
const moduleOrInstance = {
|
||||
module: null,
|
||||
instances: [],
|
||||
};
|
||||
|
||||
export async function compile() {
|
||||
moduleOrInstance.module = await WebAssembly.compile(decode(wasm));
|
||||
}
|
||||
|
||||
export async function instantiateBrowser() {
|
||||
const placeholder = { exports: null };
|
||||
const instance = await WebAssembly.instantiate(moduleOrInstance.module, env(placeholder));
|
||||
placeholder.exports = instance.exports;
|
||||
instance.exports.seed_rng(Date.now());
|
||||
moduleOrInstance.instances.push(instance);
|
||||
}
|
||||
|
||||
export function instantiate() {
|
||||
if (moduleOrInstance.instances.length) {
|
||||
return moduleOrInstance.instances.pop();
|
||||
} else {
|
||||
const placeholder = { exports: null };
|
||||
const instance = new WebAssembly.Instance(moduleOrInstance.module, env(placeholder));
|
||||
placeholder.exports = instance.exports;
|
||||
instance.exports.seed_rng(Date.now());
|
||||
return instance;
|
||||
}
|
||||
}`,
|
||||
),
|
||||
);
|
10
plugos/forked/deno-sqlite/build/hack/gen_syms.js
Normal file
10
plugos/forked/deno-sqlite/build/hack/gen_syms.js
Normal file
@ -0,0 +1,10 @@
|
||||
// Generate available import symbols
|
||||
// from vfs.js file
|
||||
|
||||
import env from "../vfs.js";
|
||||
|
||||
let symbols = "";
|
||||
for (const symbol of Object.keys(env().env)) {
|
||||
symbols += `${symbol}\n`;
|
||||
}
|
||||
await Deno.writeFile(Deno.args[0], new TextEncoder().encode(symbols));
|
98
plugos/forked/deno-sqlite/build/hack/gen_test_db.c
Normal file
98
plugos/forked/deno-sqlite/build/hack/gen_test_db.c
Normal file
@ -0,0 +1,98 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
|
||||
#define TEST_DB_FILE "2GB_test.db"
|
||||
|
||||
#define SQL_CREATE_TBL "CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"
|
||||
#define SQL_INSERT_VAL "INSERT INTO test (value) VALUES (?)"
|
||||
|
||||
#define VAL_LEN 65536
|
||||
#define VAL_NUM 45000
|
||||
|
||||
void rand_str(char *dest, size_t length) {
|
||||
char charset[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
while (length --> 0) {
|
||||
size_t index = (double) rand() / RAND_MAX * (sizeof charset - 1);
|
||||
*dest++ = charset[index];
|
||||
}
|
||||
*dest = '\0';
|
||||
}
|
||||
|
||||
int execute_query(sqlite3* db, char* query) {
|
||||
sqlite3_stmt* stmt_create;
|
||||
if (sqlite3_prepare_v2(db, query, -1, &stmt_create, NULL) != SQLITE_OK) {
|
||||
printf("Failed to prepare query statement: %s\n", sqlite3_errmsg(db));
|
||||
return FALSE;
|
||||
}
|
||||
if (sqlite3_step(stmt_create) != SQLITE_DONE) {
|
||||
printf("Failed to run query statement: %s\n", sqlite3_errmsg(db));
|
||||
return FALSE;
|
||||
}
|
||||
sqlite3_finalize(stmt_create);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
sqlite3* db;
|
||||
if (sqlite3_open(TEST_DB_FILE, &db) != SQLITE_OK) {
|
||||
printf("Failed to open database: %s\n", sqlite3_errmsg(db));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// create database table
|
||||
sqlite3_stmt* stmt_create;
|
||||
if (sqlite3_prepare_v2(db, SQL_CREATE_TBL, -1, &stmt_create, NULL) != SQLITE_OK) {
|
||||
printf("Failed to prepare create table statement: %s\n", sqlite3_errmsg(db));
|
||||
return 1;
|
||||
}
|
||||
if (sqlite3_step(stmt_create) != SQLITE_DONE) {
|
||||
printf("Failed to run create table statement: %s\n", sqlite3_errmsg(db));
|
||||
return 1;
|
||||
}
|
||||
sqlite3_finalize(stmt_create);
|
||||
|
||||
// insert values to reach 2GB
|
||||
sqlite3_stmt* stmt_insert;
|
||||
if (sqlite3_prepare_v2(db, SQL_INSERT_VAL, -1, &stmt_insert, NULL) != SQLITE_OK) {
|
||||
printf("Failed to prepare insert table statement: %s\n", sqlite3_errmsg(db));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// begin transaction
|
||||
if (!execute_query(db, "begin")) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
char* buffer = malloc(VAL_LEN + 1);
|
||||
for (int64_t i = 0; i < VAL_NUM; i++) {
|
||||
rand_str(buffer, VAL_LEN);
|
||||
if (sqlite3_bind_text(stmt_insert, 1, buffer, VAL_LEN, NULL) != SQLITE_OK) {
|
||||
printf("Failed to bind value `%s`: %s\n", buffer, sqlite3_errmsg(db));
|
||||
return 1;
|
||||
}
|
||||
if (sqlite3_step(stmt_insert) != SQLITE_DONE) {
|
||||
printf("Failed to run insert statement: %s\n", sqlite3_errmsg(db));
|
||||
return 1;
|
||||
}
|
||||
if (sqlite3_reset(stmt_insert) != SQLITE_OK) {
|
||||
printf("Failed to reset statement: %s\n", sqlite3_errmsg(db));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// end transaction
|
||||
if (!execute_query(db, "commit")) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt_insert);
|
||||
sqlite3_close(db);
|
||||
|
||||
printf("Database created successfully.\n");
|
||||
|
||||
return 0;
|
||||
}
|
156
plugos/forked/deno-sqlite/build/hack/gen_types.ts
Normal file
156
plugos/forked/deno-sqlite/build/hack/gen_types.ts
Normal file
@ -0,0 +1,156 @@
|
||||
interface Item {
|
||||
name: string;
|
||||
arguments: Argument[];
|
||||
returnType: Type;
|
||||
}
|
||||
|
||||
interface Argument {
|
||||
name: string;
|
||||
type: Type;
|
||||
}
|
||||
|
||||
enum Type {
|
||||
Void,
|
||||
VoidPtr,
|
||||
StringPtr,
|
||||
StatementPtr,
|
||||
Double,
|
||||
Int,
|
||||
}
|
||||
|
||||
const items = [
|
||||
// exported manually in compiler invocation
|
||||
{
|
||||
name: "malloc",
|
||||
arguments: [{ name: "size", type: Type.Int }],
|
||||
returnType: Type.VoidPtr,
|
||||
},
|
||||
{
|
||||
name: "free",
|
||||
arguments: [{ name: "ptr", type: Type.VoidPtr }],
|
||||
returnType: Type.Void,
|
||||
},
|
||||
];
|
||||
|
||||
const [src, dest] = Deno.args;
|
||||
const wrapperSrc = await Deno.readTextFile(src);
|
||||
|
||||
// int EXPORT(bind_int) (sqlite3_stmt* stmt, int idx, double value)
|
||||
const typeRegexp =
|
||||
`(const +)?(sqlite3_stmt\\*|char\\*|void\\*|int|uint32_t|double|void)`;
|
||||
const argRegexp = `${typeRegexp} +[a-z_]+`;
|
||||
const exportSignature = new RegExp(
|
||||
`${typeRegexp} +EXPORT\\([a-z_]+\\) +\\(((${argRegexp}( *, *${argRegexp})*)|)\\)`,
|
||||
);
|
||||
|
||||
function nullThrows<T>(value: T | null | undefined): T {
|
||||
if (value == null) {
|
||||
throw new Error("Got a null value");
|
||||
}
|
||||
return value as T;
|
||||
}
|
||||
|
||||
function typeFromCType(cType: string): Type {
|
||||
cType = cType.replace("const", "").replace(/ /g, "");
|
||||
switch (cType) {
|
||||
case "void":
|
||||
return Type.Void;
|
||||
case "void*":
|
||||
return Type.VoidPtr;
|
||||
case "char*":
|
||||
return Type.StringPtr;
|
||||
case "sqlite3_stmt*":
|
||||
return Type.StatementPtr;
|
||||
case "double":
|
||||
return Type.Double;
|
||||
case "int":
|
||||
case "uint32_t":
|
||||
return Type.Int;
|
||||
default:
|
||||
throw new Error("Unknown type");
|
||||
}
|
||||
}
|
||||
|
||||
function getReturnType(line: string): Type {
|
||||
const regexp = new RegExp(typeRegexp);
|
||||
const [, _const, cType] = nullThrows(regexp.exec(line));
|
||||
return typeFromCType(cType);
|
||||
}
|
||||
|
||||
function getName(line: string): string {
|
||||
const [, name] = nullThrows(/EXPORT\(([a-z_]+)\)/.exec(line));
|
||||
return name;
|
||||
}
|
||||
|
||||
function getArguments(line: string): Argument[] {
|
||||
const [, argList] = nullThrows(/EXPORT\([a-z_]+\) *\(([^)]*)\)/.exec(line));
|
||||
if (argList.length === 0) {
|
||||
return [];
|
||||
} else {
|
||||
return argList.split(",").map((arg) => {
|
||||
const regexp = new RegExp(`${typeRegexp} +([a-z_]+)`);
|
||||
const [, _const, cType, name] = nullThrows(regexp.exec(arg));
|
||||
return {
|
||||
name,
|
||||
type: typeFromCType(cType),
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function generateType(tp: Type): string {
|
||||
switch (tp) {
|
||||
case Type.Void:
|
||||
return "void";
|
||||
case Type.VoidPtr:
|
||||
return "VoidPtr";
|
||||
case Type.StringPtr:
|
||||
return "StringPtr";
|
||||
case Type.StatementPtr:
|
||||
return "StatementPtr";
|
||||
case Type.Int:
|
||||
case Type.Double:
|
||||
return "number";
|
||||
default:
|
||||
throw new Error("Unknown type");
|
||||
}
|
||||
}
|
||||
|
||||
function generateDecl(item: Item): string {
|
||||
const args = item.arguments.map((arg) =>
|
||||
`${arg.name}: ${generateType(arg.type)}`
|
||||
).join(", ");
|
||||
return `${item.name}: (${args}) => ${generateType(item.returnType)}`;
|
||||
}
|
||||
|
||||
const exportLines = wrapperSrc.split("\n").filter((line) =>
|
||||
exportSignature.test(line)
|
||||
);
|
||||
|
||||
for (const line of exportLines) {
|
||||
const name = getName(line);
|
||||
const returnType = getReturnType(line);
|
||||
const args = getArguments(line);
|
||||
|
||||
items.push({ name, returnType, arguments: args });
|
||||
}
|
||||
|
||||
const typeDeclaration =
|
||||
`/* This file is automatically generated. Do not edit directly. */
|
||||
|
||||
export type VoidPtr = number;
|
||||
export type StringPtr = number;
|
||||
export type StatementPtr = number;
|
||||
|
||||
export interface Wasm {
|
||||
memory: WebAssembly.Memory;
|
||||
|
||||
${items.map(generateDecl).join(";\n ")};
|
||||
}
|
||||
|
||||
export function compile(): Promise<void>;
|
||||
export function instantiateBrowser(): Promise<void>;
|
||||
export function instantiate(): { exports: Wasm };
|
||||
`;
|
||||
|
||||
await Deno.writeTextFile(dest, typeDeclaration);
|
34
plugos/forked/deno-sqlite/build/lib/pcg.c
vendored
Normal file
34
plugos/forked/deno-sqlite/build/lib/pcg.c
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
#include "pcg.h"
|
||||
|
||||
// Random number generator.
|
||||
// Based on:
|
||||
// *Really* minimal PCG32 code / (c) 2014 M.E. O'Neill / pcg-random.org
|
||||
// Licensed under Apache License 2.0 (NO WARRANTY, etc. see website)
|
||||
|
||||
uint64_t state = 0x853c49e6748fea9bULL;
|
||||
uint64_t inc = 0xda3e39cb94b95bdbULL;
|
||||
|
||||
// Update seed of generator.
|
||||
void pcg_seed(uint64_t seed) {
|
||||
state = seed;
|
||||
}
|
||||
|
||||
// Generate random integer.
|
||||
uint32_t pcg_rand() {
|
||||
uint64_t oldstate = state;
|
||||
// Advance internal state
|
||||
state = oldstate * 6364136223846793005ULL + (inc|1);
|
||||
// Calculate output function (XSH RR), uses old state for max ILP
|
||||
uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
|
||||
uint32_t rot = oldstate >> 59u;
|
||||
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
|
||||
}
|
||||
|
||||
// Fill out buffer with size random bytes.
|
||||
void pcg_bytes(char* out, int size) {
|
||||
// TODO: We can be more efficient by using all 4
|
||||
// pieces of the random number returned.
|
||||
for (int i = 0; i < size; i ++) {
|
||||
out[i] = (char)pcg_rand();
|
||||
}
|
||||
}
|
13
plugos/forked/deno-sqlite/build/lib/pcg.h
vendored
Normal file
13
plugos/forked/deno-sqlite/build/lib/pcg.h
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
#ifndef PCG_H
|
||||
#define PCG_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Seed the random number generator
|
||||
void pcg_seed(uint64_t seed);
|
||||
|
||||
// Get random numbers and random bits
|
||||
uint32_t pcg_rand();
|
||||
void pcg_bytes(char* out, int size);
|
||||
|
||||
#endif // PCG_H
|
914
plugos/forked/deno-sqlite/build/lib/printf.c
vendored
Normal file
914
plugos/forked/deno-sqlite/build/lib/printf.c
vendored
Normal file
@ -0,0 +1,914 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// \author (c) Marco Paland (info@paland.com)
|
||||
// 2014-2019, PALANDesign Hannover, Germany
|
||||
//
|
||||
// \license The MIT License (MIT)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on
|
||||
// embedded systems with a very limited resources. These routines are thread
|
||||
// safe and reentrant!
|
||||
// Use this instead of the bloated standard/newlib printf cause these use
|
||||
// malloc for printf (and may not be thread safe).
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "printf.h"
|
||||
|
||||
|
||||
// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the
|
||||
// printf_config.h header file
|
||||
// default: undefined
|
||||
#ifdef PRINTF_INCLUDE_CONFIG_H
|
||||
#include "printf_config.h"
|
||||
#endif
|
||||
|
||||
|
||||
// 'ntoa' conversion buffer size, this must be big enough to hold one converted
|
||||
// numeric number including padded zeros (dynamically created on stack)
|
||||
// default: 32 byte
|
||||
#ifndef PRINTF_NTOA_BUFFER_SIZE
|
||||
#define PRINTF_NTOA_BUFFER_SIZE 32U
|
||||
#endif
|
||||
|
||||
// 'ftoa' conversion buffer size, this must be big enough to hold one converted
|
||||
// float number including padded zeros (dynamically created on stack)
|
||||
// default: 32 byte
|
||||
#ifndef PRINTF_FTOA_BUFFER_SIZE
|
||||
#define PRINTF_FTOA_BUFFER_SIZE 32U
|
||||
#endif
|
||||
|
||||
// support for the floating point type (%f)
|
||||
// default: activated
|
||||
#ifndef PRINTF_DISABLE_SUPPORT_FLOAT
|
||||
#define PRINTF_SUPPORT_FLOAT
|
||||
#endif
|
||||
|
||||
// support for exponential floating point notation (%e/%g)
|
||||
// default: activated
|
||||
#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL
|
||||
#define PRINTF_SUPPORT_EXPONENTIAL
|
||||
#endif
|
||||
|
||||
// define the default floating point precision
|
||||
// default: 6 digits
|
||||
#ifndef PRINTF_DEFAULT_FLOAT_PRECISION
|
||||
#define PRINTF_DEFAULT_FLOAT_PRECISION 6U
|
||||
#endif
|
||||
|
||||
// define the largest float suitable to print with %f
|
||||
// default: 1e9
|
||||
#ifndef PRINTF_MAX_FLOAT
|
||||
#define PRINTF_MAX_FLOAT 1e9
|
||||
#endif
|
||||
|
||||
// support for the long long types (%llu or %p)
|
||||
// default: activated
|
||||
#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG
|
||||
#define PRINTF_SUPPORT_LONG_LONG
|
||||
#endif
|
||||
|
||||
// support for the ptrdiff_t type (%t)
|
||||
// ptrdiff_t is normally defined in <stddef.h> as long or long long type
|
||||
// default: activated
|
||||
#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T
|
||||
#define PRINTF_SUPPORT_PTRDIFF_T
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// internal flag definitions
|
||||
#define FLAGS_ZEROPAD (1U << 0U)
|
||||
#define FLAGS_LEFT (1U << 1U)
|
||||
#define FLAGS_PLUS (1U << 2U)
|
||||
#define FLAGS_SPACE (1U << 3U)
|
||||
#define FLAGS_HASH (1U << 4U)
|
||||
#define FLAGS_UPPERCASE (1U << 5U)
|
||||
#define FLAGS_CHAR (1U << 6U)
|
||||
#define FLAGS_SHORT (1U << 7U)
|
||||
#define FLAGS_LONG (1U << 8U)
|
||||
#define FLAGS_LONG_LONG (1U << 9U)
|
||||
#define FLAGS_PRECISION (1U << 10U)
|
||||
#define FLAGS_ADAPT_EXP (1U << 11U)
|
||||
|
||||
|
||||
// import float.h for DBL_MAX
|
||||
#if defined(PRINTF_SUPPORT_FLOAT)
|
||||
#include <float.h>
|
||||
#endif
|
||||
|
||||
|
||||
// output function type
|
||||
typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen);
|
||||
|
||||
|
||||
// wrapper (used as buffer) for output function type
|
||||
typedef struct {
|
||||
void (*fct)(char character, void* arg);
|
||||
void* arg;
|
||||
} out_fct_wrap_type;
|
||||
|
||||
|
||||
// internal buffer output
|
||||
static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen)
|
||||
{
|
||||
if (idx < maxlen) {
|
||||
((char*)buffer)[idx] = character;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// internal null output
|
||||
static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen)
|
||||
{
|
||||
(void)character; (void)buffer; (void)idx; (void)maxlen;
|
||||
}
|
||||
|
||||
|
||||
// internal _putchar wrapper
|
||||
static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen)
|
||||
{
|
||||
(void)buffer; (void)idx; (void)maxlen;
|
||||
if (character) {
|
||||
_putchar(character);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// internal output function wrapper
|
||||
static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen)
|
||||
{
|
||||
(void)idx; (void)maxlen;
|
||||
if (character) {
|
||||
// buffer is the output fct pointer
|
||||
((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// internal secure strlen
|
||||
// \return The length of the string (excluding the terminating 0) limited by 'maxsize'
|
||||
static inline unsigned int _strnlen_s(const char* str, size_t maxsize)
|
||||
{
|
||||
const char* s;
|
||||
for (s = str; *s && maxsize--; ++s);
|
||||
return (unsigned int)(s - str);
|
||||
}
|
||||
|
||||
|
||||
// internal test if char is a digit (0-9)
|
||||
// \return true if char is a digit
|
||||
static inline bool _is_digit(char ch)
|
||||
{
|
||||
return (ch >= '0') && (ch <= '9');
|
||||
}
|
||||
|
||||
|
||||
// internal ASCII string to unsigned int conversion
|
||||
static unsigned int _atoi(const char** str)
|
||||
{
|
||||
unsigned int i = 0U;
|
||||
while (_is_digit(**str)) {
|
||||
i = i * 10U + (unsigned int)(*((*str)++) - '0');
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
// output the specified string in reverse, taking care of any zero-padding
|
||||
static size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags)
|
||||
{
|
||||
const size_t start_idx = idx;
|
||||
|
||||
// pad spaces up to given width
|
||||
if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
|
||||
for (size_t i = len; i < width; i++) {
|
||||
out(' ', buffer, idx++, maxlen);
|
||||
}
|
||||
}
|
||||
|
||||
// reverse string
|
||||
while (len) {
|
||||
out(buf[--len], buffer, idx++, maxlen);
|
||||
}
|
||||
|
||||
// append pad spaces up to given width
|
||||
if (flags & FLAGS_LEFT) {
|
||||
while (idx - start_idx < width) {
|
||||
out(' ', buffer, idx++, maxlen);
|
||||
}
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
|
||||
// internal itoa format
|
||||
static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags)
|
||||
{
|
||||
// pad leading zeros
|
||||
if (!(flags & FLAGS_LEFT)) {
|
||||
if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
|
||||
width--;
|
||||
}
|
||||
while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
|
||||
buf[len++] = '0';
|
||||
}
|
||||
while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
|
||||
buf[len++] = '0';
|
||||
}
|
||||
}
|
||||
|
||||
// handle hash
|
||||
if (flags & FLAGS_HASH) {
|
||||
if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) {
|
||||
len--;
|
||||
if (len && (base == 16U)) {
|
||||
len--;
|
||||
}
|
||||
}
|
||||
if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
|
||||
buf[len++] = 'x';
|
||||
}
|
||||
else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
|
||||
buf[len++] = 'X';
|
||||
}
|
||||
else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
|
||||
buf[len++] = 'b';
|
||||
}
|
||||
if (len < PRINTF_NTOA_BUFFER_SIZE) {
|
||||
buf[len++] = '0';
|
||||
}
|
||||
}
|
||||
|
||||
if (len < PRINTF_NTOA_BUFFER_SIZE) {
|
||||
if (negative) {
|
||||
buf[len++] = '-';
|
||||
}
|
||||
else if (flags & FLAGS_PLUS) {
|
||||
buf[len++] = '+'; // ignore the space if the '+' exists
|
||||
}
|
||||
else if (flags & FLAGS_SPACE) {
|
||||
buf[len++] = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
|
||||
}
|
||||
|
||||
|
||||
// internal itoa for 'long' type
|
||||
static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags)
|
||||
{
|
||||
char buf[PRINTF_NTOA_BUFFER_SIZE];
|
||||
size_t len = 0U;
|
||||
|
||||
// no hash for 0 values
|
||||
if (!value) {
|
||||
flags &= ~FLAGS_HASH;
|
||||
}
|
||||
|
||||
// write if precision != 0 and value is != 0
|
||||
if (!(flags & FLAGS_PRECISION) || value) {
|
||||
do {
|
||||
const char digit = (char)(value % base);
|
||||
buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
|
||||
value /= base;
|
||||
} while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
|
||||
}
|
||||
|
||||
return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
|
||||
}
|
||||
|
||||
|
||||
// internal itoa for 'long long' type
|
||||
#if defined(PRINTF_SUPPORT_LONG_LONG)
|
||||
static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags)
|
||||
{
|
||||
char buf[PRINTF_NTOA_BUFFER_SIZE];
|
||||
size_t len = 0U;
|
||||
|
||||
// no hash for 0 values
|
||||
if (!value) {
|
||||
flags &= ~FLAGS_HASH;
|
||||
}
|
||||
|
||||
// write if precision != 0 and value is != 0
|
||||
if (!(flags & FLAGS_PRECISION) || value) {
|
||||
do {
|
||||
const char digit = (char)(value % base);
|
||||
buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
|
||||
value /= base;
|
||||
} while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
|
||||
}
|
||||
|
||||
return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
|
||||
}
|
||||
#endif // PRINTF_SUPPORT_LONG_LONG
|
||||
|
||||
|
||||
#if defined(PRINTF_SUPPORT_FLOAT)
|
||||
|
||||
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
|
||||
// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT
|
||||
static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags);
|
||||
#endif
|
||||
|
||||
|
||||
// internal ftoa for fixed decimal floating point
|
||||
static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
|
||||
{
|
||||
char buf[PRINTF_FTOA_BUFFER_SIZE];
|
||||
size_t len = 0U;
|
||||
double diff = 0.0;
|
||||
|
||||
// powers of 10
|
||||
static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
|
||||
|
||||
// test for special values
|
||||
if (value != value)
|
||||
return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags);
|
||||
if (value < -DBL_MAX)
|
||||
return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags);
|
||||
if (value > DBL_MAX)
|
||||
return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags);
|
||||
|
||||
// test for very large values
|
||||
// standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad
|
||||
if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) {
|
||||
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
|
||||
return _etoa(out, buffer, idx, maxlen, value, prec, width, flags);
|
||||
#else
|
||||
return 0U;
|
||||
#endif
|
||||
}
|
||||
|
||||
// test for negative
|
||||
bool negative = false;
|
||||
if (value < 0) {
|
||||
negative = true;
|
||||
value = 0 - value;
|
||||
}
|
||||
|
||||
// set default precision, if not set explicitly
|
||||
if (!(flags & FLAGS_PRECISION)) {
|
||||
prec = PRINTF_DEFAULT_FLOAT_PRECISION;
|
||||
}
|
||||
// limit precision to 9, cause a prec >= 10 can lead to overflow errors
|
||||
while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) {
|
||||
buf[len++] = '0';
|
||||
prec--;
|
||||
}
|
||||
|
||||
int whole = (int)value;
|
||||
double tmp = (value - whole) * pow10[prec];
|
||||
unsigned long frac = (unsigned long)tmp;
|
||||
diff = tmp - frac;
|
||||
|
||||
if (diff > 0.5) {
|
||||
++frac;
|
||||
// handle rollover, e.g. case 0.99 with prec 1 is 1.0
|
||||
if (frac >= pow10[prec]) {
|
||||
frac = 0;
|
||||
++whole;
|
||||
}
|
||||
}
|
||||
else if (diff < 0.5) {
|
||||
}
|
||||
else if ((frac == 0U) || (frac & 1U)) {
|
||||
// if halfway, round up if odd OR if last digit is 0
|
||||
++frac;
|
||||
}
|
||||
|
||||
if (prec == 0U) {
|
||||
diff = value - (double)whole;
|
||||
if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) {
|
||||
// exactly 0.5 and ODD, then round up
|
||||
// 1.5 -> 2, but 2.5 -> 2
|
||||
++whole;
|
||||
}
|
||||
}
|
||||
else {
|
||||
unsigned int count = prec;
|
||||
// now do fractional part, as an unsigned number
|
||||
while (len < PRINTF_FTOA_BUFFER_SIZE) {
|
||||
--count;
|
||||
buf[len++] = (char)(48U + (frac % 10U));
|
||||
if (!(frac /= 10U)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// add extra 0s
|
||||
while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) {
|
||||
buf[len++] = '0';
|
||||
}
|
||||
if (len < PRINTF_FTOA_BUFFER_SIZE) {
|
||||
// add decimal
|
||||
buf[len++] = '.';
|
||||
}
|
||||
}
|
||||
|
||||
// do whole part, number is reversed
|
||||
while (len < PRINTF_FTOA_BUFFER_SIZE) {
|
||||
buf[len++] = (char)(48 + (whole % 10));
|
||||
if (!(whole /= 10)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// pad leading zeros
|
||||
if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) {
|
||||
if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
|
||||
width--;
|
||||
}
|
||||
while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) {
|
||||
buf[len++] = '0';
|
||||
}
|
||||
}
|
||||
|
||||
if (len < PRINTF_FTOA_BUFFER_SIZE) {
|
||||
if (negative) {
|
||||
buf[len++] = '-';
|
||||
}
|
||||
else if (flags & FLAGS_PLUS) {
|
||||
buf[len++] = '+'; // ignore the space if the '+' exists
|
||||
}
|
||||
else if (flags & FLAGS_SPACE) {
|
||||
buf[len++] = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
|
||||
}
|
||||
|
||||
|
||||
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
|
||||
// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse <m.jasperse@gmail.com>
|
||||
static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
|
||||
{
|
||||
// check for NaN and special values
|
||||
if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) {
|
||||
return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags);
|
||||
}
|
||||
|
||||
// determine the sign
|
||||
const bool negative = value < 0;
|
||||
if (negative) {
|
||||
value = -value;
|
||||
}
|
||||
|
||||
// default precision
|
||||
if (!(flags & FLAGS_PRECISION)) {
|
||||
prec = PRINTF_DEFAULT_FLOAT_PRECISION;
|
||||
}
|
||||
|
||||
// determine the decimal exponent
|
||||
// based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c)
|
||||
union {
|
||||
uint64_t U;
|
||||
double F;
|
||||
} conv;
|
||||
|
||||
conv.F = value;
|
||||
int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2
|
||||
conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2)
|
||||
// now approximate log10 from the log2 integer part and an expansion of ln around 1.5
|
||||
int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168);
|
||||
// now we want to compute 10^expval but we want to be sure it won't overflow
|
||||
exp2 = (int)(expval * 3.321928094887362 + 0.5);
|
||||
const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453;
|
||||
const double z2 = z * z;
|
||||
conv.U = (uint64_t)(exp2 + 1023) << 52U;
|
||||
// compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex
|
||||
conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14)))));
|
||||
// correct for rounding errors
|
||||
if (value < conv.F) {
|
||||
expval--;
|
||||
conv.F /= 10;
|
||||
}
|
||||
|
||||
// the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters
|
||||
unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U;
|
||||
|
||||
// in "%g" mode, "prec" is the number of *significant figures* not decimals
|
||||
if (flags & FLAGS_ADAPT_EXP) {
|
||||
// do we want to fall-back to "%f" mode?
|
||||
if ((value >= 1e-4) && (value < 1e6)) {
|
||||
if ((int)prec > expval) {
|
||||
prec = (unsigned)((int)prec - expval - 1);
|
||||
}
|
||||
else {
|
||||
prec = 0;
|
||||
}
|
||||
flags |= FLAGS_PRECISION; // make sure _ftoa respects precision
|
||||
// no characters in exponent
|
||||
minwidth = 0U;
|
||||
expval = 0;
|
||||
}
|
||||
else {
|
||||
// we use one sigfig for the whole part
|
||||
if ((prec > 0) && (flags & FLAGS_PRECISION)) {
|
||||
--prec;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// will everything fit?
|
||||
unsigned int fwidth = width;
|
||||
if (width > minwidth) {
|
||||
// we didn't fall-back so subtract the characters required for the exponent
|
||||
fwidth -= minwidth;
|
||||
} else {
|
||||
// not enough characters, so go back to default sizing
|
||||
fwidth = 0U;
|
||||
}
|
||||
if ((flags & FLAGS_LEFT) && minwidth) {
|
||||
// if we're padding on the right, DON'T pad the floating part
|
||||
fwidth = 0U;
|
||||
}
|
||||
|
||||
// rescale the float value
|
||||
if (expval) {
|
||||
value /= conv.F;
|
||||
}
|
||||
|
||||
// output the floating part
|
||||
const size_t start_idx = idx;
|
||||
idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP);
|
||||
|
||||
// output the exponent part
|
||||
if (minwidth) {
|
||||
// output the exponential symbol
|
||||
out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen);
|
||||
// output the exponent value
|
||||
idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS);
|
||||
// might need to right-pad spaces
|
||||
if (flags & FLAGS_LEFT) {
|
||||
while (idx - start_idx < width) out(' ', buffer, idx++, maxlen);
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
#endif // PRINTF_SUPPORT_EXPONENTIAL
|
||||
#endif // PRINTF_SUPPORT_FLOAT
|
||||
|
||||
|
||||
// internal vsnprintf
|
||||
static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va)
|
||||
{
|
||||
unsigned int flags, width, precision, n;
|
||||
size_t idx = 0U;
|
||||
|
||||
if (!buffer) {
|
||||
// use null output function
|
||||
out = _out_null;
|
||||
}
|
||||
|
||||
while (*format)
|
||||
{
|
||||
// format specifier? %[flags][width][.precision][length]
|
||||
if (*format != '%') {
|
||||
// no
|
||||
out(*format, buffer, idx++, maxlen);
|
||||
format++;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
// yes, evaluate it
|
||||
format++;
|
||||
}
|
||||
|
||||
// evaluate flags
|
||||
flags = 0U;
|
||||
do {
|
||||
switch (*format) {
|
||||
case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break;
|
||||
case '-': flags |= FLAGS_LEFT; format++; n = 1U; break;
|
||||
case '+': flags |= FLAGS_PLUS; format++; n = 1U; break;
|
||||
case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break;
|
||||
case '#': flags |= FLAGS_HASH; format++; n = 1U; break;
|
||||
default : n = 0U; break;
|
||||
}
|
||||
} while (n);
|
||||
|
||||
// evaluate width field
|
||||
width = 0U;
|
||||
if (_is_digit(*format)) {
|
||||
width = _atoi(&format);
|
||||
}
|
||||
else if (*format == '*') {
|
||||
const int w = va_arg(va, int);
|
||||
if (w < 0) {
|
||||
flags |= FLAGS_LEFT; // reverse padding
|
||||
width = (unsigned int)-w;
|
||||
}
|
||||
else {
|
||||
width = (unsigned int)w;
|
||||
}
|
||||
format++;
|
||||
}
|
||||
|
||||
// evaluate precision field
|
||||
precision = 0U;
|
||||
if (*format == '.') {
|
||||
flags |= FLAGS_PRECISION;
|
||||
format++;
|
||||
if (_is_digit(*format)) {
|
||||
precision = _atoi(&format);
|
||||
}
|
||||
else if (*format == '*') {
|
||||
const int prec = (int)va_arg(va, int);
|
||||
precision = prec > 0 ? (unsigned int)prec : 0U;
|
||||
format++;
|
||||
}
|
||||
}
|
||||
|
||||
// evaluate length field
|
||||
switch (*format) {
|
||||
case 'l' :
|
||||
flags |= FLAGS_LONG;
|
||||
format++;
|
||||
if (*format == 'l') {
|
||||
flags |= FLAGS_LONG_LONG;
|
||||
format++;
|
||||
}
|
||||
break;
|
||||
case 'h' :
|
||||
flags |= FLAGS_SHORT;
|
||||
format++;
|
||||
if (*format == 'h') {
|
||||
flags |= FLAGS_CHAR;
|
||||
format++;
|
||||
}
|
||||
break;
|
||||
#if defined(PRINTF_SUPPORT_PTRDIFF_T)
|
||||
case 't' :
|
||||
flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
|
||||
format++;
|
||||
break;
|
||||
#endif
|
||||
case 'j' :
|
||||
flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
|
||||
format++;
|
||||
break;
|
||||
case 'z' :
|
||||
flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
|
||||
format++;
|
||||
break;
|
||||
default :
|
||||
break;
|
||||
}
|
||||
|
||||
// evaluate specifier
|
||||
switch (*format) {
|
||||
case 'd' :
|
||||
case 'i' :
|
||||
case 'u' :
|
||||
case 'x' :
|
||||
case 'X' :
|
||||
case 'o' :
|
||||
case 'b' : {
|
||||
// set the base
|
||||
unsigned int base;
|
||||
if (*format == 'x' || *format == 'X') {
|
||||
base = 16U;
|
||||
}
|
||||
else if (*format == 'o') {
|
||||
base = 8U;
|
||||
}
|
||||
else if (*format == 'b') {
|
||||
base = 2U;
|
||||
}
|
||||
else {
|
||||
base = 10U;
|
||||
flags &= ~FLAGS_HASH; // no hash for dec format
|
||||
}
|
||||
// uppercase
|
||||
if (*format == 'X') {
|
||||
flags |= FLAGS_UPPERCASE;
|
||||
}
|
||||
|
||||
// no plus or space flag for u, x, X, o, b
|
||||
if ((*format != 'i') && (*format != 'd')) {
|
||||
flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
|
||||
}
|
||||
|
||||
// ignore '0' flag when precision is given
|
||||
if (flags & FLAGS_PRECISION) {
|
||||
flags &= ~FLAGS_ZEROPAD;
|
||||
}
|
||||
|
||||
// convert the integer
|
||||
if ((*format == 'i') || (*format == 'd')) {
|
||||
// signed
|
||||
if (flags & FLAGS_LONG_LONG) {
|
||||
#if defined(PRINTF_SUPPORT_LONG_LONG)
|
||||
const long long value = va_arg(va, long long);
|
||||
idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
|
||||
#endif
|
||||
}
|
||||
else if (flags & FLAGS_LONG) {
|
||||
const long value = va_arg(va, long);
|
||||
idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
|
||||
}
|
||||
else {
|
||||
const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int);
|
||||
idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// unsigned
|
||||
if (flags & FLAGS_LONG_LONG) {
|
||||
#if defined(PRINTF_SUPPORT_LONG_LONG)
|
||||
idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags);
|
||||
#endif
|
||||
}
|
||||
else if (flags & FLAGS_LONG) {
|
||||
idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags);
|
||||
}
|
||||
else {
|
||||
const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int);
|
||||
idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags);
|
||||
}
|
||||
}
|
||||
format++;
|
||||
break;
|
||||
}
|
||||
#if defined(PRINTF_SUPPORT_FLOAT)
|
||||
case 'f' :
|
||||
case 'F' :
|
||||
if (*format == 'F') flags |= FLAGS_UPPERCASE;
|
||||
idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
|
||||
format++;
|
||||
break;
|
||||
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
|
||||
case 'e':
|
||||
case 'E':
|
||||
case 'g':
|
||||
case 'G':
|
||||
if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP;
|
||||
if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE;
|
||||
idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
|
||||
format++;
|
||||
break;
|
||||
#endif // PRINTF_SUPPORT_EXPONENTIAL
|
||||
#endif // PRINTF_SUPPORT_FLOAT
|
||||
case 'c' : {
|
||||
unsigned int l = 1U;
|
||||
// pre padding
|
||||
if (!(flags & FLAGS_LEFT)) {
|
||||
while (l++ < width) {
|
||||
out(' ', buffer, idx++, maxlen);
|
||||
}
|
||||
}
|
||||
// char output
|
||||
out((char)va_arg(va, int), buffer, idx++, maxlen);
|
||||
// post padding
|
||||
if (flags & FLAGS_LEFT) {
|
||||
while (l++ < width) {
|
||||
out(' ', buffer, idx++, maxlen);
|
||||
}
|
||||
}
|
||||
format++;
|
||||
break;
|
||||
}
|
||||
|
||||
case 's' : {
|
||||
const char* p = va_arg(va, char*);
|
||||
unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1);
|
||||
// pre padding
|
||||
if (flags & FLAGS_PRECISION) {
|
||||
l = (l < precision ? l : precision);
|
||||
}
|
||||
if (!(flags & FLAGS_LEFT)) {
|
||||
while (l++ < width) {
|
||||
out(' ', buffer, idx++, maxlen);
|
||||
}
|
||||
}
|
||||
// string output
|
||||
while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) {
|
||||
out(*(p++), buffer, idx++, maxlen);
|
||||
}
|
||||
// post padding
|
||||
if (flags & FLAGS_LEFT) {
|
||||
while (l++ < width) {
|
||||
out(' ', buffer, idx++, maxlen);
|
||||
}
|
||||
}
|
||||
format++;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'p' : {
|
||||
width = sizeof(void*) * 2U;
|
||||
flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE;
|
||||
#if defined(PRINTF_SUPPORT_LONG_LONG)
|
||||
const bool is_ll = sizeof(uintptr_t) == sizeof(long long);
|
||||
if (is_ll) {
|
||||
idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags);
|
||||
}
|
||||
else {
|
||||
#endif
|
||||
idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags);
|
||||
#if defined(PRINTF_SUPPORT_LONG_LONG)
|
||||
}
|
||||
#endif
|
||||
format++;
|
||||
break;
|
||||
}
|
||||
|
||||
case '%' :
|
||||
out('%', buffer, idx++, maxlen);
|
||||
format++;
|
||||
break;
|
||||
|
||||
default :
|
||||
out(*format, buffer, idx++, maxlen);
|
||||
format++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// termination
|
||||
out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
|
||||
|
||||
// return written chars without terminating \0
|
||||
return (int)idx;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
int printf_(const char* format, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, format);
|
||||
char buffer[1];
|
||||
const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
|
||||
va_end(va);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
int sprintf_(char* buffer, const char* format, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, format);
|
||||
const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va);
|
||||
va_end(va);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
int snprintf_(char* buffer, size_t count, const char* format, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, format);
|
||||
const int ret = _vsnprintf(_out_buffer, buffer, count, format, va);
|
||||
va_end(va);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
int vprintf_(const char* format, va_list va)
|
||||
{
|
||||
char buffer[1];
|
||||
return _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
|
||||
}
|
||||
|
||||
|
||||
int vsnprintf_(char* buffer, size_t count, const char* format, va_list va)
|
||||
{
|
||||
return _vsnprintf(_out_buffer, buffer, count, format, va);
|
||||
}
|
||||
|
||||
|
||||
int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, format);
|
||||
const out_fct_wrap_type out_fct_wrap = { out, arg };
|
||||
const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va);
|
||||
va_end(va);
|
||||
return ret;
|
||||
}
|
117
plugos/forked/deno-sqlite/build/lib/printf.h
vendored
Normal file
117
plugos/forked/deno-sqlite/build/lib/printf.h
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// \author (c) Marco Paland (info@paland.com)
|
||||
// 2014-2019, PALANDesign Hannover, Germany
|
||||
//
|
||||
// \license The MIT License (MIT)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on
|
||||
// embedded systems with a very limited resources.
|
||||
// Use this instead of bloated standard/newlib printf.
|
||||
// These routines are thread safe and reentrant.
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _PRINTF_H_
|
||||
#define _PRINTF_H_
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* Output a character to a custom device like UART, used by the printf() function
|
||||
* This function is declared here only. You have to write your custom implementation somewhere
|
||||
* \param character Character to output
|
||||
*/
|
||||
void _putchar(char character);
|
||||
|
||||
|
||||
/**
|
||||
* Tiny printf implementation
|
||||
* You have to implement _putchar if you use printf()
|
||||
* To avoid conflicts with the regular printf() API it is overridden by macro defines
|
||||
* and internal underscore-appended functions like printf_() are used
|
||||
* \param format A string that specifies the format of the output
|
||||
* \return The number of characters that are written into the array, not counting the terminating null character
|
||||
*/
|
||||
#define printf printf_
|
||||
int printf_(const char* format, ...);
|
||||
|
||||
|
||||
/**
|
||||
* Tiny sprintf implementation
|
||||
* Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD!
|
||||
* \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output!
|
||||
* \param format A string that specifies the format of the output
|
||||
* \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
|
||||
*/
|
||||
#define sprintf sprintf_
|
||||
int sprintf_(char* buffer, const char* format, ...);
|
||||
|
||||
|
||||
/**
|
||||
* Tiny snprintf/vsnprintf implementation
|
||||
* \param buffer A pointer to the buffer where to store the formatted string
|
||||
* \param count The maximum number of characters to store in the buffer, including a terminating null character
|
||||
* \param format A string that specifies the format of the output
|
||||
* \param va A value identifying a variable arguments list
|
||||
* \return The number of characters that COULD have been written into the buffer, not counting the terminating
|
||||
* null character. A value equal or larger than count indicates truncation. Only when the returned value
|
||||
* is non-negative and less than count, the string has been completely written.
|
||||
*/
|
||||
#define snprintf snprintf_
|
||||
#define vsnprintf vsnprintf_
|
||||
int snprintf_(char* buffer, size_t count, const char* format, ...);
|
||||
int vsnprintf_(char* buffer, size_t count, const char* format, va_list va);
|
||||
|
||||
|
||||
/**
|
||||
* Tiny vprintf implementation
|
||||
* \param format A string that specifies the format of the output
|
||||
* \param va A value identifying a variable arguments list
|
||||
* \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
|
||||
*/
|
||||
#define vprintf vprintf_
|
||||
int vprintf_(const char* format, va_list va);
|
||||
|
||||
|
||||
/**
|
||||
* printf with output function
|
||||
* You may use this as dynamic alternative to printf() with its fixed _putchar() output
|
||||
* \param out An output function which takes one character and an argument pointer
|
||||
* \param arg An argument pointer for user data passed to output function
|
||||
* \param format A string that specifies the format of the output
|
||||
* \return The number of characters that are sent to the output function, not counting the terminating null character
|
||||
*/
|
||||
int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#endif // _PRINTF_H_
|
241619
plugos/forked/deno-sqlite/build/lib/sqlite3.c
vendored
Normal file
241619
plugos/forked/deno-sqlite/build/lib/sqlite3.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
12836
plugos/forked/deno-sqlite/build/lib/sqlite3.h
vendored
Normal file
12836
plugos/forked/deno-sqlite/build/lib/sqlite3.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
7
plugos/forked/deno-sqlite/build/size.txt
Normal file
7
plugos/forked/deno-sqlite/build/size.txt
Normal file
@ -0,0 +1,7 @@
|
||||
sqlite.d.ts ➜ 2213 bytes
|
||||
sqlite.js ➜1116045 bytes
|
||||
sqlite.js.br ➜ 374501 bytes
|
||||
sqlite.js.gz ➜ 457972 bytes
|
||||
sqlite.wasm ➜ 836080 bytes
|
||||
sqlite.wasm.br ➜ 299791 bytes
|
||||
sqlite.wasm.gz ➜ 352828 bytes
|
59
plugos/forked/deno-sqlite/build/sqlite.d.ts
vendored
Normal file
59
plugos/forked/deno-sqlite/build/sqlite.d.ts
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
/* This file is automatically generated. Do not edit directly. */
|
||||
|
||||
export type VoidPtr = number;
|
||||
export type StringPtr = number;
|
||||
export type StatementPtr = number;
|
||||
|
||||
export interface Wasm {
|
||||
memory: WebAssembly.Memory;
|
||||
|
||||
malloc: (size: number) => VoidPtr;
|
||||
free: (ptr: VoidPtr) => void;
|
||||
str_len: (str: StringPtr) => number;
|
||||
seed_rng: (seed: number) => void;
|
||||
get_status: () => number;
|
||||
open: (filename: StringPtr, flags: number) => number;
|
||||
close: () => number;
|
||||
get_sqlite_error_str: () => StringPtr;
|
||||
prepare: (sql: StringPtr) => StatementPtr;
|
||||
finalize: (stmt: StatementPtr) => number;
|
||||
reset: (stmt: StatementPtr) => number;
|
||||
clear_bindings: (stmt: StatementPtr) => number;
|
||||
exec: (sql: StringPtr) => number;
|
||||
bind_int: (stmt: StatementPtr, idx: number, value: number) => number;
|
||||
bind_double: (stmt: StatementPtr, idx: number, value: number) => number;
|
||||
bind_text: (stmt: StatementPtr, idx: number, value: StringPtr) => number;
|
||||
bind_blob: (
|
||||
stmt: StatementPtr,
|
||||
idx: number,
|
||||
value: VoidPtr,
|
||||
size: number,
|
||||
) => number;
|
||||
bind_big_int: (
|
||||
stmt: StatementPtr,
|
||||
idx: number,
|
||||
sign: number,
|
||||
high: number,
|
||||
low: number,
|
||||
) => number;
|
||||
bind_null: (stmt: StatementPtr, idx: number) => number;
|
||||
bind_parameter_index: (stmt: StatementPtr, name: StringPtr) => number;
|
||||
step: (stmt: StatementPtr) => number;
|
||||
column_count: (stmt: StatementPtr) => number;
|
||||
column_type: (stmt: StatementPtr, col: number) => number;
|
||||
column_int: (stmt: StatementPtr, col: number) => number;
|
||||
column_double: (stmt: StatementPtr, col: number) => number;
|
||||
column_text: (stmt: StatementPtr, col: number) => StringPtr;
|
||||
column_blob: (stmt: StatementPtr, col: number) => VoidPtr;
|
||||
column_bytes: (stmt: StatementPtr, col: number) => number;
|
||||
column_name: (stmt: StatementPtr, col: number) => StringPtr;
|
||||
column_origin_name: (stmt: StatementPtr, col: number) => StringPtr;
|
||||
column_table_name: (stmt: StatementPtr, col: number) => StringPtr;
|
||||
last_insert_rowid: () => number;
|
||||
changes: () => number;
|
||||
total_changes: () => number;
|
||||
}
|
||||
|
||||
export function compile(): Promise<void>;
|
||||
export function instantiateBrowser(): Promise<void>;
|
||||
export function instantiate(): { exports: Wasm };
|
51
plugos/forked/deno-sqlite/build/sqlite.js
Normal file
51
plugos/forked/deno-sqlite/build/sqlite.js
Normal file
File diff suppressed because one or more lines are too long
BIN
plugos/forked/deno-sqlite/build/sqlite.wasm
Executable file
BIN
plugos/forked/deno-sqlite/build/sqlite.wasm
Executable file
Binary file not shown.
27
plugos/forked/deno-sqlite/build/src/debug.h
Normal file
27
plugos/forked/deno-sqlite/build/src/debug.h
Normal file
@ -0,0 +1,27 @@
|
||||
#ifndef DEBUG_H
|
||||
#define DEBUG_H
|
||||
#ifdef DEBUG_BUILD
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <printf.h>
|
||||
#include "imports.h"
|
||||
|
||||
// Print debug messages
|
||||
#define debug_printf(...) { \
|
||||
char* __debug_msg = malloc(2048); \
|
||||
if (!__debug_msg) { \
|
||||
js_print("ERROR: No memory for debug message.\n"); \
|
||||
} else { \
|
||||
size_t __used = snprintf(__debug_msg, 2048, "DEBUG: %s:%d:%s(): ", __FILE__, __LINE__, __func__); \
|
||||
snprintf(&__debug_msg[__used], 2048 - __used, __VA_ARGS__); \
|
||||
js_print(__debug_msg); \
|
||||
free(__debug_msg); \
|
||||
} \
|
||||
}
|
||||
|
||||
#else // DEBUG_BUILD
|
||||
|
||||
#define debug_printf(...)
|
||||
|
||||
#endif // DEBUG_BUILD
|
||||
#endif // DEBUG_H
|
22
plugos/forked/deno-sqlite/build/src/imports.h
Normal file
22
plugos/forked/deno-sqlite/build/src/imports.h
Normal file
@ -0,0 +1,22 @@
|
||||
#ifndef IMPORTS_H
|
||||
#define IMPORTS_H
|
||||
|
||||
// WASM imports specified in vfs.syms
|
||||
|
||||
extern void js_print(const char*);
|
||||
extern int js_open(const char*, int, int);
|
||||
extern void js_close(int);
|
||||
extern void js_delete(const char*);
|
||||
extern int js_read(int, const char*, double, int);
|
||||
extern int js_write(int, const char*, double, int);
|
||||
extern void js_truncate(int, double);
|
||||
extern void js_sync(int);
|
||||
extern double js_size(int);
|
||||
extern void js_lock(int, int);
|
||||
extern void js_unlock(int);
|
||||
extern double js_time();
|
||||
extern int js_timezone();
|
||||
extern int js_exists(const char*);
|
||||
extern int js_access(const char*);
|
||||
|
||||
#endif // DEBUG_H
|
302
plugos/forked/deno-sqlite/build/src/vfs.c
Normal file
302
plugos/forked/deno-sqlite/build/src/vfs.c
Normal file
@ -0,0 +1,302 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <sqlite3.h>
|
||||
#include <pcg.h>
|
||||
#include "debug.h"
|
||||
#include "imports.h"
|
||||
|
||||
// SQLite VFS component.
|
||||
// Based on demoVFS from SQLlite.
|
||||
// https://www.sqlite.org/src/doc/trunk/src/test_demovfs.c
|
||||
|
||||
#define MAXPATHNAME 1024
|
||||
#define JS_MAX_SAFE_INTEGER 9007199254740991
|
||||
|
||||
// When using this VFS, the sqlite3_file* handles that SQLite uses are
|
||||
// actually pointers to instances of type DenoFile.
|
||||
typedef struct DenoFile DenoFile;
|
||||
struct DenoFile {
|
||||
sqlite3_file base;
|
||||
// Deno file resource id
|
||||
int rid;
|
||||
};
|
||||
|
||||
static int denoClose(sqlite3_file *pFile) {
|
||||
DenoFile* p = (DenoFile*)pFile;
|
||||
js_close(p->rid);
|
||||
debug_printf("closing file (rid %i)\n", p->rid);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
// Read data from a file.
|
||||
static int denoRead(sqlite3_file *pFile, void *zBuf, int iAmt, sqlite_int64 iOfst) {
|
||||
DenoFile *p = (DenoFile*)pFile;
|
||||
|
||||
int read_bytes = 0;
|
||||
|
||||
if (iOfst <= JS_MAX_SAFE_INTEGER) {
|
||||
// Read bytes from buffer
|
||||
read_bytes = js_read(p->rid, (char*)zBuf, (double)iOfst, iAmt);
|
||||
debug_printf("attempt to read from file (rid %i, amount %i, offset %lli, read %i)\n",
|
||||
p->rid, iAmt, iOfst, read_bytes);
|
||||
} else {
|
||||
debug_printf("read offset %lli overflows JS_MAX_SAFE_INTEGER\n", iOfst);
|
||||
}
|
||||
|
||||
// Zero memory if read was short
|
||||
if (read_bytes < iAmt)
|
||||
memset(&((char*)zBuf)[read_bytes], 0, iAmt-read_bytes);
|
||||
|
||||
return read_bytes < iAmt ? SQLITE_IOERR_SHORT_READ : SQLITE_OK;
|
||||
}
|
||||
|
||||
// Write data to a file.
|
||||
static int denoWrite(sqlite3_file *pFile, const void *zBuf, int iAmt, sqlite_int64 iOfst) {
|
||||
DenoFile *p = (DenoFile*)pFile;
|
||||
|
||||
int write_bytes = 0;
|
||||
|
||||
if (iOfst <= JS_MAX_SAFE_INTEGER) {
|
||||
// Write bytes to buffer
|
||||
write_bytes = js_write(p->rid, (char*)zBuf, (double)iOfst, iAmt);
|
||||
debug_printf("attempt to write to file (rid %i, amount %i, offset %lli, written %i)\n",
|
||||
p->rid, iAmt, iOfst, write_bytes);
|
||||
} else {
|
||||
debug_printf("write offset %lli overflows JS_MAX_SAFE_INTEGER\n", iOfst);
|
||||
}
|
||||
|
||||
return write_bytes == iAmt ? SQLITE_OK : SQLITE_IOERR_WRITE;
|
||||
}
|
||||
|
||||
// Truncate file.
|
||||
static int denoTruncate(sqlite3_file *pFile, sqlite_int64 size) {
|
||||
DenoFile *p = (DenoFile*)pFile;
|
||||
if (size <= JS_MAX_SAFE_INTEGER) {
|
||||
js_truncate(p->rid, (double)size);
|
||||
debug_printf("truncating file (rid %i, size: %lli)\n", p->rid, size);
|
||||
return SQLITE_OK;
|
||||
} else {
|
||||
debug_printf("truncate length %lli overflows JS_MAX_SAFE_INTEGER\n", size);
|
||||
return SQLITE_IOERR;
|
||||
}
|
||||
}
|
||||
|
||||
// Deno provides no explicit sync for us, so we
|
||||
// just have a no-op here.
|
||||
// TODO(dyedgreen): Investigate if there is a better way
|
||||
static int denoSync(sqlite3_file *pFile, int flags) {
|
||||
DenoFile *p = (DenoFile*)pFile;
|
||||
js_sync(p->rid);
|
||||
debug_printf("syncing file (rid %i)\n", p->rid);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
// Write the size of the file in bytes to *pSize.
|
||||
static int denoFileSize(sqlite3_file *pFile, sqlite_int64 *pSize) {
|
||||
DenoFile *p = (DenoFile*)pFile;
|
||||
*pSize = (sqlite_int64)js_size(p->rid);
|
||||
debug_printf("read file size: %lli (rid %i)\n", *pSize, p->rid);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
// File locking
|
||||
static int denoLock(sqlite3_file *pFile, int eLock) {
|
||||
DenoFile *p = (DenoFile*)pFile;
|
||||
switch (eLock) {
|
||||
case SQLITE_LOCK_NONE:
|
||||
// no op
|
||||
break;
|
||||
case SQLITE_LOCK_SHARED:
|
||||
case SQLITE_LOCK_RESERVED: // one WASM process <-> one open database
|
||||
js_lock(p->rid, 0);
|
||||
break;
|
||||
case SQLITE_LOCK_PENDING:
|
||||
case SQLITE_LOCK_EXCLUSIVE:
|
||||
js_lock(p->rid, 1);
|
||||
break;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
static int denoUnlock(sqlite3_file *pFile, int eLock) {
|
||||
DenoFile *p = (DenoFile*)pFile;
|
||||
switch (eLock) {
|
||||
case SQLITE_LOCK_NONE:
|
||||
// no op
|
||||
break;
|
||||
case SQLITE_LOCK_SHARED:
|
||||
case SQLITE_LOCK_RESERVED:
|
||||
case SQLITE_LOCK_PENDING:
|
||||
case SQLITE_LOCK_EXCLUSIVE:
|
||||
js_unlock(p->rid);
|
||||
break;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
static int denoCheckReservedLock(sqlite3_file *pFile, int *pResOut) {
|
||||
*pResOut = 0;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
// No xFileControl() verbs are implemented by this VFS.
|
||||
static int denoFileControl(sqlite3_file *pFile, int op, void *pArg) {
|
||||
return SQLITE_NOTFOUND;
|
||||
}
|
||||
|
||||
// TODO(dyedgreen): Should we try to get these?
|
||||
static int denoSectorSize(sqlite3_file *pFile) {
|
||||
return 0;
|
||||
}
|
||||
static int denoDeviceCharacteristics(sqlite3_file *pFile) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Open a file handle.
|
||||
static int denoOpen(
|
||||
sqlite3_vfs *pVfs, /* VFS */
|
||||
const char *zName, /* File to open, or 0 for a temp file */
|
||||
sqlite3_file *pFile, /* Pointer to DenoFile struct to populate */
|
||||
int flags, /* Input SQLITE_OPEN_XXX flags */
|
||||
int *pOutFlags /* Output SQLITE_OPEN_XXX flags (or NULL) */
|
||||
) {
|
||||
static const sqlite3_io_methods denoio = {
|
||||
1, /* iVersion */
|
||||
denoClose, /* xClose */
|
||||
denoRead, /* xRead */
|
||||
denoWrite, /* xWrite */
|
||||
denoTruncate, /* xTruncate */
|
||||
denoSync, /* xSync */
|
||||
denoFileSize, /* xFileSize */
|
||||
denoLock, /* xLock */
|
||||
denoUnlock, /* xUnlock */
|
||||
denoCheckReservedLock, /* xCheckReservedLock */
|
||||
denoFileControl, /* xFileControl */
|
||||
denoSectorSize, /* xSectorSize */
|
||||
denoDeviceCharacteristics /* xDeviceCharacteristics */
|
||||
};
|
||||
|
||||
DenoFile *p = (DenoFile*)pFile;
|
||||
p->base.pMethods = &denoio;
|
||||
|
||||
// TODO(dyedgreen): The current approach is to raise
|
||||
// the permission error on the vfs.js side of things,
|
||||
// should the error be propagates through the wrapper
|
||||
// and be raised on the wrapper side of things?
|
||||
p->rid = js_open(zName, zName ? 0 : 1, flags);
|
||||
|
||||
if (pOutFlags) {
|
||||
*pOutFlags = flags;
|
||||
}
|
||||
|
||||
debug_printf("opened file (rid %i)\n", p->rid);
|
||||
debug_printf("file path name: '%s'\n", zName);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
// Delete the file at the path.
|
||||
static int denoDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync) {
|
||||
js_delete(zPath);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
// All valid id files are accessible.
|
||||
static int denoAccess(sqlite3_vfs *pVfs, const char *zPath, int flags, int *pResOut) {
|
||||
switch (flags) {
|
||||
case SQLITE_ACCESS_EXISTS:
|
||||
*pResOut = js_exists(zPath);
|
||||
break;
|
||||
default:
|
||||
*pResOut = js_access(zPath);
|
||||
break;
|
||||
}
|
||||
debug_printf("determining file access (path %s, access %i)\n", zPath, *pResOut);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
// TODO(dyedgreen): Actually resolve the full path name
|
||||
static int denoFullPathname(sqlite3_vfs *pVfs, const char *zPath, int nPathOut, char *zPathOut) {
|
||||
sqlite3_snprintf(nPathOut, zPathOut, "%s", zPath);
|
||||
debug_printf("requesting full path name for path: %s\n", zPath);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
// We don't support shared objects
|
||||
static void *denoDlOpen(sqlite3_vfs *pVfs, const char *zPath) {
|
||||
return 0;
|
||||
}
|
||||
static void denoDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg) {
|
||||
sqlite3_snprintf(nByte, zErrMsg, "Loadable extensions are not supported");
|
||||
zErrMsg[nByte-1] = '\0';
|
||||
}
|
||||
static void (*denoDlSym(sqlite3_vfs *pVfs, void *pH, const char *z))(void) {
|
||||
return 0;
|
||||
}
|
||||
static void denoDlClose(sqlite3_vfs *pVfs, void *pHandle) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate pseudo-random data
|
||||
static int denoRandomness(sqlite3_vfs *pVfs, int nByte, char *zByte) {
|
||||
pcg_bytes(zByte, nByte);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
// TODO(dyedgreen): Can anything be done here?
|
||||
static int denoSleep(sqlite3_vfs *pVfs, int nMicro) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Retrieve the current time
|
||||
static int denoCurrentTime(sqlite3_vfs *pVfs, double *pTime) {
|
||||
*pTime = js_time() / 1000 / 86400.0 + 2440587.5;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
// Implement localtime_r
|
||||
struct tm* localtime_r(const time_t *time, struct tm *result) {
|
||||
debug_printf("running localtime_r");
|
||||
time_t shifted = *time - 60 * js_timezone();
|
||||
return gmtime_r(&shifted, result);
|
||||
}
|
||||
|
||||
// This function returns a pointer to the VFS implemented in this file.
|
||||
sqlite3_vfs *sqlite3_denovfs(void) {
|
||||
static sqlite3_vfs denovfs = {
|
||||
3, /* iVersion */
|
||||
sizeof(DenoFile), /* szOsFile */
|
||||
MAXPATHNAME, /* mxPathname */
|
||||
0, /* pNext */
|
||||
"deno", /* zName */
|
||||
0, /* pAppData */
|
||||
denoOpen, /* xOpen */
|
||||
denoDelete, /* xDelete */
|
||||
denoAccess, /* xAccess */
|
||||
denoFullPathname, /* xFullPathname */
|
||||
denoDlOpen, /* xDlOpen */
|
||||
denoDlError, /* xDlError */
|
||||
denoDlSym, /* xDlSym */
|
||||
denoDlClose, /* xDlClose */
|
||||
denoRandomness, /* xRandomness */
|
||||
denoSleep, /* xSleep */
|
||||
denoCurrentTime, /* xCurrentTime */
|
||||
0, /* xGetLastError */
|
||||
0, /* xCurrentTimeInt64 */
|
||||
0, /* xSetSystemCall */
|
||||
0, /* xGetSystemCall */
|
||||
0, /* xNextSystemCall */
|
||||
};
|
||||
return &denovfs;
|
||||
}
|
||||
|
||||
int sqlite3_os_init(void) {
|
||||
debug_printf("running sqlite3_os_init\n");
|
||||
// Register VFS
|
||||
return sqlite3_vfs_register(sqlite3_denovfs(), 1);
|
||||
}
|
||||
|
||||
int sqlite3_os_end(void) {
|
||||
return SQLITE_OK;
|
||||
}
|
247
plugos/forked/deno-sqlite/build/src/wrapper.c
Normal file
247
plugos/forked/deno-sqlite/build/src/wrapper.c
Normal file
@ -0,0 +1,247 @@
|
||||
#include <stdlib.h>
|
||||
#include <sqlite3.h>
|
||||
#include <pcg.h>
|
||||
#include "debug.h"
|
||||
|
||||
#define EXPORT(name) __attribute__((used)) __attribute__((export_name (#name))) name
|
||||
#define ERROR_VAL -1
|
||||
|
||||
#define BIG_INT_TYPE 6
|
||||
#define JS_MAX_SAFE_INTEGER 9007199254740991
|
||||
#define JS_MIN_SAFE_INTEGER (-JS_MAX_SAFE_INTEGER)
|
||||
|
||||
// Status returned by last instruction
|
||||
int last_status = SQLITE_OK;
|
||||
// Database handle for this instance
|
||||
sqlite3* database = NULL;
|
||||
|
||||
// Return length of string pointed to by str.
|
||||
int EXPORT(str_len) (const char* str) {
|
||||
int len;
|
||||
for (len = 0; str[len] != '\0'; len ++);
|
||||
return len;
|
||||
}
|
||||
|
||||
// Seed the random number generator. We pass a double, to
|
||||
// get as many bytes from the JS number as possible.
|
||||
void EXPORT(seed_rng) (double seed) {
|
||||
pcg_seed((uint64_t)seed);
|
||||
}
|
||||
|
||||
// Return last status encountered.
|
||||
int EXPORT(get_status) () {
|
||||
return last_status;
|
||||
}
|
||||
|
||||
// Initialize the database and return the status.
|
||||
int EXPORT(open) (const char* filename, int flags) {
|
||||
// Return error is database is already open
|
||||
if (database) {
|
||||
last_status = SQLITE_MISUSE;
|
||||
return last_status;
|
||||
}
|
||||
|
||||
// Open SQLite db connection
|
||||
last_status = sqlite3_open_v2(filename, &database, flags, NULL);
|
||||
if (last_status != SQLITE_OK) {
|
||||
debug_printf("failed to open database with status %i\n", last_status);
|
||||
return last_status;
|
||||
}
|
||||
debug_printf("opened database at path '%s'\n", filename);
|
||||
return last_status;
|
||||
}
|
||||
|
||||
// Attempt to close the database connection.
|
||||
int EXPORT(close) () {
|
||||
last_status = sqlite3_close(database);
|
||||
if (last_status == SQLITE_OK) {
|
||||
database = NULL;
|
||||
debug_printf("closed database");
|
||||
} else {
|
||||
debug_printf("failed to close database with status %i\n", last_status);
|
||||
}
|
||||
return last_status;
|
||||
}
|
||||
|
||||
// Return most recent SQLite error as a string
|
||||
const char* EXPORT(get_sqlite_error_str) () {
|
||||
if (!database)
|
||||
return "No open database.";
|
||||
return sqlite3_errmsg(database);
|
||||
}
|
||||
|
||||
// Wraps sqlite3_prepare. Returns statement id.
|
||||
sqlite3_stmt* EXPORT(prepare) (const char* sql) {
|
||||
// Prepare sqlite statement
|
||||
sqlite3_stmt* stmt;
|
||||
last_status = sqlite3_prepare_v2(database, sql, -1, &stmt, NULL);
|
||||
debug_printf("prepared sql statement (status %i)\n", last_status);
|
||||
|
||||
if (last_status != SQLITE_OK)
|
||||
return NULL;
|
||||
return stmt;
|
||||
}
|
||||
|
||||
// Destruct the given statement/ transaction. This will destruct the SQLite
|
||||
// statement and free up it's transaction slot. Regardless of returned
|
||||
// status, the statement id will be freed up.
|
||||
int EXPORT(finalize) (sqlite3_stmt* stmt) {
|
||||
last_status = sqlite3_finalize(stmt);
|
||||
debug_printf("finalized statement (status %i)\n", last_status);
|
||||
return last_status;
|
||||
}
|
||||
|
||||
// Reset a given statement so it can be re-used.
|
||||
int EXPORT(reset) (sqlite3_stmt* stmt) {
|
||||
last_status = sqlite3_reset(stmt);
|
||||
debug_printf("reset statement (status %i)\n", last_status);
|
||||
return last_status;
|
||||
}
|
||||
|
||||
// Resets all bound parameter values for this statement.
|
||||
int EXPORT(clear_bindings) (sqlite3_stmt* stmt) {
|
||||
last_status = sqlite3_clear_bindings(stmt);
|
||||
debug_printf("clear bindings (status %i)\n", last_status);
|
||||
return last_status;
|
||||
}
|
||||
|
||||
// Execute multiple statements from a single string. This ignores any result
|
||||
// rows.
|
||||
int EXPORT(exec) (const char* sql) {
|
||||
last_status = sqlite3_exec(database, sql, NULL, NULL, NULL);
|
||||
debug_printf("ran exec (status %i)\n", last_status);
|
||||
return last_status;
|
||||
}
|
||||
|
||||
// Wrappers for bind statements, these return the status directly
|
||||
int EXPORT(bind_int) (sqlite3_stmt* stmt, int idx, double value) {
|
||||
// we use double to pass in the value, as JS does not support 64 bit integers,
|
||||
// but handles floats and we can contain a 32 bit in in a 64 bit float, so there
|
||||
// should be no loss.
|
||||
last_status = sqlite3_bind_int64(stmt, idx, (sqlite3_int64)value);
|
||||
debug_printf("binding int %lli (status %i)\n", (sqlite3_int64)value, last_status);
|
||||
return last_status;
|
||||
}
|
||||
|
||||
int EXPORT(bind_double) (sqlite3_stmt* stmt, int idx, double value) {
|
||||
last_status = sqlite3_bind_double(stmt, idx, value);
|
||||
debug_printf("binding double %f (status %i)\n", value, last_status);
|
||||
return last_status;
|
||||
}
|
||||
|
||||
int EXPORT(bind_text) (sqlite3_stmt* stmt, int idx, const char* value) {
|
||||
// SQLite retrains the string until we execute the statement, but any strings
|
||||
// passed in from JS are freed when the function returns. Thus we need to mark
|
||||
// is as transient.
|
||||
last_status = sqlite3_bind_text(stmt, idx, value, -1, SQLITE_TRANSIENT);
|
||||
debug_printf("binding text '%s' (status %i)\n", value, last_status);
|
||||
return last_status;
|
||||
}
|
||||
|
||||
int EXPORT(bind_blob) (sqlite3_stmt* stmt, int idx, void* value, int size) {
|
||||
// SQLite retrains the pointer until we execute the statement, but any pointers
|
||||
// passed in from JS are freed when the function returns. Thus we need to mark
|
||||
// is as transient.
|
||||
last_status = sqlite3_bind_blob(stmt, idx, value, size, SQLITE_TRANSIENT);
|
||||
debug_printf("binding blob '%s' (status %i)\n", value, last_status);
|
||||
return last_status;
|
||||
}
|
||||
|
||||
int EXPORT(bind_big_int) (sqlite3_stmt* stmt, int idx, int sign, uint32_t high, uint32_t low) {
|
||||
// Bind a big integer within the 64 bit integer range by passing it as two 32
|
||||
// bit integers. The integers are assumed to be positive, and a sign is passed
|
||||
// separately.
|
||||
sqlite3_int64 int_val = ((sqlite3_int64)low + ((sqlite3_int64)high << 32)) * (sqlite3_int64)sign;
|
||||
debug_printf("binding big_int %lld", int_val);
|
||||
last_status = sqlite3_bind_int64(stmt, idx, int_val);
|
||||
return last_status;
|
||||
}
|
||||
|
||||
int EXPORT(bind_null) (sqlite3_stmt* stmt, int idx) {
|
||||
last_status = sqlite3_bind_null(stmt, idx);
|
||||
debug_printf("binding null (status %i)\n", last_status);
|
||||
return last_status;
|
||||
}
|
||||
|
||||
// Determine parameter index for named parameters
|
||||
int EXPORT(bind_parameter_index) (sqlite3_stmt* stmt, const char* name) {
|
||||
int index = sqlite3_bind_parameter_index(stmt, name);
|
||||
if (index == 0) {
|
||||
debug_printf("parameter '%s' does not exist", name);
|
||||
// Normalize SQLite returning 0 for not found to ERROR_VAL
|
||||
return ERROR_VAL;
|
||||
}
|
||||
debug_printf("obtained parameter index (param '%s', index %i)\n", name, index);
|
||||
return index;
|
||||
}
|
||||
|
||||
// Wraps running statements, this returns the status directly
|
||||
int EXPORT(step) (sqlite3_stmt* stmt) {
|
||||
last_status = sqlite3_step(stmt);
|
||||
debug_printf("stepping statement (status %i)\n", last_status);
|
||||
return last_status;
|
||||
}
|
||||
|
||||
// Count columns returned by statement.
|
||||
int EXPORT(column_count) (sqlite3_stmt* stmt) {
|
||||
return sqlite3_column_count(stmt);
|
||||
}
|
||||
|
||||
// Determine type of column. Returns SQLITE column types.
|
||||
int EXPORT(column_type) (sqlite3_stmt* stmt, int col) {
|
||||
int type = sqlite3_column_type(stmt, col);
|
||||
if (type == SQLITE_INTEGER) {
|
||||
// handle integers that exceed JS_MAX_SAFE_INTEGER
|
||||
sqlite3_int64 col_val = sqlite3_column_int64(stmt, col);
|
||||
if (col_val > JS_MAX_SAFE_INTEGER || col_val < JS_MIN_SAFE_INTEGER) {
|
||||
debug_printf("detected big integer: %lld\n", col_val);
|
||||
return BIG_INT_TYPE;
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
// Wrap result returning functions.
|
||||
double EXPORT(column_int) (sqlite3_stmt* stmt, int col) {
|
||||
return (double)sqlite3_column_int64(stmt, col);
|
||||
}
|
||||
|
||||
double EXPORT(column_double) (sqlite3_stmt* stmt, int col) {
|
||||
return sqlite3_column_double(stmt, col);
|
||||
}
|
||||
|
||||
const char* EXPORT(column_text) (sqlite3_stmt* stmt, int col) {
|
||||
return (const char*)sqlite3_column_text(stmt, col);
|
||||
}
|
||||
|
||||
const void* EXPORT(column_blob) (sqlite3_stmt* stmt, int col) {
|
||||
return sqlite3_column_blob(stmt, col);
|
||||
}
|
||||
|
||||
int EXPORT(column_bytes) (sqlite3_stmt* stmt, int col) {
|
||||
return sqlite3_column_bytes(stmt, col);
|
||||
}
|
||||
|
||||
const char* EXPORT(column_name) (sqlite3_stmt* stmt, int col) {
|
||||
return sqlite3_column_name(stmt, col);
|
||||
}
|
||||
|
||||
const char* EXPORT(column_origin_name) (sqlite3_stmt* stmt, int col) {
|
||||
return sqlite3_column_origin_name(stmt, col);
|
||||
}
|
||||
|
||||
const char* EXPORT(column_table_name) (sqlite3_stmt* stmt, int col) {
|
||||
return sqlite3_column_table_name(stmt, col);
|
||||
}
|
||||
|
||||
double EXPORT(last_insert_rowid) () {
|
||||
return (double)sqlite3_last_insert_rowid(database);
|
||||
}
|
||||
|
||||
double EXPORT(changes) () {
|
||||
return (double)sqlite3_changes(database);
|
||||
}
|
||||
|
||||
double EXPORT(total_changes) () {
|
||||
return (double)sqlite3_total_changes(database);
|
||||
}
|
120
plugos/forked/deno-sqlite/build/vfs.js
Normal file
120
plugos/forked/deno-sqlite/build/vfs.js
Normal file
@ -0,0 +1,120 @@
|
||||
import { getStr } from "../src/wasm.ts";
|
||||
|
||||
const isWindows = Deno.build.os === "windows";
|
||||
|
||||
// Closure to return an environment that links
|
||||
// the current wasm context
|
||||
export default function env(inst) {
|
||||
// Exported environment
|
||||
const env = {
|
||||
// Print a string pointer to console
|
||||
js_print: (str_ptr) => {
|
||||
const text = getStr(inst.exports, str_ptr);
|
||||
console.log(text[text.length - 1] === "\n" ? text.slice(0, -1) : text);
|
||||
},
|
||||
// Open the file at path, mode = 0 is open RW, mode = 1 is open TEMP
|
||||
js_open: (path_ptr, mode, flags) => {
|
||||
let path;
|
||||
switch (mode) {
|
||||
case 0:
|
||||
path = getStr(inst.exports, path_ptr);
|
||||
break;
|
||||
case 1:
|
||||
path = Deno.makeTempFileSync({ prefix: "deno_sqlite" });
|
||||
break;
|
||||
}
|
||||
|
||||
const write = !!(flags & 0x00000002);
|
||||
const create = !!(flags & 0x00000004);
|
||||
const rid = Deno.openSync(path, { read: true, write, create }).rid;
|
||||
return rid;
|
||||
},
|
||||
// Close a file
|
||||
js_close: (rid) => {
|
||||
Deno.close(rid);
|
||||
},
|
||||
// Delete file at path
|
||||
js_delete: (path_ptr) => {
|
||||
const path = getStr(inst.exports, path_ptr);
|
||||
Deno.removeSync(path);
|
||||
},
|
||||
// Read from a file to a buffer in the module
|
||||
js_read: (rid, buffer_ptr, offset, amount) => {
|
||||
const buffer = new Uint8Array(
|
||||
inst.exports.memory.buffer,
|
||||
buffer_ptr,
|
||||
amount,
|
||||
);
|
||||
Deno.seekSync(rid, offset, Deno.SeekMode.Start);
|
||||
return Deno.readSync(rid, buffer);
|
||||
},
|
||||
// Write to a file from a buffer in the module
|
||||
js_write: (rid, buffer_ptr, offset, amount) => {
|
||||
const buffer = new Uint8Array(
|
||||
inst.exports.memory.buffer,
|
||||
buffer_ptr,
|
||||
amount,
|
||||
);
|
||||
Deno.seekSync(rid, offset, Deno.SeekMode.Start);
|
||||
return Deno.writeSync(rid, buffer);
|
||||
},
|
||||
// Truncate the given file
|
||||
js_truncate: (rid, size) => {
|
||||
Deno.ftruncateSync(rid, size);
|
||||
},
|
||||
// Sync file data to disk
|
||||
js_sync: (rid) => {
|
||||
Deno.fdatasyncSync(rid);
|
||||
},
|
||||
// Retrieve the size of the given file
|
||||
js_size: (rid) => {
|
||||
return Deno.fstatSync(rid).size;
|
||||
},
|
||||
// Acquire a SHARED or EXCLUSIVE file lock
|
||||
js_lock: (rid, exclusive) => {
|
||||
// this is unstable and has issues on Windows ...
|
||||
if (Deno.flockSync && !isWindows) Deno.flockSync(rid, exclusive !== 0);
|
||||
},
|
||||
// Release a file lock
|
||||
js_unlock: (rid) => {
|
||||
// this is unstable and has issues on Windows ...
|
||||
if (Deno.funlockSync && !isWindows) Deno.funlockSync(rid);
|
||||
},
|
||||
// Return current time in ms since UNIX epoch
|
||||
js_time: () => {
|
||||
return Date.now();
|
||||
},
|
||||
// Return the timezone offset in minutes for
|
||||
// the current locale.
|
||||
js_timezone: () => {
|
||||
return (new Date()).getTimezoneOffset();
|
||||
},
|
||||
// Determine if a path exists
|
||||
js_exists: (path_ptr) => {
|
||||
const path = getStr(inst.exports, path_ptr);
|
||||
try {
|
||||
Deno.statSync(path);
|
||||
} catch (e) {
|
||||
if (e instanceof Deno.errors.NotFound) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
},
|
||||
// Determine if a path is accessible i.e. if it has read/write permissions
|
||||
// TODO(dyedgreen): Properly determine if there are read permissions
|
||||
js_access: (path_ptr) => {
|
||||
const path = getStr(inst.exports, path_ptr);
|
||||
try {
|
||||
Deno.statSync(path);
|
||||
} catch (e) {
|
||||
if (e instanceof Deno.errors.PermissionDenied) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
},
|
||||
};
|
||||
|
||||
return { env };
|
||||
}
|
15
plugos/forked/deno-sqlite/build/vfs.syms
Normal file
15
plugos/forked/deno-sqlite/build/vfs.syms
Normal file
@ -0,0 +1,15 @@
|
||||
js_print
|
||||
js_open
|
||||
js_close
|
||||
js_delete
|
||||
js_read
|
||||
js_write
|
||||
js_truncate
|
||||
js_sync
|
||||
js_size
|
||||
js_lock
|
||||
js_unlock
|
||||
js_time
|
||||
js_timezone
|
||||
js_exists
|
||||
js_access
|
79
plugos/forked/deno-sqlite/examples/cli.ts
Normal file
79
plugos/forked/deno-sqlite/examples/cli.ts
Normal file
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* cli.ts
|
||||
*
|
||||
* A simple clone of the sqlite3 command line
|
||||
* interface, build using deno-sqlite.
|
||||
*
|
||||
* This is an example, meant to illustrate using
|
||||
* the API provided by deno-sqlite.
|
||||
*/
|
||||
|
||||
import { readLines, writeAll } from "https://deno.land/std@0.134.0/io/mod.ts";
|
||||
import AsciiTable from "https://deno.land/x/ascii_table@v0.1.0/mod.ts";
|
||||
import { DB } from "../mod.ts";
|
||||
|
||||
const db = new DB(Deno.args[0] ?? undefined);
|
||||
|
||||
async function print(str: string) {
|
||||
const enc = new TextEncoder();
|
||||
await writeAll(Deno.stdout, enc.encode(str));
|
||||
}
|
||||
|
||||
async function prompt() {
|
||||
await print("sqlite> ");
|
||||
}
|
||||
|
||||
const tablesQuery = db.prepareQuery<[string]>(
|
||||
"SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'",
|
||||
);
|
||||
|
||||
const commands: Record<string, () => Promise<void>> = {
|
||||
"tables": async () => {
|
||||
for (const [name] of tablesQuery.iter()) {
|
||||
await print(`${name}\n`);
|
||||
}
|
||||
},
|
||||
"quit": async () => {
|
||||
await print("\n");
|
||||
Deno.exit(0);
|
||||
},
|
||||
"help": async () => {
|
||||
await print(
|
||||
"Type an SQL query or run a command.\nThe following commands are available:\n",
|
||||
);
|
||||
for (const key in commands) {
|
||||
await print(`.${key}\n`);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
await prompt();
|
||||
for await (const cmd of readLines(Deno.stdin)) {
|
||||
if (cmd[0] === ".") {
|
||||
const action = commands[cmd.slice(1)] ??
|
||||
(() => print("Unrecognized command, try .help\n"));
|
||||
await action();
|
||||
} else {
|
||||
try {
|
||||
const query = db.prepareQuery(cmd);
|
||||
const rows = query.all();
|
||||
const cols = query.columns();
|
||||
query.finalize();
|
||||
|
||||
if (cols.length) {
|
||||
const table = new AsciiTable();
|
||||
table.setHeading("#", ...cols.map(({ name }) => name));
|
||||
for (const [idx, row] of rows.entries()) {
|
||||
table.addRow(idx + 1, ...row);
|
||||
}
|
||||
print(table.toString());
|
||||
print("\n");
|
||||
} else {
|
||||
print(`Executed query: ${db.changes} changes\n`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
await prompt();
|
||||
}
|
57
plugos/forked/deno-sqlite/examples/notes.ts
Normal file
57
plugos/forked/deno-sqlite/examples/notes.ts
Normal file
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* notes.ts
|
||||
*
|
||||
* A command line tool to manage a set
|
||||
* of simple notes.
|
||||
*
|
||||
* This is an example, meant to illustrate using
|
||||
* the API provided by deno-sqlite.
|
||||
*/
|
||||
|
||||
import { DB } from "../mod.ts";
|
||||
|
||||
const commands: Record<string, (...args: string[]) => Promise<void> | void> = {
|
||||
"create": (file: string) => {
|
||||
const db = new DB(file, { mode: "create" });
|
||||
db.query(`
|
||||
CREATE TABLE notes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
note TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
)
|
||||
`);
|
||||
db.close();
|
||||
console.log("Database created!");
|
||||
},
|
||||
"record": (file: string, note: string) => {
|
||||
const db = new DB(file, { mode: "write" });
|
||||
db.query("INSERT INTO notes (note, created_at) VALUES (?, ?)", [
|
||||
note,
|
||||
new Date(),
|
||||
]);
|
||||
db.close();
|
||||
console.log("Note recorded!");
|
||||
},
|
||||
"delete": (file: string, noteId: string) => {
|
||||
const db = new DB(file, { mode: "write" });
|
||||
db.query("DELETE FROM notes WHERE id = ?", [noteId]);
|
||||
db.close();
|
||||
console.log("Note deleted!");
|
||||
},
|
||||
"list": (file: string) => {
|
||||
const db = new DB(file, { mode: "read" });
|
||||
const query = db.prepareQuery<[number, string, string]>(
|
||||
"SELECT id, note, created_at FROM notes ORDER BY created_at DESC",
|
||||
);
|
||||
for (const [id, note, createdAt] of query.iter()) {
|
||||
const date = new Date(createdAt);
|
||||
console.log(`Note #${id} (recorded ${date.toLocaleString()})\n${note}\n`);
|
||||
}
|
||||
query.finalize();
|
||||
db.close();
|
||||
},
|
||||
};
|
||||
|
||||
const command = commands[Deno.args[0]] ??
|
||||
(() => console.error(`Unknown command '${Deno.args[0]}'.`));
|
||||
await command(...Deno.args.slice(1));
|
42
plugos/forked/deno-sqlite/examples/server.ts
Normal file
42
plugos/forked/deno-sqlite/examples/server.ts
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* server.ts
|
||||
*
|
||||
* A server which returns the number
|
||||
* of hits to any given path since
|
||||
* the server started running.
|
||||
*
|
||||
* This is an example, meant to illustrate using
|
||||
* the API provided by deno-sqlite.
|
||||
*/
|
||||
|
||||
import { serve } from "https://deno.land/std@0.134.0/http/mod.ts";
|
||||
import { DB } from "../mod.ts";
|
||||
|
||||
const db = new DB();
|
||||
|
||||
db.query(`
|
||||
CREATE TABLE visits (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
url TEXT NOT NULL,
|
||||
visited_at TEXT NOT NULL
|
||||
)
|
||||
`);
|
||||
|
||||
const addVisitQuery = db.prepareQuery(
|
||||
"INSERT INTO visits (url, visited_at) VALUES (:url, :time)",
|
||||
);
|
||||
const countVisitsQuery = db.prepareQuery<[number]>(
|
||||
"SELECT COUNT(*) FROM visits WHERE url = :url",
|
||||
);
|
||||
|
||||
console.log("Running server on localhost:8080");
|
||||
|
||||
await serve((req) => {
|
||||
addVisitQuery.execute({
|
||||
url: req.url,
|
||||
time: new Date(),
|
||||
});
|
||||
|
||||
const [count] = countVisitsQuery.one({ url: req.url });
|
||||
return new Response(`This page was visited ${count} times!`);
|
||||
}, { port: 8080 });
|
16
plugos/forked/deno-sqlite/mod.ts
Normal file
16
plugos/forked/deno-sqlite/mod.ts
Normal file
@ -0,0 +1,16 @@
|
||||
export { DB } from "./src/db.ts";
|
||||
export { SqliteError } from "./src/error.ts";
|
||||
export { Status } from "./src/constants.ts";
|
||||
|
||||
export type { SqliteOptions } from "./src/db.ts";
|
||||
export type {
|
||||
ColumnName,
|
||||
PreparedQuery,
|
||||
QueryParameter,
|
||||
QueryParameterSet,
|
||||
Row,
|
||||
RowObject,
|
||||
} from "./src/query.ts";
|
||||
|
||||
import { compile } from "./build/sqlite.js";
|
||||
await compile();
|
63
plugos/forked/deno-sqlite/src/constants.ts
Normal file
63
plugos/forked/deno-sqlite/src/constants.ts
Normal file
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Status codes which can be returned
|
||||
* by SQLite.
|
||||
*
|
||||
* Also see https://www.sqlite.org/rescode.html.
|
||||
*/
|
||||
export enum Status {
|
||||
Unknown = -1, // Unknown status
|
||||
|
||||
SqliteOk = 0, // Successful result
|
||||
SqliteError = 1, // Generic error
|
||||
SqliteInternal = 2, // Internal logic error in SQLite
|
||||
SqlitePerm = 3, // Access permission denied
|
||||
SqliteAbort = 4, // Callback routine requested an abort
|
||||
SqliteBusy = 5, // The database file is locked
|
||||
SqliteLocked = 6, // A table in the database is locked
|
||||
SqliteNoMem = 7, // A malloc() failed
|
||||
SqliteReadOnly = 8, // Attempt to write a readonly database
|
||||
SqliteInterrupt = 9, // Operation terminated by sqlite3_interrupt()
|
||||
SqliteIOErr = 10, // Some kind of disk I/O error occurred
|
||||
SqliteCorrupt = 11, // The database disk image is malformed
|
||||
SqliteNotFound = 12, // Unknown opcode in sqlite3_file_control()
|
||||
SqliteFull = 13, // Insertion failed because database is full
|
||||
SqliteCantOpen = 14, // Unable to open the database file
|
||||
SqliteProtocol = 15, // Database lock protocol error
|
||||
SqliteEmpty = 16, // Internal use only
|
||||
SqliteSchema = 17, // The database schema changed
|
||||
SqliteTooBig = 18, // String or BLOB exceeds size limit
|
||||
SqliteConstraint = 19, // Abort due to constraint violation
|
||||
SqliteMismatch = 20, // Data type mismatch
|
||||
SqliteMisuse = 21, // Library used incorrectly
|
||||
SqliteNoLFS = 22, // Uses OS features not supported on host
|
||||
SqliteAuth = 23, // Authorization denied
|
||||
SqliteFormat = 24, // Not used
|
||||
SqliteRange = 25, // 2nd parameter to sqlite3_bind out of range
|
||||
SqliteNotADB = 26, // File opened that is not a database file
|
||||
SqliteNotice = 27, // Notifications from sqlite3_log()
|
||||
SqliteWarning = 28, // Warnings from sqlite3_log()
|
||||
SqliteRow = 100, // sqlite3_step() has another row ready
|
||||
SqliteDone = 101, // sqlite3_step() has finished executing
|
||||
}
|
||||
|
||||
export enum OpenFlags {
|
||||
ReadOnly = 0x00000001,
|
||||
ReadWrite = 0x00000002,
|
||||
Create = 0x00000004,
|
||||
Uri = 0x00000040,
|
||||
Memory = 0x00000080,
|
||||
}
|
||||
|
||||
export enum Types {
|
||||
Integer = 1,
|
||||
Float = 2,
|
||||
Text = 3,
|
||||
Blob = 4,
|
||||
Null = 5,
|
||||
BigInteger = 6,
|
||||
}
|
||||
|
||||
export enum Values {
|
||||
Error = -1,
|
||||
Null = 0,
|
||||
}
|
462
plugos/forked/deno-sqlite/src/db.test.ts
Normal file
462
plugos/forked/deno-sqlite/src/db.test.ts
Normal file
@ -0,0 +1,462 @@
|
||||
import {
|
||||
assertAlmostEquals,
|
||||
assertEquals,
|
||||
assertThrows,
|
||||
} from "https://deno.land/std@0.154.0/testing/asserts.ts";
|
||||
|
||||
import { DB } from "../mod.ts";
|
||||
|
||||
const TEST_DB = "test.db";
|
||||
const LARGE_TEST_DB = "build/2GB_test.db";
|
||||
|
||||
async function dbPermissions(path: string): Promise<boolean> {
|
||||
const query = async (name: "read" | "write") =>
|
||||
(await Deno.permissions.query({ name, path })).state ===
|
||||
"granted";
|
||||
return await query("read") && await query("write");
|
||||
}
|
||||
|
||||
const TEST_DB_PERMISSIONS = await dbPermissions(TEST_DB);
|
||||
const LARGE_TEST_DB_PERMISSIONS = await dbPermissions(LARGE_TEST_DB);
|
||||
|
||||
async function deleteDatabase(file: string) {
|
||||
try {
|
||||
await Deno.remove(file);
|
||||
} catch { /* no op */ }
|
||||
try {
|
||||
await Deno.remove(`${file}-journal`);
|
||||
} catch { /* no op */ }
|
||||
}
|
||||
|
||||
Deno.test("execute multiple statements", function () {
|
||||
const db = new DB();
|
||||
|
||||
db.execute(`
|
||||
CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT);
|
||||
|
||||
INSERT INTO test (id) VALUES (1);
|
||||
INSERT INTO test (id) VALUES (2);
|
||||
INSERT INTO test (id) VALUES (3);
|
||||
`);
|
||||
assertEquals(db.query("SELECT id FROM test"), [[1], [2], [3]]);
|
||||
|
||||
// table `test` already exists ...
|
||||
assertThrows(function () {
|
||||
db.execute(`
|
||||
CREATE TABLE test2 (id INTEGER);
|
||||
CREATE TABLE test (id INTEGER);
|
||||
`);
|
||||
});
|
||||
|
||||
// ... but table `test2` was created before the error
|
||||
assertEquals(db.query("SELECT id FROM test2"), []);
|
||||
|
||||
// syntax error after first valid statement
|
||||
assertThrows(() => db.execute("SELECT id FROM test; NOT SQL ANYMORE"));
|
||||
});
|
||||
|
||||
Deno.test("foreign key constraints enabled", function () {
|
||||
const db = new DB();
|
||||
db.execute(`
|
||||
CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT);
|
||||
CREATE TABLE orders (id INTEGER PRIMARY KEY AUTOINCREMENT, user INTEGER, FOREIGN KEY(user) REFERENCES users(id));
|
||||
`);
|
||||
|
||||
db.query("INSERT INTO users (id) VALUES (1)");
|
||||
const [{ id }] = db.queryEntries<{ id: number }>("SELECT id FROM users");
|
||||
|
||||
// user must exist
|
||||
assertThrows(() =>
|
||||
db.query("INSERT INTO orders (user) VALUES (?)", [id + 1])
|
||||
);
|
||||
db.query("INSERT INTO orders (user) VALUES (?)", [id]);
|
||||
|
||||
// can't delete if that violates the constraint ...
|
||||
assertThrows(() => {
|
||||
db.query("DELETE FROM users WHERE id = ?", [id]);
|
||||
});
|
||||
|
||||
// ... after deleting the order, deleting is OK
|
||||
db.query("DELETE FROM orders WHERE user = ?", [id]);
|
||||
db.query("DELETE FROM users WHERE id = ?", [id]);
|
||||
});
|
||||
|
||||
Deno.test("json functions exist", function () {
|
||||
const db = new DB();
|
||||
|
||||
// The JSON1 functions should exist and we should be able to call them without unexpected errors
|
||||
db.query(`SELECT json('{"this is": ["json"]}')`);
|
||||
|
||||
// We should expect an error if we pass invalid JSON where valid JSON is expected
|
||||
assertThrows(() => {
|
||||
db.query(`SELECT json('this is not json')`);
|
||||
});
|
||||
|
||||
// We should be able to use bound values as arguments to the JSON1 functions,
|
||||
// and they should produce the expected results for these simple expressions.
|
||||
const [[objectType]] = db.query(`SELECT json_type('{}')`);
|
||||
assertEquals(objectType, "object");
|
||||
|
||||
const [[integerType]] = db.query(`SELECT json_type(?)`, ["2"]);
|
||||
assertEquals(integerType, "integer");
|
||||
|
||||
const [[realType]] = db.query(`SELECT json_type(?)`, ["2.5"]);
|
||||
assertEquals(realType, "real");
|
||||
|
||||
const [[stringType]] = db.query(`SELECT json_type(?)`, [`"hello"`]);
|
||||
assertEquals(stringType, "text");
|
||||
|
||||
const [[integerTypeAtPath]] = db.query(
|
||||
`SELECT json_type(?, ?)`,
|
||||
[`["hello", 2, {"world": 4}]`, `$[2].world`],
|
||||
);
|
||||
assertEquals(integerTypeAtPath, "integer");
|
||||
});
|
||||
|
||||
Deno.test("date time is correct", function () {
|
||||
const db = new DB();
|
||||
// the date/ time is passed from JS and should be current (note that it is GMT)
|
||||
const [[now]] = [...db.query("SELECT STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')")];
|
||||
const jsTime = new Date().getTime();
|
||||
const dbTime = new Date(`${now}Z`).getTime();
|
||||
// to account for runtime latency, a small difference is ok
|
||||
const tolerance = 10;
|
||||
assertAlmostEquals(jsTime, dbTime, tolerance);
|
||||
db.close();
|
||||
});
|
||||
|
||||
Deno.test("SQL localtime reflects system locale", function () {
|
||||
const db = new DB();
|
||||
const [[timeDb]] = db.query("SELECT datetime('now', 'localtime')");
|
||||
const now = new Date();
|
||||
|
||||
const jsMonth = `${now.getMonth() + 1}`.padStart(2, "0");
|
||||
const jsDate = `${now.getDate()}`.padStart(2, "0");
|
||||
const jsHour = `${now.getHours()}`.padStart(2, "0");
|
||||
const jsMinute = `${now.getMinutes()}`.padStart(2, "0");
|
||||
const jsSecond = `${now.getSeconds()}`.padStart(2, "0");
|
||||
const timeJs =
|
||||
`${now.getFullYear()}-${jsMonth}-${jsDate} ${jsHour}:${jsMinute}:${jsSecond}`;
|
||||
|
||||
assertEquals(timeDb, timeJs);
|
||||
});
|
||||
|
||||
Deno.test("database has correct changes and totalChanges", function () {
|
||||
const db = new DB();
|
||||
|
||||
db.execute(
|
||||
"CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)",
|
||||
);
|
||||
|
||||
for (const name of ["a", "b", "c"]) {
|
||||
db.query("INSERT INTO test (name) VALUES (?)", [name]);
|
||||
assertEquals(1, db.changes);
|
||||
}
|
||||
|
||||
assertEquals(3, db.totalChanges);
|
||||
|
||||
db.query("UPDATE test SET name = ?", ["new name"]);
|
||||
assertEquals(3, db.changes);
|
||||
assertEquals(6, db.totalChanges);
|
||||
});
|
||||
|
||||
Deno.test("last inserted id", function () {
|
||||
const db = new DB();
|
||||
|
||||
// By default, lastInsertRowId must be 0
|
||||
assertEquals(db.lastInsertRowId, 0);
|
||||
|
||||
// Create table and insert value
|
||||
db.query("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
|
||||
|
||||
const insertRowIds = [];
|
||||
|
||||
// Insert data to table and collect their ids
|
||||
for (let i = 0; i < 10; i++) {
|
||||
db.query("INSERT INTO users (name) VALUES ('John Doe')");
|
||||
insertRowIds.push(db.lastInsertRowId);
|
||||
}
|
||||
|
||||
// Now, the last inserted row id must be 10
|
||||
assertEquals(db.lastInsertRowId, 10);
|
||||
|
||||
// All collected row ids must be the same as in the database
|
||||
assertEquals(
|
||||
insertRowIds,
|
||||
[...db.query("SELECT id FROM users")].map(([i]) => i),
|
||||
);
|
||||
|
||||
db.close();
|
||||
|
||||
// When the database is closed, the value
|
||||
// will be reset to 0 again
|
||||
assertEquals(db.lastInsertRowId, 0);
|
||||
});
|
||||
|
||||
Deno.test("close database", function () {
|
||||
const db = new DB();
|
||||
db.close();
|
||||
assertThrows(() => db.query("CREATE TABLE test (name TEXT PRIMARY KEY)"));
|
||||
db.close(); // check close is idempotent and won't throw
|
||||
});
|
||||
|
||||
Deno.test("open queries block close", function () {
|
||||
const db = new DB();
|
||||
db.query("CREATE TABLE test (name TEXT PRIMARY KEY)");
|
||||
|
||||
const query = db.prepareQuery("SELECT name FROM test");
|
||||
assertThrows(() => db.close());
|
||||
query.finalize();
|
||||
|
||||
db.close();
|
||||
});
|
||||
|
||||
Deno.test("open queries cleaned up by forced close", function () {
|
||||
const db = new DB();
|
||||
db.query("CREATE TABLE test (name TEXT PRIMARY KEY)");
|
||||
db.query("INSERT INTO test (name) VALUES (?)", ["Deno"]);
|
||||
|
||||
db.prepareQuery("SELECT name FROM test WHERE name like '%test%'");
|
||||
|
||||
assertThrows(() => db.close());
|
||||
db.close(true);
|
||||
});
|
||||
|
||||
Deno.test("invalid bind does not leak statements", function () {
|
||||
const db = new DB();
|
||||
db.query("CREATE TABLE test (id INTEGER)");
|
||||
|
||||
for (let n = 0; n < 100; n++) {
|
||||
assertThrows(() => {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
const badBinding: any = [{}];
|
||||
db.query("INSERT INTO test (id) VALUES (?)", badBinding);
|
||||
});
|
||||
assertThrows(() => {
|
||||
const badBinding = { missingKey: null };
|
||||
db.query("INSERT INTO test (id) VALUES (?)", badBinding);
|
||||
});
|
||||
}
|
||||
|
||||
db.query("INSERT INTO test (id) VALUES (1)");
|
||||
|
||||
db.close();
|
||||
});
|
||||
|
||||
Deno.test("transactions can be nested", function () {
|
||||
const db = new DB();
|
||||
db.query("CREATE TABLE test (id INTEGER PRIMARY KEY)");
|
||||
|
||||
db.transaction(() => {
|
||||
db.query("INSERT INTO test (id) VALUES (1)");
|
||||
try {
|
||||
db.transaction(() => {
|
||||
db.query("INSERT INTO test (id) VALUES (2)");
|
||||
throw new Error("boom!");
|
||||
});
|
||||
} catch (_) { /* ignore */ }
|
||||
});
|
||||
|
||||
assertEquals([{ id: 1 }], db.queryEntries("SELECT * FROM test"));
|
||||
});
|
||||
|
||||
Deno.test("transactions commit when closure exists", function () {
|
||||
const db = new DB();
|
||||
db.query("CREATE TABLE test (id INTEGER PRIMARY KEY)");
|
||||
|
||||
db.transaction(() => {
|
||||
db.query("INSERT INTO test (id) VALUES (1)");
|
||||
});
|
||||
assertThrows(() => db.query("ROLLBACK"));
|
||||
|
||||
assertEquals([{ id: 1 }], db.queryEntries("SELECT * FROM test"));
|
||||
});
|
||||
|
||||
Deno.test("transaction rolls back on throw", function () {
|
||||
const db = new DB();
|
||||
db.query("CREATE TABLE test (id INTEGER PRIMARY KEY)");
|
||||
|
||||
assertThrows(() => {
|
||||
db.transaction(() => {
|
||||
db.query("INSERT INTO test (id) VALUES (1)");
|
||||
throw new Error("boom!");
|
||||
});
|
||||
});
|
||||
|
||||
assertEquals([], db.query("SELECT * FROM test"));
|
||||
});
|
||||
|
||||
Deno.test(
|
||||
"persist database to file",
|
||||
{
|
||||
ignore: !TEST_DB_PERMISSIONS,
|
||||
permissions: { read: true, write: true },
|
||||
sanitizeResources: true,
|
||||
},
|
||||
async function () {
|
||||
const data = [
|
||||
"Hello World!",
|
||||
"Hello Deno!",
|
||||
"JavaScript <3",
|
||||
"This costs 0€ / $0 / £0",
|
||||
"Wéll, hällö thėrè¿",
|
||||
];
|
||||
|
||||
// ensure the test database file does not exist
|
||||
await deleteDatabase(TEST_DB);
|
||||
|
||||
const db = new DB(TEST_DB);
|
||||
db.execute(
|
||||
"CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, val TEXT)",
|
||||
);
|
||||
for (const val of data) {
|
||||
db.query("INSERT INTO test (val) VALUES (?)", [val]);
|
||||
}
|
||||
|
||||
// open the same database with a separate connection
|
||||
const readOnlyDb = await new DB(TEST_DB, { mode: "read" });
|
||||
for (
|
||||
const [id, val] of readOnlyDb.query<[number, string]>(
|
||||
"SELECT * FROM test",
|
||||
)
|
||||
) {
|
||||
assertEquals(data[id - 1], val);
|
||||
}
|
||||
|
||||
await Deno.remove(TEST_DB);
|
||||
db.close();
|
||||
readOnlyDb.close();
|
||||
},
|
||||
);
|
||||
|
||||
Deno.test(
|
||||
"temporary file database read / write",
|
||||
{
|
||||
ignore: !TEST_DB_PERMISSIONS,
|
||||
permissions: { read: true, write: true },
|
||||
sanitizeResources: true,
|
||||
},
|
||||
function () {
|
||||
const data = [
|
||||
"Hello World!",
|
||||
"Hello Deno!",
|
||||
"JavaScript <3",
|
||||
"This costs 0€ / $0 / £0",
|
||||
"Wéll, hällö thėrè¿",
|
||||
];
|
||||
|
||||
const tempDb = new DB("");
|
||||
tempDb.execute(
|
||||
"CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, val TEXT)",
|
||||
);
|
||||
for (const val of data) {
|
||||
tempDb.query("INSERT INTO test (val) VALUES (?)", [val]);
|
||||
}
|
||||
|
||||
for (
|
||||
const [id, val] of tempDb.query<[number, string]>("SELECT * FROM test")
|
||||
) {
|
||||
assertEquals(data[id - 1], val);
|
||||
}
|
||||
|
||||
tempDb.close();
|
||||
},
|
||||
);
|
||||
|
||||
Deno.test(
|
||||
"database open options",
|
||||
{
|
||||
ignore: !TEST_DB_PERMISSIONS,
|
||||
permissions: { read: true, write: true },
|
||||
sanitizeResources: true,
|
||||
},
|
||||
async function () {
|
||||
await deleteDatabase(TEST_DB);
|
||||
|
||||
// when no file exists, these should error
|
||||
assertThrows(() => new DB(TEST_DB, { mode: "write" }));
|
||||
assertThrows(() => new DB(TEST_DB, { mode: "read" }));
|
||||
|
||||
// create the database
|
||||
const dbCreate = new DB(TEST_DB, { mode: "create" });
|
||||
dbCreate.execute(
|
||||
"CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)",
|
||||
);
|
||||
dbCreate.close();
|
||||
|
||||
// the default mode is create
|
||||
await deleteDatabase(TEST_DB);
|
||||
const dbCreateDefault = new DB(TEST_DB, { mode: "create" });
|
||||
dbCreateDefault.execute(
|
||||
"CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)",
|
||||
);
|
||||
dbCreateDefault.close();
|
||||
|
||||
// in write mode, we can run INSERT queries ...
|
||||
const dbWrite = new DB(TEST_DB, { mode: "write" });
|
||||
dbWrite.query("INSERT INTO test (name) VALUES (?)", ["open-options-test"]);
|
||||
dbWrite.close();
|
||||
|
||||
// ... which we can read in read-only mode ...
|
||||
const dbRead = new DB(TEST_DB, { mode: "read" });
|
||||
const rows = [...dbRead.query("SELECT id, name FROM test")];
|
||||
assertEquals(rows, [[1, "open-options-test"]]);
|
||||
|
||||
// ... but we can't write with a read-only connection
|
||||
assertThrows(() =>
|
||||
dbRead.query("INTERT INTO test (name) VALUES (?)", ["this-fails"])
|
||||
);
|
||||
dbRead.close();
|
||||
},
|
||||
);
|
||||
|
||||
Deno.test(
|
||||
"create / write mode require write permissions",
|
||||
{
|
||||
ignore: !TEST_DB_PERMISSIONS,
|
||||
permissions: { read: true, write: false },
|
||||
sanitizeResources: true,
|
||||
},
|
||||
function () {
|
||||
// opening with these modes requires write permissions ...
|
||||
assertThrows(() => new DB(TEST_DB, { mode: "create" }));
|
||||
assertThrows(() => new DB(TEST_DB, { mode: "write" }));
|
||||
|
||||
// ... and the default mode is create
|
||||
assertThrows(() => new DB(TEST_DB));
|
||||
|
||||
// however, opening in read-only mode should work (the file was created
|
||||
// in the previous test)
|
||||
(new DB(TEST_DB, { mode: "read" })).close();
|
||||
|
||||
// with memory flag set, the database will be in memory and
|
||||
// not require any permissions
|
||||
(new DB(TEST_DB, { mode: "create", memory: true })).close();
|
||||
|
||||
// the mode can also be specified via a URI flag
|
||||
(new DB(`file:${TEST_DB}?mode=memory`, { uri: true })).close();
|
||||
},
|
||||
);
|
||||
|
||||
Deno.test(
|
||||
"database larger than 2GB read / write",
|
||||
{
|
||||
ignore: !LARGE_TEST_DB_PERMISSIONS,
|
||||
permissions: { read: true, write: true },
|
||||
sanitizeResources: true,
|
||||
},
|
||||
function () {
|
||||
// generated with `cd build && make testdb`
|
||||
const db = new DB(LARGE_TEST_DB, { mode: "write" });
|
||||
|
||||
db.query("INSERT INTO test (value) VALUES (?)", ["This is a test..."]);
|
||||
|
||||
const rows = [
|
||||
...db.query("SELECT value FROM test ORDER BY id DESC LIMIT 10"),
|
||||
];
|
||||
assertEquals(rows.length, 10);
|
||||
assertEquals(rows[0][0], "This is a test...");
|
||||
|
||||
db.close();
|
||||
},
|
||||
);
|
393
plugos/forked/deno-sqlite/src/db.ts
Normal file
393
plugos/forked/deno-sqlite/src/db.ts
Normal file
@ -0,0 +1,393 @@
|
||||
import { instantiate, StatementPtr, Wasm } from "../build/sqlite.js";
|
||||
import { setStr } from "./wasm.ts";
|
||||
import { OpenFlags, Status, Values } from "./constants.ts";
|
||||
import { SqliteError } from "./error.ts";
|
||||
import { PreparedQuery, QueryParameterSet, Row, RowObject } from "./query.ts";
|
||||
|
||||
/**
|
||||
* Options for opening a database.
|
||||
*/
|
||||
export interface SqliteOptions {
|
||||
/**
|
||||
* Mode in which to open the database.
|
||||
*
|
||||
* - `read`: read-only, throws an error if
|
||||
* the database file does not exists
|
||||
* - `write`: read-write, throws an error
|
||||
* if the database file does not exists
|
||||
* - `create`: read-write, create the database
|
||||
* if the file does not exist
|
||||
*
|
||||
* `create` is the default if no mode is
|
||||
* specified.
|
||||
*/
|
||||
mode?: "read" | "write" | "create";
|
||||
/**
|
||||
* Force the database to be in-memory. When
|
||||
* this option is set, the database is opened
|
||||
* in memory, regardless of the specified
|
||||
* filename.
|
||||
*/
|
||||
memory?: boolean;
|
||||
/**
|
||||
* Interpret the file name as a URI.
|
||||
* See https://sqlite.org/uri.html
|
||||
* for more information.
|
||||
*/
|
||||
uri?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A database handle that can be used to run
|
||||
* queries.
|
||||
*/
|
||||
export class DB {
|
||||
private _wasm: Wasm;
|
||||
private _open: boolean;
|
||||
private _statements: Set<StatementPtr>;
|
||||
private _transactionDepth: number;
|
||||
|
||||
/**
|
||||
* Create a new database. The file at the
|
||||
* given path will be opened with the
|
||||
* mode specified in options. The default
|
||||
* mode is `create`.
|
||||
*
|
||||
* If no path is given, or if the `memory`
|
||||
* option is set, the database is opened in
|
||||
* memory.
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* Create an in-memory database.
|
||||
* ```typescript
|
||||
* const db = new DB();
|
||||
* ```
|
||||
*
|
||||
* Open a database backed by a file on disk.
|
||||
* ```typescript
|
||||
* const db = new DB("path/to/database.sqlite");
|
||||
* ```
|
||||
*
|
||||
* Pass options to open a read-only database.
|
||||
* ```typescript
|
||||
* const db = new DB("path/to/database.sqlite", { mode: "read" });
|
||||
* ```
|
||||
*/
|
||||
constructor(path: string = ":memory:", options: SqliteOptions = {}) {
|
||||
this._wasm = instantiate().exports;
|
||||
this._open = false;
|
||||
this._statements = new Set();
|
||||
this._transactionDepth = 0;
|
||||
|
||||
// Configure flags
|
||||
let flags = 0;
|
||||
switch (options.mode) {
|
||||
case "read":
|
||||
flags = OpenFlags.ReadOnly;
|
||||
break;
|
||||
case "write":
|
||||
flags = OpenFlags.ReadWrite;
|
||||
break;
|
||||
case "create": // fall through
|
||||
default:
|
||||
flags = OpenFlags.ReadWrite | OpenFlags.Create;
|
||||
break;
|
||||
}
|
||||
if (options.memory === true) {
|
||||
flags |= OpenFlags.Memory;
|
||||
}
|
||||
if (options.uri === true) {
|
||||
flags |= OpenFlags.Uri;
|
||||
}
|
||||
|
||||
// Try to open the database
|
||||
const status = setStr(
|
||||
this._wasm,
|
||||
path,
|
||||
(ptr) => this._wasm.open(ptr, flags),
|
||||
);
|
||||
if (status !== Status.SqliteOk) {
|
||||
throw new SqliteError(this._wasm, status);
|
||||
}
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the database and return all matching
|
||||
* rows.
|
||||
*
|
||||
* This is equivalent to calling `all` on
|
||||
* a prepared query which is then immediately
|
||||
* finalized.
|
||||
*
|
||||
* The type parameter `R` may be supplied by
|
||||
* the user to indicated the type for the rows returned
|
||||
* by the query. Notice that the user is responsible
|
||||
* for ensuring the correctness of the supplied type.
|
||||
*
|
||||
* To avoid SQL injection, user-provided values
|
||||
* should always be passed to the database through
|
||||
* a query parameter.
|
||||
*
|
||||
* See `QueryParameterSet` for documentation on
|
||||
* how values can be bound to SQL statements.
|
||||
*
|
||||
* See `QueryParameter` for documentation on how
|
||||
* values are returned from the database.
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```typescript
|
||||
* const rows = db.query<[string, number]>("SELECT name, age FROM people WHERE city = ?", [city]);
|
||||
* // rows = [["Peter Parker", 21], ...]
|
||||
* ```
|
||||
*
|
||||
* ```typescript
|
||||
* const rows = db.query<[string, number]>(
|
||||
* "SELECT name, age FROM people WHERE city = :city",
|
||||
* { city },
|
||||
* );
|
||||
* // rows = [["Peter Parker", 21], ...]
|
||||
* ```
|
||||
*/
|
||||
query<R extends Row = Row>(
|
||||
sql: string,
|
||||
params?: QueryParameterSet,
|
||||
): Array<R> {
|
||||
const query = this.prepareQuery<R>(sql);
|
||||
try {
|
||||
const rows = query.all(params);
|
||||
query.finalize();
|
||||
return rows;
|
||||
} catch (err) {
|
||||
query.finalize();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `query` except each row is returned
|
||||
* as an object containing key-value pairs.
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```typescript
|
||||
* const rows = db.queryEntries<{ name: string, age: number }>("SELECT name, age FROM people");
|
||||
* // rows = [{ name: "Peter Parker", age: 21 }, ...]
|
||||
* ```
|
||||
*
|
||||
* ```typescript
|
||||
* const rows = db.queryEntries<{ name: string, age: number }>(
|
||||
* "SELECT name, age FROM people WHERE age >= :minAge",
|
||||
* { minAge },
|
||||
* );
|
||||
* // rows = [{ name: "Peter Parker", age: 21 }, ...]
|
||||
* ```
|
||||
*/
|
||||
queryEntries<O extends RowObject = RowObject>(
|
||||
sql: string,
|
||||
params?: QueryParameterSet,
|
||||
): Array<O> {
|
||||
const query = this.prepareQuery<Row, O>(sql);
|
||||
try {
|
||||
const rows = query.allEntries(params);
|
||||
query.finalize();
|
||||
return rows;
|
||||
} catch (err) {
|
||||
query.finalize();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the given SQL query, so that it
|
||||
* can be run multiple times and potentially
|
||||
* with different parameters.
|
||||
*
|
||||
* If a query will be issued a lot, this is more
|
||||
* efficient than using `query`. A prepared
|
||||
* query also provides more control over how
|
||||
* the query is run, as well as access to meta-data
|
||||
* about the issued query.
|
||||
*
|
||||
* The returned `PreparedQuery` object must be
|
||||
* finalized by calling its `finalize` method
|
||||
* once it is no longer needed.
|
||||
*
|
||||
* # Typing Queries
|
||||
*
|
||||
* Prepared query objects accept three type parameters
|
||||
* to specify precise types for returned data and
|
||||
* query parameters.
|
||||
*
|
||||
* + The first type parameter `R` indicates the tuple type
|
||||
* for rows returned by the query.
|
||||
*
|
||||
* + The second type parameter `O` indicates the record type
|
||||
* for rows returned as entries (mappings from column names
|
||||
* to values).
|
||||
*
|
||||
* + The third type parameter `P` indicates the type this query
|
||||
* accepts as parameters.
|
||||
*
|
||||
* Note, that the correctness of those types must
|
||||
* be guaranteed by the caller of this function.
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```typescript
|
||||
* const query = db.prepareQuery<
|
||||
* [string, number],
|
||||
* { name: string, age: number },
|
||||
* { city: string },
|
||||
* >("SELECT name, age FROM people WHERE city = :city");
|
||||
*
|
||||
* // use query ...
|
||||
*
|
||||
* query.finalize();
|
||||
* ```
|
||||
*/
|
||||
prepareQuery<
|
||||
R extends Row = Row,
|
||||
O extends RowObject = RowObject,
|
||||
P extends QueryParameterSet = QueryParameterSet,
|
||||
>(
|
||||
sql: string,
|
||||
): PreparedQuery<R, O, P> {
|
||||
if (!this._open) {
|
||||
throw new SqliteError("Database was closed.");
|
||||
}
|
||||
|
||||
const stmt = setStr(
|
||||
this._wasm,
|
||||
sql,
|
||||
(ptr) => this._wasm.prepare(ptr),
|
||||
);
|
||||
if (stmt === Values.Null) {
|
||||
throw new SqliteError(this._wasm);
|
||||
}
|
||||
|
||||
this._statements.add(stmt);
|
||||
return new PreparedQuery<R, O, P>(this._wasm, stmt, this._statements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run multiple semicolon-separated statements from a single
|
||||
* string.
|
||||
*
|
||||
* This method cannot bind any query parameters, and any
|
||||
* result rows are discarded. It is only for running a chunk
|
||||
* of raw SQL; for example, to initialize a database.
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```typescript
|
||||
* db.execute(`
|
||||
* CREATE TABLE people (
|
||||
* id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
* name TEXT,
|
||||
* age REAL,
|
||||
* city TEXT
|
||||
* );
|
||||
* INSERT INTO people (name, age, city) VALUES ("Peter Parker", 21, "nyc");
|
||||
* `);
|
||||
* ```
|
||||
*/
|
||||
execute(sql: string) {
|
||||
const status = setStr(
|
||||
this._wasm,
|
||||
sql,
|
||||
(ptr) => this._wasm.exec(ptr),
|
||||
);
|
||||
|
||||
if (status !== Status.SqliteOk) {
|
||||
throw new SqliteError(this._wasm, status);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a function within the context of a database
|
||||
* transaction. If the function throws an error,
|
||||
* the transaction is rolled back. Otherwise, the
|
||||
* transaction is committed when the function returns.
|
||||
*
|
||||
* Calls to `transaction` may be nested. Nested transactions
|
||||
* behave like SQLite save points.
|
||||
*/
|
||||
transaction<V>(closure: () => V): V {
|
||||
this._transactionDepth += 1;
|
||||
this.query(`SAVEPOINT _deno_sqlite_sp_${this._transactionDepth}`);
|
||||
let value;
|
||||
try {
|
||||
value = closure();
|
||||
} catch (err) {
|
||||
this.query(`ROLLBACK TO _deno_sqlite_sp_${this._transactionDepth}`);
|
||||
this._transactionDepth -= 1;
|
||||
throw err;
|
||||
}
|
||||
this.query(`RELEASE _deno_sqlite_sp_${this._transactionDepth}`);
|
||||
this._transactionDepth -= 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the database. This must be called if
|
||||
* the database is no longer used to avoid leaking
|
||||
* open file descriptors.
|
||||
*
|
||||
* If `force = true` is passed, any non-finalized
|
||||
* `PreparedQuery` objects will be finalized. Otherwise,
|
||||
* this throws if there are active queries.
|
||||
*
|
||||
* `close` may safely be called multiple
|
||||
* times.
|
||||
*/
|
||||
close(force = false) {
|
||||
if (!this._open) {
|
||||
return;
|
||||
}
|
||||
if (force) {
|
||||
for (const stmt of this._statements) {
|
||||
if (this._wasm.finalize(stmt) !== Status.SqliteOk) {
|
||||
throw new SqliteError(this._wasm);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this._wasm.close() !== Status.SqliteOk) {
|
||||
throw new SqliteError(this._wasm);
|
||||
}
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last inserted row id. This corresponds to
|
||||
* the SQLite function `sqlite3_last_insert_rowid`.
|
||||
*
|
||||
* Before a row is inserted for the first time (since
|
||||
* the database was opened), this returns `0`.
|
||||
*/
|
||||
get lastInsertRowId(): number {
|
||||
return this._wasm.last_insert_rowid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of rows modified, inserted or
|
||||
* deleted by the most recently completed query.
|
||||
* This corresponds to the SQLite function
|
||||
* `sqlite3_changes`.
|
||||
*/
|
||||
get changes(): number {
|
||||
return this._wasm.changes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of rows modified, inserted or
|
||||
* deleted since the database was opened.
|
||||
* This corresponds to the SQLite function
|
||||
* `sqlite3_total_changes`.
|
||||
*/
|
||||
get totalChanges(): number {
|
||||
return this._wasm.total_changes();
|
||||
}
|
||||
}
|
55
plugos/forked/deno-sqlite/src/error.test.ts
Normal file
55
plugos/forked/deno-sqlite/src/error.test.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import {
|
||||
assertEquals,
|
||||
assertInstanceOf,
|
||||
assertThrows,
|
||||
} from "https://deno.land/std@0.154.0/testing/asserts.ts";
|
||||
|
||||
import { DB, SqliteError, Status } from "../mod.ts";
|
||||
|
||||
Deno.test("invalid SQL", function () {
|
||||
const db = new DB();
|
||||
const queries = [
|
||||
"INSERT INTO does_not_exist (balance) VALUES (5)",
|
||||
"this is not sql",
|
||||
";;;",
|
||||
];
|
||||
for (const query of queries) assertThrows(() => db.query(query));
|
||||
|
||||
db.close();
|
||||
});
|
||||
|
||||
Deno.test("constraint error code is correct", function () {
|
||||
const db = new DB();
|
||||
db.query("CREATE TABLE test (name TEXT PRIMARY KEY)");
|
||||
db.query("INSERT INTO test (name) VALUES (?)", ["A"]);
|
||||
|
||||
assertThrows(
|
||||
() => db.query("INSERT INTO test (name) VALUES (?)", ["A"]),
|
||||
(e: Error) => {
|
||||
assertInstanceOf(e, SqliteError);
|
||||
assertEquals(e.code, Status.SqliteConstraint, "Got wrong error code");
|
||||
assertEquals(
|
||||
Status[e.codeName],
|
||||
Status.SqliteConstraint,
|
||||
"Got wrong error code name",
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("syntax error code is correct", function () {
|
||||
const db = new DB();
|
||||
|
||||
assertThrows(
|
||||
() => db.query("CREATE TABLEX test (name TEXT PRIMARY KEY)"),
|
||||
(e: Error) => {
|
||||
assertInstanceOf(e, SqliteError);
|
||||
assertEquals(e.code, Status.SqliteError, "Got wrong error code");
|
||||
assertEquals(
|
||||
Status[e.codeName],
|
||||
Status.SqliteError,
|
||||
"Got wrong error code name",
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
57
plugos/forked/deno-sqlite/src/error.ts
Normal file
57
plugos/forked/deno-sqlite/src/error.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { Wasm } from "../build/sqlite.js";
|
||||
import { getStr } from "./wasm.ts";
|
||||
import { Status } from "./constants.ts";
|
||||
|
||||
/**
|
||||
* Errors which can occur while interacting with
|
||||
* a database.
|
||||
*/
|
||||
export class SqliteError extends Error {
|
||||
/**
|
||||
* Extension over the standard JS Error object
|
||||
* to also contain class members for error code
|
||||
* and error code name.
|
||||
*
|
||||
* Instances of this class should not be constructed
|
||||
* directly and should only be obtained
|
||||
* from exceptions raised in this module.
|
||||
*/
|
||||
constructor(context: Wasm | string, code?: Status) {
|
||||
let message;
|
||||
let status;
|
||||
if (typeof context === "string") {
|
||||
message = context;
|
||||
status = Status.Unknown;
|
||||
} else {
|
||||
message = getStr(context, context.get_sqlite_error_str());
|
||||
status = context.get_status();
|
||||
}
|
||||
super(message);
|
||||
this.code = code ?? status;
|
||||
this.name = "SqliteError";
|
||||
}
|
||||
|
||||
/**
|
||||
* The SQLite status code which caused this error.
|
||||
*
|
||||
* Errors that originate in the JavaScript part of
|
||||
* the library will not have an associated status
|
||||
* code. For these errors, the code will be
|
||||
* `Status.Unknown`.
|
||||
*
|
||||
* These codes are accessible via
|
||||
* the exported `Status` object.
|
||||
*/
|
||||
code: Status;
|
||||
|
||||
/**
|
||||
* Key of code in exported `status`
|
||||
* object.
|
||||
*
|
||||
* E.g. if `code` is `19`,
|
||||
* `codeName` would be `SqliteConstraint`.
|
||||
*/
|
||||
get codeName(): keyof typeof Status {
|
||||
return Status[this.code] as keyof typeof Status;
|
||||
}
|
||||
}
|
521
plugos/forked/deno-sqlite/src/query.test.ts
Normal file
521
plugos/forked/deno-sqlite/src/query.test.ts
Normal file
@ -0,0 +1,521 @@
|
||||
import {
|
||||
assertEquals,
|
||||
assertThrows,
|
||||
} from "https://deno.land/std@0.154.0/testing/asserts.ts";
|
||||
|
||||
import { DB, QueryParameter } from "../mod.ts";
|
||||
|
||||
function roundTripValues<T extends QueryParameter>(values: T[]): unknown[] {
|
||||
const db = new DB();
|
||||
db.execute(
|
||||
"CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, datum ANY)",
|
||||
);
|
||||
|
||||
for (const value of values) {
|
||||
db.query("INSERT INTO test (datum) VALUES (?)", [value]);
|
||||
}
|
||||
|
||||
return db
|
||||
.queryEntries<{ datum: unknown }>("SELECT datum FROM test")
|
||||
.map(({ datum }) => datum);
|
||||
}
|
||||
|
||||
Deno.test("bind string values", function () {
|
||||
const values = ["Hello World!", "I love Deno.", "Täst strüng..."];
|
||||
assertEquals(values, roundTripValues(values));
|
||||
});
|
||||
|
||||
Deno.test("bind integer values", function () {
|
||||
const values = [42, 1, 2, 3, 4, 3453246, 4536787093, 45536787093];
|
||||
assertEquals(values, roundTripValues(values));
|
||||
});
|
||||
|
||||
Deno.test("bind float values", function () {
|
||||
const values = [42.1, 1.235, 2.999, 1 / 3, 4.2345, 345.3246, 4536787.953e-8];
|
||||
assertEquals(values, roundTripValues(values));
|
||||
});
|
||||
|
||||
Deno.test("bind boolean values", function () {
|
||||
assertEquals([1, 0], roundTripValues([true, false]));
|
||||
});
|
||||
|
||||
Deno.test("bind date values", function () {
|
||||
const values = [new Date(), new Date("2018-11-20"), new Date(123456789)];
|
||||
assertEquals(
|
||||
values.map((date) => date.toISOString()),
|
||||
roundTripValues(values),
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("bind blob values", function () {
|
||||
const values = [
|
||||
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]),
|
||||
new Uint8Array([3, 57, 45]),
|
||||
];
|
||||
assertEquals(values, roundTripValues(values));
|
||||
});
|
||||
|
||||
Deno.test("blobs are copies", function () {
|
||||
const db = new DB();
|
||||
|
||||
db.query(
|
||||
"CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, val BLOB)",
|
||||
);
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5]);
|
||||
db.query("INSERT INTO test (val) VALUES (?)", [data]);
|
||||
|
||||
const [[a]] = db.query<[Uint8Array]>("SELECT val FROM test");
|
||||
const [[b]] = db.query<[Uint8Array]>("SELECT val FROM test");
|
||||
|
||||
assertEquals(data, a);
|
||||
assertEquals(data, b);
|
||||
assertEquals(a, b);
|
||||
|
||||
a[0] = 100;
|
||||
assertEquals(a[0], 100);
|
||||
assertEquals(b[0], 1);
|
||||
assertEquals(data[0], 1);
|
||||
|
||||
data[0] = 5;
|
||||
const [[c]] = db.query<[Uint8Array]>("SELECT val FROM test");
|
||||
assertEquals(c[0], 1);
|
||||
});
|
||||
|
||||
Deno.test("bind bigint values", function () {
|
||||
assertEquals(
|
||||
[9007199254741991n, 100],
|
||||
roundTripValues([9007199254741991n, 100n]),
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("bind null / undefined", function () {
|
||||
assertEquals([null, null], roundTripValues([null, undefined]));
|
||||
});
|
||||
|
||||
Deno.test("bind mixed values", function () {
|
||||
const values = [42, "Hello World!", 0.33333, null];
|
||||
assertEquals(values, roundTripValues(values));
|
||||
});
|
||||
|
||||
Deno.test("omitting a value binds NULL", function () {
|
||||
const db = new DB();
|
||||
db.execute("CREATE TABLE test (id INTEGER PRIMARY KEY, datum ANY)");
|
||||
|
||||
const insert = db.prepareQuery(
|
||||
"INSERT INTO test (datum) VALUES (?) RETURNING datum",
|
||||
);
|
||||
|
||||
assertEquals([null], insert.first());
|
||||
assertEquals([null], insert.first([]));
|
||||
assertEquals([null], insert.first({}));
|
||||
|
||||
// previously bound values are cleared
|
||||
insert.execute(["this is not null"]);
|
||||
assertEquals([null], insert.first());
|
||||
});
|
||||
|
||||
Deno.test("prepared query clears bindings before reused", function () {
|
||||
const db = new DB();
|
||||
db.query("CREATE TABLE test (id INTEGER PRIMARY KEY, value INTEGER)");
|
||||
|
||||
const query = db.prepareQuery("INSERT INTO test (value) VALUES (?)");
|
||||
query.execute([1]);
|
||||
query.execute();
|
||||
|
||||
assertEquals([[1], [null]], db.query("SELECT value FROM test"));
|
||||
|
||||
query.finalize();
|
||||
db.close();
|
||||
});
|
||||
|
||||
Deno.test("bind very large floating point numbers", function () {
|
||||
const db = new DB();
|
||||
|
||||
db.query("CREATE TABLE numbers (id INTEGER PRIMARY KEY, number REAL)");
|
||||
|
||||
db.query("INSERT INTO numbers (number) VALUES (?)", [+Infinity]);
|
||||
db.query("INSERT INTO numbers (number) VALUES (?)", [-Infinity]);
|
||||
db.query("INSERT INTO numbers (number) VALUES (?)", [+20e20]);
|
||||
db.query("INSERT INTO numbers (number) VALUES (?)", [-20e20]);
|
||||
|
||||
const [
|
||||
[positiveInfinity],
|
||||
[negativeInfinity],
|
||||
[positiveTwentyTwenty],
|
||||
[negativeTwentyTwenty],
|
||||
] = db.query("SELECT number FROM numbers");
|
||||
|
||||
assertEquals(negativeInfinity, -Infinity);
|
||||
assertEquals(positiveInfinity, +Infinity);
|
||||
assertEquals(positiveTwentyTwenty, +20e20);
|
||||
assertEquals(negativeTwentyTwenty, -20e20);
|
||||
});
|
||||
|
||||
Deno.test("big very large integers", function () {
|
||||
const db = new DB();
|
||||
db.query(
|
||||
"CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, val INTEGER)",
|
||||
);
|
||||
|
||||
const goodValues = [
|
||||
0n,
|
||||
42n,
|
||||
-42n,
|
||||
9223372036854775807n,
|
||||
-9223372036854775808n,
|
||||
];
|
||||
const overflowValues = [
|
||||
9223372036854775807n + 1n,
|
||||
-9223372036854775808n - 1n,
|
||||
2352359223372036854775807n,
|
||||
-32453249223372036854775807n,
|
||||
];
|
||||
|
||||
const query = db.prepareQuery("INSERT INTO test (val) VALUES (?)");
|
||||
for (const val of goodValues) {
|
||||
query.execute([val]);
|
||||
}
|
||||
|
||||
const dbValues = db.query<[number | bigint]>(
|
||||
"SELECT val FROM test ORDER BY id",
|
||||
).map((
|
||||
[id],
|
||||
) => BigInt(id));
|
||||
assertEquals(goodValues, dbValues);
|
||||
|
||||
for (const bigVal of overflowValues) {
|
||||
assertThrows(() => {
|
||||
query.execute([bigVal]);
|
||||
});
|
||||
}
|
||||
|
||||
query.finalize();
|
||||
db.close();
|
||||
});
|
||||
|
||||
Deno.test("bind named parameters", function () {
|
||||
const db = new DB();
|
||||
|
||||
db.query(
|
||||
"CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, val TEXT)",
|
||||
);
|
||||
|
||||
// :name
|
||||
db.query("INSERT INTO test (val) VALUES (:val)", { val: "value" });
|
||||
db.query(
|
||||
"INSERT INTO test (val) VALUES (:otherVal)",
|
||||
{ otherVal: "value other" },
|
||||
);
|
||||
db.query(
|
||||
"INSERT INTO test (val) VALUES (:explicitColon)",
|
||||
{ ":explicitColon": "value explicit" },
|
||||
);
|
||||
|
||||
// @name
|
||||
db.query(
|
||||
"INSERT INTO test (val) VALUES (@someName)",
|
||||
{ "@someName": "@value" },
|
||||
);
|
||||
|
||||
// $name
|
||||
db.query(
|
||||
"INSERT INTO test (val) VALUES ($var::Name)",
|
||||
{ "$var::Name": "$value" },
|
||||
);
|
||||
|
||||
// explicit positional syntax
|
||||
db.query("INSERT INTO test (id, val) VALUES (?2, ?1)", ["this-is-it", 1000]);
|
||||
|
||||
// names must exist
|
||||
assertThrows(() => {
|
||||
db.query(
|
||||
"INSERT INTO test (val) VALUES (:val)",
|
||||
{ Val: "miss-spelled name" },
|
||||
);
|
||||
});
|
||||
|
||||
// make sure the data came through correctly
|
||||
const vals = [...db.query("SELECT val FROM test ORDER BY id ASC")]
|
||||
.map(([datum]) => datum);
|
||||
assertEquals(
|
||||
vals,
|
||||
[
|
||||
"value",
|
||||
"value other",
|
||||
"value explicit",
|
||||
"@value",
|
||||
"$value",
|
||||
"this-is-it",
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("iterate from prepared query", function () {
|
||||
const db = new DB();
|
||||
db.execute("CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT)");
|
||||
db.execute("INSERT INTO test (id) VALUES (1), (2), (3)");
|
||||
|
||||
const res = [];
|
||||
const query = db.prepareQuery<[number]>("SELECT id FROM test");
|
||||
for (const [id] of query.iter()) {
|
||||
res.push(id);
|
||||
}
|
||||
assertEquals(res, [1, 2, 3]);
|
||||
|
||||
query.finalize();
|
||||
db.close();
|
||||
});
|
||||
|
||||
Deno.test("query all from prepared query", function () {
|
||||
const db = new DB();
|
||||
db.query("CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT)");
|
||||
const query = db.prepareQuery("SELECT id FROM test");
|
||||
|
||||
assertEquals(query.all(), []);
|
||||
db.query("INSERT INTO test (id) VALUES (1), (2), (3)");
|
||||
assertEquals(query.all(), [[1], [2], [3]]);
|
||||
|
||||
query.finalize();
|
||||
db.close();
|
||||
});
|
||||
|
||||
Deno.test("query first from prepared query", function () {
|
||||
const db = new DB();
|
||||
db.query("CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT)");
|
||||
db.query("INSERT INTO test (id) VALUES (1), (2), (3)");
|
||||
|
||||
const querySingle = db.prepareQuery("SELECT id FROM test WHERE id = ?");
|
||||
assertEquals(querySingle.first([42]), undefined);
|
||||
assertEquals(querySingle.first([2]), [2]);
|
||||
|
||||
const queryAll = db.prepareQuery("SELECT id FROM test ORDER BY id ASC");
|
||||
assertEquals(queryAll.first(), [1]);
|
||||
|
||||
querySingle.finalize();
|
||||
queryAll.finalize();
|
||||
db.close();
|
||||
});
|
||||
|
||||
Deno.test("query one from prepared query", function () {
|
||||
const db = new DB();
|
||||
db.query("CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT)");
|
||||
db.query("INSERT INTO test (id) VALUES (1), (2), (3)");
|
||||
|
||||
const queryOne = db.prepareQuery<[number]>(
|
||||
"SELECT id FROM test WHERE id = ?",
|
||||
);
|
||||
assertThrows(() => queryOne.one([42]));
|
||||
assertEquals(queryOne.one([2]), [2]);
|
||||
|
||||
const queryAll = db.prepareQuery("SELECT id FROM test");
|
||||
assertThrows(() => queryAll.one());
|
||||
|
||||
queryOne.finalize();
|
||||
queryAll.finalize();
|
||||
db.close();
|
||||
});
|
||||
|
||||
Deno.test("execute from prepared query", function () {
|
||||
const db = new DB();
|
||||
db.query("CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT)");
|
||||
|
||||
const insert = db.prepareQuery("INSERT INTO test (id) VALUES (:id)");
|
||||
for (const id of [1, 2, 3]) {
|
||||
insert.execute({ id });
|
||||
}
|
||||
insert.finalize();
|
||||
assertEquals(db.query("SELECT id FROM test"), [[1], [2], [3]]);
|
||||
|
||||
db.close();
|
||||
});
|
||||
|
||||
Deno.test("empty query returns empty array", function () {
|
||||
const db = new DB();
|
||||
db.query("CREATE TABLE test (id INTEGER PRIMARY KEY)");
|
||||
assertEquals([], db.query("SELECT * FROM test"));
|
||||
db.close();
|
||||
});
|
||||
|
||||
Deno.test("query entries returns correct object shapes", function () {
|
||||
const db = new DB();
|
||||
db.query(
|
||||
"CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, height REAL)",
|
||||
);
|
||||
|
||||
const rowsOrig = [
|
||||
{ id: 1, name: "Peter Parker", height: 1.5 },
|
||||
{ id: 2, name: "Clark Kent", height: 1.9 },
|
||||
{ id: 3, name: "Robert Paar", height: 2.1 },
|
||||
];
|
||||
|
||||
const insertQuery = db.prepareQuery(
|
||||
"INSERT INTO test (id, name, height) VALUES (:id, :name, :height)",
|
||||
);
|
||||
for (const row of rowsOrig) {
|
||||
insertQuery.execute(row);
|
||||
}
|
||||
insertQuery.finalize();
|
||||
|
||||
const query = db.prepareQuery("SELECT * FROM test");
|
||||
assertEquals(rowsOrig, [...query.iterEntries()]);
|
||||
assertEquals(rowsOrig, query.allEntries());
|
||||
assertEquals(rowsOrig[0], query.firstEntry());
|
||||
assertEquals(rowsOrig, db.queryEntries("SELECT * FROM test"));
|
||||
|
||||
query.finalize();
|
||||
db.close();
|
||||
});
|
||||
|
||||
Deno.test("prepared query can be reused", function () {
|
||||
const db = new DB();
|
||||
db.query("CREATE TABLE test (id INTEGER PRIMARY KEY)");
|
||||
|
||||
const query = db.prepareQuery("INSERT INTO test (id) VALUES (?)");
|
||||
query.execute([1]);
|
||||
query.execute([2]);
|
||||
query.execute([3]);
|
||||
|
||||
assertEquals([[1], [2], [3]], db.query("SELECT id FROM test"));
|
||||
|
||||
query.finalize();
|
||||
db.close();
|
||||
});
|
||||
|
||||
Deno.test("get columns from select query", function () {
|
||||
const db = new DB();
|
||||
|
||||
db.query(
|
||||
"CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)",
|
||||
);
|
||||
|
||||
const query = db.prepareQuery("SELECT id, name from test");
|
||||
|
||||
assertEquals(query.columns(), [
|
||||
{ name: "id", originName: "id", tableName: "test" },
|
||||
{ name: "name", originName: "name", tableName: "test" },
|
||||
]);
|
||||
});
|
||||
|
||||
Deno.test("get columns from returning query", function () {
|
||||
const db = new DB();
|
||||
|
||||
db.query(
|
||||
"CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)",
|
||||
);
|
||||
const query = db.prepareQuery(
|
||||
"INSERT INTO test (name) VALUES (?) RETURNING *",
|
||||
);
|
||||
|
||||
assertEquals(query.columns(), [
|
||||
{ name: "id", originName: "id", tableName: "test" },
|
||||
{ name: "name", originName: "name", tableName: "test" },
|
||||
]);
|
||||
|
||||
assertEquals(query.all(["name"]), [[1, "name"]]);
|
||||
});
|
||||
|
||||
Deno.test("get columns with renamed column", function () {
|
||||
const db = new DB();
|
||||
|
||||
db.query(
|
||||
"CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)",
|
||||
);
|
||||
db.query("INSERT INTO test (name) VALUES (?)", ["name"]);
|
||||
|
||||
const query = db.prepareQuery(
|
||||
"SELECT id AS test_id, name AS test_name from test",
|
||||
);
|
||||
const columns = query.columns();
|
||||
|
||||
assertEquals(columns, [
|
||||
{ name: "test_id", originName: "id", tableName: "test" },
|
||||
{ name: "test_name", originName: "name", tableName: "test" },
|
||||
]);
|
||||
});
|
||||
|
||||
Deno.test("columns can be obtained from empty prepared query", function () {
|
||||
const db = new DB();
|
||||
db.query(
|
||||
"CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEST, age INTEGER)",
|
||||
);
|
||||
db.query("INSERT INTO test (name, age) VALUES (?, ?)", ["Peter Parker", 21]);
|
||||
|
||||
const query = db.prepareQuery("SELECT * FROM test");
|
||||
const columnsFromPreparedQuery = query.columns();
|
||||
query.finalize();
|
||||
|
||||
const queryEmpty = db.prepareQuery("SELECT * FROM test WHERE 1 = 0");
|
||||
const columnsFromPreparedQueryWithEmptyQuery = queryEmpty.columns();
|
||||
assertEquals(queryEmpty.all(), []);
|
||||
query.finalize();
|
||||
|
||||
assertEquals(
|
||||
[{ name: "id", originName: "id", tableName: "test" }, {
|
||||
name: "name",
|
||||
originName: "name",
|
||||
tableName: "test",
|
||||
}, { name: "age", originName: "age", tableName: "test" }],
|
||||
columnsFromPreparedQuery,
|
||||
);
|
||||
assertEquals(
|
||||
columnsFromPreparedQueryWithEmptyQuery,
|
||||
columnsFromPreparedQuery,
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("invalid number of bound parameters throws", function () {
|
||||
const db = new DB();
|
||||
db.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)");
|
||||
|
||||
// too many
|
||||
assertThrows(() => {
|
||||
db.query("SELECT * FROM test", [null]);
|
||||
});
|
||||
assertThrows(() => {
|
||||
db.query("SELECT * FROM test LIMIT ?", [5, "extra"]);
|
||||
});
|
||||
|
||||
// too few
|
||||
assertThrows(() => db.query("SELECT * FROM test LIMIT ?", []));
|
||||
assertThrows(() => {
|
||||
db.query(
|
||||
"SELECT * FROM test WHERE id >= ? AND id <= ? LIMIT ?",
|
||||
[42],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Deno.test("using finalized prepared query throws", function () {
|
||||
const db = new DB();
|
||||
db.query("CREATE TABLE test (name TEXT)");
|
||||
const query = db.prepareQuery("INSERT INTO test (name) VALUES (?)");
|
||||
query.finalize();
|
||||
|
||||
assertThrows(() => query.execute(["test"]));
|
||||
db.close();
|
||||
});
|
||||
|
||||
Deno.test("invalid binding throws", function () {
|
||||
const db = new DB();
|
||||
db.query("CREATE TABLE test (id INTEGER)");
|
||||
assertThrows(() => {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
const badBinding: any = [{}];
|
||||
db.query("SELECT * FORM test WHERE id = ?", badBinding);
|
||||
});
|
||||
db.close();
|
||||
});
|
||||
|
||||
Deno.test("get columns from finalized query throws", function () {
|
||||
const db = new DB();
|
||||
|
||||
db.query("CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT)");
|
||||
|
||||
const query = db.prepareQuery("SELECT id from test");
|
||||
query.finalize();
|
||||
|
||||
// after iteration is done
|
||||
assertThrows(() => {
|
||||
query.columns();
|
||||
});
|
||||
});
|
692
plugos/forked/deno-sqlite/src/query.ts
Normal file
692
plugos/forked/deno-sqlite/src/query.ts
Normal file
@ -0,0 +1,692 @@
|
||||
import { StatementPtr, Wasm } from "../build/sqlite.js";
|
||||
import { getStr, setArr, setStr } from "./wasm.ts";
|
||||
import { Status, Types, Values } from "./constants.ts";
|
||||
import { SqliteError } from "./error.ts";
|
||||
|
||||
/**
|
||||
* The default type for returned rows.
|
||||
*/
|
||||
export type Row = Array<unknown>;
|
||||
|
||||
/**
|
||||
* The default type for row returned
|
||||
* as objects.
|
||||
*/
|
||||
export type RowObject = Record<string, unknown>;
|
||||
|
||||
/**
|
||||
* Possible parameter values to be bound to a query.
|
||||
*
|
||||
* When values are bound to a query, they are
|
||||
* converted between JavaScript and SQLite types
|
||||
* in the following way:
|
||||
*
|
||||
* | JS type in | SQL type | JS type out |
|
||||
* |------------|-----------------|------------------|
|
||||
* | number | INTEGER or REAL | number or bigint |
|
||||
* | bigint | INTEGER | number or bigint |
|
||||
* | boolean | INTEGER | number |
|
||||
* | string | TEXT | string |
|
||||
* | Date | TEXT | string |
|
||||
* | Uint8Array | BLOB | Uint8Array |
|
||||
* | null | NULL | null |
|
||||
* | undefined | NULL | null |
|
||||
*
|
||||
* If no value is provided for a given parameter,
|
||||
* SQLite will default to NULL.
|
||||
*
|
||||
* If a `bigint` is bound, it is converted to a
|
||||
* signed 64 bit integer, which may overflow.
|
||||
*
|
||||
* If an integer value is read from the database, which
|
||||
* is too big to safely be contained in a `number`, it
|
||||
* is automatically returned as a `bigint`.
|
||||
*
|
||||
* If a `Date` is bound, it will be converted to
|
||||
* an ISO 8601 string: `YYYY-MM-DDTHH:MM:SS.SSSZ`.
|
||||
* This format is understood by built-in SQLite
|
||||
* date-time functions. Also see https://sqlite.org/lang_datefunc.html.
|
||||
*/
|
||||
export type QueryParameter =
|
||||
| boolean
|
||||
| number
|
||||
| bigint
|
||||
| string
|
||||
| null
|
||||
| undefined
|
||||
| Date
|
||||
| Uint8Array;
|
||||
|
||||
/**
|
||||
* A set of query parameters.
|
||||
*
|
||||
* When a query is constructed, it can contain
|
||||
* either positional or named parameters. For
|
||||
* more information see https://www.sqlite.org/lang_expr.html#parameters.
|
||||
*
|
||||
* A set of parameters can be passed to
|
||||
* a query method either as an array of
|
||||
* parameters (in positional order), or
|
||||
* as an object which maps parameter names
|
||||
* to their values:
|
||||
*
|
||||
* | SQL Parameter | QueryParameterSet |
|
||||
* |---------------|-------------------------|
|
||||
* | `?NNN` or `?` | NNN-th value in array |
|
||||
* | `:AAAA` | value `AAAA` or `:AAAA` |
|
||||
* | `@AAAA` | value `@AAAA` |
|
||||
* | `$AAAA` | value `$AAAA` |
|
||||
*
|
||||
* See `QueryParameter` for documentation on
|
||||
* how values are converted between SQL
|
||||
* and JavaScript types.
|
||||
*/
|
||||
export type QueryParameterSet =
|
||||
| Record<string, QueryParameter>
|
||||
| Array<QueryParameter>;
|
||||
|
||||
/**
|
||||
* Name of a column in a database query.
|
||||
*/
|
||||
export interface ColumnName {
|
||||
/**
|
||||
* Name of the returned column.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Name of the database column that stores
|
||||
* the data returned from this query.
|
||||
*
|
||||
* This might be different from `name` if a
|
||||
* columns was renamed using e.g. as in
|
||||
* `SELECT foo AS bar FROM table`.
|
||||
*/
|
||||
originName: string;
|
||||
/**
|
||||
* Name of the table that stores the data
|
||||
* returned from this query.
|
||||
*/
|
||||
tableName: string;
|
||||
}
|
||||
|
||||
interface RowsIterator<R> {
|
||||
next: () => IteratorResult<R>;
|
||||
[Symbol.iterator]: () => RowsIterator<R>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A prepared query which can be executed many
|
||||
* times.
|
||||
*/
|
||||
export class PreparedQuery<
|
||||
R extends Row = Row,
|
||||
O extends RowObject = RowObject,
|
||||
P extends QueryParameterSet = QueryParameterSet,
|
||||
> {
|
||||
private _wasm: Wasm;
|
||||
private _stmt: StatementPtr;
|
||||
private _openStatements: Set<StatementPtr>;
|
||||
|
||||
private _status: number;
|
||||
private _iterKv: boolean;
|
||||
private _rowKeys?: Array<string>;
|
||||
private _finalized: boolean;
|
||||
|
||||
/**
|
||||
* This constructor should never be used directly.
|
||||
* Instead a prepared query can be obtained by
|
||||
* calling `DB.prepareQuery`.
|
||||
*/
|
||||
constructor(
|
||||
wasm: Wasm,
|
||||
stmt: StatementPtr,
|
||||
openStatements: Set<StatementPtr>,
|
||||
) {
|
||||
this._wasm = wasm;
|
||||
this._stmt = stmt;
|
||||
this._openStatements = openStatements;
|
||||
|
||||
this._status = Status.Unknown;
|
||||
this._iterKv = false;
|
||||
this._finalized = false;
|
||||
}
|
||||
|
||||
private startQuery(params?: P) {
|
||||
if (this._finalized) {
|
||||
throw new SqliteError("Query is finalized.");
|
||||
}
|
||||
|
||||
// Reset query
|
||||
this._wasm.reset(this._stmt);
|
||||
this._wasm.clear_bindings(this._stmt);
|
||||
|
||||
// Prepare parameter array
|
||||
let parameters = [];
|
||||
if (Array.isArray(params)) {
|
||||
parameters = params;
|
||||
} else if (typeof params === "object") {
|
||||
// Resolve parameter index for named parameter
|
||||
for (const key of Object.keys(params)) {
|
||||
let name = key;
|
||||
// blank names default to ':'
|
||||
if (name[0] !== ":" && name[0] !== "@" && name[0] !== "$") {
|
||||
name = `:${name}`;
|
||||
}
|
||||
const idx = setStr(
|
||||
this._wasm,
|
||||
name,
|
||||
(ptr) => this._wasm.bind_parameter_index(this._stmt, ptr),
|
||||
);
|
||||
if (idx === Values.Error) {
|
||||
throw new SqliteError(`No parameter named '${name}'.`);
|
||||
}
|
||||
parameters[idx - 1] = params[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Bind parameters
|
||||
for (let i = 0; i < parameters.length; i++) {
|
||||
let value = parameters[i];
|
||||
let status;
|
||||
switch (typeof value) {
|
||||
case "boolean":
|
||||
value = value ? 1 : 0;
|
||||
// fall through
|
||||
case "number":
|
||||
if (Number.isSafeInteger(value)) {
|
||||
status = this._wasm.bind_int(this._stmt, i + 1, value);
|
||||
} else {
|
||||
status = this._wasm.bind_double(this._stmt, i + 1, value);
|
||||
}
|
||||
break;
|
||||
case "bigint":
|
||||
// bigint is bound as two 32bit integers and reassembled on the C side
|
||||
if (value > 9223372036854775807n || value < -9223372036854775808n) {
|
||||
throw new SqliteError(
|
||||
`BigInt value ${value} overflows 64 bit integer.`,
|
||||
);
|
||||
} else {
|
||||
const posVal = value >= 0n ? value : -value;
|
||||
const sign = value >= 0n ? 1 : -1;
|
||||
const upper = Number(BigInt.asUintN(32, posVal >> 32n));
|
||||
const lower = Number(BigInt.asUintN(32, posVal));
|
||||
status = this._wasm.bind_big_int(
|
||||
this._stmt,
|
||||
i + 1,
|
||||
sign,
|
||||
upper,
|
||||
lower,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case "string":
|
||||
status = setStr(
|
||||
this._wasm,
|
||||
value,
|
||||
(ptr) => this._wasm.bind_text(this._stmt, i + 1, ptr),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
if (value instanceof Date) {
|
||||
// Dates are allowed and bound to TEXT, formatted `YYYY-MM-DDTHH:MM:SS.SSSZ`
|
||||
status = setStr(
|
||||
this._wasm,
|
||||
value.toISOString(),
|
||||
(ptr) => this._wasm.bind_text(this._stmt, i + 1, ptr),
|
||||
);
|
||||
} else if (value instanceof Uint8Array) {
|
||||
// Uint8Arrays are allowed and bound to BLOB
|
||||
const size = value.length;
|
||||
status = setArr(
|
||||
this._wasm,
|
||||
value,
|
||||
(ptr) => this._wasm.bind_blob(this._stmt, i + 1, ptr, size),
|
||||
);
|
||||
} else if (value === null || value === undefined) {
|
||||
// Both null and undefined result in a NULL entry
|
||||
status = this._wasm.bind_null(this._stmt, i + 1);
|
||||
} else {
|
||||
throw new SqliteError(`Can not bind ${typeof value}.`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (status !== Status.SqliteOk) {
|
||||
throw new SqliteError(this._wasm, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getQueryRow(): R {
|
||||
if (this._finalized) {
|
||||
throw new SqliteError("Query is finalized.");
|
||||
}
|
||||
|
||||
const columnCount = this._wasm.column_count(this._stmt);
|
||||
const row: Row = [];
|
||||
for (let i = 0; i < columnCount; i++) {
|
||||
switch (this._wasm.column_type(this._stmt, i)) {
|
||||
case Types.Integer:
|
||||
row.push(this._wasm.column_int(this._stmt, i));
|
||||
break;
|
||||
case Types.Float:
|
||||
row.push(this._wasm.column_double(this._stmt, i));
|
||||
break;
|
||||
case Types.Text:
|
||||
row.push(
|
||||
getStr(
|
||||
this._wasm,
|
||||
this._wasm.column_text(this._stmt, i),
|
||||
),
|
||||
);
|
||||
break;
|
||||
case Types.Blob: {
|
||||
const ptr = this._wasm.column_blob(this._stmt, i);
|
||||
if (ptr === 0) {
|
||||
// Zero pointer results in null
|
||||
row.push(null);
|
||||
} else {
|
||||
const length = this._wasm.column_bytes(this._stmt, i);
|
||||
// Slice should copy the bytes, as it makes a shallow copy
|
||||
row.push(
|
||||
new Uint8Array(this._wasm.memory.buffer, ptr, length).slice(),
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Types.BigInteger: {
|
||||
const ptr = this._wasm.column_text(this._stmt, i);
|
||||
row.push(BigInt(getStr(this._wasm, ptr)));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// TODO(dyedgreen): Differentiate between NULL and not-recognized?
|
||||
row.push(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return row as R;
|
||||
}
|
||||
|
||||
private makeRowObject(row: Row): O {
|
||||
if (this._rowKeys == null) {
|
||||
const rowCount = this._wasm.column_count(this._stmt);
|
||||
this._rowKeys = [];
|
||||
for (let i = 0; i < rowCount; i++) {
|
||||
this._rowKeys.push(
|
||||
getStr(this._wasm, this._wasm.column_name(this._stmt, i)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const obj = row.reduce<RowObject>((obj, val, idx) => {
|
||||
obj[this._rowKeys![idx]] = val;
|
||||
return obj;
|
||||
}, {});
|
||||
return obj as O;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the given parameters to the query
|
||||
* and returns an iterator over rows.
|
||||
*
|
||||
* Using an iterator avoids loading all returned
|
||||
* rows into memory and hence allows to process a large
|
||||
* number of rows.
|
||||
*
|
||||
* Calling `iter`, `all`, or `first` invalidates any iterators
|
||||
* previously returned from this prepared query.
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```typescript
|
||||
* const query = db.prepareQuery<[number, string]>("SELECT id, name FROM people");
|
||||
* for (const [id, name] of query.iter()) {
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* To avoid SQL injection, user-provided values
|
||||
* should always be passed to the database through
|
||||
* a query parameter.
|
||||
*
|
||||
* ```typescript
|
||||
* const query = db.prepareQuery("SELECT id FROM people WHERE name = ?");
|
||||
* preparedQuery.iter([name]);
|
||||
* ```
|
||||
*
|
||||
* ```typescript
|
||||
* const query = db.prepareQuery("SELECT id FROM people WHERE name = :name");
|
||||
* preparedQuery.iter({ name });
|
||||
* ```
|
||||
*
|
||||
* See `QueryParameterSet` for documentation on
|
||||
* how values can be bound to SQL statements.
|
||||
*
|
||||
* See `QueryParameter` for documentation on how
|
||||
* values are returned from the database.
|
||||
*/
|
||||
iter(params?: P): RowsIterator<R> {
|
||||
this.startQuery(params);
|
||||
this._status = this._wasm.step(this._stmt);
|
||||
if (
|
||||
this._status !== Status.SqliteRow && this._status !== Status.SqliteDone
|
||||
) {
|
||||
throw new SqliteError(this._wasm, this._status);
|
||||
}
|
||||
this._iterKv = false;
|
||||
return this as RowsIterator<R>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `iter` except each row is returned
|
||||
* as an object containing key-value pairs.
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```typescript
|
||||
* const query = db.prepareQuery<_, { id: number, name: string }>("SELECT id, name FROM people");
|
||||
* for (const { id, name } of query.iter()) {
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
iterEntries(params?: P): RowsIterator<O> {
|
||||
this.iter(params);
|
||||
this._iterKv = true;
|
||||
return this as RowsIterator<O>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*
|
||||
* Implements the iterable protocol. It is
|
||||
* a bug to call this method directly.
|
||||
*/
|
||||
[Symbol.iterator](): RowsIterator<R | O> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*
|
||||
* Implements the iterator protocol. It is
|
||||
* a bug to call this method directly.
|
||||
*/
|
||||
next(): IteratorResult<R | O> {
|
||||
if (this._status === Status.SqliteRow) {
|
||||
const value = this.getQueryRow();
|
||||
this._status = this._wasm.step(this._stmt);
|
||||
if (this._iterKv) {
|
||||
return { value: this.makeRowObject(value), done: false };
|
||||
} else {
|
||||
return { value, done: false };
|
||||
}
|
||||
} else if (this._status === Status.SqliteDone) {
|
||||
return { value: null, done: true };
|
||||
} else {
|
||||
throw new SqliteError(this._wasm, this._status);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the given parameters to the query
|
||||
* and returns an array containing all resulting
|
||||
* rows.
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```typescript
|
||||
* const query = db.prepareQuery<[number, string]>("SELECT id, name FROM people");
|
||||
* const rows = query.all();
|
||||
* // [[1, "Peter"], ...]
|
||||
* ```
|
||||
*
|
||||
* To avoid SQL injection, user-provided values
|
||||
* should always be passed to the database through
|
||||
* a query parameter.
|
||||
*
|
||||
* ```typescript
|
||||
* const query = db.prepareQuery("SELECT id FROM people WHERE name = ?");
|
||||
* preparedQuery.all([name]);
|
||||
* ```
|
||||
*
|
||||
* ```typescript
|
||||
* const query = db.prepareQuery("SELECT id FROM people WHERE name = :name");
|
||||
* preparedQuery.all({ name });
|
||||
* ```
|
||||
*
|
||||
* See `QueryParameterSet` for documentation on
|
||||
* how values can be bound to SQL statements.
|
||||
*
|
||||
* See `QueryParameter` for documentation on how
|
||||
* values are returned from the database.
|
||||
*/
|
||||
all(params?: P): Array<R> {
|
||||
this.startQuery(params);
|
||||
const rows: Array<R> = [];
|
||||
this._status = this._wasm.step(this._stmt);
|
||||
while (this._status === Status.SqliteRow) {
|
||||
rows.push(this.getQueryRow());
|
||||
this._status = this._wasm.step(this._stmt);
|
||||
}
|
||||
if (this._status !== Status.SqliteDone) {
|
||||
throw new SqliteError(this._wasm, this._status);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `all` except each row is returned
|
||||
* as an object containing key-value pairs.
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```typescript
|
||||
* const query = db.prepareQuery<_, { id: number, name: string }>("SELECT id, name FROM people");
|
||||
* const rows = query.all();
|
||||
* // [{ id: 1, name: "Peter" }, ...]
|
||||
* ```
|
||||
*/
|
||||
allEntries(params?: P): Array<O> {
|
||||
return this.all(params).map((row) => this.makeRowObject(row));
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the given parameters to the query
|
||||
* and returns the first resulting row or
|
||||
* `undefined` when there are no rows returned
|
||||
* by the query.
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```typescript
|
||||
* const query = db.prepareQuery<[number, string]>("SELECT id, name FROM people");
|
||||
* const person = query.first();
|
||||
* // [1, "Peter"]
|
||||
* ```
|
||||
*
|
||||
* ```typescript
|
||||
* const query = db.prepareQuery("SELECT id, name FROM people WHERE name = ?");
|
||||
* const person = query.first(["not a name"]);
|
||||
* // undefined
|
||||
* ```
|
||||
*
|
||||
* To avoid SQL injection, user-provided values
|
||||
* should always be passed to the database through
|
||||
* a query parameter.
|
||||
*
|
||||
* ```typescript
|
||||
* const query = db.prepareQuery("SELECT id FROM people WHERE name = ?");
|
||||
* preparedQuery.first([name]);
|
||||
* ```
|
||||
*
|
||||
* ```typescript
|
||||
* const query = db.prepareQuery("SELECT id FROM people WHERE name = :name");
|
||||
* preparedQuery.first({ name });
|
||||
* ```
|
||||
*
|
||||
* See `QueryParameterSet` for documentation on
|
||||
* how values can be bound to SQL statements.
|
||||
*
|
||||
* See `QueryParameter` for documentation on how
|
||||
* values are returned from the database.
|
||||
*/
|
||||
first(params?: P): R | undefined {
|
||||
this.startQuery(params);
|
||||
|
||||
this._status = this._wasm.step(this._stmt);
|
||||
let row = undefined;
|
||||
if (this._status === Status.SqliteRow) {
|
||||
row = this.getQueryRow();
|
||||
}
|
||||
|
||||
while (this._status === Status.SqliteRow) {
|
||||
this._status = this._wasm.step(this._stmt);
|
||||
}
|
||||
if (this._status !== Status.SqliteDone) {
|
||||
throw new SqliteError(this._wasm, this._status);
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `first` except the row is returned
|
||||
* as an object containing key-value pairs.
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```typescript
|
||||
* const query = db.prepareQuery<_, { id: number, name: string }>("SELECT id, name FROM people");
|
||||
* const person = query.first();
|
||||
* // { id: 1, name: "Peter" }
|
||||
* ```
|
||||
*/
|
||||
firstEntry(params?: P): O | undefined {
|
||||
const row = this.first(params);
|
||||
return row === undefined ? undefined : this.makeRowObject(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* **Deprecated:** prefer `first`.
|
||||
*/
|
||||
one(params?: P): R {
|
||||
const rows = this.all(params);
|
||||
|
||||
if (rows.length === 0) {
|
||||
throw new SqliteError("The query did not return any rows.");
|
||||
} else if (rows.length > 1) {
|
||||
throw new SqliteError("The query returned more than one row.");
|
||||
} else {
|
||||
return rows[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* **Deprecated:** prefer `firstEntry`.
|
||||
*/
|
||||
oneEntry(params?: P): O {
|
||||
return this.makeRowObject(this.one(params));
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the given parameters to the query and
|
||||
* executes the query, ignoring any rows which
|
||||
* might be returned.
|
||||
*
|
||||
* Using this method is more efficient when the
|
||||
* rows returned by a query are not needed or
|
||||
* the query does not return any rows.
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```typescript
|
||||
* const query = db.prepareQuery<_, _, [string]>("INSERT INTO people (name) VALUES (?)");
|
||||
* query.execute(["Peter"]);
|
||||
* ```
|
||||
*
|
||||
* ```typescript
|
||||
* const query = db.prepareQuery<_, _, { name: string }>("INSERT INTO people (name) VALUES (:name)");
|
||||
* query.execute({ name: "Peter" });
|
||||
* ```
|
||||
*
|
||||
* See `QueryParameterSet` for documentation on
|
||||
* how values can be bound to SQL statements.
|
||||
*
|
||||
* See `QueryParameter` for documentation on how
|
||||
* values are returned from the database.
|
||||
*/
|
||||
execute(params?: P) {
|
||||
this.startQuery(params);
|
||||
this._status = this._wasm.step(this._stmt);
|
||||
while (this._status === Status.SqliteRow) {
|
||||
this._status = this._wasm.step(this._stmt);
|
||||
}
|
||||
if (this._status !== Status.SqliteDone) {
|
||||
throw new SqliteError(this._wasm, this._status);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the prepared query. This must be
|
||||
* called once the query is no longer needed
|
||||
* to avoid leaking resources.
|
||||
*
|
||||
* After a prepared query has been finalized,
|
||||
* calls to `iter`, `all`, `first`, `execute`,
|
||||
* or `columns` will fail.
|
||||
*
|
||||
* Using iterators which were previously returned
|
||||
* from the finalized query will fail.
|
||||
*
|
||||
* `finalize` may safely be called multiple
|
||||
* times.
|
||||
*/
|
||||
finalize() {
|
||||
if (!this._finalized) {
|
||||
this._wasm.finalize(this._stmt);
|
||||
this._openStatements.delete(this._stmt);
|
||||
this._finalized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the column names for the query
|
||||
* results.
|
||||
*
|
||||
* This method returns an array of objects,
|
||||
* where each object has the following properties:
|
||||
*
|
||||
* | Property | Value |
|
||||
* |--------------|--------------------------------------------|
|
||||
* | `name` | the result of `sqlite3_column_name` |
|
||||
* | `originName` | the result of `sqlite3_column_origin_name` |
|
||||
* | `tableName` | the result of `sqlite3_column_table_name` |
|
||||
*/
|
||||
columns(): Array<ColumnName> {
|
||||
if (this._finalized) {
|
||||
throw new SqliteError(
|
||||
"Unable to retrieve column names from finalized transaction.",
|
||||
);
|
||||
}
|
||||
|
||||
const columnCount = this._wasm.column_count(this._stmt);
|
||||
const columns: Array<ColumnName> = [];
|
||||
for (let i = 0; i < columnCount; i++) {
|
||||
const name = getStr(
|
||||
this._wasm,
|
||||
this._wasm.column_name(this._stmt, i),
|
||||
);
|
||||
const originName = getStr(
|
||||
this._wasm,
|
||||
this._wasm.column_origin_name(this._stmt, i),
|
||||
);
|
||||
const tableName = getStr(
|
||||
this._wasm,
|
||||
this._wasm.column_table_name(this._stmt, i),
|
||||
);
|
||||
columns.push({ name, originName, tableName });
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
}
|
80
plugos/forked/deno-sqlite/src/readme.test.ts
Normal file
80
plugos/forked/deno-sqlite/src/readme.test.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import {
|
||||
assertEquals,
|
||||
assertMatch,
|
||||
} from "https://deno.land/std@0.154.0/testing/asserts.ts";
|
||||
|
||||
import { DB } from "../mod.ts";
|
||||
|
||||
Deno.test("README example", function () {
|
||||
const db = new DB(/* in memory */);
|
||||
db.execute(`
|
||||
CREATE TABLE IF NOT EXISTS people (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT
|
||||
)
|
||||
`);
|
||||
|
||||
const name =
|
||||
["Peter Parker", "Clark Kent", "Bruce Wane"][Math.floor(Math.random() * 3)];
|
||||
|
||||
// Run a simple query
|
||||
db.query("INSERT INTO people (name) VALUES (?)", [name]);
|
||||
|
||||
// Print out data in table
|
||||
for (const [_name] of db.query("SELECT name FROM people")) continue; // no console.log ;)
|
||||
|
||||
db.close();
|
||||
});
|
||||
|
||||
Deno.test("old README example", function () {
|
||||
const db = new DB();
|
||||
const first = ["Bruce", "Clark", "Peter"];
|
||||
const last = ["Wane", "Kent", "Parker"];
|
||||
db.query(
|
||||
"CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, email TEXT, subscribed INTEGER)",
|
||||
);
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const name = `${first[Math.floor(Math.random() * first.length)]} ${
|
||||
last[
|
||||
Math.floor(
|
||||
Math.random() * last.length,
|
||||
)
|
||||
]
|
||||
}`;
|
||||
const email = `${name.replace(" ", "-")}@deno.land`;
|
||||
const subscribed = Math.random() > 0.5 ? true : false;
|
||||
db.query("INSERT INTO users (name, email, subscribed) VALUES (?, ?, ?)", [
|
||||
name,
|
||||
email,
|
||||
subscribed,
|
||||
]);
|
||||
}
|
||||
|
||||
for (
|
||||
const [
|
||||
name,
|
||||
email,
|
||||
] of db.query<[string, string]>(
|
||||
"SELECT name, email FROM users WHERE subscribed = ? LIMIT 100",
|
||||
[true],
|
||||
)
|
||||
) {
|
||||
assertMatch(name, /(Bruce|Clark|Peter) (Wane|Kent|Parker)/);
|
||||
assertEquals(email, `${name.replace(" ", "-")}@deno.land`);
|
||||
}
|
||||
|
||||
const res = db.query("SELECT email FROM users WHERE name LIKE ?", [
|
||||
"Robert Parr",
|
||||
]);
|
||||
assertEquals(res, []);
|
||||
|
||||
const subscribers = db.query(
|
||||
"SELECT name, email FROM users WHERE subscribed = ?",
|
||||
[true],
|
||||
);
|
||||
for (const [_name, _email] of subscribers) {
|
||||
if (Math.random() > 0.5) continue;
|
||||
break;
|
||||
}
|
||||
});
|
40
plugos/forked/deno-sqlite/src/wasm.test.ts
Normal file
40
plugos/forked/deno-sqlite/src/wasm.test.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import {
|
||||
assertEquals,
|
||||
assertThrows,
|
||||
} from "https://deno.land/std@0.154.0/testing/asserts.ts";
|
||||
|
||||
import { Wasm } from "../build/sqlite.js";
|
||||
import * as wasm from "./wasm.ts";
|
||||
|
||||
function mock(
|
||||
malloc: () => number = () => 1,
|
||||
free: (pts: number) => void = () => {},
|
||||
): Wasm {
|
||||
const memory = new Uint8Array(2048);
|
||||
return {
|
||||
malloc,
|
||||
free,
|
||||
str_len: (ptr: number) => {
|
||||
let len = 0;
|
||||
for (let idx = ptr; memory.at(idx) != 0; idx++) len++;
|
||||
return len;
|
||||
},
|
||||
memory,
|
||||
} as unknown as Wasm;
|
||||
}
|
||||
|
||||
Deno.test("round trip string", function () {
|
||||
const mockWasm = mock();
|
||||
const testCases = ["Hello world!", "Söme, fünky lëttêrß", "你好👋"];
|
||||
for (const input of testCases) {
|
||||
const output = wasm.setStr(mockWasm, input, (ptr) => {
|
||||
return wasm.getStr(mockWasm, ptr);
|
||||
});
|
||||
assertEquals(input, output);
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("throws on allocation error", function () {
|
||||
const mockWasm = mock(() => 0);
|
||||
assertThrows(() => wasm.setStr(mockWasm, "Hello world!", (_) => null));
|
||||
});
|
87
plugos/forked/deno-sqlite/src/wasm.ts
Normal file
87
plugos/forked/deno-sqlite/src/wasm.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { Wasm } from "../build/sqlite.js";
|
||||
import { SqliteError } from "./error.ts";
|
||||
|
||||
// Move string to C
|
||||
export function setStr<T>(
|
||||
wasm: Wasm,
|
||||
str: string,
|
||||
closure: (ptr: number) => T,
|
||||
): T {
|
||||
const bytes = new TextEncoder().encode(str);
|
||||
const ptr = wasm.malloc(bytes.length + 1);
|
||||
if (ptr === 0) {
|
||||
throw new SqliteError("Out of memory.");
|
||||
}
|
||||
const mem = new Uint8Array(wasm.memory.buffer, ptr, bytes.length + 1);
|
||||
mem.set(bytes);
|
||||
mem[bytes.length] = 0; // \0 terminator
|
||||
try {
|
||||
const result = closure(ptr);
|
||||
wasm.free(ptr);
|
||||
return result;
|
||||
} catch (error) {
|
||||
wasm.free(ptr);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Move Uint8Array to C
|
||||
export function setArr<T>(
|
||||
wasm: Wasm,
|
||||
arr: Uint8Array,
|
||||
closure: (ptr: number) => T,
|
||||
): T {
|
||||
const ptr = wasm.malloc(arr.length);
|
||||
if (ptr === 0) {
|
||||
throw new SqliteError("Out of memory.");
|
||||
}
|
||||
const mem = new Uint8Array(wasm.memory.buffer, ptr, arr.length);
|
||||
mem.set(arr);
|
||||
try {
|
||||
const result = closure(ptr);
|
||||
wasm.free(ptr);
|
||||
return result;
|
||||
} catch (error) {
|
||||
wasm.free(ptr);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Read string from C
|
||||
export function getStr(wasm: Wasm, ptr: number): string {
|
||||
const len = wasm.str_len(ptr);
|
||||
const bytes = new Uint8Array(wasm.memory.buffer, ptr, len);
|
||||
if (len > 16) {
|
||||
return new TextDecoder().decode(bytes);
|
||||
} else {
|
||||
// This optimization is lifted from EMSCRIPTEN's glue code
|
||||
let str = "";
|
||||
let idx = 0;
|
||||
while (idx < len) {
|
||||
let u0 = bytes[idx++];
|
||||
if (!(u0 & 0x80)) {
|
||||
str += String.fromCharCode(u0);
|
||||
continue;
|
||||
}
|
||||
const u1 = bytes[idx++] & 63;
|
||||
if ((u0 & 0xE0) == 0xC0) {
|
||||
str += String.fromCharCode(((u0 & 31) << 6) | u1);
|
||||
continue;
|
||||
}
|
||||
const u2 = bytes[idx++] & 63;
|
||||
if ((u0 & 0xF0) == 0xE0) {
|
||||
u0 = ((u0 & 15) << 12) | (u1 << 6) | u2;
|
||||
} else {
|
||||
// cut warning
|
||||
u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (bytes[idx++] & 63);
|
||||
}
|
||||
if (u0 < 0x10000) {
|
||||
str += String.fromCharCode(u0);
|
||||
} else {
|
||||
const ch = u0 - 0x10000;
|
||||
str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF));
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user