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

fix(sql): fix comparing Date columns with date literals #3862

Merged
merged 38 commits into from Oct 31, 2023

Conversation

jerrinot
Copy link
Contributor

@jerrinot jerrinot commented Oct 17, 2023

Consider this query: select * from tab where date = '1970-01-01'

Without these new functions QuestDB resorts to picking the overloaded equal function which accepts 2 longs. Why?

  1. There is no exact match for eq(Date, Date) - such function does not exist at all!
  2. The date column is DateColumn and the Date type can be casted to Long.
    See the overloading priority: /* 7 DATE */, {DATE, TIMESTAMP, LONG}
  3. The second parameter is StrConstant and String can be also casted to Long.
    Again, the overloading priority: /* 11 STRING */, {STRING, CHAR, DOUBLE, LONG, INT, FLOAT, SHORT, BYTE}
  4. Hence, the function parser picks eq(Long, Long).

But then the StrFunction.getLong() fails to parse '1970-01-01' as a long. It expects a simple number. It does not know it's supposed to treat the argument as a Date.

The same query works if you change the column type from Date to Timestamp. Why? Timestamp does not rely on casting - it implements Timestamp-specific functions where necessary. So I copied this for Date.

Consider this query: `select * from tab where date = '1970-01-01'`

Without these new functions QuestDB resorts to picking the overloaded
equal function which accepts 2 longs. Why?
1. There is no exact match for eq(Date, Date) - such function does not exist at all!
2. The date column is `DateColumn` and the Date type can be casted to Long.
   See the overloading priority: /* 7  DATE      */, {DATE, TIMESTAMP, LONG}
3. The second parameter is `StrConstant` and String can be also casted to Long.
   Again, the overloading priority:  /* 11 STRING    */, {STRING, CHAR, DOUBLE, LONG, INT, FLOAT, SHORT, BYTE}
4. Hence, the function parser picks eq(Long, Long).

But then the `StrFunction.getLong()` fails to parse '1970-01-01' as a long.
It expects a simple number. It does not know it's supposed to treat the argument as a Date.

The same query works if you change the column type from Date to Timestamp.
Why? Timestamp does not rely on casting - it implements Timestamp-specific
functions where necessary. So I copied this for Date.
treat such timestamps as UTC
@bluestreak01
Copy link
Member

@jerrinot this is a solid PR! I do think tho that title should be a "fix" or "feat". It is user facing problem. Also description needs a succint TLDR

@jerrinot
Copy link
Contributor Author

@bluestreak01 thanks. I think "fix" is the more appropriate categorization. As most users will perceive the current behaviour as buggy.

@jerrinot jerrinot changed the title chore(sql): implement Date comparisons fix(sql): fix comparing Date columns with date literals Oct 20, 2023
@bluestreak01
Copy link
Member

@jerrinot is this draft draft or ready to go draft?

@jerrinot
Copy link
Contributor Author

@bluestreak01: I would like to add some more tests, I believe @marregui also wanted to add some tests.

  • I want to try the thing we discussed the other day: Do not modify FunctionParser, instead implement comparison functions which accept combinations of String and Date arguments.

@marregui
Copy link
Contributor

hi @bluestreak01 package io.questdb.griffin.engine.functions.cast is a led zepelin. In the long term we would benefit from generating such classes on compile time, the full extent, or found an alternative approach. I will add a few tests, and will double check the approach, but if it works well, would you agree that it should be enough?

@jerrinot
Copy link
Contributor Author

jerrinot commented Oct 27, 2023

@bluestreak01: I tried the experiment we discussed the other day: I introduced EqDateStringFunctionFactory with this signature: =(Ms) and reverted the functional changes inside FunctionParser. However, this had unexpected side effects.

Consider this table: create table tab (ts timestamp); and this SQL: select * from tab where ts = '2022-01-01' This is supposed to match the EqTimestampFunctionFactory. However, it started to match the EqDateStringFunctionFactory instead! Because the 2nd argument matched exactly (string constant) and the Timestamp column can be cast to Date. This is not desirable, we do not want to cast Timestamp to a coarse-grained Date for no good reason. Thus I think the tiny change in FunctionParser is safer.

@jerrinot jerrinot marked this pull request as ready for review October 27, 2023 09:09
@ideoma
Copy link
Collaborator

ideoma commented Oct 30, 2023

[PR Coverage check]

😍 pass : 153 / 155 (98.71%)

file detail

path covered line new line coverage
🔵 io/questdb/griffin/FunctionParser.java 38 40 95.00%
🔵 io/questdb/griffin/engine/functions/cast/CastTimestampToDateFunctionFactory.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/geohash/GeoHashFromCoordinatesFunctionFactory.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/cast/CastFloatToDateFunctionFactory.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/cast/CastStrToDateFunctionFactory.java 2 2 100.00%
🔵 io/questdb/griffin/engine/functions/cast/CastStrToGeoHashFunctionFactory.java 2 2 100.00%
🔵 io/questdb/griffin/engine/functions/eq/EqDateFunctionFactory.java 7 7 100.00%
🔵 io/questdb/griffin/engine/functions/cast/AbstractCastToDateFunction.java 2 2 100.00%
🔵 io/questdb/griffin/engine/groupby/LongNullUtils.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/cast/CastByteToDateFunctionFactory.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/conditional/CaseCommon.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/rnd/RndGeoHashFunctionFactory.java 1 1 100.00%
🔵 io/questdb/cairo/ColumnType.java 31 31 100.00%
🔵 io/questdb/griffin/engine/functions/bool/InTimestampTimestampFunctionFactory.java 1 1 100.00%
🔵 io/questdb/griffin/WhereClauseParser.java 4 4 100.00%
🔵 io/questdb/cairo/GeoHashes.java 4 4 100.00%
🔵 io/questdb/griffin/ExpressionParser.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/constants/GeoHashTypeConstant.java 2 2 100.00%
🔵 io/questdb/griffin/engine/functions/cast/CastBooleanToDateFunctionFactory.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/constants/Constants.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/cast/CastLongToDateFunctionFactory.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/cast/CastIntToDateFunctionFactory.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/catalogue/TypeOfFunctionFactory.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/lt/LtDateFunctionFactory.java 22 22 100.00%
🔵 io/questdb/griffin/engine/functions/cast/CastSymbolToDateFunctionFactory.java 1 1 100.00%
🔵 io/questdb/griffin/GeoHashUtil.java 2 2 100.00%
🔵 io/questdb/griffin/SqlException.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/AbstractUnaryDateFunction.java 4 4 100.00%
🔵 io/questdb/griffin/engine/functions/cast/CastCharToDateFunctionFactory.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/cast/CastDoubleToDateFunctionFactory.java 1 1 100.00%
🔵 io/questdb/cutlass/text/types/GeoHashAdapter.java 1 1 100.00%
🔵 io/questdb/std/datetime/millitime/DateFormatUtils.java 10 10 100.00%
🔵 io/questdb/griffin/engine/functions/cast/CastShortToDateFunctionFactory.java 1 1 100.00%
🔵 io/questdb/griffin/SqlUtil.java 3 3 100.00%

@bluestreak01 bluestreak01 merged commit 9cf62f7 into master Oct 31, 2023
21 checks passed
@bluestreak01 bluestreak01 deleted the jh_date_functions branch October 31, 2023 16:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants