SQLite

Check-in [9441fff52c]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Fix an issue going back to version 3.39.0 with transitive IS constraints in queries that make use of RIGHT JOIN. Problem reported by forum post 68f29a2005.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | trunk
Files: files | file ages | folders
SHA3-256: 9441fff52cc4e19c44df1a77ffe474f409d519b270c7166ce17f99e6ea48fc1e
User & Date: drh 2025-06-16 17:36:11.335
Context
2025-06-16
18:04
Fix an issue going back to version 3.39.0 with transitive IS constraints in queries that make use of RIGHT JOIN. (Leaf check-in: 6c5f4c8af9 user: drh tags: branch-3.50)
17:36
Fix an issue going back to version 3.39.0 with transitive IS constraints in queries that make use of RIGHT JOIN. Problem reported by forum post 68f29a2005. (Leaf check-in: 9441fff52c user: drh tags: trunk)
16:07
Improved debugging output for the transitive constraint optimization. (check-in: 94b53c20e9 user: drh tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/whereexpr.c.
927
928
929
930
931
932
933

934
935
936
937
938
939
940
941
942
943
944
945
946
947



948
949
950
951
952
953
954
955
956
957




958
959
960
961
962
963
964
/*
** We already know that pExpr is a binary operator where both operands are
** column references.  This routine checks to see if pExpr is an equivalence
** relation:
**   1.  The SQLITE_Transitive optimization must be enabled
**   2.  Must be either an == or an IS operator
**   3.  Not originating in the ON clause of an OUTER JOIN

**   4.  The affinities of A and B must be compatible
**   5a. Both operands use the same collating sequence OR
**   5b. The overall collating sequence is BINARY
** If this routine returns TRUE, that means that the RHS can be substituted
** for the LHS anyplace else in the WHERE clause where the LHS column occurs.
** This is an optimization.  No harm comes from returning 0.  But if 1 is
** returned when it should not be, then incorrect answers might result.
*/
static int termIsEquivalence(Parse *pParse, Expr *pExpr){
  char aff1, aff2;
  CollSeq *pColl;
  if( !OptimizationEnabled(pParse->db, SQLITE_Transitive) ) return 0;
  if( pExpr->op!=TK_EQ && pExpr->op!=TK_IS ) return 0;
  if( ExprHasProperty(pExpr, EP_OuterON) ) return 0;



  aff1 = sqlite3ExprAffinity(pExpr->pLeft);
  aff2 = sqlite3ExprAffinity(pExpr->pRight);
  if( aff1!=aff2
   && (!sqlite3IsNumericAffinity(aff1) || !sqlite3IsNumericAffinity(aff2))
  ){
    return 0;
  }
  pColl = sqlite3ExprCompareCollSeq(pParse, pExpr);
  if( sqlite3IsBinary(pColl) ) return 1;
  return sqlite3ExprCollSeqMatch(pParse, pExpr->pLeft, pExpr->pRight);




}

/*
** Recursively walk the expressions of a SELECT statement and generate
** a bitmask indicating which tables are used in that expression
** tree.
*/







>
|
|
|





|


|
|
|
>
>
>





|


|
|
>
>
>
>







927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
/*
** We already know that pExpr is a binary operator where both operands are
** column references.  This routine checks to see if pExpr is an equivalence
** relation:
**   1.  The SQLITE_Transitive optimization must be enabled
**   2.  Must be either an == or an IS operator
**   3.  Not originating in the ON clause of an OUTER JOIN
**   4.  The operator is not IS or else the query does not contain RIGHT JOIN
**   5.  The affinities of A and B must be compatible
**   6a. Both operands use the same collating sequence OR
**   6b. The overall collating sequence is BINARY
** If this routine returns TRUE, that means that the RHS can be substituted
** for the LHS anyplace else in the WHERE clause where the LHS column occurs.
** This is an optimization.  No harm comes from returning 0.  But if 1 is
** returned when it should not be, then incorrect answers might result.
*/
static int termIsEquivalence(Parse *pParse, Expr *pExpr, SrcList *pSrc){
  char aff1, aff2;
  CollSeq *pColl;
  if( !OptimizationEnabled(pParse->db, SQLITE_Transitive) ) return 0;  /* (1) */
  if( pExpr->op!=TK_EQ && pExpr->op!=TK_IS ) return 0;                 /* (2) */
  if( ExprHasProperty(pExpr, EP_OuterON) ) return 0;                   /* (3) */
  if( pExpr->op==TK_IS && (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){
    return 0;                                                          /* (4) */
  }
  aff1 = sqlite3ExprAffinity(pExpr->pLeft);
  aff2 = sqlite3ExprAffinity(pExpr->pRight);
  if( aff1!=aff2
   && (!sqlite3IsNumericAffinity(aff1) || !sqlite3IsNumericAffinity(aff2))
  ){
    return 0;                                                          /* (5) */
  }
  pColl = sqlite3ExprCompareCollSeq(pParse, pExpr);
  if( !sqlite3IsBinary(pColl)
   && !sqlite3ExprCollSeqMatch(pParse, pExpr->pLeft, pExpr->pRight)
  ){
    return 0;                                                          /* (6) */
  }
  return 1;
}

/*
** Recursively walk the expressions of a SELECT statement and generate
** a bitmask indicating which tables are used in that expression
** tree.
*/
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
        idxNew = whereClauseInsert(pWC, pDup, TERM_VIRTUAL|TERM_DYNAMIC);
        if( idxNew==0 ) return;
        pNew = &pWC->a[idxNew];
        markTermAsChild(pWC, idxNew, idxTerm);
        if( op==TK_IS ) pNew->wtFlags |= TERM_IS;
        pTerm = &pWC->a[idxTerm];
        pTerm->wtFlags |= TERM_COPIED;

        if( termIsEquivalence(pParse, pDup) ){
          pTerm->eOperator |= WO_EQUIV;
          eExtraOp = WO_EQUIV;
        }
      }else{
        pDup = pExpr;
        pNew = pTerm;
      }







|
|







1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
        idxNew = whereClauseInsert(pWC, pDup, TERM_VIRTUAL|TERM_DYNAMIC);
        if( idxNew==0 ) return;
        pNew = &pWC->a[idxNew];
        markTermAsChild(pWC, idxNew, idxTerm);
        if( op==TK_IS ) pNew->wtFlags |= TERM_IS;
        pTerm = &pWC->a[idxTerm];
        pTerm->wtFlags |= TERM_COPIED;
        assert( pWInfo->pTabList!=0 );
        if( termIsEquivalence(pParse, pDup, pWInfo->pTabList) ){
          pTerm->eOperator |= WO_EQUIV;
          eExtraOp = WO_EQUIV;
        }
      }else{
        pDup = pExpr;
        pNew = pTerm;
      }
