datajoe/postgresql-distinct-on
Adds PostgreSQL DISTINCT ON support to Laravel's query builder and Eloquent, so you can fetch the first row per group with a clean, chainable API. Ideal for latest-per-user or per-category queries without raw SQL or subqueries.
Architecture fit. The package sits on the query-builder seam of Laravel — extending Illuminate\Database\Query\Builder and/or PostgresGrammar to emit SELECT DISTINCT ON (cols) .... That is a low-coupling insertion point: no service provider bootstrap cost, no schema changes, no runtime service container resolution in the hot path. It only applies to applications whose primary datastore is PostgreSQL, and only the read path. Fit is therefore narrow but clean — it does not perturb the app architecture, but it also does not compound: the capability it adds is roughly one selectRaw() call.
Integration feasibility. High in principle, low in practice due to provenance. The repository lives on Bitbucket, reports no stars, no dependents, and no score. There is no evidence in the supplied metadata of Packagist publication, tagged releases, CI, or a license file. Composer can consume it via a vcs repository and a pinned commit, which is technically sufficient but operationally fragile. The claimed support matrix — Laravel 5 through 8 — is the decisive finding: every version it supports is end-of-life. Laravel 9, 10, 11, and 12 are outside the declared range. Adoption today means adopting an unmaintained artifact for a framework generation it was never tested against.
Technical risk.
DISTINCT ON is a well-understood PostgreSQL extension with stable semantics. The wrapper itself is thin. The question is not "will it work" but "is it worth a dependency."Grammar::wrap() / wrapSegments() is an injection vector if the column names ever derive from request input. This must be verified by reading the source before any pilot.DISTINCT ON interacts badly with pagination: Laravel's paginator issues a count(*) over the same builder. Count queries over a DISTINCT ON select require a subquery wrapper, and a naive implementation will produce wrong totals or invalid SQL. This is the most likely production defect class.Key questions.
dev-master/commit pin?count(), paginate(), exists(), and union() composed with DISTINCT ON?Stack fit. Appropriate only for a Laravel service on PostgreSQL 12+ where "greatest-n-per-group" reads are a recurring access pattern — latest event per entity, most recent price per SKU, current assignment per user. If the codebase already contains scattered selectRaw('DISTINCT ON ...') fragments or correlated whereIn/row_number() workarounds, the fit is real and the consolidation benefit is concrete.
Migration path. Do not take the dependency. Treat the repository as prior art and a reference implementation, and internalize the capability:
Builder::macro('distinctOn', ...) registered in an application service provider, delegating to $this->grammar->wrap() for the column list and requiring an explicit orderBy alongside it.pgsql, so failures are loud rather than silent.This yields the same capability with a ~40-line internal surface, no third-party availability risk, and a clear owner.
Compatibility. Constrain to the internal macro's behavior, not the package's version range. Verify against the actual PostgreSQL major versions in the fleet, since DISTINCT ON planning behavior and index selection have shifted across 12–17. Confirm the Laravel version's compileColumns path — the method's handling of the distinct flag has changed across major versions, and any external package reaching into it is the primary break vector.
Sequencing.
distinct on, row_number() over, and correlated whereIn subqueries used for latest-per-group.selectRaw vs. LATERAL join.EXPLAIN (ANALYZE, BUFFERS).Maintenance. Assume zero upstream velocity. There is no signal of recent commits, issue triage, or security response. Budget for the package as fork-and-own from day one, not as a maintained dependency. The internal macro route eliminates this cost entirely and is the recommended posture.
Support. No vendor, no SLA, no security contact. Any incident affecting a DISTINCT ON read becomes an in-house debugging exercise against unfamiliar third-party code, on a Bitbucket repository that could disappear. Note Bitbucket's history of free-tier policy changes as an availability risk for the source itself — mirror the code internally at adoption time if we proceed.
Scaling. DISTINCT ON forces a sort over the driving set. Performance is entirely contingent on a btree index whose leading columns match the DISTINCT ON expressions and whose trailing columns support the ORDER BY direction. Without it, the planner falls back to a full sort spilling to temp files — a query that looks fine at 10k rows and degrades sharply at 10M. For queries pulling one row per group from a small group set, a LATERAL join is frequently faster. For deep pagination over DISTINCT ON results, expect the count query to be the bottleneck because it cannot use the index in the same way. All of this requires per-query EXPLAIN review, not a blanket adoption.
Failure modes.
paginate()/count() — wrong total counts, off-by-page boundaries. Highest severity because it is invisible.ORDER BY is omitted or when the leading ORDER BY terms do not match the DISTINCT ON expressions — PostgreSQL requires this and will error, but a macro that auto-injects ORDER BY incorrectly will silently pick the wrong "first" row.Ramp-up. Effectively zero for a competent Laravel engineer — the concept is a single SQL clause and the wrapper is small. Half a day to read, reimplement, and test. The real cost is not learning the tool; it is the review discipline required to ensure every call site has a supporting index and a validated count path. Budget engineering time for query-plan review, not for onboarding.
How can I help you explore Laravel packages today?