Write to an Iceberg Catalog
You can use fivetran run to write your Connector SDK connector's data directly to an Apache Iceberg REST catalog to test how your tables, data types, and primary keys are represented in Iceberg. The command works similarly to fivetran debug, with the same options and logs, but writes the data directly to your Iceberg catalog instead of the local files/warehouse.db file.
fivetran debug does not change. It never reads destination.json and always writes to files/warehouse.db.
Prerequisites
- An Iceberg REST catalog that your machine can reach and that signs you in with a token or OAuth2 client credentials, and the storage behind it, which is Amazon S3 or S3-compatible storage.
- Credentials for the catalog, and for the storage unless the catalog vends credentials.
- A namespace used only for your connector.
fivetran rundoes not ask for confirmation, and it adds columns to, rewrites, and merges rows into any table with the same name that is already in the namespace. - Internet access on the first run. DuckDB, which
fivetran runuses to write the data, downloads its Iceberg extension the first time it is used.
fivetran run is tested with Apache Polaris, including with vended credentials, and with Project Nessie. Other Iceberg REST catalogs that meet these requirements should work, but are not tested.
Describe the catalog
fivetran run reads the catalog configuration from a destination.json file in your project folder, beside connector.py. The file is a flat JSON object that uses the same setting names as Iceberg clients such as PyIceberg and Spark. namespace is the only Fivetran-specific setting.
| Term | What it means |
|---|---|
Catalog (uri) | The Iceberg REST catalog server, which keeps track of your tables. uri is its address. |
Warehouse (warehouse) | The name the catalog gives the set of tables you write into. In Apache Polaris, it is the name of the catalog you created. |
Namespace (namespace) | The Iceberg namespace (a group of tables in the warehouse) that your tables go in. Tables with no declared schema go in it. With Fivetran naming, a table that declares a schema goes in <namespace>_<schema>, so the namespace works as a prefix; see Where your data goes. fivetran run creates the namespaces that don't exist. |
| Vended credentials | Temporary keys for the storage, which the catalog gives to fivetran run when it asks. You do not give the storage keys yourself. |
Manage storage credentials
For a catalog that vends storage credentials, provide the storage region and no storage keys:
{
"uri": "https://my-catalog.example.com/api/catalog",
"warehouse": "my_warehouse",
"namespace": "my_connector",
"client_id": "<your client ID>",
"client_secret": "<your client secret>",
"header.X-Iceberg-Access-Delegation": "vended-credentials",
"s3.region": "us-east-1"
}
If you sign in with a token, replace client_id and client_secret with "token": "<your token>".
For a catalog that does not vend credentials, provide it with storage as well:
{
"uri": "http://localhost:8181/api/catalog",
"warehouse": "local_catalog",
"namespace": "tester",
"client_id": "<your client ID>",
"client_secret": "<your client secret>",
"s3.region": "us-east-1",
"s3.endpoint": "http://localhost:8333",
"s3.force-virtual-addressing": false,
"s3.access-key-id": "<your access key ID>",
"s3.secret-access-key": "<your secret access key>"
}
Specify catalog settings
Provide catalog details:
| Setting | Required | Description |
|---|---|---|
uri | Yes | The address of the catalog, starting with http:// or https://. |
warehouse | Yes | The warehouse in the catalog. |
namespace | Yes | The namespace your tables go in. It takes the place of tester in a local fivetran debug run. A namespace that does not exist is created. |
Now, configure how fivetran run signs in to your catalog. Provide a token, or a client ID and secret, not both:
| Setting | Required | Description |
|---|---|---|
token | One of the two | A token for the catalog. |
client_id and client_secret | One of the two | Client credentials. The token is requested at <uri>/v1/oauth/tokens, unless you give oauth2-server-uri. |
scope | No | The scope to request. Goes with the client credentials. |
oauth2-server-uri | No | Where to request the token, if it is not <uri>/v1/oauth/tokens. Goes with the client credentials. |
Configure how fivetran run reaches the storage:
| Setting | Required | Description |
|---|---|---|
header.X-Iceberg-Access-Delegation | No | vended-credentials: the catalog gives out the credentials for its storage, and only s3.region may be given besides it. none, or left out (the default): it does not, and the s3.* settings below say how to reach the storage. |
s3.region | Yes, unless the catalog vends credentials | The region of the storage. With vended credentials, give it if the credentials do not carry one. |
s3.access-key-id and s3.secret-access-key | No | Static keys. Give both. s3.session-token goes with them. Without keys, the credential chain of your machine is used. |
s3.profile-name | No | The profile of your machine's credential chain to use, when you give no keys. |
s3.endpoint | No | The address of storage that is not Amazon S3. An http:// scheme means no SSL. |
s3.force-virtual-addressing | No | Whether buckets are addressed as host names (true, the default) or by path (false). |
Run your connector against a catalog
With destination.json in your project folder, run:
fivetran run
fivetran run reads the same files/state.json as fivetran debug. Before the first run against a new catalog or namespace, run fivetran reset, or replace the contents of your state file with {}. Otherwise the connector starts from the cursor of an earlier local run and the catalog does not get the earlier records.
To use a destination file that is not in the project folder, or has another name, use --destination-file:
fivetran run --destination-file ~/catalogs/my_catalog.json
For the options, see fivetran run.
A successful run ends with the same summary as a fivetran debug run, followed by where the data is and how to read it:
SYNC SUCCEEDED — total elapsed 00:00:26.880
Operation | Counts
----------------+------------
Upserts | 1
Updates | 0
Deletes | 0
Truncates | 0
Schema changes | 0
Checkpoints | 1
File uploads | 0
results are in the Iceberg catalog at https://my-catalog.example.com/api/catalog: warehouse my_warehouse, namespace my_connector. To read them in DuckDB, with the iceberg extension and a secret for this catalog: ATTACH 'my_warehouse' AS lake (TYPE ICEBERG, ENDPOINT 'https://my-catalog.example.com/api/catalog'); SELECT * FROM lake."my_connector".<table>
If your connector writes to more than one schema, the last line lists all the namespaces it wrote to.
Where your data goes
- Tables. A table your connector writes becomes an Iceberg table in the namespace. It has the same columns as in a
fivetran debugrun, including the system columns. - Schemas. A table with no declared schema goes in
namespace. With Fivetran naming, a table that declares a schema goes in<namespace>_<schema>(lower-cased, with characters a name cannot hold replaced by_); with source naming, it goes in a namespace named exactly as declared. For example, with the namespacemy_connector, a table in the schemaSalesgoes inmy_connector_saleswith Fivetran naming. Fivetran creates namespaces that don't exist. - Naming strategy. The naming strategy is stored with the namespace, so subsequent runs from any machine use the same strategy. A later
--namingthat differs is ignored, with a warning. - Primary keys. The key columns are required in the Iceberg table, and rows are merged on the full key. The key columns are also named as the table's identifier fields, unless a key column is a FLOAT or DOUBLE. Then the table has no identifier fields, and other Iceberg clients cannot discover the key from the table.
- Deletes. Like in a local run,
delete()andtruncate()set_fivetran_deletedtotrue. They do not remove rows.
Read the results
Open the catalog with any Iceberg client. This example uses DuckDB with client credentials, for a catalog that vends storage credentials:
INSTALL iceberg;
LOAD iceberg;
CREATE SECRET lake_secret (
TYPE ICEBERG,
CLIENT_ID '<your client ID>',
CLIENT_SECRET '<your client secret>',
OAUTH2_SCOPE '<scope>',
OAUTH2_SERVER_URI 'https://my-catalog.example.com/api/catalog/v1/oauth/tokens'
);
ATTACH 'my_warehouse' AS lake (
TYPE ICEBERG,
SECRET lake_secret,
ENDPOINT 'https://my-catalog.example.com/api/catalog',
ACCESS_DELEGATION_MODE 'vended_credentials',
DEFAULT_REGION 'us-east-1'
);
SELECT * FROM lake."my_connector".<table>;
If you sign in with a token, give TOKEN '<your token>' in the secret instead of the client settings. For a catalog that does not vend credentials, set ACCESS_DELEGATION_MODE 'none' and add a DuckDB secret for your storage. See the DuckDB Iceberg documentation.
Errors
If destination.json is missing or invalid, fivetran run fails before running your connector. The following table lists common errors and how to resolve them.
| Message | What to do |
|---|---|
invalid destination error: Destination path is incorrect, cannot find file at the location <path> | Create destination.json in the project folder, or give the right path with --destination-file. |
invalid destination error: cannot read destination.json: <reason> | Check that destination.json contains valid JSON. |
invalid destination error: destination.json must hold a JSON object, not <kind> | Ensure that the file contains a JSON object rather than null, an array, a string, or a number. |
destination.json: has '<name>', which is not a setting. The settings are: ... | Remove or correct the setting. |
If the catalog cannot be reached, the fivetran run command fails with could not connect to the Iceberg catalog at <uri> and the reason.
Limitations
- The
upsert()andupdate()operations don't support file uploads using thefileparameter. - The Iceberg extension for DuckDB can't write table or
namespacenames that contain non-ASCII characters. TheFIVETRANnaming strategy converts table and schema names to ASCII, but doesn't convert thenamespacevalue indestination.json. Use an ASCII-onlynamespacevalue regardless of your naming strategy. fivetran runsupports only token-based authentication or client credentials, and doesn't send additional request headers. Catalogs that require no authentication, or that sign requests differently, for example, with AWS Signature Version 4, aren't supported.- Only Amazon S3 and S3-compatible storage are supported. Google Cloud Storage and Azure Data Lake Storage aren't supported.
- Data files and delete files accumulate with each sync. Use the maintenance tools your catalog provides to compact them regularly.
- Running multiple syncs that write to the same table at the same time isn't supported.