summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKeiHibino <>2017-07-17 10:20:00 (GMT)
committerhdiff <hdiff@hdiff.luite.com>2017-07-17 10:20:00 (GMT)
commita73ceea346999e8567dd3a7e809cbae24fd06c63 (patch)
tree5655e230e7eb2fc3ebed01fab273460915022873
parentd0c6659074d5556f4abd26c3e92a15701460ac82 (diff)
version 0.5.0.20.5.0.2
-rw-r--r--ChangeLog.md46
-rw-r--r--persistable-record.cabal13
-rw-r--r--src/Database/Record.hs1
-rw-r--r--src/Database/Record/FromSql.hs115
-rw-r--r--src/Database/Record/InternalTH.hs45
-rw-r--r--src/Database/Record/Persistable.hs142
-rw-r--r--src/Database/Record/TH.hs242
-rw-r--r--src/Database/Record/ToSql.hs171
-rw-r--r--src/Database/Record/TupleInstances.hs13
-rw-r--r--test/Model.hs74
-rw-r--r--test/nestedEq.hs99
11 files changed, 596 insertions, 365 deletions
diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
index 0000000..c54cf58
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,46 @@
+<!-- -*- Markdown -*- -->
+
+## 0.5.0.2
+
+- add tested-with 8.2.1.
+
+## 0.5.0.1
+
+- Use Haskell implementation test instead of flag test in .cabal
+
+## 0.5.0.0
+
+- Add generic instances of FromSql, ToSql and PersistableWidth.
+
+## 0.4.1.1
+
+- Tested with GHC 8.0.2
+- Add a small test set.
+
+## 0.4.1.0
+
+- Export columnName of NameConfig.
+
+## 0.4.0.3
+
+- Drop an unreferenced definition.
+
+## 0.4.0.2
+
+- Add tested-with.
+
+## 0.4.0.1
+
+- Apply th-data-compat.
+
+## 0.4.0.0
+
+- Divide PersistableValue interface to FromSql and ToSql.
+
+## 0.3.0.0
+
+- Add symbol name configurations of templates.
+
+## 0.2.0.0
+
+- TH quotation of derive class names.
diff --git a/persistable-record.cabal b/persistable-record.cabal
index 661c84f..cf9c21f 100644
--- a/persistable-record.cabal
+++ b/persistable-record.cabal
@@ -1,5 +1,5 @@
name: persistable-record
-version: 0.4.1.2
+version: 0.5.0.2
synopsis: Binding between SQL database values and haskell records.
description: This package contiains types to represent table constraints and
interfaces to bind between SQL database values and Haskell records.
@@ -18,17 +18,22 @@ tested-with: GHC == 8.2.1
, GHC == 7.8.1, GHC == 7.8.2, GHC == 7.8.3, GHC == 7.8.4
, GHC == 7.6.1, GHC == 7.6.2, GHC == 7.6.3
, GHC == 7.4.1, GHC == 7.4.2
+extra-source-files: ChangeLog.md
library
exposed-modules:
Database.Record.FromSql
Database.Record.ToSql
Database.Record.Persistable
+ Database.Record.TupleInstances
Database.Record.Instances
Database.Record.KeyConstraint
Database.Record
Database.Record.TH
+ other-modules:
+ Database.Record.InternalTH
+
build-depends: base <5
, template-haskell
, th-data-compat
@@ -37,6 +42,9 @@ library
, transformers
, dlist
, names-th
+ if impl(ghc == 7.4.*)
+ build-depends: ghc-prim == 0.2.*
+
hs-source-dirs: src
ghc-options: -Wall
default-language: Haskell2010
@@ -45,9 +53,12 @@ test-suite nested
build-depends: base <5
, quickcheck-simple
, persistable-record
+ if impl(ghc == 7.4.*)
+ build-depends: ghc-prim == 0.2.*
type: exitcode-stdio-1.0
main-is: nestedEq.hs
+ other-modules: Model
hs-source-dirs: test
ghc-options: -Wall
default-language: Haskell2010
diff --git a/src/Database/Record.hs b/src/Database/Record.hs
index 99ca60c..62f8889 100644
--- a/src/Database/Record.hs
+++ b/src/Database/Record.hs
@@ -48,6 +48,7 @@ import Database.Record.ToSql
(ToSqlM, RecordToSql, ToSql(..), valueRecordToSql,
runFromRecord, wrapToSql, putRecord, putEmpty, fromRecord,
updateValuesByUnique, updateValuesByPrimary)
+import Database.Record.TupleInstances ()
{- $concepts
On most drivers for SQL database,
diff --git a/src/Database/Record/FromSql.hs b/src/Database/Record/FromSql.hs
index 4882ed6..65aaddc 100644
--- a/src/Database/Record/FromSql.hs
+++ b/src/Database/Record/FromSql.hs
@@ -1,10 +1,12 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE DefaultSignatures #-}
-- |
-- Module : Database.Record.FromSql
--- Copyright : 2013 Kei Hibino
+-- Copyright : 2013-2017 Kei Hibino
-- License : BSD3
--
-- Maintainer : ex8k.hibino@gmail.com
@@ -12,35 +14,39 @@
-- Portability : unknown
--
-- This module defines interfaces
--- from list of SQL type into Haskell type.
+-- from list of database value type into Haskell type.
+
module Database.Record.FromSql (
- -- * Conversion from list of SQL type into record type
- -- $recordFromSql
+ -- * Conversion from list of database value type into record type
RecordFromSql, runTakeRecord, runToRecord,
createRecordFromSql,
(<&>),
maybeRecord,
- -- * Inference rules of 'RecordFromSql' conversion
+ -- * Derivation rules of 'RecordFromSql' conversion
FromSql (recordFromSql),
takeRecord, toRecord,
valueRecordFromSql,
) where
+import GHC.Generics (Generic, Rep, U1 (..), K1 (..), M1 (..), (:*:)(..), to)
+import Control.Applicative ((<$>), Applicative (pure, (<*>)))
+import Control.Monad (liftM, ap)
+
import Database.Record.Persistable (PersistableType)
import qualified Database.Record.Persistable as Persistable
import Database.Record.KeyConstraint
(HasColumnConstraint(columnConstraint), ColumnConstraint, NotNull, index)
-import Control.Monad (liftM, ap)
-import Control.Applicative ((<$>), Applicative(pure, (<*>)))
+{- |
+'RecordFromSql' 'q' 'a' is data-type wrapping function
+to convert from list of database value type (to receive from database) ['q'] into Haskell type 'a'
-{- $recordFromSql
-Structure of 'RecordFromSql' 'q' 'a' is similar to parser.
-While running 'RecordFromSql' behavior is the same as parser
-which parse list of SQL type ['q'] stream.
+This structure is similar to parser.
+While running 'RecordFromSql' behavior is the same as non-fail-able parser
+which parse list of database value type ['q'] stream.
So, 'RecordFromSql' 'q' is 'Monad' and 'Applicative' instance like parser monad.
When, you have data constructor and objects like below.
@@ -65,25 +71,23 @@ You can get composed 'RecordFromSql' like below.
myRecord = MyRecord \<$\> foo \<*\> bar \<*\> baz
@
-}
-
--- | Proof object type to convert from sql value type 'q' list into Haskell type 'a'.
newtype RecordFromSql q a = RecordFromSql ([q] -> (a, [q]))
--- | Run 'RecordFromSql' proof object.
--- Convert from list of SQL type ['q'] into Haskell type 'a' and rest of list ['q'].
-runTakeRecord :: RecordFromSql q a -- ^ Proof object which has capability to convert
- -> [q] -- ^ list of SQL type
+-- | Run 'RecordFromSql' parser function object.
+-- Convert from list of database value type ['q'] into Haskell type 'a' and rest of list ['q'].
+runTakeRecord :: RecordFromSql q a -- ^ parser function object which has capability to convert
+ -> [q] -- ^ list of database value type
-> (a, [q]) -- ^ Haskell type and rest of list
runTakeRecord (RecordFromSql f) = f
--- | Axiom of 'RecordFromSql' for SQL type 'q' and Haskell type 'a'
+-- | Axiom of 'RecordFromSql' for database value type 'q' and Haskell type 'a'
createRecordFromSql :: ([q] -> (a, [q])) -- ^ Convert function body
- -> RecordFromSql q a -- ^ Result proof object
+ -> RecordFromSql q a -- ^ Result parser function object
createRecordFromSql = RecordFromSql
--- | Run 'RecordFromSql' proof object. Convert from list of SQL type ['q'] into Haskell type 'a'.
-runToRecord :: RecordFromSql q a -- ^ Proof object which has capability to convert
- -> [q] -- ^ list of SQL type
+-- | Run 'RecordFromSql' parser function object. Convert from list of database value type ['q'] into Haskell type 'a'.
+runToRecord :: RecordFromSql q a -- ^ parser function object which has capability to convert
+ -> [q] -- ^ list of database value type
-> a -- ^ Haskell type
runToRecord r = fst . runTakeRecord r
@@ -104,14 +108,14 @@ instance Applicative (RecordFromSql q) where
pure = return
(<*>) = ap
--- | Derivation rule of 'RecordFromSql' proof object for Haskell tuple (,) type.
+-- | Derivation rule of 'RecordFromSql' parser function object for Haskell tuple (,) type.
(<&>) :: RecordFromSql q a -> RecordFromSql q b -> RecordFromSql q (a, b)
a <&> b = (,) <$> a <*> b
infixl 4 <&>
--- | Derivation rule of 'RecordFromSql' proof object for Haskell 'Maybe' type.
+-- | Derivation rule of 'RecordFromSql' parser function object for Haskell 'Maybe' type.
maybeRecord :: PersistableType q
=> RecordFromSql q a
-> ColumnConstraint NotNull a
@@ -122,38 +126,67 @@ maybeRecord rec pkey = createRecordFromSql mayToRec where
| otherwise = (Nothing, vals') where
(a, vals') = runTakeRecord rec vals
+{- |
+'FromSql' 'q' 'a' is implicit rule to derive 'RecordFromSql' 'q' 'a' record parser function against type 'a'.
+
+Generic programming (<https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#generic-programming>)
+with default signature is available for 'FromSql' class,
+so you can make instance like below:
+
+@
+ \{\-\# LANGUAGE DeriveGeneric \#\-\}
+ import GHC.Generics (Generic)
+ import Database.HDBC (SqlValue)
+ --
+ data Foo = Foo { ... } deriving Generic
+ instance FromSql SqlValue Foo
+@
--- | Inference rule interface for 'RecordFromSql' proof object.
+-}
class FromSql q a where
- -- | 'RecordFromSql' proof object.
+ -- | 'RecordFromSql' 'q' 'a' record parser function.
recordFromSql :: RecordFromSql q a
--- | Inference rule of 'RecordFromSql' proof object which can convert
--- from list of SQL type ['q'] into Haskell tuple ('a', 'b') type.
-instance (FromSql q a, FromSql q b) => FromSql q (a, b) where
- recordFromSql = recordFromSql <&> recordFromSql
+ default recordFromSql :: (Generic a, GFromSql q (Rep a)) => RecordFromSql q a
+ recordFromSql = to <$> gFromSql
+
+
+class GFromSql q f where
+ gFromSql :: RecordFromSql q (f a)
+
+instance GFromSql q U1 where
+ gFromSql = createRecordFromSql $ (,) U1
+
+instance (GFromSql q a, GFromSql q b) => GFromSql q (a :*: b) where
+ gFromSql = (:*:) <$> gFromSql <*> gFromSql
+
+instance GFromSql q a => GFromSql q (M1 i c a) where
+ gFromSql = M1 <$> gFromSql
+
+instance FromSql q a => GFromSql q (K1 i a) where
+ gFromSql = K1 <$> recordFromSql
+
--- | Inference rule of 'RecordFromSql' proof object which can convert
--- from list of SQL type ['q'] into Haskell 'Maybe' type.
+-- | Implicit derivation rule of 'RecordFromSql' parser function object which can convert
+-- from list of database value type ['q'] into Haskell 'Maybe' type.
instance (HasColumnConstraint NotNull a, FromSql q a, PersistableType q)
=> FromSql q (Maybe a) where
recordFromSql = maybeRecord recordFromSql columnConstraint
--- | Inference rule of 'RecordFromSql' proof object which can convert
--- from /empty/ list of SQL type ['q'] into Haskell unit () type.
-instance FromSql q () where
- recordFromSql = RecordFromSql (\qs -> ((), qs))
+-- | Implicit derivation rule of 'RecordFromSql' parser function object which can convert
+-- from /empty/ list of database value type ['q'] into Haskell unit () type.
+instance FromSql q () -- default generic instance
--- | Run inferred 'RecordFromSql' proof object.
--- Convert from list of SQL type ['q'] into haskell type 'a' and rest of list ['q'].
+-- | Run implicit 'RecordFromSql' parser function object.
+-- Convert from list of database value type ['q'] into haskell type 'a' and rest of list ['q'].
takeRecord :: FromSql q a => [q] -> (a, [q])
takeRecord = runTakeRecord recordFromSql
--- | Run inferred 'RecordFromSql' proof object.
--- Convert from list of SQL type ['q'] into haskell type 'a'.
+-- | Run implicit 'RecordFromSql' parser function object.
+-- Convert from list of database value type ['q'] into haskell type 'a'.
toRecord :: FromSql q a => [q] -> a
toRecord = runToRecord recordFromSql
--- | Derivation rule of 'RecordFromSql' proof object for value convert function.
+-- | Derivation rule of 'RecordFromSql' parser function object for value convert function.
valueRecordFromSql :: (q -> a) -> RecordFromSql q a
valueRecordFromSql d = createRecordFromSql $ \qs -> (d $ head qs, tail qs)
diff --git a/src/Database/Record/InternalTH.hs b/src/Database/Record/InternalTH.hs
new file mode 100644
index 0000000..9310810
--- /dev/null
+++ b/src/Database/Record/InternalTH.hs
@@ -0,0 +1,45 @@
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE ConstraintKinds #-}
+
+module Database.Record.InternalTH (
+ defineTupleInstances
+ ) where
+
+import Control.Applicative ((<$>))
+import Data.List (foldl')
+import Language.Haskell.TH
+ (Q, mkName, Name,
+ conT, varT, tupleT, appT, classP,
+ Dec, instanceD, )
+
+import Database.Record.Persistable (PersistableWidth)
+import Database.Record.FromSql (FromSql)
+import Database.Record.ToSql (ToSql)
+
+
+persistableWidth :: Int -> Q [Dec]
+persistableWidth n = do
+ let vs = [ varT . mkName $ "a" ++ show i | i <- [1 .. n] ]
+ (:[]) <$> instanceD
+ -- in template-haskell 2.8 or older, Pred is not Type
+ (mapM (classP ''PersistableWidth . (:[])) vs)
+ [t| PersistableWidth $(foldl' appT (tupleT n) vs) |]
+ []
+
+tupleInstance2 :: Int -> Name -> Q [Dec]
+tupleInstance2 n clazz = do
+ let vs = [ varT . mkName $ "a" ++ show i | i <- [1 .. n] ]
+ q = varT $ mkName "q"
+ (:[]) <$> instanceD
+ -- in template-haskell 2.8 or older, Pred is not Type
+ (mapM (\v -> classP clazz [q, v]) vs)
+ [t| $(conT clazz) $q $(foldl' appT (tupleT n) vs) |]
+ []
+
+-- | Template to define tuple instances of persistable-record classes.
+defineTupleInstances :: Int -> Q [Dec]
+defineTupleInstances n =
+ concat <$> sequence
+ [ persistableWidth n
+ , tupleInstance2 n ''FromSql
+ , tupleInstance2 n ''ToSql ]
diff --git a/src/Database/Record/Persistable.hs b/src/Database/Record/Persistable.hs
index 40aaf46..dce83c2 100644
--- a/src/Database/Record/Persistable.hs
+++ b/src/Database/Record/Persistable.hs
@@ -1,100 +1,176 @@
+{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE DefaultSignatures #-}
-- |
-- Module : Database.Record.Persistable
--- Copyright : 2013 Kei Hibino
+-- Copyright : 2013-2017 Kei Hibino
-- License : BSD3
--
-- Maintainer : ex8k.hibino@gmail.com
-- Stability : experimental
-- Portability : unknown
--
--- This module defines interfaces
--- between Haskell type and list of SQL type.
+-- This module defines proposition interfaces
+-- for database value type and record type width.
module Database.Record.Persistable (
- -- * Specify SQL type
+ -- * Specify database value type
PersistableSqlType, runPersistableNullValue, unsafePersistableSqlTypeFromNull,
-- * Specify record width
PersistableRecordWidth, runPersistableRecordWidth,
unsafePersistableRecordWidth, unsafeValueWidth, (<&>), maybeWidth,
- -- * Inference rules for proof objects
-
+ -- * Implicit derivation rules, database value type and record type width
PersistableType(..), sqlNullValue,
- PersistableWidth (..), derivedWidth
+ PersistableWidth (..), derivedWidth,
+
+ -- * low-level interfaces
+ GFieldWidthList,
+ ProductConst, getProductConst,
+ genericFieldOffsets,
) where
+import GHC.Generics (Generic, Rep, U1 (..), K1 (..), M1 (..), (:*:)(..), to)
+import Control.Applicative ((<$>), pure, (<*>), Const (..))
+import Data.Monoid (Monoid, Sum (..))
+import Data.Array (Array, listArray, bounds, (!))
+import Data.DList (DList)
+import qualified Data.DList as DList
+
--- | Proof object to specify type 'q' is SQL type
+-- | Proposition to specify type 'q' is database value type, contains null value
newtype PersistableSqlType q = PersistableSqlType q
--- | Null value of SQL type 'q'.
+-- | Null value of database value type 'q'.
runPersistableNullValue :: PersistableSqlType q -> q
runPersistableNullValue (PersistableSqlType q) = q
--- | Unsafely generate 'PersistableSqlType' proof object from specified SQL null value which type is 'q'.
-unsafePersistableSqlTypeFromNull :: q -- ^ SQL null value of SQL type 'q'
+-- | Unsafely specify 'PersistableSqlType' axiom from specified database null value which type is 'q'.
+unsafePersistableSqlTypeFromNull :: q -- ^ null value of database value type 'q'
-> PersistableSqlType q -- ^ Result proof object
unsafePersistableSqlTypeFromNull = PersistableSqlType
--- | Proof object to specify width of Haskell type 'a'
--- when converting to SQL type list.
-newtype PersistableRecordWidth a =
- PersistableRecordWidth Int
+-- | Restricted in product isomorphism record type b
+newtype ProductConst a b =
+ ProductConst { unPC :: Const a b }
+
+-- | extract constant value of 'ProductConst'.
+getProductConst :: ProductConst a b -> a
+getProductConst = getConst . unPC
+{-# INLINE getProductConst #-}
+
+-- | Proposition to specify width of Haskell type 'a'.
+-- The width is length of database value list which is converted from Haskell type 'a'.
+type PersistableRecordWidth a = ProductConst (Sum Int) a
+
+-- unsafely map PersistableRecordWidth
+pmap :: Monoid e => (a -> b) -> ProductConst e a -> ProductConst e b
+f `pmap` prw = ProductConst $ f <$> unPC prw
+
+-- unsafely ap PersistableRecordWidth
+pap :: Monoid e => ProductConst e (a -> b) -> ProductConst e a -> ProductConst e b
+wf `pap` prw = ProductConst $ unPC wf <*> unPC prw
+
-- | Get width 'Int' value of record type 'a'.
runPersistableRecordWidth :: PersistableRecordWidth a -> Int
-runPersistableRecordWidth (PersistableRecordWidth w) = w
+runPersistableRecordWidth = getSum . getConst . unPC
+{-# INLINE runPersistableRecordWidth #-}
+
+instance Show a => Show (ProductConst a b) where
+ show = ("PC " ++) . show . getConst . unPC
--- | Unsafely generate 'PersistableRecordWidth' proof object from specified width of Haskell type 'a'.
+-- | Unsafely specify 'PersistableRecordWidth' axiom from specified width of Haskell type 'a'.
unsafePersistableRecordWidth :: Int -- ^ Specify width of Haskell type 'a'
-> PersistableRecordWidth a -- ^ Result proof object
-unsafePersistableRecordWidth = PersistableRecordWidth
+unsafePersistableRecordWidth = ProductConst . Const . Sum
+{-# INLINE unsafePersistableRecordWidth #-}
--- | Unsafely generate 'PersistableRecordWidth' proof object for Haskell type 'a' which is single column type.
+-- | Unsafely specify 'PersistableRecordWidth' axiom for Haskell type 'a' which is single column type.
unsafeValueWidth :: PersistableRecordWidth a
unsafeValueWidth = unsafePersistableRecordWidth 1
+{-# INLINE unsafeValueWidth #-}
-- | Derivation rule of 'PersistableRecordWidth' for tuple (,) type.
(<&>) :: PersistableRecordWidth a -> PersistableRecordWidth b -> PersistableRecordWidth (a, b)
-a <&> b = PersistableRecordWidth $ runPersistableRecordWidth a + runPersistableRecordWidth b
+a <&> b = (,) `pmap` a `pap` b
-- | Derivation rule of 'PersistableRecordWidth' from from Haskell type 'a' into for Haskell type 'Maybe' 'a'.
maybeWidth :: PersistableRecordWidth a -> PersistableRecordWidth (Maybe a)
-maybeWidth = PersistableRecordWidth . runPersistableRecordWidth
+maybeWidth = pmap Just
--- | Axiom of 'PersistableRecordWidth' for Haskell unit () type.
-voidWidth :: PersistableRecordWidth ()
-voidWidth = unsafePersistableRecordWidth 0
-
--- | Interface of inference rule for 'PersistableSqlType' proof object
+-- | Interface of derivation rule for 'PersistableSqlType'.
class Eq q => PersistableType q where
persistableType :: PersistableSqlType q
--- | Inferred Null value of SQL type.
+-- | Implicitly derived null value of database value type.
sqlNullValue :: PersistableType q => q
sqlNullValue = runPersistableNullValue persistableType
--- | Interface of inference rule for 'PersistableRecordWidth' proof object
+{- |
+'PersistableWidth' 'a' is implicit rule to derive 'PersistableRecordWidth' 'a' width proposition for type 'a'.
+
+Generic programming (<https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#generic-programming>)
+with default signature is available for 'PersistableWidth' class,
+so you can make instance like below:
+
+@
+ \{\-\# LANGUAGE DeriveGeneric \#\-\}
+ import GHC.Generics (Generic)
+ --
+ data Foo = Foo { ... } deriving Generic
+ instance PersistableWidth Foo
+@
+
+-}
class PersistableWidth a where
persistableWidth :: PersistableRecordWidth a
--- | Inference rule of 'PersistableRecordWidth' proof object for tuple ('a', 'b') type.
-instance (PersistableWidth a, PersistableWidth b) => PersistableWidth (a, b) where
- persistableWidth = persistableWidth <&> persistableWidth
+ default persistableWidth :: (Generic a, GFieldWidthList (Rep a)) => PersistableRecordWidth a
+ persistableWidth = pmapConst (Sum . lastA) genericFieldOffsets
+ where
+ lastA a = a ! (snd $ bounds a)
+
+
+pmapConst :: (a -> b) -> ProductConst a c -> ProductConst b c
+pmapConst f = ProductConst . Const . f . getConst . unPC
+
+-- | Generic width value list of record fields.
+class GFieldWidthList f where
+ gFieldWidthList :: ProductConst (DList Int) (f a)
+
+instance GFieldWidthList U1 where
+ gFieldWidthList = ProductConst $ pure U1
+
+instance (GFieldWidthList a, GFieldWidthList b) => GFieldWidthList (a :*: b) where
+ gFieldWidthList = (:*:) `pmap` gFieldWidthList `pap` gFieldWidthList
+
+instance GFieldWidthList a => GFieldWidthList (M1 i c a) where
+ gFieldWidthList = M1 `pmap` gFieldWidthList
+
+instance PersistableWidth a => GFieldWidthList (K1 i a) where
+ gFieldWidthList = K1 `pmap` pmapConst (pure . getSum) persistableWidth
+
+offsets :: [Int] -> Array Int Int
+offsets ws = listArray (0, length ws) $ scanl (+) 0 ws
+
+-- | Generic offset array of record fields.
+genericFieldOffsets :: (Generic a, GFieldWidthList (Rep a)) => ProductConst (Array Int Int) a
+genericFieldOffsets = pmapConst (offsets . DList.toList) $ to `pmap` gFieldWidthList
+
-- | Inference rule of 'PersistableRecordWidth' proof object for 'Maybe' type.
instance PersistableWidth a => PersistableWidth (Maybe a) where
persistableWidth = maybeWidth persistableWidth
-- | Inference rule of 'PersistableRecordWidth' for Haskell unit () type. Derive from axiom.
-instance PersistableWidth () where
- persistableWidth = voidWidth
+instance PersistableWidth () -- default generic instance
-- | Pass type parameter and inferred width value.
derivedWidth :: PersistableWidth a => (PersistableRecordWidth a, Int)
diff --git a/src/Database/Record/TH.hs b/src/Database/Record/TH.hs
index c0559b4..b8865a3 100644
--- a/src/Database/Record/TH.hs
+++ b/src/Database/Record/TH.hs
@@ -4,7 +4,7 @@
-- |
-- Module : Database.Record.TH
--- Copyright : 2013 Kei Hibino
+-- Copyright : 2013-2017 Kei Hibino
-- License : BSD3
--
-- Maintainer : ex8k.hibino@gmail.com
@@ -18,9 +18,6 @@ module Database.Record.TH (
defineRecord,
defineRecordWithConfig,
- -- * Deriving class symbols
- derivingEq, derivingShow, derivingRead, derivingData, derivingTypeable,
-
-- * Table constraint specified by key
defineHasColumnConstraintInstance,
defineHasPrimaryConstraintInstanceDerived,
@@ -31,70 +28,60 @@ module Database.Record.TH (
defineRecordType,
defineRecordTypeWithConfig,
- -- * Function declarations depending on SQL type
- makeRecordPersistableWithSqlType,
- makeRecordPersistableWithSqlTypeWithConfig,
- makeRecordPersistableWithSqlTypeDefault,
-
-- * Function declarations against defined record types
- makeRecordPersistableWithSqlTypeFromDefined,
- makeRecordPersistableWithSqlTypeDefaultFromDefined,
defineColumnOffsets,
recordWidthTemplate,
- defineRecordParser,
- defineRecordPrinter,
-
- definePersistableInstance,
-
-- * Reify
reifyRecordType,
- -- * Templates about record type name
+ -- * Templates about record name
NameConfig, defaultNameConfig,
recordTypeName, columnName,
- recordType,
+ recordTemplate,
columnOffsetsVarNameDefault,
- persistableFunctionNamesDefault,
-
-- * Not nullable single column type
- deriveNotNullType
- ) where
+ deriveNotNullType,
+ -- * Template for tuple types
+ defineTupleInstances,
+ ) where
-import Control.Applicative (pure, (<*>))
-import Data.List (foldl')
-import Data.Array (Array, listArray, (!))
-import Data.Data (Data, Typeable)
+import GHC.Generics (Generic)
+import Data.Array (Array)
import Language.Haskell.TH.Name.CamelCase
(ConName(conName), VarName(varName),
conCamelcaseName, varCamelcaseName, varNameWithPrefix,
- toTypeCon, toDataCon, toVarExp)
-import Language.Haskell.TH.Lib.Extra (integralE, simpleValD)
+ toTypeCon, toDataCon, )
+import Language.Haskell.TH.Lib.Extra (integralE, simpleValD, reportWarning)
import Language.Haskell.TH.Compat.Data (dataD', unDataD)
import Language.Haskell.TH
- (Q, newName, nameBase, reify, Info(TyConI), Name,
+ (Q, nameBase, reify, Info(TyConI), Name,
TypeQ, conT, Con (NormalC, RecC),
- Dec, sigD, valD,
- ExpQ, Exp(ConE), conE, varE, lamE, listE, sigE,
- varP, conP, normalB, recC,
+ Dec,
+ ExpQ, conE, listE, sigE,
+ recC,
cxt, varStrictType, strictType, isStrict)
+import Control.Arrow ((&&&))
+
import Database.Record
(HasColumnConstraint(columnConstraint), Primary, NotNull,
HasKeyConstraint(keyConstraint), derivedCompositePrimary,
PersistableRecordWidth, PersistableWidth(persistableWidth),
- FromSql(recordFromSql), RecordFromSql,
- ToSql(recordToSql), RecordToSql, wrapToSql, putRecord, putEmpty)
+ FromSql, ToSql, )
import Database.Record.KeyConstraint
(unsafeSpecifyColumnConstraint, unsafeSpecifyNotNullValue, unsafeSpecifyKeyConstraint)
-import Database.Record.Persistable (unsafePersistableRecordWidth, runPersistableRecordWidth)
+import Database.Record.Persistable
+ (runPersistableRecordWidth,
+ ProductConst, getProductConst, genericFieldOffsets)
import qualified Database.Record.Persistable as Persistable
+import Database.Record.InternalTH (defineTupleInstances)
-- | 'NameConfig' type to customize names of expanded record templates.
@@ -119,12 +106,12 @@ defaultNameConfig =
, columnName = const varCamelcaseName
}
--- | Record type constructor template from SQL table name 'String'.
-recordType :: NameConfig -- ^ name rule config
- -> String -- ^ Schema name string in SQL
- -> String -- ^ Table name string in SQL
- -> TypeQ -- ^ Record type constructor
-recordType config scm = toTypeCon . recordTypeName config scm
+-- | Record constructor templates from SQL table name 'String'.
+recordTemplate :: NameConfig -- ^ name rule config
+ -> String -- ^ Schema name string in SQL
+ -> String -- ^ Table name string in SQL
+ -> (TypeQ, ExpQ) -- ^ Record type and data constructor
+recordTemplate config scm = (toTypeCon &&& toDataCon) . recordTypeName config scm
-- | Variable expression of record column offset array.
columnOffsetsVarNameDefault :: Name -- ^ Table type name
@@ -170,31 +157,6 @@ defineHasNotNullKeyInstance :: TypeQ -- ^ Type constructor of record
defineHasNotNullKeyInstance =
defineHasColumnConstraintInstance [t| NotNull |]
-{-# DEPRECATED derivingEq "Use TH quasi-quotation like ''Eq instead of this." #-}
--- | Name to specify deriving 'Eq'
-derivingEq :: Name
-derivingEq = ''Eq
-
-{-# DEPRECATED derivingShow "Use TH quasi-quotation like ''Show instead of this." #-}
--- | Name to specify deriving 'Show'
-derivingShow :: Name
-derivingShow = ''Show
-
-{-# DEPRECATED derivingRead "Use TH quasi-quotation like ''Read instead of this." #-}
--- | Name to specify deriving 'Read'
-derivingRead :: Name
-derivingRead = ''Read
-
-{-# DEPRECATED derivingData "Use TH quasi-quotation like ''Data instead of this." #-}
--- | Name to specify deriving 'Data'
-derivingData :: Name
-derivingData = ''Data
-
-{-# DEPRECATED derivingTypeable "Use TH quasi-quotation like ''Typeable instead of this." #-}
--- | Name to specify deriving 'Typeable'
-derivingTypeable :: Name
-derivingTypeable = ''Typeable
-
-- | Record type width expression template.
recordWidthTemplate :: TypeQ -- ^ Record type constructor.
-> ExpQ -- ^ Expression to get record width.
@@ -211,10 +173,8 @@ defineColumnOffsets typeName' tys = do
let ofsVar = columnOffsetsVarNameDefault $ conName typeName'
widthIxE = integralE $ length tys
ar <- simpleValD (varName ofsVar) [t| Array Int Int |]
- [| listArray (0 :: Int, $widthIxE) $
- scanl (+) (0 :: Int) $(listE $ map recordWidthTemplate tys) |]
- pw <- [d| instance PersistableWidth $(toTypeCon typeName') where
- persistableWidth = unsafePersistableRecordWidth $ $(toVarExp ofsVar) ! $widthIxE
+ [| getProductConst (genericFieldOffsets :: ProductConst (Array Int Int) $(toTypeCon typeName')) |]
+ pw <- [d| instance PersistableWidth $(toTypeCon typeName')
|]
return $ ar ++ pw
@@ -226,9 +186,14 @@ defineRecordType :: ConName -- ^ Name of the data type of table recor
defineRecordType typeName' columns derives = do
let typeName = conName typeName'
fld (n, tq) = varStrictType (varName n) (strictType isStrict tq)
- rec <- dataD' (cxt []) typeName [] [recC typeName (map fld columns)] derives
+ derives1 <- if (''Generic `notElem` derives)
+ then do reportWarning "HRR needs Generic instance, please add ''Generic manually."
+ return $ ''Generic : derives
+ {- DROP this hack in future version ups. -}
+ else return derives
+ rec' <- dataD' (cxt []) typeName [] [recC typeName (map fld columns)] derives1
offs <- defineColumnOffsets typeName' [ty | (_, ty) <- columns]
- return $ rec : offs
+ return $ rec' : offs
-- | Record type declaration template with configured names.
defineRecordTypeWithConfig :: NameConfig -> String -> String -> [(String, TypeQ)] -> [Name] -> Q [Dec]
@@ -238,79 +203,6 @@ defineRecordTypeWithConfig config schema table columns =
[ (columnName config schema n, t) | (n, t) <- columns ]
--- | Record parser template.
-defineRecordParser :: TypeQ -- ^ SQL value type.
- -> VarName -- ^ Name of record parser.
- -> (TypeQ, ExpQ) -- ^ Record type constructor and data constructor.
- -> Int -- ^ Count of record columns.
- -> Q [Dec] -- ^ Declaration of record construct function from SQL values.
-defineRecordParser sqlValType name' (tyCon, dataCon) width = do
- let name = varName name'
- sig <- sigD name [t| RecordFromSql $sqlValType $tyCon |]
- var <- valD (varP name)
- (normalB
- (foldl' (\a x -> [| $a <*> $x |]) [| pure $dataCon |]
- $ replicate width [| recordFromSql |])
- )
- []
- return [sig, var]
-
-dataConInfo :: Exp -> Q Name
-dataConInfo = d where
- d (ConE n) = return n
- d e = fail $ "Not record data constructor: " ++ show e
-
--- | Record printer template.
-defineRecordPrinter :: TypeQ -- ^ SQL value type.
- -> VarName -- ^ Name of record printer.
- -> (TypeQ, ExpQ) -- ^ Record type constructor and data constructor.
- -> Int -- ^ Count of record columns.
- -> Q [Dec] -- ^ Declaration of record construct function from SQL values.
-defineRecordPrinter sqlValType name' (tyCon, dataCon) width = do
- let name = varName name'
- sig <- sigD name [t| RecordToSql $sqlValType $tyCon |]
- names <- mapM (newName . ('f':) . show) [1 .. width]
- dcn <- dataCon >>= dataConInfo
- var <- valD (varP name)
- (normalB [| wrapToSql
- $(lamE
- [ conP dcn [ varP n | n <- names ] ]
- (foldr (\a x -> [| $a >> $x |]) [| putEmpty () |]
- [ [| putRecord $(varE n) |] | n <- names ])) |])
- []
- return [sig, var]
-
--- | Record parser and printer instance templates for converting
--- between list of SQL type and Haskell record type.
-definePersistableInstance :: TypeQ -- ^ SQL value type.
- -> TypeQ -- ^ Record type constructor.
- -> VarName -- ^ Record parser name.
- -> VarName -- ^ Record printer name.
- -> Int -- ^ Count of record columns.
- -> Q [Dec] -- ^ Instance declarations for 'Persistable'.
-definePersistableInstance sqlType typeCon parserName printerName _width = do
- [d| instance FromSql $sqlType $typeCon where
- recordFromSql = $(toVarExp parserName)
-
- instance ToSql $sqlType $typeCon where
- recordToSql = $(toVarExp printerName)
- |]
-
--- | All templates depending on SQL value type.
-makeRecordPersistableWithSqlType :: TypeQ -- ^ SQL value type.
- -> (VarName, VarName) -- ^ Constructor function name and decompose function name.
- -> (TypeQ, ExpQ) -- ^ Record type constructor and data constructor.
- -> Int -- ^ Count of record columns.
- -> Q [Dec] -- ^ Result declarations.
-makeRecordPersistableWithSqlType
- sqlValueType
- (cF, dF) conPair@(tyCon, _)
- width = do
- fromSQL <- defineRecordParser sqlValueType cF conPair width
- toSQL <- defineRecordPrinter sqlValueType dF conPair width
- instSQL <- definePersistableInstance sqlValueType tyCon cF dF width
- return $ fromSQL ++ toSQL ++ instSQL
-
-- | Default name of record construction function from SQL table name.
fromSqlNameDefault :: String -> VarName
fromSqlNameDefault = (`varNameWithPrefix` "fromSqlOf")
@@ -319,29 +211,6 @@ fromSqlNameDefault = (`varNameWithPrefix` "fromSqlOf")
toSqlNameDefault :: String -> VarName
toSqlNameDefault = (`varNameWithPrefix` "toSqlOf")
--- | All templates depending on SQL value type with configured names.
-makeRecordPersistableWithSqlTypeWithConfig :: TypeQ -- ^ SQL value type
- -> NameConfig -- ^ name rule config
- -> String -- ^ Schema name of database
- -> String -- ^ Table name of database
- -> Int -- ^ Count of record columns
- -> Q [Dec] -- ^ Result declarations
-makeRecordPersistableWithSqlTypeWithConfig sqlValueType config schema table width =
- makeRecordPersistableWithSqlType
- sqlValueType
- (persistableFunctionNamesDefault . conName . conCamelcaseName $ table)
- (recordType config schema table, toDataCon . recordTypeName config schema $ table)
- width
-
--- | All templates depending on SQL value type with default names.
-makeRecordPersistableWithSqlTypeDefault :: TypeQ -- ^ SQL value type
- -> String -- ^ Schema name
- -> String -- ^ Table name
- -> Int -- ^ Count of record columns
- -> Q [Dec] -- ^ Result declarations
-makeRecordPersistableWithSqlTypeDefault sqlValueType =
- makeRecordPersistableWithSqlTypeWithConfig sqlValueType defaultNameConfig
-
recordInfo' :: Info -> Maybe ((TypeQ, ExpQ), (Maybe [Name], [TypeQ]))
recordInfo' = d where
d (TyConI tcon) = do
@@ -362,41 +231,29 @@ reifyRecordType recTypeName = do
return
(recordInfo' tyConInfo)
--- | Generate persistable function symbol names using default rule.
-persistableFunctionNamesDefault :: Name -> (VarName, VarName)
-persistableFunctionNamesDefault recTypeName = (fromSqlNameDefault bn, toSqlNameDefault bn) where
- bn = nameBase recTypeName
-
--- | All templates depending on SQL value type. Defined record type information is used.
-makeRecordPersistableWithSqlTypeFromDefined :: TypeQ -- ^ SQL value type
- -> (VarName, VarName) -- ^ Constructor function name and decompose function name
- -> Name -- ^ Record type constructor name
- -> Q [Dec] -- ^ Result declarations
-makeRecordPersistableWithSqlTypeFromDefined sqlValueType fnames recTypeName = do
- (conPair, (_, cts)) <- reifyRecordType recTypeName
- makeRecordPersistableWithSqlType sqlValueType fnames conPair $ length cts
-
--- | All templates depending on SQL value type with default names. Defined record type information is used.
-makeRecordPersistableWithSqlTypeDefaultFromDefined :: TypeQ -- ^ SQL value type
- -> Name -- ^ Record type constructor name
- -> Q [Dec] -- ^ Result declarations
-makeRecordPersistableWithSqlTypeDefaultFromDefined sqlValueType recTypeName =
- makeRecordPersistableWithSqlTypeFromDefined sqlValueType (persistableFunctionNamesDefault recTypeName) recTypeName
+-- | Record parser and printer instance templates for converting
+-- between list of SQL type and Haskell record type.
+definePersistableInstance :: TypeQ -- ^ SQL value type.
+ -> TypeQ -- ^ Record type constructor.
+ -> Q [Dec] -- ^ Instance declarations.
+definePersistableInstance sqlType typeCon = do
+ [d| instance FromSql $sqlType $typeCon
+ instance ToSql $sqlType $typeCon
+ |]
-- | All templates for record type.
defineRecord :: TypeQ -- ^ SQL value type
- -> (VarName, VarName) -- ^ Constructor function name and decompose function name
-> ConName -- ^ Record type name
-> [(VarName, TypeQ)] -- ^ Column schema
-> [Name] -- ^ Record derivings
-> Q [Dec] -- ^ Result declarations
defineRecord
sqlValueType
- fnames tyC
+ tyC
columns drvs = do
typ <- defineRecordType tyC columns drvs
- withSql <- makeRecordPersistableWithSqlType sqlValueType fnames (toTypeCon tyC, toDataCon tyC) $ length columns
+ withSql <- definePersistableInstance sqlValueType $ toTypeCon tyC
return $ typ ++ withSql
-- | All templates for record type with configured names.
@@ -409,7 +266,8 @@ defineRecordWithConfig :: TypeQ -- ^ SQL value type
-> Q [Dec] -- ^ Result declarations
defineRecordWithConfig sqlValueType config schema table columns derives = do
typ <- defineRecordTypeWithConfig config schema table columns derives
- withSql <- makeRecordPersistableWithSqlTypeWithConfig sqlValueType config schema table $ length columns
+ withSql <- definePersistableInstance sqlValueType . fst $ recordTemplate config schema table
+
return $ typ ++ withSql
diff --git a/src/Database/Record/ToSql.hs b/src/Database/Record/ToSql.hs
index eee5850..85a3cc5 100644
--- a/src/Database/Record/ToSql.hs
+++ b/src/Database/Record/ToSql.hs
@@ -1,10 +1,12 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE DefaultSignatures #-}
-- |
-- Module : Database.Record.ToSql
--- Copyright : 2013 Kei Hibino
+-- Copyright : 2013-2017 Kei Hibino
-- License : BSD3
--
-- Maintainer : ex8k.hibino@gmail.com
@@ -12,29 +14,30 @@
-- Portability : unknown
--
-- This module defines interfaces
--- from Haskell type into list of SQL type.
+-- from Haskell type into list of database value type.
module Database.Record.ToSql (
- -- * Conversion from record type into list of SQL type
+ -- * Conversion from record type into list of database value type
ToSqlM, RecordToSql, runFromRecord,
createRecordToSql,
(<&>),
- -- * Inference rules of 'RecordToSql' conversion
+ -- * Derivation rules of 'RecordToSql' conversion
ToSql (recordToSql),
putRecord, putEmpty, fromRecord, wrapToSql,
valueRecordToSql,
-- * Make parameter list for updating with key
- updateValuesByUnique',
updateValuesByUnique,
updateValuesByPrimary,
+ updateValuesByUnique',
untypedUpdateValuesIndex,
unsafeUpdateValuesWithIndexes
) where
+import GHC.Generics (Generic, Rep, U1 (..), K1 (..), M1 (..), (:*:)(..), from)
import Data.Array (listArray, (!))
import Data.Set (toList, fromList, (\\))
import Control.Monad.Trans.Writer (Writer, execWriter, tell)
@@ -48,13 +51,20 @@ import Database.Record.KeyConstraint
(Primary, Unique, KeyConstraint, HasKeyConstraint(keyConstraint), unique, indexes)
--- | Context type to convert SQL type list.
+-- | Context type to convert into database value list.
type ToSqlM q a = Writer (DList q) a
runToSqlM :: ToSqlM q a -> [q]
runToSqlM = DList.toList . execWriter
--- | Proof object type to convert from Haskell type 'a' into list of SQL type ['q'].
+{- |
+'RecordToSql' 'q' 'a' is data-type wrapping function
+to convert from Haskell type 'a' into list of database value type (to send to database) ['q'].
+
+This structure is similar to printer.
+While running 'RecordToSql' behavior is the same as list printer.
+which appends list of database value type ['q'] stream.
+-}
newtype RecordToSql q a = RecordToSql (a -> ToSqlM q ())
runRecordToSql :: RecordToSql q a -> a -> ToSqlM q ()
@@ -64,24 +74,33 @@ runRecordToSql (RecordToSql f) = f
wrapToSql :: (a -> ToSqlM q ()) -> RecordToSql q a
wrapToSql = RecordToSql
--- | Run 'RecordToSql' proof object. Convert from Haskell type 'a' into list of SQL type ['q'].
-runFromRecord :: RecordToSql q a -- ^ Proof object which has capability to convert
+-- | Run 'RecordToSql' printer function object. Convert from Haskell type 'a' into list of database value type ['q'].
+runFromRecord :: RecordToSql q a -- ^ printer function object which has capability to convert
-> a -- ^ Haskell type
- -> [q] -- ^ list of SQL type
+ -> [q] -- ^ list of database value
runFromRecord r = runToSqlM . runRecordToSql r
--- | Axiom of 'RecordToSql' for SQL type 'q' and Haksell type 'a'.
+-- | Axiom of 'RecordToSql' for database value type 'q' and Haksell type 'a'.
createRecordToSql :: (a -> [q]) -- ^ Convert function body
- -> RecordToSql q a -- ^ Result proof object
+ -> RecordToSql q a -- ^ Result printer function object
createRecordToSql f = wrapToSql $ tell . DList.fromList . f
--- | Derivation rule of 'RecordToSql' proof object for Haskell tuple (,) type.
-(<&>) :: RecordToSql q a -> RecordToSql q b -> RecordToSql q (a, b)
-ra <&> rb = RecordToSql $ \(a, b) -> do
+-- unsafely map record
+mapToSql :: (a -> b) -> RecordToSql q b -> RecordToSql q a
+mapToSql f x = wrapToSql $ runRecordToSql x . f
+
+-- unsafely put product record
+productToSql :: (c -> (a -> b -> ToSqlM q ()) -> ToSqlM q ())
+ -> RecordToSql q a -> RecordToSql q b -> RecordToSql q c
+productToSql run ra rb = wrapToSql $ \c -> run c $ \a b -> do
runRecordToSql ra a
runRecordToSql rb b
--- | Derivation rule of 'RecordToSql' proof object for Haskell 'Maybe' type.
+-- | Derivation rule of 'RecordToSql' printer function object for Haskell tuple (,) type.
+(<&>) :: RecordToSql q a -> RecordToSql q b -> RecordToSql q (a, b)
+(<&>) = productToSql $ flip uncurry
+
+-- | Derivation rule of 'RecordToSql' printer function object for Haskell 'Maybe' type.
maybeRecord :: PersistableSqlType q -> PersistableRecordWidth a -> RecordToSql q a -> RecordToSql q (Maybe a)
maybeRecord qt w ra = wrapToSql d where
d (Just r) = runRecordToSql ra r
@@ -89,29 +108,85 @@ maybeRecord qt w ra = wrapToSql d where
infixl 4 <&>
-
--- | Inference rule interface for 'RecordToSql' proof object.
+{- |
+'ToSql' 'q' 'a' is implicit rule to derive 'RecordToSql' 'q' 'a' record printer function for type 'a'.
+
+Generic programming (<https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#generic-programming>)
+with default signature is available for 'ToSql' class,
+so you can make instance like below:
+
+@
+ \{\-\# LANGUAGE DeriveGeneric \#\-\}
+ import GHC.Generics (Generic)
+ import Database.HDBC (SqlValue)
+ --
+ data Foo = Foo { ... } deriving Generic
+ instance ToSql SqlValue Foo
+@
+
+To make instances of 'ToSql' manually,
+'ToSql' 'q' 'a' and 'RecordToSql' 'q 'a' are composable with monadic context.
+When, you have data constructor and objects like below.
+
+@
+ data MyRecord = MyRecord Foo Bar Baz
+@
+
+@
+ instance ToSql SqlValue Foo where
+ ...
+ instance ToSql SqlValue Bar where
+ ...
+ instance ToSql SqlValue Baz where
+ ...
+@
+
+You can get composed 'ToSql' implicit rule like below.
+
+@
+ instance ToSql SqlValue MyRecord where
+ recordToSql =
+ recordToSql = wrapToSql $ \\ (MyRecord x y z) -> do
+ putRecord x
+ putRecord y
+ putRecord z
+@
+
+-}
class ToSql q a where
- -- | Infer 'RecordToSql' proof object.
+ -- | Derived 'RecordToSql' printer function object.
recordToSql :: RecordToSql q a
--- | Inference rule of 'RecordToSql' proof object which can convert
--- from Haskell tuple ('a', 'b') type into list of SQL type ['q'].
-instance (ToSql q a, ToSql q b) => ToSql q (a, b) where
- recordToSql = recordToSql <&> recordToSql
+ default recordToSql :: (Generic a, GToSql q (Rep a)) => RecordToSql q a
+ recordToSql = from `mapToSql` gToSql
+
+class GToSql q f where
+ gToSql :: RecordToSql q (f a)
+
+instance GToSql q U1 where
+ gToSql = wrapToSql $ \U1 -> tell DList.empty
+
+instance (GToSql q a, GToSql q b) => GToSql q (a :*: b) where
+ gToSql = productToSql (\ (a:*:b) f -> f a b) gToSql gToSql
+
+instance GToSql q a => GToSql q (M1 i c a) where
+ gToSql = (\(M1 a) -> a) `mapToSql` gToSql
+
+instance ToSql q a => GToSql q (K1 i a) where
+ gToSql = (\(K1 a) -> a) `mapToSql` recordToSql
+
--- | Inference rule of 'RecordToSql' proof object which can convert
--- from Haskell 'Maybe' type into list of SQL type ['q'].
+-- | Implicit derivation rule of 'RecordToSql' printer function object which can convert
+-- from Haskell 'Maybe' type into list of database value type ['q'].
instance (PersistableType q, PersistableWidth a, ToSql q a) => ToSql q (Maybe a) where
recordToSql = maybeRecord persistableType persistableWidth recordToSql
--- | Inference rule of 'RecordToSql' proof object which can convert
--- from Haskell unit () type into /empty/ list of SQL type ['q'].
-instance ToSql q () where
- recordToSql = wrapToSql $ \() -> tell DList.empty
+-- | Implicit derivation rule of 'RecordToSql' printer function object which can convert
+-- from Haskell unit () type into /empty/ list of database value type ['q'].
+instance ToSql q () -- default generic instance
--- | Run inferred 'RecordToSql' proof object.
--- Context to convert haskell record type 'a' into SQL type 'q' list.
+-- | Run implicit 'RecordToSql' printer function object.
+-- Context to convert haskell record type 'a' into lib of database value type ['q'].
putRecord :: ToSql q a => a -> ToSqlM q ()
putRecord = runRecordToSql recordToSql
@@ -119,19 +194,21 @@ putRecord = runRecordToSql recordToSql
putEmpty :: () -> ToSqlM q ()
putEmpty = putRecord
--- | Run inferred 'RecordToSql' proof object.
--- Convert from haskell type 'a' into list of SQL type ['q'].
+-- | Run implicit 'RecordToSql' printer function object.
+-- Convert from haskell type 'a' into list of database value type ['q'].
fromRecord :: ToSql q a => a -> [q]
fromRecord = runToSqlM . putRecord
--- | Derivation rule of 'RecordToSql' proof object for value convert function.
+-- | Derivation rule of 'RecordToSql' printer function object for value convert function.
valueRecordToSql :: (a -> q) -> RecordToSql q a
valueRecordToSql = createRecordToSql . ((:[]) .)
-- | Make untyped indexes to update column from key indexes and record width.
-- Expected by update form like
--
--- /UPDATE <table> SET c0 = ?, c1 = ?, ..., cn = ? WHERE key0 = ? AND key1 = ? AND key2 = ? ... /
+-- @
+-- UPDATE /table/ SET /c0/ = ?, /c1/ = ?, /c2/ = ? ... WHERE /key0/ = ? AND /key1/ = ? AND key2 = ? ...
+-- @
untypedUpdateValuesIndex :: [Int] -- ^ Key indexes
-> Int -- ^ Record width
-> [Int] -- ^ Indexes to update other than key
@@ -140,11 +217,13 @@ untypedUpdateValuesIndex key width = otherThanKey where
otherThanKey = toList $ fromList [0 .. maxIx] \\ fromList key
-- | Unsafely specify key indexes to convert from Haskell type `ra`
--- into SQL value `q` list expected by update form like
+-- into database value `q` list expected by update form like
--
--- /UPDATE <table> SET c0 = ?, c1 = ?, ..., cn = ? WHERE key0 = ? AND key1 = ? AND key2 = ? ... /
+-- @
+-- UPDATE /table/ SET /c0/ = ?, /c1/ = ?, /c2/ = ? ... WHERE /key0/ = ? AND /key1/ = ? AND /key2/ = ? ...
+-- @
--
--- using 'RecordToSql' proof object.
+-- using 'RecordToSql' printer function object.
unsafeUpdateValuesWithIndexes :: RecordToSql q ra
-> [Int]
-> ra
@@ -156,25 +235,27 @@ unsafeUpdateValuesWithIndexes pr key a =
valsA = listArray (0, width - 1) vals
otherThanKey = untypedUpdateValuesIndex key width
--- | Convert from Haskell type `ra` into SQL value `q` list expected by update form like
+-- | Convert from Haskell type `ra` into database value `q` list expected by update form like
--
--- /UPDATE <table> SET c0 = ?, c1 = ?, ..., cn = ? WHERE key0 = ? AND key1 = ? AND key2 = ? ... /
+-- @
+-- UPDATE /table/ SET /c0/ = ?, /c1/ = ?, /c2/ = ? ... WHERE /key0/ = ? AND /key1/ = ? AND /key2/ = ? ...
+-- @
--
--- using 'RecordToSql' proof object.
+-- using 'RecordToSql' printer function object.
updateValuesByUnique' :: RecordToSql q ra
- -> KeyConstraint Unique ra -- ^ Unique key table constraint proof object.
+ -> KeyConstraint Unique ra -- ^ Unique key table constraint printer function object.
-> ra
-> [q]
updateValuesByUnique' pr uk = unsafeUpdateValuesWithIndexes pr (indexes uk)
--- | Convert like 'updateValuesByUnique'' using inferred 'RecordToSql' proof object.
+-- | Convert like 'updateValuesByUnique'' using implicit 'RecordToSql' printer function object.
updateValuesByUnique :: ToSql q ra
- => KeyConstraint Unique ra -- ^ Unique key table constraint proof object.
+ => KeyConstraint Unique ra -- ^ Unique key table constraint printer function object.
-> ra
-> [q]
updateValuesByUnique = updateValuesByUnique' recordToSql
--- | Convert like 'updateValuesByUnique'' using inferred 'RecordToSql' and 'ColumnConstraint' proof objects.
+-- | Convert like 'updateValuesByUnique'' using implicit 'RecordToSql' and 'ColumnConstraint'.
updateValuesByPrimary :: (HasKeyConstraint Primary ra, ToSql q ra)
=> ra -> [q]
updateValuesByPrimary = updateValuesByUnique (unique keyConstraint)
diff --git a/src/Database/Record/TupleInstances.hs b/src/Database/Record/TupleInstances.hs
new file mode 100644
index 0000000..381cbde
--- /dev/null
+++ b/src/Database/Record/TupleInstances.hs
@@ -0,0 +1,13 @@
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}
+
+module Database.Record.TupleInstances () where
+
+import Control.Applicative ((<$>))
+
+import Database.Record.InternalTH (defineTupleInstances)
+
+
+$(concat <$> mapM defineTupleInstances [2..7])
+-- Generic instances of tuple types are generated from 2 to 7 in GHC.Generics.
diff --git a/test/Model.hs b/test/Model.hs
new file mode 100644
index 0000000..553266d
--- /dev/null
+++ b/test/Model.hs
@@ -0,0 +1,74 @@
+{-# OPTIONS -fno-warn-orphans #-}
+{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}
+{-# LANGUAGE DeriveGeneric #-}
+module Model where
+
+import GHC.Generics (Generic)
+
+import Database.Record
+ (PersistableType (..), PersistableWidth (..),
+ FromSql (..), valueRecordFromSql,
+ ToSql (..), valueRecordToSql)
+import Database.Record.KeyConstraint (HasColumnConstraint (..), NotNull, unsafeSpecifyColumnConstraint)
+import Database.Record.Persistable (unsafePersistableSqlTypeFromNull, unsafeValueWidth, )
+
+
+instance PersistableType String where
+ persistableType = unsafePersistableSqlTypeFromNull "<null>"
+
+
+instance PersistableWidth String where
+ persistableWidth = unsafeValueWidth
+
+instance PersistableWidth Int where
+ persistableWidth = unsafeValueWidth
+
+instance FromSql String String where
+ recordFromSql = valueRecordFromSql id
+
+instance FromSql String Int where
+ recordFromSql = valueRecordFromSql read
+
+instance ToSql String String where
+ recordToSql = valueRecordToSql id
+
+instance ToSql String Int where
+ recordToSql = valueRecordToSql show
+
+
+data User =
+ User
+ { uid :: Int
+ , uname :: String
+ , note :: String
+ } deriving (Eq, Show, Generic)
+
+data Group =
+ Group
+ { gid :: Int
+ , gname :: String
+ } deriving (Eq, Show, Generic)
+
+data Membership =
+ Membership
+ { user :: User
+ , group :: Maybe Group
+ } deriving (Eq, Show, Generic)
+
+instance HasColumnConstraint NotNull User where
+ columnConstraint = unsafeSpecifyColumnConstraint 0
+
+instance HasColumnConstraint NotNull Group where
+ columnConstraint = unsafeSpecifyColumnConstraint 0
+
+instance PersistableWidth User
+instance PersistableWidth Group
+instance PersistableWidth Membership
+
+instance FromSql String User
+instance FromSql String Group
+instance FromSql String Membership
+
+instance ToSql String User
+instance ToSql String Group
+instance ToSql String Membership
diff --git a/test/nestedEq.hs b/test/nestedEq.hs
index bfd7fdc..54bb0f6 100644
--- a/test/nestedEq.hs
+++ b/test/nestedEq.hs
@@ -1,67 +1,60 @@
{-# OPTIONS -fno-warn-orphans #-}
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}
-import Control.Applicative ((<$>), (<*>))
import Test.QuickCheck.Simple (defaultMain, eqTest)
-import Database.Record
- (PersistableType (..),
- FromSql (..), valueRecordFromSql, toRecord,
- ToSql (..), valueRecordToSql)
-import Database.Record.Persistable (unsafePersistableSqlTypeFromNull)
+import Database.Record (toRecord, fromRecord, persistableWidth, PersistableRecordWidth)
+import Database.Record.Persistable (runPersistableRecordWidth)
+import Model (User (..), Group (..), Membership (..))
-instance PersistableType String where
- persistableType = unsafePersistableSqlTypeFromNull "<null>"
-
-
-instance FromSql String String where
- recordFromSql = valueRecordFromSql id
-
-instance FromSql String Int where
- recordFromSql = valueRecordFromSql read
-
-instance ToSql String String where
- recordToSql = valueRecordToSql id
-
-instance ToSql String Int where
- recordToSql = valueRecordToSql show
-
-
-data User =
- User
- { uid :: Int
- , uname :: String
- , note :: String
- } deriving (Eq, Show)
-
-data Group =
- Group
- { gid :: Int
- , gname :: String
- } deriving (Eq, Show)
-
-data Membership =
- Membership
- { user :: User
- , group :: Group
- } deriving (Eq, Show)
-
-instance FromSql String User where
- recordFromSql = User <$> recordFromSql <*> recordFromSql <*> recordFromSql
-
-instance FromSql String Group where
- recordFromSql = Group <$> recordFromSql <*> recordFromSql
-
-instance FromSql String Membership where
- recordFromSql = Membership <$> recordFromSql <*> recordFromSql
main :: IO ()
main =
defaultMain
[ eqTest
- "nestedEq"
+ "toRecord just"
+ (Membership { user = User { uid = 1, uname = "Kei Hibino", note = "HRR developer" }
+ , group = Just $ Group { gid = 1, gname = "Haskellers" }
+ } )
+ (toRecord ["1", "Kei Hibino", "HRR developer", "1", "Haskellers"])
+ , eqTest
+ "toRecord nothing"
(Membership { user = User { uid = 1, uname = "Kei Hibino", note = "HRR developer" }
- , group = Group { gid = 1, gname = "Haskellers" }
+ , group = Nothing
} )
- (toRecord ["1", "Kei Hibino", "HRR developer", "1", "Haskellers"]) ]
+ (toRecord ["1", "Kei Hibino", "HRR developer", "<null>", "<null>"])
+ , eqTest
+ "fromRecord just"
+ (fromRecord $ Membership { user = User { uid = 1, uname = "Kei Hibino", note = "HRR developer" }
+ , group = Just $ Group { gid = 1, gname = "Haskellers" }
+ } )
+ ["1", "Kei Hibino", "HRR developer", "1", "Haskellers"]
+ , eqTest
+ "fromRecord note"
+ (fromRecord $ Membership { user = User { uid = 1, uname = "Kei Hibino", note = "HRR developer" }
+ , group = Nothing
+ } )
+ ["1", "Kei Hibino", "HRR developer", "<null>", "<null>"]
+
+ , eqTest
+ "toRecord pair"
+ (User { uid = 1, uname = "Kei Hibino", note = "HRR developer" },
+ Just $ Group { gid = 1, gname = "Haskellers" })
+ (toRecord ["1", "Kei Hibino", "HRR developer", "1", "Haskellers"])
+ , eqTest
+ "fromRecord pair"
+ (fromRecord $ (User { uid = 1, uname = "Kei Hibino", note = "HRR developer" },
+ Just $ Group { gid = 1, gname = "Haskellers" }))
+ ["1", "Kei Hibino", "HRR developer", "1", "Haskellers"]
+ , eqTest
+ "width pair"
+ (runPersistableRecordWidth (persistableWidth :: PersistableRecordWidth User) +
+ runPersistableRecordWidth (persistableWidth :: PersistableRecordWidth Group))
+ (runPersistableRecordWidth (persistableWidth :: PersistableRecordWidth (User, Group)))
+ , eqTest
+ "width record"
+ (runPersistableRecordWidth (persistableWidth :: PersistableRecordWidth User) +
+ runPersistableRecordWidth (persistableWidth :: PersistableRecordWidth (Maybe Group)))
+ (runPersistableRecordWidth (persistableWidth :: PersistableRecordWidth Membership))
+ ]