# Phase 34.1: Close DDL Gaps vs Snowflake - Research

**Researched:** 2026-03-22
**Domain:** DuckDB extension DDL surface (parser hooks, table functions, catalog mutation)
**Confidence:** HIGH

## Summary

This phase adds new DDL statements to close the gap between this extension's DDL surface and Snowflake's semantic view SQL reference. The new statements are: ALTER SEMANTIC VIEW (RENAME TO only), SHOW SEMANTIC DIMENSIONS, SHOW SEMANTIC METRICS, SHOW SEMANTIC FACTS, and SHOW SEMANTIC DIMENSIONS FOR METRIC.

A critical finding from researching the Snowflake docs: **Snowflake's ALTER SEMANTIC VIEW only supports RENAME TO and SET/UNSET COMMENT** -- it does NOT support granular add/drop/rename of dimensions, metrics, relationships, tables, or facts. Snowflake's documented approach for modifying sub-entities is to use `CREATE OR REPLACE SEMANTIC VIEW` instead. The phase description mentioned "add/drop/rename dimensions, metrics, relationships, tables, facts" but this does not match Snowflake's actual ALTER behavior. The implementation should match Snowflake's actual scope: RENAME TO (and optionally SET/UNSET COMMENT, though comments are not yet a first-class concept in this extension).

The existing codebase has a well-established pattern for adding new DDL verbs: (1) add a new `DdlKind` variant in `parse.rs`, (2) add prefix detection in `detect_ddl_prefix`, (3) add rewrite logic in `rewrite_ddl`/`validate_and_rewrite`, (4) create a new VTab implementation in `src/ddl/`, (5) register the table function in `lib.rs` `init_extension`, and (6) update the DDL_PREFIXES array for near-miss detection. The SHOW commands additionally follow the ListSemanticViewsVTab pattern (multi-row result, catalog-derived data).

**Primary recommendation:** Implement 6 new DDL forms following the established parser-hook + table-function pattern. ALTER SEMANTIC VIEW should support RENAME TO only (matching Snowflake). The four SHOW commands should return structured tabular results by deserializing the stored JSON definition. SHOW SEMANTIC DIMENSIONS FOR METRIC needs graph traversal to determine reachable dimensions.

## Standard Stack

### Core
| Library | Version | Purpose | Why Standard |
|---------|---------|---------|--------------|
| serde_json | (existing) | Deserialize stored definitions for SHOW commands | Already used throughout; definitions stored as JSON |
| strsim | (existing) | Near-miss detection for new DDL prefixes | Already used in parse.rs and body_parser.rs |
| duckdb (Rust crate) | (existing) | VTab trait for table function implementations | Core extension API |
| libduckdb_sys | (existing) | FFI for persist_conn operations | ALTER needs catalog persistence |

### Supporting
No new libraries needed. All functionality is achievable with existing dependencies.

## Architecture Patterns

### Recommended Module Structure
```
src/
├── parse.rs          # Add DdlKind variants, prefix detection, rewrite logic
├── ddl/
│   ├── mod.rs        # Add new module declarations
│   ├── alter.rs      # NEW: AlterSemanticViewVTab (RENAME TO)
│   ├── show_dims.rs  # NEW: ShowSemanticDimensionsVTab
│   ├── show_metrics.rs # NEW: ShowSemanticMetricsVTab
│   ├── show_facts.rs # NEW: ShowSemanticFactsVTab
│   ├── show_dims_for_metric.rs # NEW: ShowDimensionsForMetricVTab
│   ├── define.rs     # Existing (no changes)
│   ├── describe.rs   # Existing (no changes)
│   ├── drop.rs       # Existing (no changes)
│   └── list.rs       # Existing (no changes)
├── catalog.rs        # Add catalog_rename function
├── lib.rs            # Register new table functions in init_extension
└── ...
```

### Pattern 1: Adding a New DDL Verb (Established Pattern)

**What:** Every DDL verb follows the same 6-step pipeline:
1. `DdlKind` variant in `parse.rs` enum
2. Prefix detection in `detect_ddl_prefix` (longest-first ordering)
3. Rewrite logic in `rewrite_ddl` / `validate_and_rewrite`
4. VTab implementation in `src/ddl/`
5. Table function registration in `lib.rs` `init_extension`
6. DDL_PREFIXES entry for near-miss detection

