Skip to content

Commit

Permalink
initial parsing of INSERT and DELETE queries and string literals
Browse files Browse the repository at this point in the history
  • Loading branch information
Zaid-Ajaj committed Aug 21, 2020
1 parent b8a3d5b commit 9209f41
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 36 deletions.
65 changes: 61 additions & 4 deletions src/NpgsqlFSharpParser/Parser.fs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,20 @@ let boolean : Parser<Expr, unit> =
(text "true" |>> fun _ -> Expr.Boolean true)
<|> (text "false" |>> fun _ -> Expr.Boolean false)

// Applies popen, then pchar repeatedly until pclose succeeds,
// returns the string in the middle
let manyCharsBetween popen pclose pchar = popen >>? manyCharsTill pchar pclose

// Parses any string between popen and pclose
let anyStringBetween popen pclose = manyCharsBetween popen pclose anyChar

// Parses any string between double quotes
let quotedString = skipChar '\'' |> anyStringBetween <| skipChar '\''

let stringLiteral : Parser<Expr, unit> =
quotedString
|>> Expr.StringLiteral

let commaSeparatedExprs = sepBy expr comma

let selections =
Expand Down Expand Up @@ -122,6 +136,12 @@ let optionalOrderingExpr =
| Some exprs -> exprs
| None -> [ ]

let optionalRetuningExpr =
optionalExpr (text "RETURNING " >>. selections)
|>> function
| Some exprs -> exprs
| None -> [ ]

let optionalDistinct =
optional (attempt (text "DISTINCT ON") <|> attempt (text "DISTINCT"))

Expand Down Expand Up @@ -154,7 +174,7 @@ let selectQuery =
optionalHavingClause >>= fun havingExpr ->
optionalOrderingExpr >>= fun orderingExprs ->
optionalLimit >>= fun limitExpr ->
optionalOffset |>> fun offsetExpr ->
optionalOffset >>= fun offsetExpr ->
let query =
{ SelectExpr.Default with
Columns = selections
Expand All @@ -167,7 +187,36 @@ let selectQuery =
Limit = limitExpr
Offset = offsetExpr }

Expr.Query (TopLevelExpr.Select query)
preturn (Expr.SelectQuery query)

let deleteQuery =
text "DELETE FROM " >>. simpleIdentifier >>= fun tableName ->
optionalWhereClause >>= fun where ->
optionalRetuningExpr >>= fun returningExpr ->
let query = {
DeleteExpr.Default with
Table = tableName
Where = where
Returning = returningExpr
}

preturn (Expr.DeleteQuery query)

let insertQuery =
text "INSERT INTO " >>. simpleIdentifier >>= fun tableName ->
(parens (sepBy1 simpleIdentifier comma)) >>= fun columns ->
text "VALUES" >>= fun _ ->
(parens (sepBy1 expr comma)) >>= fun values ->
optionalRetuningExpr >>= fun returningExpr ->
let query = {
InsertExpr.Default with
Table = tableName
Columns = columns
Values = values
Returning = returningExpr
}

preturn (Expr.InsertQuery query)

