Skip to content

Commit 65face9

Browse files
committed
feat: update popover, select, sheet, and tooltip components to use new state attributes for animations
test: add unit tests for useIsMobile hook and db-functions utilities feat: implement clipboard export functionality and enhance tauri API with createDatabase method fix: update useAppStore to support multiple databases and improve connection handling chore: set up testing environment with mocks for localStorage, sessionStorage, and other global objects style: adjust theme handling in useAppStore and ensure proper application of dark/light classes refactor: improve test utility functions for rendering components with providers
1 parent 479105b commit 65face9

57 files changed

Lines changed: 3651 additions & 659 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package-lock.json

Lines changed: 1137 additions & 45 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
{
22
"name": "db-connect",
33
"private": true,
4-
"version": "0.0.14",
4+
"version": "0.0.15",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",
88
"build": "tsc && vite build",
9+
"test": "vitest run",
10+
"test:watch": "vitest",
911
"preview": "vite preview",
1012
"tauri": "tauri",
1113
"prepare": "git config core.hooksPath .githooks && chmod +x .githooks/*"
@@ -82,13 +84,18 @@
8284
"devDependencies": {
8385
"@tailwindcss/vite": "^4.2.1",
8486
"@tauri-apps/cli": "^2",
87+
"@testing-library/jest-dom": "^6.9.1",
88+
"@testing-library/react": "^16.3.2",
89+
"@testing-library/user-event": "^14.6.1",
8590
"@types/node": "^25.5.0",
8691
"@types/react": "^19.1.8",
8792
"@types/react-dom": "^19.1.6",
8893
"@vitejs/plugin-react": "^4.6.0",
8994
"autoprefixer": "^10.4.27",
95+
"jsdom": "^29.0.2",
9096
"tailwindcss": "^4.2.1",
9197
"typescript": "~5.8.3",
92-
"vite": "^7.0.4"
98+
"vite": "^7.0.4",
99+
"vitest": "^4.1.5"
93100
}
94101
}

src-tauri/src/commands.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,16 @@ pub async fn get_databases(id: String) -> Result<Vec<String>, String> {
859859
driver.get_databases().await.map_err(|e| e.to_string())
860860
}
861861