**When to use:** Every new DDL statement must follow this pattern.

**Example (from existing SHOW SEMANTIC VIEWS):**
```rust
// parse.rs: detect_ddl_prefix
if let Some(n) = match_keyword_prefix(b, &[b"show", b"semantic", b"views"]) {
    return Some((DdlKind::Show, n));
}

// parse.rs: rewrite_ddl
DdlKind::Show => Ok(format!("SELECT * FROM {fn_name}()")),

// lib.rs: init_extension
con.register_table_function_with_extra_info::<ListSemanticViewsVTab, _>(
    "list_semantic_views",
    &catalog_state,
)?;
```

### Pattern 2: Multi-Row VTab (for SHOW commands)

**What:** A table function that returns multiple rows from the catalog. Uses `Vec<RowType>` in BindData, populated at bind time from the deserialized definition.
**When to use:** All four SHOW commands.

**Example (from existing ListSemanticViewsVTab):**
```rust
pub struct ListBindData {
    rows: Vec<(String, String)>,  // Populated at bind time
}

impl VTab for ListSemanticViewsVTab {
    fn bind(bind: &BindInfo) -> Result<Self::BindData, Box<dyn std::error::Error>> {
        bind.add_result_column("name", LogicalTypeHandle::from(LogicalTypeId::Varchar));
        // ... read catalog, collect rows ...
        Ok(ListBindData { rows })
    }

    fn func(func: &TableFunctionInfo<Self>, output: &mut DataChunkHandle)
        -> Result<(), Box<dyn std::error::Error>> {
        // ... emit all rows in one chunk ...
        output.set_len(n);
        Ok(())
    }
}
```

### Pattern 3: Catalog Mutation (for ALTER RENAME)

**What:** ALTER RENAME needs to (1) remove the old entry and insert the new one in the in-memory HashMap, and (2) persist the change via persist_conn (UPDATE or DELETE+INSERT on `semantic_layer._definitions`).
**When to use:** ALTER SEMANTIC VIEW ... RENAME TO ...

**Key constraint:** Follow the existing write-first pattern: persist to DuckDB table first, then update the in-memory catalog. This matches `drop.rs` and `define.rs`.

```rust
// catalog.rs: new function
pub fn catalog_rename(
    state: &CatalogState,
    old_name: &str,
    new_name: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    let mut guard = state.write().unwrap();
    let json = guard.remove(old_name)
        .ok_or_else(|| format!("semantic view '{}' does not exist", old_name))?;
    if guard.contains_key(new_name) {
        // Rollback: put the old entry back
        guard.insert(old_name.to_string(), json);
        return Err(format!("semantic view '{}' already exists", new_name).into());
    }
    guard.insert(new_name.to_string(), json);
    Ok(())
}
```

### Pattern 4: SHOW SEMANTIC DIMENSIONS FOR METRIC (Graph Traversal)

**What:** For a given metric, determine which dimensions are reachable through the relationship graph. A dimension is reachable from a metric if its source table is connected to the metric's source table through the relationship tree.
**When to use:** SHOW SEMANTIC DIMENSIONS ... FOR METRIC ...

**Algorithm:**
1. Deserialize the definition, build the RelationshipGraph
2. Find the metric's source table(s) -- handle derived metrics by walking dependencies
3. For each dimension, check if its source table shares a valid join path with the metric's table
4. A dimension is reachable if its source table is an ancestor or descendant of the metric's source table in the tree (i.e., there exists a path without a fan trap)
5. Dimensions from the base table (no source_table) are always reachable

**Important:** This mirrors the logic in `expand.rs` that validates which dimension+metric combinations are legal at query time. The SHOW command should preview what `semantic_view()` would accept.

### Pattern 5: DDL Prefix Detection (Parse Order)

**What:** New prefixes must be inserted in longest-first order in `detect_ddl_prefix` to prevent prefix overlap.
**When to use:** Adding any new DDL statement prefix.

