Expand use of QualifiedName to types, composites, enums

This commit is contained in:
Shane O'Brien 2023-07-15 13:36:14 +01:00
parent cdf0c761d3
commit 08792f26f8
No known key found for this signature in database
GPG Key ID: 35A00ED1B695C1A1
12 changed files with 108 additions and 21 deletions

View File

@ -0,0 +1,40 @@
<!--
A new scriv changelog fragment.
Uncomment the section that is right (remove the HTML comment wrapper).
-->
<!--
### Removed
- A bullet item for the Removed category.
-->
<!--
### Added
- A bullet item for the Added category.
-->
### Changed
- Also use `QualifiedName` for `TypeInformation`.`typeName`, `enumTypeName` and `compositeTypeName`. This also means that `TypeInformation` now has an extra field, `arrayDepth`.
<!--
### Deprecated
- A bullet item for the Deprecated category.
-->
<!--
### Fixed
- A bullet item for the Fixed category.
-->
<!--
### Security
- A bullet item for the Security category.
-->

View File

@ -29,7 +29,7 @@ import Rel8.Expr.Opaleye
) )
import Rel8.Schema.HTable (hfoldMap) import Rel8.Schema.HTable (hfoldMap)
import Rel8.Schema.Null ( Sql ) import Rel8.Schema.Null ( Sql )
import Rel8.Schema.QualifiedName (QualifiedName, ppQualifiedName) import Rel8.Schema.QualifiedName (QualifiedName, showQualifiedName)
import Rel8.Table (Table, toColumns) import Rel8.Table (Table, toColumns)
import Rel8.Type ( DBType ) import Rel8.Type ( DBType )
@ -61,7 +61,7 @@ primFunction :: Arguments arguments
=> QualifiedName -> arguments -> Opaleye.PrimExpr => QualifiedName -> arguments -> Opaleye.PrimExpr
primFunction qualified = Opaleye.FunExpr name . arguments primFunction qualified = Opaleye.FunExpr name . arguments
where where
name = show (ppQualifiedName qualified) name = showQualifiedName qualified
-- | Construct an expression by applying an infix binary operator to two -- | Construct an expression by applying an infix binary operator to two

View File

