| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-02-04 19:04:49 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-02-04 19:04:49 UTC |
| parent | 3624a86e6dc1e56696e17fdcfe49f334f23d60e4 |
| borogove/persistence/Sqlite.hx | +53 | -17 |
| borogove/persistence/SqliteDriver.hx | +10 | -36 |
| borogove/persistence/SqliteDriver.js.hx | +11 | -6 |
diff --git a/borogove/persistence/Sqlite.hx b/borogove/persistence/Sqlite.hx index 000e1cd..b0cff33 100644 --- a/borogove/persistence/Sqlite.hx +++ b/borogove/persistence/Sqlite.hx @@ -32,6 +32,42 @@ class Sqlite implements Persistence implements KeyValueStore { final db: SqliteDriver; final media: MediaStore; + @:allow(borogove) + private static function prepare(q: { sql: String, ?params: Array<Dynamic> }): String { + return ~/\?/gm.map(q.sql, f -> { + var p = (q.params ?? []).shift(); + return switch (Type.typeof(p)) { + case TClass(String): + if (p.indexOf("\000") >= 0) { + var hexChars = new Array<String>(); + for (i in 0...p.length) { + hexChars.push(StringTools.hex(StringTools.fastCodeAt(p, i), 2)); + } + "x'" + hexChars.join("") + "'"; + } else { + "'" + p.split("'").join("''") + "'"; + } + case TBool: + p == true ? "1" : "0"; + case TFloat: + Std.string(p); + case TInt: + Std.string(p); + case TNull: + "NULL"; + case TClass(Array): + var bytes:Bytes = Bytes.ofData(p); + "X'" + bytes.toHex() + "'"; + case TClass(haxe.io.Bytes): + var bytes:Bytes = cast p; + "X'" + bytes.toHex() + "'"; + case _: + throw("UKNONWN: " + Type.typeof(p)); + } + }); + + } + /** Create a basic persistence layer based on sqlite @@ -43,7 +79,7 @@ class Sqlite implements Persistence implements KeyValueStore { this.media = media; media.setKV(this); db = new SqliteDriver(dbfile, (exec) -> { - exec(["PRAGMA user_version;"]).then(iter -> { + exec(["PRAGMA user_version"]).then(iter -> { final version = Std.parseInt(iter.next()?.user_version) ?? 0; return Promise.resolve(null).then(_ -> { if (version < 1) { @@ -62,9 +98,9 @@ class Sqlite implements Persistence implements KeyValueStore { type INTEGER NOT NULL, stanza TEXT NOT NULL, PRIMARY KEY (account_id, mam_id, mam_by, stanza_id) - ) STRICT;", - "CREATE INDEX messages_created_at ON messages (account_id, chat_id, created_at);", - "CREATE INDEX messages_correction_id ON messages (correction_id);", + ) STRICT", + "CREATE INDEX messages_created_at ON messages (account_id, chat_id, created_at)", + "CREATE INDEX messages_correction_id ON messages (correction_id)", "CREATE TABLE chats ( account_id TEXT NOT NULL, chat_id TEXT NOT NULL, @@ -80,15 +116,15 @@ class Sqlite implements Persistence implements KeyValueStore { presence BLOB NOT NULL, class TEXT NOT NULL, PRIMARY KEY (account_id, chat_id) - ) STRICT;", + ) STRICT", "CREATE TABLE keyvaluepairs ( k TEXT NOT NULL PRIMARY KEY, v TEXT NOT NULL - ) STRICT;", + ) STRICT", "CREATE TABLE caps ( sha1 BLOB NOT NULL PRIMARY KEY, caps BLOB NOT NULL - ) STRICT;", + ) STRICT", "CREATE TABLE services ( account_id TEXT NOT NULL, service_id TEXT NOT NULL, @@ -96,7 +132,7 @@ class Sqlite implements Persistence implements KeyValueStore { node TEXT, caps BLOB NOT NULL, PRIMARY KEY (account_id, service_id) - ) STRICT;", + ) STRICT", "CREATE TABLE accounts ( account_id TEXT NOT NULL, client_id TEXT NOT NULL, @@ -105,7 +141,7 @@ class Sqlite implements Persistence implements KeyValueStore { fast_count INTEGER NOT NULL DEFAULT 0, sm_state BLOB, PRIMARY KEY (account_id) - ) STRICT;", + ) STRICT", "CREATE TABLE reactions ( account_id TEXT NOT NULL, update_id TEXT NOT NULL, @@ -118,22 +154,22 @@ class Sqlite implements Persistence implements KeyValueStore { reactions BLOB NOT NULL, kind INTEGER NOT NULL, PRIMARY KEY (account_id, chat_id, sender_id, update_id) - ) STRICT;", - "PRAGMA user_version = 1;"]); + ) STRICT", + "PRAGMA user_version = 1"]); } return Promise.resolve(null); }).then(_ -> { if (version < 2) { - return exec(["ALTER TABLE chats ADD COLUMN notifications_filtered INTEGER;", - "ALTER TABLE chats ADD COLUMN notify_mention INTEGER NOT NULL DEFAULT 0;", - "ALTER TABLE chats ADD COLUMN notify_reply INTEGER NOT NULL DEFAULT 0;", - "PRAGMA user_version = 2;"]); + return exec(["ALTER TABLE chats ADD COLUMN notifications_filtered INTEGER", + "ALTER TABLE chats ADD COLUMN notify_mention INTEGER NOT NULL DEFAULT 0", + "ALTER TABLE chats ADD COLUMN notify_reply INTEGER NOT NULL DEFAULT 0", + "PRAGMA user_version = 2"]); } return Promise.resolve(null); }).then(_ -> { if (version < 3) { - return exec(["ALTER TABLE messages ADD COLUMN status_text TEXT;", - "PRAGMA user_version = 3;"]); + return exec(["ALTER TABLE messages ADD COLUMN status_text TEXT", + "PRAGMA user_version = 3"]); } return Promise.resolve(null); }); diff --git a/borogove/persistence/SqliteDriver.hx b/borogove/persistence/SqliteDriver.hx index d9af267..296440e 100644 --- a/borogove/persistence/SqliteDriver.hx +++ b/borogove/persistence/SqliteDriver.hx @@ -24,13 +24,13 @@ class SqliteDriver { db.request("PRAGMA temp_store=2"); if (Config.constrainedMemoryMode) db.request("PRAGMA cache_size=0"); dbs.push(db); - migrate((sql) -> this.execute(writePool, sql, [])).then(_ -> { + migrate((sql) -> this.execute(writePool, sql.map(q -> { sql: q, params: [] }))).then(_ -> { setReady(true); }); }); } - private function execute(pool: sys.thread.IThreadPool, qs: Array<String>, params: Array<Dynamic>) { + private function execute(pool: sys.thread.IThreadPool, qs: Array<{ sql: String, ?params: Array<Dynamic> }>) { return new Promise((resolve, reject) -> { pool.run(() -> { var db = dbs.pop(false); @@ -40,12 +40,8 @@ class SqliteDriver { } var result = null; for (q in qs) { - if (result == null) { - final prepared = prepare(db, q, params); - result = db.request(prepared); - } else { - db.request(q); - } + final prepared = Sqlite.prepare(q); + result = db.request(prepared); } // In testing, not copying to an array here caused BAD ACCESS sometimes // Though from sqlite docs it seems like it should be safe? @@ -60,37 +56,15 @@ class SqliteDriver { }); } - public function exec(sql: haxe.extern.EitherType<String, Array<String>>, ?params: Array<Dynamic>) { + + public function execMany(qs: Array<{ sql: String, ?params: Array<Dynamic> }>) { return ready.then(_ -> { - final qs = Std.isOfType(sql, String) ? [sql] : sql; - final pool = StringTools.startsWith(qs[0], "SELECT") ? readPool : writePool; - return execute(pool, qs, params ?? []); + final pool = StringTools.startsWith(qs[0].sql, "SELECT") ? readPool : writePool; + return execute(pool, qs); }); } - private function prepare(db: Connection, sql:String, params: Array<Dynamic>): String { - return ~/\?/gm.map(sql, f -> { - var p = params.shift(); - return switch (Type.typeof(p)) { - case TClass(String): - db.quote(p); - case TBool: - p == true ? "1" : "0"; - case TFloat: - Std.string(p); - case TInt: - Std.string(p); - case TNull: - "NULL"; - case TClass(Array): - var bytes:Bytes = Bytes.ofData(p); - "X'" + bytes.toHex() + "'"; - case TClass(haxe.io.Bytes): - var bytes:Bytes = cast p; - "X'" + bytes.toHex() + "'"; - case _: - throw("UKNONWN: " + Type.typeof(p)); - } - }); + public function exec(sql: String, ?params: Array<Dynamic>) { + return execMany([{ sql: sql, params: params }]); } } diff --git a/borogove/persistence/SqliteDriver.js.hx b/borogove/persistence/SqliteDriver.js.hx index 9b65d8f..0c760f9 100644 --- a/borogove/persistence/SqliteDriver.js.hx +++ b/borogove/persistence/SqliteDriver.js.hx @@ -36,25 +36,26 @@ class SqliteDriver { return sqlite("open", { filename: dbfile, vfs: "opfs-sahpool" }); }).then(openResult -> { dbId = openResult.dbId; - return migrate((sql) -> this.exec(sql)); + return migrate((sql) -> this.execMany(sql.map(q -> { sql: q, params: [] }))); }); } - public function exec(sql: haxe.extern.EitherType<String, Array<String>>, ?params: Array<Dynamic>): Promise<haxe.iterators.ArrayIterator<Dynamic>> { + public function execMany(qs: Array<{ sql: String, ?params: Array<Dynamic> }>): Promise<haxe.iterators.ArrayIterator<Dynamic>> { if (sqlite == null || dbId == null) { // Not ready yet return new Promise((resolve, reject) -> haxe.Timer.delay(() -> resolve(null), 100)) - .then(_ -> exec(sql, params)); + .then(_ -> execMany(qs)); } - final qs = Std.isOfType(sql, String) ? sql : (cast sql).join(""); + final first = qs.shift(); + final sql = qs.map(q -> Sqlite.prepare(q) + ";"); final items: Array<Dynamic> = []; var signalAllDone; final allDone = new Promise((resolve, reject) -> signalAllDone = resolve); return sqlite('exec', { dbId: dbId, - sql: qs, - bind: (params ?? []).map(formatParam), + sql: [first.sql + ";"].concat(sql), + bind: (first.params ?? []).map(formatParam), rowMode: "object", callback: (r) -> { if (r.rowNumber == null) { @@ -67,6 +68,10 @@ class SqliteDriver { }).then(_ -> allDone).then(_ -> items.iterator()); } + public function exec(sql: String, ?params: Array<Dynamic>) { + return execMany([{ sql: sql, params: params }]); + } + private function formatParam(p: Dynamic): Dynamic { return switch (Type.typeof(p)) { case TClass(haxe.io.Bytes):