diff --git a/src/PostgREST/Parsers.hs b/src/PostgREST/Parsers.hs index 4a7f82325..5ee75d9d7 100644 --- a/src/PostgREST/Parsers.hs +++ b/src/PostgREST/Parsers.hs @@ -78,8 +78,9 @@ pFieldForest :: Parser [Tree SelectItem] pFieldForest = pFieldTree `sepBy1` lexeme (char ',') pFieldTree :: Parser (Tree SelectItem) -pFieldTree = try (Node <$> pSimpleSelect <*> between (char '{') (char '}') pFieldForest) - <|> Node <$> pSelect <*> pure [] +pFieldTree = try (Node <$> pSimpleSelect <*> between (char '{') (char '}') pFieldForest) + <|> try (Node <$> pSimpleSelect <*> between (char '(') (char ')') pFieldForest) + <|> Node <$> pSelect <*> pure [] pStar :: Parser Text pStar = toS <$> (string "*" *> pure ("*"::ByteString)) @@ -143,10 +144,11 @@ pVText :: Parser Operand pVText = VText . toS <$> many anyChar pVTextL :: Parser Operand -pVTextL = VTextL <$> lexeme pLValue `sepBy1` char ',' - where - pLValue :: Parser Text - pLValue = try pQuotedValue <|> (toS <$> many (noneOf ",")) +pVTextL = VTextL <$> try (lexeme (char '(') *> pVTextLElement `sepBy1` char ',' <* lexeme (char ')')) + <|> VTextL <$> lexeme pVTextLElement `sepBy1` char ',' + +pVTextLElement :: Parser Text +pVTextLElement = try pQuotedValue <|> (toS <$> many (noneOf ",)")) pQuotedValue :: Parser Text pQuotedValue = toS <$> (char '"' *> many (noneOf "\"") <* char '"' <* notFollowedBy (noneOf ",)")) @@ -199,10 +201,7 @@ pLogicVText = VText <$> (try pQuotedValue <|> try pPgArray <|> (toS <$> many (no toS <$> pure (a ++ b ++ c) pLogicVTextL :: Parser Operand -pLogicVTextL = VTextL <$> (lexeme (char '(') *> pLValue `sepBy1` char ',' <* lexeme (char ')')) - where - pLValue :: Parser Text - pLValue = try pQuotedValue <|> (toS <$> many (noneOf ",)")) +pLogicVTextL = VTextL <$> (lexeme (char '(') *> pVTextLElement `sepBy1` char ',' <* lexeme (char ')')) pLogicPath :: Parser (EmbedPath, Text) pLogicPath = do diff --git a/test/Feature/QuerySpec.hs b/test/Feature/QuerySpec.hs index 1ed6525f9..260148a68 100644 --- a/test/Feature/QuerySpec.hs +++ b/test/Feature/QuerySpec.hs @@ -813,3 +813,30 @@ spec = do it "only returns an empty result set if the in value is empty" $ get "/items_with_different_col_types?int_data=in. ,3,4" `shouldRespondWith` 400 + + it "returns empty result when the in value is empty between parentheses" $ + get "/items_with_different_col_types?int_data=in.()" `shouldRespondWith` + [json| [] |] { matchHeaders = [matchContentTypeJson] } + + it "returns all results when the notin value is empty between parentheses" $ do + get "/items_with_different_col_types?int_data=notin.()&select=int_data" `shouldRespondWith` + [json| [{int_data: 1}] |] { matchHeaders = [matchContentTypeJson] } + get "/items_with_different_col_types?int_data=not.in.()&select=int_data" `shouldRespondWith` + [json| [{int_data: 1}] |] { matchHeaders = [matchContentTypeJson] } + + describe "Transition to url safe characters" $ do + context "top level in operator" $ do + it "works with parentheses" $ + get "/entities?id=in.(1,2,3)&select=id" `shouldRespondWith` + [json| [{"id": 1}, {"id": 2}, {"id": 3}] |] { matchHeaders = [matchContentTypeJson] } + it "works without parentheses" $ + get "/entities?id=in.1,2,3&select=id" `shouldRespondWith` + [json| [{"id": 1}, {"id": 2}, {"id": 3}] |] { matchHeaders = [matchContentTypeJson] } + + context "select query param" $ do + it "works with parentheses" $ + get "/entities?id=eq.2&select=id,child_entities(id)" `shouldRespondWith` + [json| [{"id": 2, "child_entities": [{"id": 3}]}] |] { matchHeaders = [matchContentTypeJson] } + it "works with brackets" $ + get "/entities?id=eq.2&select=id,child_entities{id}" `shouldRespondWith` + [json| [{"id": 2, "child_entities": [{"id": 3}]}] |] { matchHeaders = [matchContentTypeJson] }