@ -25,7 +25,7 @@ import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
import {-# SOURCE #-} Rel8.Expr ( Expr( Expr ) ) import {-# SOURCE #-} Rel8.Expr ( Expr( Expr ) )
import Rel8.Schema.Null ( Unnullify, Sql ) import Rel8.Schema.Null ( Unnullify, Sql )
import Rel8.Type ( DBType, typeInformation ) import Rel8.Type ( DBType, typeInformation )
import Rel8.Type.Information ( TypeInformation(..) ) import Rel8.Type.Information (TypeInformation(..), showTypeName)
-- profunctors -- profunctors
import Data.Profunctor ( Profunctor, dimap ) import Data.Profunctor ( Profunctor, dimap )
@ -47,8 +47,8 @@ scastExpr = sunsafeCastExpr
sunsafeCastExpr :: () sunsafeCastExpr :: ()
=> TypeInformation (Unnullify b) -> Expr a -> Expr b => TypeInformation (Unnullify b) -> Expr a -> Expr b
sunsafeCastExpr TypeInformation {typeName} = sunsafeCastExpr info =
fromPrimExpr . Opaleye.CastExpr typeName . toPrimExpr fromPrimExpr . Opaleye.CastExpr (showTypeName info) . toPrimExpr
-- | Unsafely construct an expression from literal SQL. -- | Unsafely construct an expression from literal SQL.

View File

@ -13,7 +13,7 @@ import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
-- rel8 -- rel8
import Rel8.Expr ( Expr ) import Rel8.Expr ( Expr )
import Rel8.Expr.Opaleye (fromPrimExpr) import Rel8.Expr.Opaleye (fromPrimExpr)
import Rel8.Schema.QualifiedName (QualifiedName, ppQualifiedName) import Rel8.Schema.QualifiedName (QualifiedName, showQualifiedName)
-- | See https://www.postgresql.org/docs/current/functions-sequence.html -- | See https://www.postgresql.org/docs/current/functions-sequence.html
@ -21,5 +21,5 @@ nextval :: QualifiedName -> Expr Int64
nextval name = nextval name =
fromPrimExpr $ fromPrimExpr $
Opaleye.FunExpr "nextval" Opaleye.FunExpr "nextval"
[ Opaleye.ConstExpr (Opaleye.StringLit (show (ppQualifiedName name))) [ Opaleye.ConstExpr (Opaleye.StringLit (showQualifiedName name))
] ]

View File

@ -7,6 +7,7 @@
module Rel8.Schema.QualifiedName module Rel8.Schema.QualifiedName
( QualifiedName (..) ( QualifiedName (..)
, ppQualifiedName , ppQualifiedName
, showQualifiedName
) )
where where
@ -47,3 +48,7 @@ ppQualifiedName QualifiedName {..} = Opaleye.ppTable Opaleye.SqlTable
{ sqlTableSchemaName = schema { sqlTableSchemaName = schema
, sqlTableName = name , sqlTableName = name
} }
showQualifiedName :: QualifiedName -> String
showQualifiedName = show . ppQualifiedName

View File

@ -55,7 +55,7 @@ import Rel8.Schema.QualifiedName (QualifiedName (QualifiedName))
import Rel8.Schema.Spec ( Spec(..) ) import Rel8.Schema.Spec ( Spec(..) )
import Rel8.Schema.Table ( TableSchema(..), ppTable ) import Rel8.Schema.Table ( TableSchema(..), ppTable )
import Rel8.Table ( Table, fromColumns, toColumns ) import Rel8.Table ( Table, fromColumns, toColumns )
import Rel8.Type.Information ( typeName ) import Rel8.Type.Information (showTypeName)
-- semigroupoids -- semigroupoids
import Data.Functor.Apply ( WrappedApplicative(..) ) import Data.Functor.Apply ( WrappedApplicative(..) )
@ -135,7 +135,7 @@ valuesspec :: Table Expr a => Opaleye.Valuesspec a a
valuesspec = dimap toColumns fromColumns $ valuesspec = dimap toColumns fromColumns $
htraversePWithField (traverseFieldP . Opaleye.valuesspecFieldType . typeName) htraversePWithField (traverseFieldP . Opaleye.valuesspecFieldType . typeName)
where where
typeName = Rel8.Type.Information.typeName . info . hfield hspecs typeName = showTypeName . info . hfield hspecs
view :: Selects names exprs => names -> exprs view :: Selects names exprs => names -> exprs

View File

@ -2,6 +2,7 @@
{-# language FlexibleInstances #-} {-# language FlexibleInstances #-}
{-# language MonoLocalBinds #-} {-# language MonoLocalBinds #-}
{-# language MultiWayIf #-} {-# language MultiWayIf #-}
{-# language OverloadedStrings #-}
{-# language StandaloneKindSignatures #-} {-# language StandaloneKindSignatures #-}
{-# language UndecidableInstances #-} {-# language UndecidableInstances #-}
@ -87,6 +88,7 @@ instance DBType Bool where
{ encode = Opaleye.ConstExpr . Opaleye.BoolLit { encode = Opaleye.ConstExpr . Opaleye.BoolLit
, decode = Hasql.bool , decode = Hasql.bool
, typeName = "bool" , typeName = "bool"
, arrayDepth = 0
} }
@ -96,6 +98,7 @@ instance DBType Char where
{ encode = Opaleye.ConstExpr . Opaleye.StringLit . pure { encode = Opaleye.ConstExpr . Opaleye.StringLit . pure
, decode = Hasql.char , decode = Hasql.char
, typeName = "char" , typeName = "char"
, arrayDepth = 0
} }
@ -105,6 +108,7 @@ instance DBType Int16 where
{ encode = Opaleye.ConstExpr . Opaleye.IntegerLit . toInteger { encode = Opaleye.ConstExpr . Opaleye.IntegerLit . toInteger
, decode = Hasql.int2 , decode = Hasql.int2
, typeName = "int2" , typeName = "int2"
, arrayDepth = 0
} }
@ -114,6 +118,7 @@ instance DBType Int32 where
{ encode = Opaleye.ConstExpr . Opaleye.IntegerLit . toInteger { encode = Opaleye.ConstExpr . Opaleye.IntegerLit . toInteger
, decode = Hasql.int4 , decode = Hasql.int4
, typeName = "int4" , typeName = "int4"
, arrayDepth = 0
} }
@ -123,6 +128,7 @@ instance DBType Int64 where
{ encode = Opaleye.ConstExpr . Opaleye.IntegerLit . toInteger { encode = Opaleye.ConstExpr . Opaleye.IntegerLit . toInteger
, decode = Hasql.int8 , decode = Hasql.int8
, typeName = "int8" , typeName = "int8"
, arrayDepth = 0
} }
@ -136,6 +142,7 @@ instance DBType Float where
| otherwise -> Opaleye.NumericLit $ realToFrac x | otherwise -> Opaleye.NumericLit $ realToFrac x
, decode = Hasql.float4 , decode = Hasql.float4
, typeName = "float4" , typeName = "float4"
, arrayDepth = 0
} }
@ -149,6 +156,7 @@ instance DBType Double where
| otherwise -> Opaleye.NumericLit $ realToFrac x | otherwise -> Opaleye.NumericLit $ realToFrac x
, decode = Hasql.float8 , decode = Hasql.float8
, typeName = "float8" , typeName = "float8"
, arrayDepth = 0
} }
@ -158,6 +166,7 @@ instance DBType Scientific where
{ encode = Opaleye.ConstExpr . Opaleye.NumericLit { encode = Opaleye.ConstExpr . Opaleye.NumericLit
, decode = Hasql.numeric , decode = Hasql.numeric
, typeName = "numeric" , typeName = "numeric"
, arrayDepth = 0
} }
@ -169,6 +178,7 @@ instance DBType UTCTime where
formatTime defaultTimeLocale "'%FT%T%QZ'" formatTime defaultTimeLocale "'%FT%T%QZ'"
, decode = Hasql.timestamptz , decode = Hasql.timestamptz
, typeName = "timestamptz" , typeName = "timestamptz"
, arrayDepth = 0
} }
@ -180,6 +190,7 @@ instance DBType Day where
formatTime defaultTimeLocale "'%F'" formatTime defaultTimeLocale "'%F'"
, decode = Hasql.date , decode = Hasql.date
, typeName = "date" , typeName = "date"
, arrayDepth = 0
} }
@ -191,6 +202,7 @@ instance DBType LocalTime where
formatTime defaultTimeLocale "'%FT%T%Q'" formatTime defaultTimeLocale "'%FT%T%Q'"
, decode = Hasql.timestamp , decode = Hasql.timestamp
, typeName = "timestamp" , typeName = "timestamp"
, arrayDepth = 0
} }
@ -202,6 +214,7 @@ instance DBType TimeOfDay where
formatTime defaultTimeLocale "'%T%Q'" formatTime defaultTimeLocale "'%T%Q'"
, decode = Hasql.time , decode = Hasql.time
, typeName = "time" , typeName = "time"
, arrayDepth = 0
} }
@ -213,6 +226,7 @@ instance DBType CalendarDiffTime where
formatTime defaultTimeLocale "'%bmon %0Es'" formatTime defaultTimeLocale "'%bmon %0Es'"
, decode = CalendarDiffTime 0 . realToFrac <$> Hasql.interval , decode = CalendarDiffTime 0 . realToFrac <$> Hasql.interval
, typeName = "interval" , typeName = "interval"
, arrayDepth = 0
} }
@ -222,6 +236,7 @@ instance DBType Text where
{ encode = Opaleye.ConstExpr . Opaleye.StringLit . Text.unpack { encode = Opaleye.ConstExpr . Opaleye.StringLit . Text.unpack
, decode = Hasql.text , decode = Hasql.text
, typeName = "text" , typeName = "text"
, arrayDepth = 0
} }
@ -251,6 +266,7 @@ instance DBType ByteString where
{ encode = Opaleye.ConstExpr . Opaleye.ByteStringLit { encode = Opaleye.ConstExpr . Opaleye.ByteStringLit
, decode = Hasql.bytea , decode = Hasql.bytea
, typeName = "bytea" , typeName = "bytea"
, arrayDepth = 0
} }
@ -267,6 +283,7 @@ instance DBType UUID where
{ encode = Opaleye.ConstExpr . Opaleye.StringLit . UUID.toString { encode = Opaleye.ConstExpr . Opaleye.StringLit . UUID.toString
, decode = Hasql.uuid , decode = Hasql.uuid
, typeName = "uuid" , typeName = "uuid"
, arrayDepth = 0
} }
@ -279,6 +296,7 @@ instance DBType Value where
Lazy.unpack . Lazy.decodeUtf8 . Aeson.encode Lazy.unpack . Lazy.decodeUtf8 . Aeson.encode
, decode = Hasql.jsonb , decode = Hasql.jsonb
, typeName = "jsonb" , typeName = "jsonb"
, arrayDepth = 0
} }
-- | Corresponds to @inet@ -- | Corresponds to @inet@
@ -288,6 +306,7 @@ instance DBType (NetAddr IP) where
Opaleye.ConstExpr . Opaleye.StringLit . printNetAddr Opaleye.ConstExpr . Opaleye.StringLit . printNetAddr
, decode = Hasql.inet , decode = Hasql.inet
, typeName = "inet" , typeName = "inet"
, arrayDepth = 0
} }

