Expression edge cases
Verification
Scope
Argument rules of row_number() and ntile(); isIn() with dates, datetimes, and times; NaN in coalesce(); one-character pads in str_pad(); parse_date_time() orders without a year; fractional periods; and dates plus clock periods. Results and error conditions on DataFrame, the SQL databases, and Polars.
References
- Reference
- R 4.6.1 with dplyr 1.2.1, stringr 1.6.0, and lubridate 1.9.5
- Versions
- R 4.6.1, dplyr 1.2.1, stringr 1.6.0, lubridate 1.9.5, jsonlite 2.0.0, PHP 8.5.10, SQLite 3.45.2, PostgreSQL 17.0 (Laravel Herd), MySQL 8.0.46, php_polars 0.1.0
Data
- edge cases
- Literal vectors in tests/ExprRTest.php and tests/Polars/PolarsExprRTest.php Source
Method
tests/r/edge_cases.R prints R's result or error for each case. The tests hold those results as expected values and check that phibble gives the same value, or raises where R raises. Fixtures regenerated with the listed R versions on 2026-11-01; output byte-identical to the committed files.
- Check
- Comparison with a reference implementation
- Recorded
- 2026-11-02, PHP 8.5.10, macOS 27.0.1 (arm64), commit 1abdeb1
Tolerances
exact equality
Applies to: Values and result types
NA in identical positions, datetimes to the microsecond
x: package value; r: reference value.
Results
21 comparisons in 15 cases: 21 pass, 0 fail, 0 skipped.
By case
| Case | Comparisons | Status |
|---|---|---|
| takes at most one argument in row_number(), as dplyr does | 1 | pass |
| takes x and a required n in ntile(), as dplyr does | 1 | pass |
| matches dates, datetimes, and times by value in isIn() | 1 | pass |
| serializes temporal isIn() lists | 1 | pass |
| matches dates and datetimes by value in isIn() | 4 | pass |
| skips NaN in coalesce(), as is_na() does | 1 | pass |
| skips NaN in coalesce() | 4 | pass |
| requires a one-character pad in str_pad(), as stringr does | 1 | pass |
| parses orders without a year into year 0, as lubridate does | 1 | pass |
| rejects a parse_date_time() order with a day but no month | 1 | pass |
| keeps fractional seconds and rejects other fractional periods, as lubridate does | 1 | pass |
| gives a datetime for a date plus hours, minutes, or seconds, as lubridate does | 1 | pass |
| buckets with ntile(x, n) | 1 | pass |
| parses orders without a year into year 0, and rejects a day without a month | 1 | pass |
| keeps fractional seconds and gives a datetime for a date plus hours | 1 | pass |
Every comparison
| Case | Implementation | Quantity | Status |
|---|---|---|---|
| takes at most one argument in row_number(), as dplyr does | DataFrame | pass | |
| takes x and a required n in ntile(), as dplyr does | DataFrame | pass | |
| matches dates, datetimes, and times by value in isIn() | DataFrame | pass | |
| serializes temporal isIn() lists | DataFrame | pass | |
| matches dates and datetimes by value in isIn() | SQLite | pass | |
| PostgreSQL | pass | ||
| MySQL | pass | ||
| skips NaN in coalesce(), as is_na() does | DataFrame | pass | |
| skips NaN in coalesce() | SQLite | pass | |
| PostgreSQL | pass | ||
| MySQL | pass | ||
| requires a one-character pad in str_pad(), as stringr does | DataFrame | pass | |
| parses orders without a year into year 0, as lubridate does | DataFrame | pass | |
| rejects a parse_date_time() order with a day but no month | DataFrame | pass | |
| keeps fractional seconds and rejects other fractional periods, as lubridate does | DataFrame | pass | |
| gives a datetime for a date plus hours, minutes, or seconds, as lubridate does | DataFrame | pass | |
| matches dates and datetimes by value in isIn() | Polars | pass | |
| skips NaN in coalesce() | Polars | pass | |
| buckets with ntile(x, n) | Polars | pass | |
| parses orders without a year into year 0, and rejects a day without a month | Polars | pass | |
| keeps fractional seconds and gives a datetime for a date plus hours | Polars | pass |
Limitations
Exclusions
- isIn() with temporal lists on SQLite and coalesce() of numbers on PostgreSQL run in memory; the results are compared the same way.
Known differences
- parse_date_time() with an order that has a day but no month: lubridate returns NA with a warning; phibble raises an error.
- Errors where R errors: phibble raises an exception with its own message.
Reproduction
Rscript tests/r/edge_cases.R
PHIBBLE_TEST_PGSQL="pgsql:host=127.0.0.1;port=5432;dbname=postgres" PHIBBLE_TEST_PGSQL_USER=postgres PHIBBLE_TEST_MYSQL="mysql:host=127.0.0.1;port=3306" PHIBBLE_TEST_MYSQL_USER=root php vendor/bin/pest tests/ExprRTest.php
php -d extension=php_polars.so php vendor/bin/pest tests/Polars/PolarsExprRTest.php
- Raw record
- site/verifications/expression-edge-cases.json
- Input data
- tests/r/edge_cases.R