├── .all-contributorsrc ├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ ├── ci.yml │ ├── codesee-arch-diagram.yml │ ├── needs-reply-remove.yml │ ├── needs-reply.yml │ └── release.yml ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── LICENSE ├── QueryBuilder.Tests ├── AggregateTests.cs ├── DefineTest.cs ├── DeleteTests.cs ├── ExecutionTests.cs ├── Firebird │ └── FirebirdLimitTests.cs ├── GeneralTests.cs ├── HelperTests.cs ├── Infrastructure │ ├── TestCompiler.cs │ ├── TestCompilersContainer.cs │ ├── TestSqlResultContainer.cs │ └── TestSupport.cs ├── InfrastructureTests.cs ├── InsertTests.cs ├── MySql │ └── MySqlLimitTests.cs ├── MySqlExecutionTest.cs ├── OperatorWhitelistTests.cs ├── Oracle │ ├── OracleDateConditionTests.cs │ ├── OracleInsertManyTests.cs │ ├── OracleLegacyLimitTests.cs │ └── OracleLimitTests.cs ├── ParameterTypeTests.cs ├── PostgreSql │ └── PostgreSqlLimitTests.cs ├── QueryBuilder.Tests.csproj ├── QueryFactoryExtension.cs ├── SQLiteExecutionTest.cs ├── SelectTests.cs ├── SqlServer │ ├── NestedSelectTests.cs │ ├── SqlServerLegacyLimitTests.cs │ ├── SqlServerLimitTests.cs │ └── SqlServerTests.cs ├── Sqlite │ └── SqliteLimitTests.cs ├── UpdateTests.cs └── WhereTests.cs ├── QueryBuilder ├── Base.Where.cs ├── BaseQuery.cs ├── Clauses │ ├── AbstractClause.cs │ ├── AggregateClause.cs │ ├── ColumnClause.cs │ ├── Combine.cs │ ├── ConditionClause.cs │ ├── FromClause.cs │ ├── IncrementClause.cs │ ├── InsertClause.cs │ ├── JoinClause.cs │ ├── LimitClause.cs │ ├── OffsetClause.cs │ └── OrderClause.cs ├── ColumnAttribute.cs ├── Compilers │ ├── Compiler.Conditions.cs │ ├── Compiler.cs │ ├── ConditionsCompilerProvider.cs │ ├── CteFinder.cs │ ├── EngineCodes.cs │ ├── FirebirdCompiler.cs │ ├── MySqlCompiler.cs │ ├── OracleCompiler.cs │ ├── PostgresCompiler.cs │ ├── SqlServerCompiler.cs │ └── SqliteCompiler.cs ├── Expressions.cs ├── Extensions │ └── QueryForExtensions.cs ├── Helper.cs ├── IgnoreAttribute.cs ├── Include.cs ├── Join.cs ├── Properties │ └── AssemblyInfo.cs ├── Query.Aggregate.cs ├── Query.Combine.cs ├── Query.Delete.cs ├── Query.Having.cs ├── Query.Insert.cs ├── Query.Join.cs ├── Query.Select.cs ├── Query.Update.cs ├── Query.cs ├── QueryBuilder.csproj ├── SqlResult.cs ├── UnsafeLiteral.cs └── Variable.cs ├── README.md ├── SqlKata.Execution ├── InsertGetId.cs ├── PaginationIterator.cs ├── PaginationResult.cs ├── Properties │ └── AssemblyInfo.cs ├── Query.Extensions.cs ├── QueryFactory.cs ├── SqlKata.Execution.csproj └── XQuery.cs └── sqlkata.sln /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "mnsrulz", 10 | "name": "mnsrulz", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/1809086?v=4", 12 | "profile": "https://github.com/mnsrulz", 13 | "contributions": [ 14 | "code" 15 | ] 16 | } 17 | ], 18 | "contributorsPerLine": 7, 19 | "projectName": "querybuilder", 20 | "projectOwner": "sqlkata", 21 | "repoType": "github", 22 | "repoHost": "https://github.com", 23 | "skipCi": true 24 | } 25 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | # Note: the trim_trailing_whitespace option is br0ken in visualstudio, it 7 | # simply does not follow the EditorConfig specification. Therefor you are 8 | # strongly encouraged to not rely on this setting alone, but please install 9 | # the following extension too: https://marketplace.visualstudio.com/items?itemName=MadsKristensen.TrailingWhitespaceVisualizer 10 | # 11 | # References: 12 | # https://developercommunity.visualstudio.com/t/EditorConfig:-trim_trailing_whitespace-d/1240174?space=8&q=trim_trailing_whitespace 13 | # https://developercommunity.visualstudio.com/t/editorconfig-trim_trailing_whitespace-on/134457?space=8&q=trim_trailing_whitespace 14 | # https://developercommunity.visualstudio.com/t/trim_trailing_whitespace-in-editorconfi/1351034?space=8&q=trim_trailing_whitespace 15 | # https://developercommunity.visualstudio.com/t/BUG:-editorconfig-trim_trailing_whitespa/953937?space=8&q=trim_trailing_whitespace 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | indent_style = tab 19 | indent_size = 2 20 | 21 | 22 | [*.cs] # To match existing style 23 | indent_style = space 24 | indent_size = 4 25 | 26 | 27 | [*.yml] 28 | indent_style = space 29 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ['ahmad-moussawi'] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: sqlkata 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | env: 6 | # Stop wasting time caching packages 7 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 8 | # Disable sending usage data to Microsoft 9 | DOTNET_CLI_TELEMETRY_OPTOUT: true 10 | 11 | # MYSQL DB 12 | DB_MYSQL_DATABASE: test_sqlkata 13 | DB_MYSQL_USER: root 14 | DB_MYSQL_PASSWORD: "root" 15 | DB_MYSQL_HOST: localhost 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | timeout-minutes: 15 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | - name: Build 24 | run: dotnet build --configuration Release 25 | - name: Start MySql 26 | run: | 27 | sudo /etc/init.d/mysql start 28 | mysqladmin -u ${{ env.DB_MYSQL_USER }} -p${{ env.DB_MYSQL_PASSWORD }} password '' 29 | mysql -e 'CREATE DATABASE ${{ env.DB_MYSQL_DATABASE }};' -u${{ env.DB_MYSQL_USER }} 30 | mysql -e 'CREATE TABLE cars(id int primary key auto_increment);' -u${{ env.DB_MYSQL_USER }} ${{ env.DB_MYSQL_DATABASE }} 31 | mysql -e 'SHOW TABLES;' -u${{ env.DB_MYSQL_USER }} ${{ env.DB_MYSQL_DATABASE }} 32 | - name: Test 33 | run: dotnet test --configuration Release --no-build 34 | env: 35 | SQLKATA_MYSQL_HOST: ${{ env.DB_MYSQL_HOST }} 36 | SQLKATA_MYSQL_USER: ${{ env.DB_MYSQL_USER }} 37 | SQLKATA_MYSQL_DB: ${{ env.DB_MYSQL_DATABASE }} 38 | -------------------------------------------------------------------------------- /.github/workflows/codesee-arch-diagram.yml: -------------------------------------------------------------------------------- 1 | # This workflow was added by CodeSee. Learn more at https://codesee.io/ 2 | # This is v2.0 of this workflow file 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request_target: 8 | types: [opened, synchronize, reopened] 9 | 10 | name: CodeSee 11 | 12 | permissions: read-all 13 | 14 | jobs: 15 | codesee: 16 | runs-on: ubuntu-latest 17 | continue-on-error: true 18 | name: Analyze the repo with CodeSee 19 | steps: 20 | - uses: Codesee-io/codesee-action@v2 21 | with: 22 | codesee-token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} 23 | codesee-url: https://app.codesee.io 24 | -------------------------------------------------------------------------------- /.github/workflows/needs-reply-remove.yml: -------------------------------------------------------------------------------- 1 | name: Remove needs-reply label 2 | 3 | on: 4 | issue_comment: 5 | types: 6 | - created 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | if: | 12 | github.event.comment.author_association != 'OWNER' && 13 | github.event.comment.author_association != 'COLLABORATOR' 14 | steps: 15 | - name: Remove needs-reply label 16 | uses: octokit/request-action@v2.x 17 | continue-on-error: true 18 | with: 19 | route: DELETE /repos/:repository/issues/:issue/labels/:label 20 | repository: ${{ github.repository }} 21 | issue: ${{ github.event.issue.number }} 22 | label: needs-reply 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/needs-reply.yml: -------------------------------------------------------------------------------- 1 | name: Close old issues that need reply 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Close old issues that need reply 12 | uses: dwieeb/needs-reply@v2 13 | with: 14 | repo-token: ${{ secrets.GITHUB_TOKEN }} 15 | issue-label: needs-reply 16 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - "v[0-9]+.[0-9]+.[0-9]+" 5 | env: 6 | # Stop wasting time caching packages 7 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 8 | # Disable sending usage data to Microsoft 9 | DOTNET_CLI_TELEMETRY_OPTOUT: true 10 | 11 | # MYSQL DB 12 | DB_MYSQL_DATABASE: test_sqlkata 13 | DB_MYSQL_USER: root 14 | DB_MYSQL_PASSWORD: "root" 15 | DB_MYSQL_HOST: localhost 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | timeout-minutes: 15 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | - name: Verify commit exists in origin/main 26 | run: git branch --remote --contains | grep origin/main 27 | # - name: Extract release notes 28 | # run: | 29 | # git log --pretty=format:'%d %s' ${GITHUB_REF} | perl -pe 's| \(.*tag: v(\d+.\d+.\d+(-preview\d{3})?)(, .*?)*\)|\n## \1\n|g' > RELEASE-NOTES 30 | - name: Set VERSION variable from tag 31 | run: echo "VERSION=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_ENV 32 | - name: Build 33 | run: dotnet build --configuration Release /p:Version=${VERSION} 34 | - name: Start MySql 35 | run: | 36 | sudo /etc/init.d/mysql start 37 | mysqladmin -u ${{ env.DB_MYSQL_USER }} -p${{ env.DB_MYSQL_PASSWORD }} password '' 38 | mysql -e 'CREATE DATABASE ${{ env.DB_MYSQL_DATABASE }};' -u${{ env.DB_MYSQL_USER }} 39 | mysql -e 'CREATE TABLE cars(id int primary key auto_increment);' -u${{ env.DB_MYSQL_USER }} ${{ env.DB_MYSQL_DATABASE }} 40 | mysql -e 'SHOW TABLES;' -u${{ env.DB_MYSQL_USER }} ${{ env.DB_MYSQL_DATABASE }} 41 | - name: Test 42 | run: dotnet test --configuration Release /p:Version=${VERSION} --no-build 43 | env: 44 | SQLKATA_MYSQL_HOST: ${{ env.DB_MYSQL_HOST }} 45 | SQLKATA_MYSQL_USER: ${{ env.DB_MYSQL_USER }} 46 | SQLKATA_MYSQL_DB: ${{ env.DB_MYSQL_DATABASE }} 47 | - name: Pack 48 | run: dotnet pack --configuration Release /p:Version=${VERSION} --no-build --output . 49 | - name: Push to Github Packages 50 | run: dotnet nuget push *.${VERSION}.nupkg --skip-duplicate --source https://nuget.pkg.github.com/sqlkata/index.json --api-key ${GITHUB_TOKEN} 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | - name: Push to Nuget 54 | run: dotnet nuget push *.${VERSION}.nupkg --skip-duplicate --source https://api.nuget.org/v3/index.json --api-key ${NUGET_KEY} 55 | env: 56 | NUGET_KEY: ${{ secrets.NUGET_KEY }} 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Other files 5 | *.pfx 6 | npm-debug.log 7 | Program/node_modules 8 | *.db 9 | 10 | # User-specific files 11 | *.suo 12 | *.user 13 | *.userosscache 14 | *.sln.docstates 15 | 16 | # User-specific files (MonoDevelop/Xamarin Studio) 17 | *.userprefs 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | bld/ 27 | [Bb]in/ 28 | [Oo]bj/ 29 | [Ll]og/ 30 | 31 | # Visual Studio 2015 cache/options directory 32 | .vs/ 33 | # Uncomment if you have tasks that create the project's static files in wwwroot 34 | #wwwroot/ 35 | 36 | # MSTest test Results 37 | [Tt]est[Rr]esult*/ 38 | [Bb]uild[Ll]og.* 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # DNX 50 | project.lock.json 51 | project.fragment.lock.json 52 | artifacts/ 53 | 54 | *_i.c 55 | *_p.c 56 | *_i.h 57 | *.ilk 58 | *.meta 59 | *.obj 60 | *.pch 61 | *.pdb 62 | *.pgc 63 | *.pgd 64 | *.rsp 65 | *.sbr 66 | *.tlb 67 | *.tli 68 | *.tlh 69 | *.tmp 70 | *.tmp_proj 71 | *.log 72 | *.vspscc 73 | *.vssscc 74 | .builds 75 | *.pidb 76 | *.svclog 77 | *.scc 78 | 79 | # Chutzpah Test files 80 | _Chutzpah* 81 | 82 | # Visual C++ cache files 83 | ipch/ 84 | *.aps 85 | *.ncb 86 | *.opendb 87 | *.opensdf 88 | *.sdf 89 | *.cachefile 90 | *.VC.db 91 | *.VC.VC.opendb 92 | 93 | # Visual Studio profiler 94 | *.psess 95 | *.vsp 96 | *.vspx 97 | *.sap 98 | 99 | # TFS 2012 Local Workspace 100 | $tf/ 101 | 102 | # Guidance Automation Toolkit 103 | *.gpState 104 | 105 | # ReSharper is a .NET coding add-in 106 | _ReSharper*/ 107 | *.[Rr]e[Ss]harper 108 | *.DotSettings.user 109 | 110 | # JustCode is a .NET coding add-in 111 | .JustCode 112 | 113 | # TeamCity is a build add-in 114 | _TeamCity* 115 | 116 | # DotCover is a Code Coverage Tool 117 | *.dotCover 118 | 119 | # Visual Studio code coverage results 120 | *.coverage 121 | *.coveragexml 122 | 123 | # NCrunch 124 | _NCrunch_* 125 | .*crunch*.local.xml 126 | nCrunchTemp_* 127 | 128 | # MightyMoose 129 | *.mm.* 130 | AutoTest.Net/ 131 | 132 | # Web workbench (sass) 133 | .sass-cache/ 134 | 135 | # Installshield output folder 136 | [Ee]xpress/ 137 | 138 | # DocProject is a documentation generator add-in 139 | DocProject/buildhelp/ 140 | DocProject/Help/*.HxT 141 | DocProject/Help/*.HxC 142 | DocProject/Help/*.hhc 143 | DocProject/Help/*.hhk 144 | DocProject/Help/*.hhp 145 | DocProject/Help/Html2 146 | DocProject/Help/html 147 | 148 | # Click-Once directory 149 | publish/ 150 | 151 | # Publish Web Output 152 | *.[Pp]ublish.xml 153 | *.azurePubxml 154 | # TODO: Comment the next line if you want to checkin your web deploy settings 155 | # but database connection strings (with potential passwords) will be unencrypted 156 | *.pubxml 157 | *.publishproj 158 | 159 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 160 | # checkin your Azure Web App publish settings, but sensitive information contained 161 | # in these scripts will be unencrypted 162 | PublishScripts/ 163 | 164 | # NuGet Packages 165 | .nupkgs 166 | *.nupkg 167 | # The packages folder can be ignored because of Package Restore 168 | **/packages/* 169 | # except build/, which is used as an MSBuild target. 170 | !**/packages/build/ 171 | # Uncomment if necessary however generally it will be regenerated when needed 172 | #!**/packages/repositories.config 173 | # NuGet v3's project.json files produces more ignoreable files 174 | *.nuget.props 175 | *.nuget.targets 176 | 177 | # Microsoft Azure Build Output 178 | csx/ 179 | *.build.csdef 180 | 181 | # Microsoft Azure Emulator 182 | ecf/ 183 | rcf/ 184 | 185 | # Windows Store app package directories and files 186 | AppPackages/ 187 | BundleArtifacts/ 188 | Package.StoreAssociation.xml 189 | _pkginfo.txt 190 | 191 | # Visual Studio cache files 192 | # files ending in .cache can be ignored 193 | *.[Cc]ache 194 | # but keep track of directories ending in .cache 195 | !*.[Cc]ache/ 196 | 197 | # Others 198 | ClientBin/ 199 | ~$* 200 | *~ 201 | *.dbmdl 202 | *.dbproj.schemaview 203 | *.jfm 204 | *.pfx 205 | *.publishsettings 206 | node_modules/ 207 | orleans.codegen.cs 208 | 209 | # Since there are multiple workflows, uncomment next line to ignore bower_components 210 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 211 | #bower_components/ 212 | 213 | # RIA/Silverlight projects 214 | Generated_Code/ 215 | 216 | # Backup & report files from converting an old project file 217 | # to a newer Visual Studio version. Backup files are not needed, 218 | # because we have git ;-) 219 | _UpgradeReport_Files/ 220 | Backup*/ 221 | UpgradeLog*.XML 222 | UpgradeLog*.htm 223 | 224 | # SQL Server files 225 | *.mdf 226 | *.ldf 227 | 228 | # Business Intelligence projects 229 | *.rdl.data 230 | *.bim.layout 231 | *.bim_*.settings 232 | 233 | # Microsoft Fakes 234 | FakesAssemblies/ 235 | 236 | # GhostDoc plugin setting file 237 | *.GhostDoc.xml 238 | 239 | # Node.js Tools for Visual Studio 240 | .ntvs_analysis.dat 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio LightSwitch build output 249 | **/*.HTMLClient/GeneratedArtifacts 250 | **/*.DesktopClient/GeneratedArtifacts 251 | **/*.DesktopClient/ModelManifest.xml 252 | **/*.Server/GeneratedArtifacts 253 | **/*.Server/ModelManifest.xml 254 | _Pvt_Extensions 255 | 256 | # Paket dependency manager 257 | .paket/paket.exe 258 | paket-files/ 259 | 260 | # FAKE - F# Make 261 | .fake/ 262 | 263 | # JetBrains Rider 264 | .idea/ 265 | *.sln.iml 266 | 267 | # CodeRush 268 | .cr/ 269 | 270 | # Python Tools for Visual Studio (PTVS) 271 | __pycache__/ 272 | *.pyc 273 | 274 | # Cake - Uncomment if you are using it 275 | # tools/ 276 | 277 | # Thumbs 278 | Thumbs.db 279 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/Program/bin/Debug/netcoreapp2.0/Program.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/Program", 16 | // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window 17 | "console": "internalConsole", 18 | "stopAtEntry": false, 19 | "internalConsoleOptions": "openOnSessionStart" 20 | }, 21 | { 22 | "name": ".NET Core Attach", 23 | "type": "coreclr", 24 | "request": "attach", 25 | "processId": "${command:pickProcess}" 26 | } 27 | ,] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/Program/Program.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 SqlKata 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 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/AggregateTests.cs: -------------------------------------------------------------------------------- 1 | using SqlKata.Compilers; 2 | using SqlKata.Tests.Infrastructure; 3 | using Xunit; 4 | 5 | namespace SqlKata.Tests 6 | { 7 | public class AggregateTests : TestSupport 8 | { 9 | [Fact] 10 | public void Count() 11 | { 12 | var query = new Query("A").AsCount(); 13 | 14 | var c = Compile(query); 15 | 16 | Assert.Equal("SELECT COUNT(*) AS [count] FROM [A]", c[EngineCodes.SqlServer]); 17 | Assert.Equal("SELECT COUNT(*) AS `count` FROM `A`", c[EngineCodes.MySql]); 18 | Assert.Equal("SELECT COUNT(*) AS \"count\" FROM \"A\"", c[EngineCodes.PostgreSql]); 19 | Assert.Equal("SELECT COUNT(*) AS \"COUNT\" FROM \"A\"", c[EngineCodes.Firebird]); 20 | } 21 | 22 | [Fact] 23 | public void CountMultipleColumns() 24 | { 25 | var query = new Query("A").AsCount(new[] { "ColumnA", "ColumnB" }); 26 | 27 | var c = Compile(query); 28 | 29 | Assert.Equal("SELECT COUNT(*) AS [count] FROM (SELECT 1 FROM [A] WHERE [ColumnA] IS NOT NULL AND [ColumnB] IS NOT NULL) AS [countQuery]", c[EngineCodes.SqlServer]); 30 | } 31 | 32 | [Fact] 33 | public void DistinctCount() 34 | { 35 | var query = new Query("A").Distinct().AsCount(); 36 | 37 | var c = Compile(query); 38 | 39 | Assert.Equal("SELECT COUNT(*) AS [count] FROM (SELECT DISTINCT * FROM [A]) AS [countQuery]", c[EngineCodes.SqlServer]); 40 | } 41 | 42 | [Fact] 43 | public void DistinctCountMultipleColumns() 44 | { 45 | var query = new Query("A").Distinct().AsCount(new[] { "ColumnA", "ColumnB" }); 46 | 47 | var c = Compile(query); 48 | 49 | Assert.Equal("SELECT COUNT(*) AS [count] FROM (SELECT DISTINCT [ColumnA], [ColumnB] FROM [A]) AS [countQuery]", c[EngineCodes.SqlServer]); 50 | } 51 | 52 | [Fact] 53 | public void Average() 54 | { 55 | var query = new Query("A").AsAverage("TTL"); 56 | 57 | var c = Compile(query); 58 | 59 | Assert.Equal("SELECT AVG([TTL]) AS [avg] FROM [A]", c[EngineCodes.SqlServer]); 60 | } 61 | 62 | [Fact] 63 | public void Sum() 64 | { 65 | var query = new Query("A").AsSum("PacketsDropped"); 66 | 67 | var c = Compile(query); 68 | 69 | Assert.Equal("SELECT SUM([PacketsDropped]) AS [sum] FROM [A]", c[EngineCodes.SqlServer]); 70 | } 71 | 72 | [Fact] 73 | public void Max() 74 | { 75 | var query = new Query("A").AsMax("LatencyMs"); 76 | 77 | var c = Compile(query); 78 | 79 | Assert.Equal("SELECT MAX([LatencyMs]) AS [max] FROM [A]", c[EngineCodes.SqlServer]); 80 | } 81 | 82 | [Fact] 83 | public void Min() 84 | { 85 | var query = new Query("A").AsMin("LatencyMs"); 86 | 87 | var c = Compile(query); 88 | 89 | Assert.Equal("SELECT MIN([LatencyMs]) AS [min] FROM [A]", c[EngineCodes.SqlServer]); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/DeleteTests.cs: -------------------------------------------------------------------------------- 1 | using SqlKata.Compilers; 2 | using SqlKata.Extensions; 3 | using SqlKata.Tests.Infrastructure; 4 | using System; 5 | using System.Linq; 6 | using Xunit; 7 | 8 | namespace SqlKata.Tests 9 | { 10 | public class DeleteTests : TestSupport 11 | { 12 | [Fact] 13 | public void BasicDelete() 14 | { 15 | var q = new Query("Posts").AsDelete(); 16 | 17 | var c = Compile(q); 18 | 19 | Assert.Equal("DELETE FROM [Posts]", c[EngineCodes.SqlServer]); 20 | } 21 | 22 | [Fact] 23 | public void DeleteWithJoin() 24 | { 25 | var q = new Query("Posts") 26 | .Join("Authors", "Authors.Id", "Posts.AuthorId") 27 | .Where("Authors.Id", 5) 28 | .AsDelete(); 29 | 30 | var c = Compile(q); 31 | 32 | Assert.Equal("DELETE [Posts] FROM [Posts] \nINNER JOIN [Authors] ON [Authors].[Id] = [Posts].[AuthorId] WHERE [Authors].[Id] = 5", c[EngineCodes.SqlServer]); 33 | Assert.Equal("DELETE `Posts` FROM `Posts` \nINNER JOIN `Authors` ON `Authors`.`Id` = `Posts`.`AuthorId` WHERE `Authors`.`Id` = 5", c[EngineCodes.MySql]); 34 | } 35 | 36 | [Fact] 37 | public void DeleteWithJoinAndAlias() 38 | { 39 | var q = new Query("Posts as P") 40 | .Join("Authors", "Authors.Id", "P.AuthorId") 41 | .Where("Authors.Id", 5) 42 | .AsDelete(); 43 | 44 | var c = Compile(q); 45 | 46 | Assert.Equal("DELETE [P] FROM [Posts] AS [P] \nINNER JOIN [Authors] ON [Authors].[Id] = [P].[AuthorId] WHERE [Authors].[Id] = 5", c[EngineCodes.SqlServer]); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/ExecutionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using SqlKata.Execution; 3 | using Xunit; 4 | 5 | namespace SqlKata.Tests 6 | { 7 | public class ExecutionTests 8 | { 9 | [Fact] 10 | public void ShouldThrowException() 11 | { 12 | Assert.Throws(() => 13 | { 14 | new Query("Books").Get(); 15 | }); 16 | } 17 | 18 | [Fact] 19 | public void TimeoutShouldBeCarriedToNewCreatedFactory() 20 | { 21 | var db = new QueryFactory(); 22 | db.QueryTimeout = 4000; 23 | var newFactory = QueryExtensions.CreateQueryFactory(db.Query()); 24 | Assert.Equal(db.QueryTimeout, newFactory.QueryTimeout); 25 | } 26 | 27 | [Fact(Skip = "timeout over cloned xQuery is not supported yet")] 28 | public void TimeoutShouldBeCarriedToNewCreatedFactoryAfterClone() 29 | { 30 | var db = new QueryFactory(); 31 | db.QueryTimeout = 4000; 32 | var newFactory = QueryExtensions.CreateQueryFactory(db.Query().Clone()); 33 | Assert.Equal(db.QueryTimeout, newFactory.QueryTimeout); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/Firebird/FirebirdLimitTests.cs: -------------------------------------------------------------------------------- 1 | using SqlKata.Compilers; 2 | using SqlKata.Tests.Infrastructure; 3 | using Xunit; 4 | 5 | namespace SqlKata.Tests.Firebird 6 | { 7 | public class FirebirdLimitTests : TestSupport 8 | { 9 | private readonly FirebirdCompiler compiler; 10 | 11 | public FirebirdLimitTests() 12 | { 13 | compiler = Compilers.Get(EngineCodes.Firebird); 14 | } 15 | 16 | [Fact] 17 | public void NoLimitNorOffset() 18 | { 19 | var query = new Query("Table"); 20 | var ctx = new SqlResult("?", "\\") {Query = query}; 21 | 22 | Assert.Null(compiler.CompileLimit(ctx)); 23 | } 24 | 25 | [Fact] 26 | public void LimitOnly() 27 | { 28 | var query = new Query("Table").Limit(10); 29 | var ctx = new SqlResult("?", "\\") {Query = query}; 30 | 31 | Assert.Null(compiler.CompileLimit(ctx)); 32 | } 33 | 34 | [Fact] 35 | public void OffsetOnly() 36 | { 37 | var query = new Query("Table").Offset(20); 38 | var ctx = new SqlResult("?", "\\") {Query = query}; 39 | 40 | Assert.Null(compiler.CompileLimit(ctx)); 41 | } 42 | 43 | [Fact] 44 | public void LimitAndOffset() 45 | { 46 | var query = new Query("Table").Limit(5).Offset(20); 47 | var ctx = new SqlResult("?", "\\") {Query = query}; 48 | 49 | Assert.Equal("ROWS ? TO ?", compiler.CompileLimit(ctx)); 50 | Assert.Equal(21L, ctx.Bindings[0]); 51 | Assert.Equal(25L, ctx.Bindings[1]); 52 | Assert.Equal(2, ctx.Bindings.Count); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/HelperTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Linq; 3 | using Xunit; 4 | 5 | namespace SqlKata.Tests 6 | { 7 | public class HelperTests 8 | { 9 | [Theory] 10 | [InlineData("")] 11 | [InlineData(null)] 12 | [InlineData(" ")] 13 | [InlineData(" ")] 14 | [InlineData(" ")] 15 | public void ItShouldKeepItAsIs(string input) 16 | { 17 | var output = Helper.ReplaceAll(input, "any", "\\", x => x + ""); 18 | 19 | Assert.Equal(input, output); 20 | } 21 | 22 | [Theory] 23 | [InlineData("hello", "hello")] 24 | [InlineData("?hello", "@hello")] 25 | [InlineData("??hello", "@@hello")] 26 | [InlineData("?? hello", "@@ hello")] 27 | [InlineData("? ? hello", "@ @ hello")] 28 | [InlineData(" ? ? hello", " @ @ hello")] 29 | public void ReplaceOnTheBegining(string input, string expected) 30 | { 31 | var output = Helper.ReplaceAll(input, "?", "\\", x => "@"); 32 | Assert.Equal(expected, output); 33 | } 34 | 35 | [Theory] 36 | [InlineData("hello?", "hello@")] 37 | [InlineData("hello? ", "hello@ ")] 38 | [InlineData("hello??? ", "hello@@@ ")] 39 | [InlineData("hello ? ?? ? ", "hello @ @@ @ ")] 40 | public void ReplaceOnTheEnd(string input, string expected) 41 | { 42 | var output = Helper.ReplaceAll(input, "?", "\\", x => "@"); 43 | Assert.Equal(expected, output); 44 | } 45 | 46 | [Theory] 47 | [InlineData("hel\\?o ??? ", "hel\\?o 012 ")] 48 | [InlineData("hel\\?o ?? \\?", "hel\\?o 01 \\?")] 49 | [InlineData("hello?", "hello0")] 50 | [InlineData("hello? ", "hello0 ")] 51 | [InlineData("hello??? ", "hello012 ")] 52 | [InlineData("hel?lo ? ?? ? ", "hel0lo 1 23 4 ")] 53 | [InlineData("????", "0123")] 54 | public void ReplaceWithPositions(string input, string expected) 55 | { 56 | var output = Helper.ReplaceAll(input, "?", "\\", x => x + ""); 57 | Assert.Equal(expected, output); 58 | } 59 | 60 | [Fact] 61 | public void AllIndexesOf_ReturnIndexes_IfValueIsContainedInAString() 62 | { 63 | // Given 64 | var input = "hello"; 65 | 66 | // When 67 | var result = Helper.AllIndexesOf(input, "l"); 68 | 69 | // Then 70 | Assert.Equal(new[] { 2, 3 }, result); 71 | } 72 | 73 | [Theory] 74 | [InlineData("")] 75 | [InlineData(null)] 76 | public void AllIndexesOf_ReturnEmptyCollection_IfValueIsEmptyOrNull(string value) 77 | { 78 | // Given 79 | var input = "hello"; 80 | 81 | // When 82 | var result = Helper.AllIndexesOf(input, value); 83 | 84 | // Then 85 | Assert.Empty(result); 86 | } 87 | 88 | [Fact] 89 | public void AllIndexesOf_ReturnEmptyCollection_IfValueIsNotContainedInAString() 90 | { 91 | // Given 92 | var input = "hello"; 93 | 94 | // When 95 | var result = Helper.AllIndexesOf(input, "F"); 96 | 97 | // Then 98 | Assert.Empty(result); 99 | } 100 | 101 | [Fact] 102 | public void Flatten_ReturnFlatttenDeepCollectionRecursively_IfArrayIsNested() 103 | { 104 | // Given 105 | var objects = new object[] 106 | { 107 | 1, 108 | 0.1, 109 | 'A', 110 | new object[] 111 | { 112 | 'A', 113 | "B", 114 | new object[] 115 | { 116 | "C", 117 | 'D' 118 | } 119 | } 120 | }; 121 | 122 | // When 123 | var flatten = Helper.FlattenDeep(objects); 124 | 125 | // Then 126 | Assert.Equal(new object[] { 1, 0.1, 'A', 'A', "B", "C", 'D' }, flatten); 127 | } 128 | 129 | [Fact] 130 | public void Flatten_FlatOneLevel() 131 | { 132 | // Given 133 | var objects = new object[] 134 | { 135 | 1, 136 | new object[] 137 | { 138 | 2, 139 | 3, 140 | new [] {4,5,6} 141 | } 142 | }; 143 | 144 | // When 145 | var flatten = Helper.Flatten(objects); 146 | 147 | // Then 148 | Assert.Equal(new[] { 4, 5, 6 }, flatten.ElementAt(3)); 149 | } 150 | [Fact] 151 | public void Flatten_ShouldRemoveEmptyCollections() 152 | { 153 | // Given 154 | var objects = new object[] 155 | { 156 | 1, 157 | new object[] {}, 158 | new object[] 159 | { 160 | 2, 161 | 3, 162 | } 163 | }; 164 | 165 | // When 166 | var flatten = Helper.Flatten(objects); 167 | 168 | // Then 169 | Assert.Equal(new object[] { 1, 2, 3 }, flatten); 170 | } 171 | 172 | [Fact] 173 | public void IsArray_ReturnFalse_IfValueIsNull() 174 | { 175 | // Given 176 | IEnumerable test = null; 177 | 178 | // When 179 | var isArray = Helper.IsArray(test); 180 | 181 | // Then 182 | Assert.False(isArray); 183 | } 184 | 185 | [Fact] 186 | public void IsArray_ReturnFalse_IfTypeOfValueIsString() 187 | { 188 | // Given 189 | var value = "string"; 190 | 191 | // When 192 | var isArray = Helper.IsArray(value); 193 | 194 | // Then 195 | Assert.False(isArray); 196 | } 197 | 198 | [Fact] 199 | public void IsArray_ReturnTrue_IfValueIsExactlyIEnumerable() 200 | { 201 | // Given 202 | var value = new object[] { 1, 'B', "C" }; 203 | 204 | // When 205 | var isArray = Helper.IsArray(value); 206 | 207 | // Then 208 | Assert.True(isArray); 209 | } 210 | 211 | [Theory] 212 | [InlineData("Users.Id", "Users.Id")] 213 | [InlineData("Users.{Id", "Users.{Id")] 214 | [InlineData("Users.{Id}", "Users.Id")] 215 | [InlineData("Users.{Id,Name}", "Users.Id, Users.Name")] 216 | [InlineData("Users.{Id,Name, Last_Name }", "Users.Id, Users.Name, Users.Last_Name")] 217 | public void ExpandExpression(string input, string expected) 218 | { 219 | Assert.Equal(expected, string.Join(", ", Helper.ExpandExpression(input))); 220 | } 221 | 222 | [Fact] 223 | public void ExpandParameters() 224 | { 225 | var expanded = Helper.ExpandParameters("where id = ? or id in (?) or id in (?)", "?", "\\", new object[] { 1, new[] { 1, 2 }, new object[] { } }); 226 | 227 | Assert.Equal("where id = ? or id in (?,?) or id in ()", expanded); 228 | } 229 | 230 | [Theory] 231 | [InlineData(@"\{ text {", @"\", "{", "[", "{ text [")] 232 | [InlineData(@"{ text {", @"\", "{", "[", "[ text [")] 233 | public void WrapIdentifiers(string input, string escapeCharacter, string identifier, string newIdentifier, string expected) 234 | { 235 | var result = input.ReplaceIdentifierUnlessEscaped(escapeCharacter, identifier, newIdentifier); 236 | Assert.Equal(expected, result); 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/Infrastructure/TestCompiler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using SqlKata.Compilers; 4 | 5 | namespace SqlKata.Tests.Infrastructure 6 | { 7 | /// 8 | /// A test class to expose private methods 9 | /// 10 | class TestCompiler : Compiler 11 | { 12 | public override string EngineCode { get; } = "test"; 13 | 14 | public virtual MethodInfo Call_FindCompilerMethodInfo(Type clauseType, string methodName) 15 | { 16 | return FindCompilerMethodInfo(clauseType, methodName); 17 | } 18 | } 19 | 20 | class TestSqlServerCompiler : SqlServerCompiler 21 | { 22 | public virtual MethodInfo Call_FindCompilerMethodInfo(Type clauseType, string methodName) 23 | { 24 | return FindCompilerMethodInfo(clauseType, methodName); 25 | } 26 | } 27 | 28 | class TestMySqlCompiler : MySqlCompiler 29 | { 30 | public virtual MethodInfo Call_FindCompilerMethodInfo(Type clauseType, string methodName) 31 | { 32 | return FindCompilerMethodInfo(clauseType, methodName); 33 | } 34 | } 35 | 36 | class TestPostgresCompiler : PostgresCompiler 37 | { 38 | public virtual MethodInfo Call_FindCompilerMethodInfo(Type clauseType, string methodName) 39 | { 40 | return FindCompilerMethodInfo(clauseType, methodName); 41 | } 42 | } 43 | 44 | class TestFirebirdCompiler : FirebirdCompiler 45 | { 46 | public virtual MethodInfo Call_FindCompilerMethodInfo(Type clauseType, string methodName) 47 | { 48 | return FindCompilerMethodInfo(clauseType, methodName); 49 | } 50 | } 51 | 52 | class TestEmptyIdentifiersCompiler : TestCompiler 53 | { 54 | protected override string OpeningIdentifier { get; set; } = ""; 55 | protected override string ClosingIdentifier { get; set; } = ""; 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/Infrastructure/TestCompilersContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using SqlKata.Compilers; 5 | 6 | namespace SqlKata.Tests.Infrastructure 7 | { 8 | public class TestCompilersContainer 9 | { 10 | private static class Messages 11 | { 12 | public const string ERR_INVALID_ENGINECODE = "Engine code '{0}' is not valid"; 13 | public const string ERR_INVALID_ENGINECODES = "Invalid engine codes supplied '{0}'"; 14 | } 15 | 16 | protected readonly IDictionary Compilers = new Dictionary 17 | { 18 | [EngineCodes.Firebird] = new FirebirdCompiler(), 19 | [EngineCodes.MySql] = new MySqlCompiler(), 20 | [EngineCodes.Oracle] = new OracleCompiler(), 21 | [EngineCodes.PostgreSql] = new PostgresCompiler(), 22 | [EngineCodes.Sqlite] = new SqliteCompiler(), 23 | [EngineCodes.SqlServer] = new SqlServerCompiler() 24 | { 25 | UseLegacyPagination = true 26 | } 27 | }; 28 | 29 | public IEnumerable KnownEngineCodes 30 | { 31 | get { return Compilers.Select(s => s.Key); } 32 | } 33 | 34 | /// 35 | /// Returns a instance for the given engine code 36 | /// 37 | /// 38 | /// 39 | public Compiler Get(string engineCode) 40 | { 41 | if (!Compilers.ContainsKey(engineCode)) 42 | { 43 | throw new InvalidOperationException(string.Format(Messages.ERR_INVALID_ENGINECODE, engineCode)); 44 | } 45 | 46 | return Compilers[engineCode]; 47 | } 48 | 49 | /// 50 | /// Convenience method 51 | /// 52 | /// Does not validate generic type against engine code before cast 53 | /// 54 | /// 55 | /// 56 | public TCompiler Get(string engineCode) where TCompiler : Compiler 57 | { 58 | return (TCompiler)Get(engineCode); 59 | } 60 | 61 | /// 62 | /// Compiles the against the given engine code 63 | /// 64 | /// 65 | /// 66 | /// 67 | public SqlResult CompileFor(string engineCode, Query query) 68 | { 69 | var compiler = Get(engineCode); 70 | return compiler.Compile(query); 71 | } 72 | 73 | /// 74 | /// Compiles the against the given engine codes 75 | /// 76 | /// 77 | /// 78 | /// 79 | public TestSqlResultContainer Compile(IEnumerable engineCodes, Query query) 80 | { 81 | var codes = engineCodes.ToList(); 82 | 83 | var results = Compilers 84 | .Where(w => codes.Contains(w.Key)) 85 | .ToDictionary(k => k.Key, v => v.Value.Compile(query.Clone())); 86 | 87 | if (results.Count != codes.Count) 88 | { 89 | var missingCodes = codes.Where(w => Compilers.All(a => a.Key != w)); 90 | var templateArg = string.Join(", ", missingCodes); 91 | throw new InvalidOperationException(string.Format(Messages.ERR_INVALID_ENGINECODES, templateArg)); 92 | } 93 | 94 | return new TestSqlResultContainer(results); 95 | } 96 | 97 | /// 98 | /// Compiles the against all s 99 | /// 100 | /// 101 | /// 102 | public TestSqlResultContainer Compile(Query query) 103 | { 104 | var resultKeyValues = Compilers 105 | .ToDictionary(k => k.Key, v => v.Value.Compile(query.Clone())); 106 | return new TestSqlResultContainer(resultKeyValues); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/Infrastructure/TestSqlResultContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | 4 | namespace SqlKata.Tests.Infrastructure 5 | { 6 | public class TestSqlResultContainer : ReadOnlyDictionary 7 | { 8 | public TestSqlResultContainer(IDictionary dictionary) : base(dictionary) 9 | { 10 | 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/Infrastructure/TestSupport.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace SqlKata.Tests.Infrastructure 5 | { 6 | public abstract class TestSupport 7 | { 8 | protected readonly TestCompilersContainer Compilers = new TestCompilersContainer(); 9 | 10 | /// 11 | /// For legacy test support 12 | /// 13 | /// 14 | /// 15 | protected IReadOnlyDictionary Compile(Query query) 16 | { 17 | return Compilers.Compile(query).ToDictionary(s => s.Key, v => v.Value.ToString()); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/InfrastructureTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using SqlKata.Compilers; 4 | using SqlKata.Tests.Infrastructure; 5 | using Xunit; 6 | 7 | namespace SqlKata.Tests 8 | { 9 | public class InfrastructureTests : TestSupport 10 | { 11 | [Fact] 12 | public void CanGetCompiler() 13 | { 14 | var compiler = Compilers.Get(EngineCodes.SqlServer); 15 | 16 | Assert.NotNull(compiler); 17 | Assert.IsType(compiler); 18 | } 19 | 20 | [Fact] 21 | public void CanCompile() 22 | { 23 | var results = Compilers.Compile(new Query("Table")); 24 | 25 | Assert.NotNull(results); 26 | Assert.Equal(Compilers.KnownEngineCodes.Count(), results.Count); 27 | } 28 | 29 | [Fact] 30 | public void CanCompileSelectively() 31 | { 32 | var desiredEngines = new[] { EngineCodes.SqlServer, EngineCodes.MySql }; 33 | var results = Compilers.Compile(desiredEngines, new Query("Table")); 34 | 35 | Assert.Equal(desiredEngines.Length, results.Count); 36 | Assert.Contains(results, a => a.Key == EngineCodes.SqlServer); 37 | Assert.Contains(results, a => a.Key == EngineCodes.MySql); 38 | } 39 | 40 | 41 | [Fact] 42 | public void ShouldThrowIfInvalidEngineCode() 43 | { 44 | Assert.Throws(() => Compilers.CompileFor("XYZ", new Query())); 45 | } 46 | 47 | [Fact] 48 | public void ShouldThrowIfAnyEngineCodesAreInvalid() 49 | { 50 | var codes = new[] { EngineCodes.SqlServer, "123", EngineCodes.MySql, "abc" }; 51 | Assert.Throws(() => Compilers.Compile(codes, new Query())); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/MySql/MySqlLimitTests.cs: -------------------------------------------------------------------------------- 1 | using SqlKata.Compilers; 2 | using SqlKata.Tests.Infrastructure; 3 | using Xunit; 4 | 5 | namespace SqlKata.Tests.MySql 6 | { 7 | public class MySqlLimitTests : TestSupport 8 | { 9 | private readonly MySqlCompiler compiler; 10 | 11 | public MySqlLimitTests() 12 | { 13 | compiler = Compilers.Get(EngineCodes.MySql); 14 | } 15 | 16 | [Fact] 17 | public void WithNoLimitNorOffset() 18 | { 19 | var query = new Query("Table"); 20 | var ctx = new SqlResult("?", "\\") {Query = query}; 21 | 22 | Assert.Null(compiler.CompileLimit(ctx)); 23 | } 24 | 25 | [Fact] 26 | public void WithNoOffset() 27 | { 28 | var query = new Query("Table").Limit(10); 29 | var ctx = new SqlResult("?", "\\") {Query = query}; 30 | 31 | Assert.Equal("LIMIT ?", compiler.CompileLimit(ctx)); 32 | Assert.Equal(10, ctx.Bindings[0]); 33 | } 34 | 35 | [Fact] 36 | public void WithNoLimit() 37 | { 38 | var query = new Query("Table").Offset(20); 39 | var ctx = new SqlResult("?", "\\") {Query = query}; 40 | 41 | Assert.Equal("LIMIT 18446744073709551615 OFFSET ?", compiler.CompileLimit(ctx)); 42 | Assert.Equal(20L, ctx.Bindings[0]); 43 | Assert.Single(ctx.Bindings); 44 | } 45 | 46 | [Fact] 47 | public void WithLimitAndOffset() 48 | { 49 | var query = new Query("Table").Limit(5).Offset(20); 50 | var ctx = new SqlResult("?", "\\") {Query = query}; 51 | 52 | Assert.Equal("LIMIT ? OFFSET ?", compiler.CompileLimit(ctx)); 53 | Assert.Equal(5, ctx.Bindings[0]); 54 | Assert.Equal(20L, ctx.Bindings[1]); 55 | Assert.Equal(2, ctx.Bindings.Count); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/MySqlExecutionTest.cs: -------------------------------------------------------------------------------- 1 | using SqlKata.Compilers; 2 | using Xunit; 3 | using SqlKata.Execution; 4 | using MySql.Data.MySqlClient; 5 | using System; 6 | using System.Linq; 7 | using static SqlKata.Expressions; 8 | using System.Collections.Generic; 9 | 10 | namespace SqlKata.Tests 11 | { 12 | public class MySqlExecutionTest 13 | { 14 | [Fact] 15 | public void EmptySelect() 16 | { 17 | 18 | var db = DB().Create("Cars", new[] { 19 | "Id INT PRIMARY KEY AUTO_INCREMENT", 20 | "Brand TEXT NOT NULL", 21 | "Year INT NOT NULL", 22 | "Color TEXT NULL", 23 | }); 24 | 25 | var rows = db.Query("Cars").Get(); 26 | 27 | Assert.Empty(rows); 28 | 29 | db.Drop("Cars"); 30 | } 31 | 32 | [Fact] 33 | public void SelectWithLimit() 34 | { 35 | var db = DB().Create("Cars", new[] { 36 | "Id INT PRIMARY KEY AUTO_INCREMENT", 37 | "Brand TEXT NOT NULL", 38 | "Year INT NOT NULL", 39 | "Color TEXT NULL", 40 | }); 41 | 42 | db.Statement("INSERT INTO `Cars`(Brand, Year) VALUES ('Honda', 2020)"); 43 | 44 | var rows = db.Query("Cars").Get().ToList(); 45 | 46 | Assert.Single(rows); 47 | 48 | db.Drop("Cars"); 49 | } 50 | 51 | [Fact] 52 | public void Count() 53 | { 54 | var db = DB().Create("Cars", new[] { 55 | "Id INT PRIMARY KEY AUTO_INCREMENT", 56 | "Brand TEXT NOT NULL", 57 | "Year INT NOT NULL", 58 | "Color TEXT NULL", 59 | }); 60 | 61 | db.Statement("INSERT INTO `Cars`(Brand, Year) VALUES ('Honda', 2020)"); 62 | var count = db.Query("Cars").Count(); 63 | Assert.Equal(1, count); 64 | 65 | db.Statement("INSERT INTO `Cars`(Brand, Year) VALUES ('Toyota', 2021)"); 66 | count = db.Query("Cars").Count(); 67 | Assert.Equal(2, count); 68 | 69 | int affected = db.Query("Cars").Delete(); 70 | Assert.Equal(2, affected); 71 | 72 | count = db.Query("Cars").Count(); 73 | Assert.Equal(0, count); 74 | 75 | db.Drop("Cars"); 76 | } 77 | 78 | [Fact] 79 | public void CloneThenCount() 80 | { 81 | var db = DB().Create("Cars", new[] { 82 | "Id INT PRIMARY KEY AUTO_INCREMENT", 83 | "Brand TEXT NOT NULL", 84 | "Year INT NOT NULL", 85 | "Color TEXT NULL", 86 | }); 87 | 88 | for (int i = 0; i < 10; i++) 89 | { 90 | db.Query("Cars").Insert(new 91 | { 92 | Brand = "Brand " + i, 93 | Year = "2020", 94 | }); 95 | } 96 | 97 | var query = db.Query("Cars").Where("Id", "<", 5); 98 | var count = query.Count(); 99 | var cloneCount = query.Clone().Count(); 100 | 101 | Assert.Equal(4, count); 102 | Assert.Equal(4, cloneCount); 103 | 104 | db.Drop("Cars"); 105 | } 106 | 107 | [Fact] 108 | public void QueryWithVariable() 109 | { 110 | var db = DB().Create("Cars", new[] { 111 | "Id INT PRIMARY KEY AUTO_INCREMENT", 112 | "Brand TEXT NOT NULL", 113 | "Year INT NOT NULL", 114 | "Color TEXT NULL", 115 | }); 116 | 117 | for (int i = 0; i < 10; i++) 118 | { 119 | db.Query("Cars").Insert(new 120 | { 121 | Brand = "Brand " + i, 122 | Year = "2020", 123 | }); 124 | } 125 | 126 | 127 | var count = db.Query("Cars") 128 | .Define("Threshold", 5) 129 | .Where("Id", "<", SqlKata.Expressions.Variable("Threshold")) 130 | .Count(); 131 | 132 | Assert.Equal(4, count); 133 | 134 | db.Drop("Cars"); 135 | } 136 | 137 | [Fact] 138 | public void InlineTable() 139 | { 140 | var db = DB().Create("Transaction", new[] { 141 | "Id INT PRIMARY KEY AUTO_INCREMENT", 142 | "Amount int NOT NULL", 143 | "Date DATE NOT NULL", 144 | }); 145 | 146 | db.Query("Transaction").Insert(new 147 | { 148 | Date = "2022-01-01", 149 | Amount = 10 150 | }); 151 | 152 | 153 | var rows = db.Query("Transaction") 154 | .With("Rates", new[] { "Date", "Rate" }, new object[][] { 155 | new object[] {"2022-01-01", 0.5}, 156 | }) 157 | .Join("Rates", "Rates.Date", "Transaction.Date") 158 | .SelectRaw("Transaction.Amount * Rates.Rate as AmountConverted") 159 | .Get(); 160 | 161 | Assert.Single(rows); 162 | Assert.Equal(5, rows.First().AmountConverted); 163 | 164 | db.Drop("Transaction"); 165 | } 166 | 167 | [Fact] 168 | public void ExistsShouldReturnFalseForEmptyTable() 169 | { 170 | var db = DB().Create("Transaction", new[] { 171 | "Id INT PRIMARY KEY AUTO_INCREMENT", 172 | "Amount int NOT NULL", 173 | "Date DATE NOT NULL", 174 | }); 175 | 176 | var exists = db.Query("Transaction").Exists(); 177 | Assert.False(exists); 178 | 179 | db.Drop("Transaction"); 180 | } 181 | 182 | [Fact] 183 | public void ExistsShouldReturnTrueForNonEmptyTable() 184 | { 185 | var db = DB().Create("Transaction", new[] { 186 | "Id INT PRIMARY KEY AUTO_INCREMENT", 187 | "Amount int NOT NULL", 188 | "Date DATE NOT NULL", 189 | }); 190 | 191 | db.Query("Transaction").Insert(new 192 | { 193 | Date = "2022-01-01", 194 | Amount = 10 195 | }); 196 | 197 | var exists = db.Query("Transaction").Exists(); 198 | Assert.True(exists); 199 | 200 | db.Drop("Transaction"); 201 | } 202 | 203 | [Fact] 204 | public void BasicSelectFilter() 205 | { 206 | var db = DB().Create("Transaction", new[] { 207 | "Id INT PRIMARY KEY AUTO_INCREMENT", 208 | "Date DATE NOT NULL", 209 | "Amount int NOT NULL", 210 | }); 211 | 212 | var data = new Dictionary { 213 | // 2020 214 | {"2020-01-01", 10}, 215 | {"2020-05-01", 20}, 216 | 217 | // 2021 218 | {"2021-01-01", 40}, 219 | {"2021-02-01", 10}, 220 | {"2021-04-01", -10}, 221 | 222 | // 2022 223 | {"2022-01-01", 80}, 224 | {"2022-02-01", -30}, 225 | {"2022-05-01", 50}, 226 | }; 227 | 228 | foreach (var row in data) 229 | { 230 | db.Query("Transaction").Insert(new 231 | { 232 | Date = row.Key, 233 | Amount = row.Value 234 | }); 235 | } 236 | 237 | var query = db.Query("Transaction") 238 | .SelectSum("Amount as Total_2020", q => q.WhereDatePart("year", "date", 2020)) 239 | .SelectSum("Amount as Total_2021", q => q.WhereDatePart("year", "date", 2021)) 240 | .SelectSum("Amount as Total_2022", q => q.WhereDatePart("year", "date", 2022)) 241 | ; 242 | 243 | var results = query.Get().ToList(); 244 | Assert.Single(results); 245 | Assert.Equal(30, results[0].Total_2020); 246 | Assert.Equal(40, results[0].Total_2021); 247 | Assert.Equal(100, results[0].Total_2022); 248 | 249 | db.Drop("Transaction"); 250 | } 251 | 252 | QueryFactory DB() 253 | { 254 | var host = System.Environment.GetEnvironmentVariable("SQLKATA_MYSQL_HOST"); 255 | var user = System.Environment.GetEnvironmentVariable("SQLKATA_MYSQL_USER"); 256 | var dbName = System.Environment.GetEnvironmentVariable("SQLKATA_MYSQL_DB"); 257 | var cs = $"server={host};user={user};database={dbName}"; 258 | 259 | var connection = new MySqlConnection(cs); 260 | 261 | var db = new QueryFactory(connection, new MySqlCompiler()); 262 | 263 | return db; 264 | } 265 | 266 | 267 | 268 | } 269 | } -------------------------------------------------------------------------------- /QueryBuilder.Tests/OperatorWhitelistTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using SqlKata.Compilers; 3 | using Xunit; 4 | 5 | namespace SqlKata.Tests 6 | { 7 | public class OperatorWhitelistTests 8 | { 9 | 10 | public OperatorWhitelistTests() 11 | { 12 | 13 | } 14 | 15 | [Theory] 16 | [InlineData("!!")] 17 | [InlineData("~!")] 18 | [InlineData("*=")] 19 | public void DenyInvalidOperatorsInWhere(string op) 20 | { 21 | var compiler = new SqlServerCompiler(); 22 | 23 | Assert.Throws(() => 24 | { 25 | compiler.Compile(new Query("Table").Where("Id", op, 1)); 26 | compiler.Compile(new Query("Table").OrWhere("Id", op, 1)); 27 | compiler.Compile(new Query("Table").WhereNot("Id", op, 1)); 28 | compiler.Compile(new Query("Table").OrWhereNot("Id", op, 1)); 29 | 30 | compiler.Compile(new Query("Table").WhereColumns("Col1", op, "Col2")); 31 | compiler.Compile(new Query("Table").OrWhereColumns("Col1", op, "Col2")); 32 | }); 33 | } 34 | 35 | [Theory] 36 | [InlineData("!!")] 37 | [InlineData("~!")] 38 | [InlineData("*=")] 39 | public void DenyInvalidOperatorsInHaving(string op) 40 | { 41 | var compiler = new SqlServerCompiler(); 42 | 43 | Assert.Throws(() => 44 | { 45 | compiler.Compile(new Query("Table").Having("Id", op, 1)); 46 | compiler.Compile(new Query("Table").OrHaving("Id", op, 1)); 47 | compiler.Compile(new Query("Table").HavingNot("Id", op, 1)); 48 | compiler.Compile(new Query("Table").OrHavingNot("Id", op, 1)); 49 | 50 | compiler.Compile(new Query("Table").HavingColumns("Col1", op, "Col2")); 51 | compiler.Compile(new Query("Table").OrHavingColumns("Col1", op, "Col2")); 52 | }); 53 | } 54 | 55 | 56 | [Theory] 57 | [InlineData("=")] 58 | [InlineData("!=")] 59 | [InlineData("ilike")] 60 | public void AllowValidOperatorsInWhere(string op) 61 | { 62 | new Query("Table").Where("Id", op, 1); 63 | new Query("Table").OrWhere("Id", op, 1); 64 | new Query("Table").WhereNot("Id", op, 1); 65 | new Query("Table").OrWhereNot("Id", op, 1); 66 | 67 | new Query("Table").WhereColumns("Col1", op, "Col2"); 68 | new Query("Table").OrWhereColumns("Col1", op, "Col2"); 69 | } 70 | 71 | [Theory] 72 | [InlineData("=")] 73 | [InlineData("!=")] 74 | [InlineData("ilike")] 75 | public void AllowValidOperatorsInHaving(string op) 76 | { 77 | new Query("Table").Having("Id", op, 1); 78 | new Query("Table").OrHaving("Id", op, 1); 79 | new Query("Table").HavingNot("Id", op, 1); 80 | new Query("Table").OrHavingNot("Id", op, 1); 81 | 82 | new Query("Table").HavingColumns("Col1", op, "Col2"); 83 | new Query("Table").OrHavingColumns("Col1", op, "Col2"); 84 | } 85 | 86 | [Theory] 87 | [InlineData("^")] 88 | [InlineData("<<")] 89 | [InlineData(">>")] 90 | [InlineData("~")] 91 | [InlineData("~*")] 92 | [InlineData("!~")] 93 | [InlineData("!~*")] 94 | public void ShouldNotThrowAfterWhiteListing(string op) 95 | { 96 | var compiler = new SqlServerCompiler().Whitelist(op); 97 | 98 | var query = new Query("Table"); 99 | 100 | compiler.Compile(query.Clone().Where("Id", op, 1)); 101 | compiler.Compile(query.Clone().OrWhere("Id", op, 1)); 102 | compiler.Compile(query.Clone().WhereNot("Id", op, 1)); 103 | compiler.Compile(query.Clone().OrWhereNot("Id", op, 1)); 104 | 105 | compiler.Compile(query.Clone().WhereColumns("Col1", op, "Col2")); 106 | compiler.Compile(query.Clone().OrWhereColumns("Col1", op, "Col2")); 107 | 108 | compiler.Compile(query.Clone().Having("Id", op, 1)); 109 | compiler.Compile(query.Clone().OrHaving("Id", op, 1)); 110 | compiler.Compile(query.Clone().HavingNot("Id", op, 1)); 111 | compiler.Compile(query.Clone().OrHavingNot("Id", op, 1)); 112 | 113 | compiler.Compile(query.Clone().HavingColumns("Col1", op, "Col2")); 114 | compiler.Compile(query.Clone().OrHavingColumns("Col1", op, "Col2")); 115 | } 116 | 117 | [Fact] 118 | public void ShouldAllowWhiteListedOperatorsInNestedWhere() 119 | { 120 | var compiler = new SqlServerCompiler().Whitelist("!!"); 121 | 122 | var query = new Query("Table") 123 | .Where(q => q.Where("A", "!!", "value")); 124 | 125 | compiler.Compile(query); 126 | } 127 | 128 | [Fact] 129 | public void ShouldNotConsiderWhereRawCondition() 130 | { 131 | var compiler = new SqlServerCompiler(); 132 | 133 | var query = new Query("Table") 134 | .WhereRaw("Col !! value"); 135 | 136 | } 137 | 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/Oracle/OracleDateConditionTests.cs: -------------------------------------------------------------------------------- 1 | using SqlKata.Compilers; 2 | using SqlKata.Tests.Infrastructure; 3 | using Xunit; 4 | 5 | namespace SqlKata.Tests.Oracle 6 | { 7 | public class OracleDateConditionTests : TestSupport 8 | { 9 | private const string TableName = "Table"; 10 | private const string SqlPlaceholder = "GENERATED_SQL"; 11 | 12 | private readonly OracleCompiler compiler; 13 | 14 | public OracleDateConditionTests() 15 | { 16 | compiler = Compilers.Get(EngineCodes.Oracle); 17 | } 18 | 19 | [Fact] 20 | public void SimpleWhereDateTest() 21 | { 22 | // Arrange: 23 | var query = new Query(TableName) 24 | .Select() 25 | .WhereDate("STAMP", "=", "2018-04-01"); 26 | 27 | // Act: 28 | var ctx = compiler.Compile(query); 29 | 30 | // Assert: 31 | Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE TO_CHAR(\"STAMP\", 'YY-MM-DD') = TO_CHAR(TO_DATE(?, 'YY-MM-DD'), 'YY-MM-DD')", ctx.RawSql); 32 | Assert.Equal("2018-04-01", ctx.Bindings[0]); 33 | Assert.Single(ctx.Bindings); 34 | } 35 | 36 | [Fact] 37 | public void SimpleWhereDatePartDateTest() 38 | { 39 | // Arrange: 40 | var query = new Query(TableName) 41 | .Select() 42 | .WhereDatePart("date", "STAMP", "=", "2018-04-01"); 43 | 44 | // Act: 45 | var ctx = compiler.Compile(query); 46 | 47 | // Assert: 48 | Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE TO_CHAR(\"STAMP\", 'YY-MM-DD') = TO_CHAR(TO_DATE(?, 'YY-MM-DD'), 'YY-MM-DD')", ctx.RawSql); 49 | Assert.Equal("2018-04-01", ctx.Bindings[0]); 50 | Assert.Single(ctx.Bindings); 51 | } 52 | 53 | [Fact] 54 | public void SimpleWhereTimeWithSecondsTest() 55 | { 56 | // Arrange: 57 | var query = new Query(TableName) 58 | .Select() 59 | .WhereTime("STAMP", "=", "19:01:10"); 60 | 61 | // Act: 62 | var ctx = compiler.Compile(query); 63 | 64 | // Assert: 65 | Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE TO_CHAR(\"STAMP\", 'HH24:MI:SS') = TO_CHAR(TO_DATE(?, 'HH24:MI:SS'), 'HH24:MI:SS')", ctx.RawSql); 66 | Assert.Equal("19:01:10", ctx.Bindings[0]); 67 | Assert.Single(ctx.Bindings); 68 | } 69 | 70 | [Fact] 71 | public void SimpleWhereDatePartTimeWithSecondsTest() 72 | { 73 | // Arrange: 74 | var query = new Query(TableName) 75 | .Select() 76 | .WhereDatePart("time", "STAMP", "=", "19:01:10"); 77 | 78 | // Act: 79 | var ctx = compiler.Compile(query); 80 | 81 | // Assert: 82 | Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE TO_CHAR(\"STAMP\", 'HH24:MI:SS') = TO_CHAR(TO_DATE(?, 'HH24:MI:SS'), 'HH24:MI:SS')", ctx.RawSql); 83 | Assert.Equal("19:01:10", ctx.Bindings[0]); 84 | Assert.Single(ctx.Bindings); 85 | } 86 | 87 | [Fact] 88 | public void SimpleWhereTimeWithoutSecondsTest() 89 | { 90 | // Arrange: 91 | var query = new Query(TableName) 92 | .Select() 93 | .WhereTime("STAMP", "=", "19:01"); 94 | 95 | // Act: 96 | var ctx = compiler.Compile(query); 97 | 98 | // Assert: 99 | Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE TO_CHAR(\"STAMP\", 'HH24:MI:SS') = TO_CHAR(TO_DATE(?, 'HH24:MI'), 'HH24:MI:SS')", ctx.RawSql); 100 | Assert.Equal("19:01", ctx.Bindings[0]); 101 | Assert.Single(ctx.Bindings); 102 | } 103 | 104 | [Fact] 105 | public void SimpleWhereDatePartTimeWithoutSecondsTest() 106 | { 107 | // Arrange: 108 | var query = new Query(TableName) 109 | .Select() 110 | .WhereDatePart("time", "STAMP", "=", "19:01"); 111 | 112 | // Act: 113 | var ctx = compiler.Compile(query); 114 | 115 | // Assert: 116 | Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE TO_CHAR(\"STAMP\", 'HH24:MI:SS') = TO_CHAR(TO_DATE(?, 'HH24:MI'), 'HH24:MI:SS')", ctx.RawSql); 117 | Assert.Equal("19:01", ctx.Bindings[0]); 118 | Assert.Single(ctx.Bindings); 119 | } 120 | 121 | [Fact] 122 | public void SimpleWhereDatePartYear() 123 | { 124 | // Arrange: 125 | var query = new Query(TableName) 126 | .Select() 127 | .WhereDatePart("year", "STAMP", "=", "2018"); 128 | 129 | // Act: 130 | var ctx = compiler.Compile(query); 131 | 132 | // Assert: 133 | Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE EXTRACT(YEAR FROM \"STAMP\") = ?", ctx.RawSql); 134 | Assert.Equal("2018", ctx.Bindings[0]); 135 | Assert.Single(ctx.Bindings); 136 | } 137 | 138 | [Fact] 139 | public void SimpleWhereDatePartMonth() 140 | { 141 | // Arrange: 142 | var query = new Query(TableName) 143 | .Select() 144 | .WhereDatePart("month", "STAMP", "=", "9"); 145 | 146 | // Act: 147 | var ctx = compiler.Compile(query); 148 | 149 | // Assert: 150 | Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE EXTRACT(MONTH FROM \"STAMP\") = ?", ctx.RawSql); 151 | Assert.Equal("9", ctx.Bindings[0]); 152 | Assert.Single(ctx.Bindings); 153 | } 154 | 155 | [Fact] 156 | public void SimpleWhereDatePartDay() 157 | { 158 | // Arrange: 159 | var query = new Query(TableName) 160 | .Select() 161 | .WhereDatePart("day", "STAMP", "=", "15"); 162 | 163 | // Act: 164 | var ctx = compiler.Compile(query); 165 | 166 | // Assert: 167 | Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE EXTRACT(DAY FROM \"STAMP\") = ?", ctx.RawSql); 168 | Assert.Equal("15", ctx.Bindings[0]); 169 | Assert.Single(ctx.Bindings); 170 | } 171 | 172 | [Fact] 173 | public void SimpleWhereDatePartHour() 174 | { 175 | // Arrange: 176 | var query = new Query(TableName) 177 | .Select() 178 | .WhereDatePart("hour", "STAMP", "=", "15"); 179 | 180 | // Act: 181 | var ctx = compiler.Compile(query); 182 | 183 | // Assert: 184 | Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE EXTRACT(HOUR FROM \"STAMP\") = ?", ctx.RawSql); 185 | Assert.Equal("15", ctx.Bindings[0]); 186 | Assert.Single(ctx.Bindings); 187 | } 188 | 189 | [Fact] 190 | public void SimpleWhereDatePartMinute() 191 | { 192 | // Arrange: 193 | var query = new Query(TableName) 194 | .Select() 195 | .WhereDatePart("minute", "STAMP", "=", "25"); 196 | 197 | // Act: 198 | var ctx = compiler.Compile(query); 199 | 200 | // Assert: 201 | Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE EXTRACT(MINUTE FROM \"STAMP\") = ?", ctx.RawSql); 202 | Assert.Equal("25", ctx.Bindings[0]); 203 | Assert.Single(ctx.Bindings); 204 | } 205 | 206 | [Fact] 207 | public void SimpleWhereDatePartSecond() 208 | { 209 | // Arrange: 210 | var query = new Query(TableName) 211 | .Select() 212 | .WhereDatePart("second", "STAMP", "=", "59"); 213 | 214 | // Act: 215 | var ctx = compiler.Compile(query); 216 | 217 | // Assert: 218 | Assert.Equal($"SELECT * FROM \"{TableName}\" WHERE EXTRACT(SECOND FROM \"STAMP\") = ?", ctx.RawSql); 219 | Assert.Equal("59", ctx.Bindings[0]); 220 | Assert.Single(ctx.Bindings); 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/Oracle/OracleInsertManyTests.cs: -------------------------------------------------------------------------------- 1 | using SqlKata.Compilers; 2 | using SqlKata.Tests.Infrastructure; 3 | using Xunit; 4 | 5 | namespace SqlKata.Tests.Oracle 6 | { 7 | public class OracleInsertManyTests : TestSupport 8 | { 9 | private const string TableName = "Table"; 10 | private readonly OracleCompiler compiler; 11 | 12 | public OracleInsertManyTests() 13 | { 14 | compiler = Compilers.Get(EngineCodes.Oracle); 15 | } 16 | 17 | [Fact] 18 | public void InsertManyForOracle_ShouldRepeatColumnsAndAddSelectFromDual() 19 | { 20 | // Arrange: 21 | var cols = new[] { "Name", "Price" }; 22 | 23 | var data = new[] { 24 | new object[] { "A", 1000 }, 25 | new object[] { "B", 2000 }, 26 | new object[] { "C", 3000 }, 27 | }; 28 | 29 | var query = new Query(TableName) 30 | .AsInsert(cols, data); 31 | 32 | 33 | // Act: 34 | var ctx = compiler.Compile(query); 35 | 36 | // Assert: 37 | Assert.Equal($@"INSERT ALL INTO ""{TableName}"" (""Name"", ""Price"") VALUES (?, ?) INTO ""{TableName}"" (""Name"", ""Price"") VALUES (?, ?) INTO ""{TableName}"" (""Name"", ""Price"") VALUES (?, ?) SELECT 1 FROM DUAL", ctx.RawSql); 38 | } 39 | 40 | [Fact] 41 | public void InsertForOracle_SingleInsertShouldNotAddALLKeywordAndNotHaveSelectFromDual() 42 | { 43 | // Arrange: 44 | var cols = new[] { "Name", "Price" }; 45 | 46 | var data = new[] { 47 | new object[] { "A", 1000 } 48 | }; 49 | 50 | var query = new Query(TableName) 51 | .AsInsert(cols, data); 52 | 53 | 54 | // Act: 55 | var ctx = compiler.Compile(query); 56 | 57 | // Assert: 58 | Assert.Equal($@"INSERT INTO ""{TableName}"" (""Name"", ""Price"") VALUES (?, ?)", ctx.RawSql); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/Oracle/OracleLegacyLimitTests.cs: -------------------------------------------------------------------------------- 1 | using SqlKata.Compilers; 2 | using SqlKata.Tests.Infrastructure; 3 | using Xunit; 4 | 5 | namespace SqlKata.Tests.Oracle 6 | { 7 | public class OracleLegacyLimitTests : TestSupport 8 | { 9 | private const string TableName = "Table"; 10 | private const string SqlPlaceholder = "GENERATED_SQL"; 11 | private readonly OracleCompiler compiler; 12 | 13 | public OracleLegacyLimitTests() 14 | { 15 | compiler = Compilers.Get(EngineCodes.Oracle); 16 | compiler.UseLegacyPagination = true; 17 | } 18 | 19 | [Fact] 20 | public void WithNoLimitNorOffset() 21 | { 22 | // Arrange: 23 | var query = new Query(TableName); 24 | var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; 25 | 26 | // Act: 27 | compiler.ApplyLegacyLimit(ctx); 28 | 29 | // Assert: 30 | Assert.Equal(SqlPlaceholder, ctx.RawSql); 31 | } 32 | 33 | [Fact] 34 | public void WithNoOffset() 35 | { 36 | // Arrange: 37 | var query = new Query(TableName).Limit(10); 38 | var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; 39 | 40 | // Act: 41 | compiler.ApplyLegacyLimit(ctx); 42 | 43 | // Assert: 44 | Assert.Matches($"SELECT \\* FROM \\({SqlPlaceholder}\\) WHERE ROWNUM <= ?", ctx.RawSql); 45 | Assert.Equal(10, ctx.Bindings[0]); 46 | Assert.Single(ctx.Bindings); 47 | } 48 | 49 | [Fact] 50 | public void WithNoLimit() 51 | { 52 | // Arrange: 53 | var query = new Query(TableName).Offset(20); 54 | var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; 55 | 56 | // Act: 57 | compiler.ApplyLegacyLimit(ctx); 58 | 59 | // Assert: 60 | Assert.Equal("SELECT * FROM (SELECT \"results_wrapper\".*, ROWNUM \"row_num\" FROM (GENERATED_SQL) \"results_wrapper\") WHERE \"row_num\" > ?", ctx.RawSql); 61 | Assert.Equal(20L, ctx.Bindings[0]); 62 | Assert.Single(ctx.Bindings); 63 | } 64 | 65 | [Fact] 66 | public void WithLimitAndOffset() 67 | { 68 | // Arrange: 69 | var query = new Query(TableName).Limit(5).Offset(20); 70 | var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; 71 | 72 | // Act: 73 | compiler.ApplyLegacyLimit(ctx); 74 | 75 | // Assert: 76 | Assert.Equal("SELECT * FROM (SELECT \"results_wrapper\".*, ROWNUM \"row_num\" FROM (GENERATED_SQL) \"results_wrapper\" WHERE ROWNUM <= ?) WHERE \"row_num\" > ?", ctx.RawSql); 77 | Assert.Equal(25L, ctx.Bindings[0]); 78 | Assert.Equal(20L, ctx.Bindings[1]); 79 | Assert.Equal(2, ctx.Bindings.Count); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/Oracle/OracleLimitTests.cs: -------------------------------------------------------------------------------- 1 | using SqlKata.Compilers; 2 | using SqlKata.Tests.Infrastructure; 3 | using Xunit; 4 | 5 | namespace SqlKata.Tests.Oracle 6 | { 7 | public class OracleLimitTests : TestSupport 8 | { 9 | private const string TableName = "Table"; 10 | private const string SqlPlaceholder = "GENERATED_SQL"; 11 | 12 | private readonly OracleCompiler compiler; 13 | 14 | public OracleLimitTests() 15 | { 16 | compiler = Compilers.Get(EngineCodes.Oracle); 17 | } 18 | 19 | [Fact] 20 | public void NoLimitNorOffset() 21 | { 22 | // Arrange: 23 | var query = new Query(TableName); 24 | var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; 25 | 26 | // Act & Assert: 27 | Assert.Null(compiler.CompileLimit(ctx)); 28 | } 29 | 30 | [Fact] 31 | public void LimitOnly() 32 | { 33 | // Arrange: 34 | var query = new Query(TableName).Limit(10); 35 | var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; 36 | 37 | // Act & Assert: 38 | Assert.EndsWith("OFFSET ? ROWS FETCH NEXT ? ROWS ONLY", compiler.CompileLimit(ctx)); 39 | Assert.Equal(2, ctx.Bindings.Count); 40 | Assert.Equal(0L, ctx.Bindings[0]); 41 | Assert.Equal(10, ctx.Bindings[1]); 42 | } 43 | 44 | [Fact] 45 | public void OffsetOnly() 46 | { 47 | // Arrange: 48 | var query = new Query(TableName).Offset(20); 49 | var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; 50 | 51 | // Act & Assert: 52 | Assert.EndsWith("OFFSET ? ROWS", compiler.CompileLimit(ctx)); 53 | 54 | Assert.Single(ctx.Bindings); 55 | Assert.Equal(20L, ctx.Bindings[0]); 56 | } 57 | 58 | [Fact] 59 | public void LimitAndOffset() 60 | { 61 | // Arrange: 62 | var query = new Query(TableName).Limit(5).Offset(20); 63 | var ctx = new SqlResult("?", "\\") { Query = query, RawSql = SqlPlaceholder }; 64 | 65 | // Act & Assert: 66 | Assert.EndsWith("OFFSET ? ROWS FETCH NEXT ? ROWS ONLY", compiler.CompileLimit(ctx)); 67 | 68 | Assert.Equal(2, ctx.Bindings.Count); 69 | Assert.Equal(20L, ctx.Bindings[0]); 70 | Assert.Equal(5, ctx.Bindings[1]); 71 | 72 | compiler.CompileLimit(ctx); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/ParameterTypeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using SqlKata.Compilers; 5 | using Xunit; 6 | using System.Collections; 7 | using SqlKata.Tests.Infrastructure; 8 | 9 | namespace SqlKata.Tests 10 | { 11 | public class ParameterTypeTests : TestSupport 12 | { 13 | public enum EnumExample 14 | { 15 | First, 16 | Second, 17 | Third, 18 | } 19 | 20 | public class ParameterTypeGenerator : IEnumerable 21 | { 22 | private readonly List _data = new List 23 | { 24 | new object[] {"1", 1}, 25 | new object[] {Convert.ToSingle("10.5", CultureInfo.InvariantCulture).ToString(), 10.5}, 26 | new object[] {"-2", -2}, 27 | new object[] {Convert.ToSingle("-2.8", CultureInfo.InvariantCulture).ToString(), -2.8}, 28 | new object[] {"cast(1 as bit)", true}, 29 | new object[] {"cast(0 as bit)", false}, 30 | new object[] {"'2018-10-28 19:22:00'", new DateTime(2018, 10, 28, 19, 22, 0)}, 31 | new object[] {"0 /* First */", EnumExample.First}, 32 | new object[] {"1 /* Second */", EnumExample.Second}, 33 | new object[] {"'a string'", "a string"}, 34 | }; 35 | 36 | public IEnumerator GetEnumerator() => _data.GetEnumerator(); 37 | 38 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 39 | } 40 | 41 | [Theory] 42 | [ClassData(typeof(ParameterTypeGenerator))] 43 | public void CorrectParameterTypeOutput(string rendered, object input) 44 | { 45 | var query = new Query("Table").Where("Col", input); 46 | 47 | var c = Compile(query); 48 | 49 | Assert.Equal($"SELECT * FROM [Table] WHERE [Col] = {rendered}", c[EngineCodes.SqlServer]); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/PostgreSql/PostgreSqlLimitTests.cs: -------------------------------------------------------------------------------- 1 | using SqlKata.Compilers; 2 | using SqlKata.Tests.Infrastructure; 3 | using Xunit; 4 | 5 | namespace SqlKata.Tests.PostgreSql 6 | { 7 | public class PostgreSqlLimitTests : TestSupport 8 | { 9 | private readonly PostgresCompiler compiler; 10 | 11 | public PostgreSqlLimitTests() 12 | { 13 | compiler = Compilers.Get(EngineCodes.PostgreSql); 14 | } 15 | 16 | [Fact] 17 | public void WithNoLimitNorOffset() 18 | { 19 | var query = new Query("Table"); 20 | var ctx = new SqlResult("?", "\\") {Query = query}; 21 | 22 | Assert.Null(compiler.CompileLimit(ctx)); 23 | } 24 | 25 | [Fact] 26 | public void WithNoOffset() 27 | { 28 | var query = new Query("Table").Limit(10); 29 | var ctx = new SqlResult("?", "\\") {Query = query}; 30 | 31 | Assert.Equal("LIMIT ?", compiler.CompileLimit(ctx)); 32 | Assert.Equal(10, ctx.Bindings[0]); 33 | } 34 | 35 | [Fact] 36 | public void WithNoLimit() 37 | { 38 | var query = new Query("Table").Offset(20); 39 | var ctx = new SqlResult("?", "\\") {Query = query}; 40 | 41 | Assert.Equal("OFFSET ?", compiler.CompileLimit(ctx)); 42 | Assert.Equal(20L, ctx.Bindings[0]); 43 | Assert.Single(ctx.Bindings); 44 | } 45 | 46 | [Fact] 47 | public void WithLimitAndOffset() 48 | { 49 | var query = new Query("Table").Limit(5).Offset(20); 50 | var ctx = new SqlResult("?", "\\") {Query = query}; 51 | 52 | Assert.Equal("LIMIT ? OFFSET ?", compiler.CompileLimit(ctx)); 53 | Assert.Equal(5, ctx.Bindings[0]); 54 | Assert.Equal(20L, ctx.Bindings[1]); 55 | Assert.Equal(2, ctx.Bindings.Count); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/QueryBuilder.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0 4 | Library 5 | false 6 | SqlKata.Tests 7 | 8 | 9 | 11 | 13 | 15 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/QueryFactoryExtension.cs: -------------------------------------------------------------------------------- 1 | 2 | using System.Collections.Generic; 3 | using SqlKata.Execution; 4 | 5 | static class QueryFactoryExtensions 6 | { 7 | public static QueryFactory Create(this QueryFactory db, string table, IEnumerable cols) 8 | { 9 | db.Drop(table); 10 | db.Statement($"CREATE TABLE `{table}`({string.Join(", ", cols)});"); 11 | return db; 12 | } 13 | 14 | public static QueryFactory Drop(this QueryFactory db, string table) 15 | { 16 | db.Statement($"DROP TABLE IF EXISTS `{table}`;"); 17 | return db; 18 | } 19 | } -------------------------------------------------------------------------------- /QueryBuilder.Tests/SQLiteExecutionTest.cs: -------------------------------------------------------------------------------- 1 | using SqlKata.Compilers; 2 | using Xunit; 3 | using SqlKata.Execution; 4 | using MySql.Data.MySqlClient; 5 | using System; 6 | using System.Linq; 7 | using static SqlKata.Expressions; 8 | using System.Collections.Generic; 9 | using Microsoft.Data.Sqlite; 10 | 11 | namespace SqlKata.Tests 12 | { 13 | public class SqliteExecutionTest 14 | { 15 | [Fact] 16 | public void EmptySelect() 17 | { 18 | 19 | var db = DB().Create("Cars", new[] { 20 | "Id INTEGER PRIMARY KEY AUTOINCREMENT", 21 | "Brand TEXT NOT NULL", 22 | "Year INT NOT NULL", 23 | "Color TEXT NULL", 24 | }); 25 | 26 | 27 | var tables = db.Select(@"SELECT name FROM sqlite_schema WHERE type ='table' AND name NOT LIKE 'sqlite_%'"); 28 | 29 | var rows = db.Query("Cars").Get(); 30 | 31 | Assert.Empty(rows); 32 | 33 | db.Drop("Cars"); 34 | } 35 | 36 | [Fact] 37 | public void SelectWithLimit() 38 | { 39 | var db = DB().Create("Cars", new[] { 40 | "Id INTEGER PRIMARY KEY AUTOINCREMENT", 41 | "Brand TEXT NOT NULL", 42 | "Year INT NOT NULL", 43 | "Color TEXT NULL", 44 | }); 45 | 46 | db.Statement("INSERT INTO `Cars`(Brand, Year) VALUES ('Honda', 2020)"); 47 | 48 | var rows = db.Query("Cars").Get().ToList(); 49 | 50 | Assert.Single(rows); 51 | 52 | db.Drop("Cars"); 53 | } 54 | 55 | [Fact] 56 | public void InsertGetId() 57 | { 58 | var db = DB().Create("Cars", new[] { 59 | "Id INTEGER PRIMARY KEY AUTOINCREMENT", 60 | "Brand TEXT NOT NULL", 61 | "Year INT NOT NULL", 62 | }); 63 | 64 | var id = db.Query("Cars").InsertGetId(new 65 | { 66 | Brand = "Toyota", 67 | Year = 1900 68 | }); 69 | 70 | Assert.Equal(1, id); 71 | 72 | id = db.Query("Cars").InsertGetId(new 73 | { 74 | Brand = "Toyota 2", 75 | Year = 1901 76 | }); 77 | 78 | Assert.Equal(2, id); 79 | 80 | id = db.Query("Cars").InsertGetId(new 81 | { 82 | Brand = "Toyota 2", 83 | Year = 1901 84 | }); 85 | 86 | Assert.Equal(3, id); 87 | 88 | 89 | db.Drop("Cars"); 90 | } 91 | 92 | 93 | 94 | [Fact] 95 | public void Count() 96 | { 97 | var db = DB().Create("Cars", new[] { 98 | "Id INTEGER PRIMARY KEY AUTOINCREMENT", 99 | "Brand TEXT NOT NULL", 100 | "Year INT NOT NULL", 101 | "Color TEXT NULL", 102 | }); 103 | 104 | db.Statement("INSERT INTO `Cars`(Brand, Year) VALUES ('Honda', 2020)"); 105 | var count = db.Query("Cars").Count(); 106 | Assert.Equal(1, count); 107 | 108 | db.Statement("INSERT INTO `Cars`(Brand, Year) VALUES ('Toyota', 2021)"); 109 | count = db.Query("Cars").Count(); 110 | Assert.Equal(2, count); 111 | 112 | int affected = db.Query("Cars").Delete(); 113 | Assert.Equal(2, affected); 114 | 115 | count = db.Query("Cars").Count(); 116 | Assert.Equal(0, count); 117 | 118 | db.Drop("Cars"); 119 | } 120 | 121 | [Fact] 122 | public void CloneThenCount() 123 | { 124 | var db = DB().Create("Cars", new[] { 125 | "Id INTEGER PRIMARY KEY AUTOINCREMENT", 126 | "Brand TEXT NOT NULL", 127 | "Year INT NOT NULL", 128 | "Color TEXT NULL", 129 | }); 130 | 131 | for (int i = 0; i < 10; i++) 132 | { 133 | db.Query("Cars").Insert(new 134 | { 135 | Brand = "Brand " + i, 136 | Year = "2020", 137 | }); 138 | } 139 | 140 | var query = db.Query("Cars").Where("Id", "<", 5); 141 | var count = query.Count(); 142 | var cloneCount = query.Clone().Count(); 143 | 144 | Assert.Equal(4, count); 145 | Assert.Equal(4, cloneCount); 146 | 147 | db.Drop("Cars"); 148 | } 149 | 150 | [Fact] 151 | public void QueryWithVariable() 152 | { 153 | var db = DB().Create("Cars", new[] { 154 | "Id INTEGER PRIMARY KEY AUTOINCREMENT", 155 | "Brand TEXT NOT NULL", 156 | "Year INT NOT NULL", 157 | "Color TEXT NULL", 158 | }); 159 | 160 | for (int i = 0; i < 10; i++) 161 | { 162 | db.Query("Cars").Insert(new 163 | { 164 | Brand = "Brand " + i, 165 | Year = "2020", 166 | }); 167 | } 168 | 169 | 170 | var count = db.Query("Cars") 171 | .Define("Threshold", 5) 172 | .Where("Id", "<", SqlKata.Expressions.Variable("Threshold")) 173 | .Count(); 174 | 175 | Assert.Equal(4, count); 176 | 177 | db.Drop("Cars"); 178 | } 179 | 180 | [Fact] 181 | public void InlineTable() 182 | { 183 | var db = DB().Create("Transaction", new[] { 184 | "Id INTEGER PRIMARY KEY AUTOINCREMENT", 185 | "Amount int NOT NULL", 186 | "Date DATE NOT NULL", 187 | }); 188 | 189 | db.Query("Transaction").Insert(new 190 | { 191 | Date = "2022-01-01", 192 | Amount = 10 193 | }); 194 | 195 | 196 | var rows = db.Query("Transaction") 197 | .With("Rates", new[] { "Date", "Rate" }, new object[][] { 198 | new object[] {"2022-01-01", 0.5}, 199 | }) 200 | .Join("Rates", "Rates.Date", "Transaction.Date") 201 | .SelectRaw("([Transaction].[Amount] * [Rates].[Rate]) as AmountConverted") 202 | .Get(); 203 | 204 | Assert.Single(rows); 205 | Assert.Equal(5, rows.First().AmountConverted); 206 | 207 | db.Drop("Transaction"); 208 | } 209 | 210 | 211 | 212 | [Fact] 213 | public void BasicSelectFilter() 214 | { 215 | var db = DB().Create("Transaction", new[] { 216 | "Id INTEGER PRIMARY KEY AUTOINCREMENT", 217 | "Date DATE NOT NULL", 218 | "Amount int NOT NULL", 219 | }); 220 | 221 | var data = new Dictionary { 222 | // 2020 223 | {"2020-01-01", 10}, 224 | {"2020-05-01", 20}, 225 | 226 | // 2021 227 | {"2021-01-01", 40}, 228 | {"2021-02-01", 10}, 229 | {"2021-04-01", -10}, 230 | 231 | // 2022 232 | {"2022-01-01", 80}, 233 | {"2022-02-01", -30}, 234 | {"2022-05-01", 50}, 235 | }; 236 | 237 | foreach (var row in data) 238 | { 239 | db.Query("Transaction").Insert(new 240 | { 241 | Date = row.Key, 242 | Amount = row.Value 243 | }); 244 | } 245 | 246 | var query = db.Query("Transaction") 247 | .SelectSum("Amount as Total_2020", q => q.WhereDatePart("year", "date", 2020)) 248 | .SelectSum("Amount as Total_2021", q => q.WhereDatePart("year", "date", 2021)) 249 | .SelectSum("Amount as Total_2022", q => q.WhereDatePart("year", "date", 2022)) 250 | ; 251 | 252 | var results = query.Get().ToList(); 253 | Assert.Single(results); 254 | Assert.Equal(30, results[0].Total_2020); 255 | Assert.Equal(40, results[0].Total_2021); 256 | Assert.Equal(100, results[0].Total_2022); 257 | 258 | db.Drop("Transaction"); 259 | } 260 | 261 | QueryFactory DB() 262 | { 263 | var cs = $"Data Source=file::memory:;Cache=Shared"; 264 | 265 | var connection = new SqliteConnection(cs); 266 | 267 | var db = new QueryFactory(connection, new SqliteCompiler()); 268 | 269 | return db; 270 | } 271 | 272 | 273 | 274 | } 275 | } -------------------------------------------------------------------------------- /QueryBuilder.Tests/SqlServer/NestedSelectTests.cs: -------------------------------------------------------------------------------- 1 | using SqlKata.Compilers; 2 | using SqlKata.Tests.Infrastructure; 3 | using Xunit; 4 | 5 | namespace SqlKata.Tests.SqlServer 6 | { 7 | public class NestedSelectTests : TestSupport 8 | { 9 | private readonly SqlServerCompiler compiler; 10 | 11 | public NestedSelectTests() 12 | { 13 | compiler = Compilers.Get(EngineCodes.SqlServer); 14 | } 15 | 16 | [Fact] 17 | public void Compile_RawSql_WithLimit_ReturnsCorrectQuery() 18 | { 19 | var q = new Query().From("Foo as src").Limit(1); 20 | 21 | var actual = compiler.Compile(q).ToString(); 22 | Assert.Contains("SELECT TOP (1) * FROM [Foo]", actual); 23 | } 24 | 25 | [Fact] 26 | public void SqlCompile_QueryAadNestedLimit_ReturnsQueryWithTop() 27 | { 28 | var q = new Query().From("Foo as src").Select("MyData"); 29 | var n = new Query().From("Bar").Limit(1).Select("MyData"); 30 | q.Select(n, "Bar"); 31 | 32 | var actual = compiler.Compile(q).ToString(); 33 | Assert.Contains("SELECT TOP (1) [MyData] FROM [Bar]", actual); 34 | Assert.Contains("SELECT [MyData], (SELECT TOP (1) [MyData] FROM [Bar]) AS [Bar] FROM [Foo] AS [src]", 35 | actual); 36 | } 37 | 38 | [Fact] 39 | public void SqlCompile_QueryLimitAndNestedLimit_ReturnsQueryWithTop() 40 | { 41 | var q = new Query().From("Foo as src").Limit(1).Select("MyData"); 42 | var n = new Query().From("Bar").Limit(1).Select("MyData"); 43 | q.Select(n, "Bar"); 44 | 45 | 46 | var actual = compiler.Compile(q).ToString(); 47 | Assert.Contains( 48 | "SELECT TOP (1) [MyData], (SELECT TOP (1) [MyData] FROM [Bar]) AS [Bar] FROM [Foo] AS [src]", actual); 49 | } 50 | 51 | [Fact] 52 | public void SqlCompile_QueryLimitAndNestedLimit_BindingValue() 53 | { 54 | var n = new Query().From("Bar"); 55 | var q = new Query().From("Foo").Where("x", true).WhereNotExists(n); 56 | // var q = new Query().From("Foo").Where("C", "c").WhereExists(n).Where("A", "a"); 57 | 58 | var actual = compiler.Compile(q).ToString(); 59 | Assert.Contains("SELECT * FROM [Foo] WHERE [x] = cast(1 as bit) AND NOT EXISTS (SELECT 1 FROM [Bar])", 60 | actual); 61 | // Assert.Contains("SELECT * FROM [Foo] WHERE [C] = 'c' AND EXISTS (SELECT TOP (1) 1 FROM [Bar]) AND [A] = 'a'", actual); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/SqlServer/SqlServerLegacyLimitTests.cs: -------------------------------------------------------------------------------- 1 | using SqlKata.Compilers; 2 | using SqlKata.Tests.Infrastructure; 3 | using Xunit; 4 | 5 | namespace SqlKata.Tests.SqlServer 6 | { 7 | public class SqlServerLegacyLimitTests : TestSupport 8 | { 9 | private readonly SqlServerCompiler compiler; 10 | 11 | public SqlServerLegacyLimitTests() 12 | { 13 | compiler = Compilers.Get(EngineCodes.SqlServer); 14 | compiler.UseLegacyPagination = true; 15 | } 16 | 17 | [Fact] 18 | public void NoLimitNorOffset() 19 | { 20 | var query = new Query("Table"); 21 | var ctx = new SqlResult("?", "\\") {Query = query}; 22 | 23 | Assert.Null(compiler.CompileLimit(ctx)); 24 | } 25 | 26 | [Fact] 27 | public void LimitOnly() 28 | { 29 | var query = new Query("Table").Limit(10); 30 | var ctx = new SqlResult("?", "\\") {Query = query}; 31 | 32 | Assert.Null(compiler.CompileLimit(ctx)); 33 | } 34 | 35 | [Fact] 36 | public void OffsetOnly() 37 | { 38 | var query = new Query("Table").Offset(20); 39 | var ctx = new SqlResult("?", "\\") {Query = query}; 40 | 41 | Assert.Null(compiler.CompileLimit(ctx)); 42 | } 43 | 44 | [Fact] 45 | public void LimitAndOffset() 46 | { 47 | var query = new Query("Table").Limit(5).Offset(20); 48 | var ctx = new SqlResult("?", "\\") {Query = query}; 49 | 50 | Assert.Null(compiler.CompileLimit(ctx)); 51 | } 52 | 53 | [Fact] 54 | public void ShouldEmulateOrderByIfNoOrderByProvided() 55 | { 56 | var query = new Query("Table").Limit(5).Offset(20); 57 | 58 | Assert.Contains("ORDER BY (SELECT 0)", compiler.Compile(query).ToString()); 59 | } 60 | 61 | [Fact] 62 | public void ShouldKeepTheOrdersAsIsIfNoPaginationProvided() 63 | { 64 | var query = new Query("Table").OrderBy("Id"); 65 | 66 | Assert.Contains("ORDER BY [Id]", compiler.Compile(query).ToString()); 67 | } 68 | 69 | [Fact] 70 | public void ShouldKeepTheOrdersAsIsIfPaginationProvided() 71 | { 72 | var query = new Query("Table").Offset(10).Limit(20).OrderBy("Id"); 73 | 74 | Assert.Contains("ORDER BY [Id]", compiler.Compile(query).ToString()); 75 | Assert.DoesNotContain("(SELECT 0)", compiler.Compile(query).ToString()); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/SqlServer/SqlServerLimitTests.cs: -------------------------------------------------------------------------------- 1 | using SqlKata.Compilers; 2 | using SqlKata.Tests.Infrastructure; 3 | using Xunit; 4 | 5 | namespace SqlKata.Tests.SqlServer 6 | { 7 | public class SqlServerLimitTests : TestSupport 8 | { 9 | private readonly SqlServerCompiler compiler; 10 | 11 | public SqlServerLimitTests() 12 | { 13 | compiler = Compilers.Get(EngineCodes.SqlServer); 14 | compiler.UseLegacyPagination = false; 15 | } 16 | 17 | [Fact] 18 | public void NoLimitNorOffset() 19 | { 20 | var query = new Query("Table"); 21 | var ctx = new SqlResult("?", "\\") {Query = query}; 22 | 23 | Assert.Null(compiler.CompileLimit(ctx)); 24 | } 25 | 26 | [Fact] 27 | public void LimitOnly() 28 | { 29 | var query = new Query("Table").Limit(10); 30 | var ctx = new SqlResult("?", "\\") {Query = query}; 31 | 32 | Assert.EndsWith("OFFSET ? ROWS FETCH NEXT ? ROWS ONLY", compiler.CompileLimit(ctx)); 33 | Assert.Equal(2, ctx.Bindings.Count); 34 | Assert.Equal(0L, ctx.Bindings[0]); 35 | Assert.Equal(10, ctx.Bindings[1]); 36 | } 37 | 38 | [Fact] 39 | public void OffsetOnly() 40 | { 41 | var query = new Query("Table").Offset(20); 42 | var ctx = new SqlResult("?", "\\") {Query = query}; 43 | 44 | Assert.EndsWith("OFFSET ? ROWS", compiler.CompileLimit(ctx)); 45 | 46 | Assert.Single(ctx.Bindings); 47 | Assert.Equal(20L, ctx.Bindings[0]); 48 | } 49 | 50 | [Fact] 51 | public void LimitAndOffset() 52 | { 53 | var query = new Query("Table").Limit(5).Offset(20); 54 | var ctx = new SqlResult("?", "\\") {Query = query}; 55 | 56 | Assert.EndsWith("OFFSET ? ROWS FETCH NEXT ? ROWS ONLY", compiler.CompileLimit(ctx)); 57 | 58 | Assert.Equal(2, ctx.Bindings.Count); 59 | Assert.Equal(20L, ctx.Bindings[0]); 60 | Assert.Equal(5, ctx.Bindings[1]); 61 | } 62 | 63 | [Fact] 64 | public void ShouldEmulateOrderByIfNoOrderByProvided() 65 | { 66 | var query = new Query("Table").Limit(5).Offset(20); 67 | 68 | Assert.Contains("ORDER BY (SELECT 0)", compiler.Compile(query).ToString()); 69 | } 70 | 71 | [Fact] 72 | public void ShouldKeepTheOrdersAsIsIfNoPaginationProvided() 73 | { 74 | var query = new Query("Table").OrderBy("Id"); 75 | 76 | Assert.Contains("ORDER BY [Id]", compiler.Compile(query).ToString()); 77 | } 78 | 79 | [Fact] 80 | public void ShouldKeepTheOrdersAsIsIfPaginationProvided() 81 | { 82 | var query = new Query("Table").Offset(10).Limit(20).OrderBy("Id"); 83 | 84 | Assert.Contains("ORDER BY [Id]", compiler.Compile(query).ToString()); 85 | Assert.DoesNotContain("(SELECT 0)", compiler.Compile(query).ToString()); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/SqlServer/SqlServerTests.cs: -------------------------------------------------------------------------------- 1 | using SqlKata.Compilers; 2 | using SqlKata.Tests.Infrastructure; 3 | using Xunit; 4 | 5 | namespace SqlKata.Tests.SqlServer 6 | { 7 | public class SqlServerTests : TestSupport 8 | { 9 | private readonly SqlServerCompiler compiler; 10 | 11 | public SqlServerTests() 12 | { 13 | compiler = Compilers.Get(EngineCodes.SqlServer); 14 | } 15 | 16 | 17 | [Fact] 18 | public void SqlServerTop() 19 | { 20 | var query = new Query("table").Limit(1); 21 | var result = compiler.Compile(query); 22 | Assert.Equal("SELECT TOP (@p0) * FROM [table]", result.Sql); 23 | } 24 | 25 | 26 | [Fact] 27 | public void SqlServerSelectWithParameterPlaceHolder() 28 | { 29 | var query = new Query("table").Select("Column\\?"); 30 | var result = compiler.Compile(query); 31 | Assert.Equal("SELECT [Column\\?] FROM [table]", result.Sql); 32 | } 33 | 34 | [Fact] 35 | public void SqlServerTopWithDistinct() 36 | { 37 | var query = new Query("table").Limit(1).Distinct(); 38 | var result = compiler.Compile(query); 39 | Assert.Equal("SELECT DISTINCT TOP (@p0) * FROM [table]", result.Sql); 40 | } 41 | 42 | 43 | [Theory()] 44 | [InlineData(-100)] 45 | [InlineData(0)] 46 | public void OffsetSqlServer_Should_Be_Ignored_If_Zero_Or_Negative(int offset) 47 | { 48 | var q = new Query().From("users").Offset(offset); 49 | var c = Compilers.CompileFor(EngineCodes.SqlServer, q); 50 | 51 | Assert.Equal("SELECT * FROM [users]", c.ToString()); 52 | } 53 | 54 | [Fact] 55 | public void SqlServerSelectWithParameterPlaceHolderEscaped() 56 | { 57 | var query = new Query("table").Select("Column\\?"); 58 | var result = compiler.Compile(query); 59 | Assert.Equal("SELECT [Column?] FROM [table]", result.ToString()); 60 | } 61 | 62 | [Theory()] 63 | [InlineData(1)] 64 | [InlineData(2)] 65 | [InlineData(3)] 66 | [InlineData(4)] 67 | [InlineData(100)] 68 | [InlineData(1000000)] 69 | public void OffsetSqlServer_Should_Be_Incremented_By_One(int offset) 70 | { 71 | var q = new Query().From("users").Offset(offset); 72 | var c = Compilers.CompileFor(EngineCodes.SqlServer, q); 73 | Assert.Equal( 74 | "SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [row_num] FROM [users]) AS [results_wrapper] WHERE [row_num] >= " + 75 | (offset + 1), c.ToString()); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/Sqlite/SqliteLimitTests.cs: -------------------------------------------------------------------------------- 1 | using SqlKata.Compilers; 2 | using SqlKata.Tests.Infrastructure; 3 | using Xunit; 4 | 5 | namespace SqlKata.Tests.Sqlite 6 | { 7 | public class SqliteLimitTests : TestSupport 8 | { 9 | private readonly SqliteCompiler compiler; 10 | 11 | public SqliteLimitTests() 12 | { 13 | compiler = Compilers.Get(EngineCodes.Sqlite); 14 | } 15 | 16 | [Fact] 17 | public void WithNoLimitNorOffset() 18 | { 19 | var query = new Query("Table"); 20 | var ctx = new SqlResult("?", "\\") { Query = query }; 21 | 22 | Assert.Null(compiler.CompileLimit(ctx)); 23 | } 24 | 25 | [Fact] 26 | public void WithNoOffset() 27 | { 28 | var query = new Query("Table").Limit(10); 29 | var ctx = new SqlResult("?", "\\") { Query = query }; 30 | 31 | Assert.Equal("LIMIT ?", compiler.CompileLimit(ctx)); 32 | Assert.Equal(10, ctx.Bindings[0]); 33 | } 34 | 35 | [Fact] 36 | public void WithNoLimit() 37 | { 38 | var query = new Query("Table").Offset(20); 39 | var ctx = new SqlResult("?", "\\") { Query = query }; 40 | 41 | Assert.Equal("LIMIT -1 OFFSET ?", compiler.CompileLimit(ctx)); 42 | Assert.Equal(20L, ctx.Bindings[0]); 43 | Assert.Single(ctx.Bindings); 44 | } 45 | 46 | [Fact] 47 | public void WithLimitAndOffset() 48 | { 49 | var query = new Query("Table").Limit(5).Offset(20); 50 | var ctx = new SqlResult("?", "\\") { Query = query }; 51 | 52 | Assert.Equal("LIMIT ? OFFSET ?", compiler.CompileLimit(ctx)); 53 | Assert.Equal(5, ctx.Bindings[0]); 54 | Assert.Equal(20L, ctx.Bindings[1]); 55 | Assert.Equal(2, ctx.Bindings.Count); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /QueryBuilder.Tests/WhereTests.cs: -------------------------------------------------------------------------------- 1 | using SqlKata.Compilers; 2 | using SqlKata.Tests.Infrastructure; 3 | using Xunit; 4 | 5 | namespace SqlKata.Tests 6 | { 7 | public class WhereTests : TestSupport 8 | { 9 | [Fact] 10 | public void GroupedWhereFilters() 11 | { 12 | var q = new Query("Table1") 13 | .Where(q => q.Or().Where("Column1", 10).Or().Where("Column2", 20)) 14 | .Where("Column3", 30); 15 | 16 | var c = Compile(q); 17 | 18 | Assert.Equal(@"SELECT * FROM ""Table1"" WHERE (""Column1"" = 10 OR ""Column2"" = 20) AND ""Column3"" = 30", c[EngineCodes.PostgreSql]); 19 | } 20 | 21 | [Fact] 22 | public void GroupedHavingFilters() 23 | { 24 | var q = new Query("Table1") 25 | .Having(q => q.Or().HavingRaw("SUM([Column1]) = ?", 10).Or().HavingRaw("SUM([Column2]) = ?", 20)) 26 | .HavingRaw("SUM([Column3]) = ?", 30); 27 | 28 | var c = Compile(q); 29 | 30 | Assert.Equal(@"SELECT * FROM ""Table1"" HAVING (SUM(""Column1"") = 10 OR SUM(""Column2"") = 20) AND SUM(""Column3"") = 30", c[EngineCodes.PostgreSql]); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /QueryBuilder/BaseQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace SqlKata 6 | { 7 | public abstract class AbstractQuery 8 | { 9 | public AbstractQuery Parent; 10 | } 11 | 12 | public abstract partial class BaseQuery : AbstractQuery where Q : BaseQuery 13 | { 14 | public List Clauses { get; set; } = new List(); 15 | 16 | private bool orFlag = false; 17 | private bool notFlag = false; 18 | public string EngineScope = null; 19 | 20 | public Q SetEngineScope(string engine) 21 | { 22 | this.EngineScope = engine; 23 | 24 | return (Q)this; 25 | } 26 | 27 | public BaseQuery() 28 | { 29 | } 30 | 31 | /// 32 | /// Return a cloned copy of the current query. 33 | /// 34 | /// 35 | public virtual Q Clone() 36 | { 37 | var q = NewQuery(); 38 | 39 | q.Clauses = this.Clauses.Select(x => x.Clone()).ToList(); 40 | 41 | return q; 42 | } 43 | 44 | public Q SetParent(AbstractQuery parent) 45 | { 46 | if (this == parent) 47 | { 48 | throw new ArgumentException($"Cannot set the same {nameof(AbstractQuery)} as a parent of itself"); 49 | } 50 | 51 | this.Parent = parent; 52 | return (Q)this; 53 | } 54 | 55 | public abstract Q NewQuery(); 56 | 57 | public Q NewChild() 58 | { 59 | var newQuery = NewQuery().SetParent((Q)this); 60 | newQuery.EngineScope = this.EngineScope; 61 | return newQuery; 62 | } 63 | 64 | /// 65 | /// Add a component clause to the query. 66 | /// 67 | /// 68 | /// 69 | /// 70 | /// 71 | public Q AddComponent(string component, AbstractClause clause, string engineCode = null) 72 | { 73 | if (engineCode == null) 74 | { 75 | engineCode = EngineScope; 76 | } 77 | 78 | clause.Engine = engineCode; 79 | clause.Component = component; 80 | Clauses.Add(clause); 81 | 82 | return (Q)this; 83 | } 84 | 85 | /// 86 | /// If the query already contains a clause for the given component 87 | /// and engine, replace it with the specified clause. Otherwise, just 88 | /// add the clause. 89 | /// 90 | /// 91 | /// 92 | /// 93 | /// 94 | public Q AddOrReplaceComponent(string component, AbstractClause clause, string engineCode = null) 95 | { 96 | engineCode = engineCode ?? EngineScope; 97 | 98 | var current = GetComponents(component).SingleOrDefault(c => c.Engine == engineCode); 99 | if (current != null) 100 | Clauses.Remove(current); 101 | 102 | return AddComponent(component, clause, engineCode); 103 | } 104 | 105 | 106 | 107 | /// 108 | /// Get the list of clauses for a component. 109 | /// 110 | /// 111 | public List GetComponents(string component, string engineCode = null) where C : AbstractClause 112 | { 113 | if (engineCode == null) 114 | { 115 | engineCode = EngineScope; 116 | } 117 | 118 | var clauses = Clauses 119 | .Where(x => x.Component == component) 120 | .Where(x => engineCode == null || x.Engine == null || engineCode == x.Engine) 121 | .Cast(); 122 | 123 | return clauses.ToList(); 124 | } 125 | 126 | /// 127 | /// Get the list of clauses for a component. 128 | /// 129 | /// 130 | /// 131 | /// 132 | public List GetComponents(string component, string engineCode = null) 133 | { 134 | if (engineCode == null) 135 | { 136 | engineCode = EngineScope; 137 | } 138 | 139 | return GetComponents(component, engineCode); 140 | } 141 | 142 | /// 143 | /// Get a single component clause from the query. 144 | /// 145 | /// 146 | public C GetOneComponent(string component, string engineCode = null) where C : AbstractClause 147 | { 148 | engineCode = engineCode ?? EngineScope; 149 | 150 | var all = GetComponents(component, engineCode); 151 | return all.FirstOrDefault(c => c.Engine == engineCode) ?? all.FirstOrDefault(c => c.Engine == null); 152 | } 153 | 154 | /// 155 | /// Get a single component clause from the query. 156 | /// 157 | /// 158 | /// 159 | /// 160 | public AbstractClause GetOneComponent(string component, string engineCode = null) 161 | { 162 | if (engineCode == null) 163 | { 164 | engineCode = EngineScope; 165 | } 166 | 167 | return GetOneComponent(component, engineCode); 168 | } 169 | 170 | /// 171 | /// Return whether the query has clauses for a component. 172 | /// 173 | /// 174 | /// 175 | /// 176 | public bool HasComponent(string component, string engineCode = null) 177 | { 178 | if (engineCode == null) 179 | { 180 | engineCode = EngineScope; 181 | } 182 | 183 | return GetComponents(component, engineCode).Any(); 184 | } 185 | 186 | /// 187 | /// Remove all clauses for a component. 188 | /// 189 | /// 190 | /// 191 | /// 192 | public Q ClearComponent(string component, string engineCode = null) 193 | { 194 | if (engineCode == null) 195 | { 196 | engineCode = EngineScope; 197 | } 198 | 199 | Clauses = Clauses 200 | .Where(x => !(x.Component == component && (engineCode == null || x.Engine == null || engineCode == x.Engine))) 201 | .ToList(); 202 | 203 | return (Q)this; 204 | } 205 | 206 | /// 207 | /// Set the next boolean operator to "and" for the "where" clause. 208 | /// 209 | /// 210 | protected Q And() 211 | { 212 | orFlag = false; 213 | return (Q)this; 214 | } 215 | 216 | /// 217 | /// Set the next boolean operator to "or" for the "where" clause. 218 | /// 219 | /// 220 | public Q Or() 221 | { 222 | orFlag = true; 223 | return (Q)this; 224 | } 225 | 226 | /// 227 | /// Set the next "not" operator for the "where" clause. 228 | /// 229 | /// 230 | public Q Not(bool flag = true) 231 | { 232 | notFlag = flag; 233 | return (Q)this; 234 | } 235 | 236 | /// 237 | /// Get the boolean operator and reset it to "and" 238 | /// 239 | /// 240 | protected bool GetOr() 241 | { 242 | var ret = orFlag; 243 | 244 | // reset the flag 245 | orFlag = false; 246 | return ret; 247 | } 248 | 249 | /// 250 | /// Get the "not" operator and clear it 251 | /// 252 | /// 253 | protected bool GetNot() 254 | { 255 | var ret = notFlag; 256 | 257 | // reset the flag 258 | notFlag = false; 259 | return ret; 260 | } 261 | 262 | /// 263 | /// Add a from Clause 264 | /// 265 | /// 266 | /// 267 | public Q From(string table) 268 | { 269 | return AddOrReplaceComponent("from", new FromClause 270 | { 271 | Table = table, 272 | }); 273 | } 274 | 275 | public Q From(Query query, string alias = null) 276 | { 277 | query = query.Clone(); 278 | query.SetParent((Q)this); 279 | 280 | if (alias != null) 281 | { 282 | query.As(alias); 283 | }; 284 | 285 | return AddOrReplaceComponent("from", new QueryFromClause 286 | { 287 | Query = query 288 | }); 289 | } 290 | 291 | public Q FromRaw(string sql, params object[] bindings) 292 | { 293 | return AddOrReplaceComponent("from", new RawFromClause 294 | { 295 | Expression = sql, 296 | Bindings = bindings, 297 | }); 298 | } 299 | 300 | public Q From(Func callback, string alias = null) 301 | { 302 | var query = new Query(); 303 | 304 | query.SetParent((Q)this); 305 | 306 | return From(callback.Invoke(query), alias); 307 | } 308 | 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /QueryBuilder/Clauses/AbstractClause.cs: -------------------------------------------------------------------------------- 1 | namespace SqlKata 2 | { 3 | public abstract class AbstractClause 4 | { 5 | /// 6 | /// Gets or sets the SQL engine. 7 | /// 8 | /// 9 | /// The SQL engine. 10 | /// 11 | public string Engine { get; set; } = null; 12 | 13 | /// 14 | /// Gets or sets the component name. 15 | /// 16 | /// 17 | /// The component name. 18 | /// 19 | public string Component { get; set; } 20 | public abstract AbstractClause Clone(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /QueryBuilder/Clauses/AggregateClause.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SqlKata 4 | { 5 | /// 6 | /// Represents aggregate clause like "COUNT", "MAX" or etc. 7 | /// 8 | /// 9 | public class AggregateClause : AbstractClause 10 | { 11 | /// 12 | /// Gets or sets columns that used in aggregate clause. 13 | /// 14 | /// 15 | /// The columns to be aggregated. 16 | /// 17 | public List Columns { get; set; } 18 | 19 | /// 20 | /// Gets or sets the type of aggregate function. 21 | /// 22 | /// 23 | /// The type of aggregate function, e.g. "MAX", "MIN", etc. 24 | /// 25 | public string Type { get; set; } 26 | 27 | /// 28 | public override AbstractClause Clone() 29 | { 30 | return new AggregateClause 31 | { 32 | Engine = Engine, 33 | Type = Type, 34 | Columns = new List(Columns), 35 | Component = Component, 36 | }; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /QueryBuilder/Clauses/ColumnClause.cs: -------------------------------------------------------------------------------- 1 | namespace SqlKata 2 | { 3 | public abstract class AbstractColumn : AbstractClause 4 | { 5 | } 6 | 7 | /// 8 | /// Represents "column" or "column as alias" clause. 9 | /// 10 | /// 11 | public class Column : AbstractColumn 12 | { 13 | /// 14 | /// Gets or sets the column name. Can be "columnName" or "columnName as columnAlias". 15 | /// 16 | /// 17 | /// The column name. 18 | /// 19 | public string Name { get; set; } 20 | 21 | /// 22 | public override AbstractClause Clone() 23 | { 24 | return new Column 25 | { 26 | Engine = Engine, 27 | Name = Name, 28 | Component = Component, 29 | }; 30 | } 31 | } 32 | 33 | /// 34 | /// Represents column clause calculated using query. 35 | /// 36 | /// 37 | public class QueryColumn : AbstractColumn 38 | { 39 | /// 40 | /// Gets or sets the query that will be used for column value calculation. 41 | /// 42 | /// 43 | /// The query for column value calculation. 44 | /// 45 | public Query Query { get; set; } 46 | public override AbstractClause Clone() 47 | { 48 | return new QueryColumn 49 | { 50 | Engine = Engine, 51 | Query = Query.Clone(), 52 | Component = Component, 53 | }; 54 | } 55 | } 56 | 57 | public class RawColumn : AbstractColumn 58 | { 59 | /// 60 | /// Gets or sets the RAW expression. 61 | /// 62 | /// 63 | /// The RAW expression. 64 | /// 65 | public string Expression { get; set; } 66 | public object[] Bindings { set; get; } 67 | 68 | /// 69 | public override AbstractClause Clone() 70 | { 71 | return new RawColumn 72 | { 73 | Engine = Engine, 74 | Expression = Expression, 75 | Bindings = Bindings, 76 | Component = Component, 77 | }; 78 | } 79 | } 80 | 81 | /// 82 | /// Represents an aggregated column clause with an optional filter 83 | /// 84 | /// 85 | public class AggregatedColumn : AbstractColumn 86 | { 87 | /// 88 | /// Gets or sets the a query that used to filter the data, 89 | /// the compiler will consider only the `Where` clause. 90 | /// 91 | /// 92 | /// The filter query. 93 | /// 94 | public Query Filter { get; set; } = null; 95 | public string Aggregate { get; set; } 96 | public AbstractColumn Column { get; set; } 97 | public override AbstractClause Clone() 98 | { 99 | return new AggregatedColumn 100 | { 101 | Engine = Engine, 102 | Filter = Filter?.Clone(), 103 | Column = Column.Clone() as AbstractColumn, 104 | Aggregate = Aggregate, 105 | Component = Component, 106 | }; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /QueryBuilder/Clauses/Combine.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace SqlKata 4 | { 5 | public abstract class AbstractCombine : AbstractClause 6 | { 7 | 8 | } 9 | 10 | public class Combine : AbstractCombine 11 | { 12 | /// 13 | /// Gets or sets the query to be combined with. 14 | /// 15 | /// 16 | /// The query that will be combined. 17 | /// 18 | public Query Query { get; set; } 19 | 20 | /// 21 | /// Gets or sets the combine operation, e.g. "UNION", etc. 22 | /// 23 | /// 24 | /// The combine operation. 25 | /// 26 | public string Operation { get; set; } 27 | 28 | /// 29 | /// Gets or sets a value indicating whether this clause will combine all. 30 | /// 31 | /// 32 | /// true if all; otherwise, false. 33 | /// 34 | public bool All { get; set; } = false; 35 | 36 | public override AbstractClause Clone() 37 | { 38 | return new Combine 39 | { 40 | Engine = Engine, 41 | Operation = Operation, 42 | Component = Component, 43 | Query = Query, 44 | All = All, 45 | }; 46 | } 47 | } 48 | 49 | public class RawCombine : AbstractCombine 50 | { 51 | public string Expression { get; set; } 52 | 53 | public object[] Bindings { get; set; } 54 | 55 | public override AbstractClause Clone() 56 | { 57 | return new RawCombine 58 | { 59 | Engine = Engine, 60 | Component = Component, 61 | Expression = Expression, 62 | Bindings = Bindings, 63 | }; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /QueryBuilder/Clauses/FromClause.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace SqlKata 5 | { 6 | public abstract class AbstractFrom : AbstractClause 7 | { 8 | protected string _alias; 9 | 10 | /// 11 | /// Try to extract the Alias for the current clause. 12 | /// 13 | /// 14 | public virtual string Alias { get => _alias; set => _alias = value; } 15 | } 16 | 17 | /// 18 | /// Represents a "from" clause. 19 | /// 20 | public class FromClause : AbstractFrom 21 | { 22 | public string Table { get; set; } 23 | 24 | public override string Alias 25 | { 26 | get 27 | { 28 | if (Table.IndexOf(" as ", StringComparison.OrdinalIgnoreCase) >= 0) 29 | { 30 | var segments = Table.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); 31 | 32 | return segments[2]; 33 | } 34 | 35 | return Table; 36 | } 37 | } 38 | 39 | /// 40 | public override AbstractClause Clone() 41 | { 42 | return new FromClause 43 | { 44 | Engine = Engine, 45 | Alias = Alias, 46 | Table = Table, 47 | Component = Component, 48 | }; 49 | } 50 | } 51 | 52 | /// 53 | /// Represents a "from subquery" clause. 54 | /// 55 | public class QueryFromClause : AbstractFrom 56 | { 57 | public Query Query { get; set; } 58 | 59 | public override string Alias 60 | { 61 | get 62 | { 63 | return string.IsNullOrEmpty(_alias) ? Query.QueryAlias : _alias; 64 | } 65 | } 66 | 67 | /// 68 | public override AbstractClause Clone() 69 | { 70 | return new QueryFromClause 71 | { 72 | Engine = Engine, 73 | Alias = Alias, 74 | Query = Query.Clone(), 75 | Component = Component, 76 | }; 77 | } 78 | } 79 | 80 | public class RawFromClause : AbstractFrom 81 | { 82 | public string Expression { get; set; } 83 | public object[] Bindings { set; get; } 84 | 85 | /// 86 | public override AbstractClause Clone() 87 | { 88 | return new RawFromClause 89 | { 90 | Engine = Engine, 91 | Alias = Alias, 92 | Expression = Expression, 93 | Bindings = Bindings, 94 | Component = Component, 95 | }; 96 | } 97 | } 98 | 99 | /// 100 | /// Represents a FROM clause that is an ad-hoc table built with predefined values. 101 | /// 102 | public class AdHocTableFromClause : AbstractFrom 103 | { 104 | public List Columns { get; set; } 105 | public List Values { get; set; } 106 | 107 | public override AbstractClause Clone() 108 | { 109 | return new AdHocTableFromClause 110 | { 111 | Engine = Engine, 112 | Alias = Alias, 113 | Columns = Columns, 114 | Values = Values, 115 | Component = Component, 116 | }; 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /QueryBuilder/Clauses/IncrementClause.cs: -------------------------------------------------------------------------------- 1 | namespace SqlKata 2 | { 3 | public class IncrementClause : InsertClause 4 | { 5 | public string Column { get; set; } 6 | public int Value { get; set; } = 1; 7 | 8 | public override AbstractClause Clone() 9 | { 10 | return new IncrementClause 11 | { 12 | Engine = Engine, 13 | Component = Component, 14 | Column = Column, 15 | Value = Value 16 | }; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /QueryBuilder/Clauses/InsertClause.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SqlKata 4 | { 5 | public abstract class AbstractInsertClause : AbstractClause 6 | { 7 | 8 | } 9 | 10 | public class InsertClause : AbstractInsertClause 11 | { 12 | public List Columns { get; set; } 13 | public List Values { get; set; } 14 | public bool ReturnId { get; set; } = false; 15 | 16 | public override AbstractClause Clone() 17 | { 18 | return new InsertClause 19 | { 20 | Engine = Engine, 21 | Component = Component, 22 | Columns = Columns, 23 | Values = Values, 24 | ReturnId = ReturnId, 25 | }; 26 | } 27 | } 28 | 29 | public class InsertQueryClause : AbstractInsertClause 30 | { 31 | public List Columns { get; set; } 32 | public Query Query { get; set; } 33 | 34 | public override AbstractClause Clone() 35 | { 36 | return new InsertQueryClause 37 | { 38 | Engine = Engine, 39 | Component = Component, 40 | Columns = Columns, 41 | Query = Query.Clone(), 42 | }; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /QueryBuilder/Clauses/JoinClause.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SqlKata 4 | { 5 | public abstract class AbstractJoin : AbstractClause 6 | { 7 | 8 | } 9 | 10 | public class BaseJoin : AbstractJoin 11 | { 12 | public Join Join { get; set; } 13 | 14 | public override AbstractClause Clone() 15 | { 16 | return new BaseJoin 17 | { 18 | Engine = Engine, 19 | Join = Join.Clone(), 20 | Component = Component, 21 | }; 22 | } 23 | } 24 | 25 | public class DeepJoin : AbstractJoin 26 | { 27 | public string Type { get; set; } 28 | public string Expression { get; set; } 29 | public string SourceKeySuffix { get; set; } 30 | public string TargetKey { get; set; } 31 | public Func SourceKeyGenerator { get; set; } 32 | public Func TargetKeyGenerator { get; set; } 33 | 34 | /// 35 | public override AbstractClause Clone() 36 | { 37 | return new DeepJoin 38 | { 39 | Engine = Engine, 40 | Component = Component, 41 | Type = Type, 42 | Expression = Expression, 43 | SourceKeySuffix = SourceKeySuffix, 44 | TargetKey = TargetKey, 45 | SourceKeyGenerator = SourceKeyGenerator, 46 | TargetKeyGenerator = TargetKeyGenerator, 47 | }; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /QueryBuilder/Clauses/LimitClause.cs: -------------------------------------------------------------------------------- 1 | namespace SqlKata 2 | { 3 | public class LimitClause : AbstractClause 4 | { 5 | private int _limit; 6 | 7 | public int Limit 8 | { 9 | get => _limit; 10 | set => _limit = value > 0 ? value : _limit; 11 | } 12 | 13 | public bool HasLimit() 14 | { 15 | return _limit > 0; 16 | } 17 | 18 | public LimitClause Clear() 19 | { 20 | _limit = 0; 21 | return this; 22 | } 23 | 24 | /// 25 | public override AbstractClause Clone() 26 | { 27 | return new LimitClause 28 | { 29 | Engine = Engine, 30 | Limit = Limit, 31 | Component = Component, 32 | }; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /QueryBuilder/Clauses/OffsetClause.cs: -------------------------------------------------------------------------------- 1 | namespace SqlKata 2 | { 3 | public class OffsetClause : AbstractClause 4 | { 5 | private long _offset; 6 | 7 | public long Offset 8 | { 9 | get => _offset; 10 | set => _offset = value > 0 ? value : _offset; 11 | } 12 | 13 | public bool HasOffset() 14 | { 15 | return _offset > 0; 16 | } 17 | 18 | public OffsetClause Clear() 19 | { 20 | _offset = 0; 21 | return this; 22 | } 23 | 24 | /// 25 | public override AbstractClause Clone() 26 | { 27 | return new OffsetClause 28 | { 29 | Engine = Engine, 30 | Offset = Offset, 31 | Component = Component, 32 | }; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /QueryBuilder/Clauses/OrderClause.cs: -------------------------------------------------------------------------------- 1 | namespace SqlKata 2 | { 3 | public abstract class AbstractOrderBy : AbstractClause 4 | { 5 | 6 | } 7 | 8 | public class OrderBy : AbstractOrderBy 9 | { 10 | public string Column { get; set; } 11 | public bool Ascending { get; set; } = true; 12 | 13 | /// 14 | public override AbstractClause Clone() 15 | { 16 | return new OrderBy 17 | { 18 | Engine = Engine, 19 | Component = Component, 20 | Column = Column, 21 | Ascending = Ascending 22 | }; 23 | } 24 | } 25 | 26 | public class RawOrderBy : AbstractOrderBy 27 | { 28 | public string Expression { get; set; } 29 | public object[] Bindings { set; get; } 30 | 31 | /// 32 | public override AbstractClause Clone() 33 | { 34 | return new RawOrderBy 35 | { 36 | Engine = Engine, 37 | Component = Component, 38 | Expression = Expression, 39 | Bindings = Bindings, 40 | }; 41 | } 42 | } 43 | 44 | public class OrderByRandom : AbstractOrderBy 45 | { 46 | /// 47 | public override AbstractClause Clone() 48 | { 49 | return new OrderByRandom 50 | { 51 | Engine = Engine, 52 | }; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /QueryBuilder/ColumnAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SqlKata 4 | { 5 | /// 6 | /// This class is used as metadata on a property to generate different name in the output query. 7 | /// 8 | public class ColumnAttribute : Attribute 9 | { 10 | public string Name { get; private set; } 11 | public ColumnAttribute(string name) 12 | { 13 | if (string.IsNullOrEmpty(name)) 14 | { 15 | throw new ArgumentNullException(nameof(name)); 16 | } 17 | 18 | Name = name; 19 | } 20 | } 21 | 22 | /// 23 | /// This class is used as metadata on a property to determine if it is a primary key 24 | /// 25 | public class KeyAttribute : ColumnAttribute 26 | { 27 | public KeyAttribute([System.Runtime.CompilerServices.CallerMemberName] string name = "") 28 | : base(name) 29 | { 30 | 31 | } 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /QueryBuilder/Compilers/Compiler.Conditions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace SqlKata.Compilers 7 | { 8 | public partial class Compiler 9 | { 10 | protected virtual MethodInfo FindCompilerMethodInfo(Type clauseType, string methodName) 11 | { 12 | return _compileConditionMethodsProvider.GetMethodInfo(clauseType, methodName); 13 | } 14 | 15 | protected virtual string CompileCondition(SqlResult ctx, AbstractCondition clause) 16 | { 17 | var clauseType = clause.GetType(); 18 | 19 | var name = clauseType.Name; 20 | 21 | name = name.Substring(0, name.IndexOf("Condition")); 22 | 23 | var methodName = "Compile" + name + "Condition"; 24 | 25 | var methodInfo = FindCompilerMethodInfo(clauseType, methodName); 26 | 27 | try 28 | { 29 | 30 | var result = methodInfo.Invoke(this, new object[] { 31 | ctx, 32 | clause 33 | }); 34 | 35 | return result as string; 36 | } 37 | catch (Exception ex) 38 | { 39 | throw new Exception($"Failed to invoke '{methodName}'", ex); 40 | } 41 | 42 | } 43 | 44 | protected virtual string CompileConditions(SqlResult ctx, List conditions) 45 | { 46 | var result = new List(); 47 | 48 | for (var i = 0; i < conditions.Count; i++) 49 | { 50 | var compiled = CompileCondition(ctx, conditions[i]); 51 | 52 | if (string.IsNullOrEmpty(compiled)) 53 | { 54 | continue; 55 | } 56 | 57 | var boolOperator = i == 0 ? "" : (conditions[i].IsOr ? "OR " : "AND "); 58 | 59 | result.Add(boolOperator + compiled); 60 | } 61 | 62 | return string.Join(" ", result); 63 | } 64 | 65 | protected virtual string CompileRawCondition(SqlResult ctx, RawCondition x) 66 | { 67 | ctx.Bindings.AddRange(x.Bindings); 68 | return WrapIdentifiers(x.Expression); 69 | } 70 | 71 | protected virtual string CompileQueryCondition(SqlResult ctx, QueryCondition x) where T : BaseQuery 72 | { 73 | var subCtx = CompileSelectQuery(x.Query); 74 | 75 | ctx.Bindings.AddRange(subCtx.Bindings); 76 | 77 | return Wrap(x.Column) + " " + checkOperator(x.Operator) + " (" + subCtx.RawSql + ")"; 78 | } 79 | 80 | protected virtual string CompileSubQueryCondition(SqlResult ctx, SubQueryCondition x) where T : BaseQuery 81 | { 82 | var subCtx = CompileSelectQuery(x.Query); 83 | 84 | ctx.Bindings.AddRange(subCtx.Bindings); 85 | 86 | return "(" + subCtx.RawSql + ") " + checkOperator(x.Operator) + " " + Parameter(ctx, x.Value); 87 | } 88 | 89 | protected virtual string CompileBasicCondition(SqlResult ctx, BasicCondition x) 90 | { 91 | var sql = $"{Wrap(x.Column)} {checkOperator(x.Operator)} {Parameter(ctx, x.Value)}"; 92 | 93 | if (x.IsNot) 94 | { 95 | return $"NOT ({sql})"; 96 | } 97 | 98 | return sql; 99 | } 100 | 101 | protected virtual string CompileBasicStringCondition(SqlResult ctx, BasicStringCondition x) 102 | { 103 | 104 | var column = Wrap(x.Column); 105 | 106 | var value = Resolve(ctx, x.Value) as string; 107 | 108 | if (value == null) 109 | { 110 | throw new ArgumentException("Expecting a non nullable string"); 111 | } 112 | 113 | var method = x.Operator; 114 | 115 | if (new[] { "starts", "ends", "contains", "like" }.Contains(x.Operator)) 116 | { 117 | 118 | method = "LIKE"; 119 | 120 | switch (x.Operator) 121 | { 122 | case "starts": 123 | value = $"{value}%"; 124 | break; 125 | case "ends": 126 | value = $"%{value}"; 127 | break; 128 | case "contains": 129 | value = $"%{value}%"; 130 | break; 131 | } 132 | } 133 | 134 | string sql; 135 | 136 | 137 | if (!x.CaseSensitive) 138 | { 139 | column = CompileLower(column); 140 | value = value.ToLowerInvariant(); 141 | } 142 | 143 | if (x.Value is UnsafeLiteral) 144 | { 145 | sql = $"{column} {checkOperator(method)} {value}"; 146 | } 147 | else 148 | { 149 | sql = $"{column} {checkOperator(method)} {Parameter(ctx, value)}"; 150 | } 151 | 152 | if (!string.IsNullOrEmpty(x.EscapeCharacter)) 153 | { 154 | sql = $"{sql} ESCAPE '{x.EscapeCharacter}'"; 155 | } 156 | 157 | return x.IsNot ? $"NOT ({sql})" : sql; 158 | 159 | } 160 | 161 | protected virtual string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition x) 162 | { 163 | var column = Wrap(x.Column); 164 | var op = checkOperator(x.Operator); 165 | 166 | var sql = $"{x.Part.ToUpperInvariant()}({column}) {op} {Parameter(ctx, x.Value)}"; 167 | 168 | return x.IsNot ? $"NOT ({sql})" : sql; 169 | } 170 | 171 | protected virtual string CompileNestedCondition(SqlResult ctx, NestedCondition x) where Q : BaseQuery 172 | { 173 | if (!(x.Query.HasComponent("where", EngineCode) || x.Query.HasComponent("having", EngineCode))) 174 | { 175 | return null; 176 | } 177 | 178 | var clause = x.Query.HasComponent("where", EngineCode) ? "where" : "having"; 179 | 180 | var clauses = x.Query.GetComponents(clause, EngineCode); 181 | 182 | var sql = CompileConditions(ctx, clauses); 183 | 184 | return x.IsNot ? $"NOT ({sql})" : $"({sql})"; 185 | } 186 | 187 | protected string CompileTwoColumnsCondition(SqlResult ctx, TwoColumnsCondition clause) 188 | { 189 | var op = clause.IsNot ? "NOT " : ""; 190 | return $"{op}{Wrap(clause.First)} {checkOperator(clause.Operator)} {Wrap(clause.Second)}"; 191 | } 192 | 193 | protected virtual string CompileBetweenCondition(SqlResult ctx, BetweenCondition item) 194 | { 195 | var between = item.IsNot ? "NOT BETWEEN" : "BETWEEN"; 196 | var lower = Parameter(ctx, item.Lower); 197 | var higher = Parameter(ctx, item.Higher); 198 | 199 | return Wrap(item.Column) + $" {between} {lower} AND {higher}"; 200 | } 201 | 202 | protected virtual string CompileInCondition(SqlResult ctx, InCondition item) 203 | { 204 | var column = Wrap(item.Column); 205 | 206 | if (!item.Values.Any()) 207 | { 208 | return item.IsNot ? $"1 = 1 /* NOT IN [empty list] */" : "1 = 0 /* IN [empty list] */"; 209 | } 210 | 211 | var inOperator = item.IsNot ? "NOT IN" : "IN"; 212 | 213 | var values = Parameterize(ctx, item.Values); 214 | 215 | return column + $" {inOperator} ({values})"; 216 | } 217 | 218 | protected virtual string CompileInQueryCondition(SqlResult ctx, InQueryCondition item) 219 | { 220 | 221 | var subCtx = CompileSelectQuery(item.Query); 222 | 223 | ctx.Bindings.AddRange(subCtx.Bindings); 224 | 225 | var inOperator = item.IsNot ? "NOT IN" : "IN"; 226 | 227 | return Wrap(item.Column) + $" {inOperator} ({subCtx.RawSql})"; 228 | } 229 | 230 | protected virtual string CompileNullCondition(SqlResult ctx, NullCondition item) 231 | { 232 | var op = item.IsNot ? "IS NOT NULL" : "IS NULL"; 233 | return Wrap(item.Column) + " " + op; 234 | } 235 | 236 | protected virtual string CompileBooleanCondition(SqlResult ctx, BooleanCondition item) 237 | { 238 | var column = Wrap(item.Column); 239 | var value = item.Value ? CompileTrue() : CompileFalse(); 240 | 241 | var op = item.IsNot ? "!=" : "="; 242 | 243 | return $"{column} {op} {value}"; 244 | } 245 | 246 | protected virtual string CompileExistsCondition(SqlResult ctx, ExistsCondition item) 247 | { 248 | var op = item.IsNot ? "NOT EXISTS" : "EXISTS"; 249 | 250 | 251 | // remove unneeded components 252 | var query = item.Query.Clone(); 253 | 254 | if (OmitSelectInsideExists) 255 | { 256 | query.ClearComponent("select").SelectRaw("1"); 257 | } 258 | 259 | var subCtx = CompileSelectQuery(query); 260 | 261 | ctx.Bindings.AddRange(subCtx.Bindings); 262 | 263 | return $"{op} ({subCtx.RawSql})"; 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /QueryBuilder/Compilers/ConditionsCompilerProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace SqlKata.Compilers 7 | { 8 | internal class ConditionsCompilerProvider 9 | { 10 | private readonly Type compilerType; 11 | private readonly Dictionary methodsCache = new Dictionary(); 12 | private readonly object syncRoot = new object(); 13 | 14 | public ConditionsCompilerProvider(Compiler compiler) 15 | { 16 | this.compilerType = compiler.GetType(); 17 | } 18 | 19 | public MethodInfo GetMethodInfo(Type clauseType, string methodName) 20 | { 21 | // The cache key should take the type and the method name into consideration 22 | var cacheKey = methodName + "::" + clauseType.FullName; 23 | 24 | lock (syncRoot) 25 | { 26 | if (methodsCache.ContainsKey(cacheKey)) 27 | { 28 | return methodsCache[cacheKey]; 29 | } 30 | 31 | return methodsCache[cacheKey] = FindMethodInfo(clauseType, methodName); 32 | } 33 | } 34 | 35 | private MethodInfo FindMethodInfo(Type clauseType, string methodName) 36 | { 37 | MethodInfo methodInfo = compilerType 38 | .GetRuntimeMethods() 39 | .FirstOrDefault(x => x.Name == methodName); 40 | 41 | if (methodInfo == null) 42 | { 43 | throw new Exception($"Failed to locate a compiler for '{methodName}'."); 44 | } 45 | 46 | if (clauseType.IsConstructedGenericType && methodInfo.GetGenericArguments().Any()) 47 | { 48 | methodInfo = methodInfo.MakeGenericMethod(clauseType.GenericTypeArguments); 49 | } 50 | 51 | return methodInfo; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /QueryBuilder/Compilers/CteFinder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SqlKata.Compilers 4 | { 5 | public class CteFinder 6 | { 7 | private readonly Query query; 8 | private readonly string engineCode; 9 | private HashSet namesOfPreviousCtes; 10 | private List orderedCteList; 11 | 12 | public CteFinder(Query query, string engineCode) 13 | { 14 | this.query = query; 15 | this.engineCode = engineCode; 16 | } 17 | 18 | public List Find() 19 | { 20 | if (null != orderedCteList) 21 | return orderedCteList; 22 | 23 | namesOfPreviousCtes = new HashSet(); 24 | 25 | orderedCteList = findInternal(query); 26 | 27 | namesOfPreviousCtes.Clear(); 28 | namesOfPreviousCtes = null; 29 | 30 | return orderedCteList; 31 | } 32 | 33 | private List findInternal(Query queryToSearch) 34 | { 35 | var cteList = queryToSearch.GetComponents("cte", engineCode); 36 | 37 | var resultList = new List(); 38 | 39 | foreach (var cte in cteList) 40 | { 41 | if (namesOfPreviousCtes.Contains(cte.Alias)) 42 | continue; 43 | 44 | namesOfPreviousCtes.Add(cte.Alias); 45 | resultList.Add(cte); 46 | 47 | if (cte is QueryFromClause queryFromClause) 48 | { 49 | resultList.InsertRange(0, findInternal(queryFromClause.Query)); 50 | } 51 | } 52 | 53 | return resultList; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /QueryBuilder/Compilers/EngineCodes.cs: -------------------------------------------------------------------------------- 1 | namespace SqlKata.Compilers 2 | { 3 | public static class EngineCodes 4 | { 5 | public const string Firebird = "firebird"; 6 | public const string Generic = "generic"; 7 | public const string MySql = "mysql"; 8 | public const string Oracle = "oracle"; 9 | public const string PostgreSql = "postgres"; 10 | public const string Sqlite = "sqlite"; 11 | public const string SqlServer = "sqlsrv"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /QueryBuilder/Compilers/FirebirdCompiler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace SqlKata.Compilers 6 | { 7 | public class FirebirdCompiler : Compiler 8 | { 9 | public FirebirdCompiler() 10 | { 11 | } 12 | 13 | public override string EngineCode { get; } = EngineCodes.Firebird; 14 | protected override string SingleRowDummyTableName => "RDB$DATABASE"; 15 | 16 | protected override SqlResult CompileInsertQuery(Query query) 17 | { 18 | var ctx = base.CompileInsertQuery(query); 19 | 20 | var inserts = ctx.Query.GetComponents("insert", EngineCode); 21 | 22 | if (inserts.Count > 1) 23 | { 24 | ctx.RawSql = Regex.Replace(ctx.RawSql, @"\)\s+VALUES\s+\(", ") SELECT "); 25 | ctx.RawSql = Regex.Replace(ctx.RawSql, @"\),\s*\(", " FROM RDB$DATABASE UNION ALL SELECT "); 26 | ctx.RawSql = Regex.Replace(ctx.RawSql, @"\)$", " FROM RDB$DATABASE"); 27 | } 28 | 29 | return ctx; 30 | } 31 | 32 | public override string CompileLimit(SqlResult ctx) 33 | { 34 | var limit = ctx.Query.GetLimit(EngineCode); 35 | var offset = ctx.Query.GetOffset(EngineCode); 36 | 37 | if (limit > 0 && offset > 0) 38 | { 39 | ctx.Bindings.Add(offset + 1); 40 | ctx.Bindings.Add(limit + offset); 41 | 42 | return $"ROWS {parameterPlaceholder} TO {parameterPlaceholder}"; 43 | } 44 | 45 | return null; 46 | } 47 | 48 | 49 | protected override string CompileColumns(SqlResult ctx) 50 | { 51 | var compiled = base.CompileColumns(ctx); 52 | 53 | var limit = ctx.Query.GetLimit(EngineCode); 54 | var offset = ctx.Query.GetOffset(EngineCode); 55 | 56 | if (limit > 0 && offset == 0) 57 | { 58 | ctx.Bindings.Insert(0, limit); 59 | 60 | ctx.Query.ClearComponent("limit"); 61 | 62 | return $"SELECT FIRST {parameterPlaceholder}" + compiled.Substring(6); 63 | } 64 | else if (limit == 0 && offset > 0) 65 | { 66 | ctx.Bindings.Insert(0, offset); 67 | 68 | ctx.Query.ClearComponent("offset"); 69 | 70 | return $"SELECT SKIP {parameterPlaceholder}" + compiled.Substring(6); 71 | } 72 | 73 | return compiled; 74 | } 75 | 76 | protected override string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition condition) 77 | { 78 | var column = Wrap(condition.Column); 79 | 80 | string left; 81 | 82 | if (condition.Part == "time") 83 | { 84 | left = $"CAST({column} as TIME)"; 85 | } 86 | else if (condition.Part == "date") 87 | { 88 | left = $"CAST({column} as DATE)"; 89 | } 90 | else 91 | { 92 | left = $"EXTRACT({condition.Part.ToUpperInvariant()} FROM {column})"; 93 | } 94 | 95 | var sql = $"{left} {condition.Operator} {Parameter(ctx, condition.Value)}"; 96 | 97 | if (condition.IsNot) 98 | { 99 | return $"NOT ({sql})"; 100 | } 101 | 102 | return sql; 103 | } 104 | 105 | public override string WrapValue(string value) 106 | { 107 | return base.WrapValue(value).ToUpperInvariant(); 108 | } 109 | 110 | public override string CompileTrue() 111 | { 112 | return "1"; 113 | } 114 | 115 | public override string CompileFalse() 116 | { 117 | return "0"; 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /QueryBuilder/Compilers/MySqlCompiler.cs: -------------------------------------------------------------------------------- 1 | namespace SqlKata.Compilers 2 | { 3 | public class MySqlCompiler : Compiler 4 | { 5 | public MySqlCompiler() 6 | { 7 | OpeningIdentifier = ClosingIdentifier = "`"; 8 | LastId = "SELECT last_insert_id() as Id"; 9 | } 10 | 11 | public override string EngineCode { get; } = EngineCodes.MySql; 12 | 13 | public override string CompileLimit(SqlResult ctx) 14 | { 15 | var limit = ctx.Query.GetLimit(EngineCode); 16 | var offset = ctx.Query.GetOffset(EngineCode); 17 | 18 | 19 | if (offset == 0 && limit == 0) 20 | { 21 | return null; 22 | } 23 | 24 | if (offset == 0) 25 | { 26 | ctx.Bindings.Add(limit); 27 | return $"LIMIT {parameterPlaceholder}"; 28 | } 29 | 30 | if (limit == 0) 31 | { 32 | 33 | // MySql will not accept offset without limit, so we will put a large number 34 | // to avoid this error. 35 | 36 | ctx.Bindings.Add(offset); 37 | return $"LIMIT 18446744073709551615 OFFSET {parameterPlaceholder}"; 38 | } 39 | 40 | // We have both values 41 | 42 | ctx.Bindings.Add(limit); 43 | ctx.Bindings.Add(offset); 44 | 45 | return $"LIMIT {parameterPlaceholder} OFFSET {parameterPlaceholder}"; 46 | 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /QueryBuilder/Compilers/OracleCompiler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | 7 | namespace SqlKata.Compilers 8 | { 9 | public class OracleCompiler : Compiler 10 | { 11 | public OracleCompiler() 12 | { 13 | ColumnAsKeyword = ""; 14 | TableAsKeyword = ""; 15 | parameterPrefix = ":p"; 16 | MultiInsertStartClause = "INSERT ALL INTO"; 17 | } 18 | 19 | public override string EngineCode { get; } = EngineCodes.Oracle; 20 | public bool UseLegacyPagination { get; set; } = false; 21 | protected override string SingleRowDummyTableName => "DUAL"; 22 | 23 | protected override SqlResult CompileSelectQuery(Query query) 24 | { 25 | if (!UseLegacyPagination) 26 | { 27 | return base.CompileSelectQuery(query); 28 | } 29 | 30 | var result = base.CompileSelectQuery(query); 31 | 32 | ApplyLegacyLimit(result); 33 | 34 | return result; 35 | } 36 | 37 | public override string CompileLimit(SqlResult ctx) 38 | { 39 | if (UseLegacyPagination) 40 | { 41 | // in pre-12c versions of Oracle, limit is handled by ROWNUM techniques 42 | return null; 43 | } 44 | 45 | var limit = ctx.Query.GetLimit(EngineCode); 46 | var offset = ctx.Query.GetOffset(EngineCode); 47 | 48 | if (limit == 0 && offset == 0) 49 | { 50 | return null; 51 | } 52 | 53 | var safeOrder = ""; 54 | 55 | if (!ctx.Query.HasComponent("order")) 56 | { 57 | safeOrder = "ORDER BY (SELECT 0 FROM DUAL) "; 58 | } 59 | 60 | if (limit == 0) 61 | { 62 | ctx.Bindings.Add(offset); 63 | return $"{safeOrder}OFFSET {parameterPlaceholder} ROWS"; 64 | } 65 | 66 | ctx.Bindings.Add(offset); 67 | ctx.Bindings.Add(limit); 68 | 69 | return $"{safeOrder}OFFSET {parameterPlaceholder} ROWS FETCH NEXT {parameterPlaceholder} ROWS ONLY"; 70 | } 71 | 72 | internal void ApplyLegacyLimit(SqlResult ctx) 73 | { 74 | var limit = ctx.Query.GetLimit(EngineCode); 75 | var offset = ctx.Query.GetOffset(EngineCode); 76 | 77 | if (limit == 0 && offset == 0) 78 | { 79 | return; 80 | } 81 | 82 | string newSql; 83 | if (limit == 0) 84 | { 85 | newSql = $"SELECT * FROM (SELECT \"results_wrapper\".*, ROWNUM \"row_num\" FROM ({ctx.RawSql}) \"results_wrapper\") WHERE \"row_num\" > {parameterPlaceholder}"; 86 | ctx.Bindings.Add(offset); 87 | } 88 | else if (offset == 0) 89 | { 90 | newSql = $"SELECT * FROM ({ctx.RawSql}) WHERE ROWNUM <= {parameterPlaceholder}"; 91 | ctx.Bindings.Add(limit); 92 | } 93 | else 94 | { 95 | newSql = $"SELECT * FROM (SELECT \"results_wrapper\".*, ROWNUM \"row_num\" FROM ({ctx.RawSql}) \"results_wrapper\" WHERE ROWNUM <= {parameterPlaceholder}) WHERE \"row_num\" > {parameterPlaceholder}"; 96 | ctx.Bindings.Add(limit + offset); 97 | ctx.Bindings.Add(offset); 98 | } 99 | 100 | ctx.RawSql = newSql; 101 | } 102 | 103 | protected override string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition condition) 104 | { 105 | 106 | var column = Wrap(condition.Column); 107 | var value = Parameter(ctx, condition.Value); 108 | 109 | var sql = ""; 110 | var valueFormat = ""; 111 | 112 | var isDateTime = (condition.Value is DateTime dt); 113 | 114 | switch (condition.Part) 115 | { 116 | case "date": // assume YY-MM-DD format 117 | if (isDateTime) 118 | valueFormat = $"{value}"; 119 | else 120 | valueFormat = $"TO_DATE({value}, 'YY-MM-DD')"; 121 | sql = $"TO_CHAR({column}, 'YY-MM-DD') {condition.Operator} TO_CHAR({valueFormat}, 'YY-MM-DD')"; 122 | break; 123 | case "time": 124 | if (isDateTime) 125 | valueFormat = $"{value}"; 126 | else 127 | { 128 | // assume HH:MM format 129 | if (condition.Value.ToString().Split(':').Count() == 2) 130 | valueFormat = $"TO_DATE({value}, 'HH24:MI')"; 131 | else // assume HH:MM:SS format 132 | valueFormat = $"TO_DATE({value}, 'HH24:MI:SS')"; 133 | } 134 | sql = $"TO_CHAR({column}, 'HH24:MI:SS') {condition.Operator} TO_CHAR({valueFormat}, 'HH24:MI:SS')"; 135 | break; 136 | case "year": 137 | case "month": 138 | case "day": 139 | case "hour": 140 | case "minute": 141 | case "second": 142 | sql = $"EXTRACT({condition.Part.ToUpperInvariant()} FROM {column}) {condition.Operator} {value}"; 143 | break; 144 | default: 145 | sql = $"{column} {condition.Operator} {value}"; 146 | break; 147 | } 148 | 149 | if (condition.IsNot) 150 | { 151 | return $"NOT ({sql})"; 152 | } 153 | 154 | return sql; 155 | 156 | } 157 | 158 | protected override SqlResult CompileRemainingInsertClauses( 159 | SqlResult ctx, string table, IEnumerable inserts) 160 | { 161 | foreach (var insert in inserts.Skip(1)) 162 | { 163 | string columns = GetInsertColumnsList(insert.Columns); 164 | string values = string.Join(", ", Parameterize(ctx, insert.Values)); 165 | 166 | string intoFormat = " INTO {0}{1} VALUES ({2})"; 167 | var nextInsert = string.Format(intoFormat, table, columns, values); 168 | 169 | ctx.RawSql += nextInsert; 170 | } 171 | 172 | ctx.RawSql += " SELECT 1 FROM DUAL"; 173 | return ctx; 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /QueryBuilder/Compilers/PostgresCompiler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace SqlKata.Compilers 5 | { 6 | public class PostgresCompiler : Compiler 7 | { 8 | public PostgresCompiler() 9 | { 10 | LastId = "SELECT lastval() AS id"; 11 | } 12 | 13 | public override string EngineCode { get; } = EngineCodes.PostgreSql; 14 | public override bool SupportsFilterClause { get; set; } = true; 15 | 16 | 17 | protected override string CompileBasicStringCondition(SqlResult ctx, BasicStringCondition x) 18 | { 19 | 20 | var column = Wrap(x.Column); 21 | 22 | var value = Resolve(ctx, x.Value) as string; 23 | 24 | if (value == null) 25 | { 26 | throw new ArgumentException("Expecting a non nullable string"); 27 | } 28 | 29 | var method = x.Operator; 30 | 31 | if (new[] { "starts", "ends", "contains", "like", "ilike" }.Contains(x.Operator)) 32 | { 33 | method = x.CaseSensitive ? "LIKE" : "ILIKE"; 34 | 35 | switch (x.Operator) 36 | { 37 | case "starts": 38 | value = $"{value}%"; 39 | break; 40 | case "ends": 41 | value = $"%{value}"; 42 | break; 43 | case "contains": 44 | value = $"%{value}%"; 45 | break; 46 | } 47 | } 48 | 49 | string sql; 50 | 51 | if (x.Value is UnsafeLiteral) 52 | { 53 | sql = $"{column} {checkOperator(method)} {value}"; 54 | } 55 | else 56 | { 57 | sql = $"{column} {checkOperator(method)} {Parameter(ctx, value)}"; 58 | } 59 | 60 | if (!string.IsNullOrEmpty(x.EscapeCharacter)) 61 | { 62 | sql = $"{sql} ESCAPE '{x.EscapeCharacter}'"; 63 | } 64 | 65 | return x.IsNot ? $"NOT ({sql})" : sql; 66 | 67 | } 68 | 69 | 70 | protected override string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition condition) 71 | { 72 | var column = Wrap(condition.Column); 73 | 74 | string left; 75 | 76 | if (condition.Part == "time") 77 | { 78 | left = $"{column}::time"; 79 | } 80 | else if (condition.Part == "date") 81 | { 82 | left = $"{column}::date"; 83 | } 84 | else 85 | { 86 | left = $"DATE_PART('{condition.Part.ToUpperInvariant()}', {column})"; 87 | } 88 | 89 | var sql = $"{left} {condition.Operator} {Parameter(ctx, condition.Value)}"; 90 | 91 | if (condition.IsNot) 92 | { 93 | return $"NOT ({sql})"; 94 | } 95 | 96 | return sql; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /QueryBuilder/Compilers/SqlServerCompiler.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace SqlKata.Compilers 4 | { 5 | public class SqlServerCompiler : Compiler 6 | { 7 | public SqlServerCompiler() 8 | { 9 | OpeningIdentifier = "["; 10 | ClosingIdentifier = "]"; 11 | LastId = "SELECT scope_identity() as Id"; 12 | } 13 | 14 | public override string EngineCode { get; } = EngineCodes.SqlServer; 15 | public bool UseLegacyPagination { get; set; } = false; 16 | 17 | protected override SqlResult CompileSelectQuery(Query query) 18 | { 19 | if (!UseLegacyPagination || !query.HasOffset(EngineCode)) 20 | { 21 | return base.CompileSelectQuery(query); 22 | } 23 | 24 | query = query.Clone(); 25 | 26 | var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter) 27 | { 28 | Query = query, 29 | }; 30 | 31 | var limit = query.GetLimit(EngineCode); 32 | var offset = query.GetOffset(EngineCode); 33 | 34 | 35 | if (!query.HasComponent("select")) 36 | { 37 | query.Select("*"); 38 | } 39 | 40 | var order = CompileOrders(ctx) ?? "ORDER BY (SELECT 0)"; 41 | 42 | query.SelectRaw($"ROW_NUMBER() OVER ({order}) AS [row_num]", ctx.Bindings.ToArray()); 43 | 44 | query.ClearComponent("order"); 45 | 46 | 47 | var result = base.CompileSelectQuery(query); 48 | 49 | if (limit == 0) 50 | { 51 | result.RawSql = $"SELECT * FROM ({result.RawSql}) AS [results_wrapper] WHERE [row_num] >= {parameterPlaceholder}"; 52 | result.Bindings.Add(offset + 1); 53 | } 54 | else 55 | { 56 | result.RawSql = $"SELECT * FROM ({result.RawSql}) AS [results_wrapper] WHERE [row_num] BETWEEN {parameterPlaceholder} AND {parameterPlaceholder}"; 57 | result.Bindings.Add(offset + 1); 58 | result.Bindings.Add(limit + offset); 59 | } 60 | 61 | return result; 62 | } 63 | 64 | protected override string CompileColumns(SqlResult ctx) 65 | { 66 | var compiled = base.CompileColumns(ctx); 67 | 68 | if (!UseLegacyPagination) 69 | { 70 | return compiled; 71 | } 72 | 73 | // If there is a limit on the query, but not an offset, we will add the top 74 | // clause to the query, which serves as a "limit" type clause within the 75 | // SQL Server system similar to the limit keywords available in MySQL. 76 | var limit = ctx.Query.GetLimit(EngineCode); 77 | var offset = ctx.Query.GetOffset(EngineCode); 78 | 79 | if (limit > 0 && offset == 0) 80 | { 81 | // top bindings should be inserted first 82 | ctx.Bindings.Insert(0, limit); 83 | 84 | ctx.Query.ClearComponent("limit"); 85 | 86 | // handle distinct 87 | if (compiled.IndexOf("SELECT DISTINCT") == 0) 88 | { 89 | return $"SELECT DISTINCT TOP ({parameterPlaceholder}){compiled.Substring(15)}"; 90 | } 91 | 92 | return $"SELECT TOP ({parameterPlaceholder}){compiled.Substring(6)}"; 93 | } 94 | 95 | return compiled; 96 | } 97 | 98 | public override string CompileLimit(SqlResult ctx) 99 | { 100 | if (UseLegacyPagination) 101 | { 102 | // in legacy versions of Sql Server, limit is handled by TOP 103 | // and ROW_NUMBER techniques 104 | return null; 105 | } 106 | 107 | var limit = ctx.Query.GetLimit(EngineCode); 108 | var offset = ctx.Query.GetOffset(EngineCode); 109 | 110 | if (limit == 0 && offset == 0) 111 | { 112 | return null; 113 | } 114 | 115 | var safeOrder = ""; 116 | if (!ctx.Query.HasComponent("order")) 117 | { 118 | safeOrder = "ORDER BY (SELECT 0) "; 119 | } 120 | 121 | if (limit == 0) 122 | { 123 | ctx.Bindings.Add(offset); 124 | return $"{safeOrder}OFFSET {parameterPlaceholder} ROWS"; 125 | } 126 | 127 | ctx.Bindings.Add(offset); 128 | ctx.Bindings.Add(limit); 129 | 130 | return $"{safeOrder}OFFSET {parameterPlaceholder} ROWS FETCH NEXT {parameterPlaceholder} ROWS ONLY"; 131 | } 132 | 133 | public override string CompileRandom(string seed) 134 | { 135 | return "NEWID()"; 136 | } 137 | 138 | public override string CompileTrue() 139 | { 140 | return "cast(1 as bit)"; 141 | } 142 | 143 | public override string CompileFalse() 144 | { 145 | return "cast(0 as bit)"; 146 | } 147 | 148 | protected override string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition condition) 149 | { 150 | var column = Wrap(condition.Column); 151 | var part = condition.Part.ToUpperInvariant(); 152 | 153 | string left; 154 | 155 | if (part == "TIME" || part == "DATE") 156 | { 157 | left = $"CAST({column} AS {part.ToUpperInvariant()})"; 158 | } 159 | else 160 | { 161 | left = $"DATEPART({part.ToUpperInvariant()}, {column})"; 162 | } 163 | 164 | var sql = $"{left} {condition.Operator} {Parameter(ctx, condition.Value)}"; 165 | 166 | if (condition.IsNot) 167 | { 168 | return $"NOT ({sql})"; 169 | } 170 | 171 | return sql; 172 | } 173 | 174 | protected override SqlResult CompileAdHocQuery(AdHocTableFromClause adHoc) 175 | { 176 | var ctx = new SqlResult(parameterPlaceholder, EscapeCharacter); 177 | 178 | var colNames = string.Join(", ", adHoc.Columns.Select(Wrap)); 179 | 180 | var valueRow = string.Join(", ", Enumerable.Repeat(parameterPlaceholder, adHoc.Columns.Count)); 181 | var valueRows = string.Join(", ", Enumerable.Repeat($"({valueRow})", adHoc.Values.Count / adHoc.Columns.Count)); 182 | var sql = $"SELECT {colNames} FROM (VALUES {valueRows}) AS tbl ({colNames})"; 183 | 184 | ctx.RawSql = sql; 185 | ctx.Bindings = adHoc.Values; 186 | 187 | return ctx; 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /QueryBuilder/Compilers/SqliteCompiler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SqlKata.Compilers 4 | { 5 | public class SqliteCompiler : Compiler 6 | { 7 | public override string EngineCode { get; } = EngineCodes.Sqlite; 8 | protected override string OpeningIdentifier { get; set; } = "\""; 9 | protected override string ClosingIdentifier { get; set; } = "\""; 10 | protected override string LastId { get; set; } = "select last_insert_rowid() as id"; 11 | public override bool SupportsFilterClause { get; set; } = true; 12 | 13 | public override string CompileTrue() 14 | { 15 | return "1"; 16 | } 17 | 18 | public override string CompileFalse() 19 | { 20 | return "0"; 21 | } 22 | 23 | public override string CompileLimit(SqlResult ctx) 24 | { 25 | var limit = ctx.Query.GetLimit(EngineCode); 26 | var offset = ctx.Query.GetOffset(EngineCode); 27 | 28 | if (limit == 0 && offset > 0) 29 | { 30 | ctx.Bindings.Add(offset); 31 | return $"LIMIT -1 OFFSET {parameterPlaceholder}"; 32 | } 33 | 34 | return base.CompileLimit(ctx); 35 | } 36 | 37 | protected override string CompileBasicDateCondition(SqlResult ctx, BasicDateCondition condition) 38 | { 39 | var column = Wrap(condition.Column); 40 | var value = Parameter(ctx, condition.Value); 41 | 42 | var formatMap = new Dictionary { 43 | { "date", "%Y-%m-%d" }, 44 | { "time", "%H:%M:%S" }, 45 | { "year", "%Y" }, 46 | { "month", "%m" }, 47 | { "day", "%d" }, 48 | { "hour", "%H" }, 49 | { "minute", "%M" }, 50 | }; 51 | 52 | if (!formatMap.ContainsKey(condition.Part)) 53 | { 54 | return $"{column} {condition.Operator} {value}"; 55 | } 56 | 57 | var sql = $"strftime('{formatMap[condition.Part]}', {column}) {condition.Operator} cast({value} as text)"; 58 | 59 | if (condition.IsNot) 60 | { 61 | return $"NOT ({sql})"; 62 | } 63 | 64 | return sql; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /QueryBuilder/Expressions.cs: -------------------------------------------------------------------------------- 1 | namespace SqlKata 2 | { 3 | public static class Expressions 4 | { 5 | /// 6 | /// Instruct the compiler to resolve the value from the predefined variables 7 | /// In the current query or parents queries. 8 | /// 9 | /// 10 | /// 11 | public static Variable Variable(string name) 12 | { 13 | return new Variable(name); 14 | } 15 | 16 | /// 17 | /// Instruct the compiler to treat this as a literal. 18 | /// WARNING: don't pass user data directly to this method. 19 | /// 20 | /// 21 | /// if true it will esacpe single quotes 22 | /// 23 | public static UnsafeLiteral UnsafeLiteral(string value, bool replaceQuotes = true) 24 | { 25 | return new UnsafeLiteral(value, replaceQuotes); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /QueryBuilder/Extensions/QueryForExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using SqlKata.Compilers; 3 | 4 | namespace SqlKata.Extensions 5 | { 6 | public static class QueryForExtensions 7 | { 8 | public static Query ForFirebird(this Query src, Func fn) 9 | { 10 | return src.For(EngineCodes.Firebird, fn); 11 | } 12 | 13 | public static Query ForMySql(this Query src, Func fn) 14 | { 15 | return src.For(EngineCodes.MySql, fn); 16 | } 17 | 18 | public static Query ForOracle(this Query src, Func fn) 19 | { 20 | return src.For(EngineCodes.Oracle, fn); 21 | } 22 | 23 | public static Query ForPostgreSql(this Query src, Func fn) 24 | { 25 | return src.For(EngineCodes.PostgreSql, fn); 26 | } 27 | 28 | public static Query ForSqlite(this Query src, Func fn) 29 | { 30 | return src.For(EngineCodes.Sqlite, fn); 31 | } 32 | 33 | public static Query ForSqlServer(this Query src, Func fn) 34 | { 35 | return src.For(EngineCodes.SqlServer, fn); 36 | } 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /QueryBuilder/Helper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | 8 | namespace SqlKata 9 | { 10 | public static class Helper 11 | { 12 | public static bool IsArray(object value) 13 | { 14 | if (value is string) 15 | { 16 | return false; 17 | } 18 | 19 | if (value is byte[]) 20 | { 21 | return false; 22 | } 23 | 24 | return value is IEnumerable; 25 | } 26 | 27 | /// 28 | /// Flat IEnumerable one level down 29 | /// 30 | /// 31 | /// 32 | public static IEnumerable Flatten(IEnumerable array) 33 | { 34 | foreach (var item in array) 35 | { 36 | if (IsArray(item)) 37 | { 38 | foreach (var sub in (item as IEnumerable)) 39 | { 40 | yield return sub; 41 | } 42 | } 43 | else 44 | { 45 | yield return item; 46 | } 47 | 48 | } 49 | } 50 | 51 | public static IEnumerable FlattenDeep(IEnumerable array) 52 | { 53 | return array.SelectMany(o => IsArray(o) ? FlattenDeep(o as IEnumerable) : new[] { o }); 54 | } 55 | 56 | public static IEnumerable AllIndexesOf(string str, string value) 57 | { 58 | if (string.IsNullOrEmpty(value)) 59 | { 60 | yield break; 61 | } 62 | 63 | var index = 0; 64 | 65 | do 66 | { 67 | index = str.IndexOf(value, index, StringComparison.Ordinal); 68 | 69 | if (index == -1) 70 | { 71 | yield break; 72 | } 73 | 74 | yield return index; 75 | 76 | } while ((index += value.Length) < str.Length); 77 | } 78 | 79 | public static string ReplaceAll(string subject, string match, string escapeCharacter, Func callback) 80 | { 81 | if (string.IsNullOrWhiteSpace(subject) || !subject.Contains(match)) 82 | { 83 | return subject; 84 | } 85 | 86 | var splitted = Regex.Split(subject, $@"(? callback(index) + item) 90 | .Aggregate(new StringBuilder(splitted.First()), (prev, right) => prev.Append(right)) 91 | .ToString(); 92 | } 93 | 94 | public static string RemoveEscapeCharacter(string subject, string match, string escapeCharacter) 95 | { 96 | var escapedRegex = new Regex($@"{Regex.Escape(escapeCharacter)}{Regex.Escape(match)}"); 97 | return escapedRegex.Replace(subject, match); 98 | } 99 | 100 | public static string JoinArray(string glue, IEnumerable array) 101 | { 102 | var result = new List(); 103 | 104 | foreach (var item in array) 105 | { 106 | result.Add(item.ToString()); 107 | } 108 | 109 | return string.Join(glue, result); 110 | } 111 | 112 | public static string ExpandParameters(string sql, string placeholder, string escapeCharacter, object[] bindings) 113 | { 114 | return ReplaceAll(sql, placeholder, escapeCharacter ,i => 115 | { 116 | var parameter = bindings[i]; 117 | 118 | if (IsArray(parameter)) 119 | { 120 | var count = EnumerableCount(parameter as IEnumerable); 121 | return string.Join(",", placeholder.Repeat(count)); 122 | } 123 | 124 | return placeholder.ToString(); 125 | }); 126 | } 127 | 128 | public static int EnumerableCount(IEnumerable obj) 129 | { 130 | int count = 0; 131 | 132 | foreach (var item in obj) 133 | { 134 | count++; 135 | } 136 | 137 | return count; 138 | } 139 | 140 | public static List ExpandExpression(string expression) 141 | { 142 | var regex = @"^(?:\w+\.){1,2}{([^}]*)}"; 143 | var match = Regex.Match(expression, regex, RegexOptions.Multiline); 144 | 145 | if (!match.Success) 146 | { 147 | // we did not found a match return the string as is. 148 | return new List { expression }; 149 | } 150 | 151 | var table = expression.Substring(0, expression.IndexOf(".{")); 152 | 153 | var captures = match.Groups[1].Value; 154 | 155 | var cols = Regex.Split(captures, @"\s*,\s*", RegexOptions.Multiline) 156 | .Select(x => $"{table}.{x.Trim()}") 157 | .ToList(); 158 | 159 | return cols; 160 | } 161 | 162 | public static IEnumerable Repeat(this string str, int count) 163 | { 164 | return Enumerable.Repeat(str, count); 165 | } 166 | 167 | public static string ReplaceIdentifierUnlessEscaped(this string input, string escapeCharacter, string identifier, string newIdentifier) 168 | { 169 | //Replace standard, non-escaped identifiers first 170 | var nonEscapedRegex = new Regex($@"(? 6 | /// This class is used as metadata to ignore a property on insert and update queries 7 | /// 8 | /// 9 | /// 10 | /// public class Person 11 | /// { 12 | /// public string Name {get ;set;} 13 | /// 14 | /// [Ignore] 15 | /// public string PhoneNumber {get ;set;} 16 | /// 17 | /// } 18 | /// 19 | /// new Query("Table").Insert(new Person { Name = "User", PhoneNumber = "70123456" }) 20 | /// 21 | /// output: INSERT INTO [Table] ([Name]) VALUES('User') 22 | /// 23 | /// 24 | public class IgnoreAttribute : Attribute 25 | { 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /QueryBuilder/Include.cs: -------------------------------------------------------------------------------- 1 | namespace SqlKata 2 | { 3 | public class Include 4 | { 5 | public string Name { get; set; } 6 | public Query Query { get; set; } 7 | public string ForeignKey { get; set; } 8 | public string LocalKey { get; set; } 9 | public bool IsMany { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /QueryBuilder/Join.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SqlKata 4 | { 5 | public class Join : BaseQuery 6 | { 7 | protected string _type = "inner join"; 8 | 9 | public string Type 10 | { 11 | get 12 | { 13 | return _type; 14 | } 15 | set 16 | { 17 | _type = value.ToUpperInvariant(); 18 | } 19 | } 20 | 21 | public Join() : base() 22 | { 23 | } 24 | 25 | public override Join Clone() 26 | { 27 | var clone = base.Clone(); 28 | clone._type = _type; 29 | return clone; 30 | } 31 | 32 | public Join AsType(string type) 33 | { 34 | Type = type; 35 | return this; 36 | } 37 | 38 | /// 39 | /// Alias for "from" operator. 40 | /// Since "from" does not sound well with join clauses 41 | /// 42 | /// 43 | /// 44 | public Join JoinWith(string table) => From(table); 45 | public Join JoinWith(Query query) => From(query); 46 | public Join JoinWith(Func callback) => From(callback); 47 | 48 | public Join AsInner() => AsType("inner join"); 49 | public Join AsOuter() => AsType("outer join"); 50 | public Join AsLeft() => AsType("left join"); 51 | public Join AsRight() => AsType("right join"); 52 | public Join AsCross() => AsType("cross join"); 53 | 54 | public Join On(string first, string second, string op = "=") 55 | { 56 | return AddComponent("where", new TwoColumnsCondition 57 | { 58 | First = first, 59 | Second = second, 60 | Operator = op, 61 | IsOr = GetOr(), 62 | IsNot = GetNot() 63 | }); 64 | 65 | } 66 | 67 | public Join OrOn(string first, string second, string op = "=") 68 | { 69 | return Or().On(first, second, op); 70 | } 71 | 72 | public override Join NewQuery() 73 | { 74 | return new Join(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /QueryBuilder/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("QueryBuilder.Tests")] 4 | -------------------------------------------------------------------------------- /QueryBuilder/Query.Aggregate.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace SqlKata 5 | { 6 | public partial class Query 7 | { 8 | public Query AsAggregate(string type, string[] columns = null) 9 | { 10 | 11 | Method = "aggregate"; 12 | 13 | this.ClearComponent("aggregate") 14 | .AddComponent("aggregate", new AggregateClause 15 | { 16 | Type = type, 17 | Columns = columns?.ToList() ?? new List(), 18 | }); 19 | 20 | return this; 21 | } 22 | 23 | public Query AsCount(string[] columns = null) 24 | { 25 | var cols = columns?.ToList() ?? new List { }; 26 | 27 | if (!cols.Any()) 28 | { 29 | cols.Add("*"); 30 | } 31 | 32 | return AsAggregate("count", cols.ToArray()); 33 | } 34 | 35 | public Query AsAvg(string column) 36 | { 37 | return AsAggregate("avg", new string[] { column }); 38 | } 39 | public Query AsAverage(string column) 40 | { 41 | return AsAvg(column); 42 | } 43 | 44 | public Query AsSum(string column) 45 | { 46 | return AsAggregate("sum", new[] { column }); 47 | } 48 | 49 | public Query AsMax(string column) 50 | { 51 | return AsAggregate("max", new[] { column }); 52 | } 53 | 54 | public Query AsMin(string column) 55 | { 56 | return AsAggregate("min", new[] { column }); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /QueryBuilder/Query.Combine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace SqlKata 5 | { 6 | public partial class Query 7 | { 8 | 9 | public Query Combine(string operation, bool all, Query query) 10 | { 11 | if (this.Method != "select" || query.Method != "select") 12 | { 13 | throw new InvalidOperationException("Only select queries can be combined."); 14 | } 15 | 16 | return AddComponent("combine", new Combine 17 | { 18 | Query = query, 19 | Operation = operation, 20 | All = all, 21 | }); 22 | } 23 | 24 | public Query CombineRaw(string sql, params object[] bindings) 25 | { 26 | if (this.Method != "select") 27 | { 28 | throw new InvalidOperationException("Only select queries can be combined."); 29 | } 30 | 31 | return AddComponent("combine", new RawCombine 32 | { 33 | Expression = sql, 34 | Bindings = bindings, 35 | }); 36 | } 37 | 38 | public Query Union(Query query, bool all = false) 39 | { 40 | return Combine("union", all, query); 41 | } 42 | 43 | public Query UnionAll(Query query) 44 | { 45 | return Union(query, true); 46 | } 47 | 48 | public Query Union(Func callback, bool all = false) 49 | { 50 | var query = callback.Invoke(new Query()); 51 | return Union(query, all); 52 | } 53 | 54 | public Query UnionAll(Func callback) 55 | { 56 | return Union(callback, true); 57 | } 58 | 59 | public Query UnionRaw(string sql, params object[] bindings) => CombineRaw(sql, bindings); 60 | 61 | public Query Except(Query query, bool all = false) 62 | { 63 | return Combine("except", all, query); 64 | } 65 | 66 | public Query ExceptAll(Query query) 67 | { 68 | return Except(query, true); 69 | } 70 | 71 | public Query Except(Func callback, bool all = false) 72 | { 73 | var query = callback.Invoke(new Query()); 74 | return Except(query, all); 75 | } 76 | 77 | public Query ExceptAll(Func callback) 78 | { 79 | return Except(callback, true); 80 | } 81 | public Query ExceptRaw(string sql, params object[] bindings) => CombineRaw(sql, bindings); 82 | 83 | public Query Intersect(Query query, bool all = false) 84 | { 85 | return Combine("intersect", all, query); 86 | } 87 | 88 | public Query IntersectAll(Query query) 89 | { 90 | return Intersect(query, true); 91 | } 92 | 93 | public Query Intersect(Func callback, bool all = false) 94 | { 95 | var query = callback.Invoke(new Query()); 96 | return Intersect(query, all); 97 | } 98 | 99 | public Query IntersectAll(Func callback) 100 | { 101 | return Intersect(callback, true); 102 | } 103 | public Query IntersectRaw(string sql, params object[] bindings) => CombineRaw(sql, bindings); 104 | 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /QueryBuilder/Query.Delete.cs: -------------------------------------------------------------------------------- 1 | namespace SqlKata 2 | { 3 | public partial class Query 4 | { 5 | public Query AsDelete() 6 | { 7 | Method = "delete"; 8 | return this; 9 | } 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /QueryBuilder/Query.Insert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace SqlKata 7 | { 8 | public partial class Query 9 | { 10 | public Query AsInsert(object data, bool returnId = false) 11 | { 12 | var propertiesKeyValues = BuildKeyValuePairsFromObject(data); 13 | 14 | return AsInsert(propertiesKeyValues, returnId); 15 | } 16 | 17 | public Query AsInsert(IEnumerable columns, IEnumerable values) 18 | { 19 | var columnsList = columns?.ToList(); 20 | var valuesList = values?.ToList(); 21 | 22 | if ((columnsList?.Count ?? 0) == 0 || (valuesList?.Count ?? 0) == 0) 23 | { 24 | throw new InvalidOperationException($"{nameof(columns)} and {nameof(values)} cannot be null or empty"); 25 | } 26 | 27 | if (columnsList.Count != valuesList.Count) 28 | { 29 | throw new InvalidOperationException($"{nameof(columns)} and {nameof(values)} cannot be null or empty"); 30 | } 31 | 32 | Method = "insert"; 33 | 34 | ClearComponent("insert").AddComponent("insert", new InsertClause 35 | { 36 | Columns = columnsList, 37 | Values = valuesList 38 | }); 39 | 40 | return this; 41 | } 42 | 43 | public Query AsInsert(IEnumerable> values, bool returnId = false) 44 | { 45 | if (values == null || values.Any() == false) 46 | { 47 | throw new InvalidOperationException($"{values} argument cannot be null or empty"); 48 | } 49 | 50 | Method = "insert"; 51 | 52 | ClearComponent("insert").AddComponent("insert", new InsertClause 53 | { 54 | Columns = values.Select(x => x.Key).ToList(), 55 | Values = values.Select(x => x.Value).ToList(), 56 | ReturnId = returnId, 57 | }); 58 | 59 | return this; 60 | } 61 | 62 | /// 63 | /// Produces insert multi records 64 | /// 65 | /// 66 | /// 67 | /// 68 | public Query AsInsert(IEnumerable columns, IEnumerable> rowsValues) 69 | { 70 | var columnsList = columns?.ToList(); 71 | var valuesCollectionList = rowsValues?.ToList(); 72 | 73 | if ((columnsList?.Count ?? 0) == 0 || (valuesCollectionList?.Count ?? 0) == 0) 74 | { 75 | throw new InvalidOperationException($"{nameof(columns)} and {nameof(rowsValues)} cannot be null or empty"); 76 | } 77 | 78 | Method = "insert"; 79 | 80 | ClearComponent("insert"); 81 | 82 | foreach (var values in valuesCollectionList) 83 | { 84 | var valuesList = values.ToList(); 85 | if (columnsList.Count != valuesList.Count) 86 | { 87 | throw new InvalidOperationException($"{nameof(columns)} count should be equal to each {nameof(rowsValues)} entry count"); 88 | } 89 | 90 | AddComponent("insert", new InsertClause 91 | { 92 | Columns = columnsList, 93 | Values = valuesList 94 | }); 95 | } 96 | 97 | return this; 98 | } 99 | 100 | /// 101 | /// Produces insert from subquery 102 | /// 103 | /// 104 | /// 105 | /// 106 | public Query AsInsert(IEnumerable columns, Query query) 107 | { 108 | Method = "insert"; 109 | 110 | ClearComponent("insert").AddComponent("insert", new InsertQueryClause 111 | { 112 | Columns = columns.ToList(), 113 | Query = query.Clone(), 114 | }); 115 | 116 | return this; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /QueryBuilder/Query.Join.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SqlKata 4 | { 5 | public partial class Query 6 | { 7 | 8 | private Query Join(Func callback) 9 | { 10 | var join = callback.Invoke(new Join().AsInner()); 11 | 12 | return AddComponent("join", new BaseJoin 13 | { 14 | Join = join 15 | }); 16 | } 17 | 18 | public Query Join( 19 | string table, 20 | string first, 21 | string second, 22 | string op = "=", 23 | string type = "inner join" 24 | ) 25 | { 26 | return Join(j => j.JoinWith(table).WhereColumns(first, op, second).AsType(type)); 27 | } 28 | 29 | public Query Join(string table, Func callback, string type = "inner join") 30 | { 31 | return Join(j => j.JoinWith(table).Where(callback).AsType(type)); 32 | } 33 | 34 | public Query Join(Query query, Func onCallback, string type = "inner join") 35 | { 36 | return Join(j => j.JoinWith(query).Where(onCallback).AsType(type)); 37 | } 38 | 39 | public Query LeftJoin(string table, string first, string second, string op = "=") 40 | { 41 | return Join(table, first, second, op, "left join"); 42 | } 43 | 44 | public Query LeftJoin(string table, Func callback) 45 | { 46 | return Join(table, callback, "left join"); 47 | } 48 | 49 | public Query LeftJoin(Query query, Func onCallback) 50 | { 51 | return Join(query, onCallback, "left join"); 52 | } 53 | 54 | public Query RightJoin(string table, string first, string second, string op = "=") 55 | { 56 | return Join(table, first, second, op, "right join"); 57 | } 58 | 59 | public Query RightJoin(string table, Func callback) 60 | { 61 | return Join(table, callback, "right join"); 62 | } 63 | 64 | public Query RightJoin(Query query, Func onCallback) 65 | { 66 | return Join(query, onCallback, "right join"); 67 | } 68 | 69 | public Query CrossJoin(string table) 70 | { 71 | return Join(j => j.JoinWith(table).AsCross()); 72 | } 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /QueryBuilder/Query.Select.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace SqlKata 6 | { 7 | public partial class Query 8 | { 9 | 10 | public Query Select(params string[] columns) 11 | { 12 | return Select(columns.AsEnumerable()); 13 | } 14 | 15 | public Query Select(IEnumerable columns) 16 | { 17 | Method = "select"; 18 | 19 | columns = columns 20 | .Select(x => Helper.ExpandExpression(x)) 21 | .SelectMany(x => x) 22 | .ToArray(); 23 | 24 | 25 | foreach (var column in columns) 26 | { 27 | AddComponent("select", new Column 28 | { 29 | Name = column 30 | }); 31 | } 32 | 33 | return this; 34 | } 35 | 36 | /// 37 | /// Add a new "raw" select expression to the query. 38 | /// 39 | /// 40 | public Query SelectRaw(string sql, params object[] bindings) 41 | { 42 | Method = "select"; 43 | 44 | AddComponent("select", new RawColumn 45 | { 46 | Expression = sql, 47 | Bindings = bindings, 48 | }); 49 | 50 | return this; 51 | } 52 | 53 | public Query Select(Query query, string alias) 54 | { 55 | Method = "select"; 56 | 57 | query = query.Clone(); 58 | 59 | AddComponent("select", new QueryColumn 60 | { 61 | Query = query.As(alias), 62 | }); 63 | 64 | return this; 65 | } 66 | 67 | public Query Select(Func callback, string alias) 68 | { 69 | return Select(callback.Invoke(NewChild()), alias); 70 | } 71 | 72 | public Query SelectAggregate(string aggregate, string column, Query filter = null) 73 | { 74 | Method = "select"; 75 | 76 | AddComponent("select", new AggregatedColumn 77 | { 78 | Column = new Column { Name = column }, 79 | Aggregate = aggregate, 80 | Filter = filter, 81 | }); 82 | 83 | return this; 84 | } 85 | 86 | public Query SelectAggregate(string aggregate, string column, Func filter) 87 | { 88 | if (filter == null) 89 | { 90 | return SelectAggregate(aggregate, column); 91 | } 92 | 93 | return SelectAggregate(aggregate, column, filter.Invoke(NewChild())); 94 | } 95 | 96 | public Query SelectSum(string column, Func filter = null) 97 | { 98 | return SelectAggregate("sum", column, filter); 99 | } 100 | 101 | public Query SelectCount(string column, Func filter = null) 102 | { 103 | return SelectAggregate("count", column, filter); 104 | } 105 | 106 | public Query SelectAvg(string column, Func filter = null) 107 | { 108 | return SelectAggregate("avg", column, filter); 109 | } 110 | 111 | public Query SelectMin(string column, Func filter = null) 112 | { 113 | return SelectAggregate("min", column, filter); 114 | } 115 | 116 | public Query SelectMax(string column, Func filter = null) 117 | { 118 | return SelectAggregate("max", column, filter); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /QueryBuilder/Query.Update.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace SqlKata 7 | { 8 | public partial class Query 9 | { 10 | public Query AsUpdate(object data) 11 | { 12 | var dictionary = BuildKeyValuePairsFromObject(data, considerKeys: true); 13 | 14 | return AsUpdate(dictionary); 15 | } 16 | 17 | public Query AsUpdate(IEnumerable columns, IEnumerable values) 18 | { 19 | if ((columns?.Any() ?? false) == false || (values?.Any() ?? false) == false) 20 | { 21 | throw new InvalidOperationException($"{columns} and {values} cannot be null or empty"); 22 | } 23 | 24 | if (columns.Count() != values.Count()) 25 | { 26 | throw new InvalidOperationException($"{columns} count should be equal to {values} count"); 27 | } 28 | 29 | Method = "update"; 30 | 31 | ClearComponent("update").AddComponent("update", new InsertClause 32 | { 33 | Columns = columns.ToList(), 34 | Values = values.ToList() 35 | }); 36 | 37 | return this; 38 | } 39 | 40 | public Query AsUpdate(IEnumerable> values) 41 | { 42 | if (values == null || values.Any() == false) 43 | { 44 | throw new InvalidOperationException($"{values} cannot be null or empty"); 45 | } 46 | 47 | Method = "update"; 48 | 49 | ClearComponent("update").AddComponent("update", new InsertClause 50 | { 51 | Columns = values.Select(x => x.Key).ToList(), 52 | Values = values.Select(x => x.Value).ToList(), 53 | }); 54 | 55 | return this; 56 | } 57 | 58 | public Query AsIncrement(string column, int value = 1) 59 | { 60 | Method = "update"; 61 | AddOrReplaceComponent("update", new IncrementClause 62 | { 63 | Column = column, 64 | Value = value 65 | }); 66 | 67 | return this; 68 | } 69 | 70 | public Query AsDecrement(string column, int value = 1) 71 | { 72 | return AsIncrement(column, -value); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /QueryBuilder/QueryBuilder.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SqlKata 5 | A powerful Dynamic Sql Query Builder supporting SQL Server, MySql, PostgreSql, Sqlite, and Oracle 6 | Ahmad Moussawi 7 | Copyright (c) 2017 Ahmad Moussawi 8 | net8.0 9 | SqlKata 10 | SqlKata.Core 11 | 12 | SqlKata 13 | sql;query-builder;dynamic-query 14 | https://github.com/sqlkata/querybuilder 15 | https://github.com/sqlkata/querybuilder 16 | true 17 | MIT 18 | git 19 | https://github.com/sqlkata/querybuilder 20 | 21 | true 22 | true 23 | CS1591 24 | 25 | -------------------------------------------------------------------------------- /QueryBuilder/SqlResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | using System.Linq; 6 | 7 | namespace SqlKata 8 | { 9 | public class SqlResult 10 | { 11 | private string ParameterPlaceholder { get; set; } 12 | private string EscapeCharacter { get; set; } 13 | public SqlResult(string parameterPlaceholder, string escapeCharacter) 14 | { 15 | ParameterPlaceholder = parameterPlaceholder; 16 | EscapeCharacter = escapeCharacter; 17 | } 18 | public Query Query { get; set; } 19 | public string RawSql { get; set; } = ""; 20 | public List Bindings { get; set; } = new List(); 21 | public string Sql { get; set; } = ""; 22 | public Dictionary NamedBindings = new Dictionary(); 23 | 24 | private static readonly Type[] NumberTypes = 25 | { 26 | typeof(int), 27 | typeof(long), 28 | typeof(decimal), 29 | typeof(double), 30 | typeof(float), 31 | typeof(short), 32 | typeof(ushort), 33 | typeof(ulong), 34 | }; 35 | 36 | public override string ToString() 37 | { 38 | var deepParameters = Helper.Flatten(Bindings).ToList(); 39 | 40 | var subject = Helper.ReplaceAll(RawSql, ParameterPlaceholder, EscapeCharacter, i => 41 | { 42 | if (i >= deepParameters.Count) 43 | { 44 | throw new Exception( 45 | $"Failed to retrieve a binding at index {i}, the total bindings count is {Bindings.Count}"); 46 | } 47 | 48 | var value = deepParameters[i]; 49 | return ChangeToSqlValue(value); 50 | }); 51 | 52 | return Helper.RemoveEscapeCharacter(subject, ParameterPlaceholder, EscapeCharacter); 53 | } 54 | 55 | private string ChangeToSqlValue(object value) 56 | { 57 | if (value == null) 58 | { 59 | return "NULL"; 60 | } 61 | 62 | if (Helper.IsArray(value)) 63 | { 64 | return Helper.JoinArray(",", value as IEnumerable); 65 | } 66 | 67 | if (NumberTypes.Contains(value.GetType())) 68 | { 69 | return Convert.ToString(value, CultureInfo.InvariantCulture); 70 | } 71 | 72 | if (value is DateTime date) 73 | { 74 | if (date.Date == date) 75 | { 76 | return "'" + date.ToString("yyyy-MM-dd") + "'"; 77 | } 78 | 79 | return "'" + date.ToString("yyyy-MM-dd HH:mm:ss") + "'"; 80 | } 81 | 82 | if (value is bool vBool) 83 | { 84 | return vBool ? "true" : "false"; 85 | } 86 | 87 | if (value is Enum vEnum) 88 | { 89 | return Convert.ToInt32(vEnum) + $" /* {vEnum} */"; 90 | } 91 | 92 | // fallback to string 93 | return "'" + value.ToString().Replace("'","''") + "'"; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /QueryBuilder/UnsafeLiteral.cs: -------------------------------------------------------------------------------- 1 | namespace SqlKata 2 | { 3 | public class UnsafeLiteral 4 | { 5 | public string Value { get; set; } 6 | 7 | public UnsafeLiteral(string value, bool replaceQuotes = true) 8 | { 9 | if (value == null) 10 | { 11 | value = ""; 12 | } 13 | 14 | if (replaceQuotes) 15 | { 16 | value = value.Replace("'", "''"); 17 | } 18 | 19 | this.Value = value; 20 | } 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /QueryBuilder/Variable.cs: -------------------------------------------------------------------------------- 1 | namespace SqlKata 2 | { 3 | public class Variable 4 | { 5 | public string Name { get; set; } 6 | 7 | public Variable(string name) 8 | { 9 | this.Name = name; 10 | } 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | SqlKata Query Builder 3 |

