Skip to content

Commit c45b336

Browse files
authored
fix: Support non-public PostgreSQL schemas in resource generator (#631)
- Add schema field to generated resources when table is in non-public schema - Fix SQL queries to use schema-qualified table names for foreign keys and constraints - Update index queries to respect actual schema instead of hardcoded 'public' - Add test coverage for tables in custom schemas with foreign keys and indexes * fix: guard against missing snapshot directories in migration generator Fixes crash when generating migrations for resources in non-public schemas where the snapshot directory doesn't exist yet. * chore: run mix format
1 parent 7d4d4a4 commit c45b336

File tree

4 files changed

+161
-11
lines changed

4 files changed

+161
-11
lines changed

lib/migration_generator/migration_generator.ex

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -649,14 +649,19 @@ defmodule AshPostgres.MigrationGenerator do
649649
folder = get_snapshot_folder(snapshot, opts)
650650
snapshot_path = get_snapshot_path(snapshot, folder)
651651

652-
snapshot_path
653-
|> File.ls!()
654-
|> Enum.filter(&String.contains?(&1, "_dev.json"))
655-
|> Enum.each(fn snapshot_name ->
652+
# Guard against missing directories - can happen for new resources or when
653+
# get_snapshot_path's fallback logic returns a non-existent path for
654+
# resources in non-public schemas
655+
if File.dir?(snapshot_path) do
656656
snapshot_path
657-
|> Path.join(snapshot_name)
658-
|> File.rm!()
659-
end)
657+
|> File.ls!()
658+
|> Enum.filter(&String.contains?(&1, "_dev.json"))
659+
|> Enum.each(fn snapshot_name ->
660+
snapshot_path
661+
|> Path.join(snapshot_name)
662+
|> File.rm!()
663+
end)
664+
end
660665
end)
661666
end
662667

lib/resource_generator/resource_generator.ex

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ if Code.ensure_loaded?(Igniter) do
115115
postgres do
116116
table #{inspect(table_spec.table_name)}
117117
repo #{inspect(table_spec.repo)}
118+
#{schema_option(table_spec)}
118119
#{no_migrate_flag}
119120
#{references(table_spec, opts[:no_migrations])}
120121
#{custom_indexes(table_spec, opts[:no_migrations])}
@@ -144,6 +145,12 @@ if Code.ensure_loaded?(Igniter) do
144145
end)
145146
end
146147

148+
defp schema_option(%{schema: schema}) when schema != "public" do
149+
"schema #{inspect(schema)}"
150+
end
151+
152+
defp schema_option(_), do: ""
153+
147154
defp default_actions(opts) do
148155
cond do
149156
opts[:default_actions] && opts[:public] ->

lib/resource_generator/spec.ex

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,13 @@ defmodule AshPostgres.ResourceGenerator.Spec do
122122
result
123123
end
124124

125+
defp qualified_table_name(%{schema: schema, table_name: table_name}) do
126+
"#{schema}.#{table_name}"
127+
end
128+
125129
defp add_foreign_keys(spec) do
130+
qualified_table = qualified_table_name(spec)
131+
126132
%Postgrex.Result{rows: fkey_rows} =
127133
spec.repo.query!(
128134
"""
@@ -178,7 +184,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do
178184
constraints.update_rule,
179185
constraints.delete_rule
180186
""",
181-
[spec.table_name, spec.schema],
187+
[qualified_table, spec.schema],
182188
log: false
183189
)
184190

@@ -218,6 +224,8 @@ defmodule AshPostgres.ResourceGenerator.Spec do
218224
end
219225

220226
defp add_check_constraints(spec) do
227+
qualified_table = qualified_table_name(spec)
228+
221229
%Postgrex.Result{rows: check_constraint_rows} =
222230
spec.repo.query!(
223231
"""
@@ -230,7 +238,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do
230238
contype = 'c'
231239
AND conrelid::regclass::text = $1
232240
""",
233-
[spec.table_name],
241+
[qualified_table],
234242
log: false
235243
)
236244

@@ -278,7 +286,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do
278286
LEFT JOIN
279287
pg_constraint c ON c.conindid = ix.indexrelid AND c.contype = 'p'
280288
JOIN
281-
pg_indexes idx ON idx.indexname = i.relname AND idx.schemaname = 'public' -- Adjust schema name if necessary
289+
pg_indexes idx ON idx.indexname = i.relname AND idx.schemaname = $2
282290
JOIN information_schema.tables ta
283291
ON ta.table_name = t.relname
284292
WHERE
@@ -312,7 +320,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do
312320
LEFT JOIN
313321
pg_constraint c ON c.conindid = ix.indexrelid AND c.contype = 'p'
314322
JOIN
315-
pg_indexes idx ON idx.indexname = i.relname AND idx.schemaname = 'public' -- Adjust schema name if necessary
323+
pg_indexes idx ON idx.indexname = i.relname AND idx.schemaname = $2
316324
JOIN information_schema.tables ta
317325
ON ta.table_name = t.relname
318326
WHERE

