From 922ae539ba0e3269e7973617cada615ce8f5337c Mon Sep 17 00:00:00 2001 From: haex Date: Thu, 23 Oct 2025 09:26:36 +0200 Subject: [PATCH] no more soft delete => we do it hard now --- .../migrations/0008_dizzy_blue_shield.sql | 1 + .../migrations/0009_boring_arclight.sql | 92 ++ .../migrations/meta/0008_snapshot.json | 1076 +++++++++++++++++ .../migrations/meta/0009_snapshot.json | 1076 +++++++++++++++++ .../database/migrations/meta/_journal.json | 14 + src-tauri/database/schemas/haex.ts | 9 +- src-tauri/database/schemas/vault.ts | 33 +- src-tauri/database/vault.db | Bin 159744 -> 167936 bytes src-tauri/src/crdt/insert_transformer.rs | 93 +- src-tauri/src/crdt/transformer.rs | 670 +--------- src-tauri/src/crdt/trigger.rs | 98 +- src-tauri/src/extension/database/executor.rs | 9 +- src-tauri/src/extension/database/mod.rs | 10 +- src/components/haex/desktop/index.vue | 4 +- src/components/haex/workspace/card.vue | 2 +- src/pages/index.vue | 3 + src/stores/desktop/workspace.ts | 66 +- 17 files changed, 2430 insertions(+), 826 deletions(-) create mode 100644 src-tauri/database/migrations/0008_dizzy_blue_shield.sql create mode 100644 src-tauri/database/migrations/0009_boring_arclight.sql create mode 100644 src-tauri/database/migrations/meta/0008_snapshot.json create mode 100644 src-tauri/database/migrations/meta/0009_snapshot.json diff --git a/src-tauri/database/migrations/0008_dizzy_blue_shield.sql b/src-tauri/database/migrations/0008_dizzy_blue_shield.sql new file mode 100644 index 0000000..7aebe89 --- /dev/null +++ b/src-tauri/database/migrations/0008_dizzy_blue_shield.sql @@ -0,0 +1 @@ +CREATE UNIQUE INDEX `haex_workspaces_position_unique` ON `haex_workspaces` (`position`); \ No newline at end of file diff --git a/src-tauri/database/migrations/0009_boring_arclight.sql b/src-tauri/database/migrations/0009_boring_arclight.sql new file mode 100644 index 0000000..fce5651 --- /dev/null +++ b/src-tauri/database/migrations/0009_boring_arclight.sql @@ -0,0 +1,92 @@ +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_haex_desktop_items` ( + `id` text PRIMARY KEY NOT NULL, + `workspace_id` text NOT NULL, + `item_type` text NOT NULL, + `reference_id` text NOT NULL, + `position_x` integer DEFAULT 0 NOT NULL, + `position_y` integer DEFAULT 0 NOT NULL, + `haex_tombstone` integer, + `haex_timestamp` text, + FOREIGN KEY (`workspace_id`) REFERENCES `haex_workspaces`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +INSERT INTO `__new_haex_desktop_items`("id", "workspace_id", "item_type", "reference_id", "position_x", "position_y", "haex_tombstone", "haex_timestamp") SELECT "id", "workspace_id", "item_type", "reference_id", "position_x", "position_y", "haex_tombstone", "haex_timestamp" FROM `haex_desktop_items`;--> statement-breakpoint +DROP TABLE `haex_desktop_items`;--> statement-breakpoint +ALTER TABLE `__new_haex_desktop_items` RENAME TO `haex_desktop_items`;--> statement-breakpoint +PRAGMA foreign_keys=ON;--> statement-breakpoint +CREATE TABLE `__new_haex_extension_permissions` ( + `id` text PRIMARY KEY NOT NULL, + `extension_id` text NOT NULL, + `resource_type` text, + `action` text, + `target` text, + `constraints` text, + `status` text DEFAULT 'denied' NOT NULL, + `created_at` text DEFAULT (CURRENT_TIMESTAMP), + `updated_at` integer, + `haex_tombstone` integer, + `haex_timestamp` text, + FOREIGN KEY (`extension_id`) REFERENCES `haex_extensions`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +INSERT INTO `__new_haex_extension_permissions`("id", "extension_id", "resource_type", "action", "target", "constraints", "status", "created_at", "updated_at", "haex_tombstone", "haex_timestamp") SELECT "id", "extension_id", "resource_type", "action", "target", "constraints", "status", "created_at", "updated_at", "haex_tombstone", "haex_timestamp" FROM `haex_extension_permissions`;--> statement-breakpoint +DROP TABLE `haex_extension_permissions`;--> statement-breakpoint +ALTER TABLE `__new_haex_extension_permissions` RENAME TO `haex_extension_permissions`;--> statement-breakpoint +CREATE UNIQUE INDEX `haex_extension_permissions_extension_id_resource_type_action_target_unique` ON `haex_extension_permissions` (`extension_id`,`resource_type`,`action`,`target`);--> statement-breakpoint +CREATE TABLE `__new_haex_passwords_group_items` ( + `group_id` text NOT NULL, + `item_id` text NOT NULL, + `haex_tombstone` integer, + PRIMARY KEY(`item_id`, `group_id`), + FOREIGN KEY (`group_id`) REFERENCES `haex_passwords_groups`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`item_id`) REFERENCES `haex_passwords_item_details`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +INSERT INTO `__new_haex_passwords_group_items`("group_id", "item_id", "haex_tombstone") SELECT "group_id", "item_id", "haex_tombstone" FROM `haex_passwords_group_items`;--> statement-breakpoint +DROP TABLE `haex_passwords_group_items`;--> statement-breakpoint +ALTER TABLE `__new_haex_passwords_group_items` RENAME TO `haex_passwords_group_items`;--> statement-breakpoint +CREATE TABLE `__new_haex_passwords_groups` ( + `id` text PRIMARY KEY NOT NULL, + `name` text, + `description` text, + `icon` text, + `order` integer, + `color` text, + `parent_id` text, + `created_at` text DEFAULT (CURRENT_TIMESTAMP), + `updated_at` integer, + `haex_tombstone` integer, + FOREIGN KEY (`parent_id`) REFERENCES `haex_passwords_groups`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +INSERT INTO `__new_haex_passwords_groups`("id", "name", "description", "icon", "order", "color", "parent_id", "created_at", "updated_at", "haex_tombstone") SELECT "id", "name", "description", "icon", "order", "color", "parent_id", "created_at", "updated_at", "haex_tombstone" FROM `haex_passwords_groups`;--> statement-breakpoint +DROP TABLE `haex_passwords_groups`;--> statement-breakpoint +ALTER TABLE `__new_haex_passwords_groups` RENAME TO `haex_passwords_groups`;--> statement-breakpoint +CREATE TABLE `__new_haex_passwords_item_history` ( + `id` text PRIMARY KEY NOT NULL, + `item_id` text NOT NULL, + `changed_property` text, + `old_value` text, + `new_value` text, + `created_at` text DEFAULT (CURRENT_TIMESTAMP), + `haex_tombstone` integer, + FOREIGN KEY (`item_id`) REFERENCES `haex_passwords_item_details`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +INSERT INTO `__new_haex_passwords_item_history`("id", "item_id", "changed_property", "old_value", "new_value", "created_at", "haex_tombstone") SELECT "id", "item_id", "changed_property", "old_value", "new_value", "created_at", "haex_tombstone" FROM `haex_passwords_item_history`;--> statement-breakpoint +DROP TABLE `haex_passwords_item_history`;--> statement-breakpoint +ALTER TABLE `__new_haex_passwords_item_history` RENAME TO `haex_passwords_item_history`;--> statement-breakpoint +CREATE TABLE `__new_haex_passwords_item_key_values` ( + `id` text PRIMARY KEY NOT NULL, + `item_id` text NOT NULL, + `key` text, + `value` text, + `updated_at` integer, + `haex_tombstone` integer, + FOREIGN KEY (`item_id`) REFERENCES `haex_passwords_item_details`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +INSERT INTO `__new_haex_passwords_item_key_values`("id", "item_id", "key", "value", "updated_at", "haex_tombstone") SELECT "id", "item_id", "key", "value", "updated_at", "haex_tombstone" FROM `haex_passwords_item_key_values`;--> statement-breakpoint +DROP TABLE `haex_passwords_item_key_values`;--> statement-breakpoint +ALTER TABLE `__new_haex_passwords_item_key_values` RENAME TO `haex_passwords_item_key_values`; \ No newline at end of file diff --git a/src-tauri/database/migrations/meta/0008_snapshot.json b/src-tauri/database/migrations/meta/0008_snapshot.json new file mode 100644 index 0000000..2cd85e1 --- /dev/null +++ b/src-tauri/database/migrations/meta/0008_snapshot.json @@ -0,0 +1,1076 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "7d76d952-5db4-4c13-bf4a-9ad4fc1560ce", + "prevId": "ef2b32f1-f57e-4775-903a-d48f0d1e4e3b", + "tables": { + "haex_crdt_configs": { + "name": "haex_crdt_configs", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_crdt_logs": { + "name": "haex_crdt_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "table_name": { + "name": "table_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "row_pks": { + "name": "row_pks", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "op_type": { + "name": "op_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "column_name": { + "name": "column_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_haex_timestamp": { + "name": "idx_haex_timestamp", + "columns": [ + "haex_timestamp" + ], + "isUnique": false + }, + "idx_table_row": { + "name": "idx_table_row", + "columns": [ + "table_name", + "row_pks" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_crdt_snapshots": { + "name": "haex_crdt_snapshots", + "columns": { + "snapshot_id": { + "name": "snapshot_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "created": { + "name": "created", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "epoch_hlc": { + "name": "epoch_hlc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "location_url": { + "name": "location_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "file_size_bytes": { + "name": "file_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_desktop_items": { + "name": "haex_desktop_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "item_type": { + "name": "item_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position_x": { + "name": "position_x", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "position_y": { + "name": "position_y", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_desktop_items_workspace_id_haex_workspaces_id_fk": { + "name": "haex_desktop_items_workspace_id_haex_workspaces_id_fk", + "tableFrom": "haex_desktop_items", + "tableTo": "haex_workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_extension_permissions": { + "name": "haex_extension_permissions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "extension_id": { + "name": "extension_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "constraints": { + "name": "constraints", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'denied'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "haex_extension_permissions_extension_id_resource_type_action_target_unique": { + "name": "haex_extension_permissions_extension_id_resource_type_action_target_unique", + "columns": [ + "extension_id", + "resource_type", + "action", + "target" + ], + "isUnique": true + } + }, + "foreignKeys": { + "haex_extension_permissions_extension_id_haex_extensions_id_fk": { + "name": "haex_extension_permissions_extension_id_haex_extensions_id_fk", + "tableFrom": "haex_extension_permissions", + "tableTo": "haex_extensions", + "columnsFrom": [ + "extension_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_extensions": { + "name": "haex_extensions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entry": { + "name": "entry", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'index.html'" + }, + "homepage": { + "name": "homepage", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": true + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signature": { + "name": "signature", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "haex_extensions_public_key_name_unique": { + "name": "haex_extensions_public_key_name_unique", + "columns": [ + "public_key", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_notifications": { + "name": "haex_notifications", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "alt": { + "name": "alt", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read": { + "name": "read", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_settings": { + "name": "haex_settings", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "haex_settings_key_type_value_unique": { + "name": "haex_settings_key_type_value_unique", + "columns": [ + "key", + "type", + "value" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_workspaces": { + "name": "haex_workspaces", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "haex_workspaces_position_unique": { + "name": "haex_workspaces_position_unique", + "columns": [ + "position" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_group_items": { + "name": "haex_passwords_group_items", + "columns": { + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_group_items_group_id_haex_passwords_groups_id_fk": { + "name": "haex_passwords_group_items_group_id_haex_passwords_groups_id_fk", + "tableFrom": "haex_passwords_group_items", + "tableTo": "haex_passwords_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "haex_passwords_group_items_item_id_haex_passwords_item_details_id_fk": { + "name": "haex_passwords_group_items_item_id_haex_passwords_item_details_id_fk", + "tableFrom": "haex_passwords_group_items", + "tableTo": "haex_passwords_item_details", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "haex_passwords_group_items_item_id_group_id_pk": { + "columns": [ + "item_id", + "group_id" + ], + "name": "haex_passwords_group_items_item_id_group_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_groups": { + "name": "haex_passwords_groups", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_groups_parent_id_haex_passwords_groups_id_fk": { + "name": "haex_passwords_groups_parent_id_haex_passwords_groups_id_fk", + "tableFrom": "haex_passwords_groups", + "tableTo": "haex_passwords_groups", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_item_details": { + "name": "haex_passwords_item_details", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_item_history": { + "name": "haex_passwords_item_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "changed_property": { + "name": "changed_property", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_item_history_item_id_haex_passwords_item_details_id_fk": { + "name": "haex_passwords_item_history_item_id_haex_passwords_item_details_id_fk", + "tableFrom": "haex_passwords_item_history", + "tableTo": "haex_passwords_item_details", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_item_key_values": { + "name": "haex_passwords_item_key_values", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_item_key_values_item_id_haex_passwords_item_details_id_fk": { + "name": "haex_passwords_item_key_values_item_id_haex_passwords_item_details_id_fk", + "tableFrom": "haex_passwords_item_key_values", + "tableTo": "haex_passwords_item_details", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/src-tauri/database/migrations/meta/0009_snapshot.json b/src-tauri/database/migrations/meta/0009_snapshot.json new file mode 100644 index 0000000..3b253ca --- /dev/null +++ b/src-tauri/database/migrations/meta/0009_snapshot.json @@ -0,0 +1,1076 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "5fd7b9bc-e8be-4d83-b234-e53bd442a9c7", + "prevId": "7d76d952-5db4-4c13-bf4a-9ad4fc1560ce", + "tables": { + "haex_crdt_configs": { + "name": "haex_crdt_configs", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_crdt_logs": { + "name": "haex_crdt_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "table_name": { + "name": "table_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "row_pks": { + "name": "row_pks", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "op_type": { + "name": "op_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "column_name": { + "name": "column_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_haex_timestamp": { + "name": "idx_haex_timestamp", + "columns": [ + "haex_timestamp" + ], + "isUnique": false + }, + "idx_table_row": { + "name": "idx_table_row", + "columns": [ + "table_name", + "row_pks" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_crdt_snapshots": { + "name": "haex_crdt_snapshots", + "columns": { + "snapshot_id": { + "name": "snapshot_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "created": { + "name": "created", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "epoch_hlc": { + "name": "epoch_hlc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "location_url": { + "name": "location_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "file_size_bytes": { + "name": "file_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_desktop_items": { + "name": "haex_desktop_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "item_type": { + "name": "item_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position_x": { + "name": "position_x", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "position_y": { + "name": "position_y", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_desktop_items_workspace_id_haex_workspaces_id_fk": { + "name": "haex_desktop_items_workspace_id_haex_workspaces_id_fk", + "tableFrom": "haex_desktop_items", + "tableTo": "haex_workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_extension_permissions": { + "name": "haex_extension_permissions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "extension_id": { + "name": "extension_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "constraints": { + "name": "constraints", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'denied'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "haex_extension_permissions_extension_id_resource_type_action_target_unique": { + "name": "haex_extension_permissions_extension_id_resource_type_action_target_unique", + "columns": [ + "extension_id", + "resource_type", + "action", + "target" + ], + "isUnique": true + } + }, + "foreignKeys": { + "haex_extension_permissions_extension_id_haex_extensions_id_fk": { + "name": "haex_extension_permissions_extension_id_haex_extensions_id_fk", + "tableFrom": "haex_extension_permissions", + "tableTo": "haex_extensions", + "columnsFrom": [ + "extension_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_extensions": { + "name": "haex_extensions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entry": { + "name": "entry", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'index.html'" + }, + "homepage": { + "name": "homepage", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": true + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signature": { + "name": "signature", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "haex_extensions_public_key_name_unique": { + "name": "haex_extensions_public_key_name_unique", + "columns": [ + "public_key", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_notifications": { + "name": "haex_notifications", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "alt": { + "name": "alt", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read": { + "name": "read", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_settings": { + "name": "haex_settings", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "haex_settings_key_type_value_unique": { + "name": "haex_settings_key_type_value_unique", + "columns": [ + "key", + "type", + "value" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_workspaces": { + "name": "haex_workspaces", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "haex_workspaces_position_unique": { + "name": "haex_workspaces_position_unique", + "columns": [ + "position" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_group_items": { + "name": "haex_passwords_group_items", + "columns": { + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_group_items_group_id_haex_passwords_groups_id_fk": { + "name": "haex_passwords_group_items_group_id_haex_passwords_groups_id_fk", + "tableFrom": "haex_passwords_group_items", + "tableTo": "haex_passwords_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "haex_passwords_group_items_item_id_haex_passwords_item_details_id_fk": { + "name": "haex_passwords_group_items_item_id_haex_passwords_item_details_id_fk", + "tableFrom": "haex_passwords_group_items", + "tableTo": "haex_passwords_item_details", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "haex_passwords_group_items_item_id_group_id_pk": { + "columns": [ + "item_id", + "group_id" + ], + "name": "haex_passwords_group_items_item_id_group_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_groups": { + "name": "haex_passwords_groups", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_groups_parent_id_haex_passwords_groups_id_fk": { + "name": "haex_passwords_groups_parent_id_haex_passwords_groups_id_fk", + "tableFrom": "haex_passwords_groups", + "tableTo": "haex_passwords_groups", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_item_details": { + "name": "haex_passwords_item_details", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_item_history": { + "name": "haex_passwords_item_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "changed_property": { + "name": "changed_property", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_item_history_item_id_haex_passwords_item_details_id_fk": { + "name": "haex_passwords_item_history_item_id_haex_passwords_item_details_id_fk", + "tableFrom": "haex_passwords_item_history", + "tableTo": "haex_passwords_item_details", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_item_key_values": { + "name": "haex_passwords_item_key_values", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_item_key_values_item_id_haex_passwords_item_details_id_fk": { + "name": "haex_passwords_item_key_values_item_id_haex_passwords_item_details_id_fk", + "tableFrom": "haex_passwords_item_key_values", + "tableTo": "haex_passwords_item_details", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/src-tauri/database/migrations/meta/_journal.json b/src-tauri/database/migrations/meta/_journal.json index 3bbd065..bb59954 100644 --- a/src-tauri/database/migrations/meta/_journal.json +++ b/src-tauri/database/migrations/meta/_journal.json @@ -57,6 +57,20 @@ "when": 1761141111765, "tag": "0007_stale_longshot", "breakpoints": true + }, + { + "idx": 8, + "version": "6", + "when": 1761145177028, + "tag": "0008_dizzy_blue_shield", + "breakpoints": true + }, + { + "idx": 9, + "version": "6", + "when": 1761203548348, + "tag": "0009_boring_arclight", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src-tauri/database/schemas/haex.ts b/src-tauri/database/schemas/haex.ts index 1704529..2fbad46 100644 --- a/src-tauri/database/schemas/haex.ts +++ b/src-tauri/database/schemas/haex.ts @@ -77,7 +77,11 @@ export const haexExtensionPermissions = sqliteTable( .$defaultFn(() => crypto.randomUUID()), extensionId: text( tableNames.haex.extension_permissions.columns.extensionId, - ).references((): AnySQLiteColumn => haexExtensions.id), + ) + .notNull() + .references((): AnySQLiteColumn => haexExtensions.id, { + onDelete: 'cascade', + }), resourceType: text('resource_type', { enum: ['fs', 'http', 'db', 'shell'], }), @@ -152,6 +156,7 @@ export const haexWorkspaces = sqliteTable( }, tableNames.haex.workspaces.columns, ), + (table) => [unique().on(table.position)], ) export type InsertHaexWorkspaces = typeof haexWorkspaces.$inferInsert export type SelectHaexWorkspaces = typeof haexWorkspaces.$inferSelect @@ -165,7 +170,7 @@ export const haexDesktopItems = sqliteTable( .$defaultFn(() => crypto.randomUUID()), workspaceId: text(tableNames.haex.desktop_items.columns.workspaceId) .notNull() - .references(() => haexWorkspaces.id), + .references(() => haexWorkspaces.id, { onDelete: 'cascade' }), itemType: text(tableNames.haex.desktop_items.columns.itemType, { enum: ['extension', 'file', 'folder'], }).notNull(), diff --git a/src-tauri/database/schemas/vault.ts b/src-tauri/database/schemas/vault.ts index 7b2b259..ab76dae 100644 --- a/src-tauri/database/schemas/vault.ts +++ b/src-tauri/database/schemas/vault.ts @@ -35,9 +35,11 @@ export const haexPasswordsItemKeyValues = sqliteTable( tableNames.haex.passwords.item_key_values, { id: text().primaryKey(), - itemId: text('item_id').references( - (): AnySQLiteColumn => haexPasswordsItemDetails.id, - ), + itemId: text('item_id') + .notNull() + .references((): AnySQLiteColumn => haexPasswordsItemDetails.id, { + onDelete: 'cascade', + }), key: text(), value: text(), updateAt: integer('updated_at', { mode: 'timestamp' }).$onUpdate( @@ -55,9 +57,11 @@ export const haexPasswordsItemHistory = sqliteTable( tableNames.haex.passwords.item_histories, { id: text().primaryKey(), - itemId: text('item_id').references( - (): AnySQLiteColumn => haexPasswordsItemDetails.id, - ), + itemId: text('item_id') + .notNull() + .references((): AnySQLiteColumn => haexPasswordsItemDetails.id, { + onDelete: 'cascade', + }), changedProperty: text('changed_property').$type(), oldValue: text('old_value'), @@ -82,6 +86,7 @@ export const haexPasswordsGroups = sqliteTable( color: text(), parentId: text('parent_id').references( (): AnySQLiteColumn => haexPasswordsGroups.id, + { onDelete: 'cascade' }, ), createdAt: text('created_at').default(sql`(CURRENT_TIMESTAMP)`), updateAt: integer('updated_at', { mode: 'timestamp' }).$onUpdate( @@ -96,12 +101,16 @@ export type SelectHaexPasswordsGroups = typeof haexPasswordsGroups.$inferSelect export const haexPasswordsGroupItems = sqliteTable( tableNames.haex.passwords.group_items, { - groupId: text('group_id').references( - (): AnySQLiteColumn => haexPasswordsGroups.id, - ), - itemId: text('item_id').references( - (): AnySQLiteColumn => haexPasswordsItemDetails.id, - ), + groupId: text('group_id') + .notNull() + .references((): AnySQLiteColumn => haexPasswordsGroups.id, { + onDelete: 'cascade', + }), + itemId: text('item_id') + .notNull() + .references((): AnySQLiteColumn => haexPasswordsItemDetails.id, { + onDelete: 'cascade', + }), haex_tombstone: integer({ mode: 'boolean' }), }, (table) => [primaryKey({ columns: [table.itemId, table.groupId] })], diff --git a/src-tauri/database/vault.db b/src-tauri/database/vault.db index 7c8b6225af628d3deedd4e4c5c436e183ed14e4e..6bcbce47d2d2595814176ca1ae91d0d39c270cdf 100644 GIT binary patch delta 1478 zcmZ{kZEO@p7{~Xy>)zh&+gyR`wuRPf1={1K?7eqIX-h5GRQdvKf)aAKz1=G(aBaDg z5;bW{4}?$loVHUcwY&Z2R1@(f;1?rejUOZ?#$ZgUkwA#V4`Oxi3I~+9o7wC$ z&ojT7`OiNy__J;B6g#p!pQ9+sWi+SJOh)sh)_H+DAD%`fs|6!w`FRSZQ4|%4UyB2x zNBC8k5SsY;bea7+KhVU|)Q~_&{hCMX3|1>aSySXrUGXSdP*v2R7O3{h5hdVN6nCdP z;tTkrol3;#^Z6qIIU0y4ia#iOyfBG=>-nU~mW9dwpymmv0WGR&5s&WGyzZder)Zil zV1WEax2i@1iX4o{%RSXTt=iYA29${E^#wh;;#R6*l8;}yoSrDzXDVdTRSMljf1%sx z2AV^^pr6n+^gX(oE_0?4n`fpV`?003ZKxQ$P|G>#%%=I7{94m3WW@Hf+^sM;GV!12 zLMvwnY2m+Gpc$%w`iMe15fYD!TZC3&DSwi8am^gV*0QwqiuD*%X_+;5nJZ1N!X)f6 zij6mQdXT&{1!oC|p@z8Lfa|1v8r~)4Q&2PW@(7#)3q`?K-Hl{+0xC!k21NcIg)-LY z-7hsZLb3%9!Lk`6(E_H9BJ$Zd$oQs-A@5Bqp0C*~| zVP@+Qc*MRUM$DjdRf6dj zB~o(~z0*8yZlmT*cf}w0k9m##fYq%Rtx?Nu%a}N1=tfZk;!x^zo2AQVkzVP@%uq;& zLUFyHq$a>YKF7c^A@`yldufth!$@^?@S3Af?cAs16jO}9vlfG$oEw1^c$(qKmm^St zpSCT*{dq!qyKNgt5j7D}HJx?QHz?lXHB>X69c#%qlTe1Qux|5{^)8Y>4VyXFa7`s6 zx!rKoMLv8JT;-MZ+Z*cI8=UQRo1brRmhVyZLm^F1?CtCB3B~&K{fTlsYhKOwcOTfB z=usnjD5l}wyrpE-N%$0RV)Jde3{gixdMNxh2^Cv&D;ru;!AMPOVQ6D4uIY!e^W=sm zLOtDySYNC=9y%D0^&Zq0rB{=8rl7d)|LeofmS$&o(WtPqGJKyKc8UGeITD|OY37-g z7O8EQk9;u-FLI@ad%aB7OS`i!F}$=Ml{_j6Q8AQ4RtP4e$vq8lGc6D_3U70{U3Pdf z9&G0dhgZ3?-aFEEpGuROF(^mU<|u7c;df?@XtMhXVuC>ZD> zqMU#N<5IxyLND=lDeLy4>)O8U({0^MMIlm7$$wel)W}L4x z8xcZo6^$xDg(K%{1>?QG0=8K>wKs5igcW=O+xQiJhTqP8;PQ&-Oma%zKCH1C@G8Q; z@DKbAf5IQ}d;Au!DrWx^tgJS>llQyW;7#^RpTY*eH~Toefh<%6VHz*<0sbk+@?csp ziOg^Ijo}WnL`(Vy`Wf9Gora3!=C9>S{fZBMf);ORk6eDG6f}&uWQ5Q7Z;#i zqe%3$q1B-JM4YA;nb9<2R*W@<=d{(3(ADc7QW5QAG|jniH@k$f#!Io>AUB9{?nb*M z-Rz)+wzPM%wn64pB3d3u!y;g6FTz4g3(Qt0qQ{0~34r97C(dLHV) zP9O_gh==~OR`v>cu!E!QIS+k6Ud_Q_*$4ZK ztu>W6b2?SUMqXcm8W8>oVB*YzhnP~plml1QYn9w2d#G+L7e!h5EM%C}X_4N{o~b5_ zGI&9arkXSL$0mmoW8=N?L~P_QiGKkeQH;+?surN$<&1=Pha=$wq3}WH$!P3M@6_1D z(BydZcx=)W@ArClHjtwdh@?RVqdZDmco)wh<9;`wxa4f4$=8ceVAK!P2u)3p-a&3j za9e(6G|}{fVXhJTzpcP3%1`)QBb1Ar6^aqPw&SIBet!3vLtm9nCRt#4C E0dj56qyPW_ diff --git a/src-tauri/src/crdt/insert_transformer.rs b/src-tauri/src/crdt/insert_transformer.rs index a8447bc..9f0b9bc 100644 --- a/src-tauri/src/crdt/insert_transformer.rs +++ b/src-tauri/src/crdt/insert_transformer.rs @@ -3,10 +3,7 @@ use crate::crdt::trigger::{HLC_TIMESTAMP_COLUMN, TOMBSTONE_COLUMN}; use crate::database::error::DatabaseError; -use sqlparser::ast::{ - Assignment, AssignmentTarget, BinaryOperator, Expr, Ident, Insert, ObjectNamePart, OnConflict, - OnConflictAction, OnInsert, SelectItem, SetExpr, Value, -}; +use sqlparser::ast::{Expr, Ident, Insert, SelectItem, SetExpr, Value}; use uhlc::Timestamp; /// Helper-Struct für INSERT-Transformationen @@ -54,14 +51,12 @@ impl InsertTransformer { } } - /// Transformiert INSERT-Statements (fügt HLC-Timestamp hinzu und behandelt Tombstone-Konflikte) - /// Fügt automatisch RETURNING für Primary Keys hinzu, damit der Executor die tatsächlichen PKs kennt + /// Transformiert INSERT-Statements (fügt HLC-Timestamp hinzu) + /// Hard Delete: Kein ON CONFLICT mehr nötig - gelöschte Einträge sind wirklich weg pub fn transform_insert( &self, insert_stmt: &mut Insert, timestamp: &Timestamp, - primary_keys: &[String], - foreign_keys: &[String], ) -> Result<(), DatabaseError> { // Add both haex_timestamp and haex_tombstone columns if not exists let hlc_col_index = @@ -69,85 +64,9 @@ impl InsertTransformer { let tombstone_col_index = Self::find_or_add_column(&mut insert_stmt.columns, self.tombstone_column); - // Füge RETURNING für alle Primary Keys hinzu (falls noch nicht vorhanden) - // Dies erlaubt uns, die tatsächlichen PK-Werte nach ON CONFLICT zu kennen - if insert_stmt.returning.is_none() && !primary_keys.is_empty() { - insert_stmt.returning = Some( - primary_keys - .iter() - .map(|pk| SelectItem::UnnamedExpr(Expr::Identifier(Ident::new(pk)))) - .collect(), - ); - } - - // Setze ON CONFLICT für UPSERT-Verhalten bei Tombstone-Einträgen - // Dies ermöglicht das Wiederverwenden von gelöschten Einträgen - if insert_stmt.on.is_none() { - // ON CONFLICT DO UPDATE SET ... - // Aktualisiere alle Spalten außer CRDT-Spalten, wenn ein Konflikt auftritt - - // Erstelle UPDATE-Assignments für alle Spalten außer CRDT-Spalten, Primary Keys und Foreign Keys - let mut assignments = Vec::new(); - for column in insert_stmt.columns.clone().iter() { - let col_name = &column.value; - - // Überspringe CRDT-Spalten - if col_name == self.hlc_timestamp_column || col_name == self.tombstone_column { - continue; - } - - // Überspringe Primary Key Spalten um FOREIGN KEY Konflikte zu vermeiden - if primary_keys.contains(col_name) { - continue; - } - - // Überspringe Foreign Key Spalten um FOREIGN KEY Konflikte zu vermeiden - // Wenn eine FK auf eine neue ID verweist, die noch nicht existiert, schlägt der Constraint fehl - if foreign_keys.contains(col_name) { - continue; - } - - // excluded.column_name referenziert die neuen Werte aus dem INSERT - assignments.push(Assignment { - target: AssignmentTarget::ColumnName(sqlparser::ast::ObjectName(vec![ - ObjectNamePart::Identifier(column.clone()), - ])), - value: Expr::CompoundIdentifier(vec![Ident::new("excluded"), column.clone()]), - }); - } - - // Füge HLC-Timestamp Update hinzu (mit dem übergebenen timestamp) - assignments.push(Assignment { - target: AssignmentTarget::ColumnName(sqlparser::ast::ObjectName(vec![ - ObjectNamePart::Identifier(Ident::new(self.hlc_timestamp_column)), - ])), - value: Expr::Value(Value::SingleQuotedString(timestamp.to_string()).into()), - }); - - // Setze Tombstone auf 0 (reaktiviere den Eintrag) - assignments.push(Assignment { - target: AssignmentTarget::ColumnName(sqlparser::ast::ObjectName(vec![ - ObjectNamePart::Identifier(Ident::new(self.tombstone_column)), - ])), - value: Expr::Value(Value::Number("0".to_string(), false).into()), - }); - - // ON CONFLICT nur wenn Tombstone = 1 (Eintrag wurde gelöscht) - // Ansonsten soll der INSERT fehlschlagen (UNIQUE constraint error) - let tombstone_condition = Expr::BinaryOp { - left: Box::new(Expr::Identifier(Ident::new(self.tombstone_column))), - op: BinaryOperator::Eq, - right: Box::new(Expr::Value(Value::Number("1".to_string(), false).into())), - }; - - insert_stmt.on = Some(OnInsert::OnConflict(OnConflict { - conflict_target: None, // Wird auf alle UNIQUE Constraints angewendet - action: OnConflictAction::DoUpdate(sqlparser::ast::DoUpdate { - assignments, - selection: Some(tombstone_condition), - }), - })); - } + // ON CONFLICT Logik komplett entfernt! + // Bei Hard Deletes gibt es keine Tombstone-Einträge mehr zu reaktivieren + // UNIQUE Constraint Violations sind echte Fehler match insert_stmt.source.as_mut() { Some(query) => match &mut *query.body { diff --git a/src-tauri/src/crdt/transformer.rs b/src-tauri/src/crdt/transformer.rs index df7c36b..fa6e84b 100644 --- a/src-tauri/src/crdt/transformer.rs +++ b/src-tauri/src/crdt/transformer.rs @@ -5,8 +5,8 @@ use crate::crdt::trigger::{HLC_TIMESTAMP_COLUMN, TOMBSTONE_COLUMN}; use crate::database::error::DatabaseError; use crate::table_names::{TABLE_CRDT_CONFIGS, TABLE_CRDT_LOGS}; use sqlparser::ast::{ - Assignment, AssignmentTarget, BinaryOperator, ColumnDef, DataType, Expr, Ident, ObjectName, - ObjectNamePart, SelectItem, SetExpr, Statement, TableFactor, TableObject, Value, + Assignment, AssignmentTarget, ColumnDef, DataType, Expr, Ident, ObjectName, + ObjectNamePart, Statement, TableFactor, TableObject, Value, }; use std::borrow::Cow; use std::collections::HashSet; @@ -25,32 +25,6 @@ impl CrdtColumns { hlc_timestamp: HLC_TIMESTAMP_COLUMN, }; - /// Erstellt einen Tombstone-Filter für eine Tabelle - fn create_tombstone_filter(&self, table_alias: Option<&str>) -> Expr { - let column_expr = match table_alias { - Some(alias) => { - // Qualifizierte Referenz: alias.tombstone - Expr::CompoundIdentifier(vec![Ident::new(alias), Ident::new(self.tombstone)]) - } - None => { - // Einfache Referenz: tombstone - Expr::Identifier(Ident::new(self.tombstone)) - } - }; - - Expr::IsNotTrue(Box::new(column_expr)) - } - - /// Erstellt eine Tombstone-Zuweisung für UPDATE/DELETE - fn create_tombstone_assignment(&self) -> Assignment { - Assignment { - target: AssignmentTarget::ColumnName(ObjectName(vec![ObjectNamePart::Identifier( - Ident::new(self.tombstone), - )])), - value: Expr::Value(Value::Number("1".to_string(), false).into()), - } - } - /// Erstellt eine HLC-Zuweisung für UPDATE/DELETE fn create_hlc_assignment(&self, timestamp: &Timestamp) -> Assignment { Assignment { @@ -112,27 +86,11 @@ impl CrdtTransformer { // ================================================================= // ÖFFENTLICHE API-METHODEN // ================================================================= - - pub fn transform_select_statement(&self, stmt: &mut Statement) -> Result<(), DatabaseError> { - match stmt { - Statement::Query(query) => { - // Ruft jetzt die private Methode in diesem Struct auf - self.transform_select_query_recursive(&mut query.body, &self.excluded_tables) - } - // Fange alle anderen Fälle ab und gib einen Fehler zurück - _ => Err(DatabaseError::UnsupportedStatement { - sql: stmt.to_string(), - reason: "This operation only accepts SELECT statements.".to_string(), - }), - } - } - - /// Transformiert Statements MIT Zugriff auf Tabelleninformationen (empfohlen) + pub fn transform_execute_statement_with_table_info( &self, stmt: &mut Statement, hlc_timestamp: &Timestamp, - tx: &rusqlite::Transaction, ) -> Result, DatabaseError> { match stmt { Statement::CreateTable(create_table) => { @@ -149,37 +107,9 @@ impl CrdtTransformer { Statement::Insert(insert_stmt) => { if let TableObject::TableName(name) = &insert_stmt.table { if self.is_crdt_sync_table(name) { - // Hole die Tabelleninformationen um PKs und FKs zu identifizieren - let table_name_str = self.normalize_table_name(name); - - let columns = crate::crdt::trigger::get_table_schema(tx, &table_name_str) - .map_err(|e| DatabaseError::ExecutionError { - sql: format!("PRAGMA table_info('{}')", table_name_str), - reason: e.to_string(), - table: Some(table_name_str.to_string()), - })?; - - let primary_keys: Vec = columns - .iter() - .filter(|c| c.is_pk) - .map(|c| c.name.clone()) - .collect(); - - let foreign_keys = - crate::crdt::trigger::get_foreign_key_columns(tx, &table_name_str) - .map_err(|e| DatabaseError::ExecutionError { - sql: format!("PRAGMA foreign_key_list('{}')", table_name_str), - reason: e.to_string(), - table: Some(table_name_str.to_string()), - })?; - + // Hard Delete: Kein Schema-Lookup mehr nötig (kein ON CONFLICT) let insert_transformer = InsertTransformer::new(); - insert_transformer.transform_insert( - insert_stmt, - hlc_timestamp, - &primary_keys, - &foreign_keys, - )?; + insert_transformer.transform_insert(insert_stmt, hlc_timestamp)?; } } Ok(None) @@ -194,26 +124,11 @@ impl CrdtTransformer { } Ok(None) } - Statement::Delete(del_stmt) => { - if let Some(table_name) = self.extract_table_name_from_delete(del_stmt) { - let table_name_str = self.normalize_table_name(&table_name); - let is_crdt = self.is_crdt_sync_table(&table_name); - eprintln!("DEBUG DELETE (with_table_info): table='{}', is_crdt_sync={}, normalized='{}'", - table_name, is_crdt, table_name_str); - if is_crdt { - eprintln!( - "DEBUG: Transforming DELETE to UPDATE for table '{}'", - table_name_str - ); - self.transform_delete_to_update(stmt, hlc_timestamp)?; - } - Ok(None) - } else { - Err(DatabaseError::UnsupportedStatement { - sql: del_stmt.to_string(), - reason: "DELETE from non-table source or multiple tables".to_string(), - }) - } + Statement::Delete(_del_stmt) => { + // Hard Delete - keine Transformation! + // DELETE bleibt DELETE + // BEFORE DELETE Trigger schreiben die Logs + Ok(None) } Statement::AlterTable { name, .. } => { if self.is_crdt_sync_table(name) { @@ -231,9 +146,6 @@ impl CrdtTransformer { stmt: &mut Statement, hlc_timestamp: &Timestamp, ) -> Result, DatabaseError> { - // Für INSERT-Statements ohne Connection nutzen wir eine leere PK-Liste - // Das bedeutet ALLE Spalten werden im ON CONFLICT UPDATE gesetzt - // Dies ist ein Fallback für den Fall, dass keine Connection verfügbar ist match stmt { Statement::CreateTable(create_table) => { if self.is_crdt_sync_table(&create_table.name) { @@ -249,14 +161,9 @@ impl CrdtTransformer { Statement::Insert(insert_stmt) => { if let TableObject::TableName(name) = &insert_stmt.table { if self.is_crdt_sync_table(name) { - // Ohne Connection: leere PK- und FK-Listen (alle Spalten werden upgedatet) + // Hard Delete: Keine ON CONFLICT Logik mehr nötig let insert_transformer = InsertTransformer::new(); - insert_transformer.transform_insert( - insert_stmt, - hlc_timestamp, - &[], - &[], - )?; + insert_transformer.transform_insert(insert_stmt, hlc_timestamp)?; } } Ok(None) @@ -264,25 +171,17 @@ impl CrdtTransformer { Statement::Update { table, assignments, .. } => { - if let TableFactor::Table { name, .. } = &table.relation { + if let TableFactor::Table { name, ..} = &table.relation { if self.is_crdt_sync_table(name) { assignments.push(self.columns.create_hlc_assignment(hlc_timestamp)); } } Ok(None) } - Statement::Delete(del_stmt) => { - if let Some(table_name) = self.extract_table_name_from_delete(del_stmt) { - if self.is_crdt_sync_table(&table_name) { - self.transform_delete_to_update(stmt, hlc_timestamp)?; - } - Ok(None) - } else { - Err(DatabaseError::UnsupportedStatement { - sql: del_stmt.to_string(), - reason: "DELETE from non-table source or multiple tables".to_string(), - }) - } + Statement::Delete(_del_stmt) => { + // Hard Delete - keine Transformation! + // DELETE bleibt DELETE + Ok(None) } Statement::AlterTable { name, .. } => { if self.is_crdt_sync_table(name) { @@ -294,539 +193,4 @@ impl CrdtTransformer { _ => Ok(None), } } - - // ================================================================= - // PRIVATE HELFER (DELETE/UPDATE) - // ================================================================= - - /// Transformiert DELETE zu UPDATE (soft delete) - fn transform_delete_to_update( - &self, - stmt: &mut Statement, - timestamp: &Timestamp, - ) -> Result<(), DatabaseError> { - if let Statement::Delete(del_stmt) = stmt { - let table_to_update = match &del_stmt.from { - sqlparser::ast::FromTable::WithFromKeyword(from) - | sqlparser::ast::FromTable::WithoutKeyword(from) => { - if from.len() == 1 { - from[0].clone() - } else { - return Err(DatabaseError::UnsupportedStatement { - reason: "DELETE with multiple tables not supported".to_string(), - sql: stmt.to_string(), - }); - } - } - }; - - let assignments = vec![ - self.columns.create_tombstone_assignment(), - self.columns.create_hlc_assignment(timestamp), - ]; - - *stmt = Statement::Update { - table: table_to_update, - assignments, - from: None, - selection: del_stmt.selection.clone(), - returning: None, - or: None, - limit: None, - }; - } - Ok(()) - } - - /// Extrahiert Tabellennamen aus DELETE-Statement - fn extract_table_name_from_delete( - &self, - del_stmt: &sqlparser::ast::Delete, - ) -> Option { - let tables = match &del_stmt.from { - sqlparser::ast::FromTable::WithFromKeyword(from) - | sqlparser::ast::FromTable::WithoutKeyword(from) => from, - }; - - if tables.len() == 1 { - if let TableFactor::Table { name, .. } = &tables[0].relation { - Some(name.clone()) - } else { - None - } - } else { - None - } - } - - // ================================================================= - // PRIVATE HELFER (SELECT-TRANSFORMATION) - // (Diese Methoden kommen aus dem alten `query_transformer.rs`) - // ================================================================= - - /// Rekursive Behandlung aller SetExpr-Typen mit vollständiger Subquery-Unterstützung - fn transform_select_query_recursive( - &self, - set_expr: &mut SetExpr, - excluded_tables: &std::collections::HashSet<&str>, - ) -> Result<(), DatabaseError> { - match set_expr { - SetExpr::Select(select) => { - self.add_tombstone_filters_to_select(select, excluded_tables)?; - - // Transformiere auch Subqueries in Projektionen - for projection in &mut select.projection { - match projection { - SelectItem::UnnamedExpr(expr) | SelectItem::ExprWithAlias { expr, .. } => { - self.transform_expression_subqueries(expr, excluded_tables)?; - } - _ => {} // Wildcard projections ignorieren - } - } - - // Transformiere Subqueries in WHERE - if let Some(where_clause) = &mut select.selection { - self.transform_expression_subqueries(where_clause, excluded_tables)?; - } - - // Transformiere Subqueries in GROUP BY - match &mut select.group_by { - sqlparser::ast::GroupByExpr::All(_) => { - // GROUP BY ALL - keine Expressions zu transformieren - } - sqlparser::ast::GroupByExpr::Expressions(exprs, _) => { - for group_expr in exprs { - self.transform_expression_subqueries(group_expr, excluded_tables)?; - } - } - } - - // Transformiere Subqueries in HAVING - if let Some(having) = &mut select.having { - self.transform_expression_subqueries(having, excluded_tables)?; - } - } - SetExpr::SetOperation { left, right, .. } => { - self.transform_select_query_recursive(left, excluded_tables)?; - self.transform_select_query_recursive(right, excluded_tables)?; - } - SetExpr::Query(query) => { - self.transform_select_query_recursive(&mut query.body, excluded_tables)?; - } - SetExpr::Values(values) => { - // Transformiere auch Subqueries in Values-Listen - for row in &mut values.rows { - for expr in row { - self.transform_expression_subqueries(expr, excluded_tables)?; - } - } - } - _ => {} // Andere Fälle - } - Ok(()) - } - - /// Transformiert Subqueries innerhalb von Expressions - fn transform_expression_subqueries( - &self, - expr: &mut Expr, - excluded_tables: &std::collections::HashSet<&str>, - ) -> Result<(), DatabaseError> { - match expr { - // Einfache Subqueries - Expr::Subquery(query) => { - self.transform_select_query_recursive(&mut query.body, excluded_tables)?; - } - // EXISTS Subqueries - Expr::Exists { subquery, .. } => { - self.transform_select_query_recursive(&mut subquery.body, excluded_tables)?; - } - // IN Subqueries - Expr::InSubquery { - expr: left_expr, - subquery, - .. - } => { - self.transform_expression_subqueries(left_expr, excluded_tables)?; - self.transform_select_query_recursive(&mut subquery.body, excluded_tables)?; - } - // ANY/ALL Subqueries - Expr::AnyOp { left, right, .. } | Expr::AllOp { left, right, .. } => { - self.transform_expression_subqueries(left, excluded_tables)?; - self.transform_expression_subqueries(right, excluded_tables)?; - } - // Binäre Operationen - Expr::BinaryOp { left, right, .. } => { - self.transform_expression_subqueries(left, excluded_tables)?; - self.transform_expression_subqueries(right, excluded_tables)?; - } - // Unäre Operationen - Expr::UnaryOp { - expr: inner_expr, .. - } => { - self.transform_expression_subqueries(inner_expr, excluded_tables)?; - } - // Verschachtelte Ausdrücke - Expr::Nested(nested) => { - self.transform_expression_subqueries(nested, excluded_tables)?; - } - // CASE-Ausdrücke - Expr::Case { - operand, - conditions, - else_result, - .. - } => { - if let Some(op) = operand { - self.transform_expression_subqueries(op, excluded_tables)?; - } - for case_when in conditions { - self.transform_expression_subqueries( - &mut case_when.condition, - excluded_tables, - )?; - self.transform_expression_subqueries(&mut case_when.result, excluded_tables)?; - } - if let Some(else_res) = else_result { - self.transform_expression_subqueries(else_res, excluded_tables)?; - } - } - // Funktionsaufrufe - Expr::Function(func) => match &mut func.args { - sqlparser::ast::FunctionArguments::List(sqlparser::ast::FunctionArgumentList { - args, - .. - }) => { - for arg in args { - if let sqlparser::ast::FunctionArg::Unnamed( - sqlparser::ast::FunctionArgExpr::Expr(expr), - ) = arg - { - self.transform_expression_subqueries(expr, excluded_tables)?; - } - } - } - _ => {} - }, - // BETWEEN - Expr::Between { - expr: main_expr, - low, - high, - .. - } => { - self.transform_expression_subqueries(main_expr, excluded_tables)?; - self.transform_expression_subqueries(low, excluded_tables)?; - self.transform_expression_subqueries(high, excluded_tables)?; - } - // IN Liste - Expr::InList { - expr: main_expr, - list, - .. - } => { - self.transform_expression_subqueries(main_expr, excluded_tables)?; - for list_expr in list { - self.transform_expression_subqueries(list_expr, excluded_tables)?; - } - } - // IS NULL/IS NOT NULL - Expr::IsNull(inner) | Expr::IsNotNull(inner) => { - self.transform_expression_subqueries(inner, excluded_tables)?; - } - // Andere Expression-Typen benötigen keine Transformation - _ => {} - } - Ok(()) - } - - /// Fügt Tombstone-Filter zu SELECT-Statements hinzu - fn add_tombstone_filters_to_select( - &self, - select: &mut sqlparser::ast::Select, - excluded_tables: &HashSet<&str>, - ) -> Result<(), DatabaseError> { - // Sammle alle CRDT-Tabellen mit ihren Aliasen - let mut crdt_tables = Vec::new(); - for twj in &select.from { - if let TableFactor::Table { name, alias, .. } = &twj.relation { - // Nutzt die zentrale Logik von CrdtTransformer - if self.is_crdt_sync_table(name) { - let table_alias = alias.as_ref().map(|a| a.name.value.as_str()); - crdt_tables.push((name.clone(), table_alias)); - } - } - } - - if crdt_tables.is_empty() { - return Ok(()); - } - - // Prüfe, welche Tombstone-Spalten bereits in der WHERE-Klausel referenziert werden - let explicitly_filtered_tables = if let Some(where_clause) = &select.selection { - self.find_explicitly_filtered_tombstone_tables(where_clause, &crdt_tables) - } else { - HashSet::new() - }; - - // Erstelle Filter nur für Tabellen, die noch nicht explizit gefiltert werden - let mut tombstone_filters = Vec::new(); - for (table_name, table_alias) in crdt_tables { - let table_name_string = table_name.to_string(); - let table_key = table_alias.unwrap_or(&table_name_string); - if !explicitly_filtered_tables.contains(table_key) { - // Nutzt die zentrale Logik von CrdtColumns - tombstone_filters.push(self.columns.create_tombstone_filter(table_alias)); - } - } - - // Füge die automatischen Filter hinzu - if !tombstone_filters.is_empty() { - let combined_filter = tombstone_filters - .into_iter() - .reduce(|acc, expr| Expr::BinaryOp { - left: Box::new(acc), - op: BinaryOperator::And, - right: Box::new(expr), - }) - .unwrap(); - - match &mut select.selection { - Some(existing) => { - *existing = Expr::BinaryOp { - left: Box::new(existing.clone()), - op: BinaryOperator::And, - right: Box::new(combined_filter), - }; - } - None => { - select.selection = Some(combined_filter); - } - } - } - - Ok(()) - } - - /// Findet alle Tabellen, die bereits explizit Tombstone-Filter in der WHERE-Klausel haben - fn find_explicitly_filtered_tombstone_tables( - &self, - where_expr: &Expr, - crdt_tables: &[(ObjectName, Option<&str>)], - ) -> HashSet { - let mut filtered_tables = HashSet::new(); - self.scan_expression_for_tombstone_references( - where_expr, - crdt_tables, - &mut filtered_tables, - ); - filtered_tables - } - - /// Rekursiv durchsucht einen Expression-Baum nach Tombstone-Spalten-Referenzen - fn scan_expression_for_tombstone_references( - &self, - expr: &Expr, - crdt_tables: &[(ObjectName, Option<&str>)], - filtered_tables: &mut HashSet, - ) { - match expr { - Expr::Identifier(ident) => { - // Nutzt die zentrale Konfiguration von CrdtColumns - if ident.value == self.columns.tombstone && crdt_tables.len() == 1 { - let table_name_str = crdt_tables[0].0.to_string(); - let table_key = crdt_tables[0].1.unwrap_or(&table_name_str); - filtered_tables.insert(table_key.to_string()); - } - } - Expr::CompoundIdentifier(idents) => { - // Nutzt die zentrale Konfiguration von CrdtColumns - if idents.len() == 2 && idents[1].value == self.columns.tombstone { - let table_ref = &idents[0].value; - for (table_name, alias) in crdt_tables { - let table_name_str = table_name.to_string(); - if table_ref == &table_name_str || alias.map_or(false, |a| a == table_ref) { - filtered_tables.insert(table_ref.clone()); - break; - } - } - } - } - Expr::BinaryOp { left, right, .. } => { - self.scan_expression_for_tombstone_references(left, crdt_tables, filtered_tables); - self.scan_expression_for_tombstone_references(right, crdt_tables, filtered_tables); - } - Expr::UnaryOp { expr, .. } => { - self.scan_expression_for_tombstone_references(expr, crdt_tables, filtered_tables); - } - Expr::Nested(nested) => { - self.scan_expression_for_tombstone_references(nested, crdt_tables, filtered_tables); - } - Expr::InList { expr, .. } => { - self.scan_expression_for_tombstone_references(expr, crdt_tables, filtered_tables); - } - Expr::Between { expr, .. } => { - self.scan_expression_for_tombstone_references(expr, crdt_tables, filtered_tables); - } - Expr::IsNull(expr) | Expr::IsNotNull(expr) => { - self.scan_expression_for_tombstone_references(expr, crdt_tables, filtered_tables); - } - Expr::Function(func) => { - if let sqlparser::ast::FunctionArguments::List( - sqlparser::ast::FunctionArgumentList { args, .. }, - ) = &func.args - { - for arg in args { - if let sqlparser::ast::FunctionArg::Unnamed( - sqlparser::ast::FunctionArgExpr::Expr(expr), - ) = arg - { - self.scan_expression_for_tombstone_references( - expr, - crdt_tables, - filtered_tables, - ); - } - } - } - } - Expr::Case { - operand, - conditions, - else_result, - .. - } => { - if let Some(op) = operand { - self.scan_expression_for_tombstone_references(op, crdt_tables, filtered_tables); - } - for case_when in conditions { - self.scan_expression_for_tombstone_references( - &case_when.condition, - crdt_tables, - filtered_tables, - ); - self.scan_expression_for_tombstone_references( - &case_when.result, - crdt_tables, - filtered_tables, - ); - } - if let Some(else_res) = else_result { - self.scan_expression_for_tombstone_references( - else_res, - crdt_tables, - filtered_tables, - ); - } - } - Expr::Subquery(query) => { - self.analyze_query_for_tombstone_references(query, crdt_tables, filtered_tables) - .ok(); - } - Expr::Exists { subquery, .. } => { - self.analyze_query_for_tombstone_references(subquery, crdt_tables, filtered_tables) - .ok(); - } - Expr::InSubquery { expr, subquery, .. } => { - self.scan_expression_for_tombstone_references(expr, crdt_tables, filtered_tables); - self.analyze_query_for_tombstone_references(subquery, crdt_tables, filtered_tables) - .ok(); - } - Expr::AnyOp { left, right, .. } | Expr::AllOp { left, right, .. } => { - self.scan_expression_for_tombstone_references(left, crdt_tables, filtered_tables); - self.scan_expression_for_tombstone_references(right, crdt_tables, filtered_tables); - } - _ => {} - } - } - - fn analyze_query_for_tombstone_references( - &self, - query: &sqlparser::ast::Query, - crdt_tables: &[(ObjectName, Option<&str>)], - filtered_tables: &mut HashSet, - ) -> Result<(), DatabaseError> { - self.analyze_set_expr_for_tombstone_references(&query.body, crdt_tables, filtered_tables) - } - - fn analyze_set_expr_for_tombstone_references( - &self, - set_expr: &SetExpr, - crdt_tables: &[(ObjectName, Option<&str>)], - filtered_tables: &mut HashSet, - ) -> Result<(), DatabaseError> { - match set_expr { - SetExpr::Select(select) => { - if let Some(where_clause) = &select.selection { - self.scan_expression_for_tombstone_references( - where_clause, - crdt_tables, - filtered_tables, - ); - } - - for projection in &select.projection { - match projection { - SelectItem::UnnamedExpr(expr) | SelectItem::ExprWithAlias { expr, .. } => { - self.scan_expression_for_tombstone_references( - expr, - crdt_tables, - filtered_tables, - ); - } - _ => {} - } - } - - match &select.group_by { - sqlparser::ast::GroupByExpr::All(_) => {} - sqlparser::ast::GroupByExpr::Expressions(exprs, _) => { - for group_expr in exprs { - self.scan_expression_for_tombstone_references( - group_expr, - crdt_tables, - filtered_tables, - ); - } - } - } - - if let Some(having) = &select.having { - self.scan_expression_for_tombstone_references( - having, - crdt_tables, - filtered_tables, - ); - } - } - SetExpr::SetOperation { left, right, .. } => { - self.analyze_set_expr_for_tombstone_references(left, crdt_tables, filtered_tables)?; - self.analyze_set_expr_for_tombstone_references( - right, - crdt_tables, - filtered_tables, - )?; - } - SetExpr::Query(query) => { - self.analyze_set_expr_for_tombstone_references( - &query.body, - crdt_tables, - filtered_tables, - )?; - } - SetExpr::Values(values) => { - for row in &values.rows { - for expr in row { - self.scan_expression_for_tombstone_references( - expr, - crdt_tables, - filtered_tables, - ); - } - } - } - _ => {} - } - Ok(()) - } } diff --git a/src-tauri/src/crdt/trigger.rs b/src-tauri/src/crdt/trigger.rs index 41585fd..eaaa828 100644 --- a/src-tauri/src/crdt/trigger.rs +++ b/src-tauri/src/crdt/trigger.rs @@ -9,6 +9,7 @@ use ts_rs::TS; // Der "z_"-Präfix soll sicherstellen, dass diese Trigger als Letzte ausgeführt werden const INSERT_TRIGGER_TPL: &str = "z_crdt_{TABLE_NAME}_insert"; const UPDATE_TRIGGER_TPL: &str = "z_crdt_{TABLE_NAME}_update"; +const DELETE_TRIGGER_TPL: &str = "z_crdt_{TABLE_NAME}_delete"; //const SYNC_ACTIVE_KEY: &str = "sync_active"; pub const TOMBSTONE_COLUMN: &str = "haex_tombstone"; @@ -143,6 +144,7 @@ pub fn setup_triggers_for_table( let insert_trigger_sql = generate_insert_trigger_sql(table_name, &pks, &cols_to_track); let update_trigger_sql = generate_update_trigger_sql(table_name, &pks, &cols_to_track); + let delete_trigger_sql = generate_delete_trigger_sql(table_name, &pks, &cols_to_track); if recreate { drop_triggers_for_table(&tx, table_name)?; @@ -150,6 +152,7 @@ pub fn setup_triggers_for_table( tx.execute_batch(&insert_trigger_sql)?; tx.execute_batch(&update_trigger_sql)?; + tx.execute_batch(&delete_trigger_sql)?; Ok(TriggerSetupResult::Success) } @@ -170,28 +173,7 @@ pub fn get_table_schema(conn: &Connection, table_name: &str) -> RusqliteResult RusqliteResult> { - if !is_safe_identifier(table_name) { - return Err(rusqlite::Error::InvalidParameterName(format!( - "Invalid or unsafe table name provided: {}", - table_name - )) - .into()); - } - - let sql = format!("PRAGMA foreign_key_list(\"{}\");", table_name); - let mut stmt = conn.prepare(&sql)?; - - // foreign_key_list gibt Spalten zurück: id, seq, table, from, to, on_update, on_delete, match - // Wir brauchen die "from" Spalte, die den Namen der FK-Spalte in der aktuellen Tabelle enthält - let rows = stmt.query_map([], |row| { - row.get::<_, String>("from") - })?; - - rows.collect() -} +// get_foreign_key_columns() removed - not needed with hard deletes (no ON CONFLICT logic) pub fn drop_triggers_for_table( tx: &Transaction, // Arbeitet direkt auf einer Transaktion @@ -209,8 +191,13 @@ pub fn drop_triggers_for_table( drop_trigger_sql(INSERT_TRIGGER_TPL.replace("{TABLE_NAME}", table_name)); let drop_update_trigger_sql = drop_trigger_sql(UPDATE_TRIGGER_TPL.replace("{TABLE_NAME}", table_name)); + let drop_delete_trigger_sql = + drop_trigger_sql(DELETE_TRIGGER_TPL.replace("{TABLE_NAME}", table_name)); - let sql_batch = format!("{}\n{}", drop_insert_trigger_sql, drop_update_trigger_sql); + let sql_batch = format!( + "{}\n{}\n{}", + drop_insert_trigger_sql, drop_update_trigger_sql, drop_delete_trigger_sql + ); tx.execute_batch(&sql_batch)?; Ok(()) @@ -350,19 +337,7 @@ fn generate_update_trigger_sql(table_name: &str, pks: &[String], cols: &[String] } } - // Soft-delete loggen - writeln!( - &mut body, - "INSERT INTO {log_table} (haex_timestamp, op_type, table_name, row_pks) - SELECT NEW.\"{hlc_col}\", 'DELETE', '{table}', json_object({pk_payload}) - WHERE NEW.\"{tombstone_col}\" = 1 AND OLD.\"{tombstone_col}\" = 0;", - log_table = TABLE_CRDT_LOGS, - hlc_col = HLC_TIMESTAMP_COLUMN, - table = table_name, - pk_payload = pk_json_payload, - tombstone_col = TOMBSTONE_COLUMN - ) - .unwrap(); + // Soft-delete Logging entfernt - wir nutzen jetzt Hard Deletes mit eigenem BEFORE DELETE Trigger let trigger_name = UPDATE_TRIGGER_TPL.replace("{TABLE_NAME}", table_name); @@ -375,3 +350,54 @@ fn generate_update_trigger_sql(table_name: &str, pks: &[String], cols: &[String] END;" ) } + +/// Generiert das SQL für den BEFORE DELETE-Trigger. +/// WICHTIG: BEFORE DELETE damit die Daten noch verfügbar sind! +fn generate_delete_trigger_sql(table_name: &str, pks: &[String], cols: &[String]) -> String { + let pk_json_payload = pks + .iter() + .map(|pk| format!("'{}', OLD.\"{}\"", pk, pk)) + .collect::>() + .join(", "); + + let mut body = String::new(); + + // Alle Spaltenwerte speichern für mögliche Wiederherstellung + if !cols.is_empty() { + for col in cols { + writeln!( + &mut body, + "INSERT INTO {log_table} (haex_timestamp, op_type, table_name, row_pks, column_name, old_value) + VALUES (OLD.\"{hlc_col}\", 'DELETE', '{table}', json_object({pk_payload}), '{column}', + json_object('value', OLD.\"{column}\"));", + log_table = TABLE_CRDT_LOGS, + hlc_col = HLC_TIMESTAMP_COLUMN, + table = table_name, + pk_payload = pk_json_payload, + column = col + ).unwrap(); + } + } else { + // Nur PKs -> minimales Delete Log + writeln!( + &mut body, + "INSERT INTO {log_table} (haex_timestamp, op_type, table_name, row_pks) + VALUES (OLD.\"{hlc_col}\", 'DELETE', '{table}', json_object({pk_payload}));", + log_table = TABLE_CRDT_LOGS, + hlc_col = HLC_TIMESTAMP_COLUMN, + table = table_name, + pk_payload = pk_json_payload + ).unwrap(); + } + + let trigger_name = DELETE_TRIGGER_TPL.replace("{TABLE_NAME}", table_name); + + format!( + "CREATE TRIGGER IF NOT EXISTS \"{trigger_name}\" + BEFORE DELETE ON \"{table_name}\" + FOR EACH ROW + BEGIN + {body} + END;" + ) +} diff --git a/src-tauri/src/extension/database/executor.rs b/src-tauri/src/extension/database/executor.rs index 223b449..a74290e 100644 --- a/src-tauri/src/extension/database/executor.rs +++ b/src-tauri/src/extension/database/executor.rs @@ -135,7 +135,6 @@ impl SqlExecutor { if let Some(table_name) = transformer.transform_execute_statement_with_table_info( &mut statement, &hlc_timestamp, - tx, )? { modified_schema_tables.insert(table_name); } @@ -239,7 +238,6 @@ impl SqlExecutor { if let Some(table_name) = transformer.transform_execute_statement_with_table_info( &mut statement, &hlc_timestamp, - tx, )? { modified_schema_tables.insert(table_name); } @@ -460,13 +458,12 @@ impl SqlExecutor { } let sql_params = ValueConverter::convert_params(params)?; - let transformer = CrdtTransformer::new(); - let mut stmt_to_execute = ast_vec.pop().unwrap(); - transformer.transform_select_statement(&mut stmt_to_execute)?; + // Hard Delete: Keine SELECT-Transformation mehr nötig + let stmt_to_execute = ast_vec.pop().unwrap(); let transformed_sql = stmt_to_execute.to_string(); - eprintln!("DEBUG: Transformed SELECT: {}", transformed_sql); + eprintln!("DEBUG: SELECT (no transformation): {}", transformed_sql); let mut prepared_stmt = conn.prepare(&transformed_sql)?; diff --git a/src-tauri/src/extension/database/mod.rs b/src-tauri/src/extension/database/mod.rs index 95e2cdd..cde094d 100644 --- a/src-tauri/src/extension/database/mod.rs +++ b/src-tauri/src/extension/database/mod.rs @@ -232,14 +232,8 @@ pub async fn extension_sql_select( // Database operation with_connection(&state.db, |conn| { let sql_params = ValueConverter::convert_params(¶ms)?; - let transformer = CrdtTransformer::new(); - - // Use the last statement for result set - let last_statement = ast_vec.pop().unwrap(); - let mut stmt_to_execute = last_statement; - - // Transform the statement - transformer.transform_select_statement(&mut stmt_to_execute)?; + // Hard Delete: Keine SELECT-Transformation mehr nötig + let stmt_to_execute = ast_vec.pop().unwrap(); let transformed_sql = stmt_to_execute.to_string(); // Prepare and execute query diff --git a/src/components/haex/desktop/index.vue b/src/components/haex/desktop/index.vue index 8c23308..393ccd6 100644 --- a/src/components/haex/desktop/index.vue +++ b/src/components/haex/desktop/index.vue @@ -563,7 +563,9 @@ const onSwiperInit = (swiper: SwiperType) => { } const onSlideChange = (swiper: SwiperType) => { - workspaceStore.switchToWorkspace(swiper.activeIndex) + workspaceStore.switchToWorkspace( + workspaceStore.workspaces.at(swiper.activeIndex)?.id, + ) } // Workspace control handlers diff --git a/src/components/haex/workspace/card.vue b/src/components/haex/workspace/card.vue index 4d92abe..0025c8b 100644 --- a/src/components/haex/workspace/card.vue +++ b/src/components/haex/workspace/card.vue @@ -6,7 +6,7 @@ ? 'ring-2 ring-secondary bg-secondary/10' : 'hover:ring-2 hover:ring-gray-300', ]" - @click="workspaceStore.slideToWorkspace(workspace.position)" + @click="workspaceStore.slideToWorkspace(workspace.id)" >