ruby-odbc driver, translates Forest filters/projections/aggregations into parameterised SQL, and streams the results back as plain Ruby objects.
This data source is read-only. Every Forest column is emitted with is_read_only: true, so the schema emitter sets the collection-level isReadOnly: true and the UI hides create/edit/delete actions. Direct calls to create, update, or delete raise a ForestException with an explicit read-only message as a defence-in-depth guard.
Installation
To make everything work as expected, you need to:- install the gem
forest_admin_datasource_snowflake. - have the unixODBC system library and the Snowflake ODBC driver installed on the host running the agent. On Ubuntu/Debian:
apt-get install unixodbc-dev, then install Snowflake’s ODBC driver and reference it from yourodbcinst.ini.
Usage
Register the datasource from inside the back-end’s setup hook (ForestAdminRails::CreateAgent.setup! for Rails apps), the same place where you would register an Active Record or Mongoid source:
add_datasource (e.g. include: / exclude:) drop into the same setup! body in place of the add_datasource(datasource, {}) call.
Authentication
The data source doesn’t interpret authentication options,conn_str is parsed as key=value;... and the resulting attributes are handed straight to the Snowflake ODBC driver. Any parameter the driver accepts works, including the production-friendly key-pair / JWT flow:
PRIV_KEY_FILE path. EXTERNALBROWSER (SSO) and OAUTH flows work the same way, set the relevant AUTHENTICATOR= and supporting parameters per the driver docs.
Automatic schema discovery
By default, every user-schema, non-system table reachable by the configured Snowflake user is exposed as a Forest collection. System tables and theINFORMATION_SCHEMA views are filtered out automatically.
At boot, the data source issues a small fixed set of metadata queries, independent of how many tables you expose:
- one ODBC
tablescall to enumerate the readable tables and views. - one bulk
INFORMATION_SCHEMA.COLUMNSquery that returns every column for the schema in a single round-trip. Each Forest collection reads its slice from the pre-fetched result, so introspection cost no longer scales with table count. - one
SHOW PRIMARY KEYS IN SCHEMAquery to recover declared primary keys (composite keys preserved, ordered bykey_sequence). - one
SHOW IMPORTED KEYS IN SCHEMAquery to recover declared foreign keys (see Foreign-key auto-discovery).
- an operator-supplied
primary_keys:override, - any Snowflake-declared primary key (via
SHOW PRIMARY KEYS IN SCHEMA), - a column literally named
id(case-insensitive), - the first column as a last resort.
[forest_admin_datasource_snowflake] prefix and skipped. The result is cached so the broken query isn’t re-issued on every collection lookup.
Restricting the imported tables
Use the standard agent-levelinclude: / exclude: options when registering the datasource. The data source itself exposes every readable user-schema table; the back-end decides which ones to publish.
Targeting a specific schema
A datasource instance always represents a single Snowflake schema, Forest collection names are unqualified, so two tables with the same name in different schemas would collide. To expose tables from multiple schemas, instantiate one datasource per schema. The active schema is resolved as follows:- if
Schema=is set in the connection string, it wins. The datasource parses it (case-insensitive) at construction time and issuesUSE SCHEMA "<schema>"on every new connection so the session, the table-list filter, and all introspection queries stay aligned. - if
Schema=is omitted, the datasource snapshotsCURRENT_SCHEMA()once at boot (whatever default the Snowflake user/role exposes) and uses that as the active schema for the rest of its lifetime. - if
Schema=is omitted andCURRENT_SCHEMA()is null (the role has no default), the datasource raisesForestAdminDatasourceSnowflake::Errorat boot with a message asking you to setSchema=explicitly.
Overriding the primary key
For tables where the primary key cannot be auto-resolved, for example, a table without a Snowflake-declared PK, noid column, and where the first column isn’t really the key, pass an explicit primary_keys: mapping. The lookup is case-insensitive on the table name. Pass an array to declare a composite key.
ForestAdminDatasourceSnowflake::Error at boot, silent fallback would otherwise mask configuration typos.
Type mapping
Column types are resolved from the Snowflake-nativeDATA_TYPE returned by INFORMATION_SCHEMA.COLUMNS.
| Snowflake type | Forest type |
|---|---|
BOOLEAN | Boolean |
NUMBER, DECIMAL, NUMERIC, INT, INTEGER, BIGINT, SMALLINT, TINYINT, BYTEINT | Number |
FLOAT, FLOAT4, FLOAT8, DOUBLE, DOUBLE PRECISION, REAL | Number |
VARCHAR, CHAR, CHARACTER, STRING, TEXT | String |
DATE | Dateonly |
TIME | Time |
DATETIME, TIMESTAMP, TIMESTAMP_NTZ, TIMESTAMP_LTZ, TIMESTAMP_TZ | Date |
VARIANT, OBJECT, ARRAY | Json |
BINARY, VARBINARY | Binary |
GEOGRAPHY, GEOMETRY, VECTOR | String |
String.
VARIANT / OBJECT / ARRAY columns are JSON-parsed at projection time so the Forest UI receives structured data, not raw strings.
TIMESTAMP_LTZ and TIMESTAMP_TZ are normalised at the session level via ALTER SESSION SET TIMEZONE = 'UTC', so all three TIMESTAMP variants serialise consistently as UTC. This avoids subtle bugs where rows render with different offsets depending on the column variant.
Foreign-key auto-discovery
Snowflake foreign keys are not enforced by the engine, they exist purely as documentation. If you have defined them in your warehouse, the data source picks them up automatically at boot and exposes them as ForestManyToOne relations.
Discovery runs unconditionally: a SHOW IMPORTED KEYS IN SCHEMA query at boot adds a relation field on the source collection for each FK it returns. The relation name is {source_column}_{target_table} (downcased).
If the introspection query fails (typically because the connecting role lacks the privilege), the failure is logged and skipped, the rest of the data source remains usable.
Auto-discovery only handles relations defined inside Snowflake. Cross-data-source relations, for example a Snowflake
BILLING_USAGE.CUSTOMER_ID pointing at a Postgres customers.id, cannot be discovered (Snowflake has no concept of an FK to another database). Wire those manually in the back-end layer with add_many_to_one_relation / add_one_to_many_relation.Connection pool
The data source usesconnection_pool under the hood. By default the pool is sized at 5 with a 5-second checkout timeout. Tune via pool_size: and pool_timeout: if you expect concurrent traffic.
with_connection automatically retries the block once after a connection-lost ODBC error (communication failures, expired sessions, expired Snowflake auth tokens, etc.), cycling the pool between attempts so stale handles get closed before re-checkout. Persistent failures bubble up to the caller.
Statement timeout
To cap any single Forest-driven Snowflake query, passstatement_timeout: (in seconds). The data source issues ALTER SESSION SET STATEMENT_TIMEOUT_IN_SECONDS = N on each new connection.
Full reference
Source code
This connector is open source. Browse the code or contribute on GitHub:forest_admin_datasource_snowflake.