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:

OptionBest ForWhat You'll Do
Automatic Pull (Recommended)Ongoing use and continuous monitoringSet up a service user that runs automatically
Manual PullQuick proof of concept or one-time setupExport data manually and upload it

What Is Collected

CategoryWhatWhy
MetadataTable/column definitions and comments from INFORMATION_SCHEMA.COLUMNS / TABLESTo build the data catalog
Query HistoryQuery text, user, role, warehouse, timestamps, and status from SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORYTo learn usage patterns and seed benchmarks
Table UsageRow insert/update/delete counts per table from TABLE_DML_HISTORYTo surface active tables and detect stale data
Primary/Foreign KeysOutput of SHOW PRIMARY KEYS / SHOW IMPORTED KEYSTo build lineage relationships and understand join paths
UsersRecords from SNOWFLAKE.ACCOUNT_USAGE.USERSTo attribute queries and usage to specific people
Data Profiling/SamplesSampled 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:

VariableDefaultDescription
solid_usernameSOLID_DATAThe name for the new Snowflake user
solid_warehouse_sizeXSMALLThe compute size for the new virtual warehouse
solid_warehouse_nameSOLID_DATA_WHThe name for the new virtual warehouse
solid_role_nameSOLID_DATA_ROLEThe 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

  1. Replace Placeholder — Before running, replace <your_database> with the actual name of the Snowflake database you want Solid to monitor.
  2. Execute in Snowflake — Run the script in your Snowflake worksheet or client.
  3. 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

FieldWhat to Enter
UserSOLID_DATA (or your service user name)
Password / Private KeyYour authentication credential
AccountYour Snowflake account identifier
WarehouseSOLID_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

  1. Run the queries below in your Snowflake Query Editor
  2. Export each result as a CSV file
  3. Compress the files (ZIP or GZIP)
  4. 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/GrantPurpose
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 WAREHOUSELets 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 tablesEnables foreign key/metadata lookups and lineage visibility without granting data access
GRANT CREATE SEMANTIC VIEW on ALL SCHEMAS / FUTURE SCHEMAS IN DATABASERequired 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 tablesEnsures the same read-only access automatically extends to new objects

Troubleshooting

Connection Issues

  1. Verify you replaced <your_database> with your actual database name in the script
  2. Confirm you have the ACCOUNTADMIN role when running the setup script
  3. Check the warehouse is running in Snowflake
  4. Verify all grants completed successfully by reviewing the script output

Permission Issues

  1. Check the role permissions by running SHOW GRANTS TO ROLE SOLID_DATA_ROLE;
  2. Verify the database name matches what you specified in the setup
  3. 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 TypeMetrics Collected
NumericMIN, MAX, AVG, row count, distinct count, null count
TextTop 100 distinct values (LISTAGG), row count, distinct count, null count
DateMIN and MAX values, row count, distinct count, null count

Did this page help you?