INFORMATION_SCHEMA
Gnok ships the SQL-standard INFORMATION_SCHEMA views for catalog introspection — the same surface BI tools, JDBC drivers, and schema-diff scripts expect. Every view is backed by the live catalog (Iceberg metadata for tables / views, in-memory registries for policies) so results reflect reality at query time.
Three rules
1. Qualifying the catalog (optional but recommended)
INFORMATION_SCHEMA is per-catalog. There are three ways to scope it:
-- 1. Three-part reference (preferred — explicit, no session state)
SELECT * FROM my_cat.information_schema.tables LIMIT 5;
-- 2. Set a session catalog first
USE CATALOG my_cat;
SELECT * FROM information_schema.tables LIMIT 5;
-- 3. Filter by table_catalog (engine infers + lazy-loads it)
SELECT table_name
FROM information_schema.tables
WHERE table_catalog = 'my_cat' AND table_schema = 'sales';
Form 3 is convenient when no session catalog is set: the engine
recognises an information_schema query and extracts the catalog
name from the WHERE table_catalog = '<X>' (or catalog_name = '<X>'
on schemata) filter, lazy-loading it if needed. If the WHERE has no
catalog filter and no session default is registered, the engine falls
back to any registered catalog so the bind succeeds (the
materialiser walks the catalog registry rather than reading from one
specific catalog's metadata).
Catalog-qualifier scoping. When a query uses the three-part form
(<catalog>.information_schema.<view>), the materialiser only
reads from that catalog, not from every catalog you can access.
Use the qualifier to keep introspection on one catalog.
2. Filter with WHERE
All five views are ordinary tables you query with standard SQL. There is no parameterised-function form today (the TABLE(INFORMATION_SCHEMA.POLICY_REFERENCES(POLICY_NAME => …)) table-function spelling isn't supported yet); use WHERE instead:
SELECT policy_name
FROM my_cat.information_schema.policy_references
WHERE ref_entity_name = 'orders'
AND policy_kind = 'ROW_ACCESS_POLICY';
When the table-function grammar ships, existing WHERE-filtered queries keep working unchanged — it'll be additive sugar.
3. JOINs, aggregates, and subqueries work
IS views materialise into inline data at plan time, and the engine routes scan-free plans through a single-node execution path — so WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, aggregates, scalar subqueries, and JOIN across IS views all work without special-casing.
View catalog
| View | Rows | Purpose |
|---|---|---|
tables | one per table | Base tables in the catalog |
columns | one per column | Per-column type metadata |
schemata | one per schema | Namespace listing |
views | one per view | View listing (body currently NULL — see gaps) |
policy_references | one per binding | Row-access + masking policy-to-object bindings |
information_schema.tables
Lists every base table, view, and materialized view in the catalog.
| Column | Type | Description |
|---|---|---|
table_catalog | VARCHAR | Catalog name |
table_schema | VARCHAR | Schema (namespace) |
table_name | VARCHAR | Table name |
table_type | VARCHAR | BASE TABLE, VIEW, or MATERIALIZED VIEW |
table_owner | VARCHAR | Owner principal (auto-stamped from JWT user on CREATE TABLE; nullable for legacy tables) |
row_count | BIGINT | Approximate row count from Iceberg manifests (nullable) |
bytes | BIGINT | Approximate byte size (nullable) |
created | VARCHAR | ISO-8601 UTC timestamp — auto-stamped on CREATE TABLE (nullable for legacy tables) |
comment | VARCHAR | Table comment (nullable) |
rls_enabled | BOOLEAN | TRUE iff at least one row-access policy is bound to the table |
Row types
BASE TABLE— physical Iceberg table (CREATE TABLE …).VIEW— logical view (CREATE VIEW …); body retrievable viaSHOW CREATE VIEW.MATERIALIZED VIEW— managed materialised view (CREATE MATERIALIZED VIEW …). The MV's underlying storage table is not double-counted as a separateBASE TABLErow.
Auto-stamped fields
Every fresh CREATE TABLE populates two fields automatically:
table_owner— taken from the authenticated JWT'spreferred_username(usernameclaim). Override at create time viaTBLPROPERTIES ('owner' = 'someone-else').created—chrono::Utc::now().to_rfc3339()stamped under bothcreated_at(Gnok Catalog convention) andcreated-at(Iceberg property name) so reads via either key succeed. Override viaTBLPROPERTIES ('created-at' = '2024-01-01T00:00:00Z').
Examples
-- Every table + view in a specific schema, with type
SELECT table_name, table_type, row_count
FROM my_cat.information_schema.tables
WHERE table_schema = 'sales'
ORDER BY table_type, table_name;
-- Top 10 schemas by base-table count
SELECT table_schema, COUNT(*) AS n
FROM my_cat.information_schema.tables
WHERE table_type = 'BASE TABLE'
GROUP BY table_schema
ORDER BY n DESC
LIMIT 10;
-- Tables protected by row-level security (editor sidebar pattern)
SELECT table_schema, table_name
FROM my_cat.information_schema.tables
WHERE rls_enabled
ORDER BY table_schema, table_name;
information_schema.columns
Per-column metadata for every table in the catalog.
| Column | Type | Description |
|---|---|---|
table_catalog | VARCHAR | Catalog name |
table_schema | VARCHAR | Schema |
table_name | VARCHAR | Table name |
column_name | VARCHAR | Column name |
ordinal_position | BIGINT | 1-based column index in the table |
data_type | VARCHAR | SQL type (e.g., INT, VARCHAR, DECIMAL(10, 2), TIMESTAMP, ARRAY<...>) — same formatter DESCRIBE uses |
is_nullable | VARCHAR | YES / NO |
column_default | VARCHAR | Default expression — populated from CREATE TABLE col TYPE DEFAULT expr (nullable) |
numeric_precision | BIGINT | For DECIMAL / NUMERIC types (nullable) |
numeric_scale | BIGINT | For DECIMAL / NUMERIC types (nullable) |
comment | VARCHAR | Column comment from CREATE TABLE col TYPE COMMENT 'x' or ALTER TABLE … ALTER COLUMN col SET COMMENT 'x' (nullable) |
is_primary_key | BOOLEAN | TRUE iff the column participates in the table's PRIMARY KEY (column-level or composite) |
data_type uses the same SQL-style formatter as DESCRIBE so the two
surfaces agree (INT not Int32, VARCHAR not Utf8, DECIMAL(10, 2)
not Decimal128(10, 2)). Column names follow the SQL standard
(column_name, is_nullable, column_default) — DESCRIBE keeps its
shorter friendly aliases (col_name, nullable, default).
-- Column list for one table
SELECT column_name, data_type, is_nullable, comment, is_primary_key
FROM my_cat.information_schema.columns
WHERE table_schema = 'sales' AND table_name = 'orders'
ORDER BY ordinal_position;
-- Every PRIMARY KEY column in the catalog
SELECT table_schema, table_name, column_name
FROM my_cat.information_schema.columns
WHERE is_primary_key
ORDER BY table_schema, table_name, ordinal_position;
-- Every DECIMAL column with its precision
SELECT table_schema, table_name, column_name, numeric_precision, numeric_scale
FROM my_cat.information_schema.columns
WHERE data_type LIKE 'DECIMAL%';
information_schema.schemata
Namespace listing — one row per schema.
| Column | Type | Description |
|---|---|---|
catalog_name | VARCHAR | Catalog name |
schema_name | VARCHAR | Schema (namespace) |
schema_owner | VARCHAR | Owner principal (nullable) |
default_character_set_name | VARCHAR | SQL-spec placeholder; always UTF8 (nullable) |
SELECT schema_name FROM my_cat.information_schema.schemata;
information_schema.views
View listing — one row per view.
| Column | Type | Description |
|---|---|---|
table_catalog | VARCHAR | Catalog name |
table_schema | VARCHAR | Schema |
table_name | VARCHAR | View name |
view_definition | VARCHAR | View body (currently NULL — follow-up) |
is_updatable | VARCHAR | Always NO — Gnok views are read-only |
SELECT table_schema, table_name
FROM my_cat.information_schema.views
ORDER BY table_schema, table_name;
view_definition is always NULL today because the catalog adapter's TableRef doesn't carry the view body. Use SHOW CREATE VIEW <view> to retrieve the SQL text; a future update will populate the column.
information_schema.policy_references
The reverse-lookup surface for row-access and masking policies. Each row binds one policy to one object (or column).
| Column | Type | Description |
|---|---|---|
policy_db | VARCHAR | Policy's catalog (mirrors target today; NULL for masking rows) |
policy_schema | VARCHAR | Policy's schema (NULL for masking rows) |
policy_name | VARCHAR | Policy name |
policy_kind | VARCHAR | ROW_ACCESS_POLICY or MASKING_POLICY |
ref_database_name | VARCHAR | Target catalog |
ref_schema_name | VARCHAR | Target schema |
ref_entity_name | VARCHAR | Target table |
ref_entity_domain | VARCHAR | TABLE today |
ref_column_name | VARCHAR | Masked column (masking rows only, otherwise NULL) |
ref_arg_column_names | VARCHAR | Columns the policy body references (currently NULL — follow-up) |
Query patterns
-- Studio catalog-tree badge: does this table have a row-access policy?
SELECT COUNT(*) > 0 AS has_rls
FROM my_cat.information_schema.policy_references
WHERE ref_database_name = 'my_cat'
AND ref_schema_name = 'sales'
AND ref_entity_name = 'orders'
AND policy_kind = 'ROW_ACCESS_POLICY';
-- Every column-level mask in the catalog
SELECT policy_name, ref_schema_name, ref_entity_name, ref_column_name
FROM my_cat.information_schema.policy_references
WHERE policy_kind = 'MASKING_POLICY'
ORDER BY ref_schema_name, ref_entity_name, ref_column_name;
-- Where is policy 'pci_mask' applied?
SELECT ref_schema_name, ref_entity_name, ref_column_name
FROM my_cat.information_schema.policy_references
WHERE policy_name = 'pci_mask';
-- Tables with an active row-access policy plus their column count
SELECT t.table_schema, t.table_name, COUNT(DISTINCT c.column_name) AS cols
FROM my_cat.information_schema.tables t
JOIN my_cat.information_schema.columns c
ON c.table_catalog = t.table_catalog
AND c.table_schema = t.table_schema
AND c.table_name = t.table_name
JOIN my_cat.information_schema.policy_references p
ON p.ref_database_name = t.table_catalog
AND p.ref_schema_name = t.table_schema
AND p.ref_entity_name = t.table_name
AND p.policy_kind = 'ROW_ACCESS_POLICY'
GROUP BY t.table_schema, t.table_name;
Prefer POLICY_REFERENCES over SHOW ROW POLICIES and SHOW MASKING POLICIES whenever you need joins, filtering, or tool interop — the SHOW variants stay available for interactive inspection of policy bodies and enabled flags.
Client access
All three client protocols work identically — IS is just SQL.
# HTTP API
curl -s -X POST https://query.example.com/api/query \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"sql": "SELECT table_name FROM my_cat.information_schema.tables LIMIT 5"}'
# PostgreSQL wire protocol
psql "host=pg.example.com port=5433 user=my_user dbname=my_catalog sslmode=verify-full" \
-c "SELECT policy_name FROM my_cat.information_schema.policy_references"
# Flight SQL (any ADBC / JDBC driver) — same SQL, same result
JDBC / ODBC clients also hit the PostgreSQL-compatible pg_catalog views; see the Postgres wire reference for the mapping.
Known gaps
views.view_definitionis always NULL. UseSHOW CREATE VIEW <v>to retrieve the body. The catalog'sTableRefdoesn't carry the text yet; this is a follow-up.policy_references.ref_arg_column_namesis always NULL. Parsing row-access expressions to enumerate referenced columns is future work.- No tag-based policy inheritance tracked yet. When tag-scoped policies ship,
policy_referenceswill gaintag_*lineage columns — an additive change. - Cold-start behaviour on
policy_references. Row-access and masking registries hydrate lazily from persistent storage on the first authenticated request per engine process. A brand-new coordinator connection that queriespolicy_referencesbefore any policy DDL has been evaluated may return empty. It self-heals as soon as any DDL touches a policy-bearing table. Treat empty as "unknown" rather than "no policies" on the first connection. - No table-function syntax yet. A
TABLE(INFORMATION_SCHEMA.POLICY_REFERENCES(POLICY_NAME => …))table-function form is planned as additive sugar;WHEREfiltering works today.
See also
- Row-Level Security — CREATE POLICY DDL, evaluation semantics, role targeting.
- Data Masking — column-level mask attachment.
- DDL Reference → Users, service accounts, and PATs.
- User Lifecycle — everything the engine's audit surface fans out from.
- Postgres Wire Protocol — pg_catalog equivalents for BI-tool interop.