Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.92) (envelope-from ) id 1jDBn9-0003Pb-5a for pgsql-hackers@arkaria.postgresql.org; Sat, 14 Mar 2020 18:45:47 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1jDBn5-0001Iw-K8 for pgsql-hackers@arkaria.postgresql.org; Sat, 14 Mar 2020 18:45:43 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1jDBn5-0001Ik-5w for pgsql-hackers@lists.postgresql.org; Sat, 14 Mar 2020 18:45:43 +0000 Received: from mail-wm1-x330.google.com ([2a00:1450:4864:20::330]) by magus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1jDBn1-0008Fl-3r for pgsql-hackers@lists.postgresql.org; Sat, 14 Mar 2020 18:45:41 +0000 Received: by mail-wm1-x330.google.com with SMTP id f7so13704048wml.4 for ; Sat, 14 Mar 2020 11:45:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=2ndquadrant-com.20150623.gappssmtp.com; s=20150623; h=date:from:to:cc:subject:message-id:references:mime-version :content-disposition:in-reply-to; bh=S4A0JfWqlgNQzP4OWl/cGo1+AP7yIi8l0ETmIUmwoGo=; b=D4UvLYAnHYvfboKeFcAtzv4hvV2miWLb+WbpQNSlwtRCD3MhKFS5336XAnHc6rl9pe ZznmQiCrDoZpgo7y0nPROGldqKK5EDwc8O7SK/p7jKH/V2c+nN+iIHbt7CBbR7TC80Zu uzsN26ImE3CMgc7tEeb7vtmyraJJpBsBJl4AG4V9MrcjDfGQRh/cqbk9t5Ap4cHILlE9 sXtXmJQLmw9f2+y2wwVF4s2be6dop6Uh5gec7kkjcSeyFnH1KG2qq/wJJIaHchl6+5ph lX9ZuRVCIzsW8VPAITZoVE5vwKZW2STtgrw9o/2v0AdH38yv4megb7pfWKs08MrBTtpC UIZg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to; bh=S4A0JfWqlgNQzP4OWl/cGo1+AP7yIi8l0ETmIUmwoGo=; b=Ki6dMiKlMWJ3YFgDUBPgDzkiPm6l6ZrSUPCa/h54wi9S7F5YtHuYj30Z+AskPuk7C/ FOfd1+Q3uabMIgBcfCaK1fsUGACQQU643ueMg4qc13KQVO6wErt6Izt/h8R4BEcWloQT pwT4s8cVXKWPiCB6q38ekT2QDE9h/7lyiKzcFQMLgrMt/ryH4+qWKRBlAj6q4tRLlhV7 7Wi8qZVysXU7dCA1+wY2G7kCmRZTxH4DUQkLS8na8SxabEmLFp1EqQE4Pgbv4ZN/oubj glDUi/iZk6mApbW8rGJfIWkpgVDN1rq+RwqyeFplg/+qu08ZpVjpGDbrFOxKAjhrSq4C SPOQ== X-Gm-Message-State: ANhLgQ2OqGNVyNXOr8xEfBJhOC2q0hBqsWWwMr5fNdAexYVzG6tgUQUW d/1wJon/2tAMHzBvWvuXlW5ozA== X-Google-Smtp-Source: ADFU+vvnUjRtmBdyTUliYVs7xRbl+Yg3WHPZIMgOWWkhMV9ArjzZ4396oxQEMblHdoUfDpz6JUBraw== X-Received: by 2002:a1c:2856:: with SMTP id o83mr17156427wmo.157.1584211537720; Sat, 14 Mar 2020 11:45:37 -0700 (PDT) Received: from localhost (ip-86-49-253-92.net.upcbroadband.cz. [86.49.253.92]) by smtp.gmail.com with ESMTPSA id c124sm22810502wma.10.2020.03.14.11.45.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 14 Mar 2020 11:45:36 -0700 (PDT) Date: Sat, 14 Mar 2020 19:45:35 +0100 From: Tomas Vondra To: Dean Rasheed Cc: Pierre Ducroquet , PostgreSQL Hackers Subject: Re: PATCH: add support for IN and @> in functional-dependency statistics use Message-ID: <20200314184535.4jgkxcyg5yprvsra@development> References: <13902317.Eha0YfKkKy@pierred-pdoc> <20200201142446.aq5zf3gawj3ynrc2@development> <3937607.jCdSCb7NGl@pierred-pdoc> <20200202184134.swoqkqlqorqolrqv@development> <20200312173047.stp55sxoj4sipn7q@development> <20200313160944.aqrbkyf7tkasoxfk@development> <20200314152159.5webesf6pfluatqj@development> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="7yiclfihkaaohw62" Content-Disposition: inline In-Reply-To: <20200314152159.5webesf6pfluatqj@development> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk --7yiclfihkaaohw62 Content-Type: text/plain; charset=us-ascii; format=flowed Content-Disposition: inline Hi, I realized there's one more thing that probably needs discussing. Essentially, these two clause types are the same: a IN (1, 2, 3) (a = 1 OR a = 2 OR a = 3) but with 8f321bd1 we only recognize the first one as compatible with functional dependencies. It was always the case that we estimated those two clauses a bit differently, but the differences were usually small. But now that we recognize IN as compatible with dependencies, the difference may be much larger, which bugs me a bit ... So I wonder if we should recognize the special form of an OR clause, with all Vars referencing to the same attribute etc. and treat this as supported by functional dependencies - the attached patch does that. MCV lists there's already no difference because OR clauses are supported. The question is whether we want to do this, and whether we should also teach the per-column estimates to recognize this special case of IN clause. That would allow producing exactly the same estimates even with functional dependencies - recognizing the OR clause as supported gets us only half-way there, because we still use estimates for each clause (and those will be slightly different). regards -- Tomas Vondra http://www.2ndQuadrant.com PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services --7yiclfihkaaohw62 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="estimate-OR-similar-to-IN-clauses.patch" diff --git a/src/backend/statistics/dependencies.c b/src/backend/statistics/dependencies.c index 72dc1cd1bd..5f9b43bf7f 100644 --- a/src/backend/statistics/dependencies.c +++ b/src/backend/statistics/dependencies.c @@ -753,24 +753,27 @@ pg_dependencies_send(PG_FUNCTION_ARGS) static bool dependency_is_compatible_clause(Node *clause, Index relid, AttrNumber *attnum) { - RestrictInfo *rinfo = (RestrictInfo *) clause; Var *var; - if (!IsA(rinfo, RestrictInfo)) - return false; + if (IsA(clause, RestrictInfo)) + { + RestrictInfo *rinfo = (RestrictInfo *) clause; - /* Pseudoconstants are not interesting (they couldn't contain a Var) */ - if (rinfo->pseudoconstant) - return false; + /* Pseudoconstants are not interesting (they couldn't contain a Var) */ + if (rinfo->pseudoconstant) + return false; - /* Clauses referencing multiple, or no, varnos are incompatible */ - if (bms_membership(rinfo->clause_relids) != BMS_SINGLETON) - return false; + /* Clauses referencing multiple, or no, varnos are incompatible */ + if (bms_membership(rinfo->clause_relids) != BMS_SINGLETON) + return false; - if (is_opclause(rinfo->clause)) + clause = (Node *) rinfo->clause; + } + + if (is_opclause(clause)) { /* If it's an opclause, check for Var = Const or Const = Var. */ - OpExpr *expr = (OpExpr *) rinfo->clause; + OpExpr *expr = (OpExpr *) clause; /* Only expressions with two arguments are candidates. */ if (list_length(expr->args) != 2) @@ -801,10 +804,10 @@ dependency_is_compatible_clause(Node *clause, Index relid, AttrNumber *attnum) /* OK to proceed with checking "var" */ } - else if (IsA(rinfo->clause, ScalarArrayOpExpr)) + else if (IsA(clause, ScalarArrayOpExpr)) { /* If it's an scalar array operator, check for Var IN Const. */ - ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) rinfo->clause; + ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) clause; /* * Reject ALL() variant, we only care about ANY/IN. @@ -839,13 +842,43 @@ dependency_is_compatible_clause(Node *clause, Index relid, AttrNumber *attnum) /* OK to proceed with checking "var" */ } - else if (is_notclause(rinfo->clause)) + else if (is_orclause(clause)) + { + BoolExpr *expr = (BoolExpr *) clause; + ListCell *lc; + + /* start with no attribute number */ + *attnum = InvalidAttrNumber; + + foreach(lc, expr->args) + { + AttrNumber clause_attnum; + + /* + * Had we found incompatible clause in the arguments, treat the + * whole clause as incompatible. + */ + if (!dependency_is_compatible_clause((Node *) lfirst(lc), + relid, &clause_attnum)) + return false; + + if (*attnum == InvalidAttrNumber) + *attnum = clause_attnum; + + if (*attnum != clause_attnum) + return false; + } + + /* the Var is already checked by the recursive call */ + return true; + } + else if (is_notclause(clause)) { /* * "NOT x" can be interpreted as "x = false", so get the argument and * proceed with seeing if it's a suitable Var. */ - var = (Var *) get_notclausearg(rinfo->clause); + var = (Var *) get_notclausearg(clause); } else { @@ -853,7 +886,7 @@ dependency_is_compatible_clause(Node *clause, Index relid, AttrNumber *attnum) * A boolean expression "x" can be interpreted as "x = true", so * proceed with seeing if it's a suitable Var. */ - var = (Var *) rinfo->clause; + var = (Var *) clause; } /* diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out index 9fa659c71d..74da75c3c6 100644 --- a/src/test/regress/expected/stats_ext.out +++ b/src/test/regress/expected/stats_ext.out @@ -458,6 +458,32 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE 3 | 400 (1 row) +-- OR clauses referencing the same attribute +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND b = ''1'''); + estimated | actual +-----------+-------- + 2 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND (b = ''1'' OR b = ''2'')'); + estimated | actual +-----------+-------- + 4 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 2 OR a = 51 OR a = 52) AND (b = ''1'' OR b = ''2'')'); + estimated | actual +-----------+-------- + 8 | 200 +(1 row) + +-- OR clauses referencing different attributes +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR b = ''1'') AND b = ''1'''); + estimated | actual +-----------+-------- + 3 | 100 +(1 row) + -- ANY SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 51]) AND b = ''1'''); estimated | actual @@ -585,6 +611,32 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE 400 | 400 (1 row) +-- OR clauses referencing the same attribute +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND b = ''1'''); + estimated | actual +-----------+-------- + 99 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND (b = ''1'' OR b = ''2'')'); + estimated | actual +-----------+-------- + 99 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 2 OR a = 51 OR a = 52) AND (b = ''1'' OR b = ''2'')'); + estimated | actual +-----------+-------- + 197 | 200 +(1 row) + +-- OR clauses referencing different attributes are incompatible +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR b = ''1'') AND b = ''1'''); + estimated | actual +-----------+-------- + 3 | 100 +(1 row) + -- ANY SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 51]) AND b = ''1'''); estimated | actual diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql index 0ece39a279..d9c935896b 100644 --- a/src/test/regress/sql/stats_ext.sql +++ b/src/test/regress/sql/stats_ext.sql @@ -286,6 +286,16 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 26, 27, 51, 52, 76, 77) AND b IN (''1'', ''2'', ''26'', ''27'') AND c IN (1, 2)'); +-- OR clauses referencing the same attribute +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND b = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND (b = ''1'' OR b = ''2'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 2 OR a = 51 OR a = 52) AND (b = ''1'' OR b = ''2'')'); + +-- OR clauses referencing different attributes +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR b = ''1'') AND b = ''1'''); + -- ANY SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 51]) AND b = ''1'''); @@ -335,6 +345,16 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 26, 27, 51, 52, 76, 77) AND b IN (''1'', ''2'', ''26'', ''27'') AND c IN (1, 2)'); +-- OR clauses referencing the same attribute +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND b = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND (b = ''1'' OR b = ''2'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 2 OR a = 51 OR a = 52) AND (b = ''1'' OR b = ''2'')'); + +-- OR clauses referencing different attributes are incompatible +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR b = ''1'') AND b = ''1'''); + -- ANY SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 51]) AND b = ''1'''); --7yiclfihkaaohw62--