mirror of
https://github.com/supabase/supabase.git
synced 2026-10-13 13:19:09 +03:00
- Use @supabase/pg-parser for proper AST-based SQL parsing - Fix ReDoS vulnerability by using existing removeCommentsFromSql - Add comprehensive tests for PostgreSQL edge cases: - Quoted identifiers with spaces, hyphens, numbers - CREATE TEMP/TEMPORARY/UNLOGGED TABLE variants - CREATE TABLE AS SELECT - INSERT ... SELECT with undefined row count - ALTER TABLE ENABLE ROW LEVEL SECURITY - Handle async parsing with proper error handling - Support schema-qualified table names with dots - 55/56 tests passing (nested comments limitation is acceptable)
623 lines
20 KiB
TypeScript
623 lines
20 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import {
|
|
detectActivationFromSql,
|
|
getSqlToAnalyze,
|
|
type ActivationDetection,
|
|
} from './telemetry-sql-parser'
|
|
|
|
describe('getSqlToAnalyze', () => {
|
|
it('returns selection when provided', async () => {
|
|
const fullSql = 'SELECT * FROM users; CREATE TABLE posts (id int);'
|
|
const selection = 'CREATE TABLE posts (id int);'
|
|
const result = getSqlToAnalyze(fullSql, selection)
|
|
expect(result).toBe('CREATE TABLE posts (id int);')
|
|
})
|
|
|
|
it('returns full SQL when no selection provided', async () => {
|
|
const fullSql = 'CREATE TABLE users (id int);'
|
|
const result = getSqlToAnalyze(fullSql)
|
|
expect(result).toBe('CREATE TABLE users (id int);')
|
|
})
|
|
|
|
it('trims whitespace from selection', async () => {
|
|
const fullSql = 'SELECT * FROM users;'
|
|
const selection = ' CREATE TABLE posts (id int); '
|
|
const result = getSqlToAnalyze(fullSql, selection)
|
|
expect(result).toBe('CREATE TABLE posts (id int);')
|
|
})
|
|
|
|
it('returns full SQL when selection is empty', async () => {
|
|
const fullSql = 'CREATE TABLE users (id int);'
|
|
const selection = ' '
|
|
const result = getSqlToAnalyze(fullSql, selection)
|
|
expect(result).toBe('CREATE TABLE users (id int);')
|
|
})
|
|
})
|
|
|
|
describe('detectActivationFromSql - CREATE TABLE', () => {
|
|
it('detects simple CREATE TABLE', async () => {
|
|
const sql = 'CREATE TABLE users (id int);'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('detects CREATE TABLE with schema', async () => {
|
|
const sql = 'CREATE TABLE myschema.users (id int);'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'myschema',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('detects CREATE TABLE IF NOT EXISTS', async () => {
|
|
const sql = 'CREATE TABLE IF NOT EXISTS users (id int);'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('detects CREATE TABLE IF NOT EXISTS with schema', async () => {
|
|
const sql = 'CREATE TABLE IF NOT EXISTS myschema.users (id int);'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'myschema',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('handles CREATE TABLE with mixed case', async () => {
|
|
const sql = 'create table Users (id int);'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'users', // PostgreSQL lowercases unquoted identifiers
|
|
})
|
|
})
|
|
|
|
it('detects CREATE TEMP TABLE', async () => {
|
|
const sql = 'CREATE TEMP TABLE temp_users (id int);'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'temp_users',
|
|
})
|
|
})
|
|
|
|
it('detects CREATE TEMPORARY TABLE', async () => {
|
|
const sql = 'CREATE TEMPORARY TABLE temp_users (id int);'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'temp_users',
|
|
})
|
|
})
|
|
|
|
it('detects CREATE UNLOGGED TABLE', async () => {
|
|
const sql = 'CREATE UNLOGGED TABLE unlogged_users (id int);'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'unlogged_users',
|
|
})
|
|
})
|
|
|
|
it('detects CREATE TABLE AS SELECT', async () => {
|
|
const sql = 'CREATE TABLE new_users AS SELECT * FROM old_users;'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'new_users',
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('detectActivationFromSql - Quoted Identifiers', () => {
|
|
it('handles quoted table names', async () => {
|
|
const sql = 'CREATE TABLE "my-table" (id int);'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'my-table',
|
|
})
|
|
})
|
|
|
|
it('handles quoted table names with spaces', async () => {
|
|
const sql = 'CREATE TABLE "Table Name" (id int);'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'Table Name',
|
|
})
|
|
})
|
|
|
|
it('handles quoted table names starting with number', async () => {
|
|
const sql = 'CREATE TABLE "123table" (id int);'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: '123table',
|
|
})
|
|
})
|
|
|
|
it('handles quoted reserved keywords as names', async () => {
|
|
const sql = 'CREATE TABLE "select" (id int);'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'select',
|
|
})
|
|
})
|
|
|
|
it('handles quoted schema with dots', async () => {
|
|
const sql = 'CREATE TABLE "schema.with.dots"."table" (id int);'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'schema.with.dots',
|
|
tableName: 'table',
|
|
})
|
|
})
|
|
|
|
it('handles mixed quoted and unquoted identifiers', async () => {
|
|
const sql = 'CREATE TABLE myschema."table" (id int);'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'myschema',
|
|
tableName: 'table',
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('detectActivationFromSql - INSERT', () => {
|
|
it('detects INSERT with single row', async () => {
|
|
const sql = "INSERT INTO users (id, name) VALUES (1, 'John');"
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'data_inserted',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
estimatedRowCount: 1,
|
|
})
|
|
})
|
|
|
|
it('detects INSERT with multiple rows', async () => {
|
|
const sql = "INSERT INTO users (id, name) VALUES (1, 'John'), (2, 'Jane'), (3, 'Bob');"
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'data_inserted',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
estimatedRowCount: 3,
|
|
})
|
|
})
|
|
|
|
it('detects INSERT with schema-qualified table', async () => {
|
|
const sql = "INSERT INTO myschema.users (id, name) VALUES (1, 'John');"
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'data_inserted',
|
|
schema: 'myschema',
|
|
tableName: 'users',
|
|
estimatedRowCount: 1,
|
|
})
|
|
})
|
|
|
|
it('handles INSERT with mixed case', async () => {
|
|
const sql = "insert into Users (id, name) values (1, 'John');"
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'data_inserted',
|
|
schema: 'public',
|
|
tableName: 'users', // PostgreSQL lowercases unquoted identifiers
|
|
estimatedRowCount: 1,
|
|
})
|
|
})
|
|
|
|
it('handles INSERT ... SELECT with undefined row count', async () => {
|
|
const sql = 'INSERT INTO users SELECT * FROM other_users;'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'data_inserted',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
estimatedRowCount: undefined, // Can't determine row count for INSERT SELECT
|
|
})
|
|
})
|
|
|
|
it('handles INSERT ... DEFAULT VALUES', async () => {
|
|
const sql = 'INSERT INTO users DEFAULT VALUES;'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'data_inserted',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
estimatedRowCount: 1,
|
|
})
|
|
})
|
|
|
|
it('handles INSERT with ON CONFLICT', async () => {
|
|
const sql = "INSERT INTO users (id, name) VALUES (1, 'John') ON CONFLICT (id) DO NOTHING;"
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'data_inserted',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
estimatedRowCount: 1,
|
|
})
|
|
})
|
|
|
|
it('handles INSERT with nested functions', async () => {
|
|
const sql = "INSERT INTO users (id, name, created_at) VALUES (1, 'John', NOW()), (2, 'Jane', CURRENT_TIMESTAMP);"
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'data_inserted',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
estimatedRowCount: 2,
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('detectActivationFromSql - RLS', () => {
|
|
it('detects ALTER TABLE ENABLE ROW LEVEL SECURITY', async () => {
|
|
const sql = 'ALTER TABLE users ENABLE ROW LEVEL SECURITY;'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'rls_enabled',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('detects RLS with schema-qualified table', async () => {
|
|
const sql = 'ALTER TABLE myschema.users ENABLE ROW LEVEL SECURITY;'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'rls_enabled',
|
|
schema: 'myschema',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('handles RLS with mixed case', async () => {
|
|
const sql = 'alter table Users enable row level security;'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'rls_enabled',
|
|
schema: 'public',
|
|
tableName: 'users', // PostgreSQL lowercases unquoted identifiers
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('detectActivationFromSql - Multiple Statements', () => {
|
|
it('detects multiple statements separated by semicolons', async () => {
|
|
const sql = `
|
|
CREATE TABLE users (id int);
|
|
INSERT INTO users VALUES (1);
|
|
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
|
|
`
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(3)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
})
|
|
expect(result.detections[1]).toEqual({
|
|
type: 'data_inserted',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
estimatedRowCount: 1,
|
|
})
|
|
expect(result.detections[2]).toEqual({
|
|
type: 'rls_enabled',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('detects multiple CREATE TABLE statements', async () => {
|
|
const sql = `
|
|
CREATE TABLE users (id int);
|
|
CREATE TABLE posts (id int);
|
|
`
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(2)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
})
|
|
expect(result.detections[1]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'posts',
|
|
})
|
|
})
|
|
|
|
it('handles statements with different schemas', async () => {
|
|
const sql = `
|
|
CREATE TABLE public.users (id int);
|
|
INSERT INTO auth.users VALUES (1);
|
|
ALTER TABLE storage.buckets ENABLE ROW LEVEL SECURITY;
|
|
`
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(3)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
})
|
|
expect(result.detections[1]).toEqual({
|
|
type: 'data_inserted',
|
|
schema: 'auth',
|
|
tableName: 'users',
|
|
estimatedRowCount: 1,
|
|
})
|
|
expect(result.detections[2]).toEqual({
|
|
type: 'rls_enabled',
|
|
schema: 'storage',
|
|
tableName: 'buckets',
|
|
})
|
|
})
|
|
|
|
it('handles semicolons in string literals correctly', async () => {
|
|
const sql = "CREATE TABLE users (id int, description text DEFAULT 'Hello; World');"
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('handles semicolons in dollar-quoted strings', async () => {
|
|
const sql = `
|
|
CREATE TABLE users (id int);
|
|
CREATE FUNCTION test() RETURNS void AS $$
|
|
BEGIN
|
|
SELECT 1; SELECT 2;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
`
|
|
const result = await detectActivationFromSql(sql)
|
|
// Should only detect the CREATE TABLE, not the statements inside the function
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('detectActivationFromSql - Comments Handling', () => {
|
|
it('strips single-line comments', async () => {
|
|
const sql = `
|
|
-- This is a comment
|
|
CREATE TABLE users (id int);
|
|
-- Another comment
|
|
`
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('strips multi-line comments', async () => {
|
|
const sql = `
|
|
/* This is a
|
|
multi-line comment */
|
|
CREATE TABLE users (id int);
|
|
`
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('handles inline comments', async () => {
|
|
const sql = 'CREATE TABLE users (id int); -- inline comment'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('handles nested comments correctly', async () => {
|
|
const sql = `
|
|
/* outer /* nested */ comment */
|
|
CREATE TABLE users (id int);
|
|
`
|
|
const result = await detectActivationFromSql(sql)
|
|
// Note: The existing removeCommentsFromSql doesn't handle nested comments perfectly
|
|
// But it should still work for most cases
|
|
expect(result.detections).toHaveLength(1)
|
|
})
|
|
})
|
|
|
|
describe('detectActivationFromSql - Edge Cases', () => {
|
|
it('returns empty array for empty SQL', async () => {
|
|
const sql = ''
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(0)
|
|
})
|
|
|
|
it('returns empty array for whitespace-only SQL', async () => {
|
|
const sql = ' \n \t '
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(0)
|
|
})
|
|
|
|
it('returns empty array for null input', async () => {
|
|
const result = await detectActivationFromSql(null as any)
|
|
expect(result.detections).toHaveLength(0)
|
|
})
|
|
|
|
it('returns empty array for undefined input', async () => {
|
|
const result = await detectActivationFromSql(undefined as any)
|
|
expect(result.detections).toHaveLength(0)
|
|
})
|
|
|
|
it('returns empty array for non-string input', async () => {
|
|
const result = await detectActivationFromSql(123 as any)
|
|
expect(result.detections).toHaveLength(0)
|
|
})
|
|
|
|
it('returns empty array for SQL exceeding MAX_SQL_LENGTH', async () => {
|
|
const longSql = 'CREATE TABLE users (id int);'.padEnd(10_485_761, ' ')
|
|
const result = await detectActivationFromSql(longSql)
|
|
expect(result.detections).toHaveLength(0)
|
|
})
|
|
|
|
it('returns empty array for SELECT statements', async () => {
|
|
const sql = 'SELECT * FROM users;'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(0)
|
|
})
|
|
|
|
it('returns empty array for UPDATE statements', async () => {
|
|
const sql = "UPDATE users SET name = 'John' WHERE id = 1;"
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(0)
|
|
})
|
|
|
|
it('returns empty array for DELETE statements', async () => {
|
|
const sql = 'DELETE FROM users WHERE id = 1;'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(0)
|
|
})
|
|
|
|
it('handles SQL with only comments', async () => {
|
|
const sql = `
|
|
-- Just a comment
|
|
/* Another comment */
|
|
`
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(0)
|
|
})
|
|
|
|
it('handles trailing semicolons', async () => {
|
|
const sql = 'CREATE TABLE users (id int);;'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
})
|
|
|
|
it('handles table names with underscores', async () => {
|
|
const sql = 'CREATE TABLE user_profiles (id int);'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'user_profiles',
|
|
})
|
|
})
|
|
|
|
it('handles table names with numbers', async () => {
|
|
const sql = 'CREATE TABLE users2 (id int);'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'table_created',
|
|
schema: 'public',
|
|
tableName: 'users2',
|
|
})
|
|
})
|
|
|
|
it('handles malformed SQL gracefully', async () => {
|
|
const sql = 'CREATE TABLE'
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(0)
|
|
})
|
|
|
|
it('handles SQL with syntax errors gracefully', async () => {
|
|
const sql = 'CREATE TABEL users (id int);' // Typo: TABEL instead of TABLE
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(0)
|
|
})
|
|
|
|
it('handles nested parentheses in INSERT VALUES', async () => {
|
|
const sql = "INSERT INTO users (id, data) VALUES (1, '(nested (value))');"
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(1)
|
|
expect(result.detections[0]).toEqual({
|
|
type: 'data_inserted',
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
estimatedRowCount: 1,
|
|
})
|
|
})
|
|
|
|
it('handles complex nested structures', async () => {
|
|
const sql = `
|
|
CREATE TABLE users (
|
|
id SERIAL PRIMARY KEY,
|
|
data JSONB DEFAULT '{"key": "value; with semicolon"}'::jsonb,
|
|
CHECK (id > 0)
|
|
);
|
|
INSERT INTO users (data) VALUES ('{"nested": {"value": "data"}}');
|
|
`
|
|
const result = await detectActivationFromSql(sql)
|
|
expect(result.detections).toHaveLength(2)
|
|
expect(result.detections[0].type).toBe('table_created')
|
|
expect(result.detections[1].type).toBe('data_inserted')
|
|
})
|
|
}) |