Skip to content

[Phase 2] Foreign key composite aggregation (STRING_AGG + FOR XML PATH fallback) #146

@debba

Description

@debba

[Phase 2] Foreign key composite aggregation (STRING_AGG + FOR XML PATH fallback)

Part of the SQL Server Phase 2 epic #150.

The driver isn't on main. Code lives on feat/sql-server and isn't in any release. Check that branch out, branch off it, target your PR at it — not at main. The whole thing squashes into main when Phase 2 closes (#149).

Phase 1 emits one ForeignKey row per FK column (same shape MySQL/Postgres drivers use). For composite FKs, the frontend SchemaDiagram.tsx needs to know which columns belong together — that's Phase 2's aggregated form.

Task

1. Evolve the model. In src-tauri/src/models.rs::ForeignKey, add two Vec<String> fields (backward-compatible: empty vec when missing from JSON):

#[derive(Debug, Serialize, Deserialize)]
pub struct ForeignKey {
    pub name: String,
    // Legacy single-column (keep populated — MySQL/Postgres/SQLite still emit these)
    pub column_name: String,
    pub ref_table: String,
    pub ref_column: String,
    pub on_delete: Option<String>,
    pub on_update: Option<String>,
    // Phase 2: composite support (populated only by drivers that aggregate)
    #[serde(default)] pub columns: Vec<String>,
    #[serde(default)] pub ref_columns: Vec<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub ref_schema: Option<String>,
}

Add helper methods local_columns() / referenced_columns() that fall back to the legacy single-column form when columns is empty. Pure and trivially testable.

2. Rewrite Q_GET_FOREIGN_KEYS. In src-tauri/src/drivers/sqlserver/introspection.rs, replace the current INFORMATION_SCHEMA-only query with:

  • SQL Server 2017+: use sys.foreign_keys + sys.foreign_key_columns with STRING_AGG(col, ',') WITHIN GROUP (ORDER BY constraint_column_id) to aggregate columns per constraint in a single row
  • SQL Server 2012–2016: fallback query using FOR XML PATH('') (subquery with STUFF(...) pattern)

Gate the choice on drivers/sqlserver/version::ServerVersion::supports_string_agg.

3. Populate both shapes. Populate the legacy fields (first column of the constraint) and columns[] / ref_columns[]. This keeps any existing code path that reads column_name working.

4. Update the batch variant (Q_GET_ALL_FOREIGN_KEYS_BATCH) identically.

Rules

  • Co-located unit tests: query shape asserts for both the STRING_AGG and FOR XML PATH branches
  • Regression test: serde_json::from_str::<ForeignKey> on old JSON (no columns field) still deserializes
  • Unit test for local_columns() / referenced_columns() fallback

Reference

Metadata

Metadata

Assignees

No one assigned

    Labels

    help wantedExtra attention is neededsql-serverSQL Server driver

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions