Skip to content

Commit bb1c502

Browse files
authored
feat: answer simple commands in sqb, upgrade fensu and accept SharedMergeTree (#1170)
* fix: release the compute log lock before rejecting a write to a finished capture A write that reached a capture already marked complete built its rejection while still holding the storage lock. Building it imports Python, which can release the GIL; the main thread then pruning the logs waited for that lock while holding the GIL, so neither could continue and the command never exited. The lock is now released before the rejection is raised. * test: fail a hung E2E command with every thread's stack instead of stalling the job run_sqb now gives each command ten minutes. A command still running then has its process group aborted with Python's fault handler enabled, so the failing test reports every thread's stack, and the group is killed. Standard input ends at once when a test passes none. * refactor: share project scaffolding and rule listing text outside the Python bindings * feat: run help, usage errors, init, skills, clean and rules list/show in sqb without Python * docs: note that Recall does not record commands sqb answers without Python * fix: resolve native project paths and the home directory as Python does on Windows * fix: report lost stdout, Ctrl+C, init failures and the version as the Python CLI does * chore(deps): bump fensu to 0.27 * chore: move function name data under the crate source * refactor: name large tuple types * chore: enable fensu dead-code enforcement and remove dead code * refactor: name large tuple types introduced on main * chore: satisfy fensu 0.27 in code added on main * fix(clickhouse): accept ClickHouse Cloud SharedMergeTree engines Shared* MergeTree engines now pass model validation like their MergeTree counterparts. Replicated* engines stay rejected. Support is best effort and untested on ClickHouse Cloud. * test: pin buffered stdout in broken-pipe parity tests and list the compute log logger * ci: give native-building verify jobs 45 minutes * fix(sqlite): switch a new database file to WAL one connection at a time Opening several connections to a new file at once could fail with A702 at once: SQLite reports the WAL switch as busy without waiting while another connection switches the same file. The switch now goes through the per-file writer gate. Also give wheel builds 75 minutes, since the Intel macOS build reached the 45-minute limit.
1 parent 92a526c commit bb1c502

281 files changed

Lines changed: 9000 additions & 3022 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/build-distributions.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ jobs:
8888
name: Build wheel (${{ matrix.platform.name }})
8989
needs: source
9090
runs-on: ${{ matrix.platform.runner }}
91-
timeout-minutes: 45
91+
timeout-minutes: 75
9292
strategy:
9393
fail-fast: false
9494
matrix:

‎.github/workflows/verify.yml‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ jobs:
255255
needs: [changes, lockfile]
256256
if: needs.changes.outputs.checks == 'true'
257257
runs-on: ubuntu-24.04
258-
timeout-minutes: 30
258+
timeout-minutes: 45
259259
steps:
260260
- uses: actions/checkout@v4
261261
- uses: astral-sh/setup-uv@v5
@@ -342,7 +342,7 @@ jobs:
342342
needs: [changes, lockfile]
343343
if: needs.changes.outputs.compiler_differential == 'true'
344344
runs-on: ubuntu-24.04
345-
timeout-minutes: 30
345+
timeout-minutes: 45
346346
steps:
347347
- uses: actions/checkout@v4
348348
- uses: astral-sh/setup-uv@v5
@@ -379,7 +379,7 @@ jobs:
379379
needs: [changes, lockfile]
380380
if: needs.changes.outputs.compiler_differential == 'true'
381381
runs-on: ubuntu-24.04
382-
timeout-minutes: 20
382+
timeout-minutes: 45
383383
strategy:
384384
fail-fast: false
385385
matrix:
@@ -415,7 +415,7 @@ jobs:
415415
needs: [changes, lockfile]
416416
if: needs.changes.outputs.compiler_differential == 'true'
417417
runs-on: windows-2025
418-
timeout-minutes: 40
418+
timeout-minutes: 45
419419
defaults:
420420
run:
421421
shell: bash
@@ -468,7 +468,7 @@ jobs:
468468
needs: [changes, lockfile]
469469
if: needs.changes.outputs.format_performance == 'true'
470470
runs-on: ubuntu-24.04
471-
timeout-minutes: 30
471+
timeout-minutes: 45
472472
steps:
473473
- uses: actions/checkout@v4
474474
- uses: astral-sh/setup-uv@v5
@@ -499,7 +499,7 @@ jobs:
499499
# These budgets are calibrated for the 8-vCPU Blacksmith runner.
500500
if: false
501501
runs-on: ubuntu-24.04
502-
timeout-minutes: 15
502+
timeout-minutes: 45
503503
steps:
504504
- uses: actions/checkout@v4
505505
- uses: astral-sh/setup-uv@v5
@@ -524,7 +524,7 @@ jobs:
524524
needs: [changes, lockfile]
525525
if: needs.changes.outputs.performance == 'true'
526526
runs-on: ubuntu-24.04
527-
timeout-minutes: 15
527+
timeout-minutes: 45
528528
strategy:
529529
fail-fast: false
530530
matrix:
@@ -572,7 +572,7 @@ jobs:
572572
if: needs.changes.outputs.performance == 'true'
573573
runs-on: ubuntu-24.04
574574
# The 3k and 10k cells also run the delta edit-loop guard, about five minutes at 10k.
575-
timeout-minutes: 30
575+
timeout-minutes: 45
576576
strategy:
577577
fail-fast: false
578578
matrix:
@@ -717,7 +717,7 @@ jobs:
717717
needs: [changes, lockfile]
718718
if: needs.changes.outputs.performance == 'true'
719719
runs-on: ubuntu-24.04
720-
timeout-minutes: 30
720+
timeout-minutes: 45
721721
strategy:
722722
fail-fast: false
723723
matrix:
@@ -788,7 +788,7 @@ jobs:
788788
# These budgets are calibrated for the 8-vCPU Blacksmith runner.
789789
if: false
790790
runs-on: ubuntu-24.04
791-
timeout-minutes: 15
791+
timeout-minutes: 45
792792
steps:
793793
- uses: actions/checkout@v4
794794
- uses: astral-sh/setup-uv@v5

‎Cargo.lock‎

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ ruff_text_size = "=0.0.4"
9090
rustc-hash = "2"
9191
# Recall's local store: SQLite compiled in, so every build has the same engine version.
9292
rusqlite = { version = "0.37", features = ["bundled"] }
93-
rustix = { version = "1.1.4", default-features = false, features = ["fs", "std"] }
93+
rustix = { version = "1.1.4", default-features = false, features = ["fs", "std", "termios"] }
9494
saphyr-parser = "0.1"
9595
regex = "1"
9696
serde = { version = "1", features = ["derive"] }

‎crates/sqlbuild-analysis/src/assembly/analysis_session/_helpers/analysis_cache.rs‎

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::assembly::analysis_session::models::{
1616
LineageRow, ModelAnalysis, ModelOutcome, ModelRequest, SessionRequest,
1717
};
1818
use crate::assembly::analysis_session::types::{Pairs, Shapes};
19-
use crate::semantic_validation::types::DiagnosticRow;
19+
use crate::semantic_validation::models::DiagnosticRow;
2020

2121
const LINEAGE_NATIVE: u64 = 0;
2222
const LINEAGE_FACTS: u64 = 1;
@@ -454,7 +454,16 @@ pub(crate) fn encode_outcome(
454454
encoder.flag(analysis.has_star);
455455
encoder.flag(analysis.star_resolved);
456456
encoder.uint(analysis.binding_diagnostics.len() as u64);
457-
for (code, message, line, column, start, end, severity) in &analysis.binding_diagnostics {
457+
for DiagnosticRow {
458+
code,
459+
message,
460+
line,
461+
column,
462+
start,
463+
end,
464+
severity,
465+
} in &analysis.binding_diagnostics
466+
{
458467
encoder.text(code);
459468
encoder.text(message);
460469
for position in [line, column, start, end] {
@@ -529,15 +538,15 @@ pub(crate) fn decode_outcome(
529538
let diagnostic_count: usize = decoder.index()?;
530539
let mut binding_diagnostics: Vec<DiagnosticRow> = Vec::new();
531540
for _ in 0..diagnostic_count {
532-
binding_diagnostics.push((
533-
decoder.text()?,
534-
decoder.text()?,
535-
decoder.optional_index()?,
536-
decoder.optional_index()?,
537-
decoder.optional_index()?,
538-
decoder.optional_index()?,
539-
decoder.text()?,
540-
));
541+
binding_diagnostics.push(DiagnosticRow {
542+
code: decoder.text()?,
543+
message: decoder.text()?,
544+
line: decoder.optional_index()?,
545+
column: decoder.optional_index()?,
546+
start: decoder.optional_index()?,
547+
end: decoder.optional_index()?,
548+
severity: decoder.text()?,
549+
});
541550
}
542551
let binding_validated: bool = decoder.flag()?;
543552
decoder.finish()?;

‎crates/sqlbuild-analysis/src/assembly/analysis_session/_helpers/compact_batch.rs‎

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ use crate::assembly::analysis_session::constants::{
2323
use crate::assembly::analysis_session::models::{ColumnFact, LineageRow};
2424
use crate::assembly::analysis_session::types::{Pairs, Shapes};
2525
use crate::semantic_validation::main::diagnostics::binding_diagnostics;
26-
use crate::semantic_validation::types::{DiagnosticRow, NormalizationRequest, Relations};
26+
use crate::semantic_validation::models::DiagnosticRow;
27+
use crate::semantic_validation::types::{NormalizationRequest, Relations};
2728

2829
const INVALID_BATCH: &str = "native compact query analysis returned an invalid batch response";
2930
const INVALID_TEMPLATE: &str = "native compact query analysis returned an invalid template";
@@ -1424,15 +1425,15 @@ fn diagnostic_row(error: &Value) -> Result<Option<DiagnosticRow>, String> {
14241425
Some(Value::String(message)) if !message.is_empty() => message.clone(),
14251426
_ => DEFAULT_BINDING_MESSAGE.to_owned(),
14261427
};
1427-
Ok(Some((
1428-
code.to_owned(),
1428+
Ok(Some(DiagnosticRow {
1429+
code: code.to_owned(),
14291430
message,
1430-
position(error, "line")?,
1431-
position(error, "column")?,
1432-
position(error, "start")?,
1433-
position(error, "end")?,
1434-
severity.to_owned(),
1435-
)))
1431+
line: position(error, "line")?,
1432+
column: position(error, "column")?,
1433+
start: position(error, "start")?,
1434+
end: position(error, "end")?,
1435+
severity: severity.to_owned(),
1436+
}))
14361437
}
14371438

14381439
fn position(error: &Value, key: &str) -> Result<Option<usize>, String> {

‎crates/sqlbuild-analysis/src/assembly/analysis_session/_helpers/dynamic_pivot.rs‎

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,12 @@ fn pivot_proof(
192192
if truthy(pivot.get("unpivot")) {
193193
return failure("UNPIVOT does not produce a dynamic column family".to_owned());
194194
}
195-
let (pivot_columns, aggregates, dynamic, columns_valid) = components(pivot, context.dialect);
195+
let PivotComponents {
196+
pivot_columns,
197+
aggregates,
198+
dynamic,
199+
columns_valid,
200+
} = components(pivot, context.dialect);
196201
if !dynamic {
197202
return failure(
198203
"static pivot values must use ordinary exact column declarations".to_owned(),
@@ -309,24 +314,31 @@ fn fixed_names(
309314
.collect()
310315
}
311316

317+
/// Python's `_pivot_components` for one pivot clause.
318+
struct PivotComponents<'a> {
319+
pivot_columns: Vec<&'a str>,
320+
aggregates: Vec<&'a Value>,
321+
/// Whether the pivot values come from a dynamic source.
322+
dynamic: bool,
323+
/// Whether every pivot column resolved to a name.
324+
columns_valid: bool,
325+
}
326+
312327
/// Python's `_pivot_components`: pivot columns, aggregates, dynamic values, columns valid.
313-
fn components<'a>(
314-
pivot: &'a Map<String, Value>,
315-
dialect: &str,
316-
) -> (Vec<&'a str>, Vec<&'a Value>, bool, bool) {
328+
fn components<'a>(pivot: &'a Map<String, Value>, dialect: &str) -> PivotComponents<'a> {
317329
if SIMPLIFIED_PIVOT_DIALECTS.contains(&dialect) && truthy(pivot.get("using")) {
318330
let expressions: Vec<&Value> = dict_list(pivot.get("expressions"));
319331
let columns: Vec<&str> = expressions
320332
.iter()
321333
.filter_map(|expression| column_name(Some(expression)))
322334
.collect();
323335
let valid: bool = columns.len() == expressions.len();
324-
return (
325-
columns,
326-
dict_list(pivot.get("using")),
327-
!truthy(pivot.get("fields")),
328-
valid,
329-
);
336+
return PivotComponents {
337+
pivot_columns: columns,
338+
aggregates: dict_list(pivot.get("using")),
339+
dynamic: !truthy(pivot.get("fields")),
340+
columns_valid: valid,
341+
};
330342
}
331343
let fields: Vec<&Value> = dict_list(pivot.get("fields"));
332344
let columns: Vec<&str> = fields
@@ -335,7 +347,12 @@ fn components<'a>(
335347
.collect();
336348
let dynamic: bool = fields.iter().any(|field| has_dynamic_values(field));
337349
let valid: bool = columns.len() == fields.len();
338-
(columns, dict_list(pivot.get("expressions")), dynamic, valid)
350+
PivotComponents {
351+
pivot_columns: columns,
352+
aggregates: dict_list(pivot.get("expressions")),
353+
dynamic,
354+
columns_valid: valid,
355+
}
339356
}
340357

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

653670
/// Python's `_families_equal` key for one family.
654-
fn normalized(family: &DynamicFamily) -> (String, String, String, String, String, Option<&str>) {
655-
(
656-
casefold(&family.name),
657-
casefold(&family.pivot_column),
658-
casefold(&family.value_column),
659-
casefold(&family.aggregate),
660-
casefold(&family.data_type),
661-
family.name_pattern.as_deref(),
662-
)
671+
#[derive(PartialEq, Eq)]
672+
struct FamilyKey<'a> {
673+
name: String,
674+
pivot_column: String,
675+
value_column: String,
676+
aggregate: String,
677+
data_type: String,
678+
name_pattern: Option<&'a str>,
679+
}
680+
681+
/// Python's `_families_equal` key for one family.
682+
fn normalized(family: &DynamicFamily) -> FamilyKey<'_> {
683+
FamilyKey {
684+
name: casefold(&family.name),
685+
pivot_column: casefold(&family.pivot_column),
686+
value_column: casefold(&family.value_column),
687+
aggregate: casefold(&family.aggregate),
688+
data_type: casefold(&family.data_type),
689+
name_pattern: family.name_pattern.as_deref(),
690+
}
663691
}

‎crates/sqlbuild-analysis/src/assembly/analysis_session/_helpers/session.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ use crate::assembly::analysis_session::models::{
3636
RelationTables, SessionModelFacts, SessionOutcome, SessionRequest, SessionStep,
3737
};
3838
use crate::assembly::analysis_session::types::{Pairs, Shapes};
39+
use crate::semantic_validation::models::DiagnosticRow;
3940
use crate::semantic_validation::models::ProjectCatalog;
40-
use crate::semantic_validation::types::DiagnosticRow;
4141

4242
/// What an internal native failure of the session names.
4343
const SESSION_CONTEXT: &str = "native model analysis";

‎crates/sqlbuild-analysis/src/assembly/analysis_session/models.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use sqlbuild_cache::store::models::NativeStore;
1111
use crate::assembly::analysis_session::_helpers::catalog_state::SessionCatalog;
1212
use crate::assembly::analysis_session::_helpers::mappings::ShapeTable;
1313
use crate::assembly::analysis_session::types::{NullabilityRuleFn, OutputSources, Pairs, Shapes};
14-
use crate::semantic_validation::types::DiagnosticRow;
14+
use crate::semantic_validation::models::DiagnosticRow;
1515

1616
/// One `ref`, `source`, `seed`, `table_fn` or `udf` call a model's SQL makes.
1717
#[derive(Debug, Clone, PartialEq, Eq)]

‎crates/sqlbuild-analysis/src/assembly/analysis_session/tests/helpers.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ pub(crate) fn described(outcome: &ModelOutcome) -> Vec<String> {
230230
analysis
231231
.binding_diagnostics
232232
.iter()
233-
.map(|row| row.0.as_str())
233+
.map(|row| row.code.as_str())
234234
.collect::<Vec<_>>()
235235
));
236236
lines

0 commit comments

Comments
 (0)