---
phase: 34.1-close-ddl-gaps-vs-snowflake
plan: 02
type: execute
wave: 1
depends_on: []
files_modified:
  - src/parse.rs
  - src/ddl/mod.rs
  - src/ddl/show_dims.rs
  - src/ddl/show_metrics.rs
  - src/ddl/show_facts.rs
  - src/lib.rs
  - test/sql/phase34_1_show_commands.test
autonomous: true
requirements: [DDL-03, DDL-04, DDL-05, DDL-06]

must_haves:
  truths:
    - "SHOW SEMANTIC DIMENSIONS IN view_name lists all dimensions for that view"
    - "SHOW SEMANTIC DIMENSIONS (without IN) lists dimensions across all views"
    - "SHOW SEMANTIC METRICS IN view_name lists all metrics for that view"
    - "SHOW SEMANTIC METRICS (without IN) lists metrics across all views"
    - "SHOW SEMANTIC FACTS IN view_name lists all facts for that view"
    - "SHOW SEMANTIC FACTS (without IN) lists facts across all views"
    - "Each SHOW command returns structured columns: semantic_view_name, name, expr, source_table, data_type"
  artifacts:
    - path: "src/ddl/show_dims.rs"
      provides: "ShowSemanticDimensionsVTab"
      exports: ["ShowSemanticDimensionsVTab"]
    - path: "src/ddl/show_metrics.rs"
      provides: "ShowSemanticMetricsVTab"
      exports: ["ShowSemanticMetricsVTab"]
    - path: "src/ddl/show_facts.rs"
      provides: "ShowSemanticFactsVTab"
      exports: ["ShowSemanticFactsVTab"]
  key_links:
    - from: "src/parse.rs"
      to: "src/ddl/show_dims.rs"
      via: "rewrite to SELECT * FROM show_semantic_dimensions('view') or show_semantic_dimensions_all()"
      pattern: "show_semantic_dimensions"
    - from: "src/parse.rs"
      to: "src/ddl/show_metrics.rs"
      via: "rewrite to SELECT * FROM show_semantic_metrics"
      pattern: "show_semantic_metrics"
    - from: "src/parse.rs"
      to: "src/ddl/show_facts.rs"
      via: "rewrite to SELECT * FROM show_semantic_facts"
      pattern: "show_semantic_facts"
    - from: "src/lib.rs"
      to: "src/ddl/show_dims.rs"
      via: "register_table_function_with_extra_info"
      pattern: "ShowSemanticDimensionsVTab"
---

<objective>
Implement SHOW SEMANTIC DIMENSIONS, SHOW SEMANTIC METRICS, and SHOW SEMANTIC FACTS as new DDL statements. Each supports two forms: with IN view_name (single-view scope) and without IN (cross-view scope listing all entities across all views).

Purpose: Enables introspection of semantic view sub-entities (dimensions, metrics, facts) without needing DESCRIBE. Matches Snowflake's SHOW SEMANTIC DIMENSIONS/METRICS/FACTS behavior.
Output: Three new SHOW commands with both scoped and cross-view forms, sqllogictest coverage.
</objective>

<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/34.1-close-ddl-gaps-vs-snowflake-alter-semantic-view-show-semantic-dimensions-facts-metrics/34.1-RESEARCH.md

<interfaces>
<!-- Key types and contracts the executor needs. -->

From src/parse.rs:
```rust
pub enum DdlKind {
    Create, CreateOrReplace, CreateIfNotExists,
    Drop, DropIfExists, Describe, Show,
    // Plan 01 adds: AlterRename, AlterRenameIfExists
}

fn detect_ddl_prefix(trimmed: &str) -> Option<(DdlKind, usize)>;
fn match_keyword_prefix(input: &[u8], keywords: &[&[u8]]) -> Option<usize>;
fn function_name(kind: DdlKind) -> &'static str;
fn rewrite_ddl(query: &str) -> Result<String, String>;
pub fn validate_and_rewrite(query: &str) -> Result<Option<String>, ParseError>;
const DDL_PREFIXES: &[&str];
```

