Skip to content

feat: Add SQL schema discovery library for PostgreSQL - #6812

Open
patelchaitany wants to merge 2 commits into
feast-dev:masterfrom
patelchaitany:feat/sql-schema-discovery-upstream
Open

feat: Add SQL schema discovery library for PostgreSQL#6812
patelchaitany wants to merge 2 commits into
feast-dev:masterfrom
patelchaitany:feat/sql-schema-discovery-upstream

Conversation

@patelchaitany

@patelchaitany patelchaitany commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

What this does

Adds feast.schema_discovery, a library for introspecting a PostgreSQL database from an ad-hoc connection URL. It needs neither a feature_store.yaml nor a registered data source, so a user can explore a database before defining anything in Feast.

SchemaDiscoveryService exposes three operations:

Call Returns
list_schemas(url) database schemas, system namespaces excluded
list_tables(url, schema) tables and views, tagged by type
describe_table(url, table, schema?) columns: name, SQL type, nullable, primary key, mapped Feast ValueType

This is the library phase only — no HTTP endpoint yet.

Design decisions

The choices worth arguing about, and what was rejected:

# Decision Alternative rejected Why
1 Column types come from Feast's own DataSource SQLAlchemy's Inspector for everything get_table_column_names_and_types() reports canonical catalog spellings (character varying) — exactly what pg_type_to_feast_value_type keys on. SQLAlchemy reports VARCHAR(255), which maps to UNKNOWN. A SQLAlchemy-only build silently mistypes most columns.
2 Navigation + nullability/PK come from SQLAlchemy Hand-rolled information_schema queries A cursor description carries neither, and DataSource cannot list schemas or tables at all. SQLAlchemy already does this well per-dialect.
3 Accept an ad-hoc connection URL Reuse a registered DataSource from the registry The point is exploring a database before it is registered. A RepoConfig is built in memory; nothing is written to a registry.
4 PostgreSQL only in v1, enforced by allowlist Ship every SQLAlchemy dialect Each dialect needs a DataSource + offline-store config pair to reuse decision 1. Unsupported dialects fail fast with a 400 rather than half-working.
5 Identifiers validated before connecting Rely on the driver to reject bad names Table names are interpolated into SQL by get_table_query_string(). Validation runs first so a malicious name never reaches a connection. Anchored with \Z, not $$ also matches before a trailing newline and would admit "users\n".
6 Errors carry http_status_code(), but no route ships Ship the endpoint in this PR Lets a future router map errors with no translation table, while keeping the security discussion (below) out of this PR.
7 Tables returned as {name, type} objects Plain strings Lets views be marked as views. Views are listed and describable.
8 sslmode defaults to require Default to disable for convenience Matches Feast's own PostgreSQLConfig default. Override per-connection with ?sslmode=disable in the URL.
9 Unqualified table names resolve to public Follow the connection's search_path Predictable and explainable. search_path resolution can be revisited if anyone asks for it.
10 Two connections per describe_table Force a single connection One SQLAlchemy, one psycopg. Acceptable for a discovery call; collapsing them would mean reimplementing decision 1 or 2 by hand. Scoped listings do share one connection.

Scope

Worth flagging for reviewers: reusing DataSource means Snowflake, BigQuery, Redshift and Trino are comparatively cheap to add later, whereas MySQL and SQLite have no offline DataSource in Feast and would each need new type mappers written first.

Testing

  • 93 unit tests, all passing. No database required. Covers URL parsing, the dialect allowlist, identifier validation, the type-merge logic and response shaping. Four exercise driver loading and error translation for real against a closed port, and assert passwords never appear in error messages.
  • 18 integration tests against PostgreSQL 16 via testcontainers, following the existing PostgresContainer pattern.

ruff check, ruff format --check and mypy feast/schema_discovery/ are clean.

Important

The integration tests have not been executed. Docker was unavailable in the environment where this was written, so every assertion in tests/integration/schema_discovery/ is unverified. They collect cleanly and the file compiles, but CI is the first place they will actually run. Please treat that suite as unproven until CI reports on it.

Still open

The REST endpoint is deliberately not in this PR. It carries a security question that deserves its own review: accepting arbitrary credentialed connection URLs lets an authenticated caller open outbound connections from the server (SSRF), and puts user database passwords through the API surface. That belongs in review of the endpoint, not the library.

