Flink SQL: batch and stream¶
Gantry exposes Flink through the execution model you need:
gantry.batch.connect("flink", ...)
gantry.stream.connect("flink", ...)
Both surfaces run native Flink SQL through the same internal adapter. Flink still owns planning, connectors, execution, and data movement. Gantry owns admission, durable job identity, observation, verification, normalized failures, and the narrow interface exposed to an agent.
Batch¶
import gantry
batch = gantry.batch.connect(
"flink",
endpoint="https://sql-gateway.acme.internal",
jobmanager_endpoint="https://jobmanager.acme.internal",
token=os.environ["FLINK_TOKEN"],
)
daily_orders = batch.job(
inputs=["raw.orders"],
outputs=["analytics.daily_orders"],
checks=[
gantry.verify.output_exists(),
gantry.verify.row_count(min=1),
gantry.verify.required_columns(["order_date", "orders"]),
],
timeout=300,
)
result = await daily_orders("""
INSERT INTO analytics.daily_orders
SELECT CAST(order_time AS DATE), COUNT(*)
FROM raw.orders
GROUP BY CAST(order_time AS DATE)
""")
Batch supports one INSERT INTO ... SELECT or INSERT OVERWRITE ... SELECT statement. Acceptance
requires the Flink job to finish successfully and every configured output check to pass.
Stream¶
stream = gantry.stream.connect(
"flink",
endpoint="https://sql-gateway.acme.internal",
jobmanager_endpoint="https://jobmanager.acme.internal",
token=os.environ["FLINK_TOKEN"],
)
clean_events = stream.job(
inputs=["raw.events"],
outputs=["clean.events"],
checks=[
gantry.verify.running(),
gantry.verify.restart_count(max=3),
gantry.verify.watermark_lag(max_seconds=60),
],
timeout=120,
)
result = await clean_events("""
INSERT INTO clean.events
SELECT * FROM raw.events WHERE event_type IS NOT NULL
""")
A stream is accepted when it reaches RUNNING and passes its health checks. It remains active after
Gantry returns. Stream v0 rejects INSERT OVERWRITE.
Direct calls and agent tools¶
Configured jobs are both callable and tool-ready:
result = await clean_events(sql)
tools = [clean_events.tool()]
The tool schema contains only sql. Endpoint, credentials, batch/stream mode, input scope, output
scope, timeouts, and checks remain trusted application configuration and cannot be changed by the
agent.
Scope enforcement¶
Gantry extracts the actual source and sink objects from each proposal before contacting Flink:
job = stream.job(inputs=["raw.*"], outputs=["clean.*"])
INSERT INTO clean.events SELECT * FROM raw.eventsis in scope.- Reading
finance.eventsfails withINPUT_NOT_ALLOWED. - Writing
finance.eventsfails withOUTPUT_NOT_ALLOWED. - DDL, multiple statements, and streaming overwrite fail with
OPERATION_NOT_ALLOWED.
Flink's planner remains authoritative for dialect syntax, catalogs, columns, connector discovery,
and sink resolution. Gantry submits EXPLAIN PLAN FOR ... after local admission and before running
the statement.
Durable lifecycle¶
handle = await clean_events.submit(sql)
status = await clean_events.status(handle)
metrics = await clean_events.metrics(handle)
health = await clean_events.health(handle)
cancelled = await clean_events.cancel(handle)
The handle contains the native Flink JobID and non-secret metadata. A fresh process can construct the same configured job and observe or cancel that handle; it does not need the original SQL Gateway session.
Normalized metrics are runtime_seconds, records_in, records_out, restart_count, and
watermark_lag_seconds. Raw Flink details remain available in execution and metric metadata.
Output references¶
Gantry returns a reference rather than moving sink data through itself. By default, a batch sink
such as analytics.daily_orders becomes flink-table:///analytics.daily_orders; a stream sink has
stream output semantics. Supply authoritative URIs when the logical table maps elsewhere:
stream = gantry.stream.connect(
"flink",
endpoint=FLINK_ENDPOINT,
output_refs={"clean.events": "kafka://clean-events"},
)
result = await stream.job(inputs=["raw.events"], outputs=["clean.events"])(sql)
print(result.uri) # kafka://clean-events
Connection options¶
The SQL Gateway URL is endpoint. jobmanager_endpoint defaults to the same origin when a proxy
serves both REST APIs. Supported authentication inputs are token,
basic_auth=(username, password), string headers, an ssl.SSLContext, or a custom HTTP transport.
You can also set api_version, default_catalog, default_database, session_properties,
execution_config, request_timeout, validation_timeout, and submission_timeout.
Local integration test¶
docker compose -f examples/flink/docker-compose.yml up -d
pytest tests/test_flink_live.py -q
The adapter intentionally does not provide a transformation DSL, PyFlink execution, JAR upload, savepoints, checkpoint management, cluster deployment, catalog management, or connector installation.