Skip to content

Support PostgreSQL: replace SQLite-only SQL functions and alias-in-HAVING - #17

Open
steveneppler wants to merge 3 commits into
mainfrom
claude/pull-request-review-nfz7in
Open

Support PostgreSQL: replace SQLite-only SQL functions and alias-in-HAVING#17
steveneppler wants to merge 3 commits into
mainfrom
claude/pull-request-review-nfz7in

Conversation

@steveneppler

Copy link
Copy Markdown
Owner

Fixes #15.

Builds on @lumberjacklv's work in #16 (his commit is preserved here), rebased onto current main after #14 landed, with formatting cleanups and one additional PostgreSQL fix.

The problem

/analytics returns HTTP 500 on PostgreSQL. Issue #15 identified the HAVING clause in TempEfficiencyChart, but that is one of four independent breakages — the same strftime problem also takes down the Dashboard and Debug pages, and teslog:process-states fails on a separate SQLite-only function.

Every failure is in raw SQL (DB::raw, selectRaw, whereRaw, orderByRaw), which Laravel passes through to the driver verbatim, plus one reliance on a non-standard dialect extension. Standard SQL in the app — window functions in the map queries, DATE(), the aggregates, all migrations — already worked on PostgreSQL untouched.

Changes

DatabaseHelper (new) — driver-specific SQL for constructs with no cross-database equivalent:

  • formatDateTime($column, $format)hour / year-month / datetime, emitting date_trunc/to_char on pgsql, strftime on sqlite, DATE_FORMAT elsewhere. Replaces all 11 hardcoded strftime call sites.
  • absTimeDiffSeconds($column) — absolute distance in seconds between a timestamp column and a bound parameter, via EXTRACT(EPOCH FROM ...) on pgsql, JULIANDAY on sqlite, TIMESTAMPDIFF elsewhere.

TempEfficiencyCharthaving('cnt', '>=', 3)havingRaw('COUNT(*) >= 3'). Referencing a select alias in HAVING is a SQLite/MySQL extension that PostgreSQL does not accept. (Note: the fix suggested in #15, having('COUNT(*)', '>=', 3), would not work — Laravel quotes the first argument as an identifier, producing having "COUNT(*)" >= 3.)

ProcessVehicleStates — the nearby-state lookup used ABS(JULIANDAY(timestamp) - JULIANDAY(?)) to order states by proximity in time. julianday() exists only in SQLite. This path triggers when a charging session's first state has no GPS fix, which is common since charging states often lack coordinates.

Verification

Tested against a live PostgreSQL 16 instance with migrations run and a seeded dataset (63 vehicle states, 10 drives, 410 drive points, 10 charges, idles, battery health, telemetry rows).

Before, on PostgreSQL:

Component Error
CostCharts 42883: function strftime(unknown, timestamp without time zone) does not exist
TempEfficiencyChart 42703: column "cnt" does not exist (issue #15)
Dashboard strftime does not exist
Debug (both tabs) strftime does not exist
teslog:process-states 42883: function julianday(timestamp without time zone) does not exist

After — full sweep of 19 authenticated routes and 8 commands, run on both PostgreSQL and SQLite:

  • All 19 routes return 200 on both drivers (/analytics previously 500 on pgsql)
  • All 8 teslog:* commands exit 0 on both drivers
  • teslog:process-states was exercised with a seeded GPS-less charging session to force the JULIANDAY path; it now creates the charge and resolves identical coordinates from the nearby state on both drivers
  • Every rewritten query was run on both engines against identical deterministic data and the results diffed: byte-identical output, so this is not merely "no longer throws"
  • 175 tests pass on SQLite
  • Pint: no new violations — the two files Update SQL queries to support multiple databases #16 had made dirty (CostCharts, ExportController) are clean again, and the new DatabaseHelper is clean

Notes for review

  • The second commit reverts changes unrelated to the fix that were in Update SQL queries to support multiple databases #16 — trailing-newline removals (.editorconfig sets insert_final_newline), CSV header array reformatting, concat spacing, arrow-fn and brace style, and an incidental package-lock.json edit. Net diff is 79/14 rather than 122/48, so the behavioral change is the whole diff.
  • DatabaseHelper interpolates $column into raw SQL. All call sites pass hard-coded names; the docblocks say so explicitly.
  • formatDateTime has a default arm throwing InvalidArgumentException rather than surfacing an UnhandledMatchError on a typo'd format.
  • BackupDatabase, RestoreDatabase, and the settings/backup route are intentionally SQLite-only and already guard on config('database.default'), failing cleanly rather than erroring. Left as-is.
  • Not addressed: LIKE is case-sensitive on PostgreSQL but case-insensitive for ASCII on SQLite, so the Debug page's field filter matches differently across drivers. This is a behavior difference rather than an error, and picking the intended semantics is a product decision — flagging rather than changing it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NRjmmn5mw7HSdtXiLHsoHs


Generated by Claude Code

lumberjacklv and others added 3 commits July 24, 2026 23:49
Keeps the multi-database SQL fix intact while dropping changes unrelated
to it, so the diff shows only the behavioral change:

- Restore the trailing newline on the seven touched files (.editorconfig
  sets insert_final_newline, and their absence made three previously
  Pint-clean files dirty)
- Revert unrelated reformatting: CSV header array layout and concat
  spacing in ExportController, arrow-fn and brace style in Dashboard,
  method-chain indentation in Debug
- Revert the incidental package-lock.json change
- DatabaseHelper: PSR-12 indentation, docblock, and a default arm that
  throws InvalidArgumentException instead of UnhandledMatchError on an
  unrecognized format

Verified against PostgreSQL 16 and SQLite: all rewritten queries return
identical results on both drivers, and /analytics renders 200 on Postgres
(it 500s on main).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRjmmn5mw7HSdtXiLHsoHs
…ression

teslog:process-states failed on PostgreSQL when a charging session's first
state had no GPS fix: the nearby-state lookup ordered by
ABS(JULIANDAY(timestamp) - JULIANDAY(?)), and julianday() exists only in
SQLite.

Adds DatabaseHelper::absTimeDiffSeconds(), which returns the absolute
distance in seconds between a timestamp column and a bound parameter,
expressed per driver (EXTRACT(EPOCH FROM ...) on pgsql, JULIANDAY on
sqlite, TIMESTAMPDIFF elsewhere).

Verified on PostgreSQL 16 and SQLite by seeding a charging session whose
states lack GPS: the command previously threw "function julianday(timestamp
without time zone) does not exist" on Postgres, and now creates the charge
and resolves the same coordinates from the nearby state on both drivers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRjmmn5mw7HSdtXiLHsoHs
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.

PostgreSQL issue on Analytics page

3 participants