Skip to content

Added sqlx based postgres implementation - #8

Merged
BtXin merged 2 commits into
mainfrom
sqlx_postgres
Mar 13, 2026
Merged

Added sqlx based postgres implementation#8
BtXin merged 2 commits into
mainfrom
sqlx_postgres

Conversation

@BtXin

@BtXin BtXin commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

Added sqlx based postgres implementation to avoid wrapping around datafusion-table-provider and address the auto-increment column bug #5

@BtXin BtXin added bug Something isn't working enhancement New feature or request labels Mar 13, 2026
@BtXin
BtXin merged commit b0b68a7 into main Mar 13, 2026
1 check passed
@BtXin
BtXin deleted the sqlx_postgres branch March 13, 2026 08:41
BtXin added a commit that referenced this pull request Jul 22, 2026
…ser, tidy validator

Second-round review follow-ups (#3#10):

- #4 Encode the trust boundary in the type. Ad-hoc /query now validates
  against a dedicated AdhocSqlPolicy (access modes + denied schemas);
  the trusted pipeline path keeps a bare SqlValidatorConfig. Reaching for
  validate_sql on untrusted input can no longer silently skip the
  allowlist + schema denial.
- #5 Add coverage proving the schema denial descends into indirect
  relations (set ops, scalar/IN/EXISTS subqueries) via visit_relations;
  document the residual (operator-defined views/federated aliases, which
  ad-hoc SQL cannot create) and its structural fix. See follow-up issue.
- #6 Parse via datafusion::sql::sqlparser (DataFusion enables the visitor
  feature); drop the standalone sqlparser dep entirely so validator and
  engine share one parser by construction — a DF bump is now a compile
  break, not silent parse divergence.
- #8 Single AUTH_SCHEMA const used at both the register and deny sites.
- #9 check_denied_schemas reports the table via extract_table_name
  (quote-stripped), matching WriteNotAllowed.
- #10 statement_keyword maps the AST variant to a &'static str instead of
  Display-rendering the whole statement on each rejection.
- #7 AppState::new derives the policy once; removes the copy-pasted
  Arc::new(validator_config_from_sources(..)) across ~9 sites.
- #3 Document the startup-snapshot invariant (no runtime access_mode
  writer) on the field and the builder.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BtXin added a commit that referenced this pull request Jul 23, 2026
* docs: design spec for POST /query ad-hoc SQL endpoint

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: implementation plan for POST /query endpoint

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(plan): extract shared require_session helper in Task 4

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(validator): block COPY, add single-statement validation with StatementKind

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(engine): add execute_with_limit pushing row cap into the plan

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(server): extract response helpers into response.rs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(server): add POST /query endpoint for ad-hoc SQL

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(server): record metrics on max_rows validation path, guard max_rows overflow

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: fmt/clippy fixes for query endpoint

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(validator): close EXPLAIN/SET statement-policy bypass in /query

EXPLAIN ANALYZE executed its wrapped statement, and SET mutated the
shared SessionContext, both bypassing DDL/COPY/write-access checks.
Recurse into EXPLAIN's inner statement and reject session-mutating SET.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(validator): reject PREPARE/EXECUTE in /query statement policy

PREPARE stored a write/DDL plan on the shared SessionContext and EXECUTE
ran it later, bypassing the per-source access-mode gate. Ad-hoc one-shot
queries have no use for prepared statements — reject both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(server): address /query review — auth-schema denial, statement allowlist, scoped policy

- Forbid ad-hoc SQL from referencing the auth schema (auth.sessions holds
  live bearer tokens); enforced via sqlparser visit_relations over every
  relation (FROM/JOIN/subquery/CTE/DML target/EXPLAIN/DESCRIBE, incl.
  3-part catalog.auth.table names).
- Invert the ad-hoc statement policy to a strict allowlist (Query,
  access-checked DML, EXPLAIN-of-allowed, SHOW/DESCRIBE); unknown or
  future sqlparser statement types (MERGE, GRANT, transactions, ...) are
  rejected by default.
