SQL-free direct table API (TableService) + lossless Avro preservation - #241
Merged
Conversation
ryannedolan
approved these changes
Jul 27, 2026
| final RelDataType rowType; | ||
| final ImmutablePairList<Integer, String> targetFields; | ||
| final Map<String, String> hints; | ||
| if (deploymentContext instanceof CalciteDeploymentContext) { |
Collaborator
There was a problem hiding this comment.
This split is the only real wart I've seen so far, but it isn't bad.
Collaborator
Author
There was a problem hiding this comment.
Yea agreed, I don't love this but this change is specifically for deployers and logical tables sort of cross the boundary between creating a deployer and creating a pipeline so had to do some weird things to get it to work.
| * projection. The resulting {@code RelNode} renders (via {@code RelToSqlConverter}) to the same | ||
| * {@code SELECT * FROM catalog.schema.table} the SQL planner would have produced. | ||
| */ | ||
| public final class IdentityQuery { |
Add TableService.create/delete: a connection-free entrypoint that runs the same validation/deployment as CREATE TABLE from a path + Avro schema, with DirectDeploymentContext carrying the caller's Avro, registry-native DatabaseConfigResolver (K8s-native impl), IdentityQuery for connection-free inter-tier pipelines, updateIfExists/fail-when-exists semantics, and lossless Avro preservation (providedAvroSchema, valueSchemaOf, the Avro key/payload split) so deployers avoid the lossy Avro->RelDataType->Avro round-trip. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Venice TableService integration tests verified registration through the shared catalogs=k8s catalog, which resolves a table by enumerating the whole schema -> the Venice router's bulk /stores list. That list is a periodically-refreshed cache that lags the controller (where create writes), so a just-created store could be momentarily absent from it -- and because the refresh window can outlast a whole test run, every store created that run went missing together (the intermittent all-three-or-none "Table not resolvable after create" flake). Resolve each store via a direct jdbc:venice:// connection instead. VeniceDriver builds a fresh ClusterSchema per connect (no Calcite DataSourcePool), and tables().get(store) resolves the single store via the router's targeted discover_cluster endpoint, which reflects the create immediately -- so the assertions are unchanged but the lookup is deterministic with no polling. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jogrogan
force-pushed
the
jogrogan/decouple-pr2-api
branch
from
July 27, 2026 22:24
02383d0 to
ea151e6
Compare
Code Coverage
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Decouple Proteus DDL
Hoptimator's validation and deployment logic is currently reachable only by issuing SQL DDL through a Calcite/JDBC connection. That couples every caller to Calcite and forces schema definitions through a lossy Avro → RelDataType → Avro round-trip. Hoptimator via a CLI or over REST/gRPC may want to create/validate tables by handing us a table name + a schema directly — not by constructing DDL.
This stacked series decouples the validators and deployers from Calcite so the same validation and deployment engine can be driven either by SQL or by a direct, connection-free call — while keeping validators/deployers as a clean SPI. The payoff: an API call to "create table from a schema" path with identical guarantees to DDL (including dry-run), lossless Avro fidelity, and no Calcite dependency for callers that don't want one. It also keeps the door open for follow-ups (e.g. materializations, source→sink field mappings) without re-plumbing.
Stacked series (review/merge in order):
(This is PR 2 of 3.)
PR 2/3 — SQL-free direct table API (
TableService) + lossless Avro preservationBase:
jogrogan/decouple-pr1-spi· Branch:jogrogan/decouple-pr2-apiStacked series: 2 of 3 (PR1 SPI foundation → this → PR3 hardening)
Why
With the SPI decoupled from
java.sql.Connection(PR1), we can now offer table validationand creation without Calcite or DDL. This PR adds a connection-free entrypoint that
takes a table path + an Avro schema and runs the same validation and deployment that
CREATE TABLEdoes. This is the pathway a gRPC service (or any non-JDBC caller) needs:"here is a table name and an Avro schema — validate it and create it," with a dry-run
option, mirroring what
plan/specifydo today.What changes
TableService.create(...)/TableService.delete(...)— a connection-free entrypointthat resolves the target from a path + Avro schema and runs the existing
validation/deployment pipeline.
updateIfExistssemantics: create-or-update vs. fail-if-exists.dryRunsemantics: render specs without mutating anything (the direct-API analogue ofplan/specify).DirectDeploymentContext— carries the caller's Avro schema through the pipeline(connection-free).
DatabaseConfigResolver(SPI + K8s-native implK8sDatabaseConfigResolver) — resolvesDatabaseconfig directly from the registryinstead of via a Calcite catalog / JDBC connection.
IdentityQuery— connection-free inter-tier pipeline rendering for logical tables(the direct-path equivalent of the identity
INSERT INTO ... SELECT).providedAvroSchema,valueSchemaOf, and the Avrokey/payload split let deployers use the caller's Avro verbatim, avoiding the lossy
Avro → RelDataType → Avroround-trip (e.g. nested record namespaces survive).Integration tests (1:1 with the SQL path)
Adds per-connector
*TableServiceIntegrationTests exercising the direct API against realinfra: K8s, Kafka, MySQL, Venice, plus Logical (online/offline tiers) — mirroring the
existing SQL-path integration coverage.
Venice test resolution note: the Venice integration test verifies store registration via
a direct
jdbc:venice://connection rather than the sharedcatalogs=k8scatalog. Thecatalog resolves a table by enumerating the whole schema → the Venice router's bulk
/storeslist, a periodically-refreshed cache that lags the controller (wherecreatewrites). Because that refresh window can outlast a whole test run, a just-created store
could be missing for every store created that run (an intermittent "Table not resolvable
after create" flake). A direct connection builds a fresh
ClusterSchemaperconnect()and resolves the single store via the router's targeted
discover_clusterendpoint,which reflects the create immediately — deterministic, no polling.
Scope / non-goals
not implemented here, but not blocked either.
Testing
TableServiceTest,DirectDeploymentContextTest,HoptimatorDriverTest,DatabaseConfigResolver(s)/provider tests,K8sDatabaseConfigResolverTest,IdentityQueryTest,VeniceDeployerTestadditions.Review notes
TableService/context/resolver/tests). Existing SQL pathuntouched.
HoptimatorDriver.rowType()/valueSchema()gain the direct-path (Avro) case alongsidethe Calcite case introduced in PR1 — this is the one dispatch point both paths share; PR3
does not re-touch it.