opp.AddOperator(InfixOperator("AND", spaces, 7, Associativity.Left, fun left right -> Expr.And(left, right)))
opp.AddOperator(InfixOperator("OR", notFollowedBy (text "DER BY"), 6, Associativity.Left, fun left right -> Expr.Or(left, right)))
Expand All @@ -182,14 +231,17 @@ opp.AddOperator(PostfixOperator("IS NULL", spaces, 8, false, fun value -> Expr.E
opp.AddOperator(PostfixOperator("IS NOT NULL", spaces, 8, false, fun value -> Expr.Not(Expr.Equals(Expr.Null, value))))

opp.TermParser <- choice [
(attempt insertQuery)
(attempt deleteQuery)
(attempt selectQuery)
(attempt functionExpr)
(text "(") >>. expr .>> (text ")")
star
parameter
identifier
integer
boolean
stringLiteral
identifier
parameter
]

let fullParser = (optional spaces) >>. expr .>> (optional spaces <|> (text ";" |>> fun _ -> ()))
Expand All @@ -198,3 +250,8 @@ let parse (input: string) : Result<Expr, string> =
match run fullParser input with
| Success(result,_,_) -> Result.Ok result
| Failure(errMsg,_,_) -> Result.Error errMsg

let parseUnsafe query =
match parse query with
| Result.Ok output -> output
| Result.Error errorMsg -> failwith errorMsg
38 changes: 25 additions & 13 deletions src/NpgsqlFSharpParser/Types.fs
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
namespace rec NpgsqlFSharpParser

[<RequireQualifiedAccess>]
type TopLevelExpr =
| Select of SelectExpr
| Insert of InsertExpr
| Delete of DeleteExpr
| Update of UpdateExpr

[<RequireQualifiedAccess>]
type Expr =
| Star
| Ident of string
| Parameter of string
| Boolean of bool
| StringLiteral of string
| Integer of int
| Float of float
| Null
Expand All @@ -27,7 +21,9 @@ type Expr =
| GreaterThanOrEqual of left:Expr * right:Expr
| LessThanOrEqual of left:Expr * right:Expr
| Between of value:Expr * leftBound:Expr * rightBound:Expr
| Query of expr:TopLevelExpr
| SelectQuery of expr:SelectExpr
| DeleteQuery of expr:DeleteExpr
| InsertQuery of expr: InsertExpr

type Ordering =
| Asc of columnName:string
Expand Down Expand Up @@ -69,20 +65,36 @@ type SelectExpr = {

type UpdateExpr = {
Table : string
Assignments : Map<string, Expr list>
ConflictResolution : Map<string, Expr list>
Where : Expr option
Assignments : (string * Expr) list
ConflictResolution : (string * Expr) list
Returning : Expr list
}

type DeleteExpr = {
Table : string
Where : Expr option
}
Returning : Expr list
} with
static member Default =
{
Table = "";
Where = None
Returning = [ ]
}

type InsertExpr = {
Table: string
Columns : string list
Values : Expr list
ConflictResolution : Map<string, Expr list>
ConflictResolution : (string * Expr) list
Returning : Expr list
}
} with
static member Default =
{
Table = "";
Columns = [ ]
Values = [ ]
ConflictResolution = [ ]
Returning = [ ]
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<Compile Include="ParserTests.fs" />
<Compile Include="ParseInsertTests.fs" />
<Compile Include="ParseDeleteTests.fs" />
<Compile Include="ParseSelectTests.fs" />
<Compile Include="AssemblyInfo.fs" />
<Compile Include="Analyzer.fs" />
<Compile Include="Tests.fs" />
Expand Down
42 changes: 42 additions & 0 deletions tests/NpgsqlFSharpAnalyzer.Tests/ParseDeleteTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module ParserDeleteTests

open Expecto
open NpgsqlFSharpParser

let testDelete inputQuery expected =
test inputQuery {
match Parser.parse inputQuery with
| Ok (Expr.DeleteQuery query) ->
Expect.equal query expected "The query is parsed correctly"
| Ok somethingElse ->
failwithf "Unexpected delete statement %A" somethingElse
| Error errorMsg ->
failwith errorMsg
}

let ftestDelete inputQuery expected =
ftest inputQuery {
match Parser.parse inputQuery with
| Ok (Expr.DeleteQuery query) ->
Expect.equal query expected "The query is parsed correctly"
| Ok somethingElse ->
failwithf "Unexpected delete statement %A" somethingElse
| Error errorMsg ->
failwith errorMsg
}

[<Tests>]
let deleteQueryTests = testList "Parse DELETE tests" [
testDelete "DELETE FROM users WHERE last_login IS NULL" {
DeleteExpr.Default with
Table = "users"
Where = Some (Expr.Equals(Expr.Null, Expr.Ident "last_login"))
}

testDelete "DELETE FROM users WHERE luck = 'bad' RETURNING *" {
DeleteExpr.Default with
Table = "users"
Where = Some (Expr.Equals(Expr.Ident "luck", Expr.StringLiteral "bad"))
Returning = [Expr.Star]
}
]
44 changes: 44 additions & 0 deletions tests/NpgsqlFSharpAnalyzer.Tests/ParseInsertTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
module ParseInsertTests

open Expecto
open NpgsqlFSharpParser

let testInsert inputQuery expected =
test inputQuery {
match Parser.parse inputQuery with
| Ok (Expr.InsertQuery query) ->
Expect.equal query expected "The query is parsed correctly"
| Ok somethingElse ->
failwithf "Unexpected insert statement %A" somethingElse
| Error errorMsg ->
failwith errorMsg
}

let ftestInsert inputQuery expected =
ftest inputQuery {
match Parser.parse inputQuery with
| Ok (Expr.InsertQuery query) ->
Expect.equal query expected "The query is parsed correctly"
| Ok somethingElse ->
failwithf "Unexpected insert statement %A" somethingElse
| Error errorMsg ->
failwith errorMsg
}

[<Tests>]
let insertQueryTests = testList "Parse INSERT queries" [
testInsert "INSERT INTO users (username, active) VALUES (@username, true)" {
InsertExpr.Default with
Table = "users"
Columns = ["username"; "active"]
Values = [ Expr.Parameter("@username"); Expr.Boolean true ]
}

testInsert "INSERT INTO users (username, active) VALUES (@username, true) RETURNING *" {
InsertExpr.Default with
Table = "users"
Columns = ["username"; "active"]
Values = [ Expr.Parameter("@username"); Expr.Boolean true ]
Returning = [Expr.Star]
}
]
Loading

0 comments on commit 9209f41

Please sign in to comment.