Skip to content

Commit 666b628

Browse files
Copilotkrlmlr
andcommitted
refactor: DFS once in main function, use enquo/quo_is_null, move allow_deep to main
Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
1 parent c861b0a commit 666b628

2 files changed

Lines changed: 69 additions & 48 deletions

File tree

R/dm-flatten.R

Lines changed: 67 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
#' Unquoted names of the parent tables to be joined into `table`.
1818
#' The order of the tables here determines the order of the joins.
1919
#' If `NULL` (the default), all direct parent tables
20-
#' (reachable via foreign keys) are joined.
20+
#' are joined in non-recursive mode,
21+
#' or all reachable ancestor tables in recursive mode.
2122
#' `tidyselect` is supported, see [dplyr::select()] for details on the semantics.
2223
#' @param recursive Logical, defaults to `FALSE`.
2324
#' If `TRUE`, recursively flatten parent tables before joining them
@@ -65,18 +66,8 @@ dm_flatten <- function(
6566

6667
start <- dm_tbl_name(dm, {{ table }})
6768

68-
vars <- setdiff(src_tbls_impl(dm), start)
69-
list_of_pts <- eval_select_table(quo(c({{ parent_tables }})), vars)
69+
parent_tables_quo <- enquo(parent_tables)
7070

71-
out <- dm_flatten_impl(dm, start, list_of_pts, recursive, allow_deep)
72-
73-
dm_flatten_explain_renames(out$all_renames)
74-
75-
out$dm
76-
}
77-
78-
#' @autoglobal
79-
dm_flatten_impl <- function(dm, start, list_of_pts, recursive, allow_deep) {
8071
all_fks <- dm_get_all_fks_impl(dm, ignore_on_delete = TRUE)
8172

8273
# Find direct parents of start
@@ -85,22 +76,29 @@ dm_flatten_impl <- function(dm, start, list_of_pts, recursive, allow_deep) {
8576
pull(parent_table) %>%
8677
unique()
8778

88-
# Auto-detect: use all direct parents
89-
auto_detect <- is_empty(list_of_pts)
90-
if (auto_detect) {
91-
list_of_pts <- direct_parents
79+
# Auto-detect or evaluate parent tables
80+
if (quo_is_null(parent_tables_quo)) {
81+
if (recursive) {
82+
# In recursive mode, default to all reachable ancestor tables
83+
g <- create_graph_from_dm(dm, directed = TRUE)
84+
list_of_pts <- get_names_of_connected(g, start, squash = TRUE)
85+
} else {
86+
list_of_pts <- direct_parents
87+
}
88+
} else {
89+
vars <- setdiff(src_tbls_impl(dm), start)
90+
list_of_pts <- eval_select_table(quo(c(!!parent_tables_quo)), vars)
9291
}
9392

9493
# Early return if nothing to flatten
9594
if (is_empty(list_of_pts)) {
96-
return(list(dm = dm, all_renames = list()))
95+
return(dm)
9796
}
9897

99-
# Validate: all listed tables must be direct parents
98+
# Validate: all listed tables must be direct parents (or reachable in recursive mode)
10099
non_parents <- setdiff(list_of_pts, direct_parents)
101100
if (length(non_parents) > 0) {
102101
if (recursive) {
103-
# In recursive mode, non-direct-parents are OK if reachable
104102
g <- create_graph_from_dm(dm, directed = TRUE)
105103
reachable <- get_names_of_connected(g, start, squash = TRUE)
106104
non_reachable <- setdiff(non_parents, reachable)
@@ -118,10 +116,18 @@ dm_flatten_impl <- function(dm, start, list_of_pts, recursive, allow_deep) {
118116
}
119117
}
120118

121-
all_renames <- list()
119+
# Non-recursive: check for deeper hierarchy
120+
if (!recursive) {
121+
has_parents <- all_fks %>%
122+
filter(child_table %in% list_of_pts) %>%
123+
nrow()
124+
if (has_parents > 0 && !allow_deep) {
125+
abort_only_parents()
126+
}
127+
}
122128

129+
# Run DFS once for cycle detection and ordering (used by recursive path)
123130
if (recursive) {
124-
# Run DFS upfront to determine join order and detect cycles
125131
g <- create_graph_from_dm(dm, directed = TRUE)
126132
g_sub <- graph_induced_subgraph(g, c(start, list_of_pts))
127133
if (length(graph_vertices(g_sub)) - 1 != length(graph_edges(g_sub))) {
@@ -130,19 +136,32 @@ dm_flatten_impl <- function(dm, start, list_of_pts, recursive, allow_deep) {
130136

131137
dfs <- graph_dfs(g_sub, start, unreachable = FALSE, dist = TRUE)
132138
dfs_order <- names(dfs$order) %>% discard(is.na)
139+
} else {
140+
dfs_order <- NULL
141+
}
142+
143+
out <- dm_flatten_impl(dm, start, list_of_pts, direct_parents, dfs_order)
133144

145+
# Handle allow_deep: transfer FKs from absorbed parents to start
146+
if (allow_deep) {
147+
parents <- intersect(direct_parents, list_of_pts)
148+
out$dm <- dm_flatten_transfer_fks(out$dm, start, parents, out$col_renames, all_fks)
149+
}
150+
151+
dm_flatten_explain_renames(out$all_renames)
152+
153+
out$dm
154+
}
155+
156+
#' @autoglobal
157+
dm_flatten_impl <- function(dm, start, list_of_pts, direct_parents, dfs_order) {
158+
all_renames <- list()
159+
160+
if (!is.null(dfs_order)) {
134161
# Up-front reduction: recursively flatten each direct parent's ancestors
135162
out <- dm_flatten_reduce_parents(dm, start, list_of_pts, direct_parents, dfs_order)
136163
dm <- out$dm
137164
all_renames <- out$all_renames
138-
} else {
139-
# Non-recursive: check for deeper hierarchy
140-
has_parents <- all_fks %>%
141-
filter(child_table %in% list_of_pts) %>%
142-
nrow()
143-
if (has_parents > 0 && !allow_deep) {
144-
abort_only_parents()
145-
}
146165
}
147166

148167
# Determine which direct parents to join into start
@@ -154,7 +173,7 @@ dm_flatten_impl <- function(dm, start, list_of_pts, recursive, allow_deep) {
154173
parents_to_join <- intersect(parents_to_join, intersect(direct_parents, list_of_pts))
155174
parents_to_join <- intersect(parents_to_join, src_tbls_impl(dm))
156175

157-
out <- dm_flatten_join(dm, start, parents_to_join, allow_deep)
176+
out <- dm_flatten_join(dm, start, parents_to_join)
158177
out$all_renames <- c(all_renames, out$all_renames)
159178
out
160179
}
@@ -170,15 +189,15 @@ dm_flatten_reduce_parents <- function(dm, start, list_of_pts, direct_parents, df
170189
function(acc, pt) {
171190
dm <- acc$dm
172191
current_fks <- dm_get_all_fks_impl(dm, ignore_on_delete = TRUE)
173-
pt_parents <- current_fks %>%
192+
pt_direct_parents <- current_fks %>%
174193
filter(child_table == pt) %>%
175194
pull(parent_table) %>%
176195
unique()
177-
pt_parents_in_list <- intersect(pt_parents, list_of_pts)
196+
pt_parents_in_list <- intersect(pt_direct_parents, list_of_pts)
178197
pt_parents_in_list <- intersect(pt_parents_in_list, src_tbls_impl(dm))
179198

180199
if (length(pt_parents_in_list) > 0) {
181-
out <- dm_flatten_impl(dm, pt, pt_parents_in_list, recursive = TRUE, allow_deep = FALSE)
200+
out <- dm_flatten_impl(dm, pt, pt_parents_in_list, pt_direct_parents, dfs_order)
182201
list(dm = out$dm, all_renames = c(acc$all_renames, out$all_renames))
183202
} else {
184203
acc
@@ -189,18 +208,18 @@ dm_flatten_reduce_parents <- function(dm, start, list_of_pts, direct_parents, df
189208
}
190209

191210
#' @autoglobal
192-
dm_flatten_join <- function(dm, start, parents, allow_deep) {
211+
dm_flatten_join <- function(dm, start, parents) {
193212
if (is_empty(parents)) {
194-
return(list(dm = dm, all_renames = list()))
213+
return(list(dm = dm, all_renames = list(), col_renames = list()))
195214
}
196215

197216
all_fks <- dm_get_all_fks_impl(dm, ignore_on_delete = TRUE)
198217

199218
start_tbl <- tbl_impl(dm, start)
200219
current_cols <- colnames(start_tbl)
201220

202-
# Track renames per parent (old_name -> new_name)
203-
parent_col_renames <- list()
221+
# Track renames per parent for allow_deep FK transfer
222+
col_renames <- list()
204223
all_renames <- list()
205224

206225
for (pt in parents) {
@@ -237,7 +256,7 @@ dm_flatten_join <- function(dm, start, parents, allow_deep) {
237256
renames <- set_names(new_names, conflicting)
238257
all_renames <- c(all_renames, list(list(table = pt, renames = renames)))
239258
}
240-
parent_col_renames[[pt]] <- renames
259+
col_renames[[pt]] <- renames
241260

242261
# Perform the join
243262
start_tbl <- left_join(start_tbl, parent_tbl, by = by)
@@ -251,18 +270,17 @@ dm_flatten_join <- function(dm, start, parents, allow_deep) {
251270
start_idx <- which(def$table == start)
252271
def$data[[start_idx]] <- start_tbl
253272

254-
# Handle allow_deep: transfer FKs from parents to start
255-
if (allow_deep) {
256-
def <- dm_flatten_transfer_fks(def, dm, start, parents, parent_col_renames, all_fks)
257-
}
258-
259273
dm_result <- dm_from_def(def)
260274

261275
# Remove parent tables
262276
remaining <- setdiff(def$table, parents)
263277
remaining <- set_names(remaining)
264278

265-
list(dm = dm_select_tbl_impl(dm_result, remaining), all_renames = all_renames)
279+
list(
280+
dm = dm_select_tbl_impl(dm_result, remaining),
281+
all_renames = all_renames,
282+
col_renames = col_renames
283+
)
266284
}
267285

268286
dm_flatten_explain_renames <- function(all_renames) {
@@ -292,7 +310,9 @@ dm_flatten_explain_renames <- function(all_renames) {
292310
}
293311

294312
#' @autoglobal
295-
dm_flatten_transfer_fks <- function(def, dm, start, parents, parent_col_renames, all_fks) {
313+
dm_flatten_transfer_fks <- function(dm, start, parents, col_renames, all_fks) {
314+
def <- dm_get_def(dm)
315+
296316
for (pt in parents) {
297317
# Find FKs where pt is the child (pt references other tables as parent)
298318
pt_child_fks <- all_fks %>%
@@ -309,7 +329,7 @@ dm_flatten_transfer_fks <- function(def, dm, start, parents, parent_col_renames,
309329
parent_cols <- pt_child_fks$parent_key_cols[[i]]
310330

311331
# Apply renames: if any FK columns were renamed during disambiguation
312-
renames <- parent_col_renames[[pt]]
332+
renames <- col_renames[[pt]]
313333
if (length(renames) > 0) {
314334
for (k in seq_along(child_cols)) {
315335
if (child_cols[k] %in% names(renames)) {
@@ -342,5 +362,5 @@ dm_flatten_transfer_fks <- function(def, dm, start, parents, parent_col_renames,
342362
}
343363
}
344364

345-
def
365+
dm_from_def(def)
346366
}

man/dm_flatten.Rd

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)