Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c9d31bd
fix: release the compute log lock before rejecting a write to a finis…
kvlonge Oct 19, 2026
c10b78c
test: fail a hung E2E command with every thread's stack instead of st…
kvlonge Oct 19, 2026
3deb6a2
refactor: share project scaffolding and rule listing text outside the…
kvlonge Oct 19, 2026
cf463ff
feat: run help, usage errors, init, skills, clean and rules list/show…
kvlonge Oct 19, 2026
986b63d
docs: note that Recall does not record commands sqb answers without P…
kvlonge Oct 19, 2026
2ca8a36
fix: resolve native project paths and the home directory as Python do…
kvlonge Oct 19, 2026
8ccb01f
fix: report lost stdout, Ctrl+C, init failures and the version as the…
kvlonge Oct 19, 2026
27fef08
Merge branch 'main' into feat/python-free-commands
kvlonge Oct 19, 2026
3e41427
Merge branch 'main' into fix/sqb-test-hang
kvlonge Oct 19, 2026
273df0c
chore(deps): bump fensu to 0.27
kvlonge Oct 19, 2026
62334d9
chore: move function name data under the crate source
kvlonge Oct 19, 2026
912815b
refactor: name large tuple types
kvlonge Oct 19, 2026
7c7bf79
chore: enable fensu dead-code enforcement and remove dead code
kvlonge Oct 19, 2026
738e9bf
refactor: name large tuple types introduced on main
kvlonge Oct 19, 2026
2655063
chore: satisfy fensu 0.27 in code added on main
kvlonge Oct 19, 2026
2dd4362
fix(clickhouse): accept ClickHouse Cloud SharedMergeTree engines
kvlonge Oct 19, 2026
aaa2ae6
Merge remote-tracking branch 'origin/fix/sqb-test-hang' into feat/com…
kvlonge Oct 19, 2026
396b190
Merge remote-tracking branch 'origin/fix/clickhouse-shared-merge-tree…
kvlonge Oct 19, 2026
410afe3
Merge remote-tracking branch 'origin/feat/python-free-commands' into …
kvlonge Oct 19, 2026
4984e9c
Merge remote-tracking branch 'origin/main' into feat/combined-batch
kvlonge Oct 19, 2026
c438f70
test: pin buffered stdout in broken-pipe parity tests and list the co…
kvlonge Oct 20, 2026
679c41a
Merge branch 'main' into feat/combined-batch
kvlonge Oct 20, 2026
bf08fac
Merge branch 'main' into feat/combined-batch
kvlonge Oct 20, 2026
76d7083
Merge branch 'main' into feat/combined-batch
kvlonge Oct 20, 2026
8fff721
ci: give native-building verify jobs 45 minutes
kvlonge Oct 20, 2026
d03c5d0
fix(sqlite): switch a new database file to WAL one connection at a time
kvlonge Oct 20, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/build-distributions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ jobs:
name: Build wheel (${{ matrix.platform.name }})
needs: source
runs-on: ${{ matrix.platform.runner }}
timeout-minutes: 45
timeout-minutes: 75
strategy:
fail-fast: false
matrix:
Expand Down
20 changes: 10 additions & 10 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ jobs:
needs: [changes, lockfile]
if: needs.changes.outputs.checks == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 30
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
Expand Down Expand Up @@ -342,7 +342,7 @@ jobs:
needs: [changes, lockfile]
if: needs.changes.outputs.compiler_differential == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 30
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
Expand Down Expand Up @@ -379,7 +379,7 @@ jobs:
needs: [changes, lockfile]
if: needs.changes.outputs.compiler_differential == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 20
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -415,7 +415,7 @@ jobs:
needs: [changes, lockfile]
if: needs.changes.outputs.compiler_differential == 'true'
runs-on: windows-2025
timeout-minutes: 40
timeout-minutes: 45
defaults:
run:
shell: bash
Expand Down Expand Up @@ -468,7 +468,7 @@ jobs:
needs: [changes, lockfile]
if: needs.changes.outputs.format_performance == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 30
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
Expand Down Expand Up @@ -499,7 +499,7 @@ jobs:
# These budgets are calibrated for the 8-vCPU Blacksmith runner.
if: false
runs-on: ubuntu-24.04
timeout-minutes: 15
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
Expand All @@ -524,7 +524,7 @@ jobs:
needs: [changes, lockfile]
if: needs.changes.outputs.performance == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 15
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -572,7 +572,7 @@ jobs:
if: needs.changes.outputs.performance == 'true'
runs-on: ubuntu-24.04
# The 3k and 10k cells also run the delta edit-loop guard, about five minutes at 10k.
timeout-minutes: 30
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -717,7 +717,7 @@ jobs:
needs: [changes, lockfile]
if: needs.changes.outputs.performance == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 30
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -788,7 +788,7 @@ jobs:
# These budgets are calibrated for the 8-vCPU Blacksmith runner.
if: false
runs-on: ubuntu-24.04
timeout-minutes: 15
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ ruff_text_size = "=0.0.4"
rustc-hash = "2"
# Recall's local store: SQLite compiled in, so every build has the same engine version.
rusqlite = { version = "0.37", features = ["bundled"] }
rustix = { version = "1.1.4", default-features = false, features = ["fs", "std"] }
rustix = { version = "1.1.4", default-features = false, features = ["fs", "std", "termios"] }
saphyr-parser = "0.1"
regex = "1"
serde = { version = "1", features = ["derive"] }
Expand Down
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
Loading
Loading