diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 3441881..d9da019 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,6 @@ +### 3.14.0 - 2020-09-07 +* Analyze transaction queries + ### 3.13.0 - 2020-09-04 * The ability to suppress warning messages generated by the analyzer diff --git a/src/FParsec/AssemblyInfo.fs b/src/FParsec/AssemblyInfo.fs index 0615425..c073fb5 100644 --- a/src/FParsec/AssemblyInfo.fs +++ b/src/FParsec/AssemblyInfo.fs @@ -4,20 +4,20 @@ open System.Reflection [] [] -[] -[] -[] -[] +[] +[] +[] +[] [] -[] +[] do () module internal AssemblyVersionInformation = let [] AssemblyTitle = "FParsec" let [] AssemblyProduct = "NpgsqlFSharpAnalyzer" - let [] AssemblyVersion = "3.13.0" - let [] AssemblyMetadata_ReleaseDate = "2020-09-04T00:00:00.0000000" - let [] AssemblyFileVersion = "3.13.0" - let [] AssemblyInformationalVersion = "3.13.0" + let [] AssemblyVersion = "3.14.0" + let [] AssemblyMetadata_ReleaseDate = "2020-09-07T00:00:00.0000000" + let [] AssemblyFileVersion = "3.14.0" + let [] AssemblyInformationalVersion = "3.14.0" let [] AssemblyMetadata_ReleaseChannel = "release" - let [] AssemblyMetadata_GitHash = "8510b403b2b0a20a3207cc5c7637d8406f2aaee5" + let [] AssemblyMetadata_GitHash = "0da6fb1b32c023b1bfa7698bf8cd916fc3077010" diff --git a/src/NpgsqlFSharpAnalyzer.Core/AssemblyInfo.fs b/src/NpgsqlFSharpAnalyzer.Core/AssemblyInfo.fs index dba4fbb..a0a22f0 100644 --- a/src/NpgsqlFSharpAnalyzer.Core/AssemblyInfo.fs +++ b/src/NpgsqlFSharpAnalyzer.Core/AssemblyInfo.fs @@ -4,20 +4,20 @@ open System.Reflection [] [] -[] -[] -[] -[] +[] +[] +[] +[] [] -[] +[] do () module internal AssemblyVersionInformation = let [] AssemblyTitle = "NpgsqlFSharpAnalyzer.Core" let [] AssemblyProduct = "NpgsqlFSharpAnalyzer" - let [] AssemblyVersion = "3.13.0" - let [] AssemblyMetadata_ReleaseDate = "2020-09-04T00:00:00.0000000" - let [] AssemblyFileVersion = "3.13.0" - let [] AssemblyInformationalVersion = "3.13.0" + let [] AssemblyVersion = "3.14.0" + let [] AssemblyMetadata_ReleaseDate = "2020-09-07T00:00:00.0000000" + let [] AssemblyFileVersion = "3.14.0" + let [] AssemblyInformationalVersion = "3.14.0" let [] AssemblyMetadata_ReleaseChannel = "release" - let [] AssemblyMetadata_GitHash = "8510b403b2b0a20a3207cc5c7637d8406f2aaee5" + let [] AssemblyMetadata_GitHash = "0da6fb1b32c023b1bfa7698bf8cd916fc3077010" diff --git a/src/NpgsqlFSharpAnalyzer.Core/SqlAnalysis.fs b/src/NpgsqlFSharpAnalyzer.Core/SqlAnalysis.fs index 143415e..f8033d4 100644 --- a/src/NpgsqlFSharpAnalyzer.Core/SqlAnalysis.fs +++ b/src/NpgsqlFSharpAnalyzer.Core/SqlAnalysis.fs @@ -370,6 +370,15 @@ module SqlAnalysis = |> List.tryFind (function | SqlAnalyzerBlock.Query(query, range) -> true | _ -> false) |> Option.map(function | SqlAnalyzerBlock.Query(query, range) -> (query, range) | _ -> failwith "should not happen") + let findTransactions (operation: SqlOperation) = + operation.blocks + |> List.tryFind (function | SqlAnalyzerBlock.Transaction queries -> true | _ -> false) + |> Option.map(function + | SqlAnalyzerBlock.Transaction queries -> queries + | _ -> failwith "should not happen" + ) + + let findParameters (operation: SqlOperation) = operation.blocks |> List.tryFind (function | SqlAnalyzerBlock.Parameters(parameters, range) -> true | _ -> false) @@ -933,24 +942,39 @@ module SqlAnalysis = operation.blocks |> List.exists (fun block -> block = SqlAnalyzerBlock.SkipAnalysis) - if skipAnalysis then [ ] - else match findQuery operation with - | None -> + if skipAnalysis then [ ] - | Some (query, queryRange) -> - let queryAnalysis = extractParametersAndOutputColumns(connectionString, query, schema) - match queryAnalysis with - | Result.Error queryError -> - [ createWarning queryError queryRange ] - | Result.Ok (parameters, outputColunms, errorMessage) -> - let potentialInsertQueryError = - match errorMessage with - | None -> [ ] - | Some message -> [ createWarning message queryRange ] - - let readingAttempts = defaultArg (findColumnReadAttempts operation) [ ] - [ - yield! potentialInsertQueryError - yield! analyzeParameters operation parameters - yield! analyzeColumnReadingAttempts readingAttempts outputColunms - ] + else + match findQuery operation with + | None -> + match findTransactions operation with + | None -> [ ] + | Some transactionQueries -> + let errors = ResizeArray() + for transaction in transactionQueries do + let query = transaction.query + let queryRange = transaction.queryRange + let queryAnalysis = extractParametersAndOutputColumns(connectionString, query, schema) + match queryAnalysis with + | Result.Error queryError -> errors.Add(createWarning queryError queryRange) + | _ -> () + + Seq.toList errors + + | Some (query, queryRange) -> + let queryAnalysis = extractParametersAndOutputColumns(connectionString, query, schema) + match queryAnalysis with + | Result.Error queryError -> + [ createWarning queryError queryRange ] + | Result.Ok (parameters, outputColunms, errorMessage) -> + let potentialInsertQueryError = + match errorMessage with + | None -> [ ] + | Some message -> [ createWarning message queryRange ] + + let readingAttempts = defaultArg (findColumnReadAttempts operation) [ ] + [ + yield! potentialInsertQueryError + yield! analyzeParameters operation parameters + yield! analyzeColumnReadingAttempts readingAttempts outputColunms + ] diff --git a/src/NpgsqlFSharpAnalyzer.Core/SyntacticAnalysis.fs b/src/NpgsqlFSharpAnalyzer.Core/SyntacticAnalysis.fs index 01fa210..912d178 100644 --- a/src/NpgsqlFSharpAnalyzer.Core/SyntacticAnalysis.fs +++ b/src/NpgsqlFSharpAnalyzer.Core/SyntacticAnalysis.fs @@ -88,6 +88,33 @@ module SyntacticAnalysis = | _ -> None + let (|TransactionQuery|_|) = function + | SynExpr.Tuple(isStruct, [ SynExpr.Const(SynConst.String(query, queryRange), constRange); secondItem ], commaRange, tupleRange) -> + Some { query = query; queryRange = queryRange } + | _ -> + None + + let rec readTransactionQueries = function + | TransactionQuery transactionQuery -> + [ transactionQuery ] + | SynExpr.Sequential(_debugSeqPoint, isTrueSeq, expr1, expr2, seqRange) -> + [ + yield! readTransactionQueries expr1; + yield! readTransactionQueries expr2 + ] + | _ -> + [ ] + + let (|SqlExecuteTransaction|_|) = function + | Apply (("Sql.executeTransaction"|"Sql.executeTransactionAsync"), SynExpr.ArrayOrListOfSeqExpr(isArray, listExpr, listRange) , funcRange, appRange) -> + match listExpr with + | SynExpr.CompExpr(isArrayOfList, isNotNakedRefCell, compExpr, compRange) -> + Some (readTransactionQueries compExpr) + | _ -> + None + | _ -> + None + let (|ReadColumnAttempt|_|) = function | Apply(funcName, SynExpr.Const(SynConst.String(columnName, queryRange), constRange), funcRange, appRange) -> if funcName.StartsWith "Sql.read" && funcName <> "Sql.readRow" @@ -207,6 +234,20 @@ module SyntacticAnalysis = | _ -> [ ] + let rec findExecuteTransaction = function + | SqlExecuteTransaction transactionQueries -> + [ SqlAnalyzerBlock.Transaction transactionQueries ] + + | SynExpr.App(exprAtomic, isInfix, funcExpr, argExpr, range) -> + [ + yield! findExecuteTransaction funcExpr; + yield! findExecuteTransaction argExpr + ] + + | _ -> + [ ] + + let rec findReadColumnAttempts = function | ReadColumnAttempt (attempt) -> [ attempt ] @@ -382,6 +423,17 @@ module SyntacticAnalysis = [ { blocks = blocks; range = range; } ] + | SqlExecuteTransaction (transactionQueries) -> + let blocks = [ + yield! findFunc funcExpr + yield! findQuery funcExpr + yield! findParameters funcExpr + yield! findSkipAnalysis funcExpr + yield SqlAnalyzerBlock.Transaction transactionQueries + ] + + [ { blocks = blocks; range = range; } ] + | FuncName(functionWithoutParameters) -> let blocks = [ yield! findFunc funcExpr diff --git a/src/NpgsqlFSharpAnalyzer.Core/Types.fs b/src/NpgsqlFSharpAnalyzer.Core/Types.fs index 91b3b18..b20b0a3 100644 --- a/src/NpgsqlFSharpAnalyzer.Core/Types.fs +++ b/src/NpgsqlFSharpAnalyzer.Core/Types.fs @@ -49,6 +49,11 @@ type UsedParameter = { applicationRange : range option } +type TransactionQuery = { + query: string + queryRange : range +} + [] type SqlAnalyzerBlock = | Query of string * range @@ -56,6 +61,7 @@ type SqlAnalyzerBlock = | StoredProcedure of string * range | Parameters of UsedParameter list * range | ReadingColumns of ColumnReadAttempt list + | Transaction of TransactionQuery list | SkipAnalysis type SqlOperation = { diff --git a/src/NpgsqlFSharpAnalyzer/AssemblyInfo.fs b/src/NpgsqlFSharpAnalyzer/AssemblyInfo.fs index 4325bda..80907f4 100644 --- a/src/NpgsqlFSharpAnalyzer/AssemblyInfo.fs +++ b/src/NpgsqlFSharpAnalyzer/AssemblyInfo.fs @@ -4,20 +4,20 @@ open System.Reflection [] [] -[] -[] -[] -[] +[] +[] +[] +[] [] -[] +[] do () module internal AssemblyVersionInformation = let [] AssemblyTitle = "NpgsqlFSharpAnalyzer" let [] AssemblyProduct = "NpgsqlFSharpAnalyzer" - let [] AssemblyVersion = "3.13.0" - let [] AssemblyMetadata_ReleaseDate = "2020-09-04T00:00:00.0000000" - let [] AssemblyFileVersion = "3.13.0" - let [] AssemblyInformationalVersion = "3.13.0" + let [] AssemblyVersion = "3.14.0" + let [] AssemblyMetadata_ReleaseDate = "2020-09-07T00:00:00.0000000" + let [] AssemblyFileVersion = "3.14.0" + let [] AssemblyInformationalVersion = "3.14.0" let [] AssemblyMetadata_ReleaseChannel = "release" - let [] AssemblyMetadata_GitHash = "8510b403b2b0a20a3207cc5c7637d8406f2aaee5" + let [] AssemblyMetadata_GitHash = "0da6fb1b32c023b1bfa7698bf8cd916fc3077010" diff --git a/src/NpgsqlFSharpParser/AssemblyInfo.fs b/src/NpgsqlFSharpParser/AssemblyInfo.fs index 9a4e437..b86aaba 100644 --- a/src/NpgsqlFSharpParser/AssemblyInfo.fs +++ b/src/NpgsqlFSharpParser/AssemblyInfo.fs @@ -4,20 +4,20 @@ open System.Reflection [] [] -[] -[] -[] -[] +[] +[] +[] +[] [] -[] +[] do () module internal AssemblyVersionInformation = let [] AssemblyTitle = "NpgsqlFSharpParser" let [] AssemblyProduct = "NpgsqlFSharpAnalyzer" - let [] AssemblyVersion = "3.13.0" - let [] AssemblyMetadata_ReleaseDate = "2020-09-04T00:00:00.0000000" - let [] AssemblyFileVersion = "3.13.0" - let [] AssemblyInformationalVersion = "3.13.0" + let [] AssemblyVersion = "3.14.0" + let [] AssemblyMetadata_ReleaseDate = "2020-09-07T00:00:00.0000000" + let [] AssemblyFileVersion = "3.14.0" + let [] AssemblyInformationalVersion = "3.14.0" let [] AssemblyMetadata_ReleaseChannel = "release" - let [] AssemblyMetadata_GitHash = "8510b403b2b0a20a3207cc5c7637d8406f2aaee5" + let [] AssemblyMetadata_GitHash = "0da6fb1b32c023b1bfa7698bf8cd916fc3077010" diff --git a/src/NpgsqlFSharpVs/source.extension.vsixmanifest b/src/NpgsqlFSharpVs/source.extension.vsixmanifest index 13e5bb4..877e6ae 100644 --- a/src/NpgsqlFSharpVs/source.extension.vsixmanifest +++ b/src/NpgsqlFSharpVs/source.extension.vsixmanifest @@ -3,7 +3,7 @@ xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011"> - + NpgsqlFSharpVs F# Analyzer for embedded SQL syntax analysis, type-checking for parameters and result sets and nullable column detection when writing queries using Npgsql.FSharp. https://github.com/Zaid-Ajaj/Npgsql.FSharp.Analyzer diff --git a/tests/NpgsqlFSharpAnalyzer.Tests/AssemblyInfo.fs b/tests/NpgsqlFSharpAnalyzer.Tests/AssemblyInfo.fs index 138e18f..90e75f8 100644 --- a/tests/NpgsqlFSharpAnalyzer.Tests/AssemblyInfo.fs +++ b/tests/NpgsqlFSharpAnalyzer.Tests/AssemblyInfo.fs @@ -4,20 +4,20 @@ open System.Reflection [] [] -[] -[] -[] -[] +[] +[] +[] +[] [] -[] +[] do () module internal AssemblyVersionInformation = let [] AssemblyTitle = "NpgsqlFSharpAnalyzer.Tests" let [] AssemblyProduct = "NpgsqlFSharpAnalyzer" - let [] AssemblyVersion = "3.13.0" - let [] AssemblyMetadata_ReleaseDate = "2020-09-04T00:00:00.0000000" - let [] AssemblyFileVersion = "3.13.0" - let [] AssemblyInformationalVersion = "3.13.0" + let [] AssemblyVersion = "3.14.0" + let [] AssemblyMetadata_ReleaseDate = "2020-09-07T00:00:00.0000000" + let [] AssemblyFileVersion = "3.14.0" + let [] AssemblyInformationalVersion = "3.14.0" let [] AssemblyMetadata_ReleaseChannel = "release" - let [] AssemblyMetadata_GitHash = "8510b403b2b0a20a3207cc5c7637d8406f2aaee5" + let [] AssemblyMetadata_GitHash = "0da6fb1b32c023b1bfa7698bf8cd916fc3077010" diff --git a/tests/NpgsqlFSharpAnalyzer.Tests/Tests.fs b/tests/NpgsqlFSharpAnalyzer.Tests/Tests.fs index 93ba353..088771e 100644 --- a/tests/NpgsqlFSharpAnalyzer.Tests/Tests.fs +++ b/tests/NpgsqlFSharpAnalyzer.Tests/Tests.fs @@ -62,6 +62,14 @@ let tests = failwith "Should not happen" } + test "Syntactic Analysis: reading queries from transactions" { + match context (find "../examples/hashing/syntaxAnalysis-usingTransactions.fs") with + | None -> failwith "Could not crack project" + | Some context -> + let operations = SyntacticAnalysis.findSqlOperations context + Expect.equal operations.Length 2 "There should be two operations" + } + test "Syntactic Analysis: simple queries can be read" { match context (find "../examples/hashing/syntacticAnalysisSimpleQuery.fs") with | None -> failwith "Could not crack project" @@ -123,6 +131,23 @@ let tests = Expect.isEmpty messages "No errors returned" } + test "Semantic analysis: incorrect queries in executeTranscation are detected" { + use db = createTestDatabase() + + match context (find "../examples/hashing/errorsInTransactions.fs") with + | None -> failwith "Could not crack project" + | Some context -> + match SqlAnalysis.databaseSchema db.ConnectionString with + | Result.Error connectionError -> + failwith connectionError + | Result.Ok schema -> + let block = List.exactlyOne (SyntacticAnalysis.findSqlOperations context) + let messages = SqlAnalysis.analyzeOperation block db.ConnectionString schema + Expect.equal 2 messages.Length "One error returned" + Expect.isTrue (messages.[0].IsWarning()) "The error is found" + Expect.isTrue (messages.[1].IsWarning()) "The error is found" + } + test "Semantic Analysis: parameter type mismatch" { use db = createTestDatabase() diff --git a/tests/examples/hashing/AssemblyInfo.fs b/tests/examples/hashing/AssemblyInfo.fs index 7bc1269..157ec79 100644 --- a/tests/examples/hashing/AssemblyInfo.fs +++ b/tests/examples/hashing/AssemblyInfo.fs @@ -4,20 +4,20 @@ open System.Reflection [] [] -[] -[] -[] -[] +[] +[] +[] +[] [] -[] +[] do () module internal AssemblyVersionInformation = let [] AssemblyTitle = "examples" let [] AssemblyProduct = "NpgsqlFSharpAnalyzer" - let [] AssemblyVersion = "3.13.0" - let [] AssemblyMetadata_ReleaseDate = "2020-09-04T00:00:00.0000000" - let [] AssemblyFileVersion = "3.13.0" - let [] AssemblyInformationalVersion = "3.13.0" + let [] AssemblyVersion = "3.14.0" + let [] AssemblyMetadata_ReleaseDate = "2020-09-07T00:00:00.0000000" + let [] AssemblyFileVersion = "3.14.0" + let [] AssemblyInformationalVersion = "3.14.0" let [] AssemblyMetadata_ReleaseChannel = "release" - let [] AssemblyMetadata_GitHash = "8510b403b2b0a20a3207cc5c7637d8406f2aaee5" + let [] AssemblyMetadata_GitHash = "0da6fb1b32c023b1bfa7698bf8cd916fc3077010" diff --git a/tests/examples/hashing/errorsInTransactions.fs b/tests/examples/hashing/errorsInTransactions.fs new file mode 100644 index 0000000..03c4468 --- /dev/null +++ b/tests/examples/hashing/errorsInTransactions.fs @@ -0,0 +1,11 @@ +module ErrorsInTransactions + +open Npgsql.FSharp + +let executeMultipleQueries = + "connectionString" + |> Sql.connect + |> Sql.executeTransaction [ + "UPDATE users SET user_id = @user_id", [ ] + "DELTE FROM meters", [ ] + ] diff --git a/tests/examples/hashing/examples.fsproj b/tests/examples/hashing/examples.fsproj index 3c4d1ba..6eb4148 100644 --- a/tests/examples/hashing/examples.fsproj +++ b/tests/examples/hashing/examples.fsproj @@ -5,6 +5,8 @@ + + diff --git a/tests/examples/hashing/syntaxAnalysis-usingTransactions.fs b/tests/examples/hashing/syntaxAnalysis-usingTransactions.fs new file mode 100644 index 0000000..dc228d4 --- /dev/null +++ b/tests/examples/hashing/syntaxAnalysis-usingTransactions.fs @@ -0,0 +1,19 @@ +module SyntaxAnalysisUsingTransactions + +open Npgsql.FSharp + +let executeMultipleQueries = + "connectionString" + |> Sql.connect + |> Sql.executeTransaction [ + "UPDATE users SET user_id = @user_id", [ ] + "DELETE FROM users", [ ] + ] + +let executeMultipleQueriesAsync = + "connectionString" + |> Sql.connect + |> Sql.executeTransactionAsync [ + "UPDATE users SET user_id = @user_id", [ ] + "DELETE FROM users", [ ] + ]