Skip to content

DDL

Data Definition Language (DDL) statements create, modify, and drop database objects.

CREATE TABLE table_name (
column_name data_type [PRIMARY KEY] [NOT NULL] [DEFAULT expr],
...
[PRIMARY KEY (col1, col2, ...)]
);
CREATE TABLE products (
id BIGINT PRIMARY KEY,
name TEXT NOT NULL,
price FLOAT64,
category TEXT DEFAULT 'uncategorized',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
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)
);

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
);
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-op
CREATE TABLE pgbench_accounts (
aid BIGINT PRIMARY KEY,
bid BIGINT,
abalance BIGINT,
filler TEXT
) WITH (fillfactor=100);
-- First, create the enum type
CREATE TYPE priority AS ENUM ('low', 'medium', 'high', 'critical');
-- Then use it in a table
CREATE TABLE tickets (
id BIGINT PRIMARY KEY,
title TEXT NOT NULL,
priority priority NOT NULL DEFAULT 'medium',
status TEXT DEFAULT 'open'
);

Remove a table and all its data.

DROP TABLE products;
-- Don't error if the table doesn't exist
DROP TABLE IF EXISTS products;
-- Drop multiple tables at once
DROP TABLE temp_data, staging_data, old_logs;

Modify an existing table’s schema or properties.

ALTER TABLE products ADD COLUMN description TEXT;
ALTER TABLE products ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE products DROP COLUMN description;
ALTER TABLE products RENAME COLUMN category TO product_category;
ALTER TABLE products RENAME TO catalog;

Add a primary key constraint to a table that does not have one:

ALTER TABLE legacy_data ADD PRIMARY KEY (id);
-- Composite primary key
ALTER TABLE event_log ADD PRIMARY KEY (event_date, event_id);
-- Named constraint form
ALTER TABLE legacy_data ADD CONSTRAINT pk_legacy PRIMARY KEY (id);

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 storage
ALTER TABLE archived_logs SET STORAGE TIER hot;

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';

Pin a table in cache to prevent LRU eviction:

-- Keep this table always in memory
ALTER TABLE hot_config SET PINNED = true;
-- Allow eviction
ALTER TABLE hot_config SET PINNED = false;
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE orders DISABLE ROW LEVEL SECURITY;

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;

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 an index on one or more columns for faster lookups.

-- Single column
CREATE INDEX idx_orders_customer ON orders (customer_id);
-- Multi-column
CREATE INDEX idx_orders_status_date ON orders (status, created_at);
-- Unique index
CREATE UNIQUE INDEX idx_users_email ON users (email);
CREATE INDEX idx_articles_embedding ON articles
USING hnsw (embedding)
WITH (
m = 16,
ef_construction = 200,
metric = 'cosine'
);
CREATE INDEX idx_articles_embedding_ivf ON articles
USING ivf_flat (embedding)
WITH (
num_lists = 100,
metric = 'l2'
);

See Vector Search for index parameter details.

DROP INDEX idx_orders_customer;
DROP INDEX IF EXISTS idx_orders_customer;

Define a named query that can be referenced like a table.

CREATE VIEW active_orders AS
SELECT o.id, o.total, c.name AS customer_name
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.status != 'cancelled';
-- Query the view like a table
SELECT * FROM active_orders WHERE total > 100;

Overwrite an existing view:

CREATE OR REPLACE VIEW active_orders AS
SELECT o.id, o.total, o.status, c.name AS customer_name
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.status NOT IN ('cancelled', 'refunded');
DROP VIEW active_orders;
DROP VIEW IF EXISTS active_orders;

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 accepted
INSERT INTO orders VALUES (1, 'shipped'); -- OK
INSERT INTO orders VALUES (2, 'unknown'); -- ERROR
DROP TYPE order_status;
DROP TYPE IF EXISTS severity;

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 archive_old_orders(90);
DROP PROCEDURE archive_old_orders;
DROP PROCEDURE IF EXISTS archive_old_orders;

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_price
FROM products;
DROP FUNCTION discount_price;
DROP FUNCTION IF EXISTS discount_price;

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 a user — a role with login capability.

CREATE USER alice WITH PASSWORD 'secure_password_here';
-- Superuser
CREATE USER admin WITH PASSWORD 'admin_pass' SUPERUSER;
CREATE USER IF NOT EXISTS bob WITH PASSWORD 'bobs_password';
DROP ROLE analyst;
DROP ROLE IF EXISTS data_engineer;
DROP USER alice;
DROP USER IF EXISTS bob;
-- Change password
ALTER ROLE alice SET PASSWORD 'new_password';
-- Rename a role
ALTER ROLE analyst RENAME TO data_analyst;
-- Grant/revoke superuser
ALTER ROLE alice SET SUPERUSER true;
ALTER ROLE alice SET SUPERUSER false;
-- Enable/disable login
ALTER ROLE data_analyst SET LOGIN true;

Grant privileges on a table to a role:

-- Grant specific privileges
GRANT 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.

Remove privileges:

REVOKE INSERT ON orders FROM analyst;
REVOKE ALL ON products FROM data_engineer;
-- Revoke role membership
REVOKE analyst FROM alice;

Row-level security restricts which rows a user can see or modify based on policies.

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

Define a policy that filters rows based on the current user’s context:

-- Users can only see their own orders
CREATE POLICY own_orders ON orders
FOR SELECT
TO ALL
USING (user_id = current_user_id());
-- Admins can see all orders
CREATE POLICY admin_all_orders ON orders
FOR ALL
TO admin
USING (true);
-- Policy for specific operations
CREATE POLICY insert_own ON orders
FOR INSERT
USING (user_id = current_user_id());
DROP POLICY own_orders ON orders;
ALTER TABLE orders DISABLE ROW LEVEL SECURITY;

Fine-grained access control based on user, resource, and environment attributes.

ALTER ROLE alice SET ATTRIBUTE department = 'engineering';
ALTER ROLE alice SET ATTRIBUTE clearance = 'top_secret';
-- Only users in the same department can view records
CREATE ABAC POLICY dept_isolation ON documents
FOR SELECT
WHEN (user.department = resource.department);
-- Clearance-based access
CREATE ABAC POLICY clearance_check ON classified_docs
FOR SELECT
WHEN (user.clearance = 'top_secret');
DROP ABAC POLICY dept_isolation ON documents;

Asynchronous notification channels (PostgreSQL-compatible):

-- Subscribe to a channel
LISTEN order_updates;
-- Send a notification (from another connection)
NOTIFY order_updates, 'order 42 has shipped';
-- Unsubscribe
UNLISTEN order_updates;

Define monitoring alert rules that fire when metrics cross thresholds:

-- Create an alert rule
CREATE ALERT high_connections
WHEN connections_active > 100
FOR 60
SEVERITY warning
MESSAGE 'Connection count exceeds 100 for over 60 seconds';
-- Modify an alert
ALTER 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 alert
DROP ALERT high_connections;

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 nothing
VACUUM;
VACUUM ANALYZE;
VACUUM orders;
VACUUM ANALYZE orders;

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 columns
COPY products FROM STDIN;
-- Load specific columns
COPY 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.99
2 Gadget 19.99
\.
COPY 2

See PostgreSQL Wire Protocol — COPY FROM STDIN for protocol-level details and performance notes.


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 ANALYZE
SELECT category, COUNT(*), AVG(price)
FROM products
GROUP BY category;