4 |

5 | 6 | 7 | 8 | 9 | Twitter 10 |

11 | 12 |

13 | Follow and Upvote SqlKata on Product Hunt to encourage the development of this project 14 |

15 |

16 | SqlKata - Dynamic Sql query builder for dotnet | Product Hunt 17 |

18 | 19 | https://private-user-images.githubusercontent.com/2517523/408899411-1f9ff89b-ed17-4d02-ae2f-afc1c66a5932.mp4 20 | 21 | ## 🚀 Introduction 22 | 23 | SqlKata Query Builder is a powerful SQL query builder written in C#. It is secure, framework-agnostic, and inspired by top query builders like Laravel Query Builder and Knex. 24 | 25 | ### ✨ Key Features 26 | - **Expressive API**: Clean and intuitive syntax similar to SQL. 27 | - **Database Agnostic**: Work with multiple databases using a unified API. 28 | - **Complex Queries**: Supports nested conditions, subqueries, conditional statements, and more. 29 | - **Execution Support**: Use the SqlKata.Execution package to execute queries with Dapper. 30 | 31 | ## Installation 32 | 33 | ```sh 34 | $ dotnet add package SqlKata 35 | $ dotnet add package SqlKata.Execution # (optional) If you want the execution support 36 | ``` 37 | 38 | ## Quick Examples 39 | 40 | ### Setup Connection 41 | 42 | ```cs 43 | var connection = new SqlConnection("..."); 44 | var compiler = new SqlCompiler(); 45 | var db = new QueryFactory(connection, compiler); 46 | ``` 47 | > `QueryFactory` is provided by the SqlKata.Execution package. 48 | 49 | ### Retrieve all records 50 | 51 | ```cs 52 | var books = db.Query("Books").Get(); 53 | ``` 54 | 55 | ### Retrieve published books only 56 | 57 | ```cs 58 | var books = db.Query("Books").WhereTrue("IsPublished").Get(); 59 | ``` 60 | 61 | ### Retrieve one book 62 | 63 | ```cs 64 | var introToSql = db.Query("Books").Where("Id", 145).Where("Lang", "en").First(); 65 | ``` 66 | 67 | ### Retrieve recent books: last 10 68 | 69 | ```cs 70 | var recent = db.Query("Books").OrderByDesc("PublishedAt").Limit(10).Get(); 71 | ``` 72 | 73 | ### Include Author information 74 | 75 | ```cs 76 | var books = db.Query("Books") 77 | .Include(db.Query("Authors")) // Assumes that the Books table has an `AuthorId` column 78 | .Get(); 79 | ``` 80 | 81 | This will include the property "Author" on each "Book": 82 | ```jsonc 83 | [{ 84 | "Id": 1, 85 | "PublishedAt": "2019-01-01", 86 | "AuthorId": 2, 87 | "Author": { // <-- included property 88 | "Id": 2, 89 | "...": "" 90 | } 91 | }] 92 | ``` 93 | 94 | ### Join with authors table 95 | 96 | ```cs 97 | var books = db.Query("Books") 98 | .Join("Authors", "Authors.Id", "Books.AuthorId") 99 | .Select("Books.*", "Authors.Name as AuthorName") 100 | .Get(); 101 | 102 | foreach(var book in books) 103 | { 104 | Console.WriteLine($"{book.Title}: {book.AuthorName}"); 105 | } 106 | ``` 107 | 108 | ### Conditional queries 109 | 110 | ```cs 111 | var isFriday = DateTime.Today.DayOfWeek == DayOfWeek.Friday; 112 | 113 | var books = db.Query("Books") 114 | .When(isFriday, q => q.WhereIn("Category", new [] {"OpenSource", "MachineLearning"})) 115 | .Get(); 116 | ``` 117 | 118 | ### Pagination 119 | 120 | ```cs 121 | var page1 = db.Query("Books").Paginate(10); 122 | 123 | foreach(var book in page1.List) 124 | { 125 | Console.WriteLine(book.Name); 126 | } 127 | 128 | ... 129 | 130 | var page2 = page1.Next(); 131 | ``` 132 | 133 | ### Insert 134 | 135 | ```cs 136 | int affected = db.Query("Users").Insert(new { 137 | Name = "Jane", 138 | CountryId = 1 139 | }); 140 | ``` 141 | 142 | ### Update 143 | 144 | ```cs 145 | int affected = db.Query("Users").Where("Id", 1).Update(new { 146 | Name = "Jane", 147 | CountryId = 1 148 | }); 149 | ``` 150 | 151 | ### Delete 152 | 153 | ```cs 154 | int affected = db.Query("Users").Where("Id", 1).Delete(); 155 | ``` 156 | 157 | ## FAQ 158 | 159 | ### How to know when a new release or a feature is available? 160 | 161 | I announce updates on my [Twitter Account](https://twitter.com/ahmadmuzavi), and you can subscribe to our newsletters from the website [https://sqlkata.com](https://sqlkata.com). 162 | 163 | ### The database that I want is not supported. Why? 164 | 165 | It's impossible to support all available database vendors, which is why we focus on the major ones. We encourage you to create your own compiler for your database. 166 | 167 | ### Do you accept new compilers? 168 | 169 | Unfortunately, no. The reason is that this would add overhead for the project contributors. We prefer to improve the quality of the existing compilers instead. 170 | 171 | ### How can I support the project? 172 | 173 | - ⭐ Star the project here on GitHub, and share it with your friends. 174 | - 🐱‍💻 Follow and upvote it on Product Hunt: 175 | SqlKata - Dynamic Sql query builder for dotnet | Product Hunt 176 | - 💰 You can also donate to support the project financially on open collection. 177 | -------------------------------------------------------------------------------- /SqlKata.Execution/InsertGetId.cs: -------------------------------------------------------------------------------- 1 | namespace SqlKata.Execution 2 | { 3 | public class InsertGetIdRow 4 | { 5 | public T Id { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /SqlKata.Execution/PaginationIterator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace SqlKata.Execution 5 | { 6 | public class PaginationIterator : IEnumerable> 7 | { 8 | public PaginationResult FirstPage { get; set; } 9 | public PaginationResult CurrentPage { get; set; } 10 | 11 | public IEnumerator> GetEnumerator() 12 | { 13 | CurrentPage = FirstPage; 14 | 15 | yield return CurrentPage; 16 | 17 | while (CurrentPage.HasNext) 18 | { 19 | CurrentPage = CurrentPage.Next(); 20 | yield return CurrentPage; 21 | } 22 | 23 | } 24 | 25 | IEnumerator IEnumerable.GetEnumerator() 26 | { 27 | return GetEnumerator(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /SqlKata.Execution/PaginationResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqlKata.Execution 8 | { 9 | public class PaginationResult 10 | { 11 | public Query Query { get; set; } 12 | public long Count { get; set; } 13 | public IEnumerable List { get; set; } 14 | public int Page { get; set; } 15 | public int PerPage { get; set; } 16 | public int TotalPages 17 | { 18 | get 19 | { 20 | 21 | if (PerPage < 1) 22 | { 23 | return 0; 24 | } 25 | 26 | var div = (float)Count / PerPage; 27 | 28 | return (int)Math.Ceiling(div); 29 | 30 | } 31 | } 32 | 33 | public bool IsFirst 34 | { 35 | get 36 | { 37 | return Page == 1; 38 | } 39 | } 40 | 41 | public bool IsLast 42 | { 43 | get 44 | { 45 | return Page == TotalPages; 46 | } 47 | } 48 | 49 | public bool HasNext 50 | { 51 | get 52 | { 53 | return Page < TotalPages; 54 | } 55 | } 56 | 57 | public bool HasPrevious 58 | { 59 | get 60 | { 61 | return Page > 1; 62 | } 63 | } 64 | 65 | public Query NextQuery() 66 | { 67 | return this.Query.ForPage(Page + 1, PerPage); 68 | } 69 | 70 | public PaginationResult Next(IDbTransaction transaction = null, int? timeout = null) 71 | { 72 | return this.Query.Paginate(Page + 1, PerPage, transaction, timeout); 73 | } 74 | 75 | public async Task> NextAsync(IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) 76 | { 77 | return await this.Query.PaginateAsync(Page + 1, PerPage, transaction, timeout, cancellationToken); 78 | } 79 | 80 | public Query PreviousQuery() 81 | { 82 | return this.Query.ForPage(Page - 1, PerPage); 83 | } 84 | 85 | public PaginationResult Previous(IDbTransaction transaction = null, int? timeout = null) 86 | { 87 | return this.Query.Paginate(Page - 1, PerPage, transaction, timeout); 88 | } 89 | 90 | public async Task> PreviousAsync(IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) 91 | { 92 | return await this.Query.PaginateAsync(Page - 1, PerPage, transaction, timeout, cancellationToken); 93 | } 94 | 95 | public PaginationIterator Each 96 | { 97 | get 98 | { 99 | return new PaginationIterator 100 | { 101 | FirstPage = this 102 | }; 103 | } 104 | } 105 | 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /SqlKata.Execution/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("QueryBuilder.Tests")] -------------------------------------------------------------------------------- /SqlKata.Execution/SqlKata.Execution.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | SqlKata 4 | Adds the execution capabilities for SqlKata 5 | Ahmad Moussawi 6 | Copyright (c) 2017 Ahmad Moussawi 7 | net8.0 8 | SqlKata 9 | 10 | SqlKata.Execution 11 | sql;query-builder;dynamic-query 12 | https://github.com/sqlkata/querybuilder 13 | https://github.com/sqlkata/querybuilder 14 | true 15 | MIT 16 | git 17 | https://github.com/sqlkata/querybuilder 18 | 19 | true 20 | true 21 | CS1591 22 | 23 | 24 | 25 | 26 | 27 | 29 | 31 | 32 | -------------------------------------------------------------------------------- /SqlKata.Execution/XQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Linq; 4 | using SqlKata.Compilers; 5 | 6 | namespace SqlKata.Execution 7 | { 8 | public class XQuery : Query 9 | { 10 | public IDbConnection Connection { get; set; } 11 | public Compiler Compiler { get; set; } 12 | public Action Logger = result => { }; 13 | public QueryFactory QueryFactory { get; set; } 14 | 15 | public XQuery(IDbConnection connection, Compiler compiler) 16 | { 17 | this.QueryFactory = new QueryFactory(connection, compiler); 18 | this.Connection = connection; 19 | this.Compiler = compiler; 20 | } 21 | 22 | public override Query Clone() 23 | { 24 | 25 | var query = new XQuery(this.QueryFactory.Connection, this.QueryFactory.Compiler); 26 | 27 | if (this.QueryFactory?.QueryTimeout != null) 28 | { 29 | query.QueryFactory.QueryTimeout = this.QueryFactory?.QueryTimeout ?? 30; 30 | } 31 | 32 | query.Clauses = this.Clauses.Select(x => x.Clone()).ToList(); 33 | query.Logger = this.Logger; 34 | 35 | query.QueryAlias = QueryAlias; 36 | query.IsDistinct = IsDistinct; 37 | query.Method = Method; 38 | query.Includes = Includes; 39 | query.Variables = Variables; 40 | 41 | query.SetEngineScope(EngineScope); 42 | 43 | return query; 44 | } 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /sqlkata.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QueryBuilder", "QueryBuilder\QueryBuilder.csproj", "{2D0657E1-7046-4B45-BAF3-90291BD74E0B}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QueryBuilder.Tests", "QueryBuilder.Tests\QueryBuilder.Tests.csproj", "{61B3CBF1-7471-4F7B-B4AE-8D7F1E124371}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SqlKata.Execution", "SqlKata.Execution\SqlKata.Execution.csproj", "{B6DF0569-6040-4EAF-A38B-E4DEB8DC76E0}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{86D00525-7428-4DD7-914D-0A10D5C53EDE}" 13 | ProjectSection(SolutionItems) = preProject 14 | .editorconfig = .editorconfig 15 | EndProjectSection 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Debug|x64 = Debug|x64 21 | Debug|x86 = Debug|x86 22 | Release|Any CPU = Release|Any CPU 23 | Release|x64 = Release|x64 24 | Release|x86 = Release|x86 25 | EndGlobalSection 26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 27 | {2D0657E1-7046-4B45-BAF3-90291BD74E0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {2D0657E1-7046-4B45-BAF3-90291BD74E0B}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {2D0657E1-7046-4B45-BAF3-90291BD74E0B}.Debug|x64.ActiveCfg = Debug|Any CPU 30 | {2D0657E1-7046-4B45-BAF3-90291BD74E0B}.Debug|x64.Build.0 = Debug|Any CPU 31 | {2D0657E1-7046-4B45-BAF3-90291BD74E0B}.Debug|x86.ActiveCfg = Debug|Any CPU 32 | {2D0657E1-7046-4B45-BAF3-90291BD74E0B}.Debug|x86.Build.0 = Debug|Any CPU 33 | {2D0657E1-7046-4B45-BAF3-90291BD74E0B}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {2D0657E1-7046-4B45-BAF3-90291BD74E0B}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {2D0657E1-7046-4B45-BAF3-90291BD74E0B}.Release|x64.ActiveCfg = Release|Any CPU 36 | {2D0657E1-7046-4B45-BAF3-90291BD74E0B}.Release|x64.Build.0 = Release|Any CPU 37 | {2D0657E1-7046-4B45-BAF3-90291BD74E0B}.Release|x86.ActiveCfg = Release|Any CPU 38 | {2D0657E1-7046-4B45-BAF3-90291BD74E0B}.Release|x86.Build.0 = Release|Any CPU 39 | {61B3CBF1-7471-4F7B-B4AE-8D7F1E124371}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {61B3CBF1-7471-4F7B-B4AE-8D7F1E124371}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {61B3CBF1-7471-4F7B-B4AE-8D7F1E124371}.Debug|x64.ActiveCfg = Debug|Any CPU 42 | {61B3CBF1-7471-4F7B-B4AE-8D7F1E124371}.Debug|x64.Build.0 = Debug|Any CPU 43 | {61B3CBF1-7471-4F7B-B4AE-8D7F1E124371}.Debug|x86.ActiveCfg = Debug|Any CPU 44 | {61B3CBF1-7471-4F7B-B4AE-8D7F1E124371}.Debug|x86.Build.0 = Debug|Any CPU 45 | {61B3CBF1-7471-4F7B-B4AE-8D7F1E124371}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {61B3CBF1-7471-4F7B-B4AE-8D7F1E124371}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {61B3CBF1-7471-4F7B-B4AE-8D7F1E124371}.Release|x64.ActiveCfg = Release|Any CPU 48 | {61B3CBF1-7471-4F7B-B4AE-8D7F1E124371}.Release|x64.Build.0 = Release|Any CPU 49 | {61B3CBF1-7471-4F7B-B4AE-8D7F1E124371}.Release|x86.ActiveCfg = Release|Any CPU 50 | {61B3CBF1-7471-4F7B-B4AE-8D7F1E124371}.Release|x86.Build.0 = Release|Any CPU 51 | {B6DF0569-6040-4EAF-A38B-E4DEB8DC76E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {B6DF0569-6040-4EAF-A38B-E4DEB8DC76E0}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {B6DF0569-6040-4EAF-A38B-E4DEB8DC76E0}.Debug|x64.ActiveCfg = Debug|Any CPU 54 | {B6DF0569-6040-4EAF-A38B-E4DEB8DC76E0}.Debug|x64.Build.0 = Debug|Any CPU 55 | {B6DF0569-6040-4EAF-A38B-E4DEB8DC76E0}.Debug|x86.ActiveCfg = Debug|Any CPU 56 | {B6DF0569-6040-4EAF-A38B-E4DEB8DC76E0}.Debug|x86.Build.0 = Debug|Any CPU 57 | {B6DF0569-6040-4EAF-A38B-E4DEB8DC76E0}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {B6DF0569-6040-4EAF-A38B-E4DEB8DC76E0}.Release|Any CPU.Build.0 = Release|Any CPU 59 | {B6DF0569-6040-4EAF-A38B-E4DEB8DC76E0}.Release|x64.ActiveCfg = Release|Any CPU 60 | {B6DF0569-6040-4EAF-A38B-E4DEB8DC76E0}.Release|x64.Build.0 = Release|Any CPU 61 | {B6DF0569-6040-4EAF-A38B-E4DEB8DC76E0}.Release|x86.ActiveCfg = Release|Any CPU 62 | {B6DF0569-6040-4EAF-A38B-E4DEB8DC76E0}.Release|x86.Build.0 = Release|Any CPU 63 | EndGlobalSection 64 | GlobalSection(SolutionProperties) = preSolution 65 | HideSolutionNode = FALSE 66 | EndGlobalSection 67 | GlobalSection(ExtensibilityGlobals) = postSolution 68 | SolutionGuid = {8FFC834B-6CAA-409C-8E6A-43D4FB187669} 69 | EndGlobalSection 70 | EndGlobal 71 | --------------------------------------------------------------------------------