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
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::assembly::analysis_session::models::{
LineageRow, ModelAnalysis, ModelOutcome, ModelRequest, SessionRequest,
};
use crate::assembly::analysis_session::types::{Pairs, Shapes};
use crate::semantic_validation::types::DiagnosticRow;
use crate::semantic_validation::models::DiagnosticRow;

const LINEAGE_NATIVE: u64 = 0;
const LINEAGE_FACTS: u64 = 1;
Expand Down Expand Up @@ -454,7 +454,16 @@ pub(crate) fn encode_outcome(
encoder.flag(analysis.has_star);
encoder.flag(analysis.star_resolved);
encoder.uint(analysis.binding_diagnostics.len() as u64);
for (code, message, line, column, start, end, severity) in &analysis.binding_diagnostics {
for DiagnosticRow {
code,
message,
line,
column,
start,
end,
severity,
} in &analysis.binding_diagnostics
{
encoder.text(code);
encoder.text(message);
for position in [line, column, start, end] {
Expand Down Expand Up @@ -529,15 +538,15 @@ pub(crate) fn decode_outcome(
let diagnostic_count: usize = decoder.index()?;
let mut binding_diagnostics: Vec<DiagnosticRow> = Vec::new();
for _ in 0..diagnostic_count {
binding_diagnostics.push((
decoder.text()?,
decoder.text()?,
decoder.optional_index()?,
decoder.optional_index()?,
decoder.optional_index()?,
decoder.optional_index()?,
decoder.text()?,
));
binding_diagnostics.push(DiagnosticRow {
code: decoder.text()?,
message: decoder.text()?,
line: decoder.optional_index()?,
column: decoder.optional_index()?,
start: decoder.optional_index()?,
end: decoder.optional_index()?,
severity: decoder.text()?,
});
}
let binding_validated: bool = decoder.flag()?;
decoder.finish()?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ use crate::assembly::analysis_session::constants::{
use crate::assembly::analysis_session::models::{ColumnFact, LineageRow};
use crate::assembly::analysis_session::types::{Pairs, Shapes};
use crate::semantic_validation::main::diagnostics::binding_diagnostics;
use crate::semantic_validation::types::{DiagnosticRow, NormalizationRequest, Relations};
use crate::semantic_validation::models::DiagnosticRow;
use crate::semantic_validation::types::{NormalizationRequest, Relations};

const INVALID_BATCH: &str = "native compact query analysis returned an invalid batch response";
const INVALID_TEMPLATE: &str = "native compact query analysis returned an invalid template";
Expand Down Expand Up @@ -1424,15 +1425,15 @@ fn diagnostic_row(error: &Value) -> Result<Option<DiagnosticRow>, String> {
Some(Value::String(message)) if !message.is_empty() => message.clone(),
_ => DEFAULT_BINDING_MESSAGE.to_owned(),
};
Ok(Some((
code.to_owned(),
Ok(Some(DiagnosticRow {
code: code.to_owned(),
message,
position(error, "line")?,
position(error, "column")?,
position(error, "start")?,
position(error, "end")?,
severity.to_owned(),
)))
line: position(error, "line")?,
column: position(error, "column")?,
start: position(error, "start")?,
end: position(error, "end")?,
severity: severity.to_owned(),
}))
}

fn position(error: &Value, key: &str) -> Result<Option<usize>, String> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,12 @@ fn pivot_proof(
if truthy(pivot.get("unpivot")) {
return failure("UNPIVOT does not produce a dynamic column family".to_owned());
}
let (pivot_columns, aggregates, dynamic, columns_valid) = components(pivot, context.dialect);
let PivotComponents {
pivot_columns,
aggregates,
dynamic,
columns_valid,
} = components(pivot, context.dialect);
if !dynamic {
return failure(
"static pivot values must use ordinary exact column declarations".to_owned(),
Expand Down Expand Up @@ -309,24 +314,31 @@ fn fixed_names(
.collect()
}

/// Python's `_pivot_components` for one pivot clause.
struct PivotComponents<'a> {
pivot_columns: Vec<&'a str>,
aggregates: Vec<&'a Value>,
/// Whether the pivot values come from a dynamic source.
dynamic: bool,
/// Whether every pivot column resolved to a name.
columns_valid: bool,
}

/// Python's `_pivot_components`: pivot columns, aggregates, dynamic values, columns valid.
fn components<'a>(
pivot: &'a Map<String, Value>,
dialect: &str,
) -> (Vec<&'a str>, Vec<&'a Value>, bool, bool) {
fn components<'a>(pivot: &'a Map<String, Value>, dialect: &str) -> PivotComponents<'a> {
if SIMPLIFIED_PIVOT_DIALECTS.contains(&dialect) && truthy(pivot.get("using")) {
let expressions: Vec<&Value> = dict_list(pivot.get("expressions"));
let columns: Vec<&str> = expressions
.iter()
.filter_map(|expression| column_name(Some(expression)))
.collect();
let valid: bool = columns.len() == expressions.len();
return (
columns,
dict_list(pivot.get("using")),
!truthy(pivot.get("fields")),
valid,
);
return PivotComponents {
pivot_columns: columns,
aggregates: dict_list(pivot.get("using")),
dynamic: !truthy(pivot.get("fields")),
columns_valid: valid,
};
}
let fields: Vec<&Value> = dict_list(pivot.get("fields"));
let columns: Vec<&str> = fields
Expand All @@ -335,7 +347,12 @@ fn components<'a>(
.collect();
let dynamic: bool = fields.iter().any(|field| has_dynamic_values(field));
let valid: bool = columns.len() == fields.len();
(columns, dict_list(pivot.get("expressions")), dynamic, valid)
PivotComponents {
pivot_columns: columns,
aggregates: dict_list(pivot.get("expressions")),
dynamic,
columns_valid: valid,
}
}

/// Python's `_has_dynamic_value_source` for one pivot field.
Expand Down Expand Up @@ -651,13 +668,24 @@ fn failure(reason: String) -> PivotOutcome {
}

/// Python's `_families_equal` key for one family.
fn normalized(family: &DynamicFamily) -> (String, String, String, String, String, Option<&str>) {
(
casefold(&family.name),
casefold(&family.pivot_column),
casefold(&family.value_column),
casefold(&family.aggregate),
casefold(&family.data_type),
family.name_pattern.as_deref(),
)
#[derive(PartialEq, Eq)]
struct FamilyKey<'a> {
name: String,
pivot_column: String,
value_column: String,
aggregate: String,
data_type: String,
name_pattern: Option<&'a str>,
}

/// Python's `_families_equal` key for one family.
fn normalized(family: &DynamicFamily) -> FamilyKey<'_> {
FamilyKey {
name: casefold(&family.name),
pivot_column: casefold(&family.pivot_column),
value_column: casefold(&family.value_column),
aggregate: casefold(&family.aggregate),
data_type: casefold(&family.data_type),
name_pattern: family.name_pattern.as_deref(),
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ use crate::assembly::analysis_session::models::{
RelationTables, SessionModelFacts, SessionOutcome, SessionRequest, SessionStep,
};
use crate::assembly::analysis_session::types::{Pairs, Shapes};
use crate::semantic_validation::models::DiagnosticRow;
use crate::semantic_validation::models::ProjectCatalog;
use crate::semantic_validation::types::DiagnosticRow;

/// What an internal native failure of the session names.
const SESSION_CONTEXT: &str = "native model analysis";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use sqlbuild_cache::store::models::NativeStore;
use crate::assembly::analysis_session::_helpers::catalog_state::SessionCatalog;
use crate::assembly::analysis_session::_helpers::mappings::ShapeTable;
use crate::assembly::analysis_session::types::{NullabilityRuleFn, OutputSources, Pairs, Shapes};
use crate::semantic_validation::types::DiagnosticRow;
use crate::semantic_validation::models::DiagnosticRow;

/// One `ref`, `source`, `seed`, `table_fn` or `udf` call a model's SQL makes.
#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ pub(crate) fn described(outcome: &ModelOutcome) -> Vec<String> {
analysis
.binding_diagnostics
.iter()
.map(|row| row.0.as_str())
.map(|row| row.code.as_str())
.collect::<Vec<_>>()
));
lines
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
use std::collections::HashSet;

use crate::binding_locations::constants::{ERROR_SEVERITY, UNKNOWN_TABLE_INTERNAL_CODE};
use crate::binding_locations::models::{AuthoredSpan, BindingSource, LocatedDiagnostic};
use crate::binding_locations::models::{
AuthoredSpan, BindingSource, LocatedDiagnostic, SeenDiagnostic,
};
use crate::semantic_validation::models::DiagnosticRow;
use crate::semantic_validation::models::{BindingPositions, PositionInput};
use crate::semantic_validation::types::DiagnosticRow;

/// Each reported diagnostic in order; an error mapping a position is the mapper's message.
pub fn locate_binding_diagnostics(
Expand All @@ -17,9 +19,18 @@ pub fn locate_binding_diagnostics(
.find(source.query)
.map(|start| source.authored[..start].matches('\n').count());
let mut positions: Option<BindingPositions> = None;
let mut seen: HashSet<(String, String, Option<usize>, Option<usize>)> = HashSet::new();
let mut seen: HashSet<SeenDiagnostic> = HashSet::new();
let mut located: Vec<LocatedDiagnostic> = Vec::new();
for (code, message, line, column, start, end, severity) in rows {
for DiagnosticRow {
code,
message,
line,
column,
start,
end,
severity,
} in rows
{
if code == UNKNOWN_TABLE_INTERNAL_CODE {
continue;
}
Expand All @@ -39,7 +50,12 @@ pub fn locate_binding_diagnostics(
*column,
),
};
let key = (code.clone(), message.clone(), line, column);
let key = SeenDiagnostic {
code: code.clone(),
message: message.clone(),
line,
column,
};
if seen.contains(&key) && start.is_some() && severity == ERROR_SEVERITY {
continue;
}
Expand Down
11 changes: 10 additions & 1 deletion crates/sqlbuild-analysis/src/binding_locations/models.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
//! A model's authored SQL texts and the located diagnostics read from them.

use crate::semantic_validation::types::Expansion;
use crate::semantic_validation::models::Expansion;

/// A reported diagnostic's identity: an error repeating it at a known start is dropped.
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct SeenDiagnostic {
pub(crate) code: String,
pub(crate) message: String,
pub(crate) line: Option<usize>,
pub(crate) column: Option<usize>,
}

/// The texts one model's binding diagnostics are mapped back through.
#[derive(Clone, Copy, Debug)]
Expand Down
20 changes: 10 additions & 10 deletions crates/sqlbuild-analysis/src/binding_locations/tests/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::semantic_validation::types::DiagnosticRow;
use crate::semantic_validation::models::DiagnosticRow;

pub(super) const MODEL: &str =
"MODEL (\n description \"Orders\"\n);\n\nSELECT o.amount, o.missing\nFROM orders AS o";
Expand All @@ -11,15 +11,15 @@ fn row(
span: (Option<usize>, Option<usize>),
severity: &str,
) -> DiagnosticRow {
(
code.to_owned(),
message.to_owned(),
position.0,
position.1,
span.0,
span.1,
severity.to_owned(),
)
DiagnosticRow {
code: code.to_owned(),
message: message.to_owned(),
line: position.0,
column: position.1,
start: span.0,
end: span.1,
severity: severity.to_owned(),
}
}

fn missing_column() -> DiagnosticRow {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::semantic_validation::types::DiagnosticRow;
use crate::semantic_validation::models::DiagnosticRow;

/// `(code, line, column, (line, column, end line, end column))` of one reported diagnostic.
pub(super) type LocatedCase = (
Expand Down
52 changes: 41 additions & 11 deletions crates/sqlbuild-analysis/src/lineage/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,15 +149,41 @@ pub enum FastLineageOutcome {
UnknownDialect(String),
}

/// A lineage outcome flattened into its status, columns, star flag and detail.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LineageOutcomeParts<C> {
pub status: &'static str,
pub columns: Vec<C>,
pub has_star: bool,
pub detail: Option<String>,
}

impl<C> LineageOutcomeParts<C> {
fn new(status: &'static str, columns: Vec<C>, has_star: bool, detail: Option<String>) -> Self {
Self {
status,
columns,
has_star,
detail,
}
}
}

impl FastLineageOutcome {
/// `(status, columns, has_star, detail)`: detail is the parse error or the unknown dialect.
pub fn into_parts(self) -> (&'static str, Vec<LineageColumn>, bool, Option<String>) {
/// The status, columns and star flag; detail is the parse error or the unknown dialect.
pub fn into_parts(self) -> LineageOutcomeParts<LineageColumn> {
match self {
Self::StarColumns(columns) => ("star", columns, true, None),
Self::Built { columns, has_star } => ("built", columns, has_star, None),
Self::Omitted => ("omitted", Vec::new(), false, None),
Self::Unparsed(message) => ("unparsed", Vec::new(), false, Some(message)),
Self::UnknownDialect(name) => ("unknown_dialect", Vec::new(), false, Some(name)),
Self::StarColumns(columns) => LineageOutcomeParts::new("star", columns, true, None),
Self::Built { columns, has_star } => {
LineageOutcomeParts::new("built", columns, has_star, None)
}
Self::Omitted => LineageOutcomeParts::new("omitted", Vec::new(), false, None),
Self::Unparsed(message) => {
LineageOutcomeParts::new("unparsed", Vec::new(), false, Some(message))
}
Self::UnknownDialect(name) => {
LineageOutcomeParts::new("unknown_dialect", Vec::new(), false, Some(name))
}
}
}
}
Expand Down Expand Up @@ -217,11 +243,15 @@ pub enum RichLineageOutcome {
}

impl RichLineageOutcome {
/// `(status, columns, has_star, detail)`: detail is the polyglot error.
pub fn into_parts(self) -> (&'static str, Vec<RichLineageColumn>, bool, Option<String>) {
/// The status, columns and star flag; detail is the polyglot error.
pub fn into_parts(self) -> LineageOutcomeParts<RichLineageColumn> {
match self {
Self::Built { columns, has_star } => ("built", columns, has_star, None),
Self::Skipped(message) => ("skipped", Vec::new(), false, Some(message)),
Self::Built { columns, has_star } => {
LineageOutcomeParts::new("built", columns, has_star, None)
}
Self::Skipped(message) => {
LineageOutcomeParts::new("skipped", Vec::new(), false, Some(message))
}
}
}
}
Expand Down
Loading
Loading