git.iko.soy/typeable/postgresql-simple
on master help sign in
Add Values constructor

Closes #61 and #91. See #61 in particular for a discussion of the design

commita1eb940108eb660b92bcd3792ccd66f685748329 authorLeon P Smith <leon@melding-monads.com> dateSat 22 Mar 2014, 17:11 (13 years ago) parentf0bfbb9
4 files changed+124−1
src/Database/PostgreSQL/Simple/ToField.hs+57 src/Database/PostgreSQL/Simple/ToField.hs-boot+23 src/Database/PostgreSQL/Simple/ToRow.hs-boot+2−1 src/Database/PostgreSQL/Simple/Types.hs+42
src/Database/PostgreSQL/Simple/ToField.hs+57
@@ -33,6 +33,7 @@ import Data.Monoid (mappend)
3333import Data.Time (Day, TimeOfDay, LocalTime, UTCTime, ZonedTime)
3434import Data.Typeable (Typeable)
3535import Data.Word (Word, Word8, Word16, Word32, Word64)
36+import {-# SOURCE #-} Database.PostgreSQL.Simple.ToRow
3637import Database.PostgreSQL.Simple.Types
3738import qualified Blaze.ByteString.Builder.Char.Utf8 as Utf8
3839import qualified Data.ByteString as SB
@@ -279,3 +280,59 @@ toJSONField = toField . JSON.toJSON
279280inQuotes :: Builder -> Builder
280281inQuotes b = quote `mappend` b `mappend` quote
281282 where quote = Utf8.fromChar '\''
283+
284+interleaveFoldr :: (a -> [b] -> [b]) -> b -> [b] -> [a] -> [b]
285+interleaveFoldr f b bs as = foldr (\a bs -> b : f a bs) bs as
286+{-# INLINE interleaveFoldr #-}
287+
288+instance ToRow a => ToField (Values a) where
289+ toField (Values types rows) =
290+ case rows of
291+ [] -> case types of
292+ [] -> error err
293+ (_:_) -> values $ typedRow (repeat (lit "null"))
294+ types
295+ [lit " LIMIT 0)"]
296+ (_:_) -> case types of
297+ [] -> values $ untypedRows rows [litC ')']
298+ (_:_) -> values $ typedRows rows types [litC ')']
299+ where
300+ err = "Database.PostgreSQL.Simple.toField :: Values -> Action either values or types must be non-empty"
301+ lit = Plain . fromByteString
302+ litC = Plain . fromChar
303+ values x = Many (lit "(VALUES ": x)
304+
305+ typedField :: (Action, QualifiedIdentifier) -> [Action] -> [Action]
306+ typedField (val,typ) rest = val : lit "::" : toField typ : rest
307+
308+ typedRow :: [Action] -> [QualifiedIdentifier] -> [Action] -> [Action]
309+ typedRow (val:vals) (typ:typs) rest =
310+ litC '(' :
311+ typedField (val,typ) ( interleaveFoldr
312+ typedField
313+ (litC ',')
314+ (litC ')' : rest)
315+ (zip vals typs) )
316+
317+ untypedRow :: [Action] -> [Action] -> [Action]
318+ untypedRow (val:vals) rest =
319+ litC '(' : val :
320+ interleaveFoldr
321+ (:)
322+ (litC ',')
323+ (litC ')' : rest)
324+ vals
325+
326+ typedRows :: ToRow a => [a] -> [QualifiedIdentifier] -> [Action] -> [Action]
327+ typedRows (val:vals) types rest =
328+ typedRow (toRow val) types (litC ',' : untypedRows vals rest)
329+
330+ untypedRows :: ToRow a => [a] -> [Action] -> [Action]
331+ untypedRows [] rest = rest
332+ untypedRows (val:vals) rest =
333+ untypedRow (toRow val) $
334+ interleaveFoldr
335+ (untypedRow . toRow)
336+ (litC ',')
337+ rest
338+ vals
src/Database/PostgreSQL/Simple/ToField.hs-boot+23
@@ -1,6 +1,29 @@
11module Database.PostgreSQL.Simple.ToField where
22
33import Database.PostgreSQL.Simple.Types
4+import Blaze.ByteString.Builder(Builder)
5+import Data.ByteString(ByteString)
6+
7+-- | How to render an element when substituting it into a query.
8+data Action =
9+ Plain Builder
10+ -- ^ Render without escaping or quoting. Use for non-text types
11+ -- such as numbers, when you are /certain/ that they will not
12+ -- introduce formatting vulnerabilities via use of characters such
13+ -- as spaces or \"@'@\".
14+ | Escape ByteString
15+ -- ^ Escape and enclose in quotes before substituting. Use for all
16+ -- text-like types, and anything else that may contain unsafe
17+ -- characters when rendered.
18+ | EscapeByteA ByteString
19+ -- ^ Escape binary data for use as a @bytea@ literal. Include surrounding
20+ -- quotes. This is used by the 'Binary' newtype wrapper.
21+ | EscapeIdentifier ByteString
22+ -- ^ Escape before substituting. Use for all sql identifiers like
23+ -- table, column names, etc. This is used by the 'Identifier' newtype
24+ -- wrapper.
25+ | Many [Action]
26+ -- ^ Concatenate a series of rendering actions.
427
528class ToField a
629
src/Database/PostgreSQL/Simple/ToRow.hs-boot+2−1
@@ -3,6 +3,7 @@ module Database.PostgreSQL.Simple.ToRow where
33import Database.PostgreSQL.Simple.Types
44import {-# SOURCE #-} Database.PostgreSQL.Simple.ToField
55
6−class ToRow a
6+class ToRow a where
7+ toRow :: a -> [Action]
78
89instance ToField a => ToRow (Only a)
src/Database/PostgreSQL/Simple/Types.hs+42
@@ -27,6 +27,7 @@ module Database.PostgreSQL.Simple.Types
2727 , (:.)(..)
2828 , Savepoint(..)
2929 , PGArray(..)
30+ , Values(..)
3031 ) where
3132
3233import Blaze.ByteString.Builder (toByteString)
@@ -173,3 +174,44 @@ infixr 3 :.
173174
174175newtype Savepoint = Savepoint Query
175176 deriving (Eq, Ord, Show, Read, Typeable)
177+
178+-- | Represents a @VALUES@ table literal, usable as an alternative
179+-- to @executeMany@ and @returning@. For example:
180+--
181+-- > execute c "INSERT INTO table (key,val) ?"
182+-- > (Only (Values ["int4","text"]
183+-- > [(1,"hello"),(2,"world")]))
184+--
185+-- Issues the following query:
186+--
187+-- > INSERT INTO table (key,val) (VALUES (1::"int4",'hello'::"text"),(2,'world'))
188+--
189+-- When the list of values is empty, the following query will be issued:
190+--
191+-- > INSERT INTO table (key,val) (VALUES (null::"int4",null::"text") LIMIT 0)
192+--
193+-- By contrast, @executeMany@ and @returning@ don't issue the query
194+-- in the empty case, and simply return @0@ and @[]@ respectively.
195+--
196+-- The advantage over @executeMany@ is in cases when you want to
197+-- parameterize table literals in addition to other parameters, as can
198+-- occur with writable common table expressions, for example.
199+--
200+-- The first argument is a list of postgresql type names. Because this
201+-- is turned into a properly quoted identifier, the type name is case
202+-- sensitive and must be as it appears in the @pg_type@ table. Thus,
203+-- you must write @timestamptz@ instead of @timestamp with time zone@,
204+-- @int4@ instead of @integer@, @_int8@ instead of @bigint[]@, etcetera.
205+--
206+-- You may omit the type names, however, if you do so the list
207+-- of values must be non-empty, and postgresql must be able to infer
208+-- the types of the columns from the surrounding context. If these
209+-- conditions are not met, postgresql-simple will throw an exception
210+-- without issuing the query in the former case, and in the latter
211+-- the postgres server will return an error which will be turned into
212+-- a @SqlError@ exception.
213+--
214+-- See <http://www.postgresql.org/docs/9.3/static/sql-values.html> for
215+-- more information.
216+data Values a = Values [QualifiedIdentifier] [a]
217+ deriving (Eq, Ord, Show, Read, Typeable)