confluent-sql 0.4.1__tar.gz → 0.5.0__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 (68) hide show
  1. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/.semaphore/semaphore.yml +1 -1
  2. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/ARCHITECTURE.md +1 -1
  3. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/CHANGELOG.md +24 -0
  4. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/CLAUDE.md +32 -1
  5. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/DBAPI_EXTENSIONS.md +240 -14
  6. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/PKG-INFO +39 -6
  7. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/README.md +38 -5
  8. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/STREAMING.md +0 -2
  9. confluent_sql-0.5.0/examples/byoidc_bearer_token_example.py +34 -0
  10. confluent_sql-0.5.0/examples/statement_properties_example.py +73 -0
  11. confluent_sql-0.5.0/examples/tableflow_lifecycle_example.py +50 -0
  12. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/pyproject.toml +2 -2
  13. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/src/confluent_sql/__init__.py +60 -1
  14. confluent_sql-0.5.0/src/confluent_sql/auth.py +30 -0
  15. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/src/confluent_sql/connection.py +1073 -115
  16. confluent_sql-0.5.0/src/confluent_sql/connectors.py +492 -0
  17. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/src/confluent_sql/cursor.py +9 -5
  18. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/src/confluent_sql/exceptions.py +64 -0
  19. confluent_sql-0.5.0/src/confluent_sql/statement_properties.py +322 -0
  20. confluent_sql-0.5.0/src/confluent_sql/tableflow.py +363 -0
  21. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/src/confluent_sql/types.py +18 -9
  22. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/tests/conftest.py +8 -0
  23. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/tests/integration/conftest.py +4 -1
  24. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/tests/integration/test_connection.py +2 -2
  25. confluent_sql-0.5.0/tests/integration/test_connector.py +184 -0
  26. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/tests/integration/test_cursor.py +82 -7
  27. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/tests/integration/test_fetch.py +2 -6
  28. confluent_sql-0.5.0/tests/integration/test_tableflow.py +152 -0
  29. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/tests/unit/conftest.py +0 -1
  30. confluent_sql-0.5.0/tests/unit/test_auth_unit.py +28 -0
  31. confluent_sql-0.5.0/tests/unit/test_connection_byoidc_unit.py +221 -0
  32. confluent_sql-0.5.0/tests/unit/test_connection_connector_unit.py +184 -0
  33. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/tests/unit/test_connection_unit.py +389 -133
  34. confluent_sql-0.5.0/tests/unit/test_connection_unit_properties.py +413 -0
  35. confluent_sql-0.5.0/tests/unit/test_connectors_connection_unit.py +502 -0
  36. confluent_sql-0.5.0/tests/unit/test_connectors_unit.py +191 -0
  37. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/tests/unit/test_cursor_unit.py +9 -79
  38. confluent_sql-0.5.0/tests/unit/test_statement_properties_unit.py +396 -0
  39. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/tests/unit/test_statement_unit.py +3 -9
  40. confluent_sql-0.5.0/tests/unit/test_tableflow_connection_unit.py +525 -0
  41. confluent_sql-0.5.0/tests/unit/test_tableflow_unit.py +322 -0
  42. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/uv.lock +6 -6
  43. confluent_sql-0.4.1/tests/unit/test_connection_unit_properties.py +0 -200
  44. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/.github/CODEOWNERS +0 -0
  45. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/.gitignore +0 -0
  46. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/.semaphore/publish_to_pypi.yml +0 -0
  47. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/LICENSE.txt +0 -0
  48. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/Makefile +0 -0
  49. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/TYPES.md +0 -0
  50. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/examples/errors.py +0 -0
  51. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/examples/simple_append_only_streaming_query_example.py +0 -0
  52. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/examples/snapshot_mode_tuple_cursor_simple_example.py +0 -0
  53. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/service.yml +0 -0
  54. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/src/confluent_sql/__version__.py +0 -0
  55. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/src/confluent_sql/changelog_compressor.py +0 -0
  56. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/src/confluent_sql/execution_mode.py +0 -0
  57. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/src/confluent_sql/polling.py +0 -0
  58. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/src/confluent_sql/result_readers.py +0 -0
  59. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/src/confluent_sql/retry.py +0 -0
  60. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/src/confluent_sql/statement.py +0 -0
  61. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/tests/__init__.py +0 -0
  62. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/tests/unit/test_changelog_compressor_unit.py +0 -0
  63. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/tests/unit/test_changelog_unit.py +0 -0
  64. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/tests/unit/test_execution_mode_unit.py +0 -0
  65. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/tests/unit/test_polling_unit.py +0 -0
  66. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/tests/unit/test_result_readers_unit.py +0 -0
  67. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/tests/unit/test_retry_unit.py +0 -0
  68. {confluent_sql-0.4.1 → confluent_sql-0.5.0}/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:
