@@ -6,6 +6,7 @@ rel_join_impl <- function(
66 na_matches ,
77 suffix = c(" .x" , " .y" ),
88 keep = NULL ,
9+ relationship = NULL ,
910 error_call = caller_env()
1011) {
1112 mutating <- ! (join %in% c(" semi" , " anti" ))
@@ -25,6 +26,10 @@ rel_join_impl <- function(
2526 by <- as_join_by(by , error_call = error_call )
2627 }
2728
29+ if (mutating ) {
30+ check_relationship(relationship , x , y , by , error_call = error_call )
31+ }
32+
2833 x_by <- by $ x
2934 y_by <- by $ y
3035 x_rel <- duckdb_rel_from_df(x )
@@ -136,3 +141,62 @@ rel_join_impl <- function(
136141
137142 return (out )
138143}
144+
145+ check_relationship <- function (relationship , x , y , by , error_call ) {
146+ if (is_null(relationship )) {
147+ # FIXME: Determine behavior based on option
148+ if (! is_key(x , by $ x ) && ! is_key(y , by $ y )) {
149+ warn_join(
150+ message = c(
151+ " Detected an unexpected many-to-many relationship between `x` and `y`." ,
152+ i = paste0(
153+ " If a many-to-many relationship is expected, " ,
154+ " set `relationship = \" many-to-many\" ` to silence this warning."
155+ )
156+ ),
157+ class = " dplyr_warning_join_relationship_many_to_many" ,
158+ call = error_call
159+ )
160+ }
161+ return ()
162+ }
163+
164+ if (relationship %in% c(" one-to-many" , " one-to-one" )) {
165+ if (! is_key(x , by $ x )) {
166+ stop_join(
167+ message = c(
168+ glue(" Each row in `{x_name}` must match at most 1 row in `{y_name}`." ),
169+ ),
170+ class = paste0(" dplyr_error_join_relationship_" , gsub(" -" , " _" , relationship )),
171+ call = error_call
172+ )
173+ }
174+ }
175+
176+ if (relationship %in% c(" many-to-one" , " one-to-one" )) {
177+ if (! is_key(y , by $ y )) {
178+ stop_join(
179+ message = c(
180+ glue(" Each row in `{y_name}` must match at most 1 row in `{x_name}`." ),
181+ ),
182+ class = paste0(" dplyr_error_join_relationship_" , gsub(" -" , " _" , relationship )),
183+ call = error_call
184+ )
185+ }
186+ }
187+ }
188+
189+ is_key <- function (x , cols ) {
190+ local_options(duckdb.materialize_message = FALSE )
191+
192+ rows <-
193+ x %> %
194+ # FIXME: Why does this materialize
195+ # as_duckplyr_tibble() %>%
196+ summarize(.by = c(!!! syms(cols )), `___n` = n()) %> %
197+ filter(`___n` > 1L ) %> %
198+ head(1L ) %> %
199+ nrow()
200+
201+ rows == 0
202+ }
0 commit comments