├── .gitattributes
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── config.yml
│ └── feature_request.md
├── dependabot.yml
├── stale.yml
└── workflows
│ ├── merge-dependabot.yml
│ └── on-push-do-docs.yml
├── .gitignore
├── code_of_conduct.md
├── license.txt
├── readme.md
└── src
├── .editorconfig
├── Directory.Build.props
├── Directory.Packages.props
├── Shared.sln.DotSettings
├── Tests
├── GlobalUsings.cs
├── ModuleInit.cs
├── Tests.CommandEmpty.verified.txt
├── Tests.CommandEscaped.verified.txt
├── Tests.CommandFull.verified.txt
├── Tests.Error.verified.txt
├── Tests.Exception.verified.txt
├── Tests.ParameterCollectionFull.verified.txt
├── Tests.ParameterEmpty.verified.txt
├── Tests.ParameterFull.verified.txt
├── Tests.RecordingError.verified.txt
├── Tests.RecordingReadingResults.verified.txt
├── Tests.RecordingSpecific.verified.txt
├── Tests.RecordingTest.verified.txt
├── Tests.RecordingUsage.verified.txt
├── Tests.RecordingWithParameter.verified.txt
├── Tests.Schema.verified.md
├── Tests.SchemaAsSql.verified.sql
├── Tests.SchemaFilter.verified.md
├── Tests.SchemaInDynamic.verified.txt
├── Tests.SchemaInclude.verified.md
├── Tests.SchemaIncludeAll.verified.md
├── Tests.cs
└── Tests.csproj
├── Verify.SqlServer.sln
├── Verify.SqlServer.sln.DotSettings
├── Verify.SqlServer
├── Converters
│ ├── CommandConverter.cs
│ ├── ConnectionConverter.cs
│ ├── ErrorConverter.cs
│ ├── ExceptionConverter.cs
│ ├── ParameterCollectionConverter.cs
│ └── ParameterConverter.cs
├── Extensions.cs
├── GlobalUsings.cs
├── Recording
│ ├── ErrorEntry.cs
│ └── Listener.cs
├── RemoveSquareBracketVisitor.cs
├── SchemaValidation
│ ├── DbObjects.cs
│ ├── Format.cs
│ ├── Obsoletes.cs
│ ├── SchemaSettings.cs
│ ├── SqlScriptBuilder.cs
│ └── VerifySettingsExtensions.cs
├── SqlFormatter.cs
├── Verify.SqlServer.csproj
└── VerifySqlServer.cs
├── appveyor.yml
├── global.json
├── icon.png
├── key.snk
├── mdsnippets.json
└── nuget.config
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text
2 | *.png binary
3 | *.snk binary
4 |
5 | *.verified.* text eol=lf working-tree-encoding=UTF-8
6 |
7 | .editorconfig text eol=lf working-tree-encoding=UTF-8
8 | Shared.sln.DotSettings text eol=lf working-tree-encoding=UTF-8
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: VerifyTests
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug fix
3 | about: Create a bug fix to help us improve
4 | ---
5 |
6 | Note: New issues raised, where it is clear the submitter has not read the issue template, are likely to be closed with "please read the issue template". Please don't take offense at this. It is simply a time management decision. If someone raises an issue, and can't be bothered to spend the time to read the issue template, then the project maintainers should not be expected to spend the time to read the submitted issue. Often too much time is spent going back and forth in issue comments asking for information that is outlined in the issue template.
7 |
8 |
9 | #### Preamble
10 |
11 | General questions may be better placed [StackOveflow](https://stackoverflow.com/).
12 |
13 | Where relevant, ensure you are using the current stable versions on your development stack. For example:
14 |
15 | * Visual Studio
16 | * [.NET SDK or .NET Core SDK](https://www.microsoft.com/net/download)
17 | * Any related NuGet packages
18 |
19 | Any code or stack traces must be properly formatted with [GitHub markdown](https://guides.github.com/features/mastering-markdown/).
20 |
21 |
22 | #### Describe the bug
23 |
24 | A clear and concise description of what the bug is. Include any relevant version information.
25 |
26 | A clear and concise description of what you expected to happen.
27 |
28 | Add any other context about the problem here.
29 |
30 |
31 | #### Minimal Repro
32 |
33 | Ensure you have replicated the bug in a minimal solution with the fewest moving parts. Often this will help point to the true cause of the problem. Upload this repro as part of the issue, preferably a public GitHub repository or a downloadable zip. The repro will allow the maintainers of this project to smoke test the any fix.
34 |
35 | #### Submit a PR that fixes the bug
36 |
37 | Submit a [Pull Request (PR)](https://help.github.com/articles/about-pull-requests/) that fixes the bug. Include in this PR a test that verifies the fix. If you were not able to fix the bug, a PR that illustrates your partial progress will suffice.
38 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: How to raise feature requests
4 | ---
5 |
6 |
7 | Note: New issues raised, where it is clear the submitter has not read the issue template, are likely to be closed with "please read the issue template". Please don't take offense at this. It is simply a time management decision. If someone raises an issue, and can't be bothered to spend the time to read the issue template, then the project maintainers should not be expected to spend the time to read the submitted issue. Often too much time is spent going back and forth in issue comments asking for information that is outlined in the issue template.
8 |
9 | If you are certain the feature will be accepted, it is better to raise a [Pull Request (PR)](https://help.github.com/articles/about-pull-requests/).
10 |
11 | If you are uncertain if the feature will be accepted, outline the proposal below to confirm it is viable, prior to raising a PR that implements the feature.
12 |
13 | Note that even if the feature is a good idea and viable, it may not be accepted since the ongoing effort in maintaining the feature may outweigh the benefit it delivers.
14 |
15 |
16 | #### Is the feature request related to a problem
17 |
18 | A clear and concise description of what the problem is.
19 |
20 |
21 | #### Describe the solution
22 |
23 | A clear and concise proposal of how you intend to implement the feature.
24 |
25 |
26 | #### Describe alternatives considered
27 |
28 | A clear and concise description of any alternative solutions or features you've considered.
29 |
30 |
31 | #### Additional context
32 |
33 | Add any other context about the feature request here.
34 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: nuget
4 | directory: "/src"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 10
8 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 7
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Set to true to ignore issues in a milestone (defaults to false)
6 | exemptMilestones: true
7 | # Comment to post when marking an issue as stale. Set to `false` to disable
8 | markComment: >
9 | This issue has been automatically marked as stale because it has not had
10 | recent activity. It will be closed if no further activity occurs. Thank you
11 | for your contributions.
12 | # Comment to post when closing a stale issue. Set to `false` to disable
13 | closeComment: false
14 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
15 | pulls:
16 | daysUntilStale: 30
17 | exemptLabels:
18 | - Question
19 | - Bug
20 | - Feature
21 | - Improvement
--------------------------------------------------------------------------------
/.github/workflows/merge-dependabot.yml:
--------------------------------------------------------------------------------
1 | name: merge-dependabot
2 | on:
3 | pull_request:
4 | jobs:
5 | automerge:
6 | runs-on: ubuntu-latest
7 | if: github.actor == 'dependabot[bot]'
8 | steps:
9 | - name: Dependabot Auto Merge
10 | uses: ahmadnassri/action-dependabot-auto-merge@v2.6.6
11 | with:
12 | target: minor
13 | github-token: ${{ secrets.dependabot }}
14 | command: squash and merge
--------------------------------------------------------------------------------
/.github/workflows/on-push-do-docs.yml:
--------------------------------------------------------------------------------
1 | name: on-push-do-docs
2 | on:
3 | push:
4 | jobs:
5 | docs:
6 | runs-on: windows-latest
7 | steps:
8 | - uses: actions/checkout@v4
9 | - name: Run MarkdownSnippets
10 | run: |
11 | dotnet tool install --global MarkdownSnippets.Tool
12 | mdsnippets ${GITHUB_WORKSPACE}
13 | shell: bash
14 | - name: Push changes
15 | run: |
16 | git config --local user.email "action@github.com"
17 | git config --local user.name "GitHub Action"
18 | git commit -m "Docs changes" -a || echo "nothing to commit"
19 | remote="https://${GITHUB_ACTOR}:${{secrets.GITHUB_TOKEN}}@github.com/${GITHUB_REPOSITORY}.git"
20 | branch="${GITHUB_REF:11}"
21 | git push "${remote}" ${branch} || echo "nothing to push"
22 | shell: bash
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.suo
2 | *.user
3 | bin/
4 | obj/
5 | .vs/
6 | *.DotSettings.user
7 | .idea/
8 | *.received.*
9 | nugets/
--------------------------------------------------------------------------------
/code_of_conduct.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | This project has adopted the code of conduct defined by the Contributor Covenant
4 | to clarify expected behavior in our community.
5 | For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/about/code-of-conduct).
6 |
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) .NET Foundation and Contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | #
Verify.SqlServer
2 |
3 | [](https://github.com/orgs/VerifyTests/discussions)
4 | [](https://ci.appveyor.com/project/SimonCropp/verify-sqlserver)
5 | [](https://www.nuget.org/packages/Verify.SqlServer/)
6 |
7 | Extends [Verify](https://github.com/VerifyTests/Verify) to allow verification of SqlServer bits.
8 |
9 | **See [Milestones](../../milestones?state=closed) for release notes.**
10 |
11 |
12 | ## NuGet package
13 |
14 | https://nuget.org/packages/Verify.SqlServer/
15 |
16 |
17 | ## Usage
18 |
19 |
20 |
21 | ```cs
22 | [ModuleInitializer]
23 | public static void Init() =>
24 | VerifySqlServer.Initialize();
25 | ```
26 | snippet source | anchor
27 |
28 |
29 |
30 | ### SqlServer Schema
31 |
32 | This test:
33 |
34 |
35 |
36 | ```cs
37 | await Verify(connection);
38 | ```
39 | snippet source | anchor
40 |
41 |
42 | Will result in the following verified file:
43 |
44 |
45 |
46 |
47 |
48 |
49 | #### Object types to include
50 |
51 |
52 |
53 | ```cs
54 | await Verify(connection)
55 | // include only tables and views
56 | .SchemaIncludes(DbObjects.Tables | DbObjects.Views);
57 | ```
58 | snippet source | anchor
59 |
60 |
61 | Available values:
62 |
63 |
64 |
65 | ```cs
66 | namespace VerifyTests.SqlServer;
67 |
68 | [Flags]
69 | public enum DbObjects
70 | {
71 | StoredProcedures = 1,
72 | Synonyms = 2,
73 | Tables = 4,
74 | UserDefinedFunctions = 8,
75 | Views = 16,
76 | All = StoredProcedures | Synonyms | Tables | UserDefinedFunctions | Views
77 | }
78 | ```
79 | snippet source | anchor
80 |
81 |
82 |
83 | #### Filtering
84 |
85 | Objects can be dynamically filtered:
86 |
87 |
88 |
89 | ```cs
90 | await Verify(connection)
91 | // include tables & views, or named MyTrigger
92 | .SchemaFilter(
93 | _ => _ is TableViewBase ||
94 | _.Name == "MyTrigger");
95 | ```
96 | snippet source | anchor
97 |
98 |
99 |
100 |
101 | ### Recording
102 |
103 | Recording allows all commands executed to be captured and then (optionally) verified.
104 |
105 | Call `SqlRecording.StartRecording()`:
106 |
107 |
108 |
109 | ```cs
110 | await using var connection = new SqlConnection(connectionString);
111 | await connection.OpenAsync();
112 | Recording.Start();
113 | await using var command = connection.CreateCommand();
114 | command.CommandText = "select Value from MyTable";
115 | var value = await command.ExecuteScalarAsync();
116 | await Verify(value!);
117 | ```
118 | snippet source | anchor
119 |
120 |
121 | Will result in the following verified file:
122 |
123 |
124 |
125 | ```txt
126 | {
127 | target: 42,
128 | sql: {
129 | Text:
130 | select Value
131 | from MyTable,
132 | HasTransaction: false
133 | }
134 | }
135 | ```
136 | snippet source | anchor
137 |
138 |
139 |
140 | Sql entries can be explicitly read using `SqlRecording.FinishRecording`, optionally filtered, and passed to Verify:
141 |
142 |
143 |
144 | ```cs
145 | await using var connection = new SqlConnection(connectionString);
146 | await connection.OpenAsync();
147 | Recording.Start();
148 | await using var command = connection.CreateCommand();
149 | command.CommandText = "select Value from MyTable";
150 | var value = await command.ExecuteScalarAsync();
151 |
152 | await using var errorCommand = connection.CreateCommand();
153 | errorCommand.CommandText = "select Value from BadTable";
154 | try
155 | {
156 | await errorCommand.ExecuteScalarAsync();
157 | }
158 | catch
159 | {
160 | }
161 |
162 | var entries = Recording
163 | .Stop()
164 | .Select(_ => _.Data);
165 | //Optionally filter results
166 | await Verify(
167 | new
168 | {
169 | value,
170 | sqlEntries = entries
171 | });
172 | ```
173 | snippet source | anchor
174 |
175 |
176 |
177 | #### Interpreting recording results
178 |
179 | Recording results can be interpreted in a a variety of ways:
180 |
181 |
182 |
183 | ```cs
184 | var entries = Recording.Stop();
185 |
186 | // all sql entries via key
187 | var sqlEntries = entries
188 | .Where(_ => _.Name == "sql")
189 | .Select(_ => _.Data);
190 |
191 | // successful Commands via Type
192 | var sqlCommandsViaType = entries
193 | .Select(_ => _.Data)
194 | .OfType();
195 |
196 | // failed Commands via Type
197 | var sqlErrorsViaType = entries
198 | .Select(_ => _.Data)
199 | .OfType();
200 | ```
201 | snippet source | anchor
202 |
203 |
204 |
205 | ## Icon
206 |
207 | [Database](https://thenounproject.com/term/database/310841/) designed by [Creative Stall](https://thenounproject.com/creativestall/) from [The Noun Project](https://thenounproject.com).
208 |
--------------------------------------------------------------------------------
/src/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 |
6 | [*.cs]
7 | indent_size = 4
8 | charset = utf-8
9 |
10 | # Redundant accessor body
11 | resharper_redundant_accessor_body_highlighting = error
12 |
13 | # Replace with field keyword
14 | resharper_replace_with_field_keyword_highlighting = error
15 |
16 | # Replace with single call to Single(..)
17 | resharper_replace_with_single_call_to_single_highlighting = error
18 |
19 | # Replace with single call to SingleOrDefault(..)
20 | resharper_replace_with_single_call_to_single_or_default_highlighting = error
21 |
22 | # Replace with single call to LastOrDefault(..)
23 | resharper_replace_with_single_call_to_last_or_default_highlighting = error
24 |
25 | # Replace with single call to Last(..)
26 | resharper_replace_with_single_call_to_last_highlighting = error
27 |
28 | # Replace with single call to First(..)
29 | resharper_replace_with_single_call_to_first_highlighting = error
30 |
31 | # Replace with single call to FirstOrDefault(..)
32 | resharper_replace_with_single_call_to_first_or_default_highlighting = error
33 |
34 | # Replace with single call to Any(..)
35 | resharper_replace_with_single_call_to_any_highlighting = error
36 |
37 | # Replace with single call to Count(..)
38 | resharper_replace_with_single_call_to_count_highlighting = error
39 |
40 | # Declare types in namespaces
41 | dotnet_diagnostic.CA1050.severity = none
42 |
43 | # Use Literals Where Appropriate
44 | dotnet_diagnostic.CA1802.severity = error
45 |
46 | # Template should be a static expression
47 | dotnet_diagnostic.CA2254.severity = error
48 |
49 | # Potentially misleading parameter name in lambda or local function
50 | resharper_all_underscore_local_parameter_name_highlighting = none
51 |
52 | # Redundant explicit collection creation in argument of 'params' parameter
53 | resharper_redundant_explicit_params_array_creation_highlighting = error
54 |
55 | # Do not initialize unnecessarily
56 | dotnet_diagnostic.CA1805.severity = error
57 |
58 | # Avoid unsealed attributes
59 | dotnet_diagnostic.CA1813.severity = error
60 |
61 | # Test for empty strings using string length
62 | dotnet_diagnostic.CA1820.severity = none
63 |
64 | # Remove empty finalizers
65 | dotnet_diagnostic.CA1821.severity = error
66 |
67 | # Mark members as static
68 | dotnet_diagnostic.CA1822.severity = error
69 |
70 | # Avoid unused private fields
71 | dotnet_diagnostic.CA1823.severity = error
72 |
73 | # Avoid zero-length array allocations
74 | dotnet_diagnostic.CA1825.severity = error
75 |
76 | # Use property instead of Linq Enumerable method
77 | dotnet_diagnostic.CA1826.severity = error
78 |
79 | # Do not use Count()/LongCount() when Any() can be used
80 | dotnet_diagnostic.CA1827.severity = error
81 | dotnet_diagnostic.CA1828.severity = error
82 |
83 | # Use Length/Count property instead of Enumerable.Count method
84 | dotnet_diagnostic.CA1829.severity = error
85 |
86 | # Prefer strongly-typed Append and Insert method overloads on StringBuilder
87 | dotnet_diagnostic.CA1830.severity = error
88 |
89 | # Use AsSpan instead of Range-based indexers for string when appropriate
90 | dotnet_diagnostic.CA1831.severity = error
91 |
92 | # Use AsSpan instead of Range-based indexers for string when appropriate
93 | dotnet_diagnostic.CA1831.severity = error
94 | dotnet_diagnostic.CA1832.severity = error
95 | dotnet_diagnostic.CA1833.severity = error
96 |
97 | # Use StringBuilder.Append(char) for single character strings
98 | dotnet_diagnostic.CA1834.severity = error
99 |
100 | # Prefer IsEmpty over Count when available
101 | dotnet_diagnostic.CA1836.severity = error
102 |
103 | # Prefer IsEmpty over Count when available
104 | dotnet_diagnostic.CA1836.severity = error
105 |
106 | # Use Environment.ProcessId instead of Process.GetCurrentProcess().Id
107 | dotnet_diagnostic.CA1837.severity = error
108 |
109 | # Use Environment.ProcessPath instead of Process.GetCurrentProcess().MainModule.FileName
110 | dotnet_diagnostic.CA1839.severity = error
111 |
112 | # Use Environment.CurrentManagedThreadId instead of Thread.CurrentThread.ManagedThreadId
113 | dotnet_diagnostic.CA1840.severity = error
114 |
115 | # Prefer Dictionary Contains methods
116 | dotnet_diagnostic.CA1841.severity = error
117 |
118 | # Do not use WhenAll with a single task
119 | dotnet_diagnostic.CA1842.severity = error
120 |
121 | # Do not use WhenAll/WaitAll with a single task
122 | dotnet_diagnostic.CA1842.severity = error
123 | dotnet_diagnostic.CA1843.severity = error
124 |
125 | # Use span-based 'string.Concat'
126 | dotnet_diagnostic.CA1845.severity = error
127 |
128 | # Prefer AsSpan over Substring
129 | dotnet_diagnostic.CA1846.severity = error
130 |
131 | # Use string.Contains(char) instead of string.Contains(string) with single characters
132 | dotnet_diagnostic.CA1847.severity = error
133 |
134 | # Prefer static HashData method over ComputeHash
135 | dotnet_diagnostic.CA1850.severity = error
136 |
137 | # Possible multiple enumerations of IEnumerable collection
138 | dotnet_diagnostic.CA1851.severity = error
139 |
140 | # Unnecessary call to Dictionary.ContainsKey(key)
141 | dotnet_diagnostic.CA1853.severity = error
142 |
143 | # Prefer the IDictionary.TryGetValue(TKey, out TValue) method
144 | dotnet_diagnostic.CA1854.severity = error
145 |
146 | # Use Span.Clear() instead of Span.Fill()
147 | dotnet_diagnostic.CA1855.severity = error
148 |
149 | # Incorrect usage of ConstantExpected attribute
150 | dotnet_diagnostic.CA1856.severity = error
151 |
152 | # The parameter expects a constant for optimal performance
153 | dotnet_diagnostic.CA1857.severity = error
154 |
155 | # Use StartsWith instead of IndexOf
156 | dotnet_diagnostic.CA1858.severity = error
157 |
158 | # Avoid using Enumerable.Any() extension method
159 | dotnet_diagnostic.CA1860.severity = error
160 |
161 | # Avoid constant arrays as arguments
162 | dotnet_diagnostic.CA1861.severity = error
163 |
164 | # Use the StringComparison method overloads to perform case-insensitive string comparisons
165 | dotnet_diagnostic.CA1862.severity = error
166 |
167 | # Prefer the IDictionary.TryAdd(TKey, TValue) method
168 | dotnet_diagnostic.CA1864.severity = error
169 |
170 | # Use string.Method(char) instead of string.Method(string) for string with single char
171 | dotnet_diagnostic.CA1865.severity = error
172 | dotnet_diagnostic.CA1866.severity = error
173 | dotnet_diagnostic.CA1867.severity = error
174 |
175 | # Unnecessary call to 'Contains' for sets
176 | dotnet_diagnostic.CA1868.severity = error
177 |
178 | # Cache and reuse 'JsonSerializerOptions' instances
179 | dotnet_diagnostic.CA1869.severity = error
180 |
181 | # Use a cached 'SearchValues' instance
182 | dotnet_diagnostic.CA1870.severity = error
183 |
184 | # Microsoft .NET properties
185 | trim_trailing_whitespace = true
186 | csharp_preferred_modifier_order = public, private, protected, internal, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async:suggestion
187 | resharper_namespace_body = file_scoped
188 | dotnet_naming_rule.private_constants_rule.severity = warning
189 | dotnet_naming_rule.private_constants_rule.style = lower_camel_case_style
190 | dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols
191 | dotnet_naming_rule.private_instance_fields_rule.severity = warning
192 | dotnet_naming_rule.private_instance_fields_rule.style = lower_camel_case_style
193 | dotnet_naming_rule.private_instance_fields_rule.symbols = private_instance_fields_symbols
194 | dotnet_naming_rule.private_static_fields_rule.severity = warning
195 | dotnet_naming_rule.private_static_fields_rule.style = lower_camel_case_style
196 | dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols
197 | dotnet_naming_rule.private_static_readonly_rule.severity = warning
198 | dotnet_naming_rule.private_static_readonly_rule.style = lower_camel_case_style
199 | dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols
200 | dotnet_naming_style.lower_camel_case_style.capitalization = camel_case
201 | dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case
202 | dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private
203 | dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field
204 | dotnet_naming_symbols.private_constants_symbols.required_modifiers = const
205 | dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities = private
206 | dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds = field
207 | dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private
208 | dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field
209 | dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
210 | dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
211 | dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
212 | dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly
213 | dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
214 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none
215 | dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
216 |
217 | # ReSharper properties
218 | resharper_object_creation_when_type_not_evident = target_typed
219 |
220 | # ReSharper inspection severities
221 | resharper_arrange_object_creation_when_type_evident_highlighting = error
222 | resharper_arrange_object_creation_when_type_not_evident_highlighting = error
223 | resharper_arrange_redundant_parentheses_highlighting = error
224 | resharper_arrange_static_member_qualifier_highlighting = error
225 | resharper_arrange_this_qualifier_highlighting = error
226 | resharper_arrange_type_member_modifiers_highlighting = none
227 | resharper_built_in_type_reference_style_for_member_access_highlighting = hint
228 | resharper_built_in_type_reference_style_highlighting = hint
229 | resharper_check_namespace_highlighting = none
230 | resharper_convert_to_using_declaration_highlighting = error
231 | resharper_css_not_resolved_highlighting = warning
232 | resharper_field_can_be_made_read_only_local_highlighting = none
233 | resharper_merge_into_logical_pattern_highlighting = warning
234 | resharper_merge_into_pattern_highlighting = error
235 | resharper_method_has_async_overload_highlighting = warning
236 | # because stop rider giving errors before source generators have run
237 | resharper_partial_type_with_single_part_highlighting = warning
238 | resharper_redundant_base_qualifier_highlighting = warning
239 | resharper_redundant_cast_highlighting = error
240 | resharper_redundant_empty_object_creation_argument_list_highlighting = error
241 | resharper_redundant_empty_object_or_collection_initializer_highlighting = error
242 | resharper_redundant_name_qualifier_highlighting = error
243 | resharper_redundant_suppress_nullable_warning_expression_highlighting = error
244 | resharper_redundant_using_directive_highlighting = error
245 | resharper_redundant_verbatim_string_prefix_highlighting = error
246 | resharper_redundant_lambda_signature_parentheses_highlighting = error
247 | resharper_replace_substring_with_range_indexer_highlighting = warning
248 | resharper_suggest_var_or_type_built_in_types_highlighting = error
249 | resharper_suggest_var_or_type_elsewhere_highlighting = error
250 | resharper_suggest_var_or_type_simple_types_highlighting = error
251 | resharper_unnecessary_whitespace_highlighting = error
252 | resharper_use_await_using_highlighting = warning
253 | resharper_use_deconstruction_highlighting = warning
254 |
255 | # Sort using and Import directives with System.* appearing first
256 | dotnet_sort_system_directives_first = true
257 |
258 | # Avoid "this." and "Me." if not necessary
259 | dotnet_style_qualification_for_field = false:error
260 | dotnet_style_qualification_for_property = false:error
261 | dotnet_style_qualification_for_method = false:error
262 | dotnet_style_qualification_for_event = false:error
263 |
264 | # Use language keywords instead of framework type names for type references
265 | dotnet_style_predefined_type_for_locals_parameters_members = true:error
266 | dotnet_style_predefined_type_for_member_access = true:error
267 |
268 | # Suggest more modern language features when available
269 | dotnet_style_object_initializer = true:error
270 | dotnet_style_collection_initializer = true:error
271 | dotnet_style_coalesce_expression = false:error
272 | dotnet_style_null_propagation = true:error
273 | dotnet_style_explicit_tuple_names = true:error
274 |
275 | # Use collection expression syntax
276 | resharper_use_collection_expression_highlighting = error
277 |
278 | # Prefer "var" everywhere
279 | csharp_style_var_for_built_in_types = true:error
280 | csharp_style_var_when_type_is_apparent = true:error
281 | csharp_style_var_elsewhere = true:error
282 |
283 | # Prefer method-like constructs to have a block body
284 | csharp_style_expression_bodied_methods = true:error
285 | csharp_style_expression_bodied_local_functions = true:error
286 | csharp_style_expression_bodied_constructors = true:error
287 | csharp_style_expression_bodied_operators = true:error
288 | resharper_place_expr_method_on_single_line = false
289 |
290 | # Prefer property-like constructs to have an expression-body
291 | csharp_style_expression_bodied_properties = true:error
292 | csharp_style_expression_bodied_indexers = true:error
293 | csharp_style_expression_bodied_accessors = true:error
294 |
295 | # Suggest more modern language features when available
296 | csharp_style_pattern_matching_over_is_with_cast_check = true:error
297 | csharp_style_pattern_matching_over_as_with_null_check = true:error
298 | csharp_style_inlined_variable_declaration = true:suggestion
299 | csharp_style_throw_expression = true:suggestion
300 | csharp_style_conditional_delegate_call = true:suggestion
301 |
302 | # Newline settings
303 | #csharp_new_line_before_open_brace = all:error
304 | resharper_max_array_initializer_elements_on_line = 1
305 | csharp_new_line_before_else = true
306 | csharp_new_line_before_catch = true
307 | csharp_new_line_before_finally = true
308 | csharp_new_line_before_members_in_object_initializers = true
309 | csharp_new_line_before_members_in_anonymous_types = true
310 | resharper_wrap_before_first_type_parameter_constraint = true
311 | resharper_wrap_extends_list_style = chop_always
312 | resharper_wrap_after_dot_in_method_calls = false
313 | resharper_wrap_before_binary_pattern_op = false
314 | resharper_wrap_object_and_collection_initializer_style = chop_always
315 | resharper_place_simple_initializer_on_single_line = false
316 |
317 | # space
318 | resharper_space_around_lambda_arrow = true
319 |
320 | dotnet_style_require_accessibility_modifiers = never:error
321 | resharper_place_type_constraints_on_same_line = false
322 | resharper_blank_lines_inside_namespace = 0
323 | resharper_blank_lines_after_file_scoped_namespace_directive = 1
324 | resharper_blank_lines_inside_type = 0
325 |
326 | insert_final_newline = false
327 | resharper_place_attribute_on_same_line = false
328 | resharper_space_around_lambda_arrow = true
329 | resharper_place_constructor_initializer_on_same_line = false
330 |
331 | #braces https://www.jetbrains.com/help/resharper/EditorConfig_CSHARP_CSharpCodeStylePageImplSchema.html#Braces
332 | resharper_braces_for_ifelse = required
333 | resharper_braces_for_foreach = required
334 | resharper_braces_for_while = required
335 | resharper_braces_for_dowhile = required
336 | resharper_braces_for_lock = required
337 | resharper_braces_for_fixed = required
338 | resharper_braces_for_for = required
339 |
340 | resharper_return_value_of_pure_method_is_not_used_highlighting = error
341 |
342 | resharper_all_underscore_local_parameter_name_highlighting = none
343 |
344 | resharper_misleading_body_like_statement_highlighting = error
345 |
346 | resharper_redundant_record_class_keyword_highlighting = error
347 |
348 | resharper_redundant_extends_list_entry_highlighting = error
349 |
350 | # Xml files
351 | [*.{xml,config,nuspec,resx,vsixmanifest,csproj,targets,props,fsproj}]
352 | indent_size = 2
353 | # https://www.jetbrains.com/help/resharper/EditorConfig_XML_XmlCodeStylePageSchema.html#resharper_xml_blank_line_after_pi
354 | resharper_blank_line_after_pi = false
355 | resharper_space_before_self_closing = true
356 | ij_xml_space_inside_empty_tag = true
357 |
358 | [*.json]
359 | indent_size = 2
360 |
361 | # Verify settings
362 | [*.{received,verified}.{txt,xml,json,md,sql,csv,html,htm,md}]
363 | charset = utf-8-bom
364 | end_of_line = lf
365 | indent_size = unset
366 | indent_style = unset
367 | insert_final_newline = false
368 | tab_width = unset
369 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CS1591;CS0649;CS8632;NU1608;NU1109
5 | 11.1.1
6 | preview
7 | 1.0.0
8 | SqlServer, Verify
9 | Extends Verify (https://github.com/VerifyTests/Verify) to allow verification of SqlServer bits.
10 | true
11 | true
12 | true
13 | true
14 |
15 |
--------------------------------------------------------------------------------
/src/Directory.Packages.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 | true
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/Shared.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | False
3 | Quiet
4 | True
5 | True
6 | True
7 | DO_NOT_SHOW
8 | ERROR
9 | ERROR
10 | ERROR
11 | WARNING
12 | ERROR
13 | ERROR
14 | ERROR
15 | ERROR
16 | ERROR
17 | ERROR
18 | ERROR
19 | ERROR
20 | ERROR
21 | ERROR
22 | ERROR
23 | ERROR
24 | ERROR
25 | ERROR
26 | ERROR
27 | ERROR
28 | ERROR
29 | ERROR
30 | ERROR
31 | ERROR
32 | ERROR
33 | ERROR
34 | ERROR
35 | ERROR
36 | ERROR
37 | ERROR
38 | ERROR
39 | ERROR
40 | ERROR
41 | ERROR
42 | ERROR
43 | DO_NOT_SHOW
44 | DO_NOT_SHOW
45 | ERROR
46 | ERROR
47 | ERROR
48 | ERROR
49 | ERROR
50 | ERROR
51 | ERROR
52 | ERROR
53 | ERROR
54 | ERROR
55 | ERROR
56 | ERROR
57 | C90+,E79+,S14+
58 | ERROR
59 | ERROR
60 | ERROR
61 | ERROR
62 | ERROR
63 | ERROR
64 | ERROR
65 | ERROR
66 | ERROR
67 | ERROR
68 | ERROR
69 | ERROR
70 | ERROR
71 | ERROR
72 | ERROR
73 | ERROR
74 | ERROR
75 | ERROR
76 | ERROR
77 | ERROR
78 | ERROR
79 | ERROR
80 | ERROR
81 | ERROR
82 | ERROR
83 | ERROR
84 | ERROR
85 | ERROR
86 | ERROR
87 | ERROR
88 | ERROR
89 | ERROR
90 | ERROR
91 | ERROR
92 | ERROR
93 | ERROR
94 | ERROR
95 | ERROR
96 | ERROR
97 | ERROR
98 | ERROR
99 | ERROR
100 | ERROR
101 | ERROR
102 | ERROR
103 | ERROR
104 | ERROR
105 | ERROR
106 | ERROR
107 | ERROR
108 | ERROR
109 | ERROR
110 | ERROR
111 | ERROR
112 | ERROR
113 | ERROR
114 | ERROR
115 | ERROR
116 | ERROR
117 | ERROR
118 | ERROR
119 | ERROR
120 | ERROR
121 | ERROR
122 | DO_NOT_SHOW
123 | *.received.*
124 | *.verified.*
125 | ERROR
126 | ERROR
127 | DO_NOT_SHOW
128 | ECMAScript 2016
129 | <?xml version="1.0" encoding="utf-16"?><Profile name="c# Cleanup"><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><CSCodeStyleAttributes ArrangeVarStyle="True" ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeCodeBodyStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" /><CssAlphabetizeProperties>True</CssAlphabetizeProperties><JSStringLiteralQuotesDescriptor>True</JSStringLiteralQuotesDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><JsInsertSemicolon>True</JsInsertSemicolon><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><AsInsteadOfCastTs>True</AsInsteadOfCastTs><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CssReformatCode>True</CssReformatCode><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><HtmlReformatCode>True</HtmlReformatCode><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><IDEA_SETTINGS><profile version="1.0">
130 | <option name="myName" value="c# Cleanup" />
131 | </profile></IDEA_SETTINGS><RIDER_SETTINGS><profile>
132 | <Language id="EditorConfig">
133 | <Reformat>false</Reformat>
134 | </Language>
135 | <Language id="HTML">
136 | <OptimizeImports>false</OptimizeImports>
137 | <Reformat>false</Reformat>
138 | <Rearrange>false</Rearrange>
139 | </Language>
140 | <Language id="JSON">
141 | <Reformat>false</Reformat>
142 | </Language>
143 | <Language id="RELAX-NG">
144 | <Reformat>false</Reformat>
145 | </Language>
146 | <Language id="XML">
147 | <OptimizeImports>false</OptimizeImports>
148 | <Reformat>false</Reformat>
149 | <Rearrange>false</Rearrange>
150 | </Language>
151 | </profile></RIDER_SETTINGS></Profile>
152 | ExpressionBody
153 | ExpressionBody
154 | ExpressionBody
155 | False
156 | NEVER
157 | NEVER
158 | False
159 | False
160 | False
161 | True
162 | False
163 | CHOP_ALWAYS
164 | False
165 | False
166 | RemoveIndent
167 | RemoveIndent
168 | False
169 | True
170 | True
171 | True
172 | True
173 | True
174 | ERROR
175 | DoNothing
176 |
--------------------------------------------------------------------------------
/src/Tests/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | // Global using directives
2 |
3 | global using System.Data;
4 | global using System.Data.SqlTypes;
5 | global using Microsoft.Data.SqlClient;
6 | global using Microsoft.SqlServer.Management.Common;
7 | global using Microsoft.SqlServer.Management.Smo;
8 | global using VerifyTests.SqlServer;
--------------------------------------------------------------------------------
/src/Tests/ModuleInit.cs:
--------------------------------------------------------------------------------
1 | public static class ModuleInit
2 | {
3 | #region Enable
4 |
5 | [ModuleInitializer]
6 | public static void Init() =>
7 | VerifySqlServer.Initialize();
8 |
9 | #endregion
10 |
11 | [ModuleInitializer]
12 | public static void InitOther() =>
13 | VerifierSettings.InitializePlugins();
14 | }
--------------------------------------------------------------------------------
/src/Tests/Tests.CommandEmpty.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Text:
3 | select *
4 | from MyTable,
5 | HasTransaction: false
6 | }
--------------------------------------------------------------------------------
/src/Tests/Tests.CommandEscaped.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Text:
3 | select *
4 | from MyTable,
5 | HasTransaction: false
6 | }
--------------------------------------------------------------------------------
/src/Tests/Tests.CommandFull.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Text:
3 | select *
4 | from MyTable,
5 | Parameters: {
6 | name: 10
7 | },
8 | HasTransaction: false,
9 | Timeout: 10,
10 | Type: StoredProcedure,
11 | Notification: {
12 | Options: options,
13 | Timeout: 10,
14 | UserData: user data
15 | },
16 | UpdatedRowSource: FirstReturnedRecord,
17 | EnableOptimizedParameterBinding: true
18 | }
--------------------------------------------------------------------------------
/src/Tests/Tests.Error.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Message: Invalid object name 'MyTabl2e'.,
3 | Number: 208,
4 | Line: 1
5 | }
--------------------------------------------------------------------------------
/src/Tests/Tests.Exception.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Message: Invalid object name 'MyTabl2e'.,
3 | Number: 208,
4 | Line: 1
5 | }
--------------------------------------------------------------------------------
/src/Tests/Tests.ParameterCollectionFull.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | name: {
3 | Value: DateTime_1,
4 | Direction: InputOutput,
5 | Offset: 5,
6 | Precision: 2,
7 | Scale: 3,
8 | Size: 4,
9 | LocaleId: 10,
10 | SourceColumn: sourceColumn,
11 | SourceVersion: Proposed,
12 | ForceColumnEncryption: true
13 | }
14 | }
--------------------------------------------------------------------------------
/src/Tests/Tests.ParameterEmpty.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Name: name,
3 | DbType: Date
4 | }
--------------------------------------------------------------------------------
/src/Tests/Tests.ParameterFull.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | Name: name,
3 | Value: DateTime_1,
4 | Direction: InputOutput,
5 | Offset: 5,
6 | Precision: 2,
7 | Scale: 3,
8 | Size: 4,
9 | LocaleId: 10,
10 | SourceColumn: sourceColumn,
11 | SourceVersion: Proposed,
12 | ForceColumnEncryption: true
13 | }
--------------------------------------------------------------------------------
/src/Tests/Tests.RecordingError.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | sql: {
3 | Exception: {
4 | Message: Invalid object name 'MyTabl2e'.,
5 | Number: 208,
6 | Line: 1
7 | },
8 | Command: {
9 | Text:
10 | select *
11 | from MyTabl2e,
12 | HasTransaction: false
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/src/Tests/Tests.RecordingReadingResults.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | sqlEntries: [
3 | {
4 | Text:
5 | select Value
6 | from MyTable,
7 | HasTransaction: false
8 | },
9 | {
10 | Exception: {
11 | Message: Invalid object name 'BadTable'.,
12 | Number: 208,
13 | Line: 1
14 | },
15 | Command: {
16 | Text:
17 | select Value
18 | from BadTable,
19 | HasTransaction: false
20 | }
21 | }
22 | ],
23 | sqlCommandsViaType: [
24 | {
25 | Text:
26 | select Value
27 | from MyTable,
28 | HasTransaction: false
29 | }
30 | ],
31 | sqlErrorsViaType: [
32 | {
33 | Exception: {
34 | Message: Invalid object name 'BadTable'.,
35 | Number: 208,
36 | Line: 1
37 | },
38 | Command: {
39 | Text:
40 | select Value
41 | from BadTable,
42 | HasTransaction: false
43 | }
44 | }
45 | ]
46 | }
--------------------------------------------------------------------------------
/src/Tests/Tests.RecordingSpecific.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | value: 42,
3 | sqlEntries: [
4 | {
5 | Text:
6 | select Value
7 | from MyTable,
8 | HasTransaction: false
9 | },
10 | {
11 | Exception: {
12 | Message: Invalid object name 'BadTable'.,
13 | Number: 208,
14 | Line: 1
15 | },
16 | Command: {
17 | Text:
18 | select Value
19 | from BadTable,
20 | HasTransaction: false
21 | }
22 | }
23 | ]
24 | }
--------------------------------------------------------------------------------
/src/Tests/Tests.RecordingTest.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | sql: [
3 | {
4 | Text:
5 | select *
6 | from MyTable,
7 | HasTransaction: false
8 | },
9 | {
10 | Text:
11 | select *
12 | from MyTable,
13 | HasTransaction: false
14 | },
15 | {
16 | Text:
17 | select *
18 | from MyTable,
19 | HasTransaction: false
20 | }
21 | ]
22 | }
--------------------------------------------------------------------------------
/src/Tests/Tests.RecordingUsage.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | target: 42,
3 | sql: {
4 | Text:
5 | select Value
6 | from MyTable,
7 | HasTransaction: false
8 | }
9 | }
--------------------------------------------------------------------------------
/src/Tests/Tests.RecordingWithParameter.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | target: null,
3 | sql: {
4 | Text:
5 | select Value
6 | from MyTable
7 | where Value = @param,
8 | Parameters: {
9 | param: 10
10 | },
11 | HasTransaction: false
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Tests/Tests.Schema.verified.md:
--------------------------------------------------------------------------------
1 | ## Tables
2 |
3 | ### MyOtherTable
4 |
5 | ```sql
6 | CREATE TABLE [dbo].[MyOtherTable](
7 | [Value] [int] NULL
8 | ) ON [PRIMARY]
9 | ```
10 |
11 | ### MyTable
12 |
13 | ```sql
14 | CREATE TABLE [dbo].[MyTable](
15 | [Value] [int] NULL
16 | ) ON [PRIMARY]
17 |
18 | CREATE NONCLUSTERED INDEX [MyIndex] ON [dbo].[MyTable]
19 | (
20 | [Value] ASC
21 | ) ON [PRIMARY]
22 |
23 | CREATE TRIGGER MyTrigger
24 | ON MyTable
25 | AFTER UPDATE
26 | AS RAISERROR ('Notify Customer Relations', 16, 10);
27 |
28 | ALTER TABLE [dbo].[MyTable] ENABLE TRIGGER [MyTrigger]
29 | ```
30 |
31 | ## Views
32 |
33 | ### MyView
34 |
35 | ```sql
36 | CREATE VIEW MyView
37 | AS
38 | SELECT Value
39 | FROM MyTable
40 | WHERE (Value > 10);
41 | ```
42 |
43 | ## StoredProcedures
44 |
45 | ### MyProcedure
46 |
47 | ```sql
48 | CREATE PROCEDURE MyProcedure
49 | AS
50 | BEGIN
51 | SET NOCOUNT ON;
52 | SELECT Value
53 | FROM MyTable
54 | WHERE (Value > 10);
55 | END;
56 | ```
57 |
58 | ## UserDefinedFunctions
59 |
60 | ### MyFunction
61 |
62 | ```sql
63 | CREATE FUNCTION MyFunction(
64 | @quantity INT,
65 | @list_price DEC(10,2),
66 | @discount DEC(4,2)
67 | )
68 | RETURNS DEC(10,2)
69 | AS
70 | BEGIN
71 | RETURN @quantity * @list_price * (1 - @discount);
72 | END;
73 | ```
74 |
75 | ## Synonyms
76 |
77 | ### synonym1
78 |
79 | ```sql
80 | CREATE SYNONYM [dbo].[synonym1] FOR [MyTable]
81 | ```
82 |
--------------------------------------------------------------------------------
/src/Tests/Tests.SchemaAsSql.verified.sql:
--------------------------------------------------------------------------------
1 | -- Tables
2 |
3 | CREATE TABLE [dbo].[MyOtherTable](
4 | [Value] [int] NULL
5 | ) ON [PRIMARY]
6 |
7 | CREATE TABLE [dbo].[MyTable](
8 | [Value] [int] NULL
9 | ) ON [PRIMARY]
10 |
11 | CREATE NONCLUSTERED INDEX [MyIndex] ON [dbo].[MyTable]
12 | (
13 | [Value] ASC
14 | ) ON [PRIMARY]
15 |
16 | CREATE TRIGGER MyTrigger
17 | ON MyTable
18 | AFTER UPDATE
19 | AS RAISERROR ('Notify Customer Relations', 16, 10);
20 |
21 | ALTER TABLE [dbo].[MyTable] ENABLE TRIGGER [MyTrigger]
22 |
23 | -- Views
24 |
25 | CREATE VIEW MyView
26 | AS
27 | SELECT Value
28 | FROM MyTable
29 | WHERE (Value > 10);
30 |
31 | -- StoredProcedures
32 |
33 | CREATE PROCEDURE MyProcedure
34 | AS
35 | BEGIN
36 | SET NOCOUNT ON;
37 | SELECT Value
38 | FROM MyTable
39 | WHERE (Value > 10);
40 | END;
41 |
42 | -- UserDefinedFunctions
43 |
44 | CREATE FUNCTION MyFunction(
45 | @quantity INT,
46 | @list_price DEC(10,2),
47 | @discount DEC(4,2)
48 | )
49 | RETURNS DEC(10,2)
50 | AS
51 | BEGIN
52 | RETURN @quantity * @list_price * (1 - @discount);
53 | END;
54 |
55 | -- Synonyms
56 |
57 | CREATE SYNONYM [dbo].[synonym1] FOR [MyTable]
--------------------------------------------------------------------------------
/src/Tests/Tests.SchemaFilter.verified.md:
--------------------------------------------------------------------------------
1 | ## Tables
2 |
3 | ### MyOtherTable
4 |
5 | ```sql
6 | CREATE TABLE [dbo].[MyOtherTable](
7 | [Value] [int] NULL
8 | ) ON [PRIMARY]
9 | ```
10 |
11 | ### MyTable
12 |
13 | ```sql
14 | CREATE TABLE [dbo].[MyTable](
15 | [Value] [int] NULL
16 | ) ON [PRIMARY]
17 |
18 | CREATE NONCLUSTERED INDEX [MyIndex] ON [dbo].[MyTable]
19 | (
20 | [Value] ASC
21 | ) ON [PRIMARY]
22 |
23 | CREATE TRIGGER MyTrigger
24 | ON MyTable
25 | AFTER UPDATE
26 | AS RAISERROR ('Notify Customer Relations', 16, 10);
27 |
28 | ALTER TABLE [dbo].[MyTable] ENABLE TRIGGER [MyTrigger]
29 | ```
30 |
31 | ## Views
32 |
33 | ### MyView
34 |
35 | ```sql
36 | CREATE VIEW MyView
37 | AS
38 | SELECT Value
39 | FROM MyTable
40 | WHERE (Value > 10);
41 | ```
42 |
--------------------------------------------------------------------------------
/src/Tests/Tests.SchemaInDynamic.verified.txt:
--------------------------------------------------------------------------------
1 | {
2 | connection:
3 | ## Tables
4 |
5 | ### MyOtherTable
6 |
7 | ```sql
8 | CREATE TABLE [dbo].[MyOtherTable](
9 | [Value] [int] NULL
10 | ) ON [PRIMARY]
11 | ```
12 |
13 | ### MyTable
14 |
15 | ```sql
16 | CREATE TABLE [dbo].[MyTable](
17 | [Value] [int] NULL
18 | ) ON [PRIMARY]
19 |
20 | CREATE NONCLUSTERED INDEX [MyIndex] ON [dbo].[MyTable]
21 | (
22 | [Value] ASC
23 | ) ON [PRIMARY]
24 |
25 | CREATE TRIGGER MyTrigger
26 | ON MyTable
27 | AFTER UPDATE
28 | AS RAISERROR ('Notify Customer Relations', 16, 10);
29 |
30 | ALTER TABLE [dbo].[MyTable] ENABLE TRIGGER [MyTrigger]
31 | ```
32 |
33 | ## Views
34 |
35 | ### MyView
36 |
37 | ```sql
38 | CREATE VIEW MyView
39 | AS
40 | SELECT Value
41 | FROM MyTable
42 | WHERE (Value > 10);
43 | ```
44 |
45 | ## StoredProcedures
46 |
47 | ### MyProcedure
48 |
49 | ```sql
50 | CREATE PROCEDURE MyProcedure
51 | AS
52 | BEGIN
53 | SET NOCOUNT ON;
54 | SELECT Value
55 | FROM MyTable
56 | WHERE (Value > 10);
57 | END;
58 | ```
59 |
60 | ## UserDefinedFunctions
61 |
62 | ### MyFunction
63 |
64 | ```sql
65 | CREATE FUNCTION MyFunction(
66 | @quantity INT,
67 | @list_price DEC(10,2),
68 | @discount DEC(4,2)
69 | )
70 | RETURNS DEC(10,2)
71 | AS
72 | BEGIN
73 | RETURN @quantity * @list_price * (1 - @discount);
74 | END;
75 | ```
76 |
77 | ## Synonyms
78 |
79 | ### synonym1
80 |
81 | ```sql
82 | CREATE SYNONYM [dbo].[synonym1] FOR [MyTable]
83 | ```
84 | }
--------------------------------------------------------------------------------
/src/Tests/Tests.SchemaInclude.verified.md:
--------------------------------------------------------------------------------
1 | ## Tables
2 |
3 | ### MyOtherTable
4 |
5 | ```sql
6 | CREATE TABLE [dbo].[MyOtherTable](
7 | [Value] [int] NULL
8 | ) ON [PRIMARY]
9 | ```
10 |
11 | ### MyTable
12 |
13 | ```sql
14 | CREATE TABLE [dbo].[MyTable](
15 | [Value] [int] NULL
16 | ) ON [PRIMARY]
17 |
18 | CREATE NONCLUSTERED INDEX [MyIndex] ON [dbo].[MyTable]
19 | (
20 | [Value] ASC
21 | ) ON [PRIMARY]
22 |
23 | CREATE TRIGGER MyTrigger
24 | ON MyTable
25 | AFTER UPDATE
26 | AS RAISERROR ('Notify Customer Relations', 16, 10);
27 |
28 | ALTER TABLE [dbo].[MyTable] ENABLE TRIGGER [MyTrigger]
29 | ```
30 |
31 | ## Views
32 |
33 | ### MyView
34 |
35 | ```sql
36 | CREATE VIEW MyView
37 | AS
38 | SELECT Value
39 | FROM MyTable
40 | WHERE (Value > 10);
41 | ```
42 |
--------------------------------------------------------------------------------
/src/Tests/Tests.SchemaIncludeAll.verified.md:
--------------------------------------------------------------------------------
1 | ## Tables
2 |
3 | ### MyOtherTable
4 |
5 | ```sql
6 | CREATE TABLE [dbo].[MyOtherTable](
7 | [Value] [int] NULL
8 | ) ON [PRIMARY]
9 | ```
10 |
11 | ### MyTable
12 |
13 | ```sql
14 | CREATE TABLE [dbo].[MyTable](
15 | [Value] [int] NULL
16 | ) ON [PRIMARY]
17 |
18 | CREATE NONCLUSTERED INDEX [MyIndex] ON [dbo].[MyTable]
19 | (
20 | [Value] ASC
21 | ) ON [PRIMARY]
22 |
23 | CREATE TRIGGER MyTrigger
24 | ON MyTable
25 | AFTER UPDATE
26 | AS RAISERROR ('Notify Customer Relations', 16, 10);
27 |
28 | ALTER TABLE [dbo].[MyTable] ENABLE TRIGGER [MyTrigger]
29 | ```
30 |
31 | ## Views
32 |
33 | ### MyView
34 |
35 | ```sql
36 | CREATE VIEW MyView
37 | AS
38 | SELECT Value
39 | FROM MyTable
40 | WHERE (Value > 10);
41 | ```
42 |
43 | ## StoredProcedures
44 |
45 | ### MyProcedure
46 |
47 | ```sql
48 | CREATE PROCEDURE MyProcedure
49 | AS
50 | BEGIN
51 | SET NOCOUNT ON;
52 | SELECT Value
53 | FROM MyTable
54 | WHERE (Value > 10);
55 | END;
56 | ```
57 |
58 | ## UserDefinedFunctions
59 |
60 | ### MyFunction
61 |
62 | ```sql
63 | CREATE FUNCTION MyFunction(
64 | @quantity INT,
65 | @list_price DEC(10,2),
66 | @discount DEC(4,2)
67 | )
68 | RETURNS DEC(10,2)
69 | AS
70 | BEGIN
71 | RETURN @quantity * @list_price * (1 - @discount);
72 | END;
73 | ```
74 |
75 | ## Synonyms
76 |
77 | ### synonym1
78 |
79 | ```sql
80 | CREATE SYNONYM [dbo].[synonym1] FOR [MyTable]
81 | ```
82 |
--------------------------------------------------------------------------------
/src/Tests/Tests.cs:
--------------------------------------------------------------------------------
1 | [TestFixture]
2 | public class Tests
3 | {
4 | static SqlInstance sqlInstance;
5 |
6 | static Tests() =>
7 | sqlInstance = new(
8 | "VerifySqlServer",
9 | connection =>
10 | {
11 | var server = new Server(new ServerConnection(connection));
12 | server.ConnectionContext.ExecuteNonQuery(
13 | """
14 | CREATE TABLE
15 | MyTable(Value int);
16 | GO
17 |
18 | CREATE INDEX MyIndex
19 | ON MyTable (Value);
20 | GO
21 |
22 | INSERT INTO MyTable (Value)
23 | VALUES (42);
24 | GO
25 |
26 | CREATE TRIGGER MyTrigger
27 | ON MyTable
28 | AFTER UPDATE
29 | AS RAISERROR ('Notify Customer Relations', 16, 10);
30 | GO
31 |
32 | CREATE TABLE
33 | MyOtherTable(Value int);
34 | GO
35 |
36 | CREATE VIEW MyView
37 | AS
38 | SELECT Value
39 | FROM MyTable
40 | WHERE (Value > 10);
41 | GO
42 |
43 | create synonym synonym1
44 | for MyTable;
45 | GO
46 |
47 | CREATE PROCEDURE MyProcedure
48 | AS
49 | BEGIN
50 | SET NOCOUNT ON;
51 | SELECT Value
52 | FROM MyTable
53 | WHERE (Value > 10);
54 | END;
55 | GO
56 |
57 | CREATE FUNCTION MyFunction(
58 | @quantity INT,
59 | @list_price DEC(10,2),
60 | @discount DEC(4,2)
61 | )
62 | RETURNS DEC(10,2)
63 | AS
64 | BEGIN
65 | RETURN @quantity * @list_price * (1 - @discount);
66 | END;
67 | """);
68 | return Task.CompletedTask;
69 | });
70 |
71 | [Test]
72 | public async Task Schema()
73 | {
74 | await using var database = await sqlInstance.Build();
75 | var connection = database.Connection;
76 |
77 | #region SqlServerSchema
78 |
79 | await Verify(connection);
80 |
81 | #endregion
82 | }
83 |
84 | [Test]
85 | public async Task SchemaAsSql()
86 | {
87 | await using var database = await sqlInstance.Build();
88 | var connection = database.Connection;
89 |
90 | #region SqlServerSchemaAsSql
91 |
92 | await Verify(connection)
93 | .SchemaAsSql();
94 |
95 | #endregion
96 | }
97 |
98 | [Test]
99 | public async Task SchemaInDynamic()
100 | {
101 | await using var database = await sqlInstance.Build();
102 | var connection = database.Connection;
103 | await Verify(new
104 | {
105 | connection
106 | });
107 | }
108 |
109 | [Test]
110 | public async Task RecordingError()
111 | {
112 | await using var database = await sqlInstance.Build();
113 | await using var connection = new SqlConnection(database.ConnectionString);
114 | await connection.OpenAsync();
115 | Recording.Start();
116 | await using var command = connection.CreateCommand();
117 | command.CommandText = "select * from MyTabl2e";
118 | try
119 | {
120 | await using var dataReader = await command.ExecuteReaderAsync();
121 | }
122 | catch
123 | {
124 | }
125 |
126 | await Verify()
127 | .ScrubLinesContaining("HelpLink.ProdVer");
128 | }
129 |
130 | [Test]
131 | public async Task CommandEscaped()
132 | {
133 | var command = new SqlCommand
134 | {
135 | CommandText = "select * from [MyTable]"
136 | };
137 | await Verify(command);
138 | }
139 |
140 | [Test]
141 | public async Task CommandEmpty()
142 | {
143 | var command = new SqlCommand
144 | {
145 | CommandText = "select * from MyTable"
146 | };
147 | await Verify(command);
148 | }
149 |
150 | [Test]
151 | public async Task CommandFull()
152 | {
153 | var command = new SqlCommand
154 | {
155 | CommandText = "select * from MyTable",
156 | CommandTimeout = 10,
157 | CommandType = CommandType.StoredProcedure,
158 | DesignTimeVisible = true,
159 | UpdatedRowSource = UpdateRowSource.FirstReturnedRecord,
160 | EnableOptimizedParameterBinding = true,
161 | Notification = new("user data", "options", 10)
162 | };
163 | command.Parameters.AddWithValue("name", 10);
164 | await Verify(command);
165 | }
166 |
167 | [Test]
168 | public async Task Exception()
169 | {
170 | await using var database = await sqlInstance.Build();
171 | await using var connection = new SqlConnection(database.ConnectionString);
172 | await connection.OpenAsync();
173 | await using var command = connection.CreateCommand();
174 | command.CommandText = "select * from MyTabl2e";
175 | await ThrowsTask(() => command.ExecuteReaderAsync());
176 | }
177 |
178 | [Test]
179 | public async Task Error()
180 | {
181 | await using var database = await sqlInstance.Build();
182 | await using var connection = new SqlConnection(database.ConnectionString);
183 | await connection.OpenAsync();
184 | await using var command = connection.CreateCommand();
185 | command.CommandText = "select * from MyTabl2e";
186 | try
187 | {
188 | await command.ExecuteReaderAsync();
189 | }
190 | catch (SqlException exception)
191 | {
192 | await Verify(exception.Errors[0]);
193 | }
194 | }
195 |
196 | [Test]
197 | public async Task RecordingUsage()
198 | {
199 | await using var database = await sqlInstance.Build();
200 | var connectionString = database.ConnectionString;
201 |
202 | #region Recording
203 |
204 | await using var connection = new SqlConnection(connectionString);
205 | await connection.OpenAsync();
206 | Recording.Start();
207 | await using var command = connection.CreateCommand();
208 | command.CommandText = "select Value from MyTable";
209 | var value = await command.ExecuteScalarAsync();
210 | await Verify(value!);
211 |
212 | #endregion
213 | }
214 |
215 | [Test]
216 | public async Task RecordingWithParameter()
217 | {
218 | await using var database = await sqlInstance.Build();
219 | var connectionString = database.ConnectionString;
220 |
221 | await using var connection = new SqlConnection(connectionString);
222 | await connection.OpenAsync();
223 | Recording.Start();
224 | await using var command = connection.CreateCommand();
225 | command.Parameters.AddWithValue("param", 10);
226 | command.CommandText = "select Value from MyTable where Value = @param";
227 | var value = await command.ExecuteScalarAsync();
228 | await Verify(value!);
229 | }
230 |
231 | [Test]
232 | public Task ParameterEmpty()
233 | {
234 | var parameter = new SqlParameter("name", SqlDbType.Date);
235 | return Verify(parameter);
236 | }
237 |
238 | [Test]
239 | public Task ParameterFull()
240 | {
241 | var parameter = BuildFullParameter();
242 |
243 | return Verify(parameter);
244 | }
245 |
246 | [Test]
247 | public Task ParameterCollectionFull()
248 | {
249 | var parameter = BuildFullParameter();
250 |
251 | var parameters = new SqlCommand().Parameters;
252 | parameters.Add(parameter);
253 | return Verify(parameters);
254 | }
255 |
256 | static SqlParameter BuildFullParameter() =>
257 | new("name", SqlDbType.DateTime)
258 | {
259 | Direction = ParameterDirection.InputOutput,
260 | Offset = 5,
261 | Precision = 2,
262 | Scale = 3,
263 | Value = DateTime.Now,
264 | CompareInfo = SqlCompareOptions.BinarySort2,
265 | LocaleId = 10,
266 | Size = 4,
267 | IsNullable = false,
268 | SourceVersion = DataRowVersion.Proposed,
269 | ForceColumnEncryption = true,
270 | SourceColumn = "sourceColumn"
271 | };
272 |
273 | [Test]
274 | public async Task RecordingSpecific()
275 | {
276 | await using var database = await sqlInstance.Build();
277 | var connectionString = database.ConnectionString;
278 |
279 | #region RecordingSpecific
280 |
281 | await using var connection = new SqlConnection(connectionString);
282 | await connection.OpenAsync();
283 | Recording.Start();
284 | await using var command = connection.CreateCommand();
285 | command.CommandText = "select Value from MyTable";
286 | var value = await command.ExecuteScalarAsync();
287 |
288 | await using var errorCommand = connection.CreateCommand();
289 | errorCommand.CommandText = "select Value from BadTable";
290 | try
291 | {
292 | await errorCommand.ExecuteScalarAsync();
293 | }
294 | catch
295 | {
296 | }
297 |
298 | var entries = Recording
299 | .Stop()
300 | .Select(_ => _.Data);
301 | //Optionally filter results
302 | await Verify(
303 | new
304 | {
305 | value,
306 | sqlEntries = entries
307 | });
308 |
309 | #endregion
310 | }
311 |
312 | [Test]
313 | public async Task RecordingReadingResults()
314 | {
315 | await using var database = await sqlInstance.Build();
316 | var connectionString = database.ConnectionString;
317 |
318 | await using var connection = new SqlConnection(connectionString);
319 | await connection.OpenAsync();
320 | Recording.Start();
321 | await using var command = connection.CreateCommand();
322 | command.CommandText = "select Value from MyTable";
323 | await command.ExecuteScalarAsync();
324 |
325 | await using var errorCommand = connection.CreateCommand();
326 | errorCommand.CommandText = "select Value from BadTable";
327 | try
328 | {
329 | await errorCommand.ExecuteScalarAsync();
330 | }
331 | catch
332 | {
333 | }
334 |
335 | #region RecordingReadingResults
336 |
337 | var entries = Recording.Stop();
338 |
339 | // all sql entries via key
340 | var sqlEntries = entries
341 | .Where(_ => _.Name == "sql")
342 | .Select(_ => _.Data);
343 |
344 | // successful Commands via Type
345 | var sqlCommandsViaType = entries
346 | .Select(_ => _.Data)
347 | .OfType();
348 |
349 | // failed Commands via Type
350 | var sqlErrorsViaType = entries
351 | .Select(_ => _.Data)
352 | .OfType();
353 |
354 | #endregion
355 |
356 | await Verify(
357 | new
358 | {
359 | sqlEntries,
360 | sqlCommandsViaType,
361 | sqlErrorsViaType,
362 | });
363 |
364 | }
365 |
366 | [Test]
367 | public async Task RecordingTest()
368 | {
369 | static async Task Execute(SqlConnection sqlConnection)
370 | {
371 | await using var command = sqlConnection.CreateCommand();
372 | command.CommandText = "select * from MyTable";
373 | await using var dataReader = await command.ExecuteReaderAsync();
374 | }
375 |
376 | await using var database = await sqlInstance.Build();
377 | await using var connection = new SqlConnection(database.ConnectionString);
378 | await connection.OpenAsync();
379 | await Execute(connection);
380 | Recording.Start();
381 | await Execute(connection);
382 | await Execute(connection);
383 | await Execute(connection);
384 | await Verify();
385 | }
386 |
387 | [Test]
388 | public async Task SchemaInclude()
389 | {
390 | await using var database = await sqlInstance.Build();
391 | var connection = database.Connection;
392 |
393 | #region SchemaInclude
394 |
395 | await Verify(connection)
396 | // include only tables and views
397 | .SchemaIncludes(DbObjects.Tables | DbObjects.Views);
398 |
399 | #endregion
400 | }
401 |
402 | [Test]
403 | public async Task SchemaIncludeAll()
404 | {
405 | await using var database = await sqlInstance.Build();
406 | var connection = database.Connection;
407 |
408 | await Verify(connection)
409 | .SchemaIncludes(DbObjects.All);
410 | }
411 |
412 | [Test]
413 | public async Task SchemaFilter()
414 | {
415 | await using var database = await sqlInstance.Build();
416 | var connection = database.Connection;
417 |
418 | #region SchemaFilter
419 |
420 | await Verify(connection)
421 | // include tables & views, or named MyTrigger
422 | .SchemaFilter(
423 | _ => _ is TableViewBase ||
424 | _.Name == "MyTrigger");
425 |
426 | #endregion
427 | }
428 | }
--------------------------------------------------------------------------------
/src/Tests/Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Verify.SqlServer.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31717.71
5 | MinimumVisualStudioVersion = 16.0.29201.188
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Verify.SqlServer", "Verify.SqlServer\Verify.SqlServer.csproj", "{C35E31DE-40A9-45C0-817B-41BE5B7D076E}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{76FE87D9-EF6B-4D31-99F2-7099C719AE99}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9A6C4741-C83D-4E05-A62F-8049F65B9B6C}"
11 | ProjectSection(SolutionItems) = preProject
12 | .editorconfig = .editorconfig
13 | ..\.gitignore = ..\.gitignore
14 | appveyor.yml = appveyor.yml
15 | Directory.Build.props = Directory.Build.props
16 | Directory.Packages.props = Directory.Packages.props
17 | global.json = global.json
18 | mdsnippets.json = mdsnippets.json
19 | ..\readme.md = ..\readme.md
20 | nuget.config = nuget.config
21 | ..\.gitattributes = ..\.gitattributes
22 | EndProjectSection
23 | EndProject
24 | Global
25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
26 | Debug|Any CPU = Debug|Any CPU
27 | Release|Any CPU = Release|Any CPU
28 | EndGlobalSection
29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
30 | {C35E31DE-40A9-45C0-817B-41BE5B7D076E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {C35E31DE-40A9-45C0-817B-41BE5B7D076E}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {C35E31DE-40A9-45C0-817B-41BE5B7D076E}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {C35E31DE-40A9-45C0-817B-41BE5B7D076E}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {76FE87D9-EF6B-4D31-99F2-7099C719AE99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {76FE87D9-EF6B-4D31-99F2-7099C719AE99}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {76FE87D9-EF6B-4D31-99F2-7099C719AE99}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {76FE87D9-EF6B-4D31-99F2-7099C719AE99}.Release|Any CPU.Build.0 = Release|Any CPU
38 | EndGlobalSection
39 | GlobalSection(SolutionProperties) = preSolution
40 | HideSolutionNode = FALSE
41 | EndGlobalSection
42 | GlobalSection(ExtensibilityGlobals) = postSolution
43 | SolutionGuid = {B0B94980-5385-4EC6-A0EE-C9A191DDFC05}
44 | EndGlobalSection
45 | EndGlobal
46 |
--------------------------------------------------------------------------------
/src/Verify.SqlServer.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | ..\Shared.sln.DotSettings
3 | True
4 | True
5 | 1
6 |
--------------------------------------------------------------------------------
/src/Verify.SqlServer/Converters/CommandConverter.cs:
--------------------------------------------------------------------------------
1 | class CommandConverter :
2 | WriteOnlyJsonConverter
3 | {
4 | public override void Write(VerifyJsonWriter writer, SqlCommand command)
5 | {
6 | writer.WriteStartObject();
7 | writer.WriteMember(command, SqlFormatter.Format(command.CommandText).ToString(), "Text");
8 | writer.WriteMember(command, command.Parameters, "Parameters");
9 | writer.WriteMember(command, command.Transaction != null, "HasTransaction");
10 |
11 | if (command.CommandTimeout != 30)
12 | {
13 | writer.WriteMember(command, command.CommandTimeout, "Timeout");
14 | }
15 |
16 | if (command.CommandType != CommandType.Text)
17 | {
18 | writer.WriteMember(command, command.CommandType, "Type");
19 | }
20 |
21 | if (!command.DesignTimeVisible)
22 | {
23 | writer.WriteMember(command, command.DesignTimeVisible, "DesignTimeVisible");
24 | }
25 |
26 | writer.WriteMember(command, command.Notification, "Notification");
27 |
28 | if (command.UpdatedRowSource != UpdateRowSource.Both)
29 | {
30 | writer.WriteMember(command, command.UpdatedRowSource, "UpdatedRowSource");
31 | }
32 |
33 | if (command.EnableOptimizedParameterBinding)
34 | {
35 | writer.WriteMember(command, command.EnableOptimizedParameterBinding, "EnableOptimizedParameterBinding");
36 | }
37 |
38 | writer.WriteEndObject();
39 | }
40 | }
--------------------------------------------------------------------------------
/src/Verify.SqlServer/Converters/ConnectionConverter.cs:
--------------------------------------------------------------------------------
1 | class ConnectionConverter :
2 | WriteOnlyJsonConverter
3 | {
4 | public override void Write(VerifyJsonWriter writer, SqlConnection connection)
5 | {
6 | var schemaSettings = writer.Context.GetSchemaSettings();
7 | var builder = new SqlScriptBuilder(schemaSettings);
8 | var script = builder.BuildContent(connection);
9 | writer.WriteValue(script);
10 | }
11 | }
--------------------------------------------------------------------------------
/src/Verify.SqlServer/Converters/ErrorConverter.cs:
--------------------------------------------------------------------------------
1 | class ErrorConverter :
2 | WriteOnlyJsonConverter
3 | {
4 | public override void Write(VerifyJsonWriter writer, SqlError error)
5 | {
6 | writer.WriteStartObject();
7 | writer.WriteMember(error, error.Message, "Message");
8 | writer.WriteMember(error, error.Number, "Number");
9 | writer.WriteMember(error, error.LineNumber, "Line");
10 | if (error.Procedure != "")
11 | {
12 | writer.WriteMember(error, error.Procedure, "Procedure");
13 | }
14 |
15 | writer.WriteEndObject();
16 | }
17 | }
--------------------------------------------------------------------------------
/src/Verify.SqlServer/Converters/ExceptionConverter.cs:
--------------------------------------------------------------------------------
1 | class ExceptionConverter :
2 | WriteOnlyJsonConverter
3 | {
4 | public override void Write(VerifyJsonWriter writer, SqlException exception)
5 | {
6 | writer.WriteStartObject();
7 |
8 | var errors = exception.Errors;
9 |
10 | if (errors.Count == 1)
11 | {
12 | var error = errors[0];
13 | writer.WriteMember(error, error.Message, "Message");
14 | writer.WriteMember(error, error.Number, "Number");
15 | writer.WriteMember(error, error.LineNumber, "Line");
16 | if (exception.Procedure != "")
17 | {
18 | writer.WriteMember(error, error.Procedure, "Procedure");
19 | }
20 | }
21 | else
22 | {
23 | writer.WriteMember(exception, errors, "Errors");
24 | }
25 |
26 | writer.WriteEndObject();
27 | }
28 | }
--------------------------------------------------------------------------------
/src/Verify.SqlServer/Converters/ParameterCollectionConverter.cs:
--------------------------------------------------------------------------------
1 | class ParameterCollectionConverter :
2 | WriteOnlyJsonConverter
3 | {
4 | public override void Write(VerifyJsonWriter writer, SqlParameterCollection parameters)
5 | {
6 | writer.WriteStartObject();
7 |
8 | ParameterConverter.OmitName();
9 | foreach (SqlParameter parameter in parameters)
10 | {
11 | var name = parameter.ParameterName;
12 | object? value;
13 | if (ParameterConverter.IsOnlyValue(parameter))
14 | {
15 | value = parameter.Value;
16 | }
17 | else
18 | {
19 | value = parameter;
20 | }
21 |
22 | writer.WriteMember(parameters, value, name);
23 | }
24 |
25 | ParameterConverter.ClearOmitName();
26 |
27 | writer.WriteEndObject();
28 | }
29 | }
--------------------------------------------------------------------------------
/src/Verify.SqlServer/Converters/ParameterConverter.cs:
--------------------------------------------------------------------------------
1 | class ParameterConverter :
2 | WriteOnlyJsonConverter
3 | {
4 | static AsyncLocal omitName = new();
5 |
6 | public static void OmitName() =>
7 | omitName.Value = true;
8 |
9 | public static void ClearOmitName() =>
10 | omitName.Value = false;
11 |
12 | public override void Write(VerifyJsonWriter writer, SqlParameter parameter)
13 | {
14 | writer.WriteStartObject();
15 | if (!omitName.Value)
16 | {
17 | writer.WriteMember(parameter, parameter.ParameterName, "Name");
18 | }
19 | writer.WriteMember(parameter, parameter.Value, "Value");
20 |
21 | var (tempDbType, tempSqlDbType, tempSqlValue) = InferExpectedProperties(parameter);
22 | if (parameter.SqlValue != parameter.Value &&
23 | !Equals(parameter.SqlValue, tempSqlValue))
24 | {
25 | writer.WriteMember(parameter, parameter.SqlValue, "SqlValue");
26 | }
27 |
28 | if (tempDbType != parameter.DbType)
29 | {
30 | writer.WriteMember(parameter, parameter.DbType, "DbType");
31 | }
32 |
33 | if (tempSqlDbType != parameter.SqlDbType &&
34 | parameter.SqlDbType != parameter.DbType.ToSqlDbType())
35 | {
36 | writer.WriteMember(parameter, parameter.SqlDbType, "SqlDbType");
37 | }
38 |
39 | if (parameter.Direction != ParameterDirection.Input)
40 | {
41 | writer.WriteMember(parameter, parameter.Direction, "Direction");
42 | }
43 |
44 | if (parameter.Offset != 0)
45 | {
46 | writer.WriteMember(parameter, parameter.Offset, "Offset");
47 | }
48 |
49 | if (parameter.Precision != 0)
50 | {
51 | writer.WriteMember(parameter, parameter.Precision, "Precision");
52 | }
53 |
54 | if (parameter.Scale != 0)
55 | {
56 | writer.WriteMember(parameter, parameter.Scale, "Scale");
57 | }
58 |
59 | if (parameter.Size != 0)
60 | {
61 | writer.WriteMember(parameter, parameter.Size, "Size");
62 | }
63 |
64 | if (parameter.CompareInfo != SqlCompareOptions.None)
65 | {
66 | writer.WriteMember(parameter, parameter.CompareInfo, "CompareInfo");
67 | }
68 |
69 | if (parameter.IsNullable)
70 | {
71 | writer.WriteMember(parameter, parameter.IsNullable, "IsNullable");
72 | }
73 |
74 | if (parameter.LocaleId != 0)
75 | {
76 | writer.WriteMember(parameter, parameter.LocaleId, "LocaleId");
77 | }
78 |
79 | if (parameter.SourceColumn != "")
80 | {
81 | writer.WriteMember(parameter, parameter.SourceColumn, "SourceColumn");
82 | }
83 |
84 | if (parameter.SourceVersion != DataRowVersion.Current)
85 | {
86 | writer.WriteMember(parameter, parameter.SourceVersion, "SourceVersion");
87 | }
88 |
89 | if (parameter.TypeName != "")
90 | {
91 | writer.WriteMember(parameter, parameter.TypeName, "TypeName");
92 | }
93 |
94 | if (parameter.ForceColumnEncryption)
95 | {
96 | writer.WriteMember(parameter, parameter.ForceColumnEncryption, "ForceColumnEncryption");
97 | }
98 |
99 | if (parameter.UdtTypeName != "")
100 | {
101 | writer.WriteMember(parameter, parameter.UdtTypeName, "UdtTypeName");
102 | }
103 |
104 | if (parameter.SourceColumnNullMapping)
105 | {
106 | writer.WriteMember(parameter, parameter.SourceColumnNullMapping, "SourceColumnNullMapping");
107 | }
108 |
109 | if (parameter.XmlSchemaCollectionDatabase != "")
110 | {
111 | writer.WriteMember(parameter, parameter.XmlSchemaCollectionDatabase, "XmlSchemaCollectionDatabase");
112 | }
113 |
114 | if (parameter.XmlSchemaCollectionName != "")
115 | {
116 | writer.WriteMember(parameter, parameter.XmlSchemaCollectionName, "XmlSchemaCollectionName");
117 | }
118 |
119 | if (parameter.XmlSchemaCollectionOwningSchema != "")
120 | {
121 | writer.WriteMember(parameter, parameter.XmlSchemaCollectionOwningSchema, "XmlSchemaCollectionOwningSchema");
122 | }
123 |
124 | writer.WriteEndObject();
125 | }
126 |
127 | internal static bool IsOnlyValue(SqlParameter parameter)
128 | {
129 | var (tempDbType, tempSqlDbType, tempSqlValue) = InferExpectedProperties(parameter);
130 | return (parameter.SqlValue == parameter.Value ||
131 | Equals(parameter.SqlValue, tempSqlValue)) &&
132 | tempDbType == parameter.DbType &&
133 | (tempSqlDbType == parameter.SqlDbType ||
134 | parameter.SqlDbType == parameter.DbType.ToSqlDbType()) &&
135 | parameter is
136 | {
137 | Direction: ParameterDirection.Input,
138 | Offset: 0,
139 | Precision: 0,
140 | Scale: 0,
141 | Size: 0,
142 | CompareInfo: SqlCompareOptions.None,
143 | IsNullable: false,
144 | LocaleId: 0,
145 | SourceColumn: "",
146 | SourceVersion: DataRowVersion.Current,
147 | TypeName: "",
148 | ForceColumnEncryption: false,
149 | UdtTypeName: "",
150 | SourceColumnNullMapping: false,
151 | XmlSchemaCollectionDatabase: "",
152 | XmlSchemaCollectionName: "",
153 | XmlSchemaCollectionOwningSchema: ""
154 | };
155 | }
156 |
157 | static (DbType? dbType, SqlDbType? sqlDbType, object? sqlValue) InferExpectedProperties(SqlParameter parameter)
158 | {
159 | if (parameter.Value == null)
160 | {
161 | return (null, null, null);
162 | }
163 |
164 | var temp = new SqlParameter("temp", parameter.Value);
165 | return (temp.DbType, temp.SqlDbType, temp.SqlValue);
166 | }
167 | }
--------------------------------------------------------------------------------
/src/Verify.SqlServer/Extensions.cs:
--------------------------------------------------------------------------------
1 | static class Extensions
2 | {
3 | public static SqlDbType ToSqlDbType(this DbType dbType) =>
4 | dbType switch
5 | {
6 | DbType.AnsiString => SqlDbType.VarChar,
7 | DbType.Binary => SqlDbType.Binary,
8 | DbType.Byte => SqlDbType.TinyInt,
9 | DbType.Boolean => SqlDbType.Bit,
10 | DbType.Currency => SqlDbType.Money,
11 | DbType.Date => SqlDbType.Date,
12 | DbType.DateTime => SqlDbType.DateTime,
13 | DbType.Decimal => SqlDbType.Decimal,
14 | DbType.Double => SqlDbType.Float,
15 | DbType.Guid => SqlDbType.UniqueIdentifier,
16 | DbType.Int16 => SqlDbType.SmallInt,
17 | DbType.Int32 => SqlDbType.Int,
18 | DbType.Int64 => SqlDbType.BigInt,
19 | DbType.Object => SqlDbType.Variant,
20 | DbType.String => SqlDbType.NVarChar,
21 | DbType.Time => SqlDbType.Time,
22 | DbType.AnsiStringFixedLength => SqlDbType.Char,
23 | DbType.StringFixedLength => SqlDbType.NChar,
24 | DbType.Xml => SqlDbType.Xml,
25 | DbType.DateTime2 => SqlDbType.DateTime2,
26 | DbType.DateTimeOffset => SqlDbType.DateTimeOffset,
27 | _ => throw new ArgumentOutOfRangeException(nameof(dbType), dbType, null)
28 | };
29 | }
--------------------------------------------------------------------------------
/src/Verify.SqlServer/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using System.Data;
2 | global using System.Data.Common;
3 | global using System.Data.SqlTypes;
4 | global using System.Globalization;
5 | global using Microsoft.Data.SqlClient;
6 | global using Microsoft.Extensions.DiagnosticAdapter;
7 | global using Microsoft.SqlServer.Management.Common;
8 | global using Microsoft.SqlServer.TransactSql.ScriptDom;
9 | global using VerifyTests.SqlServer;
--------------------------------------------------------------------------------
/src/Verify.SqlServer/Recording/ErrorEntry.cs:
--------------------------------------------------------------------------------
1 | namespace VerifyTests.SqlServer;
2 |
3 | public class ErrorEntry(DbCommand command, DbException exception)
4 | {
5 | public DbException Exception { get; } = exception;
6 | public DbCommand Command { get; } = command;
7 | }
--------------------------------------------------------------------------------
/src/Verify.SqlServer/Recording/Listener.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable CA1822
2 | // https://github.com/dotnet/SqlClient/blob/master/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientDiagnosticListenerExtensions.cs
3 | class Listener :
4 | IObserver,
5 | IDisposable
6 | {
7 | ConcurrentQueue subscriptions = [];
8 |
9 | public void OnNext(DiagnosticListener value)
10 | {
11 | if (value.Name != "SqlClientDiagnosticListener")
12 | {
13 | return;
14 | }
15 |
16 | subscriptions.Enqueue(
17 | value.SubscribeWithAdapter(
18 | this,
19 | _ => Recording.IsRecording()));
20 | }
21 |
22 | [DiagnosticName("Microsoft.Data.SqlClient.WriteCommandAfter")]
23 | public void OnMsCommandAfter(SqlCommand command) =>
24 | Recording.TryAdd("sql", command.Clone());
25 |
26 | [DiagnosticName("Microsoft.Data.SqlClient.WriteCommandError")]
27 | public void OnMsErrorExecuteCommand(SqlCommand command, SqlException exception) =>
28 | Recording.TryAdd("sql", new ErrorEntry(command.Clone(), exception));
29 |
30 | void Clear()
31 | {
32 | foreach (var subscription in subscriptions)
33 | {
34 | subscription.Dispose();
35 | }
36 | }
37 |
38 | public void Dispose() =>
39 | Clear();
40 |
41 | public void OnCompleted() =>
42 | Clear();
43 |
44 | public void OnError(Exception error)
45 | {
46 | }
47 | }
--------------------------------------------------------------------------------
/src/Verify.SqlServer/RemoveSquareBracketVisitor.cs:
--------------------------------------------------------------------------------
1 | class RemoveSquareBracketVisitor : TSqlFragmentVisitor
2 | {
3 | public override void Visit(Identifier node)
4 | {
5 | if (node.QuoteType == QuoteType.SquareBracket)
6 | {
7 | node.QuoteType = QuoteType.NotQuoted;
8 | }
9 |
10 | base.Visit(node);
11 | }
12 | }
--------------------------------------------------------------------------------
/src/Verify.SqlServer/SchemaValidation/DbObjects.cs:
--------------------------------------------------------------------------------
1 | namespace VerifyTests.SqlServer;
2 |
3 | [Flags]
4 | public enum DbObjects
5 | {
6 | StoredProcedures = 1,
7 | Synonyms = 2,
8 | Tables = 4,
9 | UserDefinedFunctions = 8,
10 | Views = 16,
11 | All = StoredProcedures | Synonyms | Tables | UserDefinedFunctions | Views
12 | }
--------------------------------------------------------------------------------
/src/Verify.SqlServer/SchemaValidation/Format.cs:
--------------------------------------------------------------------------------
1 | enum Format
2 | {
3 | Md,
4 | Sql,
5 | }
--------------------------------------------------------------------------------
/src/Verify.SqlServer/SchemaValidation/Obsoletes.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable UnusedParameter.Global
2 |
3 | namespace VerifyTests;
4 |
5 | public static partial class VerifySettingsSqlExtensions
6 | {
7 | [Obsolete($"Use {nameof(SchemaIncludes)} or {nameof(SchemaFilter)}", true)]
8 | public static SettingsTask SchemaSettings(
9 | this SettingsTask settings,
10 | bool storedProcedures = true,
11 | bool tables = true,
12 | bool views = true,
13 | bool userDefinedFunctions = true,
14 | bool synonyms = true,
15 | Func? includeItem = null) =>
16 | throw new();
17 |
18 | [Obsolete($"Use {nameof(SchemaIncludes)} or {nameof(SchemaFilter)}", true)]
19 | public static void SchemaSettings(
20 | this VerifySettings settings,
21 | bool storedProcedures = true,
22 | bool tables = true,
23 | bool views = true,
24 | bool userDefinedFunctions = true,
25 | bool synonyms = true,
26 | Func? includeItem = null)
27 | {
28 | }
29 | }
--------------------------------------------------------------------------------
/src/Verify.SqlServer/SchemaValidation/SchemaSettings.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.SqlServer.Management.Smo;
2 |
3 | class SchemaSettings
4 | {
5 | public DbObjects Includes { get; set; } = DbObjects.All;
6 | public Format Format { get; set; } = Format.Md;
7 | public bool IsMd => Format == Format.Md;
8 | public Func IncludeItem { get; set; } = _ => true;
9 | }
--------------------------------------------------------------------------------
/src/Verify.SqlServer/SchemaValidation/SqlScriptBuilder.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.SqlServer.Management.Smo;
2 |
3 | class SqlScriptBuilder(SchemaSettings settings)
4 | {
5 | static Dictionary tableSettingsToScrubLookup;
6 |
7 | static SqlScriptBuilder()
8 | {
9 | string[] defaultsToScrub =
10 | [
11 | "PAD_INDEX = OFF",
12 | "STATISTICS_NORECOMPUTE = OFF",
13 | "SORT_IN_TEMPDB = OFF",
14 | "DROP_EXISTING = OFF",
15 | "ONLINE = OFF",
16 | "ALLOW_ROW_LOCKS = ON",
17 | "IGNORE_DUP_KEY = OFF",
18 | "ALLOW_PAGE_LOCKS = ON",
19 | "OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF",
20 | "SORT_IN_TEMPDB = OFF"
21 | ];
22 | tableSettingsToScrubLookup = [];
23 | foreach (var toScrub in defaultsToScrub)
24 | {
25 | tableSettingsToScrubLookup[$"({toScrub}, "] = "(";
26 | tableSettingsToScrubLookup[$"({toScrub})"] = "()";
27 | tableSettingsToScrubLookup[$", {toScrub}"] = "";
28 | }
29 | }
30 |
31 | public string BuildContent(SqlConnection connection)
32 | {
33 | var builder = new SqlConnectionStringBuilder(connection.ConnectionString);
34 | var server = new Server(new ServerConnection(connection));
35 |
36 | return BuildContent(server, builder);
37 | }
38 |
39 | string BuildContent(Server server, SqlConnectionStringBuilder builder)
40 | {
41 | server.SetDefaultInitFields(typeof(Table), "Name", "IsSystemObject");
42 | server.SetDefaultInitFields(typeof(View), "Name", "IsSystemObject");
43 | server.SetDefaultInitFields(typeof(StoredProcedure), "Name", "IsSystemObject");
44 | server.SetDefaultInitFields(typeof(UserDefinedFunction), "Name", "IsSystemObject");
45 | server.SetDefaultInitFields(typeof(Trigger), "Name", "IsSystemObject");
46 | server.SetDefaultInitFields(typeof(Synonym), "Name");
47 | var database = server.Databases[builder.InitialCatalog];
48 | database.Tables.Refresh();
49 |
50 | return GetScriptingObjects(database);
51 | }
52 |
53 | string GetScriptingObjects(Database database)
54 | {
55 | var options = new ScriptingOptions
56 | {
57 | ChangeTracking = true,
58 | NoCollation = true,
59 | Triggers = true,
60 | Indexes = true,
61 | };
62 |
63 | var builder = new StringBuilder();
64 |
65 | if (settings.Includes.HasFlag(DbObjects.Tables))
66 | {
67 | AppendType(builder, options, database.Tables, _ => _.IsSystemObject);
68 | ScrubTableSettings(builder);
69 | }
70 |
71 | if (settings.Includes.HasFlag(DbObjects.Views))
72 | {
73 | AppendType(builder, options, database.Views, _ => _.IsSystemObject);
74 | }
75 |
76 | if (settings.Includes.HasFlag(DbObjects.StoredProcedures))
77 | {
78 | AppendType(builder, options, database.StoredProcedures, _ => _.IsSystemObject);
79 | }
80 |
81 | if (settings.Includes.HasFlag(DbObjects.UserDefinedFunctions))
82 | {
83 | AppendType(builder, options, database.UserDefinedFunctions, _ => _.IsSystemObject);
84 | }
85 |
86 | if (settings.Includes.HasFlag(DbObjects.Synonyms))
87 | {
88 | AppendType(builder, options, database.Synonyms, _ => false);
89 | }
90 |
91 | var result = builder
92 | .ToString()
93 | .TrimEnd();
94 |
95 | if (string.IsNullOrWhiteSpace(result))
96 | {
97 | if (settings.IsMd)
98 | {
99 | return "## No matching items found";
100 | }
101 |
102 | return "-- No matching items found";
103 | }
104 |
105 | return result;
106 | }
107 |
108 | static void ScrubTableSettings(StringBuilder builder)
109 | {
110 | foreach (var toScrub in tableSettingsToScrubLookup)
111 | {
112 | builder.Replace(toScrub.Key, toScrub.Value);
113 | }
114 |
115 | builder.Replace(")WITH () ", ") ");
116 | }
117 |
118 | void AppendType(StringBuilder builder, ScriptingOptions options, SmoCollectionBase items, Func isSystem)
119 | where T : NamedSmoObject, IScriptable
120 | {
121 | var filtered = items
122 | .Cast()
123 | .Where(_ => !isSystem(_) && settings.IncludeItem(_))
124 | .ToList();
125 | if (filtered.Count == 0)
126 | {
127 | return;
128 | }
129 |
130 | if (settings.IsMd)
131 | {
132 | builder.AppendLineN($"## {typeof(T).Name}s");
133 | }
134 | else
135 | {
136 | builder.AppendLineN($"-- {typeof(T).Name}s");
137 | }
138 | builder.AppendLineN();
139 |
140 | foreach (var item in filtered)
141 | {
142 | AddItem(builder, options, item);
143 | }
144 | }
145 |
146 | void AddItem(StringBuilder builder, ScriptingOptions options, T item)
147 | where T : NamedSmoObject, IScriptable
148 | {
149 | var lines = ScriptLines(options, item);
150 | if (settings.IsMd)
151 | {
152 | builder.AppendLineN($"### {item.Name}");
153 | builder.AppendLineN();
154 | builder.AppendLineN("```sql");
155 | AppendLines(builder, lines);
156 | builder.AppendLineN("```");
157 | }
158 | else
159 | {
160 | AppendLines(builder, lines);
161 | }
162 |
163 | builder.AppendLineN();
164 | }
165 |
166 | static void AppendLines(StringBuilder builder, List lines)
167 | {
168 | if (lines.Count == 1)
169 | {
170 | builder.AppendLineN(
171 | lines[0]
172 | .AsSpan()
173 | .Trim());
174 | return;
175 | }
176 |
177 | for (var index = 0; index < lines.Count; index++)
178 | {
179 | var line = lines[index];
180 | var span = line.AsSpan();
181 | if (index == 0)
182 | {
183 | builder.AppendLineN(span.TrimStart());
184 | continue;
185 | }
186 |
187 | if (index == lines.Count - 1)
188 | {
189 | builder.AppendLineN(span.TrimEnd());
190 | continue;
191 | }
192 |
193 | builder.AppendLineN(span);
194 | }
195 | }
196 |
197 | static List ScriptLines(ScriptingOptions options, T item)
198 | where T : NamedSmoObject, IScriptable =>
199 | item
200 | .Script(options)
201 | .Cast()
202 | .Where(_ => !IsSet(_))
203 | .ToList();
204 |
205 | static bool IsSet(string script) =>
206 | script is
207 | "SET ANSI_NULLS ON" or
208 | "SET QUOTED_IDENTIFIER ON";
209 | }
--------------------------------------------------------------------------------
/src/Verify.SqlServer/SchemaValidation/VerifySettingsExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.SqlServer.Management.Smo;
2 |
3 | namespace VerifyTests;
4 |
5 | public static partial class VerifySettingsSqlExtensions
6 | {
7 | public static SettingsTask SchemaIncludes(
8 | this SettingsTask settings,
9 | DbObjects includes)
10 | {
11 | settings.CurrentSettings.SchemaIncludes(includes);
12 | return settings;
13 | }
14 |
15 | public static void SchemaIncludes(
16 | this VerifySettings settings,
17 | DbObjects includes) =>
18 | GetOrAddSettings(settings)
19 | .Includes = includes;
20 |
21 | public static SettingsTask SchemaFilter(
22 | this SettingsTask settings,
23 | Func filter)
24 | {
25 | settings.CurrentSettings.SchemaFilter(filter);
26 | return settings;
27 | }
28 |
29 | public static void SchemaFilter(
30 | this VerifySettings settings,
31 | Func filter) =>
32 | GetOrAddSettings(settings)
33 | .IncludeItem = filter;
34 |
35 | public static SettingsTask SchemaAsMarkdown(
36 | this SettingsTask settings)
37 | {
38 | settings.CurrentSettings.SchemaAsMarkdown();
39 | return settings;
40 | }
41 |
42 | public static void SchemaAsMarkdown(
43 | this VerifySettings settings) =>
44 | GetOrAddSettings(settings)
45 | .Format = Format.Md;
46 |
47 | public static SettingsTask SchemaAsSql(
48 | this SettingsTask settings)
49 | {
50 | settings.CurrentSettings.SchemaAsSql();
51 | return settings;
52 | }
53 |
54 | public static void SchemaAsSql(
55 | this VerifySettings settings) =>
56 | GetOrAddSettings(settings)
57 | .Format = Format.Sql;
58 |
59 | static SchemaSettings GetOrAddSettings(VerifySettings settings)
60 | {
61 | var context = settings.Context;
62 | if (context.TryGetValue("SqlServer", out var value))
63 | {
64 | return (SchemaSettings) value;
65 | }
66 |
67 | var schemaSettings = new SchemaSettings();
68 | context["SqlServer"] = schemaSettings;
69 | return schemaSettings;
70 | }
71 |
72 | internal static SchemaSettings GetSchemaSettings(this IReadOnlyDictionary context)
73 | {
74 | if (context.TryGetValue("SqlServer", out var value))
75 | {
76 | return (SchemaSettings) value;
77 | }
78 |
79 | return defaultSettings;
80 | }
81 |
82 | static SchemaSettings defaultSettings = new();
83 | }
--------------------------------------------------------------------------------
/src/Verify.SqlServer/SqlFormatter.cs:
--------------------------------------------------------------------------------
1 | static class SqlFormatter
2 | {
3 | public static StringBuilder Format(string input)
4 | {
5 | var parser = new TSql170Parser(false);
6 | using var reader = new StringReader(input);
7 | var fragment = parser.Parse(reader, out var errors);
8 |
9 | if (errors.Count > 0)
10 | {
11 | throw new(
12 | $"""
13 | Failed to parse sql.
14 |
15 | Errors:
16 | {string.Join(Environment.NewLine, errors.Select(_ => _.Message))}
17 |
18 | Sql input:
19 | {input}
20 | """);
21 | }
22 |
23 | var visitor = new RemoveSquareBracketVisitor();
24 | fragment.Accept(visitor);
25 |
26 | var generator = new Sql170ScriptGenerator(
27 | new()
28 | {
29 | SqlVersion = SqlVersion.Sql170,
30 | KeywordCasing = KeywordCasing.Lowercase,
31 | IndentationSize = 2,
32 | AlignClauseBodies = true
33 | });
34 |
35 | var builder = new StringBuilder();
36 | using (var writer = new StringWriter(builder, CultureInfo.InvariantCulture))
37 | {
38 | generator.GenerateScript(fragment, writer);
39 | }
40 |
41 | builder.TrimEnd();
42 | // ReSharper disable once UseIndexFromEndExpression
43 | if (builder[builder.Length -1] == ';')
44 | {
45 | builder.Length--;
46 | }
47 |
48 | return builder;
49 | }
50 | }
--------------------------------------------------------------------------------
/src/Verify.SqlServer/Verify.SqlServer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net48;net8.0;net9.0
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/Verify.SqlServer/VerifySqlServer.cs:
--------------------------------------------------------------------------------
1 | namespace VerifyTests;
2 |
3 | public static class VerifySqlServer
4 | {
5 | static Listener listener = new();
6 |
7 | public static bool Initialized { get; private set; }
8 |
9 | public static void Initialize()
10 | {
11 | if (Initialized)
12 | {
13 | throw new("Already Initialized");
14 | }
15 |
16 | Initialized = true;
17 |
18 | InnerVerifier.ThrowIfVerifyHasBeenRun();
19 | VerifierSettings.AddExtraSettings(settings =>
20 | {
21 | var converters = settings.Converters;
22 | converters.Add(new ErrorConverter());
23 | converters.Add(new ConnectionConverter());
24 | converters.Add(new CommandConverter());
25 | converters.Add(new ExceptionConverter());
26 | converters.Add(new ParameterConverter());
27 | converters.Add(new ParameterCollectionConverter());
28 | });
29 |
30 | VerifierSettings.RegisterFileConverter(ToSql);
31 | // ReSharper disable once UnusedVariable
32 | var subscription = DiagnosticListener.AllListeners.Subscribe(listener);
33 | }
34 |
35 | static ConversionResult ToSql(SqlConnection connection, IReadOnlyDictionary context)
36 | {
37 | var settings = context.GetSchemaSettings();
38 | var builder = new SqlScriptBuilder(settings);
39 | var content = builder.BuildContent(connection);
40 | var extension = GetExtension(settings);
41 | return new(null, extension, content);
42 | }
43 |
44 | static string GetExtension(SchemaSettings settings)
45 | {
46 | var format = settings.Format;
47 | return format switch
48 | {
49 | Format.Md => "md",
50 | Format.Sql => "sql",
51 | _ => throw new ArgumentOutOfRangeException(nameof(format), format, null)
52 | };
53 | }
54 | }
--------------------------------------------------------------------------------
/src/appveyor.yml:
--------------------------------------------------------------------------------
1 | image: Visual Studio 2022
2 | environment:
3 | DOTNET_NOLOGO: true
4 | DOTNET_CLI_TELEMETRY_OPTOUT: true
5 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
6 | build_script:
7 | - pwsh: |
8 | Invoke-WebRequest "https://dot.net/v1/dotnet-install.ps1" -OutFile "./dotnet-install.ps1"
9 | ./dotnet-install.ps1 -JSonFile src/global.json -Architecture x64 -InstallDir 'C:\Program Files\dotnet'
10 | - dotnet build src --configuration Release
11 | - dotnet test src --configuration Release --no-build --no-restore
12 | test: off
13 | artifacts:
14 | - path: nugets\**\*.nupkg
--------------------------------------------------------------------------------
/src/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "9.0.301",
4 | "allowPrerelease": true,
5 | "rollForward": "latestFeature"
6 | }
7 | }
--------------------------------------------------------------------------------
/src/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VerifyTests/Verify.SqlServer/72cfc602c4a86887ed1df21f8b3bbaf56ea84e17/src/icon.png
--------------------------------------------------------------------------------
/src/key.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VerifyTests/Verify.SqlServer/72cfc602c4a86887ed1df21f8b3bbaf56ea84e17/src/key.snk
--------------------------------------------------------------------------------
/src/mdsnippets.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json",
3 | "TocExcludes": [ "NuGet package", "Release Notes", "Icon" ],
4 | "MaxWidth": 80,
5 | "ValidateContent": true,
6 | "Convention": "InPlaceOverwrite"
7 | }
--------------------------------------------------------------------------------
/src/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
20 |
21 |
24 |
25 |
26 |
--------------------------------------------------------------------------------