Document Store

Nucleus includes a built-in document store for schemaless JSON data. Store, query, and index documents alongside your relational tables — no separate database needed.

Document Operations

Insert & Retrieve

-- Insert a document, get back its ID
SELECT DOC_INSERT('{"name": "Alice", "age": 30, "tags": ["admin", "dev"]}');
-- → 1

-- Get document by ID
SELECT DOC_GET(1);
-- → {"name":"Alice","age":30,"tags":["admin","dev"]}

-- Count all documents
SELECT DOC_COUNT();

Query by Containment

Find documents matching a JSON pattern using the @> containment operator:

-- Find all documents where age = 30
SELECT DOC_QUERY('{"age": 30}');
-- → "1"  (comma-separated IDs)

-- Nested containment
SELECT DOC_QUERY('{"tags": ["admin"]}');

Extract Nested Values

-- Get a value at a path
SELECT DOC_PATH(1, 'name');
-- → "Alice"

-- Nested paths
SELECT DOC_PATH(1, 'address', 'city');

JSONB Columns

Store JSON directly in relational tables using the JSONB type:

CREATE TABLE products (
  id SERIAL PRIMARY KEY,
  name TEXT,
  metadata JSONB
);

INSERT INTO products (name, metadata)
VALUES ('Widget', '{"color": "blue", "weight": 2.5, "dimensions": {"w": 10, "h": 5}}');

JSONB Operators

PostgreSQL-compatible arrow operators for JSON traversal:

-- Get field as JSONB
SELECT metadata -> 'color' FROM products;
-- → "blue"

-- Get field as text
SELECT metadata ->> 'color' FROM products;
-- → blue

-- Nested path as JSONB
SELECT metadata #> '{dimensions,w}' FROM products;
-- → 10

-- Nested path as text
SELECT metadata #>> '{dimensions,w}' FROM products;
-- → 10

JSONB Functions

Construction

-- Build an object from key-value pairs
SELECT JSON_BUILD_OBJECT('name', 'Alice', 'age', 30);
-- → {"name":"Alice","age":30}

-- Build an array
SELECT JSON_BUILD_ARRAY(1, 'two', true, null);
-- → [1,"two",true,null]

-- Convert any value to JSON
SELECT TO_JSONB(42);

Inspection

-- Get the JSON type
SELECT JSON_TYPEOF('{"a":1}');  -- → "object"
SELECT JSON_TYPEOF('[1,2]');    -- → "array"
SELECT JSON_TYPEOF('"hello"');  -- → "string"

-- Array length
SELECT JSON_ARRAY_LENGTH('[1,2,3]');  -- → 3

-- Object keys
SELECT JSON_OBJECT_KEYS('{"a":1,"b":2}');  -- → ["a","b"]

Transformation

-- Set a value at a path
SELECT JSON_SET('{"a":1}', 'b', '2');
-- → {"a":1,"b":2}

-- Pretty print
SELECT JSON_PRETTY('{"a":1,"b":[2,3]}');

-- Strip null values
SELECT JSON_STRIP_NULLS('{"a":1,"b":null,"c":3}');
-- → {"a":1,"c":3}

Path Extraction

-- Extract nested value as JSONB
SELECT JSON_EXTRACT_PATH('{"a":{"b":{"c":42}}}', 'a', 'b', 'c');
-- → 42

-- Extract as text
SELECT JSON_EXTRACT_PATH_TEXT('{"a":{"b":"hello"}}', 'a', 'b');
-- → hello

GIN Index

GIN (Generalized Inverted Index) accelerates containment queries on JSONB columns:

CREATE INDEX idx_products_metadata ON products USING gin (metadata);

-- This query uses the GIN index
SELECT * FROM products WHERE metadata @> '{"color": "blue"}';

The GIN index extracts all path/value pairs from each document, enabling fast lookups without scanning every row.

Use Cases

  • User profiles — Flexible schema for varying user attributes
  • Product catalogs — Different products have different fields
  • Event logging — Store arbitrary event payloads
  • Configuration — Store app settings as JSON
  • API responses — Cache and query external API data