Skip to content

Fix GetSchema("Columns") silently ignoring restrictions beyond database and table - #497

Merged
alex-clickhouse merged 2 commits into
mainfrom
polyglot/getschema-columns-reject-extra-restrictions
Aug 3, 2026
Merged

Fix GetSchema("Columns") silently ignoring restrictions beyond database and table#497
alex-clickhouse merged 2 commits into
mainfrom
polyglot/getschema-columns-reject-extra-restrictions

Conversation

@polyglotAI-bot

Copy link
Copy Markdown
Collaborator

Description

SchemaDescriber.DescribeColumns reads only restrictions[0] (database) and restrictions[1] (table). Any further restriction value is neither used nor reported, so a call like

connection.GetSchema("Columns", ["system", "functions", "name"]);

runs cleanly and returns all 16 columns of system.functions — the caller's column filter is silently discarded. Verified against a live server (26.x) before the fix: 16 rows, name, is_aggregate, case_insensitive, alias_to, …. A metadata API quietly returning a wider result than was asked for is worse than an error, because callers have no way to tell that their filter was dropped.

The fix rejects restriction values in positions the Columns collection does not support, with an ArgumentException whose message mirrors .NET's own provider behaviour (DbMetaDataFactory throws ArgumentException "More restrictions were provided than the requested schema … supports."). Positions left null remain "unspecified" — exactly how the two supported positions already read null — so a canonically-sized array such as ["system", "functions", null, null] keeps working unchanged.

This deliberately does not add support for a column restriction: that would be a new capability, and it would have to invent positional semantics (this provider puts database at index 0 and table at index 1, whereas the canonical ADO.NET Columns layout is catalog/schema/table/column). Making the drop loud is the bug fix; adding a column filter can follow as a feature if wanted.

Found while working on #496 (independent of it, and it does not touch the WHERE-composition lines that PR changes).

Changes

  • ClickHouse.Driver/Utility/SchemaDescriber.csDescribeColumns throws ArgumentException when a restriction value is supplied past the supported database/table positions; the supported restriction names are declared once and used in the message. The collection name is threaded through so the message reports what the caller asked for.
  • ClickHouse.Driver/ADO/ClickHouseConnection.cs — XML doc on GetSchema(string, string[]) stating the supported collection, its two restrictions, and both exceptions.
  • ClickHouse.Driver.Tests/ADO/ConnectionTests.cs — new tests (see below).
  • CHANGELOG.md / RELEASENOTES.md — Bug Fixes entry (this is an observable behaviour change for callers that were passing extra restrictions).

Test

GetSchema_ColumnsWithRestrictionBeyondDatabaseAndTable_ThrowsArgumentException is a TestCaseSource-parametrized test over the shapes that used to be silently dropped:

  • ["system", "functions", "name"] — third position set
  • ["system", "functions", ""] — empty string counts as supplied (consistent with how the supported positions treat "" as a real filter and only null as unspecified)
  • [null, null, "name"] — an unsupported restriction with no supported ones
  • ["system", "functions", null, "name"] — fourth position set, third unspecified

Contrast cases pin the behaviour that must not change:

  • GetSchema_ColumnsWithUnspecifiedRestrictionsBeyondTable_AppliesSupportedRestrictions["system", "functions", null, null] still returns rows, all with Database == "system" and Table == "functions".
  • GetSchema_ColumnsWithEmptySupportedRestriction_FiltersOnTheEmptyValue["system", ""] still filters on the empty table name (empty result), unchanged.
  • The pre-existing ShouldFetchSchemaDatabaseColumns / ShouldFetchSchemaTableColumns cover the 1- and 2-restriction paths.

Verified: the four parametrized cases fail on unpatched main (Expected: <System.ArgumentException> But was: null) and pass with the fix; both contrast cases pass before and after. Full ADO suite green on net10.0: 916 passed, 0 failed.

