Skip to content

Commit ba0bf98

Browse files
committed
core: DDL builders + IndexInfo / ForeignKeyInfo (DDL UI #1)
Lay the core SQL-generation foundation for the upcoming Structure workspace tab: - crates/core/src/query.rs: IndexInfo + ForeignKeyInfo structs carrying secondary-index and FK metadata (with primary flag on the implicit PK index so the UI can render it as read-only). - crates/core/src/connection.rs: extend the Connection trait with fetch_indexes() and fetch_foreign_keys() default impls returning empty Vec, so existing drivers compile before they're filled in. - crates/core/src/read_only.rs: forward the two new reads to the inner connection (read-only blocks mutations, not introspection). - crates/core/src/sql_ddl.rs: the full CREATE / DROP / ALTER DDL surface across MySQL + Postgres + SQLite — build_create_table, build_drop_table, build_rename_table, build_add_column, build_drop_column, build_rename_column, build_alter_column, build_reorder_column (MySQL only), build_create_index, build_drop_index, build_add_foreign_key, build_drop_foreign_key. Dialect quirks captured: - Postgres auto-increment renders as SERIAL / BIGSERIAL / SMALLSERIAL based on the user-typed integer width. - MySQL ALTER COLUMN routes through MODIFY COLUMN which replaces the whole definition (single-call); Postgres emits per-attribute ALTER COLUMN ... TYPE / SET NOT NULL / SET DEFAULT. - SQLite limits surface as BuildDdlError::SqliteNotSupported for alter-type / alter-nullable / alter-default / drop-FK so the UI can disable the affordances rather than fall through to a runtime driver error. - SQLite FK constraints get a leading PRAGMA foreign_keys = ON emitted by build_create_table when the columns list contains FKs. 43 unit tests cover every builder × every driver × edge cases (composite PK, schema qualification, identifier quote-doubling, empty-name validation, default expressions, auto-increment routing).
1 parent 1b4094c commit ba0bf98

5 files changed

Lines changed: 1166 additions & 3 deletions

File tree

linux/crates/core/src/connection.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use async_trait::async_trait;
22
use secrecy::SecretString;
33

44
use crate::error::DriverError;
5-
use crate::query::{ColumnInfo, ExecResult, QueryResult, TableInfo, Value};
5+
use crate::query::{ColumnInfo, ExecResult, ForeignKeyInfo, IndexInfo, QueryResult, TableInfo, Value};
66

77
#[derive(Debug, Clone)]
88
pub struct ConnectOptions {
@@ -49,6 +49,22 @@ pub trait Connection: Send + Sync {
4949
/// changeset Save flow so all pending row inserts / updates /
5050
/// deletes commit atomically.
5151
async fn execute_in_transaction(&self, statements: &[(String, Vec<Value>)]) -> Result<Vec<u64>, DriverError>;
52+
/// Indexes defined on `table`. Implementations may include the
53+
/// implicit primary-key index with `primary = true` so the UI can
54+
/// render it as read-only. Default returns empty so existing
55+
/// drivers compile before they're filled in.
56+
async fn fetch_indexes(&self, _schema: Option<&str>, _table: &str) -> Result<Vec<IndexInfo>, DriverError> {
57+
Ok(Vec::new())
58+
}
59+
/// Foreign-key constraints declared on `table`. Default returns
60+
/// empty for the same reason as `fetch_indexes`.
61+
async fn fetch_foreign_keys(
62+
&self,
63+
_schema: Option<&str>,
64+
_table: &str,
65+
) -> Result<Vec<ForeignKeyInfo>, DriverError> {
66+
Ok(Vec::new())
67+
}
5268
async fn ping(&self) -> Result<(), DriverError>;
5369
async fn close(self: Box<Self>) -> Result<(), DriverError>;
5470
}

linux/crates/core/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ mod error;
44
mod query;
55
mod read_only;
66
mod registry;
7+
pub mod sql_ddl;
78
pub mod sql_dialect;
89

910
pub use connection::{ConnectOptions, Connection};
1011
pub use driver::DatabaseDriver;
1112
pub use error::DriverError;
12-
pub use query::{ColumnInfo, ExecResult, MAX_QUERY_ROWS, QueryResult, TableInfo, Value};
13+
pub use query::{ColumnInfo, ExecResult, ForeignKeyInfo, IndexInfo, MAX_QUERY_ROWS, QueryResult, TableInfo, Value};
1314
pub use read_only::ReadOnlyConnection;
1415
pub use registry::DriverRegistry;

linux/crates/core/src/query.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,35 @@ pub struct TableInfo {
99
pub name: String,
1010
}
1111

12+
/// Secondary-index metadata for a table. `primary` is set on the
13+
/// auto-PK index returned by the catalog query so the UI can render
14+
/// it as read-only (the PK is owned by the column definition, not
15+
/// by an editable index entry).
16+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17+
pub struct IndexInfo {
18+
pub name: String,
19+
pub columns: Vec<String>,
20+
pub unique: bool,
21+
pub primary: bool,
22+
}
23+
24+
/// Foreign-key constraint metadata. `on_delete` / `on_update` carry
25+
/// the referential action as a normalised SQL keyword string
26+
/// ("RESTRICT", "CASCADE", "SET NULL", "SET DEFAULT", "NO ACTION").
27+
/// `None` means the driver returned a value we don't recognise — the
28+
/// UI displays it as a dim-label "—" and the DDL builder omits the
29+
/// clause so the database picks its default.
30+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31+
pub struct ForeignKeyInfo {
32+
pub name: String,
33+
pub columns: Vec<String>,
34+
pub ref_schema: Option<String>,
35+
pub ref_table: String,
36+
pub ref_columns: Vec<String>,
37+
pub on_delete: Option<String>,
38+
pub on_update: Option<String>,
39+
}
40+
1241
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1342
pub struct ColumnInfo {
1443
pub name: String,

linux/crates/core/src/read_only.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use async_trait::async_trait;
22

33
use crate::connection::Connection;
44
use crate::error::DriverError;
5-
use crate::query::{ColumnInfo, ExecResult, QueryResult, TableInfo, Value};
5+
use crate::query::{ColumnInfo, ExecResult, ForeignKeyInfo, IndexInfo, QueryResult, TableInfo, Value};
66

77
pub struct ReadOnlyConnection {
88
inner: Box<dyn Connection>,
@@ -50,6 +50,14 @@ impl Connection for ReadOnlyConnection {
5050
Err(DriverError::ReadOnly)
5151
}
5252

53+
async fn fetch_indexes(&self, schema: Option<&str>, table: &str) -> Result<Vec<IndexInfo>, DriverError> {
54+
self.inner.fetch_indexes(schema, table).await
55+
}
56+
57+
async fn fetch_foreign_keys(&self, schema: Option<&str>, table: &str) -> Result<Vec<ForeignKeyInfo>, DriverError> {
58+
self.inner.fetch_foreign_keys(schema, table).await
59+
}
60+
5361
async fn ping(&self) -> Result<(), DriverError> {
5462
self.inner.ping().await
5563
}

0 commit comments

Comments
 (0)