Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 31 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,12 @@ sqlbuild-state = { path = "crates/sqlbuild-state" }
# SQL lint Rules, quality Rules and the formatter.
sqlclean = { git = "https://github.com/sqlstack-io/sqlclean.git", rev = "cf79656d247277a30bc81ef39b6dee420e721221" }
# Scopes, name resolution, types and lineage for the native analysis route.
sqlquack-semantics = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "19c51fa29ab01dbef9dddd18e8bfc406ed8e65f5" }
sqlquack-semantics = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "87df54081f4471615780943855015e142a348689" }
# The lossless SQL tokenizer behind query fingerprints.
sqlquack-syntax = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "19c51fa29ab01dbef9dddd18e8bfc406ed8e65f5" }
sqlquack-syntax-postgres = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "19c51fa29ab01dbef9dddd18e8bfc406ed8e65f5" }
sqlquack-syntax = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "87df54081f4471615780943855015e142a348689" }
sqlquack-syntax-duckdb = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "87df54081f4471615780943855015e142a348689" }
sqlquack-syntax-postgres = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "87df54081f4471615780943855015e142a348689" }
sqlquack-syntax-sqlite = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "87df54081f4471615780943855015e142a348689" }
stacker = "0.1"
syn = { version = "2", features = ["full", "visit", "extra-traits"] }
subtle = "2"
Expand Down
2 changes: 2 additions & 0 deletions crates/sqlbuild-analysis/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ sqlbuild-sqltext.workspace = true
sqlclean.workspace = true
sqlquack-semantics.workspace = true
sqlquack-syntax.workspace = true
sqlquack-syntax-duckdb.workspace = true
sqlquack-syntax-postgres.workspace = true
sqlquack-syntax-sqlite.workspace = true

[lints]
workspace = true
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//! DuckDB's native module: SQLQuack's native DuckDB parser reads its SQL. Validation only.

use sqlquack_semantics::dialects::constants::DUCKDB;
use sqlquack_semantics::dialects::types::DialectProfile;
use sqlquack_syntax_duckdb::{Parsed, StrictFailure, parse_strict};

use crate::analysis_route::_helpers::type_spelling::respelt;
use crate::analysis_route::constants::{
DUCKDB_DIALECT, DUCKDB_TYPE_SPELLING, UNQUOTED_STUB_PREFIX, UNSCANNED_CONSTRUCT,
};
use crate::analysis_route::models::{DuckDbDialect, ParserGap};
use crate::analysis_route::types::NativeDialect;
use crate::type_system::main::normalize_text_type::normalize_text_type;
use crate::type_system::models::{NormalizedType, TypeDialect};

impl NativeDialect for DuckDbDialect {
fn name(&self) -> &'static str {
DUCKDB_DIALECT
}

fn stub_prefix(&self) -> &'static str {
UNQUOTED_STUB_PREFIX
}

fn parse(&self, sql: &str) -> Result<Parsed, ParserGap> {
parse_strict(sql).map_err(|failure| match failure {
StrictFailure::Parse(error) => ParserGap {
construct: error.message.clone(),
message: error.message,
offset: error.position,
syntax: error.syntax,
},
StrictFailure::Uncovered { position } => ParserGap {
construct: UNSCANNED_CONSTRUCT.to_owned(),
message: UNSCANNED_CONSTRUCT.to_owned(),
offset: position,
syntax: false,
},
})
}

fn profile(&self) -> &'static dyn DialectProfile {
&DUCKDB
}

/// The type as Polyglot normalised it, or `None` (unknown) where the text fallback can't match it.
fn normalize_type(&self, spelling: &str) -> Option<NormalizedType> {
respelt(spelling, &DUCKDB_TYPE_SPELLING)
.map(|respelt| normalize_text_type(&respelt, Some(TypeDialect::DuckDb)))
}
}
4 changes: 4 additions & 0 deletions crates/sqlbuild-analysis/src/analysis_route/_helpers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
pub(crate) mod clickhouse_types;
pub(crate) mod duckdb_dialect;
pub(crate) mod native_analysis;
pub(crate) mod snowflake_dialect;
pub(crate) mod sqlite_dialect;
pub(crate) mod type_spelling;
pub(crate) mod whole_gap;
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//! Snowflake's native module: SQLQuack's Snowflake parser reads its SQL. Validation only.

use sqlquack_semantics::dialects::constants::SNOWFLAKE;
use sqlquack_semantics::dialects::types::DialectProfile;
use sqlquack_syntax::{
Dialect, Error, Expression, ParseOptions, Parsed, StrictFailure, TokenizeFailure, parse_strict,
};