The new prefixes, in longest-first order:
1. `SHOW SEMANTIC DIMENSIONS FOR METRIC` (5 keywords) -- before SHOW SEMANTIC DIMENSIONS
2. `SHOW SEMANTIC DIMENSIONS IN` or just `SHOW SEMANTIC DIMENSIONS` (3 keywords)
3. `SHOW SEMANTIC METRICS` (3 keywords)
4. `SHOW SEMANTIC FACTS` (3 keywords)
5. `ALTER SEMANTIC VIEW IF EXISTS` (5 keywords) -- before ALTER SEMANTIC VIEW
6. `ALTER SEMANTIC VIEW` (3 keywords)

Note: The existing `SHOW SEMANTIC VIEWS` (plural) must not conflict with `SHOW SEMANTIC DIMENSIONS/METRICS/FACTS`. Since the 3rd keyword differs (`VIEWS` vs `DIMENSIONS/METRICS/FACTS`), there is no overlap -- longest-first ordering within each group handles it.

### Anti-Patterns to Avoid
- **Sharing DdlKind variants for semantically different operations:** Each SHOW command must have its own DdlKind variant (e.g., ShowDimensions, ShowMetrics, ShowFacts, ShowDimensionsForMetric). Do not try to multiplex through a single variant.
- **Running SQL from within bind:** Use persist_conn for mutations, never the main connection. The main connection holds an execution lock during bind.
- **Mixing SHOW SEMANTIC VIEWS (plural) and SHOW SEMANTIC DIMENSIONS (singular):** These are different prefixes with the same 2-word start. Prefix detection handles them naturally since the 3rd word differs.

## Don't Hand-Roll

| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| Graph reachability for SHOW DIMS FOR METRIC | Custom BFS/DFS | Existing `RelationshipGraph` + `ancestors_to_root` in expand.rs | Already handles role-playing dims, fan trap detection, parent_map construction |
| JSON deserialization | Manual parsing | `SemanticViewDefinition::from_json()` | Handles all backward compat fields, serde defaults |
| SQL identifier quoting | String concatenation | `expand::quote_ident()` | Handles embedded double quotes per SQL standard |
| Near-miss suggestions | Custom Levenshtein | `strsim::levenshtein` + existing suggest pattern | Already used in parse.rs and body_parser.rs |

## Common Pitfalls

### Pitfall 1: DdlKind Prefix Overlap Between SHOW SEMANTIC VIEWS and New SHOW Commands
**What goes wrong:** If prefix detection is not ordered correctly, `SHOW SEMANTIC DIMENSIONS` could match as `SHOW SEMANTIC` and then fail.
**Why it happens:** All SHOW SEMANTIC commands share the first two keywords.
**How to avoid:** The third keyword differs for each (`VIEWS`, `DIMENSIONS`, `METRICS`, `FACTS`). The existing `match_keyword_prefix` function matches the full keyword sequence, so there is no partial-match risk. Just ensure longest-first ordering within each group: `SHOW SEMANTIC DIMENSIONS FOR METRIC` (5 tokens) before `SHOW SEMANTIC DIMENSIONS` (3 tokens).
**Warning signs:** Tests where `SHOW SEMANTIC DIMENSIONS` is detected as `SHOW SEMANTIC VIEWS`.

### Pitfall 2: ALTER RENAME Must Update Both In-Memory Cache AND Persistent Table
**What goes wrong:** Renaming only in the HashMap means the old name reappears after restart (from the DuckDB table). Renaming only in the DuckDB table means the old name persists until restart.
**Why it happens:** The two-tier catalog (in-memory HashMap + DuckDB table) requires coordinated updates.
**How to avoid:** Follow the existing write-first pattern: persist_conn DELETE+INSERT first, then in-memory HashMap remove+insert. Use a new `persist_rename` function modeled on `persist_define` and `persist_drop`.
**Warning signs:** Restart test shows old name still present after RENAME.

### Pitfall 3: SHOW SEMANTIC DIMENSIONS FOR METRIC Must Handle Derived Metrics
**What goes wrong:** A derived metric (one whose `expr` references another metric) has no `source_table` of its own. Treating it as having no source table means no dimensions are shown.
**Why it happens:** Derived metrics inherit their source tables transitively via `collect_derived_metric_source_tables` in expand.rs.
**How to avoid:** Reuse or replicate the derived metric source table resolution from expand.rs.
**Warning signs:** SHOW DIMS FOR METRIC returns empty for a derived metric that clearly has reachable dimensions.

