Skip to content

Commit

Permalink
v3.15 analyzing transaction parameter sets and allowing for literal q…
Browse files Browse the repository at this point in the history
…ueries
  • Loading branch information
Zaid-Ajaj committed Sep 15, 2020
1 parent b8f1eb5 commit 9ada119
Show file tree
Hide file tree
Showing 17 changed files with 498 additions and 86 deletions.
4 changes: 4 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### 3.15.0 - 2020-09-15
* Analyze transaction parameter sets
* Allow for literal queries on transactions

### 3.14.0 - 2020-09-07
* Analyze transaction queries

Expand Down
20 changes: 10 additions & 10 deletions src/FParsec/AssemblyInfo.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ open System.Reflection

[<assembly: AssemblyTitleAttribute("FParsec")>]
[<assembly: AssemblyProductAttribute("NpgsqlFSharpAnalyzer")>]
[<assembly: AssemblyVersionAttribute("3.14.0")>]
[<assembly: AssemblyMetadataAttribute("ReleaseDate","2020-09-07T00:00:00.0000000")>]
[<assembly: AssemblyFileVersionAttribute("3.14.0")>]
[<assembly: AssemblyInformationalVersionAttribute("3.14.0")>]
[<assembly: AssemblyVersionAttribute("3.15.0")>]
[<assembly: AssemblyMetadataAttribute("ReleaseDate","2020-09-15T00:00:00.0000000")>]
[<assembly: AssemblyFileVersionAttribute("3.15.0")>]
[<assembly: AssemblyInformationalVersionAttribute("3.15.0")>]
[<assembly: AssemblyMetadataAttribute("ReleaseChannel","release")>]
[<assembly: AssemblyMetadataAttribute("GitHash","0da6fb1b32c023b1bfa7698bf8cd916fc3077010")>]
[<assembly: AssemblyMetadataAttribute("GitHash","b8f1eb5700564f78feb13f98df1d1228d34e4dda")>]
do ()

module internal AssemblyVersionInformation =
let [<Literal>] AssemblyTitle = "FParsec"
let [<Literal>] AssemblyProduct = "NpgsqlFSharpAnalyzer"
let [<Literal>] AssemblyVersion = "3.14.0"
let [<Literal>] AssemblyMetadata_ReleaseDate = "2020-09-07T00:00:00.0000000"
let [<Literal>] AssemblyFileVersion = "3.14.0"
let [<Literal>] AssemblyInformationalVersion = "3.14.0"
let [<Literal>] AssemblyVersion = "3.15.0"
let [<Literal>] AssemblyMetadata_ReleaseDate = "2020-09-15T00:00:00.0000000"
let [<Literal>] AssemblyFileVersion = "3.15.0"
let [<Literal>] AssemblyInformationalVersion = "3.15.0"
let [<Literal>] AssemblyMetadata_ReleaseChannel = "release"
let [<Literal>] AssemblyMetadata_GitHash = "0da6fb1b32c023b1bfa7698bf8cd916fc3077010"
let [<Literal>] AssemblyMetadata_GitHash = "b8f1eb5700564f78feb13f98df1d1228d34e4dda"
20 changes: 10 additions & 10 deletions src/NpgsqlFSharpAnalyzer.Core/AssemblyInfo.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ open System.Reflection

[<assembly: AssemblyTitleAttribute("NpgsqlFSharpAnalyzer.Core")>]
[<assembly: AssemblyProductAttribute("NpgsqlFSharpAnalyzer")>]
[<assembly: AssemblyVersionAttribute("3.14.0")>]
[<assembly: AssemblyMetadataAttribute("ReleaseDate","2020-09-07T00:00:00.0000000")>]
[<assembly: AssemblyFileVersionAttribute("3.14.0")>]
[<assembly: AssemblyInformationalVersionAttribute("3.14.0")>]
[<assembly: AssemblyVersionAttribute("3.15.0")>]
[<assembly: AssemblyMetadataAttribute("ReleaseDate","2020-09-15T00:00:00.0000000")>]
[<assembly: AssemblyFileVersionAttribute("3.15.0")>]
[<assembly: AssemblyInformationalVersionAttribute("3.15.0")>]
[<assembly: AssemblyMetadataAttribute("ReleaseChannel","release")>]
[<assembly: AssemblyMetadataAttribute("GitHash","0da6fb1b32c023b1bfa7698bf8cd916fc3077010")>]
[<assembly: AssemblyMetadataAttribute("GitHash","b8f1eb5700564f78feb13f98df1d1228d34e4dda")>]
do ()

