Skip to content

Commit 041af9e

Browse files
authored
fix(electric): Allow creation of triggers on electrified tables (#663)
and other sundry table maintainance actions that don't affect table structure fixes #652
1 parent 51c461f commit 041af9e

File tree

3 files changed

+138
-0
lines changed

3 files changed

+138
-0
lines changed

components/electric/lib/electric/postgres/proxy/query_analyser.ex

+33
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,34 @@ end
125125
defimpl QueryAnalyser, for: PgQuery.AlterTableStmt do
126126
import QueryAnalyser.Impl
127127

128+
@allowed_subtypes [
129+
:AT_EnableAlwaysTrig,
130+
:AT_EnableTrig,
131+
:AT_EnableReplicaTrig,
132+
:AT_DisableTrig,
133+
:AT_ChangeOwner,
134+
:AT_SetStorage,
135+
:AT_SetStatistics,
136+
:AT_SetOptions,
137+
:AT_ResetOptions,
138+
:AT_SetCompression,
139+
:AT_SetRelOptions,
140+
:AT_ResetRelOptions,
141+
:AT_SetAccessMethod
142+
]
143+
144+
# actions that we maybe could support but are currently disabled:
145+
#
146+
# - DROP CONSTRAINT
147+
# - DISABLE/ENABLE ROW LEVEL SECURITY
148+
# - NO FORCE/FORCE ROW LEVEL SECURITY
149+
# - CLUSTER ON
150+
# - SET WITHOUT CLUSTER
151+
# - SET TABLESPACE
152+
#
153+
# there's a bunch of stuff to do with inheritance, but my instinct is that
154+
# allowing that would cause a lot of problems
155+
128156
def analyse(stmt, %QueryAnalysis{} = analysis, _state) do
129157
stmt.cmds
130158
|> Enum.map(&unwrap_node/1)
@@ -175,6 +203,11 @@ defimpl QueryAnalyser, for: PgQuery.AlterTableStmt do
175203
}}
176204
end
177205

206+
defp analyse_alter_table_cmd(%{subtype: subtype}, analysis)
207+
when subtype in @allowed_subtypes do
208+
{:cont, %{analysis | allowed?: analysis.allowed? && true}}
209+
end
210+
178211
defp analyse_alter_table_cmd(cmd, analysis) do
179212
{:halt, %{analysis | allowed?: false, error: error_for(cmd, analysis)}}
180213
end

components/electric/test/electric/postgres/proxy/injector_test.exs

+26
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,32 @@ defmodule Electric.Postgres.Proxy.InjectorTest do
575575
|> idle!()
576576
end
577577

578+
test "enable trigger", cxt do
579+
query1 =
580+
"create or replace function function1() returns trigger as $$ return true; $$ language pgpsql"
581+
582+
query2 =
583+
"alter table public.truths enable always trigger function1"
584+
585+
query = Enum.join([query1 <> ";", query2 <> ";"], "\n")
586+
587+
cxt.injector
588+
|> client(begin())
589+
|> server(complete_ready("BEGIN"))
590+
|> client(query(query), server: query(query1))
591+
|> server(complete_ready("CREATE FUNCTION1"), server: query(query2))
592+
|> server(complete_ready("ALTER TABLE"),
593+
client: [
594+
complete("CREATE FUNCTION1"),
595+
complete("ALTER TABLE"),
596+
ready(:tx)
597+
]
598+
)
599+
|> client(commit())
600+
|> server(complete_ready("COMMIT", :idle))
601+
|> idle!()
602+
end
603+
578604
test "drop random things", cxt do
579605
cxt.injector
580606
|> client(begin())

components/electric/test/electric/postgres/proxy/query_analyser_test.exs

+79
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,85 @@ defmodule Electric.Postgres.Proxy.QueryAnalyserTest do
820820
)
821821
end
822822

823+
test "ALTER TABLE ... {ENABLE | DISABLE} TRIGGER", cxt do
824+
stmts = [
825+
"ALTER TABLE public.truths ENABLE ALWAYS TRIGGER my_totally_valid_trigger",
826+
"ALTER TABLE public.truths ENABLE TRIGGER my_totally_valid_trigger",
827+
"ALTER TABLE public.truths ENABLE REPLICA TRIGGER my_totally_valid_trigger",
828+
"ALTER TABLE public.truths DISABLE TRIGGER my_totally_valid_trigger"
829+
]
830+
831+
for stmt <- stmts do
832+
assert [
833+
%QueryAnalysis{
834+
action: {:alter, "table"},
835+
allowed?: true,
836+
capture?: false
837+
}
838+
] = analyse(simple(stmt), cxt)
839+
end
840+
end
841+
842+
test "ALTER TABLE ... allowed variants", cxt do
843+
stmts = [
844+
"ALTER TABLE public.truths OWNER TO someone",
845+
"ALTER TABLE public.truths OWNER TO CURRENT_ROLE",
846+
"ALTER TABLE public.truths ALTER COLUMN something SET STORAGE PLAIN",
847+
"ALTER TABLE public.truths ALTER COLUMN something SET STATISTICS 3",
848+
"ALTER TABLE public.truths ALTER COLUMN something SET ( n_distinct = -1 )",
849+
"ALTER TABLE public.truths ALTER COLUMN something RESET ( n_distinct )",
850+
"ALTER TABLE public.truths ALTER COLUMN something SET STORAGE MAIN",
851+
"ALTER TABLE public.truths ALTER COLUMN something SET COMPRESSION lz4",
852+
"ALTER TABLE public.truths SET ( fillfactor = 10 )",
853+
"ALTER TABLE public.truths RESET ( fillfactor )",
854+
"ALTER TABLE public.truths SET ACCESS METHOD something"
855+
]
856+
857+
for stmt <- stmts do
858+
assert [
859+
%QueryAnalysis{
860+
action: {:alter, "table"},
861+
allowed?: true,
862+
capture?: false
863+
}
864+
] = analyse(simple(stmt), cxt)
865+
end
866+
end
867+
868+
test "ALTER TABLE ... mix of allowed and disallowed", cxt do
869+
assert [
870+
%QueryAnalysis{
871+
action: {:alter, "table"},
872+
allowed?: false,
873+
capture?: false
874+
}
875+
] =
876+
analyse(
877+
simple(
878+
"alter table public.truths " <>
879+
"enable always trigger my_totally_valid_trigger, " <>
880+
"drop column hat"
881+
),
882+
cxt
883+
)
884+
885+
assert [
886+
%QueryAnalysis{
887+
action: {:alter, "table"},
888+
allowed?: false,
889+
capture?: false
890+
}
891+
] =
892+
analyse(
893+
simple(
894+
"alter table public.truths " <>
895+
"drop column hat, " <>
896+
"enable always trigger my_totally_valid_trigger"
897+
),
898+
cxt
899+
)
900+
end
901+
823902
# TODO: how to handle invalid sql (that doesn't include electric syntax)?
824903
# ideally we'd want to forward to pg so it can give proper
825904
# error messages

0 commit comments

Comments
 (0)