Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IGNITE-19615: Index is not used while performing SELECT over an indexed column. #2196

Merged
merged 15 commits into from
Jul 4, 2023

Conversation

lowka
Copy link
Contributor

@lowka lowka commented Jun 15, 2023

Removed int type / interval type checks from TypeCoercion::needToCast(Type, Type) - it behaves exactly the same way as TypeUtils::needToCast (which was removed in https://issues.apache.org/jira/browse/IGNITE-19128).

  • Partially reverts changes, that were made in https://issues.apache.org/jira/browse/IGNITE-19128 and moved type cast checks to TypeUtils.
  • Adds test cases with indexes to ImplicitCastsTest.
  • Adds saturation casts for numeric types in index bounds to prevent runtime exceptions (see comments).

@lowka lowka force-pushed the IGNITE-19615 branch 2 times, most recently from 408027e to d6e5f4f Compare June 15, 2023 10:11
@lowka lowka marked this pull request as ready for review June 16, 2023 08:15
Copy link
Contributor

@korlov42 korlov42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After this patch an index is used indeed. But resulting plan is rather invalid. For a query like SELECT val FROM my WHERE id = 100500, where id is of type TINYINT, I've got following plan:

IgniteExchange<...>
  IgniteIndexScan(<...>, searchBounds=[[ExactBounds [bound=CAST(100500):TINYINT NOT NULL]]], filters=[=(CAST($t0):INTEGER NOT NULL, 100500)]<...>

this results in RuntimeException, since 100500 is out of range of TINYINT

@lowka
Copy link
Contributor Author

lowka commented Jun 23, 2023

After this patch an index is used indeed. But resulting plan is rather invalid. For a query like SELECT val FROM my WHERE id = 100500, where id is of type TINYINT, I've got following plan:

IgniteExchange<...>
  IgniteIndexScan(<...>, searchBounds=[[ExactBounds [bound=CAST(100500):TINYINT NOT NULL]]], filters=[=(CAST($t0):INTEGER NOT NULL, 100500)]<...>

this results in RuntimeException, since 100500 is out of range of TINYINT

@korlov42
I going to look into it. I looked at the result of IgniteSqlValidator and it add INTEGER casts to both id and literal (this is expected). But somehow the bounds are still use TINYINT.

@lowka
Copy link
Contributor Author

lowka commented Jun 23, 2023

Optimiser starts from:

LogicalProject(VAL=[$1]), id = 116
LogicalFilter(condition=[=(CAST($0):INTEGER NOT NULL, 100500)]), id = 114
IgniteLogicalTableScan(table=[[PUBLIC, MY]]), id = 113

And produces:

IgniteExchange(distribution=[single]), id = 144
IgniteIndexScan(table=[[PUBLIC, MY]], index=[MY_PK], type=[HASH], searchBounds=[[ExactBounds [bound=CAST(100500):TINYINT NOT NULL]]], filters=[=(CAST($t0):INTEGER NOT NULL, 100500)], projects=[[$t1]], requiredColumns=[{0, 1}], collation=[[0 CLU]]), id = 140

@lowka
Copy link
Contributor Author

lowka commented Jun 23, 2023

Yep. Types are correct.

SqlValidator BINARY_COMPARISON: CAST(MY.ID AS INTEGER) = 100500 types: INTEGER INTEGER

So the problem should be in SearchBound assembly code.

@lowka lowka marked this pull request as draft June 26, 2023 15:58
@lowka lowka force-pushed the IGNITE-19615 branch 3 times, most recently from 9ad11fe to baadc49 Compare June 27, 2023 18:02
Copy link
Contributor

@korlov42 korlov42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First problem with this patch is that now IgniteTypeCoercion has two similar method with different behaviour:

boolean needToCast(RelDataType fromType, RelDataType toType)
boolean needToCast(SqlValidatorScope scope, SqlNode node, RelDataType toType)

The former returns false for (TINYINT, INTEGER), while the latter returns true in the very similar case (node::TINYINT, INTEGER).

The second problem is that it deals with literals only, while leaving dynamic params and columns references out of scope for some reason

@lowka lowka force-pushed the IGNITE-19615 branch 2 times, most recently from d5c2cd9 to 20f7db9 Compare June 28, 2023 10:27
@lowka
Copy link
Contributor Author

lowka commented Jun 28, 2023

The second problem is that it deals with literals only, while leaving dynamic params and columns references out of scope for some reason

What makes you think so? Search bounds can be built from arbitrary expressions, if it was not the case then the optimizer wouldn't be able to select an index for SELECT * FROM t WHERE c1=?
But nonetheless I have reverted the changes to the state as were copied from ignite2.

@lowka lowka force-pushed the IGNITE-19615 branch 2 times, most recently from 6524587 to f9b7b91 Compare June 28, 2023 10:37
@lowka lowka requested a review from AMashenkov June 28, 2023 10:41
@lowka lowka force-pushed the IGNITE-19615 branch 2 times, most recently from e3a2944 to 1806534 Compare June 28, 2023 11:33
@lowka
Copy link
Contributor Author

lowka commented Jun 28, 2023

I have reverted the changes to the state as were copied from ignite2.

That would not work. Left all as it is - just renamed a method to needToCastInIndex.

@lowka
Copy link
Contributor Author

lowka commented Jun 29, 2023

@lowka lowka marked this pull request as ready for review June 29, 2023 13:49
Comment on lines 569 to 570
* Checks whether one type can be casted to another if one of type is a custom data type.
* This method expects at least one of its arguments to be a custom data type.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Checks whether one type can be casted to another if one of type is a custom data type.
* This method expects at least one of its arguments to be a custom data type.
* Checks whether one type can be casted to another if one of type is a custom data type.
*
* <p>This method expects at least one of its arguments to be a custom data type.

* This method expects at least one of its arguments to be a custom data type.
*/
public static boolean customDataTypeNeedCast(IgniteTypeFactory factory, RelDataType fromType, RelDataType toType) {
assert fromType.getSqlTypeName() == SqlTypeName.ANY || toType.getSqlTypeName() == SqlTypeName.ANY :
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need both this assertion and one on line 584? Let's leave only one on line 584 with improved message

/** Checks whether cast operation is necessary in {@code SearchBound}. */
public static boolean needCastInSearchBounds(IgniteTypeFactory typeFactory, RelDataType fromType, RelDataType toType) {
// Check custom data types first.
if (toType.getSqlTypeName() == SqlTypeName.ANY || fromType.getSqlTypeName() == SqlTypeName.ANY) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this predicate doesn't seem right... Method customDataTypeNeedCast actually expects that at least one of the argument is instance of IgniteCustomType. If there is an invariant that there should not be any ANY type but IgniteCustomType, it's better to check it explicitly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@korlov42 yeap. I removed the first assertion since the second one is the only one that is needed.

public static boolean needCastInSearchBounds(IgniteTypeFactory typeFactory, RelDataType fromType, RelDataType toType) {
// Check custom data types first.
if (toType.getSqlTypeName() == SqlTypeName.ANY || fromType.getSqlTypeName() == SqlTypeName.ANY) {
return customDataTypeNeedCast(typeFactory, fromType, toType);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do even need call in needCastInSearchBounds? This is the very same rule as in TypeCoercion

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@lowka lowka requested a review from korlov42 June 30, 2023 12:36
@korlov42 korlov42 merged commit 566458f into apache:main Jul 4, 2023
1 check passed
@korlov42 korlov42 deleted the IGNITE-19615 branch July 4, 2023 15:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants