Skip to content

Conversation

@martin-georgiev
Copy link
Owner

@martin-georgiev martin-georgiev commented Apr 12, 2025

Summary by CodeRabbit

  • New Features

    • Introduced robust date manipulation capabilities, allowing users to add, subtract, and bin dates.
    • Enhanced date handling with improved timezone validation for more reliable query operations.
  • Tests

    • Expanded automated tests to ensure consistent behavior and proper error handling for the new date functions, including DATE_ADD, DATE_SUBTRACT, and DATE_BIN.

@martin-georgiev martin-georgiev enabled auto-merge (squash) April 12, 2025 03:31
@coderabbitai
Copy link

coderabbitai bot commented Apr 12, 2025

Walkthrough

This pull request introduces three new DQL functions—DATE_ADD, DATE_SUBTRACT, and DATE_BIN—for PostgreSQL operations within Doctrine ORM. It adds corresponding classes that extend base function classes and utilize custom timezone validation through a new trait. Additionally, a new exception class for timezone errors is implemented. Comprehensive test classes for each function ensure proper functionality and argument validation. A minor accessibility change in the test helper class allows extended test cases to build the entity manager.

Changes

File Path(s) Summary
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAdd.php
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateSubtract.php
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateBin.php
Added new classes implementing PostgreSQL functions DATE_ADD, DATE_SUBTRACT, and DATE_BIN with argument validation and function prototype customization.
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidTimezoneException.php
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Traits/TimezoneValidationTrait.php
Introduced a new exception for invalid timezones and a trait providing timezone validation logic used by the date functions.
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAddTest.php
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateSubtractTest.php
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateBinTest.php
Added new test classes covering the functionality and error handling of the DATE_ADD, DATE_SUBTRACT, and DATE_BIN functions.
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TestCase.php Changed the visibility of the buildEntityManager method from private to protected to enable subclass access in test cases.

Sequence Diagram(s)

sequenceDiagram
    participant C as Client
    participant P as DQL Parser
    participant F as Date Function (Add/Subtract)
    participant T as Timezone Validator
    participant DB as Database

    C->>P: Submit DQL query with date function
    P->>F: Parse and invoke custom date function
    F->>F: Validate arguments (ensure count between 2 and 3)
    alt Three arguments provided
        F->>T: Validate timezone argument
        T-->>F: Return valid result or throw InvalidTimezoneException
    end
    F->>DB: Build and execute SQL query
    DB-->>F: Return SQL result
    F-->>C: Return query result
Loading

Poem

I’m a little rabbit in the code-filled glen,
Hopping through functions again and again.
With DATE_ADD and DATE_SUBTRACT in sight,
And DATE_BIN to keep dates aligned just right.
Timezones and tests, all in a cheer—
Coding just got a whole lot more dear! 🐰💻

Tip

⚡💬 Agentic Chat (Pro Plan, General Availability)
  • We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

coderabbitai[bot]
coderabbitai bot previously requested changes Apr 12, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateBin.php (1)

1-28: Implementation matches PostgreSQL DATE_BIN() function

The implementation correctly defines the DATE_BIN() function with the proper arguments and structure. The syntax matches PostgreSQL's date_bin(interval, timestamp, origin) function and the documentation is thorough, providing good context and example usage.

One minor suggestion: Consider updating the PostgreSQL documentation link to version 16 for consistency with the other date function implementations.

-@see https://www.postgresql.org/docs/14/functions-datetime.html
+@see https://www.postgresql.org/docs/16/functions-datetime.html
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAdd.php (1)

33-44: Inconsistent function name casing in error messages

There's an inconsistency in the function name casing used for error messages. For argument validation, lowercase date_add is used, while for timezone validation, uppercase DATE_ADD is used.

-            throw InvalidArgumentForVariadicFunctionException::between('date_add', 2, 3);
+            throw InvalidArgumentForVariadicFunctionException::between('DATE_ADD', 2, 3);

Or alternatively:

-            $this->validateTimezone($arguments[2], 'DATE_ADD');
+            $this->validateTimezone($arguments[2], 'date_add');

Choose one casing style and use it consistently.

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateSubtract.php (1)

33-44: Inconsistent function name casing in error messages

Similar to the DateAdd class, there's an inconsistency in the function name casing used for error messages. For argument validation, lowercase date_subtract is used, while for timezone validation, uppercase DATE_SUBTRACT is used.

