From 930c906ae116df11f06fe0bfb5a4433f7ab8e506 Mon Sep 17 00:00:00 2001 From: xpain Date: Tue, 25 Aug 2026 10:36:49 +0200 Subject: [PATCH 1/2] fix(mssql): bracket-quote table and column identifiers Table and column names were interpolated raw into count and paging queries. A table named after a T-SQL reserved word (User, Order, Group, ...) broke the overview, tables list, table page and table data endpoints with 'Incorrect syntax near the keyword'. Add quote_ident that wraps identifiers in [] and escapes ] and use it at every interpolation site in the mssql module. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_014KHtEx6XUzzfa8QHsEUP9p --- src/main.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 50e21ec..eb1d5fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4386,6 +4386,10 @@ mod mssql { query_timeout: Duration, } + fn quote_ident(name: &str) -> String { + format!("[{}]", name.replace(']', "]]")) + } + impl Db { pub async fn open(connection: String, query_timeout: Duration) -> color_eyre::Result { use tokio_util::compat::TokioAsyncWriteCompatExt; @@ -4550,7 +4554,7 @@ mod mssql { .await; for count in row_counts.iter_mut() { - let sql = format!("SELECT count(*) AS count FROM {}", count.name); + let sql = format!("SELECT count(*) AS count FROM {}", quote_ident(&count.name)); count.count = client .query(sql, &[]) @@ -4696,7 +4700,7 @@ mod mssql { .await; for count in tables.iter_mut() { - let sql = format!("SELECT count(*) AS count FROM {}", count.name); + let sql = format!("SELECT count(*) AS count FROM {}", quote_ident(&count.name)); count.count = client .query(sql, &[]) @@ -4716,7 +4720,10 @@ mod mssql { let mut client = self.client.lock().await; let row_count: i32 = client - .query(format!("SELECT count(*) AS count FROM {name}"), &[]) + .query( + format!("SELECT count(*) AS count FROM {}", quote_ident(&name)), + &[], + ) .await? .into_row() .await? @@ -4812,10 +4819,12 @@ mod mssql { let offset = (page - 1) * ROWS_PER_PAGE; let sql = format!( r#" - SELECT * FROM "{name}" - ORDER BY {first_column} + SELECT * FROM {} + ORDER BY {} OFFSET {offset} ROWS FETCH NEXT {ROWS_PER_PAGE} ROWS ONLY; - "# + "#, + quote_ident(&name), + quote_ident(&first_column), ); let mut query = client.query(sql, &[]).await?; From 1846755d124eaa5fae2a5dc72fad78f1745c2048 Mon Sep 17 00:00:00 2001 From: xpain Date: Tue, 25 Aug 2026 10:56:31 +0200 Subject: [PATCH 2/2] fix(mysql,sqlite,libsql): quote identifiers in count and paging queries Same class of bug as the mssql fix: mysql interpolated table names raw into count(*) and SELECT * queries, and sqlite/libsql left the ORDER BY column unquoted. A table or first column named after a reserved word (order, group, key, ...) broke those pages. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_014KHtEx6XUzzfa8QHsEUP9p --- src/main.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index eb1d5fa..65503df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -806,8 +806,8 @@ mod sqlite { let mut stmt = match conn.prepare(&format!( r#" SELECT * - FROM '{name}' - ORDER BY {first_column} + FROM "{name}" + ORDER BY "{first_column}" LIMIT {ROWS_PER_PAGE} OFFSET {offset} "# @@ -1392,8 +1392,8 @@ mod libsql { &format!( r#" SELECT * - FROM '{name}' - ORDER BY {first_column} + FROM "{name}" + ORDER BY "{first_column}" LIMIT {ROWS_PER_PAGE} OFFSET {offset} "#, @@ -2233,6 +2233,10 @@ mod mysql { query_timeout: Duration, } + fn quote_ident(name: &str) -> String { + format!("`{}`", name.replace('`', "``")) + } + impl Db { pub async fn open(url: String, query_timeout: Duration) -> color_eyre::Result { let pool = Pool::from_url(&url)?; @@ -2343,7 +2347,7 @@ mod mysql { .await?; for count in row_counts.iter_mut() { - count.count = format!("SELECT count(*) AS count FROM {}", count.name) + count.count = format!("SELECT count(*) AS count FROM {}", quote_ident(&count.name)) .with(()) .first(&mut conn) .await? @@ -2434,7 +2438,7 @@ mod mysql { .await?; for table in tables.iter_mut() { - table.count = format!("SELECT count(*) AS count FROM {}", table.name) + table.count = format!("SELECT count(*) AS count FROM {}", quote_ident(&table.name)) .with(()) .first(&mut conn) .await? @@ -2457,7 +2461,7 @@ mod mysql { .map(|(_, sql): (String, String)| sql) .ok_or_eyre("couldn't get table sql")?; - let row_count = format!("SELECT count(*) AS count FROM {name}") + let row_count = format!("SELECT count(*) AS count FROM {}", quote_ident(&name)) .with(()) .first(&mut conn) .await? @@ -2536,11 +2540,13 @@ mod mysql { let offset = (page - 1) * ROWS_PER_PAGE; let sql = format!( r#" - SELECT * FROM {name} - ORDER BY {first_column} + SELECT * FROM {} + ORDER BY {} LIMIT {ROWS_PER_PAGE} OFFSET {offset} - "# + "#, + quote_ident(&name), + quote_ident(&first_column), ); let stmt = conn.prep(&sql).await?;