Skip to content

Commit

Permalink
Merge pull request #31 from dbrattli/correlation
Browse files Browse the repository at this point in the history
Fix parsing of  quoted `schema.table.column` references and comments
  • Loading branch information
Zaid-Ajaj authored May 6, 2021
2 parents bd25fcd + b05ec33 commit 89f1b78
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 19 deletions.
60 changes: 42 additions & 18 deletions src/NpgsqlFSharpParser/Parser.fs
Original file line number Diff line number Diff line change
Expand Up @@ -87,22 +87,37 @@ let manyCharsBetween popen pclose pchar = popen >>? manyCharsTill pchar pclose
let anyStringBetween popen pclose = manyCharsBetween popen pclose anyChar

// Cannot be a reserved keyword.
let stringIdentifier : Parser<string, unit> =
let unquotedIdentifier : Parser<string, unit> =
let isIdentifierFirstChar token = isLetter token
let isIdentifierChar token = isLetter token || isDigit token || token = '.' || token = '_'
many1Satisfy2L isIdentifierFirstChar isIdentifierChar "identifier" .>> spaces
let isIdentifierChar token = isLetter token || isDigit token || token = '_'

many1Satisfy2L isIdentifierFirstChar isIdentifierChar "identifier" .>> spacesOrComment
>>= fun identifier ->
if List.contains (identifier.ToUpper()) reserved
then fail (sprintf "Identifier %s is a reserved keyword" identifier)
else preturn identifier

// Can be a reserved keyword.
let quotedIdentifier : Parser<string, unit> =
(skipChar '\"' |> anyStringBetween <| skipChar '\"')
(skipChar '\"' |> anyStringBetween <| skipChar '\"') .>> spacesOrComment

let simpleIdentifier =
let stringIdentifier =
quotedIdentifier
<|> stringIdentifier
<|> unquotedIdentifier

let simpleIdentifier =
attempt(
stringIdentifier >>= fun schema ->
text "." >>. stringIdentifier >>= fun table ->
text "." >>. stringIdentifier >>= fun column ->
preturn (sprintf "%s.%s.%s" schema table column))
<|>
attempt(
stringIdentifier >>= fun table ->
text "." >>. stringIdentifier >>= fun column ->
preturn (sprintf "%s.%s" table column))
<|>
attempt stringIdentifier

let identifier : Parser<Expr, unit> =
simpleIdentifier |>> Expr.Ident
Expand All @@ -114,7 +129,7 @@ let parameter : Parser<Expr, unit> =
|>> Expr.Parameter

let text value : Parser<string, unit> =
(optional spaces) >>. pstringCI value .>> (optional spaces)
spaces >>. pstringCI value .>> spacesOrComment

let star : Parser<Expr, unit> =
text "*" |>> fun _ -> Expr.Star
Expand All @@ -128,11 +143,11 @@ let parens parser = between (text "(") (text ")") parser
let comma = text ","

let integer : Parser<Expr, unit> =
(optional spaces) >>. pint32 .>> (optional spaces)
spaces >>. pint32 .>> spacesOrComment
|>> Expr.Integer

let number : Parser<Expr, unit> =
(optional spaces) >>. pfloat .>> (optional spaces)
spaces >>. pfloat .>> spacesOrComment
|>> Expr.Float

