Skip to content

feat(salesforce): shared-engine support for contract checks on an org (R-642) - #2829

Open
Niels-b wants to merge 13 commits into
mainfrom
r-642-salesforce-support-for-contract-evaluation
Open

feat(salesforce): shared-engine support for contract checks on an org (R-642)#2829
Niels-b wants to merge 13 commits into
mainfrom
r-642-salesforce-support-for-contract-evaluation

Conversation

@Niels-b

@Niels-b Niels-b commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Shared-engine changes needed to run contract checks against a Salesforce org (R-642). The Salesforce
side is sodadata/soda-extensions#565.

Five production files, each pairing its change with tests that run on every data source, not
just Salesforce.

What changes for other data sources

Four of these alter shared behaviour, so that is the part worth reviewing hardest:

Change Effect elsewhere
The schema check reads columns through get_schema_check_columns_metadata() A new seam over the existing accessor, so the same metadata and the same SQL for every source. The one delta is where the SQL is built: it moves from __init__ to execute(), so a builder that raises (sparkdf rejects >2 prefixes) now leaves one check NOT_EVALUATED instead of aborting the verification. Outcomes are otherwise identical to main, including for zero resolved columns. Databricks overrides the new seam so its accessor's swallow keeps serving the bulk sweep without silencing the schema check.
Dataset-level duplicate refuses when the source cannot hash Flag defaults True, so no SQL source reaches the guard.
Sampling refusal guards on supports_row_sampling() Defaults True, and only SalesforceSqlDialect overrides it across all 20 installed dialects.
supported_check_types gates check parsing Defaults None — no restriction — so every existing source keeps running every registered check type. Only a source that declares a set is affected.
Empty check diagnostics no longer raise on Cloud publish Strictly more permissive: an empty dict previously fell to raise SodaException("Unrecognized check result type"), and now returns None. A populated non-SodaCloudJsonable dict still raises exactly as before.

Why the correctness guards exist

Dataset-level duplicate reported every row as a duplicate. The count is derived as
row_count - distinct_count, the distinct count comes from a row hash, and a source that cannot
express one leaves it unmeasured — which convert_db_value coerces to 0. Measured against a
Salesforce fixture with zero duplicates: FAILED at 100%. reconciliation's duplicate_diff already
guards on this flag; the direct check did not.

The refusal resolves in parse_check, so no metric is built and the query the source cannot answer is
never sent. It shares one mechanism with the declared-check-type gate rather than adding a second.
Note the name: a dataset-level duplicate hashes even for columns: [one]
(_build_combined_hash_sql keeps STRING_HASH for a single column), so the message says
"dataset-level" rather than "multi-column" and points at the per-column form, which needs no hash —
with the caveat that it excludes missing values where this one counts NULL as a value.

Sampling was requested but unavailable. Two coordinated guards. The construction-time one is
load-bearing: without it .SAMPLE() reaches the dialect's _build_sample_sql, whose default raises
NotImplementedError inside CheckCollectionImpl.__init__ — before the execute loop exists to catch
anything — and the whole verification aborts. The execute-time one turns the refusal into
NOT_EVALUATED with a reason instead of a raised exception. data_source_supports_sampling answers only
the capability question; each call site pairs it with the "was sampling requested" half it already asks.

supported_check_types

A source that speaks something other than SQL can only answer part of the contract language. Until now
the unanswerable part failed deep inside execution, surfacing as whatever the source said — a raw
MALFORMED_QUERY, which reads like an engine defect rather than a boundary.

DataSourceImpl.supported_check_types is Optional[frozenset[str]], default None. A source that
declares a set gets checks outside it refused in CheckImpl.parse_check, before any metric or query
is built
— that ordering is the point, because some check types run a user-written query and reaching
setup would send it to a source that cannot run it.

The gate is enforced in the shared CheckImpl.parse_check, so it covers every CheckCollectionImpl
subtype
— contracts, data standards, metric monitoring — and every parser in the check_parsers
registry, group_by included. Only reconciliation escapes, via its own parse_check.