View File

@ -25,13 +25,18 @@ import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
-- rel8 -- rel8
import Rel8.Schema.Null ( Unnullify, Nullity( Null, NotNull ) ) import Rel8.Schema.Null ( Unnullify, Nullity( Null, NotNull ) )
import Rel8.Type.Information ( TypeInformation(..), parseTypeInformation ) import Rel8.Schema.QualifiedName (QualifiedName, showQualifiedName)
import Rel8.Type.Information
( TypeInformation(..)
, parseTypeInformation
, showTypeName
)
array :: Foldable f array :: Foldable f
=> TypeInformation a -> f Opaleye.PrimExpr -> Opaleye.PrimExpr => TypeInformation a -> f Opaleye.PrimExpr -> Opaleye.PrimExpr
array info = array info =
Opaleye.CastExpr (arrayType info <> "[]") . Opaleye.CastExpr (showQualifiedName (arrayType info) <> "[]") .
Opaleye.ArrayExpr . map (encodeArrayElement info) . toList Opaleye.ArrayExpr . map (encodeArrayElement info) . toList
{-# INLINABLE array #-} {-# INLINABLE array #-}
@ -54,7 +59,8 @@ listTypeInformation nullity info@TypeInformation {encode, decode} =
NotNull -> NotNull ->
Opaleye.ArrayExpr . Opaleye.ArrayExpr .
fmap (encodeArrayElement info . encode) fmap (encodeArrayElement info . encode)
, typeName = arrayType info <> "[]" , typeName = arrayType info
, arrayDepth = 1
} }
where where
null = Opaleye.ConstExpr Opaleye.NullLit null = Opaleye.ConstExpr Opaleye.NullLit
@ -72,12 +78,10 @@ nonEmptyTypeInformation nullity =
isArray :: TypeInformation a -> Bool isArray :: TypeInformation a -> Bool
isArray = \case isArray = (> 0) . arrayDepth
(reverse . typeName -> ']' : '[' : _) -> True
_ -> False
arrayType :: TypeInformation a -> String arrayType :: TypeInformation a -> QualifiedName
arrayType info arrayType info
| isArray info = "record" | isArray info = "record"
| otherwise = typeName info | otherwise = typeName info
@ -107,7 +111,7 @@ extractArrayElement info
minus a b = Opaleye.BinExpr (Opaleye.:-) a b minus a b = Opaleye.BinExpr (Opaleye.:-) a b
len = Opaleye.FunExpr "length" . pure len = Opaleye.FunExpr "length" . pure
substr s a b = Opaleye.FunExpr "substr" [s, a, b] substr s a b = Opaleye.FunExpr "substr" [s, a, b]
cast = Opaleye.CastExpr (typeName info) cast = Opaleye.CastExpr (showTypeName info)
text = Opaleye.CastExpr "text" input text = Opaleye.CastExpr "text" input
unrow = unrow =
Opaleye.CaseExpr Opaleye.CaseExpr