### Pitfall 4: Chunked Emission for Large Result Sets
**What goes wrong:** If a semantic view has many dimensions (100+), trying to emit them all in one chunk may exceed `STANDARD_VECTOR_SIZE` (2048).
**Why it happens:** Existing list.rs emits all rows in one func() call, which works for small sets.
**How to avoid:** For SHOW commands, use an offset counter in InitData (like `SvDdlGlobalState`) to emit in chunks. Or, since semantic views typically have dozens, not thousands, of entities, the single-chunk approach (like list.rs) is acceptable if you assert < 2048 rows.
**Warning signs:** Crash or truncation with a view having > 2048 dimensions.

### Pitfall 5: ALTER RENAME with IF EXISTS Must Be Silent on Missing View
**What goes wrong:** Without IF EXISTS handling, RENAME on a non-existent view throws an error.
**Why it happens:** Snowflake supports `ALTER SEMANTIC VIEW IF EXISTS name RENAME TO new_name`.
**How to avoid:** Check for IF EXISTS in the DdlKind variant and silently succeed (no-op) when the view doesn't exist. This matches the DROP IF EXISTS pattern.
**Warning signs:** Error thrown when using ALTER ... IF EXISTS on a non-existent view.

### Pitfall 6: FOR METRIC Parsing -- View Name and Metric Name Extraction
**What goes wrong:** `SHOW SEMANTIC DIMENSIONS IN view_name FOR METRIC metric_name` requires extracting two names from the statement.
**Why it happens:** This is more complex than existing name-only forms (which extract one name).
**How to avoid:** In `rewrite_ddl` / `validate_and_rewrite`, parse the `IN view_name FOR METRIC metric_name` clause by:
1. After `SHOW SEMANTIC DIMENSIONS`, check for `IN` keyword
2. Extract `view_name` (up to whitespace)
3. Expect `FOR METRIC` keywords
4. Extract `metric_name`
5. Rewrite to `SELECT * FROM show_semantic_dimensions_for_metric('view_name', 'metric_name')`
**Warning signs:** Parse error or wrong name extracted when view/metric names contain special characters.

## Code Examples

### Example 1: New DdlKind Variants
```rust
// parse.rs -- extend the enum
pub enum DdlKind {
    Create,
    CreateOrReplace,
    CreateIfNotExists,
    Drop,
    DropIfExists,
    Describe,
    Show,
    // New in Phase 34.1:
    AlterRename,
    AlterRenameIfExists,
    ShowDimensions,
    ShowMetrics,
    ShowFacts,
    ShowDimensionsForMetric,
}
```

### Example 2: New Prefix Detection (parse.rs detect_ddl_prefix)
```rust
// ALTER SEMANTIC VIEW IF EXISTS (5 keywords) -- before ALTER SEMANTIC VIEW
if let Some(n) = match_keyword_prefix(b, &[b"alter", b"semantic", b"view", b"if", b"exists"]) {
    return Some((DdlKind::AlterRenameIfExists, n));
}
// ALTER SEMANTIC VIEW (3 keywords)
if let Some(n) = match_keyword_prefix(b, &[b"alter", b"semantic", b"view"]) {
    return Some((DdlKind::AlterRename, n));
}
// SHOW SEMANTIC DIMENSIONS (3 keywords) -- note: FOR METRIC variant handled post-detection
if let Some(n) = match_keyword_prefix(b, &[b"show", b"semantic", b"dimensions"]) {
    return Some((DdlKind::ShowDimensions, n));
}
// SHOW SEMANTIC METRICS (3 keywords)
if let Some(n) = match_keyword_prefix(b, &[b"show", b"semantic", b"metrics"]) {
    return Some((DdlKind::ShowMetrics, n));
}
// SHOW SEMANTIC FACTS (3 keywords)
if let Some(n) = match_keyword_prefix(b, &[b"show", b"semantic", b"facts"]) {
    return Some((DdlKind::ShowFacts, n));
}
```