- Scope the new denials to /query: the pipeline-load path reverts to its
  pre-PR policy (DDL blocked, DML access-checked, COPY/SET allowed), so
  existing deployments keep booting.
- Skip {param} brace preprocessing for ad-hoc SQL — it rewrote string
  literals and rejected valid queries.
- Match write-access checks on the fully qualified table name, honoring a
  source's access mode for source.table references.
- Build the validator config once at startup into AppState instead of per
  request (no runtime config writer exists).
- Log every ad-hoc statement before execution as an audit trail.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(deps): unify sqlparser at 0.59 to match DataFusion's parser

The validator previously parsed with a standalone sqlparser 0.53 while
DataFusion 52.5 executes with 0.59 — a version skew that could let the
two parsers disagree about a statement's shape. The workspace-root 0.55
entry had no users at all. Now a single workspace dep (0.59, visitor
feature) is shared with DataFusion, so validation and execution always
parse identically.

Migration notes:
- Insert targets are now TableObject; non-table-name targets (table
  functions) have no registered name to access-check.
- ObjectName components are ObjectNamePart; identity comparisons use the
  bare ident value, which also fixes quoted identifiers slipping past
  access-mode and denied-schema checks (INSERT INTO "users",
  "auth"."sessions") — covered by a new test.
- 'SELECT FROM users' (empty projection) parses in 0.59; dropped from
  the parse-error test, it fails at DataFusion planning instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(validator): close default-qualifier write bypass; fail closed on non-table INSERT targets

Round-2 review fixes:

- check_write_access now strips DataFusion's default catalog/schema
  qualifiers before lookup, so INSERT/UPDATE/DELETE against
  public.<table> or datafusion.public.<table> honors the same access
  mode as the bare name (they all resolve to the same table). A
  datafusion.<source>.<table> reference still honors the source's mode.
- INSERT with a non-TableName target (ClickHouse INSERT INTO FUNCTION)
  is rejected instead of skipping the access check — unreachable via
  GenericDialect today, but the write path must fail closed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(validator): type-split ad-hoc policy, share DataFusion's parser, tidy validator

Second-round review follow-ups (#3#10):

- #4 Encode the trust boundary in the type. Ad-hoc /query now validates
  against a dedicated AdhocSqlPolicy (access modes + denied schemas);
  the trusted pipeline path keeps a bare SqlValidatorConfig. Reaching for
  validate_sql on untrusted input can no longer silently skip the
  allowlist + schema denial.
- #5 Add coverage proving the schema denial descends into indirect
  relations (set ops, scalar/IN/EXISTS subqueries) via visit_relations;
  document the residual (operator-defined views/federated aliases, which
  ad-hoc SQL cannot create) and its structural fix. See follow-up issue.
- #6 Parse via datafusion::sql::sqlparser (DataFusion enables the visitor
  feature); drop the standalone sqlparser dep entirely so validator and
  engine share one parser by construction — a DF bump is now a compile
  break, not silent parse divergence.
- #8 Single AUTH_SCHEMA const used at both the register and deny sites.
- #9 check_denied_schemas reports the table via extract_table_name
  (quote-stripped), matching WriteNotAllowed.
- #10 statement_keyword maps the AST variant to a &'static str instead of
  Display-rendering the whole statement on each rejection.
- #7 AppState::new derives the policy once; removes the copy-pasted
  Arc::new(validator_config_from_sources(..)) across ~9 sites.
- #3 Document the startup-snapshot invariant (no runtime access_mode
  writer) on the field and the builder.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(query): fail closed on multi-target DELETE; stop leaking engine internals

Third-round defense-in-depth review:

- Reject DELETE with a non-empty `tables` list or a `USING` clause on the
  ad-hoc /query path instead of relying on DataFusion to reject the form at
  plan time (only `delete.from` was access-checked before).
- Stop returning raw engine/schema internals in /query error responses:
  the execution-error and JSON-conversion-error bodies now carry a generic
  message with no `details`, while the full DataFusion error and the
  record-batch schema stay in the server-side log.

The auth-catalog isolation suggestion is tracked in #164 (the reviewer
asked for an issue, not a block).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant