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

Add maketime and makedate #102

Merged
merged 10 commits into from
Aug 12, 2022
Merged

Conversation

Yury-Fridlyand
Copy link

@Yury-Fridlyand Yury-Fridlyand commented Aug 9, 2022

Description

  • maketime link link
    • Supports any values in range 00:00:00.0 - 23:59:59.(9);
    • All arguments are double, but hour and minute are rounded (as MySQL works);
    • Fraction second part is kept;
    • NULL values causes NULL result;
    • Unlike MySQL, negative values, and hour > 23 are not supported. Implementation of that requires big changes.
  • makedate link link
    • All arguments are double, but rounded (as MySQL works);
    • Zero year interpreted as 2000 (as MySQL works);
    • NULL values causes NULL result;
    • day < 1 causes NULL result;
    • day > 365/366 causes switching to the next year.

Functions have 2 implementations:

  • easier
  • faster

I used easier one, but faster one used in unit tests. Could be swapped.

According to MySQL both functions accept double values. Arguments which are not supposed to have fraction parts (e.g. hour, year) are rounded.

TODO

  • Implementation
  • Unit tests
  • Integration tests
  • Doctests

Check List

  • New functionality includes testing.
    • All tests pass, including unit test, integration test and doctest
  • New functionality has been documented.
    • New functionality has javadoc added
    • New functionality has user manual doc added
  • Commits are signed per the DCO using --signoff

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

Signed-off-by: Yury Fridlyand <yuryf@bitquilltech.com>
Signed-off-by: Yury Fridlyand <yuryf@bitquilltech.com>
@codecov
Copy link

codecov bot commented Aug 9, 2022

Codecov Report

Merging #102 (1083a44) into integ-make-time-n-date (d26cde3) will decrease coverage by 2.89%.
The diff coverage is 100.00%.

@@                     Coverage Diff                      @@
##             integ-make-time-n-date     #102      +/-   ##
============================================================
- Coverage                     97.74%   94.84%   -2.90%     
- Complexity                     2858     2908      +50     
============================================================
  Files                           273      287      +14     
  Lines                          7020     7825     +805     
  Branches                        442      571     +129     
============================================================
+ Hits                           6862     7422     +560     
- Misses                          157      349     +192     
- Partials                          1       54      +53     
Flag Coverage Δ
query-workbench 62.76% <ø> (?)
sql-engine 97.79% <100.00%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
...ensearch/sql/planner/physical/PhysicalPlanDSL.java 100.00% <ø> (ø)
.../sql/planner/physical/PhysicalPlanNodeVisitor.java 100.00% <ø> (ø)
...ain/java/org/opensearch/sql/analysis/Analyzer.java 100.00% <100.00%> (ø)
...rg/opensearch/sql/analysis/ExpressionAnalyzer.java 100.00% <100.00%> (ø)
...org/opensearch/sql/analysis/HighlightAnalyzer.java 100.00% <100.00%> (ø)
...ensearch/sql/expression/ExpressionNodeVisitor.java 100.00% <100.00%> (ø)
...opensearch/sql/expression/HighlightExpression.java 100.00% <100.00%> (ø)
...arch/sql/expression/datetime/DateTimeFunction.java 100.00% <100.00%> (ø)
...h/sql/expression/function/BuiltinFunctionName.java 100.00% <100.00%> (ø)
...h/sql/expression/function/OpenSearchFunctions.java 100.00% <100.00%> (ø)
... and 26 more

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

Signed-off-by: Yury Fridlyand <yuryf@bitquilltech.com>
Signed-off-by: Yury Fridlyand <yuryf@bitquilltech.com>
@Yury-Fridlyand Yury-Fridlyand changed the title [WIP] maketime and makedate Add maketime and makedate Aug 9, 2022
@Yury-Fridlyand Yury-Fridlyand marked this pull request as ready for review August 9, 2022 02:17
@Yury-Fridlyand Yury-Fridlyand requested a review from a team August 9, 2022 02:17
}