From src/catalog.rs:
```rust
pub type CatalogState = Arc<RwLock<HashMap<String, String>>>;
```

From src/model.rs:
```rust
pub struct Dimension {
    pub name: String,
    pub expr: String,
    pub source_table: Option<String>,
    pub output_type: Option<String>,
}
pub struct Metric {
    pub name: String,
    pub expr: String,
    pub source_table: Option<String>,
    pub output_type: Option<String>,
    pub using_relationships: Vec<String>,
}
pub struct Fact {
    pub name: String,
    pub expr: String,
    pub source_table: Option<String>,
}
pub struct SemanticViewDefinition {
    pub base_table: String,
    pub tables: Vec<TableRef>,
    pub dimensions: Vec<Dimension>,
    pub metrics: Vec<Metric>,
    pub facts: Vec<Fact>,
    // ...
}
impl SemanticViewDefinition {
    pub fn from_json(name: &str, json: &str) -> Result<Self, String>;
}
```

From src/ddl/list.rs (pattern for multi-row VTab with CatalogState):
```rust
pub struct ListBindData { rows: Vec<(String, String)> }
pub struct ListInitData { done: AtomicBool }
impl VTab for ListSemanticViewsVTab {
    fn bind(bind: &BindInfo) -> Result<Self::BindData, Box<dyn std::error::Error>> {
        let state_ptr = bind.get_extra_info::<CatalogState>();
        let guard = unsafe { (*state_ptr).read().expect("catalog RwLock poisoned") };
        // ... populate rows ...
    }
    fn parameters() -> Option<Vec<LogicalTypeHandle>> { None } // no params
}
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add SHOW DIMENSIONS/METRICS/FACTS parser detection, VTabs, and registration</name>
  <files>src/parse.rs, src/ddl/mod.rs, src/ddl/show_dims.rs, src/ddl/show_metrics.rs, src/ddl/show_facts.rs, src/lib.rs</files>
  <read_first>
    - src/parse.rs (DdlKind enum, detect_ddl_prefix, function_name, rewrite_ddl, validate_and_rewrite, DDL_PREFIXES -- note Plan 01 may have already added AlterRename variants)
    - src/ddl/list.rs (ListSemanticViewsVTab -- the pattern for multi-row catalog VTab)
    - src/ddl/describe.rs (DescribeSemanticViewVTab -- pattern for single-view name parameter)
    - src/model.rs (Dimension, Metric, Fact structs, SemanticViewDefinition::from_json)
    - src/ddl/mod.rs (existing module declarations)
    - src/lib.rs (registration block, lines 350-450)
  </read_first>
  <action>
**IMPORTANT: Plan 01 may run in parallel. If AlterRename variants are already in parse.rs, integrate alongside them. If not, this plan adds its variants independently and they merge cleanly (different enum variants, different prefix matchers).**

**1. src/parse.rs -- Add 3 new DdlKind variants:**

```rust
pub enum DdlKind {
    // ... existing + AlterRename variants from Plan 01 ...
    ShowDimensions,
    ShowMetrics,
    ShowFacts,
}
```

In `detect_ddl_prefix`, add BEFORE the existing `SHOW SEMANTIC VIEWS` check. The order within the SHOW group must be:
```rust
// SHOW SEMANTIC DIMENSIONS (3 keywords) -- before SHOW SEMANTIC VIEWS
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));
}
// Existing: SHOW SEMANTIC VIEWS (3 keywords)
if let Some(n) = match_keyword_prefix(b, &[b"show", b"semantic", b"views"]) {
    return Some((DdlKind::Show, n));
}
```

Note: `SHOW SEMANTIC DIMENSIONS`, `SHOW SEMANTIC METRICS`, `SHOW SEMANTIC FACTS`, and `SHOW SEMANTIC VIEWS` all have 3 keywords. Since the 3rd keyword differs, there is no prefix overlap. Put the new ones BEFORE `SHOW SEMANTIC VIEWS` for clarity.

In `function_name`, add:
```rust
DdlKind::ShowDimensions => "show_semantic_dimensions",
DdlKind::ShowMetrics => "show_semantic_metrics",
DdlKind::ShowFacts => "show_semantic_facts",
```

In `rewrite_ddl`, add handling for the new SHOW commands. Each supports two forms:
- `SHOW SEMANTIC DIMENSIONS` (no args) -> `SELECT * FROM show_semantic_dimensions_all()`
- `SHOW SEMANTIC DIMENSIONS IN view_name` -> `SELECT * FROM show_semantic_dimensions('view_name')`

```rust
DdlKind::ShowDimensions | DdlKind::ShowMetrics | DdlKind::ShowFacts => {
    let after_prefix = trimmed[plen..].trim();
    if after_prefix.is_empty() {
        // Cross-view form: no IN clause
        let all_fn = match kind {
            DdlKind::ShowDimensions => "show_semantic_dimensions_all",
            DdlKind::ShowMetrics => "show_semantic_metrics_all",
            DdlKind::ShowFacts => "show_semantic_facts_all",
            _ => unreachable!(),
        };
        Ok(format!("SELECT * FROM {all_fn}()"))
    } else {
        // Check for IN keyword
        let upper = after_prefix.to_ascii_uppercase();
        if !upper.starts_with("IN") || (after_prefix.len() > 2 && !after_prefix.as_bytes()[2].is_ascii_whitespace()) {
            return Err(format!("Expected IN after SHOW SEMANTIC {}. Usage: SHOW SEMANTIC {} [IN view_name]",
                match kind { DdlKind::ShowDimensions => "DIMENSIONS", DdlKind::ShowMetrics => "METRICS", _ => "FACTS" },
                match kind { DdlKind::ShowDimensions => "DIMENSIONS", DdlKind::ShowMetrics => "METRICS", _ => "FACTS" }));
        }
        let after_in = after_prefix[2..].trim();
        if after_in.is_empty() {
            return Err("Missing view name after IN".to_string());
        }
        // Extract view name (up to whitespace or end)
        let name_end = after_in.find(|c: char| c.is_whitespace()).unwrap_or(after_in.len());
        let view_name = &after_in[..name_end];
        let safe_name = view_name.replace('\'', "''");
        Ok(format!("SELECT * FROM {fn_name}('{safe_name}')"))
    }
}
```

In `validate_and_rewrite`, add the new SHOW variants alongside existing Show:
```rust
DdlKind::ShowDimensions | DdlKind::ShowMetrics | DdlKind::ShowFacts => {
    rewrite_ddl(query).map(Some).map_err(|e| ParseError {
        message: e,
        position: Some(trim_offset + plen),
    })
}
```

In `extract_ddl_name`:
```rust
DdlKind::ShowDimensions | DdlKind::ShowMetrics | DdlKind::ShowFacts => {
    let after_prefix = trimmed[plen..].trim();
    if after_prefix.is_empty() {
        return Ok(None); // Cross-view form, no specific name
    }
    let upper = after_prefix.to_ascii_uppercase();
    if upper.starts_with("IN") && (after_prefix.len() <= 2 || after_prefix.as_bytes()[2].is_ascii_whitespace()) {
        let after_in = after_prefix[2..].trim();
        if after_in.is_empty() { return Ok(None); }
        let name_end = after_in.find(|c: char| c.is_whitespace()).unwrap_or(after_in.len());
        Ok(Some(after_in[..name_end].to_string()))
    } else {
        Ok(None)
    }
}
```

Add to `DDL_PREFIXES`:
```rust
"show semantic dimensions",
"show semantic metrics",
"show semantic facts",
```

**2. src/ddl/show_dims.rs -- Create ShowSemanticDimensionsVTab:**

Two table functions: `show_semantic_dimensions(view_name)` and `show_semantic_dimensions_all()`.

Both share the same output schema (5 VARCHAR columns):
- `semantic_view_name`
- `name`
- `expr`
- `source_table`
- `data_type`

For the single-view form (`show_semantic_dimensions`):
- Takes one VARCHAR parameter (view_name)
- At bind time, read from CatalogState, deserialize via `SemanticViewDefinition::from_json`, collect dimensions
- Error if view not found

For the all-views form (`show_semantic_dimensions_all`):
- Takes no parameters
- At bind time, iterate all entries in CatalogState, deserialize each, collect all dimensions
- Sort by (semantic_view_name, name) for deterministic output

Both use the same row struct:
```rust
pub struct ShowDimRow {
    semantic_view_name: String,
    name: String,
    expr: String,
    source_table: String,
    data_type: String,
}
```

Use `AtomicBool` done pattern from `ListSemanticViewsVTab`.

Create two VTab structs: `ShowSemanticDimensionsVTab` (1 param) and `ShowSemanticDimensionsAllVTab` (0 params). They can share BindData and InitData types.

**3. src/ddl/show_metrics.rs -- Create ShowSemanticMetricsVTab:**

Same pattern as show_dims.rs but iterates `def.metrics` instead of `def.dimensions`. Same 5-column schema. Two VTab structs: `ShowSemanticMetricsVTab` and `ShowSemanticMetricsAllVTab`.

**4. src/ddl/show_facts.rs -- Create ShowSemanticFactsVTab:**

Same pattern as show_dims.rs but iterates `def.facts` instead. Facts have 4 columns (no `data_type` since Fact has no `output_type`):
- `semantic_view_name`
- `name`
- `expr`
- `source_table`

Two VTab structs: `ShowSemanticFactsVTab` and `ShowSemanticFactsAllVTab`.

**5. src/ddl/mod.rs -- Add module declarations:**
```rust
pub mod show_dims;
pub mod show_metrics;
pub mod show_facts;
```

**6. src/lib.rs -- Register 6 new table functions:**

After the existing `describe_semantic_view` registration, add:

```rust
// SHOW SEMANTIC DIMENSIONS
con.register_table_function_with_extra_info::<ShowSemanticDimensionsVTab, _>(
    "show_semantic_dimensions",
    &catalog_state,
)?;
con.register_table_function_with_extra_info::<ShowSemanticDimensionsAllVTab, _>(
    "show_semantic_dimensions_all",
    &catalog_state,
)?;