Three deliberate choices, each measured rather than assumed:

  • NOT_EVALUATED, not EXCLUDED. EXCLUDED means the user deselected the check, which is a different
    statement from "you asked for this and are not getting it".
  • Logged as an error, so the contract lands on ERROR (exit 3) rather than UNKNOWN (exit 0). Note
    UNKNOWN, not PASSED: _get_contract_verification_status reaches PASSED only via
    all(outcome == PASSED), so one NOT_EVALUATED already drops it. The reason to go further is
    permanence — unlike a NOT_EVALUATED from an unmeasured metric, which may succeed next run, this check
    will never evaluate on this source until someone edits the contract, and exit 0 would leave the
    contract quietly asserting less than it claims.
  • Sources declare the whole supported set, not the gaps, so a check type added to soda-core is
    unsupported on such a source until someone has verified it there.

The Cloud-payload fix is what makes the refusal publishable at all.
_build_v4_diagnostics_check_type_json_dict dispatches partly on the check type and partly on the
result class: type-matched branches call .get() on the diagnostics, while the class-matched ones
(schema, freshness) fall through to an else-arm that accepted only None or a SodaCloudJsonable. So a
result with no diagnostics had no representation that worked for both — None crashed the first set with
an AttributeError, {} crashed the second with Unrecognized check result type. Both failure modes
were hit for real while building this.

Test additions

test_aggregate_check.py had no min or max case at all, and sum only in its filtered variant
— three of the four documented aggregate functions were asserted nowhere, for any source. Added, plus a
filtered max.

New test_sampling_unsupported.py covers both sampling guards by patching the capability flag, so it
runs everywhere. Mutation-checked: removing the construction-time guard fails 2 of its 3 tests.

New test_supported_check_types.py patches the capability onto whatever source the suite runs against,
so the behaviour is asserted on every source rather than only the one that declares a set. The publish
test is the one that catches both crash modes above — mutation-checked at 0 → 4 failures.

New soda-databricks/tests/unit/test_databricks_columns_metadata.py pins the seam that keeps Databricks
loud: its accessor still absorbs failures for bulk callers, and still lets them surface for the schema
check. Mutation-checked — deleting the override fails it.

test_duplicate_dataset_check was defended by an assertion that held for the wrong reason: it
asserted only the outcome and the absent number, so downgrading the reason to debug would have produced
a silent NOT_EVALUATED with a clean exit, which is exactly what the guard exists to prevent. It now
asserts the message via caplog, and that the message names the per-column alternative.

Verification

Postgres soda-tests/tests/integration 190 passed, 10 skipped
DuckDB soda-tests/tests/integration 179 passed, 17 skipped
sparkdf soda-tests/tests/integration 179 passed, 17 skipped
soda-tests/tests/unit + feature 1204 passed, 2 skipped
soda-databricks/tests/unit 37 passed

Every count was compared against the same suite on a clean tree, not against a remembered baseline.
The integration deltas are exactly the one test removed with the zero-columns error.

Reviewer notes

  • No cross-source behaviour change. An earlier revision made zero resolved columns an error; that
    has been removed, so outcomes match main on every source. Routing the schema check through its own
    accessor is what remains, and Databricks overrides it so the routing does not quieten a metadata
    failure that used to be loud.
  • supported_check_types gates every CheckCollectionImpl subtype, not contracts alone. A test in
    sodadata/soda-extensions#565 pins that data standards are covered — it lives there because
    soda-data-standard is packaged there.
  • Not included, and open: a filter: on a query:-form check is silently ignored on every data source.
    Measured and documented, but fixing it changes a long-standing shared flow, so it was left out of this
    PR deliberately.

🤖 Generated with Claude Code

@Niels-b

Niels-b commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

Dependency note: the Salesforce side is sodadata/soda-extensions#565, which imports SODA_FAILED_ROWS_CTE_NAME from this PR. Merge order is this PR first, then the extensions one.