let boolean : Parser<Expr, unit> =
Expand Down Expand Up @@ -246,14 +261,14 @@ let optionalHavingClause = optionalExpr (text "HAVING" >>. expr)
let optionalFrom =
optionalExpr (
attempt (
text "FROM " >>. (parens selectQuery) >>= fun subQuery ->
text "FROM" >>. (parens selectQuery) >>= fun subQuery ->
optional (text "AS") >>= fun _ ->
simpleIdentifier >>= fun alias ->
preturn (Expr.As(subQuery, Expr.Ident alias))
)
<|>
attempt (
text "FROM " >>. simpleIdentifier >>= fun table ->
text "FROM" >>. simpleIdentifier >>= fun table ->
optional (text "AS") >>= fun _ ->
simpleIdentifier >>= fun alias ->
preturn (Expr.As(Expr.Ident table, Expr.Ident alias))
Expand Down Expand Up @@ -350,11 +365,20 @@ let updateQuery =

preturn (Expr.UpdateQuery query)

opp.AddOperator(InfixOperator("AND", spaces, 7, Associativity.Left, fun left right -> Expr.And(left, right)))
opp.AddOperator(InfixOperator("AS", spaces, 6, Associativity.Left, fun left right -> Expr.As(left, right)))
opp.AddOperator(InfixOperator("as", spaces, 6, Associativity.Left, fun left right -> Expr.As(left, right)))
let spacesOrComment =
let comment = skipString "/*" >>. (charsTillString "*/" true 8096)
let commentEol = skipString "--" >>. skipRestOfLine true

spaces .>>
optional comment .>>
optional commentEol .>>
spaces

opp.AddOperator(InfixOperator("AND", spacesOrComment, 7, Associativity.Left, fun left right -> Expr.And(left, right)))
opp.AddOperator(InfixOperator("AS", spacesOrComment, 6, Associativity.Left, fun left right -> Expr.As(left, right)))
opp.AddOperator(InfixOperator("as", spacesOrComment, 6, Associativity.Left, fun left right -> Expr.As(left, right)))
opp.AddOperator(InfixOperator("OR", notFollowedBy (text "DER BY"), 6, Associativity.Left, fun left right -> Expr.Or(left, right)))
opp.AddOperator(InfixOperator("IN", spaces, 8, Associativity.Left, fun left right -> Expr.In(left, right)))
opp.AddOperator(InfixOperator("IN", spacesOrComment, 8, Associativity.Left, fun left right -> Expr.In(left, right)))
opp.AddOperator(InfixOperator(">", spaces, 9, Associativity.Left, fun left right -> Expr.GreaterThan(left, right)))
opp.AddOperator(InfixOperator("<", spaces, 9, Associativity.Left, fun left right -> Expr.LessThan(left, right)))
opp.AddOperator(InfixOperator("<=", spaces, 9, Associativity.Left, fun left right -> Expr.LessThanOrEqual(left, right)))
Expand All @@ -365,8 +389,8 @@ opp.AddOperator(InfixOperator("||", spaces, 9, Associativity.Left, fun left righ
opp.AddOperator(InfixOperator("::", spaces, 9, Associativity.Left, fun left right -> Expr.TypeCast(left, right)))
opp.AddOperator(InfixOperator("->>", spaces, 9, Associativity.Left, fun left right -> Expr.JsonIndex(left, right)))

opp.AddOperator(PostfixOperator("IS NULL", spaces, 8, false, fun value -> Expr.Equals(Expr.Null, value)))
opp.AddOperator(PostfixOperator("IS NOT NULL", spaces, 8, false, fun value -> Expr.Not(Expr.Equals(Expr.Null, value))))
opp.AddOperator(PostfixOperator("IS NULL", spacesOrComment, 8, false, fun value -> Expr.Equals(Expr.Null, value)))
opp.AddOperator(PostfixOperator("IS NOT NULL", spacesOrComment, 8, false, fun value -> Expr.Not(Expr.Equals(Expr.Null, value))))

opp.TermParser <- choice [
(attempt updateQuery)
Expand All @@ -384,7 +408,7 @@ opp.TermParser <- choice [
parameter
]

let fullParser = (optional spaces) >>. expr .>> (optional spaces <|> (text ";" |>> fun _ -> ()))
let fullParser = spacesOrComment >>. expr .>> (spacesOrComment <|> (text ";" |>> ignore))

let parse (input: string) : Result<Expr, string> =
match run fullParser input with
Expand Down
119 changes: 118 additions & 1 deletion tests/NpgsqlFSharpAnalyzer.Tests/ParseSelectTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ let selectQueryTests = testList "Parse SELECT tests" [

Offset = Some (Expr.Integer 100)
}

testSelect """
SELECT *
FROM (SELECT NOW()) AS time
Expand All @@ -440,4 +441,120 @@ let selectQueryTests = testList "Parse SELECT tests" [
}, Expr.Ident "time"))
Limit = Some (Expr.Integer 1)
}
]

testSelect """
SELECT *
FROM
(SELECT NOW()) time
LIMIT 1
""" {
SelectExpr.Default with
Columns = [Expr.Star]
From = Some (Expr.As (Expr.SelectQuery {
SelectExpr.Default with
Columns = [Expr.Function ("NOW", [])]
}, Expr.Ident "time"))
Limit = Some (Expr.Integer 1)
}

testSelect """
SELECT "username" FROM "users"
""" {
SelectExpr.Default with
Columns = [Expr.Ident "username"]
From = Expr.Ident "users" |> Some
}

testSelect """
SELECT "username" AS "name" FROM "users"
""" {
SelectExpr.Default with
Columns = [ Expr.As(Expr.Ident "username", Expr.Ident "name") ]
From = Expr.Ident "users" |> Some
}

testSelect """
SELECT "$Table"."timestamp" FROM "$Table"
""" {
SelectExpr.Default with
Columns = [ Expr.Ident("$Table.timestamp") ]
From = Expr.Ident "$Table" |> Some
}

testSelect """
SELECT "$Table".timestamp AS ts FROM "$Table"
""" {
SelectExpr.Default with
Columns = [ Expr.As(Expr.Ident("$Table.timestamp"), Expr.Ident("ts")) ]
From = Expr.Ident "$Table" |> Some
}

testSelect """
SELECT public."$Table"."timestamp" FROM "$Table"
""" {
SelectExpr.Default with
Columns = [ Expr.Ident("public.$Table.timestamp") ]
From = Expr.Ident "$Table" |> Some
}

testSelect """
SELECT public."$Table"."timestamp" FROM "$Table" AS tbl
""" {
SelectExpr.Default with
Columns = [ Expr.Ident("public.$Table.timestamp") ]
From = Expr.As(Expr.Ident "$Table", Expr.Ident "tbl") |> Some
}

testSelect """
SELECT public."$Table"."timestamp" FROM "$Table" tbl LIMIT 100
""" {
SelectExpr.Default with
Columns = [ Expr.Ident("public.$Table.timestamp") ]
From = Expr.As(Expr.Ident "$Table", Expr.Ident "tbl") |> Some
Limit = Some (Expr.Integer 100)
}

testSelect "SELECT 1 -- This is a comment" {
SelectExpr.Default with Columns = [Expr.Integer 1]
}

testSelect """
-- This is a comment
SELECT 1""" {
SelectExpr.Default with Columns = [Expr.Integer 1]
}

testSelect """
/* Comment inserted first */
SELECT 1""" {
SelectExpr.Default with Columns = [Expr.Integer 1]
}

testSelect """
SELECT 1
/* Comment inserted last */
""" {
SelectExpr.Default with Columns = [Expr.Integer 1]
}

testSelect """
SELECT 1;
/* Comment inserted after semicolon */
""" {
SelectExpr.Default with Columns = [Expr.Integer 1]
}

testSelect """
SELECT * /* Comment inserted here */
FROM -- Ignore
(SELECT NOW()) time /* Comment inserted here */
LIMIT 1 -- Ignore
""" {
SelectExpr.Default with
Columns = [Expr.Star]
From = Some (Expr.As (Expr.SelectQuery {
SelectExpr.Default with
Columns = [Expr.Function ("NOW", [])]
}, Expr.Ident "time"))
Limit = Some (Expr.Integer 1)
}]

0 comments on commit 89f1b78

Please sign in to comment.