diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index dad7bc5..b88de45 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest container: - image: supabase/postgres:latest + image: supabase/postgres:15.1.1.79 options: --tmpfs=/pgtmpfs -e PGDATA=/pgtmpfs steps: @@ -29,13 +29,19 @@ jobs: run: | # install wal2json cd wal2json - apt-get update && apt-get install build-essential llvm-11 postgresql-server-dev-14 -y + apt-get update && apt-get install build-essential llvm-11 -y + apt install -y wget lsb-release + sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' + wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - + apt-get update + apt install libpq5=16.3-1.pgdg20.04+1 libpq-dev postgresql-server-dev-15 -y --allow-downgrades + make make install # run tests cd .. chown -R postgres /__w/walrus/walrus - su postgres -c 'bin/installcheck' + su postgres -c 'export PATH=$PATH:/usr/lib/postgresql/15/bin/ ; bin/installcheck' - uses: actions/upload-artifact@v3 if: always() diff --git a/bin/installcheck b/bin/installcheck index 36b4dde..1ec7c88 100755 --- a/bin/installcheck +++ b/bin/installcheck @@ -41,7 +41,7 @@ REGRESS="${PGXS}/../test/regress/pg_regress" TESTS=$(ls ${TESTDIR}/sql | sed -e 's/\..*$//' | sort ) # Execute the test fixtures -psql -v ON_ERROR_STOP=1 -f sql/setup.sql -f sql/walrus--0.1.sql -f sql/walrus_migration_0001*.sql -f sql/walrus_migration_0002*.sql -f sql/walrus_migration_0003*.sql -f sql/walrus_migration_0004*.sql -f sql/walrus_migration_0005*.sql -f sql/walrus_migration_0006*.sql -f sql/walrus_migration_0007*.sql -f sql/walrus_migration_0008*.sql -f sql/walrus_migration_0009*.sql -f sql/walrus_migration_0010*.sql -f test/fixtures.sql -d contrib_regression +psql -v ON_ERROR_STOP=1 -f sql/setup.sql -f sql/walrus--0.1.sql -f sql/walrus_migration_0001*.sql -f sql/walrus_migration_0002*.sql -f sql/walrus_migration_0003*.sql -f sql/walrus_migration_0004*.sql -f sql/walrus_migration_0005*.sql -f sql/walrus_migration_0006*.sql -f sql/walrus_migration_0007*.sql -f sql/walrus_migration_0008*.sql -f sql/walrus_migration_0009*.sql -f sql/walrus_migration_0010*.sql -f sql/walrus_migration_0011*.sql -f test/fixtures.sql -d contrib_regression # Run tests ${REGRESS} --use-existing --dbname=contrib_regression --inputdir=${TESTDIR} ${TESTS} diff --git a/sql/walrus_migration_0011_delete_filters.sql b/sql/walrus_migration_0011_delete_filters.sql new file mode 100644 index 0000000..9ddd133 --- /dev/null +++ b/sql/walrus_migration_0011_delete_filters.sql @@ -0,0 +1,301 @@ +create or replace function realtime.apply_rls(wal jsonb, max_record_bytes int = 1024 * 1024) + returns setof realtime.wal_rls + language plpgsql + volatile +as $$ +declare + -- Regclass of the table e.g. public.notes + entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass; + + -- I, U, D, T: insert, update ... + action realtime.action = ( + case wal ->> 'action' + when 'I' then 'INSERT' + when 'U' then 'UPDATE' + when 'D' then 'DELETE' + else 'ERROR' + end + ); + + -- Is row level security enabled for the table + is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_; + + subscriptions realtime.subscription[] = array_agg(subs) + from + realtime.subscription subs + where + subs.entity = entity_; + + -- Subscription vars + roles regrole[] = array_agg(distinct us.claims_role::text) + from + unnest(subscriptions) us; + + working_role regrole; + claimed_role regrole; + claims jsonb; + + subscription_id uuid; + subscription_has_access bool; + visible_to_subscription_ids uuid[] = '{}'; + + -- structured info for wal's columns + columns realtime.wal_column[]; + -- previous identity values for update/delete + old_columns realtime.wal_column[]; + + error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes; + + -- Primary jsonb output for record + output jsonb; + +begin + perform set_config('role', null, true); + + columns = + array_agg( + ( + x->>'name', + x->>'type', + x->>'typeoid', + realtime.cast( + (x->'value') #>> '{}', + coalesce( + (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4 + (x->>'type')::regtype + ) + ), + (pks ->> 'name') is not null, + true + )::realtime.wal_column + ) + from + jsonb_array_elements(wal -> 'columns') x + left join jsonb_array_elements(wal -> 'pk') pks + on (x ->> 'name') = (pks ->> 'name'); + + old_columns = + array_agg( + ( + x->>'name', + x->>'type', + x->>'typeoid', + realtime.cast( + (x->'value') #>> '{}', + coalesce( + (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4 + (x->>'type')::regtype + ) + ), + (pks ->> 'name') is not null, + true + )::realtime.wal_column + ) + from + jsonb_array_elements(wal -> 'identity') x + left join jsonb_array_elements(wal -> 'pk') pks + on (x ->> 'name') = (pks ->> 'name'); + + for working_role in select * from unnest(roles) loop + + -- Update `is_selectable` for columns and old_columns + columns = + array_agg( + ( + c.name, + c.type_name, + c.type_oid, + c.value, + c.is_pkey, + pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT') + )::realtime.wal_column + ) + from + unnest(columns) c; + + old_columns = + array_agg( + ( + c.name, + c.type_name, + c.type_oid, + c.value, + c.is_pkey, + pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT') + )::realtime.wal_column + ) + from + unnest(old_columns) c; + + if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then + return next ( + jsonb_build_object( + 'schema', wal ->> 'schema', + 'table', wal ->> 'table', + 'type', action + ), + is_rls_enabled, + -- subscriptions is already filtered by entity + (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role), + array['Error 400: Bad Request, no primary key'] + )::realtime.wal_rls; + + -- The claims role does not have SELECT permission to the primary key of entity + elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then + return next ( + jsonb_build_object( + 'schema', wal ->> 'schema', + 'table', wal ->> 'table', + 'type', action + ), + is_rls_enabled, + (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role), + array['Error 401: Unauthorized'] + )::realtime.wal_rls; + + else + output = jsonb_build_object( + 'schema', wal ->> 'schema', + 'table', wal ->> 'table', + 'type', action, + 'commit_timestamp', to_char( + ((wal ->> 'timestamp')::timestamptz at time zone 'utc'), + 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"' + ), + 'columns', ( + select + jsonb_agg( + jsonb_build_object( + 'name', pa.attname, + 'type', pt.typname + ) + order by pa.attnum asc + ) + from + pg_attribute pa + join pg_type pt + on pa.atttypid = pt.oid + where + attrelid = entity_ + and attnum > 0 + and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT') + ) + ) + -- Add "record" key for insert and update + || case + when action in ('INSERT', 'UPDATE') then + jsonb_build_object( + 'record', + ( + select + jsonb_object_agg( + -- if unchanged toast, get column name and value from old record + coalesce((c).name, (oc).name), + case + when (c).name is null then (oc).value + else (c).value + end + ) + from + unnest(columns) c + full outer join unnest(old_columns) oc + on (c).name = (oc).name + where + coalesce((c).is_selectable, (oc).is_selectable) + and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64)) + ) + ) + else '{}'::jsonb + end + -- Add "old_record" key for update and delete + || case + when action = 'UPDATE' then + jsonb_build_object( + 'old_record', + ( + select jsonb_object_agg((c).name, (c).value) + from unnest(old_columns) c + where + (c).is_selectable + and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64)) + ) + ) + when action = 'DELETE' then + jsonb_build_object( + 'old_record', + ( + select jsonb_object_agg((c).name, (c).value) + from unnest(old_columns) c + where + (c).is_selectable + and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64)) + and ( not is_rls_enabled or (c).is_pkey ) -- if RLS enabled, we can't secure deletes so filter to pkey + ) + ) + else '{}'::jsonb + end; + + -- Create the prepared statement + if is_rls_enabled and action <> 'DELETE' then + if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then + deallocate walrus_rls_stmt; + end if; + execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns); + end if; + + visible_to_subscription_ids = '{}'; + + for subscription_id, claims in ( + select + subs.subscription_id, + subs.claims + from + unnest(subscriptions) subs + where + subs.entity = entity_ + and subs.claims_role = working_role + and ( + realtime.is_visible_through_filters(columns, subs.filters) + or ( + action = 'DELETE' + and realtime.is_visible_through_filters(old_columns, subs.filters) + ) + ) + ) loop + + if not is_rls_enabled or action = 'DELETE' then + visible_to_subscription_ids = visible_to_subscription_ids || subscription_id; + else + -- Check if RLS allows the role to see the record + perform + -- Trim leading and trailing quotes from working_role because set_config + -- doesn't recognize the role as valid if they are included + set_config('role', trim(both '"' from working_role::text), true), + set_config('request.jwt.claims', claims::text, true); + + execute 'execute walrus_rls_stmt' into subscription_has_access; + + if subscription_has_access then + visible_to_subscription_ids = visible_to_subscription_ids || subscription_id; + end if; + end if; + end loop; + + perform set_config('role', null, true); + + return next ( + output, + is_rls_enabled, + visible_to_subscription_ids, + case + when error_record_exceeds_max_size then array['Error 413: Payload Too Large'] + else '{}' + end + )::realtime.wal_rls; + + end if; + end loop; + + perform set_config('role', null, true); +end; +$$; diff --git a/test/expected/issue_50_delete_filters.out b/test/expected/issue_50_delete_filters.out new file mode 100644 index 0000000..404e593 --- /dev/null +++ b/test/expected/issue_50_delete_filters.out @@ -0,0 +1,170 @@ +select 1 from pg_create_logical_replication_slot('realtime', 'wal2json', false); + ?column? +---------- + 1 +(1 row) + +create table public.notes( + id int primary key, + body text +); +insert into realtime.subscription(subscription_id, entity, claims, filters) +select + seed_uuid(id), + 'public.notes', + jsonb_build_object( + 'role', 'authenticated', + 'email', 'example@example.com', + 'sub', seed_uuid(id)::text + ), + array[(column_name, op, value)::realtime.user_defined_filter] +from + ( + values + (1 , 'body', 'eq', 'bbb'), + (2 , 'id', 'eq', '2') + ) f(id, column_name, op, value); +select subscription_id, filters from realtime.subscription; + subscription_id | filters +--------------------------------------+------------------- + f4539ebe-c779-5788-bbc1-2421ffaa8954 | {"(body,eq,bbb)"} + 5211e8ec-8c25-5c7f-9b03-6ff1eac0159e | {"(id,eq,2)"} +(2 rows) + +---------------------------------------------------------------------------------------- +-- When Replica Identity is Not Full, only filters referencing the pkey are respected -- +---------------------------------------------------------------------------------------- +insert into public.notes(id, body) +values + (1, 'bbb'), + (2, 'ccc'); +select clear_wal(); + clear_wal +----------- + +(1 row) + +delete from public.notes; +select + rec, + is_rls_enabled, + subscription_ids, + errors +from + walrus; + rec | is_rls_enabled | subscription_ids | errors +----------------------------------------------------+----------------+----------------------------------------+-------- + { +| f | {} | {} + "type": "DELETE", +| | | + "table": "notes", +| | | + "schema": "public", +| | | + "columns": [ +| | | + { +| | | + "name": "id", +| | | + "type": "int4" +| | | + }, +| | | + { +| | | + "name": "body", +| | | + "type": "text" +| | | + } +| | | + ], +| | | + "old_record": { +| | | + "id": 1 +| | | + }, +| | | + "commit_timestamp": "2000-01-01T08:01:01.000Z"+| | | + } | | | + { +| f | {5211e8ec-8c25-5c7f-9b03-6ff1eac0159e} | {} + "type": "DELETE", +| | | + "table": "notes", +| | | + "schema": "public", +| | | + "columns": [ +| | | + { +| | | + "name": "id", +| | | + "type": "int4" +| | | + }, +| | | + { +| | | + "name": "body", +| | | + "type": "text" +| | | + } +| | | + ], +| | | + "old_record": { +| | | + "id": 2 +| | | + }, +| | | + "commit_timestamp": "2000-01-01T08:01:01.000Z"+| | | + } | | | +(2 rows) + +---------------------------------------------------------------------------------------- +-- When Replica Identity is Not Full, only filters referencing the pkey are respected -- +---------------------------------------------------------------------------------------- +alter table public.notes replica identity full; +insert into public.notes(id, body) +values + (1, 'bbb'), + (2, 'ccc'); +select clear_wal(); + clear_wal +----------- + +(1 row) + +delete from public.notes; +select + rec, + is_rls_enabled, + subscription_ids, + errors +from + walrus; + rec | is_rls_enabled | subscription_ids | errors +----------------------------------------------------+----------------+----------------------------------------+-------- + { +| f | {f4539ebe-c779-5788-bbc1-2421ffaa8954} | {} + "type": "DELETE", +| | | + "table": "notes", +| | | + "schema": "public", +| | | + "columns": [ +| | | + { +| | | + "name": "id", +| | | + "type": "int4" +| | | + }, +| | | + { +| | | + "name": "body", +| | | + "type": "text" +| | | + } +| | | + ], +| | | + "old_record": { +| | | + "id": 1, +| | | + "body": "bbb" +| | | + }, +| | | + "commit_timestamp": "2000-01-01T08:01:01.000Z"+| | | + } | | | + { +| f | {5211e8ec-8c25-5c7f-9b03-6ff1eac0159e} | {} + "type": "DELETE", +| | | + "table": "notes", +| | | + "schema": "public", +| | | + "columns": [ +| | | + { +| | | + "name": "id", +| | | + "type": "int4" +| | | + }, +| | | + { +| | | + "name": "body", +| | | + "type": "text" +| | | + } +| | | + ], +| | | + "old_record": { +| | | + "id": 2, +| | | + "body": "ccc" +| | | + }, +| | | + "commit_timestamp": "2000-01-01T08:01:01.000Z"+| | | + } | | | +(2 rows) + +drop table public.notes; +select pg_drop_replication_slot('realtime'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +truncate table realtime.subscription; diff --git a/test/expected/test_integration_filters.out b/test/expected/test_integration_filters.out index 51d852c..c3d74b7 100644 --- a/test/expected/test_integration_filters.out +++ b/test/expected/test_integration_filters.out @@ -8,6 +8,7 @@ create table public.notes( id int primary key, body text ); +alter table public.notes replica identity full; insert into realtime.subscription(subscription_id, entity, claims, filters) select seed_uuid(id), @@ -48,47 +49,48 @@ select errors from walrus; - rec | is_rls_enabled | subscription_ids | errors -----------------------------------------------------+----------------+------------------------------------------------------------------------------------------------------------------+-------- - { +| f | {f4539ebe-c779-5788-bbc1-2421ffaa8954} | {} - "type": "INSERT", +| | | - "table": "notes", +| | | - "record": { +| | | - "id": 1, +| | | - "body": "bbb" +| | | - }, +| | | - "schema": "public", +| | | - "columns": [ +| | | - { +| | | - "name": "id", +| | | - "type": "int4" +| | | - }, +| | | - { +| | | - "name": "body", +| | | - "type": "text" +| | | - } +| | | - ], +| | | - "commit_timestamp": "2000-01-01T08:01:01.000Z"+| | | - } | | | - { +| f | {f4539ebe-c779-5788-bbc1-2421ffaa8954,5211e8ec-8c25-5c7f-9b03-6ff1eac0159e,11955172-4e1d-5836-925f-2bcb7a287b87} | {} - "type": "DELETE", +| | | - "table": "notes", +| | | - "schema": "public", +| | | - "columns": [ +| | | - { +| | | - "name": "id", +| | | - "type": "int4" +| | | - }, +| | | - { +| | | - "name": "body", +| | | - "type": "text" +| | | - } +| | | - ], +| | | - "old_record": { +| | | - "id": 1 +| | | - }, +| | | - "commit_timestamp": "2000-01-01T08:01:01.000Z"+| | | - } | | | + rec | is_rls_enabled | subscription_ids | errors +----------------------------------------------------+----------------+----------------------------------------+-------- + { +| f | {f4539ebe-c779-5788-bbc1-2421ffaa8954} | {} + "type": "INSERT", +| | | + "table": "notes", +| | | + "record": { +| | | + "id": 1, +| | | + "body": "bbb" +| | | + }, +| | | + "schema": "public", +| | | + "columns": [ +| | | + { +| | | + "name": "id", +| | | + "type": "int4" +| | | + }, +| | | + { +| | | + "name": "body", +| | | + "type": "text" +| | | + } +| | | + ], +| | | + "commit_timestamp": "2000-01-01T08:01:01.000Z"+| | | + } | | | + { +| f | {f4539ebe-c779-5788-bbc1-2421ffaa8954} | {} + "type": "DELETE", +| | | + "table": "notes", +| | | + "schema": "public", +| | | + "columns": [ +| | | + { +| | | + "name": "id", +| | | + "type": "int4" +| | | + }, +| | | + { +| | | + "name": "body", +| | | + "type": "text" +| | | + } +| | | + ], +| | | + "old_record": { +| | | + "id": 1, +| | | + "body": "bbb" +| | | + }, +| | | + "commit_timestamp": "2000-01-01T08:01:01.000Z"+| | | + } | | | (2 rows) drop table public.notes; diff --git a/test/expected/test_integration_in_filter.out b/test/expected/test_integration_in_filter.out index a21d649..cadf78f 100644 --- a/test/expected/test_integration_in_filter.out +++ b/test/expected/test_integration_in_filter.out @@ -8,6 +8,7 @@ create table public.notes( id int primary key, body text ); +alter table public.notes replica identity full; insert into realtime.subscription(subscription_id, entity, claims, filters) select seed_uuid(id), @@ -48,47 +49,48 @@ select errors from walrus; - rec | is_rls_enabled | subscription_ids | errors -----------------------------------------------------+----------------+------------------------------------------------------------------------------------------------------------------+-------- - { +| f | {f4539ebe-c779-5788-bbc1-2421ffaa8954} | {} - "type": "INSERT", +| | | - "table": "notes", +| | | - "record": { +| | | - "id": 1, +| | | - "body": "bbb" +| | | - }, +| | | - "schema": "public", +| | | - "columns": [ +| | | - { +| | | - "name": "id", +| | | - "type": "int4" +| | | - }, +| | | - { +| | | - "name": "body", +| | | - "type": "text" +| | | - } +| | | - ], +| | | - "commit_timestamp": "2000-01-01T08:01:01.000Z"+| | | - } | | | - { +| f | {f4539ebe-c779-5788-bbc1-2421ffaa8954,5211e8ec-8c25-5c7f-9b03-6ff1eac0159e,11955172-4e1d-5836-925f-2bcb7a287b87} | {} - "type": "DELETE", +| | | - "table": "notes", +| | | - "schema": "public", +| | | - "columns": [ +| | | - { +| | | - "name": "id", +| | | - "type": "int4" +| | | - }, +| | | - { +| | | - "name": "body", +| | | - "type": "text" +| | | - } +| | | - ], +| | | - "old_record": { +| | | - "id": 1 +| | | - }, +| | | - "commit_timestamp": "2000-01-01T08:01:01.000Z"+| | | - } | | | + rec | is_rls_enabled | subscription_ids | errors +----------------------------------------------------+----------------+----------------------------------------+-------- + { +| f | {f4539ebe-c779-5788-bbc1-2421ffaa8954} | {} + "type": "INSERT", +| | | + "table": "notes", +| | | + "record": { +| | | + "id": 1, +| | | + "body": "bbb" +| | | + }, +| | | + "schema": "public", +| | | + "columns": [ +| | | + { +| | | + "name": "id", +| | | + "type": "int4" +| | | + }, +| | | + { +| | | + "name": "body", +| | | + "type": "text" +| | | + } +| | | + ], +| | | + "commit_timestamp": "2000-01-01T08:01:01.000Z"+| | | + } | | | + { +| f | {f4539ebe-c779-5788-bbc1-2421ffaa8954} | {} + "type": "DELETE", +| | | + "table": "notes", +| | | + "schema": "public", +| | | + "columns": [ +| | | + { +| | | + "name": "id", +| | | + "type": "int4" +| | | + }, +| | | + { +| | | + "name": "body", +| | | + "type": "text" +| | | + } +| | | + ], +| | | + "old_record": { +| | | + "id": 1, +| | | + "body": "bbb" +| | | + }, +| | | + "commit_timestamp": "2000-01-01T08:01:01.000Z"+| | | + } | | | (2 rows) -- Confirm that filtering on `in` more than 100 entries throws an error diff --git a/test/expected/test_integration_in_uuid_filter.out b/test/expected/test_integration_in_uuid_filter.out index cae20c7..bed1550 100644 --- a/test/expected/test_integration_in_uuid_filter.out +++ b/test/expected/test_integration_in_uuid_filter.out @@ -8,6 +8,7 @@ create table public.notes( id int primary key, identifier uuid ); +alter table public.notes replica identity full; insert into realtime.subscription(subscription_id, entity, claims, filters) select seed_uuid(5), @@ -80,7 +81,8 @@ from } +| | | ], +| | | "old_record": { +| | | - "id": 1 +| | | + "id": 1, +| | | + "identifier": "ace23052-568e-4951-acc8-fd510ec667f9"+| | | }, +| | | "commit_timestamp": "2000-01-01T08:01:01.000Z" +| | | } | | | diff --git a/test/expected/test_query_from_publication.out b/test/expected/test_query_from_publication.out index 018d29d..a71c30a 100644 --- a/test/expected/test_query_from_publication.out +++ b/test/expected/test_query_from_publication.out @@ -48,10 +48,10 @@ select clear_wal(); insert into public.notes(id) values (1); insert into public.not_in_pub(id) values (1); -select * from pg_publication_tables; - pubname | schemaname | tablename --------------------+------------+----------- - supabase_realtime | public | notes +select pubname, schemaname, tablename, attnames from pg_publication_tables; + pubname | schemaname | tablename | attnames +-------------------+------------+-----------+-------------- + supabase_realtime | public | notes | {id,user_id} (1 row) select diff --git a/test/sql/issue_50_delete_filters.sql b/test/sql/issue_50_delete_filters.sql new file mode 100644 index 0000000..77d4253 --- /dev/null +++ b/test/sql/issue_50_delete_filters.sql @@ -0,0 +1,78 @@ +select 1 from pg_create_logical_replication_slot('realtime', 'wal2json', false); + +create table public.notes( + id int primary key, + body text +); + +insert into realtime.subscription(subscription_id, entity, claims, filters) +select + seed_uuid(id), + 'public.notes', + jsonb_build_object( + 'role', 'authenticated', + 'email', 'example@example.com', + 'sub', seed_uuid(id)::text + ), + array[(column_name, op, value)::realtime.user_defined_filter] +from + ( + values + (1 , 'body', 'eq', 'bbb'), + (2 , 'id', 'eq', '2') + + ) f(id, column_name, op, value); + +select subscription_id, filters from realtime.subscription; + +---------------------------------------------------------------------------------------- +-- When Replica Identity is Not Full, only filters referencing the pkey are respected -- +---------------------------------------------------------------------------------------- + +insert into public.notes(id, body) +values + (1, 'bbb'), + (2, 'ccc'); + +select clear_wal(); + +delete from public.notes; + + +select + rec, + is_rls_enabled, + subscription_ids, + errors +from + walrus; + + +---------------------------------------------------------------------------------------- +-- When Replica Identity is Not Full, only filters referencing the pkey are respected -- +---------------------------------------------------------------------------------------- + +alter table public.notes replica identity full; + +insert into public.notes(id, body) +values + (1, 'bbb'), + (2, 'ccc'); + +select clear_wal(); + +delete from public.notes; + + +select + rec, + is_rls_enabled, + subscription_ids, + errors +from + walrus; + + +drop table public.notes; +select pg_drop_replication_slot('realtime'); +truncate table realtime.subscription; diff --git a/test/sql/test_integration_filters.sql b/test/sql/test_integration_filters.sql index e8d469f..63cbc83 100644 --- a/test/sql/test_integration_filters.sql +++ b/test/sql/test_integration_filters.sql @@ -5,6 +5,8 @@ create table public.notes( body text ); +alter table public.notes replica identity full; + insert into realtime.subscription(subscription_id, entity, claims, filters) select seed_uuid(id), diff --git a/test/sql/test_integration_in_filter.sql b/test/sql/test_integration_in_filter.sql index 4e9216d..2608099 100644 --- a/test/sql/test_integration_in_filter.sql +++ b/test/sql/test_integration_in_filter.sql @@ -5,6 +5,8 @@ create table public.notes( body text ); +alter table public.notes replica identity full; + insert into realtime.subscription(subscription_id, entity, claims, filters) select seed_uuid(id), diff --git a/test/sql/test_integration_in_uuid_filter.sql b/test/sql/test_integration_in_uuid_filter.sql index cb4a3b7..5ac3385 100644 --- a/test/sql/test_integration_in_uuid_filter.sql +++ b/test/sql/test_integration_in_uuid_filter.sql @@ -5,6 +5,8 @@ create table public.notes( identifier uuid ); +alter table public.notes replica identity full; + insert into realtime.subscription(subscription_id, entity, claims, filters) select seed_uuid(5), diff --git a/test/sql/test_query_from_publication.sql b/test/sql/test_query_from_publication.sql index 24d0a87..9b6c7cf 100644 --- a/test/sql/test_query_from_publication.sql +++ b/test/sql/test_query_from_publication.sql @@ -49,7 +49,7 @@ select clear_wal(); insert into public.notes(id) values (1); insert into public.not_in_pub(id) values (1); -select * from pg_publication_tables; +select pubname, schemaname, tablename, attnames from pg_publication_tables; select