@@ -33,36 +33,59 @@ defmodule Feeb.DB.Query do
3333 end
3434
3535 @ doc """
36- Compiling an adhoc query is useful when you want to select custom fields
37- off of a "select *" query. It's like a subset of the original query
36+ Compiling an adhoc query is useful when the user wants to select custom fields off of a `SELECT *`
37+ query. It's essentially a subset of the original query, with specific fields being selected.
3838 """
3939 @ spec compile_adhoc_query ( term , term ) :: no_return
40- def compile_adhoc_query ( { context , domain , query_name } = query_id , custom_fields ) do
41- raise "Deprecated; consider implementing this feature as part of `get_templated_query_id/3`"
42- query_name = :" #{ query_name } $ #{ Enum . join ( custom_fields , "$" ) } "
43- adhoc_query_id = { context , domain , query_name }
40+ def compile_adhoc_query ( { context , domain , query_name } = original_query_id , target_fields ) do
41+ model = Schema . get_model_from_query_id ( original_query_id )
42+ valid_fields = model . __cols__ ( )
43+ sorted_target_fields = Enum . sort ( target_fields )
4444
45- { sql , { fields_bindings , params_bindings } , qt } = fetch! ( query_id )
45+ adhoc_query_name = :"#{ query_name } $#{ get_query_name_suffix ( sorted_target_fields ) } "
46+ adhoc_query_id = { context , domain , adhoc_query_name }
47+
48+ { sql , { fields_bindings , params_bindings } , qt } = fetch! ( original_query_id )
4649
4750 if fields_bindings != [ :* ] do
48- raise "#{ inspect ( query_id ) } : Custom fields can only be used on 'SELECT *' queries"
51+ raise "#{ inspect ( original_query_id ) } : Custom selection can only be used on 'SELECT *' queries"
4952 end
5053
51- # "Compile" new query
52- new_sql = String . replace ( sql , "*" , Enum . join ( custom_fields , ", " ) )
53- adhoc_q = { new_sql , { custom_fields , params_bindings } , qt }
54+ Enum . each ( target_fields , fn field ->
55+ if field not in valid_fields ,
56+ do: raise ( "Can't select #{ inspect ( field ) } ; not a valid field for #{ model } " )
57+ end )
58+
59+ # "Compile" new query (replaces `SELECT *` with `SELECT <sorted_target_fields>`)
60+ new_sql = String . replace ( sql , "*" , Enum . join ( sorted_target_fields , ", " ) , global: false )
61+ adhoc_q = { new_sql , { sorted_target_fields , params_bindings } , qt }
5462
5563 append_runtime_query ( adhoc_query_id , adhoc_q )
5664 adhoc_query_id
5765 end
5866
5967 def get_templated_query_id ( query_id , target_fields , meta \\ % { } )
6068
69+ def get_templated_query_id ( { context , domain , query_name } = query_id , target_fields , _meta )
70+ when query_name in [ :__all , :__fetch ] do
71+ model = Schema . get_model_from_query_id ( query_id )
72+
73+ real_query_id = { context , domain , :"#{ query_name } $#{ get_query_name_suffix ( target_fields ) } " }
74+
75+ case get ( real_query_id ) do
76+ { _ , _ , _ } = _compiled_query ->
77+ real_query_id
78+
79+ nil ->
80+ compile_templated_query ( query_name , query_id , target_fields , model )
81+ end
82+ end
83+
6184 def get_templated_query_id ( { context , domain , :__insert } = query_id , target_fields , _meta ) do
6285 model = Schema . get_model_from_query_id ( query_id )
6386
6487 target_fields =
65- if target_fields == :all do
88+ if target_fields == [ :* ] do
6689 model . __cols__ ( )
6790 else
6891 raise "Not supported for now, add & test it once needed"
@@ -106,16 +129,15 @@ defmodule Feeb.DB.Query do
106129 end
107130 end
108131
109- def get_templated_query_id ( { _context , _domain , query_name } = query_id , target_fields , _meta )
110- when query_name in [ :__all , :__fetch , :__delete ] do
132+ def get_templated_query_id ( { _context , _domain , :__delete } = query_id , target_fields , _meta ) do
111133 model = Schema . get_model_from_query_id ( query_id )
112134
113135 case get ( query_id ) do
114136 { _ , _ , _ } = _compiled_query ->
115137 query_id
116138
117139 nil ->
118- compile_templated_query ( query_name , query_id , target_fields , model )
140+ compile_templated_query ( :__delete , query_id , target_fields , model )
119141 end
120142 end
121143
@@ -139,34 +161,32 @@ defmodule Feeb.DB.Query do
139161 primary_keys = model . __primary_keys__ ( )
140162 assert_adhoc_query! ( primary_keys , query_id , model )
141163
142- set_conditions =
143- target_fields
144- |> Enum . reduce ( [ ] , fn field , acc ->
145- [ "#{ field } = ?" | acc ]
146- end )
147- |> Enum . reverse ( )
148- |> Enum . join ( ", " )
164+ set_clause = generate_update_set_clause ( target_fields )
165+ where_clause = generate_where_clause ( primary_keys )
166+ sql = "UPDATE #{ domain } #{ set_clause } #{ where_clause } ;"
149167
150- sql = "UPDATE #{ domain } SET #{ set_conditions } #{ generate_where_clause ( primary_keys ) } ;"
151168 adhoc_query = { sql , { [ ] , target_fields ++ primary_keys } , :update }
152169 append_runtime_query ( query_id , adhoc_query )
153170
154171 query_id
155172 end
156173
157- defp compile_templated_query ( :__all , { _ , domain , _ } = query_id , _target_fields , _model ) do
158- sql = "SELECT * FROM #{ domain } ;"
159- adhoc_query = { sql , { [ :* ] , [ ] } , :select }
174+ defp compile_templated_query ( :__all , { _ , domain , _ } = query_id , target_fields , model ) do
175+ sql = "#{ generate_select_clause ( target_fields , model ) } FROM #{ domain } ;"
176+ adhoc_query = { sql , { target_fields , [ ] } , :select }
160177 append_runtime_query ( query_id , adhoc_query )
161178 query_id
162179 end
163180
164- defp compile_templated_query ( :__fetch , { _ , domain , _ } = query_id , _target_fields , model ) do
181+ defp compile_templated_query ( :__fetch , { _ , domain , _ } = query_id , target_fields , model ) do
165182 primary_keys = model . __primary_keys__ ( )
166183 assert_adhoc_query! ( primary_keys , query_id , model )
167- sql = "SELECT * FROM #{ domain } #{ generate_where_clause ( primary_keys ) } ;"
168184
169- adhoc_query = { sql , { [ :* ] , primary_keys } , :select }
185+ select_clause = generate_select_clause ( target_fields , model )
186+ where_clause = generate_where_clause ( primary_keys )
187+ sql = "#{ select_clause } FROM #{ domain } #{ where_clause } ;"
188+
189+ adhoc_query = { sql , { target_fields , primary_keys } , :select }
170190 append_runtime_query ( query_id , adhoc_query )
171191 query_id
172192 end
@@ -232,6 +252,16 @@ defmodule Feeb.DB.Query do
232252 end
233253 end
234254
255+ defp get_query_name_suffix ( target_fields ) when is_list ( target_fields ) do
256+ target_fields
257+ |> Enum . sort ( )
258+ |> Enum . reduce ( [ ] , fn field , acc ->
259+ [ "#{ field } " | acc ]
260+ end )
261+ |> Enum . reverse ( )
262+ |> Enum . join ( "$" )
263+ end
264+
235265 defp maybe_inject_returning_clause ( nil , _ ) , do: nil
236266 defp maybe_inject_returning_clause ( query , [ ] ) , do: query
237267
@@ -253,6 +283,37 @@ defmodule Feeb.DB.Query do
253283 defp get_returning_fields ( { _ , _ , operation } ) when operation in [ :update , :delete ] ,
254284 do: "*"
255285
286+ defp generate_select_clause ( [ :* ] , _ ) , do: "SELECT *"
287+
288+ defp generate_select_clause ( fields , model ) when is_list ( fields ) do
289+ valid_fields = model . __cols__ ( )
290+
291+ select_conditions =
292+ fields
293+ |> Enum . reduce ( [ ] , fn field , acc ->
294+ if field not in valid_fields ,
295+ do: raise ( "Can't select #{ inspect ( field ) } ; not a valid field for #{ model } " )
296+
297+ [ "#{ field } " | acc ]
298+ end )
299+ |> Enum . reverse ( )
300+ |> Enum . join ( ", " )
301+
302+ "SELECT #{ select_conditions } "
303+ end
304+
305+ defp generate_update_set_clause ( fields ) when is_list ( fields ) do
306+ set_conditions =
307+ fields
308+ |> Enum . reduce ( [ ] , fn field , acc ->
309+ [ "#{ field } = ?" | acc ]
310+ end )
311+ |> Enum . reverse ( )
312+ |> Enum . join ( ", " )
313+
314+ "SET #{ set_conditions } "
315+ end
316+
256317 defp generate_where_clause ( primary_keys ) when is_list ( primary_keys ) do
257318 where_conditions =
258319 primary_keys
0 commit comments