metadata-ingestion/docs/sources/snowflake/snowflake_post.md
Use the Important Capabilities table above as the source of truth for supported features and whether additional configuration is required.
If you are using Snowflake Shares to share data across different Snowflake accounts, and you have set up DataHub recipes for ingesting metadata from all these accounts, you may end up having multiple similar dataset entities corresponding to virtual versions of the same table in different Snowflake accounts. The DataHub Snowflake connector can automatically link such tables together through Siblings and Lineage relationships if the user provides information necessary to establish the relationship using the shares configuration in the recipe.
Note: The shares configuration is also strongly recommended when using the marketplace.enabled feature to ingest Snowflake internal marketplace listings as Data Products. Without it, ingestion still produces the Data Products, but _find_listing_for_purchase cannot map purchased databases back to their listings, so the Data Products end up with no associated datasets (assets) and a structured_reporter.warning is emitted. See the Internal Marketplace section below for details.
account1 (ingested as platform_instance instance1) owns a database db1. A share X is created in account1 that includes database db1 along with schemas and tables inside it.X is shared with Snowflake account account2 (ingested as platform_instance instance2). A database db1_from_X is created from inbound share X in account2. In this case, all tables and views included in share X will also be present in instance2.db1_from_X.shares configuration section as
shares:
X: # name of the share
database: db1
platform_instance: instance1
consumers: # list of all databases created from share X
- database: db1_from_X
platform_instance: instance2
X is shared with more Snowflake accounts and a database is created from share X in those accounts, then additional entries need to be added to the consumers list for share X, one per Snowflake account. The same shares config can then be copied across recipes for all accounts.If you are using the Snowflake internal marketplace (private data sharing within your organization via Data Exchange) and want to ingest marketplace listings as DataHub Data Products, you can operate in two modes:
For organizations that purchase/install internal marketplace listings:
Grant the required privileges (already covered in the Prerequisites section above):
-- Basic marketplace access
grant imported privileges on database snowflake to role datahub_role;
-- For SHOW SHARES to discover INBOUND shares
use role accountadmin;
grant import share on account to role datahub_role;
Enable marketplace ingestion in your recipe:
marketplace:
enabled: true
marketplace_mode: "consumer" # Default
# Optional: Configure time window for usage statistics
start_time: "-7 days" # Default: -1 day
end_time: "now"
bucket_duration: "DAY" # Options: "DAY", "HOUR"
The marketplace configuration inherits from BaseTimeWindowConfig, allowing you to control the time window for extracting marketplace usage statistics. This follows the same pattern as other DataHub connectors.
Configure the shares mapping (strongly recommended for linking Data Products to their purchased databases):
Snowflake does not expose a direct link between imported databases and the marketplace listings they came from. Without shares, ingestion still succeeds but each affected Data Product is emitted without assets and a structured_reporter.warning is logged.
shares:
<SHARE_NAME>: # From: SHOW SHARES
database: "<SOURCE_DATABASE>" # Source database in the share
platform_instance: null
consumers:
- database: "<IMPORTED_DATABASE>" # Your purchased/imported database
platform_instance: null
# Optional but recommended: Explicit listing mapping
listing_global_name: "PROVIDER.REGION.LISTING_NAME" # From: SHOW AVAILABLE LISTINGS
Explicit Listing Mapping (Recommended): Add listing_global_name to explicitly link your purchased database to its marketplace listing. This ensures accurate Data Product associations, especially when you have multiple listings with similar names. Without it, the connector will attempt to match listings by finding the source database name within the listing's global name or title (case-insensitive substring match).
To discover the correct values, run these SQL commands in Snowflake:
-- Find marketplace listings
SHOW AVAILABLE LISTINGS IS_ORGANIZATION = TRUE;
-- Find imported databases
SELECT DATABASE_NAME, TYPE FROM SNOWFLAKE.ACCOUNT_USAGE.DATABASES
WHERE TYPE = 'IMPORTED DATABASE' AND DELETED IS NULL;
-- Find shares and their mappings
SHOW SHARES;
Without the shares configuration:
With the shares configuration:
For organizations that publish/share internal marketplace listings:
Grant the required privileges (already covered in the Prerequisites section above):
-- Basic marketplace access
grant imported privileges on database snowflake to role datahub_role;
-- For SHOW SHARES to discover OUTBOUND shares (provider mode)
-- The role can ONLY see shares it owns or inherits
use role securityadmin;
grant role sysadmin to role datahub_role;
-- OR use SYSADMIN/ACCOUNTADMIN directly in your recipe:
-- role: SYSADMIN
Important Note: In Snowflake, SHOW SHARES returns OUTBOUND shares based on ownership. A role can see:
datahub_role created the share)datahub_role inherits from SYSADMIN)ACCOUNTADMIN that datahub_role doesn't inherit from)Solutions:
ACCOUNTADMIN or SYSADMIN: Grant SYSADMIN role to datahub_role (recommended)datahub_role creates the shares: No additional grant neededSYSADMIN or ACCOUNTADMIN directly in your ingestion recipeWithout proper role hierarchy, provider mode cannot discover outbound shares owned by other roles, and tables won't be added as assets to Data Products.
Enable marketplace ingestion in provider mode in your recipe:
marketplace:
enabled: true
marketplace_mode: "provider"
# Assign owners to your Data Products
internal_marketplace_owner_patterns:
"^Your Listing.*": ["data-team"]
# Optional: restrict the database fallback to specific schemas per listing.
# Required when DESC SHARE is not permitted (see troubleshooting below).
listing_to_schemas_overrides:
YOUR_LISTING_GLOBAL_NAME: [YOUR_SCHEMA]
# Include your source databases being shared
database_pattern:
allow:
- "YOUR_SOURCE_DATABASE"
No shares configuration needed!
Provider mode automatically:
What you'll get in provider mode:
Example use case:
You publish a "Customer 360" listing from your CUSTOMER_DATA database. Provider mode will:
SHOW AVAILABLE LISTINGSSHOW SHARESCUSTOMER_DATA database to the Data ProductSet marketplace_mode: "both" to track both purchased listings (consumer) AND published listings (provider) in the same ingestion.
For more details, see the marketplace configuration guide in the connector documentation.
Marketplace ingestion does not create domains. Map each Snowflake organization to an existing DataHub domain (URN, GUID, or name) via marketplace.organization_to_domain:
marketplace:
enabled: true
organization_to_domain:
"ACME Corp": "urn:li:domain:finance"
"Weather Co": "data-products" # resolved via DomainRegistry
Unmapped organizations produce Data Products with no domain. The domains aspect is written as CREATE only, so UI-assigned domains survive subsequent runs.
Caveats:
organization_to_domain for an already-ingested organization has no effect — soft-delete the Data Product's domains aspect to re-map.Marketplace:Provider:<old> tag on the purchased database container; remove it manually.Common Permission Errors:
If marketplace ingestion fails or produces incomplete data, check for these common permission issues:
"Failed to get marketplace listings - insufficient permissions"
SHOW AVAILABLE LISTINGSgrant imported privileges on database snowflake to role datahub_role;
"Failed to describe provider share - insufficient permissions"
DESC SHARE requires ownership of the share. Snowflake Marketplace creates shares
under ACCOUNTADMIN, and Snowflake does not allow transferring share ownership.SHOW SHARES). By default this includes all schemas in that database;
use listing_to_schemas_overrides to restrict to the specific schemas the share exposes:
marketplace:
listing_to_schemas_overrides:
YOUR_LISTING_GLOBAL_NAME: [YOUR_SCHEMA]
DESC SHARE output: Set role: ACCOUNTADMIN in your recipe (broader
permissions, not recommended for production)."Failed to query imported database tables - insufficient permissions"
INFORMATION_SCHEMA in imported databasesgrant usage on database "<imported-database>" to role datahub_role;
grant usage on all schemas in database "<imported-database>" to role datahub_role;
grant references on all tables in database "<imported-database>" to role datahub_role;
Data Products created but no assets appear
shares configuration (consumer mode)listing_global_name mapping in your shares config (see consumer mode documentation above)Incomplete listing metadata
fetch_internal_marketplace_listing_details: true requires additional time per listingDESCRIBE AVAILABLE LISTING for each listing to fetch enriched metadata (descriptions, owners, documentation links). For large catalogs (100+ listings), consider setting this to false to improve performance.Debugging Tips:
imported privileges on the snowflake databaseDataHub supports two strategies for extracting lineage and usage information from Snowflake:
use_queries_v2: true)The default and recommended approach uses an optimized query extraction method that:
include_queries)include_query_usage_statistics)pushdown_deny_usernames, pushdown_allow_usernames)push_down_database_pattern_access_history)query_dedup_strategy)use_queries_v2: false)The older approach that will be deprecated in future versions:
Both strategies access the same Snowflake system tables (account_usage.query_history, account_usage.access_history), but the new strategy provides significant performance improvements and additional functionality.
DataHub extracts lineage when a query reads from a Snowflake Stream. Coverage details:
INSERT ALL from a Stream — emits one lineage entry per downstream table, including column-level lineage. Requires use_queries_v2: true (the default).$SYS_VIEW_<id> or other $-prefixed names) for stream-driven queries. When this happens for a given row, that row falls back to SQL parsing so DataHub never builds lineage from unusable URNs.The Stream entity itself is also extracted as a top-level dataset; the lineage above is in addition to that.
When ingesting metadata from large Snowflake environments, you can improve performance by pushing down pattern filters directly to Snowflake SQL queries using the push_down_metadata_patterns configuration option.
Note: This option applies only to metadata extraction queries (
information_schema.databases,schemata,tables,views). For pushing down filters on lineage/usage queries (account_usage.access_history), usepush_down_database_pattern_access_historyinstead. These two options are independent and target completely separate query paths.
source:
type: snowflake
config:
# Enable pattern pushdown for improved performance
push_down_metadata_patterns: true
# Your existing patterns - MUST follow Snowflake RLIKE syntax
database_pattern:
allow:
- "PROD_.*" # Matches databases starting with PROD_
- "ANALYTICS.*" # Matches databases starting with ANALYTICS
deny:
- ".*_TEMP$" # Excludes databases ending with _TEMP
table_pattern:
allow:
- ".*" # Allow all tables
deny:
- ".*_BACKUP$" # Exclude tables ending with _BACKUP
By default, Snowflake views are fetched using SHOW VIEWS, which does not support SQL-level filtering. When push_down_metadata_patterns: true, the view_pattern is pushed down only if fetch_views_from_information_schema: true is also set. Otherwise, view filtering falls back to Python's re.match(), even with pushdown enabled.
This means if you write patterns in Snowflake RLIKE syntax (e.g., PROD.* for prefix matching), they will still work correctly with Python filtering since PROD.* is valid in both. However, patterns that rely on RLIKE's full-string matching semantics (e.g., exact match PROD_DB without .*) will behave differently — Python re.match() treats it as a prefix match.
Recommendation: If you enable push_down_metadata_patterns, also enable fetch_views_from_information_schema: true to ensure consistent behavior for view patterns.
When push_down_metadata_patterns: true, patterns are executed in Snowflake using the RLIKE operator instead of Python's re.match(). These have different matching behaviors:
| Behavior | Python re.match() | Snowflake RLIKE | Fix for Snowflake |
|---|---|---|---|
| Prefix match | 'PROD' matches 'PROD_DB' | 'PROD' does NOT match 'PROD_DB' | Use PROD.* |
| Suffix match | '.*_BACKUP' matches 'TABLE_BACKUP_V2' | Does NOT match | Use .*_BACKUP$ |
| Start anchor | '^PROD' matches 'PROD_DB' | Does NOT match | Use PROD.* |
| Alternation | 'PROD|DEV' matches 'PROD_DB' | Does NOT match | Use (PROD|DEV).* |
| Case sensitivity | Case-insensitive by default | Case-sensitive by default | Handled automatically |
Key difference: Python re.match() anchors at the START only (prefix matching), while Snowflake RLIKE requires a FULL STRING match.
| Intent | Without Pushdown (Python) | With Pushdown (Snowflake RLIKE) |
|---|---|---|
Starts with PROD | PROD | PROD.* |
Ends with _BACKUP | .*_BACKUP | .*_BACKUP$ |
Contains TEMP | .*TEMP.* | .*TEMP.* (same) |
| Exact match | PROD_DB | PROD_DB (same) |
Match PROD or DEV prefix | PROD|DEV | (PROD|DEV).* |
Before enabling pushdown in production, test your patterns in Snowflake:
-- Test prefix matching
SELECT 'PROD_DB' RLIKE 'PROD'; -- FALSE (needs .*)
SELECT 'PROD_DB' RLIKE 'PROD.*'; -- TRUE
-- Test suffix matching
SELECT 'TABLE_BACKUP_V2' RLIKE '.*_BACKUP'; -- FALSE (needs $)
SELECT 'TABLE_BACKUP' RLIKE '.*_BACKUP$'; -- TRUE
-- Test FQN with escaped dots
SELECT 'PROD_DB.PUBLIC.TABLE' RLIKE 'PROD_DB\\.PUBLIC\\..*'; -- TRUE
DataHub supports ingestion of Snowflake Semantic Views, which are business-defined views that define metrics, dimensions, and relationships for consistent data modeling and AI-powered analytics.
A semantic view is ingested in one of two representations, selected by the semantic_views.emit_semantic_model_entities flag (see below):
Semantic View): its dimension, fact, and metric columns are tagged DIMENSION/FACT/METRIC on the schema, table-level synonyms are stored as custom properties, and table-level (and, with column_lineage, column-level) lineage to the underlying base tables is emitted as a standard dataset upstreamLineage aspect.CREATE SEMANTIC VIEW DDL as its native definition). Each logical table the view exposes becomes its own dataset entity (subtype Semantic Model Dataset) carrying a semanticModelProperties back-reference to the model and a schemaMetadata projection of its dimension/fact columns; per-field semantic metadata (type, expression, dimension) lives on semanticFieldAnnotation aspects on each column's schemaField entity, and the logical dataset carries upstreamLineage to its physical base table. Each METRIC column becomes a separate metric entity linked back to the semanticModel (no metricUpstreams — lineage flows Metric → SemanticModel → Logical Dataset → Physical Dataset).The flag is tri-state and server-aware:
2.1.0 it is enabled unless the Metrics feature is explicitly disabled; it stays off on older Cloud, on OSS/self-hosted (which is recipe-driven — see below), and with no DataHub connection (e.g. a file sink). If the Metrics feature-flag probe cannot be read (a transient auth/network/GraphQL error), the connector fails closed to legacy datasets rather than emitting the new entities.true: request emission. On DataHub Cloud, still subject to the hard blocks above — below 2.1.0, with Metrics off, or when the Metrics probe cannot be read, the connector warns and falls back to legacy datasets. On OSS/self-hosted, true enables emission.false: always force legacy dataset behavior, overriding any server-side auto-enable.Semantic view ingestion is disabled by default (requires Snowflake Enterprise Edition or above). You can enable it using the following configuration options:
# Enable semantic view ingestion (requires Enterprise Edition)
semantic_views:
enabled: true # Default: false
column_lineage: true # Default: false - enable column-level lineage
include_queries: true # Default: false - emit query entities for queries against semantic views
# Filter semantic views using regex patterns
semantic_view_pattern:
allow:
- "ANALYTICS_DB.PUBLIC.*"
- "SALES_DB.*"
deny:
- ".*_INTERNAL"
column_lineage is enabledextract_tags is enabledsemanticModel entities carry no Snowsight URLinclude_queries is enabled, queries against semantic views are ingested as query entitiesemit_semantic_model_entitiesMETRIC column and a logical-dataset per logical table) instead of a single Dataset; the legacy dataset URN, subtype, and dataset-specific aspects no longer apply.upstreamLineage to each logical table's physical base table, plus column-level fine-grained lineage when column_lineage: true). Metrics carry no metricUpstreams — their lineage flows Metric → SemanticModel → Logical Dataset → Physical Dataset.include_usage is ignored (semanticModel/logical-dataset entities carry no usage statistics; usage is emitted only in the legacy dataset representation). include_queries still emits query entities, attributed to the semanticModel.semanticModel/metric/semanticFieldAnnotation. On Cloud the connector auto-enables only at ≥ 2.1.0 (which implies they're present), else warns and falls back; on OSS it is recipe-driven, so run a compatible server before setting true.FROM SEMANTIC_VIEW(...) can't declare the semanticModel as an upstream (upstreamLineage is dataset-URN-typed); with use_queries_v2, parsing may resolve it to the old (soft-deleted) dataset URN.include_technical_schema: false (semantic-view processing is skipped).env, so the same account in two environments collides — use a distinct platform_instance per environment.REFERENCES or SELECT privileges on semantic views (they are treated as views in Snowflake's permission model)GET_DDL functionDataHub supports ingestion of Snowflake Stages, Tasks, and Snowpipe objects. All three features are disabled by default and can be enabled independently.
include_stages: true)Stages are ingested as containers nested under their parent schema. Internal stages additionally emit a placeholder dataset representing the staged data, which is used for pipe lineage resolution. External stages (S3, GCS, Azure) resolve their URLs to the corresponding cloud platform dataset URN.
include_stages: true
stage_pattern:
allow:
- "MY_DB.MY_SCHEMA.*"
include_tasks: true)Tasks are ingested as DataJob entities grouped under a per-schema DataFlow. Predecessor dependencies between tasks are captured as inputDatajobs on the DataJobInputOutput aspect, preserving the DAG structure.
include_tasks: true
task_pattern:
allow:
- "MY_DB.MY_SCHEMA.*"
include_pipes: true)Snowpipe objects are ingested as DataJob entities with lineage derived from parsing the COPY INTO statement. The pipe's source stage resolves to an upstream dataset (internal placeholder or external cloud URN) and the target table resolves to a downstream dataset. Enabling pipes automatically scans stages for lineage resolution, even if include_stages is false.
The parser handles the following COPY INTO patterns:
COPY INTO t(a, b) FROM @stage resolves the target table correctlyCOPY INTO t FROM (SELECT ... FROM @s1 UNION ALL SELECT ... FROM @s2) captures all referenced stages as upstream datasetsCOPY pipe bodies are silently skipped; stage refs that cannot be normalized to a three-part FQN emit a warning in the ingestion reportinclude_pipes: true
pipe_pattern:
allow:
- "MY_DB.MY_SCHEMA.*"
Module behavior is constrained by source APIs, permissions, and metadata exposed by the platform. Refer to capability notes for unsupported or conditional features.
monitor privilege for full metadata extraction. Without this privilege, dynamic table entities will still appear in DataHub but their DDL is not accessible, so lineage will not be extracted.REFERENCES or SELECT privileges for metadata extraction. Without these privileges, semantic views will not be visible to DataHub.If ingestion fails, validate credentials, permissions, connectivity, and scope filters first. Then review ingestion logs for source-specific errors and adjust configuration accordingly.
By default, DataHub generates Snowsight links of the form https://app.snowflake.com/<region>/<account>/.... If Snowsight is only reachable through private link in your environment, these links will not work for your users.
To fix this, set snowsight_base_url in the recipe to your private-link Snowsight URL. Retrieve the value from Snowflake as ACCOUNTADMIN:
SELECT SYSTEM$GET_PRIVATELINK_CONFIG();
-- Use either snowsight-privatelink-url or regionless-snowsight-privatelink-url.
source:
type: snowflake
config:
snowsight_base_url: "https://app.us-east-1.privatelink.snowflakecomputing.com/"