Skip to content

Commit 98d3f12

Browse files
committed
Version bump to v0.1.4
1 parent 4817ec3 commit 98d3f12

File tree

9 files changed

+171
-9
lines changed

9 files changed

+171
-9
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ exclude = [
99
]
1010

1111
[workspace.package]
12-
version = "0.1.3"
12+
version = "0.1.4"
1313
edition = "2021"
1414
license = "MIT"
1515
authors = ["polyglot contributors"]

crates/polyglot-sql/src/expressions.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,119 @@ pub enum Expression {
10961096
}
10971097

10981098
impl Expression {
1099+
/// Returns `true` if this expression is a valid top-level SQL statement.
1100+
///
1101+
/// Bare expressions like identifiers, literals, and function calls are not
1102+
/// valid statements. This is used by `validate()` to reject inputs like
1103+
/// `SELECT scooby dooby doo` which the parser splits into `SELECT scooby AS dooby`
1104+
/// plus the bare identifier `doo`.
1105+
pub fn is_statement(&self) -> bool {
1106+
match self {
1107+
// Queries
1108+
Expression::Select(_)
1109+
| Expression::Union(_)
1110+
| Expression::Intersect(_)
1111+
| Expression::Except(_)
1112+
| Expression::Subquery(_)
1113+
| Expression::Values(_)
1114+
| Expression::PipeOperator(_)
1115+
1116+
// DML
1117+
| Expression::Insert(_)
1118+
| Expression::Update(_)
1119+
| Expression::Delete(_)
1120+
| Expression::Copy(_)
1121+
| Expression::Put(_)
1122+
| Expression::Merge(_)
1123+
1124+
// DDL
1125+
| Expression::CreateTable(_)
1126+
| Expression::DropTable(_)
1127+
| Expression::AlterTable(_)
1128+
| Expression::CreateIndex(_)
1129+
| Expression::DropIndex(_)
1130+
| Expression::CreateView(_)
1131+
| Expression::DropView(_)
1132+
| Expression::AlterView(_)
1133+
| Expression::AlterIndex(_)
1134+
| Expression::Truncate(_)
1135+
| Expression::TruncateTable(_)
1136+
| Expression::CreateSchema(_)
1137+
| Expression::DropSchema(_)
1138+
| Expression::DropNamespace(_)
1139+
| Expression::CreateDatabase(_)
1140+
| Expression::DropDatabase(_)
1141+
| Expression::CreateFunction(_)
1142+
| Expression::DropFunction(_)
1143+
| Expression::CreateProcedure(_)
1144+
| Expression::DropProcedure(_)
1145+
| Expression::CreateSequence(_)
1146+
| Expression::DropSequence(_)
1147+
| Expression::AlterSequence(_)
1148+
| Expression::CreateTrigger(_)
1149+
| Expression::DropTrigger(_)
1150+
| Expression::CreateType(_)
1151+
| Expression::DropType(_)
1152+
| Expression::Comment(_)
1153+
1154+
// Session/Transaction/Control
1155+
| Expression::Use(_)
1156+
| Expression::Set(_)
1157+
| Expression::SetStatement(_)
1158+
| Expression::Transaction(_)
1159+
| Expression::Commit(_)
1160+
| Expression::Rollback(_)
1161+
| Expression::Grant(_)
1162+
| Expression::Revoke(_)
1163+
| Expression::Cache(_)
1164+
| Expression::Uncache(_)
1165+
| Expression::LoadData(_)
1166+
| Expression::Pragma(_)
1167+
| Expression::Describe(_)
1168+
| Expression::Show(_)
1169+
| Expression::Kill(_)
1170+
| Expression::Execute(_)
1171+
| Expression::Declare(_)
1172+
| Expression::Refresh(_)
1173+
| Expression::AlterSession(_)
1174+
| Expression::LockingStatement(_)
1175+
1176+
// Analyze
1177+
| Expression::Analyze(_)
1178+
| Expression::AnalyzeStatistics(_)
1179+
| Expression::AnalyzeHistogram(_)
1180+
| Expression::AnalyzeSample(_)
1181+
| Expression::AnalyzeListChainedRows(_)
1182+
| Expression::AnalyzeDelete(_)
1183+
1184+
// Attach/Detach/Install/Summarize
1185+
| Expression::Attach(_)
1186+
| Expression::Detach(_)
1187+
| Expression::Install(_)
1188+
| Expression::Summarize(_)
1189+
1190+
// Pivot at statement level
1191+
| Expression::Pivot(_)
1192+
| Expression::Unpivot(_)
1193+
1194+
// Command (raw/unparsed statements)
1195+
| Expression::Command(_)
1196+
| Expression::Raw(_)
1197+
1198+
// Return statement
1199+
| Expression::ReturnStmt(_) => true,
1200+
1201+
// Annotated wraps another expression with comments — check inner
1202+
Expression::Annotated(a) => a.this.is_statement(),
1203+
1204+
// Alias at top level can wrap a statement (e.g., parenthesized subquery with alias)
1205+
Expression::Alias(a) => a.this.is_statement(),
1206+
1207+
// Everything else (identifiers, literals, operators, functions, etc.)
1208+
_ => false,
1209+
}
1210+
}
1211+
10991212
/// Create a literal number expression from an integer.
11001213
pub fn number(n: i64) -> Self {
11011214
Expression::Literal(Literal::Number(n.to_string()))
@@ -3602,6 +3715,9 @@ pub enum DataType {
36023715
// fields: Vec of (field_name, field_type, not_null)
36033716
Object { fields: Vec<(String, DataType, bool)>, modifier: Option<String> },
36043717

3718+
// Nullable wrapper (ClickHouse): Nullable(String), Nullable(Int32)
3719+
Nullable { inner: Box<DataType> },
3720+
36053721
// Custom/User-defined
36063722
Custom { name: String },
36073723

crates/polyglot-sql/src/generator.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19344,6 +19344,24 @@ impl Generator {
1934419344
}
1934519345
self.write(")");
1934619346
}
19347+
DataType::Nullable { inner } => {
19348+
// ClickHouse: Nullable(T), other dialects: just the inner type
19349+
if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
19350+
self.write("Nullable(");
19351+
self.generate_data_type(inner)?;
19352+
self.write(")");
19353+
} else {
19354+
// Map ClickHouse-specific custom type names to standard types
19355+
match inner.as_ref() {
19356+
DataType::Custom { name } if name.to_uppercase() == "DATETIME" => {
19357+
self.generate_data_type(&DataType::Timestamp { precision: None, timezone: false })?;
19358+
}
19359+
_ => {
19360+
self.generate_data_type(inner)?;
19361+
}
19362+
}
19363+
}
19364+
}
1934719365
DataType::Custom { name } => {
1934819366
// Handle dialect-specific type transformations
1934919367
let name_upper = name.to_uppercase();

crates/polyglot-sql/src/lib.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,20 @@ pub fn generate(expression: &Expression, dialect: DialectType) -> Result<String>
176176
pub fn validate(sql: &str, dialect: DialectType) -> ValidationResult {
177177
let d = Dialect::get(dialect);
178178
match d.parse(sql) {
179-
Ok(_) => ValidationResult::success(),
179+
Ok(expressions) => {
180+
// Reject bare expressions that aren't valid SQL statements.
181+
// The parser accepts any expression at the top level, but bare identifiers,
182+
// literals, function calls, etc. are not valid statements.
183+
for expr in &expressions {
184+
if !expr.is_statement() {
185+
let msg = format!("Invalid expression / Unexpected token");
186+
return ValidationResult::with_errors(vec![
187+
ValidationError::error(msg, "E004"),
188+
]);
189+
}
190+
}
191+
ValidationResult::success()
192+
}
180193
Err(e) => {
181194
let error = match &e {
182195
Error::Syntax {

crates/polyglot-sql/src/parser.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24640,7 +24640,7 @@ impl Parser {
2464024640
// JSON_KEYS, TO_JSON, PARSE_JSON etc. support additional args including named args (BigQuery)
2464124641
// e.g., JSON_KEYS(expr, depth, mode => 'lax'), TO_JSON(expr, stringify_wide_numbers => FALSE)
2464224642
// e.g., PARSE_JSON('{}', wide_number_mode => 'exact')
24643-
"JSON_ARRAY_LENGTH" | "JSON_KEYS" | "JSON_TYPE" | "TO_JSON" | "TYPEOF" | "PARSE_JSON" => {
24643+
"JSON_ARRAY_LENGTH" | "JSON_KEYS" | "JSON_TYPE" | "TO_JSON" | "TYPEOF" | "TOTYPENAME" | "PARSE_JSON" => {
2464424644
let this = self.parse_expression()?;
2464524645

2464624646
// Check for additional arguments (comma-separated, possibly named)
@@ -24668,7 +24668,7 @@ impl Parser {
2466824668
"JSON_KEYS" => Expression::JsonKeys(Box::new(func)),
2466924669
"JSON_TYPE" => Expression::JsonType(Box::new(func)),
2467024670
"TO_JSON" => Expression::ToJson(Box::new(func)),
24671-
"TYPEOF" => Expression::Typeof(Box::new(func)),
24671+
"TYPEOF" | "TOTYPENAME" => Expression::Typeof(Box::new(func)),
2467224672
"PARSE_JSON" => Expression::ParseJson(Box::new(func)),
2467324673
_ => unreachable!("JSON function name already matched in caller"),
2467424674
})
@@ -29165,6 +29165,13 @@ impl Parser {
2916529165
}
2916629166
Ok(DataType::Custom { name })
2916729167
}
29168+
// ClickHouse Nullable(T) wrapper type
29169+
"NULLABLE" => {
29170+
self.expect(TokenType::LParen)?;
29171+
let inner = self.parse_data_type()?;
29172+
self.expect(TokenType::RParen)?;
29173+
Ok(DataType::Nullable { inner: Box::new(inner) })
29174+
}
2916829175
_ => {
2916929176
// Handle custom types with optional parenthesized precision/args
2917029177
// e.g., DATETIME2(2), DATETIMEOFFSET(7), NVARCHAR2(100)
@@ -29605,6 +29612,13 @@ impl Parser {
2960529612
}
2960629613
DataType::Custom { name }
2960729614
}
29615+
// ClickHouse Nullable(T) wrapper type
29616+
"NULLABLE" => {
29617+
self.expect(TokenType::LParen)?;
29618+
let inner = self.parse_data_type_for_cast()?;
29619+
self.expect(TokenType::RParen)?;
29620+
DataType::Nullable { inner: Box::new(inner) }
29621+
}
2960829622
// For simple types, use convert_name_to_type to get proper DataType variants
2960929623
// This ensures VARCHAR becomes DataType::VarChar, not DataType::Custom
2961029624
_ => self.convert_name_to_type(&name)?
@@ -29781,6 +29795,7 @@ impl Parser {
2978129795
DataType::String { length: Some(n) } => format!("STRING({})", n),
2978229796
DataType::String { length: None } => "STRING".to_string(),
2978329797
DataType::Array { element_type, .. } => format!("ARRAY({})", self.data_type_to_string(element_type)),
29798+
DataType::Nullable { inner } => format!("Nullable({})", self.data_type_to_string(inner)),
2978429799
DataType::Custom { name } => name.clone(),
2978529800
_ => format!("{:?}", dt),
2978629801
}

packages/documentation/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@polyglot-sql/documentation",
3-
"version": "0.1.3",
3+
"version": "0.1.4",
44
"private": true,
55
"type": "module",
66
"scripts": {

packages/playground/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@polyglot-sql/playground",
3-
"version": "0.1.3",
3+
"version": "0.1.4",
44
"private": true,
55
"type": "module",
66
"scripts": {

packages/sdk/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@polyglot-sql/sdk",
3-
"version": "0.1.3",
3+
"version": "0.1.4",
44
"description": "SQL dialect translator powered by WebAssembly",
55
"type": "module",
66
"main": "./dist/index.js",

0 commit comments

Comments
 (0)