// SHOW SEMANTIC METRICS
con.register_table_function_with_extra_info::<ShowSemanticMetricsVTab, _>(
    "show_semantic_metrics",
    &catalog_state,
)?;
con.register_table_function_with_extra_info::<ShowSemanticMetricsAllVTab, _>(
    "show_semantic_metrics_all",
    &catalog_state,
)?;

// SHOW SEMANTIC FACTS
con.register_table_function_with_extra_info::<ShowSemanticFactsVTab, _>(
    "show_semantic_facts",
    &catalog_state,
)?;
con.register_table_function_with_extra_info::<ShowSemanticFactsAllVTab, _>(
    "show_semantic_facts_all",
    &catalog_state,
)?;
```

Add the imports:
```rust
use ddl::show_dims::{ShowSemanticDimensionsVTab, ShowSemanticDimensionsAllVTab};
use ddl::show_metrics::{ShowSemanticMetricsVTab, ShowSemanticMetricsAllVTab};
use ddl::show_facts::{ShowSemanticFactsVTab, ShowSemanticFactsAllVTab};
```
  </action>
  <verify>
    <automated>cargo test 2>&1 | tail -5</automated>
  </verify>
  <acceptance_criteria>
    - src/parse.rs contains `ShowDimensions,` and `ShowMetrics,` and `ShowFacts,` in DdlKind enum
    - src/parse.rs detect_ddl_prefix contains `match_keyword_prefix(b, &[b"show", b"semantic", b"dimensions"])`
    - src/parse.rs detect_ddl_prefix contains `match_keyword_prefix(b, &[b"show", b"semantic", b"metrics"])`
    - src/parse.rs detect_ddl_prefix contains `match_keyword_prefix(b, &[b"show", b"semantic", b"facts"])`
    - src/parse.rs DDL_PREFIXES contains `"show semantic dimensions"` and `"show semantic metrics"` and `"show semantic facts"`
    - src/ddl/show_dims.rs exists and contains `pub struct ShowSemanticDimensionsVTab`
    - src/ddl/show_dims.rs contains `pub struct ShowSemanticDimensionsAllVTab`
    - src/ddl/show_metrics.rs exists and contains `pub struct ShowSemanticMetricsVTab`
    - src/ddl/show_facts.rs exists and contains `pub struct ShowSemanticFactsVTab`
    - src/ddl/mod.rs contains `pub mod show_dims;` and `pub mod show_metrics;` and `pub mod show_facts;`
    - src/lib.rs contains `"show_semantic_dimensions"` and `"show_semantic_metrics"` and `"show_semantic_facts"`
    - `cargo test` exits 0
  </acceptance_criteria>
  <done>Three SHOW commands (DIMENSIONS, METRICS, FACTS) have parser detection, rewrite logic, VTab implementations for both single-view and cross-view forms, and compile successfully</done>
</task>

<task type="auto">
  <name>Task 2: Add sqllogictest coverage for SHOW DIMENSIONS/METRICS/FACTS</name>
  <files>test/sql/phase34_1_show_commands.test</files>
  <read_first>
    - src/ddl/show_dims.rs (verify output column names and order)
    - src/ddl/show_metrics.rs (verify output column names and order)
    - src/ddl/show_facts.rs (verify output column names and order)
    - test/sql/phase28_e2e.test (pattern for multi-view setup with FACTS)
    - test/sql/phase29_facts.test (how FACTS are defined in tests)
  </read_first>
  <action>
Create `test/sql/phase34_1_show_commands.test` with the following test cases:

```
# Phase 34.1: SHOW SEMANTIC DIMENSIONS / METRICS / FACTS

