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
268286dm_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}
0 commit comments