### Example 3: ALTER RENAME Rewrite Logic
```rust
// In validate_and_rewrite, after detecting AlterRename/AlterRenameIfExists:
fn rewrite_alter_rename(trimmed: &str, prefix_len: usize, if_exists: bool)
    -> Result<Option<String>, ParseError> {
    let after_prefix = trimmed[prefix_len..].trim();
    // Extract old_name
    let name_end = after_prefix.find(|c: char| c.is_whitespace()).unwrap_or(after_prefix.len());
    let old_name = &after_prefix[..name_end];
    let rest = after_prefix[name_end..].trim();
    // Expect "RENAME TO"
    let rest_upper = rest.to_ascii_uppercase();
    if !rest_upper.starts_with("RENAME TO") {
        return Err(ParseError {
            message: "Expected RENAME TO after view name".to_string(),
            position: None,
        });
    }
    let new_name = rest["RENAME TO".len()..].trim();
    let fn_name = if if_exists {
        "alter_semantic_view_rename_if_exists"
    } else {
        "alter_semantic_view_rename"
    };
    let safe_old = old_name.replace('\'', "''");
    let safe_new = new_name.replace('\'', "''");
    Ok(Some(format!("SELECT * FROM {fn_name}('{safe_old}', '{safe_new}')")))
}
```

### Example 4: ShowSemanticDimensionsVTab
```rust
pub struct ShowDimsBindData {
    rows: Vec<ShowDimRow>,
}
pub struct ShowDimRow {
    semantic_view_name: String,
    name: String,
    expr: String,
    source_table: String,
    output_type: String,
}

impl VTab for ShowSemanticDimensionsVTab {
    type BindData = ShowDimsBindData;
    type InitData = ShowDimsInitData;

    fn bind(bind: &BindInfo) -> Result<Self::BindData, Box<dyn std::error::Error>> {
        bind.add_result_column("semantic_view_name", LogicalTypeHandle::from(LogicalTypeId::Varchar));
        bind.add_result_column("name", LogicalTypeHandle::from(LogicalTypeId::Varchar));
        bind.add_result_column("expr", LogicalTypeHandle::from(LogicalTypeId::Varchar));
        bind.add_result_column("source_table", LogicalTypeHandle::from(LogicalTypeId::Varchar));
        bind.add_result_column("data_type", LogicalTypeHandle::from(LogicalTypeId::Varchar));

        let view_name = bind.get_parameter(0).to_string();
        let state_ptr = bind.get_extra_info::<CatalogState>();
        let guard = unsafe { (*state_ptr).read().expect("catalog RwLock poisoned") };
        let json_str = guard.get(&view_name)
            .ok_or_else(|| format!("semantic view '{}' does not exist", view_name))?;
        let def = SemanticViewDefinition::from_json(&view_name, json_str)?;

        let mut rows = Vec::new();
        for dim in &def.dimensions {
            rows.push(ShowDimRow {
                semantic_view_name: view_name.clone(),
                name: dim.name.clone(),
                expr: dim.expr.clone(),
                source_table: dim.source_table.clone().unwrap_or_default(),
                output_type: dim.output_type.clone().unwrap_or_default(),
            });
        }

        Ok(ShowDimsBindData { rows })
    }
}
```

### Example 5: SHOW SEMANTIC DIMENSIONS FOR METRIC (Reachability)
```rust
// Determine reachable dimensions for a given metric
fn reachable_dimensions(
    def: &SemanticViewDefinition,
    metric_name: &str,
) -> Result<Vec<&Dimension>, String> {
    let metric = def.metrics.iter()
        .find(|m| m.name.eq_ignore_ascii_case(metric_name))
        .ok_or_else(|| format!("unknown metric '{}'", metric_name))?;

    // Get metric's source tables (handles derived metrics)
    let met_tables: HashSet<String> = if let Some(ref st) = metric.source_table {
        [st.to_ascii_lowercase()].into_iter().collect()
    } else {
        collect_derived_metric_source_tables(metric, &def.metrics)
            .into_iter().map(|s| s.to_ascii_lowercase()).collect()
    };

    // Build graph for ancestor traversal
    let graph = RelationshipGraph::from_definition(def)?;
    let parent_map = build_parent_map(&graph);

    let mut result = Vec::new();
    for dim in &def.dimensions {
        let dim_table = dim.source_table.as_ref()
            .map(|s| s.to_ascii_lowercase())
            .unwrap_or_else(|| graph.root.clone());

        // A dimension is reachable if its table shares any ancestor with the metric's table,
        // or if it's on the base table (always reachable).
        let reachable = dim.source_table.is_none() || met_tables.iter().any(|mt| {
            let met_ancestors: HashSet<String> = ancestors_to_root(mt, &parent_map).into_iter().collect();
            let dim_ancestors: HashSet<String> = ancestors_to_root(&dim_table, &parent_map).into_iter().collect();
            // They share a common ancestor (they're in the same tree)
            met_ancestors.intersection(&dim_ancestors).next().is_some()
        });

        if reachable {
            result.push(dim);
        }
    }

    Ok(result)
}
```

