Databricks SQL Image Source¶
roboflow_core/databricks_sql_image_source@v1 reads selected columns from a table or view on a
Databricks SQL warehouse and emits every returned row as a Workflow image, with
an optional key column as the image name and explicitly selected columns as metadata.
This page is the operator guide: how the block is enabled, bounded and
verified. The block's own description (shown in the Workflow builder) covers
its inputs and outputs.
Query contract¶
Configure catalog, schema_name, table, image_column (default bytes),
optional image_name_column, metadata_columns (default empty, at most 32) and
max_rows (1-1000). Each name is a single unquoted identifier, bounded to 255
characters. Embedded backticks are escaped within each component; dots are literal
parts of a component. The block executes only this template, extending the
projection with additional bound identifiers for the name and metadata columns:
SELECT IDENTIFIER(:column_0)
FROM IDENTIFIER(:source)
LIMIT :row_limit
Native parameters contain the quoted names and integer limit. SQL fragments,
expressions and sql_statement are not accepted; old workflow definitions must
migrate to these inputs. For example, use catalog=main, schema_name=vision,
table=images_to_label, image_column=image_bytes, max_rows=100.
Define filters and joins in a customer-managed Databricks view. A view preserves
selection flexibility; the fixed query and native parameter binding prevent input
from changing SQL structure. Requires SQL warehouse support for IDENTIFIER.
This does not prove authorization or tenant ownership, enforce read-only grants, or bound view computation. Provision a dedicated identity with SELECT on the intended sources and CAN USE on the warehouse. SQL LIMIT bounds returned rows, not warehouse cost. Aggregate admission controls are a separate stacked change.
Enabling and disabling¶
| Variable | Default | Meaning |
|---|---|---|
DATABRICKS_SQL_IMAGE_SOURCE_ENABLED |
true self-hosted, false when GCP_SERVERLESS or LAMBDA is set |
The rollout switch. An explicit value wins on every deployment. |
ALLOW_DATABRICKS_WORKFLOWS_SOURCE_TO_NON_GLOBAL_ADDRESSES |
false |
Accept warehouse hosts that resolve to private or link-local addresses (Private Link on a self-hosted server). Never honoured on the hosted platform. |
DATABRICKS_SQL_IMAGE_SOURCE_MAX_RUN_BYTES |
1073741824 (1 GiB) |
Byte budget per block invocation: encoded bytes, decoded pixels, names and metadata. |
DATABRICKS_SQL_IMAGE_SOURCE_MAX_IMAGE_BYTES |
33554432 (32 MiB) |
Encoded size limit per image. Also sizes the connector's inline result chunk (twice this value). |
DATABRICKS_SQL_IMAGE_SOURCE_MAX_IMAGE_PIXELS |
25000000 |
Pixel limit per image, read from the header before decoding. |
Installing the connector (pip install "roboflow-workflows[databricks]",
shipped in every Roboflow server image) and enabling the block are separate
decisions. The transport supports connector 4.8.0; the extra pins that version.
When the block is disabled, or the connector is missing or unsupported:
/workflows/blocks/describedoes not list it,- a Workflow that references it, saved or submitted inline, fails at compile
time with
Block ... is not available on this server: ...naming the reason, - no DNS lookup and no connection to Databricks is made.
The variables are read when the server process starts. Changing one is a redeployment or restart, not a live kill switch: on the hosted platform set it in the worker values and roll the deployment; on a self-hosted server restart the container. During a rollback:
- jobs queued before the rollout that reference the block fail at compile time once they reach a restarted worker,
- jobs already running on a worker that has not restarted finish or hit their
own
timeout_secondsdeadline (at most 600 s) plus the cleanup interval below; a worker that is terminated mid-run cancels nothing on the warehouse, rely on the warehouse statement timeout and verify its terminal state, WORKFLOW_DISABLED_BLOCK_PATTERNS=databricksremains a valid additional safeguard on multi-tenant workers and is applied by the same mechanism.
Memory and time bounds¶
Each invocation runs DNS resolution, the SQL connector and image decoding in a
fresh Python subprocess. The parent checks availability and disable_sinks
before launch. Credentials and resolved configuration travel over a private
pipe; connector patches never alter clients in the parent process. The serialized
request (credentials and configuration together) is measured before serialization
and limited to 1 MiB.
All budgets are per invocation, and account for encoded image bytes, decoded
pixels, names and metadata. They are application-data budgets, not hard process
RSS limits. Connector buffers, Python imports, decode temporaries and filesystem
cache require additional memory. Measure the candidate image at the intended
concurrency before enabling hosted execution; do not infer a pod memory limit
from MAX_RUN_BYTES alone.
The child writes decoded arrays and a bounded manifest into a private temporary directory. The parent loads them only after the child exits successfully, checks array headers and file sizes before allocation, and removes the directory on success or failure. Provision temporary storage for up to the invocation budget plus file/manifest overhead per concurrent invocation. Disk exhaustion fails the step; no partial output is returned.
The connector requests inline chunks of at most 8 rows and
2 x MAX_IMAGE_BYTES bytes, with a server-side row_limit of max_rows.
The transport enforces an HTTP body cap of
max(1 MiB, 2 x MAX_IMAGE_BYTES) + 64 KiB on every response, including chunked
responses. Oversized Content-Length is rejected before reading; no rejected body
is drained. Retryable HTTP errors are discarded at the urllib3 response boundary
before its retry logic can drain or decompress their bodies. HTTP compression is refused. Thrift strings are limited to this byte
cap, containers to 65,536 entries, total structural items to 131,072 and nesting
to 64 levels. The worker requests column-based results and rejects Arrow schemas,
Arrow/LZ4 batches and result links before their native decoders run. This remains
an application bound with Python object overhead, not an RSS guarantee.
Metadata strings (including nested keys) are limited to 4096 characters, containers
to 4 nesting levels and 256 items per collection, and serialized metadata to 64 KiB
per row (UTF-8 JSON, including keys and separators). Limits are checked during
conversion, before walking an oversized/deep collection or retaining an oversized
row. Values are never truncated or replaced with strings at the depth limit.
JPEG and PNG are accepted;
WebP and TIFF are rejected before constructing their decoders. Total budget exhaustion fails the step,
while ordinary image conversion failures follow skip_undecodable.
Rejected metadata rows¶
A metadata limit violation skips the entire row, before image decoding, and
returns a diagnostic on $steps.<source>.errors, regardless of skip_undecodable.
This also succeeds when every fetched row is rejected for metadata. The diagnostic
contains a zero-based row_index in the fetched result (not a stable table ID),
image_name (null when no name column was configured), field (the top-level
metadata column, or null for the aggregate row limit), code and message.
Only the first metadata violation per row is reported; raw metadata is not echoed.
| Code | Limit |
|---|---|
METADATA_VALUE_TOO_LONG |
A string or nested key exceeds 4096 characters. |
METADATA_TOO_DEEP |
A container occurs at depth 4 or greater; each column starts at depth 0. |
METADATA_TOO_MANY_ITEMS |
A collection contains more than 256 members, including binary members. |
METADATA_TOO_LARGE |
Serialized metadata exceeds 64 KiB. |
Successful entries have images, image_names, and metadata, with errors=null.
Rejected entries have errors, with all three success outputs null. The engine
preserves these positions in workflow JSON; it does not compact each output into
an independent batch. Image-processing blocks that use the usual non-empty-input
contract skip rejected entries automatically. Blocks explicitly accepting empty
values must handle nulls. A separate reporting branch should consume only
errors, not combine it with the success outputs (which are mutually exclusive).
Expose that selector as a JsonField to include diagnostics in the API response.
For example, a valid row followed by a rejected row produces:
{
"image_names": ["inspection-41", null],
"metadata": [{"site": "A"}, null],
"errors": [null, {
"row_index": 1,
"image_name": "inspection-42",
"field": "annotations",
"code": "METADATA_TOO_DEEP",
"message": "Metadata exceeds 4 nesting levels."
}]
}
Diagnostics are bounded by the fetched-row limit (at most 1000), fixed messages, 255-character field/name limits and the invocation byte budget. The parent checks their schema, counts, indices and bytes before returning worker output. Connection, query, timeout and total-budget failures still fail the block. An import containing only skipped image-conversion failures still fails as before; metadata rejections remain reportable even without successful images.
timeout_seconds (5 to 600, default 120) begins before process launch and covers
imports, DNS, connection, execution, fetching, decoding and parent output loading.
The warehouse receives a statement timeout based on the remaining time. On
expiry the parent terminates the child, giving cleanup a single 35-second
allowance before forcing termination and reaping it. Connector cancellation and
closing can block on the connector's request lock, so they are best effort;
killing a client does not prove its warehouse query stopped. Verify warehouse
terminal state in the deferred staging acceptance run.
Parent output loading checks expiry between bounded operations and before returning success. Arbitrary OS/filesystem stalls are outside this wall-clock guarantee. A server restart is still required to change the rollout flag.
Network policy¶
Only *.cloud.databricks.com, *.gcp.databricks.com and
*.azuredatabricks.net hosts are accepted, validated as a whole string, and
only SQL warehouse paths on the hosted platform (/sql/1.0/warehouses/<id>,
cluster paths are self-hosted only). The host is resolved once; every answer
must be a global address (unless the non-global flag is set on a self-hosted
server) and the first allowed answer is the one the connector connects to. The
connector's Thrift HTTP transport is pinned to that address while keeping the
hostname for TLS SNI, certificate verification and the Host header, so a DNS
change after validation cannot redirect the connection. Proxies, HTTP, disabled
TLS verification and redirects are refused by the transport; the connector's
second HTTP client (telemetry, feature flags, OAuth, cloud fetch, staging) is
refused for the entire child-process lifetime, including its background threads.
Unrelated clients in the parent process remain independent.
Transport inventory for databricks-sql-connector 4.8.0 (verified with
urllib3 2.8): one urllib3 pool for every Thrift call (session, execute,
polling, fetch, cancel, close, retries and reconnects) and one PoolManager
for everything else. Both are covered as described above; the REST (SEA)
and native kernel backends, including automatic fallback, are rejected before
backend construction.
Verification¶
Deterministic, in CI with the workflows unit tests:
workflows/tests/unit_tests/core_steps/integrations/test_databricks_sql_image_source.py: manifest and destination validation, enablement gate (discovery, execution, direct submission with zero connection attempts), budgets and limits at their exact boundaries, the crafted-TIFF case, oversized metadata and names, batching, cleanup on every failure path, repeated failures.test_databricks_transport_and_availability.py: the pinned pool, the second-client guard, loader and parser behaviour.test_databricks_real_transport.py: the real connector against a local HTTPS Thrift warehouse (databricks_thrift_harness.py, test-only CA): import across fetch chunks, SNI /Host/ single-destination assertions from socket-level observation, DNS changing after validation, retryable errors and forced reconnects, redirects to forbidden destinations (including HTTP and the metadata service address, refused at the socket), warehouse supplied result links, invalid certificates, proxies, slow-query cancellation within the cleanup interval, recovery and thread hygiene.test_databricks_connector_contract.py: the connector keywords, methods and transport hooks the block relies on, against the installed version.
Credentialed, against a real warehouse: workflows/scripts/databricks_live_integration.py
(see its docstring) runs import, bad image, oversized metadata, invalid
credential, slow-query cancellation verified through the warehouse's query
history, recovery and concurrent-job latency, locally or through the synchronous server /workflows/run endpoint, plus --expect-disabled and
--expect-budget-failure modes for deployments configured that way. The
GitHub workflow Databricks SQL Image Source - live warehouse integration
runs it on dispatch under the protected databricks-live environment. Configure
its HTTPS server origin with the environment variable DATABRICKS_LIVE_BASE_URL
(GitHub environment variable of the same name); callers cannot choose a URL in
dispatch inputs. Requests refuse redirects. The synchronous runner does not
validate the async-serverless producer/job API: that path requires a separate
staging run after PR review. No credentialed staging test is part of local review.
Record the image
digest, connector version and report in the release notes of any hosted
rollout.
The live runner creates temporary fixture views using DATABRICKS_SETUP_TOKEN
for every run, then submits only the fixed table/view reader configuration with
DATABRICKS_TOKEN. Grant that restricted identity SELECT on the fixture schema.
Views are dropped after the suite, with an exit cleanup retry on failures. Slow
case verification requires query history to expose the unique view identifier;
if it is absent, the case fails instead of claiming cancellation was verified.