Skip to content
Merged
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
10 changes: 9 additions & 1 deletion crates/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,15 @@ The same check fails when the workspace version, the `pyproject.toml` version an
`import(MODULE)?.getattr("name")`, or written as a `("sqlbuild.module", "name")` pair;
- module paths are never built at runtime: a `format!` or `concat!` template may not start with
`sqlbuild.` or with a `{..PACKAGE..}.`/`{..MODULE..}.` constant. Write the complete module
path as a constant instead.
path as a constant instead;
- every row of the host table (`bindings/_helpers/native_host/`) must resolve as an attribute of
`sqlbuild._native_host`.

Staying-Python entry points (adapter, user code, executor and runtime, third-party and public
facade) are reached through the host table and `native_host::host::entry`, which imports only
`sqlbuild._native_host`. Classes, enums, errors and functions `sqlbuild._native` registers are
reached through the typed registry (`records::registry` with `RecordId`, `ErrorId` and
`NativeAttr`), never by module path.

## Compiler

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Expected results and assertions must not define or read each other, even through helpers.

use std::collections::{BTreeSet, HashMap, HashSet, VecDeque};
use std::collections::{BTreeSet, HashMap};

use sqlbuild_core::graph_walk::main::breadth_first_path::breadth_first_path;

use polyglot_sql::{Dialect, DialectType, Expression, ExpressionWalk};
use serde_json::Value;
Expand Down Expand Up @@ -246,20 +248,12 @@ fn dependency_path(
prohibited: &BTreeSet<String>,
dependencies: &HashMap<String, Vec<String>>,
) -> Option<Vec<String>> {
let mut pending: VecDeque<Vec<String>> = VecDeque::from([vec![origin.to_owned()]]);
let mut visited: HashSet<String> = HashSet::from([origin.to_owned()]);
while let Some(path) = pending.pop_front() {
let last: &String = path.last()?;
for dependency in dependencies.get(last).into_iter().flatten() {
let mut candidate: Vec<String> = path.clone();
candidate.push(dependency.clone());
if prohibited.contains(dependency) {
return Some(candidate);
}
if visited.insert(dependency.clone()) {
pending.push_back(candidate);
}
}
}
None
breadth_first_path(
origin.to_owned(),
(
|last: &String| dependencies.get(last).into_iter().flatten().cloned(),
|dependency: &String| prohibited.contains(dependency),
|_: &String| true,
),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

use std::collections::{BTreeMap, HashMap, HashSet};

use sqlbuild_core::graph_walk::main::depth_first_postorder::depth_first_postorder;

use polyglot_sql::Dialect;

use crate::compiler::_helpers::sql_tests::cte_rename::defined_cte_keys;
Expand Down Expand Up @@ -103,36 +105,6 @@ pub(crate) struct ScopeGraph {
rewritten: HashMap<String, String>,
}

/// Graph nodes in dependency-first order, built by depth-first traversal.
struct ScopeOrder<'a> {
graph: &'a ScopeGraph,
visited: HashSet<ScopeNode>,
ordered: Vec<ScopeNode>,
through_mocks: bool,
}

impl ScopeOrder<'_> {
fn visit_tokens(&mut self, tokens: &[String]) {
for token in tokens {
if let Some(node) = self.graph.node(token) {
if !self.through_mocks && matches!(node, ScopeNode::Mock(_)) {
continue;
}
self.visit(node);
}
}
}

fn visit(&mut self, node: ScopeNode) {
if !self.visited.insert(node) {
return;
}
let graph = self.graph;
self.visit_tokens(graph.tokens(node));
self.ordered.push(node);
}
}