@@ -45,7 +45,7 @@ Every statement progresses through these phases:
45
45
 
46
46
  ### How Phases Progress
47
47
 
48
- **Snapshot queries (the default):** _(⚠️ [Early Access](https://docs.confluent.io/cloud/current/flink/concepts/snapshot-queries.html))_
48
+ **Snapshot queries (the default):**
49
49
 
50
50
  ```
51
51
  PENDING → RUNNING → COMPLETED
@@ -2,6 +2,30 @@
2
2
 
3
3
  All notable changes to this dbapi driver will be documented in this file.
4
4
 
5
+ ## 0.5.0, 2026-08-07
6
+
7
+ ### Added
8
+
9
+ - `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.
10
+ - `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)
11
+ - 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)
12
+ - 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.
13
+ - `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.
14
+ - `Connection.get_tableflow(table_name)` returns the current `TableflowTopic` (phase, spec, status), raising `TableflowTopicNotFoundError` if Tableflow is not enabled for the topic.
15
+ - `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.
16
+ - `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)
17
+ - 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)
18
+
19
+ ### Fixed
20
+
21
+ - 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)
22
+
23
+ ## 0.4.2, 2026-07-17
24
+
25
+ ### Changed
26
+
27
+ - 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)
28
+
5
29
  ## 0.4.1, 2026-07-07
6
30
 
7
31
  ### Fixed
