korlov42 commented on code in PR #4221:
URL: https://github.com/apache/ignite-3/pull/4221#discussion_r1720024290
##########
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerHelper.java:
##########
@@ -343,4 +360,171 @@ static boolean hasSubQuery(SqlNode node) {
return super.visit(call);
}
}
+
+
+ /**
+ * Tries to optimize a query that looks like {@code SELECT count(*)}.
+ *
+ * @param planner Planner.
+ * @param txContext Transactional context.
+ * @param node Query node.
+ * @return Plan node with list of aliases, if the optimization is
applicable.
+ */
+ public static @Nullable Pair<IgniteRel, List<String>>
tryOptimizeSelectCount(
+ IgnitePlanner planner,
+ @Nullable QueryTransactionContext txContext,
+ SqlNode node
+ ) {
+ if (txContext != null && txContext.explicitTx() != null) {
+ return null;
+ }
+
+ if (!(node instanceof SqlSelect)) {
+ return null;
+ }
+
+ SqlSelect select = (SqlSelect) node;
+
+ if (select.getGroup() != null
+ || select.getWhere() != null
+ || select.getHaving() != null
+ || select.getQualify() != null
+ || !select.getWindowList().isEmpty()
+ || select.getOffset() != null
+ || select.getFetch() != null) {
+ return null;
+ }
+
+ IgniteSqlToRelConvertor converter = planner.sqlToRelConverter();
+
+ RelOptTable targetTable;
+
+ if (select.getFrom() != null) {
+ SqlNode from = SqlUtil.stripAs(select.getFrom());
+ // Skip non-references such as VALUES ..
+ if (from.getKind() != SqlKind.IDENTIFIER) {
+ return null;
+ }
+
+ targetTable = converter.getTargetTable(select.getFrom());
+ IgniteDataSource dataSource =
targetTable.unwrap(IgniteDataSource.class);
+ if (!(dataSource instanceof IgniteTable)) {
+ return null;
+ }
+ } else {
+ // Try to optimize SELECT count(*) as well
+ targetTable = null;
+ }
+
+ IgniteTypeFactory typeFactory = planner.getTypeFactory();
+
+ // SELECT COUNT(*) ... row type
+ RelDataType countResultType =
typeFactory.createSqlType(SqlTypeName.BIGINT);
+
+ RelDataTypeFactory.Builder inputRowBuilder = new Builder(typeFactory);
+ inputRowBuilder.add("ROWCOUNT", countResultType);
+ RelDataType inputRowType = inputRowBuilder.build();
+
+ RelDataType getCountType =
inputRowType.getFieldList().get(0).getType();
+
+ // Build projection
+ // Rewrites SELECT count(*) ... as Project(exprs = [lit, $0, ... ]),
where $0 references a row that stores a count.
+ // So we can feed results of get count operation into a projection to
compute final results.
+
+ List<RexNode> expressions = new ArrayList<>();
+ List<String> expressionNames = new ArrayList<>();
+ boolean countAdded = false;
+
+ for (SqlNode selectItem : select.getSelectList()) {
+ SqlNode expr = SqlUtil.stripAs(selectItem);
+
+ if (isCountStar(planner.validator(), expr)) {
+ SqlCall call = (SqlCall) expr;
+
+ // Reject COUNT(DISTINCT id), but allow COUNT(DISTINCT 1)
Review Comment:
`COUNT(DISTINCT 1)` must return 0 for empty table and 1 for non empty one,
but after this patch latter case return N where N is a number of rows in a
table.
##########
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerHelper.java:
##########
@@ -343,4 +360,171 @@ static boolean hasSubQuery(SqlNode node) {
return super.visit(call);
}
}
+
+
+ /**
+ * Tries to optimize a query that looks like {@code SELECT count(*)}.
+ *
+ * @param planner Planner.
+ * @param txContext Transactional context.
+ * @param node Query node.
+ * @return Plan node with list of aliases, if the optimization is
applicable.
+ */
+ public static @Nullable Pair<IgniteRel, List<String>>
tryOptimizeSelectCount(
+ IgnitePlanner planner,
+ @Nullable QueryTransactionContext txContext,
+ SqlNode node
+ ) {
+ if (txContext != null && txContext.explicitTx() != null) {
+ return null;
+ }
+
+ if (!(node instanceof SqlSelect)) {
+ return null;
+ }
+
+ SqlSelect select = (SqlSelect) node;
+
+ if (select.getGroup() != null
+ || select.getWhere() != null
+ || select.getHaving() != null
+ || select.getQualify() != null
+ || !select.getWindowList().isEmpty()
+ || select.getOffset() != null
+ || select.getFetch() != null) {
+ return null;
+ }
+
+ IgniteSqlToRelConvertor converter = planner.sqlToRelConverter();
+
+ RelOptTable targetTable;
+
+ if (select.getFrom() != null) {
+ SqlNode from = SqlUtil.stripAs(select.getFrom());
+ // Skip non-references such as VALUES ..
+ if (from.getKind() != SqlKind.IDENTIFIER) {
+ return null;
+ }
+
+ targetTable = converter.getTargetTable(select.getFrom());
+ IgniteDataSource dataSource =
targetTable.unwrap(IgniteDataSource.class);
+ if (!(dataSource instanceof IgniteTable)) {
+ return null;
+ }
+ } else {
+ // Try to optimize SELECT count(*) as well
+ targetTable = null;
+ }
+
+ IgniteTypeFactory typeFactory = planner.getTypeFactory();
+
+ // SELECT COUNT(*) ... row type
+ RelDataType countResultType =
typeFactory.createSqlType(SqlTypeName.BIGINT);
+
+ RelDataTypeFactory.Builder inputRowBuilder = new Builder(typeFactory);
+ inputRowBuilder.add("ROWCOUNT", countResultType);
+ RelDataType inputRowType = inputRowBuilder.build();
Review Comment:
looks like these lines can be safely removed: you already have created
`countResultType` at line 422
##########
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerHelper.java:
##########
@@ -343,4 +360,171 @@ static boolean hasSubQuery(SqlNode node) {
return super.visit(call);
}
}
+
+
+ /**
+ * Tries to optimize a query that looks like {@code SELECT count(*)}.
+ *
+ * @param planner Planner.
+ * @param txContext Transactional context.
+ * @param node Query node.
+ * @return Plan node with list of aliases, if the optimization is
applicable.
+ */
+ public static @Nullable Pair<IgniteRel, List<String>>
tryOptimizeSelectCount(
+ IgnitePlanner planner,
+ @Nullable QueryTransactionContext txContext,
+ SqlNode node
+ ) {
+ if (txContext != null && txContext.explicitTx() != null) {
+ return null;
+ }
+
+ if (!(node instanceof SqlSelect)) {
+ return null;
+ }
+
+ SqlSelect select = (SqlSelect) node;
+
+ if (select.getGroup() != null
+ || select.getWhere() != null
+ || select.getHaving() != null
+ || select.getQualify() != null
+ || !select.getWindowList().isEmpty()
+ || select.getOffset() != null
+ || select.getFetch() != null) {
+ return null;
+ }
+
+ IgniteSqlToRelConvertor converter = planner.sqlToRelConverter();
+
+ RelOptTable targetTable;
+
+ if (select.getFrom() != null) {
+ SqlNode from = SqlUtil.stripAs(select.getFrom());
+ // Skip non-references such as VALUES ..
+ if (from.getKind() != SqlKind.IDENTIFIER) {
+ return null;
+ }
+
+ targetTable = converter.getTargetTable(select.getFrom());
+ IgniteDataSource dataSource =
targetTable.unwrap(IgniteDataSource.class);
+ if (!(dataSource instanceof IgniteTable)) {
+ return null;
+ }
+ } else {
+ // Try to optimize SELECT count(*) as well
+ targetTable = null;
+ }
+
+ IgniteTypeFactory typeFactory = planner.getTypeFactory();
+
+ // SELECT COUNT(*) ... row type
+ RelDataType countResultType =
typeFactory.createSqlType(SqlTypeName.BIGINT);
+
+ RelDataTypeFactory.Builder inputRowBuilder = new Builder(typeFactory);
+ inputRowBuilder.add("ROWCOUNT", countResultType);
+ RelDataType inputRowType = inputRowBuilder.build();
+
+ RelDataType getCountType =
inputRowType.getFieldList().get(0).getType();
+
+ // Build projection
+ // Rewrites SELECT count(*) ... as Project(exprs = [lit, $0, ... ]),
where $0 references a row that stores a count.
+ // So we can feed results of get count operation into a projection to
compute final results.
+
+ List<RexNode> expressions = new ArrayList<>();
+ List<String> expressionNames = new ArrayList<>();
+ boolean countAdded = false;
+
+ for (SqlNode selectItem : select.getSelectList()) {
+ SqlNode expr = SqlUtil.stripAs(selectItem);
+
+ if (isCountStar(planner.validator(), expr)) {
+ SqlCall call = (SqlCall) expr;
+
+ // Reject COUNT(DISTINCT id), but allow COUNT(DISTINCT 1)
+ if (call.getFunctionQuantifier() != null &&
call.getOperandList().get(0).getKind() != SqlKind.LITERAL) {
+ return null;
+ }
+
+ RexBuilder rexBuilder = planner.cluster().getRexBuilder();
+ RexSlot countValRef = rexBuilder.makeInputRef(getCountType, 0);
+
+ expressions.add(countValRef);
+
+ countAdded = true;
+ } else if (isCountNull(expr)) {
Review Comment:
it's better to introduce general purpose optimization (under separate
ticket), which will reduce `COUNT(NULL)` to simple `Values([[0]])` node.
##########
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerHelper.java:
##########
@@ -343,4 +360,171 @@ static boolean hasSubQuery(SqlNode node) {
return super.visit(call);
}
}
+
+
+ /**
+ * Tries to optimize a query that looks like {@code SELECT count(*)}.
+ *
+ * @param planner Planner.
+ * @param txContext Transactional context.
+ * @param node Query node.
+ * @return Plan node with list of aliases, if the optimization is
applicable.
+ */
+ public static @Nullable Pair<IgniteRel, List<String>>
tryOptimizeSelectCount(
+ IgnitePlanner planner,
+ @Nullable QueryTransactionContext txContext,
+ SqlNode node
+ ) {
+ if (txContext != null && txContext.explicitTx() != null) {
+ return null;
+ }
+
+ if (!(node instanceof SqlSelect)) {
+ return null;
+ }
+
+ SqlSelect select = (SqlSelect) node;
+
+ if (select.getGroup() != null
+ || select.getWhere() != null
+ || select.getHaving() != null
+ || select.getQualify() != null
+ || !select.getWindowList().isEmpty()
+ || select.getOffset() != null
+ || select.getFetch() != null) {
+ return null;
+ }
+
+ IgniteSqlToRelConvertor converter = planner.sqlToRelConverter();
+
+ RelOptTable targetTable;
+
+ if (select.getFrom() != null) {
+ SqlNode from = SqlUtil.stripAs(select.getFrom());
+ // Skip non-references such as VALUES ..
+ if (from.getKind() != SqlKind.IDENTIFIER) {
+ return null;
+ }
+
+ targetTable = converter.getTargetTable(select.getFrom());
+ IgniteDataSource dataSource =
targetTable.unwrap(IgniteDataSource.class);
+ if (!(dataSource instanceof IgniteTable)) {
+ return null;
+ }
+ } else {
+ // Try to optimize SELECT count(*) as well
Review Comment:
what is the point of such optimization? What is the difference between new
path and old path in numbers?
##########
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PlannerHelper.java:
##########
@@ -343,4 +360,171 @@ static boolean hasSubQuery(SqlNode node) {
return super.visit(call);
}
}
+
+
+ /**
+ * Tries to optimize a query that looks like {@code SELECT count(*)}.
+ *
+ * @param planner Planner.
+ * @param txContext Transactional context.
+ * @param node Query node.
+ * @return Plan node with list of aliases, if the optimization is
applicable.
+ */
+ public static @Nullable Pair<IgniteRel, List<String>>
tryOptimizeSelectCount(
+ IgnitePlanner planner,
+ @Nullable QueryTransactionContext txContext,
+ SqlNode node
+ ) {
+ if (txContext != null && txContext.explicitTx() != null) {
+ return null;
+ }
+
+ if (!(node instanceof SqlSelect)) {
+ return null;
+ }
+
+ SqlSelect select = (SqlSelect) node;
Review Comment:
```suggestion
SqlSelect select = (SqlSelect) node;
// make sure that the following IF statement does not leave out any
operand of the SELECT node
assert select.getOperandList().size() == 12 : "Expected 12 operands,
but was " + select.getOperandList().size();
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]