Snowflake
This guide walks you through connecting Snowflake to Solid so you can get insights into your data lineage, usage, and quality.
Overview
Solid requires access to:
- The schema of your Snowflake datasets
- Query logs from the Query History table
- Read-only grants on your data — required for Solid's Text2SQL engine to generate and validate SQL
Note: You will need the ACCOUNTADMIN role to create the Solid role and user.
You have two options for connecting Snowflake to Solid:
| Option | Best For | What You'll Do |
|---|---|---|
| Automatic Pull (Recommended) | Ongoing use and continuous monitoring | Set up a service user that runs automatically |
| Manual Pull | Quick proof of concept or one-time setup | Export data manually and upload it |
What Is Collected
| Category | What | Why |
|---|---|---|
| Metadata | Table/column definitions and comments from INFORMATION_SCHEMA.COLUMNS / TABLES | To build the data catalog |
| Query History | Query text, user, role, warehouse, timestamps, and status from SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY | To learn usage patterns and seed benchmarks |
| Table Usage | Row insert/update/delete counts per table from TABLE_DML_HISTORY | To surface active tables and detect stale data |
| Primary/Foreign Keys | Output of SHOW PRIMARY KEYS / SHOW IMPORTED KEYS | To build lineage relationships and understand join paths |
| Users | Records from SNOWFLAKE.ACCOUNT_USAGE.USERS | To attribute queries and usage to specific people |
| Data Profiling/Samples | Sampled values from up to 100,000 rows per table (MIN/MAX/AVG, distinct counts, null counts, top values) | Required for Solid's Text2SQL engine to generate and validate SQL, and for data quality checks |
Automatic Pull (Recommended)
Best for: Production use with continuous monitoring and updates.
What This Does
This script creates a dedicated service user that Solid uses to automatically pull metadata from your Snowflake account. It includes the read-only grants Solid's Text2SQL engine requires to generate and validate SQL against your data.
The warehouse is configured to:
- Use XSMALL size (minimal cost)
- Auto-suspend after 5 minutes of inactivity
- Auto-resume when needed
Prerequisites
- You need the ACCOUNTADMIN role to run this script
- Replace
<your_database>with your actual database name
Configuration Variables
The script defines several variables to customize the setup:
| Variable | Default | Description |
|---|---|---|
solid_username | SOLID_DATA | The name for the new Snowflake user |
solid_warehouse_size | XSMALL | The compute size for the new virtual warehouse |
solid_warehouse_name | SOLID_DATA_WH | The name for the new virtual warehouse |
solid_role_name | SOLID_DATA_ROLE | The name for the new Snowflake role |
database_to_monitor | (must be replaced) | The database you want Solid to monitor |
Setup Script
-- ==========================================
-- 1. CONFIGURATION & VARIABLES
-- ==========================================
set solid_username='SOLID_DATA';
set solid_warehouse_size='XSMALL';
set solid_warehouse_name='SOLID_DATA_WH';
set solid_role_name='SOLID_DATA_ROLE';
set database_to_monitor=''; -- REPLACE THIS WITH YOUR DATABASE NAME
USE ROLE ACCOUNTADMIN;
-- ==========================================
-- 2. CORE SETUP: Role, User, and Warehouse
-- ==========================================
-- Create virtual warehouse (Auto-suspends after 5 min to optimize cost)
CREATE WAREHOUSE IF NOT EXISTS identifier($solid_warehouse_name)
WAREHOUSE_SIZE=$solid_warehouse_size
INITIALLY_SUSPENDED=TRUE
AUTO_SUSPEND = 5
AUTO_RESUME = TRUE;
-- Create the custom role for Solid
CREATE ROLE IF NOT EXISTS identifier($solid_role_name);
-- Create the Service User and assign the role
CREATE USER IF NOT EXISTS identifier($solid_username)
DEFAULT_ROLE=$solid_role_name
TYPE=SERVICE;
GRANT ROLE identifier($solid_role_name) TO USER identifier($solid_username);
-- ==========================================
-- 3. GRANT REQUIRED PERMISSIONS
-- ==========================================
-- Warehouse Permissions
-- OPERATE: Allows starting, stopping, and resizing the warehouse
-- USAGE: Allows using the warehouse to execute queries
-- MONITOR: Allows viewing warehouse status, usage statistics, and query history
GRANT OPERATE, USAGE, MONITOR ON WAREHOUSE identifier($solid_warehouse_name)
TO ROLE identifier($solid_role_name);
-- Allow access to internal query history and metadata
GRANT IMPORTED PRIVILEGES ON DATABASE "SNOWFLAKE"
TO ROLE identifier($solid_role_name);
-- Grant access to the monitored database
GRANT USAGE, MONITOR ON DATABASE identifier($database_to_monitor)
TO ROLE identifier($solid_role_name);
GRANT USAGE, MONITOR ON ALL SCHEMAS IN DATABASE identifier($database_to_monitor)
TO ROLE identifier($solid_role_name);
GRANT USAGE, MONITOR ON FUTURE SCHEMAS IN DATABASE identifier($database_to_monitor)
TO ROLE identifier($solid_role_name);
-- ==========================================
-- 4. REFERENCE GRANTS PROCEDURE
-- ==========================================
-- Logic to ensure lineage visibility even if schema-level future grants exist
-- REFERENCES privilege is vital for foreign key constraints and metadata lookups
-- but does not allow data access
USE DATABASE identifier($database_to_monitor);
CREATE OR REPLACE PROCEDURE GRANT_REFERENCES_TO_SOLID()
RETURNS VARCHAR LANGUAGE javascript EXECUTE AS CALLER
AS
$$
snowflake.createStatement({sqlText: `use database identifier($database_to_monitor)`}).execute();
var show_future_grants = snowflake.createStatement({sqlText: `SHOW FUTURE GRANTS IN DATABASE identifier($database_to_monitor)`}).execute();
var schema_future_grants = snowflake.createStatement({sqlText: `select * from TABLE(RESULT_SCAN('${show_future_grants.getQueryId()}')) where "grant_on" = 'SCHEMA'`}).execute();
if (schema_future_grants.getRowCount() > 0) {
var schemas_to_grant = snowflake.createStatement({ sqlText:`select * from information_schema.SCHEMATA where SCHEMA_NAME <> 'INFORMATION_SCHEMA'`}).execute();
var granted_schemas = "";
while(schemas_to_grant.next()) {
table_schema = schemas_to_grant.getColumnValue("SCHEMA_NAME");
snowflake.createStatement({ sqlText:`GRANT REFERENCES ON ALL TABLES IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT REFERENCES ON ALL VIEWS IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT REFERENCES ON ALL EXTERNAL TABLES IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT MONITOR ON ALL DYNAMIC TABLES IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT REFERENCES ON FUTURE TABLES IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT REFERENCES ON FUTURE VIEWS IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT REFERENCES ON FUTURE EXTERNAL TABLES IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT MONITOR ON FUTURE DYNAMIC TABLES IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
granted_schemas += table_schema + "; "
}
return `Granted references for schemas ${granted_schemas}`;
}
snowflake.createStatement({ sqlText: `GRANT REFERENCES ON ALL TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText: `GRANT REFERENCES ON ALL VIEWS IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText: `GRANT REFERENCES ON ALL EXTERNAL TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText: `GRANT MONITOR ON ALL DYNAMIC TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText: `GRANT REFERENCES ON FUTURE TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText: `GRANT REFERENCES ON FUTURE VIEWS IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText: `GRANT REFERENCES ON FUTURE EXTERNAL TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText: `GRANT MONITOR ON FUTURE DYNAMIC TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
return `Granted references for database`;
$$;
CALL GRANT_REFERENCES_TO_SOLID();
-- ==========================================
-- 5. SEMANTIC VIEW GRANTS (REQUIRED)
-- ==========================================
-- Allows Solid to create semantic views in the monitored database
-- This grant is required for Solid to function
-- Option A: Grant on all current and future schemas in the database (recommended)
-- ADDED: This grants permission on schemas that ALREADY exist (like 'RAW')
GRANT CREATE SEMANTIC VIEW ON ALL SCHEMAS IN DATABASE identifier($database_to_monitor)
TO ROLE identifier($solid_role_name);
-- EXISTING: This grants permission on schemas you might create tomorrow
GRANT CREATE SEMANTIC VIEW ON FUTURE SCHEMAS IN DATABASE identifier($database_to_monitor)
TO ROLE identifier($solid_role_name);
-- Option B: Grant on a specific schema only (if you prefer not to grant on all schemas)
-- Replace with the specific schema name
-- GRANT CREATE SEMANTIC VIEW ON SCHEMA identifier($database_to_monitor).
-- TO ROLE identifier($solid_role_name);
-- ==========================================
-- 6. SOLID READ ONLY GRANTS (REQUIRED)
-- ==========================================
-- Required for Solid's Text2SQL engine to generate and validate SQL, and to
-- perform data profiling/quality checks
-- This gives SOLID_DATA_ROLE read-only access to the data within the monitored database
GRANT SELECT ON ALL TABLES IN DATABASE identifier($database_to_monitor)
TO ROLE identifier($solid_role_name);
GRANT SELECT ON ALL VIEWS IN DATABASE identifier($database_to_monitor)
TO ROLE identifier($solid_role_name);
GRANT SELECT ON ALL EXTERNAL TABLES IN DATABASE identifier($database_to_monitor)
TO ROLE identifier($solid_role_name);
GRANT SELECT ON ALL STREAMS IN DATABASE identifier($database_to_monitor)
TO ROLE identifier($solid_role_name);
GRANT SELECT ON ALL DYNAMIC TABLES IN DATABASE identifier($database_to_monitor)
TO ROLE identifier($solid_role_name);
CREATE OR REPLACE PROCEDURE GRANT_SELECT_FUTURES_TO_SOLID()
RETURNS VARCHAR LANGUAGE javascript EXECUTE AS CALLER
AS
$$
snowflake.createStatement({sqlText: `use database identifier($database_to_monitor)`}).execute();
var show_future_grants = snowflake.createStatement({sqlText: `SHOW FUTURE GRANTS IN DATABASE identifier($database_to_monitor)`}).execute();
var schema_future_grants = snowflake.createStatement({sqlText: `select * from TABLE(RESULT_SCAN('${show_future_grants.getQueryId()}')) where "grant_on" = 'SCHEMA'`}).execute();
if (schema_future_grants.getRowCount() > 0) {
var schemas_to_grant = snowflake.createStatement({ sqlText:`select SCHEMA_NAME from information_schema.SCHEMATA where SCHEMA_NAME <> 'INFORMATION_SCHEMA'`}).execute();
while(schemas_to_grant.next()) {
table_schema = schemas_to_grant.getColumnValue("SCHEMA_NAME");
snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE TABLES IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE VIEWS IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE EXTERNAL TABLES IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE STREAMS IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE DYNAMIC TABLES IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
}
return `Granted future select for schemas`;
}
snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE VIEWS IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE EXTERNAL TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE STREAMS IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE DYNAMIC TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
return `Granted future select for database`;
$$;
CALL GRANT_SELECT_FUTURES_TO_SOLID();How to Use the Script
- Replace Placeholder — Before running, replace
<your_database>with the actual name of the Snowflake database you want Solid to monitor. - Execute in Snowflake — Run the script in your Snowflake worksheet or client.
- Run the Full Script — Run all six sections, including Section 6: SOLID READ ONLY GRANTS. Solid's Text2SQL engine requires read access to your data to generate and validate SQL; it is not an optional step.
Note on Semantic View Grants (Section 5): This grant is required for Solid to function. Option A (recommended) grants permission across all current and future schemas in the database. Option B allows you to restrict the grant to a specific schema if your security policies require more granular control.
Configure in Solid
Step 1: Navigate to Integrations
Go to Settings → Integrations → Snowflake
Step 2: Enter Connection Details
| Field | What to Enter |
|---|---|
| User | SOLID_DATA (or your service user name) |
| Password / Private Key | Your authentication credential |
| Account | Your Snowflake account identifier |
| Warehouse | SOLID_DATA_WH (or your warehouse name) |
Step 3: Test the Connection
Click Test Connection. If it fails:
- Double-check your credentials
- Make sure the warehouse is running
- Confirm the role has USAGE rights on the database
Step 4: Select What to Monitor
Choose which databases and schemas you want Solid to analyze.
Step 5: Save
Click Save and Solid will start analyzing your data.
Manual Pull
Best for: Testing Solid or if you can't grant direct database access yet.
Steps
- Run the queries below in your Snowflake Query Editor
- Export each result as a CSV file
- Compress the files (ZIP or GZIP)
- Upload to the Solid Azure Storage container
Metadata Collection
Columns
For each database, run the following query. You can choose the relevant schemas or remove the filter in the last line:
SELECT
*,
t1.COMMENT AS COLUMN_COMMENT,
t2.COMMENT AS TABLE_COMMENT
FROM
{DB}.INFORMATION_SCHEMA.COLUMNS t1
LEFT JOIN {DB}.INFORMATION_SCHEMA.TABLES t2 ON
t1.TABLE_CATALOG = t2.TABLE_CATALOG
AND t1.TABLE_SCHEMA = t2.TABLE_SCHEMA
AND t1.TABLE_NAME = t2.TABLE_NAME
WHERE
t1.TABLE_SCHEMA != 'INFORMATION_SCHEMA'
AND t1.TABLE_CATALOG ILIKE '{DB}'
AND t1.TABLE_SCHEMA IN ({relevant schemas});Example:
SELECT
*,
t1.COMMENT AS COLUMN_COMMENT,
t2.COMMENT AS TABLE_COMMENT
FROM
ACME_DEMO.INFORMATION_SCHEMA.COLUMNS t1
LEFT JOIN ACME_DEMO.INFORMATION_SCHEMA.TABLES t2 ON
t1.TABLE_CATALOG = t2.TABLE_CATALOG
AND t1.TABLE_SCHEMA = t2.TABLE_SCHEMA
AND t1.TABLE_NAME = t2.TABLE_NAME
WHERE
t1.TABLE_SCHEMA != 'INFORMATION_SCHEMA'
AND t1.TABLE_CATALOG ILIKE 'ACME_DEMO'
AND t1.TABLE_SCHEMA IN ('MARTS', 'INTERMEDIATE', 'STAGING', 'RAW');Primary Keys, Foreign Keys, and Unique Constraints
For each database and schema, run these queries:
SHOW PRIMARY KEYS IN SCHEMA {db}.{schema};
SHOW IMPORTED KEYS IN SCHEMA {db}.{schema};Example:
SHOW PRIMARY KEYS IN SCHEMA ACME_DEMO.INTERMEDIATE;
SHOW IMPORTED KEYS IN SCHEMA ACME_DEMO.INTERMEDIATE;Query History Collection
Queries with Database Context
For each database, run:
SELECT
QUERY_ID,
QUERY_TEXT,
DATABASE_NAME,
SCHEMA_NAME,
QUERY_TYPE,
USER_NAME,
ROLE_NAME,
WAREHOUSE_NAME,
EXECUTION_STATUS,
START_TIME,
END_TIME,
TOTAL_ELAPSED_TIME,
QUERY_HASH,
QUERY_PARAMETERIZED_HASH,
USER_TYPE,
USER_DATABASE_NAME,
USER_SCHEMA_NAME,
ROWS_WRITTEN_TO_RESULT
FROM
SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE
QUERY_TYPE = 'SELECT'
AND QUERY_TAG != 'SolidData'
AND EXECUTION_STATUS = 'SUCCESS'
AND END_TIME > '{today - 90 days}'
AND DATABASE_NAME ILIKE '{DB}'
AND ROWS_WRITTEN_TO_RESULT > 0
AND SCHEMA_NAME IN ({relevant schemas})
ORDER BY
END_TIME DESC
LIMIT 1000000;Example:
SELECT
QUERY_ID,
QUERY_TEXT,
DATABASE_NAME,
SCHEMA_NAME,
QUERY_TYPE,
USER_NAME,
ROLE_NAME,
WAREHOUSE_NAME,
EXECUTION_STATUS,
START_TIME,
END_TIME,
TOTAL_ELAPSED_TIME,
QUERY_HASH,
QUERY_PARAMETERIZED_HASH,
USER_TYPE,
USER_DATABASE_NAME,
USER_SCHEMA_NAME,
ROWS_WRITTEN_TO_RESULT
FROM
SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE
QUERY_TYPE = 'SELECT'
AND QUERY_TAG != 'SolidData'
AND EXECUTION_STATUS = 'SUCCESS'
AND END_TIME > '2025-09-07 18:49:47 +0000'
AND DATABASE_NAME ILIKE 'ACME_DEMO'
AND ROWS_WRITTEN_TO_RESULT > 0
AND SCHEMA_NAME IN ('MARTS', 'INTERMEDIATE', 'STAGING', 'RAW')
ORDER BY
END_TIME DESC
LIMIT 1000000;Queries without Database Context
Run this query to capture queries that don't reference a specific database:
SELECT
QUERY_ID,
QUERY_TEXT,
DATABASE_NAME,
SCHEMA_NAME,
QUERY_TYPE,
USER_NAME,
ROLE_NAME,
WAREHOUSE_NAME,
EXECUTION_STATUS,
START_TIME,
END_TIME,
TOTAL_ELAPSED_TIME,
QUERY_HASH,
QUERY_PARAMETERIZED_HASH,
USER_TYPE,
USER_DATABASE_NAME,
USER_SCHEMA_NAME,
ROWS_WRITTEN_TO_RESULT
FROM
SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE
QUERY_TYPE = 'SELECT'
AND EXECUTION_STATUS = 'SUCCESS'
AND END_TIME > '{today - 90 days}'
AND DATABASE_NAME IS NULL
AND ROWS_WRITTEN_TO_RESULT > 0
ORDER BY
END_TIME DESC
LIMIT 1000000;Example:
SELECT
QUERY_ID,
QUERY_TEXT,
DATABASE_NAME,
SCHEMA_NAME,
QUERY_TYPE,
USER_NAME,
ROLE_NAME,
WAREHOUSE_NAME,
EXECUTION_STATUS,
START_TIME,
END_TIME,
TOTAL_ELAPSED_TIME,
QUERY_HASH,
QUERY_PARAMETERIZED_HASH,
USER_TYPE,
USER_DATABASE_NAME,
USER_SCHEMA_NAME,
ROWS_WRITTEN_TO_RESULT
FROM
SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE
QUERY_TYPE = 'SELECT'
AND EXECUTION_STATUS = 'SUCCESS'
AND END_TIME > '2025-09-07 18:49:47 +0000'
AND DATABASE_NAME IS NULL
AND ROWS_WRITTEN_TO_RESULT > 0
ORDER BY
END_TIME DESC
LIMIT 1000000;Users Collection
SELECT * FROM SNOWFLAKE.ACCOUNT_USAGE.USERS;Table Usage Collection
For each database, run:
SELECT
DATABASE_NAME,
SCHEMA_NAME,
TABLE_NAME,
DATE(START_TIME) AS DAY,
SUM(ROWS_ADDED) AS total_rows_added,
SUM(ROWS_REMOVED) AS total_rows_removed,
SUM(ROWS_UPDATED) AS total_rows_updated,
(SUM(ROWS_ADDED) + SUM(ROWS_REMOVED) + SUM(ROWS_UPDATED)) AS total_count
FROM
SNOWFLAKE.ACCOUNT_USAGE.TABLE_DML_HISTORY
WHERE
START_TIME >= '{today - 90 days}'
AND END_TIME <= 'today'
AND DATABASE_NAME = '{db}'
AND SCHEMA_NAME IN ({relevant schemas})
GROUP BY
DATABASE_NAME,
SCHEMA_NAME,
TABLE_NAME,
DATE(START_TIME)
HAVING
(SUM(ROWS_ADDED) + SUM(ROWS_REMOVED) + SUM(ROWS_UPDATED)) > 0
ORDER BY
DAY ASC;Example:
SELECT
DATABASE_NAME,
SCHEMA_NAME,
TABLE_NAME,
DATE(START_TIME) AS DAY,
SUM(ROWS_ADDED) AS total_rows_added,
SUM(ROWS_REMOVED) AS total_rows_removed,
SUM(ROWS_UPDATED) AS total_rows_updated,
(SUM(ROWS_ADDED) + SUM(ROWS_REMOVED) + SUM(ROWS_UPDATED)) AS total_count
FROM
SNOWFLAKE.ACCOUNT_USAGE.TABLE_DML_HISTORY
WHERE
START_TIME >= '2025-09-07 00:00:00.000'
AND END_TIME <= '2025-10-12 00:00:00.000'
AND DATABASE_NAME = 'ACME_DEMO'
AND SCHEMA_NAME IN ('MARTS', 'INTERMEDIATE', 'STAGING', 'RAW')
GROUP BY
DATABASE_NAME,
SCHEMA_NAME,
TABLE_NAME,
DATE(START_TIME)
HAVING
(SUM(ROWS_ADDED) + SUM(ROWS_REMOVED) + SUM(ROWS_UPDATED)) > 0
ORDER BY
DAY ASC;Permissions Needed
| Permission/Grant | Purpose |
|---|---|
SOLID_DATA_ROLE (custom role) | Dedicated role that scopes every permission granted to Solid |
SOLID_DATA (service user) | Dedicated service account Solid uses to connect |
SOLID_DATA_WH (warehouse) | Dedicated XSMALL warehouse (auto-suspend/auto-resume) used to run Solid's queries |
GRANT OPERATE, USAGE, MONITOR ON WAREHOUSE | Lets the role start/stop/resize the warehouse, run queries, and view usage stats |
GRANT IMPORTED PRIVILEGES ON DATABASE "SNOWFLAKE" | Access to Snowflake's internal query history and metadata (ACCOUNT_USAGE) |
GRANT USAGE, MONITOR ON DATABASE (and its ALL SCHEMAS / FUTURE SCHEMAS) | Visibility into the monitored database and its current and future schemas |
GRANT_REFERENCES_TO_SOLID procedure — GRANT REFERENCES / MONITOR on all and future tables, views, external tables, and dynamic tables | Enables foreign key/metadata lookups and lineage visibility without granting data access |
GRANT CREATE SEMANTIC VIEW on ALL SCHEMAS / FUTURE SCHEMAS IN DATABASE | Required for Solid to create semantic views in the monitored database |
GRANT SELECT on ALL TABLES / VIEWS / EXTERNAL TABLES / STREAMS / DYNAMIC TABLES IN DATABASE (Section 6) | Read-only access to the data itself — required for Solid's Text2SQL engine to generate and validate SQL, and for data profiling |
GRANT_SELECT_FUTURES_TO_SOLID procedure — GRANT SELECT on future tables, views, external tables, streams, and dynamic tables | Ensures the same read-only access automatically extends to new objects |
Troubleshooting
Connection Issues
- Verify you replaced
<your_database>with your actual database name in the script - Confirm you have the ACCOUNTADMIN role when running the setup script
- Check the warehouse is running in Snowflake
- Verify all grants completed successfully by reviewing the script output
Permission Issues
- Check the role permissions by running
SHOW GRANTS TO ROLE SOLID_DATA_ROLE; - Verify the database name matches what you specified in the setup
- Ensure future grants are in place for new tables and views
Performance Concerns
The Solid warehouse uses XSMALL size and auto-suspends after 5 minutes of inactivity. You can adjust these settings by modifying the warehouse configuration in the setup script.
Security Notes
- The service user created has read-only access to metadata, query history, and your data (Section 6) — required for Solid's Text2SQL engine
- All credentials should be stored securely
- The service user does not have permissions to modify data or schema
- The REFERENCES privilege allows foreign key constraints and metadata lookups but does not allow data access
Appendix
For transparency, here are the specific queries Solid executes when automatically collecting data.
Metadata
SELECT
*,
t1.COMMENT AS COLUMN_COMMENT,
t2.COMMENT AS TABLE_COMMENT
FROM
{database}.INFORMATION_SCHEMA.COLUMNS t1
LEFT JOIN {database}.INFORMATION_SCHEMA.TABLES t2
ON t1.TABLE_CATALOG = t2.TABLE_CATALOG
AND t1.TABLE_SCHEMA = t2.TABLE_SCHEMA
AND t1.TABLE_NAME = t2.TABLE_NAME
WHERE
t1.TABLE_SCHEMA != 'INFORMATION_SCHEMA'
AND t1.TABLE_CATALOG ILIKE '{database}'
AND t1.TABLE_SCHEMA IN ({schemas});Query History
Queries with database context:
SELECT
QUERY_ID, QUERY_TEXT, DATABASE_NAME, SCHEMA_NAME, QUERY_TYPE,
USER_NAME, ROLE_NAME, WAREHOUSE_NAME, EXECUTION_STATUS,
START_TIME, END_TIME, TOTAL_ELAPSED_TIME, QUERY_HASH,
QUERY_PARAMETERIZED_HASH, USER_TYPE, USER_DATABASE_NAME,
USER_SCHEMA_NAME, ROWS_WRITTEN_TO_RESULT
FROM
SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE
QUERY_TYPE='SELECT'
AND QUERY_TAG!='SolidData'
AND EXECUTION_STATUS='SUCCESS'
AND END_TIME>'{last_collect_time}'
AND DATABASE_NAME ILIKE '{database}'
AND ROWS_WRITTEN_TO_RESULT > 0
ORDER BY END_TIME DESC
LIMIT 1000000;Queries without database context:
SELECT
QUERY_ID, QUERY_TEXT, DATABASE_NAME, SCHEMA_NAME, QUERY_TYPE,
USER_NAME, ROLE_NAME, WAREHOUSE_NAME, EXECUTION_STATUS,
START_TIME, END_TIME, TOTAL_ELAPSED_TIME, QUERY_HASH,
QUERY_PARAMETERIZED_HASH, USER_TYPE, USER_DATABASE_NAME,
USER_SCHEMA_NAME, ROWS_WRITTEN_TO_RESULT
FROM
SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE
QUERY_TYPE='SELECT'
AND EXECUTION_STATUS='SUCCESS'
AND END_TIME>'{last_collect_time}'
AND DATABASE_NAME IS NULL
AND ROWS_WRITTEN_TO_RESULT > 0
ORDER BY END_TIME DESC
LIMIT 1000000;Table Usage
SELECT
DATABASE_NAME, SCHEMA_NAME, TABLE_NAME,
DATE(START_TIME) AS day,
SUM(ROWS_ADDED) AS total_rows_added,
SUM(ROWS_REMOVED) AS total_rows_removed,
SUM(ROWS_UPDATED) AS total_rows_updated,
(SUM(ROWS_ADDED) + SUM(ROWS_REMOVED) + SUM(ROWS_UPDATED)) AS total_count
FROM
SNOWFLAKE.ACCOUNT_USAGE.TABLE_DML_HISTORY
WHERE
START_TIME >= '{start_day}'
AND END_TIME <= '{end_day}'
AND DATABASE_NAME = '{database}'
GROUP BY DATABASE_NAME, SCHEMA_NAME, TABLE_NAME, DATE(START_TIME)
HAVING (SUM(ROWS_ADDED) + SUM(ROWS_REMOVED) + SUM(ROWS_UPDATED)) > 0
ORDER BY day ASC;Primary and Foreign Keys
SHOW PRIMARY KEYS IN SCHEMA {database}.{schema};
SHOW IMPORTED KEYS IN SCHEMA {database}.{schema};Users
SELECT * FROM SNOWFLAKE.ACCOUNT_USAGE.USERS;Column Data Profiling
Using the read-only grants configured in Section 6 of the setup script, Solid samples up to 100,000 rows per table to profile your data:
| Column Type | Metrics Collected |
|---|---|
| Numeric | MIN, MAX, AVG, row count, distinct count, null count |
| Text | Top 100 distinct values (LISTAGG), row count, distinct count, null count |
| Date | MIN and MAX values, row count, distinct count, null count |
Updated 8 days ago
