mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-09-17 08:27:11 -04:00
Merge pull request #3400 from lightpanda-io/idb-rename
indexeddb: Add setName to IDBObjectStore and IDBIndex
This commit is contained in:
5 files changed
+146
-6
No files matched your search
@@ -1189,7 +1189,6 @@
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<<<<<<< HEAD
|
||||
|
||||
<!-- put()/add() structured-clone the value at call time: the record is the
|
||||
clone as of the call (later mutation of the argument is not stored), a
|
||||
@@ -1439,4 +1438,86 @@
|
||||
await state.done((got) => testing.expectEqual(true, got));
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Object store and index renames during an upgrade: names, name lists and
|
||||
data follow; same-name is a no-op; collisions are a ConstraintError;
|
||||
outside an upgrade it's an InvalidStateError. -->
|
||||
<script id="rename_store_and_index" type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const open = indexedDB.open("rename-db", 1);
|
||||
open.onupgradeneeded = (e) => {
|
||||
const db = e.target.result;
|
||||
const books = db.createObjectStore("books", { keyPath: "isbn" });
|
||||
books.createIndex("by_author", "author");
|
||||
books.put({ isbn: 1, author: "Herbert" });
|
||||
db.createObjectStore("other");
|
||||
|
||||
books.name = "renamed_books";
|
||||
books.name = "renamed_books"; // same name: no-op
|
||||
testing.expectError("ConstraintError", () => books.name = "other");
|
||||
testing.expectEqual("renamed_books", books.name);
|
||||
testing.expectEqual(["other", "renamed_books"], [...db.objectStoreNames]);
|
||||
testing.expectEqual(books, e.target.transaction.objectStore("renamed_books"));
|
||||
|
||||
const index = books.index("by_author");
|
||||
index.name = "by_writer";
|
||||
testing.expectEqual("by_writer", index.name);
|
||||
testing.expectEqual(["by_writer"], [...books.indexNames]);
|
||||
testing.expectEqual(index, books.index("by_writer"));
|
||||
};
|
||||
open.onsuccess = () => {
|
||||
const db = open.result;
|
||||
const tx = db.transaction("renamed_books", "readwrite");
|
||||
const store = tx.objectStore("renamed_books");
|
||||
testing.expectError("InvalidStateError", () => store.name = "nope");
|
||||
testing.expectError("InvalidStateError", () => store.index("by_writer").name = "nope");
|
||||
const req = store.index("by_writer").get("Herbert");
|
||||
req.onsuccess = () => state.resolve(req.result);
|
||||
};
|
||||
await state.done((got) => testing.expectEqual({ isbn: 1, author: "Herbert" }, got));
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- An aborted upgrade reverts renames on the handles the caller still holds. -->
|
||||
<script id="rename_reverts_on_abort" type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const setup = indexedDB.open("rename-abort-db", 1);
|
||||
setup.onupgradeneeded = (e) => e.target.result.createObjectStore("books").createIndex("by_author", "author");
|
||||
setup.onsuccess = () => {
|
||||
setup.result.close();
|
||||
const open = indexedDB.open("rename-abort-db", 2);
|
||||
let store = null;
|
||||
let index = null;
|
||||
open.onupgradeneeded = (e) => {
|
||||
const tx = e.target.transaction;
|
||||
store = tx.objectStore("books");
|
||||
index = store.index("by_author");
|
||||
store.name = "renamed";
|
||||
index.name = "renamed_index";
|
||||
tx.abort();
|
||||
};
|
||||
open.onerror = (e) => {
|
||||
e.preventDefault();
|
||||
const reopen = indexedDB.open("rename-abort-db", 1);
|
||||
reopen.onsuccess = () => {
|
||||
const db = reopen.result;
|
||||
state.resolve({
|
||||
store: store.name,
|
||||
index: index.name,
|
||||
stores: [...db.objectStoreNames],
|
||||
indexes: [...db.transaction("books").objectStore("books").indexNames],
|
||||
});
|
||||
};
|
||||
};
|
||||
};
|
||||
await state.done((got) => {
|
||||
testing.expectEqual("books", got.store);
|
||||
testing.expectEqual("by_author", got.index);
|
||||
testing.expectEqual(["books"], got.stores);
|
||||
testing.expectEqual(["by_author"], got.indexes);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
@@ -315,6 +315,15 @@ pub fn createObjectStore(
|
||||
return (try self.objectStoreId(database_id, name)).?;
|
||||
}
|
||||
|
||||
// A duplicate name surfaces as error.Constraint.
|
||||
pub fn renameObjectStore(self: *Engine, object_store_id: i64, name: []const u8) !void {
|
||||
try self.conn.exec("update idb_object_stores set name = ?2 where id = ?1", .{ object_store_id, name });
|
||||
}
|
||||
|
||||
pub fn renameIndex(self: *Engine, index_id: i64, name: []const u8) !void {
|
||||
try self.conn.exec("update idb_indexes set name = ?2 where id = ?1", .{ index_id, name });
|
||||
}
|
||||
|
||||
pub fn deleteObjectStore(self: *Engine, database_id: i64, name: []const u8) !void {
|
||||
// caller has a transaction open; cascade drops records, indexes and index
|
||||
// records.
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../../js/js.zig");
|
||||
|
||||
const Page = @import("../../../Page.zig");
|
||||
|
||||
const idb = @import("idb.zig");
|
||||
const Key = @import("Key.zig");
|
||||
const Engine = @import("Engine.zig");
|
||||
@@ -40,6 +41,7 @@ _store: *IDBObjectStore,
|
||||
_engine: *Engine,
|
||||
_index_id: i64,
|
||||
_name: []const u8,
|
||||
_original_name: ?[]const u8 = null, // needed for restore incase of abort
|
||||
_key_path: Key.KeyPath,
|
||||
_unique: bool,
|
||||
_multi_entry: bool,
|
||||
@@ -214,6 +216,26 @@ pub fn getName(self: *const IDBIndex) []const u8 {
|
||||
return self._name;
|
||||
}
|
||||
|
||||
pub fn setName(self: *IDBIndex, name: []const u8, _: *Execution) !void {
|
||||
try self.assertLive();
|
||||
const t = self._store._txn;
|
||||
if (t._mode != .versionchange) {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
try t.assertActive();
|
||||
if (std.mem.eql(u8, name, self._name)) {
|
||||
return;
|
||||
}
|
||||
self._engine.renameIndex(self._index_id, name) catch |err| switch (err) {
|
||||
error.Constraint => return error.ConstraintError,
|
||||
else => return err,
|
||||
};
|
||||
if (self._original_name == null) {
|
||||
self._original_name = self._name;
|
||||
}
|
||||
self._name = try t.dupe(name);
|
||||
}
|
||||
|
||||
pub fn getKeyPath(self: *IDBIndex, exec: *Execution) !js.Value {
|
||||
return idb.cachedKeyPathJs(&self._key_path_js, self._store._txn, self._key_path, exec);
|
||||
}
|
||||
@@ -239,7 +261,7 @@ pub const JsApi = struct {
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const name = bridge.accessor(IDBIndex.getName, null, .{});
|
||||
pub const name = bridge.accessor(IDBIndex.getName, IDBIndex.setName, .{});
|
||||
pub const keyPath = bridge.accessor(IDBIndex.getKeyPath, null, .{});
|
||||
pub const unique = bridge.accessor(IDBIndex.getUnique, null, .{});
|
||||
pub const multiEntry = bridge.accessor(IDBIndex.getMultiEntry, null, .{});
|
||||
|
||||
@@ -20,8 +20,8 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../../js/js.zig");
|
||||
|
||||
const Page = @import("../../../Page.zig");
|
||||
|
||||
const idb = @import("idb.zig");
|
||||
const Key = @import("Key.zig");
|
||||
const Engine = @import("Engine.zig");
|
||||
@@ -42,6 +42,7 @@ const IDBObjectStore = @This();
|
||||
_engine: *Engine,
|
||||
_store_id: i64,
|
||||
_name: []const u8,
|
||||
_original_name: ?[]const u8 = null, // needed for restore incase of abort
|
||||
_key_path: ?Key.KeyPath,
|
||||
_auto_increment: bool,
|
||||
_txn: *IDBTransaction,
|
||||
@@ -275,6 +276,27 @@ pub fn getName(self: *const IDBObjectStore) []const u8 {
|
||||
return self._name;
|
||||
}
|
||||
|
||||
// Only during an upgrade.
|
||||
pub fn setName(self: *IDBObjectStore, name: []const u8, _: *Execution) !void {
|
||||
try self.assertLive();
|
||||
const txn = self._txn;
|
||||
if (txn._mode != .versionchange) {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
try txn.assertActive();
|
||||
if (std.mem.eql(u8, name, self._name)) {
|
||||
return;
|
||||
}
|
||||
self._engine.renameObjectStore(self._store_id, name) catch |err| switch (err) {
|
||||
error.Constraint => return error.ConstraintError,
|
||||
else => return err,
|
||||
};
|
||||
if (self._original_name == null) {
|
||||
self._original_name = self._name;
|
||||
}
|
||||
self._name = try txn.dupe(name);
|
||||
}
|
||||
|
||||
pub fn getKeyPath(self: *IDBObjectStore, exec: *Execution) !js.Value {
|
||||
return idb.cachedKeyPathJs(&self._key_path_js, self._txn, self._key_path, exec);
|
||||
}
|
||||
@@ -643,7 +665,7 @@ pub const JsApi = struct {
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const name = bridge.accessor(IDBObjectStore.getName, null, .{});
|
||||
pub const name = bridge.accessor(IDBObjectStore.getName, IDBObjectStore.setName, .{});
|
||||
pub const keyPath = bridge.accessor(IDBObjectStore.getKeyPath, null, .{});
|
||||
pub const autoIncrement = bridge.accessor(IDBObjectStore.getAutoIncrement, null, .{});
|
||||
pub const transaction = bridge.accessor(IDBObjectStore.getTransaction, null, .{ .null_as_undefined = true });
|
||||
|
||||
@@ -293,15 +293,21 @@ pub fn abortWith(self: *IDBTransaction, exec: *Execution, reason: ?anyerror) err
|
||||
self._error = reason;
|
||||
|
||||
// An aborted upgrade reverts the schema: stores and indexes created during
|
||||
// it no longer exist, so handles the caller still holds must report deleted.
|
||||
// it no longer exist, so handles the caller still holds must report
|
||||
// deleted; pre-existing ones that were renamed get their names back (a
|
||||
// created one has no earlier name to go back to and keeps its last).
|
||||
if (self._mode == .versionchange) {
|
||||
for (self._stores.items) |store| {
|
||||
if (store._created) {
|
||||
store._deleted = true;
|
||||
} else if (store._original_name) |name| {
|
||||
store._name = name;
|
||||
}
|
||||
for (store._indexes.items) |idx| {
|
||||
if (idx._created) {
|
||||
idx._deleted = true;
|
||||
} else if (idx._original_name) |name| {
|
||||
idx._name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user