require semantic_views

# --- Setup: create two views ---
statement ok
CREATE TABLE p34s_customers (id INTEGER PRIMARY KEY, name VARCHAR, region VARCHAR);

statement ok
CREATE TABLE p34s_orders (id INTEGER PRIMARY KEY, customer_id INTEGER, amount DECIMAL(10,2), order_date DATE);

statement ok
CREATE SEMANTIC VIEW orders_sv AS
  TABLES (
    c AS p34s_customers PRIMARY KEY (id),
    o AS p34s_orders PRIMARY KEY (id)
  )
  RELATIONSHIPS (
    orders_to_customers AS o(customer_id) REFERENCES c
  )
  FACTS (
    raw_amount := o.amount SOURCE o
  )
  DIMENSIONS (
    customer_name := c.name SOURCE c TYPE VARCHAR,
    region := c.region SOURCE c TYPE VARCHAR,
    order_date := o.order_date SOURCE o TYPE DATE
  )
  METRICS (
    total_amount := SUM(o.amount) SOURCE o TYPE DECIMAL(10,2),
    order_count := COUNT(o.id) SOURCE o TYPE BIGINT
  )

statement ok
CREATE TABLE p34s_products (id INTEGER PRIMARY KEY, product_name VARCHAR, price DECIMAL(10,2));

