Skip to content

Latest commit

 

History

History
56 lines (41 loc) · 4.5 KB

File metadata and controls

56 lines (41 loc) · 4.5 KB

Project Overview

QueryGate is a Rust CLI that acts as a read-only SQL gateway for AI agents. It parses PostgreSQL SELECT queries with a real SQL AST (via sqlparser), validates them against a user-level allowlist policy, and executes approved queries through read-only database sessions.

Build, Test, and Run Commands

Task Command
Run tests cargo test
Run a single test cargo test <test_name>
Run integration tests (needs PostgreSQL) export QUERYGATE_TEST_DATABASE_URL="postgres://..." && cargo test -- --ignored
Build release binary cargo build --release
Run the CLI locally cargo run -- <subcommand>

High-Level Architecture

Data Flow

A query flows through three layers:

  1. Parse (src/sql/parse.rs): Uses sqlparser with the PostgreSQL dialect. Only a single SELECT statement is accepted; everything else (INSERT, UPDATE, multiple statements) is rejected at parse time.
  2. Validate (src/sql/validate.rs): Walks the AST and checks every table, column, and function against the loaded policy. Columns are resolved via a QueryScope that tracks table aliases and derived sources (CTEs, subqueries). Violations are collected and returned as a ValidationResult.
  3. Execute (src/db.rs): Opens a read-only transaction (BEGIN READ ONLY), sets statement_timeout, wraps the query in an outer LIMIT if none exists, executes via tokio-postgres, and returns JSON rows.

Key Modules

  • src/cli.rs — clap argument definitions. Subcommands: init, databases, schema, validate, run.
  • src/config.rs — Loads ~/.queryGate/config.yaml (or via --config / QUERYGATE_CONFIG). Defines RawConfig (deserialized from YAML) and LoadedConfig (validated, processed).
  • src/policy.rs — Core access-control types: Policy, TablePolicy, ColumnPolicy, QualifiedTable. Identifiers are normalized (lowercased, quotes stripped). Missing access defaults to denied.
  • src/sql/resolve.rs — Scope resolution. QueryScope maps table aliases to either physical tables or derived sources. Used by the validator to resolve unqualified column names and detect ambiguity.
  • src/init.rsquerygate init implementation. Connects to PostgreSQL, introspects information_schema.columns, and generates a config YAML. With --suggest-safe, marks columns as allowed if their names/data types don't match a hardcoded sensitive-name/type heuristic.
  • src/output.rs — All CLI output is JSON. Success responses wrap data in { ok: true, ... }; errors go to stderr as { ok: false, error: { code, message, hint } }.

Exit Codes

Code Meaning
0 Success
2 Config error
3 SQL parse error
4 Policy validation error
5 Database execution error

Testing Strategy

  • Unit tests are in tests/validation.rs (policy/AST validation) and tests/cli.rs (binary-level CLI tests using CARGO_BIN_EXE_querygate).
  • Integration tests in tests/db_integration.rs require a live PostgreSQL instance and the QUERYGATE_TEST_DATABASE_URL env var. They are marked #[ignore] and only run when explicitly invoked with cargo test -- --ignored.
  • tempfile is used in tests to create throwaway config files.

Important Design Constraints

  • Deny-by-default: Any table, column, or function not explicitly listed in the policy is denied.
  • Identifier normalization: All SQL identifiers are lowercased and double-quotes are stripped before policy lookup. This means the policy file should use lowercase names.
  • SELECT * is only allowed when a table has default_column_access: allowed.
  • The validator rejects locking clauses (FOR UPDATE), SELECT INTO, VALUES, and non-SELECT set expressions.
  • When adding new AST node validation, update both validate.rs (the visitor) and resolve.rs (scope tracking) if the node introduces new tables or aliases.