Changeset: abd79374b24e for MonetDB
URL: https://dev.monetdb.org/hg/MonetDB/rev/abd79374b24e
Added Files:
        sql/test/BugTracker-2021/Tests/merge-stmt.wrong-error.Bug-7109.sql
        
sql/test/BugTracker-2021/Tests/merge-stmt.wrong-error.Bug-7109.stable.err
        
sql/test/BugTracker-2021/Tests/merge-stmt.wrong-error.Bug-7109.stable.out
Modified Files:
        sql/server/rel_updates.c
        sql/test/BugTracker-2021/Tests/All
        sql/test/SQLancer/Tests/sqlancer11.stable.out
Branch: Oct2020
Log Message:

Do a better heuristic to find duplicate matches on a merge statement with a 
left join, then compare the output cardinality. This fixes bug #7109

The sqlancer11 test gives right results on default, I think it was because a 
sqlancer bugfix related to unnesting.


diffs (truncated from 326 to 300 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
@@ -1190,44 +1190,103 @@ truncate_table(mvc *sql, dlist *qname, i
        return NULL;
 }
 
+static sql_exp *
+null_check_best_score(list *exps)
+{
+       int max = 0;
+       sql_exp *res = exps->h->data;
+
+       for (node *n = exps->h ; n ; n = n->next) {
+               sql_exp *e = n->data;
+               sql_subtype *t = exp_subtype(e);
+               int score = 0;
+
+               if (find_prop(e->p, PROP_HASHCOL)) /* distinct columns */
+                       score += 700;
+               if (find_prop(e->p, PROP_SORTIDX)) /* has sort index */
+                       score += 400;
+               if (find_prop(e->p, PROP_HASHIDX)) /* has hash index */
+                       score += 300;
+
+               if (t) {
+                       switch (ATOMstorage(t->type->localtype)) {
+                               case TYPE_bte:
+                                       score += 150 - 8;
+                                       break;
+                               case TYPE_sht:
+                                       score += 150 - 16;
+                                       break;
+                               case TYPE_int:
+                                       score += 150 - 32;
+                                       break;
+                               case TYPE_void:
+                               case TYPE_lng:
+                                       score += 150 - 64;
+                                       break;
+#ifdef HAVE_HGE
+                               case TYPE_hge:
+                                       score += 150 - 128;
+                                       break;
+#endif
+                               case TYPE_flt:
+                                       score += 75 - 24;
+                                       break;
+                               case TYPE_dbl:
+                                       score += 75 - 53;
+                                       break;
+                               default:
+                                       break;
+                       }
+               }
+               if (score > max) {
+                       max = score;
+                       res = e;
+               }
+       }
+       return res;
+}
+
 #define MERGE_UPDATE_DELETE 1
 #define MERGE_INSERT        2
 
 static sql_rel *
 validate_merge_update_delete(mvc *sql, sql_table *t, str alias, sql_rel 
*joined_table, tokens upd_token,
-                                                        sql_rel *upd_del, 
sql_rel *bt, sql_rel *extra_projection)
+                                                        sql_rel *upd_del, 
sql_rel *join_rel, const char *bt_name)
 {
        char buf[BUFSIZ];
-       sql_exp *aggr, *bigger, *ex;
-       sql_subfunc *cf = sql_bind_func(sql->sa, sql->session->schema, "count", 
sql_bind_localtype("void"), NULL, F_AGGR);
-       sql_subfunc *bf;
+       sql_exp *aggr1, *aggr2, *bigger, *ex;
+       sql_subfunc *bf, *cf = sql_bind_func(sql->sa, sql->session->schema, 
"count", sql_bind_localtype("void"), NULL, F_AGGR);
        list *exps = new_exp_list(sql->sa);
-       sql_rel *groupby, *res;
+       sql_rel *groupby1, *groupby2, *res, *cross;
        const char *join_rel_name = rel_name(joined_table);
+       exp_kind ek = {type_value, card_value, FALSE};
 
        assert(upd_token == SQL_UPDATE || upd_token == SQL_DELETE);
 
-       groupby = rel_groupby(sql, rel_dup(extra_projection), 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);
+       groupby1 = rel_groupby(sql, rel_dup(join_rel), NULL); /* count the 
number of rows of the join */
+       aggr1 = exp_aggr(sql->sa, NULL, cf, 0, 0, groupby1->card, 0);
+       (void) rel_groupby_add_aggr(sql, groupby1, aggr1);
+       exp_label(sql->sa, aggr1, ++sql->label);
 
-       bf = sql_bind_func(sql->sa, sql->session->schema, ">", 
exp_subtype(aggr), exp_subtype(aggr), F_FUNC);
+       groupby2 = rel_groupby(sql, rel_basetable(sql, t, bt_name), NULL); /* 
the current count of the table */
+       aggr2 = exp_aggr(sql->sa, NULL, cf, 0, 0, groupby2->card, 0);
+       (void) rel_groupby_add_aggr(sql, groupby2, aggr2);
+       exp_label(sql->sa, aggr2, ++sql->label);
+
+       cross = rel_crossproduct(sql->sa, groupby1, groupby2, op_join); /* both 
should have 1 row */
+
+       bf = sql_bind_func(sql->sa, sql->session->schema, ">", 
exp_subtype(aggr1), exp_subtype(aggr2), F_FUNC);
        if (!bf)
                return sql_error(sql, 02, SQLSTATE(42000) "MERGE: function '>' 
not found");
-       list_append(exps, exp_ref(sql, aggr));
-       list_append(exps, exp_atom_lng(sql->sa, 1));
+       list_append(exps, exp_ref(sql, aggr1));
+       list_append(exps, exp_ref(sql, aggr2));
        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
+       exp_label(sql->sa, bigger, ++sql->label); /* the join cannot produce 
more rows than what the table has */
 
-       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
+       res = rel_project(sql->sa, cross, list_append(sa_list(sql->sa), 
bigger));
+       res = rel_return_zero_or_one(sql, res, ek);
 
-       ex = exp_ref(sql, aggr);
+       ex = exp_ref(sql, res->exps->h->data);
        snprintf(buf, BUFSIZ, "MERGE %s: Multiple rows in the input 
relation%s%s%s match the same row in the target %s '%s%s%s'",
                         (upd_token == SQL_DELETE) ? "DELETE" : "UPDATE",
                         join_rel_name ? " '" : "", join_rel_name ? 
join_rel_name : "", join_rel_name ? "'" : "",
@@ -1235,7 +1294,7 @@ validate_merge_update_delete(mvc *sql, s
                         alias ? alias : t->s ? t->s->base.name : "", alias ? 
"" : ".", alias ? "" : t->base.name);
        ex = exp_exception(sql->sa, ex, buf);
 
-       res = rel_exception(sql->sa, groupby, NULL, 
list_append(new_exp_list(sql->sa), ex));
+       res = rel_exception(sql->sa, res, NULL, 
list_append(new_exp_list(sql->sa), ex));
        return rel_list(sql->sa, res, upd_del);
 }
 
@@ -1298,16 +1357,19 @@ merge_into_table(sql_query *query, dlist
                                if ((processed & MERGE_INSERT) == MERGE_INSERT) 
{
                                        join_rel = rel_dup(join_rel);
                                } else {
-                                       join_rel = rel_crossproduct(sql->sa, 
joined, bt, op_join);
+                                       join_rel = rel_crossproduct(sql->sa, 
bt, joined, op_left);
                                        if (!(join_rel = rel_logical_exp(query, 
join_rel, search_cond, sql_where | sql_join)))
                                                return NULL;
                                        set_processed(join_rel);
                                }
 
-                               //project columns of both bt and joined + oid 
to be used on update
-                               extra_project = rel_project(sql->sa, join_rel, 
rel_projections(sql, bt, NULL, 1, 0));
-                               extra_project->exps = 
list_merge(extra_project->exps, rel_projections(sql, joined, NULL, 1, 0), 
(fdup)NULL);
-                               list_prepend(extra_project->exps, 
exp_column(sql->sa, bt_name, TID, sql_bind_localtype("oid"), CARD_MULTI, 0, 1));
+                               sql_exp *extra = 
null_check_best_score(rel_projections(sql, joined, NULL, 1, 0));
+                               /* select values with correspondent on the 
table side, ie they are not null */
+                               sql_exp *le = exp_compare(sql->sa, exp_ref(sql, 
extra), exp_atom(sql->sa, atom_general(sql->sa, exp_subtype(extra), NULL)), 
cmp_equal);
+                               set_anti(le);
+                               set_has_no_nil(le);
+                               set_semantics(le);
+                               extra_project = rel_project(sql->sa, 
rel_select(sql->sa, join_rel, le), rel_projections(sql, join_rel, NULL, 1, 1));
 
                                upd_del = update_generate_assignments(query, t, 
extra_project, rel_basetable(sql, t, bt_name), sts->h->data.lval, "MERGE");
                        } else if (uptdel == SQL_DELETE) {
@@ -1316,21 +1378,25 @@ merge_into_table(sql_query *query, dlist
                                if ((processed & MERGE_INSERT) == MERGE_INSERT) 
{
                                        join_rel = rel_dup(join_rel);
                                } else {
-                                       join_rel = rel_crossproduct(sql->sa, 
joined, bt, op_join);
+                                       join_rel = rel_crossproduct(sql->sa, 
bt, joined, op_left);
                                        if (!(join_rel = rel_logical_exp(query, 
join_rel, search_cond, sql_where | sql_join)))
                                                return NULL;
                                        set_processed(join_rel);
                                }
 
-                               //project columns of bt + oid to be used on 
delete
-                               extra_project = rel_project(sql->sa, join_rel, 
rel_projections(sql, bt, NULL, 1, 0));
-                               list_prepend(extra_project->exps, 
exp_column(sql->sa, bt_name, TID, sql_bind_localtype("oid"), CARD_MULTI, 0, 1));
+                               sql_exp *extra = 
null_check_best_score(rel_projections(sql, joined, NULL, 1, 0));
+                               /* select values with correspondent on the 
table side, ie they are not null */
+                               sql_exp *le = exp_compare(sql->sa, exp_ref(sql, 
extra), exp_atom(sql->sa, atom_general(sql->sa, exp_subtype(extra), NULL)), 
cmp_equal);
+                               set_anti(le);
+                               set_has_no_nil(le);
+                               set_semantics(le);
+                               extra_project = rel_project(sql->sa, 
rel_select(sql->sa, join_rel, le), list_append(new_exp_list(sql->sa), 
exp_column(sql->sa, bt_name, TID, sql_bind_localtype("oid"), CARD_MULTI, 0, 
1)));
 
                                upd_del = rel_delete(sql->sa, 
rel_basetable(sql, t, bt_name), extra_project);
                        } else {
                                assert(0);
                        }
-                       if (!upd_del || !(upd_del = 
validate_merge_update_delete(sql, t, alias, joined, uptdel, upd_del, bt, 
extra_project)))
+                       if (!upd_del || !(upd_del = 
validate_merge_update_delete(sql, t, alias, joined, uptdel, upd_del, join_rel, 
bt_name)))
                                return NULL;
                } else if (token == SQL_MERGE_NO_MATCH) {
                        if ((processed & MERGE_INSERT) == MERGE_INSERT)
@@ -1343,7 +1409,7 @@ merge_into_table(sql_query *query, dlist
                        if ((processed & MERGE_UPDATE_DELETE) == 
MERGE_UPDATE_DELETE) {
                                join_rel = rel_dup(join_rel);
                        } else {
-                               join_rel = rel_crossproduct(sql->sa, joined, 
bt, op_join);
+                               join_rel = rel_crossproduct(sql->sa, bt, 
joined, op_left);
                                if (!(join_rel = rel_logical_exp(query, 
join_rel, search_cond, sql_where | sql_join)))
                                        return NULL;
                                set_processed(join_rel);
diff --git a/sql/test/BugTracker-2021/Tests/All 
b/sql/test/BugTracker-2021/Tests/All
--- a/sql/test/BugTracker-2021/Tests/All
+++ b/sql/test/BugTracker-2021/Tests/All
@@ -2,3 +2,4 @@ update-from-count.Bug-7079
 HAVE_PYMONETDB?remote-table-ranges.Bug-7089
 KNOWNFAIL?query-too-complex.Bug-7092
 ntile-wrong-result.Bug-7104
+merge-stmt.wrong-error.Bug-7109
diff --git a/sql/test/BugTracker-2021/Tests/merge-stmt.wrong-error.Bug-7109.sql 
b/sql/test/BugTracker-2021/Tests/merge-stmt.wrong-error.Bug-7109.sql
new file mode 100644
--- /dev/null
+++ b/sql/test/BugTracker-2021/Tests/merge-stmt.wrong-error.Bug-7109.sql
@@ -0,0 +1,14 @@
+
+start transaction;
+
+create table ce (id bigint);
+insert into ce values (1), (2);
+create table attr (id bigint, value text);
+insert into attr values (1,'a'),(1,'b'),(2,'a'),(2,'a'),(3,'a'),(3,'b');
+
+merge into attr using ce on ce.id = attr.id when matched then delete;
+
+select * from attr;
+       -- 3 a
+       -- 3 b
+rollback;
diff --git 
a/sql/test/BugTracker-2021/Tests/merge-stmt.wrong-error.Bug-7109.stable.err 
b/sql/test/BugTracker-2021/Tests/merge-stmt.wrong-error.Bug-7109.stable.err
new file mode 100644
--- /dev/null
+++ b/sql/test/BugTracker-2021/Tests/merge-stmt.wrong-error.Bug-7109.stable.err
@@ -0,0 +1,29 @@
+stderr of test 'merge-stmt.wrong-error.Bug-7109` in directory 
'sql/test/BugTracker-2021` itself:
+
+
+# 23:15:06 >  
+# 23:15:06 >  "mserver5" "--debug=10" "--set" "gdk_nr_threads=0" "--set" 
"mapi_listenaddr=all" "--set" "mapi_port=35497" "--set" 
"mapi_usock=/var/tmp/mtest-14496/.s.monetdb.35497" "--forcemito" 
"--dbpath=/home/ferreira/repositories/MonetDB-Oct2020/BUILD/var/MonetDB/mTests_sql_test_BugTracker-2021"
 "--set" "embedded_r=yes" "--set" "embedded_c=true"
+# 23:15:06 >  
+
+# builtin opt  gdk_dbpath = 
/home/ferreira/repositories/MonetDB-Oct2020/BUILD/var/monetdb5/dbfarm/demo
+# builtin opt  mapi_port = 50000
+# builtin opt  sql_optimizer = default_pipe
+# builtin opt  sql_debug = 0
+# builtin opt  raw_strings = false
+# cmdline opt  gdk_nr_threads = 0
+# cmdline opt  mapi_listenaddr = all
+# cmdline opt  mapi_port = 35497
+# cmdline opt  mapi_usock = /var/tmp/mtest-14496/.s.monetdb.35497
+# cmdline opt  gdk_dbpath = 
/home/ferreira/repositories/MonetDB-Oct2020/BUILD/var/MonetDB/mTests_sql_test_BugTracker-2021
+# cmdline opt  embedded_r = yes
+# cmdline opt  embedded_c = true
+
+# 23:15:07 >  
+# 23:15:07 >  "mclient" "-lsql" "-ftest" "-tnone" "-Eutf-8" "-i" "-e" 
"--host=/var/tmp/mtest-14496" "--port=35497"
+# 23:15:07 >  
+
+
+# 23:15:07 >  
+# 23:15:07 >  "Done."
+# 23:15:07 >  
+
diff --git 
a/sql/test/BugTracker-2021/Tests/merge-stmt.wrong-error.Bug-7109.stable.out 
b/sql/test/BugTracker-2021/Tests/merge-stmt.wrong-error.Bug-7109.stable.out
new file mode 100644
--- /dev/null
+++ b/sql/test/BugTracker-2021/Tests/merge-stmt.wrong-error.Bug-7109.stable.out
@@ -0,0 +1,47 @@
+stdout of test 'merge-stmt.wrong-error.Bug-7109` in directory 
'sql/test/BugTracker-2021` itself:
+
+
+# 23:15:06 >  
+# 23:15:06 >  "mserver5" "--debug=10" "--set" "gdk_nr_threads=0" "--set" 
"mapi_listenaddr=all" "--set" "mapi_port=35497" "--set" 
"mapi_usock=/var/tmp/mtest-14496/.s.monetdb.35497" "--forcemito" 
"--dbpath=/home/ferreira/repositories/MonetDB-Oct2020/BUILD/var/MonetDB/mTests_sql_test_BugTracker-2021"
 "--set" "embedded_r=yes" "--set" "embedded_c=true"
+# 23:15:06 >  
+
+# MonetDB 5 server v11.39.16 (hg id: eca29d0d38a6)
+# This is an unreleased version
+# Serving database 'mTests_sql_test_BugTracker-2021', using 8 threads
+# Compiled for x86_64-pc-linux-gnu/64bit with 128bit integers
+# Found 15.493 GiB available main-memory of which we use 12.627 GiB
+# Copyright (c) 1993 - July 2008 CWI.
+# Copyright (c) August 2008 - 2021 MonetDB B.V., all rights reserved
+# Visit https://www.monetdb.org/ for further information
+# Listening for connection requests on 
mapi:monetdb://localhost.localdomain:35497/
+# Listening for UNIX domain connection requests on 
mapi:monetdb:///var/tmp/mtest-14496/.s.monetdb.35497
+# MonetDB/GIS module loaded
+# MonetDB/R   module loaded
+# MonetDB/SQL module loaded
+
+# 23:15:07 >  
+# 23:15:07 >  "mclient" "-lsql" "-ftest" "-tnone" "-Eutf-8" "-i" "-e" 
"--host=/var/tmp/mtest-14496" "--port=35497"
+# 23:15:07 >    
+
+#start transaction;
+#create table ce (id bigint);
+#insert into ce values (1), (2);
+[ 2    ]
+#create table attr (id bigint, value text);
+#insert into attr values (1,'a'),(1,'b'),(2,'a'),(2,'a'),(3,'a'),(3,'b');
+[ 6    ]
+#merge into attr using ce on ce.id = attr.id when matched then delete;
_______________________________________________
checkin-list mailing list
checkin-list@monetdb.org
https://www.monetdb.org/mailman/listinfo/checkin-list

Reply via email to