Skip to content

Commit f652acc

Browse files
committed
Move Spec out of api.rs
1 parent d74bca3 commit f652acc

5 files changed

Lines changed: 176 additions & 162 deletions

File tree

ggsql-python/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use pyo3::prelude::*;
66
use pyo3::types::{PyBytes, PyDict, PyList};
77
use std::io::Cursor;
88

9-
use ggsql::api::{validate as rust_validate, Spec, ValidationWarning};
9+
use ggsql::api::{validate as rust_validate, ValidationWarning};
10+
use ggsql::reader::Spec;
1011
use ggsql::reader::{DuckDBReader as RustDuckDBReader, Reader};
1112
use ggsql::writer::VegaLiteWriter as RustVegaLiteWriter;
1213
use ggsql::GgsqlError;

src/api.rs

Lines changed: 2 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -1,164 +1,14 @@
11
//! High-level ggsql API.
22
//!
3-
//! Two-stage API: `reader.execute()` → `render()`.
3+
//! Validation and query inspection without SQL execution.
44
5-
use crate::naming;
65
use crate::parser;
7-
use crate::plot::Plot;
8-
use crate::{DataFrame, Result};
9-
use std::collections::HashMap;
10-
11-
#[cfg(feature = "vegalite")]
12-
use crate::writer::Writer;
6+
use crate::Result;
137

148
// ============================================================================
159
// Core Types
1610
// ============================================================================
1711