View File

@ -34,6 +34,7 @@ import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
import Rel8.Expr ( Expr ) import Rel8.Expr ( Expr )
import Rel8.Expr.Opaleye ( castExpr, fromPrimExpr, toPrimExpr ) import Rel8.Expr.Opaleye ( castExpr, fromPrimExpr, toPrimExpr )
import Rel8.Schema.HTable ( HTable, hfield, hspecs, htabulate, htabulateA ) import Rel8.Schema.HTable ( HTable, hfield, hspecs, htabulate, htabulateA )
import Rel8.Schema.QualifiedName (QualifiedName)
import Rel8.Schema.Name ( Name( Name ) ) import Rel8.Schema.Name ( Name( Name ) )
import Rel8.Schema.Null ( Nullity( Null, NotNull ) ) import Rel8.Schema.Null ( Nullity( Null, NotNull ) )
import Rel8.Schema.Result ( Result ) import Rel8.Schema.Result ( Result )
@ -71,6 +72,7 @@ instance DBComposite a => DBType (Composite a) where
{ decode = Hasql.composite (Composite . fromResult @_ @(HKD a Expr) <$> decoder) { decode = Hasql.composite (Composite . fromResult @_ @(HKD a Expr) <$> decoder)
, encode = encoder . litHTable . toResult @_ @(HKD a Expr) . unComposite , encode = encoder . litHTable . toResult @_ @(HKD a Expr) . unComposite
, typeName = compositeTypeName @a , typeName = compositeTypeName @a
, arrayDepth = 0
} }
@ -94,7 +96,7 @@ class (DBType a, HKDable a) => DBComposite a where
compositeFields :: HKD a Name compositeFields :: HKD a Name
-- | The name of the composite type that @a@ maps to. -- | The name of the composite type that @a@ maps to.
compositeTypeName :: String compositeTypeName :: QualifiedName
-- | Collapse a 'HKD' into a PostgreSQL composite type. -- | Collapse a 'HKD' into a PostgreSQL composite type.