private ExprValue exprMakeTime(ExprValue hour, ExprValue minute, ExprValue second) {
if (0 > hour.doubleValue() || 0 > minute.doubleValue() || 0 > second.doubleValue()) {

Choose a reason for hiding this comment

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

Can the minutes and seconds not be valid at 0?

Copy link
Author

Choose a reason for hiding this comment

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

I check here for negative values, zero is OK for maketime:

opensearchsql> select maketime(0, 0, 0);
fetched rows / total rows = 1/1
+---------------------+
| maketime(0, 0, 0)   |
|---------------------|
| 00:00:00            |
+---------------------+

reference:

mysql> select maketime(0, 0, 0);
+-------------------+
| maketime(0, 0, 0) |
+-------------------+
| 00:00:00          |
+-------------------+
1 row in set (0.00 sec)

var r = new Random();
assertEquals(maketime(20., 30., 40.), LocalTime.of(20, 30, 40));

for (var ignored : IntStream.range(0, 25).toArray()) {

Choose a reason for hiding this comment

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

Why is this for loop necessary? What does it test that isn't covered by the test on line 1024 above?

As an aside, tests must be reproducible so if a test uses RNG, the user must be able to seed it and any test failures must include the seed used. Otherwise, if it does fail, there is no way to investigate the failure.

Choose a reason for hiding this comment

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

Instead of testing RNG numbers, test boundary cases. For example: 0, and 23:59:59.99 would be decent test cases for time.
Feb 29 on leap years, and older years would be good boundary cases for dates.
Lots of timezones (and over the the dateline) are also excellent test cases when timezones are included.

Copy link
Author

Choose a reason for hiding this comment

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

I dump values used on case of failure, but if it not enough I can hardcode values gotten from RNG.

* @param exprToFill Expression to fill the rest place, usually valid one
* @return A collection with all possible permutations
*/
private Set<Expression[]> permuteArgumentsWith(int size,

Choose a reason for hiding this comment

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

The tests would be easier to understand if the permutations tested were listed.

Since this function is only used with size of 2 and 3, I'd replace it with a function that just uses List.of(...) and lists all the permutations.

"Negative year doesn't produce NULL");
assertEquals(nullValue(), eval(makedate(DSL.literal(42.), DSL.literal(-1.))),
"Negative dayOfYear doesn't produce NULL");
assertEquals(nullValue(), eval(makedate(DSL.literal(42.), DSL.literal(0.))),

Choose a reason for hiding this comment

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

What about day 366 of a non-leap year?

@Test
public void makedate() {
var r = new Random();
for (var ignored : IntStream.range(0, 1025).toArray()) {

Choose a reason for hiding this comment

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

What cases are tested in this for loop? Why is RNG necessary?

Copy link
Author

Choose a reason for hiding this comment

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

RNG helps to find unconsidered cases, it helped me to find few bugs. I dump values which cause a failure, so any other developer would be able to reproduce it.

Choose a reason for hiding this comment

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

>>>>>>>>>>>

Returns a date, given `year` and `day-of-year` values. `dayofyear` must be greater than 0 or the result is `NULL`. The result is also `NULL` if either argument is `NULL`.
Arguments are rounded to an integer.

Choose a reason for hiding this comment

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

Was there a test case for this rounding behaviour in DateTimeFunctionTest?

Choose a reason for hiding this comment

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

What about invalid positive numbers? such as dayofyear 999?

Copy link
Author

Choose a reason for hiding this comment

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

Was there a test case for this rounding behaviour in DateTimeFunctionTest?

Thanks, I will add.

What about invalid positive numbers? such as dayofyear 999?

There is no limit! Freedom!

+------------+------------+
| f1 | f2 |
|------------+------------|
| 1945-01-06 | 1989-06-06 |

Choose a reason for hiding this comment

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

If sample code is going to show that MAKEDATE(1984, 1984) is 1989-... the description should describe this behaviour as well.

This is also a good test case to have in the unit tests -- that MAKEDATE($year, $days_in_year + 1) == MAKEDATE($year + 1, 1).

MAKETIME
--------

Description
>>>>>>>>>>>

Returns a time value calculated from the hour, minute, and second arguments. Returns `NULL` if any of its arguments are `NULL`.
The second argument can have a fractional part, rest arguments are rounded to an integer.

Choose a reason for hiding this comment

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

Is the fractional part rounded to a particular number of decimal places?

Choose a reason for hiding this comment

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

What about hour > 24? What about minute/second > 60?
Make sure to indicate 24 hour time is used, not 12-hour with am/pm.

@@ -388,15 +388,60 @@ Example::
+--------------------------+


MAKEDATE

Choose a reason for hiding this comment

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

Same comments as in SQL apply here.

@@ -994,6 +1006,139 @@ public void date_format() {
assertEquals(missingValue(), eval(dsl.date_format(DSL.literal(""), missingRef)));
}

private FunctionExpression maketime(Expression hour, Expression minute, Expression second) {

Choose a reason for hiding this comment

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

I realize these tests follow the convention in this file, but the tests would be much easier to understand if each function had it's own test suite, and each test in a suite tested a particular scenario -- all valid arguments, null as an argument, negative values, rounding behaviour, etc.

I'll leave it up to you whether you want to break with the convention and introduce a MakeTimeTests.

Choose a reason for hiding this comment

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

This is a good suggestion. I would recommend setting the convention for the test file to include the original name, such as: DateTimeFunctionMakedateTest and DateTimeFunctionMaketimeTest. It would be easier to find for future developers.

@@ -994,6 +1006,139 @@ public void date_format() {
assertEquals(missingValue(), eval(dsl.date_format(DSL.literal(""), missingRef)));
}

private FunctionExpression maketime(Expression hour, Expression minute, Expression second) {

Choose a reason for hiding this comment

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

This is a good suggestion. I would recommend setting the convention for the test file to include the original name, such as: DateTimeFunctionMakedateTest and DateTimeFunctionMaketimeTest. It would be easier to find for future developers.

var r = new Random();
assertEquals(maketime(20., 30., 40.), LocalTime.of(20, 30, 40));

for (var ignored : IntStream.range(0, 25).toArray()) {

Choose a reason for hiding this comment

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

Instead of testing RNG numbers, test boundary cases. For example: 0, and 23:59:59.99 would be decent test cases for time.
Feb 29 on leap years, and older years would be good boundary cases for dates.
Lots of timezones (and over the the dateline) are also excellent test cases when timezones are included.

>>>>>>>>>>>

Returns a date, given `year` and `day-of-year` values. `dayofyear` must be greater than 0 or the result is `NULL`. The result is also `NULL` if either argument is `NULL`.
Arguments are rounded to an integer.

Choose a reason for hiding this comment

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

What about invalid positive numbers? such as dayofyear 999?

assertEquals(nullValue(), eval(makedate(DSL.literal(42.), DSL.literal(-1.))),
"Negative dayOfYear doesn't produce NULL");
assertEquals(nullValue(), eval(makedate(DSL.literal(42.), DSL.literal(0.))),
"Zero dayOfYear doesn't produce NULL");

Choose a reason for hiding this comment

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

what about tests for invalid hour/minute/second or year/month/day?
(is there an invalid year, or is year 0 and 9999 valid?)

Copy link
Author

Choose a reason for hiding this comment

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

MySQL is limited on output value: from 0001-01-01 to 9999-12-31. Java has no limits.

MAKETIME
--------

Description
>>>>>>>>>>>

Returns a time value calculated from the hour, minute, and second arguments. Returns `NULL` if any of its arguments are `NULL`.
The second argument can have a fractional part, rest arguments are rounded to an integer.

Choose a reason for hiding this comment

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

What about hour > 24? What about minute/second > 60?
Make sure to indicate 24 hour time is used, not 12-hour with am/pm.

Signed-off-by: Yury Fridlyand <yuryf@bitquilltech.com>
Signed-off-by: Yury Fridlyand <yuryf@bitquilltech.com>
@@ -512,6 +528,28 @@ private ExprValue exprHour(ExprValue time) {
return new ExprIntegerValue(time.timeValue().getHour());
}

private ExprValue exprMakeDate(ExprValue yearExpr, ExprValue dayOfYearExp) {

Choose a reason for hiding this comment

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

A javadoc with some explanation of the mysql compliance would be nice.

Signed-off-by: Yury Fridlyand <yuryf@bitquilltech.com>
Signed-off-by: Yury Fridlyand <yuryf@bitquilltech.com>
Signed-off-by: Yury Fridlyand <yuryf@bitquilltech.com>
var hour = Math.round(hourExpr.doubleValue());
var minute = Math.round(minuteExpr.doubleValue());
var second = secondExpr.doubleValue();
if (0 > hour || 0 > minute || 0 > second) {

Choose a reason for hiding this comment

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

nit: Can we make this check prior to rounding? Then if the value is negative, we save those operations.

var year = Math.round(yearExpr.doubleValue());
var dayOfYear = Math.round(dayOfYearExp.doubleValue());
// We need to do this to comply with MySQL
if (0 >= dayOfYear || 0 > year) {

Choose a reason for hiding this comment

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

nit: Can we make this check prior to rounding? Then if the value is negative, we save those operations.

Copy link
Author

Choose a reason for hiding this comment

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

To optimize performance we shouldn't use LocalDate::parse nor LocalDate::plusDays. See my note about faster and easier implementation.

Signed-off-by: Yury Fridlyand <yuryf@bitquilltech.com>
@Yury-Fridlyand Yury-Fridlyand merged commit 1b6877d into integ-make-time-n-date Aug 12, 2022
@Yury-Fridlyand Yury-Fridlyand deleted the dev-make-time-n-date branch August 12, 2022 18:51
Yury-Fridlyand added a commit that referenced this pull request Aug 12, 2022
Signed-off-by: Yury Fridlyand <yuryf@bitquilltech.com>
Yury-Fridlyand added a commit that referenced this pull request Aug 17, 2022
Signed-off-by: Yury Fridlyand <yuryf@bitquilltech.com>

Signed-off-by: Yury Fridlyand <yuryf@bitquilltech.com>
andy-k-improving pushed a commit that referenced this pull request Nov 16, 2024
…attern_CI

Include feature branch in workflow to trigger CI
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.

5 participants