18-
/// Result of `reader.execute()`, ready for rendering.
19-
pub struct Spec {
20-
/// Single resolved plot specification
21-
plot: Plot,
22-
/// Internal data map (global + layer-specific DataFrames)
23-
data: HashMap<String, DataFrame>,
24-
/// Cached metadata about the prepared visualization
25-
metadata: Metadata,
26-
/// The main SQL query that was executed
27-
sql: String,
28-
/// The raw VISUALISE portion text
29-
visual: String,
30-
/// Per-layer filter/source queries (None = uses global data directly)
31-
layer_sql: Vec<Option<String>>,
32-
/// Per-layer stat transform queries (None = no stat transform)
33-
stat_sql: Vec<Option<String>>,
34-
/// Validation warnings from preparation
35-
warnings: Vec<ValidationWarning>,
36-
}
37-
38-
impl Spec {
39-
/// Create a new Spec from PreparedData
40-
pub(crate) fn new(
41-
plot: Plot,
42-
data: HashMap<String, DataFrame>,
43-
sql: String,
44-
visual: String,
45-
layer_sql: Vec<Option<String>>,
46-
stat_sql: Vec<Option<String>>,
47-
warnings: Vec<ValidationWarning>,
48-
) -> Self {
49-
// Compute metadata from data
50-
let (rows, columns) = if let Some(df) = data.get(naming::GLOBAL_DATA_KEY) {
51-
let cols: Vec<String> = df
52-
.get_column_names()
53-
.iter()
54-
.map(|s| s.to_string())
55-
.collect();
56-
(df.height(), cols)
57-
} else if let Some(df) = data.values().next() {
58-
let cols: Vec<String> = df
59-
.get_column_names()
60-
.iter()
61-
.map(|s| s.to_string())
62-
.collect();
63-
(df.height(), cols)
64-
} else {
65-
(0, Vec::new())
66-
};
67-
68-
let layer_count = plot.layers.len();
69-
let metadata = Metadata {
70-
rows,
71-
columns,
72-
layer_count,
73-
};
74-
75-
Self {
76-
plot,
77-
data,
78-
metadata,
79-
sql,
80-
visual,
81-
layer_sql,
82-
stat_sql,
83-
warnings,
84-
}
85-
}
86-
87-
/// Render to output format (e.g., Vega-Lite JSON).
88-
#[cfg(feature = "vegalite")]
89-
pub fn render(&self, writer: &dyn Writer) -> Result<String> {
90-
writer.write(&self.plot, &self.data)
91-
}
92-
93-
/// Get the resolved plot specification.
94-
pub fn plot(&self) -> &Plot {
95-
&self.plot
96-
}
97-
98-
/// Get visualization metadata.
99-
pub fn metadata(&self) -> &Metadata {
100-
&self.metadata
101-
}
102-
103-
/// Number of layers.
104-
pub fn layer_count(&self) -> usize {
105-
self.plot.layers.len()
106-
}
107-
108-
/// Get global data (main query result).
109-
pub fn data(&self) -> Option<&DataFrame> {
110-
self.data.get(naming::GLOBAL_DATA_KEY)
111-
}
112-
113-
/// Get layer-specific data (from FILTER or FROM clause).
114-
pub fn layer_data(&self, layer_index: usize) -> Option<&DataFrame> {
115-
self.data.get(&naming::layer_key(layer_index))
116-
}
117-
118-
/// Get stat transform data (e.g., histogram bins, density estimates).
119-
pub fn stat_data(&self, layer_index: usize) -> Option<&DataFrame> {
120-
self.layer_data(layer_index)
121-
}
122-
123-
/// Get internal data map (all DataFrames by key).
124-
pub fn data_map(&self) -> &HashMap<String, DataFrame> {
125-
&self.data
126-
}
127-
128-
/// The main SQL query that was executed.
129-
pub fn sql(&self) -> &str {
130-
&self.sql
131-
}
132-
133-
/// The VISUALISE portion (raw text).
134-
pub fn visual(&self) -> &str {
135-
&self.visual
136-
}
137-
138-
/// Layer filter/source query, or `None` if using global data.
139-
pub fn layer_sql(&self, layer_index: usize) -> Option<&str> {
140-
self.layer_sql.get(layer_index).and_then(|s| s.as_deref())
141-
}
142-
143-
/// Stat transform query, or `None` if no stat transform.
144-
pub fn stat_sql(&self, layer_index: usize) -> Option<&str> {
145-
self.stat_sql.get(layer_index).and_then(|s| s.as_deref())
146-
}
147-
148-
/// Validation warnings from preparation.
149-
pub fn warnings(&self) -> &[ValidationWarning] {
150-
&self.warnings
151-
}
152-
}
153-
154-
/// Metadata about the prepared visualization.
155-
#[derive(Debug, Clone)]
156-
pub struct Metadata {
157-
pub rows: usize,
158-
pub columns: Vec<String>,
159-
pub layer_count: usize,
160-
}
161-
16212
/// Result of `validate()` - query inspection and validation without SQL execution.
16313
pub struct Validated {
16414
sql: String,

src/lib.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ ggsql splits queries at the `VISUALISE` boundary:
2727
2828
## Core Components
2929
30-
- [`api`] - High-level API (validate, Spec)
30+
- [`api`] - Validation API (validate, Validated)
3131
- [`parser`] - Query parsing and AST generation
3232
- [`reader`] - Data source abstraction layer
3333
- [`writer`] - Output format abstraction layer
@@ -54,9 +54,10 @@ pub use plot::{
5454
};
5555

5656
// Re-export API types and functions
57-
pub use api::{
58-
validate, Location, Metadata, Spec, Validated, ValidationError, ValidationWarning,
59-
};
57+
pub use api::{validate, Location, Validated, ValidationError, ValidationWarning};
58+
59+
// Re-export reader types
60+
pub use reader::{Metadata, Spec};
6061

6162
// DataFrame abstraction (wraps Polars)
6263
pub use polars::prelude::DataFrame;

src/reader/mod.rs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,59 @@
3131
//! let spec = reader.execute("SELECT * FROM my_table VISUALISE x, y DRAW point")?;
3232
//! ```
3333
34-
use crate::{DataFrame, GgsqlError, Result};
34+
use std::collections::HashMap;
3535

36-
#[cfg(feature = "duckdb")]
37-
use crate::api::{validate, Spec, ValidationWarning};
38-
#[cfg(feature = "duckdb")]
36+
use crate::api::{validate, ValidationWarning};
3937
use crate::execute::prepare_data_with_executor;
38+
use crate::plot::Plot;
39+
use crate::{DataFrame, GgsqlError, Result};
4040

4141
#[cfg(feature = "duckdb")]
4242
pub mod duckdb;
4343

4444
pub mod connection;
45-
4645
pub mod data;
46+
mod spec;
4747

4848
#[cfg(feature = "duckdb")]
4949
pub use duckdb::DuckDBReader;
5050

51+
// ============================================================================
52+
// Spec - Result of reader.execute()
53+
// ============================================================================
54+
55+
/// Result of executing a ggsql query, ready for rendering.
56+
pub struct Spec {
57+
/// Single resolved plot specification
58+
pub(crate) plot: Plot,
59+
/// Internal data map (global + layer-specific DataFrames)
60+
pub(crate) data: HashMap<String, DataFrame>,
61+
/// Cached metadata about the prepared visualization
62+
pub(crate) metadata: Metadata,
63+
/// The main SQL query that was executed
64+
pub(crate) sql: String,
65+
/// The raw VISUALISE portion text
66+
pub(crate) visual: String,
67+
/// Per-layer filter/source queries (None = uses global data directly)
68+
pub(crate) layer_sql: Vec<Option<String>>,
69+
/// Per-layer stat transform queries (None = no stat transform)
70+
pub(crate) stat_sql: Vec<Option<String>>,
71+
/// Validation warnings from preparation
72+
pub(crate) warnings: Vec<ValidationWarning>,
73+
}
74+
75+
/// Metadata about the prepared visualization.
76+
#[derive(Debug, Clone)]
77+
pub struct Metadata {
78+
pub rows: usize,
79+
pub columns: Vec<String>,
80+
pub layer_count: usize,
81+
}
82+
83+
// ============================================================================
84+
// Reader Trait
85+
// ============================================================================
86+
5187
/// Trait for data source readers
5288
///
5389
/// Readers execute SQL queries and return Polars DataFrames.

src/reader/spec.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//! Implementation of Spec methods.
2+
3+
use std::collections::HashMap;
4+
5+
use crate::api::ValidationWarning;
6+
use crate::naming;
7+
use crate::plot::Plot;
8+
use crate::writer::Writer;
9+
use crate::{DataFrame, Result};
10+
11+
use super::{Metadata, Spec};
12+
13+
impl Spec {
14+
/// Create a new Spec from PreparedData
15+
pub(crate) fn new(
16+
plot: Plot,
17+
data: HashMap<String, DataFrame>,
18+
sql: String,
19+
visual: String,
20+
layer_sql: Vec<Option<String>>,
21+
stat_sql: Vec<Option<String>>,
22+
warnings: Vec<ValidationWarning>,
23+
) -> Self {
24+
// Compute metadata from data
25+
let (rows, columns) = if let Some(df) = data.get(naming::GLOBAL_DATA_KEY) {
26+
let cols: Vec<String> = df
27+
.get_column_names()
28+
.iter()
29+
.map(|s| s.to_string())
30+
.collect();
31+
(df.height(), cols)
32+
} else if let Some(df) = data.values().next() {
33+
let cols: Vec<String> = df
34+
.get_column_names()
35+
.iter()
36+
.map(|s| s.to_string())
37+
.collect();
38+
(df.height(), cols)
39+
} else {
40+
(0, Vec::new())
41+
};
42+
43+
let layer_count = plot.layers.len();
44+
let metadata = Metadata {
45+
rows,
46+
columns,
47+
layer_count,
48+
};
49+
50+
Self {
51+
plot,
52+
data,
53+
metadata,
54+
sql,
55+
visual,
56+
layer_sql,
57+
stat_sql,
58+
warnings,
59+
}
60+
}
61+
62+
/// Render to output format (e.g., Vega-Lite JSON).
63+
pub fn render(&self, writer: &dyn Writer) -> Result<String> {
64+
writer.write(&self.plot, &self.data)
65+
}
66+
67+
/// Get the resolved plot specification.
68+
pub fn plot(&self) -> &Plot {
69+
&self.plot
70+
}
71+
72+
/// Get visualization metadata.
73+
pub fn metadata(&self) -> &Metadata {
74+
&self.metadata
75+
}
76+
77+
/// Number of layers.
78+
pub fn layer_count(&self) -> usize {
79+
self.plot.layers.len()
80+
}
81+
82+
/// Get global data (main query result).
83+
pub fn data(&self) -> Option<&DataFrame> {
84+
self.data.get(naming::GLOBAL_DATA_KEY)
85+
}
86+
87+
/// Get layer-specific data (from FILTER or FROM clause).
88+
pub fn layer_data(&self, layer_index: usize) -> Option<&DataFrame> {
89+
self.data.get(&naming::layer_key(layer_index))
90+
}
91+
92+
/// Get stat transform data (e.g., histogram bins, density estimates).
93+
pub fn stat_data(&self, layer_index: usize) -> Option<&DataFrame> {
94+
self.layer_data(layer_index)
95+
}
96+
97+
/// Get internal data map (all DataFrames by key).
98+
pub fn data_map(&self) -> &HashMap<String, DataFrame> {
99+
&self.data
100+
}
101+
102+
/// The main SQL query that was executed.
103+
pub fn sql(&self) -> &str {
104+
&self.sql
105+
}
106+
107+
/// The VISUALISE portion (raw text).
108+
pub fn visual(&self) -> &str {
109+
&self.visual
110+
}
111+
112+
/// Layer filter/source query, or `None` if using global data.
113+
pub fn layer_sql(&self, layer_index: usize) -> Option<&str> {
114+
self.layer_sql.get(layer_index).and_then(|s| s.as_deref())
115+
}
116+
117+
/// Stat transform query, or `None` if no stat transform.
118+
pub fn stat_sql(&self, layer_index: usize) -> Option<&str> {
119+
self.stat_sql.get(layer_index).and_then(|s| s.as_deref())
120+
}
121+
122+
/// Validation warnings from preparation.
123+
pub fn warnings(&self) -> &[ValidationWarning] {
124+
&self.warnings
125+
}
126+
}

0 commit comments

Comments
 (0)