Changes to test/join.test.
1337
1338
1339
1340
1341
1342
1343



























1344
1345
} {3 NULL NULL}
do_execsql_test join-31.7 {
  SELECT * FROM t3 LEFT JOIN t2 ON true NATURAL LEFT JOIN t1;
} {3 NULL NULL}
do_execsql_test join-31.8 {
  SELECT * FROM t3 LEFT JOIN t2 ON true JOIN t4 ON true NATURAL LEFT JOIN t1;
} {3 NULL 4 NULL}




























finish_test







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
} {3 NULL NULL}
do_execsql_test join-31.7 {
  SELECT * FROM t3 LEFT JOIN t2 ON true NATURAL LEFT JOIN t1;
} {3 NULL NULL}
do_execsql_test join-31.8 {
  SELECT * FROM t3 LEFT JOIN t2 ON true JOIN t4 ON true NATURAL LEFT JOIN t1;
} {3 NULL 4 NULL}

# 2025-06-16 https://sqlite.org/forum/forumpost/68f29a2005
#
# The transitive-constraint optimization was not working for RIGHT JOIN.
#
reset_db
db null NULL
do_execsql_test join-32.1 {
  CREATE TABLE t0(w INT);
  CREATE TABLE t1(x INT);
  CREATE TABLE t2(y INT UNIQUE);
  CREATE VIEW v0(z) AS SELECT CAST(x AS INT) FROM t1 LEFT JOIN t2 ON true;
  INSERT INTO t1(x) VALUES(123);
  INSERT INTO t2(y) VALUES(NULL);
}
do_execsql_test join-32.2 {
  SELECT *
  FROM t0 JOIN v0 ON w=z
          RIGHT JOIN t1 ON true
          INNER JOIN t2 ON y IS z;
} {NULL NULL 123 NULL}
do_execsql_test join-32.3 {
  SELECT *
  FROM t0 JOIN v0 ON w=z
          RIGHT JOIN t1 ON true
          INNER JOIN t2 ON +y IS z;
} {NULL NULL 123 NULL}

finish_test