## State of the Art

| Old Approach | Current Approach | When Changed | Impact |
|--------------|------------------|--------------|--------|
| Function-based DDL | Native DDL via parser hooks | v0.5.0 (Phase 15) | All new DDL goes through parser hook pipeline |
| Paren-body syntax | AS keyword body syntax | v0.5.2 (Phase 25) | CREATE bodies use TABLES(...) DIMENSIONS(...) etc. |
| Explicit cardinality | Inferred from PK/UNIQUE | v0.5.4 (Phase 33) | Cardinality keywords removed, FK/PK inference |

**Snowflake reference status (as of 2026-03-22):**
- ALTER SEMANTIC VIEW: Only RENAME TO and SET/UNSET COMMENT (no granular sub-entity modification)
- SHOW SEMANTIC DIMENSIONS: Returns 8 columns with LIKE/IN/STARTS WITH/LIMIT filtering
- SHOW SEMANTIC METRICS: Same 8 columns, same filtering
- SHOW SEMANTIC FACTS: Same 8 columns, same filtering
- SHOW SEMANTIC DIMENSIONS FOR METRIC: Returns 6 columns including `required` flag

**Our simplified mapping (no multi-database/schema support):**
- Skip: database_name, schema_name, synonyms, comment (not applicable to our single-DB extension model)
- Include: semantic_view_name, name, expr, source_table, data_type (mapped from output_type)

## Open Questions

1. **Scope of ALTER SEMANTIC VIEW**
   - What we know: Snowflake only supports RENAME TO and SET/UNSET COMMENT. The phase description mentions granular add/drop/rename of sub-entities, which Snowflake does not support.
   - What's unclear: Does the user want to go beyond Snowflake's scope, or match it?
   - Recommendation: Match Snowflake's scope (RENAME TO only). Sub-entity modification is already handled by CREATE OR REPLACE. This keeps the scope tight and the implementation clean. If granular ALTER is desired later, it can be a separate phase.

2. **SHOW commands: single-view vs cross-view scope**
   - What we know: Snowflake supports IN ACCOUNT/DATABASE/SCHEMA for cross-view listing. Our extension has no multi-database concept.
   - What's unclear: Should we support SHOW SEMANTIC DIMENSIONS (without IN view_name) to list all dimensions across all views?
   - Recommendation: Support two forms: (a) `SHOW SEMANTIC DIMENSIONS IN view_name` (required) for single-view scope, and (b) `SHOW SEMANTIC DIMENSIONS` (no IN clause) to list all dimensions across all views. This matches Snowflake's behavior where omitting the scope shows all accessible dimensions.

3. **FOR METRIC reachability: Fan trap filtering**
   - What we know: Snowflake's SHOW SEMANTIC DIMENSIONS FOR METRIC shows dimensions "related to the logical table for the metric". The docs state the dimension table must have "equal or lower granularity" than the metric table.
   - What's unclear: Should we exclude dimensions that would cause a fan trap at query time?
   - Recommendation: Include all graph-reachable dimensions but do NOT filter by fan trap. The SHOW command is informational; fan trap detection belongs at query time. This matches Snowflake's semantics.

## Validation Architecture

### Test Framework
| Property | Value |
|----------|-------|
| Framework | sqllogictest + cargo test |
| Config file | Justfile recipes |
| Quick run command | `cargo test` |
| Full suite command | `just test-all` |

### Phase Requirements -> Test Map

This phase does not have formal requirement IDs in REQUIREMENTS.md (it was inserted as an urgent addition after Phase 34). Tests should cover each new DDL form end-to-end.

