mirror of
https://github.com/kunkunsh/kunkun.git
synced 2025-05-24 13:45:03 +00:00
Compare commits
4 Commits
b3e2082abe
...
20449b846b
Author | SHA1 | Date | |
---|---|---|---|
![]() |
20449b846b | ||
![]() |
37c72a3931 | ||
![]() |
382ceb120f | ||
![]() |
3271507d0c |
@ -1,41 +1,34 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import * as schema from "@kksh/drizzle/schema"
|
||||
import Database from "@tauri-apps/plugin-sql"
|
||||
import * as dbCmd from "@kunkunapi/src/commands/db"
|
||||
import { drizzle } from "drizzle-orm/sqlite-proxy"
|
||||
|
||||
/**
|
||||
* Represents the result of a SELECT query.
|
||||
*/
|
||||
export type SelectQueryResult = {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the sqlite database via the Tauri Proxy.
|
||||
*/
|
||||
// export const sqlite = await Database.load("sqlite:test.db");
|
||||
|
||||
export async function getDb() {
|
||||
return await Database.load("sqlite:test.db")
|
||||
}
|
||||
|
||||
/**
|
||||
* The drizzle database instance.
|
||||
*/
|
||||
export const db = drizzle<typeof schema>(
|
||||
async (sql, params, method) => {
|
||||
const sqlite = await getDb()
|
||||
let rows: any = []
|
||||
let results = []
|
||||
|
||||
// console.log({
|
||||
// sql,
|
||||
// params,
|
||||
// method
|
||||
// })
|
||||
// If the query is a SELECT, use the select method
|
||||
if (isSelectQuery(sql)) {
|
||||
rows = await sqlite.select(sql, params).catch((e) => {
|
||||
rows = await dbCmd.select(sql, params).catch((e) => {
|
||||
console.error("SQL Error:", e)
|
||||
return []
|
||||
})
|
||||
} else {
|
||||
// Otherwise, use the execute method
|
||||
rows = await sqlite.execute(sql, params).catch((e) => {
|
||||
rows = await dbCmd.execute(sql, params).catch((e) => {
|
||||
console.error("SQL Error:", e)
|
||||
return []
|
||||
})
|
||||
@ -48,7 +41,6 @@ export const db = drizzle<typeof schema>(
|
||||
|
||||
// If the method is "all", return all rows
|
||||
results = method === "all" ? rows : rows[0]
|
||||
await sqlite.close()
|
||||
return { rows: results }
|
||||
},
|
||||
// Pass the schema to the drizzle instance
|
||||
|
@ -5,6 +5,7 @@
|
||||
import { systemCommands, systemCommandsFiltered } from "@/cmds/system"
|
||||
import AppsCmds from "@/components/main/AppsCmds.svelte"
|
||||
import { i18n } from "@/i18n"
|
||||
import { db } from "@/orm/database"
|
||||
import * as m from "@/paraglide/messages"
|
||||
import {
|
||||
appConfig,
|
||||
@ -110,6 +111,7 @@
|
||||
<Inspect name="devStoreExtCmds" value={$devStoreExtCmds} />
|
||||
<Inspect name="$appState.searchTerm" value={$appState.searchTerm} />
|
||||
-->
|
||||
|
||||
<Command.Root
|
||||
class={cn("h-screen rounded-lg shadow-md")}
|
||||
bind:value={$appState.highlightedCmd}
|
||||
|
@ -5,6 +5,34 @@ import { CmdType, Ext, ExtCmd, ExtData } from "../models/extension"
|
||||
import { convertDateToSqliteString, SearchMode, SearchModeEnum, SQLSortOrder } from "../models/sql"
|
||||
import { generateJarvisPluginCommand } from "./common"
|
||||
|
||||
export interface QueryResult {
|
||||
/** The number of rows affected by the query. */
|
||||
rowsAffected: number
|
||||
/**
|
||||
* The last inserted `id`.
|
||||
*
|
||||
* This value is not set for Postgres databases. If the
|
||||
* last inserted id is required on Postgres, the `select` function
|
||||
* must be used, with a `RETURNING` clause
|
||||
* (`INSERT INTO todos (title) VALUES ($1) RETURNING id`).
|
||||
*/
|
||||
lastInsertId?: number
|
||||
}
|
||||
|
||||
export function select(query: string, values: any[]) {
|
||||
return invoke<any[]>(generateJarvisPluginCommand("select"), {
|
||||
query,
|
||||
values
|
||||
})
|
||||
}
|
||||
|
||||
export function execute(query: string, values: any[]) {
|
||||
return invoke<QueryResult>(generateJarvisPluginCommand("execute"), {
|
||||
query,
|
||||
values
|
||||
})
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Extension CRUD */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
@ -4,7 +4,9 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
rusqlite = { version = "0.31.0", features = ["bundled-sqlcipher-vendored-openssl"] }
|
||||
rusqlite = { version = "0.31.0", features = [
|
||||
"bundled-sqlcipher-vendored-openssl",
|
||||
] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
tempfile = "3.10.1"
|
||||
|
@ -2,6 +2,7 @@ pub mod models;
|
||||
pub mod schema;
|
||||
use models::{CmdType, ExtDataField, ExtDataSearchQuery, SearchMode};
|
||||
use rusqlite::{params, params_from_iter, Connection, Result, ToSql};
|
||||
use serde_json::Value as JsonValue;
|
||||
use serde_json::{json, Value};
|
||||
use std::path::Path;
|
||||
|
||||
@ -122,6 +123,97 @@ impl JarvisDB {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn select(&self, query: String, values: Vec<JsonValue>) -> Result<Vec<JsonValue>> {
|
||||
println!("DB selecting: {}", query);
|
||||
println!("DB selecting values: {:?}", values);
|
||||
let mut stmt = self.conn.prepare(&query)?;
|
||||
|
||||
// Convert JsonValue parameters to appropriate types for rusqlite
|
||||
let mut params: Vec<Box<dyn ToSql>> = Vec::new();
|
||||
for value in values {
|
||||
if value.is_null() {
|
||||
params.push(Box::new(Option::<String>::None));
|
||||
} else if value.is_string() {
|
||||
params.push(Box::new(value.as_str().unwrap().to_owned()));
|
||||
} else if let Some(number) = value.as_number() {
|
||||
if number.is_i64() {
|
||||
params.push(Box::new(number.as_i64().unwrap()));
|
||||
} else if number.is_u64() {
|
||||
params.push(Box::new(number.as_u64().unwrap() as i64));
|
||||
} else {
|
||||
params.push(Box::new(number.as_f64().unwrap()));
|
||||
}
|
||||
} else {
|
||||
params.push(Box::new(value.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
// Get column names from statement
|
||||
let column_names: Vec<String> = (0..stmt.column_count())
|
||||
.map(|i| stmt.column_name(i).unwrap().to_string())
|
||||
.collect();
|
||||
|
||||
// Execute the query with the converted parameters and map results
|
||||
let rows = stmt.query_map(params_from_iter(params.iter().map(|p| p.as_ref())), |row| {
|
||||
let mut result = Vec::new();
|
||||
for i in 0..column_names.len() {
|
||||
let value: Value = match row.get_ref(i)? {
|
||||
rusqlite::types::ValueRef::Null => Value::Null,
|
||||
rusqlite::types::ValueRef::Integer(i) => Value::Number(i.into()),
|
||||
rusqlite::types::ValueRef::Real(r) => Value::Number(
|
||||
serde_json::Number::from_f64(r).unwrap_or(serde_json::Number::from(0)),
|
||||
),
|
||||
rusqlite::types::ValueRef::Text(t) => {
|
||||
Value::String(String::from_utf8_lossy(t).into_owned())
|
||||
}
|
||||
rusqlite::types::ValueRef::Blob(b) => {
|
||||
Value::String(String::from_utf8_lossy(b).into_owned())
|
||||
}
|
||||
};
|
||||
result.push(value);
|
||||
}
|
||||
Ok(Value::Array(result))
|
||||
})?;
|
||||
|
||||
let mut results = Vec::new();
|
||||
for row in rows {
|
||||
results.push(row?);
|
||||
}
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
pub fn execute(&self, query: &str, values: Vec<JsonValue>) -> Result<(u64, i64)> {
|
||||
let mut stmt = self.conn.prepare(query)?;
|
||||
|
||||
// Convert JsonValue parameters to appropriate types for rusqlite
|
||||
let mut params: Vec<Box<dyn ToSql>> = Vec::new();
|
||||
for value in values {
|
||||
if value.is_null() {
|
||||
params.push(Box::new(Option::<String>::None));
|
||||
} else if value.is_string() {
|
||||
params.push(Box::new(value.as_str().unwrap().to_owned()));
|
||||
} else if let Some(number) = value.as_number() {
|
||||
if number.is_i64() {
|
||||
params.push(Box::new(number.as_i64().unwrap()));
|
||||
} else if number.is_u64() {
|
||||
params.push(Box::new(number.as_u64().unwrap() as i64));
|
||||
} else {
|
||||
params.push(Box::new(number.as_f64().unwrap()));
|
||||
}
|
||||
} else {
|
||||
params.push(Box::new(value.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the query with the converted parameters
|
||||
let rows_affected = stmt.execute(params_from_iter(params.iter().map(|p| p.as_ref())))?;
|
||||
|
||||
// Get the last insert rowid
|
||||
let last_insert_id = self.conn.last_insert_rowid();
|
||||
|
||||
Ok((rows_affected as u64, last_insert_id))
|
||||
}
|
||||
|
||||
pub fn get_all_extensions(&self) -> Result<Vec<models::Ext>> {
|
||||
let mut stmt = self.conn.prepare(
|
||||
"SELECT ext_id, identifier, path, data, version, enabled, installed_at FROM extensions",
|
||||
@ -983,4 +1075,72 @@ mod tests {
|
||||
|
||||
fs::remove_file(&db_path).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_and_execute() {
|
||||
let dir = tempdir().unwrap();
|
||||
let db_path = dir.path().join("test.db");
|
||||
let db = JarvisDB::new(&db_path, None).unwrap();
|
||||
db.init().unwrap();
|
||||
|
||||
// Create a simple todo table
|
||||
db.execute(
|
||||
"CREATE TABLE todos (id INTEGER PRIMARY KEY, title TEXT, completed BOOLEAN)",
|
||||
vec![],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Test execute with INSERT
|
||||
let (rows_affected, last_id) = db
|
||||
.execute(
|
||||
"INSERT INTO todos (title, completed) VALUES (?1, ?2)",
|
||||
vec![json!("Buy groceries"), json!(false)],
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(rows_affected, 1);
|
||||
assert_eq!(last_id, 1);
|
||||
|
||||
// Test select with basic query
|
||||
let results = db
|
||||
.select(
|
||||
"SELECT title, completed FROM todos WHERE id = ?1".to_string(),
|
||||
vec![json!(1)],
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(results.len(), 1);
|
||||
assert_eq!(results[0]["title"], "Buy groceries");
|
||||
assert_eq!(results[0]["completed"], "false");
|
||||
|
||||
// Test execute with UPDATE
|
||||
let (rows_affected, _) = db
|
||||
.execute(
|
||||
"UPDATE todos SET completed = ?1 WHERE id = ?2",
|
||||
vec![json!(true), json!(1)],
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(rows_affected, 1);
|
||||
|
||||
// Verify the update with select
|
||||
let results = db
|
||||
.select(
|
||||
"SELECT completed FROM todos WHERE id = ?1".to_string(),
|
||||
vec![json!(1)],
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(results[0]["completed"], "true");
|
||||
|
||||
// Test execute with DELETE
|
||||
let (rows_affected, _) = db
|
||||
.execute("DELETE FROM todos WHERE id = ?1", vec![json!(1)])
|
||||
.unwrap();
|
||||
assert_eq!(rows_affected, 1);
|
||||
|
||||
// Verify the deletion
|
||||
let results = db
|
||||
.select("SELECT COUNT(*) as count FROM todos".to_string(), vec![])
|
||||
.unwrap();
|
||||
assert_eq!(results[0]["count"], 0);
|
||||
|
||||
fs::remove_file(&db_path).unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -98,6 +98,8 @@ const COMMANDS: &[&str] = &[
|
||||
"search_extension_data",
|
||||
"delete_extension_data_by_id",
|
||||
"update_extension_data_by_id",
|
||||
"select",
|
||||
"execute",
|
||||
/* -------------------------------- Clipboard ------------------------------- */
|
||||
"add_to_history",
|
||||
"get_history",
|
||||
|
@ -65,6 +65,8 @@ commands.allow = [
|
||||
"get_ext_label_map",
|
||||
"get_frontmost_app",
|
||||
# Database
|
||||
"select",
|
||||
"execute",
|
||||
"create_extension",
|
||||
"get_all_extensions",
|
||||
"get_unique_extension_by_identifier",
|
||||
|
@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-execute"
|
||||
description = "Enables the execute command without any pre-configured scope."
|
||||
commands.allow = ["execute"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-execute"
|
||||
description = "Denies the execute command without any pre-configured scope."
|
||||
commands.deny = ["execute"]
|
@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-select"
|
||||
description = "Enables the select command without any pre-configured scope."
|
||||
commands.allow = ["select"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-select"
|
||||
description = "Denies the select command without any pre-configured scope."
|
||||
commands.deny = ["select"]
|
@ -492,6 +492,32 @@ Denies the empty_trash command without any pre-configured scope.
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`jarvis:allow-execute`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the execute command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`jarvis:deny-execute`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the execute command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`jarvis:allow-file-search`
|
||||
|
||||
</td>
|
||||
@ -1610,6 +1636,32 @@ Denies the search_extension_data command without any pre-configured scope.
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`jarvis:allow-select`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the select command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`jarvis:deny-select`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the select command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`jarvis:allow-server-is-running`
|
||||
|
||||
</td>
|
||||
|
@ -479,6 +479,16 @@
|
||||
"type": "string",
|
||||
"const": "deny-empty-trash"
|
||||
},
|
||||
{
|
||||
"description": "Enables the execute command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-execute"
|
||||
},
|
||||
{
|
||||
"description": "Denies the execute command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-execute"
|
||||
},
|
||||
{
|
||||
"description": "Enables the file_search command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@ -909,6 +919,16 @@
|
||||
"type": "string",
|
||||
"const": "deny-search-extension-data"
|
||||
},
|
||||
{
|
||||
"description": "Enables the select command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-select"
|
||||
},
|
||||
{
|
||||
"description": "Denies the select command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-select"
|
||||
},
|
||||
{
|
||||
"description": "Enables the server_is_running command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
|
@ -2,6 +2,7 @@ use db::{
|
||||
models::{Cmd, CmdType, Ext, ExtData, ExtDataField, ExtDataSearchQuery, SQLSortOrder},
|
||||
JarvisDB,
|
||||
};
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
use std::{path::PathBuf, sync::Mutex};
|
||||
use tauri::State;
|
||||
|
||||
@ -245,3 +246,31 @@ pub async fn update_extension_data_by_id(
|
||||
.update_extension_data_by_id(data_id, data, search_text)
|
||||
.map_err(|err| err.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn select(
|
||||
db: State<'_, DBState>,
|
||||
query: &str,
|
||||
values: Vec<JsonValue>,
|
||||
) -> Result<Vec<JsonValue>, String> {
|
||||
db.db
|
||||
.lock()
|
||||
.unwrap()
|
||||
.select(query.to_string(), values)
|
||||
.map_err(|err| err.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn execute(
|
||||
db: State<'_, DBState>,
|
||||
query: &str,
|
||||
values: Vec<JsonValue>,
|
||||
) -> Result<Vec<JsonValue>, String> {
|
||||
let (rows_affected, last_id) = db
|
||||
.db
|
||||
.lock()
|
||||
.unwrap()
|
||||
.execute(query, values)
|
||||
.map_err(|err| err.to_string())?;
|
||||
Ok(vec![json!([rows_affected, last_id])])
|
||||
}
|
||||
|
@ -140,6 +140,9 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
// commands::storage::ext_store_wrapper_load,
|
||||
// commands::storage::ext_store_wrapper_save,
|
||||
/* -------------------------------- database -------------------------------- */
|
||||
commands::db::select,
|
||||
commands::db::execute,
|
||||
commands::db::create_extension,
|
||||
commands::db::create_extension,
|
||||
commands::db::get_all_extensions,
|
||||
commands::db::get_unique_extension_by_identifier,
|
||||
|
Loading…
x
Reference in New Issue
Block a user