Skip to content

Commit 8824d59

Browse files
committed
2.0.4 wip
1 parent 188f50c commit 8824d59

4 files changed

Lines changed: 97 additions & 141 deletions

File tree

examples/aws/aws-vpc-webserver/resources/example_subnet_rt_assn.iql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*+ exists */
1+
/*+ exists, retries=10, retry_delay=5 */
22
SELECT
33
id as subnet_route_table_assn_id
44
FROM awscc.ec2.vw_subnet_route_table_associations
Lines changed: 10 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,26 @@
11
/*+ exists */
2-
SELECT count(*) as count
2+
SELECT id AS databricks_group_id
33
FROM databricks_account.iam.account_groups
4-
WHERE account_id = '{{ account_id }}'
5-
AND filter = 'displayName Eq "{{ displayName }}"';
4+
WHERE account_id = '{{ databricks_account_id }}'
5+
AND filter = 'displayName Eq "{{ display_name }}"';
66

77
/*+ create */
88
INSERT INTO databricks_account.iam.account_groups (
9-
display_name,
10-
external_id,
11-
id,
12-
members,
13-
meta,
14-
roles,
9+
displayName,
1510
account_id
1611
)
1712
SELECT
1813
'{{ display_name }}',
19-
'{{ external_id }}',
20-
'{{ id }}',
21-
'{{ members }}',
22-
'{{ meta }}',
23-
'{{ roles }}',
24-
'{{ account_id }}'
14+
'{{ databricks_account_id }}'
2515
RETURNING
26-
id,
27-
account_id,
28-
displayName,
29-
externalId,
30-
members,
31-
meta,
32-
roles
16+
id
3317
;
3418

35-
/*+ update */
36-
UPDATE databricks_account.iam.account_groups
37-
SET
38-
operations = '{{ operations }}',
39-
schemas = '{{ schemas }}'
40-
WHERE
41-
account_id = '{{ account_id }}'
42-
AND id = '{{ id }}';
43-
44-
/*+ statecheck, retries=5, retry_delay=10 */
45-
SELECT count(*) as count
46-
FROM databricks_account.iam.account_groups
47-
WHERE
48-
id = '{{ id }}' AND
49-
members = '{{ members }}' AND
50-
meta = '{{ meta }}' AND
51-
roles = '{{ roles }}' AND
52-
account_id = '{{ account_id }}'
53-
AND attributes = '{{ attributes }}'
54-
AND count = '{{ count }}'
55-
AND excluded_attributes = '{{ excluded_attributes }}'
56-
AND filter = '{{ filter }}'
57-
AND sort_by = '{{ sort_by }}'
58-
AND sort_order = '{{ sort_order }}'
59-
AND start_index = '{{ start_index }}';
60-
6119
/*+ exports */
62-
SELECT id,
63-
members,
64-
meta,
65-
roles
66-
FROM databricks_account.iam.account_groups
67-
WHERE account_id = '{{ account_id }}'
68-
AND attributes = '{{ attributes }}'
69-
AND count = '{{ count }}'
70-
AND excluded_attributes = '{{ excluded_attributes }}'
71-
AND filter = '{{ filter }}'
72-
AND sort_by = '{{ sort_by }}'
73-
AND sort_order = '{{ sort_order }}'
74-
AND start_index = '{{ start_index }}';
20+
SELECT '{{ this.databricks_group_id }}' as databricks_group_id,
21+
'{{ display_name }}' as display_name;
7522

7623
/*+ delete */
7724
DELETE FROM databricks_account.iam.account_groups
78-
WHERE account_id = '{{ account_id }}'
79-
AND id = '{{ id }}';
25+
WHERE account_id = '{{ databricks_account_id }}'
26+
AND id = '{{ databricks_group_id }}';

examples/databricks/serverless/stackql_manifest.yml

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -276,14 +276,17 @@ resources:
276276
- workspace_status
277277
- workspace_url
278278

279-
# - name: workspace_admins_group
280-
# file: databricks_account/account_groups.iql
281-
# props:
282-
# - name: display_name
283-
# value: "{{ stack_name }}-{{ stack_env }}-workspace-admins"
284-
# exports:
285-
# - id
286-
# - display_name
279+
- name: workspace_admins_group
280+
file: databricks_account/account_groups.iql
281+
props:
282+
- name: display_name
283+
value: "{{ stack_name }}-{{ stack_env }}-workspace-admins"
284+
return_vals:
285+
create:
286+
- id: databricks_group_id
287+
exports:
288+
- databricks_group_id
289+
- display_name
287290

288291
# - name: databricks_account/get_users
289292
# type: query

src/commands/build.rs

Lines changed: 75 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::collections::HashMap;
1010
use std::time::Instant;
1111

1212
use clap::{Arg, ArgMatches, Command};
13-
use log::{debug, info};
13+
use log::{debug, info, warn};
1414

1515
use crate::commands::base::CommandRunner;
1616
use 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

Comments
 (0)