862+
#[tauri::command]
863+
pub async fn create_database(id: String, name: String) -> Result<(), String> {
864+
let driver = REGISTRY
865+
.connections
866+
.get(&id)
867+
.ok_or_else(|| "Not connected".to_string())?;
868+
869+
driver.create_database(&name).await.map_err(|e| e.to_string())
870+
}
871+
862872
#[tauri::command]
863873
pub async fn get_tables(
864874
id: String,

src-tauri/src/db/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ pub trait DatabaseDriver: Send + Sync {
4545
page_size: u32,
4646
) -> Result<QueryResult>;
4747

48+
async fn create_database(&self, _name: &str) -> Result<()> {
49+
Err(anyhow::anyhow!("Create database not supported for this database type"))
50+
}
51+
4852
async fn dump_database(
4953
&self,
5054
_database: &str,

src-tauri/src/db/mongodb.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,20 @@ impl DatabaseDriver for MongoDriver {
188188
Ok(client.list_database_names(None, None).await?)
189189
}
190190

191+
async fn create_database(&self, name: &str) -> Result<()> {
192+
// MongoDB creates a database implicitly when a collection is inserted into.
193+
// We create a sentinel collection then drop it so the DB persists as empty.
194+
let client_lock = self.client.read().await;
195+
let client = client_lock
196+
.as_ref()
197+
.ok_or_else(|| anyhow!("Not connected"))?;
198+
199+
let db = client.database(name);
200+
// Creating a collection forces the database to materialise on disk.
201+
db.create_collection("_init", None).await?;
202+
Ok(())
203+
}
204+
191205
async fn get_schemas(&self, _database: &str) -> Result<Vec<String>> {
192206
Ok(vec!["collection".to_string()])
193207
}

src-tauri/src/db/mysql.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ impl DatabaseDriver for MySqlDriver {
7373
Ok(rows.iter().map(|r| r.get::<String, _>(0)).collect())
7474
}
7575

76+
async fn create_database(&self, name: &str) -> Result<()> {
77+
let pool_lock = self.pool.read().await;
78+
let pool = pool_lock.as_ref().ok_or_else(|| anyhow!("Not connected"))?;
79+
let sql = format!("CREATE DATABASE `{}`", name.replace('`', ""));
80+
sqlx::query(&sql).execute(pool).await?;
81+
Ok(())
82+
}
83+
7684
async fn get_schemas(&self, _database: &str) -> Result<Vec<String>> {
7785
Ok(vec!["default".to_string()])
7886
}

src-tauri/src/db/postgres.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ impl DatabaseDriver for PostgresDriver {
6969
Ok(rows.iter().map(|r| r.get::<String, _>(0)).collect())
7070
}
7171

72+
async fn create_database(&self, name: &str) -> Result<()> {
73+
let pool_lock = self.pool.read().await;
74+
let pool = pool_lock.as_ref().ok_or_else(|| anyhow!("Not connected"))?;
75+
// CREATE DATABASE cannot run inside a transaction; use a simple execute.
76+
let sql = format!("CREATE DATABASE \"{}\"", name.replace('"', ""));
77+
sqlx::query(&sql).execute(pool).await?;
78+
Ok(())
79+
}
80+
7281
async fn get_schemas(&self, _database: &str) -> Result<Vec<String>> {
7382
let pool_lock = self.pool.read().await;
7483
let pool = pool_lock.as_ref().ok_or_else(|| anyhow!("Not connected"))?;
@@ -323,6 +332,24 @@ impl DatabaseDriver for PostgresDriver {
323332
let pool = pool_lock.as_ref().ok_or_else(|| anyhow!("Not connected"))?;
324333

325334
let start = Instant::now();
335+
336+
// Multi-statement SQL (e.g. CREATE TABLE + COMMENT ON COLUMN) must use the
337+
// simple query protocol — prepared statements reject multiple commands.
338+
let stmt_count = query
339+
.split(';')
340+
.map(|s| s.trim())
341+
.filter(|s| !s.is_empty())
342+
.count();
343+
if stmt_count > 1 {
344+
sqlx::raw_sql(query).execute(pool).await?;
345+
let duration = start.elapsed().as_millis() as u64;
346+
return Ok(QueryResult {
347+
columns: vec![],
348+
rows: vec![],
349+
execution_time_ms: duration,
350+
});
351+
}
352+
326353
let rows = sqlx::query(query).fetch_all(pool).await?;
327354
let duration = start.elapsed().as_millis() as u64;
328355

src-tauri/src/db/sqlite.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,39 @@ impl DatabaseDriver for SqliteDriver {
4848
Ok(vec!["main".to_string()])
4949
}
5050

51+
async fn create_database(&self, name: &str) -> Result<()> {
52+
// SQLite "databases" are files. Create a new .db file next to the current one.
53+
let pool_lock = self.pool.read().await;
54+
let pool = pool_lock.as_ref().ok_or_else(|| anyhow!("Not connected"))?;
55+
56+
// Get the current file path from PRAGMA database_list
57+
let row = sqlx::query("PRAGMA database_list")
58+
.fetch_one(pool)
59+
.await?;
60+
let current_path: String = row.get(2);
61+
62+
let parent = std::path::Path::new(&current_path)
63+
.parent()
64+
.ok_or_else(|| anyhow!("Cannot determine parent directory of current SQLite file"))?;
65+
66+
let safe_name = name.replace(['/', '\\', ':', '*', '?', '"', '<', '>', '|'], "_");
67+
let new_path = parent.join(format!("{}.db", safe_name));
68+
69+
if new_path.exists() {
70+
return Err(anyhow!("A database file '{}' already exists", new_path.display()));
71+
}
72+
73+
// Opening (connecting) to a new path creates the file in SQLite
74+
let url = format!("sqlite:{}", new_path.to_string_lossy());
75+
let new_pool = sqlx::sqlite::SqlitePoolOptions::new()
76+
.max_connections(1)
77+
.connect(&url)
78+
.await?;
79+
new_pool.close().await;
80+
81+
Ok(())
82+
}
83+
5184
async fn get_schemas(&self, _database: &str) -> Result<Vec<String>> {
5285
Ok(vec!["main".to_string()])
5386
}

src-tauri/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub fn run() {
3434
commands::connect_database,
3535
commands::disconnect_database,
3636
commands::get_databases,
37+
commands::create_database,
3738
commands::get_tables,
3839
commands::execute_query,
3940
commands::ping_connection,

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "DB Connect",
4-
"version": "0.0.14",
4+
"version": "0.0.15",
55
"identifier": "com.rajeshwar.db-connect",
66
"build": {
77
"beforeDevCommand": "npm run dev",

0 commit comments

Comments
 (0)