module internal AssemblyVersionInformation =
let [<Literal>] AssemblyTitle = "NpgsqlFSharpAnalyzer.Core"
let [<Literal>] AssemblyProduct = "NpgsqlFSharpAnalyzer"
let [<Literal>] AssemblyVersion = "3.14.0"
let [<Literal>] AssemblyMetadata_ReleaseDate = "2020-09-07T00:00:00.0000000"
let [<Literal>] AssemblyFileVersion = "3.14.0"
let [<Literal>] AssemblyInformationalVersion = "3.14.0"
let [<Literal>] AssemblyVersion = "3.15.0"
let [<Literal>] AssemblyMetadata_ReleaseDate = "2020-09-15T00:00:00.0000000"
let [<Literal>] AssemblyFileVersion = "3.15.0"
let [<Literal>] AssemblyInformationalVersion = "3.15.0"
let [<Literal>] AssemblyMetadata_ReleaseChannel = "release"
let [<Literal>] AssemblyMetadata_GitHash = "0da6fb1b32c023b1bfa7698bf8cd916fc3077010"
let [<Literal>] AssemblyMetadata_GitHash = "b8f1eb5700564f78feb13f98df1d1228d34e4dda"
57 changes: 39 additions & 18 deletions src/NpgsqlFSharpAnalyzer.Core/SqlAnalysis.fs
Original file line number Diff line number Diff line change
Expand Up @@ -389,38 +389,38 @@ module SqlAnalysis =
|> List.tryFind (function | SqlAnalyzerBlock.ReadingColumns(attempts) -> true | _ -> false)
|> Option.map(function | SqlAnalyzerBlock.ReadingColumns(attempts) -> attempts | _ -> failwith "should not happen")

let analyzeParameters (operation: SqlOperation) (requiredParameters: InformationSchema.Parameter list) =
match findParameters operation with
let analyzeParameters parameters parametersRange (requiredParameters: InformationSchema.Parameter list) =
match parameters with
| None ->
if not (List.isEmpty requiredParameters) then
let missingParameters =
requiredParameters
|> List.map (fun p -> sprintf "%s:%s" p.Name p.DataType.Name)
|> String.concat ", "
|> sprintf "Missing parameters [%s]. Please use Sql.parameters to provide them."
[ createWarning missingParameters operation.range ]
[ createWarning missingParameters parametersRange ]
else
[ ]

| Some (queryParams, queryParamsRange) ->
if List.isEmpty requiredParameters then
[ createWarning "Provided parameters are redundant. Sql query is not parameterized" operation.range ]
[ createWarning "Provided parameters are redundant. Sql query is not parameterized" parametersRange ]
else