statement ok
CREATE SEMANTIC VIEW products_sv AS
  TABLES (
    p AS p34s_products PRIMARY KEY (id)
  )
  DIMENSIONS (
    product_name := p.product_name TYPE VARCHAR
  )
  METRICS (
    avg_price := AVG(p.price) TYPE DECIMAL(10,2)
  )

# --- Test 1: SHOW SEMANTIC DIMENSIONS IN (single view) ---
query TTTTT rowsort
SHOW SEMANTIC DIMENSIONS IN orders_sv
----
orders_sv	customer_name	c.name	c	VARCHAR
orders_sv	order_date	o.order_date	o	DATE
orders_sv	region	c.region	c	VARCHAR

# --- Test 2: SHOW SEMANTIC DIMENSIONS (cross-view) ---
query TTTTT rowsort
SHOW SEMANTIC DIMENSIONS
----
orders_sv	customer_name	c.name	c	VARCHAR
orders_sv	order_date	o.order_date	o	DATE
orders_sv	region	c.region	c	VARCHAR
products_sv	product_name	p.product_name	(empty)	VARCHAR

# --- Test 3: SHOW SEMANTIC METRICS IN (single view) ---
query TTTTT rowsort
SHOW SEMANTIC METRICS IN orders_sv
----
orders_sv	order_count	COUNT(o.id)	o	BIGINT
orders_sv	total_amount	SUM(o.amount)	o	DECIMAL(10,2)

