fixed drizzle rust logic

This commit is contained in:
2025-10-21 16:29:13 +02:00
parent e268947593
commit 9ea057e943
13 changed files with 151 additions and 112 deletions

View File

@ -164,9 +164,11 @@ pub fn execute(
if has_returning {
// Use prepare + query for RETURNING statements
let mut stmt = conn.prepare(&sql).map_err(|e| DatabaseError::PrepareError {
reason: e.to_string(),
})?;
let mut stmt = conn
.prepare(&sql)
.map_err(|e| DatabaseError::PrepareError {
reason: e.to_string(),
})?;
let num_columns = stmt.column_count();
let mut rows = stmt
@ -183,11 +185,11 @@ pub fn execute(
let mut row_values: Vec<JsonValue> = Vec::with_capacity(num_columns);
for i in 0..num_columns {
let value_ref = row.get_ref(i).map_err(|e| {
DatabaseError::RowProcessingError {
reason: format!("Failed to get column {}: {}", i, e),
}
})?;
let value_ref =
row.get_ref(i)
.map_err(|e| DatabaseError::RowProcessingError {
reason: format!("Failed to get column {}: {}", i, e),
})?;
let json_val = convert_value_ref_to_json(value_ref)?;
row_values.push(json_val);
@ -274,6 +276,16 @@ pub fn select(
})
}
pub fn select_with_crdt(
sql: String,
params: Vec<JsonValue>,
connection: &DbConnection,
) -> Result<Vec<Vec<JsonValue>>, DatabaseError> {
with_connection(&connection, |conn| {
SqlExecutor::select_internal(conn, &sql, &params)
})
}
/// Konvertiert rusqlite ValueRef zu JSON
pub fn convert_value_ref_to_json(value_ref: ValueRef) -> Result<JsonValue, DatabaseError> {
let json_val = match value_ref {

View File

@ -43,6 +43,15 @@ pub fn sql_execute(
core::execute(sql, params, &state.db)
}
#[tauri::command]
pub fn sql_select_with_crdt(
sql: String,
params: Vec<JsonValue>,
state: State<'_, AppState>,
) -> Result<Vec<Vec<JsonValue>>, DatabaseError> {
core::select_with_crdt(sql, params, &state.db)
}
#[tauri::command]
pub fn sql_execute_with_crdt(
sql: String,

View File

@ -167,7 +167,6 @@ impl ExtensionManager {
Ok(specific_extension_dir)
}
pub fn add_production_extension(&self, extension: Extension) -> Result<(), ExtensionError> {
if extension.id.is_empty() {
return Err(ExtensionError::ValidationError {
@ -223,11 +222,12 @@ impl ExtensionManager {
name: &str,
) -> Result<Option<(String, Extension)>, ExtensionError> {
// 1. Check dev extensions first (higher priority)
let dev_extensions = self.dev_extensions.lock().map_err(|e| {
ExtensionError::MutexPoisoned {
reason: e.to_string(),
}
})?;
let dev_extensions =
self.dev_extensions
.lock()
.map_err(|e| ExtensionError::MutexPoisoned {
reason: e.to_string(),
})?;
for (id, ext) in dev_extensions.iter() {
if ext.manifest.public_key == public_key && ext.manifest.name == name {
@ -236,11 +236,12 @@ impl ExtensionManager {
}
// 2. Check production extensions
let prod_extensions = self.production_extensions.lock().map_err(|e| {
ExtensionError::MutexPoisoned {
reason: e.to_string(),
}
})?;
let prod_extensions =
self.production_extensions
.lock()
.map_err(|e| ExtensionError::MutexPoisoned {
reason: e.to_string(),
})?;
for (id, ext) in prod_extensions.iter() {
if ext.manifest.public_key == public_key && ext.manifest.name == name {
@ -262,11 +263,7 @@ impl ExtensionManager {
.map(|(_, ext)| ext))
}
pub fn remove_extension(
&self,
public_key: &str,
name: &str,
) -> Result<(), ExtensionError> {
pub fn remove_extension(&self, public_key: &str, name: &str) -> Result<(), ExtensionError> {
let (id, _) = self
.find_extension_id_by_public_key_and_name(public_key, name)?
.ok_or_else(|| ExtensionError::NotFound {
@ -276,11 +273,12 @@ impl ExtensionManager {
// Remove from dev extensions first
{
let mut dev_extensions = self.dev_extensions.lock().map_err(|e| {
ExtensionError::MutexPoisoned {
reason: e.to_string(),
}
})?;
let mut dev_extensions =
self.dev_extensions
.lock()
.map_err(|e| ExtensionError::MutexPoisoned {
reason: e.to_string(),
})?;
if dev_extensions.remove(&id).is_some() {
return Ok(());
}
@ -288,11 +286,12 @@ impl ExtensionManager {
// Remove from production extensions
{
let mut prod_extensions = self.production_extensions.lock().map_err(|e| {
ExtensionError::MutexPoisoned {
reason: e.to_string(),
}
})?;
let mut prod_extensions =
self.production_extensions
.lock()
.map_err(|e| ExtensionError::MutexPoisoned {
reason: e.to_string(),
})?;
prod_extensions.remove(&id);
}
@ -316,7 +315,10 @@ impl ExtensionManager {
})?;
eprintln!("DEBUG: Removing extension with ID: {}", extension.id);
eprintln!("DEBUG: Extension name: {}, version: {}", extension_name, extension_version);
eprintln!(
"DEBUG: Extension name: {}, version: {}",
extension_name, extension_version
);
// Lösche Permissions und Extension-Eintrag in einer Transaktion
with_connection(&state.db, |conn| {
@ -327,12 +329,11 @@ impl ExtensionManager {
})?;
// Lösche alle Permissions mit extension_id
eprintln!("DEBUG: Deleting permissions for extension_id: {}", extension.id);
PermissionManager::delete_permissions_in_transaction(
&tx,
&hlc_service,
&extension.id,
)?;
eprintln!(
"DEBUG: Deleting permissions for extension_id: {}",
extension.id
);
PermissionManager::delete_permissions_in_transaction(&tx, &hlc_service, &extension.id)?;
// Lösche Extension-Eintrag mit extension_id
let sql = format!("DELETE FROM {} WHERE id = ?", TABLE_EXTENSIONS);
@ -573,6 +574,7 @@ impl ExtensionManager {
app_handle: &AppHandle,
state: &State<'_, AppState>,
) -> Result<Vec<String>, ExtensionError> {
// Clear existing data
self.production_extensions
.lock()
.map_err(|e| ExtensionError::MutexPoisoned {
@ -592,19 +594,22 @@ impl ExtensionManager {
})?
.clear();
// Schritt 1: Alle Daten aus der Datenbank in einem Rutsch laden.
// Lade alle Daten aus der Datenbank
let extensions = with_connection(&state.db, |conn| {
let sql = format!(
"SELECT id, name, version, author, entry, icon, public_key, signature, homepage, description, enabled FROM {}",
TABLE_EXTENSIONS
);
"SELECT id, name, version, author, entry, icon, public_key, signature, homepage, description, enabled FROM {}",
TABLE_EXTENSIONS
);
eprintln!("DEBUG: SQL Query before transformation: {}", sql);
// select_internal gibt jetzt Vec<Vec<JsonValue>> zurück
let results = SqlExecutor::select_internal(conn, &sql, &[])?;
eprintln!("DEBUG: Query returned {} results", results.len());
let mut data = Vec::new();
for result in results {
let id = result["id"]
for row in results {
// Wir erwarten die Werte in der Reihenfolge der SELECT-Anweisung
let id = row[0]
.as_str()
.ok_or_else(|| DatabaseError::SerializationError {
reason: "Missing id field".to_string(),
@ -612,31 +617,31 @@ impl ExtensionManager {
.to_string();
let manifest = ExtensionManifest {
name: result["name"]
name: row[1]
.as_str()
.ok_or_else(|| DatabaseError::SerializationError {
reason: "Missing name field".to_string(),
})?
.to_string(),
version: result["version"]
version: row[2]
.as_str()
.ok_or_else(|| DatabaseError::SerializationError {
reason: "Missing version field".to_string(),
})?
.to_string(),
author: result["author"].as_str().map(String::from),
entry: result["entry"].as_str().unwrap_or("index.html").to_string(),
icon: result["icon"].as_str().map(String::from),
public_key: result["public_key"].as_str().unwrap_or("").to_string(),
signature: result["signature"].as_str().unwrap_or("").to_string(),
author: row[3].as_str().map(String::from),
entry: row[4].as_str().unwrap_or("index.html").to_string(),
icon: row[5].as_str().map(String::from),
public_key: row[6].as_str().unwrap_or("").to_string(),
signature: row[7].as_str().unwrap_or("").to_string(),
permissions: ExtensionPermissions::default(),
homepage: result["homepage"].as_str().map(String::from),
description: result["description"].as_str().map(String::from),
homepage: row[8].as_str().map(String::from),
description: row[9].as_str().map(String::from),
};
let enabled = result["enabled"]
let enabled = row[10]
.as_bool()
.or_else(|| result["enabled"].as_i64().map(|v| v != 0))
.or_else(|| row[10].as_i64().map(|v| v != 0))
.unwrap_or(false);
data.push(ExtensionDataFromDb {
@ -684,10 +689,7 @@ impl ExtensionManager {
continue;
}
eprintln!(
"DEBUG: Extension loaded successfully: {}",
extension_id
);
eprintln!("DEBUG: Extension loaded successfully: {}", extension_id);
let extension = Extension {
id: extension_id.clone(),

View File

@ -5,6 +5,8 @@ use crate::crdt::transformer::CrdtTransformer;
use crate::crdt::trigger;
use crate::database::core::{convert_value_ref_to_json, parse_sql_statements, ValueConverter};
use crate::database::error::DatabaseError;
use crate::database::DbConnection;
use rusqlite::Connection;
use rusqlite::{params_from_iter, types::Value as SqliteValue, ToSql, Transaction};
use serde_json::Value as JsonValue;
use sqlparser::ast::{Insert, Statement, TableObject};
@ -423,10 +425,10 @@ impl SqlExecutor {
/// Führt SELECT aus (mit CRDT-Transformation) - OHNE Permission-Check
pub fn select_internal(
conn: &rusqlite::Connection,
conn: &Connection,
sql: &str,
params: &[JsonValue],
) -> Result<Vec<JsonValue>, DatabaseError> {
) -> Result<Vec<Vec<JsonValue>>, DatabaseError> {
// Parameter validation
let total_placeholders = sql.matches('?').count();
if total_placeholders != params.len() {
@ -457,42 +459,40 @@ impl SqlExecutor {
let sql_params = ValueConverter::convert_params(params)?;
let transformer = CrdtTransformer::new();
let last_statement = ast_vec.pop().unwrap();
let mut stmt_to_execute = last_statement;
let mut stmt_to_execute = ast_vec.pop().unwrap();
transformer.transform_select_statement(&mut stmt_to_execute)?;
let transformed_sql = stmt_to_execute.to_string();
let mut prepared_stmt =
conn.prepare(&transformed_sql)
.map_err(|e| DatabaseError::ExecutionError {
sql: transformed_sql.clone(),
reason: e.to_string(),
table: None,
})?;
let mut prepared_stmt = conn.prepare(&transformed_sql)?;
let num_columns = prepared_stmt.column_count();
let column_names: Vec<String> = prepared_stmt
.column_names()
.into_iter()
.map(|s| s.to_string())
.collect();
let rows = prepared_stmt
.query_map(params_from_iter(sql_params.iter()), |row| {
crate::extension::database::row_to_json_value(row, &column_names)
})
let mut rows = prepared_stmt
.query(params_from_iter(sql_params.iter()))
.map_err(|e| DatabaseError::QueryError {
reason: e.to_string(),
})?;
let mut results = Vec::new();
for row_result in rows {
results.push(row_result.map_err(|e| DatabaseError::RowProcessingError {
reason: e.to_string(),
})?);
let mut result_vec: Vec<Vec<JsonValue>> = Vec::new();
while let Some(row) = rows.next().map_err(|e| DatabaseError::RowProcessingError {
reason: format!("Row iteration error: {}", e),
})? {
let mut row_values: Vec<JsonValue> = Vec::with_capacity(num_columns);
for i in 0..num_columns {
let value_ref = row
.get_ref(i)
.map_err(|e| DatabaseError::RowProcessingError {
reason: format!("Failed to get column {}: {}", i, e),
})?;
let json_val = convert_value_ref_to_json(value_ref)?;
row_values.push(json_val);
}
result_vec.push(row_values);
}
Ok(results)
Ok(result_vec)
}
/// Führt SQL mit CRDT-Transformation aus und gibt RETURNING-Ergebnisse zurück

View File

@ -70,15 +70,16 @@ pub fn run() {
database::delete_vault,
database::list_vaults,
database::open_encrypted_database,
database::sql_execute,
database::sql_execute_with_crdt,
database::sql_execute,
database::sql_query_with_crdt,
database::sql_select_with_crdt,
database::sql_select,
database::vault_exists,
extension::database::extension_sql_execute,
extension::database::extension_sql_select,
extension::get_all_extensions,
extension::get_all_dev_extensions,
extension::get_all_extensions,
extension::get_extension_info,
extension::install_extension_with_permissions,
extension::is_extension_installed,

View File

@ -17,6 +17,7 @@
class="w-full h-full"
@swiper="onSwiperInit"
@slide-change="onSlideChange"
direction="vertical"
>
<SwiperSlide
v-for="workspace in workspaces"

View File

@ -52,7 +52,7 @@
<p
v-if="extension.description"
class="text-sm text-gray-600 dark:text-gray-300 mt-2 line-clamp-2"
class="hidden @lg:flex text-sm text-gray-600 dark:text-gray-300 mt-2 line-clamp-2"
>
{{ extension.description }}
</p>
@ -67,7 +67,9 @@
>
<UIcon name="i-heroicons-check-circle-solid" />
<span v-if="!extension.installedVersion">{{ t('installed') }}</span>
<span v-else>{{ t('installedVersion', { version: extension.installedVersion }) }}</span>
<span v-else>{{
t('installedVersion', { version: extension.installedVersion })
}}</span>
</div>
<div
v-if="extension.downloads"
@ -114,10 +116,16 @@
<div class="flex items-center justify-between gap-2">
<UButton
:label="getInstallButtonLabel()"
:color="extension.isInstalled && !extension.installedVersion ? 'neutral' : 'primary'"
:color="
extension.isInstalled && !extension.installedVersion
? 'neutral'
: 'primary'
"
:disabled="extension.isInstalled && !extension.installedVersion"
:icon="
extension.isInstalled && !extension.installedVersion ? 'i-heroicons-check' : 'i-heroicons-arrow-down-tray'
extension.isInstalled && !extension.installedVersion
? 'i-heroicons-check'
: 'i-heroicons-arrow-down-tray'
"
size="sm"
@click.stop="$emit('install')"

View File

@ -1,8 +1,8 @@
<template>
<div class="flex flex-col h-full">
<div class="flex flex-col h-full bg-default">
<!-- Header with Actions -->
<div
class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 p-6 border-b border-gray-200 dark:border-gray-800"
class="flex flex-col @lg:flex-row @lg:items-center justify-between gap-4 p-6 border-b border-gray-200 dark:border-gray-800"
>
<div>
<h1 class="text-2xl font-bold">
@ -14,14 +14,14 @@
</div>
<div
class="flex flex-col sm:flex-row items-stretch sm:items-center gap-3"
class="flex flex-col @lg:flex-row items-stretch @lg:items-center gap-3"
>
<!-- Marketplace Selector -->
<USelectMenu
v-model="selectedMarketplace"
:items="marketplaces"
value-key="id"
class="w-full sm:w-48"
class="w-full @lg:w-48"
>
<template #leading>
<UIcon name="i-heroicons-building-storefront" />
@ -34,13 +34,14 @@
icon="i-heroicons-arrow-up-tray"
color="neutral"
@click="onSelectExtensionAsync"
block
/>
</div>
</div>
<!-- Search and Filters -->
<div
class="flex flex-col sm:flex-row items-stretch sm:items-center gap-4 p-6 border-b border-gray-200 dark:border-gray-800"
class="flex flex-col @lg:flex-row items-stretch @lg:items-center gap-4 p-6 border-b border-gray-200 dark:border-gray-800"
>
<UInput
v-model="searchQuery"
@ -53,7 +54,7 @@
:items="categories"
:placeholder="t('filter.category')"
value-key="id"
class="w-full sm:w-48"
class="w-full @lg:w-48"
>
<template #leading>
<UIcon name="i-heroicons-tag" />
@ -65,7 +66,7 @@
<div class="flex-1 overflow-auto p-6">
<div
v-if="filteredExtensions.length"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
class="grid grid-cols-1 @md:grid-cols-2 @2xl:grid-cols-3 gap-4"
>
<!-- Marketplace Extension Card -->
<HaexExtensionMarketplaceCard

View File

@ -1,8 +1,6 @@
<template>
<div>
<div
class="grid grid-rows-2 sm:grid-cols-2 sm:gap-2 p-2 max-w-2xl w-full h-fit"
>
<div class="w-full h-full bg-default">
<div class="grid grid-cols-2 p-2">
<div class="p-2">{{ t('language') }}</div>
<div><UiDropdownLocale @select="onSelectLocaleAsync" /></div>
@ -34,6 +32,8 @@
@change="onUpdateDeviceNameAsync"
/>
</div>
<div class="h-full"></div>
</div>
</div>
</template>

View File

@ -5,7 +5,7 @@
:class="[
'absolute bg-default/80 backdrop-blur-xl rounded-xl shadow-2xl overflow-hidden isolate',
'border border-gray-200 dark:border-gray-700 transition-all ease-out duration-600 ',
'flex flex-col',
'flex flex-col @container',
{ 'select-none': isResizingOrDragging },
isActive ? 'z-50' : 'z-10',
]"
@ -208,6 +208,7 @@ useDrag(
}
}
globalThis.getSelection()?.removeAllRanges()
emit('positionChanged', x.value, y.value)
emit('sizeChanged', width.value, height.value)
emit('dragEnd')

View File

@ -2,7 +2,7 @@
<UCard
class="cursor-pointer transition-all h-32 w-72 shrink-0 group duration-500"
:class="[
workspace.position === currentWorkspaceIndex
workspace.id === currentWorkspace?.id
? 'ring-2 ring-secondary bg-secondary/10'
: 'hover:ring-2 hover:ring-gray-300',
]"
@ -31,5 +31,5 @@ defineProps<{ workspace: IWorkspace }>()
const workspaceStore = useWorkspaceStore()
const { currentWorkspaceIndex } = storeToRefs(workspaceStore)
const { currentWorkspace } = storeToRefs(workspaceStore)
</script>

View File

@ -38,6 +38,7 @@ export const useWorkspaceStore = defineStore('workspaceStore', () => {
.from(haexWorkspaces)
.orderBy(asc(haexWorkspaces.position))
console.log('loadWorkspacesAsync', items)
workspaces.value = items
// Create default workspace if none exist

View File

@ -142,7 +142,10 @@ const drizzleCallback = (async (
if (isSelectQuery(sql)) {
// SELECT statements
rows = await invoke<unknown[]>('sql_select', { sql, params }).catch((e) => {
rows = await invoke<unknown[]>('sql_select_with_crdt', {
sql,
params,
}).catch((e) => {
console.error('SQL select Error:', e, sql, params)
return []
})
@ -164,9 +167,9 @@ const drizzleCallback = (async (
console.error('SQL execute with CRDT Error:', e, sql, params, rows)
return []
})
return { rows: undefined }
}
console.log('drizzle found', rows)
if (method === 'get') {
return { rows: rows.length > 0 ? [rows[0]] : [] }
} else {