🤖 Generated with Claude Code

@Niels-b
Niels-b marked this pull request as ready for review August 19, 2026 12:43
Comment thread soda-core/src/soda_core/check_collections/base.py Outdated
f"'{self.data_source_impl.type_name}' cannot sample rows. No checks were evaluated. "
f"Remove the sampling configuration for this dataset to run them against all rows."
)
else:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest extracting this to a helper function to avoid nesting even more

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both arms extracted: _execute_queries() and _log_sampling_refusal(). The branch is now two lines and reads as the decision it is. The nesting was this PR's doing — the loop used to sit directly under the elif — so it seemed right to take the suggestion rather than defend it.

@@ -281,6 +281,23 @@ def get_required_metric_impls(self) -> list[MetricImpl]:
return [self.row_count_metric_impl, self.multi_column_distinct_count_metric_impl]

def evaluate(self, measurement_values: MeasurementValues) -> CheckResult:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This validates at check evaluation time, but hasn't the query already happened at this point?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and it was the most useful comment on the PR. The execute loop completes before the evaluate loop, so the row-hash query was being sent and refused before the guard ran.

Moved to parse_check via a new CheckImpl.unsupported_reason() hook, so no metric is built and nothing is sent. It also collapses the two refusal mechanisms you flag below into one, with the condition living on the check rather than the data source.

] = self.data_source_impl.sql_dialect.build_column_metadatas_from_query_result(query_result)
except Exception as e:
logger.error(f"Error building column metadata from query result: {e}")
if not metadata_columns:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about a table without columns? I guess most data sources may not support that, but apparently postgres does

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moot now — the zero-columns error is removed entirely (see m1n0's thread on the same block). Worth saying the question was a real one: Postgres does allow CREATE TABLE t(), and the guard would have called that ERROR.

return False

@property
def supported_check_types(self) -> Optional[frozenset[str]]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be out of scope for this PR, but I started poking at the flow & number of properties we're creating here. supported_check_types, supports_row_hashing, supports_row_sampling all fall into this category, and there's probably more.

The design feels clunky with a data source having to define what checks it supports. Logic about what capabilities a check requires should live in the check IMO. On top of this property, we have duplicate check doing exactly that conditionally. It asserts supports_row_hashing, but only if there are multiple columns involved.

Claude came up with an approach of having both sides define capabilities:

 class CheckImpl:
     def required_capabilities(self) -> frozenset[Capability]: ...
     # multi-column duplicate returns {ROW_HASHING}; single-column returns empty

 class DataSourceImpl:
     def capabilities(self) -> frozenset[Capability]: ...

The engine does one thing, in parse_check, still before setup_metrics:

 missing = check_impl.required_capabilities() - data_source_impl.capabilities()
 if missing:
     # refuse: "duplicate check on (a, b) requires row hashing,
     #          which <source> does not support"

This seems like a better design to me than what we have today, but likely involves a bigger change

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed on the direction, and parse_check is the right seam.

This PR takes the first step: all refusals now resolve there, and the multi-column condition moved onto the check class — your "logic about what a check requires should live in the check" point. CheckImpl.unsupported_reason() is your required_capabilities() with a string instead of an enum. The rest is follow-up work.

One thing worth recording from doing it: hashing and sampling unify cleanly as capabilities, but supported_check_types does not. "SOQL cannot do group_by" is a check type nobody has verified against the source, not a capability a check can name a requirement for — forcing it into the same enum means inventing one capability per check type. That is the design question the follow-up has to answer, and it is why this is not a one-hour change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As was discussed this morning; we'll should do that properly at a later time.

@m1n0 m1n0 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — guards are correctly scoped and every hook #565 overrides lands on the class core actually consults. Fix the description before merging though:

  • "A contract against a non-existent dataset becomes loud" isn't what changes. That's already ERROR/exit 3 on main: row_count always runs an aggregation and a missing table throws there. Ran a schema-only contract on a missing table on both trees — ERROR both. The real flip is a queryable dataset whose metadata query resolves zero columns (prefix/casing mismatch — DuckDB with a 2-part DQN does it): UNKNOWN/exit 0 → ERROR/exit 3. Right call, but say that, and add a customer-facing-release-note block for it — there's none.
  • The gate isn't contract-only. Data standards go through CheckCollectionImpl._parse_checks too, and group_by registers into the shared CheckImpl.check_parsers, so it's gated as well. Only recon's own parse_check escapes.
  • Your comment about #565 importing SODA_FAILED_ROWS_CTE_NAME is stale since the revert.

# and the CLI exiting 0. Reporting it here is what keeps a metadata failure visible for a
# data source whose accessor absorbs its own exceptions and returns an empty list
# (Databricks warns and does exactly that). It also makes a contract against a
# non-existent dataset loud on every source, where it was previously a clean scan.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the case — this path was already ERROR on main via the row_count aggregation. The new case is zero columns on a queryable dataset.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You were right, and the change is removed rather than reworded. Outcomes now match main on every source, including zero resolved columns — so there is no behaviour change to release-note.

Removing it did expose one thing worth knowing: routing the schema check through get_columns_metadata() is still needed for a source whose metadata is not SQL, but Databricks overrides that accessor to absorb exceptions for the bulk-metadata sweep. So the routing alone would have quietened a metadata failure that used to be loud on that one source. get_schema_check_columns_metadata() separates the two audiences — the sweep keeps its empty list, the schema check sees the failure — and Databricks overrides it. Pinned by a mutation-checked unit test.

# data source whose accessor absorbs its own exceptions and returns an empty list
# (Databricks warns and does exactly that). It also makes a contract against a
# non-existent dataset loud on every source, where it was previously a clean scan.
logger.error(msg=f"No columns metadata resolved for '{self.dataset_name}'")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add the prefixes here — the failure mode is a schema/prefix mis-split and this line doesn't say where it looked. Also this now logs three ERROR lines together with the Actual columns are None pair from evaluate().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefixes added, on the surviving message — the line you commented on goes away with the zero-columns error.

The pile-up goes with it on that path: without the error, execute() returns a measurement carrying an empty list, so get_value yields [] rather than None and the evaluate() pair never fires. That path drops from three ERROR lines to zero. The pair still fires on the exception path, which is pre-existing on main, so I left it alone.

unsupported on such a source until someone has verified it.

Enforced where contract checks are parsed, which is every check the contract language exposes.
Reconciliation parses its own checks and is not gated here; a recon diff type listed in the set

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also gates data standards and group_by (shared check_parsers registry). Only recon escapes via its own parse_check.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected — and it reaches further than either of us said. MonitoringImpl and MonitoringBackfillImpl in soda-metric-monitoring are CheckCollectionImpl subclasses with no parse_check of their own, so metric monitoring is gated too.

The docstring now states the rule once — every check type in the check_parsers registry, for every CheckCollectionImpl subtype — rather than listing kinds and getting the list wrong. A test pinning the data-standards half is in sodadata/soda-extensions#565, since soda-data-standard is packaged there.

Niels-b and others added 12 commits August 20, 2026 16:45
SchemaQuery rebuilt the information_schema SQL itself, duplicating the three
steps DataSourceImpl.get_columns_metadata() already performs and bypassing any
override of it. A source whose metadata does not come from SQL — Salesforce
reads the Describe API — could therefore never be reached by the schema check.

Routing through the accessor also moves the SQL build from __init__ to
execute(), so a builder that raises (sparkdf rejects more than two prefixes) now
leaves one check NOT_EVALUATED instead of aborting the whole verification.

Zero resolved columns is reported as an error. evaluate() only logs for a
*missing* measurement; an empty one falls through silently, leaving the scan
UNKNOWN and the CLI exiting 0. That matters for a source whose accessor absorbs
its own exceptions and returns an empty list, and it makes a contract against a
non-existent dataset loud on every source.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A multi-column duplicate count is derived as row_count - distinct_count, and the
distinct count comes from a row hash. A source whose query language cannot
express one leaves that metric unmeasured, which convert_db_value coerces to 0 —
making duplicate_count equal row_count and reporting every row as a duplicate.
Measured on a Salesforce fixture with zero duplicates: FAILED at 100%.

Guarded on supports_row_hashing, matching the guard reconciliation's
duplicate_diff already applies. The flag defaults True, so no SQL source is
affected; the accompanying test patches it and runs on every data source.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sampling was requested but silently unavailable on a source whose query language
has no row sample. Two coordinated guards, keyed on the dialect's own
supports_row_sampling() which defaults True:

At construction the sampler is not attached. This is the load-bearing half:
without it .SAMPLE() reaches the dialect's _build_sample_sql, whose default
raises NotImplementedError inside CheckCollectionImpl.__init__ — before the
execute loop exists to catch anything — and the whole verification aborts.

At execution the queries are refused and the reason reported, which turns the
refusal into NOT_EVALUATED for every check on the dataset rather than a raised
exception. Sampling is configured per dataset, so honouring some checks
unsampled would be worse than honouring none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The count query's CTE alias was a bare literal in soda-core and a copy of the
same literal in the Salesforce connector, which recognises this shape to reduce
the count to a server-side aggregate. A rename on either side would have rerouted
those queries silently, so the alias moves to a shared constant next to
SODA_FILTERED_CTE_NAME and the connector keys on it.

The over-threshold warning gains one sentence. Its existing advice — rewrite the
query so it can be CTE-wrapped — is unachievable on a source with no CTEs, so it
now also names the shape that can be counted server-side. The original sentence
is kept: it is still the right advice everywhere CTEs exist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The module had no min or max case at all, and sum only in its filtered variant —
so three of the four documented aggregate functions were asserted nowhere, for
any data source. Each renders as its own function in a translating dialect, so
one regressing would have been invisible.

Adds min, max, sum and a filtered max, on the fixture's existing rows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ck advice"

This reverts 917207f. Its only consumer was the Salesforce connector's
server-side reduction of a user's `failed_rows: query:`, and that reduction has
been removed: a user's own query now runs as written.

With nothing keying on the alias there is no drift to guard against, so the
constant is churn. The appended advice went with it — it described the shape the
reduction recognised, which no longer means anything.

soda-core's shared footprint for this workstream drops from five files to three.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
_build_v4_diagnostics_check_type_json_dict dispatches partly on the check TYPE
and partly on the result CLASS. The type-matched branches call .get() on
diagnostic_metric_values; the class-matched ones (schema, freshness) fall
through to the else-arm, which required either None or a SodaCloudJsonable and
raised "Unrecognized check result type" on a plain dict.

So a check result with no diagnostics had no representation that worked for
both sets: None crashed the first, {} crashed the second. Treating falsy as
"nothing to report" gives one — a check that was never evaluated carries no
diagnostics whatever its type.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A source that speaks something other than SQL can only answer part of the
contract language. Until now the unanswerable part failed deep inside
execution, surfacing as whatever the source said — a raw MALFORMED_QUERY, for
instance, which reads like an engine defect rather than a boundary.

DataSourceImpl.supported_check_types defaults to None, no restriction, so every
existing source keeps running every registered check type. A source that
declares a set gets checks outside it refused at parse time, before any metric
or query is built: some check types run a user-written query, so reaching setup
would send it to a source that cannot run it.

The refusal reports NOT_EVALUATED rather than EXCLUDED — EXCLUDED means the
user deselected the check, which is a different statement from "you asked for
this and are not getting it" — and logs an error, so the contract lands on
ERROR (exit 3) rather than UNKNOWN (exit 0). Unlike a NOT_EVALUATED from an
unmeasured metric, which may succeed next run, this one is permanent: the check
will never evaluate on this source until someone edits the contract. Exiting 0
would leave the contract quietly asserting less than it claims.

Sources declare the whole supported set rather than the gaps, so a check type
added to soda-core is unsupported until someone has verified it there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both guards were defended by assertions that held for the wrong reason.

test_schema_check asserted result.has_errors, which a verification already
satisfies from unrelated errors of its own — silencing the guard entirely left
the test green. test_duplicate_dataset_check asserted only the outcome and the
absent number, so downgrading the reason to debug would have produced a silent
NOT_EVALUATED with a clean exit, which is what the guard exists to prevent.

Both now assert the message itself via caplog. No set_level: that would raise
the level for every test in the run, and the assertions pass without it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Six review points from mivds and m1n0, plus one internal review pass.

Schema check keeps main's behaviour. The zero-columns error is removed: it was
the one intentional cross-source behaviour change, and restoring the previous
outcome is preferred over closing the silent-scan hole here. Routing through
get_columns_metadata() is still needed so a source whose metadata is not SQL can
override it — but that alone would have made Databricks quieter, because its
override absorbs exceptions for the bulk-metadata sweep. get_schema_check_columns_metadata()
separates the two audiences: the sweep keeps its empty list, the schema check
sees the failure. Databricks overrides it; every other source inherits the
default and is untouched.

Sampling capability is now a plain question about the data source rather than a
compound one. data_source_supports_sampling answers only "can it sample",
leaving the "was sampling requested" half at the two call sites that already ask
it, and removing a double negative. True when there is no data source: nothing
is queried, so nothing is refused — which was the unexplained branch mivds asked
about. The two arms of that branch are extracted so the decision reads in place.

Duplicate hashing is refused at parse time. The guard ran in evaluate(), after
the execute loop had already sent a query the source cannot answer. It now
resolves through CheckImpl.unsupported_reason(), the same field the declared
check-type gate sets — one refusal mechanism instead of two, and the condition
lives on the check rather than the data source. The message says "dataset-level"
rather than "multi-column", because the hash is emitted even for `columns: [one]`
(_build_combined_hash_sql keeps STRING_HASH for a single column), and it names
the per-column form as an alternative along with its NULL-semantics caveat.

The supported_check_types docstring stated the gate's reach as a partial list.
It is enforced in the shared CheckImpl.parse_check, so it covers every
CheckCollectionImpl subtype — contracts, data standards, metric monitoring —
and every parser in the registry. Only reconciliation escapes.

The columns-metadata failure message now names the prefixes it searched: a
prefix mis-split is the likely cause and the message did not say where it looked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Niels-b
Niels-b force-pushed the r-642-salesforce-support-for-contract-evaluation branch from 4f57502 to 472bd73 Compare August 20, 2026 14:45

@m1n0 m1n0 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-ran the probe on the new head vs main: all four rows match, including zero resolved columns — so there's no behaviour change left to release-note. unsupported_reason() in parse_check is the right seam and the Databricks split keeps metadata failures loud. Approving.

Leftovers, none blocking: the CTE dependency comment on this PR is still up and still wrong; test_sampling_unsupported.py still calls caplog.set_level; the construction-time comment in base.py (~595) still describes the old sampling_is_unsupported framing.

Two leftovers from splitting `sampling_is_unsupported` into a pure capability.

The construction-time comment still read as though one negative guard existed and
pointed at "the execute-time guard below", which is now the extracted
`_log_sampling_refusal`. Reworded to the positive framing the condition actually
uses: the CTE is sampled only when sampling is both requested and supported, and
the capability term is the load-bearing half.

test_sampling_unsupported.py still called `caplog.set_level(logging.ERROR)`, which
contradicted the claim that these assertions hold without it — raising the level
for the whole run was the thing to avoid. Removed, with the import that existed
only for it. Verified rather than assumed: the test passes on postgres and duckdb
without it, and downgrading the refusal to `logger.debug` still fails it, so the
assertion is not vacuous.

No stale `sampling_is_unsupported` references remain anywhere in the repo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@Niels-b

Niels-b commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the leftovers; both have been addressed 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants