mirror of
https://github.com/supabase/supabase.git
synced 2026-05-08 01:40:13 -04:00
3036bb103b
The SQL Editor was warning about missing RLS even when the same query enabled it, if the user wrote `ALTER TABLE IF EXISTS ...`. The parser regex didn't recognise `IF EXISTS` and was capturing `IF` as the table name, so the RLS event never matched the `CREATE TABLE`. **Changed:** - `ALTER TABLE` regex in `sql-event-parser.ts` now accepts the optional `IF EXISTS` and `ONLY` modifiers, matching Postgres's `ALTER TABLE [ IF EXISTS ] [ ONLY ] name` grammar. **Added:** - Unit tests for `IF EXISTS`, `ONLY`, and both combined. - Regression test in `SQLEditor.utils.test.ts` using the customer's exact SQL. ## To test 1. Open the SQL Editor and paste: ```sql CREATE TABLE IF NOT EXISTS public."Conversations" (id int8 primary key); ALTER TABLE IF EXISTS public."Conversations" ENABLE ROW LEVEL SECURITY; ``` 2. Hit Run – the "table will not have RLS" warning should **not** appear. 3. Sanity check: a `CREATE TABLE` without any matching `ENABLE ROW LEVEL SECURITY` still triggers the warning. Addresses [FE-3134](https://linear.app/supabase/issue/FE-3134/sql-editor-warns-about-missing-rls-policy-incorrectly). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Added comprehensive test coverage for Row Level Security detection across different SQL syntax patterns and clause combinations * **Bug Fixes** * Enhanced Row Level Security detection capabilities in the SQL editor by extending support for additional ALTER TABLE statement syntax variations, improving the accuracy and completeness of security configuration recognition <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
573 lines
19 KiB
TypeScript
573 lines
19 KiB
TypeScript
import { TABLE_EVENT_ACTIONS } from 'common/telemetry-constants'
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
import { sqlEventParser } from './sql-event-parser'
|
|
|
|
describe('SQL Event Parser', () => {
|
|
describe('CREATE TABLE detection', () => {
|
|
it('detects basic CREATE TABLE', () => {
|
|
const results = sqlEventParser.getTableEvents('CREATE TABLE users (id INT PRIMARY KEY)')
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: undefined,
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('detects CREATE TABLE with schema', () => {
|
|
const results = sqlEventParser.getTableEvents('CREATE TABLE public.users (id INT)')
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('detects CREATE TABLE IF NOT EXISTS', () => {
|
|
const results = sqlEventParser.getTableEvents('CREATE TABLE IF NOT EXISTS users (id INT)')
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: undefined,
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('handles quoted identifiers', () => {
|
|
const results = sqlEventParser.getTableEvents('CREATE TABLE "public"."user_table" (id INT)')
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: 'public',
|
|
tableName: 'user_table',
|
|
})
|
|
})
|
|
|
|
it('returns empty array for non-matching SQL', () => {
|
|
const results = sqlEventParser.getTableEvents('SELECT * FROM users')
|
|
expect(results).toHaveLength(0)
|
|
})
|
|
|
|
it('detects CREATE TEMPORARY TABLE', () => {
|
|
const results = sqlEventParser.getTableEvents('CREATE TEMPORARY TABLE temp_users (id INT)')
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: undefined,
|
|
tableName: 'temp_users',
|
|
})
|
|
})
|
|
|
|
it('detects CREATE TEMP TABLE', () => {
|
|
const results = sqlEventParser.getTableEvents('CREATE TEMP TABLE temp_users (id INT)')
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: undefined,
|
|
tableName: 'temp_users',
|
|
})
|
|
})
|
|
|
|
it('detects CREATE UNLOGGED TABLE', () => {
|
|
const results = sqlEventParser.getTableEvents('CREATE UNLOGGED TABLE fast_table (id INT)')
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: undefined,
|
|
tableName: 'fast_table',
|
|
})
|
|
})
|
|
|
|
it('detects CREATE TEMP TABLE IF NOT EXISTS', () => {
|
|
const results = sqlEventParser.getTableEvents(
|
|
'CREATE TEMP TABLE IF NOT EXISTS temp_users (id INT)'
|
|
)
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: undefined,
|
|
tableName: 'temp_users',
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('INSERT detection', () => {
|
|
it('detects basic INSERT INTO', () => {
|
|
const results = sqlEventParser.getTableEvents("INSERT INTO users (name) VALUES ('John')")
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableDataAdded,
|
|
schema: undefined,
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('detects INSERT with schema', () => {
|
|
const results = sqlEventParser.getTableEvents(
|
|
"INSERT INTO public.users (name) VALUES ('John')"
|
|
)
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableDataAdded,
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('handles quoted identifiers', () => {
|
|
const results = sqlEventParser.getTableEvents('INSERT INTO "auth"."users" (id) VALUES (1)')
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableDataAdded,
|
|
schema: 'auth',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('returns empty array for non-matching SQL', () => {
|
|
const results = sqlEventParser.getTableEvents('UPDATE users SET name = "John"')
|
|
expect(results).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
describe('COPY detection', () => {
|
|
it('detects basic COPY FROM', () => {
|
|
const results = sqlEventParser.getTableEvents("COPY users FROM '/tmp/users.csv'")
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableDataAdded,
|
|
schema: undefined,
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('detects COPY with schema', () => {
|
|
const results = sqlEventParser.getTableEvents(
|
|
"COPY public.users FROM '/tmp/users.csv' WITH CSV HEADER"
|
|
)
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableDataAdded,
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('handles quoted identifiers', () => {
|
|
const results = sqlEventParser.getTableEvents('COPY "auth"."users" FROM STDIN')
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableDataAdded,
|
|
schema: 'auth',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('returns empty array for COPY TO', () => {
|
|
const results = sqlEventParser.getTableEvents("COPY users TO '/tmp/users.csv'")
|
|
expect(results).toHaveLength(0)
|
|
})
|
|
|
|
it('returns empty array for non-matching SQL', () => {
|
|
const results = sqlEventParser.getTableEvents('SELECT * FROM users')
|
|
expect(results).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
describe('SELECT INTO detection', () => {
|
|
it('detects SELECT INTO', () => {
|
|
const results = sqlEventParser.getTableEvents('SELECT * INTO new_users FROM users')
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: undefined,
|
|
tableName: 'new_users',
|
|
})
|
|
})
|
|
|
|
it('detects SELECT INTO with schema', () => {
|
|
const results = sqlEventParser.getTableEvents(
|
|
'SELECT id, name INTO public.new_users FROM users'
|
|
)
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: 'public',
|
|
tableName: 'new_users',
|
|
})
|
|
})
|
|
|
|
it('detects CREATE TABLE AS SELECT', () => {
|
|
const results = sqlEventParser.getTableEvents('CREATE TABLE new_users AS SELECT * FROM users')
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: undefined,
|
|
tableName: 'new_users',
|
|
})
|
|
})
|
|
|
|
it('detects CREATE TABLE IF NOT EXISTS AS SELECT', () => {
|
|
const results = sqlEventParser.getTableEvents(
|
|
'CREATE TABLE IF NOT EXISTS new_users AS SELECT * FROM users WHERE active = true'
|
|
)
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: undefined,
|
|
tableName: 'new_users',
|
|
})
|
|
})
|
|
|
|
it('handles quoted identifiers', () => {
|
|
const results = sqlEventParser.getTableEvents(
|
|
'SELECT * INTO "backup"."users_2024" FROM users'
|
|
)
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: 'backup',
|
|
tableName: 'users_2024',
|
|
})
|
|
})
|
|
|
|
it('returns empty array for regular SELECT', () => {
|
|
const results = sqlEventParser.getTableEvents('SELECT * FROM users')
|
|
expect(results).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
describe('RLS detection', () => {
|
|
it('detects ALTER TABLE ENABLE ROW LEVEL SECURITY', () => {
|
|
const results = sqlEventParser.getTableEvents('ALTER TABLE users ENABLE ROW LEVEL SECURITY')
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableRLSEnabled,
|
|
schema: undefined,
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('detects short form ENABLE RLS', () => {
|
|
const results = sqlEventParser.getTableEvents('ALTER TABLE users ENABLE RLS')
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableRLSEnabled,
|
|
schema: undefined,
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('detects with schema', () => {
|
|
const results = sqlEventParser.getTableEvents(
|
|
'ALTER TABLE public.users ENABLE ROW LEVEL SECURITY'
|
|
)
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableRLSEnabled,
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('handles other ALTER TABLE statements in between', () => {
|
|
const results = sqlEventParser.getTableEvents(
|
|
'ALTER TABLE users ADD COLUMN test INT, ENABLE ROW LEVEL SECURITY'
|
|
)
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableRLSEnabled,
|
|
schema: undefined,
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('returns empty array for disabling RLS', () => {
|
|
const results = sqlEventParser.getTableEvents('ALTER TABLE users DISABLE ROW LEVEL SECURITY')
|
|
expect(results).toHaveLength(0)
|
|
})
|
|
|
|
it('detects ALTER TABLE IF EXISTS ENABLE ROW LEVEL SECURITY', () => {
|
|
const results = sqlEventParser.getTableEvents(
|
|
'ALTER TABLE IF EXISTS public."Conversations" ENABLE ROW LEVEL SECURITY'
|
|
)
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableRLSEnabled,
|
|
schema: 'public',
|
|
tableName: 'Conversations',
|
|
})
|
|
})
|
|
|
|
it('detects ALTER TABLE ONLY ENABLE ROW LEVEL SECURITY', () => {
|
|
const results = sqlEventParser.getTableEvents(
|
|
'ALTER TABLE ONLY public.users ENABLE ROW LEVEL SECURITY'
|
|
)
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableRLSEnabled,
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('detects ALTER TABLE IF EXISTS ONLY ENABLE ROW LEVEL SECURITY', () => {
|
|
const results = sqlEventParser.getTableEvents(
|
|
'ALTER TABLE IF EXISTS ONLY public.users ENABLE ROW LEVEL SECURITY'
|
|
)
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableRLSEnabled,
|
|
schema: 'public',
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('ReDoS protection', () => {
|
|
it('handles extremely long identifier names efficiently', () => {
|
|
const longIdentifier = 'a'.repeat(10000)
|
|
const sql = `CREATE TABLE ${longIdentifier} (id INT)`
|
|
|
|
const startTime = Date.now()
|
|
const results = sqlEventParser.getTableEvents(sql)
|
|
const duration = Date.now() - startTime
|
|
|
|
expect(duration).toBeLessThan(100)
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: undefined,
|
|
tableName: longIdentifier,
|
|
})
|
|
})
|
|
|
|
it('handles nested dots in schema names without catastrophic backtracking', () => {
|
|
const maliciousInput = 'a.'.repeat(1000) + 'table'
|
|
const sql = `CREATE TABLE ${maliciousInput} (id INT)`
|
|
|
|
const startTime = Date.now()
|
|
const results = sqlEventParser.getTableEvents(sql)
|
|
const duration = Date.now() - startTime
|
|
|
|
expect(duration).toBeLessThan(100)
|
|
expect(results.length).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('handles pathological SELECT INTO patterns', () => {
|
|
const maliciousSQL = 'SELECT ' + 'a '.repeat(1000) + 'INTO table FROM users'
|
|
|
|
const startTime = Date.now()
|
|
const results = sqlEventParser.getTableEvents(maliciousSQL)
|
|
const duration = Date.now() - startTime
|
|
|
|
expect(duration).toBeLessThan(100)
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: undefined,
|
|
tableName: 'table',
|
|
})
|
|
})
|
|
|
|
it('handles ALTER TABLE with many operations between', () => {
|
|
const manyOperations = 'ADD COLUMN test INT, '.repeat(100)
|
|
const sql = `ALTER TABLE users ${manyOperations} ENABLE ROW LEVEL SECURITY`
|
|
|
|
const startTime = Date.now()
|
|
const results = sqlEventParser.getTableEvents(sql)
|
|
const duration = Date.now() - startTime
|
|
|
|
expect(duration).toBeLessThan(100)
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableRLSEnabled,
|
|
schema: undefined,
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('handles mixed quotes and backticks efficiently', () => {
|
|
const mixedQuotes = '`"`.'.repeat(100) + 'tablename'
|
|
const sql = `CREATE TABLE ${mixedQuotes} (id INT)`
|
|
|
|
const startTime = Date.now()
|
|
sqlEventParser.getTableEvents(sql)
|
|
const duration = Date.now() - startTime
|
|
|
|
expect(duration).toBeLessThan(100)
|
|
})
|
|
})
|
|
|
|
describe('Edge cases and special characters', () => {
|
|
it('handles Unicode identifiers', () => {
|
|
const sql = 'CREATE TABLE 用户表 (id INT)'
|
|
const results = sqlEventParser.getTableEvents(sql)
|
|
expect(results).toHaveLength(0)
|
|
})
|
|
|
|
it('handles identifiers with numbers', () => {
|
|
const sql = 'CREATE TABLE table123 (id INT)'
|
|
const results = sqlEventParser.getTableEvents(sql)
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: undefined,
|
|
tableName: 'table123',
|
|
})
|
|
})
|
|
|
|
it('handles identifiers with underscores', () => {
|
|
const sql = 'CREATE TABLE user_accounts (id INT)'
|
|
const results = sqlEventParser.getTableEvents(sql)
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: undefined,
|
|
tableName: 'user_accounts',
|
|
})
|
|
})
|
|
|
|
it('handles escaped quotes in identifiers', () => {
|
|
const sql = 'CREATE TABLE "user""table" (id INT)'
|
|
const results = sqlEventParser.getTableEvents(sql)
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: undefined,
|
|
tableName: 'usertable',
|
|
})
|
|
})
|
|
|
|
it('does not scan inside dollar-quoted string literals', () => {
|
|
// $$...$$ is a string literal in Postgres — its contents must not be
|
|
// parsed as DDL, otherwise a user inserting SQL-shaped text into a log
|
|
// table would trigger false-positive table-created events.
|
|
const sql = `
|
|
CREATE TABLE users (id INT);
|
|
INSERT INTO logs VALUES ($$CREATE TABLE fake$$);
|
|
INSERT INTO users VALUES (1);
|
|
`
|
|
const results = sqlEventParser.getTableEvents(sql)
|
|
expect(results).toHaveLength(3)
|
|
expect(results[0]).toMatchObject({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
tableName: 'users',
|
|
})
|
|
expect(results[1]).toMatchObject({
|
|
type: TABLE_EVENT_ACTIONS.TableDataAdded,
|
|
tableName: 'logs',
|
|
})
|
|
expect(results[2]).toMatchObject({
|
|
type: TABLE_EVENT_ACTIONS.TableDataAdded,
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
|
|
it('does not treat SELECT..INTO inside a plpgsql body as table creation', () => {
|
|
// Regression for the RLS warning modal false-positive: variable
|
|
// assignment inside a function body must not be reported as a new table.
|
|
const sql = `
|
|
create or replace function schema_checks()
|
|
returns jsonb
|
|
language plpgsql
|
|
as $$
|
|
declare
|
|
ret jsonb;
|
|
begin
|
|
select jsonb_build_object('value', 'ok') into ret;
|
|
return ret;
|
|
end;
|
|
$$;
|
|
`
|
|
const results = sqlEventParser.getTableEvents(sql)
|
|
expect(results).toEqual([])
|
|
})
|
|
|
|
it('does not leak nested dollar-quoted dynamic SQL through statement splitting', () => {
|
|
// Regression: an inner $sql$...$sql$ tag used inside an outer $fn$...$fn$
|
|
// body was pairing with the outer opening tag (the splitStatements regex
|
|
// doesn't enforce matching tags), which caused the inner semicolon to
|
|
// split the statement and exposed `create table fake` to the detectors.
|
|
// The fix is that stripDollarQuoteBodies runs before splitStatements and
|
|
// uses a backreference to require matching tags.
|
|
const sql = `
|
|
create function f()
|
|
returns void
|
|
language plpgsql
|
|
as $fn$
|
|
begin
|
|
execute $sql$create table fake(id int);$sql$;
|
|
end;
|
|
$fn$;
|
|
`
|
|
const results = sqlEventParser.getTableEvents(sql)
|
|
expect(results).toEqual([])
|
|
})
|
|
|
|
it('still detects a real top-level CREATE TABLE next to a function with nested dollar tags', () => {
|
|
const sql = `
|
|
create table public.real_table(id int);
|
|
create function f()
|
|
returns void
|
|
language plpgsql
|
|
as $fn$
|
|
begin
|
|
execute $sql$create table fake(id int);$sql$;
|
|
end;
|
|
$fn$;
|
|
`
|
|
const results = sqlEventParser.getTableEvents(sql)
|
|
expect(results).toEqual([
|
|
{
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: 'public',
|
|
tableName: 'real_table',
|
|
},
|
|
])
|
|
})
|
|
|
|
it('handles SQL injection attempts safely', () => {
|
|
const sql = "CREATE TABLE users'; DROP TABLE users; -- (id INT)"
|
|
const results = sqlEventParser.getTableEvents(sql)
|
|
expect(results).toHaveLength(1)
|
|
expect(results[0]).toEqual({
|
|
type: TABLE_EVENT_ACTIONS.TableCreated,
|
|
schema: undefined,
|
|
tableName: 'users',
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('getTableEvents', () => {
|
|
it('filters only table-related events', () => {
|
|
const sql = `
|
|
CREATE TABLE users (id INT);
|
|
CREATE FUNCTION test() RETURNS INT AS $$ BEGIN RETURN 1; END; $$ LANGUAGE plpgsql;
|
|
INSERT INTO users (id) VALUES (1);
|
|
ALTER TABLE users ENABLE RLS;
|
|
CREATE VIEW user_view AS SELECT * FROM users;
|
|
`
|
|
const results = sqlEventParser.getTableEvents(sql)
|
|
expect(results).toHaveLength(3)
|
|
expect(results.map((r) => r.type)).toEqual([
|
|
TABLE_EVENT_ACTIONS.TableCreated,
|
|
TABLE_EVENT_ACTIONS.TableDataAdded,
|
|
TABLE_EVENT_ACTIONS.TableRLSEnabled,
|
|
])
|
|
})
|
|
|
|
it('returns empty array for non-table SQL', () => {
|
|
const sql = `
|
|
CREATE FUNCTION test() RETURNS INT AS $$ BEGIN RETURN 1; END; $$ LANGUAGE plpgsql;
|
|
CREATE VIEW user_view AS SELECT * FROM users;
|
|
SELECT * FROM users;
|
|
`
|
|
const results = sqlEventParser.getTableEvents(sql)
|
|
expect(results).toHaveLength(0)
|
|
})
|
|
})
|
|
})
|