@@ -10,7 +10,7 @@ use std::collections::HashMap;
1010use std:: time:: Instant ;
1111
1212use clap:: { Arg , ArgMatches , Command } ;
13- use log:: { debug, info} ;
13+ use log:: { debug, info, warn } ;
1414
1515use crate :: commands:: base:: CommandRunner ;
1616use crate :: commands:: common_args:: {
@@ -182,85 +182,62 @@ fn run_build(
182182 ( runner. get_queries ( resource, & full_context) , None )
183183 } ;
184184
185- // Provisioning queries for resource/multi types
186- let mut create_query : Option < String > = None ;
187- let mut create_retries = 1u32 ;
188- let mut create_retry_delay = 0u32 ;
189- let mut update_query : Option < String > = None ;
190- let mut update_retries = 1u32 ;
191- let mut update_retry_delay = 0u32 ;
192- let mut has_createorupdate = false ;
185+ // Detect anchor presence and extract retry options (no rendering yet).
186+ // All query rendering is deferred to the point of use (JIT) because
187+ // exists may capture this.* fields needed by downstream queries.
188+ let has_createorupdate = resource_queries . contains_key ( "createorupdate" ) ;
189+ let create_retries ;
190+ let create_retry_delay ;
191+ let update_retries ;
192+ let update_retry_delay ;
193193
194194 if res_type == "resource" || res_type == "multi" {
195- if let Some ( cou) = resource_queries. get ( "createorupdate" ) {
196- has_createorupdate = true ;
197- let rendered = runner. render_query (
198- & resource. name ,
199- "createorupdate" ,
200- & cou. template ,
201- & full_context,
202- ) ;
203- create_query = Some ( rendered. clone ( ) ) ;
195+ if has_createorupdate {
196+ let cou = resource_queries. get ( "createorupdate" ) . unwrap ( ) ;
204197 create_retries = cou. options . retries ;
205198 create_retry_delay = cou. options . retry_delay ;
206- update_query = Some ( rendered) ;
207199 update_retries = cou. options . retries ;
208200 update_retry_delay = cou. options . retry_delay ;
209201 } else {
210202 if let Some ( cq) = resource_queries. get ( "create" ) {
211- create_query = Some ( runner. render_query (
212- & resource. name ,
213- "create" ,
214- & cq. template ,
215- & full_context,
216- ) ) ;
217203 create_retries = cq. options . retries ;
218204 create_retry_delay = cq. options . retry_delay ;
205+ } else {
206+ catch_error_and_exit (
207+ "iql file must include either 'create' or 'createorupdate' anchor." ,
208+ ) ;
219209 }
220210 if let Some ( uq) = resource_queries. get ( "update" ) {
221- update_query = Some ( runner. render_query (
222- & resource. name ,
223- "update" ,
224- & uq. template ,
225- & full_context,
226- ) ) ;
227211 update_retries = uq. options . retries ;
228212 update_retry_delay = uq. options . retry_delay ;
213+ } else {
214+ update_retries = 1 ;
215+ update_retry_delay = 0 ;
229216 }
230217 }
231-
232- if create_query. is_none ( ) {
233- catch_error_and_exit (
234- "iql file must include either 'create' or 'createorupdate' anchor." ,
235- ) ;
236- }
218+ } else {
219+ create_retries = 1 ;
220+ create_retry_delay = 0 ;
221+ update_retries = 1 ;
222+ update_retry_delay = 0 ;
237223 }
238224
239- // Render the exists query eagerly (it never depends on this.* fields)
225+ // Render exists eagerly (it never depends on this.* fields)
240226 let exists_query = resource_queries. get ( "exists" ) . map ( |q| {
241227 let rendered =
242228 runner. render_query ( & resource. name , "exists" , & q. template , & full_context) ;
243229 ( rendered, q. options . clone ( ) )
244230 } ) ;
245231
246- // Statecheck and exports rendering is deferred until after the exists
247- // check runs, because the exists query may capture fields (e.g.
248- // `identifier`) that should be available as {{ this.<field> }} in
249- // subsequent queries.
250232 let mut full_context = full_context;
251233 let exports_opts = resource_queries. get ( "exports" ) ;
252234 let exports_retries = exports_opts. map_or ( 1 , |q| q. options . retries ) ;
253235 let exports_retry_delay = exports_opts. map_or ( 0 , |q| q. options . retry_delay ) ;
254236
255- // Only eagerly render exports if there's no exists query; otherwise
256- // defer until after exists has captured this.* fields.
257- let mut exports_query_str: Option < String > = if resource_queries. contains_key ( "exists" ) {
258- None // will be rendered after exists check injects this.* fields
259- } else {
260- resource_queries
261- . get ( "exports" )
262- . map ( |q| runner. render_query ( & resource. name , "exports" , & q. template , & full_context) )
263- } ;
237+ // All other queries (create, update, statecheck, exports) are rendered
238+ // JIT at the point of use, after exists has had a chance to capture
239+ // this.* fields into full_context.
240+ let mut exports_query_str: Option < String > = None ;
264241
265242 // Handle query type with no exports
266243 if res_type == "query" && exports_query_str. is_none ( ) {
@@ -302,12 +279,12 @@ fn run_build(
302279 } else if resource_queries. contains_key ( "statecheck" ) {
303280 // Flow 1: Traditional flow when statecheck exists
304281 if let Some ( ref eq) = exists_query {
305- let eq_opts = resource_queries . get ( "exists" ) . unwrap ( ) ;
282+ // Pre-create: fast fail (1 attempt, no delay)
306283 let ( exists, fields) = runner. check_if_resource_exists (
307284 resource,
308285 & eq. 0 ,
309- eq_opts . options . retries ,
310- eq_opts . options . retry_delay ,
286+ 1 ,
287+ 0 ,
311288 dry_run,
312289 show_queries,
313290 false ,
@@ -398,12 +375,12 @@ fn run_build(
398375 exports_result_from_proxy = None ;
399376
400377 if let Some ( ref eq) = exists_query {
401- let eq_opts = resource_queries . get ( "exists" ) . unwrap ( ) ;
378+ // Pre-create: fast fail (1 attempt, no delay)
402379 let ( exists, fields) = runner. check_if_resource_exists (
403380 resource,
404381 & eq. 0 ,
405- eq_opts . options . retries ,
406- eq_opts . options . retry_delay ,
382+ 1 ,
383+ 0 ,
407384 dry_run,
408385 show_queries,
409386 false ,
@@ -424,12 +401,12 @@ fn run_build(
424401 }
425402 } else if let Some ( ref eq) = exists_query {
426403 // Flow 3: exists query only (no statecheck rendered yet)
427- let eq_opts = resource_queries . get ( "exists" ) . unwrap ( ) ;
404+ // Pre-create: fast fail (1 attempt, no delay)
428405 let ( exists, fields) = runner. check_if_resource_exists (
429406 resource,
430407 & eq. 0 ,
431- eq_opts . options . retries ,
432- eq_opts . options . retry_delay ,
408+ 1 ,
409+ 0 ,
433410 dry_run,
434411 show_queries,
435412 false ,
@@ -479,9 +456,23 @@ fn run_build(
479456 let mut is_created_or_updated = false ;
480457
481458 if !resource_exists {
459+ // JIT render create/createorupdate query
460+ let create_query = if has_createorupdate {
461+ let cou = resource_queries. get ( "createorupdate" ) . unwrap ( ) ;
462+ runner. render_query (
463+ & resource. name ,
464+ "createorupdate" ,
465+ & cou. template ,
466+ & full_context,
467+ )
468+ } else {
469+ let cq = resource_queries. get ( "create" ) . unwrap ( ) ;
470+ runner. render_query ( & resource. name , "create" , & cq. template , & full_context)
471+ } ;
472+
482473 let ( created, returning_row) = runner. create_resource (
483474 resource,
484- create_query. as_ref ( ) . unwrap ( ) ,
475+ & create_query,
485476 create_retries,
486477 create_retry_delay,
487478 dry_run,
@@ -529,11 +520,11 @@ fn run_build(
529520 render_exports ! ( runner, resource_queries, resource, & full_context) ;
530521 }
531522 } else if !resource. get_return_val_mappings ( "create" ) . is_empty ( ) {
532- catch_error_and_exit ( & format ! (
523+ warn ! (
533524 "return_vals specified for [{}] create but no RETURNING data received. \
534- Ensure the create query includes 'RETURNING *' .",
525+ Will fall back to post-create exists query .",
535526 resource. name
536- ) ) ;
527+ ) ;
537528 }
538529
539530 // Run callback:create block if present.
@@ -573,6 +564,21 @@ fn run_build(
573564 }
574565
575566 if resource_exists && !is_correct_state {
567+ // JIT render update/createorupdate query
568+ let update_query: Option < String > = if has_createorupdate {
569+ let cou = resource_queries. get ( "createorupdate" ) . unwrap ( ) ;
570+ Some ( runner. render_query (
571+ & resource. name ,
572+ "createorupdate" ,
573+ & cou. template ,
574+ & full_context,
575+ ) )
576+ } else {
577+ resource_queries. get ( "update" ) . map ( |uq| {
578+ runner. render_query ( & resource. name , "update" , & uq. template , & full_context)
579+ } )
580+ } ;
581+
576582 let ( updated, returning_row) = runner. update_resource (
577583 resource,
578584 update_query. as_deref ( ) ,
@@ -627,11 +633,11 @@ fn run_build(
627633 } else if !resource. get_return_val_mappings ( "update" ) . is_empty ( )
628634 && is_created_or_updated
629635 {
630- catch_error_and_exit ( & format ! (
636+ warn ! (
631637 "return_vals specified for [{}] update but no RETURNING data received. \
632- Ensure the update query includes 'RETURNING *' .",
638+ Will fall back to post-update exists query .",
633639 resource. name
634- ) ) ;
640+ ) ;
635641 }
636642
637643 // Run callback:update block if present.
@@ -686,8 +692,8 @@ fn run_build(
686692 let ( post_exists, fields) = runner. check_if_resource_exists (
687693 resource,
688694 & eq. 0 ,
689- eq_opts. options . retries . max ( 3 ) ,
690- eq_opts. options . retry_delay . max ( 5 ) ,
695+ eq_opts. options . retries ,
696+ eq_opts. options . retry_delay ,
691697 dry_run,
692698 show_queries,
693699 false ,
0 commit comments