diff --git a/src/PostgREST/QueryBuilder.hs b/src/PostgREST/QueryBuilder.hs index 87487ba32..75754cd6b 100644 --- a/src/PostgREST/QueryBuilder.hs +++ b/src/PostgREST/QueryBuilder.hs @@ -93,11 +93,11 @@ encodeUniformObjs = createReadStatement :: SqlQuery -> SqlQuery -> NonnegRange -> Bool -> Bool -> Bool -> H.Query () ResultsWithCount createReadStatement selectQuery countQuery range isSingle countTotal asCsv = - H.statement sql HE.unit decodeStandard True + H.statement (cs sql) HE.unit decodeStandard True where sql = [qc| WITH {sourceCTEName} AS ({selectQuery}) SELECT {cols} - FROM ( SELECT * FROM {sourceCTEName} {limitF range}) t |] + FROM ( SELECT * FROM {sourceCTEName} {limitF range}) t |] :: Text countResultF = if countTotal then "("<>countQuery<>")" else "null" cols = intercalate ", " [ countResultF <> " AS total_result_set", @@ -116,20 +116,20 @@ createWriteStatement :: QualifiedIdentifier -> SqlQuery -> SqlQuery -> Bool -> createWriteStatement _ _ _ _ _ _ _ (PayloadParseError _) = undefined createWriteStatement _ _ mutateQuery _ None _ _ (PayloadJSON (UniformObjects _)) = - H.statement sql encodeUniformObjs decodeStandardMay True + H.statement (cs sql) encodeUniformObjs decodeStandardMay True where sql = [qc| WITH {sourceCTEName} AS ({mutateQuery}) - SELECT '', 0, '', '' |] + SELECT '', 0, '', '' |] :: Text createWriteStatement qi _ mutateQuery isSingle HeadersOnly pKeys _ (PayloadJSON (UniformObjects _)) = - H.statement sql encodeUniformObjs decodeStandardMay True + H.statement (cs sql) encodeUniformObjs decodeStandardMay True where sql = [qc| WITH {sourceCTEName} AS ({mutateQuery} RETURNING {fromQi qi}.*) SELECT {cols} - FROM (SELECT 1 FROM {sourceCTEName}) t |] + FROM (SELECT 1 FROM {sourceCTEName}) t |] :: Text cols = intercalate ", " [ "'' AS total_result_set", "pg_catalog.count(t) AS page_total", @@ -139,12 +139,12 @@ createWriteStatement qi _ mutateQuery isSingle HeadersOnly createWriteStatement qi selectQuery mutateQuery isSingle Full pKeys asCsv (PayloadJSON (UniformObjects _)) = - H.statement sql encodeUniformObjs decodeStandardMay True + H.statement (cs sql) encodeUniformObjs decodeStandardMay True where sql = [qc| WITH {sourceCTEName} AS ({mutateQuery} RETURNING {fromQi qi}.*) SELECT {cols} - FROM ({selectQuery}) t |] + FROM ({selectQuery}) t |] :: Text cols = intercalate ", " [ "'' AS total_result_set", -- when updateing it does not make sense "pg_catalog.count(t) AS page_total", @@ -206,7 +206,7 @@ addJoinConditions schema (Node (query, (n, r)) forest) = type ProcResults = (Maybe Int64, Int64, JSON.Value) callProc :: QualifiedIdentifier -> JSON.Object -> NonnegRange -> Bool -> H.Query () (Maybe ProcResults) callProc qi params range countTotal = - H.statement sql HE.unit decodeProc True + H.statement (cs sql) HE.unit decodeProc True where sql = [qc| WITH t AS (select * {_callSql}) @@ -217,13 +217,13 @@ callProc qi params range countTotal = coalesce(array_agg(row_to_json(r)), '\{}') )::character varying FROM (select * from t {limitF range}) r; - |] + |] :: Text _args = intercalate "," $ map _assignment (HM.toList params) _assignment (n,v) = pgFmtIdent n <> ":=" <> insertableValue v - _callSql = [qc| from {fromQi qi}({_args}) |] :: BS.ByteString + _callSql = [qc| from {fromQi qi}({_args}) |] :: Text _countExpr = if countTotal then "(select pg_catalog.count(1) from t)" - else "null::bigint" :: BS.ByteString + else "null::bigint" :: Text decodeProc = HD.maybeRow procRow procRow = (,,) <$> HD.nullableValue HD.int8 <*> HD.value HD.int8 <*> HD.value HD.json diff --git a/test/Feature/InsertSpec.hs b/test/Feature/InsertSpec.hs index 7b3d41bde..c157b84f6 100644 --- a/test/Feature/InsertSpec.hs +++ b/test/Feature/InsertSpec.hs @@ -9,10 +9,11 @@ import SpecHelper import qualified Data.Aeson as JSON import Data.Maybe (fromJust) +import Data.Monoid ((<>)) import Text.Heredoc import Network.HTTP.Types.Header import Network.HTTP.Types -import Control.Monad (replicateM_) +import Control.Monad (replicateM_, void) import TestTypes(IncPK(..), CompoundPK(..)) import Network.Wai (Application) @@ -233,10 +234,21 @@ spec = do it "fails for too few" $ do p <- request methodPost "/no_pk" [("Content-Type", "text/csv")] "a,b\nfoo,bar\nbaz" liftIO $ simpleStatus p `shouldBe` badRequest400 - -- it does not fail because the extra columns are ignored - -- it "fails for too many" $ do - -- p <- request methodPost "/no_pk" [("Content-Type", "text/csv")] "a,b\nfoo,bar\nbaz,bat,bad" - -- liftIO $ simpleStatus p `shouldBe` badRequest400 + + context "with unicode values" $ + it "succeeds and returns usable location header" $ do + let payload = [json| { "a":"圍棋", "b":"¥" } |] + p <- request methodPost "/no_pk" + [("Prefer", "return=representation")] + payload + liftIO $ do + simpleBody p `shouldBe` payload + simpleStatus p `shouldBe` created201 + + let Just location = lookup hLocation $ simpleHeaders p + r <- get location + liftIO $ simpleBody r `shouldBe` "["<>payload<>"]" + describe "Putting record" $ do @@ -387,6 +399,17 @@ spec = do , matchHeaders = [] } + context "with unicode values" $ + it "succeeds and returns values intact" $ do + void $ request methodPost "/no_pk" [] + [json| { "a":"patchme", "b":"patchme" } |] + let payload = [json| { "a":"圍棋", "b":"¥" } |] + p <- request methodPatch "/no_pk?a=eq.patchme&b=eq.patchme" + [("Prefer", "return=representation")] payload + liftIO $ do + simpleBody p `shouldBe` "["<>payload<>"]" + simpleStatus p `shouldBe` ok200 + describe "Row level permission" $ it "set user_id when inserting rows" $ do let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqZG9lIn0.y4vZuu1dDdwAl0-S00MCRWRYMlJ5YAMSir6Es6WtWx0" diff --git a/test/Feature/QuerySpec.hs b/test/Feature/QuerySpec.hs index 7e43f465d..0d0f71a1d 100644 --- a/test/Feature/QuerySpec.hs +++ b/test/Feature/QuerySpec.hs @@ -390,11 +390,15 @@ spec = do post "/rpc/test_empty_rowset" [json| {} |] `shouldRespondWith` [json| [] |] - context "a proc that returns plain text" $ + context "a proc that returns plain text" $ do it "returns proper json" $ post "/rpc/sayhello" [json| { "name": "world" } |] `shouldRespondWith` [json| [{"sayhello":"Hello, world"}] |] + it "can handle unicode" $ + post "/rpc/sayhello" [json| { "name": "¥" } |] `shouldRespondWith` + [json| [{"sayhello":"Hello, ¥"}] |] + context "improper input" $ do it "rejects unknown content type even if payload is good" $ request methodPost "/rpc/sayhello"