@@ -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.
@@ -4,8 +4,6 @@ The `confluent-sql` driver extends the standard [DB-API v2](https://peps.python.
4
4
 
5
5
  ## Understanding Snapshot vs Streaming Modes
6
6
 
7
- > **⚠️ Early Access:** [Snapshot queries](https://docs.confluent.io/cloud/current/flink/concepts/snapshot-queries.html) on Confluent Cloud Flink SQL are currently in Early Access and may be subject to change.
8
-
9
7
  **By default, the driver operates in [SNAPSHOT mode](https://docs.confluent.io/cloud/current/flink/concepts/snapshot-queries.html)**, producing behavior very similar to traditional SQL databases:
10
8
 
11
9
  - Queries execute and block until complete
@@ -36,6 +34,7 @@ For comprehensive details on streaming queries, polling patterns, and changelog
36
34
  - [Result Format Extensions](#result-format-extensions) - Dictionary rows, custom types
37
35
  - [Streaming Query Support](#streaming-query-support) - Comprehensive streaming guide
38
36
  - [Statement Lifecycle Management](#statement-lifecycle-management) - DDL, naming, stopping, deletion
37
+ - [Tableflow Lifecycle](#tableflow-lifecycle) - Enable, read, and disable Iceberg/Delta sinks
39
38
  - [Introspection and Metadata](#introspection-and-metadata) - Properties for query state
40
39
  - [Performance Monitoring](#performance-monitoring) - Fetch metrics
41
40
  - [Type System Extensions](#type-system-extensions) - Flink type support
@@ -567,6 +566,147 @@ cursor.delete_statement()
567
566
 
568
567
  ---
569
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
+
570
710
  ## Introspection and Metadata
571
711
 
572
712
  ### Connection Properties
@@ -896,7 +1036,8 @@ cursor.execute(
896
1036
  timeout: int = 3000,
897
1037
  statement_name: str | None = None,
898
1038
  statement_labels: list[str] | None = None,
899
- 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,
900
1041
  ) -> None
901
1042
  ```
902
1043
 
@@ -909,20 +1050,73 @@ cursor.execute(
909
1050
  | `timeout` | `int` | 3000 | Max seconds to wait for statement to reach RUNNING/COMPLETED phase |
910
1051
  | `statement_name` | `str \| None` | None | Custom statement identifier (defaults to UUID) |
911
1052
  | `statement_labels` | `list[str] \| None` | None | List of labels for grouping related statements |
912
- | `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 |
913
1055
 
914
1056
  ### Statement Properties
915
1057
 
916
- 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.
917
1107
 
918
1108
  **Important Precedence Rules:**
919
- - Connection-level defaults (catalog, database) are always applied
920
- - Cursor execution mode settings (e.g., `sql.snapshot.mode` for snapshot queries) are always applied
921
- - User-provided properties in the `properties` parameter can extend these settings but cannot override system properties
922
-
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.
923
1116
 
924
1117
  **Accessing Properties After Execution:**
925
- 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]`:
926
1120
 
927
1121
  ```python
928
1122
  cursor.execute(query, properties={"sql.state-ttl": "100 ms"})
@@ -930,7 +1124,31 @@ props = cursor.statement.properties
930
1124
  assert props["sql.state-ttl"] == "100 ms"
931
1125
  ```
932
1126
 
933
- **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:**
934
1152
 
935
1153
  ```python
936
1154
  # Basic execution
@@ -942,7 +1160,7 @@ cursor.execute("SELECT * FROM users WHERE age > %s", (18,))
942
1160
  # With custom timeout
943
1161
  cursor.execute(
944
1162
  "SELECT * FROM users",
945
- timeout=5000 # Wait up to 5000 seconds
1163
+ timeout=100 # Wait up to 100 seconds
946
1164
  )
947
1165
 
948
1166
  # With statement naming
@@ -952,7 +1170,7 @@ cursor.execute(
952
1170
  statement_name="completed-orders-daily"
953
1171
  )
954
1172
 
955
- # With statement labeling (for batch operations)
1173
+ # With statement labeling
956
1174
  cursor.execute(
957
1175
  "CREATE TABLE orders_backup AS SELECT * FROM orders",
958
1176
  statement_labels=["daily-backups", "batch-job"]
@@ -967,13 +1185,21 @@ cursor.execute(
967
1185
  statement_labels=["analytics", "hourly"]
968
1186
  )
969
1187
 
970
- # With statement properties
1188
+ # With statement properties (raw dict)
971
1189
  cursor.execute(
972
1190
  "SELECT * FROM orders WHERE status = %s",
973
1191
  ("pending",),
974
1192
  statement_name="pending-orders-query",
975
1193
  properties={"sql.state-ttl": "100 ms"}
976
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
+ )
977
1203
  ```
978
1204
 
979
1205
  ---
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: confluent-sql
3
- Version: 0.4.1
3
+ Version: 0.5.0
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
@@ -245,7 +245,7 @@ This is pre-production code mainly developed as the lower level portion of a `db
245
245
 
246
246
  The behavior of snapshot-mode cursors, complying with dbapi semantics, are well stable. The streaming query extensions are more of a work in progress at this time. Feedback and suggestions are welcome!
247
247
 
248
- > **⚠️ Early Access:** [Snapshot queries](https://docs.confluent.io/cloud/current/flink/concepts/snapshot-queries.html) on Confluent Cloud Flink SQL are currently in Early Access and may be subject to change. You will need to request access to snapshot queries for your organization from Confluent. The driver defaults to snapshot mode for all queries unless streaming mode is explicitly requested.
248
+ The driver defaults to [snapshot mode](https://docs.confluent.io/cloud/current/flink/concepts/snapshot-queries.html) for all queries unless streaming mode is explicitly requested.
249
249
 
250
250
  ## Prerequisites
251
251
 
@@ -253,9 +253,11 @@ The behavior of snapshot-mode cursors, complying with dbapi semantics, are well
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
  ```
@@ -14,7 +14,7 @@ This is pre-production code mainly developed as the lower level portion of a `db
14
14
 
15
15
  The behavior of snapshot-mode cursors, complying with dbapi semantics, are well stable. The streaming query extensions are more of a work in progress at this time. Feedback and suggestions are welcome!
16
16
 
17
- > **⚠️ Early Access:** [Snapshot queries](https://docs.confluent.io/cloud/current/flink/concepts/snapshot-queries.html) on Confluent Cloud Flink SQL are currently in Early Access and may be subject to change. You will need to request access to snapshot queries for your organization from Confluent. The driver defaults to snapshot mode for all queries unless streaming mode is explicitly requested.
17
+ The driver defaults to [snapshot mode](https://docs.confluent.io/cloud/current/flink/concepts/snapshot-queries.html) for all queries unless streaming mode is explicitly requested.
18
18
 
19
19
  ## Prerequisites
20
20
 
@@ -22,9 +22,11 @@ The behavior of snapshot-mode cursors, complying with dbapi semantics, are well
22
22
  - **Existing Flink Database** (Confluent Cloud Kafka cluster)
23
23
  - **API credentials**, one of:
24
24
  - a **"Global" Confluent Cloud API key** and secret, passed as `global_api_key` / `global_api_secret`, or
25
- - a **Flink Region API key** and secret, passed as `flink_api_key` / `flink_api_secret`.
25
+ - a **Flink Region API key** and secret, passed as `flink_api_key` / `flink_api_secret`, or
26
+ - 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).
26
27
 
27
28
  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.
29
+ - **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.
28
30
 
29
31
  ### How to Obtain a Flink Region API Key
30
32
 
@@ -45,6 +47,35 @@ To create or find a Flink Region API key:
45
47
 
46
48
  API keys may also be generated using the Confluent CLI or by API access, outside the scope of this document.
47
49
 
50
+ ### BYOIDC bearer-token authentication
51
+
52
+ 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:
53
+
54
+ ```python
55
+ import os
56
+ import confluent_sql
57
+
58
+ connection = confluent_sql.connect(
59
+ external_access_token=os.environ["MY_IDP_BEARER_TOKEN"],
60
+ identity_pool_id="pool-abc123",
61
+ environment_id="env-...",
62
+ organization_id="org-...", # required under BYOIDC (see below)
63
+ cloud_provider="aws",
64
+ cloud_region="us-east-2",
65
+ database="your-database-name", # optional, and works under BYOIDC: it only sets the default
66
+ # database, which needs no CMK cluster-id lookup
67
+ )
68
+ ```
69
+
70
+ 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).
71
+
72
+ Things to know:
73
+
74
+ - **`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.
75
+ - **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.
76
+ - **`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.
77
+ - **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.
78
+
48
79
  ## Installation
49
80
 
50
81
  ```bash
@@ -64,7 +95,7 @@ import confluent_sql
64
95
 
65
96
  # Connect to Confluent Cloud Flink SQL
66
97
  connection = confluent_sql.connect(
67
- organization_id="your-org-uuid",
98
+ organization_id="your-org-uuid", # optional if using a Global key -- see Prerequisites
68
99
  environment_id="env-123456",
69
100
  cloud_provider="aws",
70
101
  cloud_region="us-east-2",
@@ -148,6 +179,7 @@ This driver extends the standard DB-API v2 interface with additional features:
148
179
  - **Streaming cursors** - Non-blocking result consumption from continuous queries
149
180
  - **Changelog compression** - Automatic state management for aggregations and joins
150
181
  - **Statement lifecycle management** - Named statements, labels, and resource management
182
+ - **Statement properties** - Execution controls not expressible inline within the SQL statement
151
183
  - **Type system** - Full support for all Flink SQL types including streaming-specific types
152
184
  - **Performance monitoring** - Built-in fetch metrics and introspection
153
185
 
@@ -206,14 +238,15 @@ Set required environment variables for integration tests.
206
238
  If any of the variables is not set, integration tests will be skipped.
207
239
 
208
240
  ```bash
209
- export CONFLUENT_ORG_ID="org-123456"
241
+ export CONFLUENT_ORG_ID="org-123456" # Optional if using a Global key that can see exactly one org.
210
242
  export CONFLUENT_ENV_ID="env-123456"
211
243
  export CONFLUENT_CLOUD_PROVIDER="aws"
212
244
  export CONFLUENT_CLOUD_REGION="us-east-2"
213
245
  export CONFLUENT_FLINK_API_KEY="your-key" # Flink Region API key for the above cloud/region ...
214
246
  export CONFLUENT_FLINK_API_SECRET="your-secret" # and associated secret.
215
247
  # Alternatively, a "Global" Confluent Cloud API key (used in preference to the Flink pair if both
216
- # are set): export CONFLUENT_GLOBAL_API_KEY / CONFLUENT_GLOBAL_API_SECRET instead.
248
+ # are set): export CONFLUENT_GLOBAL_API_KEY / CONFLUENT_GLOBAL_API_SECRET instead. With a Global
249
+ # key, CONFLUENT_ORG_ID may be omitted -- the integration suite infers it, same as connect() does.
217
250
  export CONFLUENT_TEST_DBNAME="test-db" # A database/kafka cluster name within the above cloud/region.
218
251
  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.
219
252
  ```
@@ -4,8 +4,6 @@ The `confluent-sql` driver provides full support for continuous streaming querie
4
4
 
5
5
  ## Overview
6
6
 
7
- > **⚠️ Early Access:** [Snapshot queries](https://docs.confluent.io/cloud/current/flink/concepts/snapshot-queries.html) on Confluent Cloud Flink SQL are currently in Early Access and may be subject to change. The SNAPSHOT mode described below relies on this feature.
8
-
9
7
  This driver supports two primary execution modes:
10
8
 
11
9
  | Mode | Use Case | Query Examples | Result Behavior |