packages feed

postgresql-simple-postgresql-types 0.1.1 → 0.1.2.0

raw patch · 7 files changed

+227/−213 lines, 7 filesdep ~postgresql-typesdep ~postgresql-types-algebra

Dependency ranges changed: postgresql-types, postgresql-types-algebra

Files

postgresql-simple-postgresql-types.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: postgresql-simple-postgresql-types-version: 0.1.1+version: 0.1.2.0 category: PostgreSQL, Codecs synopsis: Integration of "postgresql-simple" with "postgresql-types" homepage: https://github.com/nikita-volkov/postgresql-simple-postgresql-types@@ -90,14 +90,14 @@    other-modules:     Database.PostgreSQL.Simple.PostgresqlTypes.Prelude-    Database.PostgreSQL.Simple.PostgresqlTypes.ViaIsScalar+    Database.PostgreSQL.Simple.PostgresqlTypes.ViaIsPrimitive    build-depends:     attoparsec ^>=0.14.4,     base >=4.11 && <5,     postgresql-simple >=0.7.0.1 && <0.8,-    postgresql-types ^>=0.1,-    postgresql-types-algebra ^>=0.1,+    postgresql-types ^>=0.1.5,+    postgresql-types-algebra ^>=0.2,     tagged ^>=0.8.9,     text >=1.2 && <3,     text-builder ^>=1.0.0.5,@@ -118,8 +118,8 @@     hspec >=2.11 && <3,     postgresql-simple >=0.7.0.1 && <0.8,     postgresql-simple-postgresql-types,-    postgresql-types ^>=0.1,-    postgresql-types-algebra ^>=0.1,+    postgresql-types ^>=0.1.5,+    postgresql-types-algebra ^>=0.2,     quickcheck-instances ^>=0.3.33,     stm >=2.5 && <3,     tagged ^>=0.8.9,
src/integration-tests/IntegrationTests/Scopes.hs view
@@ -53,6 +53,7 @@       void do         Ps.execute_ connection "SET client_min_messages TO WARNING"       createExtensionIfNotExists connection "hstore"+      createExtensionIfNotExists connection "citext"       pure connection       where         createExtensionIfNotExists connection extension =
src/integration-tests/IntegrationTests/Scripts.hs view
@@ -26,7 +26,7 @@     QuickCheck.Arbitrary a,     Show a,     Eq a,-    Pta.IsScalar a,+    Pta.IsPrimitive a,     Ps.ToField a,     Ps.FromField a,     Typeable a
src/integration-tests/Main.hs view
@@ -34,6 +34,7 @@           withType @Pt.Char [mappingSpec True]           withType @Pt.Cidr [mappingSpec True]           withType @Pt.Circle [mappingSpec True]+          withType @Pt.Citext [mappingSpec True]           withType @Pt.Date [mappingSpec True]           withType @Pt.Float4 [mappingSpec True]           withType @Pt.Float8 [mappingSpec True]@@ -89,6 +90,7 @@           withType @Pt.Char [mappingSpec True]           withType @Pt.Cidr [mappingSpec True]           withType @Pt.Circle [mappingSpec True]+          withType @Pt.Citext [mappingSpec True]           withType @Pt.Date [mappingSpec True]           withType @Pt.Float4 [mappingSpec True]           withType @Pt.Float8 [mappingSpec True]
src/library/Database/PostgreSQL/Simple/PostgresqlTypes.hs view
@@ -23,171 +23,182 @@  import Data.Data (Typeable) import Database.PostgreSQL.Simple.FromField (FromField)-import Database.PostgreSQL.Simple.PostgresqlTypes.ViaIsScalar (ViaIsScalar (ViaIsScalar))+import Database.PostgreSQL.Simple.PostgresqlTypes.ViaIsPrimitive (ViaIsPrimitive (ViaIsPrimitive)) import Database.PostgreSQL.Simple.ToField import GHC.TypeLits import PostgresqlTypes import PostgresqlTypes.Algebra -deriving via ViaIsScalar (Bit length) instance (KnownNat length) => FromField (Bit length)+deriving via ViaIsPrimitive (Bit length) instance (KnownNat length) => FromField (Bit length) -deriving via ViaIsScalar (Bit length) instance (KnownNat length) => ToField (Bit length)+deriving via ViaIsPrimitive (Bit length) instance (KnownNat length) => ToField (Bit length) -deriving via ViaIsScalar (Bpchar length) instance (KnownNat length) => FromField (Bpchar length)+deriving via ViaIsPrimitive (Bpchar length) instance (KnownNat length) => FromField (Bpchar length) -deriving via ViaIsScalar (Bpchar length) instance (KnownNat length) => ToField (Bpchar length)+deriving via ViaIsPrimitive (Bpchar length) instance (KnownNat length) => ToField (Bpchar length) -deriving via ViaIsScalar (Numeric precision scale) instance (KnownNat precision, KnownNat scale) => FromField (Numeric precision scale)+deriving via ViaIsPrimitive (Numeric precision scale) instance (KnownNat precision, KnownNat scale) => FromField (Numeric precision scale) -deriving via ViaIsScalar (Numeric precision scale) instance (KnownNat precision, KnownNat scale) => ToField (Numeric precision scale)+deriving via ViaIsPrimitive (Numeric precision scale) instance (KnownNat precision, KnownNat scale) => ToField (Numeric precision scale) -deriving via ViaIsScalar (Varbit maxLen) instance (KnownNat maxLen) => FromField (Varbit maxLen)+deriving via ViaIsPrimitive (Varbit maxLen) instance (KnownNat maxLen) => FromField (Varbit maxLen) -deriving via ViaIsScalar (Varbit maxLen) instance (KnownNat maxLen) => ToField (Varbit maxLen)+deriving via ViaIsPrimitive (Varbit maxLen) instance (KnownNat maxLen) => ToField (Varbit maxLen) -deriving via ViaIsScalar (Varchar maxLen) instance (KnownNat maxLen) => FromField (Varchar maxLen)+deriving via ViaIsPrimitive (Varchar maxLen) instance (KnownNat maxLen) => FromField (Varchar maxLen) -deriving via ViaIsScalar (Varchar maxLen) instance (KnownNat maxLen) => ToField (Varchar maxLen)+deriving via ViaIsPrimitive (Varchar maxLen) instance (KnownNat maxLen) => ToField (Varchar maxLen)  -- | Decoder of 'Multirange' types. -- -- Notice that \"postgresql-simple\" has an issue due to which queries producing arrays of multiranges always fail. See https://github.com/haskellari/postgresql-simple/issues/163. In other cases everything should work fine.-deriving via ViaIsScalar (Multirange a) instance (IsMultirangeElement a, Typeable a) => FromField (Multirange a)+deriving via ViaIsPrimitive (Multirange a) instance (IsMultirangeElement a, Typeable a) => FromField (Multirange a) -deriving via ViaIsScalar (Multirange a) instance (IsMultirangeElement a) => ToField (Multirange a)+deriving via ViaIsPrimitive (Multirange a) instance (IsMultirangeElement a) => ToField (Multirange a) -deriving via ViaIsScalar (Range a) instance (IsRangeElement a, Typeable a) => FromField (Range a)+deriving via ViaIsPrimitive (Range a) instance (IsRangeElement a, Typeable a) => FromField (Range a) -deriving via ViaIsScalar (Range a) instance (IsRangeElement a) => ToField (Range a)+deriving via ViaIsPrimitive (Range a) instance (IsRangeElement a) => ToField (Range a) -deriving via ViaIsScalar Bool instance FromField Bool+deriving via ViaIsPrimitive Bool instance FromField Bool -deriving via ViaIsScalar Bool instance ToField Bool+deriving via ViaIsPrimitive Bool instance ToField Bool -deriving via ViaIsScalar Box instance FromField Box+deriving via ViaIsPrimitive Box instance FromField Box -deriving via ViaIsScalar Box instance ToField Box+deriving via ViaIsPrimitive Box instance ToField Box -deriving via ViaIsScalar Bytea instance FromField Bytea+deriving via ViaIsPrimitive Bytea instance FromField Bytea -deriving via ViaIsScalar Bytea instance ToField Bytea+deriving via ViaIsPrimitive Bytea instance ToField Bytea -deriving via ViaIsScalar Char instance FromField Char+deriving via ViaIsPrimitive Char instance FromField Char -deriving via ViaIsScalar Char instance ToField Char+deriving via ViaIsPrimitive Char instance ToField Char -deriving via ViaIsScalar Cidr instance FromField Cidr+deriving via ViaIsPrimitive Cidr instance FromField Cidr -deriving via ViaIsScalar Cidr instance ToField Cidr+deriving via ViaIsPrimitive Cidr instance ToField Cidr -deriving via ViaIsScalar Circle instance FromField Circle+deriving via ViaIsPrimitive Circle instance FromField Circle -deriving via ViaIsScalar Circle instance ToField Circle+deriving via ViaIsPrimitive Circle instance ToField Circle -deriving via ViaIsScalar Date instance FromField Date+deriving via ViaIsPrimitive Citext instance FromField Citext -deriving via ViaIsScalar Date instance ToField Date+deriving via ViaIsPrimitive Citext instance ToField Citext -deriving via ViaIsScalar Float4 instance FromField Float4+deriving via ViaIsPrimitive Date instance FromField Date -deriving via ViaIsScalar Float4 instance ToField Float4+deriving via ViaIsPrimitive Date instance ToField Date -deriving via ViaIsScalar Float8 instance FromField Float8+deriving via ViaIsPrimitive Float4 instance FromField Float4 -deriving via ViaIsScalar Float8 instance ToField Float8+deriving via ViaIsPrimitive Float4 instance ToField Float4 -deriving via ViaIsScalar Hstore instance FromField Hstore+deriving via ViaIsPrimitive Float8 instance FromField Float8 -deriving via ViaIsScalar Hstore instance ToField Hstore+deriving via ViaIsPrimitive Float8 instance ToField Float8 -deriving via ViaIsScalar Inet instance FromField Inet+-- | Decoder of the 'Geometry' PostGIS type.+--+-- Requires the @postgis@ extension to be installed in PostgreSQL.+deriving via ViaIsPrimitive Geometry instance FromField Geometry -deriving via ViaIsScalar Inet instance ToField Inet+deriving via ViaIsPrimitive Geometry instance ToField Geometry -deriving via ViaIsScalar Int2 instance FromField Int2+deriving via ViaIsPrimitive Hstore instance FromField Hstore -deriving via ViaIsScalar Int2 instance ToField Int2+deriving via ViaIsPrimitive Hstore instance ToField Hstore -deriving via ViaIsScalar Int4 instance FromField Int4+deriving via ViaIsPrimitive Inet instance FromField Inet -deriving via ViaIsScalar Int4 instance ToField Int4+deriving via ViaIsPrimitive Inet instance ToField Inet -deriving via ViaIsScalar Int8 instance FromField Int8+deriving via ViaIsPrimitive Int2 instance FromField Int2 -deriving via ViaIsScalar Int8 instance ToField Int8+deriving via ViaIsPrimitive Int2 instance ToField Int2 -deriving via ViaIsScalar Interval instance FromField Interval+deriving via ViaIsPrimitive Int4 instance FromField Int4 -deriving via ViaIsScalar Interval instance ToField Interval+deriving via ViaIsPrimitive Int4 instance ToField Int4 -deriving via ViaIsScalar Json instance FromField Json+deriving via ViaIsPrimitive Int8 instance FromField Int8 -deriving via ViaIsScalar Json instance ToField Json+deriving via ViaIsPrimitive Int8 instance ToField Int8 -deriving via ViaIsScalar Jsonb instance FromField Jsonb+deriving via ViaIsPrimitive Interval instance FromField Interval -deriving via ViaIsScalar Jsonb instance ToField Jsonb+deriving via ViaIsPrimitive Interval instance ToField Interval -deriving via ViaIsScalar Line instance FromField Line+deriving via ViaIsPrimitive Json instance FromField Json -deriving via ViaIsScalar Line instance ToField Line+deriving via ViaIsPrimitive Json instance ToField Json -deriving via ViaIsScalar Lseg instance FromField Lseg+deriving via ViaIsPrimitive Jsonb instance FromField Jsonb -deriving via ViaIsScalar Lseg instance ToField Lseg+deriving via ViaIsPrimitive Jsonb instance ToField Jsonb -deriving via ViaIsScalar Macaddr instance FromField Macaddr+deriving via ViaIsPrimitive Line instance FromField Line -deriving via ViaIsScalar Macaddr instance ToField Macaddr+deriving via ViaIsPrimitive Line instance ToField Line -deriving via ViaIsScalar Macaddr8 instance FromField Macaddr8+deriving via ViaIsPrimitive Lseg instance FromField Lseg -deriving via ViaIsScalar Macaddr8 instance ToField Macaddr8+deriving via ViaIsPrimitive Lseg instance ToField Lseg -deriving via ViaIsScalar Money instance FromField Money+deriving via ViaIsPrimitive Macaddr instance FromField Macaddr -deriving via ViaIsScalar Money instance ToField Money+deriving via ViaIsPrimitive Macaddr instance ToField Macaddr -deriving via ViaIsScalar Oid instance FromField Oid+deriving via ViaIsPrimitive Macaddr8 instance FromField Macaddr8 -deriving via ViaIsScalar Oid instance ToField Oid+deriving via ViaIsPrimitive Macaddr8 instance ToField Macaddr8 -deriving via ViaIsScalar Path instance FromField Path+deriving via ViaIsPrimitive Money instance FromField Money -deriving via ViaIsScalar Path instance ToField Path+deriving via ViaIsPrimitive Money instance ToField Money -deriving via ViaIsScalar Point instance FromField Point+deriving via ViaIsPrimitive Oid instance FromField Oid -deriving via ViaIsScalar Point instance ToField Point+deriving via ViaIsPrimitive Oid instance ToField Oid -deriving via ViaIsScalar Polygon instance FromField Polygon+deriving via ViaIsPrimitive Path instance FromField Path -deriving via ViaIsScalar Polygon instance ToField Polygon+deriving via ViaIsPrimitive Path instance ToField Path -deriving via ViaIsScalar Text instance FromField Text+deriving via ViaIsPrimitive Point instance FromField Point -deriving via ViaIsScalar Text instance ToField Text+deriving via ViaIsPrimitive Point instance ToField Point -deriving via ViaIsScalar Time instance FromField Time+deriving via ViaIsPrimitive Polygon instance FromField Polygon -deriving via ViaIsScalar Time instance ToField Time+deriving via ViaIsPrimitive Polygon instance ToField Polygon -deriving via ViaIsScalar Timestamp instance FromField Timestamp+deriving via ViaIsPrimitive Text instance FromField Text -deriving via ViaIsScalar Timestamp instance ToField Timestamp+deriving via ViaIsPrimitive Text instance ToField Text -deriving via ViaIsScalar Timestamptz instance FromField Timestamptz+deriving via ViaIsPrimitive Time instance FromField Time -deriving via ViaIsScalar Timestamptz instance ToField Timestamptz+deriving via ViaIsPrimitive Time instance ToField Time -deriving via ViaIsScalar Timetz instance FromField Timetz+deriving via ViaIsPrimitive Timestamp instance FromField Timestamp -deriving via ViaIsScalar Timetz instance ToField Timetz+deriving via ViaIsPrimitive Timestamp instance ToField Timestamp -deriving via ViaIsScalar Tsvector instance FromField Tsvector+deriving via ViaIsPrimitive Timestamptz instance FromField Timestamptz -deriving via ViaIsScalar Tsvector instance ToField Tsvector+deriving via ViaIsPrimitive Timestamptz instance ToField Timestamptz -deriving via ViaIsScalar Uuid instance FromField Uuid+deriving via ViaIsPrimitive Timetz instance FromField Timetz -deriving via ViaIsScalar Uuid instance ToField Uuid+deriving via ViaIsPrimitive Timetz instance ToField Timetz++deriving via ViaIsPrimitive Tsvector instance FromField Tsvector++deriving via ViaIsPrimitive Tsvector instance ToField Tsvector++deriving via ViaIsPrimitive Uuid instance FromField Uuid++deriving via ViaIsPrimitive Uuid instance ToField Uuid
+ src/library/Database/PostgreSQL/Simple/PostgresqlTypes/ViaIsPrimitive.hs view
@@ -0,0 +1,125 @@+{-# OPTIONS_GHC -Wno-orphans #-}++-- |+-- This module provides a bridge between PostgreSQL's standard types and the postgresql-simple library,+-- offering automatic ToField and FromField instance generation for types that implement the 'IsPrimitive' constraint.+--+-- = Usage+--+-- Import this module in addition to @Database.PostgreSQL.Simple@ to get encoding/decoding support+-- for postgresql-types in postgresql-simple queries:+--+-- > import Database.PostgreSQL.Simple+-- > import Database.PostgreSQL.Simple.PostgresqlTypes+-- > import PostgresqlTypes.Types (Int4, Text)+-- >+-- > -- Now you can use postgresql-types directly in queries+-- > example :: Connection -> Int4 -> IO [Only Text]+-- > example conn myInt = query conn "SELECT name FROM users WHERE id = ?" (Only myInt)+--+-- = How it works+--+-- * 'toFieldVia' creates a 'ToField' compatible 'Action' using the 'textualEncoder' from 'Pta.IsPrimitive'+-- * 'fromFieldVia' creates a 'FromField' compatible parser using the 'textualDecoder' from 'Pta.IsPrimitive'+--+-- The module uses textual format for encoding/decoding since that's what postgresql-simple primarily uses.+module Database.PostgreSQL.Simple.PostgresqlTypes.ViaIsPrimitive (ViaIsPrimitive (..)) where++import qualified Data.Attoparsec.Text as Attoparsec+import qualified Data.Text as Text+import qualified Data.Text.Encoding as TextEncoding+import qualified Data.Text.Encoding.Error as TextEncoding+import Database.PostgreSQL.Simple+import Database.PostgreSQL.Simple.FromField+import Database.PostgreSQL.Simple.PostgresqlTypes.Prelude+import Database.PostgreSQL.Simple.ToField+import qualified PostgresqlTypes.Algebra as Pta+import qualified TextBuilder++newtype ViaIsPrimitive a = ViaIsPrimitive a++instance (Pta.IsPrimitive a) => ToField (ViaIsPrimitive a) where+  toField (ViaIsPrimitive value) = toFieldVia value++instance (Typeable a, Pta.IsPrimitive a) => FromField (ViaIsPrimitive a) where+  fromField field mdata = ViaIsPrimitive <$> fromFieldVia field mdata++-- | Convert a postgresql-types value to a postgresql-simple 'Action'.+--+-- This function uses the textual encoder from 'IsPrimitive' to produce+-- an escaped text value suitable for use in SQL queries.+--+-- > instance ToField Int4 where+-- >   toField = toFieldVia+toFieldVia :: forall a. (Pta.IsPrimitive a) => a -> Action+toFieldVia value =+  Many+    [ Escape (TextEncoding.encodeUtf8 (TextBuilder.toText (Pta.textualEncoder value))),+      Plain ("::" <> TextEncoding.encodeUtf8Builder (untag (Pta.typeSignature @a)))+    ]++-- | Parse a postgresql-types value from a postgresql-simple field.+--+-- This function uses the textual decoder from 'Pta.IsPrimitive' to parse+-- values received from PostgreSQL in text format.+--+-- It validates the field's type by comparing:+-- * The field's OID against the type's expected base OID or array OID+-- * When OID is not statically known, falls back to comparing type names+-- * Automatically handles array types by checking against arrayOid+--+-- > instance FromField Int4 where+-- >   fromField = fromFieldVia+fromFieldVia :: forall a. (Typeable a, Pta.IsPrimitive a) => FieldParser a+fromFieldVia field mdata = do+  -- Type validation: check OID or name+  let expectedBaseOid = untag (Pta.baseOid @a)+      expectedArrayOid = untag (Pta.arrayOid @a)+      expectedTypeName = untag (Pta.typeName @a)+      fieldOid = typeOid field++  case (expectedBaseOid, expectedArrayOid) of+    -- For types with known OIDs, validate against OID without calling typename+    (Just expectedBaseOid, Just expectedArrayOid) -> do+      let typeMatches =+            fieldOid == Oid (fromIntegral expectedBaseOid)+              || fieldOid == Oid (fromIntegral expectedArrayOid)++      unless typeMatches do+        returnError Incompatible field $+          mconcat+            [ "Type mismatch: expected ",+              Text.unpack expectedTypeName,+              " (OID ",+              show expectedBaseOid,+              ", array OID " <> show expectedArrayOid,+              ") but got field with OID ",+              show fieldOid+            ]++    -- Only call typename if we need it for validation (when OID is not available)+    _ -> do+      fieldTypeName <- typename field+      let expectedName = TextEncoding.encodeUtf8 expectedTypeName+      unless (fieldTypeName == expectedName) do+        returnError Incompatible field $+          mconcat+            [ "Type mismatch: expected ",+              Text.unpack expectedTypeName,+              " (OID unknown) but got field with type name ",+              show (TextEncoding.decodeUtf8With TextEncoding.lenientDecode fieldTypeName)+            ]++  -- Data validation and parsing+  case mdata of+    Nothing -> returnError UnexpectedNull field ""+    Just bytes -> case TextEncoding.decodeUtf8' bytes of+      Left err ->+        returnError ConversionFailed field $+          "UTF-8 decoding failed: " <> show err+      Right text ->+        case Attoparsec.parseOnly (Pta.textualDecoder @a <* Attoparsec.endOfInput) text of+          Left err ->+            returnError ConversionFailed field $+              "Parsing failed: " <> err+          Right value -> pure value
− src/library/Database/PostgreSQL/Simple/PostgresqlTypes/ViaIsScalar.hs
@@ -1,125 +0,0 @@-{-# OPTIONS_GHC -Wno-orphans #-}---- |--- This module provides a bridge between PostgreSQL's standard types and the postgresql-simple library,--- offering automatic ToField and FromField instance generation for types that implement the 'IsScalar' constraint.------ = Usage------ Import this module in addition to @Database.PostgreSQL.Simple@ to get encoding/decoding support--- for postgresql-types in postgresql-simple queries:------ > import Database.PostgreSQL.Simple--- > import Database.PostgreSQL.Simple.PostgresqlTypes--- > import PostgresqlTypes.Types (Int4, Text)--- >--- > -- Now you can use postgresql-types directly in queries--- > example :: Connection -> Int4 -> IO [Only Text]--- > example conn myInt = query conn "SELECT name FROM users WHERE id = ?" (Only myInt)------ = How it works------ * 'toFieldVia' creates a 'ToField' compatible 'Action' using the 'textualEncoder' from 'Pta.IsScalar'--- * 'fromFieldVia' creates a 'FromField' compatible parser using the 'textualDecoder' from 'Pta.IsScalar'------ The module uses textual format for encoding/decoding since that's what postgresql-simple primarily uses.-module Database.PostgreSQL.Simple.PostgresqlTypes.ViaIsScalar (ViaIsScalar (..)) where--import qualified Data.Attoparsec.Text as Attoparsec-import qualified Data.Text as Text-import qualified Data.Text.Encoding as TextEncoding-import qualified Data.Text.Encoding.Error as TextEncoding-import Database.PostgreSQL.Simple-import Database.PostgreSQL.Simple.FromField-import Database.PostgreSQL.Simple.PostgresqlTypes.Prelude-import Database.PostgreSQL.Simple.ToField-import qualified PostgresqlTypes.Algebra as Pta-import qualified TextBuilder--newtype ViaIsScalar a = ViaIsScalar a--instance (Pta.IsScalar a) => ToField (ViaIsScalar a) where-  toField (ViaIsScalar value) = toFieldVia value--instance (Typeable a, Pta.IsScalar a) => FromField (ViaIsScalar a) where-  fromField field mdata = ViaIsScalar <$> fromFieldVia field mdata---- | Convert a postgresql-types value to a postgresql-simple 'Action'.------ This function uses the textual encoder from 'IsScalar' to produce--- an escaped text value suitable for use in SQL queries.------ > instance ToField Int4 where--- >   toField = toFieldVia-toFieldVia :: forall a. (Pta.IsScalar a) => a -> Action-toFieldVia value =-  Many-    [ Escape (TextEncoding.encodeUtf8 (TextBuilder.toText (Pta.textualEncoder value))),-      Plain ("::" <> TextEncoding.encodeUtf8Builder (untag (Pta.typeSignature @a)))-    ]---- | Parse a postgresql-types value from a postgresql-simple field.------ This function uses the textual decoder from 'Pta.IsScalar' to parse--- values received from PostgreSQL in text format.------ It validates the field's type by comparing:--- * The field's OID against the type's expected base OID or array OID--- * When OID is not statically known, falls back to comparing type names--- * Automatically handles array types by checking against arrayOid------ > instance FromField Int4 where--- >   fromField = fromFieldVia-fromFieldVia :: forall a. (Typeable a, Pta.IsScalar a) => FieldParser a-fromFieldVia field mdata = do-  -- Type validation: check OID or name-  let expectedBaseOid = untag (Pta.baseOid @a)-      expectedArrayOid = untag (Pta.arrayOid @a)-      expectedTypeName = untag (Pta.typeName @a)-      fieldOid = typeOid field--  case (expectedBaseOid, expectedArrayOid) of-    -- For types with known OIDs, validate against OID without calling typename-    (Just expectedBaseOid, Just expectedArrayOid) -> do-      let typeMatches =-            fieldOid == Oid (fromIntegral expectedBaseOid)-              || fieldOid == Oid (fromIntegral expectedArrayOid)--      unless typeMatches do-        returnError Incompatible field $-          mconcat-            [ "Type mismatch: expected ",-              Text.unpack expectedTypeName,-              " (OID ",-              show expectedBaseOid,-              ", array OID " <> show expectedArrayOid,-              ") but got field with OID ",-              show fieldOid-            ]--    -- Only call typename if we need it for validation (when OID is not available)-    _ -> do-      fieldTypeName <- typename field-      let expectedName = TextEncoding.encodeUtf8 expectedTypeName-      unless (fieldTypeName == expectedName) do-        returnError Incompatible field $-          mconcat-            [ "Type mismatch: expected ",-              Text.unpack expectedTypeName,-              " (OID unknown) but got field with type name ",-              show (TextEncoding.decodeUtf8With TextEncoding.lenientDecode fieldTypeName)-            ]--  -- Data validation and parsing-  case mdata of-    Nothing -> returnError UnexpectedNull field ""-    Just bytes -> case TextEncoding.decodeUtf8' bytes of-      Left err ->-        returnError ConversionFailed field $-          "UTF-8 decoding failed: " <> show err-      Right text ->-        case Attoparsec.parseOnly (Pta.textualDecoder @a <* Attoparsec.endOfInput) text of-          Left err ->-            returnError ConversionFailed field $-              "Parsing failed: " <> err-          Right value -> pure value