UNPKG

@linked-db/linked-ql

Version:

A query client that extends standard SQL with new syntax sugars and enables auto-versioning capabilities on any database

386 lines (335 loc) 17.6 kB
import { $describe, $it, testParseAndStringify } from './00.parser.js'; $describe('Parser - DDL Constraints', () => { // --------------------- // COLUMN CONSTRAINTS // --------------------- $describe('Column Constraints', () => { $it('should parse NULL and NOT NULL constraints', async () => { await testParseAndStringify('ColumnNullConstraint', 'NOT NULL'); await testParseAndStringify('ColumnNullConstraint', 'NULL'); await testParseAndStringify('ColumnNullConstraint', 'CONSTRAINT c_null NULL'); }); $it('should parse DEFAULT constraints', async () => { await testParseAndStringify('ColumnDefaultConstraint', 'DEFAULT 0'); await testParseAndStringify('ColumnDefaultConstraint', 'DEFAULT NULL'); await testParseAndStringify('ColumnDefaultConstraint', 'DEFAULT CURRENT_TIMESTAMP'); await testParseAndStringify('ColumnDefaultConstraint', 'CONSTRAINT def_key DEFAULT \'abc\''); }); $it('should parse CHECK constraints', async () => { await testParseAndStringify('CheckConstraint', 'CHECK (age > 0)'); await testParseAndStringify('CheckConstraint', 'CHECK (salary BETWEEN 1000 AND 5000) NO INHERIT'); await testParseAndStringify('CheckConstraint', 'CHECK (status IN (\'active\', \'inactive\'))'); await testParseAndStringify('CheckConstraint', 'CONSTRAINT chk_age CHECK (age > 0)'); }); $it('should parse GENERATED ALWAYS AS constraints (stored/generated)', async () => { await testParseAndStringify('ColumnExpressionConstraint', 'GENERATED ALWAYS AS (col1 + col2) STORED'); await testParseAndStringify('ColumnExpressionConstraint', 'CONSTRAINT gen_expr GENERATED ALWAYS AS (LOWER(name)) STORED'); }); $it('should parse GENERATED ... AS IDENTITY constraints', async () => { await testParseAndStringify('ColumnIdentityConstraint', 'GENERATED BY DEFAULT AS IDENTITY'); await testParseAndStringify('ColumnIdentityConstraint', 'GENERATED ALWAYS AS IDENTITY'); await testParseAndStringify('ColumnIdentityConstraint', 'CONSTRAINT id1 GENERATED ALWAYS AS IDENTITY'); return; // TODO await testParseAndStringify('ColumnIdentityConstraint', 'GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1)'); }); $it('should parse PRIMARY KEY and UNIQUE constraints', async () => { await testParseAndStringify('ColumnPKConstraint', 'PRIMARY KEY'); await testParseAndStringify('ColumnPKConstraint', 'CONSTRAINT pk PRIMARY KEY'); await testParseAndStringify('ColumnUKConstraint', 'UNIQUE'); await testParseAndStringify('ColumnUKConstraint', 'CONSTRAINT uk UNIQUE'); }); $it('should parse UNIQUE with NULLS DISTINCT / NOT DISTINCT', async () => { await testParseAndStringify('ColumnUKConstraint', 'UNIQUE NULLS DISTINCT'); await testParseAndStringify('ColumnUKConstraint', 'UNIQUE NULLS NOT DISTINCT'); }); $it('should parse column-level REFERENCES (foreign key) constraints', async () => { await testParseAndStringify('ColumnFKConstraint', 'REFERENCES users (id)'); await testParseAndStringify('ColumnFKConstraint', 'CONSTRAINT fk1 REFERENCES users (id)'); await testParseAndStringify('ColumnFKConstraint', 'REFERENCES users (id) MATCH FULL'); await testParseAndStringify('ColumnFKConstraint', 'REFERENCES users (id) ON DELETE CASCADE'); await testParseAndStringify('ColumnFKConstraint', 'REFERENCES users (id) ON UPDATE SET NULL'); await testParseAndStringify('ColumnFKConstraint', 'REFERENCES users (id) MATCH FULL ON DELETE SET NULL ON UPDATE SET DEFAULT'); await testParseAndStringify('ColumnFKConstraint', 'REFERENCES users'); await testParseAndStringify('ColumnFKConstraint', 'REFERENCES users (id) DEFERRABLE INITIALLY DEFERRED', { dialect: 'postgres' }); }); }); // --------------------- // TABLE CONSTRAINTS // --------------------- $describe('Table Constraints', () => { $it('should parse PRIMARY KEY constraints', async () => { await testParseAndStringify('TablePKConstraint', 'CONSTRAINT pk_id PRIMARY KEY (id)'); await testParseAndStringify('TablePKConstraint', 'PRIMARY KEY (id)'); await testParseAndStringify('TablePKConstraint', 'PRIMARY KEY (id, name)'); }); $it('should parse UNIQUE constraints', async () => { await testParseAndStringify('TableUKConstraint', 'CONSTRAINT uk_email UNIQUE (email)'); await testParseAndStringify('TableUKConstraint', 'UNIQUE (email)'); await testParseAndStringify('TableUKConstraint', 'UNIQUE (email, username)'); await testParseAndStringify('TableUKConstraint', 'UNIQUE NULLS DISTINCT (email)'); await testParseAndStringify('TableUKConstraint', 'UNIQUE NULLS NOT DISTINCT (email)'); }); $it('should parse table-level CHECK constraints', async () => { await testParseAndStringify('CheckConstraint', 'CHECK (amount > 0)'); await testParseAndStringify('CheckConstraint', 'CONSTRAINT chk CHECK (amount > 0) NO INHERIT'); }); $it('should parse FOREIGN KEY constraints with match + actions', async () => { await testParseAndStringify('TableFKConstraint', 'CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users (id)'); await testParseAndStringify('TableFKConstraint', 'FOREIGN KEY (user_id) REFERENCES users (id)'); await testParseAndStringify('TableFKConstraint', 'CONSTRAINT fk_user FOREIGN KEY (user_id, group_id) REFERENCES users (id, group_id) ON DELETE CASCADE ON UPDATE SET NULL'); await testParseAndStringify('TableFKConstraint', 'FOREIGN KEY (user_id) REFERENCES users'); await testParseAndStringify('TableFKConstraint', 'FOREIGN KEY (user_id) REFERENCES users (id) DEFERRABLE INITIALLY DEFERRED', { dialect: 'postgres' }); await testParseAndStringify('TableFKConstraint', 'FOREIGN KEY (col1, col2) REFERENCES ref_table (ref1, ref2) ON DELETE SET DEFAULT (col1) ON UPDATE SET NULL (col2)'); }); $it('should parse EXCLUDE constraints', async () => { await testParseAndStringify('PGTableEXConstraint', 'EXCLUDE USING GIST (c WITH &&)'); await testParseAndStringify('PGTableEXConstraint', 'EXCLUDE USING GIST ((lower(col)) WITH =, other_col WITH &&)'); const sql = `EXCLUDE USING GIST (( lower(col1) ) WITH =, col2 WITH &&) INCLUDE (extra) WITH (fillfactor = 80) USING INDEX TABLESPACE fastspace WHERE (col3 IS NOT NULL)`; await testParseAndStringify('PGTableEXConstraint', sql, { prettyPrint: true, autoLineBreakThreshold: 5 }); }); }); // --------------------- // CREATE TABLE TESTS // --------------------- $describe('CREATE TABLE Statements', () => { $it('should parse CREATE TABLE with various column types', async () => { await testParseAndStringify('CreateTableStmt', 'CREATE TABLE users (id INT)'); await testParseAndStringify('CreateTableStmt', 'CREATE TABLE users (id INT, name VARCHAR(100))'); await testParseAndStringify('CreateTableStmt', 'CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE)'); }); $it('should parse CREATE TABLE with table constraints', async () => { await testParseAndStringify('CreateTableStmt', 'CREATE TABLE users (id INT, name TEXT, CONSTRAINT pk_id PRIMARY KEY (id))'); }); $it('should parse CREATE TABLE with multiple constraints', async () => { await testParseAndStringify('CreateTableStmt', 'CREATE TABLE users (id INT, name TEXT, PRIMARY KEY (id), UNIQUE (name))'); }); $it('should parse CREATE TABLE with all constraint types', async () => { const sql = `CREATE TABLE users ( id INT PRIMARY KEY, email VARCHAR(255) UNIQUE, age INT CHECK (age > 0), group_id INT, CONSTRAINT fk_group FOREIGN KEY (group_id) REFERENCES tbl_groups (id), CONSTRAINT email_unique UNIQUE NULLS DISTINCT (email), CONSTRAINT exclude_circle EXCLUDE USING GIST (circle_col WITH &&) WHERE (circle_col IS NOT NULL) )`; await testParseAndStringify('CreateTableStmt', sql, { prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with composite primary and unique keys', async () => { await testParseAndStringify('CreateTableStmt', 'CREATE TABLE orders (order_id INT, product_id INT, PRIMARY KEY (order_id, product_id), UNIQUE (product_id))'); }); $it('should parse CREATE TABLE with multiple foreign keys', async () => { const sql = `CREATE TABLE order_items ( id INT PRIMARY KEY, order_id INT, product_id INT, CONSTRAINT fk_order FOREIGN KEY (order_id) REFERENCES orders (id), CONSTRAINT fk_product FOREIGN KEY (product_id) REFERENCES products (id) )`; await testParseAndStringify('CreateTableStmt', sql, { prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with CHECK constraints and expressions', async () => { const sql = `CREATE TABLE employees ( id INT PRIMARY KEY, salary DECIMAL(10, 2), CHECK (salary > 0), CHECK (salary < 100000) )`; await testParseAndStringify('CreateTableStmt', sql, { prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with DEFERRABLE constraints', async () => { const sql = `CREATE TABLE payments ( id INT PRIMARY KEY, user_id INT, CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users (id) DEFERRABLE INITIALLY DEFERRED )`; await testParseAndStringify('CreateTableStmt', sql, { dialect: 'postgres', prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with all constraint clauses and options', async () => { const sql = `CREATE TABLE inventory ( id INT GENERATED ALWAYS AS IDENTITY, sku VARCHAR(50) NOT NULL UNIQUE, quantity INT DEFAULT 0 CHECK (quantity >= 0), price DECIMAL(10, 2) DEFAULT 0.00, location_id INT, CONSTRAINT pk_inventory PRIMARY KEY (id), CONSTRAINT fk_location FOREIGN KEY (location_id) REFERENCES locations (id) ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT uq_sku UNIQUE NULLS DISTINCT (sku) )`; await testParseAndStringify('CreateTableStmt', sql, { prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with quoted identifiers and reserved words', async () => { const sql = `CREATE TABLE "select" ( "from" INT PRIMARY KEY, "to" VARCHAR(100) NOT NULL, "order" INT )`; await testParseAndStringify('CreateTableStmt', sql, { prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with column comments and table comments', async () => { const sql = `CREATE TABLE products ( id INT PRIMARY KEY COMMENT 'Product ID', name VARCHAR(100) COMMENT 'Product name', price DECIMAL(10, 2) COMMENT 'Product price' ) COMMENT = 'Products table'`; await testParseAndStringify('CreateTableStmt', sql, { dialect: 'mysql', prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with VISIBLE and INVISIBLE column modifiers (MySQL)', async () => { const sql = `CREATE TABLE t1 ( a INT VISIBLE, b INT INVISIBLE, c VARCHAR(100) INVISIBLE )`; await testParseAndStringify('CreateTableStmt', sql, { dialect: 'mysql', prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with generated columns (MySQL)', async () => { const sql = `CREATE TABLE t1 ( a INT, b INT GENERATED ALWAYS AS (a + 1) STORED, c INT GENERATED ALWAYS AS (a * 2) VIRTUAL )`; await testParseAndStringify('CreateTableStmt', sql, { dialect: 'mysql', prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with ENUM and SET types (MySQL)', async () => { const sql = `CREATE TABLE enums ( status ENUM('active', 'inactive', 'pending'), flags SET('a', 'b', 'c') )`; await testParseAndStringify('CreateTableStmt', sql, { dialect: 'mysql', prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with composite foreign keys and actions', async () => { const sql = `CREATE TABLE child ( id INT, parent_id1 INT, parent_id2 INT, CONSTRAINT fk_parent FOREIGN KEY (parent_id1, parent_id2) REFERENCES parent (id1, id2) ON DELETE CASCADE ON UPDATE SET NULL )`; await testParseAndStringify('CreateTableStmt', sql, { prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with multiple constraints and mixed syntax', async () => { const sql = `CREATE TABLE test_mix ( id INT PRIMARY KEY, code VARCHAR(10) UNIQUE, value INT, CONSTRAINT chk_value CHECK (value > 0), UNIQUE (code, value), FOREIGN KEY (value) REFERENCES ref_table (ref_col) )`; await testParseAndStringify('CreateTableStmt', sql, { prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with default expressions and functions', async () => { const sql = `CREATE TABLE logs ( id SERIAL PRIMARY KEY, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT NOW() )`; await testParseAndStringify('CreateTableStmt', sql, { dialect: 'postgres', prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with array types and constraints', async () => { const sql = `CREATE TABLE arr_test ( id INT PRIMARY KEY, tags TEXT[], scores INT[] CHECK (array_length(scores, 1) > 0) )`; await testParseAndStringify('CreateTableStmt', sql, { dialect: 'postgres', prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with identity columns and sequences', async () => { const sql = `CREATE TABLE seq_test ( id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, val INT )`; await testParseAndStringify('CreateTableStmt', sql, { dialect: 'postgres', prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with partitioning (Postgres)', async () => { const sql = `CREATE TABLE measurement ( city_id INT NOT NULL, logdate DATE NOT NULL, peaktemp INT, unitsales INT )`; await testParseAndStringify('CreateTableStmt', sql, { dialect: 'postgres', prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with character set (MySQL)', async () => { const sql = `CREATE TABLE collate_test ( name VARCHAR(100) ) ENGINE = InnoDB DEFAULT CHARSET = utf8 COLLATE = utf8_unicode_ci`; await testParseAndStringify('CreateTableStmt', sql, { dialect: 'mysql', prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with schema-qualified name and options', async () => { await testParseAndStringify('CreateTableStmt', 'CREATE TABLE public.users (id INT)'); await testParseAndStringify('CreateTableStmt', 'CREATE TABLE IF NOT EXISTS users (id INT)'); await testParseAndStringify('CreateTableStmt', 'CREATE TEMPORARY TABLE temp_users (id INT)'); await testParseAndStringify('CreateTableStmt', 'CREATE TABLE users (id INT) ENGINE = InnoDB DEFAULT CHARSET = utf8', { dialect: 'mysql' }); }); }); // --------------------- // MySQL-specific features // --------------------- $describe('MySQL Specific Constraints', () => { $it('should parse AUTO_INCREMENT', async () => { await testParseAndStringify('MYColumnAutoIncrementModifier', 'AUTO_INCREMENT', { dialect: 'mysql' }); }); $it('should parse MySQL column expressions and virtual columns', async () => { await testParseAndStringify('ColumnExpressionConstraint', 'GENERATED ALWAYS AS (LOWER(name))', { dialect: 'mysql' }); await testParseAndStringify('ColumnExpressionConstraint', 'GENERATED ALWAYS AS (LOWER(name)) STORED', { dialect: 'mysql' }); await testParseAndStringify('ColumnExpressionConstraint', 'GENERATED ALWAYS AS (LOWER(name)) VIRTUAL', { dialect: 'mysql' }); await testParseAndStringify('ColumnExpressionConstraint', 'AS (col1 * 2)', { dialect: 'mysql' }); }); $it('should parse ON UPDATE CURRENT_TIMESTAMP (MySQL)', async () => { await testParseAndStringify('MYColumnOnUpdateModifier', 'ON UPDATE CURRENT_TIMESTAMP', { dialect: 'mysql' }); }); $it('should parse CREATE TABLE with AUTO_INCREMENT (MySQL)', async () => { const sql = `CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) )`; await testParseAndStringify('CreateTableStmt', sql, { dialect: 'mysql', prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with generated columns and VIRTUAL/STORED (MySQL)', async () => { const sql = `CREATE TABLE t1 ( a INT, b INT GENERATED ALWAYS AS (a + 1) STORED, c INT GENERATED ALWAYS AS (a * 2) VIRTUAL )`; await testParseAndStringify('CreateTableStmt', sql, { dialect: 'mysql', prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with AS expression (MySQL)', async () => { const sql = `CREATE TABLE t2 ( a INT, b INT AS (a * 2) )`; await testParseAndStringify('CreateTableStmt', sql, { dialect: 'mysql', prettyPrint: true, autoLineBreakThreshold: 5 }); }); $it('should parse CREATE TABLE with ON UPDATE CURRENT_TIMESTAMP (MySQL)', async () => { const sql = `CREATE TABLE logs ( id INT PRIMARY KEY AUTO_INCREMENT, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP )`; await testParseAndStringify('CreateTableStmt', sql, { dialect: 'mysql', prettyPrint: true, autoLineBreakThreshold: 5 }); }); }); });