Draft while the integration suite gets its first real CI run.

Introduces feast.schema_discovery, which introspects a PostgreSQL
database from an ad-hoc connection URL. It requires neither a
feature_store.yaml nor a registered data source, so a user can explore a
database before defining anything in Feast.

SchemaDiscoveryService exposes list_schemas(), list_tables() and
describe_table(). Column discovery deliberately combines two sources:

- Feast's own DataSource supplies column types. Its
  get_table_column_names_and_types() reports canonical catalog spellings
  such as "character varying", which is what pg_type_to_feast_value_type
  keys on. SQLAlchemy reports "VARCHAR(255)" for the same column, which
  maps to UNKNOWN.
- SQLAlchemy's Inspector supplies schema and table navigation, plus the
  nullability and primary-key metadata that a cursor description omits.

Table and schema names reach the database through string interpolation
in DataSource.get_table_query_string(), so identifiers are validated
against a strict unquoted-identifier pattern before any connection is
opened. That pattern is anchored with \Z rather than $, because $ also
matches immediately before a trailing newline and would admit "users\n".

Errors subclass FeastError and carry http_status_code(), so the REST
layer added later can map them without a translation table.

v1 accepts PostgreSQL only; every other dialect is refused by allowlist.

Signed-off-by: Chaitany Patel <patelchaitany93@gmail.com>
Exercises SchemaDiscoveryService against a real PostgreSQL 16 instance
via testcontainers, following the PostgresContainer pattern already used
elsewhere in the integration suite.

Covers schema listing with system namespaces excluded, table and view
enumeration, column type mapping across the common PostgreSQL types,
primary-key and nullability reporting, describing a view, and the
not-found and connection-failure paths.

Includes a regression guard asserting that no column in the fixture
table maps to UNKNOWN. That assertion fails if column types ever start
coming from SQLAlchemy's type spellings ("VARCHAR(255)") rather than the
catalog spellings the Feast type mappers expect.

These tests require Docker and are marked with pytest.mark.integration.

Signed-off-by: Chaitany Patel <patelchaitany93@gmail.com>
@codecov-commenter

codecov-commenter commented Sep 4, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 77.97834% with 61 lines in your changes missing coverage. Please review.
✅ Project coverage is 47.26%. Comparing base (5ad5592) to head (3e40a54).

Files with missing lines Patch % Lines
sdk/python/feast/schema_discovery/introspector.py 50.74% 32 Missing and 1 partial ⚠️
sdk/python/feast/schema_discovery/service.py 75.86% 14 Missing ⚠️
sdk/python/feast/schema_discovery/dialects.py 84.00% 8 Missing and 4 partials ⚠️
sdk/python/feast/schema_discovery/errors.py 95.45% 2 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #6812      +/-   ##
==========================================
+ Coverage   47.08%   47.26%   +0.17%     
==========================================
  Files         419      425       +6     
  Lines       51878    52155     +277     
  Branches     7525     7549      +24     
==========================================
+ Hits        24429    24649     +220     
- Misses      25700    25754      +54     
- Partials     1749     1752       +3     
Flag Coverage Δ
go-feature-server 30.58% <ø> (ø)
python-unit 48.57% <77.97%> (+0.17%) ⬆️
Files with missing lines Coverage Δ
sdk/python/feast/schema_discovery/__init__.py 100.00% <100.00%> (ø)
sdk/python/feast/schema_discovery/models.py 100.00% <100.00%> (ø)
sdk/python/feast/schema_discovery/errors.py 95.45% <95.45%> (ø)
sdk/python/feast/schema_discovery/dialects.py 84.00% <84.00%> (ø)
sdk/python/feast/schema_discovery/service.py 75.86% <75.86%> (ø)
sdk/python/feast/schema_discovery/introspector.py 50.74% <50.74%> (ø)

... and 3 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5ad5592...3e40a54. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@patelchaitany
patelchaitany force-pushed the feat/sql-schema-discovery-upstream branch 2 times, most recently from 3cf819e to 3e40a54 Compare September 8, 2026 10:08
@patelchaitany
patelchaitany marked this pull request as ready for review September 8, 2026 11:31
@patelchaitany
patelchaitany requested a review from a team as a code owner September 8, 2026 11:31
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.

2 participants