PostgreSQL: what runs differently¶
Where SQLite or ClickHouse compute something their own way, hare leaves their result as it is — see SQLite: what runs differently and ClickHouse: what runs differently. What differs on PostgreSQL is what the server itself does — how it ends a transaction after an error, how long a name may be, what each server version adds. Each row below says what hare does about it.
Transactions¶
| PostgreSQL | hare |
|---|---|
| A failed statement aborts the transaction | Leaving the block without an exception rolls it back and raises TransactionManagementError; a nested atomic() around the statement keeps the outer transaction usable — see A failed statement in a transaction. |
CREATE INDEX CONCURRENTLY outside a transaction only |
AddIndex(concurrently=True) needs atomic = False on its migration — see Indexes. |
| Waiting for a lock without a limit | migrate(lock_timeout=...) bounds how long a migration waits for a table's lock — see Lock timeout. |
Values and names¶
| PostgreSQL | hare |
|---|---|
| A session time zone of the server's setting | Both drivers run every session in UTC — see The session time zone. |
| Names cut to 63 bytes | A name hare generates is kept within 63 bytes, a longer one shortened with a digest; an explicit name over 63 bytes raises ConfigurationError — see table. |
NULL placement in an ordering |
NULL sorts as the largest value, last ascending — see NULL ordering. |
| Text order of the database's collation | Text compares and sorts by the collation of the database; Collate(...) asks for another — see Database functions. |
| Integer and decimal ranges of a column type | The column refuses a value out of its range itself. |
Server versions¶
hare reads the server's version when it connects and turns on what the version has; on an older server the feature is refused — see the features table.
| Version | Adds |
|---|---|
| 15 | merge(); UniqueConstraint(nulls_distinct=False) |
| 16 | explain(generic_plan=True) |
| 17 | merge().returning() and when_not_matched_by_source(); JsonTable; an ExclusionConstraint on a partitioned table; explain(memory=True), explain(serialize=True) |
| 18 | A VIRTUAL GeneratedField (stored=False); a UuidV7() db_default; without_overlaps=True; returning(old=...) |
What an older server does itself, hare leaves to it:
- PostgreSQL before 18 refuses to cast a JSON
nullof aJSONFieldpath to another type (Cast(F("data__n"), IntField())— cannot cast jsonb null to type integer); PostgreSQL 18 givesNULL. - PostgreSQL before 18 doesn't tell a transaction pooler its search path — the
schemaoption and a schema per tenant behind one are refused (see Connection poolers).
Schema and migrations¶
| PostgreSQL | hare |
|---|---|
| Extensions for vectors, geodata, trigrams and some types | Meta.extensions declares them, and the migration autodetector writes CreateExtension for each — see Extensions. |
| Partitioned tables, index types | See Indexes and tables. |
| Schemas | Meta.schema, a schema per tenant and row level security work as written — see Schema per tenant. |
Drivers¶
rust_pg and asyncpg give the same results through hare's API; raw SQL differs in how the two bind and
read values — see Raw SQL on the two drivers.