@@ -42,7 +42,8 @@ defmodule Ecto.Query.Builder.Join do
42
42
{:x, {:{}, [], [:fragment, [], [raw: "foo"]]}, nil, nil, []}
43
43
44
44
"""
45
- @ spec escape ( Macro . t , Keyword . t , Macro.Env . t ) :: { atom , Macro . t | nil , Macro . t | nil , list }
45
+ @ spec escape ( Macro . t ( ) , Keyword . t ( ) , Macro.Env . t ( ) ) ::
46
+ { atom , Macro . t ( ) | nil , Macro . t ( ) | nil , list }
46
47
def escape ( { :in , _ , [ { var , _ , context } , expr ] } , vars , env )
47
48
when is_atom ( var ) and is_atom ( context ) do
48
49
{ _ , expr , assoc , prelude , params } = escape ( expr , vars , env )
@@ -76,14 +77,14 @@ defmodule Ecto.Query.Builder.Join do
76
77
{ :_ , { string , schema } , nil , nil , [ ] }
77
78
78
79
_ ->
79
- Builder . error! "malformed join `#{ Macro . to_string ( join ) } ` in query expression"
80
+ Builder . error! ( "malformed join `#{ Macro . to_string ( join ) } ` in query expression" )
80
81
end
81
82
end
82
83
83
84
def escape ( { :assoc , _ , [ { var , _ , context } , field ] } , vars , _env )
84
85
when is_atom ( var ) and is_atom ( context ) do
85
86
ensure_field! ( field )
86
- var = Builder . find_var! ( var , vars )
87
+ var = Builder . find_var! ( var , vars )
87
88
field = Builder . quoted_atom! ( field , "field/2" )
88
89
{ :_ , nil , { var , field } , nil , [ ] }
89
90
end
@@ -103,7 +104,8 @@ defmodule Ecto.Query.Builder.Join do
103
104
def escape ( join , vars , env ) do
104
105
case Macro . expand ( join , env ) do
105
106
^ join ->
106
- Builder . error! "malformed join `#{ Macro . to_string ( join ) } ` in query expression"
107
+ Builder . error! ( "malformed join `#{ Macro . to_string ( join ) } ` in query expression" )
108
+
107
109
join ->
108
110
escape ( join , vars , env )
109
111
end
@@ -114,10 +116,13 @@ defmodule Ecto.Query.Builder.Join do
114
116
"""
115
117
def join! ( expr ) when is_atom ( expr ) ,
116
118
do: { nil , expr }
119
+
117
120
def join! ( expr ) when is_binary ( expr ) ,
118
121
do: { expr , nil }
122
+
119
123
def join! ( { source , module } ) when is_binary ( source ) and is_atom ( module ) ,
120
124
do: { source , module }
125
+
121
126
def join! ( expr ) ,
122
127
do: Ecto.Queryable . to_query ( expr )
123
128
@@ -128,8 +133,19 @@ defmodule Ecto.Query.Builder.Join do
128
133
If possible, it does all calculations at compile time to avoid
129
134
runtime work.
130
135
"""
131
- @ spec build ( Macro . t , atom , [ Macro . t ] , Macro . t , Macro . t , Macro . t , atom , nil | { :ok , Ecto.Schema . prefix } , nil | String . t | [ String . t ] , Macro.Env . t ) ::
132
- { Macro . t , Keyword . t , non_neg_integer | nil }
136
+ @ spec build (
137
+ Macro . t ( ) ,
138
+ atom ,
139
+ [ Macro . t ( ) ] ,
140
+ Macro . t ( ) ,
141
+ Macro . t ( ) ,
142
+ Macro . t ( ) ,
143
+ atom ,
144
+ nil | { :ok , Ecto.Schema . prefix ( ) } ,
145
+ nil | String . t ( ) | [ String . t ( ) ] ,
146
+ Macro.Env . t ( )
147
+ ) ::
148
+ { Macro . t ( ) , Keyword . t ( ) , non_neg_integer | nil }
133
149
def build ( query , qual , binding , expr , count_bind , on , as , prefix , maybe_hints , env ) do
134
150
{ :ok , prefix } = prefix || { :ok , nil }
135
151
hints = List . wrap ( maybe_hints )
@@ -141,18 +157,36 @@ defmodule Ecto.Query.Builder.Join do
141
157
)
142
158
end
143
159
144
- prefix = case prefix do
145
- nil -> nil
146
- prefix when is_binary ( prefix ) -> prefix
147
- { :^ , _ , [ prefix ] } -> prefix
148
- prefix -> Builder . error! ( "`prefix` must be a compile time string or an interpolated value using ^, got: #{ Macro . to_string ( prefix ) } " )
149
- end
160
+ prefix =
161
+ case prefix do
162
+ nil ->
163
+ nil
150
164
151
- as = case as do
152
- { :^ , _ , [ as ] } -> as
153
- as when is_atom ( as ) -> as
154
- as -> Builder . error! ( "`as` must be a compile time atom or an interpolated value using ^, got: #{ Macro . to_string ( as ) } " )
155
- end
165
+ prefix when is_binary ( prefix ) ->
166
+ prefix
167
+
168
+ { :^ , _ , [ prefix ] } ->
169
+ prefix
170
+
171
+ prefix ->
172
+ Builder . error! (
173
+ "`prefix` must be a compile time string or an interpolated value using ^, got: #{ Macro . to_string ( prefix ) } "
174
+ )
175
+ end
176
+
177
+ as =
178
+ case as do
179
+ { :^ , _ , [ as ] } ->
180
+ as
181
+
182
+ as when is_atom ( as ) ->
183
+ as
184
+
185
+ as ->
186
+ Builder . error! (
187
+ "`as` must be a compile time atom or an interpolated value using ^, got: #{ Macro . to_string ( as ) } "
188
+ )
189
+ end
156
190
157
191
{ query , binding } = Builder . escape_binding ( query , binding , env )
158
192
{ join_bind , join_source , join_assoc , join_prelude , join_params } = escape ( expr , binding , env )
@@ -163,11 +197,14 @@ defmodule Ecto.Query.Builder.Join do
163
197
164
198
{ count_bind , query } =
165
199
if is_nil ( count_bind ) do
200
+ var = Macro . unique_var ( :query , __MODULE__ )
201
+
166
202
query =
167
203
quote do
168
- Ecto.Queryable . to_query ( unquote ( query ) )
204
+ unquote ( var ) = Ecto.Queryable . to_query ( unquote ( query ) )
169
205
end
170
- { quote ( do: Builder . count_binds ( unquote ( query ) ) ) , query }
206
+
207
+ { quote ( do: Builder . count_binds ( unquote ( var ) ) ) , query }
171
208
else
172
209
{ count_bind , query }
173
210
end
@@ -252,10 +289,11 @@ defmodule Ecto.Query.Builder.Join do
252
289
253
290
defp ensure_on ( on , _assoc , _qual , _source , _env ) when on != nil , do: on
254
291
255
- defp ensure_on ( nil , _assoc = nil , qual , source , env ) when qual not in [ :cross , :cross_lateral ] do
292
+ defp ensure_on ( nil , _assoc = nil , qual , source , env )
293
+ when qual not in [ :cross , :cross_lateral ] do
256
294
maybe_source =
257
295
with { source , alias } <- source ,
258
- source when source != nil <- source || alias do
296
+ source when source != nil <- source || alias do
259
297
" on #{ inspect ( source ) } "
260
298
else
261
299
_ -> ""
@@ -275,6 +313,7 @@ defmodule Ecto.Query.Builder.Join do
275
313
def apply ( % Ecto.Query { joins: joins } = query , expr , nil , _count_bind ) do
276
314
% { query | joins: joins ++ [ expr ] }
277
315
end
316
+
278
317
def apply ( % Ecto.Query { joins: joins , aliases: aliases } = query , expr , as , count_bind ) do
279
318
aliases =
280
319
case aliases do
@@ -284,6 +323,7 @@ defmodule Ecto.Query.Builder.Join do
284
323
285
324
% { query | joins: joins ++ [ expr ] , aliases: aliases }
286
325
end
326
+
287
327
def apply ( query , expr , as , count_bind ) do
288
328
apply ( Ecto.Queryable . to_query ( query ) , expr , as , count_bind )
289
329
end
@@ -295,20 +335,24 @@ defmodule Ecto.Query.Builder.Join do
295
335
296
336
def runtime_aliases ( aliases , name , join_count ) when is_integer ( join_count ) do
297
337
if Map . has_key? ( aliases , name ) do
298
- Builder . error! "alias `#{ inspect name } ` already exists"
338
+ Builder . error! ( "alias `#{ inspect ( name ) } ` already exists" )
299
339
else
300
340
Map . put ( aliases , name , join_count )
301
341
end
302
342
end
303
343
304
344
defp compile_aliases ( { :%{} , meta , aliases } , name , join_count )
305
345
when is_atom ( name ) and is_integer ( join_count ) do
306
- { :%{} , meta , aliases |> Map . new |> runtime_aliases ( name , join_count ) |> Map . to_list }
346
+ { :%{} , meta , aliases |> Map . new ( ) |> runtime_aliases ( name , join_count ) |> Map . to_list ( ) }
307
347
end
308
348
309
349
defp compile_aliases ( aliases , name , join_count ) do
310
350
quote do
311
- Ecto.Query.Builder.Join . runtime_aliases ( unquote ( aliases ) , unquote ( name ) , unquote ( join_count ) )
351
+ Ecto.Query.Builder.Join . runtime_aliases (
352
+ unquote ( aliases ) ,
353
+ unquote ( name ) ,
354
+ unquote ( join_count )
355
+ )
312
356
end
313
357
end
314
358
@@ -319,9 +363,20 @@ defmodule Ecto.Query.Builder.Join do
319
363
# join without expanded :on is built and applied to the query,
320
364
# so that expansion of dynamic :on accounts for the new binding
321
365
{ on_expr , on_params , on_file , on_line } =
322
- Ecto.Query.Builder.Filter . filter! ( :on , apply ( query , join , as , count_bind ) , expr , count_bind , file , line )
366
+ Ecto.Query.Builder.Filter . filter! (
367
+ :on ,
368
+ apply ( query , join , as , count_bind ) ,
369
+ expr ,
370
+ count_bind ,
371
+ file ,
372
+ line
373
+ )
374
+
375
+ join = % {
376
+ join
377
+ | on: % QueryExpr { expr: on_expr , params: on_params , line: on_line , file: on_file }
378
+ }
323
379
324
- join = % { join | on: % QueryExpr { expr: on_expr , params: on_params , line: on_line , file: on_file } }
325
380
apply ( query , join , as , count_bind )
326
381
end
327
382
@@ -335,24 +390,37 @@ defmodule Ecto.Query.Builder.Join do
335
390
336
391
defp validate_bind ( bind , all ) do
337
392
if bind != :_ and bind in all do
338
- Builder . error! "variable `#{ bind } ` is already defined in query"
393
+ Builder . error! ( "variable `#{ bind } ` is already defined in query" )
339
394
end
340
395
end
341
396
342
- @ qualifiers [ :inner , :inner_lateral , :left , :left_lateral , :right , :full , :cross , :cross_lateral ]
397
+ @ qualifiers [
398
+ :inner ,
399
+ :inner_lateral ,
400
+ :left ,
401
+ :left_lateral ,
402
+ :right ,
403
+ :full ,
404
+ :cross ,
405
+ :cross_lateral
406
+ ]
343
407
344
408
@ doc """
345
409
Called at runtime to check dynamic qualifier.
346
410
"""
347
411
def qual! ( qual ) when qual in @ qualifiers , do: qual
412
+
348
413
def qual! ( qual ) do
349
414
raise ArgumentError ,
350
- "invalid join qualifier `#{ inspect qual } `, accepted qualifiers are: " <>
351
- Enum . map_join ( @ qualifiers , ", " , & "`#{ inspect & 1 } `" )
415
+ "invalid join qualifier `#{ inspect ( qual ) } `, accepted qualifiers are: " <>
416
+ Enum . map_join ( @ qualifiers , ", " , & "`#{ inspect ( & 1 ) } `" )
352
417
end
353
418
354
419
defp ensure_field! ( { var , _ , _ } ) when var != :^ do
355
- Builder . error! "you passed the variable `#{ var } ` to `assoc/2`. Did you mean to pass the atom `:#{ var } `?"
420
+ Builder . error! (
421
+ "you passed the variable `#{ var } ` to `assoc/2`. Did you mean to pass the atom `:#{ var } `?"
422
+ )
356
423
end
424
+
357
425
defp ensure_field! ( _ ) , do: true
358
426
end
0 commit comments