Skip to content

Commit 1c203a5

Browse files
committed
feat: squash migrations
squashes all migrations to reduce migration time
1 parent 3546c52 commit 1c203a5

File tree

69 files changed

+903
-6567
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+903
-6567
lines changed

lib/realtime/tenants/migrations.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ defmodule Realtime.Tenants.Migrations do
8181
CreateMessagesReplayIndex,
8282
BroadcastSendIncludePayloadId,
8383
AddActionToSubscriptions,
84-
FilterActionPostgresChanges
84+
FilterActionPostgresChanges,
85+
SquashMigrations
8586
}
8687

8788
@migrations [
@@ -151,7 +152,8 @@ defmodule Realtime.Tenants.Migrations do
151152
{20_250_905_041_441, CreateMessagesReplayIndex},
152153
{20_251_103_001_201, BroadcastSendIncludePayloadId},
153154
{20_251_120_212_548, AddActionToSubscriptions},
154-
{20_251_120_215_549, FilterActionPostgresChanges}
155+
{20_251_120_215_549, FilterActionPostgresChanges},
156+
{20_260_211_000_000, SquashMigrations}
155157
]
156158

157159
defstruct [:tenant_external_id, :settings, migrations_ran: 0]
Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,5 @@
11
defmodule Realtime.Tenants.Migrations.CreateRealtimeSubscriptionTable do
22
@moduledoc false
3-
43
use Ecto.Migration
5-
6-
def change do
7-
execute("""
8-
DO $$
9-
BEGIN
10-
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'equality_op') THEN
11-
CREATE TYPE realtime.equality_op AS ENUM(
12-
'eq', 'neq', 'lt', 'lte', 'gt', 'gte'
13-
);
14-
END IF;
15-
END$$;
16-
""")
17-
18-
execute("""
19-
DO $$
20-
BEGIN
21-
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'user_defined_filter') THEN
22-
CREATE TYPE realtime.user_defined_filter as (
23-
column_name text,
24-
op realtime.equality_op,
25-
value text
26-
);
27-
END IF;
28-
END$$;
29-
""")
30-
31-
execute("create table if not exists realtime.subscription (
32-
-- Tracks which users are subscribed to each table
33-
id bigint not null generated always as identity,
34-
user_id uuid not null,
35-
-- Populated automatically by trigger. Required to enable auth.email()
36-
email varchar(255),
37-
entity regclass not null,
38-
filters realtime.user_defined_filter[] not null default '{}',
39-
created_at timestamp not null default timezone('utc', now()),
40-
41-
constraint pk_subscription primary key (id),
42-
unique (entity, user_id, filters)
43-
)")
44-
45-
execute("create index if not exists ix_realtime_subscription_entity on realtime.subscription using hash (entity)")
46-
end
4+
def change, do: nil
475
end
Lines changed: 1 addition & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,5 @@
11
defmodule Realtime.Tenants.Migrations.CreateRealtimeCheckFiltersTrigger do
22
@moduledoc false
3-
43
use Ecto.Migration
5-
6-
def change do
7-
execute("create or replace function realtime.subscription_check_filters()
8-
returns trigger
9-
language plpgsql
10-
as $$
11-
/*
12-
Validates that the user defined filters for a subscription:
13-
- refer to valid columns that 'authenticated' may access
14-
- values are coercable to the correct column type
15-
*/
16-
declare
17-
col_names text[] = coalesce(
18-
array_agg(c.column_name order by c.ordinal_position),
19-
'{}'::text[]
20-
)
21-
from
22-
information_schema.columns c
23-
where
24-
(quote_ident(c.table_schema) || '.' || quote_ident(c.table_name))::regclass = new.entity
25-
and pg_catalog.has_column_privilege('authenticated', new.entity, c.column_name, 'SELECT');
26-
filter realtime.user_defined_filter;
27-
col_type text;
28-
begin
29-
for filter in select * from unnest(new.filters) loop
30-
-- Filtered column is valid
31-
if not filter.column_name = any(col_names) then
32-
raise exception 'invalid column for filter %', filter.column_name;
33-
end if;
34-
35-
-- Type is sanitized and safe for string interpolation
36-
col_type = (
37-
select atttypid::regtype
38-
from pg_catalog.pg_attribute
39-
where attrelid = new.entity
40-
and attname = filter.column_name
41-
)::text;
42-
if col_type is null then
43-
raise exception 'failed to lookup type for column %', filter.column_name;
44-
end if;
45-
-- raises an exception if value is not coercable to type
46-
perform format('select %s::%I', filter.value, col_type);
47-
end loop;
48-
49-
-- Apply consistent order to filters so the unique constraint on
50-
-- (user_id, entity, filters) can't be tricked by a different filter order
51-
new.filters = coalesce(
52-
array_agg(f order by f.column_name, f.op, f.value),
53-
'{}'
54-
) from unnest(new.filters) f;
55-
56-
return new;
57-
end;
58-
$$;")
59-
60-
execute("create trigger tr_check_filters
61-
before insert or update on realtime.subscription
62-
for each row
63-
execute function realtime.subscription_check_filters();")
64-
end
4+
def change, do: nil
655
end
Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,5 @@
11
defmodule Realtime.Tenants.Migrations.CreateRealtimeQuoteWal2jsonFunction do
22
@moduledoc false
3-
43
use Ecto.Migration
5-
6-
def change do
7-
execute("create function realtime.quote_wal2json(entity regclass)
8-
returns text
9-
language sql
10-
immutable
11-
strict
12-
as $$
13-
select
14-
(
15-
select string_agg('\' || ch,'')
16-
from unnest(string_to_array(nsp.nspname::text, null)) with ordinality x(ch, idx)
17-
where
18-
not (x.idx = 1 and x.ch = '\"')
19-
and not (
20-
x.idx = array_length(string_to_array(nsp.nspname::text, null), 1)
21-
and x.ch = '\"'
22-
)
23-
)
24-
|| '.'
25-
|| (
26-
select string_agg('\' || ch,'')
27-
from unnest(string_to_array(pc.relname::text, null)) with ordinality x(ch, idx)
28-
where
29-
not (x.idx = 1 and x.ch = '\"')
30-
and not (
31-
x.idx = array_length(string_to_array(nsp.nspname::text, null), 1)
32-
and x.ch = '\"'
33-
)
34-
)
35-
from
36-
pg_class pc
37-
join pg_namespace nsp
38-
on pc.relnamespace = nsp.oid
39-
where
40-
pc.oid = entity
41-
$$;")
42-
end
4+
def change, do: nil
435
end
Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,5 @@
11
defmodule Realtime.Tenants.Migrations.CreateRealtimeCheckEqualityOpFunction do
22
@moduledoc false
3-
43
use Ecto.Migration
5-
6-
def change do
7-
execute("create function realtime.check_equality_op(
8-
op realtime.equality_op,
9-
type_ regtype,
10-
val_1 text,
11-
val_2 text
12-
)
13-
returns bool
14-
immutable
15-
language plpgsql
16-
as $$
17-
/*
18-
Casts *val_1* and *val_2* as type *type_* and check the *op* condition for truthiness
19-
*/
20-
declare
21-
op_symbol text = (
22-
case
23-
when op = 'eq' then '='
24-
when op = 'neq' then '!='
25-
when op = 'lt' then '<'
26-
when op = 'lte' then '<='
27-
when op = 'gt' then '>'
28-
when op = 'gte' then '>='
29-
else 'UNKNOWN OP'
30-
end
31-
);
32-
res boolean;
33-
begin
34-
execute format('select %L::'|| type_::text || ' ' || op_symbol || ' %L::'|| type_::text, val_1, val_2) into res;
35-
return res;
36-
end;
37-
$$;")
38-
end
4+
def change, do: nil
395
end
Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,5 @@
11
defmodule Realtime.Tenants.Migrations.CreateRealtimeBuildPreparedStatementSqlFunction do
22
@moduledoc false
3-
43
use Ecto.Migration
5-
6-
def change do
7-
execute("""
8-
DO $$
9-
BEGIN
10-
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'wal_column') THEN
11-
CREATE TYPE realtime.wal_column AS (
12-
name text,
13-
type text,
14-
value jsonb,
15-
is_pkey boolean,
16-
is_selectable boolean
17-
);
18-
END IF;
19-
END$$;
20-
""")
21-
22-
execute("create function realtime.build_prepared_statement_sql(
23-
prepared_statement_name text,
24-
entity regclass,
25-
columns realtime.wal_column[]
26-
)
27-
returns text
28-
language sql
29-
as $$
30-
/*
31-
Builds a sql string that, if executed, creates a prepared statement to
32-
tests retrive a row from *entity* by its primary key columns.
33-
34-
Example
35-
select realtime.build_prepared_statment_sql('public.notes', '{\"id\"}'::text[], '{\"bigint\"}'::text[])
36-
*/
37-
select
38-
'prepare ' || prepared_statement_name || ' as
39-
select
40-
exists(
41-
select
42-
1
43-
from
44-
' || entity || '
45-
where
46-
' || string_agg(quote_ident(pkc.name) || '=' || quote_nullable(pkc.value) , ' and ') || '
47-
)'
48-
from
49-
unnest(columns) pkc
50-
where
51-
pkc.is_pkey
52-
group by
53-
entity
54-
$$;")
55-
end
4+
def change, do: nil
565
end
Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,5 @@
11
defmodule Realtime.Tenants.Migrations.CreateRealtimeCastFunction do
22
@moduledoc false
3-
43
use Ecto.Migration
5-
6-
def change do
7-
execute("create function realtime.cast(val text, type_ regtype)
8-
returns jsonb
9-
immutable
10-
language plpgsql
11-
as $$
12-
declare
13-
res jsonb;
14-
begin
15-
execute format('select to_jsonb(%L::'|| type_::text || ')', val) into res;
16-
return res;
17-
end
18-
$$;")
19-
end
4+
def change, do: nil
205
end
Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,5 @@
11
defmodule Realtime.Tenants.Migrations.CreateRealtimeIsVisibleThroughFiltersFunction do
22
@moduledoc false
3-
43
use Ecto.Migration
5-
6-
def change do
7-
execute(
8-
"create function realtime.is_visible_through_filters(columns realtime.wal_column[], filters realtime.user_defined_filter[])
9-
returns bool
10-
language sql
11-
immutable
12-
as $$
13-
/*
14-
Should the record be visible (true) or filtered out (false) after *filters* are applied
15-
*/
16-
select
17-
-- Default to allowed when no filters present
18-
coalesce(
19-
sum(
20-
realtime.check_equality_op(
21-
op:=f.op,
22-
type_:=col.type::regtype,
23-
-- cast jsonb to text
24-
val_1:=col.value #>> '{}',
25-
val_2:=f.value
26-
)::int
27-
) = count(1),
28-
true
29-
)
30-
from
31-
unnest(filters) f
32-
join unnest(columns) col
33-
on f.column_name = col.name;
34-
$$;"
35-
)
36-
end
4+
def change, do: nil
375
end

0 commit comments

Comments
 (0)