[
/// None of the required parameters have the name of this provided parameter
let isUnknown (parameter: UsedParameter) =
requiredParameters
|> List.forall (fun requiredParam -> parameter.name <> requiredParam.Name)
/// None of the required parameters have the name of this provided parameter
let isUnknown (parameter: UsedParameter) =
requiredParameters
|> List.forall (fun requiredParam -> parameter.name <> requiredParam.Name)

let isRedundant (parameter: UsedParameter) =
// every required parameter has a corresponding provided query parameter
let requirementSatisfied =
requiredParameters
|> List.forall (fun requiredParam -> queryParams |> List.exists (fun queryParam -> queryParam.name = requiredParam.Name))
let isRedundant (parameter: UsedParameter) =
// every required parameter has a corresponding provided query parameter
let requirementSatisfied =
requiredParameters
|> List.forall (fun requiredParam -> queryParams |> List.exists (fun queryParam -> queryParam.name = requiredParam.Name))

requirementSatisfied && isUnknown parameter
requirementSatisfied && isUnknown parameter

[
for requiredParameter in requiredParameters do
if not (queryParams |> List.exists (fun providedParam -> providedParam.name = requiredParameter.Name))
then
Expand Down Expand Up @@ -956,8 +956,29 @@ module SqlAnalysis =
let queryRange = transaction.queryRange
let queryAnalysis = extractParametersAndOutputColumns(connectionString, query, schema)
match queryAnalysis with
| Result.Error queryError -> errors.Add(createWarning queryError queryRange)
| _ -> ()
| Result.Error queryError ->
errors.Add(createWarning queryError queryRange)
| Result.Ok (parameters, outputColunms, errorMessage) ->
match errorMessage with
| None -> ()
| Some message -> errors.Add(createWarning message queryRange)

if List.isEmpty transaction.parameterSets && not (List.isEmpty parameters) then
errors.Add (createWarning "Transaction query is parameterized but the parameter sets are empty" transaction.queryRange)
else
for parameterSet in transaction.parameterSets do
let transactionParams = Some (parameterSet.parameters, parameterSet.range)
if not (List.isEmpty parameters) && List.isEmpty parameterSet.parameters then
let missingParameters =
parameters
|> List.map (fun p -> sprintf "%s:%s" p.Name p.DataType.Name)
|> String.concat ", "
|> sprintf "Missing parameters [%s] for this parameter set"

errors.Add(createWarning missingParameters parameterSet.range)
else
for error in analyzeParameters transactionParams parameterSet.range parameters do
errors.Add(error)

Seq.toList errors

Expand All @@ -975,6 +996,6 @@ module SqlAnalysis =
let readingAttempts = defaultArg (findColumnReadAttempts operation) [ ]
[
yield! potentialInsertQueryError
yield! analyzeParameters operation parameters
yield! analyzeParameters (findParameters operation) operation.range parameters
yield! analyzeColumnReadingAttempts readingAttempts outputColunms
]
167 changes: 164 additions & 3 deletions src/NpgsqlFSharpAnalyzer.Core/SyntacticAnalysis.fs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ module SyntacticAnalysis =
| _ ->
[ ]

let rec flattenList = function
| SynExpr.Sequential(_debugSeqPoint, isTrueSeq, expr1, expr2, seqRange) ->
[ yield! flattenList expr1; yield! flattenList expr2 ]
| expr ->
[ expr ]

let (|SqlParameters|_|) = function
| Apply ("Sql.parameters", SynExpr.ArrayOrListOfSeqExpr(isArray, listExpr, listRange) , funcRange, appRange) ->
match listExpr with
Expand All @@ -88,9 +94,152 @@ module SyntacticAnalysis =
| _ ->
None

let readParameterSets parameterSetsExpr =
let sets = ResizeArray<ParameterSet>()

match parameterSetsExpr with
| SynExpr.ArrayOrListOfSeqExpr(isArray, listExpr, listRange) ->
match listExpr with
| SynExpr.CompExpr(isArrayOfList, isNotNakedRefCell, outerListExpr, outerListRange) ->
match outerListExpr with
| SynExpr.ForEach(_, _, _, _, enumExpr, bodyExpr, forEachRange) ->
match bodyExpr with
| SynExpr.YieldOrReturn(_, outputExpr, outputExprRange) ->
match outputExpr with
| SynExpr.ArrayOrListOfSeqExpr(isArray, parameterListExpr, parameterListRange) ->
let parameterSet = {
range = parameterListRange
parameters = [ ]
}

match parameterListExpr with
| SynExpr.CompExpr(isArrayOfList, isNotNakedRefCell, compExpr, compRange) ->
let parameters : UsedParameter list = [
for expr in flattenList compExpr do
match expr with
| ParameterTuple(name, range, func, funcRange, appRange) ->
{
paramFunc = func
paramFuncRange = funcRange
name = name.TrimStart '@'
range = range
applicationRange = appRange
}
| _ ->
()
]

sets.Add { parameterSet with parameters = parameters }

| _ ->
()

| SynExpr.ArrayOrList(isList, expressions, range) ->
let parameters : UsedParameter list = [
for expr in expressions do
match expr with
| ParameterTuple(name, range, func, funcRange, appRange) ->
{
paramFunc = func
paramFuncRange = funcRange
name = name.TrimStart '@'
range = range
applicationRange = appRange
}
| _ ->
()
]

sets.Add {
range = range
parameters = parameters
}

| _ ->
()
| _ ->
()
| _ ->
let parameterSets = flattenList outerListExpr

for parameterSetExpr in parameterSets do
match parameterSetExpr with
| SynExpr.ArrayOrListOfSeqExpr(isArray, parameterListExpr, parameterListRange) ->
let parameterSet = {
range = parameterListRange
parameters = [ ]
}

match parameterListExpr with
| SynExpr.CompExpr(isArrayOfList, isNotNakedRefCell, compExpr, compRange) ->
let parameters : UsedParameter list = [
for expr in flattenList compExpr do
match expr with
| ParameterTuple(name, range, func, funcRange, appRange) ->
{
paramFunc = func
paramFuncRange = funcRange
name = name.TrimStart '@'
range = range
applicationRange = appRange
}
| _ ->
()
]

sets.Add { parameterSet with parameters = parameters }

| _ ->
()

| SynExpr.ArrayOrList(isList, expressions, range) ->
let parameters : UsedParameter list = [
for expr in expressions do
match expr with
| ParameterTuple(name, range, func, funcRange, appRange) ->
{
paramFunc = func
paramFuncRange = funcRange
name = name.TrimStart '@'
range = range
applicationRange = appRange
}
| _ ->
()
]

sets.Add {
range = range
parameters = parameters
}

| _ ->
()
| _ ->
()

| _ -> ()

Seq.toList sets

let (|TransactionQuery|_|) = function
| SynExpr.Tuple(isStruct, [ SynExpr.Const(SynConst.String(query, queryRange), constRange); secondItem ], commaRange, tupleRange) ->
Some { query = query; queryRange = queryRange }
| SynExpr.Tuple(isStruct, [ SynExpr.Const(SynConst.String(query, queryRange), constRange); parameterSetsExpr ], commaRange, tupleRange) ->
let transaction = {
query = query;
queryRange = queryRange
parameterSets = readParameterSets parameterSetsExpr
}

Some transaction

| SynExpr.Tuple(isStruct, [ SynExpr.Ident value; parameterSetsExpr ], commaRange, tupleRange) ->
let transaction = {
query = value.idText;
queryRange = value.idRange
parameterSets = readParameterSets parameterSetsExpr
}

Some transaction
| _ ->
None

Expand Down Expand Up @@ -384,7 +533,6 @@ module SyntacticAnalysis =

| Apply(("Sql.execute"|"Sql.executeAsync"|"Sql.executeRow"|"Sql.executeRowAsync"|"Sql.iter"|"Sql.iterAsync"), lambdaExpr, funcRange, appRange) ->
let columns = findReadColumnAttempts lambdaExpr
let x = 1
let blocks = [
yield! findQuery funcExpr
yield! findParameters funcExpr
Expand Down Expand Up @@ -518,6 +666,18 @@ module SyntacticAnalysis =
match literals.TryFind identifier with
| Some literalQuery -> Some (SqlAnalyzerBlock.Query(literalQuery, range))
| None -> None

| SqlAnalyzerBlock.Transaction queries ->
let modifiedQueries =
queries
|> List.map (fun transactionQuery ->
match literals.TryFind transactionQuery.query with
| Some literalQuery -> { transactionQuery with query = literalQuery }
| None -> transactionQuery
)

Some (SqlAnalyzerBlock.Transaction modifiedQueries)

| differentBlock ->
Some differentBlock)

Expand Down Expand Up @@ -575,6 +735,7 @@ module SyntacticAnalysis =

| SynModuleDecl.Types(definitions, range) ->
iterTypeDefs definitions

| _ ->
()

Expand Down
6 changes: 6 additions & 0 deletions src/NpgsqlFSharpAnalyzer.Core/Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,15 @@ type UsedParameter = {
applicationRange : range option
}

type ParameterSet = {
parameters : UsedParameter list
range : range
}

type TransactionQuery = {
query: string
queryRange : range
parameterSets : ParameterSet list
}

[<RequireQualifiedAccess>]
Expand Down
Loading

0 comments on commit 9ada119

Please sign in to comment.