| Behavior | Test Type | Automated Command | File Exists? |
|----------|-----------|-------------------|-------------|
| ALTER SEMANTIC VIEW name RENAME TO new_name | sqllogictest + unit | `just test-sql` | Wave 0 |
| ALTER SEMANTIC VIEW IF EXISTS (missing) RENAME TO | sqllogictest | `just test-sql` | Wave 0 |
| ALTER RENAME persists across restart | sqllogictest | `just test-sql` | Wave 0 |
| ALTER RENAME to existing name errors | unit | `cargo test` | Wave 0 |
| SHOW SEMANTIC DIMENSIONS IN view | sqllogictest + unit | `just test-all` | Wave 0 |
| SHOW SEMANTIC DIMENSIONS (all views) | sqllogictest | `just test-sql` | Wave 0 |
| SHOW SEMANTIC METRICS IN view | sqllogictest + unit | `just test-all` | Wave 0 |
| SHOW SEMANTIC FACTS IN view | sqllogictest + unit | `just test-all` | Wave 0 |
| SHOW SEMANTIC DIMENSIONS IN view FOR METRIC name | sqllogictest + unit | `just test-all` | Wave 0 |
| SHOW DIMS FOR METRIC with derived metric | unit | `cargo test` | Wave 0 |
| DDL prefix detection for all new forms | unit | `cargo test` | Wave 0 |
| Near-miss detection for new DDL forms | unit | `cargo test` | Wave 0 |

### Sampling Rate
- **Per task commit:** `cargo test`
- **Per wave merge:** `just test-all`
- **Phase gate:** Full suite green before verify

### Wave 0 Gaps
- [ ] `test/sql/phase34_1_alter_rename.test` -- ALTER RENAME sqllogictest
- [ ] `test/sql/phase34_1_show_commands.test` -- SHOW DIMENSIONS/METRICS/FACTS sqllogictest
- [ ] `test/sql/phase34_1_show_dims_for_metric.test` -- SHOW DIMS FOR METRIC sqllogictest
- [ ] Unit tests in new modules: `ddl/alter.rs`, `ddl/show_dims.rs`, `ddl/show_metrics.rs`, `ddl/show_facts.rs`, `ddl/show_dims_for_metric.rs`
- [ ] Parse detection tests in `parse.rs` for all new DdlKind variants

## Sources

### Primary (HIGH confidence)
- Snowflake ALTER SEMANTIC VIEW docs: https://docs.snowflake.com/en/sql-reference/sql/alter-semantic-view -- confirms only RENAME TO and SET/UNSET COMMENT are supported
- Snowflake SHOW SEMANTIC DIMENSIONS docs: https://docs.snowflake.com/en/sql-reference/sql/show-semantic-dimensions -- 8-column output schema
- Snowflake SHOW SEMANTIC METRICS docs: https://docs.snowflake.com/en/sql-reference/sql/show-semantic-metrics -- 8-column output schema
- Snowflake SHOW SEMANTIC FACTS docs: https://docs.snowflake.com/en/sql-reference/sql/show-semantic-facts -- 8-column output schema
- Snowflake SHOW SEMANTIC DIMENSIONS FOR METRIC docs: https://docs.snowflake.com/en/sql-reference/sql/show-semantic-dimensions-for-metric -- 6-column output, reachability constraint
- Snowflake SQL management guide: https://docs.snowflake.com/en/user-guide/views-semantic/sql -- confirms CREATE OR REPLACE for modifications
- Codebase analysis: `src/parse.rs`, `src/ddl/*.rs`, `src/catalog.rs`, `src/lib.rs`, `src/model.rs`, `src/graph.rs`, `src/expand.rs`, `cpp/src/shim.cpp`

### Secondary (MEDIUM confidence)
- None

### Tertiary (LOW confidence)
- None

## Metadata

**Confidence breakdown:**
- Standard stack: HIGH - no new libraries needed, all patterns established in codebase
- Architecture: HIGH - follows 6 existing DDL verb implementations exactly
- Pitfalls: HIGH - identified from codebase analysis and Snowflake doc review

**Research date:** 2026-03-22
**Valid until:** 2026-04-22 (stable domain, no external dependencies changing)