-            throw InvalidArgumentForVariadicFunctionException::between('date_subtract', 2, 3);
+            throw InvalidArgumentForVariadicFunctionException::between('DATE_SUBTRACT', 2, 3);

Or alternatively:

-            $this->validateTimezone($arguments[2], 'DATE_SUBTRACT');
+            $this->validateTimezone($arguments[2], 'date_subtract');

Choose one casing style and use it consistently.

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Traits/TimezoneValidationTrait.php (1)

38-47: Consider improving error handling in isValidTimezone method.

The method catches all exceptions generically. While this works, it could be more specific.

Consider catching the specific \Exception type that DateTimeZone constructor throws for invalid timezones (\Exception is currently used which is very broad):

 private function isValidTimezone(string $timezone): bool
 {
     try {
         new \DateTimeZone($timezone);

         return true;
-    } catch (\Exception) {
+    } catch (\Exception $e) {
+        // DateTimeZone constructor throws exception for invalid timezone identifiers
         return false;
     }
 }
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAddTest.php (1)

20-36: Test cases provide good coverage for DATE_ADD function.

The test covers multiple variations of the DATE_ADD() function including different time intervals and with/without timezone specifications.

Consider adding edge case tests to ensure robustness:

 protected function getExpectedSqlStatements(): array
 {
     return [
         'adds 1 day with timezone' => "SELECT date_add(c0_.datetimetz1, '1 day', 'Europe/Sofia') AS sclr_0 FROM ContainsDates c0_",
         'adds 2 hours with timezone' => "SELECT date_add(c0_.datetimetz1, '2 hours', 'UTC') AS sclr_0 FROM ContainsDates c0_",
         'adds 3 days without timezone' => "SELECT date_add(c0_.datetimetz1, '3 days') AS sclr_0 FROM ContainsDates c0_",
+        'adds complex interval' => "SELECT date_add(c0_.datetimetz1, '1 year 2 months 3 days 4 hours 5 minutes 6 seconds') AS sclr_0 FROM ContainsDates c0_",
     ];
 }

 protected function getDqlStatements(): array
 {
     return [
         'adds 1 day with timezone' => \sprintf("SELECT DATE_ADD(e.datetimetz1, '1 day', 'Europe/Sofia') FROM %s e", ContainsDates::class),
         'adds 2 hours with timezone' => \sprintf("SELECT DATE_ADD(e.datetimetz1, '2 hours', 'UTC') FROM %s e", ContainsDates::class),
         'adds 3 days without timezone' => \sprintf("SELECT DATE_ADD(e.datetimetz1, '3 days') FROM %s e", ContainsDates::class),
+        'adds complex interval' => \sprintf("SELECT DATE_ADD(e.datetimetz1, '1 year 2 months 3 days 4 hours 5 minutes 6 seconds') FROM %s e", ContainsDates::class),
     ];
 }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 553a30c and f9ebc62.