Pre-PR validation gate

  • Deterministic repro confirmed (16 unfiltered rows returned for a 3-restriction call against a live server)
  • Root cause documented above
  • Fix targets the root cause (the ignored positions), not a symptom
  • Tests fail without the fix, pass with it
  • No existing tests weakened, edited, or removed
  • Convention compliance per AGENTS.md (integration-style tests, Method_Scenario_ExpectedBehavior naming, TestCaseSource parametrization, ADO.NET compliance)
  • No public API surface change (PublicAPI/*.txt untouched — the guard is inside an internal helper)
  • CHANGELOG + RELEASENOTES entries added

DescribeColumns only reads restrictions[0] (database) and restrictions[1]
(table). Any further restriction value was ignored, so a call such as
GetSchema("Columns", ["system", "functions", "name"]) ran cleanly and
returned every column of the table instead of the requested one - a silent
wrong result. Unsupported restriction values are now rejected with an
ArgumentException, matching how ADO.NET providers report more restrictions
than a metadata collection supports. Positions left null stay unspecified.
Copilot AI review requested due to automatic review settings July 31, 2026 18:37
@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copilot AI 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.

Pull request overview

This PR fixes a correctness issue in the ADO.NET metadata surface (ClickHouseConnection.GetSchema("Columns", ...)) by making unsupported restriction positions fail loudly instead of being silently ignored, and documents/tests the behavior as an observable bug fix.

Changes:

  • Add an ArgumentException guard in SchemaDescriber.DescribeColumns when non-null restrictions are supplied beyond the supported database/table positions.
  • Document supported GetSchema("Columns") restrictions and possible exceptions on ClickHouseConnection.GetSchema(string, string[]).
  • Add regression tests plus changelog/release-notes entries for the behavior change.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
ClickHouse.Driver/Utility/SchemaDescriber.cs Rejects extra restriction values for the Columns collection (throws ArgumentException).
ClickHouse.Driver/ADO/ClickHouseConnection.cs Adds XML docs clarifying the only supported collection and restriction semantics.
ClickHouse.Driver.Tests/ADO/ConnectionTests.cs Adds tests covering unsupported restriction positions and contrast cases.
CHANGELOG.md Documents the behavioral bug fix under Unreleased.
RELEASENOTES.md Documents the behavioral bug fix under Unreleased.

Comment on lines +89 to +93
if (restrictions != null && restrictions.Skip(ColumnsRestrictionNames.Length).Any(restriction => restriction != null))
{
throw new ArgumentException(
$"More restrictions were provided than the requested schema ('{collectionName}') supports. " +
$"Supported restrictions are: {string.Join(", ", ColumnsRestrictionNames)}.");

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch, and the concern is real — but it is deliberately out of scope for this PR, because it is already fixed in a separate, earlier PR: #496 — "Fix GetSchema(\"Columns\") building invalid SQL for a table-only restriction".

That PR replaces exactly the inline " WHERE" / " AND" appends flagged here with collected predicates joined at the end:

var predicates = new List<string>();
if (database != null) { predicates.Add("database={database:String}"); ... }
if (table    != null) { predicates.Add("table={table:String}");       ... }
if (predicates.Count > 0) query.Append(" WHERE ").Append(string.Join(" AND ", predicates));

so GetSchema("Columns", [null, "functions"]) composes valid SQL and filters by table across all databases. #496 carries its own regression tests for the table-only and no-restriction cases.

I am keeping the two changes separate on purpose: #496 fixes the SQL-composition defect, this PR (#497) fixes a distinct defect — extra positional restriction values (a column restriction, per the canonical ADO.NET Columns collection) being silently dropped and returning a wrong result instead of raising ArgumentException. Folding the WHERE-composition rewrite in here would bundle two unrelated fixes into one PR and duplicate #496's diff on the same lines.

No code change here, so I am leaving this thread unresolved for the reviewer to decide. Whichever of #496/#497 merges second will need a trivial rebase on this method; happy to reorder them if you prefer one first.

@alex-clickhouse

Copy link
Copy Markdown
Collaborator

@polyglotAI-bot please fix the merge conflict

…lumns-reject-extra-restrictions

# Conflicts:
#	ClickHouse.Driver/Utility/SchemaDescriber.cs
@polyglotAI-bot

Copy link
Copy Markdown
Collaborator Author

@alex-clickhouse conflict resolved and pushed (4cb7cf1) — GitHub now reports the PR as MERGEABLE.

What conflicted and how it was resolved:

  • ClickHouse.Driver/Utility/SchemaDescriber.csmain has since merged the sibling fixes (Fix GetSchema("Columns") building invalid SQL for a table-only restriction #496 predicate-list WHERE composition, and the using var command disposal fix) on the exact lines this PR touches. I merged main in (merge commit, no history rewrite) and kept both sides: main's using var command = connection.CreateCommand(); plus predicate-list composition, and this PR's extra-restriction validation.
  • CHANGELOG.md / RELEASENOTES.md — both sets of entries kept; nothing dropped.

Net diff versus current main is unchanged in intent (restriction validation + XML doc + tests only).

Verification in a devbox against a live ClickHouse server: ClickHouse.Driver.Tests.ADO on net10.0 → 916 passed, 0 failed (5 skipped, all pre-existing environment skips), including the 4 parametrized ArgumentException cases and the 2 contrast tests.

Side note: since #496 is now in main, the earlier Copilot comment about the hard-coded WHERE/AND composition is resolved upstream — this branch now carries that fix via the merge.

Heads-up: the merge may have dismissed any prior approval, so a re-approve might be needed.

@alex-clickhouse
alex-clickhouse merged commit a397e9a into main Aug 3, 2026
18 checks passed
@alex-clickhouse
alex-clickhouse deleted the polyglot/getschema-columns-reject-extra-restrictions branch August 3, 2026 17:57
alex-clickhouse added a commit that referenced this pull request Aug 5, 2026
main added 22 new Unreleased entries since this branch was cut. Each is now
its own changelog.d/ fragment, extracted verbatim by line number rather than
retyped, so the assembled Unreleased section reproduces main's exactly (as a
set of lines; sorting by PR number reorders entries within their sections).

New fragments, one per (PR, category):

  #390 improvements   multidim blittable inserts
  #472 improvements   per-scalar Span<byte> reads
  #484 fixes          byte[]/TimeOnly HTTP parameters
  #485 fixes          JSON strings under ReadStringsAsByteArrays
  #490 breaking       raw results return compressed bytes
  #490 features       AcceptEncoding response compression
  #490 improvements   lz4 by default, HttpClient, errors, deflate
  #492 fixes          HTTP response disposal
  #493 fixes          Enum type declarations
  #494 fixes          raw-stream double dispose
  #497 fixes          GetSchema("Columns") restrictions
  #498 fixes          JSON paths starting with setting names
  #503 fixes          quoted JSON typed paths
  #504 fixes          quoted Tuple/Nested element names
  #509 fixes          {name:Type} scanner vs server lexer
  #511 fixes          {name:Type} hints after a non-hint brace
  #513 fixes          @name placeholders, heredocs, $ in names

#390's entry was appended to the *released* v1.3.0 section on main (v1.3.0
shipped 2026-06-29), so it would have documented an unreleased change under a
shipped version and never appeared in 1.4.0's notes. It moves to Unreleased as
a fragment; the rest of v1.3.0 is byte-identical.

RELEASENOTES.md regenerated with --sync-notes. `--check` passes, the solution
builds, and the packed .nupkg's releaseNotes open on v1.3.0 with no Unreleased
stub and no #390 bullet.
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