diff --git a/crates/sqlquack-syntax-coverage/src/cli/_helpers/commands.rs b/crates/sqlquack-syntax-coverage/src/cli/_helpers/commands.rs index abde02535..c7c98dfff 100644 --- a/crates/sqlquack-syntax-coverage/src/cli/_helpers/commands.rs +++ b/crates/sqlquack-syntax-coverage/src/cli/_helpers/commands.rs @@ -7,15 +7,14 @@ use std::path::{Path, PathBuf}; use std::process::{Command, ExitCode}; use std::time::Duration; +use clap::ValueEnum; use serde_json::{Value, json}; use crate::check::main::check_coverage::check_coverage; use crate::check::models::{ CheckResult, FormResult, LocalFailure, LocalVerdict, ObligationResult, Reason, }; -use crate::cli::_helpers::local::{ - frozen_catalogue, parser_tree, postgres_verdict, snowflake_verdict, -}; +use crate::cli::_helpers::local::{frozen_catalogue, local_verdict, parser_tree}; use crate::cli::_helpers::output::{print, say}; use crate::cli::_helpers::worker::Worker; use crate::cli::models::{Common, TranslationArguments}; @@ -53,10 +52,12 @@ const PARSE_MEMORY_KIB: u64 = 4 * 1024 * 1024; /// The worker command: this binary, its memory bounded by the shell before it starts. fn worker_command(binary: PathBuf, local_parser: LocalParser) -> Box Command> { - let worker: &str = match local_parser { - LocalParser::Postgres => "parse-worker --postgres", - LocalParser::Snowflake | LocalParser::None => "parse-worker", - }; + let worker: String = format!( + "parse-worker --parser {}", + local_parser + .to_possible_value() + .map_or_else(String::new, |value| value.get_name().to_owned()) + ); Box::new(move || { let mut command = Command::new("sh"); command @@ -101,9 +102,10 @@ fn checked(coverage: &SyntaxCoverage) -> Result { worker_command(binary, local_parser), PARSE_BOUND, )); - let check: CheckResult = check_coverage(coverage, tree.as_deref(), &|sql| match local_parser { - LocalParser::Snowflake | LocalParser::Postgres => worker.borrow_mut().verdict(sql), - LocalParser::None => LocalVerdict::Failed { + let parses: bool = local_verdict(local_parser).is_some(); + let check: CheckResult = check_coverage(coverage, tree.as_deref(), &|sql| match parses { + true => worker.borrow_mut().verdict(sql), + false => LocalVerdict::Failed { failure: LocalFailure::NoParser, }, }); @@ -521,11 +523,8 @@ pub(crate) fn diff( } /// Reads one JSON string of SQL per line and prints the local verdict on each, flushed. -pub(crate) fn parse_worker(postgres: bool) -> Result { - let verdict_of: fn(&str) -> LocalVerdict = match postgres { - true => postgres_verdict, - false => snowflake_verdict, - }; +pub(crate) fn parse_worker(parser: LocalParser) -> Result { + let verdict_of: fn(&str) -> LocalVerdict = local_verdict(parser).ok_or(ExitCode::FAILURE)?; let stdout = std::io::stdout(); for line in std::io::stdin().lock().lines() { let line: String = line.map_err(|_| ExitCode::FAILURE)?; diff --git a/crates/sqlquack-syntax-coverage/src/cli/_helpers/local.rs b/crates/sqlquack-syntax-coverage/src/cli/_helpers/local.rs index a00b8a6fe..3f166b869 100644 --- a/crates/sqlquack-syntax-coverage/src/cli/_helpers/local.rs +++ b/crates/sqlquack-syntax-coverage/src/cli/_helpers/local.rs @@ -2,12 +2,13 @@ use std::process::Command; -use sqlquack_syntax::{CallMode, Dialect, Expression, ExpressionWalk, ParseOptions}; +use sqlquack_syntax::{CallMode, Dialect, Expression, ExpressionWalk, ParseOptions, Parsed}; use crate::check::models::{LocalFailure, LocalVerdict}; use crate::formats::constants::SNOWFLAKE_DIALECT; use crate::formats::main::content_sha256::content_sha256; use crate::formats::main::current_dialect::current_dialect; +use crate::formats::models::LocalParser; /// The code prefix of every complexity-guard error, which is never a syntax verdict. const GUARD_PREFIX: &str = "E_GUARD_"; @@ -28,7 +29,7 @@ fn accepted(statements: &[Expression], gapped: bool) -> LocalVerdict { } /// SQLQuack's Snowflake verdict; a Command or Raw node anywhere makes the parse opaque. -pub(crate) fn snowflake_verdict(sql: &str) -> LocalVerdict { +fn snowflake_verdict(sql: &str) -> LocalVerdict { match sqlquack_syntax::parse_statements_with( sql, Dialect::Snowflake, @@ -45,14 +46,28 @@ pub(crate) fn snowflake_verdict(sql: &str) -> LocalVerdict { } } -/// SQLQuack's strict PostgreSQL verdict: a gap makes it opaque, and any refusal rejects it. -pub(crate) fn postgres_verdict(sql: &str) -> LocalVerdict { - match sqlquack_syntax_postgres::parse_strict(sql) { +/// A native dialect parser's strict verdict: a gap makes it opaque, and any refusal rejects it. +fn native_verdict(parsed: Result) -> LocalVerdict { + match parsed { Ok(parsed) => accepted(&parsed.statements, !parsed.gaps.is_empty()), Err(_) => LocalVerdict::Rejected, } } +/// SQLQuack's strict PostgreSQL verdict. +pub(crate) fn postgres_verdict(sql: &str) -> LocalVerdict { + native_verdict(sqlquack_syntax_postgres::parse_strict(sql)) +} + +/// The verdict of each dialect's local parser, or none when the dialect has no parser yet. +pub(crate) fn local_verdict(parser: LocalParser) -> Option LocalVerdict> { + match parser { + LocalParser::Snowflake => Some(snowflake_verdict), + LocalParser::Postgres => Some(postgres_verdict), + LocalParser::None => None, + } +} + /// Runs git; its exit code, and its trimmed stdout, or why it could not run. pub(crate) fn git(arguments: &[&str]) -> Result<(i32, String), String> { match Command::new("git").args(arguments).output() { diff --git a/crates/sqlquack-syntax-coverage/src/cli/main/run_cli.rs b/crates/sqlquack-syntax-coverage/src/cli/main/run_cli.rs index 77527d37f..96365e22b 100644 --- a/crates/sqlquack-syntax-coverage/src/cli/main/run_cli.rs +++ b/crates/sqlquack-syntax-coverage/src/cli/main/run_cli.rs @@ -41,7 +41,7 @@ pub fn run_cli() -> ExitCode { &common, ), Command::Diff { base, head, common } => diff(&base, head.as_deref(), &common), - Command::ParseWorker { postgres } => parse_worker(postgres), + Command::ParseWorker { parser } => parse_worker(parser), }; result.unwrap_or_else(|code| code) } diff --git a/crates/sqlquack-syntax-coverage/src/cli/models.rs b/crates/sqlquack-syntax-coverage/src/cli/models.rs index 305306f1e..557408c2b 100644 --- a/crates/sqlquack-syntax-coverage/src/cli/models.rs +++ b/crates/sqlquack-syntax-coverage/src/cli/models.rs @@ -5,6 +5,7 @@ use std::path::PathBuf; use clap::{Args, Parser, Subcommand}; use crate::formats::constants::COVERAGE_ROOT; +use crate::formats::models::LocalParser; #[derive(Debug, Parser)] #[command( @@ -103,9 +104,9 @@ pub enum Command { /// Internal: one verdict line per SQL line on stdin, for the isolated local parse. #[command(hide = true)] ParseWorker { - /// Parse with the PostgreSQL parser instead of the Snowflake one. - #[arg(long)] - postgres: bool, + /// The dialect parser that gives the verdicts. + #[arg(long, value_enum)] + parser: LocalParser, }, /// What conversion makes of every case Snowflake parsed, by form and family (decision D107). Translation { diff --git a/crates/sqlquack-syntax-coverage/src/formats/models.rs b/crates/sqlquack-syntax-coverage/src/formats/models.rs index b3fa5dcc9..e8d536f32 100644 --- a/crates/sqlquack-syntax-coverage/src/formats/models.rs +++ b/crates/sqlquack-syntax-coverage/src/formats/models.rs @@ -634,8 +634,8 @@ pub struct Dialect { pub syntax_codes: Vec, } -/// The parser the checker compares receipts with. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] +/// The parser the checker compares receipts with; `local_verdict` maps each to its verdict. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, clap::ValueEnum)] #[serde(rename_all = "snake_case")] pub enum LocalParser { /// `sqlquack_syntax` in the Snowflake dialect, through the isolated worker. diff --git a/crates/sqlquack-syntax-postgres/src/lib.rs b/crates/sqlquack-syntax-postgres/src/lib.rs index f383c7281..4b51f39fe 100644 --- a/crates/sqlquack-syntax-postgres/src/lib.rs +++ b/crates/sqlquack-syntax-postgres/src/lib.rs @@ -11,4 +11,4 @@ pub use crate::parse::main::format_type::format_type; pub use crate::parse::main::parse::parse; pub use crate::parse::main::parse_strict::parse_strict; pub use crate::parse::main::parse_type::parse_type; -pub use crate::parse::models::{Gap, Parsed}; +pub use sqlquack_syntax::{Gap, Parsed}; diff --git a/crates/sqlquack-syntax-postgres/src/parse/_helpers/tree/cursor.rs b/crates/sqlquack-syntax-postgres/src/parse/_helpers/tree/cursor.rs index e8bb80cfa..251adf061 100644 --- a/crates/sqlquack-syntax-postgres/src/parse/_helpers/tree/cursor.rs +++ b/crates/sqlquack-syntax-postgres/src/parse/_helpers/tree/cursor.rs @@ -5,8 +5,8 @@ use sqlquack_syntax::expressions::names::models::Identifier; use crate::keywords::models::KeywordCategory; use crate::parse::constants::{EXPRESSION_KEYWORDS, QUERY_STARTS}; use crate::parse::errors::ParseError; -use crate::parse::models::Gap; use crate::scan::models::{Token, TokenKind}; +use sqlquack_syntax::Gap; pub(crate) type Parsing = Result; diff --git a/crates/sqlquack-syntax-postgres/src/parse/main/parse.rs b/crates/sqlquack-syntax-postgres/src/parse/main/parse.rs index bd8a35401..a79b61578 100644 --- a/crates/sqlquack-syntax-postgres/src/parse/main/parse.rs +++ b/crates/sqlquack-syntax-postgres/src/parse/main/parse.rs @@ -5,8 +5,8 @@ use sqlquack_syntax::Expression; use crate::parse::_helpers::tree::cursor::Parser; use crate::parse::_helpers::tree::nodes::command; use crate::parse::errors::ParseError; -use crate::parse::models::Parsed; use crate::scan::main::scan::scan; +use sqlquack_syntax::Parsed; /// Every statement of `sql`; a statement holding a construct the shared tree lacks is a `Command`. pub fn parse(sql: &str) -> Result { diff --git a/crates/sqlquack-syntax-postgres/src/parse/main/parse_strict.rs b/crates/sqlquack-syntax-postgres/src/parse/main/parse_strict.rs index 41fb9bbf2..7a0436d25 100644 --- a/crates/sqlquack-syntax-postgres/src/parse/main/parse_strict.rs +++ b/crates/sqlquack-syntax-postgres/src/parse/main/parse_strict.rs @@ -2,8 +2,8 @@ use crate::parse::errors::StrictFailure; use crate::parse::main::parse::parse; -use crate::parse::models::Parsed; use crate::scan::main::uncovered::uncovered; +use sqlquack_syntax::Parsed; /// `parse`'s statements, refused on any parse error, whatever its SQLSTATE, or uncovered byte. pub fn parse_strict(sql: &str) -> Result { diff --git a/crates/sqlquack-syntax-postgres/src/parse/mod.rs b/crates/sqlquack-syntax-postgres/src/parse/mod.rs index 2dbbea664..0863bf5a3 100644 --- a/crates/sqlquack-syntax-postgres/src/parse/mod.rs +++ b/crates/sqlquack-syntax-postgres/src/parse/mod.rs @@ -4,7 +4,6 @@ pub(crate) mod _helpers; pub(crate) mod constants; pub mod errors; pub mod main; -pub mod models; #[cfg(test)] mod tests; diff --git a/crates/sqlquack-syntax-postgres/src/parse/models.rs b/crates/sqlquack-syntax-postgres/src/parse/models.rs deleted file mode 100644 index b3ba23501..000000000 --- a/crates/sqlquack-syntax-postgres/src/parse/models.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! What a parse yields: the statements and the gaps that made a statement opaque. - -use sqlquack_syntax::Expression; - -/// A construct the shared tree cannot hold: its statement became a `Command` holding its text. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Gap { - /// A stable name for the construct, such as `tablesample` or `merge`. - pub construct: &'static str, - /// Byte range in the input where it starts and where parsing it ended. - pub start: usize, - pub end: usize, -} - -/// Every statement of the input, in order, and the gaps behind any `Command`. -#[derive(Clone, Debug, PartialEq)] -pub struct Parsed { - pub statements: Vec, - pub gaps: Vec, -} diff --git a/crates/sqlquack-syntax/src/lib.rs b/crates/sqlquack-syntax/src/lib.rs index 06f96d57b..d5ace9b26 100644 --- a/crates/sqlquack-syntax/src/lib.rs +++ b/crates/sqlquack-syntax/src/lib.rs @@ -23,6 +23,8 @@ pub use crate::lossless::models::TokenKind; pub use crate::modes::errors::StrictFailure; pub use crate::modes::main::parse_strict::parse_strict; pub use crate::modes::main::parse_tolerant::parse_tolerant; +pub use crate::modes::models::Gap; +pub use crate::modes::models::Parsed; pub use crate::modes::models::TolerantParse; pub use crate::modes::models::TolerantStatement; pub use crate::parser::main::parse_statements::parse_statements; diff --git a/crates/sqlquack-syntax/src/modes/models.rs b/crates/sqlquack-syntax/src/modes/models.rs index cd6c7ed66..d45436f63 100644 --- a/crates/sqlquack-syntax/src/modes/models.rs +++ b/crates/sqlquack-syntax/src/modes/models.rs @@ -24,3 +24,20 @@ pub struct TolerantParse { /// Each statement between top-level semicolons, in order. pub statements: Vec, } + +/// A construct a native parser's tree cannot hold: its statement became a `Command` holding its text. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Gap { + /// A stable name for the construct, such as `tablesample` or `merge`. + pub construct: &'static str, + /// Byte range in the input where it starts and where parsing it ended. + pub start: usize, + pub end: usize, +} + +/// Every statement a native dialect parser read, in order, and the gaps behind any `Command`. +#[derive(Clone, Debug, PartialEq)] +pub struct Parsed { + pub statements: Vec, + pub gaps: Vec, +}