📒 Files selected for processing (9)
  • src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAdd.php (1 hunks)
  • src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateBin.php (1 hunks)
  • src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateSubtract.php (1 hunks)
  • src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidTimezoneException.php (1 hunks)
  • src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Traits/TimezoneValidationTrait.php (1 hunks)
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAddTest.php (1 hunks)
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateBinTest.php (1 hunks)
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateSubtractTest.php (1 hunks)
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TestCase.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/**/*.php`: Use the PostgreSQL official documentation to verify that tests include comprehensive use cases and example SQL que...

tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/**/*.php: Use the PostgreSQL official documentation to verify that tests include comprehensive use cases and example SQL queries for the tested SQL functions and operators.

  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TestCase.php
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateBinTest.php
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateSubtractTest.php
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAddTest.php
🧬 Code Graph Analysis (7)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateBin.php (3)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseFunction.php (3)
  • BaseFunction (20-105)
  • setFunctionPrototype (57-60)
  • addNodeMapping (62-65)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAdd.php (1)
  • customizeFunction (28-31)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateSubtract.php (1)
  • customizeFunction (28-31)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateBinTest.php (3)
fixtures/MartinGeorgiev/Doctrine/Entity/Entity.php (1)
  • Entity (10-16)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateBin.php (1)
  • DateBin (19-28)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TestCase.php (4)
  • TestCase (15-120)
  • getStringFunctions (79-82)
  • getExpectedSqlStatements (87-87)
  • getDqlStatements (92-92)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateSubtract.php (4)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidArgumentForVariadicFunctionException.php (2)
  • InvalidArgumentForVariadicFunctionException (7-46)
  • between (29-37)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseVariadicFunction.php (1)
  • BaseVariadicFunction (20-73)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseFunction.php (1)
  • setFunctionPrototype (57-60)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Traits/TimezoneValidationTrait.php (1)
  • validateTimezone (25-36)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAdd.php (4)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidArgumentForVariadicFunctionException.php (2)
  • InvalidArgumentForVariadicFunctionException (7-46)
  • between (29-37)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseVariadicFunction.php (1)
  • BaseVariadicFunction (20-73)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseFunction.php (1)
  • setFunctionPrototype (57-60)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Traits/TimezoneValidationTrait.php (1)
  • validateTimezone (25-36)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Traits/TimezoneValidationTrait.php (1)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidTimezoneException.php (3)
  • InvalidTimezoneException (14-33)
  • forNonLiteralNode (16-23)
  • forInvalidTimezone (25-32)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateSubtractTest.php (4)
fixtures/MartinGeorgiev/Doctrine/Entity/Entity.php (1)
  • Entity (10-16)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateSubtract.php (1)
  • DateSubtract (24-45)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidTimezoneException.php (1)
  • InvalidTimezoneException (14-33)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TestCase.php (4)
  • TestCase (15-120)
  • getStringFunctions (79-82)
  • getExpectedSqlStatements (87-87)
  • getDqlStatements (92-92)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAddTest.php (4)
fixtures/MartinGeorgiev/Doctrine/Entity/Entity.php (1)
  • Entity (10-16)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAdd.php (1)
  • DateAdd (24-45)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidTimezoneException.php (1)
  • InvalidTimezoneException (14-33)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TestCase.php (5)
  • TestCase (15-120)
  • getStringFunctions (79-82)
  • getExpectedSqlStatements (87-87)
  • getDqlStatements (92-92)
  • buildEntityManager (116-119)
⏰ Context from checks skipped due to timeout of 90000ms (14)
  • GitHub Check: PHP 8.4 + Doctrine ORM 2.14 + Doctrine Lexer latest
  • GitHub Check: PHP 8.4 + Doctrine ORM latest + Doctrine Lexer latest
  • GitHub Check: PHP 8.4 + Doctrine ORM latest + Doctrine Lexer 3.0
  • GitHub Check: PHP 8.1 + Doctrine ORM 2.14 + Doctrine Lexer 1.2
  • GitHub Check: PHP 8.4 + Doctrine ORM 3.0 + Doctrine Lexer 3.0
  • GitHub Check: PHP 8.3 + Doctrine ORM 2.18 + Doctrine Lexer 3.0
  • GitHub Check: PHP 8.3 + Doctrine ORM 2.14 + Doctrine Lexer 2.1
  • GitHub Check: PHP 8.2 + Doctrine ORM 2.18 + Doctrine Lexer latest
  • GitHub Check: PHP 8.3 + Doctrine ORM 3.0 + Doctrine Lexer latest
  • GitHub Check: PHP 8.2 + Doctrine ORM latest + Doctrine Lexer 3.0
  • GitHub Check: PHP 8.2 + Doctrine ORM 3.0 + Doctrine Lexer 3.0
  • GitHub Check: PHP 8.1 + Doctrine ORM 3.0 + Doctrine Lexer latest
  • GitHub Check: PHP 8.1 + Doctrine ORM 2.18 + Doctrine Lexer 2.1
  • GitHub Check: sloth
🔇 Additional comments (12)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TestCase.php (1)

116-116: LGTM: Appropriate visibility change

Changing the visibility from private to protected allows test subclasses to access the buildEntityManager method, which is necessary for the new date function test classes.

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAdd.php (2)

1-27: Implementation matches PostgreSQL DATE_ADD() function

The implementation correctly defines the DATE_ADD() function and includes the TimezoneValidationTrait to validate the optional timezone parameter. The documentation is thorough with appropriate reference to PostgreSQL docs and a clear usage example.


28-31: Function prototype is correct

The function prototype is properly set to correctly format the SQL output.

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateSubtract.php (3)

1-27: Implementation matches PostgreSQL DATE_SUBTRACT() function

The implementation correctly defines the DATE_SUBTRACT() function and includes the TimezoneValidationTrait to validate the optional timezone parameter. The documentation is thorough with appropriate reference to PostgreSQL docs and a clear usage example.


28-31: Function prototype is correct

The function prototype is properly set to correctly format the SQL output.


1-44:

✅ Verification successful

Missing test cases verification

According to the coding guidelines, test files for SQL functions should include comprehensive use cases and example SQL queries matching PostgreSQL documentation. As the test files aren't included in this review, I can't verify this requirement.


🏁 Script executed:

#!/bin/bash
# Check test files for these functions to ensure they include comprehensive test cases
echo "Checking test files for date functions..."

echo "1. DateBinTest.php:"
fd "DateBinTest.php" tests/ --exec cat

echo "2. DateAddTest.php:"
fd "DateAddTest.php" tests/ --exec cat

echo "3. DateSubtractTest.php:"
fd "DateSubtractTest.php" tests/ --exec cat

Length of output: 5409


Test Cases Verified for DateSubtract Function

I confirmed that the test cases for SQL functions are comprehensive. The tests/DateSubtractTest.php file includes valid examples for subtracting intervals both with and without a timezone, as well as negative tests for invalid timezones. This aligns with the PostgreSQL documentation requirements and our coding guidelines.

No further changes are required.

tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateSubtractTest.php (3)

38-45: Test case for invalid timezone properly validates error handling.

The test correctly validates that an invalid timezone throws the expected exception with the appropriate error message.


20-36: Test coverage includes good examples aligned with PostgreSQL documentation.

The test cases for DATE_SUBTRACT() cover important scenarios with varying time intervals (days, hours) and both with and without timezone specifications.


11-18: Function registration looks good.

The function is correctly registered with the appropriate class mapping.

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Traits/TimezoneValidationTrait.php (1)

25-36: Timezone validation implementation properly handles edge cases.

The validation logic correctly checks for literal nodes and validates timezone strings by trimming quotes and using PHP's native timezone validation.

tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAddTest.php (1)

38-45: Test for invalid timezone correctly validates exception behavior.

The test properly checks that an invalid timezone throws the expected exception with the right error message.

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidTimezoneException.php (1)

1-33: Well-structured exception class with clear factory methods.

This is a well-organized exception class that follows good PHP practices. The static factory methods provide contextual error messages that will help developers understand what went wrong with timezone validation.

A few small observations:

  • Good use of extending the appropriate Doctrine ConversionException base class
  • Factory methods follow a clean pattern with descriptive names
  • Error messages are clear and include the relevant context

Comment on lines 1 to 34
<?php

declare(strict_types=1);

namespace Tests\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;

use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsDates;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateBin;

class DateBinTest extends TestCase
{
protected function getStringFunctions(): array
{
return [
'DATE_BIN' => DateBin::class,
];
}

protected function getExpectedSqlStatements(): array
{
return [
'bins by 15 minutes' => "SELECT date_bin('15 minutes', c0_.datetime1, '2001-02-16 20:05:00') AS sclr_0 FROM ContainsDates c0_",
'bins by 1 hour' => "SELECT date_bin('1 hour', c0_.datetime1, '2001-02-16 00:00:00') AS sclr_0 FROM ContainsDates c0_",
];
}

protected function getDqlStatements(): array
{
return [
'bins by 15 minutes' => \sprintf("SELECT DATE_BIN('15 minutes', e.datetime1, '2001-02-16 20:05:00') FROM %s e", ContainsDates::class),
'bins by 1 hour' => \sprintf("SELECT DATE_BIN('1 hour', e.datetime1, '2001-02-16 00:00:00') FROM %s e", ContainsDates::class),
];
}
}
Copy link

@coderabbitai coderabbitai bot Apr 12, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Test coverage should include additional use cases based on PostgreSQL documentation.

The test covers basic functionality for the DATE_BIN() function with two test cases (15 minutes and 1 hour binning), but would benefit from more comprehensive coverage based on PostgreSQL documentation.

Consider adding the following test cases to ensure comprehensive coverage:

  1. Binning with a timestamp literal instead of a field
  2. Edge cases like binning with small intervals (e.g., seconds)
  3. Test with different origin values and their effects on binning
  4. Different interval formats accepted by PostgreSQL

According to the PostgreSQL documentation, date_bin() has specific behavior when changing the origin parameter, which affects where the bins are anchored. Adding these test cases would ensure the function behaves as expected across various scenarios.

 protected function getExpectedSqlStatements(): array
 {
     return [
         'bins by 15 minutes' => "SELECT date_bin('15 minutes', c0_.datetime1, '2001-02-16 20:05:00') AS sclr_0 FROM ContainsDates c0_",
         'bins by 1 hour' => "SELECT date_bin('1 hour', c0_.datetime1, '2001-02-16 00:00:00') AS sclr_0 FROM ContainsDates c0_",
+        'bins by 30 seconds with timestamp literal' => "SELECT date_bin('30 seconds', '2023-03-10 12:34:56', '2000-01-01 00:00:00') AS sclr_0 FROM ContainsDates c0_",
+        'bins with different origin' => "SELECT date_bin('1 day', c0_.datetime1, '2023-01-01 12:00:00') AS sclr_0 FROM ContainsDates c0_",
     ];
 }

 protected function getDqlStatements(): array
 {
     return [
         'bins by 15 minutes' => \sprintf("SELECT DATE_BIN('15 minutes', e.datetime1, '2001-02-16 20:05:00') FROM %s e", ContainsDates::class),
         'bins by 1 hour' => \sprintf("SELECT DATE_BIN('1 hour', e.datetime1, '2001-02-16 00:00:00') FROM %s e", ContainsDates::class),
+        'bins by 30 seconds with timestamp literal' => \sprintf("SELECT DATE_BIN('30 seconds', '2023-03-10 12:34:56', '2000-01-01 00:00:00') FROM %s e", ContainsDates::class),
+        'bins with different origin' => \sprintf("SELECT DATE_BIN('1 day', e.datetime1, '2023-01-01 12:00:00') FROM %s e", ContainsDates::class),
     ];
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<?php
declare(strict_types=1);
namespace Tests\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsDates;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateBin;
class DateBinTest extends TestCase
{
protected function getStringFunctions(): array
{
return [
'DATE_BIN' => DateBin::class,
];
}
protected function getExpectedSqlStatements(): array
{
return [
'bins by 15 minutes' => "SELECT date_bin('15 minutes', c0_.datetime1, '2001-02-16 20:05:00') AS sclr_0 FROM ContainsDates c0_",
'bins by 1 hour' => "SELECT date_bin('1 hour', c0_.datetime1, '2001-02-16 00:00:00') AS sclr_0 FROM ContainsDates c0_",
];
}
protected function getDqlStatements(): array
{
return [
'bins by 15 minutes' => \sprintf("SELECT DATE_BIN('15 minutes', e.datetime1, '2001-02-16 20:05:00') FROM %s e", ContainsDates::class),
'bins by 1 hour' => \sprintf("SELECT DATE_BIN('1 hour', e.datetime1, '2001-02-16 00:00:00') FROM %s e", ContainsDates::class),
];
}
}
<?php
declare(strict_types=1);
namespace Tests\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsDates;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateBin;
class DateBinTest extends TestCase
{
protected function getStringFunctions(): array
{
return [
'DATE_BIN' => DateBin::class,
];
}
protected function getExpectedSqlStatements(): array
{
return [
'bins by 15 minutes' => "SELECT date_bin('15 minutes', c0_.datetime1, '2001-02-16 20:05:00') AS sclr_0 FROM ContainsDates c0_",
'bins by 1 hour' => "SELECT date_bin('1 hour', c0_.datetime1, '2001-02-16 00:00:00') AS sclr_0 FROM ContainsDates c0_",
'bins by 30 seconds with timestamp literal' => "SELECT date_bin('30 seconds', '2023-03-10 12:34:56', '2000-01-01 00:00:00') AS sclr_0 FROM ContainsDates c0_",
'bins with different origin' => "SELECT date_bin('1 day', c0_.datetime1, '2023-01-01 12:00:00') AS sclr_0 FROM ContainsDates c0_",
];
}
protected function getDqlStatements(): array
{
return [
'bins by 15 minutes' => \sprintf("SELECT DATE_BIN('15 minutes', e.datetime1, '2001-02-16 20:05:00') FROM %s e", ContainsDates::class),
'bins by 1 hour' => \sprintf("SELECT DATE_BIN('1 hour', e.datetime1, '2001-02-16 00:00:00') FROM %s e", ContainsDates::class),
'bins by 30 seconds with timestamp literal' => \sprintf("SELECT DATE_BIN('30 seconds', '2023-03-10 12:34:56', '2000-01-01 00:00:00') FROM %s e", ContainsDates::class),
'bins with different origin' => \sprintf("SELECT DATE_BIN('1 day', e.datetime1, '2023-01-01 12:00:00') FROM %s e", ContainsDates::class),
];
}
}

Copy link
Owner Author

Choose a reason for hiding this comment

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

Are these really relevant test scenarios?

Copy link

Choose a reason for hiding this comment

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

@martin-georgiev Looking at the PostgreSQL documentation more carefully, I think the most relevant additional test case would be the one with a different origin value, as the origin parameter significantly affects where bin boundaries are created.

The timestamp literal test is less critical since the function is primarily translating DQL to SQL correctly, which is already demonstrated with field references. The smaller interval test (30 seconds) doesn't really test anything fundamentally different from the existing 15-minute test.

The existing tests already cover the basic functionality of the DATE_BIN() implementation. If you want to keep the test focused on the most essential scenarios, just adding a test with a different origin value would be the most valuable addition.

Copy link
Owner Author

Choose a reason for hiding this comment

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

How is "bins by 1 hour" any different from "bins with different origin"?

Copy link

Choose a reason for hiding this comment

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

@martin-georgiev You're absolutely right. Looking more closely at the existing test cases, there's no fundamental difference in what's being tested. Both cases already test the function's ability to handle different interval values and different origin timestamps:

  • "bins by 1 hour": Uses '1 hour' interval with origin '2001-02-16 00:00:00'
  • "bins with different origin" (suggested): Uses '1 day' interval with origin '2023-01-01 12:00:00'

The existing test cases already adequately cover the functionality by testing different intervals ('15 minutes' and '1 hour') and different origins. My suggested addition doesn't add any meaningful test coverage that isn't already present in your implementation.

@coveralls
Copy link

coveralls commented Apr 12, 2025

Coverage Status

coverage: 95.11% (-0.4%) from 95.507%
when pulling 1940bd2 on new-funcs
into 553a30c on main.

coderabbitai[bot]
coderabbitai bot previously requested changes Apr 12, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAddTest.php (1)

21-29: Consider adding more test cases for different interval formats.

While the current test cases cover the basic functionality, PostgreSQL supports various interval formats that could be tested:

  • Fractional intervals (e.g., '1.5 days')
  • Compound intervals (e.g., '1 day 2 hours')
  • Negative intervals (e.g., '-1 day')
 protected function getExpectedSqlStatements(): array
 {
     return [
         'adds 1 day with timezone' => "SELECT date_add(c0_.datetimetz1, '1 day', 'Europe/Sofia') AS sclr_0 FROM ContainsDates c0_",
         'adds 2 hours with timezone' => "SELECT date_add(c0_.datetimetz1, '2 hours', 'UTC') AS sclr_0 FROM ContainsDates c0_",
         'adds 3 days without timezone' => "SELECT date_add(c0_.datetimetz1, '3 days') AS sclr_0 FROM ContainsDates c0_",
         'adds with WHERE clause' => "SELECT c0_.datetimetz1 AS datetimetz1_0 FROM ContainsDates c0_ WHERE date_add(c0_.datetimetz1, '1 day') = '2023-01-02 00:00:00'",
+        'adds fractional interval' => "SELECT date_add(c0_.datetimetz1, '1.5 days') AS sclr_0 FROM ContainsDates c0_",
+        'adds compound interval' => "SELECT date_add(c0_.datetimetz1, '1 day 2 hours') AS sclr_0 FROM ContainsDates c0_",
+        'adds negative interval' => "SELECT date_add(c0_.datetimetz1, '-1 day') AS sclr_0 FROM ContainsDates c0_",
     ];
 }

Corresponding DQL changes would also be needed in the getDqlStatements() method.

tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateSubtractTest.php (1)

21-28: Consider adding more test cases for different interval formats.

Similar to the suggestion for DateAddTest, consider adding test cases for:

  • Fractional intervals (e.g., '1.5 days')
  • Compound intervals (e.g., '1 day 2 hours')
  • Negative intervals (e.g., '-1 day')
 protected function getExpectedSqlStatements(): array
 {
     return [
         'subtracts 1 day with timezone' => "SELECT date_subtract(c0_.datetimetz1, '1 day', 'Europe/Sofia') AS sclr_0 FROM ContainsDates c0_",
         'subtracts 2 hours with timezone' => "SELECT date_subtract(c0_.datetimetz1, '2 hours', 'UTC') AS sclr_0 FROM ContainsDates c0_",
         'subtracts 3 days without timezone' => "SELECT date_subtract(c0_.datetimetz1, '3 days') AS sclr_0 FROM ContainsDates c0_",
+        'subtracts fractional interval' => "SELECT date_subtract(c0_.datetimetz1, '1.5 days') AS sclr_0 FROM ContainsDates c0_",
+        'subtracts compound interval' => "SELECT date_subtract(c0_.datetimetz1, '1 day 2 hours') AS sclr_0 FROM ContainsDates c0_",
+        'subtracts negative interval' => "SELECT date_subtract(c0_.datetimetz1, '-1 day') AS sclr_0 FROM ContainsDates c0_",
     ];
 }

Corresponding DQL changes would also be needed in the getDqlStatements() method.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f9ebc62 and 1940bd2.

📒 Files selected for processing (3)
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAddTest.php (1 hunks)
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateBinTest.php (1 hunks)
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateSubtractTest.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateBinTest.php
🧰 Additional context used
📓 Path-based instructions (1)
`tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/**/*.php`: Use the PostgreSQL official documentation to verify that tests include comprehensive use cases and example SQL que...

tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/**/*.php: Use the PostgreSQL official documentation to verify that tests include comprehensive use cases and example SQL queries for the tested SQL functions and operators.

  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAddTest.php
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateSubtractTest.php
🧬 Code Graph Analysis (2)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAddTest.php (4)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAdd.php (1)
  • DateAdd (24-45)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidArgumentForVariadicFunctionException.php (1)
  • InvalidArgumentForVariadicFunctionException (7-46)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidTimezoneException.php (1)
  • InvalidTimezoneException (14-33)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TestCase.php (5)
  • TestCase (15-120)
  • getStringFunctions (79-82)
  • getExpectedSqlStatements (87-87)
  • getDqlStatements (92-92)
  • buildEntityManager (116-119)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateSubtractTest.php (4)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateSubtract.php (1)
  • DateSubtract (24-45)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidArgumentForVariadicFunctionException.php (1)
  • InvalidArgumentForVariadicFunctionException (7-46)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidTimezoneException.php (1)
  • InvalidTimezoneException (14-33)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TestCase.php (5)
  • TestCase (15-120)
  • getStringFunctions (79-82)
  • getExpectedSqlStatements (87-87)
  • getDqlStatements (92-92)
  • buildEntityManager (116-119)
⏰ Context from checks skipped due to timeout of 90000ms (20)
  • GitHub Check: sloth
  • GitHub Check: PHP 8.4 + Doctrine ORM 3.0 + Doctrine Lexer latest
  • GitHub Check: PHP 8.1 + Doctrine ORM 2.14 + Doctrine Lexer 1.2
  • GitHub Check: PHP 8.4 + Doctrine ORM latest + Doctrine Lexer 3.0
  • GitHub Check: PHP 8.4 + Doctrine ORM 2.18 + Doctrine Lexer latest
  • GitHub Check: PHP 8.4 + Doctrine ORM 2.14 + Doctrine Lexer latest
  • GitHub Check: PHP 8.3 + Doctrine ORM latest + Doctrine Lexer latest
  • GitHub Check: PHP 8.4 + Doctrine ORM latest + Doctrine Lexer 2.1
  • GitHub Check: PHP 8.4 + Doctrine ORM 3.0 + Doctrine Lexer 3.0
  • GitHub Check: PHP 8.3 + Doctrine ORM 3.0 + Doctrine Lexer 3.0
  • GitHub Check: PHP 8.3 + Doctrine ORM 2.14 + Doctrine Lexer latest
  • GitHub Check: PHP 8.3 + Doctrine ORM 2.14 + Doctrine Lexer 2.1
  • GitHub Check: PHP 8.2 + Doctrine ORM 3.0 + Doctrine Lexer latest
  • GitHub Check: PHP 8.2 + Doctrine ORM 2.14 + Doctrine Lexer latest
  • GitHub Check: PHP 8.2 + Doctrine ORM latest + Doctrine Lexer 3.0
  • GitHub Check: PHP 8.1 + Doctrine ORM 2.18 + Doctrine Lexer latest
  • GitHub Check: PHP 8.2 + Doctrine ORM 2.18 + Doctrine Lexer 2.1
  • GitHub Check: PHP 8.1 + Doctrine ORM 2.14 + Doctrine Lexer latest
  • GitHub Check: PHP 8.1 + Doctrine ORM 3.0 + Doctrine Lexer 3.0
  • GitHub Check: PHP 8.1 + Doctrine ORM 2.18 + Doctrine Lexer 3.0
🔇 Additional comments (4)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAddTest.php (2)

12-67: Tests look comprehensive with good coverage of the DATE_ADD function.

The test class provides thorough coverage for the DATE_ADD function implementation including:

  • Basic functionality with various time intervals (days, hours)
  • Support for timezone handling
  • Usage in WHERE clauses
  • Error handling for invalid arguments

The test cases align well with how PostgreSQL handles date/time operations with intervals, though implemented as a custom function.


41-48:

✅ Verification successful

Verify test against PostgreSQL documentation for timezone validation.

This test correctly verifies the function throws an exception for invalid timezones. According to PostgreSQL documentation, timezone identifiers should be valid IANA timezone names, which aligns with the implementation here.


🌐 Web query:

What are the valid timezone formats in PostgreSQL?

💡 Result:

PostgreSQL supports three primary formats for specifying time zones, each with distinct characteristics and use cases:

1. IANA Time Zone Names

These use the format Area/Location (e.g., America/New_York, Europe/Paris) and provide comprehensive historical and daylight savings time (DST) rules. They are stored in the pg_timezone_names system catalog and are the most reliable for accurate time zone conversions[1][2].
Example:

SET TimeZone = 'Europe/Vienna';
SELECT TIMESTAMPTZ '2022-04-01 12:00:00 Europe/Vienna';  -- Accounts for DST shifts[1]

2. Time Zone Abbreviations

Abbreviations like CET (Central European Time) or PST (Pacific Standard Time) represent fixed offsets from UTC. However, they lack DST awareness and can be ambiguous (e.g., IST could mean Irish, Israeli, or Indian time). Valid abbreviations are listed in pg_timezone_abbrevs[1][2][6].
Example:

SELECT TIMESTAMP '2023-03-15 18:00:00-11' AT TIME ZONE 'CET';  -- Converts using abbreviation[2]

3. POSIX-Style Specifications

These use formats like UTC±H or ±HH:MM (e.g., UTC+3, -04:30). Notably, POSIX defines offsets as hours west of GMT, so UTC+3 becomes -03 in PostgreSQL[4][6].
Example:

SET TIME ZONE 'UTC+3';  -- Stored as UTC-03:00[4]

Key Considerations:

  • Ambiguity: Abbreviations like CST (Central Standard Time) can conflict between regions (e.g., Australia vs. North America)[5][6].
  • Session Settings: Use SET TimeZone to define the session’s time zone, or override it with AT TIME ZONE in queries[1][4][7].
  • System Catalogs: Query pg_timezone_names for IANA zones and pg_timezone_abbrevs for abbreviations[1][3].

For most applications, IANA time zone names are recommended due to their DST awareness and global consistency[1][2][7].

Citations:


Verified: Test for Invalid Timezone Exception is Correct

The test_invalid_timezone_throws_exception() test in tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAddTest.php correctly expects an InvalidTimezoneException with the precise message when an invalid timezone string is provided. Our verification against PostgreSQL’s documentation confirms that valid time zone identifiers must follow the IANA naming convention (e.g., Europe/Vienna), which means that "Invalid/Timezone" is indeed invalid. This behavior matches the intended implementation in the test. No changes are required at this time.

tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateSubtractTest.php (2)

39-46: Error handling test looks good.

The test for invalid timezone exception is properly implemented and validates the expected behavior according to PostgreSQL's timezone requirements.


48-64: Argument validation tests are thorough.

The tests for argument validation ensure that the function correctly enforces its expected interface, requiring between 2 and 3 arguments. This is good practice for robust API design.

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.

3 participants