confluent-sql 0.4.2__tar.gz → 0.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/.semaphore/semaphore.yml +1 -1
  2. confluent_sql-0.5.1/CHANGELOG.md +104 -0
  3. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/CLAUDE.md +32 -1
  4. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/DBAPI_EXTENSIONS.md +240 -12
  5. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/PKG-INFO +39 -6
  6. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/README.md +37 -4
  7. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/STREAMING.md +1 -1
  8. confluent_sql-0.5.1/examples/byoidc_bearer_token_example.py +34 -0
  9. confluent_sql-0.5.1/examples/statement_properties_example.py +73 -0
  10. confluent_sql-0.5.1/examples/tableflow_lifecycle_example.py +50 -0
  11. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/pyproject.toml +2 -2
  12. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/src/confluent_sql/__init__.py +60 -1
  13. confluent_sql-0.5.1/src/confluent_sql/auth.py +30 -0
  14. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/src/confluent_sql/changelog_compressor.py +58 -128
  15. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/src/confluent_sql/connection.py +1073 -99
  16. confluent_sql-0.5.1/src/confluent_sql/connectors.py +492 -0
  17. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/src/confluent_sql/cursor.py +9 -5
  18. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/src/confluent_sql/exceptions.py +64 -0
  19. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/src/confluent_sql/statement.py +19 -0
  20. confluent_sql-0.5.1/src/confluent_sql/statement_properties.py +322 -0
  21. confluent_sql-0.5.1/src/confluent_sql/tableflow.py +363 -0
  22. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/src/confluent_sql/types.py +18 -9
  23. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/tests/conftest.py +8 -0
  24. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/tests/integration/conftest.py +4 -1
  25. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/tests/integration/test_connection.py +2 -2
  26. confluent_sql-0.5.1/tests/integration/test_connector.py +184 -0
  27. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/tests/integration/test_cursor.py +444 -12
  28. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/tests/integration/test_fetch.py +2 -6
  29. confluent_sql-0.5.1/tests/integration/test_tableflow.py +152 -0
  30. confluent_sql-0.5.1/tests/unit/test_auth_unit.py +28 -0
  31. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/tests/unit/test_changelog_compressor_unit.py +335 -330
  32. confluent_sql-0.5.1/tests/unit/test_connection_byoidc_unit.py +221 -0
  33. confluent_sql-0.5.1/tests/unit/test_connection_connector_unit.py +184 -0
  34. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/tests/unit/test_connection_unit.py +389 -133
  35. confluent_sql-0.5.1/tests/unit/test_connection_unit_properties.py +413 -0
  36. confluent_sql-0.5.1/tests/unit/test_connectors_connection_unit.py +502 -0
  37. confluent_sql-0.5.1/tests/unit/test_connectors_unit.py +191 -0
  38. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/tests/unit/test_cursor_unit.py +9 -21
  39. confluent_sql-0.5.1/tests/unit/test_statement_properties_unit.py +396 -0
  40. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/tests/unit/test_statement_unit.py +17 -9
  41. confluent_sql-0.5.1/tests/unit/test_tableflow_connection_unit.py +525 -0
  42. confluent_sql-0.5.1/tests/unit/test_tableflow_unit.py +322 -0
  43. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/uv.lock +6 -6
  44. confluent_sql-0.4.2/CHANGELOG.md +0 -76
  45. confluent_sql-0.4.2/tests/unit/test_connection_unit_properties.py +0 -200
  46. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/.github/CODEOWNERS +0 -0
  47. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/.gitignore +0 -0
  48. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/.semaphore/publish_to_pypi.yml +0 -0
  49. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/ARCHITECTURE.md +0 -0
  50. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/LICENSE.txt +0 -0
  51. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/Makefile +0 -0
  52. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/TYPES.md +0 -0
  53. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/examples/errors.py +0 -0
  54. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/examples/simple_append_only_streaming_query_example.py +0 -0
  55. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/examples/snapshot_mode_tuple_cursor_simple_example.py +0 -0
  56. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/service.yml +0 -0
  57. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/src/confluent_sql/__version__.py +0 -0
  58. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/src/confluent_sql/execution_mode.py +0 -0
  59. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/src/confluent_sql/polling.py +0 -0
  60. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/src/confluent_sql/result_readers.py +0 -0
  61. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/src/confluent_sql/retry.py +0 -0
  62. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/tests/__init__.py +0 -0
  63. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/tests/unit/conftest.py +0 -0
  64. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/tests/unit/test_changelog_unit.py +0 -0
  65. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/tests/unit/test_execution_mode_unit.py +0 -0
  66. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/tests/unit/test_polling_unit.py +0 -0
  67. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/tests/unit/test_result_readers_unit.py +0 -0
  68. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/tests/unit/test_retry_unit.py +0 -0
  69. {confluent_sql-0.4.2 → confluent_sql-0.5.1}/tests/unit/test_types_unit.py +0 -0
@@ -31,7 +31,7 @@ blocks:
31
31
  jobs:
32
32
  - name: "Lint, Type Check, and Test"
33
33
  commands:
34
- - uv run --frozen ruff check && uv run --frozen pyright && uv run --frozen pytest -m unit --junitxml=test-results.xml
34
+ - uv run --frozen ruff format --check && uv run --frozen ruff check && uv run --frozen pyright && uv run --frozen pytest -m unit --junitxml=test-results.xml
35
35
  epilogue:
36
36
  always:
37
37
  commands:
