hubcio commented on code in PR #4219:
URL: https://github.com/apache/iggy/pull/4219#discussion_r4071042749
##########
core/connectors/sinks/delta_sink/README.md:
##########
@@ -10,40 +10,166 @@ The Delta Lake Sink Connector allows you to consume
messages from Iggy topics an
The table must already exist. The connector appends each successful nonempty
batch in one Delta transaction and keeps its schema snapshot until restart. The
plugin has no failed-batch retry loop; the Delta library can retry eligible
commit conflicts and storage requests. Write or commit errors clear the writer
buffers and return an error. The runtime uses consumer auto-commit and does not
replay failed sink batches, so end-to-end at-least-once delivery is not
guaranteed.
-## Configuration example
-
-### Local filesystem
-
-```toml
-[plugin_config]
-table_uri = "file:///tmp/iggy_delta_table"
+## How to configure a Delta Sink connector
+
+First, make sure that the Delta table already exists in the location you're
providing. You can use this script for an example workload:
+
+```python
+import pyarrow as pa
+from deltalake import DeltaTable
+
+table_uri = "s3://test_location/tables/test"
+
+schema = pa.schema([
+ pa.field("user_id", pa.string(), nullable=True),
+ pa.field("user_type", pa.uint8(), nullable=True),
+ pa.field("email", pa.string(), nullable=True),
+ pa.field("source", pa.string(), nullable=True),
+ pa.field("state", pa.string(), nullable=True),
+ pa.field("message", pa.string(), nullable=True),
+ pa.field("created_at", pa.timestamp("us"), nullable=True),
+])
+
+DeltaTable.create(
+ table_uri,
+ schema,
+ name="test",
+ storage_options={"AWS_REGION": "us-east-1"},
+)
+
+print(f"Created table at {table_uri}")
```
-### AWS S3
-
-```toml
-[plugin_config]
-table_uri = "s3://my-bucket/delta-tables/users"
-storage_backend_type = "s3"
-aws_s3_access_key = "your-access-key"
-aws_s3_secret_key = "your-secret-key"
-aws_s3_region = "us-east-1"
-aws_s3_endpoint_url = "https://s3.amazonaws.com"
-aws_s3_allow_http = false
-```
+The configuration is usually wrtitten individually for every connector and
consists of two parts: the runtime settings which are registering the sink and
telling which streams should plug into it, and the plugin's settings
themselves. Here's an example of a working configuration:
+
+ ```toml
+ type = "sink"
+ key = "delta"
+ enabled = true
+ version = 0
+ name = "Delta Lake sink"
+ path = "target/release/libiggy_connector_delta_sink" # make sure you have a
compiled plugin binary in this path
+ verbose = true
+
+ # these settings are common between all sinks
+ [[streams]]
+ stream = "your_stream"
+ topics = ["topic_inside_of_your_stream"]
+ schema = "json"
+ # important: read about batch and poll interval settings below
+ batch_length = 10000
+ poll_interval = "3s"
+ consumer_group = "delta_sink_connector"
+
+ # these settings are specific to each plugin, and in case of Delta sink, to
the type of storage used
+ [plugin_config]
+ # the table must exist in the given location
+ table_uri = "s3://iggy-sandbox/tables/test"
+ storage_backend_type = "s3"
+ aws_s3_region = "eu-central-1"
+ aws_s3_allow_http = false
+ ```
+
+### `[[streams]]` section
+
+- Find a topic that you need and the corresponding stream that this topic is
in and add it into the configuration.
+- `batch_length` and `poll_interval` have to be carefully set for Delta
tables. The write happens when there is either `batch_length` number of records
in the buffer or we hit `poll_interval` timeout. For Delta, writing means
creating a log entry and a separate Parquet file, so if these values are too
low (roughly `batch_length` < 1000 and `poll_interval` < 1s), the connector is
going to write lots of small files.
+ This is highly undesirable for the reader as it will have to read from many
small files instead of a few larger ones. For query performance optimization,
the system consuming these files will have to apply `OPTIMIZE` query in order
to consolidate the files. We recommend setting these values pretty high based
on your workload so that ideally the files are already optimized for reading.
+ The guideline recommended by the developers of Delta is to keep individual
file sizes between 128 MB and 1 GB. In the context of this document, it means
that ideally `batch_length` + `poll_interval` should cut off the files in the
way that their size is in the suggested range. The given range is only a rough
guideline and you need to find a good setting based on the patterns of reading
and writing in your systems.
+
+### Plugin configuration
+
+#### Attributes common to all types of storage
+
+- **table_uri** (required): Path or URI to the Delta table. Supported schemes:
`file://`, `s3://`, `az://`, `gs://`.
Review Comment:
warning: `table_uri` says "Path or URI", but `open()` parses it with
`url::Url::parse`, so a bare path fails - your own malformed-uri test proves
it. say absolute URI only, bare filesystem paths are not accepted.
##########
core/connectors/sinks/delta_sink/README.md:
##########
@@ -88,6 +186,6 @@ Required when `storage_backend_type = "gcs"`.
The connector automatically coerces JSON values to match the Delta table
schema:
-- **Timestamp fields**: ISO 8601 / RFC 3339 formatted strings (e.g.
`"2021-11-11T22:11:58Z", "2021-11-11 22:11:58"`) are converted to microsecond
timestamps. Integer epoch-microsecond timestamps pass through unchanged.
Space-separated timestamps without an offset are interpreted as UTC. Invalid
timestamp strings fail the batch.
-- **String fields**: Non-null, non-string values (numbers, booleans, objects,
arrays) are converted to their string representation.
-- **Nested fields**: Coercions cover nested structs, arrays of strings or
timestamps, and arrays of structs. Nested arrays, maps, and variant columns
pass through without these coercions. Nulls remain null.
+- **Timestamp fields**: ISO 8601 / RFC 3339 formatted strings (e.g.
`"2021-11-11T22:11:58Z", "2021-11-11 22:11:58"`) are converted to microsecond
timestamps. Numeric timestamps pass through unchanged.
+- **String fields**: Non-string values (numbers, booleans, objects, arrays)
are converted to their string representation.
+- **Nested fields**: Coercions are applied recursively to nested structs and
arrays.
Review Comment:
warning: the merge dropped the coercion caveats: map and variant columns
pass through uncoerced (`coercions.rs:82`), invalid timestamp strings fail the
whole batch, nulls stay null. restore those three bullets from master.
##########
core/connectors/sinks/delta_sink/README.md:
##########
@@ -10,40 +10,166 @@ The Delta Lake Sink Connector allows you to consume
messages from Iggy topics an
The table must already exist. The connector appends each successful nonempty
batch in one Delta transaction and keeps its schema snapshot until restart. The
plugin has no failed-batch retry loop; the Delta library can retry eligible
commit conflicts and storage requests. Write or commit errors clear the writer
buffers and return an error. The runtime uses consumer auto-commit and does not
replay failed sink batches, so end-to-end at-least-once delivery is not
guaranteed.
-## Configuration example
-
-### Local filesystem
-
-```toml
-[plugin_config]
-table_uri = "file:///tmp/iggy_delta_table"
+## How to configure a Delta Sink connector
+
+First, make sure that the Delta table already exists in the location you're
providing. You can use this script for an example workload:
+
+```python
+import pyarrow as pa
+from deltalake import DeltaTable
+
+table_uri = "s3://test_location/tables/test"
+
+schema = pa.schema([
+ pa.field("user_id", pa.string(), nullable=True),
+ pa.field("user_type", pa.uint8(), nullable=True),
+ pa.field("email", pa.string(), nullable=True),
+ pa.field("source", pa.string(), nullable=True),
+ pa.field("state", pa.string(), nullable=True),
+ pa.field("message", pa.string(), nullable=True),
+ pa.field("created_at", pa.timestamp("us"), nullable=True),
+])
+
+DeltaTable.create(
+ table_uri,
+ schema,
+ name="test",
+ storage_options={"AWS_REGION": "us-east-1"},
+)
+
+print(f"Created table at {table_uri}")
```
-### AWS S3
-
-```toml
-[plugin_config]
-table_uri = "s3://my-bucket/delta-tables/users"
-storage_backend_type = "s3"
-aws_s3_access_key = "your-access-key"
-aws_s3_secret_key = "your-secret-key"
-aws_s3_region = "us-east-1"
-aws_s3_endpoint_url = "https://s3.amazonaws.com"
-aws_s3_allow_http = false
-```
+The configuration is usually wrtitten individually for every connector and
consists of two parts: the runtime settings which are registering the sink and
telling which streams should plug into it, and the plugin's settings
themselves. Here's an example of a working configuration:
+
+ ```toml
+ type = "sink"
+ key = "delta"
+ enabled = true
+ version = 0
+ name = "Delta Lake sink"
+ path = "target/release/libiggy_connector_delta_sink" # make sure you have a
compiled plugin binary in this path
+ verbose = true
+
+ # these settings are common between all sinks
+ [[streams]]
+ stream = "your_stream"
+ topics = ["topic_inside_of_your_stream"]
+ schema = "json"
+ # important: read about batch and poll interval settings below
+ batch_length = 10000
+ poll_interval = "3s"
+ consumer_group = "delta_sink_connector"
+
+ # these settings are specific to each plugin, and in case of Delta sink, to
the type of storage used
+ [plugin_config]
+ # the table must exist in the given location
+ table_uri = "s3://iggy-sandbox/tables/test"
+ storage_backend_type = "s3"
+ aws_s3_region = "eu-central-1"
+ aws_s3_allow_http = false
+ ```
+
+### `[[streams]]` section
+
+- Find a topic that you need and the corresponding stream that this topic is
in and add it into the configuration.
+- `batch_length` and `poll_interval` have to be carefully set for Delta
tables. The write happens when there is either `batch_length` number of records
in the buffer or we hit `poll_interval` timeout. For Delta, writing means
creating a log entry and a separate Parquet file, so if these values are too
low (roughly `batch_length` < 1000 and `poll_interval` < 1s), the connector is
going to write lots of small files.
+ This is highly undesirable for the reader as it will have to read from many
small files instead of a few larger ones. For query performance optimization,
the system consuming these files will have to apply `OPTIMIZE` query in order
to consolidate the files. We recommend setting these values pretty high based
on your workload so that ideally the files are already optimized for reading.
+ The guideline recommended by the developers of Delta is to keep individual
file sizes between 128 MB and 1 GB. In the context of this document, it means
that ideally `batch_length` + `poll_interval` should cut off the files in the
way that their size is in the suggested range. The given range is only a rough
guideline and you need to find a good setting based on the patterns of reading
and writing in your systems.
+
+### Plugin configuration
+
+#### Attributes common to all types of storage
+
+- **table_uri** (required): Path or URI to the Delta table. Supported schemes:
`file://`, `s3://`, `az://`, `gs://`.
+- **storage_backend_type** (optional): The cloud storage backend to use. One
of `"s3"`, `"azure"`, or `"gcs"`. Omit for local filesystem tables.
-### Azure Blob Storage
+#### Local filesystem
+
+ ```toml
+ [plugin_config]
+ table_uri = "file:///tmp/iggy_delta_table"
+ ```
+
+#### AWS S3
+
+Currently the implementation offers two possible ways of accessing the bucket.
+
+1. Temporary security credentials issued by AWS STS, which is a best practice
recommended by AWS. The role assumed by the writer should allow these actions
on the bucket, here is how a working policy looks in HCL:
+
+ ```hcl
+ data "aws_iam_policy_document" "s3_write" {
+ statement {
+ sid = "BucketLevelActions"
+ effect = "Allow"
+ actions = [
+ "s3:ListBucket"
+ ]
+ resources = [aws_s3_bucket.unity_catalog["iggy-sandbox"].arn]
+ }
+
+ statement {
+ sid = "ObjectLevelActions"
+ effect = "Allow"
+ actions = [
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:PutObjectAcl",
+ "s3:AbortMultipartUpload"
+ ]
+ resources = ["${aws_s3_bucket.unity_catalog["iggy-sandbox"].arn}/*"]
+ }
+ }
+ ```
+
+ The corresponding plugin configuration will look like this:
+
+ ```toml
+ [plugin_config]
+ table_uri = "s3://iggy-sandbox/tables/test"
+ storage_backend_type = "s3"
+ aws_s3_region = "us-east-1"
+ ```
+
+2. Long-term access key pairs (access + secret key), which AWS recommends to
avoid for security reasons. The example configuration:
+
+ ```toml
+ [plugin_config]
+ table_uri = "s3://my-bucket/delta-tables/users"
+ storage_backend_type = "s3"
+ aws_s3_access_key = "your-access-key"
+ aws_s3_secret_key = "your-secret-key"
+ aws_s3_region = "us-east-1"
+ ```
+
+Parameter descriptions:
+
+- **aws_s3_access_key**: Optional. AWS access key ID. Can only be passed
together with the secret key.
+- **aws_s3_secret_key**: Optional. AWS secret access key. Can only be passed
together with the access key.
+- **aws_s3_region**: Required. AWS region (e.g. `us-east-1`).
+- **aws_s3_endpoint_url**: Optional. S3 endpoint URL. Use for S3-compatible
services. Make sure that the URL implies the same regions that is set in
**aws_s3_region**, otherwise you'll have an error.
+- **aws_s3_allow_http**: Optional. Set to `true` to allow HTTP connections
(for local development).
+
+#### Azure Blob Storage
```toml
[plugin_config]
table_uri = "az://my-container/delta-tables/users"
storage_backend_type = "azure"
azure_storage_account_name = "mystorageaccount"
azure_storage_account_key = "account-key"
+azure_storage_sas_token = "sas-token"
Review Comment:
warning: this example sets both `azure_storage_account_key` and
`azure_storage_sas_token`, and `build_storage_options` rejects that pair at
`open()`. keep one credential here and restore the "exactly one of" sentence at
line 165 that the merge dropped.
##########
core/connectors/sinks/delta_sink/src/storage.rs:
##########
@@ -27,25 +27,33 @@ pub(crate) fn build_storage_options(
match config.storage_backend_type {
Some(StorageBackendType::S3) => {
- let access_key = config.aws_s3_access_key.as_ref().ok_or_else(|| {
- Error::InitError("S3 backend requires
'aws_s3_access_key'".into())
- })?;
- let secret_key = config.aws_s3_secret_key.as_ref().ok_or_else(|| {
- Error::InitError("S3 backend requires
'aws_s3_secret_key'".into())
- })?;
+ match (
+ config.aws_s3_access_key.as_ref(),
+ config.aws_s3_secret_key.as_ref(),
+ ) {
+ (Some(access_key), Some(secret_key)) => {
+ opts.insert(
+ "AWS_ACCESS_KEY_ID".into(),
+ access_key.expose_secret().to_owned(),
+ );
+ opts.insert(
+ "AWS_SECRET_ACCESS_KEY".into(),
+ secret_key.expose_secret().to_owned(),
+ );
+ }
+ (None, None) => {}
Review Comment:
warning: the keyless branch is not isolated - deltalake-aws exports a keyed
sink's credentials into the process env (`ensure_env_var`), and `with_env_s3`
reads them back here. a keyless sink in the same runtime can pick up the keyed
sink's keys, document that.
##########
core/connectors/sinks/delta_sink/src/sink.rs:
##########
@@ -38,33 +38,69 @@ impl Sink for DeltaSink {
);
let table_url = url::Url::parse(&self.config.table_uri).map_err(|e| {
- error!("Failed to parse table URI '{}': {e}",
self.config.table_uri);
- Error::InitError(format!("Invalid table URI: {e}"))
+ error!(
+ "Connector configuration: failed to parse table_uri = '{}':
{e}.",
+ self.config.table_uri
+ );
+ Error::InvalidConfigValue(format!("table_uri: {e}"))
})?;
+ let table_url_parsed = &self.config.table_uri;
- info!("Parsed table URI: {}", table_url);
+ info!("Parsed table URI: {}", table_url_parsed);
let storage_options = build_storage_options(&self.config).map_err(|e| {
- error!("Invalid storage configuration: {e}");
- Error::InitError(format!("Invalid storage configuration: {e}"))
+ error!("Connector configuration: invalid storage configuration.
Error message: {e}");
+ Error::InitError(format!(
+ "Connector configuration: invalid storage configuration. Error
message: {e}"
+ ))
})?;
- let table =
- match deltalake::open_table_with_storage_options(table_url,
storage_options).await {
- Ok(table) => table,
- Err(e) => {
- error!("Failed to load Delta table: {e}");
- return Err(Error::InitError(format!("Failed to load Delta
table: {e}")));
+ info!("Successfully composed the storage options for accessing the
storage backend");
+
+ let builder = deltalake::DeltaTableBuilder::from_url(table_url)
+ .map_err(|e| {
+ error!("deltalake-rs interface: failed to configure with
table_uri = '{table_url_parsed}'. Check deltalake::DeltaTableBuilder::from_url
docs and code to correct your table_uri. Error message: {e}");
+ Error::InvalidConfigValue(format!("table_uri =
'{table_url_parsed}' caused an error in deltalake-rs interface. Check
deltalake::DeltaTableBuilder::from_url docs and code to correct your table_uri.
Error message: {e}"))
+ })?
+ .with_storage_options(storage_options);
+ let mut table = builder.build().map_err(|e| {
+ error!("deltalake-rs interface: failed to configure with provided
storage configuration. Error message: {e}");
+ Error::InitError(format!("deltalake-rs interface: failed to
configure with provided storage configuration. Error message: {e}"))
+ })?;
+ let table_exists = table
+ .verify_deltatable_existence()
+ .await
+ .map_err(
+ |e| {
+ error!("deltalake-rs interface: failed to list table_url
'{table_url_parsed}' directory to verify delta table existence. Make sure the
destination exists and the access to the destination is set up correctly - read
the Iggy delta connector docs for more information. Error message: {e}");
+ Error::InitError(format!("deltalake-rs interface: failed
to list table_url '{table_url_parsed}' directory to verify delta table
existence. Make sure the destination exists and the access to the destination
is set up correctly - read the Iggy delta connector docs for more information.
Error message: {e}"))
}
- };
+ )?;
+ if !table_exists {
+ error!(
+ "No delta table found in '{table_url_parsed}. Make sure to
create the delta table in the destination manually or verify the validity of
such table."
Review Comment:
nit: the quote before `{table_url_parsed}` is never closed, here and in the
error at line 84.
##########
core/connectors/sinks/delta_sink/src/sink.rs:
##########
@@ -122,9 +158,6 @@ impl Sink for DeltaSink {
return Ok(());
}
- // TODO: all partition consume() calls serialize on this single lock,
holding it
- // through flush_and_commit() I/O. fix: per-partition writers keyed by
partition_id.
- // Ref:
https://github.com/apache/iggy/pull/2889/#discussion_r2936719763
let mut state_guard = self.state.lock().await;
Review Comment:
nit: deleting the TODO does not fix #3839 - `consume()` still holds the
state lock across write and commit, so topic tasks of one sink serialize.
change the PR body to "Relates to #3839", or merging auto-closes the issue.
##########
core/connectors/sinks/delta_sink/README.md:
##########
@@ -10,40 +10,166 @@ The Delta Lake Sink Connector allows you to consume
messages from Iggy topics an
The table must already exist. The connector appends each successful nonempty
batch in one Delta transaction and keeps its schema snapshot until restart. The
plugin has no failed-batch retry loop; the Delta library can retry eligible
commit conflicts and storage requests. Write or commit errors clear the writer
buffers and return an error. The runtime uses consumer auto-commit and does not
replay failed sink batches, so end-to-end at-least-once delivery is not
guaranteed.
-## Configuration example
-
-### Local filesystem
-
-```toml
-[plugin_config]
-table_uri = "file:///tmp/iggy_delta_table"
+## How to configure a Delta Sink connector
+
+First, make sure that the Delta table already exists in the location you're
providing. You can use this script for an example workload:
+
+```python
+import pyarrow as pa
+from deltalake import DeltaTable
+
+table_uri = "s3://test_location/tables/test"
+
+schema = pa.schema([
+ pa.field("user_id", pa.string(), nullable=True),
+ pa.field("user_type", pa.uint8(), nullable=True),
+ pa.field("email", pa.string(), nullable=True),
+ pa.field("source", pa.string(), nullable=True),
+ pa.field("state", pa.string(), nullable=True),
+ pa.field("message", pa.string(), nullable=True),
+ pa.field("created_at", pa.timestamp("us"), nullable=True),
+])
+
+DeltaTable.create(
+ table_uri,
+ schema,
+ name="test",
+ storage_options={"AWS_REGION": "us-east-1"},
+)
+
+print(f"Created table at {table_uri}")
```
-### AWS S3
-
-```toml
-[plugin_config]
-table_uri = "s3://my-bucket/delta-tables/users"
-storage_backend_type = "s3"
-aws_s3_access_key = "your-access-key"
-aws_s3_secret_key = "your-secret-key"
-aws_s3_region = "us-east-1"
-aws_s3_endpoint_url = "https://s3.amazonaws.com"
-aws_s3_allow_http = false
-```
+The configuration is usually wrtitten individually for every connector and
consists of two parts: the runtime settings which are registering the sink and
telling which streams should plug into it, and the plugin's settings
themselves. Here's an example of a working configuration:
+
+ ```toml
+ type = "sink"
+ key = "delta"
+ enabled = true
+ version = 0
+ name = "Delta Lake sink"
+ path = "target/release/libiggy_connector_delta_sink" # make sure you have a
compiled plugin binary in this path
+ verbose = true
+
+ # these settings are common between all sinks
+ [[streams]]
+ stream = "your_stream"
+ topics = ["topic_inside_of_your_stream"]
+ schema = "json"
+ # important: read about batch and poll interval settings below
+ batch_length = 10000
+ poll_interval = "3s"
+ consumer_group = "delta_sink_connector"
+
+ # these settings are specific to each plugin, and in case of Delta sink, to
the type of storage used
+ [plugin_config]
+ # the table must exist in the given location
+ table_uri = "s3://iggy-sandbox/tables/test"
+ storage_backend_type = "s3"
+ aws_s3_region = "eu-central-1"
+ aws_s3_allow_http = false
+ ```
+
+### `[[streams]]` section
+
+- Find a topic that you need and the corresponding stream that this topic is
in and add it into the configuration.
+- `batch_length` and `poll_interval` have to be carefully set for Delta
tables. The write happens when there is either `batch_length` number of records
in the buffer or we hit `poll_interval` timeout. For Delta, writing means
creating a log entry and a separate Parquet file, so if these values are too
low (roughly `batch_length` < 1000 and `poll_interval` < 1s), the connector is
going to write lots of small files.
Review Comment:
warning: `poll_interval` is the minimum gap between polls, not a flush
timeout, and each commit holds at most `batch_length` rows. so file size is
capped by `batch_length` times row size, and intake by `batch_length` per
interval per topic.
##########
core/connectors/sinks/delta_sink/src/sink.rs:
##########
@@ -38,33 +38,69 @@ impl Sink for DeltaSink {
);
let table_url = url::Url::parse(&self.config.table_uri).map_err(|e| {
- error!("Failed to parse table URI '{}': {e}",
self.config.table_uri);
- Error::InitError(format!("Invalid table URI: {e}"))
+ error!(
+ "Connector configuration: failed to parse table_uri = '{}':
{e}.",
+ self.config.table_uri
+ );
+ Error::InvalidConfigValue(format!("table_uri: {e}"))
})?;
+ let table_url_parsed = &self.config.table_uri;
Review Comment:
nit: `table_url_parsed` holds the raw config string, so the "Parsed table
URI" log prints the unparsed value. log `table_url` and rename this to
`table_uri`.
##########
core/connectors/sinks/delta_sink/README.md:
##########
@@ -10,40 +10,166 @@ The Delta Lake Sink Connector allows you to consume
messages from Iggy topics an
The table must already exist. The connector appends each successful nonempty
batch in one Delta transaction and keeps its schema snapshot until restart. The
plugin has no failed-batch retry loop; the Delta library can retry eligible
commit conflicts and storage requests. Write or commit errors clear the writer
buffers and return an error. The runtime uses consumer auto-commit and does not
replay failed sink batches, so end-to-end at-least-once delivery is not
guaranteed.
-## Configuration example
-
-### Local filesystem
-
-```toml
-[plugin_config]
-table_uri = "file:///tmp/iggy_delta_table"
+## How to configure a Delta Sink connector
+
+First, make sure that the Delta table already exists in the location you're
providing. You can use this script for an example workload:
+
+```python
+import pyarrow as pa
+from deltalake import DeltaTable
+
+table_uri = "s3://test_location/tables/test"
+
+schema = pa.schema([
+ pa.field("user_id", pa.string(), nullable=True),
+ pa.field("user_type", pa.uint8(), nullable=True),
+ pa.field("email", pa.string(), nullable=True),
+ pa.field("source", pa.string(), nullable=True),
+ pa.field("state", pa.string(), nullable=True),
+ pa.field("message", pa.string(), nullable=True),
+ pa.field("created_at", pa.timestamp("us"), nullable=True),
+])
+
+DeltaTable.create(
+ table_uri,
+ schema,
+ name="test",
+ storage_options={"AWS_REGION": "us-east-1"},
+)
+
+print(f"Created table at {table_uri}")
```
-### AWS S3
-
-```toml
-[plugin_config]
-table_uri = "s3://my-bucket/delta-tables/users"
-storage_backend_type = "s3"
-aws_s3_access_key = "your-access-key"
-aws_s3_secret_key = "your-secret-key"
-aws_s3_region = "us-east-1"
-aws_s3_endpoint_url = "https://s3.amazonaws.com"
-aws_s3_allow_http = false
-```
+The configuration is usually wrtitten individually for every connector and
consists of two parts: the runtime settings which are registering the sink and
telling which streams should plug into it, and the plugin's settings
themselves. Here's an example of a working configuration:
+
+ ```toml
+ type = "sink"
+ key = "delta"
+ enabled = true
+ version = 0
+ name = "Delta Lake sink"
+ path = "target/release/libiggy_connector_delta_sink" # make sure you have a
compiled plugin binary in this path
+ verbose = true
+
+ # these settings are common between all sinks
+ [[streams]]
+ stream = "your_stream"
+ topics = ["topic_inside_of_your_stream"]
+ schema = "json"
+ # important: read about batch and poll interval settings below
+ batch_length = 10000
+ poll_interval = "3s"
+ consumer_group = "delta_sink_connector"
+
+ # these settings are specific to each plugin, and in case of Delta sink, to
the type of storage used
+ [plugin_config]
+ # the table must exist in the given location
+ table_uri = "s3://iggy-sandbox/tables/test"
+ storage_backend_type = "s3"
+ aws_s3_region = "eu-central-1"
+ aws_s3_allow_http = false
+ ```
+
+### `[[streams]]` section
+
+- Find a topic that you need and the corresponding stream that this topic is
in and add it into the configuration.
+- `batch_length` and `poll_interval` have to be carefully set for Delta
tables. The write happens when there is either `batch_length` number of records
in the buffer or we hit `poll_interval` timeout. For Delta, writing means
creating a log entry and a separate Parquet file, so if these values are too
low (roughly `batch_length` < 1000 and `poll_interval` < 1s), the connector is
going to write lots of small files.
+ This is highly undesirable for the reader as it will have to read from many
small files instead of a few larger ones. For query performance optimization,
the system consuming these files will have to apply `OPTIMIZE` query in order
to consolidate the files. We recommend setting these values pretty high based
on your workload so that ideally the files are already optimized for reading.
+ The guideline recommended by the developers of Delta is to keep individual
file sizes between 128 MB and 1 GB. In the context of this document, it means
that ideally `batch_length` + `poll_interval` should cut off the files in the
way that their size is in the suggested range. The given range is only a rough
guideline and you need to find a good setting based on the patterns of reading
and writing in your systems.
+
+### Plugin configuration
+
+#### Attributes common to all types of storage
+
+- **table_uri** (required): Path or URI to the Delta table. Supported schemes:
`file://`, `s3://`, `az://`, `gs://`.
+- **storage_backend_type** (optional): The cloud storage backend to use. One
of `"s3"`, `"azure"`, or `"gcs"`. Omit for local filesystem tables.
-### Azure Blob Storage
+#### Local filesystem
+
+ ```toml
+ [plugin_config]
+ table_uri = "file:///tmp/iggy_delta_table"
+ ```
+
+#### AWS S3
+
+Currently the implementation offers two possible ways of accessing the bucket.
+
+1. Temporary security credentials issued by AWS STS, which is a best practice
recommended by AWS. The role assumed by the writer should allow these actions
on the bucket, here is how a working policy looks in HCL:
Review Comment:
nit: STS is not the only keyless option, with no keys set deltalake-aws uses
the AWS SDK default chain (env vars, shared profile incl. SSO, web identity,
ECS, instance metadata). say that here.
##########
core/connectors/sinks/delta_sink/README.md:
##########
@@ -10,40 +10,166 @@ The Delta Lake Sink Connector allows you to consume
messages from Iggy topics an
The table must already exist. The connector appends each successful nonempty
batch in one Delta transaction and keeps its schema snapshot until restart. The
plugin has no failed-batch retry loop; the Delta library can retry eligible
commit conflicts and storage requests. Write or commit errors clear the writer
buffers and return an error. The runtime uses consumer auto-commit and does not
replay failed sink batches, so end-to-end at-least-once delivery is not
guaranteed.
-## Configuration example
-
-### Local filesystem
-
-```toml
-[plugin_config]
-table_uri = "file:///tmp/iggy_delta_table"
+## How to configure a Delta Sink connector
+
+First, make sure that the Delta table already exists in the location you're
providing. You can use this script for an example workload:
+
+```python
+import pyarrow as pa
+from deltalake import DeltaTable
+
+table_uri = "s3://test_location/tables/test"
+
+schema = pa.schema([
+ pa.field("user_id", pa.string(), nullable=True),
+ pa.field("user_type", pa.uint8(), nullable=True),
+ pa.field("email", pa.string(), nullable=True),
+ pa.field("source", pa.string(), nullable=True),
+ pa.field("state", pa.string(), nullable=True),
+ pa.field("message", pa.string(), nullable=True),
+ pa.field("created_at", pa.timestamp("us"), nullable=True),
+])
+
+DeltaTable.create(
+ table_uri,
+ schema,
+ name="test",
+ storage_options={"AWS_REGION": "us-east-1"},
+)
+
+print(f"Created table at {table_uri}")
```
-### AWS S3
-
-```toml
-[plugin_config]
-table_uri = "s3://my-bucket/delta-tables/users"
-storage_backend_type = "s3"
-aws_s3_access_key = "your-access-key"
-aws_s3_secret_key = "your-secret-key"
-aws_s3_region = "us-east-1"
-aws_s3_endpoint_url = "https://s3.amazonaws.com"
-aws_s3_allow_http = false
-```
+The configuration is usually wrtitten individually for every connector and
consists of two parts: the runtime settings which are registering the sink and
telling which streams should plug into it, and the plugin's settings
themselves. Here's an example of a working configuration:
+
+ ```toml
+ type = "sink"
+ key = "delta"
+ enabled = true
+ version = 0
+ name = "Delta Lake sink"
+ path = "target/release/libiggy_connector_delta_sink" # make sure you have a
compiled plugin binary in this path
+ verbose = true
+
+ # these settings are common between all sinks
+ [[streams]]
+ stream = "your_stream"
+ topics = ["topic_inside_of_your_stream"]
+ schema = "json"
+ # important: read about batch and poll interval settings below
+ batch_length = 10000
+ poll_interval = "3s"
+ consumer_group = "delta_sink_connector"
+
+ # these settings are specific to each plugin, and in case of Delta sink, to
the type of storage used
+ [plugin_config]
+ # the table must exist in the given location
+ table_uri = "s3://iggy-sandbox/tables/test"
+ storage_backend_type = "s3"
+ aws_s3_region = "eu-central-1"
+ aws_s3_allow_http = false
+ ```
+
+### `[[streams]]` section
+
+- Find a topic that you need and the corresponding stream that this topic is
in and add it into the configuration.
+- `batch_length` and `poll_interval` have to be carefully set for Delta
tables. The write happens when there is either `batch_length` number of records
in the buffer or we hit `poll_interval` timeout. For Delta, writing means
creating a log entry and a separate Parquet file, so if these values are too
low (roughly `batch_length` < 1000 and `poll_interval` < 1s), the connector is
going to write lots of small files.
+ This is highly undesirable for the reader as it will have to read from many
small files instead of a few larger ones. For query performance optimization,
the system consuming these files will have to apply `OPTIMIZE` query in order
to consolidate the files. We recommend setting these values pretty high based
on your workload so that ideally the files are already optimized for reading.
+ The guideline recommended by the developers of Delta is to keep individual
file sizes between 128 MB and 1 GB. In the context of this document, it means
that ideally `batch_length` + `poll_interval` should cut off the files in the
way that their size is in the suggested range. The given range is only a rough
guideline and you need to find a good setting based on the patterns of reading
and writing in your systems.
+
+### Plugin configuration
+
+#### Attributes common to all types of storage
+
+- **table_uri** (required): Path or URI to the Delta table. Supported schemes:
`file://`, `s3://`, `az://`, `gs://`.
+- **storage_backend_type** (optional): The cloud storage backend to use. One
of `"s3"`, `"azure"`, or `"gcs"`. Omit for local filesystem tables.
-### Azure Blob Storage
+#### Local filesystem
+
+ ```toml
+ [plugin_config]
+ table_uri = "file:///tmp/iggy_delta_table"
+ ```
+
+#### AWS S3
+
+Currently the implementation offers two possible ways of accessing the bucket.
+
+1. Temporary security credentials issued by AWS STS, which is a best practice
recommended by AWS. The role assumed by the writer should allow these actions
on the bucket, here is how a working policy looks in HCL:
+
+ ```hcl
+ data "aws_iam_policy_document" "s3_write" {
+ statement {
+ sid = "BucketLevelActions"
+ effect = "Allow"
+ actions = [
+ "s3:ListBucket"
+ ]
+ resources = [aws_s3_bucket.unity_catalog["iggy-sandbox"].arn]
+ }
+
+ statement {
+ sid = "ObjectLevelActions"
+ effect = "Allow"
+ actions = [
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:PutObjectAcl",
+ "s3:AbortMultipartUpload"
+ ]
+ resources = ["${aws_s3_bucket.unity_catalog["iggy-sandbox"].arn}/*"]
+ }
+ }
+ ```
+
+ The corresponding plugin configuration will look like this:
+
+ ```toml
+ [plugin_config]
+ table_uri = "s3://iggy-sandbox/tables/test"
+ storage_backend_type = "s3"
+ aws_s3_region = "us-east-1"
+ ```
+
+2. Long-term access key pairs (access + secret key), which AWS recommends to
avoid for security reasons. The example configuration:
+
+ ```toml
+ [plugin_config]
+ table_uri = "s3://my-bucket/delta-tables/users"
+ storage_backend_type = "s3"
+ aws_s3_access_key = "your-access-key"
+ aws_s3_secret_key = "your-secret-key"
+ aws_s3_region = "us-east-1"
+ ```
+
+Parameter descriptions:
+
+- **aws_s3_access_key**: Optional. AWS access key ID. Can only be passed
together with the secret key.
+- **aws_s3_secret_key**: Optional. AWS secret access key. Can only be passed
together with the access key.
+- **aws_s3_region**: Required. AWS region (e.g. `us-east-1`).
+- **aws_s3_endpoint_url**: Optional. S3 endpoint URL. Use for S3-compatible
services. Make sure that the URL implies the same regions that is set in
**aws_s3_region**, otherwise you'll have an error.
Review Comment:
nit: a custom endpoint makes deltalake-aws skip the AWS SDK credential
chain, so profile and SSO credentials stop working. only static keys, web
identity, container credentials or instance metadata remain unless
`AWS_FORCE_CREDENTIAL_LOAD` is set in the runtime env.
##########
core/connectors/sinks/delta_sink/README.md:
##########
@@ -10,40 +10,166 @@ The Delta Lake Sink Connector allows you to consume
messages from Iggy topics an
The table must already exist. The connector appends each successful nonempty
batch in one Delta transaction and keeps its schema snapshot until restart. The
plugin has no failed-batch retry loop; the Delta library can retry eligible
commit conflicts and storage requests. Write or commit errors clear the writer
buffers and return an error. The runtime uses consumer auto-commit and does not
replay failed sink batches, so end-to-end at-least-once delivery is not
guaranteed.
-## Configuration example
-
-### Local filesystem
-
-```toml
-[plugin_config]
-table_uri = "file:///tmp/iggy_delta_table"
+## How to configure a Delta Sink connector
+
+First, make sure that the Delta table already exists in the location you're
providing. You can use this script for an example workload:
+
+```python
+import pyarrow as pa
+from deltalake import DeltaTable
+
+table_uri = "s3://test_location/tables/test"
+
+schema = pa.schema([
+ pa.field("user_id", pa.string(), nullable=True),
+ pa.field("user_type", pa.uint8(), nullable=True),
+ pa.field("email", pa.string(), nullable=True),
+ pa.field("source", pa.string(), nullable=True),
+ pa.field("state", pa.string(), nullable=True),
+ pa.field("message", pa.string(), nullable=True),
+ pa.field("created_at", pa.timestamp("us"), nullable=True),
+])
+
+DeltaTable.create(
+ table_uri,
+ schema,
+ name="test",
+ storage_options={"AWS_REGION": "us-east-1"},
+)
+
+print(f"Created table at {table_uri}")
```
-### AWS S3
-
-```toml
-[plugin_config]
-table_uri = "s3://my-bucket/delta-tables/users"
-storage_backend_type = "s3"
-aws_s3_access_key = "your-access-key"
-aws_s3_secret_key = "your-secret-key"
-aws_s3_region = "us-east-1"
-aws_s3_endpoint_url = "https://s3.amazonaws.com"
-aws_s3_allow_http = false
-```
+The configuration is usually wrtitten individually for every connector and
consists of two parts: the runtime settings which are registering the sink and
telling which streams should plug into it, and the plugin's settings
themselves. Here's an example of a working configuration:
+
+ ```toml
+ type = "sink"
+ key = "delta"
+ enabled = true
+ version = 0
+ name = "Delta Lake sink"
+ path = "target/release/libiggy_connector_delta_sink" # make sure you have a
compiled plugin binary in this path
+ verbose = true
+
+ # these settings are common between all sinks
+ [[streams]]
+ stream = "your_stream"
+ topics = ["topic_inside_of_your_stream"]
+ schema = "json"
+ # important: read about batch and poll interval settings below
+ batch_length = 10000
+ poll_interval = "3s"
+ consumer_group = "delta_sink_connector"
+
+ # these settings are specific to each plugin, and in case of Delta sink, to
the type of storage used
+ [plugin_config]
+ # the table must exist in the given location
+ table_uri = "s3://iggy-sandbox/tables/test"
+ storage_backend_type = "s3"
+ aws_s3_region = "eu-central-1"
+ aws_s3_allow_http = false
+ ```
+
+### `[[streams]]` section
+
+- Find a topic that you need and the corresponding stream that this topic is
in and add it into the configuration.
+- `batch_length` and `poll_interval` have to be carefully set for Delta
tables. The write happens when there is either `batch_length` number of records
in the buffer or we hit `poll_interval` timeout. For Delta, writing means
creating a log entry and a separate Parquet file, so if these values are too
low (roughly `batch_length` < 1000 and `poll_interval` < 1s), the connector is
going to write lots of small files.
+ This is highly undesirable for the reader as it will have to read from many
small files instead of a few larger ones. For query performance optimization,
the system consuming these files will have to apply `OPTIMIZE` query in order
to consolidate the files. We recommend setting these values pretty high based
on your workload so that ideally the files are already optimized for reading.
+ The guideline recommended by the developers of Delta is to keep individual
file sizes between 128 MB and 1 GB. In the context of this document, it means
that ideally `batch_length` + `poll_interval` should cut off the files in the
way that their size is in the suggested range. The given range is only a rough
guideline and you need to find a good setting based on the patterns of reading
and writing in your systems.
+
+### Plugin configuration
+
+#### Attributes common to all types of storage
+
+- **table_uri** (required): Path or URI to the Delta table. Supported schemes:
`file://`, `s3://`, `az://`, `gs://`.
+- **storage_backend_type** (optional): The cloud storage backend to use. One
of `"s3"`, `"azure"`, or `"gcs"`. Omit for local filesystem tables.
-### Azure Blob Storage
+#### Local filesystem
+
+ ```toml
+ [plugin_config]
+ table_uri = "file:///tmp/iggy_delta_table"
+ ```
+
+#### AWS S3
+
+Currently the implementation offers two possible ways of accessing the bucket.
+
+1. Temporary security credentials issued by AWS STS, which is a best practice
recommended by AWS. The role assumed by the writer should allow these actions
on the bucket, here is how a working policy looks in HCL:
+
+ ```hcl
+ data "aws_iam_policy_document" "s3_write" {
+ statement {
+ sid = "BucketLevelActions"
+ effect = "Allow"
+ actions = [
+ "s3:ListBucket"
+ ]
+ resources = [aws_s3_bucket.unity_catalog["iggy-sandbox"].arn]
Review Comment:
nit: `aws_s3_bucket.unity_catalog["iggy-sandbox"]` is a resource address
from a specific terraform setup, use a generic bucket ARN placeholder here and
at line 120.
##########
core/connectors/sinks/delta_sink/README.md:
##########
@@ -10,40 +10,166 @@ The Delta Lake Sink Connector allows you to consume
messages from Iggy topics an
The table must already exist. The connector appends each successful nonempty
batch in one Delta transaction and keeps its schema snapshot until restart. The
plugin has no failed-batch retry loop; the Delta library can retry eligible
commit conflicts and storage requests. Write or commit errors clear the writer
buffers and return an error. The runtime uses consumer auto-commit and does not
replay failed sink batches, so end-to-end at-least-once delivery is not
guaranteed.
-## Configuration example
-
-### Local filesystem
-
-```toml
-[plugin_config]
-table_uri = "file:///tmp/iggy_delta_table"
+## How to configure a Delta Sink connector
+
+First, make sure that the Delta table already exists in the location you're
providing. You can use this script for an example workload:
+
+```python
+import pyarrow as pa
+from deltalake import DeltaTable
+
+table_uri = "s3://test_location/tables/test"
+
+schema = pa.schema([
+ pa.field("user_id", pa.string(), nullable=True),
+ pa.field("user_type", pa.uint8(), nullable=True),
+ pa.field("email", pa.string(), nullable=True),
+ pa.field("source", pa.string(), nullable=True),
+ pa.field("state", pa.string(), nullable=True),
+ pa.field("message", pa.string(), nullable=True),
+ pa.field("created_at", pa.timestamp("us"), nullable=True),
+])
+
+DeltaTable.create(
+ table_uri,
+ schema,
+ name="test",
+ storage_options={"AWS_REGION": "us-east-1"},
+)
+
+print(f"Created table at {table_uri}")
```
-### AWS S3
-
-```toml
-[plugin_config]
-table_uri = "s3://my-bucket/delta-tables/users"
-storage_backend_type = "s3"
-aws_s3_access_key = "your-access-key"
-aws_s3_secret_key = "your-secret-key"
-aws_s3_region = "us-east-1"
-aws_s3_endpoint_url = "https://s3.amazonaws.com"
-aws_s3_allow_http = false
-```
+The configuration is usually wrtitten individually for every connector and
consists of two parts: the runtime settings which are registering the sink and
telling which streams should plug into it, and the plugin's settings
themselves. Here's an example of a working configuration:
Review Comment:
nit: typo - "wrtitten".
##########
core/connectors/sinks/delta_sink/src/storage.rs:
##########
@@ -27,25 +27,33 @@ pub(crate) fn build_storage_options(
match config.storage_backend_type {
Some(StorageBackendType::S3) => {
- let access_key = config.aws_s3_access_key.as_ref().ok_or_else(|| {
- Error::InitError("S3 backend requires
'aws_s3_access_key'".into())
- })?;
- let secret_key = config.aws_s3_secret_key.as_ref().ok_or_else(|| {
- Error::InitError("S3 backend requires
'aws_s3_secret_key'".into())
- })?;
+ match (
+ config.aws_s3_access_key.as_ref(),
+ config.aws_s3_secret_key.as_ref(),
+ ) {
+ (Some(access_key), Some(secret_key)) => {
+ opts.insert(
+ "AWS_ACCESS_KEY_ID".into(),
+ access_key.expose_secret().to_owned(),
+ );
+ opts.insert(
+ "AWS_SECRET_ACCESS_KEY".into(),
+ secret_key.expose_secret().to_owned(),
+ );
+ }
+ (None, None) => {}
+ _ => {
+ return Err(Error::InitError(
Review Comment:
nit: the sibling sinks return `Error::InvalidConfigValue` naming the fields
here (`iceberg_sink/src/props.rs:47`). do the same with `aws_s3_access_key` and
`aws_s3_secret_key`, and let `open()` pass it through instead of wrapping it in
a second `InitError`.
##########
core/integration/tests/connectors/fixtures/delta/fixture.rs:
##########
@@ -200,13 +200,72 @@ impl TestFixture for DeltaFixture {
}
}
-pub struct DeltaS3Fixture {
- #[allow(dead_code)]
- minio: ContainerAsync<GenericImage>,
- minio_endpoint: String,
+pub struct DeltaCorruptedLogFixture {
+ _temp_dir: TempDir,
+ table_path: PathBuf,
}
-impl DeltaS3Fixture {
+impl DeltaCorruptedLogFixture {
+ // Overwrites the version-0 commit file with garbage bytes but keeps its
+ // name intact: `is_delta_table_location` only lists filenames under
+ // `_delta_log`, so the table still "exists"; `DeltaTable::load` is what
+ // then fails parsing this file during log replay.
+ async fn corrupt_commit_log(table_path: &Path) -> Result<(),
TestBinaryError> {
+ let commit_path = table_path
+ .join("_delta_log")
+ .join("00000000000000000000.json");
+ tokio::fs::write(&commit_path, b"not a valid delta log commit entry")
+ .await
+ .map_err(|error| TestBinaryError::FixtureSetup {
+ fixture_type: "DeltaCorruptedLogFixture".to_string(),
+ message: format!(
+ "Failed to corrupt commit log at {}: {error}",
+ commit_path.display()
+ ),
+ })
+ }
+}
+
+#[async_trait]
+impl TestFixture for DeltaCorruptedLogFixture {
+ async fn setup() -> Result<Self, TestBinaryError> {
+ let temp_dir = TempDir::new().map_err(|error|
TestBinaryError::FixtureSetup {
+ fixture_type: "DeltaCorruptedLogFixture".to_string(),
+ message: format!("Failed to create temp directory: {error}"),
+ })?;
+
+ let table_path = temp_dir.path().join("delta_table");
+ let table_uri = format!("file://{}", table_path.display());
+ DeltaFixture::create_table(&table_uri).await?;
+ Self::corrupt_commit_log(&table_path).await?;
+ info!(
+ "Delta corrupted-log fixture created with table path: {}",
+ table_path.display()
+ );
+
+ Ok(Self {
+ _temp_dir: temp_dir,
+ table_path,
+ })
+ }
+
+ fn connectors_runtime_envs(&self) -> HashMap<String, String> {
+ let table_uri = format!("file://{}", self.table_path.display());
+
+ let mut envs = HashMap::new();
+ envs.insert(ENV_SINK_TABLE_URI.to_string(), table_uri);
+ envs.insert(
+ ENV_SINK_PATH.to_string(),
+ "../../target/debug/libiggy_connector_delta_sink".to_string(),
+ );
+ envs
+ }
+}
+
+#[async_trait]
+pub trait DeltaS3SinkOps {
Review Comment:
simplification: `DeltaS3SinkOps` moves the inherent `DeltaS3Fixture` helpers
into a trait, and the four wrapper impls exist to expose `minio_endpoint()`,
which no test calls. keep the inherent impl, drop the trait, its re-exports,
the two `.clone()` on owned strings and the `Box` around minio.
##########
core/integration/tests/connectors/fixtures/delta/fixture.rs:
##########
@@ -382,9 +453,137 @@ impl TestFixture for DeltaS3Fixture {
envs.insert(ENV_SINK_AWS_S3_REGION.to_string(),
"us-east-1".to_string());
envs.insert(
ENV_SINK_AWS_S3_ENDPOINT_URL.to_string(),
- self.minio_endpoint.clone(),
+ self.minio_endpoint().clone(),
);
envs.insert(ENV_SINK_AWS_S3_ALLOW_HTTP.to_string(),
"true".to_string());
envs
}
}
+
+pub struct DeltaS3NoTableFixture {
+ inner: DeltaS3Fixture,
+}
+
+impl DeltaS3SinkOps for DeltaS3NoTableFixture {
+ fn minio_endpoint(&self) -> String {
+ self.inner.minio_endpoint.clone()
+ }
+}
+
+#[async_trait]
+impl TestFixture for DeltaS3NoTableFixture {
+ async fn setup() -> Result<Self, TestBinaryError> {
+ let id = Uuid::new_v4();
+ let network = format!("iggy-delta-s3-{id}");
+ let minio_name =
fixtures::unique_container_name("minio-delta-no-table");
+
+ let (minio, minio_endpoint) = DeltaS3Fixture::start_minio(&network,
&minio_name).await?;
+ DeltaS3Fixture::create_bucket(&minio_endpoint).await?;
+
+ info!("Delta S3 'no table' fixture ready with MinIO at
{minio_endpoint}");
+
+ Ok(Self {
+ inner: DeltaS3Fixture {
+ minio: Box::new(minio),
+ minio_endpoint,
+ },
+ })
+ }
+
+ fn connectors_runtime_envs(&self) -> HashMap<String, String> {
+ self.inner.connectors_runtime_envs()
+ }
+}
+
+pub struct DeltaS3NoBucketFixture {
+ inner: DeltaS3Fixture,
+}
+
+impl DeltaS3SinkOps for DeltaS3NoBucketFixture {
+ fn minio_endpoint(&self) -> String {
+ self.inner.minio_endpoint.clone()
+ }
+}
+
+#[async_trait]
+impl TestFixture for DeltaS3NoBucketFixture {
+ async fn setup() -> Result<Self, TestBinaryError> {
+ let id = Uuid::new_v4();
+ let network = format!("iggy-delta-s3-{id}");
+ let minio_name =
fixtures::unique_container_name("minio-delta-no-bucket");
+
+ let (minio, minio_endpoint) = DeltaS3Fixture::start_minio(&network,
&minio_name).await?;
+
+ info!("Delta S3 'no bucket' fixture ready with MinIO at
{minio_endpoint}");
+
+ Ok(Self {
+ inner: DeltaS3Fixture {
+ minio: Box::new(minio),
+ minio_endpoint,
+ },
+ })
+ }
+
+ fn connectors_runtime_envs(&self) -> HashMap<String, String> {
+ self.inner.connectors_runtime_envs()
+ }
+}
+
+pub struct DeltaS3MissingSecretKeyFixture {
Review Comment:
simplification: this fixture starts minio, a bucket and a table to hit a
branch that fails before any storage call (`storage.rs:45`), and
`s3_backend_missing_secret_key_errors` already covers it. make the test
config-only like the malformed-uri one.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]