@@ -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 )
@@ -137,3 +142,62 @@ rel_join_impl <- function(
137142
138143 return (out )
139144}
145+
146+ check_relationship <- function (relationship , x , y , by , error_call ) {
147+ if (is_null(relationship )) {
148+ # FIXME: Determine behavior based on option
149+ if (! is_key(x , by $ x ) && ! is_key(y , by $ y )) {
150+ warn_join(
151+ message = c(
152+ " Detected an unexpected many-to-many relationship between `x` and `y`." ,
153+ i = paste0(
154+ " If a many-to-many relationship is expected, " ,
155+ " set `relationship = \" many-to-many\" ` to silence this warning."
156+ )
157+ ),
158+ class = " dplyr_warning_join_relationship_many_to_many" ,
159+ call = error_call
160+ )
161+ }
162+ return ()
163+ }
164+
165+ if (relationship %in% c(" one-to-many" , " one-to-one" )) {
166+ if (! is_key(x , by $ x )) {
167+ stop_join(
168+ message = c(
169+ glue(" Each row in `{x_name}` must match at most 1 row in `{y_name}`." ),
170+ ),
171+ class = paste0(" dplyr_error_join_relationship_" , gsub(" -" , " _" , relationship )),
172+ call = error_call
173+ )
174+ }
175+ }
176+
177+ if (relationship %in% c(" many-to-one" , " one-to-one" )) {
178+ if (! is_key(y , by $ y )) {
179+ stop_join(
180+ message = c(
181+ glue(" Each row in `{y_name}` must match at most 1 row in `{x_name}`." ),
182+ ),
183+ class = paste0(" dplyr_error_join_relationship_" , gsub(" -" , " _" , relationship )),
184+ call = error_call
185+ )
186+ }
187+ }
188+ }
189+
190+ is_key <- function (x , cols ) {
191+ local_options(duckdb.materialize_message = FALSE )
192+
193+ rows <-
194+ x %> %
195+ # FIXME: Why does this materialize
196+ # as_duckplyr_tibble() %>%
197+ summarize(.by = c(!!! syms(cols )), `___n` = n()) %> %
198+ filter(`___n` > 1L ) %> %
199+ head(1L ) %> %
200+ nrow()
201+
202+ rows == 0
203+ }
0 commit comments