View File

@ -38,6 +38,7 @@ import qualified Hasql.Decoders as Hasql
import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
-- rel8 -- rel8
import Rel8.Schema.QualifiedName (QualifiedName)
import Rel8.Type ( DBType, typeInformation ) import Rel8.Type ( DBType, typeInformation )
import Rel8.Type.Eq ( DBEq ) import Rel8.Type.Eq ( DBEq )
import Rel8.Type.Information ( TypeInformation(..) ) import Rel8.Type.Information ( TypeInformation(..) )
@ -76,6 +77,7 @@ instance DBEnum a => DBType (Enum a) where
enumValue @a . enumValue @a .
unEnum unEnum
, typeName = enumTypeName @a , typeName = enumTypeName @a
, arrayDepth = 0
} }
@ -101,7 +103,7 @@ class (DBType a, Enumable a) => DBEnum a where
enumValue = gshow @(Rep a) . from enumValue = gshow @(Rep a) . from
-- | The name of the PostgreSQL @enum@ type that @a@ maps to. -- | The name of the PostgreSQL @enum@ type that @a@ maps to.
enumTypeName :: String enumTypeName :: QualifiedName
-- | Types that are sum types, where each constructor is unary (that is, has no -- | Types that are sum types, where each constructor is unary (that is, has no

View File

@ -6,12 +6,14 @@ module Rel8.Type.Information
( TypeInformation(..) ( TypeInformation(..)
, mapTypeInformation , mapTypeInformation
, parseTypeInformation , parseTypeInformation
, showTypeName
) )
where where
-- base -- base
import Data.Bifunctor ( first ) import Data.Bifunctor ( first )
import Data.Kind ( Type ) import Data.Kind ( Type )
import Data.Semigroup (mtimesDefault)
import Prelude import Prelude
-- hasql -- hasql
@ -20,6 +22,9 @@ import qualified Hasql.Decoders as Hasql
-- opaleye -- opaleye
import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
-- rel8
import Rel8.Schema.QualifiedName (QualifiedName, showQualifiedName)
-- text -- text
import qualified Data.Text as Text import qualified Data.Text as Text
@ -33,8 +38,10 @@ data TypeInformation a = TypeInformation
-- ^ How to encode a single Haskell value as a SQL expression. -- ^ How to encode a single Haskell value as a SQL expression.
, decode :: Hasql.Value a , decode :: Hasql.Value a
-- ^ How to deserialize a single result back to Haskell. -- ^ How to deserialize a single result back to Haskell.
, typeName :: String , typeName :: QualifiedName
-- ^ The name of the SQL type. -- ^ The name of the SQL type.
, arrayDepth :: Word
-- ^ How many levels of @[]@ (or 0 if type is not array).
} }
@ -59,9 +66,15 @@ mapTypeInformation = parseTypeInformation . fmap pure
parseTypeInformation :: () parseTypeInformation :: ()
=> (a -> Either String b) -> (b -> a) => (a -> Either String b) -> (b -> a)
-> TypeInformation a -> TypeInformation b -> TypeInformation a -> TypeInformation b
parseTypeInformation to from TypeInformation {encode, decode, typeName} = parseTypeInformation to from TypeInformation {encode, decode, typeName, arrayDepth} =
TypeInformation TypeInformation
{ encode = encode . from { encode = encode . from
, decode = Hasql.refine (first Text.pack . to) decode , decode = Hasql.refine (first Text.pack . to) decode
, typeName , typeName
, arrayDepth
} }
showTypeName :: TypeInformation a -> String
showTypeName TypeInformation {typeName, arrayDepth} =
showQualifiedName typeName <> mtimesDefault arrayDepth "[]"

View File

@ -1,3 +1,4 @@
{-# language OverloadedStrings #-}
{-# language StandaloneKindSignatures #-} {-# language StandaloneKindSignatures #-}
module Rel8.Type.JSONBEncoded ( JSONBEncoded(..) ) where module Rel8.Type.JSONBEncoded ( JSONBEncoded(..) ) where
@ -32,4 +33,5 @@ instance (FromJSON a, ToJSON a) => DBType (JSONBEncoded a) where
{ encode = encode typeInformation . toJSON . fromJSONBEncoded { encode = encode typeInformation . toJSON . fromJSONBEncoded
, decode = Hasql.refine (first pack . fmap JSONBEncoded . parseEither parseJSON) Hasql.jsonb , decode = Hasql.refine (first pack . fmap JSONBEncoded . parseEither parseJSON) Hasql.jsonb
, typeName = "jsonb" , typeName = "jsonb"
, arrayDepth = 0
} }