@@ -0,0 +1,276 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE DeriveFunctor #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE FlexibleInstances #-}
6+------------------------------------------------------------------------------
8+-- Module: Database.PostgreSQL.Simple.Range
9+-- Copyright: (c) 2014-2015 Leonid Onokhov
10+-- (c) 2014-2015 Leon P Smith
12+-- Maintainer: Leon P Smith <leon@melding-monads.com>
14+------------------------------------------------------------------------------
16+module Database.PostgreSQL.Simple.Range
24+import Control.Applicative hiding (empty)
25+import Data.Attoparsec.ByteString.Char8 (Parser, parseOnly)
26+import qualified Data.Attoparsec.ByteString.Char8 as A
27+import qualified Data.ByteString as B
28+import Data.ByteString.Builder
29+ ( Builder, byteString, lazyByteString, char8
30+ , intDec, int8Dec, int16Dec, int32Dec, int64Dec, integerDec
31+ , wordDec, word8Dec, word16Dec, word32Dec, word64Dec
32+ , doubleDec, floatDec )
33+import Data.Int (Int16, Int32, Int64,
35+import Data.Function (on)
36+import Data.Monoid (mempty)
37+import Data.Scientific (Scientific)
38+import qualified Data.Text.Lazy.Builder as LT
39+import qualified Data.Text.Lazy.Encoding as LT
40+import Data.Time (Day, LocalTime,
45+import Data.Typeable (Typeable)
46+import Data.Word (Word, Word16, Word32,
49+import Database.PostgreSQL.Simple.Compat (scientificBuilder, (<>), toByteString)
50+import Database.PostgreSQL.Simple.FromField
51+import Database.PostgreSQL.Simple.Time
52+ hiding (PosInfinity, NegInfinity)
53+-- import qualified Database.PostgreSQL.Simple.Time as Time
54+import Database.PostgreSQL.Simple.ToField
56+-- | Represents boundary of a range
57+data RangeBound a = NegInfinity
61+ deriving (Show, Typeable, Eq, Functor)
63+-- | Generic range type
64+data PGRange a = PGRange !(RangeBound a) !(RangeBound a)
65+ deriving (Show, Typeable, Functor)
68+empty = PGRange PosInfinity NegInfinity
70+instance Ord a => Eq (PGRange a) where
71+ x == y = eq x y || (isEmpty x && isEmpty y)
72+ where eq (PGRange a m) (PGRange b n) = a == b && m == n
74+isEmptyBy :: (a -> a -> Ordering) -> PGRange a -> Bool
77+ (PGRange PosInfinity _) -> True
78+ (PGRange _ NegInfinity) -> True
79+ (PGRange NegInfinity _) -> False
80+ (PGRange _ PosInfinity) -> False
81+ (PGRange (Inclusive x) (Inclusive y)) -> cmp x y == GT
82+ (PGRange (Inclusive x) (Exclusive y)) -> cmp x y /= LT
83+ (PGRange (Exclusive x) (Inclusive y)) -> cmp x y /= LT
84+ (PGRange (Exclusive x) (Exclusive y)) -> cmp x y /= LT
86+isEmpty :: Ord a => PGRange a -> Bool
87+isEmpty = isEmptyBy compare
89+lowerBound :: Parser (a -> RangeBound a)
90+lowerBound = (A.char '(' *> pure Exclusive) <|> (A.char '[' *> pure Inclusive)
91+{-# INLINE lowerBound #-}
93+upperBound :: Parser (a -> RangeBound a)
94+upperBound = (A.char ')' *> pure Exclusive) <|> (A.char ']' *> pure Inclusive)
95+{-# INLINE upperBound #-}
97+-- | Generic range parser
98+pgrange :: Parser (RangeBound B.ByteString, RangeBound B.ByteString)
101+ v1 <- (A.char ',' *> "") <|> (rangeElem (==',') <* A.char ',')
102+ v2 <- rangeElem $ \c -> c == ')' || c == ']'
105+ let low = if B.null v1 then NegInfinity else lb v1
106+ up = if B.null v2 then PosInfinity else ub v2
109+rangeElem :: (Char -> Bool) -> Parser B.ByteString
110+rangeElem end = (A.char '"' *> doubleQuoted)
112+{-# INLINE rangeElem #-}
114+-- | Simple double quoted value parser
115+doubleQuoted :: Parser B.ByteString
116+doubleQuoted = toByteString <$> go mempty
119+ h <- byteString <$> A.takeTill (\c -> c == '\\' || c == '"')
125+ go (acc <> h <> char8 c)
126+ '"' -> (A.char '"' *> go (acc <> h <> char8 '"'))
128+ _ -> error "impossible in doubleQuoted"
131+rangeToBuilder :: Ord a => (a -> Builder) -> PGRange a -> Builder
132+rangeToBuilder = rangeToBuilderBy compare
134+-- | Generic range to builder for plain values
135+rangeToBuilderBy :: (a -> a -> Ordering) => (a -> Builder) -> PGRange a -> Builder
136+rangeToBuilderBy cmp f x =
138+ then byteString "'empty'"
139+ else let (PGRange a b) = x
140+ in buildLB a <> buildUB b
142+ buildLB NegInfinity = byteString "'[,"
143+ buildLB (Inclusive v) = byteString "'[\"" <> f v <> byteString "\","
144+ buildLB (Exclusive v) = byteString "'(\"" <> f v <> byteString "\","
145+ buildLB PosInfinity = error "impossible in rangeToBuilder"
147+ buildUB NegInfinity = error "impossible in rangeToBuilder"
148+ buildUB (Inclusive v) = char8 '"' <> f v <> byteString "\"]'"
149+ buildUB (Exclusive v) = char8 '"' <> f v <> byteString "\")'"
150+ buildUB PosInfinity = byteString "]'"
151+{-# INLINE rangeToBuilder #-}
154+instance (FromField a, Typeable a) => FromField (PGRange a) where
155+ fromField f mdat = do
159+ let f' = f { typeOid = typoid (rngsubtype info) }
161+ Nothing -> returnError UnexpectedNull f ""
162+ Just "empty" -> pure $ empty
164+ let parseIt NegInfinity = pure NegInfinity
165+ parseIt (Inclusive v) = Inclusive <$> fromField f' (Just v)
166+ parseIt (Exclusive v) = Exclusive <$> fromField f' (Just v)
167+ parseIt PosInfinity = pure PosInfinity
168+ in case parseOnly pgrange bs of
169+ Left e -> returnError ConversionFailed f e
170+ Right (lb,ub) -> PGRange <$> parseIt lb <*> parseIt ub
171+ _ -> returnError Incompatible f ""
174+instance ToField (PGRange Int8) where
175+ toField = Plain . rangeToBuilder int8Dec
176+ {-# INLINE toField #-}
178+instance ToField (PGRange Int16) where
179+ toField = Plain . rangeToBuilder int16Dec
180+ {-# INLINE toField #-}
182+instance ToField (PGRange Int32) where
183+ toField = Plain . rangeToBuilder int32Dec
184+ {-# INLINE toField #-}
186+instance ToField (PGRange Int) where
187+ toField = Plain . rangeToBuilder intDec
188+ {-# INLINE toField #-}
190+instance ToField (PGRange Int64) where
191+ toField = Plain . rangeToBuilder int64Dec
192+ {-# INLINE toField #-}
194+instance ToField (PGRange Integer) where
195+ toField = Plain . rangeToBuilder integerDec
196+ {-# INLINE toField #-}
198+instance ToField (PGRange Word8) where
199+ toField = Plain . rangeToBuilder word8Dec
200+ {-# INLINE toField #-}
202+instance ToField (PGRange Word16) where
203+ toField = Plain . rangeToBuilder word16Dec
204+ {-# INLINE toField #-}
206+instance ToField (PGRange Word32) where
207+ toField = Plain . rangeToBuilder word32Dec
208+ {-# INLINE toField #-}
210+instance ToField (PGRange Word) where
211+ toField = Plain . rangeToBuilder wordDec
212+ {-# INLINE toField #-}
214+instance ToField (PGRange Word64) where
215+ toField = Plain . rangeToBuilder word64Dec
216+ {-# INLINE toField #-}
218+instance ToField (PGRange Float) where
219+ toField = Plain . rangeToBuilder floatDec
220+ {-# INLINE toField #-}
222+instance ToField (PGRange Double) where
223+ toField = Plain . rangeToBuilder doubleDec
224+ {-# INLINE toField #-}
226+instance ToField (PGRange Scientific) where
227+ toField = Plain . rangeToBuilder f
229+ f = lazyByteString . LT.encodeUtf8 . LT.toLazyText . scientificBuilder
230+ {-# INLINE toField #-}
232+instance ToField (PGRange UTCTime) where
233+ toField = Plain . rangeToBuilder utcTimeToBuilder
234+ {-# INLINE toField #-}
236+instance ToField (PGRange ZonedTime) where
237+ toField = Plain . rangeToBuilderBy cmpZonedTime zonedTimeToBuilder
238+ {-# INLINE toField #-}
240+cmpZonedTime :: ZonedTime -> ZonedTime -> Ordering
241+cmpZonedTime = compare `on` zonedTimeToUTC -- FIXME: optimize
243+instance ToField (PGRange LocalTime) where
244+ toField = Plain . rangeToBuilder localTimeToBuilder
245+ {-# INLINE toField #-}
247+instance ToField (PGRange Day) where
248+ toField = Plain . rangeToBuilder dayToBuilder
249+ {-# INLINE toField #-}
251+instance ToField (PGRange TimeOfDay) where
252+ toField = Plain . rangeToBuilder timeOfDayToBuilder
253+ {-# INLINE toField #-}
255+instance ToField (PGRange UTCTimestamp) where
256+ toField = Plain . rangeToBuilder utcTimestampToBuilder
257+ {-# INLINE toField #-}
259+instance ToField (PGRange ZonedTimestamp) where
260+ toField = Plain . rangeToBuilderBy cmpZonedTimestamp zonedTimestampToBuilder
261+ {-# INLINE toField #-}
263+cmpZonedTimestamp :: ZonedTimestamp -> ZonedTimestamp -> Ordering
264+cmpZonedTimestamp = compare `on` (zonedTimeToUTC <$>)
266+instance ToField (PGRange LocalTimestamp) where
267+ toField = Plain . rangeToBuilder localTimestampToBuilder
268+ {-# INLINE toField #-}
270+instance ToField (PGRange Date) where
271+ toField = Plain . rangeToBuilder dateToBuilder
272+ {-# INLINE toField #-}
274+instance ToField (PGRange NominalDiffTime) where
275+ toField = Plain . rangeToBuilder nominalDiffTimeToBuilder
276+ {-# INLINE toField #-}