DDL
Data Definition Language (DDL) statements create, modify, and drop database objects.
CREATE TABLE
Section titled “CREATE TABLE”CREATE TABLE table_name ( column_name data_type [PRIMARY KEY] [NOT NULL] [DEFAULT expr], ... [PRIMARY KEY (col1, col2, ...)]);Basic Table
Section titled “Basic Table”CREATE TABLE products ( id BIGINT PRIMARY KEY, name TEXT NOT NULL, price FLOAT64, category TEXT DEFAULT 'uncategorized', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);Composite Primary Key
Section titled “Composite Primary Key”CREATE TABLE order_items ( order_id BIGINT NOT NULL, product_id BIGINT NOT NULL, quantity INT64 NOT NULL DEFAULT 1, unit_price FLOAT64 NOT NULL, PRIMARY KEY (order_id, product_id));IF NOT EXISTS
Section titled “IF NOT EXISTS”Skip creation if the table already exists (no error):
CREATE TABLE IF NOT EXISTS sessions ( id BIGINT PRIMARY KEY, user_id BIGINT NOT NULL, token TEXT NOT NULL, expires_at TIMESTAMP);Table with Vector Column
Section titled “Table with Vector Column”CREATE TABLE articles ( id BIGINT PRIMARY KEY, title TEXT NOT NULL, body TEXT, embedding VECTOR(768), metadata JSON);Table with Storage Options (pgbench Compatibility)
Section titled “Table with Storage Options (pgbench Compatibility)”The WITH (...) clause is accepted on CREATE TABLE for compatibility with pgbench and
other PostgreSQL tools. ParticleDB parses these options but does not enforce them —
they are silently accepted.
-- Accepted for pgbench compatibility; fillfactor is a no-opCREATE TABLE pgbench_accounts ( aid BIGINT PRIMARY KEY, bid BIGINT, abalance BIGINT, filler TEXT) WITH (fillfactor=100);Table with Enum Column
Section titled “Table with Enum Column”-- First, create the enum typeCREATE TYPE priority AS ENUM ('low', 'medium', 'high', 'critical');
-- Then use it in a tableCREATE TABLE tickets ( id BIGINT PRIMARY KEY, title TEXT NOT NULL, priority priority NOT NULL DEFAULT 'medium', status TEXT DEFAULT 'open');DROP TABLE
Section titled “DROP TABLE”Remove a table and all its data.
DROP TABLE products;
-- Don't error if the table doesn't existDROP TABLE IF EXISTS products;
-- Drop multiple tables at onceDROP TABLE temp_data, staging_data, old_logs;ALTER TABLE
Section titled “ALTER TABLE”Modify an existing table’s schema or properties.
ADD COLUMN
Section titled “ADD COLUMN”ALTER TABLE products ADD COLUMN description TEXT;
ALTER TABLE products ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP;DROP COLUMN
Section titled “DROP COLUMN”ALTER TABLE products DROP COLUMN description;RENAME COLUMN
Section titled “RENAME COLUMN”ALTER TABLE products RENAME COLUMN category TO product_category;RENAME TABLE
Section titled “RENAME TABLE”ALTER TABLE products RENAME TO catalog;ADD PRIMARY KEY
Section titled “ADD PRIMARY KEY”Add a primary key constraint to a table that does not have one:
ALTER TABLE legacy_data ADD PRIMARY KEY (id);
-- Composite primary keyALTER TABLE event_log ADD PRIMARY KEY (event_date, event_id);
-- Named constraint formALTER TABLE legacy_data ADD CONSTRAINT pk_legacy PRIMARY KEY (id);SET STORAGE TIER
Section titled “SET STORAGE TIER”Move a table’s data between storage tiers:
-- Move to cold storage (cheaper, slower)ALTER TABLE archived_logs SET STORAGE TIER cold;
-- Move back to hot storageALTER TABLE archived_logs SET STORAGE TIER hot;SET COMPRESSION
Section titled “SET COMPRESSION”Configure per-table compression codec for storage segments:
-- No compression (fastest reads)ALTER TABLE realtime_metrics SET COMPRESSION 'none';
-- LZ4 compression (fast, moderate ratio)ALTER TABLE logs SET COMPRESSION 'lz4';
-- Zstandard compression (best ratio, slower)ALTER TABLE archives SET COMPRESSION 'zstd';SET PINNED
Section titled “SET PINNED”Pin a table in cache to prevent LRU eviction:
-- Keep this table always in memoryALTER TABLE hot_config SET PINNED = true;
-- Allow evictionALTER TABLE hot_config SET PINNED = false;ENABLE / DISABLE Row-Level Security
Section titled “ENABLE / DISABLE Row-Level Security”ALTER TABLE orders ENABLE ROW LEVEL SECURITY;ALTER TABLE orders DISABLE ROW LEVEL SECURITY;TRUNCATE
Section titled “TRUNCATE”Remove all rows from a table without dropping the table itself. Faster than DELETE FROM table because it doesn’t scan individual rows.
TRUNCATE TABLE temp_results;ANALYZE
Section titled “ANALYZE”Compute or refresh table statistics used by the cost-based query optimizer. Run this after bulk data loads for optimal query plans.
ANALYZE orders;CREATE INDEX
Section titled “CREATE INDEX”Create an index on one or more columns for faster lookups.
B-Tree Index (Default)
Section titled “B-Tree Index (Default)”-- Single columnCREATE INDEX idx_orders_customer ON orders (customer_id);
-- Multi-columnCREATE INDEX idx_orders_status_date ON orders (status, created_at);
-- Unique indexCREATE UNIQUE INDEX idx_users_email ON users (email);HNSW Vector Index
Section titled “HNSW Vector Index”CREATE INDEX idx_articles_embedding ON articlesUSING hnsw (embedding)WITH ( m = 16, ef_construction = 200, metric = 'cosine');IVF Flat Vector Index
Section titled “IVF Flat Vector Index”CREATE INDEX idx_articles_embedding_ivf ON articlesUSING ivf_flat (embedding)WITH ( num_lists = 100, metric = 'l2');See Vector Search for index parameter details.
DROP INDEX
Section titled “DROP INDEX”DROP INDEX idx_orders_customer;
DROP INDEX IF EXISTS idx_orders_customer;CREATE VIEW
Section titled “CREATE VIEW”Define a named query that can be referenced like a table.
CREATE VIEW active_orders ASSELECT o.id, o.total, c.name AS customer_nameFROM orders oJOIN customers c ON o.customer_id = c.idWHERE o.status != 'cancelled';
-- Query the view like a tableSELECT * FROM active_orders WHERE total > 100;OR REPLACE
Section titled “OR REPLACE”Overwrite an existing view:
CREATE OR REPLACE VIEW active_orders ASSELECT o.id, o.total, o.status, c.name AS customer_nameFROM orders oJOIN customers c ON o.customer_id = c.idWHERE o.status NOT IN ('cancelled', 'refunded');DROP VIEW
Section titled “DROP VIEW”DROP VIEW active_orders;
DROP VIEW IF EXISTS active_orders;CREATE TYPE AS ENUM
Section titled “CREATE TYPE AS ENUM”Define a user-defined enumerated type. Values are validated on INSERT and UPDATE.
CREATE TYPE order_status AS ENUM ('pending', 'processing', 'shipped', 'delivered', 'cancelled');
CREATE TYPE severity AS ENUM ('info', 'warning', 'error', 'critical');Use in table definitions:
CREATE TABLE orders ( id BIGINT PRIMARY KEY, status order_status NOT NULL DEFAULT 'pending');
-- Only valid enum values are acceptedINSERT INTO orders VALUES (1, 'shipped'); -- OKINSERT INTO orders VALUES (2, 'unknown'); -- ERRORDROP TYPE
Section titled “DROP TYPE”DROP TYPE order_status;
DROP TYPE IF EXISTS severity;CREATE PROCEDURE
Section titled “CREATE PROCEDURE”Define a stored procedure with a body enclosed in $$ delimiters.
CREATE PROCEDURE archive_old_orders(cutoff_days INT64) AS $$ INSERT INTO orders_archive SELECT * FROM orders WHERE created_at < CURRENT_TIMESTAMP - (cutoff_days * 86400000000);
DELETE FROM orders WHERE created_at < CURRENT_TIMESTAMP - (cutoff_days * 86400000000);$$;Call a Procedure
Section titled “Call a Procedure”CALL archive_old_orders(90);Drop a Procedure
Section titled “Drop a Procedure”DROP PROCEDURE archive_old_orders;
DROP PROCEDURE IF EXISTS archive_old_orders;CREATE FUNCTION
Section titled “CREATE FUNCTION”Define a user-defined scalar function:
CREATE FUNCTION discount_price(original FLOAT64, discount_pct FLOAT64) RETURNS FLOAT64 AS $$ SELECT original * (1.0 - discount_pct / 100.0)$$;Use in queries:
SELECT name, price, discount_price(price, 15) AS sale_priceFROM products;Drop a Function
Section titled “Drop a Function”DROP FUNCTION discount_price;
DROP FUNCTION IF EXISTS discount_price;Access Control
Section titled “Access Control”CREATE ROLE
Section titled “CREATE ROLE”Create a role (a named set of privileges). Roles cannot log in by default.
CREATE ROLE analyst;
CREATE ROLE IF NOT EXISTS data_engineer;CREATE USER
Section titled “CREATE USER”Create a user — a role with login capability.
CREATE USER alice WITH PASSWORD 'secure_password_here';
-- SuperuserCREATE USER admin WITH PASSWORD 'admin_pass' SUPERUSER;
CREATE USER IF NOT EXISTS bob WITH PASSWORD 'bobs_password';DROP ROLE / DROP USER
Section titled “DROP ROLE / DROP USER”DROP ROLE analyst;DROP ROLE IF EXISTS data_engineer;
DROP USER alice;DROP USER IF EXISTS bob;ALTER ROLE / ALTER USER
Section titled “ALTER ROLE / ALTER USER”-- Change passwordALTER ROLE alice SET PASSWORD 'new_password';
-- Rename a roleALTER ROLE analyst RENAME TO data_analyst;
-- Grant/revoke superuserALTER ROLE alice SET SUPERUSER true;ALTER ROLE alice SET SUPERUSER false;
-- Enable/disable loginALTER ROLE data_analyst SET LOGIN true;Grant privileges on a table to a role:
-- Grant specific privilegesGRANT SELECT ON orders TO analyst;GRANT SELECT, INSERT ON products TO data_engineer;GRANT SELECT, INSERT, UPDATE, DELETE ON orders TO admin;GRANT ALL ON orders TO admin;
-- Grant role membership (role inheritance)GRANT analyst TO alice;GRANT data_engineer TO bob;Available privileges: SELECT, INSERT, UPDATE, DELETE, ALL.
REVOKE
Section titled “REVOKE”Remove privileges:
REVOKE INSERT ON orders FROM analyst;REVOKE ALL ON products FROM data_engineer;
-- Revoke role membershipREVOKE analyst FROM alice;Row-Level Security (RLS)
Section titled “Row-Level Security (RLS)”Row-level security restricts which rows a user can see or modify based on policies.
Enable RLS
Section titled “Enable RLS”ALTER TABLE orders ENABLE ROW LEVEL SECURITY;CREATE POLICY
Section titled “CREATE POLICY”Define a policy that filters rows based on the current user’s context:
-- Users can only see their own ordersCREATE POLICY own_orders ON ordersFOR SELECTTO ALLUSING (user_id = current_user_id());
-- Admins can see all ordersCREATE POLICY admin_all_orders ON ordersFOR ALLTO adminUSING (true);
-- Policy for specific operationsCREATE POLICY insert_own ON ordersFOR INSERTUSING (user_id = current_user_id());DROP POLICY
Section titled “DROP POLICY”DROP POLICY own_orders ON orders;Disable RLS
Section titled “Disable RLS”ALTER TABLE orders DISABLE ROW LEVEL SECURITY;Attribute-Based Access Control (ABAC)
Section titled “Attribute-Based Access Control (ABAC)”Fine-grained access control based on user, resource, and environment attributes.
Set User Attributes
Section titled “Set User Attributes”ALTER ROLE alice SET ATTRIBUTE department = 'engineering';ALTER ROLE alice SET ATTRIBUTE clearance = 'top_secret';CREATE ABAC POLICY
Section titled “CREATE ABAC POLICY”-- Only users in the same department can view recordsCREATE ABAC POLICY dept_isolation ON documentsFOR SELECTWHEN (user.department = resource.department);
-- Clearance-based accessCREATE ABAC POLICY clearance_check ON classified_docsFOR SELECTWHEN (user.clearance = 'top_secret');DROP ABAC POLICY
Section titled “DROP ABAC POLICY”DROP ABAC POLICY dept_isolation ON documents;Notifications
Section titled “Notifications”LISTEN / UNLISTEN / NOTIFY
Section titled “LISTEN / UNLISTEN / NOTIFY”Asynchronous notification channels (PostgreSQL-compatible):
-- Subscribe to a channelLISTEN order_updates;
-- Send a notification (from another connection)NOTIFY order_updates, 'order 42 has shipped';
-- UnsubscribeUNLISTEN order_updates;Alerts
Section titled “Alerts”Define monitoring alert rules that fire when metrics cross thresholds:
-- Create an alert ruleCREATE ALERT high_connectionsWHEN connections_active > 100FOR 60SEVERITY warningMESSAGE 'Connection count exceeds 100 for over 60 seconds';
-- Modify an alertALTER ALERT RULE 'high_connections' SET threshold = 150;ALTER ALERT RULE 'high_connections' SET severity = 'critical';ALTER ALERT RULE 'high_connections' SET enabled = 'false';
-- Remove an alertDROP ALERT high_connections;VACUUM
Section titled “VACUUM”VACUUM and VACUUM ANALYZE are accepted as no-op statements for compatibility
with PostgreSQL tools (pgbench, pg_dump, ORMs). ParticleDB’s LSM-tree storage engine
handles space reclamation through background compaction, so explicit vacuuming is not
needed.
-- Accepted, does nothingVACUUM;VACUUM ANALYZE;VACUUM orders;VACUUM ANALYZE orders;COPY FROM STDIN
Section titled “COPY FROM STDIN”Bulk-load data from a client using the PostgreSQL COPY protocol. Data must be in
tab-separated format with one row per line.
-- Load all columnsCOPY products FROM STDIN;
-- Load specific columnsCOPY products (id, name, price) FROM STDIN;When executed from psql, the client enters copy mode and expects tab-separated rows
terminated by a \. line:
main=# COPY products (id, name, price) FROM STDIN;1 Widget 9.992 Gadget 19.99\.COPY 2See PostgreSQL Wire Protocol — COPY FROM STDIN for protocol-level details and performance notes.
EXPLAIN
Section titled “EXPLAIN”View the query execution plan without running the query:
EXPLAIN SELECT * FROM orders WHERE customer_id = 42;Execute the query and show actual timing statistics:
EXPLAIN ANALYZESELECT category, COUNT(*), AVG(price)FROM productsGROUP BY category;