packages feed

pqi-native 0.2.0.1 → 0.2.0.2

raw patch · 4 files changed

+66/−12 lines, 4 filesdep ~pqi-conformancePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: pqi-conformance

API changes (from Hackage documentation)

Files

CHANGELOG.md view
@@ -1,3 +1,9 @@+# v0.2.0.2++## Fixes++- Connection startup now forwards extra conninfo params (e.g. `application_name`, `options`) instead of silently dropping everything but `user` and `database`, so they reach the server the way libpq's do. Caught by the new differential coverage in `pqi-conformance` 0.1.1.0.+ # v0.2.0.1  Documentation corrections.
README.md view
@@ -3,9 +3,15 @@ [![Hackage](https://img.shields.io/hackage/v/pqi-native.svg)](https://hackage.haskell.org/package/pqi-native) [![Continuous Haddock](https://img.shields.io/badge/haddock-master-blue)](https://nikita-volkov.github.io/pqi-native/) -> **Status: Alpha.** This is a young, LLM-generated reimplementation of `libpq`'s-> wire protocol handling. It is verified against a conformance suite (see-> below), but it hasn't seen much production use yet. See+> **Status: Alpha.** `pqi-native` is an early implementation of a pure-Haskell+> transport for [`pqi`](https://github.com/nikita-volkov/pqi). It exists+> alongside [`pqi-ffi`](https://github.com/nikita-volkov/pqi-ffi), the+> established C-backed adapter, and the two are fully interchangeable: any+> code written against `pqi` runs unchanged on either one, so trying+> `pqi-native` carries no lock-in and no rewrite cost. Correctness is checked+> continuously against a conformance suite (see below), but the adapter+> hasn't yet accumulated production mileage. If you need a production-proven+> transport today, use `pqi-ffi`. See > [_Making libpq a choice_](https://nikita-volkov.github.io/pqi-making-libpq-a-choice/) > for why this project exists and what tradeoffs that implies. @@ -13,7 +19,9 @@ that speaks the PostgreSQL frontend/backend wire protocol directly — no dependency on the C `libpq` library. -`pqi-native` is an LLM-generated port of the PostgreSQL C client library, [`libpq`](https://www.postgresql.org/docs/current/libpq.html). The upstream [`libpq` source](https://github.com/postgres/postgres/tree/master/src/interfaces/libpq) is the direct reference for the implementation.+`pqi-native` reimplements the wire protocol handled by the PostgreSQL C+client library, [`libpq`](https://www.postgresql.org/docs/current/libpq.html),+from scratch in Haskell. The upstream [`libpq` source](https://github.com/postgres/postgres/tree/master/src/interfaces/libpq) is the direct reference for the implementation.  ## Fidelity goal @@ -30,7 +38,15 @@ **Alpha.** The full `Pqi.Connection` capability record is implemented and verified against the `postgresql-libpq` reference via the conformance differential suite, but the library hasn't yet accumulated real-world-production mileage. Read+production mileage.++Because it implements the same `pqi` interface as+[`pqi-ffi`](https://github.com/nikita-volkov/pqi-ffi), switching between the+two is a one-line change — pass a different `Adapter` value, nothing else in+your code moves. That makes `pqi-native` low-risk to evaluate now and easy to+fall back from: adopt it where you want to shed the `libpq` dependency, and+drop back to `pqi-ffi` at any time without touching the rest of your+codebase. Read [_Making libpq a choice_](https://nikita-volkov.github.io/pqi-making-libpq-a-choice/) for the motivation and the tradeoffs of adopting it at this stage. 
pqi-native.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: pqi-native-version: 0.2.0.1+version: 0.2.0.2 category: Database, PostgreSQL synopsis: Native (pure-Haskell) adapter for pqi description:@@ -146,5 +146,5 @@   build-depends:     base >=4.11 && <5,     hspec >=2.11 && <2.12,-    pqi-conformance ^>=0.1,+    pqi-conformance ^>=0.1.1,     pqi-native,
src/library/Pqi/Native/Connection.hs view
@@ -19,6 +19,7 @@ import qualified Data.ByteString as ByteString import qualified Data.ByteString.Char8 as ByteString.Char8 import qualified Data.Map.Strict as Map+import qualified Data.Set as Set import Pqi (ConnStatus (..), Notify (..), PipelineStatus (..), Verbosity (..)) import qualified Pqi.Native.Auth as Auth import Pqi.Native.Prelude@@ -36,7 +37,11 @@     port :: Int,     user :: ByteString,     database :: ByteString,-    password :: ByteString+    password :: ByteString,+    -- | Every other recognized @key=value@ pair (e.g. @application_name@,+    -- @options@), forwarded verbatim in the startup message so the server+    -- sees them, the way libpq does.+    extraParams :: Map.Map ByteString ByteString   }   deriving stock (Eq, Show) @@ -71,7 +76,8 @@       port = maybe 5432 fst (ByteString.Char8.readInt (get "port" "5432")),       user = theUser,       database = get "dbname" theUser,-      password = get "password" ""+      password = get "password" "",+      extraParams = Map.withoutKeys settings reservedKeys     }   where     pairs = mapMaybe toPair (ByteString.Char8.words raw)@@ -83,12 +89,17 @@         | not (ByteString.null value) -> Just (key, ByteString.drop 1 value)       _ -> Nothing +-- | Conninfo keys already surfaced via their own 'ConnInfo' fields, so they're+-- excluded from 'extraParams' rather than duplicated there.+reservedKeys :: Set.Set ByteString+reservedKeys = Set.fromList ["host", "port", "user", "dbname", "password"]+ -- | Parse the authority+path portion of a @postgresql://@ URI (scheme already -- stripped). Handles @[user[:password]@][host[:port]][/dbname]@; ignores query -- parameters other than what appears in those components. parseUri :: ByteString -> ByteString -> ConnInfo parseUri dfltUser withoutScheme =-  ConnInfo {host, port, user, database, password}+  ConnInfo {host, port, user, database, password, extraParams}   where     -- Split off optional "userinfo@" prefix. The '@' is unambiguous in this     -- position: hosts do not contain '@' in practice.@@ -115,6 +126,21 @@         then user         else pctDecode rawDbname +    -- Everything after the first '?', parsed as '&'-separated key=value pairs+    -- (e.g. ?application_name=foo&sslmode=disable).+    rawQuery = ByteString.drop 1 (ByteString.dropWhile (/= 0x3f) pathAndQuery)++    extraParams =+      Map.withoutKeys+        (Map.fromList (mapMaybe toQueryPair (ByteString.split 0x26 rawQuery)))+        reservedKeys++    toQueryPair token = case ByteString.elemIndex 0x3d token of+      Just i ->+        let (k, v) = ByteString.splitAt i token+         in if ByteString.null k then Nothing else Just (pctDecode k, pctDecode (ByteString.drop 1 v))+      Nothing -> Nothing+     -- Parse host and port from "host:port", handling IPv6 "[::1]:port".     (host, port)       | not (ByteString.null hostport) && ByteString.head hostport == 0x5b =@@ -241,7 +267,7 @@       pure connection     Right transport -> do       connection <- newConnection False transport info-      sendMessage connection (startupMessage [("user", user info), ("database", database info)])+      sendMessage connection (startupMessage (startupParams info))       handshake connection       pure connection @@ -268,8 +294,14 @@   writeIORef (txStatus connection) 0x49   writeIORef (connStatus connection) ConnectionBad   writeIORef (lastError connection) (Just "")-  sendMessage connection (startupMessage [("user", user (info connection)), ("database", database (info connection))])+  sendMessage connection (startupMessage (startupParams (info connection)))   handshake connection++-- | The startup message parameter list: @user@ and @database@, plus any+-- extra conninfo params (e.g. @application_name@) forwarded verbatim.+startupParams :: ConnInfo -> [(ByteString, ByteString)]+startupParams info =+  ("user", user info) : ("database", database info) : Map.toList (extraParams info)  newConnection :: Bool -> Transport -> ConnInfo -> IO Connection newConnection isNull transport info = do