Skip to content

Commit c8f3092

Browse files
Merge pull request #36 from cheerfulstoic/add-errors-for-long-postgres-identifiers
fix: Add errors for when postgres identifiers would be too long
2 parents 3526724 + d871bb4 commit c8f3092

File tree

2 files changed

+160
-1
lines changed

2 files changed

+160
-1
lines changed

lib/ecto_watch/watcher_server.ex

+42-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ defmodule EctoWatch.WatcherServer do
6969
[options.schema_definition.primary_key | options.extra_columns]
7070
|> Enum.map_join(",", &"'#{&1}',row.#{&1}")
7171

72-
details = watcher_details(%{unique_label: unique_label, repo_mod: repo_mod, options: options})
72+
details =
73+
watcher_details(%{unique_label: unique_label, repo_mod: repo_mod, options: options})
74+
75+
validate_watcher_details!(details, options)
7376

7477
Ecto.Adapters.SQL.query!(
7578
repo_mod,
@@ -92,6 +95,7 @@ defmodule EctoWatch.WatcherServer do
9295
)
9396

9497
# Can't use the "OR REPLACE" syntax before postgres v13.3.4, so using DROP TRIGGER IF EXISTS
98+
# THOUGHT: Could messages be lost during the drop/re-create?
9599
Ecto.Adapters.SQL.query!(
96100
repo_mod,
97101
"""
@@ -238,6 +242,43 @@ defmodule EctoWatch.WatcherServer do
238242
}
239243
end
240244

245+
defp validate_watcher_details!(watcher_details, watcher_options) do
246+
case Ecto.Adapters.SQL.query!(watcher_details.repo_mod, "SHOW max_identifier_length", []) do
247+
%Postgrex.Result{rows: [[max_identifier_length]]} ->
248+
max_identifier_length = String.to_integer(max_identifier_length)
249+
250+
max_byte_size =
251+
max(
252+
byte_size(watcher_details.function_name),
253+
byte_size(watcher_details.trigger_name)
254+
)
255+
256+
if max_byte_size > max_identifier_length do
257+
difference = max_byte_size - max_identifier_length
258+
259+
if watcher_options.label do
260+
raise """
261+
Error for watcher: #{inspect(identifier(watcher_options))}
262+
263+
Label is #{difference} character(s) too long for the auto-generated Postgres trigger name.
264+
"""
265+
else
266+
raise """
267+
Error for watcher: #{inspect(identifier(watcher_options))}
268+
269+
Schema module name is #{difference} character(s) too long for the auto-generated Postgres trigger name.
270+
271+
You may want to use the `label` option
272+
273+
"""
274+
end
275+
end
276+
277+
other ->
278+
raise "Unexpected result when querying for max_identifier_length: #{inspect(other)}"
279+
end
280+
end
281+
241282
def topics(update_type, unique_label, returned_values, identifier_columns)
242283
when update_type in ~w[inserted updated deleted]a do
243284
[

test/ecto_watch_test.exs

+118
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,30 @@ defmodule EctoWatchTest do
5050
end
5151
end
5252

53+
defmodule ModuleWithAReallyLongName do
54+
use Ecto.Schema
55+
56+
@moduledoc """
57+
A module that *just* barely fits when creating function/trigger names
58+
"""
59+
60+
schema "a_module_with_a_really_long_name" do
61+
field(:the_string, :string)
62+
end
63+
end
64+
65+
defmodule ModuleWithJustTooLongAName do
66+
use Ecto.Schema
67+
68+
@moduledoc """
69+
A module that *just* barely fits when creating function/trigger names
70+
"""
71+
72+
schema "a_module_with_just_too_long_a_name" do
73+
field(:the_string, :string)
74+
end
75+
end
76+
5377
setup do
5478
start_supervised!(TestRepo)
5579

@@ -91,6 +115,28 @@ defmodule EctoWatchTest do
91115
[]
92116
)
93117

118+
Ecto.Adapters.SQL.query!(
119+
TestRepo,
120+
"""
121+
CREATE TABLE a_module_with_a_really_long_name (
122+
id SERIAL PRIMARY KEY,
123+
the_string TEXT
124+
)
125+
""",
126+
[]
127+
)
128+
129+
Ecto.Adapters.SQL.query!(
130+
TestRepo,
131+
"""
132+
CREATE TABLE a_module_with_just_too_long_a_name (
133+
id SERIAL PRIMARY KEY,
134+
the_string TEXT
135+
)
136+
""",
137+
[]
138+
)
139+
94140
%Postgrex.Result{
95141
rows: [[already_existing_id1]]
96142
} =
@@ -486,6 +532,78 @@ defmodule EctoWatchTest do
486532
start_supervised!({EctoWatch, repo: TestRepo, pub_sub: TestPubSub, watchers: []})
487533
end
488534

535+
test "Errors should be given if the schema module is too long for creating the trigger name" do
536+
assert_raise RuntimeError,
537+
~r/Schema module name is 1 character\(s\) too long for the auto-generated Postgres trigger/,
538+
fn ->
539+
start_supervised!(
540+
{EctoWatch,
541+
repo: TestRepo,
542+
pub_sub: TestPubSub,
543+
watchers: [
544+
{ModuleWithJustTooLongAName, :inserted}
545+
]}
546+
)
547+
end
548+
549+
# Everything works fine if you're just at the limit
550+
start_supervised!(
551+
{EctoWatch,
552+
repo: TestRepo,
553+
pub_sub: TestPubSub,
554+
watchers: [
555+
{ModuleWithAReallyLongName, :inserted}
556+
]}
557+
)
558+
559+
EctoWatch.subscribe({ModuleWithAReallyLongName, :inserted})
560+
561+
Ecto.Adapters.SQL.query!(
562+
TestRepo,
563+
"INSERT INTO a_module_with_a_really_long_name (the_string) VALUES ('the value')",
564+
[]
565+
)
566+
567+
assert_receive {{ModuleWithAReallyLongName, :inserted}, %{id: _}}
568+
end
569+
570+
test "Errors should be given if the label is too long" do
571+
assert_raise RuntimeError,
572+
~r/Label is 1 character\(s\) too long for the auto-generated Postgres trigger name/,
573+
fn ->
574+
start_supervised!(
575+
{EctoWatch,
576+
repo: TestRepo,
577+
pub_sub: TestPubSub,
578+
watchers: [
579+
{ModuleWithJustTooLongAName, :inserted,
580+
label: :the_label_is_also_just_much_too_long_such_a_shame}
581+
]}
582+
)
583+
end
584+
585+
# Everything works fine if you're just at the limit
586+
start_supervised!(
587+
{EctoWatch,
588+
repo: TestRepo,
589+
pub_sub: TestPubSub,
590+
watchers: [
591+
{ModuleWithJustTooLongAName, :inserted,
592+
label: :the_label_is_also_just_much_too_long_such_a_sham}
593+
]}
594+
)
595+
596+
EctoWatch.subscribe(:the_label_is_also_just_much_too_long_such_a_sham)
597+
598+
Ecto.Adapters.SQL.query!(
599+
TestRepo,
600+
"INSERT INTO a_module_with_just_too_long_a_name (the_string) VALUES ('the value')",
601+
[]
602+
)
603+
604+
assert_receive {:the_label_is_also_just_much_too_long_such_a_sham, %{id: _}}
605+
end
606+
489607
test "subscribe requires proper Ecto schema", %{
490608
already_existing_id1: already_existing_id1
491609
} do

0 commit comments

Comments
 (0)