# --- Test 4: SHOW SEMANTIC METRICS (cross-view) ---
query TTTTT rowsort
SHOW SEMANTIC METRICS
----
orders_sv	order_count	COUNT(o.id)	o	BIGINT
orders_sv	total_amount	SUM(o.amount)	o	DECIMAL(10,2)
products_sv	avg_price	AVG(p.price)	(empty)	DECIMAL(10,2)

# --- Test 5: SHOW SEMANTIC FACTS IN (single view) ---
query TTTT rowsort
SHOW SEMANTIC FACTS IN orders_sv
----
orders_sv	raw_amount	o.amount	o

# --- Test 6: SHOW SEMANTIC FACTS (cross-view, includes view with no facts) ---
query TTTT rowsort
SHOW SEMANTIC FACTS
----
orders_sv	raw_amount	o.amount	o

# --- Test 7: Non-existent view ---
statement error
SHOW SEMANTIC DIMENSIONS IN nonexistent_view

# --- Test 8: Case insensitivity ---
query TTTTT rowsort
show semantic dimensions in orders_sv
----
orders_sv	customer_name	c.name	c	VARCHAR
orders_sv	order_date	o.order_date	o	DATE
orders_sv	region	c.region	c	VARCHAR

# --- Cleanup ---
statement ok
DROP SEMANTIC VIEW orders_sv

statement ok
DROP SEMANTIC VIEW products_sv

statement ok
DROP TABLE p34s_orders

statement ok
DROP TABLE p34s_customers

statement ok
DROP TABLE p34s_products
```

**NOTE:** The exact expected output for `source_table` when it is `None` depends on the VTab implementation. If `None` maps to empty string `""`, the sqllogictest should show `(empty)` for that cell. Adjust the expected results based on actual DuckDB behavior with empty VARCHAR values in sqllogictest (DuckDB renders empty string as `(empty)` in sqllogictest). If DuckDB renders it differently, adjust accordingly.
  </action>
  <verify>
    <automated>just build && just test-sql 2>&1 | tail -10</automated>
  </verify>
  <acceptance_criteria>
    - test/sql/phase34_1_show_commands.test exists
    - test/sql/phase34_1_show_commands.test contains `SHOW SEMANTIC DIMENSIONS IN orders_sv`
    - test/sql/phase34_1_show_commands.test contains `SHOW SEMANTIC DIMENSIONS` (cross-view)
    - test/sql/phase34_1_show_commands.test contains `SHOW SEMANTIC METRICS`
    - test/sql/phase34_1_show_commands.test contains `SHOW SEMANTIC FACTS`
    - test/sql/phase34_1_show_commands.test contains `statement error` for nonexistent view
    - `just build && just test-sql` exits 0
  </acceptance_criteria>
  <done>SHOW DIMENSIONS/METRICS/FACTS have sqllogictest coverage for: single-view scope, cross-view scope, facts for view with no facts, non-existent view error, case insensitivity</done>
</task>

</tasks>

<verification>
- `cargo test` passes (all existing + new unit tests)
- `just build && just test-sql` passes (new sqllogictest file)
- SHOW SEMANTIC DIMENSIONS IN view_name returns expected columns
- SHOW SEMANTIC DIMENSIONS (without IN) returns dimensions across all views
- Same for METRICS and FACTS
- SHOW SEMANTIC FACTS for a view with no facts returns 0 rows
</verification>

<success_criteria>
- ShowDimensions, ShowMetrics, ShowFacts DdlKind variants exist in parse.rs
- show_dims.rs, show_metrics.rs, show_facts.rs VTab modules exist
- Each has both single-view and cross-view table function implementations
- 6 table functions registered in lib.rs
- sqllogictest covers all 8 test scenarios
- `just test-all` passes
</success_criteria>

<output>
After completion, create `.planning/phases/34.1-close-ddl-gaps-vs-snowflake-alter-semantic-view-show-semantic-dimensions-facts-metrics/34.1-02-SUMMARY.md`
</output>
