pqi-native 1.0.1.4 → 1.0.1.5
raw patch · 3 files changed
+70/−39 lines, 3 filesdep ~pqi-conformancePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: pqi-conformance
API changes (from Hackage documentation)
Files
- CHANGELOG.md +6/−0
- pqi-native.cabal +2/−2
- src/library/Pqi/Native/Query.hs +62/−37
CHANGELOG.md view
@@ -1,3 +1,9 @@+# v1.0.1.5++## Fixes++- `sendQueryParams`, `execParams`, `sendPrepare`, `prepare`, `sendQueryPrepared`, and `execPrepared` now reject a parameter list (or, for `prepare`\/`sendPrepare`, a parameter-type list) longer than 65535 locally, without writing anything to the socket, mirroring `libpq`'s `PQ_QUERY_PARAM_MAX_LIMIT` check. The `Parse`\/`Bind` messages encode their parameter count as a 16-bit field; past the limit `fromIntegral` silently wrapped it (65536 became 0), producing a malformed message the server rejected with `invalid message format` and leaving the connection desynchronized - inside a pipeline, every command dispatched before the failing one was left with its results undrained. Caught by the differential coverage added in `pqi-conformance` 1.0.5.0. Found via `hasql` issue #326.+ # v1.0.1.4 ## Fixes
pqi-native.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: pqi-native-version: 1.0.1.4+version: 1.0.1.5 category: Database, PostgreSQL synopsis: Native (pure-Haskell) adapter for pqi description:@@ -158,5 +158,5 @@ build-depends: base >=4.11 && <5, hspec >=2.11 && <2.12,- pqi-conformance ^>=1.0.4,+ pqi-conformance ^>=1.0.5, pqi-native,
src/library/Pqi/Native/Query.hs view
@@ -59,6 +59,19 @@ <> describePortalMessage "" <> executeMessage "" 0 +-- * Parameter-count limit++-- | The largest parameter count the wire protocol's 16-bit count fields can+-- carry, matching libpq's @PQ_QUERY_PARAM_MAX_LIMIT@. 'Parse' and 'Bind'+-- messages encode their parameter count as an 'Int16'; past this limit that+-- encoding wraps around instead of overflowing, so the count must be+-- rejected locally before it corrupts the message.+maxParamCount :: Int+maxParamCount = 65535++tooManyParams :: [a] -> Bool+tooManyParams = (> maxParamCount) . length+ -- * Synchronous flows -- | Simple query. Returns the last result, mirroring @PQexec@.@@ -69,21 +82,27 @@ -- | Parameterized query via the extended protocol. execParams :: Connection -> ByteString -> [Maybe (Word32, ByteString, Format)] -> Format -> IO (Maybe NativeResult)-execParams connection sql params resultFormat = withReady connection do- sendMessage connection (paramsWrite sql params resultFormat)- Just <$> collectExtended connection sql+execParams connection sql params resultFormat+ | tooManyParams params = pure Nothing+ | otherwise = withReady connection do+ sendMessage connection (paramsWrite sql params resultFormat)+ Just <$> collectExtended connection sql -- | Prepare a named statement. prepare :: Connection -> ByteString -> ByteString -> Maybe [Word32] -> IO (Maybe NativeResult)-prepare connection name sql parameterTypes = withReady connection do- sendMessage connection (prepareWrite name sql parameterTypes)- Just <$> collectExtended connection sql+prepare connection name sql parameterTypes+ | maybe False tooManyParams parameterTypes = pure Nothing+ | otherwise = withReady connection do+ sendMessage connection (prepareWrite name sql parameterTypes)+ Just <$> collectExtended connection sql -- | Execute a previously prepared statement. execPrepared :: Connection -> ByteString -> [Maybe (ByteString, Format)] -> Format -> IO (Maybe NativeResult)-execPrepared connection name params resultFormat = withReady connection do- sendMessage connection (preparedWrite name params resultFormat)- Just <$> collectExtended connection ""+execPrepared connection name params resultFormat+ | tooManyParams params = pure Nothing+ | otherwise = withReady connection do+ sendMessage connection (preparedWrite name params resultFormat)+ Just <$> collectExtended connection "" -- * Asynchronous flows @@ -112,38 +131,44 @@ -- Extended query: include Sync when not in pipeline mode; omit Sync in -- pipeline mode (the caller drives sync boundaries via 'pipelineSync'). sendQueryParams :: Connection -> ByteString -> [Maybe (Word32, ByteString, Format)] -> Format -> IO Bool-sendQueryParams connection sql params resultFormat = do- pipeline <- inPipeline connection- ok <-- sendAsync connection sql- $ if pipeline- then asyncParamsWrite sql params resultFormat- else paramsWrite sql params resultFormat- -- 'sendQueryParams' drives the extended protocol and so always sends an- -- unnamed @Parse@, whose @ParseComplete@ must fold into this command's own- -- result (like 'sendQueryPrepared'). Record its origin so it is not mistaken- -- for the terminal @ParseComplete@ of a pipelined 'sendPrepare'.- when (ok && pipeline) $ modifyIORef' (pendingParseOrigins connection) (Seq.|> False)- pure ok+sendQueryParams connection sql params resultFormat+ | tooManyParams params = pure False+ | otherwise = do+ pipeline <- inPipeline connection+ ok <-+ sendAsync connection sql+ $ if pipeline+ then asyncParamsWrite sql params resultFormat+ else paramsWrite sql params resultFormat+ -- 'sendQueryParams' drives the extended protocol and so always sends an+ -- unnamed @Parse@, whose @ParseComplete@ must fold into this command's own+ -- result (like 'sendQueryPrepared'). Record its origin so it is not mistaken+ -- for the terminal @ParseComplete@ of a pipelined 'sendPrepare'.+ when (ok && pipeline) $ modifyIORef' (pendingParseOrigins connection) (Seq.|> False)+ pure ok sendPrepare :: Connection -> ByteString -> ByteString -> Maybe [Word32] -> IO Bool-sendPrepare connection name sql parameterTypes = do- pipeline <- inPipeline connection- ok <-- sendAsync connection sql- $ if pipeline- then parseMessage name sql (fromMaybe [] parameterTypes)- else prepareWrite name sql parameterTypes- when (ok && pipeline) $ modifyIORef' (pendingParseOrigins connection) (Seq.|> True)- pure ok+sendPrepare connection name sql parameterTypes+ | maybe False tooManyParams parameterTypes = pure False+ | otherwise = do+ pipeline <- inPipeline connection+ ok <-+ sendAsync connection sql+ $ if pipeline+ then parseMessage name sql (fromMaybe [] parameterTypes)+ else prepareWrite name sql parameterTypes+ when (ok && pipeline) $ modifyIORef' (pendingParseOrigins connection) (Seq.|> True)+ pure ok sendQueryPrepared :: Connection -> ByteString -> [Maybe (ByteString, Format)] -> Format -> IO Bool-sendQueryPrepared connection name params resultFormat = do- pipeline <- inPipeline connection- sendAsync connection ""- $ if pipeline- then asyncPreparedWrite name params resultFormat- else preparedWrite name params resultFormat+sendQueryPrepared connection name params resultFormat+ | tooManyParams params = pure False+ | otherwise = do+ pipeline <- inPipeline connection+ sendAsync connection ""+ $ if pipeline+ then asyncPreparedWrite name params resultFormat+ else preparedWrite name params resultFormat sendDescribePrepared :: Connection -> ByteString -> IO Bool sendDescribePrepared connection name = do