This is an automated email from the ASF dual-hosted git repository.
soumyava pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new 75af741a96 Revert "SQL: Plan non-equijoin conditions as cross join
followed by filter. (#14978)" (#15029)
75af741a96 is described below
commit 75af741a96df10f646d91e262cf6a08e56bda091
Author: Soumyava <[email protected]>
AuthorDate: Mon Sep 25 11:35:44 2023 -0700
Revert "SQL: Plan non-equijoin conditions as cross join followed by filter.
(#14978)" (#15029)
This reverts commit 4f498e64691ecd22eaa2c940d1d0d57e769ee9e7.
---
docs/querying/datasource.md | 36 ++---
.../druid/msq/test/CalciteSelectQueryMSQTest.java | 2 +-
.../sql/calcite/planner/CalciteRulesManager.java | 10 --
.../druid/sql/calcite/CalciteJoinQueryTest.java | 180 ++++-----------------
.../apache/druid/sql/calcite/CalciteQueryTest.java | 25 ++-
website/.spelling | 1 -
6 files changed, 67 insertions(+), 187 deletions(-)
diff --git a/docs/querying/datasource.md b/docs/querying/datasource.md
index 333461b729..ae87431587 100644
--- a/docs/querying/datasource.md
+++ b/docs/querying/datasource.md
@@ -320,14 +320,12 @@ Join datasources allow you to do a SQL-style join of two
datasources. Stacking j
you to join arbitrarily many datasources.
In Druid {{DRUIDVERSION}}, joins in native queries are implemented with a
broadcast hash-join algorithm. This means
-that all datasources other than the leftmost "base" datasource must fit in
memory. In native queries, the join condition
-must be an equality. In SQL, any join condition is accepted, but only
equalities of a certain form
-(see [Joins in SQL](#joins-in-sql)) execute as part of a native join. Other
kinds of conditions execute as a cross join
-(cartesian product) plus a filter.
+that all datasources other than the leftmost "base" datasource must fit in
memory. It also means that the join condition
+must be an equality. This feature is intended mainly to allow joining regular
Druid tables with [lookup](#lookup),
+[inline](#inline), and [query](#query) datasources.
-This feature is intended mainly to allow joining regular Druid tables with
[lookup](#lookup), [inline](#inline), and
-[query](#query) datasources. Refer to the [Query
execution](query-execution.md#join) page for more details on how
-queries are executed when you use join datasources.
+Refer to the [Query execution](query-execution.md#join) page for more details
on how queries are executed when you
+use join datasources.
#### Joins in SQL
@@ -337,23 +335,21 @@ SQL joins take the form:
<o1> [ INNER | LEFT [OUTER] ] JOIN <o2> ON <condition>
```
-Any condition is accepted, but only certain kinds of conditions execute as
part of a native join. To execute efficiently
-as part of a native join, a condition must be a single clause like the
following, or an `AND` of clauses like the
-following:
+The condition must involve only equalities, but functions are okay, and there
can be multiple equalities ANDed together.
+Conditions like `t1.x = t2.x`, or `LOWER(t1.x) = t2.x`, or `t1.x = t2.x AND
t1.y = t2.y` can all be handled. Conditions
+like `t1.x <> t2.x` cannot currently be handled.
-- Equality between fields of the same type on each side, like `t1 JOIN t2 ON
t1.x = t2.x`.
-- Equality between a function call on one side, and a field on the other side,
like `t1 JOIN t2 ON LOWER(t1.x) = t2.x`.
-- The equality operator may be `=` (which does not match nulls) or `IS NOT
DISTINCT FROM` (which does match nulls).
+Note that Druid SQL is less rigid than what native join datasources can
handle. In cases where a SQL query does
+something that is not allowed as-is with a native join datasource, Druid SQL
will generate a subquery. This can have
+a substantial effect on performance and scalability, so it is something to
watch out for. Some examples of when the
+SQL layer will generate subqueries include:
-In other cases, Druid will either insert a subquery below the join, or will
use a cross join (cartesian product)
-followed by a filter. Joins executed in these ways may run into resource or
performance constraints. To determine
-if your query is using one of these execution paths, run `EXPLAIN PLAN FOR
<query>` and look for the following:
+- Joining a regular Druid table to itself, or to another regular Druid table.
The native join datasource can accept
+a table on the left-hand side, but not the right, so a subquery is needed.
-- `query` type datasources under the `left` or `right` key of your `join`
datasource.
-- `join` type datasource with `condition` set to `"1"` (cartesian product)
followed by a `filter` that encodes the
- condition you provided.
+- Join conditions where the expressions on either side are of different types.
-In these cases, you may be able to improve the performance of your query by
rewriting it.
+- Join conditions where the right-hand expression is not a direct column
access.
For more information about how Druid translates SQL to native queries, refer
to the
[Druid SQL](sql-translation.md) documentation.
diff --git
a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteSelectQueryMSQTest.java
b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteSelectQueryMSQTest.java
index d7131d0b7e..5ee3ba8753 100644
---
a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteSelectQueryMSQTest.java
+++
b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteSelectQueryMSQTest.java
@@ -144,7 +144,7 @@ public class CalciteSelectQueryMSQTest extends
CalciteQueryTest
@Ignore
@Override
- public void testUnplannableScanOrderByNonTime()
+ public void testUnplannableQueries()
{
}
diff --git
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/CalciteRulesManager.java
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/CalciteRulesManager.java
index a3a46de2ba..8d2f110392 100644
---
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/CalciteRulesManager.java
+++
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/CalciteRulesManager.java
@@ -162,15 +162,6 @@ public class CalciteRulesManager
CoreRules.INTERSECT_TO_DISTINCT
);
- /**
- * Rules from Calcite that are not part of Calcite's standard set, but that
we use anyway.
- */
- private static final List<RelOptRule> EXTRA_CALCITE_RULES =
- ImmutableList.of(
- // Useful for planning funky join conditions as filters on top of
cross joins.
- CoreRules.JOIN_EXTRACT_FILTER
- );
-
/**
* Rules from {@link
org.apache.calcite.plan.RelOptRules#ABSTRACT_RELATIONAL_RULES}, minus:
*
@@ -349,7 +340,6 @@ public class CalciteRulesManager
rules.addAll(BASE_RULES);
rules.addAll(ABSTRACT_RULES);
rules.addAll(ABSTRACT_RELATIONAL_RULES);
- rules.addAll(EXTRA_CALCITE_RULES);
if (plannerContext.getJoinAlgorithm().requiresSubquery()) {
rules.addAll(FANCY_JOIN_RULES);
diff --git
a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteJoinQueryTest.java
b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteJoinQueryTest.java
index a0e96a8763..fa159a0132 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteJoinQueryTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteJoinQueryTest.java
@@ -3470,7 +3470,7 @@ public class CalciteJoinQueryTest extends
BaseCalciteQueryTest
// Cannot vectorize due to 'concat' expression.
cannotVectorize();
- ScanQuery expectedQuery = newScanQueryBuilder()
+ ScanQuery nullCompatibleModePlan = newScanQueryBuilder()
.dataSource(
join(
new TableDataSource(CalciteTests.DATASOURCE1),
@@ -3496,6 +3496,33 @@ public class CalciteJoinQueryTest extends
BaseCalciteQueryTest
.context(queryContext)
.build();
+ ScanQuery nonNullCompatibleModePlan = newScanQueryBuilder()
+ .dataSource(
+ join(
+ new TableDataSource(CalciteTests.DATASOURCE1),
+ new QueryDataSource(
+ GroupByQuery
+ .builder()
+ .setDataSource(new LookupDataSource("lookyloo"))
+ .setInterval(querySegmentSpec(Filtration.eternity()))
+ .setGranularity(Granularities.ALL)
+ .setVirtualColumns(
+ expressionVirtualColumn("v0", "concat(\"k\",'')",
ColumnType.STRING)
+ )
+ .setDimensions(new DefaultDimensionSpec("v0", "d0"))
+ .build()
+ ),
+ "j0.",
+ equalsCondition(makeColumnExpression("dim1"),
makeColumnExpression("j0.d0")),
+ JoinType.LEFT
+ )
+ )
+ .intervals(querySegmentSpec(Filtration.eternity()))
+ .columns("dim1", "j0.d0")
+ .filters(notNull("j0.d0"))
+ .context(queryContext)
+ .build();
+
boolean isJoinFilterRewriteEnabled =
queryContext.getOrDefault(QueryContexts.JOIN_FILTER_REWRITE_ENABLE_KEY, true)
.toString()
.equals("true");
@@ -3505,7 +3532,7 @@ public class CalciteJoinQueryTest extends
BaseCalciteQueryTest
+ "LEFT JOIN (select k || '' as k from lookup.lookyloo group by 1) l1
ON foo.dim1 = l1.k\n"
+ "WHERE l1.k IS NOT NULL\n",
queryContext,
- ImmutableList.of(expectedQuery),
+ ImmutableList.of(NullHandling.sqlCompatible() ? nullCompatibleModePlan
: nonNullCompatibleModePlan),
NullHandling.sqlCompatible() || !isJoinFilterRewriteEnabled
? ImmutableList.of(new Object[]{"abc", "abc"})
: ImmutableList.of(
@@ -4515,155 +4542,6 @@ public class CalciteJoinQueryTest extends
BaseCalciteQueryTest
);
}
- @Test
- @Parameters(source = QueryContextForJoinProvider.class)
- public void testJoinWithImplicitIsNotDistinctFromCondition(Map<String,
Object> queryContext)
- {
- // Like "testInnerJoin", but uses an implied is-not-distinct-from instead
of equals.
- cannotVectorize();
-
- testQuery(
- "SELECT x.m1, y.m1\n"
- + "FROM foo x INNER JOIN foo y ON (x.m1 = y.m1) OR (x.m1 IS NULL AND
y.m1 IS NULL)",
- queryContext,
- ImmutableList.of(
- newScanQueryBuilder()
- .dataSource(
- join(
- new TableDataSource(CalciteTests.DATASOURCE1),
- new QueryDataSource(
- newScanQueryBuilder()
- .dataSource(CalciteTests.DATASOURCE1)
-
.intervals(querySegmentSpec(Filtration.eternity()))
- .columns("m1")
- .context(queryContext)
- .build()
- ),
- "j0.",
- "notdistinctfrom(\"m1\",\"j0.m1\")",
- JoinType.INNER
- )
- )
- .intervals(querySegmentSpec(Filtration.eternity()))
- .columns("j0.m1", "m1")
- .context(queryContext)
- .build()
- ),
- ImmutableList.of(
- new Object[]{1.0f, 1.0f},
- new Object[]{2.0f, 2.0f},
- new Object[]{3.0f, 3.0f},
- new Object[]{4.0f, 4.0f},
- new Object[]{5.0f, 5.0f},
- new Object[]{6.0f, 6.0f}
- )
- );
- }
-
- @Test
- @Parameters(source = QueryContextForJoinProvider.class)
- public void testJoinWithNonEquiCondition(Map<String, Object> queryContext)
- {
- // Native JOIN operator cannot handle the condition, so a SQL JOIN with
greater-than is translated into a
- // cross join with a filter.
- cannotVectorize();
-
- testQuery(
- "SELECT x.m1, y.m1 FROM foo x INNER JOIN foo y ON x.m1 > y.m1",
- queryContext,
- ImmutableList.of(
- newScanQueryBuilder()
- .dataSource(
- join(
- new TableDataSource(CalciteTests.DATASOURCE1),
- new QueryDataSource(
- newScanQueryBuilder()
- .dataSource(CalciteTests.DATASOURCE1)
-
.intervals(querySegmentSpec(Filtration.eternity()))
- .columns("m1")
- .context(queryContext)
- .build()
- ),
- "j0.",
- "1",
- JoinType.INNER
- )
- )
- .intervals(querySegmentSpec(Filtration.eternity()))
- .filters(expressionFilter("(\"m1\" > \"j0.m1\")"))
- .columns("j0.m1", "m1")
- .context(queryContext)
- .build()
- ),
- sortIfSortBased(
- ImmutableList.of(
- new Object[]{2.0f, 1.0f},
- new Object[]{3.0f, 1.0f},
- new Object[]{3.0f, 2.0f},
- new Object[]{4.0f, 1.0f},
- new Object[]{4.0f, 2.0f},
- new Object[]{4.0f, 3.0f},
- new Object[]{5.0f, 1.0f},
- new Object[]{5.0f, 2.0f},
- new Object[]{5.0f, 3.0f},
- new Object[]{5.0f, 4.0f},
- new Object[]{6.0f, 1.0f},
- new Object[]{6.0f, 2.0f},
- new Object[]{6.0f, 3.0f},
- new Object[]{6.0f, 4.0f},
- new Object[]{6.0f, 5.0f}
- ),
- 1,
- 0
- )
- );
- }
-
- @Test
- @Parameters(source = QueryContextForJoinProvider.class)
- public void testJoinWithEquiAndNonEquiCondition(Map<String, Object>
queryContext)
- {
- // Native JOIN operator cannot handle the condition, so a SQL JOIN with
greater-than is translated into a
- // cross join with a filter.
- cannotVectorize();
-
- testQuery(
- "SELECT x.m1, y.m1 FROM foo x INNER JOIN foo y ON x.m1 = y.m1 AND x.m1
+ y.m1 = 6.0",
- queryContext,
- ImmutableList.of(
- newScanQueryBuilder()
- .dataSource(
- join(
- new TableDataSource(CalciteTests.DATASOURCE1),
- new QueryDataSource(
- newScanQueryBuilder()
- .dataSource(CalciteTests.DATASOURCE1)
-
.intervals(querySegmentSpec(Filtration.eternity()))
- .columns("m1")
- .context(queryContext)
- .build()
- ),
- "j0.",
- "1",
- JoinType.INNER
- )
- )
- .virtualColumns(expressionVirtualColumn("v0", "(\"m1\" +
\"j0.m1\")", ColumnType.DOUBLE))
- .intervals(querySegmentSpec(Filtration.eternity()))
- .filters(
- and(
- expressionFilter("(\"m1\" == \"j0.m1\")"),
- equality("v0", 6.0, ColumnType.DOUBLE)
- )
- )
- .columns("j0.m1", "m1")
- .context(queryContext)
- .build()
- ),
- ImmutableList.of(new Object[]{3.0f, 3.0f})
- );
- }
-
@Test
@Parameters(source = QueryContextForJoinProvider.class)
public void testUsingSubqueryAsPartOfAndFilter(Map<String, Object>
queryContext)
diff --git
a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
index 74800270bc..2d6dc078b9 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
@@ -5688,15 +5688,32 @@ public class CalciteQueryTest extends
BaseCalciteQueryTest
@DecoupledIgnore(mode = Modes.ERROR_HANDLING)
@Test
- public void testUnplannableScanOrderByNonTime()
+ public void testUnplannableQueries()
{
- // Scan can ORDER BY non-time in MSQ.
notMsqCompatible();
+ // All of these queries are unplannable because they rely on features
Druid doesn't support.
+ // This test is here to confirm that we don't fall back to Calcite's
interpreter or enumerable implementation.
+ // It's also here so when we do support these features, we can have "real"
tests for these queries.
- assertQueryIsUnplannable(
+ final Map<String, String> queries = ImmutableMap.of(
+ // SELECT query with order by non-__time.
"SELECT dim1 FROM druid.foo ORDER BY dim1",
- "SQL query requires order by non-time column [[dim1 ASC]], which is
not supported."
+ "SQL query requires order by non-time column [[dim1 ASC]], which is
not supported.",
+
+ // JOIN condition with not-equals (<>).
+ "SELECT foo.dim1, foo.dim2, l.k, l.v\n"
+ + "FROM foo INNER JOIN lookup.lookyloo l ON foo.dim2 <> l.k",
+ "SQL requires a join with 'NOT_EQUALS' condition that is not
supported.",
+
+ // JOIN condition with a function of both sides.
+ "SELECT foo.dim1, foo.dim2, l.k, l.v\n"
+ + "FROM foo INNER JOIN lookup.lookyloo l ON CHARACTER_LENGTH(foo.dim2
|| l.k) > 3\n",
+ "SQL requires a join with 'GREATER_THAN' condition that is not
supported."
);
+
+ for (final Map.Entry<String, String> queryErrorPair : queries.entrySet()) {
+ assertQueryIsUnplannable(queryErrorPair.getKey(),
queryErrorPair.getValue());
+ }
}
@Test
diff --git a/website/.spelling b/website/.spelling
index 4333cf5dc0..10751d6953 100644
--- a/website/.spelling
+++ b/website/.spelling
@@ -43,7 +43,6 @@ Base64
Base64-encoded
ByteBuffer
bottlenecked
-cartesian
concat
CIDR
CORS
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]