use crate::analysis_route::_helpers::type_spelling::respelt;
use crate::analysis_route::constants::{
SNOWFLAKE_DIALECT, SNOWFLAKE_TYPE_SPELLING, UNQUOTED_STUB_PREFIX, UNSCANNED_CONSTRUCT,
};
use crate::analysis_route::models::{ParserGap, SnowflakeDialect};
use crate::analysis_route::types::NativeDialect;
use crate::type_system::main::normalize_text_type::normalize_text_type;
use crate::type_system::models::{NormalizedType, TypeDialect};

impl NativeDialect for SnowflakeDialect {
fn name(&self) -> &'static str {
SNOWFLAKE_DIALECT
}

fn stub_prefix(&self) -> &'static str {
UNQUOTED_STUB_PREFIX
}

fn parse(&self, sql: &str) -> Result<Parsed, ParserGap> {
shared_parsed(sql, Dialect::Snowflake)
}

fn profile(&self) -> &'static dyn DialectProfile {
&SNOWFLAKE
}

/// The type as Polyglot normalised it, or `None` (unknown) where the text fallback can't match it.
fn normalize_type(&self, spelling: &str) -> Option<NormalizedType> {
respelt(spelling, &SNOWFLAKE_TYPE_SPELLING)
.map(|respelt| normalize_text_type(&respelt, Some(TypeDialect::Snowflake)))
}
}

/// Every statement the shared parser reads from `sql` in `dialect`, or its first refusal, never marked syntax.
pub(crate) fn shared_parsed(sql: &str, dialect: Dialect) -> Result<Parsed, ParserGap> {
let statements: Vec<Expression> = parse_strict(sql, dialect, &ParseOptions::default())
.map_err(|failure| match failure {
StrictFailure::Tokens(
TokenizeFailure::Uncovered { start, .. } | TokenizeFailure::Overlap { start, .. },
) => ParserGap {
construct: UNSCANNED_CONSTRUCT.to_owned(),
message: UNSCANNED_CONSTRUCT.to_owned(),
offset: start,
syntax: false,
},
StrictFailure::Parse(error)
| StrictFailure::Tokens(TokenizeFailure::Tokenizer(error)) => {
let offset: usize = error.start().unwrap_or_default();
let message: String = unplaced(error);
ParserGap {
construct: message.clone(),
message,
offset,
syntax: false,
}
}
})?;
Ok(Parsed {
statements,
gaps: Vec::new(),
})
}

/// The parser's message without its own line and column, which P026 reports from the offset.
fn unplaced(error: Error) -> String {
match error {
Error::Tokenize { message, .. }
| Error::Parse { message, .. }
| Error::Syntax { message, .. } => message,
other => other.to_string(),
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//! SQLite's native module: SQLQuack's native SQLite parser reads its SQL. Validation only.

use sqlquack_semantics::dialects::constants::SQLITE;
use sqlquack_semantics::dialects::types::DialectProfile;
use sqlquack_syntax_sqlite::{Parsed, StrictFailure, parse_strict};

use crate::analysis_route::_helpers::type_spelling::respelt;
use crate::analysis_route::constants::{
SQLITE_DIALECT, SQLITE_TYPE_SPELLING, UNQUOTED_STUB_PREFIX, UNSCANNED_CONSTRUCT,
};
use crate::analysis_route::models::{ParserGap, SqliteDialect};
use crate::analysis_route::types::NativeDialect;
use crate::type_system::main::normalize_text_type::normalize_text_type;
use crate::type_system::models::NormalizedType;

impl NativeDialect for SqliteDialect {
fn name(&self) -> &'static str {
SQLITE_DIALECT
}

fn stub_prefix(&self) -> &'static str {
UNQUOTED_STUB_PREFIX
}

fn parse(&self, sql: &str) -> Result<Parsed, ParserGap> {
parse_strict(sql).map_err(|failure| match failure {
StrictFailure::Parse(error) => ParserGap {
construct: error.message.clone(),
message: error.message,
offset: error.position,
syntax: error.syntax,
},
StrictFailure::Uncovered { position } => ParserGap {
construct: UNSCANNED_CONSTRUCT.to_owned(),
message: UNSCANNED_CONSTRUCT.to_owned(),
offset: position,
syntax: false,
},
})
}

fn profile(&self) -> &'static dyn DialectProfile {
&SQLITE
}

/// The type as Polyglot normalised it, or `None` (unknown) where the text fallback can't match it.
fn normalize_type(&self, spelling: &str) -> Option<NormalizedType> {
respelt(spelling, &SQLITE_TYPE_SPELLING).map(|respelt| normalize_text_type(&respelt, None))
}
}
Loading