Changeset: 5ccab190745e for MonetDB
URL: https://dev.monetdb.org/hg/MonetDB/rev/5ccab190745e
Modified Files:
        sql/server/rel_optimizer.c
        sql/storage/store.c
        sql/test/miscellaneous/Tests/simple_plans.test
Branch: antipush
Log Message:

Update join2semi optimizer to use the new unique flag and fixed a bug on the 
unique flag propagation on storage. I should backport it into Jul2021


diffs (263 lines):

diff --git a/sql/server/rel_optimizer.c b/sql/server/rel_optimizer.c
--- a/sql/server/rel_optimizer.c
+++ b/sql/server/rel_optimizer.c
@@ -5280,12 +5280,16 @@ rel_push_join_down_outer(visitor *v, sql
        return rel;
 }
 
-static bool
+#define NO_PROJECTION_FOUND 0
+#define MAY_HAVE_DUPLICATE_NULLS 1
+#define ALL_VALUES_DISTINCT 2
+
+static int
 find_projection_for_join2semi(sql_rel *rel)
 {
-       if (is_project(rel->op) && !is_union(rel->op)) {
+       if (is_simple_project(rel->op) || is_groupby(rel->op) || 
is_inter(rel->op) || is_except(rel->op) || is_basetable(rel->op) || 
(is_union(rel->op) && need_distinct(rel))) {
                if (rel->card < CARD_AGGR) /* const or groupby without group by 
exps */
-                       return true;
+                       return ALL_VALUES_DISTINCT;
                if (list_length(rel->exps) == 1) {
                        sql_exp *e = rel->exps->h->data;
                        /* a single group by column in the projection list from 
a group by relation is guaranteed to be unique, but not an aggregate */
@@ -5295,28 +5299,32 @@ find_projection_for_join2semi(sql_rel *r
                                bool underjoin = false;
 
                                /* if just one groupby column is projected or 
the relation needs distinct values and one column is projected or is a primary 
key, it will be distinct */
-                               if ((is_groupby(rel->op) && list_length(rel->r) 
== 1 && exps_find_exp(rel->r, e)) ||
-                                       (is_simple_project(rel->op) && 
need_distinct(rel) && list_length(rel->exps) == 1) || find_prop(e->p, 
PROP_HASHCOL))
-                                       return true;
-
-                               if ((found = 
rel_find_exp_and_corresponding_rel(rel->l, e, &res, &underjoin)) && !underjoin) 
{ /* grouping column on inner relation */
-                                       if ((is_simple_project(res->op) && 
need_distinct(res) && list_length(res->exps) == 1) || find_prop(found->p, 
PROP_HASHCOL))
-                                               return true;
+                               if ((is_groupby(rel->op) && list_length(rel->r) 
== 1 && exps_find_exp(rel->r, e)) || (need_distinct(rel) && 
list_length(rel->exps) == 1))
+                                       return ALL_VALUES_DISTINCT;
+                               if (is_unique(e))
+                                       return has_nil(e) ? 
MAY_HAVE_DUPLICATE_NULLS : ALL_VALUES_DISTINCT;
+
+                               if ((is_simple_project(rel->op) || 
is_groupby(rel->op) || is_inter(rel->op) || is_except(rel->op)) &&
+                                       (found = 
rel_find_exp_and_corresponding_rel(rel->l, e, &res, &underjoin)) && !underjoin) 
{ /* grouping column on inner relation */
+                                       if (need_distinct(res) && 
list_length(res->exps) == 1)
+                                               return ALL_VALUES_DISTINCT;
+                                       if (is_unique(found))
+                                               return has_nil(e) ? 
MAY_HAVE_DUPLICATE_NULLS : ALL_VALUES_DISTINCT;
                                        if (found->type == e_column && 
found->card <= CARD_AGGR) {
                                                if (!is_groupby(res->op) && 
list_length(res->exps) != 1)
-                                                       return false;
+                                                       return 
NO_PROJECTION_FOUND;
                                                for (node *n = res->exps->h ; n 
; n = n->next) { /* must be the single column in the group by expression list */
                                                        sql_exp *e = n->data;
                                                        if (e != found && 
e->type == e_column)
-                                                               return false;
+                                                               return 
NO_PROJECTION_FOUND;
                                                }
-                                               return true;
+                                               return ALL_VALUES_DISTINCT;
                                        }
                                }
                        }
                }
        }
-       return false;
+       return NO_PROJECTION_FOUND;
 }
 
 static sql_rel *
@@ -5327,23 +5335,28 @@ find_candidate_join2semi(visitor *v, sql
                return NULL;
        if (rel->op == op_join && !list_empty(rel->exps)) {
                sql_rel *l = rel->l, *r = rel->r;
+               int foundr = 0, foundl = 0, found = 0;
                bool ok = false;
 
-               if (find_projection_for_join2semi(r)) {
+               foundr = find_projection_for_join2semi(r);
+               if (foundr < ALL_VALUES_DISTINCT)
+                       foundl = find_projection_for_join2semi(l);
+               if (foundr && foundr > foundl) {
                        *swap = false;
-                       ok = true;
-               } else if (find_projection_for_join2semi(l)) {
+                       found = foundr;
+               } else if (foundl) {
                        *swap = true;
-                       ok = true;
-               }
-
-               if (ok) {
+                       found = foundl;
+               }
+
+               if ((ok = found > 0)) {
                        ok = false;
                        /* if all join expressions can be pushed down or have 
function calls, then it cannot be rewritten into a semijoin */
                        for (node *n=rel->exps->h; n && !ok; n = n->next) {
                                sql_exp *e = n->data;
 
-                               ok |= e->type == e_cmp && (e->flag == cmp_equal 
|| e->flag == mark_in) && !exp_has_func(e) && !rel_rebind_exp(v->sql, l, e) && 
!rel_rebind_exp(v->sql, r, e);
+                               ok |= e->type == e_cmp && e->flag == cmp_equal 
&& !exp_has_func(e) && !rel_rebind_exp(v->sql, l, e) && !rel_rebind_exp(v->sql, 
r, e) &&
+                                       (found == ALL_VALUES_DISTINCT || 
!is_semantics(e) || (!has_nil((sql_exp *)e->l) && !has_nil((sql_exp *)e->r)));
                        }
                }
 
@@ -5606,7 +5619,7 @@ score_gbe(visitor *v, sql_rel *rel, sql_
        if (e->card == CARD_ATOM) /* constants are trivial to group */
                res += 1000;
        /* can we find out if the underlying table is sorted */
-       if (find_prop(e->p, PROP_HASHCOL) || (c && v->storage_based_opt && 
mvc_is_unique(v->sql, c))) /* distinct columns */
+       if (is_unique(e) || find_prop(e->p, PROP_HASHCOL) || (c && 
v->storage_based_opt && mvc_is_unique(v->sql, c))) /* distinct columns */
                res += 700;
        if (c && v->storage_based_opt && mvc_is_sorted(v->sql, c))
                res += 500;
diff --git a/sql/storage/store.c b/sql/storage/store.c
--- a/sql/storage/store.c
+++ b/sql/storage/store.c
@@ -420,12 +420,14 @@ load_idxcolumn(sql_trans *tr, sql_idx * 
        kc->c = find_sql_column(i->t, v);
        assert(kc->c);
        list_append(i->columns, kc);
-       if (hash_index(i->type))
-               kc->c->unique = 1;
-       if (hash_index(i->type) && list_length(i->columns) > 1) {
-               /* Correct the unique flag of the keys first column */
-               sql_kc *ic1 = i->columns->h->data;
-               ic1->c->unique = kc->c->unique = 2;
+       if (hash_index(i->type)) {
+               if (list_length(i->columns) == 1) {
+                       kc->c->unique = 1;
+               } else {
+                       /* Correct unique flag of first key column */
+                       sql_kc *ic1 = i->columns->h->data;
+                       ic1->c->unique = kc->c->unique = 2;
+               }
        }
 }
 
@@ -3238,7 +3240,7 @@ sql_trans_copy_idx( sql_trans *tr, sql_t
        sql_table *sysidx = find_sql_table(tr, syss, "idxs");
        sql_table *sysic = find_sql_table(tr, syss, "objects");
        node *n;
-       int nr, unique = 0, res = LOG_OK;
+       int nr, res = LOG_OK;
        sql_table *dup = NULL;
 
        if ((res = new_table(tr, t, &dup)))
@@ -3252,15 +3254,19 @@ sql_trans_copy_idx( sql_trans *tr, sql_t
        ni->key = NULL;
        ATOMIC_PTR_INIT(&ni->data, NULL);
 
-       if (i->type == hash_idx && list_length(i->columns) == 1)
-               unique = 1;
        for (n = i->columns->h, nr = 0; n; n = n->next, nr++) {
                sql_kc *okc = n->data, *ic;
 
                list_append(ni->columns, ic = kc_dup(tr, okc, t));
-               if (ic->c->unique != (unique & !okc->c->null))
-                       okc->c->unique = ic->c->unique = (unique & 
(!okc->c->null));
-
+               if (i->type == hash_idx) {
+                       if (nr == 0) {
+                               ic->c->unique = 1;
+                       } else {
+                               /* Correct unique flag of first key column */
+                               sql_kc *ic1 = ni->columns->h->data;
+                               ic1->c->unique = ic->c->unique = 2;
+                       }
+               }
                if ((res = store->table_api.table_insert(tr, sysic, 
&ni->base.id, &ic->c->base.name, &nr, ATOMnilptr(TYPE_int)))) {
                        idx_destroy(store, ni);
                        return res;
@@ -5547,10 +5553,14 @@ create_sql_ic(sqlstore *store, sql_alloc
        list_append(i->columns, ic);
 
        (void)store;
-       if (hash_index(i->type) && list_length(i->columns) > 1) {
-               /* Correct the unique flag of the keys first column */
-               sql_kc *ic1 = i->columns->h->data;
-               ic1->c->unique = c->unique = 2;
+       if (hash_index(i->type)) {
+               if (list_length(i->columns) == 1) {
+                       c->unique = 1;
+               } else {
+                       /* Correct unique flag of first key column */
+                       sql_kc *ic1 = i->columns->h->data;
+                       ic1->c->unique = c->unique = 2;
+               }
        }
 
        /* should we switch to oph_idx ? */
@@ -6427,10 +6437,14 @@ sql_trans_create_ic(sql_trans *tr, sql_i
        ic->c = c;
        list_append(i->columns, ic);
 
-       if (hash_index(i->type) && list_length(i->columns) > 1) {
-               /* Correct the unique flag of the keys first column */
-               sql_kc *ic1 = i->columns->h->data;
-               ic1->c->unique = c->unique = 2;
+       if (hash_index(i->type)) {
+               if (list_length(i->columns) == 1) {
+                       c->unique = 1;
+               } else {
+                       /* Correct unique flag of first key column */
+                       sql_kc *ic1 = i->columns->h->data;
+                       ic1->c->unique = c->unique = 2;
+               }
        }
 
        if ((res = store->table_api.table_insert(tr, sysic, &i->base.id, 
&ic->c->base.name, &nr, ATOMnilptr(TYPE_int))))
diff --git a/sql/test/miscellaneous/Tests/simple_plans.test 
b/sql/test/miscellaneous/Tests/simple_plans.test
--- a/sql/test/miscellaneous/Tests/simple_plans.test
+++ b/sql/test/miscellaneous/Tests/simple_plans.test
@@ -588,6 +588,50 @@ project (
 ) [ "x"."x", "x"."y" ]
 
 statement ok
+create table testkeys (a int primary key, b int unique)
+
+statement ok rowcount 3
+insert into testkeys values (1,1),(2,2),(3,3)
+
+# The following joins can be converted into semijoins
+plan select y from (values (1),(2),(cast(3 as int))) y(y) inner join testkeys 
on y.y = testkeys.a
+project (
+| semijoin (
+| |  [  [ int "1", int "2", int "3" ] as "y"."y" ],
+| | table("sys"."testkeys") [ "testkeys"."a" NOT NULL UNIQUE HASHCOL  ]
+| ) [ "y"."y" = "testkeys"."a" NOT NULL HASHCOL  ]
+) [ "y"."y" ]
+
+plan select y from (values (1),(2),(cast(3 as int))) y(y) inner join testkeys 
on y.y = testkeys.b
+project (
+| semijoin (
+| |  [  [ int "1", int "2", int "3" ] as "y"."y" ],
+| | table("sys"."testkeys") [ "testkeys"."b" UNIQUE HASHCOL  ]
+| ) [ "y"."y" = "testkeys"."b" HASHCOL  ]
+) [ "y"."y" ]
+
+query T nosort
+plan select y from (values (1),(2),(cast(3 as int))) y(y) inner join testkeys 
on y.y = testkeys.a or (y.y is null and testkeys.a is null)
+----
+project (
+| semijoin (
+| |  [  [ int "1", int "2", int "3" ] as "y"."y" ],
+| | table("sys"."testkeys") [ "testkeys"."a" NOT NULL UNIQUE HASHCOL  ]
+| ) [ "y"."y" = "testkeys"."a" NOT NULL HASHCOL  ]
+) [ "y"."y" ]
+
+# Here the inner join cannot be converted to a semijoin
+query T nosort
+plan select y from (values (1),(2),(cast(3 as int))) y(y) inner join testkeys 
on y.y = testkeys.b or (y.y is null and testkeys.b is null)
+----
+project (
+| join (
+| |  [  [ int "1", int "2", int "3" ] as "y"."y" ],
+| | table("sys"."testkeys") [ "testkeys"."b" UNIQUE HASHCOL  ]
+| ) [ "y"."y" * = "testkeys"."b" HASHCOL  ]
+) [ "y"."y" ]
+
+statement ok
 rollback
 
 statement ok
_______________________________________________
checkin-list mailing list
[email protected]
https://www.monetdb.org/mailman/listinfo/checkin-list

Reply via email to