Changeset: f7e599a82881 for MonetDB
URL: https://dev.monetdb.org/hg/MonetDB?cmd=changeset;node=f7e599a82881
Modified Files:
        sql/server/rel_updates.c
        sql/test/merge-statements/Tests/mergestmt01.sql
        sql/test/merge-statements/Tests/mergestmt01.stable.err
        sql/test/merge-statements/Tests/mergestmt01.stable.out
Branch: default
Log Message:

According to the SQL standard, if a row in the target table matches more than 
one source row, an exception must be thrown. To implement this restriction, we 
generate a group by relation (like in distinct case) around the update/delete, 
then generate an exception relation around it.


diffs (146 lines):

diff --git a/sql/server/rel_updates.c b/sql/server/rel_updates.c
--- a/sql/server/rel_updates.c
+++ b/sql/server/rel_updates.c
@@ -1370,6 +1370,43 @@ truncate_table(mvc *sql, dlist *qname, i
 extern sql_rel *rel_list(sql_allocator *sa, sql_rel *l, sql_rel *r);
 
 static sql_rel *
+validate_merge_update_delete(mvc *sql, sql_table *t, sql_rel *upd_del, sql_rel 
*bt, sql_rel *join_rel)
+{
+       char buf[BUFSIZ];
+       sql_exp *aggr, *bigger, *ex;
+       sql_subaggr *cf = sql_bind_aggr(sql->sa, sql->session->schema, "count", 
NULL);
+       sql_subfunc *bf;
+       list *exps = new_exp_list(sql->sa);
+       sql_rel *groupby;
+
+       groupby = rel_groupby(sql, rel_dup(join_rel), NULL); //aggregate by all 
column and count (distinct values)
+       groupby->r = rel_projections(sql, bt, NULL, 1, 0);
+       aggr = exp_aggr(sql->sa, NULL, cf, 0, 0, groupby->card, 0);
+       (void) rel_groupby_add_aggr(sql, groupby, aggr);
+       exp_label(sql->sa, aggr, ++sql->label);
+
+       bf = sql_bind_func(sql->sa, sql->session->schema, ">", 
exp_subtype(aggr), exp_subtype(aggr), F_FUNC);
+       if (!bf)
+               return sql_error(sql, 02, SQLSTATE(42000) "MERGE: function '>' 
not found");
+       list_append(exps, exp_column(sql->sa, exp_relname(aggr), 
exp_name(aggr), exp_subtype(aggr), aggr->card, has_nil(aggr), is_intern(aggr)));
+       list_append(exps, exp_atom_lng(sql->sa, 1));
+       bigger = exp_op(sql->sa, exps, bf);
+       exp_label(sql->sa, bigger, ++sql->label);
+       groupby = rel_select(sql->sa, groupby, bigger); //select only columns 
with more than 1 value
+
+       groupby = rel_groupby(sql, groupby, NULL);
+       aggr = exp_aggr(sql->sa, NULL, cf, 0, 0, groupby->card, 0);
+       (void) rel_groupby_add_aggr(sql, groupby, aggr);
+       exp_label(sql->sa, aggr, ++sql->label); //count all of them, if there 
is at least one, throw the exception
+
+       ex = exp_column(sql->sa, exp_relname(aggr), exp_name(aggr), 
exp_subtype(aggr), aggr->card, has_nil(aggr), is_intern(aggr));
+       snprintf(buf, BUFSIZ, "MERGE: There are rows in '%s.%s' with multiple 
matches on source relation", t->s->base.name, t->base.name);
+       ex = exp_exception(sql->sa, ex, buf);
+
+       return rel_exception(sql->sa, upd_del, groupby, 
list_append(new_exp_list(sql->sa), ex));
+}
+
+static sql_rel *
 merge_into_table(mvc *sql, dlist *qname, str alias, symbol *tref, symbol 
*search_cond, dlist *merge_list)
 {
        char *sname = qname_schema(qname), *tname = qname_table(qname), 
*alias_name;
@@ -1456,11 +1493,10 @@ merge_into_table(mvc *sql, dlist *qname,
                                if((processed & MERGE_INSERT) == MERGE_INSERT)
                                        joined = rel_dup(joined);
 
-                               join_rel = rel_crossproduct(sql->sa, bt, 
joined, op_left);
+                               join_rel = rel_crossproduct(sql->sa, bt, 
joined, op_join);
                                join_rel = rel_logical_exp(sql, join_rel, 
search_cond, sql_where);
                                if (!join_rel)
                                        return NULL;
-                               join_rel->op = op_semi;
                                set_processed(join_rel);
 
                                e = exp_column(sql->sa, alias_name, TID, 
sql_bind_localtype("oid"), CARD_MULTI, 0, 1);
@@ -1470,7 +1506,7 @@ merge_into_table(mvc *sql, dlist *qname,
                        } else {
                                assert(0);
                        }
-                       if(!upd_del)
+                       if(!upd_del || !(upd_del = 
validate_merge_update_delete(sql, t, upd_del, bt, join_rel)))
                                return NULL;
                } else if(token == SQL_MERGE_NO_MATCH) {
                        if((processed & MERGE_INSERT) == MERGE_INSERT)
@@ -1506,6 +1542,7 @@ merge_into_table(mvc *sql, dlist *qname,
                res->p = prop_create(sql->sa, PROP_DISTRIBUTE, res->p);
        } else if((processed & MERGE_UPDATE_DELETE) == MERGE_UPDATE_DELETE) {
                res = upd_del;
+               res->p = prop_create(sql->sa, PROP_DISTRIBUTE, res->p);
        } else if((processed & MERGE_INSERT) == MERGE_INSERT) {
                res = insert;
        } else {
diff --git a/sql/test/merge-statements/Tests/mergestmt01.sql 
b/sql/test/merge-statements/Tests/mergestmt01.sql
--- a/sql/test/merge-statements/Tests/mergestmt01.sql
+++ b/sql/test/merge-statements/Tests/mergestmt01.sql
@@ -49,6 +49,17 @@ select aa, bb from predata;
 
 rollback;
 
+insert into predata values (1, 1);
+insert into merging values (1, 1), (2, 1);
+
+merge into predata using (select aa, bb from merging) as sub on predata.bb = 
sub.bb
+      when matched then update set aa = sub.aa; --error, each target row must 
match one and only one source row
+select aa, bb from predata;
+
+merge into predata using (select aa, bb from merging) as sub on predata.bb = 
sub.bb
+      when matched then delete; --error, each target row must match one and 
only one source row
+select aa, bb from predata;
+
 merge into predata othertt using (select aa, bb from merging) as sub on 
othertt.bb = sub.bb
       when not matched then insert values (othertt.aa, othertt.bb); --error 
there was no match for the merged table, so it shouldn't appear in the insert 
clause
 
diff --git a/sql/test/merge-statements/Tests/mergestmt01.stable.err 
b/sql/test/merge-statements/Tests/mergestmt01.stable.err
--- a/sql/test/merge-statements/Tests/mergestmt01.stable.err
+++ b/sql/test/merge-statements/Tests/mergestmt01.stable.err
@@ -28,7 +28,17 @@ stderr of test 'mergestmt01` in director
 # 14:42:45 >  "mclient" "-lsql" "-ftest" "-tnone" "-Eutf-8" "-i" "-e" 
"--host=/var/tmp/mtest-2774" "--port=35532"
 # 14:42:45 >  
 
-MAPI  = (monetdb) /var/tmp/mtest-11874/.s.monetdb.35844
+MAPI  = (monetdb) /var/tmp/mtest-13603/.s.monetdb.37525
+QUERY = merge into predata using (select aa, bb from merging) as sub on 
predata.bb = sub.bb
+              when matched then update set aa = sub.aa; --error, each target 
row must match one and only one source row
+ERROR = !MERGE: There are rows in 'sys.predata' with multiple matches on 
source relation
+CODE  = M0M29
+MAPI  = (monetdb) /var/tmp/mtest-13603/.s.monetdb.37525
+QUERY = merge into predata using (select aa, bb from merging) as sub on 
predata.bb = sub.bb
+              when matched then delete; --error, each target row must match 
one and only one source row
+ERROR = !MERGE: There are rows in 'sys.predata' with multiple matches on 
source relation
+CODE  = M0M29
+MAPI  = (monetdb) /var/tmp/mtest-13603/.s.monetdb.37525
 QUERY = merge into predata othertt using (select aa, bb from merging) as sub 
on othertt.bb = sub.bb
               when not matched then insert values (othertt.aa, othertt.bb); 
--error there was no match for the merged table, so it shouldn't appear in the 
insert clause
 ERROR = !SELECT: no such column 'othertt.aa'
diff --git a/sql/test/merge-statements/Tests/mergestmt01.stable.out 
b/sql/test/merge-statements/Tests/mergestmt01.stable.out
--- a/sql/test/merge-statements/Tests/mergestmt01.stable.out
+++ b/sql/test/merge-statements/Tests/mergestmt01.stable.out
@@ -133,6 +133,22 @@ Ready.
 [ NULL,        NULL    ]
 [ NULL,        NULL    ]
 #rollback;
+#insert into predata values (1, 1);
+[ 1    ]
+#insert into merging values (1, 1), (2, 1);
+[ 2    ]
+#select aa, bb from predata;
+% sys.predata, sys.predata # table_name
+% aa,  bb # name
+% int, int # type
+% 1,   1 # length
+[ 1,   1       ]
+#select aa, bb from predata;
+% sys.predata, sys.predata # table_name
+% aa,  bb # name
+% int, int # type
+% 1,   1 # length
+[ 1,   1       ]
 #drop table merging;
 #drop table predata;
 
_______________________________________________
checkin-list mailing list
checkin-list@monetdb.org
https://www.monetdb.org/mailman/listinfo/checkin-list

Reply via email to