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 +6/−6
- src/integration-tests/IntegrationTests/Scopes.hs +1/−0
- src/integration-tests/IntegrationTests/Scripts.hs +1/−1
- src/integration-tests/Main.hs +2/−0
- src/library/Database/PostgreSQL/Simple/PostgresqlTypes.hs +92/−81
- src/library/Database/PostgreSQL/Simple/PostgresqlTypes/ViaIsPrimitive.hs +125/−0
- src/library/Database/PostgreSQL/Simple/PostgresqlTypes/ViaIsScalar.hs +0/−125
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