mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 01:15:03 +03:00
Closes [DOCS-1289](https://linear.app/supabase/issue/DOCS-1289/get-the-linter-to-fix-what-it-flags-or-retirereplace-the-linter) Stacked on #50600, which points contributors at the authoring skills. Merge that one first. ## Problem Contributors experienced friction with the linter. They felt nickle and dimed for tiny nits and felt detracted from the work itself. PRs would become noisy with tiny one-word suggestions. Additionally, our homegrown linter is not very intelligent, causing frequent overrides. ## Solution This removes the linter entirely in favor of directing contributors to use SKILLS instead. The removal entails... - **CI.** Delete the three `docs_lint` workflows: the PR check, the external-PR comment companion, and the nightly `--fix` bot. Drop the stale `zizmor.yml` ignore entry for the deleted workflow. - **Tooling.** Delete `supa-mdx-lint.config.toml` and the 14 rule files. Drop the `lint:mdx` script and the `@supabase/supa-mdx-lint` dependency from docs, learn, and ui-library, and regenerate the lockfile. - **Content.** Remove the 181 directives. A separate commit carries Prettier's reformatting of the tables and blank lines those comments had suppressed, so the deletion commit stays readable. No prose changes. - **Style guide.** The word list states each rule directly instead of describing what the linter flagged. Every term survives, including the phrase groups that mirrored `Rule004ExcludeWords`. - **Skills.** `write-the-docs`, `edit-the-docs`, and `review-the-docs` drop `pnpm lint:mdx` from their self-review commands and check the word list directly. `ask-the-docs`'s CI reference drops both workflows. ## Manual testing 1. Run `git grep -i supa-mdx-lint -- . ':!pnpm-lock.yaml'`. No matches. 2. Run `pnpm install --frozen-lockfile --lockfile-only`. It passes, so the lockfile matches the three trimmed manifests. 3. Run `git diff master...HEAD --name-only --diff-filter=ACMR | grep -E '\.(md|mdx)$' | xargs npx prettier --config prettier.config.mjs --check`. All changed markdown passes. 4. Open the [reformatted filter table](https://docs-git-docs-retire-mdx-linter-supabase.vercel.app/docs/guides/observability/logs#filter-events) on the preview and compare it with [production](https://supabase.com/docs/guides/observability/logs#filter-events). The table renders the same. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Documentation guidance now uses manual prose and terminology review with the shared word list. * Clarified storage configuration and common Realtime channel mistakes. * Improved table formatting, text wrapping, and selected reference links. * Updated documentation authoring and review guidance. * **Chores** * Retired automated MDX linting from workflows and local validation commands. * Removed lint-suppression markers throughout documentation without changing instructions. * Added targeted documentation review guidance for pull requests. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
367 lines
26 KiB
Plaintext
367 lines
26 KiB
Plaintext
---
|
|
id: 'snowflake-destination'
|
|
title: 'Snowflake destination'
|
|
description: 'Configure Snowflake as a Supabase Pipelines destination.'
|
|
subtitle: 'Replicate Supabase Postgres changes to Snowflake.'
|
|
sidebar_label: 'Snowflake'
|
|
---
|
|
|
|
<$Partial path="pipelines-public-alpha.mdx" />
|
|
|
|
The Snowflake destination is in private alpha and available only to approved organizations. [Request access](/go/supabase-pipelines-new-destinations) before following this guide.
|
|
|
|
[Snowflake](https://www.snowflake.com/) is a managed data platform. Supabase Pipelines writes an append-only change history for each replicated Postgres table to Snowflake.
|
|
|
|
[Prepare resources](#prepare-snowflake-resources), [configure the destination](#configure-snowflake-as-a-destination), then [query replicated data](#query-and-materialize-current-state).
|
|
|
|
## Source table requirements
|
|
|
|
Required `REPLICA IDENTITY` depends on the operations enabled in the Postgres publication:
|
|
|
|
| Published operations | Required replica identity |
|
|
| -------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
| `INSERT` only | No row identity is required. |
|
|
| `DELETE` | A primary key, an identity index (`USING INDEX`), or full identity (`FULL`). Publish all identity columns. |
|
|
| `UPDATE` | `REPLICA IDENTITY FULL`. |
|
|
|
|
Set full replica identity before publishing updates:
|
|
|
|
```sql
|
|
alter table public.your_table replica identity full;
|
|
```
|
|
|
|
`REPLICA IDENTITY FULL` increases WAL volume, but lets Pipelines construct complete new rows when Postgres omits unchanged out-of-line TOAST values. The setting applies only to new WAL records. If retained WAL already contains an incompatible update, restart replication for the affected table after changing the setting.
|
|
|
|
## Prepare Snowflake resources
|
|
|
|
Create a dedicated Snowflake database, schema, role, and service user for Pipelines. Keep the schema otherwise empty to avoid ownership conflicts. Use unquoted identifiers for the service user and role. Pipelines converts the account and user names to uppercase during authentication.
|
|
|
|
Run the following as a Snowflake administrator. Change the example names as needed:
|
|
|
|
```sql
|
|
create role if not exists PIPELINES_ROLE;
|
|
|
|
create user if not exists PIPELINES_USER
|
|
type = service;
|
|
|
|
grant role PIPELINES_ROLE to user PIPELINES_USER;
|
|
alter user PIPELINES_USER set default_role = PIPELINES_ROLE;
|
|
|
|
create database if not exists PIPELINES_DB;
|
|
create schema if not exists PIPELINES_DB.REPLICATED;
|
|
|
|
grant usage on database PIPELINES_DB to role PIPELINES_ROLE;
|
|
grant usage on schema PIPELINES_DB.REPLICATED to role PIPELINES_ROLE;
|
|
grant create table on schema PIPELINES_DB.REPLICATED to role PIPELINES_ROLE;
|
|
```
|
|
|
|
The pipeline role must own destination tables so it can alter, truncate, or drop them. Don't pre-create destination tables under another role.
|
|
|
|
Snowflake creates each table's managed default pipe, `<TABLE>-STREAMING`, automatically. No virtual warehouse, stage, or manually created pipe is required. See [Snowpipe Streaming access privileges](https://docs.snowflake.com/en/user-guide/snowpipe-streaming/snowpipe-streaming-access-control) for the ingestion requirements.
|
|
|
|
Use a separate role and warehouse for downstream queries and transformations.
|
|
|
|
### Keep the SQL and streaming roles aligned
|
|
|
|
Pipelines uses two Snowflake interfaces:
|
|
|
|
- SQL requests use the optional **Role** configured in the Dashboard. When **Role** is empty, they use the user's default role.
|
|
- Snowpipe Streaming uses the user's `DEFAULT_ROLE`. It does not use the optional **Role** setting.
|
|
|
|
Set the pipeline role as the service user's `DEFAULT_ROLE`. Leave **Role** empty or set it to that same role. Otherwise, SQL validation can succeed while streaming fails.
|
|
|
|
### Generate a key pair
|
|
|
|
Pipelines authenticates with an RSA key pair. Snowflake requires a key of at least 2048 bits and recommends PKCS #8. Choose one of the following commands to generate `rsa_key.p8`.
|
|
|
|
For an unencrypted private key:
|
|
|
|
```bash
|
|
openssl genrsa 2048 | openssl pkcs8 -topk8 \
|
|
-inform PEM -out rsa_key.p8 -nocrypt
|
|
```
|
|
|
|
Or, for a passphrase-protected private key:
|
|
|
|
```bash
|
|
openssl genrsa 2048 | openssl pkcs8 -topk8 -v2 des3 \
|
|
-inform PEM -out rsa_key.p8
|
|
```
|
|
|
|
Derive the public key:
|
|
|
|
```bash
|
|
openssl rsa -in rsa_key.p8 -pubout -out rsa_key.pub
|
|
```
|
|
|
|
Register only the public-key body with the service user. Omit the `BEGIN PUBLIC KEY` and `END PUBLIC KEY` lines:
|
|
|
|
```sql
|
|
alter user PIPELINES_USER set rsa_public_key = '<public-key-body>';
|
|
```
|
|
|
|
Keep `rsa_key.p8` and its passphrase secret. Don't commit them, paste them into logs, or send them to support. The Dashboard accepts unencrypted PKCS #1 or PKCS #8 keys, and encrypted PKCS #8 keys with a passphrase. It does not support encrypted PKCS #1 keys.
|
|
|
|
See [Snowflake key-pair authentication](https://docs.snowflake.com/en/user-guide/key-pair-auth) to verify the public-key fingerprint and rotate keys with `RSA_PUBLIC_KEY_2`.
|
|
|
|
### Find the account identifier
|
|
|
|
Run this query in Snowflake:
|
|
|
|
```sql
|
|
select current_organization_name() || '-' || current_account_name();
|
|
```
|
|
|
|
Enter the result as **Account ID**, for example `MYORG-MYACCOUNT`. Do not enter a full URL or dotted locator-and-region hostname. Account IDs can contain up to 63 characters. Legacy one-part account locators are also accepted. See [Snowflake account identifiers](https://docs.snowflake.com/en/user-guide/admin-account-identifier) for details.
|
|
|
|
## Configure Snowflake as a destination
|
|
|
|
Follow [Set up Pipelines](/docs/guides/database/replication/pipelines#setup-overview) and select **Snowflake**. Enter these settings:
|
|
|
|
| Field | Value |
|
|
| -------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
| **Account ID** | `MYORG-MYACCOUNT`, for example; use an organization-account identifier |
|
|
| **User** | `PIPELINES_USER`, or your unquoted service user |
|
|
| **Database** | `PIPELINES_DB`, or your destination database |
|
|
| **Schema** | `REPLICATED`, or your dedicated destination schema |
|
|
| **Role** | The service user's default role name, or empty; see [role alignment](#keep-the-sql-and-streaming-roles-aligned) |
|
|
| **Private key** | Complete PEM, including begin and end lines |
|
|
| **Private key passphrase** | Only for an encrypted PKCS #8 key |
|
|
|
|
Click **Create and start pipeline** and complete the validation and cost confirmations.
|
|
|
|
Enter the database and schema identifiers exactly as stored in Snowflake. Unquoted identifiers are stored in uppercase. Choose an account near the [managed pipeline region](/docs/guides/database/replication/pipelines#region).
|
|
|
|
## How it works
|
|
|
|
Pipelines uses Snowflake's SQL REST API to validate the database and schema, create, evolve, and recreate destination tables, and apply source `TRUNCATE` operations. It sends initial and ongoing row data through Snowpipe Streaming.
|
|
|
|
Validation checks authentication, database and schema visibility, and that `QUOTED_IDENTIFIERS_IGNORE_CASE` is `FALSE`. It does not verify that the role can create or own tables or write through Snowpipe Streaming.
|
|
|
|
### Destination table names
|
|
|
|
Pipelines maps each Postgres schema and table pair to one Snowflake table name. It doubles existing underscores, joins the names with one underscore, and uppercases the result:
|
|
|
|
| Postgres table | Snowflake table |
|
|
| ---------------------- | ------------------------ |
|
|
| `public.orders` | `PUBLIC_ORDERS` |
|
|
| `sales_eu.order_items` | `SALES__EU_ORDER__ITEMS` |
|
|
|
|
Postgres schema and table names cannot start or end with `_` or contain `"` or `;`. Names that differ only in case map to the same Snowflake name. Use lowercase Postgres names to avoid collisions. Source column names are preserved as quoted identifiers, except for the [reserved metadata names](#append-only-change-history).
|
|
|
|
### Append-only change history
|
|
|
|
Each destination table contains the replicated source columns plus two `VARCHAR NOT NULL` metadata columns:
|
|
|
|
| Column | Meaning |
|
|
| ---------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
| `_cdc_operation` | Lowercase operation: `insert`, `update`, or `delete`. |
|
|
| `_cdc_sequence_number` | Fixed-width hexadecimal commit LSN and transaction ordinal, such as `00000000016b3740/0000000000000002`. |
|
|
|
|
The metadata names are reserved and can't be used by source columns. Initial-sync rows use `insert` and the shared sequence number `0000000000000000/0000000000000000`.
|
|
|
|
Snowflake tables are an event history, not a current-state replica:
|
|
|
|
- An insert appends the new row.
|
|
- An update appends the complete new row. It does not append a before image.
|
|
- A delete appends the complete old row for `REPLICA IDENTITY FULL`. For a primary-key or `USING INDEX` identity, it sends only the identity columns. Other columns can contain destination defaults or `NULL`; do not treat them as the deleted row's original values.
|
|
- A source `TRUNCATE` truncates the Snowflake table, resets its streaming state, and does not append a truncate event.
|
|
|
|
The sequence number orders changes but is not a globally unique event ID. Snowpipe committed offsets suppress routine replay; consumers must still tolerate [duplicate processing](/docs/guides/database/replication/pipelines-faq#can-data-be-processed-more-than-once).
|
|
|
|
A [table restart](/docs/guides/database/replication/pipelines-monitoring#restarting-tables) drops the Snowflake table and managed streaming state, erasing its history. It cannot recover past events. [Removing a table from the publication](/docs/guides/database/replication/pipelines#removing-tables-from-replication) leaves its destination history in place.
|
|
|
|
## Query replicated data [#query-and-materialize-current-state]
|
|
|
|
Use the replicated change history to build a current-state dataset for reports and analytics. Pipelines maintains the history table. You create and maintain the queries, views, or dynamic tables that read it.
|
|
|
|
| Approach | When to use it | Tradeoff |
|
|
| -------------------------------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
| [Query or view](#query-current-state) | Read current state from the changes already in Snowflake. | Computes the result when queried, so query cost can grow with the history. |
|
|
| [Dynamic table](#materialize-with-a-dynamic-table) | Store current state for repeated analytics queries. | Uses compute and storage to maintain the result, with a configurable freshness target. |
|
|
| [Streams and tasks](#use-streams-and-tasks) | Control how and when a separate table is updated. | Requires your own merge, initialization, and recovery logic. |
|
|
|
|
### Before you start
|
|
|
|
The examples use `public.orders`, replicated to `PIPELINES_DB.REPLICATED.PUBLIC_ORDERS`, with source columns `id` and `status`. Replace these names with your own. Wait for the table's initial sync to finish before treating the result as a complete replica.
|
|
|
|
Choose a unique, non-null identity that stays the same when a row is updated. The examples use `id`. For a composite key, include every key column in `partition by`, such as `partition by "tenant_id", "id"`. Include those columns in the publication and in delete events. `REPLICA IDENTITY FULL` alone does not make rows unique.
|
|
|
|
<Admonition type="caution">
|
|
|
|
Changing an identity column can leave the old identity in these results. Pipelines appends the new row for an update without a delete for the previous identity. Use an immutable key for this pattern.
|
|
|
|
</Admonition>
|
|
|
|
Use a separate analytics role and warehouse, with a schema outside the Pipelines-managed `REPLICATED` schema for derived objects. The examples use `ANALYTICS_ROLE`, `ANALYTICS_WH`, and `PIPELINES_DB.ANALYTICS`. Ask your Snowflake administrator to prepare these resources and grant the analytics role:
|
|
|
|
- `USAGE` on the warehouse, database, and both schemas.
|
|
- `SELECT` on the replicated table.
|
|
- `CREATE VIEW` on the analytics schema to create a view, or `CREATE DYNAMIC TABLE` to create a dynamic table.
|
|
|
|
The role must be available to the Snowflake user running the examples. Keep ownership of the replicated table with `PIPELINES_ROLE`. See Snowflake's [dynamic table access control](https://docs.snowflake.com/en/user-guide/dynamic-tables/privileges) for the full privilege requirements.
|
|
|
|
### Query current state
|
|
|
|
Run these statements in a Snowflake SQL worksheet with your analytics role:
|
|
|
|
```sql
|
|
use role ANALYTICS_ROLE;
|
|
use warehouse ANALYTICS_WH;
|
|
|
|
select "id", "status"
|
|
from PIPELINES_DB.REPLICATED.PUBLIC_ORDERS
|
|
qualify row_number() over (
|
|
partition by "id" order by "_cdc_sequence_number" desc
|
|
) = 1
|
|
and "_cdc_operation" != 'delete';
|
|
```
|
|
|
|
The result contains one row per identity whose latest operation is not `delete`. Ordering by the fixed-width sequence string selects the latest change. Repeated copies of the same event produce one result row. Keep the double quotes around source and metadata column names because Pipelines creates them as case-sensitive identifiers.
|
|
|
|
Keep the delete condition in `qualify`. A `where "_cdc_operation" != 'delete'` condition would remove delete events before ranking and could bring back an older row. Snowflake's [`QUALIFY` reference](https://docs.snowflake.com/en/sql-reference/constructs/qualify) explains this evaluation order.
|
|
|
|
To reuse the query from an analytics tool, save it as a view:
|
|
|
|
```sql
|
|
create view PIPELINES_DB.ANALYTICS.ORDERS_CURRENT_VIEW as
|
|
select "id", "status"
|
|
from PIPELINES_DB.REPLICATED.PUBLIC_ORDERS
|
|
qualify row_number() over (
|
|
partition by "id" order by "_cdc_sequence_number" desc
|
|
) = 1
|
|
and "_cdc_operation" != 'delete';
|
|
```
|
|
|
|
A regular view stores the query definition, not a separate copy of its results. Each read derives current state from the history available to that query. See Snowflake's [comparison of views and dynamic tables](https://docs.snowflake.com/en/user-guide/overview-view-mview-dts).
|
|
|
|
### Materialize with a dynamic table
|
|
|
|
A dynamic table stores the query result and refreshes it as the replicated history changes. Use it when you want to query a maintained current-state dataset without defining a scheduled merge task.
|
|
|
|
1. Ask the owner of the replicated table to enable change tracking in Snowflake. This is a table setting, not a change to the replicated columns or data. Run as `PIPELINES_ROLE`, or another role that inherits ownership:
|
|
|
|
```sql
|
|
alter table PIPELINES_DB.REPLICATED.PUBLIC_ORDERS
|
|
set change_tracking = true;
|
|
```
|
|
|
|
The analytics role does not own the replicated table, so it cannot enable change tracking automatically when creating the dynamic table. See Snowflake's [change tracking requirements](https://docs.snowflake.com/en/user-guide/dynamic-tables/troubleshoot-creation#change-tracking-not-enabled-on-base-tables).
|
|
|
|
2. Switch to the analytics role and create the dynamic table:
|
|
|
|
```sql
|
|
use role ANALYTICS_ROLE;
|
|
use warehouse ANALYTICS_WH;
|
|
|
|
create dynamic table PIPELINES_DB.ANALYTICS.ORDERS_CURRENT
|
|
target_lag = '5 minutes'
|
|
warehouse = ANALYTICS_WH
|
|
refresh_mode = incremental
|
|
initialize = on_create
|
|
as
|
|
select "id", "status"
|
|
from PIPELINES_DB.REPLICATED.PUBLIC_ORDERS
|
|
qualify row_number() over (
|
|
partition by "id" order by "_cdc_sequence_number" desc
|
|
) = 1
|
|
and "_cdc_operation" != 'delete';
|
|
```
|
|
|
|
`initialize = on_create` populates the dynamic table before creation finishes. Explicit `refresh_mode = incremental` makes creation fail if your adapted query cannot refresh incrementally, instead of choosing a full refresh through `AUTO`. See Snowflake's [refresh modes](https://docs.snowflake.com/en/user-guide/dynamic-tables/refresh-modes) and [`CREATE DYNAMIC TABLE` reference](https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table).
|
|
|
|
3. Check the refresh mode and read the materialized rows:
|
|
|
|
```sql
|
|
show dynamic tables like 'ORDERS_CURRENT'
|
|
in schema PIPELINES_DB.ANALYTICS;
|
|
|
|
select "id", "status"
|
|
from PIPELINES_DB.ANALYTICS.ORDERS_CURRENT;
|
|
```
|
|
|
|
Confirm that `refresh_mode` is `INCREMENTAL` and scheduling is running. Use [Snowflake's refresh monitoring](https://docs.snowflake.com/en/user-guide/dynamic-tables/monitoring) to check the last successful refresh and any errors. After an insert, update, or delete reaches the replicated table, the next successful refresh reflects it in `ORDERS_CURRENT`.
|
|
|
|
The five-minute `target_lag` is an example freshness target relative to the history in Snowflake. It is not a fixed refresh schedule or an end-to-end latency guarantee from Postgres. Pipeline replication lag and dynamic-table refresh lag both affect freshness. See Snowflake's [target lag guide](https://docs.snowflake.com/en/user-guide/dynamic-tables/target-lag).
|
|
|
|
Dynamic-table refreshes consume warehouse compute, and the materialized results consume storage. These costs are additional to ingestion and querying. Start with a freshness target that meets your reporting needs and measure a representative workload. A dedicated warehouse helps isolate refresh costs. See Snowflake's [dynamic table cost guide](https://docs.snowflake.com/en/user-guide/dynamic-tables/cost).
|
|
|
|
### Use streams and tasks
|
|
|
|
Snowflake [streams and tasks](https://docs.snowflake.com/en/user-guide/data-pipelines-intro) can maintain a separate table with scheduled `MERGE` statements. Use this option when you need control over the update procedure or schedule. Snowflake's [SCD Type 1 examples](https://docs.snowflake.com/en/user-guide/dynamic-tables/migrate-streams-tasks#scd-type-1-upsert) compare this approach with dynamic tables.
|
|
|
|
Adapt the merge to Pipelines' `"_cdc_operation"` and `"_cdc_sequence_number"` columns. A stream on the history table sees appended rows, including rows representing source updates and deletes. Your job must interpret those operations, load existing history, tolerate replay, and rebuild current state after a source truncate or pipeline table reset.
|
|
|
|
### Maintain derived objects
|
|
|
|
Pipelines maintains the replicated history table, but does not update your view or dynamic-table definitions.
|
|
|
|
| Change | What to do |
|
|
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| Source `TRUNCATE` | A direct query or view reads the truncated history. Check that the dynamic table completes a refresh before relying on its contents. |
|
|
| Pipeline table reset | Wait for the new initial sync. Reapply table-specific read grants and change tracking to the recreated history table. Check dependent objects and recreate the dynamic table if it cannot refresh. |
|
|
| Added, renamed, or dropped source column | Review the explicit column list. Add new columns to your definition when needed. Update or recreate derived objects that reference renamed or dropped columns. |
|
|
|
|
Recreating a dynamic table initializes its contents again and uses compute. See Snowflake's [dynamic table modification guide](https://docs.snowflake.com/en/user-guide/dynamic-tables/modify) for changes that require reinitialization.
|
|
|
|
## Type mapping
|
|
|
|
Pipelines creates Snowflake columns with these mappings:
|
|
|
|
| Postgres type | Snowflake type |
|
|
| --------------------------------------- | ------------------------------- |
|
|
| `boolean` | `BOOLEAN` |
|
|
| `smallint`, `integer`, `bigint` | `SMALLINT`, `INTEGER`, `BIGINT` |
|
|
| `real`, `double precision` | `FLOAT`, `DOUBLE` |
|
|
| `date`, `time` | `DATE`, `TIME` |
|
|
| `timestamp`, `timestamp with time zone` | `TIMESTAMP_NTZ`, `TIMESTAMP_TZ` |
|
|
| `json`, `jsonb` | `VARIANT` |
|
|
| One-dimensional arrays | `ARRAY` |
|
|
| `oid` | `BIGINT` |
|
|
| Other types | `VARCHAR` |
|
|
|
|
Pipelines uses `VARCHAR` for character and text types, `numeric`, `time with time zone`, `interval`, `uuid`, `bytea`, bit strings, and custom or unknown types. `bytea` values are lowercase hexadecimal strings. Pipelines stores these values in serialized form, not as native Snowflake types.
|
|
|
|
Additional limits apply:
|
|
|
|
- Multi-dimensional arrays aren't supported. Non-default lower bounds on one-dimensional arrays aren't preserved.
|
|
- Non-finite `real` and `double precision` values are rejected. Non-finite `numeric` values are preserved as strings in `VARCHAR` columns.
|
|
- An uncompressed serialized row larger than 2 MiB is rejected.
|
|
- Source primary-key, unique, check, length, precision, and nullability constraints aren't copied. Only the two CDC metadata columns are `NOT NULL`.
|
|
|
|
## Schema change support
|
|
|
|
Pipelines supports:
|
|
|
|
- Adding, renaming, or dropping columns
|
|
- Adding or removing published columns on tracked tables
|
|
|
|
Replicated columns remain nullable in Snowflake, and changes to existing column defaults are not propagated. Initial table creation can copy compatible literal defaults. Columns added in Postgres can copy string, numeric, or boolean literal defaults; other defaults are omitted. Postgres still supplies the source values through replication.
|
|
|
|
Schema changes also affect stored history: renaming a column changes its name in old events, dropping it removes its historical values, and adding one with a default can populate older rows.
|
|
|
|
Previously excluded columns are added without defaults, leaving historical events `NULL` for those columns. Removing a published column drops its destination values; adding it again does not restore them.
|
|
|
|
For type changes, unsupported changes, and interrupted schema changes, see the shared [schema-change behavior and recovery](/docs/guides/database/replication/pipelines#schema-change-support). Apart from [enabling change tracking](#materialize-with-a-dynamic-table), do not alter managed destination objects manually.
|
|
|
|
## Troubleshooting
|
|
|
|
| Symptom | What to check |
|
|
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
| Authentication fails | [Account identifier](#find-the-account-identifier), user, [key fingerprint and PEM](#generate-a-key-pair), and passphrase. Omit the passphrase for an unencrypted key. |
|
|
| Database or schema isn't found | Exact identifier case and `USAGE` permissions on both objects |
|
|
| Validation passes but table creation fails | [Grants and ownership](#prepare-snowflake-resources), including `CREATE TABLE` |
|
|
| Tables are created but writes fail | [SQL and streaming role alignment](#keep-the-sql-and-streaming-roles-aligned), table permissions, and network access to the account control and discovered Snowpipe ingest endpoints |
|
|
| Updates or deletes fail | [Source replica identity and published columns](#source-table-requirements) |
|
|
| A row is rejected | [Type and row-size limits](#type-mapping) and [reserved metadata columns](#append-only-change-history) |
|
|
| A schema change fails | [Supported changes](#schema-change-support); do not repair managed tables manually |
|
|
|
|
Use [pipeline monitoring](/docs/guides/database/replication/pipelines-monitoring) to inspect errors. For unresolved failures, [contact support](/dashboard/support/new) with the pipeline ID and error details.
|
|
|
|
## Additional resources
|
|
|
|
- [Snowflake key-pair authentication](https://docs.snowflake.com/en/user-guide/key-pair-auth)
|
|
- [Snowflake account identifiers](https://docs.snowflake.com/en/user-guide/admin-account-identifier)
|
|
- [Snowpipe Streaming default pipe](https://docs.snowflake.com/en/user-guide/snowpipe-streaming/snowpipe-streaming-pipe-object)
|
|
- [Snowpipe Streaming access control](https://docs.snowflake.com/en/user-guide/snowpipe-streaming/snowpipe-streaming-access-control)
|