test/resource_generator_test.exs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,134 @@ defmodule AshPostgres.ResourceGeenratorTests do
6262
end
6363
""")
6464
end
65+
66+
test "a resource is generated from a table in a non-public schema with foreign keys and indexes" do
67+
AshPostgres.TestRepo.query!("CREATE SCHEMA IF NOT EXISTS inventory")
68+
69+
AshPostgres.TestRepo.query!("DROP TABLE IF EXISTS inventory.products CASCADE")
70+
AshPostgres.TestRepo.query!("DROP TABLE IF EXISTS inventory.warehouses CASCADE")
71+
72+
AshPostgres.TestRepo.query!("""
73+
CREATE TABLE inventory.warehouses (
74+
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
75+
name VARCHAR(255) NOT NULL,
76+
location VARCHAR(255)
77+
)
78+
""")
79+
80+
AshPostgres.TestRepo.query!("CREATE INDEX warehouses_name_idx ON inventory.warehouses(name)")
81+
82+
AshPostgres.TestRepo.query!("""
83+
CREATE TABLE inventory.products (
84+
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
85+
name VARCHAR(255) NOT NULL,
86+
warehouse_id UUID REFERENCES inventory.warehouses(id) ON DELETE CASCADE,
87+
quantity INTEGER
88+
)
89+
""")
90+
91+
AshPostgres.TestRepo.query!(
92+
"CREATE INDEX products_warehouse_id_idx ON inventory.products(warehouse_id)"
93+
)
94+
95+
test_project()
96+
|> Igniter.compose_task("ash_postgres.gen.resources", [
97+
"MyApp.Inventory",
98+
"--tables",
99+
"inventory.warehouses,inventory.products",
100+
"--yes",
101+
"--repo",
102+
"AshPostgres.TestRepo"
103+
])
104+
|> assert_creates("lib/my_app/inventory/warehouse.ex", """
105+
defmodule MyApp.Inventory.Warehouse do
106+
use Ash.Resource,
107+
domain: MyApp.Inventory,
108+
data_layer: AshPostgres.DataLayer
109+
110+
actions do
111+
defaults([:read, :destroy, create: :*, update: :*])
112+
end
113+
114+
postgres do
115+
table("warehouses")
116+
repo(AshPostgres.TestRepo)
117+
schema("inventory")
118+
end
119+
120+
attributes do
121+
uuid_primary_key :id do
122+
public?(true)
123+
end
124+
125+
attribute :name, :string do
126+
allow_nil?(false)
127+
public?(true)
128+
end
129+
130+
attribute :location, :string do
131+
public?(true)
132+
end
133+
end
134+
135+
relationships do
136+
has_many :products, MyApp.Inventory.Product do
137+
public?(true)
138+
end
139+
end
140+
end
141+
""")
142+
|> assert_creates("lib/my_app/inventory/product.ex", """
143+
defmodule MyApp.Inventory.Product do
144+
use Ash.Resource,
145+
domain: MyApp.Inventory,
146+
data_layer: AshPostgres.DataLayer
147+
148+
actions do
149+
defaults([:read, :destroy, create: :*, update: :*])
150+
end
151+
152+
postgres do
153+
table("products")
154+
repo(AshPostgres.TestRepo)
155+
schema("inventory")
156+
157+
references do
158+
reference :warehouse do
159+
on_delete(:delete)
160+
end
161+
end
162+
end
163+
164+
attributes do
165+
uuid_primary_key :id do
166+
public?(true)
167+
end
168+
169+
uuid_primary_key :id do
170+
public?(true)
171+
end
172+
173+
attribute :name, :string do
174+
public?(true)
175+
end
176+
177+
attribute :name, :string do
178+
allow_nil?(false)
179+
public?(true)
180+
end
181+
182+
attribute :quantity, :integer do
183+
public?(true)
184+
end
185+
end
186+
187+
relationships do
188+
belongs_to :warehouse, MyApp.Inventory.Warehouse do
189+
public?(true)
190+
end
191+
end
192+
end
193+
""")
194+
end
65195
end

0 commit comments

Comments
 (0)