-
Notifications
You must be signed in to change notification settings - Fork 49
Expand file tree
/
Copy pathFromField.hs
More file actions
222 lines (180 loc) · 8.31 KB
/
Copy pathFromField.hs
File metadata and controls
222 lines (180 loc) · 8.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
{-# LANGUAGE CPP, DeriveDataTypeable, DeriveFunctor #-}
{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
------------------------------------------------------------------------------
-- |
-- Module: Database.SQLite.Simple.FromField
-- Copyright: (c) 2011 MailRank, Inc.
-- (c) 2011-2012 Leon P Smith
-- (c) 2012-2013 Janne Hellsten
-- License: BSD3
-- Maintainer: Janne Hellsten <[email protected]>
-- Portability: portable
--
-- The 'FromField' typeclass, for converting a single value in a row
-- returned by a SQL query into a more useful Haskell representation.
--
-- A Haskell numeric type is considered to be compatible with all
-- SQLite numeric types that are less accurate than it. For instance,
-- the Haskell 'Double' type is compatible with the SQLite's 32-bit
-- @Int@ type because it can represent a @Int@ exactly. On the other hand,
-- since a 'Double' might lose precision if representing a 64-bit @BigInt@,
-- the two are /not/ considered compatible.
--
------------------------------------------------------------------------------
module Database.SQLite.Simple.FromField
(
FromField(..)
, FieldParser
, ResultError(..)
, Field
, fieldData
, returnError
) where
import Control.Applicative (Applicative, (<$>), pure)
import Control.Exception (SomeException(..), Exception)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy as LB
import Data.Functor.Identity (Identity (..))
import Data.Int (Int8, Int16, Int32, Int64)
import Data.Time (UTCTime, Day)
import qualified Data.Text as T
import qualified Data.Text.Lazy as LT
import Data.Typeable (Typeable, typeOf)
import Data.Word (Word, Word8, Word16, Word32, Word64)
import GHC.Float (double2Float)
import Database.SQLite3 as Base
import Database.SQLite.Simple.Types
import Database.SQLite.Simple.Internal
import Database.SQLite.Simple.Ok
import Database.SQLite.Simple.Time
-- | Exception thrown if conversion from a SQL value to a Haskell
-- value fails.
data ResultError = Incompatible { errSQLType :: String
, errHaskellType :: String
, errMessage :: String }
-- ^ The SQL and Haskell types are not compatible.
| UnexpectedNull { errSQLType :: String
, errHaskellType :: String
, errMessage :: String }
-- ^ A SQL @NULL@ was encountered when the Haskell
-- type did not permit it.
| ConversionFailed { errSQLType :: String
, errHaskellType :: String
, errMessage :: String }
-- ^ The SQL value could not be parsed, or could not
-- be represented as a valid Haskell value, or an
-- unexpected low-level error occurred (e.g. mismatch
-- between metadata and actual data in a row).
deriving (Eq, Show, Typeable)
instance Exception ResultError
left :: Exception a => a -> Ok b
left = Errors . (:[]) . SomeException
type FieldParser a = Field -> Ok a
-- | A type that may be converted from a SQL type.
class FromField a where
fromField :: FieldParser a
-- ^ Convert a SQL value to a Haskell value.
--
-- Returns a list of exceptions if the conversion fails. In the case of
-- library instances, this will usually be a single 'ResultError', but
-- may be a 'UnicodeException'.
--
-- Implementations of 'fromField' should not retain any references to
-- the 'Field' nor the 'ByteString' arguments after the result has
-- been evaluated to WHNF. Such a reference causes the entire
-- @LibPQ.'PQ.Result'@ to be retained.
--
-- For example, the instance for 'ByteString' uses 'B.copy' to avoid
-- such a reference, and that using bytestring functions such as 'B.drop'
-- and 'B.takeWhile' alone will also trigger this memory leak.
instance (FromField a) => FromField (Maybe a) where
fromField (Field SQLNull _) = pure Nothing
fromField f = Just <$> fromField f
instance FromField Null where
fromField (Field SQLNull _) = pure Null
fromField f = returnError ConversionFailed f "data is not null"
takeInt :: (Num a, Typeable a) => Field -> Ok a
takeInt (Field (SQLInteger i) _) = Ok . fromIntegral $ i
takeInt f = returnError ConversionFailed f "need an int"
instance FromField Int8 where
fromField = takeInt
instance FromField Int16 where
fromField = takeInt
instance FromField Int32 where
fromField = takeInt
instance FromField Int where
fromField = takeInt
instance FromField Int64 where
fromField = takeInt
instance FromField Integer where
fromField = takeInt
instance FromField Word8 where
fromField = takeInt
instance FromField Word16 where
fromField = takeInt
instance FromField Word32 where
fromField = takeInt
instance FromField Word64 where
fromField = takeInt
instance FromField Word where
fromField = takeInt
instance FromField Double where
fromField (Field (SQLFloat flt) _) = Ok flt
fromField f = returnError ConversionFailed f "expecting an SQLFloat column type"
instance FromField Float where
fromField (Field (SQLFloat flt) _) = Ok . double2Float $ flt
fromField f = returnError ConversionFailed f "expecting an SQLFloat column type"
instance FromField Bool where
fromField f@(Field (SQLInteger b) _)
| (b == 0) || (b == 1) = Ok (b /= 0)
| otherwise = returnError ConversionFailed f ("bool must be 0 or 1, got " ++ show b)
fromField f = returnError ConversionFailed f "expecting an SQLInteger column type"
instance FromField T.Text where
fromField (Field (SQLText txt) _) = Ok txt
fromField f = returnError ConversionFailed f "need a text"
instance FromField LT.Text where
fromField (Field (SQLText txt) _) = Ok . LT.fromStrict $ txt
fromField f = returnError ConversionFailed f "need a text"
instance FromField [Char] where
fromField (Field (SQLText t) _) = Ok $ T.unpack t
fromField f = returnError ConversionFailed f "expecting SQLText column type"
instance FromField ByteString where
fromField (Field (SQLBlob blb) _) = Ok blb
fromField f = returnError ConversionFailed f "expecting SQLBlob column type"
instance FromField LB.ByteString where
fromField (Field (SQLBlob blb) _) = Ok . LB.fromChunks $ [blb]
fromField f = returnError ConversionFailed f "expecting SQLBlob column type"
instance FromField UTCTime where
fromField f@(Field (SQLText t) _) =
case parseUTCTime t of
Right t -> Ok t
Left e -> returnError ConversionFailed f ("couldn't parse UTCTime field: " ++ e)
fromField f = returnError ConversionFailed f "expecting SQLText column type"
instance FromField Day where
fromField f@(Field (SQLText t) _) =
case parseDay t of
Right t -> Ok t
Left e -> returnError ConversionFailed f ("couldn't parse Day field: " ++ e)
fromField f = returnError ConversionFailed f "expecting SQLText column type"
instance FromField SQLData where
fromField f = Ok (fieldData f)
instance (FromField a) => FromField (Identity a) where
fromField f = Identity <$> fromField f
fieldTypename :: Field -> String
fieldTypename = B.unpack . gettypename . result
-- | Return the actual SQL data for a database field. This allows
-- user-defined 'FromField' instances to access the SQL data
-- associated with a field being parsed.
fieldData :: Field -> SQLData
fieldData = result
-- | Given one of the constructors from 'ResultError', the field,
-- and an 'errMessage', this fills in the other fields in the
-- exception value and returns it in a 'Left . SomeException'
-- constructor.
returnError :: forall a err . (Typeable a, Exception err)
=> (String -> String -> String -> err)
-> Field -> String -> Ok a
returnError mkErr f = left . mkErr (fieldTypename f)
(show (typeOf (undefined :: a)))