impl ScopeGraph {
/// Tokenize one test's helpers and mocks and decide how its helpers are named and placed.
pub(crate) fn new(request: &ScopeRequest<'_>) -> Result<Self, String> {
Expand Down Expand Up @@ -294,14 +266,14 @@ impl ScopeGraph {
}

fn closure(&self, tokens: &[String], through_mocks: bool) -> Vec<ScopeNode> {
let mut order = ScopeOrder {
graph: self,
visited: HashSet::new(),
ordered: Vec::new(),
through_mocks,
let entered = |tokens: &[String]| -> Vec<ScopeNode> {
tokens
.iter()
.filter_map(|token| self.node(token))
.filter(|node| through_mocks || !matches!(node, ScopeNode::Mock(_)))
.collect()
};
order.visit_tokens(tokens);
order.ordered
depth_first_postorder(entered(tokens), |node| entered(self.tokens(*node)))
}

/// Indexes of the helpers the compiler found the test's expected rows and assertions read.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
//! Python's `report_mocks_reading_referencing_helpers`: P013s for mocks reading referencing helpers.

use std::collections::HashSet;
use std::convert::Infallible;

use polyglot_sql::expressions::With;
use polyglot_sql::{
ComplexityGuardOptions, Dialect, DialectType, Expression, ExpressionWalk, ParseOptions,
};
use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach;
use sqlbuild_core::graph_walk::models::VisitOrder;
use sqlbuild_core::graph_walk::types::NodeSet;
use sqlbuild_core::text::main::ignorecase_key::ignorecase_key;
use sqlbuild_core::text::main::is_word_char::is_word_char;
use sqlbuild_sqltext::sql_references::main::extract_sql_references::extract_sql_references;
Expand Down Expand Up @@ -111,30 +115,18 @@ impl CteGraph<'_> {

/// Python's `reachable_cte_keys`: breadth first, roots included, in visit order.
fn reachable(&self, roots: &[String]) -> Vec<String> {
let mut pending: Vec<String> = Vec::new();
for root in roots {
if self.cte(root).is_some() {
pending.push(root.clone());
}
}
let mut visited: Vec<String> = Vec::new();
for key in &pending {
if !visited.contains(key) {
visited.push(key.clone());
}
}
let mut cursor = 0;
while cursor < pending.len() {
let key = pending[cursor].clone();
cursor += 1;
for dependency in self.reads(&key) {
if !visited.contains(dependency) {
visited.push(dependency.clone());
pending.push(dependency.clone());
}
}
let known: Vec<&String> = roots
.iter()
.filter(|root| self.cte(root).is_some())
.collect();
let mut visited: VisitOrder<&String> = VisitOrder::default();
for root in &known {
let Ok(_) = visited.insert_new(root);
}
visited
let Ok(()) = breadth_first_reach(known, None, &mut visited, |key| {
Ok::<_, Infallible>(self.reads(key))
});
visited.order.into_iter().cloned().collect()
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Coarse project-level planning for SQL-native test artifacts.

use std::collections::{BTreeMap, HashMap, HashSet};
use std::convert::Infallible;
use std::sync::{Arc, Mutex, OnceLock, PoisonError};
use std::time::Instant;

Expand All @@ -9,6 +10,8 @@ use rayon::iter::{IntoParallelIterator, ParallelIterator};
use regex::Regex;
#[cfg(test)]
use serde::Serialize;
use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach;
use sqlbuild_core::graph_walk::main::depth_first_postorder::depth_first_postorder;

use crate::compiler::_helpers::sql_tests::cte_rename::defined_cte_keys;
use crate::compiler::_helpers::sql_tests::cte_slices::{SliceDialect, strip_statement_terminators};
Expand Down Expand Up @@ -256,22 +259,12 @@ impl TextualChain {
/// Return the generated CTEs needed by `roots`, dependencies first, each exactly once.
fn closure_ctes(&self, roots: &[String], include_roots: bool) -> Vec<(String, String)> {
let mut needed: HashSet<String> = HashSet::new();
let mut stack: Vec<String> = Vec::new();
for root in roots {
if include_roots {
stack.push(root.clone());
} else if let Some(dependencies) = self.dependencies.get(root) {
stack.extend(dependencies.iter().cloned());
}
}
while let Some(name) = stack.pop() {
if !needed.insert(name.clone()) {
continue;
}
if let Some(dependencies) = self.dependencies.get(&name) {
stack.extend(dependencies.iter().cloned());
}
if include_roots {
needed.extend(roots.iter().cloned());
}
let Ok(()) = breadth_first_reach(roots.iter().cloned(), None, &mut needed, |name| {
Ok::<_, Infallible>(self.dependencies.get(name).into_iter().flatten().cloned())
});
let mut ctes: Vec<(String, String)> = with_unique_ctes(
Vec::new(),
self.order
Expand Down Expand Up @@ -1310,60 +1303,27 @@ fn classify_fixtures(
}

fn topo_sort_model_chain(request: TopoSortRequest<'_>) -> Vec<String> {
fn visit(
name: &str,
models: &HashMap<String, ModelInputOwned>,
overrides: &BTreeMap<String, String>,
mock_refs: &BTreeMap<String, String>,
patterns: &SqlTestPatterns,
visited: &mut HashSet<String>,
ordered: &mut Vec<String>,
) {
if !visited.insert(name.to_string()) {
return;
}
if let Some(model) = models.get(name) {
let mut dependencies = if let Some(override_sql) = overrides.get(name) {
marker_names(&patterns.reference, &patterns.lexical, override_sql)
} else {
model.model_dependencies.clone()
};
dependencies.retain(|dependency| {
!mock_refs.contains_key(dependency) && models.contains_key(dependency)
});
dependencies.sort();
dependencies.dedup();
for dependency in dependencies {
visit(
&dependency,
models,
overrides,
mock_refs,
patterns,
visited,
ordered,
);
}
}
ordered.push(name.to_string());
}

let mut roots = request.expected_names.to_vec();
let mut roots: Vec<String> = request.expected_names.to_vec();
roots.sort();
let mut visited: HashSet<String> = HashSet::new();
let mut ordered: Vec<String> = Vec::new();
for root in roots {
visit(
&root,
request.models,
request.overrides,
request.mock_refs,
request.patterns,
&mut visited,
&mut ordered,
);
}
ordered
depth_first_postorder(roots, |name| {
let Some(model) = request.models.get(name) else {
return Vec::new();
};
let mut dependencies: Vec<String> = match request.overrides.get(name) {
Some(override_sql) => marker_names(
&request.patterns.reference,
&request.patterns.lexical,
override_sql,
),
None => model.model_dependencies.clone(),
};
dependencies.retain(|dependency| {
!request.mock_refs.contains_key(dependency) && request.models.contains_key(dependency)
});
dependencies.sort();
dependencies.dedup();
dependencies
})
}

fn analyze_and_resolve_sql(
Expand Down
60 changes: 24 additions & 36 deletions crates/sqlbuild-analysis/src/dag_artifact/_helpers/selection.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! Breadth-first closures and directed path nodes over string-keyed DAG edges.
//! Closures, directed path nodes and selector resolution over string-keyed DAG edges.

use std::collections::{BTreeMap, BTreeSet, VecDeque};
use std::collections::{BTreeMap, BTreeSet};
use std::convert::Infallible;

use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach;
use sqlbuild_core::graph_walk::main::directed_path_nodes::directed_path_nodes;
use sqlbuild_core::text::main::python_isspace::python_isspace;
use sqlbuild_core::text::main::python_str_repr::python_str_repr;

Expand All @@ -23,48 +26,33 @@ pub(crate) fn closure(
edges: &Adjacency,
include_starts: bool,
) -> BTreeSet<String> {
let mut visited: BTreeSet<String> = if include_starts {
starts.clone()
} else {
BTreeSet::new()
};
let mut frontier: VecDeque<&String> = starts.iter().collect();
while let Some(current) = frontier.pop_front() {
for neighbor in edges.get(current).into_iter().flatten() {
if visited.insert(neighbor.clone()) {
frontier.push_back(neighbor);
}
}
let mut visited: BTreeSet<&String> = BTreeSet::new();
if include_starts {
visited.extend(starts);
}
visited
let Ok(()) = breadth_first_reach(starts.iter(), None, &mut visited, |current| {
Ok::<_, Infallible>(edges.get(*current).into_iter().flatten())
});
visited.into_iter().cloned().collect()
}

/// Ids on directed paths from `start` to `end`, or `None` when `end` is not downstream.
pub(crate) fn path_nodes(
(start, end): (&str, &str),
(upstream, downstream): (&Adjacency, &Adjacency),
) -> Option<BTreeSet<String>> {
let reachable: BTreeSet<String> =
closure(&BTreeSet::from([start.to_owned()]), downstream, false);
if !reachable.contains(end) {
return None;
}
let mut upstream_of_end: BTreeSet<String> = BTreeSet::new();
let mut stack: Vec<String> = vec![end.to_owned()];
while let Some(current) = stack.pop() {
if !upstream_of_end.insert(current.clone()) {
continue;
}
for parent in upstream.get(&current).into_iter().flatten() {
if reachable.contains(parent) || parent == start {
stack.push(parent.clone());
}
}
}
let mut nodes: BTreeSet<String> = reachable.intersection(&upstream_of_end).cloned().collect();
nodes.insert(start.to_owned());
nodes.insert(end.to_owned());
Some(nodes)
let (start, end): (String, String) = (start.to_owned(), end.to_owned());
let (mut reachable, mut path): (BTreeSet<&String>, BTreeSet<&String>) =
(BTreeSet::new(), BTreeSet::new());
let Ok(found) = directed_path_nodes(
(&start, &end),
(
|current: &&String| Ok(downstream.get(*current).into_iter().flatten()),
|current: &&String| Ok(upstream.get(*current).into_iter().flatten()),
),
(&mut reachable, &mut path),
);
found.then(|| path.into_iter().cloned().collect())
}

pub(crate) fn resolve_expression(
Expand Down
Loading
Loading