@@ -0,0 +1,104 @@
1
+ # Change Log
2
+
3
+ All notable changes to this dbapi driver will be documented in this file.
4
+
5
+ ## Unreleased
6
+
7
+ ## 0.5.1, 2026-08-25
8
+
9
+ ### Fixed
10
+
11
+ - Streaming changelog compressor fixes for nontrivial changelog streams written to Kafka topics with multiple partitions:
12
+ - The changelog compressor for streaming non-append-only queries **without** upsert columns (`cursor.changelog_compressor()` on a keyless result, e.g. a global aggregation) no longer raises `InterfaceError` on legitimately out-of-order changelog events. When such a changelog is sinked to a multi-partition keyless topic, partitions are assigned by whole-row hash, so an updated row's `+U`/`-D` spelling can land on a different partition than its original `+I`/`-U` spelling; since Kafka only guarantees ordering within a partition, the events can be observed in a surprising order across spellings (a `+U` before its logical `-U`, or a `-D` before a later `+I`). The compressor previously assumed each `UPDATE_BEFORE` was immediately followed by its `UPDATE_AFTER` and failed on those sequences. It now treats the two additive ops (`+I`, `+U`) as inserts and the two retracting ops (`-U`, `-D`) as deletions, makes no ordering assumptions across different rows' spellings, and converges to the correct result set (intermediate snapshots may transiently show an extra row). Adds `Op.treat_as_insert` / `Op.treat_as_delete` helper properties. (#184)
13
+ - The changelog compressor for streaming non-append-only queries **with** upsert columns (`cursor.changelog_compressor()` on a keyed result, e.g. a `GROUP BY`) no longer raises `InterfaceError` when an unrelated key's changelog event arrives between one key's `UPDATE_BEFORE` and its own `UPDATE_AFTER`. A consumer reading a keyed upsert topic drains multiple partitions per poll; same-key events stay ordered (a key always hashes to the same partition), but a single fetch batch can still interleave *different* keys' events. The compressor previously modeled `UPDATE_BEFORE`/`UPDATE_AFTER` as a single-slot pending pair that had to be adjacent, and failed on that interleaving even though nothing was actually wrong. `UPDATE_BEFORE` is now treated as an unconditional no-op (it carries no information the matching insert/update doesn't already supply under key-based upsert semantics), and `UPDATE_AFTER` is handled exactly like `INSERT` (last write for the key wins); `DELETE` is unaffected and still validates that the key exists. As part of the same cleanup, the now-unused pending-update tracking hooks are removed from the shared compressor base class. (#185)
14
+
15
+ ## 0.5.0, 2026-08-07
16
+
17
+ ### Added
18
+
19
+ - `connect()`/`Connection()` accept a `local_time_zone` keyword argument that seeds a read/write `Connection.local_time_zone` property: a connection-level default for the `sql.local-time-zone` statement property, applied to every statement created by the connection unless that statement's own `properties=`/`StatementProperties` sets `local_time_zone`, which takes precedence.
20
+ - `StatementProperties` -- a frozen, keyword-only dataclass giving a typed, autocomplete-friendly way to set the curated statement options instead of hand-building a `sql.*` dict. Fields (all optional): `snapshot_write_mode`, `state_ttl` (a `timedelta`, rendered to a Flink duration such as `"3600 s"`), `scan_startup_mode`, `local_time_zone`, plus an `extra` dict escape hatch for options not yet modeled. Only set fields are emitted, so an instance never pins a default nor collides with the driver-owned overlay. The enum-typed fields also accept a bare `str` so a Flink value newer than this driver can still be passed; a wrong-property enum value, a field of the wrong Python type, or an `extra` key that duplicates a modeled field, raises at construction. `extra` is copied into a read-only mapping, so the frozen guarantee holds after construction too. Pass one anywhere a `properties=` dict is accepted (`Cursor.execute`, `execute_snapshot_ddl`, `execute_streaming_ddl`, ...); it is downgraded to a dict and validated identically. Adds the `ScanStartupMode` value enum (`earliest-offset`/`latest-offset`/`timestamp`/`specific-offsets`). (#163)
21
+ - New module `confluent_sql.statement_properties` gives statement `SET` options a discoverable, type-checkable face alongside the existing open-ended `PropertiesDict`. `Property` is a `str` enum of the `sql.*` option keys from the [SET-options reference](https://docs.confluent.io/cloud/current/flink/reference/statements/set.html) (e.g. `Property.SNAPSHOT_WRITE_MODE`); `SnapshotWriteMode` (`default`/`fast-write`) and `SnapshotMode` (`now`/`off`) enumerate the fixed value sets, sharing the `PropertyValue` base. Members are `str` instances that compare, hash, JSON-serialize, and stringify as their wire string, so they drop straight into a `properties=` dict with no `.value` unwrapping. `PropertiesDict` now advertises `Property` keys and `PropertyValue` values. (#162)
22
+ - class `Connection` now has methods to enable / inspect / disable [Tableflow](https://www.confluent.io/product/tableflow/) materialization of the Kafka topic backing a Flink table (#117). Tableflow-enabled topics/tables can be snapshot queried in an optimized fashion.
23
+ - `Connection.enable_tableflow(table_name, *, tableflow_formats, storage, config=None, wait_for_running=True, timeout=300)` adds an Iceberg/Delta sink. `tableflow_formats` takes a single `TableFormat` (e.g. `TableFormat.ICEBERG`) or a collection for several; `storage` is one of `ManagedStorage()` (zero-config), `ByobAwsStorage`, or `AzureAdlsStorage`; `config` is an optional `TableflowTopicConfig`. By default it blocks until the topic reaches `RUNNING`; pass `wait_for_running=False` to return as soon as the create is accepted (topic in `PENDING`). Raises `TableflowTopicAlreadyExistsError` if Tableflow is already enabled.
24
+ - `Connection.get_tableflow(table_name)` returns the current `TableflowTopic` (phase, spec, status), raising `TableflowTopicNotFoundError` if Tableflow is not enabled for the topic.
25
+ - `Connection.disable_tableflow(table_name, *, wait_for_removal=True, timeout=300)` tears the sink down (all-or-nothing in v1, no per-format disable). By default it blocks until the topic is confirmed gone; pass `wait_for_removal=False` to return as soon as the delete is accepted.
26
+ - `organization_id` is now optional in `connect()`/`Connection()` when a global API key is supplied: if omitted, it's inferred via `GET /org/v2/organizations` -- lazily, on first use of the connection (not at `connect()` time) -- and used when exactly one organization is visible to the key. Raises `OperationalError` on first use if zero or multiple organizations are visible. Unchanged (still required, validated eagerly by `connect()`) for a Flink-region-only key or a dedicated Tableflow/Connect key, neither of which has `/org/v2` reach. (#132)
27
+ - BYOIDC bearer-token authentication for the Flink data plane: `connect()`/`Connection()` now accept an `external_access_token` / `identity_pool_id` pair, letting you authenticate with a bearer token minted by your own OAuth/OIDC identity provider (an "external" token) instead of a Confluent API key + secret. The `external_access_token` name mirrors the Confluent Flink Table API plugin's `client.oauth.external-access-token` option. When supplied, every Flink request carries `Authorization: Bearer <external_access_token>` and `Confluent-Identity-Pool-Id: <identity_pool_id>`. The two are mutually exclusive with every API-key parameter and must be provided together. Scope is the Flink data plane only -- Confluent's authorization model accepts no external token on the control-plane routes this driver calls, so Tableflow, Connectors, and the CMK cluster-id lookup fail closed under BYOIDC (they require an API-key connection), and `organization_id` stays mandatory (there is no control-plane reach to infer it). The token is used verbatim with no refresh; an expired token starts failing requests (surfaced as `OperationalError`) and the connection must be re-opened with a fresh token. (#100)
28
+
29
+ ### Fixed
30
+
31
+ - Every network-level transport failure from the Flink gateway (`httpx.ConnectError`, `httpx.ReadError`, `httpx.RemoteProtocolError`, timeouts, etc.) is now translated to `OperationalError` instead of leaking the raw `httpx` exception -- this applies uniformly to idempotent GETs (once #137's retry budget is exhausted) and to non-idempotent POST/PATCH/DELETE calls (statement submission, `stop_statement()`, `delete_statement()`), matching the DB-API v2 contract that every exception the driver raises is one of `confluent_sql`'s own `Error` subclasses. The original exception remains available via `__cause__`. (#138)
32
+
33
+ ## 0.4.2, 2026-07-17
34
+
35
+ ### Changed
36
+
37
+ - Snapshot queries on Confluent Cloud Flink SQL are now Generally Available. Removed the Early Access warning previously emitted on first creation of a snapshot-mode cursor, along with the Early Access advisories throughout the documentation (`README.md`, `ARCHITECTURE.md`, `DBAPI_EXTENSIONS.md`, `STREAMING.md`). (#119, #160)
38
+
39
+ ## 0.4.1, 2026-07-07
40
+
41
+ ### Fixed
42
+
43
+ - Idempotent GET requests (`list_statements()`'s page-fetch loop, `get_statement()`, and result-page fetching) now retry transient transport errors -- connection resets (`httpx.NetworkError`) and servers that close pooled connections without responding (`httpx.RemoteProtocolError`) -- up to 3 times with a short exponential backoff, instead of failing on the first blip. POST/PATCH/DELETE requests (statement submission, `stop_statement()`, `delete_statement()`) are deliberately left unretried, since re-issuing them after a connection reset could double-submit or double-mutate state. (#137)
44
+ - The same idempotent GET requests now also retry transient HTTP response statuses (429, 500, 502, 503, 504), not just transport-level exceptions -- a request that reaches Confluent Cloud but gets a momentarily-overloaded gateway response was previously failing on the first such response instead of being retried like a dropped connection. (#140)
45
+ - Increased the default HTTP timeout from 5 to 10s for safety. `connect()` and `Connection.__init__()` no longer accept explicit `None` for `http_timeout_secs`.
46
+
47
+ ## 0.4.0, 2026-06-15
48
+
49
+ ### Added
50
+
51
+ - Support for "Global" Confluent Cloud API keys: `confluent_sql.connect()` (and `Connection`) now accept `global_api_key` / `global_api_secret`. A Global key works against every route this driver touches, so it is preferred over a Flink Region key when both are supplied. `flink_api_key` / `flink_api_secret` remain supported and are now optional; at least one fully-specified pair must be provided. If both pairs are supplied, the Global pair is used (and the Flink pair ignored, with a warning); a half-specified pair (key without secret, or vice versa) is rejected. (#112)
52
+ - Support for "poolless Flink": `confluent_sql.connect()` now treats `compute_pool_id` as optional. Statements submitted w/o their own overriding `compute_pool_id` via a connection w/o a default compute pool id will make use of the default compute pool in the environment+cloud region (provisioning one if necessary). See [the Confluent documentation](https://docs.confluent.io/cloud/current/flink/concepts/compute-pools.html#default-compute-pools) for more details.
53
+ - New `Connection.stop_statement(statement, *, wait_for_stopped=True, timeout=300)` method to stop a running statement without deleting it, leaving the statement resource around for inspection (unlike `delete_statement()`, which also destroys it). Accepts a statement name or a `Statement` object. By default it blocks until the statement reaches `STOPPED`; pass `wait_for_stopped=False` to return as soon as the stop is accepted. A matching `Cursor.stop_statement()` stops the cursor's current statement. New `Statement.is_stopped`, `Statement.is_stopping`, and `Statement.stop_requested` properties expose the relevant state. (#61)
54
+
55
+ ### Changed
56
+
57
+ - `Connection.list_statements()`:
58
+ - New optional parameter `compute_pool_id` to list statements only in a single compute pool (otherwise environment-wide).
59
+ - New optional parameter `name_contains: str` to filter statements server-side to those whose name contains the given substring (case-sensitive).
60
+ - Existing parameter `label` is now optional.
61
+ - The end result is that callers can now provide between zero and all of the possible kwargs to vary between 'no filtering at all, return all current statements in the environment' and 'apply all the possible filters as if ANDed together.'
62
+
63
+ ## 0.3.1, 2026-05-21
64
+
65
+ ### Added
66
+
67
+ - New `http_timeout_secs` parameter for `connect()` to let the caller control how long to wait in HTTP requests.
68
+
69
+ ## 0.3.0, 2026-04-09
70
+
71
+ ### Changed - Breaking
72
+
73
+ - `connect()` / `Connection.__init__()`: Renamed `environment` parameter to `environment_id` to clarify that an environment ID (_not_ name) is expected. The internal attribute `Connection.environment` has also been renamed to `Connection.environment_id`. Update all calls from `connect(environment="env-123")` to `connect(environment_id="env-123")`. (#92)
74
+ - `Cursor.execute()` and peers: Respelled and re-typed the `statement_label: str | None` parameter to be `statement_labels: list[str] | None` to allow multiple labels to be applied to a statement, including `HIDDEN_LABEL`.
75
+
76
+ ### Added
77
+
78
+ - New `Connection.get_statement(statement)` method to retrieve statement metadata by name or refresh a Statement object with the latest server state. Accepts either a statement name (string) or a Statement object. Returns a Statement object with current phase, schema, and execution traits. (#86)
79
+ - New `StatementNotFoundError` exception, a subclass of `OperationalError`, raised by `Connection.get_statement(statement)` when attempting to retrieve a statement that does not exist. Provides programmatic access to the statement name via the `statement_name` attribute.
80
+ - New constant `confluent_sql.HIDDEN_LABEL` used for driving `Cursor.execute()` to indicate that the statement should be hidden in default listings in Confluent Cloud UIs. This feature is intended to be used for minor queries, such as when investigating `INFORMATION_SCHEMA`.
81
+ - Added documentation regarding use of `connect(endpoint=)` parameter to make use of private networking endpoints (README.md, docstrings).
82
+
83
+ ## 0.2.0, 2026-03-26
84
+
85
+ ### Changed
86
+
87
+ - Respelled the `connect()` parameter `dbname` to `database`. The old spelling `dbname` is deprecated and will be removed in after one release cycle.
88
+ - Class `SqlNone` now gracefully strips trailing `NOT NULL` constraints from type names (case-insensitively), so that `str(SqlNone("DATE NOT NULL"))` returns valid FlinkSQL `"cast (null as DATE)"`.
89
+ - `connect()` is now keyword-only callable.
90
+ - The `host` parameter for `Connection.__init__()` has been renamed to `endpoint`.
91
+ - Clarified and improved documentation around Flink region API key use.
92
+
93
+ ### Added
94
+
95
+ - New optional keyword parameter `properties: PropertiesDict | None` on `Cursor.execute()` and related methods to allow callers to provide [statement execution properties](https://docs.confluent.io/cloud/current/flink/reference/statements/set.html#table-options). Note: connection or cursor-level properties for default catalog, database, and execution mode cannot be overridden by this parameter.
96
+ - New optional `endpoint` parameter on `connect()` and `Connection.__init__` to allow users to specify a custom Confluent Cloud API base endpoint (e.g., for private networking, staging, etc.). Mutually exclusive with (`cloud_provider`, `cloud_region`) -- either `endpoint` or (`cloud_provider`, `cloud_region`) must be provided. This replaces the `host` parameter in `Connection.__init__()`. (#66)
97
+
98
+ ### Removed
99
+
100
+ - The unused control-plane `api_key` and `api_secret` `connect()` parameters have been removed. The Flink Region API key params `flink_api_key` and `flink_api_secret` remain.
101
+
102
+ ## 0.1.x
103
+
104
+ Early access release of the driver.
@@ -1,4 +1,7 @@
1
- # Project-specific instructions for confluent-sql
1
+ # Repository conventions for Claude
2
+
3
+ Guidance for any Claude working in this repository. Keep it short and concrete; add a convention
4
+ here only once the codebase actually follows it in more than one place.
2
5
 
3
6
  ## HTTP request routing: idempotent GETs vs mutating requests
4
7
 
@@ -27,3 +30,31 @@ those are equally idempotent GETs even though they weren't the reported failure.
27
30
  the same call sites to also retry on retryable HTTP status codes, not just transport exceptions.
28
31
  The reasoning, and the case for a plain function over a decorator, is captured in `retry.py`'s
29
32
  module docstring and `_request_get`'s docstring.
33
+
34
+ ## Blocking vs. non-blocking lifecycle methods
35
+
36
+ Methods that drive a server-side resource toward a steady state (start a statement, stop it,
37
+ enable/disable a Tableflow sink, etc.) follow a uniform shape:
38
+
39
+ - **Block by default; return the settled state.** The common call should leave the resource in its
40
+ intended terminal/ready state, not hand back a transient one the caller must then poll itself.
41
+ - **Expose a `wait_for_<settled-state>: bool = True` keyword-only argument.** The suffix names the
42
+ exact condition awaited — *not* a generic `wait`/`block`. Existing names: `wait_for_stopped`
43
+ (statement reaches STOPPED), `wait_for_running` (Tableflow topic reaches RUNNING),
44
+ `wait_for_removal` (Tableflow topic is gone). A new method picks the suffix that is literally
45
+ true of what it waits for.
46
+ - **Pair it with a uniform `timeout` keyword arg** (seconds; the standardized knob across all such
47
+ methods).
48
+ - **`=False` is the opt-out**: return as soon as the request is accepted (resource typically in a
49
+ PENDING/just-requested state).
50
+ - **While waiting, raise on terminal failure and on timeout** — surface the failure detail
51
+ (`OperationalError`, e.g. a statement/topic that went FAILED), don't silently return a broken
52
+ resource.
53
+ - **Implement the wait by polling with `sleep_with_backoff`** from `polling.py`; don't hand-roll a
54
+ sleep loop.
55
+
56
+ Exemplars: `Connection.stop_statement` / `Cursor.stop_statement` (`wait_for_stopped=True`),
57
+ `Connection.enable_tableflow` (`wait_for_running=True`), `Connection.disable_tableflow`
58
+ (`wait_for_removal=True`). The DDL convenience methods (`execute_snapshot_ddl`,
59
+ `execute_streaming_ddl`) block unconditionally and offer no opt-out — that's also acceptable when a
60
+ non-blocking variant would be meaningless.
@@ -34,6 +34,7 @@ For comprehensive details on streaming queries, polling patterns, and changelog
34
34
  - [Result Format Extensions](#result-format-extensions) - Dictionary rows, custom types
35
35
  - [Streaming Query Support](#streaming-query-support) - Comprehensive streaming guide
36
36
  - [Statement Lifecycle Management](#statement-lifecycle-management) - DDL, naming, stopping, deletion
37
+ - [Tableflow Lifecycle](#tableflow-lifecycle) - Enable, read, and disable Iceberg/Delta sinks
37
38
  - [Introspection and Metadata](#introspection-and-metadata) - Properties for query state
38
39
  - [Performance Monitoring](#performance-monitoring) - Fetch metrics
39
40
  - [Type System Extensions](#type-system-extensions) - Flink type support
@@ -565,6 +566,147 @@ cursor.delete_statement()
565
566
 
566
567
  ---
567
568
 
569
+ ## Tableflow Lifecycle
570
+
571
+ [Tableflow](https://www.confluent.io/product/tableflow/) materializes the Kafka topic backing a
572
+ Flink table into an Iceberg or Delta table. Three `Connection` methods manage that sink. Enabling
573
+ it also unlocks
574
+ [efficiency gains for snapshot queries](https://docs.confluent.io/cloud/current/flink/concepts/snapshot-queries.html#snapshot-queries-and-tableflow)
575
+ against the table.
576
+
577
+ In Confluent Flink a table is backed by a like-named Kafka topic, so the `table_name` you pass is
578
+ both the Flink table and the topic — no escaping or casing translation.
579
+
580
+ A runnable example covering the full enable/get/disable lifecycle is in
581
+ [examples/tableflow_lifecycle_example.py](examples/tableflow_lifecycle_example.py).
582
+
583
+ > **Not available under BYOIDC.** Tableflow is a control-plane surface, and Confluent's
584
+ > authorization model accepts no BYOIDC bearer token there. A connection authenticated with
585
+ > `external_access_token` / `identity_pool_id` (see the README's [BYOIDC bearer-token
586
+ > authentication](README.md#byoidc-bearer-token-authentication)) fails closed on these methods —
587
+ > use an API-key connection for Tableflow.
588
+
589
+ ### Selecting formats: `TableFormat`
590
+
591
+ There is a single format vocabulary, `TableFormat` (`ICEBERG` / `DELTA`), on both the request and
592
+ response sides. A topic can carry **both** formats at once (there is no per-format config), so
593
+ `enable_tableflow`'s `tableflow_formats` argument accepts either a single `TableFormat` for the
594
+ common case or a collection for both:
595
+
596
+ ```python
597
+ connection.enable_tableflow("orders", tableflow_formats=TableFormat.ICEBERG, storage=...)
598
+ connection.enable_tableflow(
599
+ "orders", tableflow_formats={TableFormat.ICEBERG, TableFormat.DELTA}, storage=...
600
+ )
601
+ ```
602
+
603
+ Responses name the same `TableFormat`s (`topic.spec.table_formats`,
604
+ `topic.status.failing_table_formats`), so checking what you got against what you asked for is a
605
+ plain set comparison:
606
+
607
+ ```python
608
+ topic = connection.enable_tableflow(
609
+ "orders", tableflow_formats={TableFormat.ICEBERG, TableFormat.DELTA},
610
+ storage=ManagedStorage(), wait_for_running=True,
611
+ )
612
+ assert set(topic.spec.table_formats) == {TableFormat.ICEBERG, TableFormat.DELTA}
613
+ ```
614
+
615
+ ### Storage variants
616
+
617
+ `enable_tableflow` requires an explicit, frozen storage spec — no silent default:
618
+
619
+ - `ManagedStorage()` — Confluent-managed bucket, zero config.
620
+ - `ByobAwsStorage(bucket_name=..., provider_integration_id=...)` — bring-your-own AWS S3 bucket.
621
+ - `AzureAdlsStorage(storage_account_name=..., container_name=..., provider_integration_id=...)` —
622
+ customer-owned Azure Data Lake Storage Gen2.
623
+
624
+ ### Cluster-id resolution
625
+
626
+ The Tableflow API addresses the cluster by its `lkc-…` id, which the connection must know. Either:
627
+
628
+ - Pass `database_kafka_cluster_id` to `connect()` (works with only a `tableflow_api_key` pair), or
629
+ - Let it resolve lazily from `database` (the cluster name) via CMK on first use — this path
630
+ requires a **global** API key, and the resolved id is cached for the connection's life. A name
631
+ that matches more than one cluster raises, listing the candidate ids so you can disambiguate with
632
+ `database_kafka_cluster_id`.
633
+
634
+ ### `enable_tableflow()` — add an Iceberg/Delta sink
635
+
636
+ ```python
637
+ from confluent_sql import ManagedStorage, TableFormat, TableflowPhase
638
+
639
+ topic = connection.enable_tableflow(
640
+ "orders",
641
+ tableflow_formats=TableFormat.ICEBERG,
642
+ storage=ManagedStorage(),
643
+ )
644
+ assert topic.phase is TableflowPhase.RUNNING # blocked to RUNNING by default
645
+ ```
646
+
647
+ **Behavior notes:**
648
+
649
+ - `tableflow_formats` and `storage` are required (no defaults); `tableflow_formats` must name at
650
+ least one format. `config` is an optional `TableflowTopicConfig` (retention, error-handling)
651
+ shared across all enabled formats.
652
+ - Blocks until `RUNNING` by default (`wait_for_running=True`), raising `OperationalError` on
653
+ `FAILED` (surfacing `status.error_message` and `failing_table_formats`) — consistent with
654
+ `stop_statement`'s wait-by-default. Pass `wait_for_running=False` to return as soon as the create
655
+ is accepted (topic in `PENDING`).
656
+ - Raises `TableflowTopicAlreadyExistsError` if Tableflow is already enabled (HTTP 409), or
657
+ `ProgrammingError` if no management credential is available or the cluster id can't be resolved.
658
+
659
+ ### `get_tableflow()` — read current state
660
+
661
+ ```python
662
+ topic = connection.get_tableflow("orders")
663
+ print(topic.phase) # TableflowPhase.PENDING / RUNNING / FAILED
664
+ print(topic.spec.table_formats) # [TableFormat.ICEBERG, ...]
665
+ ```
666
+
667
+ Raises `TableflowTopicNotFoundError` if Tableflow is not enabled for the topic (HTTP 404). There is
668
+ no separate health check — health is read off `get_tableflow(...).phase`.
669
+
670
+ ### `disable_tableflow()` — tear down the sink
671
+
672
+ ```python
673
+ connection.disable_tableflow("orders") # blocks until confirmed gone by default
674
+ ```
675
+
676
+ **Behavior notes:**
677
+
678
+ - All-or-nothing in v1: removes the entire Tableflow topic. (Removing just one of two enabled
679
+ formats needs a future API and is not yet supported.)
680
+ - Deletion is asynchronous. Blocks until removal is confirmed by default (`wait_for_removal=True`),
681
+ polling `get_tableflow` until it `404`s. This is why a following `DROP TABLE` is safe by default —
682
+ dropping the Flink table drops its backing topic, so Tableflow must be confirmed gone first to
683
+ avoid racing an active materialization. Pass `wait_for_removal=False` to return as soon as the
684
+ `DELETE` is accepted.
685
+ - Raises `TableflowTopicNotFoundError` if Tableflow was not enabled (HTTP 404).
686
+
687
+ ### Reusing format and config across many tables
688
+
689
+ Every input is a reusable value — the selection is an enum member, and `storage`/`config` are
690
+ frozen — so hoist them out of the loop. The cluster-id lookup resolves once and is cached, so the
691
+ loop hits CMK at most once:
692
+
693
+ ```python
694
+ from confluent_sql import ManagedStorage, TableFormat, TableflowTopicConfig
695
+
696
+ storage = ManagedStorage()
697
+ config = TableflowTopicConfig(retention_ms="604800000")
698
+
699
+ for table in ("orders", "shipments", "returns"):
700
+ connection.enable_tableflow(
701
+ table,
702
+ tableflow_formats=TableFormat.ICEBERG,
703
+ storage=storage,
704
+ config=config,
705
+ )
706
+ ```
707
+
708
+ ---
709
+
568
710
  ## Introspection and Metadata
569
711
 
570
712
  ### Connection Properties
@@ -894,7 +1036,8 @@ cursor.execute(
894
1036
  timeout: int = 3000,
895
1037
  statement_name: str | None = None,
896
1038
  statement_labels: list[str] | None = None,
897
- properties: dict[str, str | int | bool] | None = None,
1039
+ properties: dict[str, str | int | bool] | StatementProperties | None = None,
1040
+ compute_pool_id: str | None = None,
898
1041
  ) -> None
899
1042
  ```
900
1043
 
@@ -907,20 +1050,73 @@ cursor.execute(
907
1050
  | `timeout` | `int` | 3000 | Max seconds to wait for statement to reach RUNNING/COMPLETED phase |
908
1051
  | `statement_name` | `str \| None` | None | Custom statement identifier (defaults to UUID) |
909
1052
  | `statement_labels` | `list[str] \| None` | None | List of labels for grouping related statements |
910
- | `properties` | `dict[str, str \| int \| bool] \| None` | None | [Statement properties](#statement-properties) to set for execution |
1053
+ | `properties` | `dict[str, str \| int \| bool] \| StatementProperties \| None` | None | [Statement properties](#statement-properties) to set for execution |
1054
+ | `compute_pool_id` | `str \| None` | None | Compute pool to run this statement on, overriding the connection's default |
911
1055
 
912
1056
  ### Statement Properties
913
1057
 
914
- The `properties` parameter allows you to set [Flink SQL statement properties](https://docs.confluent.io/cloud/current/flink/reference/statements/set.html#table-options) at query execution time. These are the same properties that can be set with Flink SQL `SET` statements.
1058
+ The `properties` parameter sets [Flink SQL statement properties](https://docs.confluent.io/cloud/current/flink/reference/statements/set.html#table-options) at query execution time the same properties Flink SQL `SET` statements control. There are two ways to provide them:
1059
+
1060
+ - **`StatementProperties`** (recommended) — a frozen, keyword-only dataclass covering the curated
1061
+ options below, discoverable via autocomplete and validated at construction time instead of at
1062
+ the server. A wrong-property enum value (e.g. a `SnapshotMode` passed to `scan_startup_mode`), a
1063
+ field of the wrong Python type, or an `extra` key that duplicates a modeled field all raise
1064
+ immediately:
1065
+
1066
+ ```python
1067
+ from confluent_sql import Property, ScanStartupMode, SnapshotWriteMode, StatementProperties
1068
+ from datetime import timedelta
1069
+
1070
+ cursor.execute(
1071
+ "SELECT * FROM orders WHERE status = %s",
1072
+ ("pending",),
1073
+ properties=StatementProperties(
1074
+ state_ttl=timedelta(hours=1), # -> "3600 s"
1075
+ snapshot_write_mode=SnapshotWriteMode.FAST_WRITE,
1076
+ scan_startup_mode=ScanStartupMode.EARLIEST_OFFSET,
1077
+ # `extra` escape hatch for a property not yet a typed field; Keys can
1078
+ # either be Property enums or strings.
1079
+ extra={Property.SCAN_IDLE_TIMEOUT: "30 s"},
1080
+ ),
1081
+ )
1082
+ ```
1083
+
1084
+ The set of modeled fields grows over time -- see the `StatementProperties` docstring/source for
1085
+ the current list -- but the shape is uniform: only fields you actually set are emitted, so an
1086
+ unset field never pins a server default or collides with the driver's own overlay, and each
1087
+ enum-typed field also accepts a bare `str`, so a Flink value newer than this driver's enum can
1088
+ still be passed through without waiting for a driver release.
1089
+
1090
+ - **A raw `dict[str, str | int | bool]`** — the original, open-ended form. Any `sql.*` key is
1091
+ accepted, keyed by the string from the [SET-options
1092
+ reference](https://docs.confluent.io/cloud/current/flink/reference/statements/set.html), which is
1093
+ useful for options `StatementProperties` doesn't model yet (equivalent to `extra` above, without
1094
+ needing to go via the dataclass):
1095
+
1096
+ ```python
1097
+ cursor.execute(query, properties={"sql.state-ttl": "3600 s"})
1098
+ ```
1099
+
1100
+ `confluent_sql.Property` enumerates the known `sql.*` keys (e.g. `Property.STATE_TTL`) if you
1101
+ want autocomplete on the keys without adopting the full dataclass; members are plain `str`
1102
+ instances, so they drop straight into the dict with no `.value` unwrapping.
1103
+
1104
+ Both forms are validated identically — a `StatementProperties` is downgraded to a dict internally
1105
+ before the same checks run, so a reserved key smuggled through `extra` is rejected the same way a
1106
+ raw dict would be.
915
1107
 
916
1108
  **Important Precedence Rules:**
917
- - Connection-level defaults (catalog, database) are always applied
918
- - Cursor execution mode settings (e.g., `sql.snapshot.mode` for snapshot queries) are always applied
919
- - User-provided properties in the `properties` parameter can extend these settings but cannot override system properties
920
-
1109
+ - System properties are always applied and cannot be overridden by the caller: the connection's
1110
+ catalog/database and the cursor's execution mode (e.g. `sql.snapshot.mode` for snapshot queries).
1111
+ - The connection-level `local_time_zone` default (see [`Connection.local_time_zone`](#connection-level-local_time_zone-default) below) fills in `sql.local-time-zone` only when the
1112
+ call's own `properties` didn't already set it.
1113
+ - User-provided properties in the `properties` parameter can set anything not covered by the two
1114
+ rules above, but attempting to set a system property (e.g. `sql.current-catalog`) raises
1115
+ `InterfaceError` rather than being silently overridden.
921
1116
 
922
1117
  **Accessing Properties After Execution:**
923
- The properties are stored in the Statement object and can be accessed via `statement.properties`:
1118
+ The properties are stored in the cursor-captured Statement object and can be accessed via
1119
+ `statement.properties`, a `dict[str, str | int | bool]`:
924
1120
 
925
1121
  ```python
926
1122
  cursor.execute(query, properties={"sql.state-ttl": "100 ms"})
@@ -928,7 +1124,31 @@ props = cursor.statement.properties
928
1124
  assert props["sql.state-ttl"] == "100 ms"
929
1125
  ```
930
1126
 
931
- **Examples:**
1127
+ ### Connection-level `local_time_zone` default
1128
+
1129
+ `Connection.local_time_zone` (also settable via `connect(local_time_zone=...)`) is a read/write
1130
+ property that seeds `sql.local-time-zone` for every statement the connection executes, so you don't
1131
+ have to repeat it on each `execute()` call:
1132
+
1133
+ ```python
1134
+ connection.local_time_zone = "America/Chicago"
1135
+ cursor.execute("SELECT CURRENT_TIMESTAMP") # runs with sql.local-time-zone = America/Chicago
1136
+
1137
+ # A statement can still override it for itself:
1138
+ cursor.execute(
1139
+ "SELECT CURRENT_TIMESTAMP",
1140
+ properties=StatementProperties(local_time_zone="America/Los_Angeles"),
1141
+ )
1142
+ ```
1143
+
1144
+ The connection-level value only fills in where a statement's own `properties`
1145
+ (dict or `StatementProperties`) didn't already set `sql.local-time-zone` — it never overrides an
1146
+ explicit per-call value. Set it to `None` to stop emitting a default.
1147
+
1148
+ A runnable example covering both property forms and the connection-level default is in
1149
+ [examples/statement_properties_example.py](examples/statement_properties_example.py).
1150
+
1151
+ **General Usage Examples:**
932
1152
 
933
1153
  ```python
934
1154
  # Basic execution
@@ -940,7 +1160,7 @@ cursor.execute("SELECT * FROM users WHERE age > %s", (18,))
940
1160
  # With custom timeout
941
1161
  cursor.execute(
942
1162
  "SELECT * FROM users",
943
- timeout=5000 # Wait up to 5000 seconds
1163
+ timeout=100 # Wait up to 100 seconds
944
1164
  )
945
1165
 
946
1166
  # With statement naming
@@ -950,7 +1170,7 @@ cursor.execute(
950
1170
  statement_name="completed-orders-daily"
951
1171
  )
952
1172
 
953
- # With statement labeling (for batch operations)
1173
+ # With statement labeling
954
1174
  cursor.execute(
955
1175
  "CREATE TABLE orders_backup AS SELECT * FROM orders",
956
1176
  statement_labels=["daily-backups", "batch-job"]
@@ -965,13 +1185,21 @@ cursor.execute(
965
1185
  statement_labels=["analytics", "hourly"]
966
1186
  )
967
1187
 
968
- # With statement properties
1188
+ # With statement properties (raw dict)
969
1189
  cursor.execute(
970
1190
  "SELECT * FROM orders WHERE status = %s",
971
1191
  ("pending",),
972
1192
  statement_name="pending-orders-query",
973
1193
  properties={"sql.state-ttl": "100 ms"}
974
1194
  )
1195
+
1196
+ # With statement properties (StatementProperties)
1197
+ cursor.execute(
1198
+ "SELECT * FROM orders WHERE status = %s",
1199
+ ("pending",),
1200
+ statement_name="pending-orders-query",
1201
+ properties=StatementProperties(state_ttl=timedelta(milliseconds=100)),
1202
+ )
975
1203
  ```
976
1204
 
977
1205
  ---
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: confluent-sql
3
- Version: 0.4.2
3
+ Version: 0.5.1
4
4
  Summary: DB-API v2 compliant driver for Confluent Cloud Flink SQL
5
5
  Project-URL: Repository, https://github.com/confluentinc/confluent-sql
6
6
  Project-URL: Documentation, https://github.com/confluentinc/confluent-sql?tab=readme-ov-file#confluent-sql
@@ -253,9 +253,11 @@ The driver defaults to [snapshot mode](https://docs.confluent.io/cloud/current/f
253
253
  - **Existing Flink Database** (Confluent Cloud Kafka cluster)
254
254
  - **API credentials**, one of:
255
255
  - a **"Global" Confluent Cloud API key** and secret, passed as `global_api_key` / `global_api_secret`, or
256
- - a **Flink Region API key** and secret, passed as `flink_api_key` / `flink_api_secret`.
256
+ - a **Flink Region API key** and secret, passed as `flink_api_key` / `flink_api_secret`, or
257
+ - a **BYOIDC bearer token** minted by your own OAuth/OIDC identity provider, passed as `external_access_token` / `identity_pool_id` (see [BYOIDC bearer-token authentication](#byoidc-bearer-token-authentication) below).
257
258
 
258
259
  A Global key works against every route this driver touches, so it is the more future-proof choice; a Flink Region key works against the Flink SQL routes that are the driver's focus today. Provide at least one pair. If you supply both, the Global pair is used and the Flink pair is ignored. A half-supplied pair (a key without its secret, or vice versa) is rejected.
260
+ - **Organization ID** — required, *unless* you're using a Global API key and it can see exactly one organization, in which case you can omit `organization_id` and it's inferred automatically on first use of the connection. A Flink Region key has no way to discover it, so it always requires `organization_id` explicitly.
259
261
 
260
262
  ### How to Obtain a Flink Region API Key
261
263
 
@@ -276,6 +278,35 @@ To create or find a Flink Region API key:
276
278
 
277
279
  API keys may also be generated using the Confluent CLI or by API access, outside the scope of this document.
278
280
 
281
+ ### BYOIDC bearer-token authentication
282
+
283
+ If your Confluent Cloud organization is configured with your own registered OAuth/OIDC identity provider, you can authenticate to the **Flink data plane** with a bearer token you mint yourself (an "external" token in Confluent's authorization vocabulary) rather than a Confluent API key. Pass the token and the identity-pool id that scopes it:
284
+
285
+ ```python
286
+ import os
287
+ import confluent_sql
288
+
289
+ connection = confluent_sql.connect(
290
+ external_access_token=os.environ["MY_IDP_BEARER_TOKEN"],
291
+ identity_pool_id="pool-abc123",
292
+ environment_id="env-...",
293
+ organization_id="org-...", # required under BYOIDC (see below)
294
+ cloud_provider="aws",
295
+ cloud_region="us-east-2",
296
+ database="your-database-name", # optional, and works under BYOIDC: it only sets the default
297
+ # database, which needs no CMK cluster-id lookup
298
+ )
299
+ ```
300
+
301
+ The driver stamps `Authorization: Bearer <external_access_token>` and `Confluent-Identity-Pool-Id: <identity_pool_id>` on every Flink request. The parameter name mirrors the Confluent Flink Table API plugin's `client.oauth.external-access-token` option. A runnable example is in [examples/byoidc_bearer_token_example.py](examples/byoidc_bearer_token_example.py).
302
+
303
+ Things to know:
304
+
305
+ - **`external_access_token` and `identity_pool_id` are a pair** and are **mutually exclusive** with every API-key parameter (`global_*`, `flink_*`, `tableflow_*`, `connect_*`). Supplying a bearer token alongside any API key is rejected.
306
+ - **Flink data plane only.** Confluent's authorization model does not accept a raw external token on any control-plane route this driver calls. So under BYOIDC the control-plane surfaces — Tableflow (`enable_tableflow` / `get_tableflow` / `disable_tableflow`), Connectors, and the CMK cluster-id lookup that resolves a `database` name to its `lkc-…` id — fail closed with a clear error. Use an API-key connection for those, and pass `database_kafka_cluster_id` to `connect()` if you need the driver to know the cluster id without the CMK lookup.
307
+ - **`organization_id` is required** under BYOIDC. Inferring it needs a control-plane call an external token can't make, so it must be supplied explicitly.
308
+ - **No token refresh.** The token is used verbatim on every request. When it expires, requests begin failing (surfaced as `OperationalError`) and the connection is effectively dead — open a fresh connection with a fresh token.
309
+
279
310
  ## Installation
280
311
 
281
312
  ```bash
@@ -295,7 +326,7 @@ import confluent_sql
295
326
 
296
327
  # Connect to Confluent Cloud Flink SQL
297
328
  connection = confluent_sql.connect(
298
- organization_id="your-org-uuid",
329
+ organization_id="your-org-uuid", # optional if using a Global key -- see Prerequisites
299
330
  environment_id="env-123456",
300
331
  cloud_provider="aws",
301
332
  cloud_region="us-east-2",
@@ -379,6 +410,7 @@ This driver extends the standard DB-API v2 interface with additional features:
379
410
  - **Streaming cursors** - Non-blocking result consumption from continuous queries
380
411
  - **Changelog compression** - Automatic state management for aggregations and joins
381
412
  - **Statement lifecycle management** - Named statements, labels, and resource management
413
+ - **Statement properties** - Execution controls not expressible inline within the SQL statement
382
414
  - **Type system** - Full support for all Flink SQL types including streaming-specific types
383
415
  - **Performance monitoring** - Built-in fetch metrics and introspection
384
416
 
@@ -437,14 +469,15 @@ Set required environment variables for integration tests.
437
469
  If any of the variables is not set, integration tests will be skipped.
438
470
 
439
471
  ```bash
440
- export CONFLUENT_ORG_ID="org-123456"
472
+ export CONFLUENT_ORG_ID="org-123456" # Optional if using a Global key that can see exactly one org.
441
473
  export CONFLUENT_ENV_ID="env-123456"
442
474
  export CONFLUENT_CLOUD_PROVIDER="aws"
443
475
  export CONFLUENT_CLOUD_REGION="us-east-2"
444
476
  export CONFLUENT_FLINK_API_KEY="your-key" # Flink Region API key for the above cloud/region ...
445
477
  export CONFLUENT_FLINK_API_SECRET="your-secret" # and associated secret.
446
478
  # Alternatively, a "Global" Confluent Cloud API key (used in preference to the Flink pair if both
447
- # are set): export CONFLUENT_GLOBAL_API_KEY / CONFLUENT_GLOBAL_API_SECRET instead.
479
+ # are set): export CONFLUENT_GLOBAL_API_KEY / CONFLUENT_GLOBAL_API_SECRET instead. With a Global
480
+ # key, CONFLUENT_ORG_ID may be omitted -- the integration suite infers it, same as connect() does.
448
481
  export CONFLUENT_TEST_DBNAME="test-db" # A database/kafka cluster name within the above cloud/region.
449
482
  export CONFLUENT_COMPUTE_POOL_ID="lfcp-789012" # Optional. If set, the integration suite runs against this pool; if unset, the suite runs against the environment's default pool. The driver treats it as optional at connect() either way.
450
483
  ```