@@ -4,18 +4,21 @@
4{-# LANGUAGE DoAndIfThenElse #-}
4{-# LANGUAGE DoAndIfThenElse #-}
5{-# LANGUAGE ScopedTypeVariables #-}
5{-# LANGUAGE ScopedTypeVariables #-}
6import Common
6import Common
7+import Database.PostgreSQL.Simple.Copy
7import Database.PostgreSQL.Simple.FromField (FromField)
8import Database.PostgreSQL.Simple.FromField (FromField)
8−import Database.PostgreSQL.Simple.Types(Query(..),Values(..))
9import Database.PostgreSQL.Simple.HStore
9import Database.PostgreSQL.Simple.HStore
10−import Database.PostgreSQL.Simple.Copy
10+import Database.PostgreSQL.Simple.Internal (breakOnSingleQuestionMark)
11+import Database.PostgreSQL.Simple.Types(Query(..),Values(..), PGArray(..))
11import qualified Database.PostgreSQL.Simple.Transaction as ST
12import qualified Database.PostgreSQL.Simple.Transaction as ST
13import Control.Applicative
14import Control.Applicative
14import Control.Exception as E
15import Control.Exception as E
15import Control.Monad
16import Control.Monad
16import Data.Char
17import Data.Char
17−import Data.List (sort)
18+import Data.List (concat, sort)
18import Data.IORef
19import Data.IORef
20+import Data.Monoid ((<>))
21+import Data.String (fromString)
19import Data.Typeable
22import Data.Typeable
20import GHC.Generics (Generic)
23import GHC.Generics (Generic)
@@ -44,26 +47,27 @@ tests :: TestEnv -> TestTree
44tests env = testGroup "tests"
47tests env = testGroup "tests"
45 $ map ($ env)
48 $ map ($ env)
46 [ testBytea
49 [ testBytea
47− , testCase "ExecuteMany" . testExecuteMany
50+ , testCase "ExecuteMany" . testExecuteMany
48− , testCase "Fold" . testFold
51+ , testCase "Fold" . testFold
49− , testCase "Notify" . testNotify
52+ , testCase "Notify" . testNotify
50− , testCase "Serializable" . testSerializable
53+ , testCase "Serializable" . testSerializable
51− , testCase "Time" . testTime
54+ , testCase "Time" . testTime
52− , testCase "Array" . testArray
55+ , testCase "Array" . testArray
53− , testCase "Array of nullables" . testNullableArray
56+ , testCase "Array of nullables" . testNullableArray
54− , testCase "HStore" . testHStore
57+ , testCase "HStore" . testHStore
55− , testCase "citext" . testCIText
58+ , testCase "citext" . testCIText
56− , testCase "JSON" . testJSON
59+ , testCase "JSON" . testJSON
60+ , testCase "Question mark escape" . testQM
57− , testCase "Savepoint" . testSavepoint
61+ , testCase "Savepoint" . testSavepoint
58− , testCase "Unicode" . testUnicode
62+ , testCase "Unicode" . testUnicode
59− , testCase "Values" . testValues
63+ , testCase "Values" . testValues
60− , testCase "Copy" . testCopy
64+ , testCase "Copy" . testCopy
61 , testCopyFailures
65 , testCopyFailures
62− , testCase "Double" . testDouble
66+ , testCase "Double" . testDouble
63− , testCase "1-ary generic" . testGeneric1
67+ , testCase "1-ary generic" . testGeneric1
64− , testCase "2-ary generic" . testGeneric2
68+ , testCase "2-ary generic" . testGeneric2
65− , testCase "3-ary generic" . testGeneric3
69+ , testCase "3-ary generic" . testGeneric3
66− , testCase "Timeout" . testTimeout
70+ , testCase "Timeout" . testTimeout
69testBytea :: TestEnv -> TestTree
73testBytea :: TestEnv -> TestTree
@@ -234,6 +238,56 @@ testJSON TestEnv{..} = do
234 js' <- query conn "SELECT ?::json" js
238 js' <- query conn "SELECT ?::json" js
235 [js] @?= js'
239 [js] @?= js'
241+testQM :: TestEnv -> Assertion
242+testQM TestEnv{..} = do
243+ -- Just test on a single string
244+ let testQuery' b = "testing for ?" <> b <> " and making sure "
245+ testQueryDoubleQM = testQuery' "?"
246+ testQueryRest = "? is substituted"
247+ testQuery = fromString $ testQueryDoubleQM <> testQueryRest
248+ -- expect the entire first part with double QMs replaced with literal '?'
249+ expected = (fromString $ testQuery' "", fromString testQueryRest)
250+ tried = breakOnSingleQuestionMark testQuery
252+ [ "Failed to break on single question mark exclusively:\n"
253+ , "expected: ", show expected
254+ , "result: ", show tried
256+ assertBool errMsg $ tried == expected
258+ -- Let's also test the question mark operators in action
259+ -- ? -> Does the string exist as a top-level key within the JSON value?
260+ positiveQuery "SELECT ?::jsonb ?? ?" (testObj, "foo" :: Text)
261+ negativeQuery "SELECT ?::jsonb ?? ?" (testObj, "baz" :: Text)
262+ negativeQuery "SELECT ?::jsonb ?? ?" (toJSON numArray, "1" :: Text)
263+ -- ?| -> Do any of these array strings exist as top-level keys?
264+ positiveQuery "SELECT ?::jsonb ??| ?" (testObj, PGArray ["nope","bar","6" :: Text])
265+ negativeQuery "SELECT ?::jsonb ??| ?" (testObj, PGArray ["nope","6" :: Text])
266+ negativeQuery "SELECT ?::jsonb ??| ?" (toJSON numArray, PGArray ["1","2","6" :: Text])
267+ -- ?& -> Do all of these array strings exist as top-level keys?
268+ positiveQuery "SELECT ?::jsonb ??& ?" (testObj, PGArray ["foo","bar","quux" :: Text])
269+ positiveQuery "SELECT ?::jsonb ??& ?" (testObj, PGArray ["foo","bar" :: Text])
270+ negativeQuery "SELECT ?::jsonb ??& ?" (testObj, PGArray ["foo","bar","baz" :: Text])
271+ negativeQuery "SELECT ?::jsonb ??& ?" (toJSON numArray, PGArray ["1","2","3","4","5" :: Text])
272+ -- Format error for 2 question marks, not 4
273+ True <- expectError (isFormatError 2) $
274+ (query conn "SELECT ?::jsonb ?? ?" $ Only testObj :: IO [Only Bool])
276+ where positiveQuery :: ToRow a => Query -> a -> Assertion
277+ positiveQuery = boolQuery True
278+ negativeQuery :: ToRow a => Query -> a -> Assertion
279+ negativeQuery = boolQuery False
281+ numArray = [1,2,3,4,5]
282+ boolQuery :: ToRow a => Bool -> Query -> a -> Assertion
283+ boolQuery b t x = do
286+ testObj = toJSON (Map.fromList [("foo",toJSON (1 :: Int))
287+ ,("bar",String "baz")
288+ ,("quux",toJSON [1 :: Int,2,3,4,5])] :: Map Text Value
237testSavepoint :: TestEnv -> Assertion
291testSavepoint :: TestEnv -> Assertion
238testSavepoint TestEnv{..} = do
292testSavepoint TestEnv{..} = do
239 True <- expectError ST.isNoActiveTransactionError $
293 True <- expectError ST.isNoActiveTransactionError $
@@ -478,6 +532,14 @@ isUniqueViolation SqlError{..} = sqlState == "23505"
478isSyntaxError :: SqlError -> Bool
532isSyntaxError :: SqlError -> Bool
479isSyntaxError SqlError{..} = sqlState == "42601"
533isSyntaxError SqlError{..} = sqlState == "42601"
535+isFormatError :: Int -> FormatError -> Bool
536+isFormatError i FormatError{..}
537+ | null fmtMessage = False
538+ | otherwise = fmtMessage == concat [ show i
539+ , " single '?' characters, but "
540+ , show (length fmtParams)
481------------------------------------------------------------------------
543------------------------------------------------------------------------
483-- | Action for connecting to the database that will be used for testing.
545-- | Action for connecting to the database that will be used for testing.