diff --git a/src/browser/tests/indexeddb.html b/src/browser/tests/indexeddb.html index 4e0e657fa..b95640f9f 100644 --- a/src/browser/tests/indexeddb.html +++ b/src/browser/tests/indexeddb.html @@ -1189,7 +1189,6 @@ }); } -<<<<<<< HEAD + + + + diff --git a/src/browser/webapi/storage/idb/Engine.zig b/src/browser/webapi/storage/idb/Engine.zig index 7f51e5bce..7c3cb9d1f 100644 --- a/src/browser/webapi/storage/idb/Engine.zig +++ b/src/browser/webapi/storage/idb/Engine.zig @@ -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. diff --git a/src/browser/webapi/storage/idb/IDBIndex.zig b/src/browser/webapi/storage/idb/IDBIndex.zig index ab2002264..4f55c34c7 100644 --- a/src/browser/webapi/storage/idb/IDBIndex.zig +++ b/src/browser/webapi/storage/idb/IDBIndex.zig @@ -16,11 +16,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +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, .{}); diff --git a/src/browser/webapi/storage/idb/IDBObjectStore.zig b/src/browser/webapi/storage/idb/IDBObjectStore.zig index 620a2f29c..e38b0c79f 100644 --- a/src/browser/webapi/storage/idb/IDBObjectStore.zig +++ b/src/browser/webapi/storage/idb/IDBObjectStore.zig @@ -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 }); diff --git a/src/browser/webapi/storage/idb/IDBTransaction.zig b/src/browser/webapi/storage/idb/IDBTransaction.zig index e742f1f2d..fdde9cdef 100644 --- a/src/browser/webapi/storage/idb/IDBTransaction.zig +++ b/src/browser/webapi/storage/idb/IDBTransaction.zig @@ -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; } } }