diff --git a/content/compatibility/sql_features.md b/content/compatibility/sql_features.md
index 6e6f2eca..d3db35c2 100644
--- a/content/compatibility/sql_features.md
+++ b/content/compatibility/sql_features.md
@@ -17,50 +17,75 @@ the [system table compatibility](../system_table) page.
### Table Creation & Deletion
-| **Feature** | **Support State** | **Details** |
-|-----------------------|-------------------|------------------------------------------------------------------------|
-| CREATE TABLE | Yes | [Documentation](/docs/references/objects/tables/) |
-| DROP TABLE | Yes | |
-| Default Values | Yes | |
-| GENERATED | Yes | only AS IDENTITY |
-| Check Constraints | No | |
-| Not-Null Constraints | Yes | [Documentation](/docs/references/objects/tables/) |
-| Unique Constraints | Yes | [Documentation](/docs/references/objects/tables/) |
-| Primary Keys | Yes | [Documentation](/docs/references/objects/tables/) |
-| Foreign Keys | Yes | Without ON DELETE
[Documentation](/docs/references/objects/tables/) |
-| Named Constraints | No | |
-| Exclusion Constraints | No | |
-| System Columns | Yes | Only meaningful for tableoid and ctid |
+| **Feature** | **Support State** | **Details** |
+|--------------------------------|-------------------|----------------------------------------------------------------------------------------------------------------|
+| CREATE TABLE | Yes | [Documentation](/docs/references/objects/tables/) |
+| DROP TABLE | Yes | |
+| Default Values | Yes | |
+| GENERATED AS IDENTITY | Yes | Both `ALWAYS` and `BY DEFAULT` variants |
+| GENERATED ALWAYS AS (computed) | No | |
+| Check Constraints | Yes | Only at CREATE TABLE time. [Documentation](/docs/references/objects/tables/#constraints) |
+| Not-Null Constraints | Yes | [Documentation](/docs/references/objects/tables/) |
+| Unique Constraints | Yes | [Documentation](/docs/references/objects/tables/) |
+| Primary Keys | Yes | [Documentation](/docs/references/objects/tables/) |
+| Foreign Keys | Yes | [Documentation](/docs/references/objects/tables/) |
+| FK ON DELETE CASCADE | Yes | |
+| FK ON DELETE RESTRICT | Yes | |
+| FK ON DELETE NO ACTION | Yes | |
+| FK ON UPDATE CASCADE | Yes | |
+| FK ON DELETE SET NULL | No | |
+| FK ON DELETE SET DEFAULT | No | |
+| Named Constraints | Yes | For PRIMARY KEY, UNIQUE, FOREIGN KEY, and CHECK. [Documentation](/docs/references/objects/tables/#constraints) |
+| Exclusion Constraints | No | |
+| System Columns | Yes | Only meaningful for tableoid and ctid |
+| UNLOGGED TABLE | No | |
+| CREATE TABLE LIKE | Partial | Copies column names and types. `INCLUDING` options not supported |
+| DROP TABLE CASCADE | No | |
### Table Modification (ALTER TABLE)
-| **Feature** | **Support State** | **Details** |
-|-----------------|-------------------|-------------|
-| ADD COLUMN | Yes | |
-| DROP COLUMN | Yes | |
-| ADD CHECK | No | |
-| ADD CONSTRAINT | Yes | |
-| ADD FOREIGN KEY | Yes | |
-| DROP CONSTRAINT | No | |
-| ALTER COLUMN | No | |
-| SET DEFAULT | No | |
-| DROP DEFAULT | No | |
-| COLUMN TYPE | No | |
-| RENAME COLUMN | Yes | |
-| RENAME TO | Yes | |
+| **Feature** | **Support State** | **Details** |
+|-----------------------------------|-------------------|---------------------------------------------------|
+| ADD COLUMN | Yes | |
+| ADD COLUMN IF NOT EXISTS | Yes | |
+| DROP COLUMN | Yes | |
+| DROP COLUMN IF EXISTS | Yes | |
+| DROP COLUMN CASCADE | Yes | |
+| RENAME COLUMN | Yes | |
+| RENAME TO | Yes | |
+| ADD CHECK | No | Only at CREATE TABLE time |
+| ADD CONSTRAINT (PRIMARY KEY) | Yes | [Documentation](/docs/references/objects/tables/) |
+| ADD CONSTRAINT (UNIQUE) | Yes | [Documentation](/docs/references/objects/tables/) |
+| ADD CONSTRAINT (FOREIGN KEY) | Yes | [Documentation](/docs/references/objects/tables/) |
+| DROP CONSTRAINT | Yes | [Documentation](/docs/references/objects/tables/) |
+| DROP CONSTRAINT IF EXISTS | Yes | |
+| DROP CONSTRAINT CASCADE | Yes | |
+| RENAME CONSTRAINT | No | |
+| ALTER COLUMN SET/DROP DEFAULT | No | |
+| ALTER COLUMN SET/DROP NOT NULL | No | |
+| ALTER COLUMN TYPE | No | |
+| SET SCHEMA | No | |
+| OWNER TO | Yes | |
+| ENABLE/DISABLE ROW LEVEL SECURITY | Yes | CREATE POLICY requires an enterprise license |
+| FORCE/NO FORCE ROW LEVEL SECURITY | Yes | |
+| SET TABLESPACE | No | |
+| CLUSTER ON / SET WITHOUT CLUSTER | No | |
+| ATTACH / DETACH PARTITION | No | |
+| ENABLE/DISABLE TRIGGER | No | Triggers are not implemented |
+| VALIDATE CONSTRAINT | No | |
### Privileges
-| **Feature** | **Support State** | **Details** |
-|-----------------------|-------------------|--------------------------------------------------|
-| CREATE ROLE | Yes | [Documentation](/docs/references/objects/roles) |
-| OWNER TO | Yes | |
-| ALTER ROLE | Yes | [Documentation](/docs/references/objects/roles) |
-| GRANT | Yes | Only GRANT role to other_role |
-| REVOKE | No | |
-| SET ROLE | No | |
-| INHERIT | Yes | [Documentation](/docs/references/objects/roles/) |
-| Row Security Policies | No | |
+| **Feature** | **Support State** | **Details** |
+|-----------------------|-------------------|------------------------------------------------------------|
+| CREATE ROLE | Yes | [Documentation](/docs/references/objects/roles) |
+| OWNER TO | Yes | |
+| ALTER ROLE | Yes | [Documentation](/docs/references/objects/roles) |
+| GRANT | Yes | Requires an enterprise license |
+| REVOKE | Yes | Requires an enterprise license |
+| SET ROLE | Yes | Requires an enterprise license |
+| INHERIT | Yes | [Documentation](/docs/references/objects/roles/) |
+| Row Security Policies | Yes | Requires an enterprise license |
### Indexes
@@ -83,7 +108,7 @@ the [system table compatibility](../system_table) page.
| DROP SCHEMA | Yes | Only if the schema is empty |
| search_path | Yes | [Documentation](/docs/references/objects/schemas/#using-schemas) |
| Table Inheritance | No | |
-| Table Partitioning | Yes | Only at creation, only by hash |
+| Table Partitioning | Partial | Only hash partitioning. Only at CREATE TABLE time |
| Foreign Data Wrappers | No | |
| Views | Yes | [Documentation](/docs/references/objects/views/) |
| Databases | Yes | [Documentation](/docs/references/objects/databases/) |
diff --git a/content/references/objects/tables.md b/content/references/objects/tables.md
index 491f8b34..eab9a5e1 100644
--- a/content/references/objects/tables.md
+++ b/content/references/objects/tables.md
@@ -6,16 +6,16 @@ weight: 10
## CREATE TABLE
-Create table creates a new relation with the specified columns.
+CREATE TABLE creates a new relation with the specified columns.
Usage example:
```sql
-create table species (
- id int primary key,
- common_name text,
- botanical_name text not null,
- genus_id int foreign key references genus
+CREATE TABLE species (
+ id int PRIMARY KEY,
+ common_name text,
+ botanical_name text NOT NULL,
+ genus_id int REFERENCES genus(id)
);
```
@@ -25,141 +25,334 @@ After executing this statement, you can find the created table in the `pg_tables
Column definitions are specified as: `name type constraint`.
Column names can be any identifier; however, for arbitrary character sequences, you need to enclose them in double
-quotes: `"complex name""`.
+quotes: `"complex name"`.
You can find a list of supported types in the [data types reference](/docs/references/datatypes).
-CedarDB supports the following constraints, which can either be specified per-column, or separately when referencing
-multiple columns:
+CedarDB supports the following column-level constraints:
-* `unique(...)`
-* `primary key(...)`
-* `foreign key(...) references other_table(...)`
-* `not null`
+* `DEFAULT expression`: default value used when no value is supplied on INSERT.
+* `GENERATED ALWAYS AS IDENTITY` and `GENERATED BY DEFAULT AS IDENTITY`: sequence / serial column.
+* `NOT NULL`: rejects null values.
+* `UNIQUE`: enforces uniqueness for this column.
+* `PRIMARY KEY`: equivalent to UNIQUE and NOT NULL. Only one is allowed per table.
+* `REFERENCES other_table(col)`: foreign key into another table.
+* `CHECK (condition)`: rejects rows that do not satisfy the condition.
-The `unique`, `primary key`, `foreign key`, and `check` constraints can optionally be given a name using the `constraint` keyword:
+### Constraints
+
+The `UNIQUE`, `PRIMARY KEY`, `FOREIGN KEY`, and `CHECK` constraints can be specified per-column or at the table level (after all column definitions), and can optionally be given a name using the `CONSTRAINT` keyword:
```sql
-create table orders (
- id int,
- customer int,
- item int,
- constraint orders_pk primary key (id),
- constraint orders_customer_fk foreign key (customer) references customers (id),
- constraint orders_item_fk foreign key (item) references items (id),
- constraint orders_unique unique (customer, item),
- constraint orders_id_positive check (id > 0)
+CREATE TABLE observations (
+ id int,
+ species int,
+ site int,
+ observed_at date,
+ CONSTRAINT observations_pk PRIMARY KEY (id),
+ CONSTRAINT observations_species_fk FOREIGN KEY (species) REFERENCES species (id),
+ CONSTRAINT observations_site_fk FOREIGN KEY (site) REFERENCES sites (id),
+ CONSTRAINT observations_unique UNIQUE (species, site, observed_at),
+ CONSTRAINT observations_id_positive CHECK (id > 0)
);
```
-The `constraint ` part can be omitted entirely, in which case CedarDB automatically assigns a default name using the same conventions as PostgreSQL:
+The `CONSTRAINT ` part can be omitted entirely, in which case CedarDB automatically assigns a default name using the same conventions as PostgreSQL:
* Primary key: `tablename_pkey`
* Unique: `tablename_colname_key`
* Foreign key: `tablename_colname_fkey`
* Check: `tablename_colname_check`
-These default names can be used just like explicit names, e.g., to drop a constraint with `alter table ... drop constraint `.
-Naming is not supported for `not null`.
+These default names can be used just like explicit names, e.g., to drop a constraint with `ALTER TABLE ... DROP CONSTRAINT `.
+Naming is not supported for `NOT NULL`.
+
+### Foreign Key Actions
+
+CedarDB supports the following referential actions on foreign keys:
+
+* `ON DELETE CASCADE`: deletes child rows when the referenced row is deleted.
+* `ON DELETE RESTRICT`: prevents deletion if child rows exist.
+* `ON DELETE NO ACTION`: same as RESTRICT, and the default.
+* `ON UPDATE CASCADE`: updates child rows when the referenced key value changes.
+
+`ON DELETE SET NULL` and `ON DELETE SET DEFAULT` are not yet implemented.
### Options
-Create a temporary table that will be dropped when the current client disconnects:
+Create a temporary table that exists only for the current session:
```sql
-create temp table table_name (...);
+CREATE TEMP TABLE temp_results (id int, score numeric);
```
Do not throw an error if a table with the same name already exists:
```sql
-create table table_name if not exists (...);
+CREATE TABLE IF NOT EXISTS species (id int PRIMARY KEY, common_name text);
+```
+
+Create a table using the column layout of an existing table.
+This copies the column names and types, but not constraints or defaults:
+
+```sql
+CREATE TABLE species_archive (LIKE species);
+```
+
+Create a table partitioned by a hash of a column:
+
+```sql
+CREATE TABLE observations (
+ id int,
+ species int,
+ site text
+) PARTITION BY HASH (id);
```
-Create a table that stores all compressed data on remote server, which was created with name `remote_storage` (see [create server](/docs/references/advanced/createserver) for more infos):
+Create a table that stores all compressed data on a remote server previously created with name `remote_storage`
+(see [CREATE SERVER](/docs/references/advanced/createserver) for more information):
```sql
-create table table_name (...) with (server = remote_storage);
+CREATE TABLE remote_species (...) WITH (server = remote_storage);
```
+### Identity Columns
+
+Identity columns automatically generate unique integer values:
+
+```sql
+CREATE TABLE specimens (
+ id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
+ accession text NOT NULL,
+ collected date
+);
+
+-- id is assigned automatically
+INSERT INTO specimens (accession, collected) VALUES ('HRB-00142', '2024-03-15');
+```
+
+With `GENERATED BY DEFAULT AS IDENTITY`, you can supply a value explicitly on INSERT.
+With `GENERATED ALWAYS AS IDENTITY`, you need to use `OVERRIDING SYSTEM VALUE` to override the generated value.
+
### Permissions
-To create a role, you need to have superuser or `createrole` permissions.
+To create a table, you need the `CREATE` privilege on the target schema.
+By default, every role has the `CREATE` privilege on the `public` schema.
## CREATE TABLE AS
-`Create table as` allows creating tables with an inferred schema from the output of a query.
+`CREATE TABLE AS` creates a table with a schema inferred from the output of a query, and optionally populates it with the query result.
Usage example:
```sql
-create table recent_movies as
- select *
- from movies
- where release_date >= now()::date - interval '2' year;
+CREATE TABLE recent_observations AS
+ SELECT *
+ FROM observations
+ WHERE observed_at >= now()::date - INTERVAL '1' year;
```
-This statement creates a table which resembles the output schema (names and data types) of the query.
-Then, the output of the `select` query is stored in this newly created table.
+Create the table structure without copying any rows:
-### Select into
+```sql
+CREATE TABLE observations_archive AS
+ SELECT * FROM observations
+ WITH NO DATA;
+```
-CedarDB also supports the alternative `select into` syntax for compatibility with PostgreSQL:
+Override the output column names:
```sql
-select *
-into recent_movies
-from movies
-where release_date >= now()::date - interval '2' year;
+CREATE TABLE taxon_counts (taxon, cnt) AS
+ SELECT family, count(*) FROM species GROUP BY family;
+```
+
+Create a temporary table that is automatically dropped at the end of the transaction:
+
+```sql
+BEGIN;
+CREATE TEMP TABLE session_results ON COMMIT DROP AS
+ SELECT id, score FROM compute_scores();
+COMMIT;
+```
+
+### SELECT INTO
+
+CedarDB also supports the alternative `SELECT INTO` syntax for compatibility with PostgreSQL:
+
+```sql
+SELECT *
+INTO recent_observations
+FROM observations
+WHERE observed_at >= now()::date - INTERVAL '1' year;
```
### Caveats
The data types and names of the created columns can be surprising for queries with multiple expressions.
E.g., CedarDB promotes the precision of numeric types to avoid overflows,
-or infers that columns are guaranteed not-null, e.g., `release_date` in the example above (due to the `>=` predicate).
+or infers that columns are guaranteed not-null from query predicates.
For more control over the schema, consider using the regular
-[create table](/docs/references/objects/tables) statement.
+[CREATE TABLE](/docs/references/objects/tables) statement.
+
+## DROP TABLE
+
+DROP TABLE removes a table and all its data.
+
+```sql
+DROP TABLE observations;
+```
+
+Do not throw an error if the table does not exist:
+
+```sql
+DROP TABLE IF EXISTS staging_data;
+```
+
+Drop multiple tables in one statement:
+
+```sql
+DROP TABLE staging_data, temp_results;
+```
+
+If another table has a foreign key referencing the table being dropped, the DROP fails with an error.
+Drop the dependent table first.
+`DROP TABLE CASCADE` is not yet supported.
+
+### Permissions
+
+To drop a table you must own it, own its schema, or be a database superuser.
## ALTER TABLE
-Alter table allows modifying the definition of a table.
-See [PostgreSQL: ALTER TABLE](https://www.postgresql.org/docs/current/sql-altertable.html) for the PostgreSQL documentation.
+ALTER TABLE modifies the definition of an existing table.
### Column statements
-#### `RENAME COLUMN`
+#### ADD COLUMN
+
+```sql
+ALTER TABLE species ADD COLUMN iucn_status text;
+```
+
+Add a column only if it does not already exist:
+
+```sql
+ALTER TABLE species ADD COLUMN IF NOT EXISTS iucn_status text;
+```
+
+#### DROP COLUMN
+
+```sql
+ALTER TABLE species DROP COLUMN iucn_status;
+```
+
+Drop a column, silently ignoring if it does not exist:
```sql
-ALTER TABLE IF EXISTS movies RENAME COLUMN runlength TO duration;
+ALTER TABLE species DROP COLUMN IF EXISTS iucn_status;
```
-Rename column of table with `table_name` and `current_column_name` to the `new_column_name`.
-If exists checks if the table exists and only tries to rename in the case of existence.
+Drop a column and cascade to any dependent objects, like other columns that reference it:
+
+```sql
+ALTER TABLE observations DROP COLUMN raw_notes CASCADE;
+```
+
+#### RENAME COLUMN
+
+```sql
+ALTER TABLE species RENAME COLUMN botanical_name TO scientific_name;
+```
+
+Renaming a column does not rename any constraints whose default name was derived from the old column name.
+For example, a unique constraint with the default name `species_botanical_name_key` keeps that name after the column is renamed.
+
+#### RENAME TABLE
+
+```sql
+ALTER TABLE species RENAME TO plant_species;
+```
### Constraint statements
-#### `ADD CONSTRAINT`
+#### ADD CONSTRAINT
Adds a constraint to an existing table.
-CedarDB supports named `primary key`, `foreign key`, and `unique` constraints:
+CedarDB supports adding named `PRIMARY KEY`, `FOREIGN KEY`, and `UNIQUE` constraints:
```sql
-ALTER TABLE orders ADD CONSTRAINT orders_pk primary key (id);
-ALTER TABLE orders ADD CONSTRAINT orders_customer_fk foreign key (customer) references customers (id);
-ALTER TABLE orders ADD CONSTRAINT orders_unique unique (customer, item);
+ALTER TABLE orders ADD CONSTRAINT orders_pk PRIMARY KEY (id);
+ALTER TABLE orders ADD CONSTRAINT orders_customer_fk FOREIGN KEY (customer) REFERENCES customers (id);
+ALTER TABLE orders ADD CONSTRAINT orders_unique UNIQUE (customer, item);
```
-The `CONSTRAINT ` part can be omitted entirely, in which case CedarDB automatically assigns a default name using the same conventions as PostgreSQL:
+Foreign key actions like `ON DELETE CASCADE` are also supported when adding a constraint:
+
+```sql
+ALTER TABLE observations ADD CONSTRAINT obs_species_fk
+ FOREIGN KEY (species_id) REFERENCES species (id) ON DELETE CASCADE;
+```
+
+The `CONSTRAINT ` part can be omitted, in which case CedarDB automatically assigns a default name:
* Primary key: `tablename_pkey`
* Unique: `tablename_colname_key`
* Foreign key: `tablename_colname_fkey`
-#### `DROP CONSTRAINT`
+When adding a `FOREIGN KEY` constraint to an existing table, CedarDB validates all existing rows.
+If any row would violate the constraint, the statement fails.
+
+Adding a `CHECK` constraint to an existing table is not yet supported.
+`CHECK` constraints can only be specified at `CREATE TABLE` time.
-Removes a constraint by name, using either an explicit name or the default name:
+#### DROP CONSTRAINT
+
+Removes a constraint by name:
```sql
ALTER TABLE orders DROP CONSTRAINT orders_unique;
ALTER TABLE orders DROP CONSTRAINT orders_pkey;
```
+
+Silently ignore if the constraint does not exist:
+
+```sql
+ALTER TABLE orders DROP CONSTRAINT IF EXISTS orders_unique;
+```
+
+Drop a constraint and cascade to dependent constraints:
+
+```sql
+ALTER TABLE child_table DROP CONSTRAINT fk_constraint CASCADE;
+```
+
+### Ownership
+
+```sql
+ALTER TABLE species OWNER TO botanist_role;
+```
+
+### Row Level Security
+
+Enable row-level security on a table.
+Policies must be created separately to actually filter rows:
+
+```sql
+ALTER TABLE observations ENABLE ROW LEVEL SECURITY;
+ALTER TABLE observations DISABLE ROW LEVEL SECURITY;
+```
+
+Force row-level security to apply even to the table owner:
+
+```sql
+ALTER TABLE observations FORCE ROW LEVEL SECURITY;
+ALTER TABLE observations NO FORCE ROW LEVEL SECURITY;
+```
+
+Row-level security policies are created with `CREATE POLICY`.
+This feature requires an enterprise license.
+
+### Permissions
+
+To alter a table you must be its owner.
+Superusers can alter any table.