├── .github └── workflows │ └── deploy_nuget.yml ├── .gitignore ├── LICENSE ├── README-jp.md ├── README.md ├── src ├── SqModel.sln └── SqModel │ ├── Analysis │ ├── CaseExpressionParser.cs │ ├── CaseWhenExpressionParser.cs │ ├── ConditionGroupParser.cs │ ├── FromClauseParser.cs │ ├── GroupClauseParser.cs │ ├── HavingClauseParser.cs │ ├── LogicalExpressionParser.cs │ ├── NamelessItemsParser.cs │ ├── OrderClauseParser.cs │ ├── SelectClauseParser.cs │ ├── SqlParser.Disposable.cs │ ├── SqlParser.Object.cs │ ├── SqlParser.ParseAlias.cs │ ├── SqlParser.ParseSelectQuery.cs │ ├── SqlParser.ReadSpace.cs │ ├── SqlParser.ReadTokens.cs │ ├── SqlParser.cs │ ├── SyntaxException.cs │ ├── UnionClauseParser.cs │ ├── ValueClauseParser.cs │ ├── ValuesClauseParser.cs │ ├── WhereClauseParser.cs │ └── WithClauseParser.cs │ ├── ColumnValue.cs │ ├── CommandValue.cs │ ├── CommonTable.cs │ ├── Condition.cs │ ├── ConditionClause.cs │ ├── ConditionGroup.cs │ ├── CreateTableQuery.cs │ ├── CreateViewQuery.cs │ ├── Expression │ ├── CaseExpression.cs │ ├── CaseValuePair.cs │ ├── CaseWhenExpression.cs │ ├── CaseWhenValuePair.cs │ ├── ConcatExpression.cs │ ├── ConditionExtension.cs │ ├── ExistsExpression.cs │ ├── IThenCommand.cs │ ├── IValueContainerExtension.cs │ ├── StringCollectionExpression.cs │ └── ValueExpression.cs │ ├── Extension │ ├── DictionaryExtension.cs │ ├── IEnumerableExtension.cs │ ├── charExtension.cs │ ├── intExtensions.cs │ └── stringExtension.cs │ ├── ICondition.cs │ ├── ILogicalExpression.cs │ ├── IQueryable.cs │ ├── IValueClause.cs │ ├── IValueContainer.cs │ ├── InsertQuery.cs │ ├── LogicalExpression.cs │ ├── NamelessItem.cs │ ├── NamelessItemClause.cs │ ├── Query.cs │ ├── RelationTypes.cs │ ├── SelectClause.cs │ ├── SelectItem.cs │ ├── SelectQuery.cs │ ├── SelectQueryExtension.cs │ ├── SelectQueryFromExtension.cs │ ├── SelectQuerySelectExtension.cs │ ├── SelectQueryValue.cs │ ├── SelectQueryWithExtension.cs │ ├── SqModel.csproj │ ├── TableClause.cs │ ├── TableClauseExtension.cs │ ├── UnionClause.cs │ ├── ValueBuilder.cs │ ├── ValueContainer.cs │ ├── ValuesClause.cs │ └── WithClause.cs └── test └── SqModelTest ├── AnalysisTest ├── CommonTableTest.cs ├── ParseAliasTest.cs ├── ParseCaseTest.cs ├── ParseCteTest.cs ├── ParseFunctionTest.cs ├── ParseGroupTest.cs ├── ParseLogicalExpressionTest.cs ├── ParseOrderTest.cs ├── ParseTableTest.cs ├── ParseTest.cs ├── ParseUnionTest.cs ├── ParseValueClauseTest.cs ├── ParseValuesTest.cs ├── ParseWhereTest.cs ├── ReadTokensTest.cs ├── SelectItemTest.cs └── TableClauseTest.cs ├── CreateTable.cs ├── CreateView.cs ├── CteQuery.cs ├── CteSubquery.cs ├── Demo.cs ├── DistinctQuery.cs ├── Element.cs ├── Insert.cs ├── ORMTest.cs ├── SelectCaseWhen.cs ├── SelectColumn.cs ├── SelectRelationTable.cs ├── SelectSingleTable.cs ├── SelectSubquery.cs ├── SqModelTest.csproj └── WhereQuery.cs /.github/workflows/deploy_nuget.yml: -------------------------------------------------------------------------------- 1 | name: Publish - Nuget 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | 14 | - name: Setup .NET Core 15 | uses: actions/setup-dotnet@v1 16 | with: 17 | dotnet-version: 6.0.x 18 | 19 | - name: Build 20 | run: dotnet build --configuration Release 21 | working-directory: ./src 22 | 23 | - name: Generate nuget package. 24 | run: dotnet pack --configuration Release -o nupkg 25 | working-directory: ./src 26 | 27 | - name: Publish to nuget 28 | run: find . -type f -name *.nupkg -print0 | xargs -0 -I pkg dotnet nuget push pkg -k $nuget_api_key -s "https://api.nuget.org/v3/index.json" --skip-duplicate 29 | env: 30 | nuget_api_key: ${{ secrets.NUGET_API_KEY }} 31 | working-directory: ./src/nupkg 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 mk3008 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 | -------------------------------------------------------------------------------- /src/SqModel.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32228.430 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SqModel", "SqModel\SqModel.csproj", "{D2A5C7E3-6F77-4966-A506-BC1A80819369}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SqModelTest", "..\test\SqModelTest\SqModelTest.csproj", "{9CDC708B-012B-4C5A-999B-EF3524ECBF23}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {D2A5C7E3-6F77-4966-A506-BC1A80819369}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {D2A5C7E3-6F77-4966-A506-BC1A80819369}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {D2A5C7E3-6F77-4966-A506-BC1A80819369}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {D2A5C7E3-6F77-4966-A506-BC1A80819369}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {9CDC708B-012B-4C5A-999B-EF3524ECBF23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {9CDC708B-012B-4C5A-999B-EF3524ECBF23}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {9CDC708B-012B-4C5A-999B-EF3524ECBF23}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {9CDC708B-012B-4C5A-999B-EF3524ECBF23}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {D45FC75E-226D-4B81-9A47-C819823E50CD} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/SqModel/Analysis/CaseExpressionParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using SqModel.Expression; 7 | using SqModel.Extension; 8 | 9 | namespace SqModel.Analysis; 10 | 11 | public static class CaseExpressionParser 12 | { 13 | private static List splitTokens = new() { "when", "then", "else", "end" }; 14 | 15 | private static List breakTokens = new() { "", "end" }; 16 | 17 | private static string ReadUntilSplitToken(SqlParser parser) 18 | => parser.ReadUntilTokens(splitTokens, "case", "end"); 19 | 20 | public static IValueClause Parse(SqlParser parser) 21 | { 22 | var q = parser.ReadTokensWithoutComment(); 23 | 24 | var token = parser.CurrentToken.IsNotEmpty() ? parser.CurrentToken : q.First(); 25 | var next = q.First(); 26 | 27 | if (token.ToLower() == "case" && next.ToLower() == "when") 28 | { 29 | return CaseWhenExpressionParser.ParseExpression(parser); 30 | } 31 | if (token.ToLower() == "case") 32 | { 33 | return ParseExpression(parser); 34 | } 35 | throw new SyntaxException("case expression parse error."); 36 | } 37 | 38 | public static CaseExpression ParseExpression(SqlParser parser) 39 | { 40 | /* 41 | * case CaseValue 42 | * when ConditionValue then ReturnValue 43 | * when ConditionValue then ReturnValue 44 | * else ReturnValue 45 | * end 46 | */ 47 | var c = new CaseExpression(); 48 | 49 | //CaseValue 50 | c.Value = ParseCaseValue(parser); 51 | 52 | var token = ReadUntilSplitToken(parser); 53 | 54 | while (true) 55 | { 56 | if (parser.CurrentToken.ToLower() == "when" || parser.CurrentToken.ToLower() == "else") 57 | { 58 | token = ReadUntilSplitToken(parser); 59 | continue; 60 | }; 61 | 62 | if (parser.CurrentToken.ToLower() == "then") 63 | { 64 | var cv = CreateCaseValuePair(parser, token); 65 | c.Collection.Add(cv); 66 | 67 | if (parser.CurrentToken.ToLower() == "end") break; 68 | 69 | token = ReadUntilSplitToken(parser); 70 | continue; 71 | 72 | } 73 | 74 | if (parser.CurrentToken.ToLower() == "end") 75 | { 76 | var cv = CreateCaseElseValue(token); 77 | c.Collection.Add(cv); 78 | } 79 | 80 | if (breakTokens.Contains(parser.CurrentToken)) break; 81 | 82 | throw new SyntaxException("case expression parse error."); 83 | } 84 | 85 | parser.ReadToken(); 86 | return c; 87 | } 88 | 89 | private static IValueClause ParseCaseValue(SqlParser parser) 90 | { 91 | var text = $"{parser.CurrentToken}{parser.ReadUntilTokens(new() { "when" })}"; 92 | using var p = new SqlParser(text) { Logger = parser.Logger }; 93 | if (parser.CurrentToken != "when") throw new InvalidOperationException(); 94 | return ValueClauseParser.Parse(p); 95 | } 96 | 97 | private static CaseValuePair CreateCaseValuePair(SqlParser parser, string token) 98 | { 99 | //set ConditionValue 100 | var cv = new CaseValuePair(); 101 | cv.When(ValueClauseParser.Parse(token)); 102 | 103 | //set ReturnValue 104 | var valuetoken = ReadUntilSplitToken(parser); 105 | cv.Then(ValueClauseParser.Parse(valuetoken)); 106 | return cv; 107 | } 108 | 109 | private static CaseValuePair CreateCaseElseValue(string token) 110 | { 111 | //set ReturnValue 112 | var cv = new CaseValuePair(); 113 | cv.Then(ValueClauseParser.Parse(token)); 114 | return cv; 115 | } 116 | } -------------------------------------------------------------------------------- /src/SqModel/Analysis/CaseWhenExpressionParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using SqModel.Expression; 7 | using SqModel.Extension; 8 | 9 | namespace SqModel.Analysis; 10 | 11 | internal static class CaseWhenExpressionParser 12 | { 13 | private static List splitTokens = new() { "when", "then", "else", "end" }; 14 | 15 | private static List breakTokens = new() { "", "end" }; 16 | 17 | private static string ReadUntilSplitToken(SqlParser parser) 18 | => parser.ReadUntilTokens(splitTokens, "case", "end"); 19 | 20 | public static CaseWhenExpression ParseExpression(SqlParser parser) 21 | { 22 | /* 23 | * case 24 | * when Condition then Value 25 | * when Condition then Value 26 | * else Value 27 | * end 28 | */ 29 | 30 | if (parser.CurrentToken != "when") throw new InvalidProgramException(); 31 | 32 | var c = new CaseWhenExpression(); 33 | var q = parser.ReadTokensWithoutComment(); 34 | 35 | var fn = () => 36 | { 37 | if (parser.CurrentToken.ToLower() == "else") 38 | { 39 | var value = ReadUntilSplitToken(parser); 40 | if (parser.CurrentToken.ToLower() != "end") throw new InvalidProgramException(); 41 | 42 | //set ReturnValue 43 | var cv = new CaseWhenValuePair(); 44 | cv.Then(ValueClauseParser.Parse(value)); 45 | 46 | c.Collection.Add(cv); 47 | return; 48 | } 49 | 50 | if (parser.CurrentToken.ToLower() == "when") 51 | { 52 | var condition = ReadUntilSplitToken(parser); 53 | if (parser.CurrentToken.ToLower() != "then") throw new InvalidProgramException(); 54 | 55 | //set Condition 56 | var cv = new CaseWhenValuePair(); 57 | cv.When(LogicalExpressionParser.Parse(condition)); 58 | 59 | //set ReturnValue 60 | var valuetoken = ReadUntilSplitToken(parser); 61 | cv.Then(ValueClauseParser.Parse(valuetoken)); 62 | 63 | c.Collection.Add(cv); 64 | return; 65 | } 66 | 67 | throw new InvalidProgramException(); 68 | }; 69 | 70 | while (parser.CurrentToken.ToLower() != "end" && parser.CurrentToken.IsNotEmpty()) 71 | { 72 | fn(); 73 | }; 74 | 75 | if (parser.CurrentToken.ToLower() == "end") q.First(); 76 | return c; 77 | } 78 | } -------------------------------------------------------------------------------- /src/SqModel/Analysis/ConditionGroupParser.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Expression; 2 | using SqModel.Extension; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SqModel.Analysis; 10 | 11 | internal class ConditionGroupParser 12 | { 13 | internal static ConditionGroup Parse(SqlParser parser, string starttoken) 14 | { 15 | var group = new ConditionGroup(); 16 | var q = parser.ReadTokensWithoutComment(); 17 | var startToken = parser.CurrentToken.IsNotEmpty() ? parser.CurrentToken : q.First(); 18 | 19 | if (startToken.ToLower() != starttoken) throw new InvalidProgramException($"First token must be '{starttoken}'."); 20 | q.First(); //skip start token token 21 | 22 | var isFirst = true; 23 | while (!parser.ConditionBreakTokens.Contains(parser.CurrentToken.ToLower()) && parser.CurrentToken.IsNotEmpty()) 24 | { 25 | var op = string.Empty; 26 | var sop = string.Empty; 27 | if (!isFirst) 28 | { 29 | op = parser.CurrentToken; 30 | q.First(); 31 | if (parser.CurrentToken.ToLower() == "not") 32 | { 33 | sop = parser.CurrentToken.ToLower(); 34 | q.First(); 35 | } 36 | } 37 | else 38 | { 39 | isFirst = false; 40 | } 41 | var c = ValueClauseParser.ParseAsExpression(parser); 42 | c.Operator = op; 43 | c.SubOperator = sop; 44 | group.Collection.Add(c); 45 | } 46 | return group; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/SqModel/Analysis/FromClauseParser.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Extension; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SqModel.Analysis; 9 | 10 | public static class FromClauseParser 11 | { 12 | private static string StartToken = "from"; 13 | 14 | public static TableClause Parse(string text) 15 | { 16 | using var p = new SqlParser(text); 17 | return Parse(p); 18 | } 19 | 20 | public static TableClause Parse(SqlParser parser) 21 | { 22 | var q = parser.ReadTokensWithoutComment(); 23 | var startToken = parser.CurrentToken.IsNotEmpty() ? parser.CurrentToken : q.First(); 24 | 25 | if (startToken.ToLower() != StartToken) throw new InvalidProgramException($"First token must be '{StartToken}'."); 26 | 27 | var fromTable = ParseSingleTableClause(parser); 28 | 29 | while (parser.CurrentToken.ToRelationType() != RelationTypes.Undefined) 30 | { 31 | var joinTable = ParseSingleTableClause(parser); 32 | fromTable.SubTableClauses.Add(joinTable); 33 | } 34 | 35 | return fromTable; 36 | } 37 | 38 | private static TableClause ParseSingleTableClause(SqlParser parser) 39 | { 40 | var q = parser.ReadTokensWithoutComment(); 41 | 42 | var token = parser.CurrentToken.IsNotEmpty() ? parser.CurrentToken : q.First(); 43 | var tp = token.ToRelationType(); 44 | 45 | var setRelation = (TableClause t) => 46 | { 47 | if (tp == RelationTypes.From || tp == RelationTypes.Cross) return t; 48 | if (parser.CurrentToken != "on") return t; 49 | 50 | t.RelationClause = ConditionGroupParser.Parse(parser, "on"); 51 | t.RelationClause.IsDecorateBracket = false; 52 | return t; 53 | }; 54 | 55 | token = q.First(); 56 | if (token == "(") 57 | { 58 | token = q.First(); //inner text 59 | using var p = new SqlParser(token); 60 | var t = new TableClause() { RelationType = tp }; 61 | t.RelationClause.IsDecorateBracket = false; 62 | 63 | t.SubSelectClause = p.ParseSelectQuery(); 64 | 65 | q.First(); //skip inner sql token 66 | 67 | if (parser.CurrentToken != ")") throw new SyntaxException("From clause syntax errpr."); 68 | 69 | q.First(); //skip ')' token 70 | 71 | t.AliasName = parser.ParseAlias(); 72 | 73 | if (parser.CurrentToken == "(") 74 | { 75 | q.First(); 76 | var items = NamelessItemsParser.Parse(parser.CurrentToken); 77 | t.ColumnNames = items.Collection.Select(x => x.ToQuery().CommandText).ToList(); 78 | q.First(); 79 | if (parser.CurrentToken != ")") throw new InvalidProgramException(); 80 | q.First(); 81 | } 82 | 83 | setRelation(t); 84 | return t; 85 | } 86 | else 87 | { 88 | var t = new TableClause() { RelationType = tp }; 89 | t.RelationClause.IsDecorateBracket = false; 90 | 91 | var sb = new StringBuilder(); 92 | 93 | sb.Append(token); 94 | 95 | q.First(); 96 | 97 | while (parser.CurrentToken == ".") 98 | { 99 | sb.Append(parser.CurrentToken); 100 | q.First(); 101 | sb.Append(parser.CurrentToken); 102 | q.First(); 103 | } 104 | 105 | t.TableName = sb.ToString(); 106 | 107 | if (!parser.TableBreakTokens.Contains(parser.CurrentToken.ToLower()) && parser.CurrentToken.ToLower() != "on") 108 | { 109 | t.AliasName = parser.ParseAlias(); 110 | } 111 | if (t.AliasName.IsEmpty()) t.AliasName = t.TableName; 112 | 113 | setRelation(t); 114 | return t; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/SqModel/Analysis/GroupClauseParser.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Extension; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SqModel.Analysis; 9 | 10 | public static class GroupClauseParser 11 | { 12 | private static string StartToken = "group by"; 13 | 14 | public static NamelessItemClause Parse(string text) 15 | { 16 | using var p = new SqlParser(text); 17 | return Parse(p); 18 | } 19 | 20 | public static NamelessItemClause Parse(SqlParser parser) 21 | => NamelessItemsParser.Parse(parser, StartToken); 22 | } -------------------------------------------------------------------------------- /src/SqModel/Analysis/HavingClauseParser.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Expression; 2 | using SqModel.Extension; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SqModel.Analysis; 10 | 11 | public static class HavingClauseParser 12 | { 13 | private static string StartToken = "having"; 14 | 15 | public static ConditionClause Parse(string text) 16 | { 17 | using var p = new SqlParser(text); 18 | return Parse(p); 19 | } 20 | 21 | public static ConditionClause Parse(SqlParser parser) 22 | { 23 | var w = new ConditionClause(StartToken); 24 | w.ConditionGroup = ConditionGroupParser.Parse(parser, StartToken); 25 | w.ConditionGroup.IsDecorateBracket = false; 26 | w.ConditionGroup.IsOneLineFormat = false; 27 | return w; 28 | } 29 | } -------------------------------------------------------------------------------- /src/SqModel/Analysis/LogicalExpressionParser.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Extension; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SqModel.Analysis; 9 | 10 | public static class LogicalExpressionParser 11 | { 12 | public static LogicalExpression Parse(string text) 13 | { 14 | using var p = new SqlParser(text); 15 | return Parse(p); 16 | } 17 | 18 | public static LogicalExpression Parse(SqlParser parser) 19 | { 20 | var q = parser.ReadTokensWithoutComment(); 21 | 22 | var c = new LogicalExpression(); 23 | 24 | c.Left = ValueClauseParser.Parse(parser); 25 | var sign = string.Empty; 26 | if (parser.CurrentToken.IsNotEmpty()) 27 | { 28 | sign = parser.CurrentToken; 29 | q.First(); 30 | 31 | c.Right = ValueClauseParser.Parse(parser); 32 | c.Right.Conjunction = sign; 33 | } 34 | return c; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/SqModel/Analysis/NamelessItemsParser.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Extension; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SqModel.Analysis; 9 | 10 | public static class NamelessItemsParser 11 | { 12 | public static NamelessItemClause Parse(string text, string starttoken = "") 13 | { 14 | using var p = new SqlParser(text); 15 | return Parse(p, starttoken); 16 | } 17 | 18 | public static NamelessItemClause Parse(SqlParser parser, string starttoken) 19 | { 20 | var q = parser.ReadTokensWithoutComment(); 21 | 22 | if (starttoken.IsNotEmpty()) 23 | { 24 | var startToken = parser.CurrentToken.IsNotEmpty() ? parser.CurrentToken : q.First(); 25 | if (startToken.ToLower() != starttoken) throw new SyntaxException($"First token must be '{starttoken}'."); 26 | q.First(); //skip start token token 27 | } 28 | 29 | var oc = new NamelessItemClause(starttoken); 30 | 31 | oc.Collection.Add(ParseOrderItem(parser)); 32 | 33 | while (parser.CurrentToken == ",") 34 | { 35 | q.First();//skip ',' token 36 | oc.Collection.Add(ParseOrderItem(parser)); 37 | } 38 | return oc; 39 | } 40 | 41 | public static NamelessItem ParseOrderItem(SqlParser parser) 42 | { 43 | var c = new NamelessItem(); 44 | c.Command = ValueClauseParser.Parse(parser); 45 | return c; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/SqModel/Analysis/OrderClauseParser.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Extension; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SqModel.Analysis; 9 | 10 | public static class OrderClauseParser 11 | { 12 | private static string StartToken = "order by"; 13 | 14 | public static NamelessItemClause Parse(string text) 15 | { 16 | using var p = new SqlParser(text); 17 | return Parse(p); 18 | } 19 | 20 | public static NamelessItemClause Parse(SqlParser parser) 21 | => NamelessItemsParser.Parse(parser, StartToken); 22 | } -------------------------------------------------------------------------------- /src/SqModel/Analysis/SelectClauseParser.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Expression; 2 | using SqModel.Extension; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SqModel.Analysis; 10 | 11 | internal static class SelectClauseParser 12 | { 13 | private static string StartToken = "select"; 14 | 15 | public static SelectClause Parse(SqlParser parser) 16 | { 17 | var q = parser.ReadTokensWithoutComment(); 18 | var startToken = parser.CurrentToken.IsNotEmpty() ? parser.CurrentToken : q.First(); 19 | 20 | if (startToken.ToLower() != StartToken) throw new SyntaxException($"First token must be '{StartToken}'."); 21 | q.First(); //skip start token token 22 | 23 | var sc = new SelectClause(); 24 | 25 | if (parser.CurrentToken.ToLower() == "distinct") 26 | { 27 | sc.Distinct(); 28 | q.First(); //skip distinct token token 29 | } 30 | 31 | sc.Collection.Add(ParseSelecItem(parser)); 32 | 33 | while (parser.CurrentToken == ",") 34 | { 35 | q.First();//skip ',' token 36 | sc.Collection.Add(ParseSelecItem(parser)); 37 | } 38 | return sc; 39 | } 40 | 41 | public static SelectItem ParseSelecItem(SqlParser parser) 42 | { 43 | var c = new SelectItem(); 44 | c.Command = ValueClauseParser.Parse(parser); 45 | 46 | if (!parser.AliasBreakTokens.Contains(parser.CurrentToken.ToLower())) 47 | { 48 | c.Name = parser.ParseAlias(); 49 | } 50 | if (c.Name.IsEmpty()) 51 | { 52 | var n = c.Command.GetName(); 53 | if (n.IsLetter()) c.Name = c.Command.GetName(); 54 | } 55 | 56 | return c; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/SqModel/Analysis/SqlParser.Disposable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel.Analysis; 8 | 9 | public partial class SqlParser : IDisposable 10 | { 11 | private bool disposedValue; 12 | 13 | protected virtual void Dispose(bool disposing) 14 | { 15 | if (!disposedValue) 16 | { 17 | if (disposing) 18 | { 19 | // TODO: マネージド状態を破棄します (マネージド オブジェクト) 20 | Reader.Dispose(); 21 | } 22 | 23 | // TODO: アンマネージド リソース (アンマネージド オブジェクト) を解放し、ファイナライザーをオーバーライドします 24 | // TODO: 大きなフィールドを null に設定します 25 | disposedValue = true; 26 | } 27 | } 28 | 29 | // // TODO: 'Dispose(bool disposing)' にアンマネージド リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします 30 | // ~Parser() 31 | // { 32 | // // このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します 33 | // Dispose(disposing: false); 34 | // } 35 | 36 | public void Dispose() 37 | { 38 | // このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します 39 | Dispose(disposing: true); 40 | GC.SuppressFinalize(this); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/SqModel/Analysis/SqlParser.Object.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Extension; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SqModel.Analysis; 10 | 11 | public partial class SqlParser 12 | { 13 | public static SelectQuery Parse(object obj, string parametercmd = ":", Func? nameconverter = null, Func? propfilter = null) 14 | { 15 | var conv = nameconverter; 16 | conv ??= x => x; 17 | 18 | var filter = propfilter; 19 | filter ??= _ => true; 20 | 21 | var sq = new SelectQuery(); 22 | 23 | obj.GetType().GetProperties().Where(x => filter(x)).ToList().ForEach(x => 24 | { 25 | var key = $"{parametercmd}{x.Name}".ToLower(); 26 | var val = x.GetValue(obj); 27 | sq.Select.Add().Value(key).AddParameter(key, val).As($"{conv(x.Name)}"); 28 | }); 29 | 30 | return sq; 31 | } 32 | 33 | public static SelectQuery Parse(Func? nameconverter = null, Func? propfilter = null) 34 | { 35 | var conv = nameconverter; 36 | conv ??= x => x.ToLower(); 37 | 38 | var filter = propfilter; 39 | filter ??= _ => true; 40 | 41 | var sq = new SelectQuery(); 42 | var t = sq.From(conv(typeof(T).Name)).As("t"); 43 | typeof(T).GetProperties().Where(x => filter(x)).ToList().ForEach(x => 44 | { 45 | sq.Select.Add().Column(t, conv(x.Name)).As(x.Name.ToLower()); 46 | }); 47 | 48 | return sq; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/SqModel/Analysis/SqlParser.ParseAlias.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using SqModel.Extension; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SqModel.Analysis; 10 | 11 | public partial class SqlParser 12 | { 13 | public string ParseAlias() 14 | { 15 | Logger?.Invoke($"{nameof(ParseAlias)} start"); 16 | 17 | var q = ReadTokensWithoutComment(); 18 | 19 | var alias = CurrentToken.IsNotEmpty() ? CurrentToken : q.First(); 20 | if (alias.IsEmpty()) return String.Empty; 21 | if (AliasTokens.Contains(alias)) alias = q.First(); 22 | 23 | if (AliasBreakTokens.Where(x => x == CurrentToken).Any()) return String.Empty; 24 | 25 | q.First(); 26 | return alias; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/SqModel/Analysis/SqlParser.ParseSelectQuery.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Extension; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SqModel.Analysis; 9 | 10 | public partial class SqlParser 11 | { 12 | public SelectQuery ParseSelectQuery() 13 | { 14 | Logger?.Invoke($"{nameof(ParseSelectQuery)} start"); 15 | 16 | var sq = new SelectQuery(); 17 | 18 | var q = ReadTokensWithoutComment(); 19 | if (CurrentToken.IsEmpty()) q.First();// read first token 20 | 21 | if (CurrentToken.ToLower() == "values") 22 | { 23 | sq.ValuesClause = ValuesClauseParser.Parse(this); 24 | return sq; 25 | } 26 | 27 | if (CurrentToken.ToLower() == "with") 28 | { 29 | sq.WithClause = WithClauseParser.Parse(this); 30 | } 31 | if (CurrentToken.ToLower() == "select") 32 | { 33 | sq.SelectClause = SelectClauseParser.Parse(this); 34 | } 35 | if (CurrentToken.ToLower() == "from") 36 | { 37 | sq.FromClause = FromClauseParser.Parse(this); 38 | } 39 | if (CurrentToken.ToLower() == "where") 40 | { 41 | sq.WhereClause = WhereClauseParser.Parse(this); 42 | }; 43 | if (CurrentToken.ToLower() == "group by") 44 | { 45 | sq.GroupClause = GroupClauseParser.Parse(this); 46 | }; 47 | if (CurrentToken.ToLower() == "having") 48 | { 49 | sq.HavingClause = HavingClauseParser.Parse(this); 50 | }; 51 | if (CurrentToken.ToLower() == "union") 52 | { 53 | sq.UnionClause = UnionClauseParser.Parse(this); 54 | }; 55 | if (CurrentToken.ToLower() == "order by") 56 | { 57 | sq.OrderClause = OrderClauseParser.Parse(this); 58 | }; 59 | 60 | return sq; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/SqModel/Analysis/SqlParser.ReadSpace.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using SqModel.Extension; 7 | 8 | namespace SqModel.Analysis; 9 | 10 | public partial class SqlParser 11 | { 12 | public string ReadUntilTokens(List breakTokens, string leveluptoken = "", string leveldowntoken = "") 13 | { 14 | var sb = new StringBuilder(); 15 | 16 | var level = 0; 17 | while (true) 18 | { 19 | var token = ReadToken(false); 20 | if (token == " ") 21 | { 22 | sb.Append(token); 23 | continue; 24 | } 25 | if (token.IsEmpty()) break; 26 | 27 | if (token.ToLower() == leveluptoken) level++; 28 | if (level == 0 && breakTokens.Contains(token.ToLower())) break; 29 | 30 | if (token.ToLower() == leveldowntoken) level--; 31 | sb.Append(token); 32 | } 33 | 34 | return sb.ToString(); 35 | } 36 | 37 | public string ReadWhileSpace() 38 | { 39 | var digit = (char c) => c.IsSpace(); 40 | return ReadWhile(digit); 41 | } 42 | 43 | public string ReadWhile(Func digit) 44 | { 45 | var s = new StringBuilder(); 46 | 47 | while (true) 48 | { 49 | var c = PeekOrDefault(); 50 | if (c == null) break; 51 | if (!digit(c.Value)) break; 52 | s.Append(Read()); 53 | continue; 54 | }; 55 | 56 | return s.ToString(); 57 | } 58 | 59 | 60 | public string ReadWhileQuoteToken() 61 | { 62 | var s = new StringBuilder(); 63 | 64 | while (true) 65 | { 66 | var c = PeekOrDefault(); 67 | if (c == null) break; 68 | 69 | if (c != '\'') 70 | { 71 | s.Append(Read()); 72 | continue; 73 | } 74 | 75 | s.Append(Read()); 76 | c = PeekOrDefault(); 77 | if (c.HasValue && c == '\'') 78 | { 79 | //espaced 80 | s.Append(Read()); 81 | continue; 82 | } 83 | break; 84 | }; 85 | return s.ToString(); 86 | } 87 | } -------------------------------------------------------------------------------- /src/SqModel/Analysis/SqlParser.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Extension; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | using System.Threading.Tasks; 8 | 9 | namespace SqModel.Analysis; 10 | 11 | public partial class SqlParser 12 | { 13 | public SqlParser(string text) 14 | { 15 | Text = text; 16 | Reader = new StringReader(Text); 17 | } 18 | 19 | public static SelectQuery Parse(string text) 20 | { 21 | using var p = new SqlParser(text); 22 | return p.ParseSelectQuery(); 23 | } 24 | 25 | public string Text { get; init; } 26 | 27 | public StringReader Reader { get; private set; } 28 | 29 | public Action? Logger { get; set; } 30 | 31 | public static char[] SpaceTokens = " \t\r\n;".ToCharArray(); 32 | 33 | public static char[] SymbolTokens = "+-*/.,()!=%<>'".ToCharArray(); 34 | 35 | public static char[] LetterChars = "abcdefghijklmnopqrstuvwxyz".ToCharArray(); 36 | 37 | public static char[] ArithmeticOperatorTokens = "+-*/%".ToCharArray(); 38 | 39 | public static string[] LogicalOperatorTokens = new[] { 40 | "and", 41 | "or" 42 | }; 43 | 44 | public static string[] FromTokens = new[] 45 | { 46 | "from", 47 | }; 48 | 49 | public static string[] InnerJoinTokens = new[] 50 | { 51 | "inner join", 52 | }; 53 | 54 | public static string[] LeftJoinTokens = new[] 55 | { 56 | "left join", 57 | "left outer join", 58 | }; 59 | 60 | public static string[] RightJoinTokens = new[] 61 | { 62 | "right join", 63 | "right outer join", 64 | }; 65 | 66 | public static string[] CrossJoinTokens = new[] 67 | { 68 | "cross join", 69 | }; 70 | 71 | public static string[] WhereTokens = new[] 72 | { 73 | "where", 74 | }; 75 | 76 | public static string[] GroupTokens = new[] 77 | { 78 | "group by", 79 | }; 80 | 81 | public static string[] HavingTokens = new[] 82 | { 83 | "having", 84 | }; 85 | 86 | public static string[] UnionTokens = new[] 87 | { 88 | "union", 89 | }; 90 | 91 | public static string[] OrderTokens = new[] 92 | { 93 | "order by", 94 | }; 95 | 96 | public static string[] QueryBreakTokens = new[] 97 | { 98 | ";", 99 | }; 100 | 101 | public static List ColumnSplitTokens = new() { 102 | ",", 103 | }; 104 | 105 | private static string[] SignTokens = new[] { 106 | "=", 107 | "!=", 108 | ">", 109 | ">=", 110 | "<", 111 | "<=", 112 | "is", 113 | }; 114 | 115 | private static string[] AliasTokens = new[] { 116 | "as", 117 | }; 118 | 119 | internal string[] AliasBreakTokens = 120 | ColumnSplitTokens 121 | .Union(FromTokens) 122 | .Union(InnerJoinTokens) 123 | .Union(LeftJoinTokens) 124 | .Union(RightJoinTokens) 125 | .Union(CrossJoinTokens) 126 | .Union(WhereTokens) 127 | .Union(GroupTokens) 128 | .Union(HavingTokens) 129 | .Union(UnionTokens) 130 | .Union(OrderTokens) 131 | .Union(QueryBreakTokens).ToArray(); 132 | 133 | internal string[] TableBreakTokens = 134 | ColumnSplitTokens 135 | .Union(InnerJoinTokens) 136 | .Union(LeftJoinTokens) 137 | .Union(RightJoinTokens) 138 | .Union(CrossJoinTokens) 139 | .Union(WhereTokens) 140 | .Union(GroupTokens) 141 | .Union(HavingTokens) 142 | .Union(UnionTokens) 143 | .Union(OrderTokens) 144 | .Union(QueryBreakTokens).ToArray(); 145 | 146 | internal string[] ValueBreakTokens = 147 | ColumnSplitTokens 148 | .Union(FromTokens) 149 | .Union(InnerJoinTokens) 150 | .Union(LeftJoinTokens) 151 | .Union(RightJoinTokens) 152 | .Union(CrossJoinTokens) 153 | .Union(WhereTokens) 154 | .Union(GroupTokens) 155 | .Union(HavingTokens) 156 | .Union(UnionTokens) 157 | .Union(OrderTokens) 158 | .Union(QueryBreakTokens) 159 | .Union(SignTokens) 160 | .Union(LogicalOperatorTokens) 161 | .Union(AliasTokens).ToArray(); 162 | 163 | internal string[] ConditionBreakTokens = 164 | InnerJoinTokens 165 | .Union(LeftJoinTokens) 166 | .Union(RightJoinTokens) 167 | .Union(CrossJoinTokens) 168 | .Union(WhereTokens) 169 | .Union(GroupTokens) 170 | .Union(HavingTokens) 171 | .Union(UnionTokens) 172 | .Union(OrderTokens) 173 | .Union(QueryBreakTokens).ToArray(); 174 | 175 | public char Read() 176 | { 177 | var i = Reader.Read(); 178 | 179 | if (i.IsEof()) throw new EndOfStreamException(); 180 | 181 | return (char)i; 182 | } 183 | 184 | public char Peek() 185 | { 186 | var i = Reader.Peek(); 187 | if (i.IsEof()) throw new EndOfStreamException(); 188 | return (char)i; 189 | } 190 | 191 | public char? PeekOrDefault() 192 | { 193 | var i = Reader.Peek(); 194 | if (i.IsEof()) return null; 195 | return (char)i; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/SqModel/Analysis/SyntaxException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel.Analysis; 8 | 9 | public class SyntaxException : Exception 10 | { 11 | public SyntaxException(string? message) : base(message) { } 12 | } -------------------------------------------------------------------------------- /src/SqModel/Analysis/UnionClauseParser.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Expression; 2 | using SqModel.Extension; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SqModel.Analysis; 10 | 11 | public static class UnionClauseParser 12 | { 13 | private static string StartToken = "union"; 14 | 15 | public static UnionClause Parse(string text) 16 | { 17 | using var p = new SqlParser(text); 18 | return Parse(p); 19 | } 20 | 21 | public static UnionClause Parse(SqlParser parser) 22 | { 23 | var c = new UnionClause(); 24 | var q = parser.ReadTokensWithoutComment(); 25 | 26 | var startToken = parser.CurrentToken.IsNotEmpty() ? parser.CurrentToken : q.First(); 27 | 28 | if (startToken.ToLower() != StartToken) throw new InvalidProgramException($"First token must be '{StartToken}'."); 29 | q.First(); //skip start token token 30 | 31 | if (parser.CurrentToken.ToLower() == "all") 32 | { 33 | c.IsUnionAll = true; 34 | q.First(); //skil 'all' token 35 | } 36 | else 37 | { 38 | c.IsUnionAll = false; 39 | } 40 | 41 | if (parser.CurrentToken != "select") throw new SyntaxException($"union clause syntax error."); 42 | c.SelectQuery = parser.ParseSelectQuery(); 43 | 44 | return c; 45 | } 46 | } -------------------------------------------------------------------------------- /src/SqModel/Analysis/ValueClauseParser.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Expression; 2 | using SqModel.Extension; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SqModel.Analysis; 10 | 11 | public static class ValueClauseParser 12 | { 13 | public static IValueClause Parse(string text) 14 | { 15 | using var p = new SqlParser(text); 16 | return Parse(p); 17 | } 18 | 19 | public static IValueClause Parse(SqlParser parser) 20 | { 21 | var c = ParseCore(parser); 22 | if (!parser.CurrentToken.IsConjunction()) return c; 23 | 24 | var exp = new ValueExpression(); 25 | exp.Collection.Add(c); 26 | 27 | while (parser.CurrentToken.IsConjunction()) 28 | { 29 | var q = parser.ReadTokensWithoutComment(); 30 | var con = parser.CurrentToken; 31 | q.First(); 32 | 33 | var cmd = ValueClauseParser.ParseCore(parser); 34 | cmd.Conjunction = con; 35 | exp.Collection.Add(cmd); 36 | } 37 | 38 | return exp; 39 | } 40 | 41 | public static ValueExpression ParseAsExpression(SqlParser parser) 42 | { 43 | var c = ParseCore(parser); 44 | var exp = new ValueExpression(); 45 | exp.Collection.Add(c); 46 | 47 | while (parser.CurrentToken.IsConjunction()) 48 | { 49 | var q = parser.ReadTokensWithoutComment(); 50 | var con = parser.CurrentToken; 51 | q.First(); 52 | 53 | var cmd = ValueClauseParser.ParseCore(parser); 54 | cmd.Conjunction = con; 55 | exp.Collection.Add(cmd); 56 | } 57 | 58 | return exp; 59 | } 60 | 61 | private static IValueClause ParseCore(SqlParser parser) 62 | { 63 | var cache = new List(); 64 | var q = parser.ReadTokensWithoutComment(); 65 | 66 | var recursiveParse = () => 67 | { 68 | var text = Parse(parser).ToQuery().CommandText; 69 | cache.Add(text); 70 | return ToCommandValue(cache); 71 | }; 72 | 73 | cache.Add(parser.CurrentToken.IsNotEmpty() ? parser.CurrentToken : q.First()); 74 | 75 | if (parser.CurrentToken == "(") 76 | { 77 | cache.Add(q.First()); // inner text 78 | 79 | using var p = new SqlParser(parser.CurrentToken) { Logger = parser.Logger }; 80 | var f = p.ReadTokens().FirstOrDefault(); 81 | if (f?.ToLower() == "select") 82 | { 83 | var iq = p.ParseSelectQuery(); 84 | iq.IsOneLineFormat = true; 85 | var item = new SelectQueryValue() { Query = iq }; 86 | q.First(); // skip inner text token 87 | if (parser.CurrentToken != ")") throw new SyntaxException("Value clause syntax error"); 88 | q.First(); // skip ')' text token 89 | return item; 90 | } 91 | cache.Add(q.First()); // cache ')' token 92 | } 93 | else if (parser.CurrentToken.ToLower() == "case") 94 | { 95 | return CaseExpressionParser.Parse(parser); 96 | } 97 | 98 | q.First(); 99 | if (parser.CurrentToken.IsEmpty() || parser.ValueBreakTokens.Contains(parser.CurrentToken.ToLower())) return ToCommandValue(cache); 100 | if (parser.CurrentToken.IsWord() && parser.CurrentToken.ToLower() != "is" && parser.CurrentToken.ToLower() != "null") return ToCommandValue(cache); 101 | if (parser.CurrentToken.IsConjunction()) return ToCommandValue(cache); 102 | 103 | cache.Add(parser.CurrentToken); 104 | 105 | if (parser.CurrentToken == "." && cache.Count == 2 && cache.First().IsLetter()) 106 | { 107 | q.First(); 108 | cache.Add(parser.CurrentToken); 109 | var col = parser.CurrentToken; 110 | q.First(); 111 | if (parser.CurrentToken != "(") 112 | { 113 | var item = new ColumnValue() { Table = cache.First(), Column = col }; 114 | return item; 115 | } 116 | cache.Add(parser.CurrentToken); 117 | } 118 | 119 | if (parser.CurrentToken == ".*" && cache.Count == 2) 120 | { 121 | var item = new ColumnValue() { Table = cache.First(), Column = "*" }; 122 | q.First(); 123 | return item; 124 | } 125 | 126 | if (parser.CurrentToken == "(") 127 | { 128 | q.First(); //inner text 129 | if (parser.CurrentToken.IsNotEmpty()) cache.Add(parser.CurrentToken); 130 | q.First(); //close 131 | cache.Add(parser.CurrentToken); 132 | } 133 | 134 | q.First(); 135 | 136 | if (parser.CurrentToken.ToLower() == "over" || parser.CurrentToken == "(") 137 | { 138 | return recursiveParse(); 139 | } 140 | 141 | if (parser.CurrentToken.StartsWith("::")) 142 | { 143 | cache.Add(parser.CurrentToken); 144 | q.First(); 145 | } 146 | 147 | return ToCommandValue(cache); 148 | } 149 | 150 | private static CommandValue ToCommandValue(List cache) 151 | { 152 | var c = new CommandValue(); 153 | var sb = new StringBuilder(); 154 | var prev = string.Empty; 155 | cache.ForEach(x => 156 | { 157 | if (sb.Length == 0) 158 | { 159 | sb.Append(x); 160 | } 161 | else if (prev.ToLower() == "exists") 162 | { 163 | sb.Append($" {x}"); 164 | } 165 | else if (prev == "." || prev == "(") 166 | { 167 | sb.Append(x); 168 | } 169 | else if (x == "." || x.StartsWith("(") || x == ")" || x.StartsWith("::")) 170 | { 171 | sb.Append(x); 172 | } 173 | else 174 | { 175 | sb.Append($" {x}"); 176 | } 177 | prev = x; 178 | }); 179 | c.CommandText = sb.ToString(); 180 | return c; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/SqModel/Analysis/ValuesClauseParser.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Extension; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SqModel.Analysis; 9 | 10 | public static class ValuesClauseParser 11 | { 12 | private static string StartToken = "values"; 13 | 14 | public static ValuesClause Parse(string text) 15 | { 16 | using var p = new SqlParser(text); 17 | var c = Parse(p); 18 | return c; 19 | } 20 | 21 | public static ValuesClause Parse(SqlParser parser) 22 | { 23 | var q = parser.ReadTokensWithoutComment(); 24 | 25 | var token = parser.CurrentToken.IsNotEmpty() ? parser.CurrentToken : q.First(); 26 | if (token.ToLower() != StartToken) throw new InvalidProgramException(); 27 | 28 | var c = new ValuesClause(); 29 | var fn = () => 30 | { 31 | q.First(); 32 | if (parser.CurrentToken != "(") throw new InvalidProgramException(); 33 | q.First(); 34 | c.Collection.Add(NamelessItemsParser.Parse(parser.CurrentToken)); 35 | q.First(); 36 | if (parser.CurrentToken != ")") throw new InvalidProgramException(); 37 | q.First(); 38 | }; 39 | 40 | fn(); 41 | while (parser.CurrentToken == ",") 42 | { 43 | fn(); 44 | } 45 | return c; 46 | } 47 | } -------------------------------------------------------------------------------- /src/SqModel/Analysis/WhereClauseParser.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Expression; 2 | using SqModel.Extension; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SqModel.Analysis; 10 | 11 | public static class WhereClauseParser 12 | { 13 | private static string StartToken = "where"; 14 | 15 | public static ConditionClause Parse(string text) 16 | { 17 | using var p = new SqlParser(text); 18 | return Parse(p); 19 | } 20 | 21 | public static ConditionClause Parse(SqlParser parser) 22 | { 23 | var w = new ConditionClause(StartToken); 24 | w.ConditionGroup = ConditionGroupParser.Parse(parser, StartToken); 25 | w.ConditionGroup.IsDecorateBracket = false; 26 | w.ConditionGroup.IsOneLineFormat = false; 27 | return w; 28 | } 29 | } -------------------------------------------------------------------------------- /src/SqModel/Analysis/WithClauseParser.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Extension; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SqModel.Analysis; 9 | 10 | internal static class WithClauseParser 11 | { 12 | private static string StartToken = "with"; 13 | 14 | public static WithClause Parse(SqlParser parser) 15 | { 16 | var q = parser.ReadTokensWithoutComment(); 17 | var startToken = parser.CurrentToken.IsNotEmpty() ? parser.CurrentToken : q.First(); 18 | 19 | if (startToken.ToLower() != StartToken) throw new SyntaxException($"First token must be '{StartToken}'."); 20 | q.First(); //skip start token token 21 | 22 | var w = new WithClause(); 23 | 24 | w.Collection.Add(ParseSelecItem(parser)); 25 | 26 | while (parser.CurrentToken == ",") 27 | { 28 | q.First();//skip ',' token 29 | w.Collection.Add(ParseSelecItem(parser)); 30 | } 31 | 32 | return w; 33 | } 34 | 35 | private static CommonTable ParseSelecItem(SqlParser parser) 36 | { 37 | var q = parser.ReadTokensWithoutComment(); 38 | 39 | var c = new CommonTable(); 40 | 41 | c.Name = parser.CurrentToken; 42 | q.First(); 43 | 44 | if (parser.CurrentToken == "(") 45 | { 46 | q.First(); 47 | var items = NamelessItemsParser.Parse(parser.CurrentToken); 48 | c.ColumnNames = items.Collection.Select(x => x.ToQuery().CommandText).ToList(); 49 | q.First(); 50 | if (parser.CurrentToken != ")") throw new InvalidProgramException(); 51 | q.First(); 52 | } 53 | 54 | if (parser.CurrentToken.ToLower() == "as") q.First(); // skip 'as' token 55 | 56 | while (parser.CurrentToken != "(") 57 | { 58 | c.Keywords.Add(parser.CurrentToken); 59 | q.First(); 60 | } 61 | q.First(); // skip '(' token 62 | 63 | c.Query = SqlParser.Parse(parser.CurrentToken); 64 | 65 | q.First(); // skip sqltext token 66 | 67 | if (parser.CurrentToken != ")") throw new SyntaxException("with clauese syntax error."); 68 | 69 | q.First(); // skip ')' token 70 | 71 | return c; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/SqModel/ColumnValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel; 8 | 9 | public class ColumnValue : IValueClause 10 | { 11 | /// 12 | /// Table name. 13 | /// Specify an alias for the table clause. 14 | /// 15 | public string Table { get; set; } = string.Empty; 16 | 17 | /// 18 | /// Value name. 19 | /// 20 | public string Column { get; set; } = string.Empty; 21 | 22 | public string Conjunction { get; set; } = string.Empty; 23 | 24 | public void AddParameter(string name, object? value) 25 | => throw new NotSupportedException(); 26 | 27 | public Query ToQuery() 28 | => new Query() { CommandText = $"{Table}.{Column}" }.InsertToken(Conjunction); 29 | 30 | public string GetName() => Column; 31 | } -------------------------------------------------------------------------------- /src/SqModel/CommandValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel; 8 | 9 | public class CommandValue : IValueClause 10 | { 11 | /// 12 | /// Value name. 13 | /// 14 | public string CommandText { get; set; } = string.Empty; 15 | 16 | public Dictionary? Parameters { get; set; } = null; 17 | 18 | public string Conjunction { get; set; } = string.Empty; 19 | 20 | public void AddParameter(string name, object? value) 21 | { 22 | Parameters ??= new(); 23 | Parameters.Add(name, value); 24 | } 25 | 26 | public string GetName() => CommandText; 27 | 28 | public Query ToQuery() 29 | { 30 | var q = new Query() { CommandText = CommandText, Parameters = Parameters ?? new() }; 31 | q = q.InsertToken(Conjunction); 32 | return q; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/SqModel/CommonTable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using SqModel.Extension; 7 | 8 | namespace SqModel; 9 | 10 | public class CommonTable : IQueryable 11 | { 12 | public SelectQuery Query { get; set; } = new(); 13 | 14 | public List Keywords { get; set; } = new(); 15 | 16 | public string Name { get; set; } = string.Empty; 17 | 18 | public List ColumnNames { get; set; } = new(); 19 | 20 | public Query ToQuery() 21 | { 22 | Query.IsIncludeCte = false; 23 | 24 | var q = Query.ToQuery(); 25 | var cols = (!ColumnNames.Any()) ? String.Empty : $"({ColumnNames.ToString(", ")})"; 26 | 27 | if (Keywords.Any()) 28 | { 29 | q.CommandText = $"{Name}{cols} as {Keywords.ToString(" ")} (\r\n{q.CommandText.InsertIndent()}\r\n)"; 30 | } 31 | else 32 | { 33 | q.CommandText = $"{Name}{cols} as (\r\n{q.CommandText.InsertIndent()}\r\n)"; 34 | } 35 | 36 | return q; 37 | } 38 | } 39 | 40 | public static class CommonTableExtension 41 | { 42 | public static CommonTable As(this CommonTable source, string name) 43 | { 44 | source.Name = name; 45 | return source; 46 | } 47 | } -------------------------------------------------------------------------------- /src/SqModel/Condition.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using SqModel.Expression; 3 | using SqModel.Extension; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace SqModel; 11 | 12 | public class Condition : IQueryable, ICondition 13 | { 14 | public string Operator { get; set; } = "and"; 15 | 16 | public string SubOperator { get; set; } = string.Empty; 17 | 18 | public string LeftTable { get; set; } = string.Empty; 19 | 20 | public string RightTable { get; set; } = string.Empty; 21 | 22 | public ILogicalExpression? Expression { get; set; } = null; 23 | 24 | public Query ToQuery() 25 | { 26 | if (Expression == null) throw new InvalidProgramException(); 27 | var q = Expression.ToQuery(); 28 | if (SubOperator.IsNotEmpty()) q = q.InsertToken(SubOperator); 29 | return q; 30 | } 31 | } 32 | 33 | public static class ConditionExtension 34 | { 35 | public static Condition And(this Condition source) 36 | => source.SetOperator("and"); 37 | 38 | public static Condition Or(this Condition source) 39 | => source.SetOperator("or"); 40 | 41 | internal static Condition SetOperator(this Condition source, string @operator) 42 | { 43 | source.Operator = @operator; 44 | return source; 45 | } 46 | 47 | internal static Condition SetOperator(this Condition source, string @operator, string suboperator) 48 | { 49 | source.Operator = @operator; 50 | source.SubOperator = suboperator; 51 | return source; 52 | } 53 | 54 | public static Condition Not(this Condition source) 55 | => source.SetSubOperator("not"); 56 | 57 | private static Condition SetSubOperator(this Condition source, string suboperator) 58 | { 59 | source.SubOperator = suboperator; 60 | return source; 61 | } 62 | 63 | public static void Equal(this Condition source, string column) 64 | { 65 | source.SetLeftValue(ValueBuilder.Create(source.LeftTable, column)).Equal(source.RightTable, column); 66 | } 67 | 68 | public static void Equal(this Condition source, TableClause left, TableClause right, string column) 69 | { 70 | source.SetLeftValue(ValueBuilder.Create(left, column)).Equal(right, column); 71 | } 72 | 73 | public static void Equal(this Condition source, string left, string right, string column) 74 | { 75 | source.SetLeftValue(ValueBuilder.Create(left, column)).Equal(right, column); 76 | } 77 | 78 | public static LogicalExpression Value(this Condition source, object commandtext) 79 | => source.SetLeftValue(ValueBuilder.Create(commandtext)); 80 | 81 | public static LogicalExpression Column(this Condition source, TableClause table, string column) 82 | => source.SetLeftValue(ValueBuilder.Create(table, column)); 83 | 84 | public static LogicalExpression Column(this Condition source, string table, string column) 85 | => source.SetLeftValue(ValueBuilder.Create(table, column)); 86 | 87 | internal static LogicalExpression SetLeftValue(this Condition source, IValueClause value) 88 | { 89 | var c = new LogicalExpression() { Left = value }; 90 | source.Expression = c; 91 | return c; 92 | } 93 | } -------------------------------------------------------------------------------- /src/SqModel/ConditionClause.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using SqModel; 7 | using SqModel.Extension; 8 | 9 | namespace SqModel; 10 | 11 | public class ConditionClause 12 | { 13 | public ConditionClause(string token) 14 | { 15 | Token = token; 16 | } 17 | 18 | public string Token { get; init; } 19 | 20 | public ConditionGroup ConditionGroup { get; set; } = new() { IsOneLineFormat = false, IsDecorateBracket = false }; 21 | 22 | public bool IsOneLineFormat { get; set; } = false; 23 | 24 | public Query ToQuery() 25 | { 26 | var q = new Query(); 27 | q = ConditionGroup.ToQuery(); 28 | if (q.IsEmpty()) return q; 29 | 30 | if (IsOneLineFormat) 31 | { 32 | q.CommandText = $"{Token} {q.CommandText}"; 33 | } 34 | else 35 | { 36 | q.CommandText = $"{Token}\r\n{q.CommandText.InsertIndent()}"; 37 | } 38 | return q; 39 | } 40 | } -------------------------------------------------------------------------------- /src/SqModel/ConditionGroup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel; 8 | 9 | public class ConditionGroup : IQueryable, ICondition 10 | { 11 | public string Operator { get; set; } = "and"; 12 | 13 | public string SubOperator { get; set; } = ""; 14 | 15 | public List Collection { get; } = new(); 16 | 17 | public string LeftTable { get; set; } = string.Empty; 18 | 19 | public string RightTable { get; set; } = string.Empty; 20 | 21 | public bool IsDecorateBracket { get; set; } = true; 22 | 23 | public bool IsOneLineFormat { get; set; } = true; 24 | 25 | public Query ToQuery() 26 | { 27 | var q = new Query(); 28 | Collection.ForEach(x => 29 | { 30 | var splitter = " "; 31 | if (IsOneLineFormat == false && x.Operator.ToLower() == "and") splitter = "\r\n"; 32 | q = q.Merge(x.ToQuery(), $"{splitter}{x.Operator} "); 33 | }); 34 | if (IsDecorateBracket) q = q.DecorateBracket(); 35 | return q; 36 | } 37 | } 38 | 39 | public static class ConditionGroupExtension 40 | { 41 | public static Condition Add(this ConditionGroup source) 42 | { 43 | var c = new Condition() { LeftTable = source.LeftTable, RightTable = source.RightTable }; 44 | source.Collection.Add(c); 45 | return c; 46 | } 47 | 48 | public static void AddGroup(this ConditionGroup source, Action action) 49 | { 50 | var c = new ConditionGroup() { LeftTable = source.LeftTable, RightTable = source.RightTable }; 51 | source.Collection.Add(c); 52 | action(c); 53 | } 54 | 55 | public static ConditionGroup And(this ConditionGroup source) 56 | => source.SetOperator("and"); 57 | 58 | public static ConditionGroup Or(this ConditionGroup source) 59 | => source.SetOperator("or"); 60 | 61 | public static ConditionGroup Not(this ConditionGroup source) 62 | => source.SetSubOperator("not"); 63 | 64 | private static ConditionGroup SetSubOperator(this ConditionGroup source, string suboperator) 65 | { 66 | source.SubOperator = suboperator; 67 | return source; 68 | } 69 | 70 | internal static ConditionGroup SetOperator(this ConditionGroup source, string @operator) 71 | { 72 | source.Operator = @operator; 73 | return source; 74 | } 75 | 76 | internal static ConditionGroup SetOperator(this ConditionGroup source, string @operator, string suboperator) 77 | { 78 | source.Operator = @operator; 79 | source.SubOperator = suboperator; 80 | return source; 81 | } 82 | } -------------------------------------------------------------------------------- /src/SqModel/CreateTableQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel; 8 | 9 | public class CreateTableQuery 10 | { 11 | public string TableName { get; set; } = string.Empty; 12 | 13 | public bool IsTemporary { get; set; } = false; 14 | 15 | public SelectQuery SelectQuery { get; set; } = new(); 16 | 17 | public Query ToQuery() 18 | { 19 | var q = SelectQuery.ToQuery(); 20 | var tmp = (IsTemporary) ? "temporary " : ""; 21 | q.CommandText = $"create {tmp}table {TableName}\r\nas\r\n{q.CommandText}"; 22 | return q; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/SqModel/CreateViewQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel; 8 | 9 | public class CreateViewQuery 10 | { 11 | public string ViewName { get; set; } = string.Empty; 12 | 13 | public bool IsTemporary { get; set; } = false; 14 | 15 | public SelectQuery SelectQuery { get; set; } = new(); 16 | 17 | public Query ToQuery() 18 | { 19 | var q = SelectQuery.ToQuery(); 20 | var tmp = (IsTemporary) ? "temporary " : ""; 21 | q.CommandText = $"create {tmp}view {ViewName}\r\nas\r\n{q.CommandText}"; 22 | return q; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/SqModel/Expression/CaseExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel.Expression; 8 | 9 | public class CaseExpression : IValueClause 10 | { 11 | public IValueClause? Value { get; set; } = null; 12 | 13 | public List Collection { get; set; } = new(); 14 | 15 | private static string PrefixToken { get; set; } = "case"; 16 | 17 | private static string SufixToken { get; set; } = "end"; 18 | 19 | public string Conjunction { get; set; } = string.Empty; 20 | 21 | public Query ToQuery() 22 | { 23 | var q = Value?.ToQuery(); 24 | q ??= new(); 25 | Collection.ForEach(x => q = q.Merge(x.ToQuery())); 26 | q = q.Decorate(PrefixToken, SufixToken).InsertToken(Conjunction); 27 | return q; 28 | } 29 | 30 | public void AddParameter(string name, object? value) 31 | => throw new NotSupportedException(); 32 | 33 | public string GetName() => string.Empty; 34 | } 35 | 36 | public static class CaseExpressionExtension 37 | { 38 | public static CaseValuePair Add(this CaseExpression source) 39 | { 40 | var c = new CaseValuePair(); 41 | source.Collection.Add(c); 42 | return c; 43 | } 44 | } -------------------------------------------------------------------------------- /src/SqModel/Expression/CaseValuePair.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel.Expression; 8 | 9 | public class CaseValuePair : IThenCommand 10 | { 11 | public IValueClause? WhenValue { get; set; } = null; 12 | 13 | public IValueClause? ThenValue { get; set; } = null; 14 | 15 | private static string PrefixToken { get; set; } = "when"; 16 | 17 | private static string SufixToken { get; set; } = "then"; 18 | 19 | private static string OmitToken { get; set; } = "else"; 20 | 21 | public Query ToQuery() 22 | { 23 | if (ThenValue == null) throw new InvalidProgramException(); 24 | 25 | Query? q = null; 26 | 27 | // when value then 28 | if (WhenValue != null) q = WhenValue.ToQuery().Decorate(PrefixToken, SufixToken); 29 | // else 30 | else q = new Query() { CommandText = OmitToken }; 31 | 32 | // ... value 33 | q = q.Merge(ThenValue.ToQuery()); 34 | return q; 35 | } 36 | } 37 | 38 | public static class CaseValuePairExtension 39 | { 40 | public static CaseValuePair When(this CaseValuePair source, TableClause table, string column) 41 | => source.When(ValueBuilder.Create(table, column)); 42 | 43 | public static CaseValuePair When(this CaseValuePair source, string table, string column) 44 | => source.When(ValueBuilder.Create(table, column)); 45 | 46 | public static CaseValuePair When(this CaseValuePair source, object commandtext) 47 | => source.When(ValueBuilder.Create(commandtext)); 48 | 49 | public static CaseValuePair When(this CaseValuePair source, IValueClause value) 50 | { 51 | source.WhenValue = value; 52 | return source; 53 | } 54 | } 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/SqModel/Expression/CaseWhenExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel.Expression; 8 | 9 | public class CaseWhenExpression : IValueClause 10 | { 11 | public List Collection { get; set; } = new(); 12 | 13 | internal static string PrefixToken { get; set; } = "case"; 14 | 15 | internal static string SufixToken { get; set; } = "end"; 16 | 17 | public string Conjunction { get; set; } = string.Empty; 18 | 19 | public Query ToQuery() 20 | { 21 | var q = new Query(); 22 | Collection.ForEach(x => q = q.Merge(x.ToQuery())); 23 | q = q.Decorate(PrefixToken, SufixToken).InsertToken(Conjunction); 24 | return q; 25 | } 26 | 27 | public void AddParameter(string name, object? value) 28 | => throw new NotSupportedException(); 29 | 30 | public string GetName() => string.Empty; 31 | } 32 | 33 | public static class CaseWhenExpressionExtension 34 | { 35 | public static CaseWhenValuePair Add(this CaseWhenExpression source) 36 | { 37 | var c = new CaseWhenValuePair(); 38 | source.Collection.Add(c); 39 | return c; 40 | } 41 | } -------------------------------------------------------------------------------- /src/SqModel/Expression/CaseWhenValuePair.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel.Expression; 8 | 9 | public class CaseWhenValuePair : IThenCommand 10 | { 11 | public ICondition? WhenExpression { get; set; } = null; 12 | 13 | public IValueClause? ThenValue { get; set; } = null; 14 | 15 | internal static string PrefixToken { get; set; } = "when"; 16 | 17 | internal static string SufixToken { get; set; } = "then"; 18 | 19 | internal static string OmitToken { get; set; } = "else"; 20 | 21 | public Query ToQuery() 22 | { 23 | if (ThenValue == null) throw new InvalidProgramException(); 24 | 25 | Query? q = null; 26 | 27 | // when condition then 28 | if (WhenExpression != null) q = WhenExpression.ToQuery().Decorate(PrefixToken, SufixToken); 29 | // else 30 | else q = new Query() { CommandText = OmitToken }; 31 | 32 | // ... value 33 | q = q.Merge(ThenValue.ToQuery()); 34 | return q; 35 | } 36 | } 37 | 38 | public static class CaseWhenValuePairExtension 39 | { 40 | public static CaseWhenValuePair When(this CaseWhenValuePair source, Action action) 41 | { 42 | var c = new Condition(); 43 | source.WhenExpression = c; 44 | action(c); 45 | return source; 46 | } 47 | 48 | public static CaseWhenValuePair WhenGroup(this CaseWhenValuePair source, Action action) 49 | { 50 | var c = new ConditionGroup(); 51 | source.WhenExpression = c; 52 | action(c); 53 | return source; 54 | } 55 | 56 | public static CaseWhenValuePair When(this CaseWhenValuePair source, LogicalExpression expression) 57 | { 58 | var c = new Condition() { Expression = expression }; 59 | source.WhenExpression = c; 60 | return source; 61 | } 62 | 63 | public static CaseWhenValuePair When(this CaseWhenValuePair source, ICondition condition) 64 | { 65 | source.WhenExpression = condition; 66 | return source; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/SqModel/Expression/ConcatExpression.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Extension; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SqModel.Expression; 9 | 10 | public class ConcatExpression : IValueClause 11 | { 12 | public List Collection { get; } = new(); 13 | 14 | public string FunctionToken { get; set; } = "concat"; 15 | 16 | public string SplitToken { get; set; } = ","; 17 | 18 | public string Conjunction { get; set; } = String.Empty; 19 | 20 | public void AddParameter(string name, object? value) 21 | => throw new NotSupportedException(); 22 | 23 | public Query ToQuery() 24 | { 25 | var q = new Query(); 26 | Collection.ForEach(x => q = q.Merge(x.ToQuery(), SplitToken)); 27 | q.DecorateBracket().InsertToken(FunctionToken, ""); 28 | return q; 29 | } 30 | 31 | public string GetName() => string.Empty; 32 | } 33 | 34 | public static class ConcatExpressionExtension 35 | { 36 | public static ValueContainer Add(this ConcatExpression source) 37 | { 38 | var c = new ValueContainer(); 39 | source.Collection.Add(c); 40 | return c; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/SqModel/Expression/ConditionExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel.Expression; 8 | 9 | public static class ConditionExtension 10 | { 11 | 12 | public static LogicalExpression CaseWhen(this Condition source, Action action) 13 | { 14 | var c = new CaseWhenExpression(); 15 | action(c); 16 | return source.SetLeftValue(c); 17 | } 18 | 19 | public static LogicalExpression Case(this Condition source, TableClause table, string column, Action action) 20 | => source.Case(ValueBuilder.Create(table, column), action); 21 | 22 | public static LogicalExpression Case(this Condition source, string table, string column, Action action) 23 | => source.Case(ValueBuilder.Create(table, column), action); 24 | 25 | public static LogicalExpression Case(this Condition source, string commandtext, Action action) 26 | => source.Case(ValueBuilder.Create(commandtext), action); 27 | 28 | public static LogicalExpression Case(this Condition source, IValueClause value, Action action) 29 | { 30 | var c = new CaseExpression() { Value = value }; 31 | action(c); 32 | return source.SetLeftValue(c); 33 | } 34 | 35 | public static void Exists(this Condition source, Action action) 36 | { 37 | var q = new SelectQuery(); 38 | q.IsOneLineFormat = true; 39 | 40 | var c = new ExistsExpression() { Query = q }; 41 | source.Expression = c; 42 | action(q); 43 | } 44 | 45 | public static void Exists(this Condition source, SelectQuery query) 46 | { 47 | var c = new ExistsExpression() { Query = query }; 48 | source.Expression = c; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/SqModel/Expression/ExistsExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel.Expression; 8 | 9 | public class ExistsExpression : ILogicalExpression 10 | { 11 | public SelectQuery? Query { get; set; } = null; 12 | 13 | public Query ToQuery() 14 | { 15 | if (Query == null) throw new InvalidProgramException(); 16 | Query.IsIncludeCte = false; 17 | //Query.IsOneLineFormat = true; 18 | return Query.ToQuery().DecorateBracket().InsertToken("exists"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/SqModel/Expression/IThenCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel.Expression; 8 | 9 | public interface IThenCommand 10 | { 11 | IValueClause? ThenValue { get; set; } 12 | } 13 | 14 | public static class IThenCommandExtension 15 | { 16 | public static void Then(this IThenCommand source, TableClause table, string column) 17 | => source.Then(ValueBuilder.Create(table, column)); 18 | 19 | public static void Then(this IThenCommand source, string table, string column) 20 | => source.Then(ValueBuilder.Create(table, column)); 21 | 22 | public static void Then(this IThenCommand source, object commandtext) 23 | => source.Then(ValueBuilder.Create(commandtext)); 24 | 25 | public static void Then(this IThenCommand source, IValueClause value) 26 | => source.ThenValue = value; 27 | 28 | public static void Then(this IThenCommand source, bool value) 29 | => source.Then(ValueBuilder.Create(value)); 30 | 31 | public static void ThenNull(this IThenCommand source) 32 | => source.Then(ValueBuilder.GetNullValue()); 33 | 34 | public static void Else(this IThenCommand source, TableClause table, string column) 35 | => source.Then(ValueBuilder.Create(table, column)); 36 | 37 | public static void Else(this IThenCommand source, string table, string column) 38 | => source.Then(ValueBuilder.Create(table, column)); 39 | 40 | public static void Else(this IThenCommand source, object commandtext) 41 | => source.Then(ValueBuilder.Create(commandtext)); 42 | 43 | public static void Else(this IThenCommand source, IValueClause value) 44 | => source.ThenValue = value; 45 | 46 | public static void Else(this IThenCommand source, bool value) 47 | => source.Then(ValueBuilder.Create(value)); 48 | 49 | public static void ElseNull(this IThenCommand source) 50 | => source.Then(ValueBuilder.GetNullValue()); 51 | } -------------------------------------------------------------------------------- /src/SqModel/Expression/IValueContainerExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel.Expression; 8 | 9 | public static class IValueContainerExtension 10 | { 11 | public static IValueContainer Case(this IValueContainer source, TableClause table, string column, Action action) 12 | => source.Case(ValueBuilder.Create(table, column), action); 13 | 14 | public static IValueContainer Case(this IValueContainer source, string table, string column, Action action) 15 | => source.Case(ValueBuilder.Create(table, column), action); 16 | 17 | public static IValueContainer Case(this IValueContainer source, string commandtext, Action action) 18 | => source.Case(ValueBuilder.Create(commandtext), action); 19 | 20 | public static IValueContainer Case(this IValueContainer source, IValueClause value, Action action) 21 | { 22 | var c = new CaseExpression() { Value = value }; 23 | source.Command = c; 24 | action(c); 25 | return source; 26 | } 27 | 28 | public static IValueContainer CaseWhen(this IValueContainer source, Action action) 29 | { 30 | var c = new CaseWhenExpression(); 31 | source.Command = c; 32 | action(c); 33 | return source; 34 | } 35 | 36 | public static IValueContainer Strings(this IValueContainer source, Action action) 37 | { 38 | var c = new StringsExpression(); 39 | source.Command = c; 40 | action(c); 41 | return source; 42 | } 43 | 44 | public static IValueContainer Concat(this IValueContainer source, Action action) 45 | { 46 | var c = new ConcatExpression(); 47 | source.Command = c; 48 | action(c); 49 | return source; 50 | } 51 | 52 | public static IValueContainer Concat(this IValueContainer source, string func, string split, Action action) 53 | { 54 | var c = new ConcatExpression(); 55 | source.Command = c; 56 | c.FunctionToken = func; 57 | c.SplitToken = split; 58 | action(c); 59 | return source; 60 | } 61 | } -------------------------------------------------------------------------------- /src/SqModel/Expression/StringCollectionExpression.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Extension; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SqModel.Expression; 9 | 10 | public class StringsExpression : IValueClause 11 | { 12 | public List Collection { get; } = new(); 13 | 14 | public string Conjunction { get; set; } = String.Empty; 15 | 16 | public void AddParameter(string name, object? value) 17 | => throw new NotSupportedException(); 18 | 19 | public Query ToQuery() 20 | { 21 | var q = new Query(); 22 | Collection.ForEach(x => q = q.Merge(x.ToQuery(), " || ")); 23 | return q; 24 | } 25 | 26 | public string GetName() => string.Empty; 27 | } 28 | 29 | public static class StringsExpressionExtension 30 | { 31 | public static ValueContainer Add(this StringsExpression source) 32 | { 33 | var c = new ValueContainer(); 34 | source.Collection.Add(c); 35 | return c; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/SqModel/Expression/ValueExpression.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Extension; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SqModel.Expression; 9 | 10 | public class ValueExpression : IValueClause, ICondition 11 | { 12 | public List Collection { get; } = new(); 13 | 14 | public string Conjunction { get; set; } = String.Empty; 15 | 16 | public string Operator { get; set; } = String.Empty; 17 | 18 | public string SubOperator { get; set; } = String.Empty; 19 | 20 | public void AddParameter(string name, object? value) 21 | => throw new NotSupportedException(); 22 | 23 | public Query ToQuery() 24 | { 25 | var q = new Query(); 26 | Collection.ForEach(x => q = q.Merge(x.ToQuery())); 27 | if (Conjunction.IsNotEmpty()) 28 | { 29 | q.InsertToken(Conjunction); 30 | } 31 | else 32 | { 33 | q.InsertToken(SubOperator); 34 | //q.InsertToken(Operator); 35 | } 36 | return q; 37 | } 38 | 39 | public string GetName() => string.Empty; 40 | } -------------------------------------------------------------------------------- /src/SqModel/Extension/DictionaryExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel.Extension; 8 | 9 | internal static class DictionaryExtension 10 | { 11 | public static void ForEach(this Dictionary source, Action> action) where T1 : notnull 12 | { 13 | foreach (var x in source) action(x); 14 | } 15 | 16 | public static Dictionary Merge(this Dictionary source, Dictionary? dic) where T1 : notnull 17 | { 18 | if (dic == null) return source; 19 | dic.ForEach(x => source[x.Key] = x.Value); 20 | return source; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/SqModel/Extension/IEnumerableExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel.Extension; 8 | 9 | internal static class IEnumerableExtension 10 | { 11 | public static bool Contains(this IEnumerable source, string? value) 12 | { 13 | if (value == null || value.IsEmpty()) return false; 14 | if (value.Length != 1) return false; 15 | return source.Contains(value.First()); 16 | } 17 | 18 | public static string ToString(this IEnumerable source, string separator) 19 | { 20 | var sb = new StringBuilder(); 21 | var prev = string.Empty; 22 | 23 | var fn = (string? current) => 24 | { 25 | if (prev == string.Empty) return false; 26 | if (prev == "(") return false; 27 | if (current == ")") return false; 28 | if (prev == ".") return false; 29 | if (current == ".") return false; 30 | 31 | return true; 32 | }; 33 | 34 | foreach (var item in source) 35 | { 36 | if (item == null || item.ToString().IsEmpty()) return sb.ToString(); 37 | if (fn(item.ToString())) sb.Append(separator); 38 | prev = item.ToString(); 39 | sb.Append(prev); 40 | } 41 | return sb.ToString(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/SqModel/Extension/charExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using SqModel.Analysis; 8 | using SqModel.Extension; 9 | 10 | namespace SqModel.Extension; 11 | 12 | internal static class charExtension 13 | { 14 | public static bool Contains(this char[]? source, char? item) 15 | { 16 | if (source == null) return false; 17 | if (item == null) return false; 18 | return source.Where(x => x == item).Any(); 19 | } 20 | 21 | public static bool IsSpace(this char source) 22 | => SqlParser.SpaceTokens.Where(x => x == source).Any(); 23 | 24 | public static bool IsSpace(this char? source) 25 | => source == null ? false : source.Value.IsSpace(); 26 | 27 | public static bool IsSymbol(this char source) 28 | => SqlParser.SymbolTokens.Where(x => x == source).Any(); 29 | 30 | public static bool IsSymbol(this char? source) 31 | => source == null ? false : source.Value.IsSymbol(); 32 | 33 | public static bool IsNumeric(this char? source) 34 | => source == null ? false : "0123456789".Any(source.Value); 35 | } 36 | -------------------------------------------------------------------------------- /src/SqModel/Extension/intExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel.Extension; 8 | 9 | internal static class intExtensions 10 | { 11 | public static void ForEach(this int source, Action action) 12 | { 13 | for (int i = 0; i < source; i++) action(i); 14 | } 15 | 16 | public static string ToSpaceString(this int source) 17 | { 18 | var space = string.Empty; 19 | source.ForEach(x => space += " "); 20 | return space; 21 | } 22 | } -------------------------------------------------------------------------------- /src/SqModel/Extension/stringExtension.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Analysis; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SqModel.Extension; 9 | 10 | public static class stringExtension 11 | { 12 | public static string InsertIndent(this string source, string separator = "\r\n", int spaceCount = 4) 13 | { 14 | if (source.IsEmpty()) return source; 15 | 16 | var indent = spaceCount.ToSpaceString(); 17 | 18 | return $"{indent}{source.Replace(separator, $"{separator}{indent}")}"; 19 | } 20 | 21 | public static bool IsEmpty(this string? source) 22 | => string.IsNullOrEmpty(source); 23 | 24 | public static bool IsNotEmpty(this string? source) 25 | => !string.IsNullOrEmpty(source); 26 | 27 | public static Dictionary ToDictionary(this List source) 28 | { 29 | var dic = new Dictionary(); 30 | source.ForEach(x => dic.Add(x, x)); 31 | return dic; 32 | } 33 | 34 | public static RelationTypes ToRelationType(this string source) 35 | { 36 | if (source.IsFromRealtion()) return RelationTypes.From; 37 | else if (source.IsInnerJoinRealtion()) return RelationTypes.Inner; 38 | else if (source.IsLeftJoinRelation()) return RelationTypes.Left; 39 | else if (source.IsRightJoinRealtion()) return RelationTypes.Right; 40 | else if (source.IsCrossJoinRealtion()) return RelationTypes.Cross; 41 | return RelationTypes.Undefined; 42 | } 43 | 44 | public static bool IsFromRealtion(this string source) 45 | => SqlParser.FromTokens.Where(x => x == source.ToLower()).Any(); 46 | 47 | public static bool IsInnerJoinRealtion(this string source) 48 | => SqlParser.InnerJoinTokens.Where(x => x == source.ToLower()).Any(); 49 | 50 | public static bool IsLeftJoinRelation(this string source) 51 | => SqlParser.LeftJoinTokens.Where(x => x == source.ToLower()).Any(); 52 | 53 | public static bool IsRightJoinRealtion(this string source) 54 | => SqlParser.RightJoinTokens.Where(x => x == source.ToLower()).Any(); 55 | 56 | public static bool IsCrossJoinRealtion(this string source) 57 | => SqlParser.CrossJoinTokens.Where(x => x == source.ToLower()).Any(); 58 | 59 | public static bool Any(this IEnumerable source, string token) 60 | => source.Where(x => x == token.ToLower()).Any(); 61 | 62 | public static bool Any(this string source, char token) 63 | => source.ToArray().Where(x => x == token).Any(); 64 | 65 | public static bool IsEof(this int source) 66 | => source < 0; 67 | 68 | public static bool IsLogicalOperator(this string source) 69 | => SqlParser.LogicalOperatorTokens.Where(x => x == source.ToLower()).Any(); 70 | 71 | public static bool IsLetter(this string source) 72 | { 73 | if (source.Length == 0) return false; 74 | var c = source.ToLower().ToCharArray().First(); 75 | return SqlParser.LetterChars.Where(x => x == c).Any(); 76 | } 77 | 78 | public static bool IsWord(this string source) 79 | { 80 | if (source.Length == 0) return false; 81 | return !(source.ToCharArray().ToList().Where(x => !SqlParser.LetterChars.Contains(x)).Any()); 82 | } 83 | 84 | public static string ToSnakeCase(this string source) 85 | { 86 | var sb = new StringBuilder(); 87 | source.ToList().ForEach(x => 88 | { 89 | if (sb.Length != 0 && x.ToString() == x.ToString().ToUpper()) sb.Append("_"); 90 | sb.Append(x); 91 | }); 92 | return sb.ToString(); 93 | } 94 | 95 | public static bool IsConjunction(this string source) 96 | { 97 | var strings = new[] { "||", "=", "!=", "<>", ">=", "<=", "and", "or", "is" }.ToList(); 98 | if (SqlParser.ArithmeticOperatorTokens.Contains(source) || strings.Contains(source)) return true; 99 | return false; 100 | } 101 | } -------------------------------------------------------------------------------- /src/SqModel/ICondition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel; 8 | 9 | public interface ICondition : IQueryable 10 | { 11 | string Operator { get; set; } 12 | 13 | string SubOperator { get; set; } 14 | } -------------------------------------------------------------------------------- /src/SqModel/ILogicalExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel; 8 | 9 | public interface ILogicalExpression : IQueryable 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/SqModel/IQueryable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel; 8 | 9 | public interface IQueryable 10 | { 11 | Query ToQuery(); 12 | } 13 | -------------------------------------------------------------------------------- /src/SqModel/IValueClause.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel; 8 | 9 | public interface IValueClause : IQueryable 10 | { 11 | string Conjunction { get; set; } 12 | 13 | void AddParameter(string name, object? value); 14 | 15 | string GetName(); 16 | } 17 | 18 | public static class IValueClauseExtension 19 | { 20 | public static IValueClause Conjunction(this IValueClause source, string sign) 21 | { 22 | source.Conjunction = sign; 23 | return source; 24 | } 25 | 26 | public static IValueClause Parameter(this IValueClause source, string key, object value) 27 | { 28 | source.AddParameter(key, value); 29 | return source; 30 | } 31 | } -------------------------------------------------------------------------------- /src/SqModel/IValueContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel; 8 | 9 | public interface IValueContainer : IQueryable 10 | { 11 | public IValueClause? Command { get; set; } 12 | 13 | string ColumnName { set; } 14 | 15 | string Name { set; } 16 | } 17 | 18 | public static class IValueContainerExtension 19 | { 20 | public static IValueContainer Value(this IValueContainer source, object commandtext) 21 | => source.Value(ValueBuilder.Create(commandtext)); 22 | 23 | public static IValueContainer Column(this IValueContainer source, TableClause table, string column) 24 | => source.Value(ValueBuilder.Create(table, column), column); 25 | 26 | public static IValueContainer Column(this IValueContainer source, string table, string column) 27 | => source.Value(ValueBuilder.Create(table, column), column); 28 | 29 | public static IValueContainer Null(this IValueContainer source) 30 | => source.Value(ValueBuilder.GetNullValue()); 31 | 32 | public static IValueContainer NotNull(this IValueContainer source) 33 | => source.Value(ValueBuilder.GetNotNullValue()); 34 | 35 | public static IValueContainer Value(this IValueContainer source, IValueClause value, string column = "") 36 | { 37 | source.Command = value; 38 | source.ColumnName = column; 39 | if (column != "*") source.Name = column; 40 | return source; 41 | } 42 | 43 | public static IValueContainer As(this IValueContainer source, string name) 44 | { 45 | source.Name = name; 46 | return source; 47 | } 48 | 49 | [Obsolete("use AddParameter")] 50 | public static IValueContainer Parameter(this IValueContainer source, string name, object? value) 51 | => AddParameter(source, name, value); 52 | 53 | public static IValueContainer AddParameter(this IValueContainer source, string name, object? value) 54 | { 55 | if (source.Command == null) throw new InvalidProgramException(); 56 | source.Command.AddParameter(name, value); 57 | return source; 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/SqModel/InsertQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using SqModel.Extension; 7 | 8 | namespace SqModel; 9 | 10 | public class InsertQuery 11 | { 12 | public string TableName { get; set; } = string.Empty; 13 | 14 | public SelectQuery SelectQuery { get; set; } = new(); 15 | 16 | public Query ToQuery() 17 | { 18 | var q = SelectQuery.ToQuery(); 19 | var cols = SelectQuery.Select.GetColumnNames(); 20 | 21 | //If you are using wildcards, omit the column clause. 22 | var coltext = !cols.Any() ? "" : $"({cols.ToString(", ")})"; 23 | 24 | q.CommandText = $"insert into {TableName}{coltext}\r\n{q.CommandText}"; 25 | return q; 26 | } 27 | } -------------------------------------------------------------------------------- /src/SqModel/LogicalExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Common; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SqModel; 9 | 10 | public class LogicalExpression : ILogicalExpression 11 | { 12 | public IValueClause? Left { get; set; } = null; 13 | 14 | public IValueClause? Right { get; set; } = null; 15 | 16 | public virtual Query ToQuery() 17 | { 18 | if (Left == null) throw new InvalidProgramException("Left property is null."); 19 | var q = Left.ToQuery(); 20 | 21 | if (Right == null) return q; 22 | return q.Merge(Right.ToQuery()); 23 | } 24 | } 25 | 26 | public static class LogicalExpressionExtension 27 | { 28 | public static LogicalExpression Value(this LogicalExpression source, object commandtext) 29 | => source.SetLeftValue(ValueBuilder.Create(commandtext)); 30 | 31 | public static LogicalExpression Column(this LogicalExpression source, TableClause table, string column) 32 | => source.SetLeftValue(ValueBuilder.Create(table, column)); 33 | 34 | public static LogicalExpression Column(this LogicalExpression source, string table, string column) 35 | => source.SetLeftValue(ValueBuilder.Create(table, column)); 36 | 37 | private static LogicalExpression SetLeftValue(this LogicalExpression source, IValueClause value) 38 | { 39 | source.Left = value; 40 | return source; 41 | } 42 | 43 | public static void Equal(this LogicalExpression source, TableClause table, string column) 44 | => source.SetRightCommand("=", ValueBuilder.Create(table, column)); 45 | 46 | public static void Equal(this LogicalExpression source, string table, string column) 47 | => source.SetRightCommand("=", ValueBuilder.Create(table, column)); 48 | 49 | public static IValueClause Equal(this LogicalExpression source, object commandtext) 50 | => source.SetRightCommand("=", ValueBuilder.Create(commandtext)); 51 | 52 | public static void NotEqual(this LogicalExpression source, TableClause table, string column) 53 | => source.SetRightCommand("<>", ValueBuilder.Create(table, column)); 54 | 55 | public static void NotEqual(this LogicalExpression source, string table, string column) 56 | => source.SetRightCommand("<>", ValueBuilder.Create(table, column)); 57 | 58 | public static IValueClause NotEqual(this LogicalExpression source, object commandtext) 59 | => source.SetRightCommand("<>", ValueBuilder.Create(commandtext)); 60 | 61 | public static void IsNull(this LogicalExpression source) 62 | => source.SetRightCommand("is", ValueBuilder.GetNullValue()); 63 | 64 | public static void IsNotNull(this LogicalExpression source) 65 | => source.SetRightCommand("is", ValueBuilder.GetNotNullValue()); 66 | 67 | public static void True(this LogicalExpression source) 68 | => source.SetRightCommand("=", ValueBuilder.Create(true)); 69 | 70 | public static void False(this LogicalExpression source) 71 | => source.SetRightCommand("=", ValueBuilder.Create(false)); 72 | 73 | public static IValueClause Comparison(this LogicalExpression source, string @operator, TableClause table, string column) 74 | => source.SetRightCommand(@operator, ValueBuilder.Create(table, column)); 75 | 76 | public static IValueClause Comparison(this LogicalExpression source, string @operator, string table, string column) 77 | => source.SetRightCommand(@operator, ValueBuilder.Create(table, column)); 78 | 79 | public static IValueClause Comparison(this LogicalExpression source, string @operator, object commandtext) 80 | => source.SetRightCommand(@operator, ValueBuilder.Create(commandtext)); 81 | 82 | private static IValueClause SetRightCommand(this LogicalExpression source, string conjunction, IValueClause value) 83 | { 84 | source.Right = value; 85 | source.Right.Conjunction = conjunction; 86 | return source.Right; 87 | } 88 | } -------------------------------------------------------------------------------- /src/SqModel/NamelessItem.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Extension; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SqModel; 9 | 10 | public class NamelessItem : IValueContainer 11 | { 12 | public IValueClause? Command { get; set; } 13 | 14 | public string ColumnName { get; set; } = String.Empty; 15 | 16 | public string Name { get; set; } = String.Empty; 17 | 18 | public Query ToQuery() 19 | { 20 | if (Command == null) throw new InvalidProgramException(); 21 | var q = Command.ToQuery(); 22 | return q; 23 | } 24 | } -------------------------------------------------------------------------------- /src/SqModel/NamelessItemClause.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using SqModel; 7 | using SqModel.Extension; 8 | 9 | namespace SqModel; 10 | 11 | public class NamelessItemClause 12 | { 13 | public NamelessItemClause(string token) 14 | { 15 | Token = token; 16 | } 17 | 18 | private string Token { get; init; } 19 | 20 | public List Collection { get; set; } = new(); 21 | 22 | public bool IsOneLineFormat { get; set; } = true; 23 | 24 | public Query ToQuery() 25 | { 26 | if (!Collection.Any()) return new Query(); 27 | 28 | if (IsOneLineFormat) 29 | { 30 | var q = Collection.Select(x => x.ToQuery()).ToList().ToQuery(", "); 31 | if (Token.IsNotEmpty()) q.CommandText = $"{Token} {q.CommandText}"; 32 | return q; 33 | } 34 | else 35 | { 36 | var q = Collection.Select(x => x.ToQuery()).ToList().ToQuery("\r\n, ").InsertIndent(); 37 | if (Token.IsNotEmpty()) q.CommandText = $"{Token}\r\n{q.CommandText}"; 38 | return q; 39 | } 40 | } 41 | } 42 | 43 | public static class NamelessItemsExtension 44 | { 45 | public static NamelessItem Add(this NamelessItemClause source) 46 | { 47 | var c = new NamelessItem(); 48 | source.Collection.Add(c); 49 | return c; 50 | } 51 | } -------------------------------------------------------------------------------- /src/SqModel/Query.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using SqModel.Extension; 8 | 9 | namespace SqModel; 10 | 11 | public class Query 12 | { 13 | public string CommandText { get; set; } = string.Empty; 14 | 15 | public Dictionary Parameters { get; set; } = new(); 16 | 17 | public string ToDebugString() 18 | { 19 | var sb = new StringBuilder(); 20 | 21 | sb.AppendLine(";"); 22 | sb.AppendLine("--sql"); 23 | if (Parameters.Any()) 24 | { 25 | sb.Append("/*"); 26 | sb.AppendLine(); 27 | Parameters.ForEach(x => 28 | { 29 | var v = x.Value?.ToString(); 30 | v ??= "[NULL]"; 31 | sb.Append($" {x.Key} = {v}"); 32 | sb.AppendLine(); 33 | }); 34 | sb.Append("*/"); 35 | sb.AppendLine(); 36 | } 37 | sb.Append(CommandText); 38 | sb.AppendLine(); 39 | sb.Append(";"); 40 | 41 | return sb.ToString(); 42 | } 43 | } 44 | 45 | internal static class QueryExtension 46 | { 47 | public static Query Merge(this Query source, Query query, string separator = " ") 48 | { 49 | var text = source.CommandText; 50 | if (source.CommandText.IsEmpty()) text = query.CommandText; 51 | else if (query.CommandText.IsNotEmpty()) text += $"{separator}{query.CommandText}"; 52 | 53 | var prm = new Dictionary(); 54 | prm.Merge(source.Parameters); 55 | prm.Merge(query.Parameters); 56 | 57 | return new Query() { CommandText = text, Parameters = prm }; 58 | } 59 | 60 | public static Query ToQuery(this List source, string separator) 61 | { 62 | var text = source.Select(x => x.CommandText).ToList().ToString(separator); 63 | var prm = new Dictionary(); 64 | source.ForEach(x => prm.Merge(x.Parameters)); 65 | 66 | return new Query() { CommandText = text, Parameters = prm }; 67 | } 68 | 69 | public static bool IsEmpty(this Query? source) 70 | => string.IsNullOrEmpty(source?.CommandText); 71 | 72 | public static bool IsNotEmpty(this Query? source) 73 | => !string.IsNullOrEmpty(source?.CommandText); 74 | 75 | public static Query InsertToken(this Query source, string token, string splitter = " ") 76 | { 77 | if (token.IsEmpty()) return source; 78 | source.CommandText = $"{token}{splitter}{source.CommandText}"; 79 | return source; 80 | } 81 | 82 | public static Query AddToken(this Query source, string token, string splitter = " ") 83 | { 84 | if (token.IsEmpty()) return source; 85 | source.CommandText = $"{source.CommandText}{splitter}{token}"; 86 | return source; 87 | } 88 | 89 | public static Query Decorate(this Query source, string prefix, string sufix, string splitter = " ") 90 | { 91 | source.CommandText = $"{prefix}{splitter}{source.CommandText}{splitter}{sufix}"; 92 | return source; 93 | } 94 | 95 | public static Query DecorateBracket(this Query source, string splitter = "") 96 | => source.Decorate("(", ")", splitter); 97 | 98 | public static Query InsertIndent(this Query source, string separator = "\r\n", int spaceCount = 4) 99 | { 100 | source.CommandText = source.CommandText.InsertIndent(separator, spaceCount); 101 | return source; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/SqModel/RelationTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel; 8 | 9 | public enum RelationTypes 10 | { 11 | Undefined = 0, 12 | From = 1, 13 | Inner = 2, 14 | Left = 3, 15 | Right = 4, 16 | Cross = 5, 17 | } -------------------------------------------------------------------------------- /src/SqModel/SelectClause.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using SqModel; 7 | using SqModel.Extension; 8 | 9 | namespace SqModel; 10 | 11 | /// 12 | /// Select column clause. 13 | /// Define a list of columns to get. 14 | /// 15 | public class SelectClause 16 | { 17 | public bool IsDistinct { get; set; } = false; 18 | 19 | public List Collection { get; set; } = new(); 20 | 21 | public bool IsOneLineFormat { get; set; } = true; 22 | 23 | public Query ToQuery() 24 | { 25 | var distinct = IsDistinct ? " distinct" : ""; 26 | 27 | if (IsOneLineFormat) 28 | { 29 | var q = Collection.Select(x => x.ToQuery()).ToList().ToQuery(", "); 30 | q.CommandText = $"select{distinct} {q.CommandText}"; 31 | return q; 32 | } 33 | else 34 | { 35 | var q = Collection.Select(x => x.ToQuery()).ToList().ToQuery("\r\n, ").InsertIndent(); 36 | q.CommandText = $"select{distinct}\r\n{q.CommandText}"; 37 | return q; 38 | } 39 | } 40 | } 41 | 42 | public static class SelectClauseExtension 43 | { 44 | public static void Distinct(this SelectClause source, bool isdistinct = true) 45 | => source.IsDistinct = isdistinct; 46 | 47 | public static List GetColumnNames(this SelectClause source) 48 | { 49 | var lst = new List(); 50 | 51 | source.Collection.ForEach(x => lst.Add(x.Name.IsNotEmpty() ? x.Name : x.ColumnName)); 52 | 53 | if (lst.Where(x => x == "*").Any()) return new(); 54 | return lst; 55 | } 56 | 57 | public static SelectItem Add(this SelectClause source) 58 | { 59 | var c = new SelectItem(); 60 | source.Collection.Add(c); 61 | return c; 62 | } 63 | 64 | public static void AddRange(this SelectClause source, TableClause table, List columns) 65 | { 66 | columns.ForEach(x => 67 | { 68 | var c = new SelectItem(); 69 | c.Column(table, x); 70 | source.Collection.Add(c); 71 | }); 72 | } 73 | 74 | public static void AddRangeOrDefault(this SelectClause source, TableClause table, List columns) 75 | { 76 | var name = source.GetColumnNames(); 77 | var cols = columns.Where(x => !name.Contains(x)).ToList(); 78 | source.AddRange(table, cols); 79 | } 80 | } -------------------------------------------------------------------------------- /src/SqModel/SelectItem.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using SqModel.Expression; 3 | using SqModel.Extension; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace SqModel; 11 | 12 | public class SelectItem : IValueContainer, IQueryable 13 | { 14 | public IValueClause? Command { get; set; } = null; 15 | 16 | public string ColumnName { get; set; } = string.Empty; 17 | 18 | /// 19 | /// Column alias name. 20 | /// 21 | public string Name { get; set; } = string.Empty; 22 | 23 | public Query ToQuery() 24 | { 25 | if (Command == null) throw new InvalidProgramException("Command property is null."); 26 | var q = Command.ToQuery(); 27 | 28 | if (Name.IsNotEmpty() && Name != Command.GetName()) q.AddToken($"as {Name}"); 29 | 30 | return q; 31 | } 32 | 33 | public string ToCommandText() 34 | { 35 | if (Command == null) throw new InvalidProgramException("Command property is null."); 36 | var q = Command.ToQuery(); 37 | return q.CommandText; 38 | } 39 | } 40 | 41 | public static class SelectItemExtension 42 | { 43 | public static void All(this SelectItem source) 44 | => source.Value("*"); 45 | 46 | public static void All(this SelectItem source, TableClause table) 47 | => source.Column(table, "*"); 48 | 49 | public static void All(this SelectItem source, string table) 50 | => source.Column(table, "*"); 51 | 52 | public static SelectItem InlineQuery(this SelectItem source, Action action) 53 | { 54 | var c = new SelectQueryValue() { Query = new() { IsOneLineFormat = true } }; 55 | source.Command = c; 56 | action(c.Query); 57 | return source; 58 | } 59 | } -------------------------------------------------------------------------------- /src/SqModel/SelectQuery.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using SqModel.Extension; 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace SqModel; 11 | 12 | public partial class SelectQuery 13 | { 14 | public TableClause FromClause { get; set; } = new(); 15 | 16 | public SelectClause? SelectClause { get; set; } 17 | 18 | public SelectClause Select => GetSelectClause(); 19 | 20 | private SelectClause GetSelectClause() 21 | { 22 | SelectClause ??= new(); 23 | return SelectClause; 24 | } 25 | 26 | public ValuesClause? ValuesClause { get; set; } = null; 27 | 28 | public ValuesClause Values => GetValueClause(); 29 | 30 | private ValuesClause GetValueClause() 31 | { 32 | ValuesClause ??= new(); 33 | return ValuesClause; 34 | } 35 | 36 | public WithClause WithClause { get; set; } = new(); 37 | 38 | public WithClause With => WithClause; 39 | 40 | public WithClause GetAllWith() 41 | { 42 | var w = new WithClause(); 43 | this.GetCommonTables().ToList().ForEach(x => w.Collection.Add(x)); 44 | 45 | if (UnionClause != null && UnionClause.SelectQuery != null) 46 | { 47 | w.Collection.AddRange(UnionClause.SelectQuery.GetAllWith().Collection); 48 | } 49 | 50 | return w; 51 | } 52 | 53 | public ConditionClause WhereClause { get; set; } = new("where"); 54 | 55 | public ConditionGroup Where => WhereClause.ConditionGroup; 56 | 57 | public NamelessItemClause GroupClause { get; set; } = new("group by"); 58 | 59 | public NamelessItemClause GroupBy => GroupClause; 60 | 61 | public ConditionClause HavingClause { get; set; } = new("having"); 62 | 63 | public ConditionGroup Having => HavingClause.ConditionGroup; 64 | 65 | public UnionClause? UnionClause { get; set; } = null; 66 | 67 | public NamelessItemClause OrderClause { get; set; } = new("order by"); 68 | 69 | public NamelessItemClause OrderBy => OrderClause; 70 | 71 | public bool IsOneLineFormat { get; set; } = false; 72 | 73 | public bool IsIncludeCte { get; set; } = true; 74 | 75 | public bool IsIncludeOrder { get; set; } = true; 76 | 77 | public Dictionary? Parameters = null; 78 | 79 | public Query ToQuery() 80 | { 81 | var splitter = IsOneLineFormat ? " " : "\r\n"; 82 | 83 | if (ValuesClause == null && SelectClause == null) throw new InvalidProgramException(); 84 | if (ValuesClause != null && SelectClause != null) throw new InvalidProgramException(); 85 | 86 | if (ValuesClause != null) return ValuesClause.ToQuery(); 87 | 88 | if (SelectClause == null) throw new InvalidProgramException("Select clause is null"); 89 | SelectClause.IsOneLineFormat = IsOneLineFormat; 90 | WhereClause.IsOneLineFormat = IsOneLineFormat; 91 | OrderClause.IsOneLineFormat = IsOneLineFormat; 92 | GroupClause.IsOneLineFormat = IsOneLineFormat; 93 | 94 | var withQ = (IsIncludeCte) ? GetAllWith().ToQuery() : null; //ex. with a as (...) 95 | var selectQ = SelectClause.ToQuery(); //ex. select column_a, column_b 96 | var fromQ = FromClause.ToQuery(); //ex. from table_a as a inner join table_b as b on a.id = b.id 97 | var whereQ = WhereClause.ToQuery();//ex. where a.id = 1 98 | var groupQ = GroupClause.ToQuery();//ex. group by a.id 99 | var havingQ = HavingClause.ToQuery();//ex. having sum(a.value) = 10 100 | var unionQ = UnionClause?.ToQuery();//ex. union select... 101 | var orderQ = (IsIncludeOrder) ? OrderClause.ToQuery() : null; //ex. order by a.id 102 | 103 | //command text 104 | var sb = new StringBuilder(); 105 | //parameter 106 | var prms = new Dictionary(); 107 | 108 | var append = (Query? query) => 109 | { 110 | if (query == null) return; 111 | prms.Merge(query.Parameters); 112 | if (query.IsNotEmpty()) 113 | { 114 | if (sb.Length != 0) sb.Append(splitter); 115 | sb.Append($"{query.CommandText}"); 116 | } 117 | }; 118 | 119 | append(withQ); 120 | append(selectQ); 121 | append(fromQ); 122 | append(whereQ); 123 | append(groupQ); 124 | append(havingQ); 125 | append(unionQ); 126 | append(orderQ); 127 | 128 | prms.Merge(Parameters); 129 | 130 | return new Query() { CommandText = sb.ToString(), Parameters = prms }; 131 | } 132 | 133 | 134 | } -------------------------------------------------------------------------------- /src/SqModel/SelectQueryFromExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel; 8 | 9 | public static class SelectQueryFromExtension 10 | { 11 | public static TableClause From(this SelectQuery source, CommonTable ct) 12 | { 13 | source.FromClause = new TableClause() { TableName = ct.Name, AliasName = ct.Name, RelationType = RelationTypes.From, Actual = ct.Query }; 14 | return source.FromClause; 15 | } 16 | 17 | public static TableClause From(this SelectQuery source, string tableName) 18 | { 19 | source.FromClause = new TableClause() { TableName = tableName, AliasName = tableName, RelationType = RelationTypes.From }; 20 | return source.FromClause; 21 | } 22 | 23 | public static TableClause From(this SelectQuery source, SelectQuery subquery) 24 | { 25 | source.FromClause = new TableClause() { SubSelectClause = subquery, RelationType = RelationTypes.From, Actual = subquery }; 26 | return source.FromClause; 27 | } 28 | 29 | public static TableClause From(this SelectQuery source, Action action) 30 | { 31 | var q = new SelectQuery(); 32 | action(q); 33 | source.FromClause = new TableClause() { SubSelectClause = q, RelationType = RelationTypes.From, Actual = q }; 34 | return source.FromClause; 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /src/SqModel/SelectQuerySelectExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SqModel; 9 | 10 | public static class SelectQuerySelectExtension 11 | { 12 | public static IValueContainer Select(this SelectQuery source, TableClause table, string column) 13 | => source.Select.Add().Column(table, column); 14 | 15 | public static IValueContainer Select(this SelectQuery source, string table, string column) 16 | => source.Select.Add().Column(table, column); 17 | 18 | public static IValueContainer Select(this SelectQuery source, object value) 19 | => source.Select.Add().Value(value); 20 | 21 | public static void SelectAll(this SelectQuery source, TableClause table) 22 | { 23 | if (table.Actual == null) 24 | { 25 | source.Select.Add().All(table); 26 | return; 27 | } 28 | 29 | var names = source.GetSelectItems().Select(x => x.Name).ToList(); 30 | table.Actual.GetSelectItems().Where(x => !names.Contains(x.Name)).ToList().ForEach(x => 31 | { 32 | source.Select.Add().Column(table, x.Name); 33 | }); 34 | } 35 | 36 | public static void SelectAll(this SelectQuery source, string table) 37 | => source.Select.Add().All(table); 38 | 39 | public static void SelectAll(this SelectQuery source) 40 | => source.Select.Add().All(); 41 | 42 | public static void SelectCount(this SelectQuery source) 43 | => source.Select.Add().Value("count(*)"); 44 | 45 | public static void SelectAll(this SelectQuery source, CommonTable table, string? tableAlias = null) 46 | { 47 | var tname = tableAlias ?? table.Name; 48 | var cols = table.Query.GetSelectItems().Select(x => x.Name).ToList(); 49 | cols.ForEach(x => source.Select.Add().Column(tname, x)); 50 | } 51 | } -------------------------------------------------------------------------------- /src/SqModel/SelectQueryValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel; 8 | 9 | public class SelectQueryValue : IValueClause 10 | { 11 | public SelectQuery? Query { get; set; } = null; 12 | 13 | public string Conjunction { get; set; } = string.Empty; 14 | 15 | public void AddParameter(string name, object? value) 16 | => throw new NotSupportedException(); 17 | 18 | public string GetName() => string.Empty; 19 | 20 | public Query ToQuery() 21 | { 22 | if (Query == null) throw new InvalidProgramException(); 23 | Query.IsIncludeCte = false; 24 | return Query.ToQuery().DecorateBracket().InsertToken(Conjunction); 25 | } 26 | } -------------------------------------------------------------------------------- /src/SqModel/SelectQueryWithExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel; 8 | 9 | public static class SelectQueryWithExtension 10 | { 11 | public static CommonTable With(this SelectQuery source, string name) 12 | { 13 | var c = new CommonTable() { Name = name }; 14 | source.With.Collection.Add(c); 15 | return c; 16 | } 17 | 18 | public static CommonTable As(this CommonTable source, Action action) 19 | { 20 | action(source.Query); 21 | return source; 22 | } 23 | 24 | public static CommonTable As(this CommonTable source, SelectQuery query) 25 | { 26 | source.Query = query; 27 | return source; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/SqModel/SqModel.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | mk3008 8 | https://github.com/mk3008/SqModel 9 | 10 | 11 | 0.8.15 12 | Sql;QueryBuilder;SqlParser;SqlBuilder; 13 | README.md 14 | Parse and build select queries. 15 | https://github.com/mk3008/SqModel.git 16 | mk3008net 17 | 18 | MIT 19 | False 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | True 37 | \ 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/SqModel/TableClause.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using SqModel.Extension; 3 | 4 | namespace SqModel; 5 | 6 | public class TableClause 7 | { 8 | public TableClause() { } 9 | 10 | public TableClause(string tableName) 11 | { 12 | TableName = tableName; 13 | AliasName = tableName; 14 | } 15 | 16 | public TableClause(string tableName, string aliasName) 17 | { 18 | TableName = tableName; 19 | AliasName = aliasName; 20 | } 21 | 22 | public TableClause(SelectQuery subSelectClause, string aliasName) 23 | { 24 | SubSelectClause = subSelectClause; 25 | AliasName = aliasName; 26 | } 27 | 28 | public SelectQuery? Actual { get; internal set; } 29 | 30 | public RelationTypes RelationType { get; set; } = RelationTypes.Undefined; 31 | 32 | public string TableName { get; set; } = string.Empty; 33 | 34 | public SelectQuery? SubSelectClause { get; set; } = null; 35 | 36 | public string AliasName { get; set; } = string.Empty; 37 | 38 | public string SourceAlias { get; set; } = string.Empty; 39 | 40 | public ConditionGroup RelationClause { get; set; } = new ConditionGroup() { IsDecorateBracket = false }; 41 | 42 | public ConditionGroup On => RelationClause; 43 | 44 | public List SubTableClauses { get; set; } = new(); 45 | 46 | public string GetName() => AliasName != string.Empty ? AliasName : TableName; 47 | 48 | public string GetAliasCommand() 49 | { 50 | var cols = (!ColumnNames.Any()) ? String.Empty : $"({ColumnNames.ToString(", ")})"; 51 | 52 | if (TableName != GetName() || SubSelectClause != null || cols.IsNotEmpty()) 53 | { 54 | return $" as {GetName()}{cols}"; 55 | } 56 | return string.Empty; 57 | } 58 | 59 | public List ColumnNames { get; set; } = new(); 60 | 61 | public Query ToQuery() 62 | { 63 | var q = ToCurrentQuery(); 64 | var subQ = SubTableClauses.Select(x => x.ToQuery()).ToList(); 65 | subQ.ForEach(x => q = q.Merge(x, "\r\n")); 66 | return q; 67 | } 68 | 69 | public Query ToCurrentQuery()//string alias, Query tableQ 70 | { 71 | var join = string.Empty; 72 | 73 | switch (RelationType) 74 | { 75 | case RelationTypes.From: 76 | join = "from "; 77 | break; 78 | case RelationTypes.Inner: 79 | join = "inner join "; 80 | break; 81 | case RelationTypes.Left: 82 | join = "left join "; 83 | break; 84 | case RelationTypes.Right: 85 | join = "right join "; 86 | break; 87 | case RelationTypes.Cross: 88 | join = "cross join "; 89 | break; 90 | } 91 | 92 | var fnQuery = () => 93 | { 94 | if (SubSelectClause != null) 95 | { 96 | SubSelectClause.IsIncludeCte = false; 97 | var sq = SubSelectClause.ToQuery(); 98 | var text = $"{join}(\r\n{sq.CommandText.InsertIndent()}\r\n){GetAliasCommand()}"; 99 | return new Query() { CommandText = text, Parameters = sq.Parameters }; 100 | } 101 | else 102 | { 103 | var text = $"{join}{TableName}{GetAliasCommand()}"; 104 | return new Query() { CommandText = text, Parameters = new() }; 105 | } 106 | }; 107 | 108 | var q = fnQuery(); 109 | if (RelationType == RelationTypes.From || RelationType == RelationTypes.Cross) return q; 110 | 111 | return q.Merge(RelationClause.ToQuery(), " on "); 112 | } 113 | 114 | public IEnumerable GetCommonTableClauses() 115 | { 116 | foreach (var x in SubTableClauses) foreach (var item in x.GetCommonTableClauses()) yield return item; 117 | 118 | var lst = SubSelectClause?.GetAllWith().Collection.ToList(); 119 | if (SubSelectClause != null) foreach (var item in SubSelectClause.GetAllWith().Collection) yield return item; 120 | } 121 | } -------------------------------------------------------------------------------- /src/SqModel/TableClauseExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel; 8 | 9 | 10 | public static class TableClauseExtension 11 | { 12 | public static List GetTableClauses(this TableClause source) 13 | { 14 | var lst = new List(); 15 | 16 | lst.Add(source); 17 | source.SubTableClauses.ForEach(x => lst.AddRange(x.GetTableClauses())); 18 | 19 | return lst; 20 | } 21 | 22 | public static TableClause Join(this TableClause source, string tableName, string aliasName, RelationTypes type) 23 | => source.Join(new TableClause(tableName, aliasName), type); 24 | 25 | public static TableClause Join(this TableClause source, SelectQuery subSelectClause, string aliasName, RelationTypes type) 26 | => source.Join(new TableClause(subSelectClause, aliasName), type); 27 | 28 | public static TableClause Join(this TableClause source, TableClause destination, RelationTypes type) 29 | { 30 | destination.RelationClause.LeftTable = source.AliasName; 31 | destination.RelationClause.RightTable = destination.AliasName; 32 | destination.RelationType = type; 33 | source.SubTableClauses.Add(destination); 34 | return destination; 35 | } 36 | 37 | public static TableClause InnerJoin(this TableClause source, string tableName) 38 | => source.Join(tableName, tableName, RelationTypes.Inner); 39 | 40 | public static TableClause InnerJoin(this TableClause source, TableClause destination) 41 | => source.Join(destination, RelationTypes.Inner); 42 | 43 | public static TableClause InnerJoin(this TableClause source, SelectQuery subSelectClause) 44 | => source.Join(subSelectClause, "", RelationTypes.Inner); 45 | 46 | public static TableClause InnerJoin(this TableClause source, CommonTable ct) 47 | => source.Join(ct.Name, ct.Name, RelationTypes.Inner); 48 | 49 | public static TableClause InnerJoin(this TableClause source, Func fn) 50 | => source.Join(fn(), "", RelationTypes.Inner); 51 | 52 | public static TableClause InnerJoin(this TableClause source, Action action) 53 | { 54 | var subSelectClause = new SelectQuery(); 55 | action(subSelectClause); 56 | return source.Join(subSelectClause, "", RelationTypes.Inner); 57 | } 58 | 59 | public static TableClause LeftJoin(this TableClause source, string tableName) 60 | => source.Join(tableName, tableName, RelationTypes.Left); 61 | 62 | public static TableClause LeftJoin(this TableClause source, TableClause destination) 63 | => source.Join(destination, RelationTypes.Left); 64 | 65 | public static TableClause LeftJoin(this TableClause source, SelectQuery subSelectClause) 66 | => source.Join(subSelectClause, "", RelationTypes.Left); 67 | 68 | public static TableClause LeftJoin(this TableClause source, CommonTable ct) 69 | => source.Join(ct.Name, ct.Name, RelationTypes.Left); 70 | 71 | public static TableClause LeftJoin(this TableClause source, Func fn) 72 | => source.Join(fn(), "", RelationTypes.Left); 73 | 74 | public static TableClause LeftJoin(this TableClause source, Action action) 75 | { 76 | var subSelectClause = new SelectQuery(); 77 | action(subSelectClause); 78 | return source.Join(subSelectClause, "", RelationTypes.Left); 79 | } 80 | 81 | public static TableClause RightJoin(this TableClause source, string tableName) 82 | => source.Join(tableName, tableName, RelationTypes.Right); 83 | 84 | public static TableClause RightJoin(this TableClause source, TableClause destination) 85 | => source.Join(destination, RelationTypes.Right); 86 | 87 | public static TableClause RightJoin(this TableClause source, SelectQuery subSelectClause) 88 | => source.Join(subSelectClause, "", RelationTypes.Right); 89 | 90 | public static TableClause RightJoin(this TableClause source, CommonTable ct) 91 | => source.Join(ct.Name, ct.Name, RelationTypes.Right); 92 | 93 | public static TableClause RightJoin(this TableClause source, Func fn) 94 | => source.Join(fn(), "", RelationTypes.Right); 95 | 96 | public static TableClause RightJoin(this TableClause source, Action action) 97 | { 98 | var subSelectClause = new SelectQuery(); 99 | action(subSelectClause); 100 | return source.Join(subSelectClause, "", RelationTypes.Right); 101 | } 102 | 103 | public static TableClause CrossJoin(this TableClause source, string tableName) 104 | => source.Join(tableName, tableName, RelationTypes.Cross); 105 | 106 | public static TableClause CrossJoin(this TableClause source, TableClause destination) 107 | => source.Join(destination, RelationTypes.Cross); 108 | 109 | public static TableClause CrossJoin(this TableClause source, SelectQuery subSelectClause) 110 | => source.Join(subSelectClause, "", RelationTypes.Cross); 111 | 112 | public static TableClause CrossJoin(this TableClause source, CommonTable ct) 113 | => source.Join(ct.Name, ct.Name, RelationTypes.Cross); 114 | 115 | public static TableClause CrossJoin(this TableClause source, Func fn) 116 | => source.Join(fn(), "", RelationTypes.Cross); 117 | 118 | public static TableClause CrossJoin(this TableClause source, Action action) 119 | { 120 | var subSelectClause = new SelectQuery(); 121 | action(subSelectClause); 122 | return source.Join(subSelectClause, "", RelationTypes.Cross); 123 | } 124 | 125 | public static TableClause As(this TableClause source, string aliasName) 126 | { 127 | source.AliasName = aliasName; 128 | source.RelationClause.RightTable = source.AliasName; 129 | return source; 130 | } 131 | 132 | public static TableClause On(this TableClause source, string column) 133 | => source.On(column, column); 134 | 135 | public static TableClause On(this TableClause source, List columns) 136 | { 137 | source.On(g => 138 | { 139 | g.IsDecorateBracket = false; 140 | columns.ForEach(x => g.Add().Column(g.LeftTable, x).Equal(g.RightTable, x)); 141 | }); 142 | return source; 143 | } 144 | 145 | public static TableClause On(this TableClause source, string sourcecolumn, string destinationcolumn) 146 | { 147 | var st = source.RelationClause.LeftTable; 148 | var dt = source.RelationClause.RightTable; 149 | source.On.Add().Column(st, sourcecolumn).Equal(dt, destinationcolumn); 150 | return source; 151 | } 152 | 153 | public static TableClause On(this TableClause source, Action fn) 154 | { 155 | var r = source.RelationClause; 156 | var g = new ConditionGroup() { LeftTable = r.LeftTable, RightTable = r.RightTable, IsDecorateBracket = false }; 157 | source.RelationClause.Collection.Add(g); 158 | fn(g); 159 | return source; 160 | } 161 | } -------------------------------------------------------------------------------- /src/SqModel/UnionClause.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel; 8 | 9 | public class UnionClause 10 | { 11 | public bool IsUnionAll { get; set; } = true; 12 | 13 | public SelectQuery? SelectQuery { get; set; } = null; 14 | 15 | public Query ToQuery() 16 | { 17 | if (SelectQuery == null) return new Query(); 18 | 19 | SelectQuery.IsIncludeCte = false; 20 | SelectQuery.IsIncludeOrder = false; 21 | 22 | var q = SelectQuery.ToQuery(); 23 | if (IsUnionAll) 24 | { 25 | q = q.InsertToken("union all", "\r\n"); 26 | } 27 | else 28 | { 29 | q = q.InsertToken("union", "\r\n"); 30 | } 31 | return q; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/SqModel/ValueBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel; 8 | 9 | internal static class ValueBuilder 10 | { 11 | public static IValueClause Create(TableClause table, string column) 12 | => new ColumnValue() { Table = table.AliasName, Column = column }; 13 | 14 | public static IValueClause Create(string table, string column) 15 | => new ColumnValue() { Table = table, Column = column }; 16 | 17 | public static IValueClause Create(bool value) 18 | { 19 | if (value) return new CommandValue() { CommandText = "true" }; 20 | return new CommandValue() { CommandText = "false" }; 21 | } 22 | 23 | public static IValueClause Create(object commandtext) 24 | { 25 | var val = commandtext?.ToString(); 26 | if (val == null) throw new InvalidProgramException(); 27 | var c = new CommandValue() { CommandText = val }; 28 | return c; 29 | } 30 | 31 | public static IValueClause GetNullValue() 32 | => new CommandValue() { CommandText = "null" }; 33 | 34 | public static IValueClause GetNotNullValue() 35 | => new CommandValue() { CommandText = "not null" }; 36 | } -------------------------------------------------------------------------------- /src/SqModel/ValueContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel; 8 | 9 | public class ValueContainer : IValueContainer 10 | { 11 | public IValueClause? Command { get; set; } 12 | 13 | public string ColumnName 14 | { 15 | set { return; } 16 | } 17 | 18 | public string Name 19 | { 20 | set { return; } 21 | } 22 | 23 | public virtual Query ToQuery() 24 | { 25 | if (Command == null) throw new InvalidProgramException(); 26 | return Command.ToQuery(); 27 | } 28 | } -------------------------------------------------------------------------------- /src/SqModel/ValuesClause.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SqModel; 8 | 9 | public class ValuesClause 10 | { 11 | public List Collection { get; set; } = new(); 12 | 13 | public Query ToQuery() 14 | { 15 | /* 16 | * values 17 | * (1, 2, 3) 18 | * , (4, 5, 6) 19 | */ 20 | var q = Collection.Select(x => x.ToQuery().DecorateBracket()).ToList().ToQuery("\r\n, ").InsertIndent(); 21 | q.CommandText = $"values\r\n{q.CommandText}"; 22 | return q; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/SqModel/WithClause.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using SqModel; 7 | 8 | namespace SqModel; 9 | 10 | public class WithClause 11 | { 12 | public List Collection = new(); 13 | 14 | public IEnumerable GetCommonTableClauses() 15 | { 16 | foreach (var y in Collection) 17 | { 18 | if (y.Query == null) continue; 19 | foreach (var item in y.Query.GetAllWith().Collection) yield return item; 20 | } 21 | foreach (var item in Collection) yield return item; 22 | } 23 | 24 | internal CommonTable Add(SelectQuery selectQuery, string alias) 25 | { 26 | var c = new CommonTable() { Query = selectQuery, Name = alias }; 27 | Collection.Add(c); 28 | return c; 29 | } 30 | 31 | public Query ToQuery() 32 | { 33 | var q = Collection.Select(x => x.ToQuery()).ToList().ToQuery(",\r\n"); 34 | if (q.CommandText != string.Empty) q.CommandText = $"with\r\n{q.CommandText}"; 35 | 36 | return q; 37 | } 38 | } 39 | 40 | public static class WithClauseExtension 41 | { 42 | public static CommonTable Add(this WithClause source, Action action) 43 | { 44 | var sq = new SelectQuery(); 45 | action(sq); 46 | var c = new CommonTable() { Query = sq }; 47 | source.Collection.Add(c); 48 | return c; 49 | } 50 | } -------------------------------------------------------------------------------- /test/SqModelTest/AnalysisTest/CommonTableTest.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using SqModel.Analysis; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | 11 | namespace SqModelTest.AnalysisTest; 12 | 13 | public class CommonTableTest 14 | { 15 | public CommonTableTest(ITestOutputHelper output) 16 | { 17 | Output = output; 18 | } 19 | 20 | private readonly ITestOutputHelper Output; 21 | 22 | private string ToString(List items) 23 | { 24 | var sb = new StringBuilder(); 25 | sb.AppendLine("| Name | Query |"); 26 | sb.AppendLine("| ---- | ----- |"); 27 | 28 | foreach (var item in items) 29 | { 30 | item.Query.IsOneLineFormat = true; 31 | sb.AppendLine($"| {item.Name} | {item.Query.ToQuery().CommandText} |"); 32 | } 33 | Output.WriteLine(sb.ToString()); 34 | return sb.ToString(); 35 | } 36 | 37 | [Fact] 38 | public void CommonTableInfo_Parse() 39 | { 40 | var sq = SqlParser.Parse(@"with 41 | a as ( 42 | select id, name from table_a 43 | ), 44 | b as ( 45 | select id, value from table_b 46 | ), 47 | c as ( 48 | select id, text from table_c 49 | ) 50 | select 51 | * 52 | from 53 | a 54 | inner join b on a.id = b.id 55 | inner join c on a.id = c.id 56 | "); 57 | 58 | var items = sq.GetCommonTables().ToList(); 59 | var s = ToString(items); 60 | 61 | var expect = @"| Name | Query | 62 | | ---- | ----- | 63 | | a | select id, name from table_a | 64 | | b | select id, value from table_b | 65 | | c | select id, text from table_c | 66 | "; 67 | 68 | Assert.Equal(expect, s); 69 | 70 | Assert.Equal(3, sq.GetCommonTables().ToList().Count); 71 | 72 | Assert.True(sq.CommonTableContains("a")); 73 | var item = sq.GetCommonTable("a"); 74 | Assert.Equal("a", item.Name); 75 | Assert.Equal("select id, name from table_a", item.Query.ToQuery().CommandText); 76 | 77 | Assert.False(sq.CommonTableContains("z")); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /test/SqModelTest/AnalysisTest/ParseAliasTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestPlatform.Utilities; 2 | using SqModel.Analysis; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | 11 | namespace SqModelTest.AnalysisTest; 12 | 13 | public class ParseAliasTest 14 | { 15 | public ParseAliasTest(ITestOutputHelper output) 16 | { 17 | Output = output; 18 | } 19 | 20 | private readonly ITestOutputHelper Output; 21 | 22 | [Fact] 23 | public void Default() 24 | { 25 | var text = "as col"; 26 | using var p = new SqlParser(text); 27 | p.Logger = (x) => Output.WriteLine(x); 28 | var val = p.ParseAlias(); 29 | 30 | Assert.Equal("col", val); 31 | } 32 | 33 | [Fact] 34 | public void Omit() 35 | { 36 | var text = "col"; 37 | using var p = new SqlParser(text); 38 | p.Logger = (x) => Output.WriteLine(x); 39 | var val = p.ParseAlias(); 40 | 41 | Assert.Equal("col", val); 42 | } 43 | 44 | [Fact] 45 | public void NoAlias() 46 | { 47 | var text = ","; 48 | using var p = new SqlParser(text); 49 | p.Logger = (x) => Output.WriteLine(x); 50 | var val = p.ParseAlias(); 51 | 52 | Assert.Equal("", val); 53 | } 54 | 55 | [Fact] 56 | public void ColumnAlias() 57 | { 58 | var text = "t.column as col"; 59 | using var p = new SqlParser(text); 60 | p.Logger = (x) => Output.WriteLine(x); 61 | var val = ValueClauseParser.Parse(p); 62 | var sql = val.ToQuery().CommandText; 63 | 64 | Assert.Equal("t.column", sql); 65 | Assert.Equal("col", p.ParseAlias()); 66 | } 67 | 68 | [Fact] 69 | public void ColumnAliasOmit() 70 | { 71 | var text = "t.column col"; 72 | using var p = new SqlParser(text); 73 | p.Logger = (x) => Output.WriteLine(x); 74 | var val = ValueClauseParser.Parse(p); 75 | var sql = val.ToQuery().CommandText; 76 | 77 | Assert.Equal("t.column", sql); 78 | Assert.Equal("col", p.ParseAlias()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /test/SqModelTest/AnalysisTest/ParseCaseTest.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using SqModel.Analysis; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | 11 | namespace SqModelTest.AnalysisTest; 12 | 13 | public class ParseCaseTest 14 | { 15 | public ParseCaseTest(ITestOutputHelper output) 16 | { 17 | Output = output; 18 | } 19 | 20 | private readonly ITestOutputHelper Output; 21 | 22 | [Fact] 23 | public void CaseWhenTest() 24 | { 25 | using var p = new SqlParser(@"case when a.col = 1 then 1 when a.col = 2 then 2 else 3 end"); 26 | var q = CaseExpressionParser.Parse(p); 27 | var text = q.ToQuery().CommandText; 28 | var expect = @"case when a.col = 1 then 1 when a.col = 2 then 2 else 3 end"; 29 | Assert.Equal(expect, text); 30 | } 31 | 32 | [Fact] 33 | public void CaseTest() 34 | { 35 | using var p = new SqlParser(@"case a.col when 1 then 1 when 2 then 2 else 3 end"); 36 | var q = CaseExpressionParser.Parse(p); 37 | var text = q.ToQuery().CommandText; 38 | var expect = @"case a.col when 1 then 1 when 2 then 2 else 3 end"; 39 | Assert.Equal(expect, text); 40 | } 41 | 42 | [Fact] 43 | public void NestTest() 44 | { 45 | using var p = new SqlParser(@" 46 | case a.col 47 | when 1 then 48 | case b.col 49 | when 10 then 10 50 | else 20 51 | end 52 | when 2 then 2 53 | else 3 54 | end"); 55 | var q = CaseExpressionParser.Parse(p); 56 | var text = q.ToQuery().CommandText; 57 | var expect = @"case a.col when 1 then case b.col when 10 then 10 else 20 end when 2 then 2 else 3 end"; 58 | Assert.Equal(expect, text); 59 | } 60 | 61 | [Fact] 62 | public void SelectTest() 63 | { 64 | using var p = new SqlParser(@"select case when a.col = 1 then 1 else 2 end as val from table_a as a"); 65 | var q = p.ParseSelectQuery(); 66 | var text = q.ToQuery().CommandText; 67 | var expect = @"select 68 | case when a.col = 1 then 1 else 2 end as val 69 | from table_a as a"; 70 | Assert.Equal(expect, text); 71 | } 72 | 73 | [Fact] 74 | public void AndOrTest() 75 | { 76 | using var p = new SqlParser(@"select 77 | case 78 | when 1 = 1 and 2 = 2 and 3 = 3 and 4 = 4 then 'A' 79 | when 1 = 1 or 2 = 2 or 3 = 3 or 4 = 4 then 'B' 80 | when 1 = 1 and 2 = 2 and 3 = 3 or 4 = 4 then 'C' 81 | end as val"); 82 | var q = p.ParseSelectQuery(); 83 | var text = q.ToQuery().CommandText; 84 | var expect = @"select 85 | case when 1 = 1 and 2 = 2 and 3 = 3 and 4 = 4 then 'A' when 1 = 1 or 2 = 2 or 3 = 3 or 4 = 4 then 'B' when 1 = 1 and 2 = 2 and 3 = 3 or 4 = 4 then 'C' end as val"; 86 | Assert.Equal(expect, text); 87 | } 88 | 89 | 90 | [Fact] 91 | public void WhereTest() 92 | { 93 | using var p = new SqlParser(@"select * from table_a as a where 94 | case when a.col = 1 then 1 else 2 end = 1"); 95 | var q = p.ParseSelectQuery(); 96 | var text = q.ToQuery().CommandText; 97 | var expect = @"select 98 | * 99 | from table_a as a 100 | where 101 | case when a.col = 1 then 1 else 2 end = 1"; 102 | Assert.Equal(expect, text); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /test/SqModelTest/AnalysisTest/ParseCteTest.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Analysis; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | 10 | namespace SqModelTest.AnalysisTest; 11 | public class ParseCteTest 12 | { 13 | public ParseCteTest(ITestOutputHelper output) 14 | { 15 | Output = output; 16 | } 17 | 18 | private readonly ITestOutputHelper Output; 19 | 20 | [Fact] 21 | public void Default() 22 | { 23 | var sql = @"with 24 | a as ( 25 | select 26 | * 27 | from table_a 28 | ), 29 | b as ( 30 | select 31 | * 32 | from table_b 33 | ) 34 | select 35 | * 36 | from a 37 | inner join b on a.column_1 = b.column_1"; 38 | 39 | using var p = new SqlParser(sql); 40 | p.Logger = (x) => Output.WriteLine(x); 41 | var sq = p.ParseSelectQuery(); 42 | var text = sq.ToQuery().CommandText; 43 | 44 | Assert.Equal(sql, text); 45 | } 46 | 47 | [Fact] 48 | public void NotMaterialized() 49 | { 50 | var sql = @"with 51 | a as not materialized ( 52 | select 53 | * 54 | from table_a 55 | ) 56 | select 57 | * 58 | from a"; 59 | 60 | using var p = new SqlParser(sql); 61 | p.Logger = (x) => Output.WriteLine(x); 62 | var sq = p.ParseSelectQuery(); 63 | var text = sq.ToQuery().CommandText; 64 | 65 | Assert.Equal(sql, text); 66 | } 67 | } -------------------------------------------------------------------------------- /test/SqModelTest/AnalysisTest/ParseFunctionTest.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Analysis; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | 10 | namespace SqModelTest.AnalysisTest; 11 | 12 | public class ParseFunctionTest 13 | { 14 | public ParseFunctionTest(ITestOutputHelper output) 15 | { 16 | Output = output; 17 | } 18 | 19 | private readonly ITestOutputHelper Output; 20 | 21 | [Fact] 22 | public void Coalesce() 23 | { 24 | var sql = @"select 25 | coalesce(a.name_2, a.name_1) as name 26 | from table_a"; 27 | var sq = SqlParser.Parse(sql); 28 | 29 | Assert.Equal(sql, sq.ToQuery().CommandText); 30 | } 31 | 32 | [Fact] 33 | public void Like() 34 | { 35 | var sql = @"select 36 | * 37 | from table_a 38 | where 39 | a.name like :name"; 40 | var sq = SqlParser.Parse(sql); 41 | 42 | Assert.Equal(sql, sq.ToQuery().CommandText); 43 | } 44 | 45 | [Fact] 46 | public void NotLike() 47 | { 48 | var sql = @"select 49 | * 50 | from table_a 51 | where 52 | a.name not like :name"; 53 | var sq = SqlParser.Parse(sql); 54 | 55 | Assert.Equal(sql, sq.ToQuery().CommandText); 56 | } 57 | 58 | [Fact] 59 | public void In() 60 | { 61 | var sql = @"select 62 | * 63 | from table_a 64 | where 65 | a.id in (1, 2, 3)"; 66 | var sq = SqlParser.Parse(sql); 67 | 68 | Assert.Equal(sql, sq.ToQuery().CommandText); 69 | } 70 | 71 | [Fact] 72 | public void Any() 73 | { 74 | var sql = @"select 75 | * 76 | from table_a 77 | where 78 | a.id = any(1, 2, 3)"; 79 | 80 | var sq = SqlParser.Parse(sql); 81 | 82 | Assert.Equal(sql, sq.ToQuery().CommandText); 83 | } 84 | 85 | [Fact] 86 | public void Concat() 87 | { 88 | var sql = @"select 89 | concat('a', 'b', 'c') as text 90 | from table_a"; 91 | 92 | var sq = SqlParser.Parse(sql); 93 | 94 | Assert.Equal(sql, sq.ToQuery().CommandText); 95 | } 96 | 97 | [Fact] 98 | public void StringJoin() 99 | { 100 | var sql = @"select 101 | 'a' || 'b' || 'c' as text 102 | from table_a"; 103 | 104 | var sq = SqlParser.Parse(sql); 105 | 106 | Assert.Equal(sql, sq.ToQuery().CommandText); 107 | } 108 | 109 | [Fact] 110 | public void Now() 111 | { 112 | //NoArgument 113 | var sql = @"select 114 | now() as date1 115 | from table_a"; 116 | 117 | var sq = SqlParser.Parse(sql); 118 | 119 | Assert.Equal(sql, sq.ToQuery().CommandText); 120 | } 121 | 122 | [Fact] 123 | public void RowNumber() 124 | { 125 | var sql = @"select 126 | row_number() over(partition by name1, name2 order by id, sub_id) as rowid 127 | from table_a"; 128 | 129 | var sq = SqlParser.Parse(sql); 130 | 131 | Assert.Equal(sql, sq.ToQuery().CommandText); 132 | } 133 | } -------------------------------------------------------------------------------- /test/SqModelTest/AnalysisTest/ParseGroupTest.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Analysis; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | 10 | namespace SqModelTest.AnalysisTest; 11 | 12 | public class ParseGroupTest 13 | { 14 | public ParseGroupTest(ITestOutputHelper output) 15 | { 16 | Output = output; 17 | } 18 | 19 | private readonly ITestOutputHelper Output; 20 | 21 | [Fact] 22 | public void GroupBy() 23 | { 24 | /// inner join table_b as b on ... 25 | var sql = "group by a.id, 1, b.id"; 26 | var clause = GroupClauseParser.Parse(sql); 27 | 28 | Assert.Equal(sql, clause.ToQuery().CommandText); 29 | } 30 | 31 | [Fact] 32 | public void Default() 33 | { 34 | /// inner join table_b as b on ... 35 | var sql = @"select 36 | 1 37 | from table_a as a 38 | where 39 | 1 = 1 40 | group by 41 | a.id 42 | , 1 43 | , a.name"; 44 | var sq = SqlParser.Parse(sql); 45 | 46 | Assert.Equal(sql, sq.ToQuery().CommandText); 47 | } 48 | 49 | [Fact] 50 | public void SumTest() 51 | { 52 | /// inner join table_b as b on ... 53 | var sql = @"select 54 | sum(a.price) as price 55 | from table_a as a 56 | where 57 | 1 = 1 58 | group by 59 | a.id 60 | , 1 61 | , a.name"; 62 | var sq = SqlParser.Parse(sql); 63 | 64 | Assert.Equal(sql, sq.ToQuery().CommandText); 65 | } 66 | 67 | [Fact] 68 | public void CountTest() 69 | { 70 | /// inner join table_b as b on ... 71 | var sql = @"select 72 | count(*) as cnt 73 | from table_a as a 74 | where 75 | 1 = 1 76 | group by 77 | a.id 78 | , 1 79 | , a.name"; 80 | var sq = SqlParser.Parse(sql); 81 | 82 | Assert.Equal(sql, sq.ToQuery().CommandText); 83 | } 84 | 85 | [Fact] 86 | public void HavingTest() 87 | { 88 | /// inner join table_b as b on ... 89 | var sql = @"select 90 | a.id 91 | from table_a as a 92 | where 93 | 1 = 1 94 | group by 95 | a.id 96 | , 1 97 | , a.name 98 | having 99 | sum(price) = 10"; 100 | var sq = SqlParser.Parse(sql); 101 | var q = sq.ToQuery(); 102 | Assert.Equal(sql, q.CommandText); 103 | } 104 | } -------------------------------------------------------------------------------- /test/SqModelTest/AnalysisTest/ParseLogicalExpressionTest.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Analysis; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | 10 | namespace SqModelTest.AnalysisTest; 11 | 12 | public class ParseLogicalExpressionTest 13 | { 14 | public ParseLogicalExpressionTest(ITestOutputHelper output) 15 | { 16 | Output = output; 17 | } 18 | 19 | private readonly ITestOutputHelper Output; 20 | 21 | [Fact] 22 | public void Default() 23 | { 24 | /// inner join table_b as b on ... 25 | var relationSql = "a.id1 = b.id2"; 26 | var clause = LogicalExpressionParser.Parse(relationSql); 27 | 28 | Assert.Equal(relationSql, clause.ToQuery().CommandText); 29 | } 30 | 31 | [Fact] 32 | public void Expression() 33 | { 34 | /// inner join table_b as b on ... 35 | var relationSql = "a.id1 = (1+2) * 3"; 36 | var clause = LogicalExpressionParser.Parse(relationSql); 37 | 38 | Assert.Equal(relationSql, clause.ToQuery().CommandText); 39 | } 40 | 41 | [Fact] 42 | public void SubQuery() 43 | { 44 | /// inner join table_b as b on ... 45 | var relationSql = @"a.id1 = (select x.id from table_x as x)"; 46 | var clause = LogicalExpressionParser.Parse(relationSql); 47 | var text = clause.ToQuery().CommandText; 48 | 49 | Assert.Equal(relationSql, text); 50 | } 51 | 52 | [Fact] 53 | public void Sign() 54 | { 55 | /// inner join table_b as b on ... 56 | var relationSql = @"a.id1 >= 10"; 57 | var clause = LogicalExpressionParser.Parse(relationSql); 58 | var text = clause.ToQuery().CommandText; 59 | 60 | Assert.Equal(relationSql, text); 61 | } 62 | 63 | [Fact] 64 | public void IsNull() 65 | { 66 | /// inner join table_b as b on ... 67 | var relationSql = @"a.id1 is null"; 68 | var clause = LogicalExpressionParser.Parse(relationSql); 69 | var text = clause.ToQuery().CommandText; 70 | 71 | Assert.Equal(relationSql, text); 72 | } 73 | 74 | [Fact] 75 | public void IsNotNull() 76 | { 77 | /// inner join table_b as b on ... 78 | var relationSql = @"a.id1 is not null"; 79 | var clause = LogicalExpressionParser.Parse(relationSql); 80 | var text = clause.ToQuery().CommandText; 81 | 82 | Assert.Equal(relationSql, text); 83 | } 84 | 85 | [Fact] 86 | public void IsNullAnd() 87 | { 88 | /// inner join table_b as b on ... 89 | var relationSql = @"a.id1 is null and a.id1 is not null"; 90 | var clause = LogicalExpressionParser.Parse(relationSql); 91 | var text = clause.ToQuery().CommandText; 92 | 93 | Assert.Equal("a.id1 is null and a.id1 is not null", text); 94 | } 95 | 96 | [Fact] 97 | public void IsNotNullAnd() 98 | { 99 | /// inner join table_b as b on ... 100 | var relationSql = @"a.id1 is not null and a.id1 is not null"; 101 | var clause = LogicalExpressionParser.Parse(relationSql); 102 | var text = clause.ToQuery().CommandText; 103 | 104 | Assert.Equal("a.id1 is not null and a.id1 is not null", text); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /test/SqModelTest/AnalysisTest/ParseOrderTest.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Analysis; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | 10 | namespace SqModelTest.AnalysisTest; 11 | 12 | public class ParseOrderTest 13 | { 14 | public ParseOrderTest(ITestOutputHelper output) 15 | { 16 | Output = output; 17 | } 18 | 19 | private readonly ITestOutputHelper Output; 20 | 21 | [Fact] 22 | public void OrderBy() 23 | { 24 | /// inner join table_b as b on ... 25 | var sql = "order by a.id, 1, b.id"; 26 | var clause = OrderClauseParser.Parse(sql); 27 | 28 | Assert.Equal(sql, clause.ToQuery().CommandText); 29 | } 30 | 31 | [Fact] 32 | public void Default() 33 | { 34 | /// inner join table_b as b on ... 35 | var sql = @"select 36 | * 37 | from table_a 38 | where 39 | 1 = 1 40 | order by 41 | a.id 42 | , 1 43 | , a.name"; 44 | var sq = SqlParser.Parse(sql); 45 | 46 | Assert.Equal(sql, sq.ToQuery().CommandText); 47 | } 48 | } -------------------------------------------------------------------------------- /test/SqModelTest/AnalysisTest/ParseTableTest.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Analysis; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | 10 | namespace SqModelTest.AnalysisTest; 11 | 12 | public class ParseTableTest 13 | { 14 | public ParseTableTest(ITestOutputHelper output) 15 | { 16 | Output = output; 17 | } 18 | 19 | private readonly ITestOutputHelper Output; 20 | 21 | [Fact] 22 | public void FromTable() 23 | { 24 | /// inner join table_b as b on ... 25 | var sql = "from table_a as a"; 26 | var clause = FromClauseParser.Parse(sql); 27 | 28 | Assert.Equal("table_a", clause.TableName); 29 | Assert.Equal("a", clause.AliasName); 30 | Assert.Equal(sql, clause.ToQuery().CommandText); 31 | } 32 | 33 | [Fact] 34 | public void JoinTable() 35 | { 36 | /// inner join table_b as b on ... 37 | var text = @"from table_a as a 38 | inner join table_b as b on a.column_1 = b.column_1 and a.column_2 and b.column_2"; 39 | var clause = FromClauseParser.Parse(text); 40 | 41 | var sql = clause.ToQuery().CommandText; 42 | 43 | Assert.Equal(text, sql); 44 | } 45 | 46 | [Fact] 47 | public void Default() 48 | { 49 | /// inner join table_b as b on ... 50 | var sql = @"from table_a as a 51 | inner join table_b as b on a.column_1 = b.column_1 and a.column_2 = b.column_2"; 52 | var clause = FromClauseParser.Parse(sql); 53 | 54 | Assert.Equal(sql, clause.ToQuery().CommandText); 55 | } 56 | 57 | 58 | [Fact] 59 | public void ManyJoin() 60 | { 61 | /// inner join table_b as b on ... 62 | var sql = @"from table_a as a 63 | inner join table_b as b on a.column_1 = b.column_1 and a.column_2 and b.column_2 64 | left join table_c as c on b.column_3 = c.column_3 65 | right join table_d as d on c.column_4 = c.column4 66 | cross join table_e as e"; 67 | var clause = FromClauseParser.Parse(sql); 68 | 69 | Assert.Equal(sql, clause.ToQuery().CommandText); 70 | } 71 | 72 | [Fact] 73 | public void SubQuery() 74 | { 75 | /// inner join table_b as b on ... 76 | var sql = @"from ( 77 | select 78 | * 79 | from table_a 80 | ) as a"; 81 | var clause = FromClauseParser.Parse(sql); 82 | 83 | Assert.Equal(sql, clause.ToQuery().CommandText); 84 | } 85 | 86 | [Fact] 87 | public void SubQueryMany() 88 | { 89 | /// inner join table_b as b on ... 90 | var sql = @"from ( 91 | select 92 | * 93 | from table_a 94 | ) as a 95 | inner join ( 96 | select 97 | * 98 | from table_b 99 | ) as b on a.column_1 = b.column_1"; 100 | var clause = FromClauseParser.Parse(sql); 101 | 102 | Assert.Equal(sql, clause.ToQuery().CommandText); 103 | } 104 | 105 | [Fact] 106 | public void AliasOmit() 107 | { 108 | var sql = @"from table_a 109 | inner join table_b on table_a.column_1 = table_b.column_1"; 110 | var clause = FromClauseParser.Parse(sql); 111 | 112 | var text = clause.ToQuery().CommandText; 113 | Assert.Equal(sql, clause.ToQuery().CommandText); 114 | } 115 | 116 | [Fact] 117 | public void Schema() 118 | { 119 | var sql = @"from schema.table as t"; 120 | var clause = FromClauseParser.Parse(sql); 121 | 122 | var text = clause.ToQuery().CommandText; 123 | Assert.Equal(sql, clause.ToQuery().CommandText); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /test/SqModelTest/AnalysisTest/ParseUnionTest.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Analysis; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit.Abstractions; 8 | using Xunit; 9 | 10 | namespace SqModelTest.AnalysisTest; 11 | 12 | public class ParseUnionTest 13 | { 14 | 15 | public ParseUnionTest(ITestOutputHelper output) 16 | { 17 | Output = output; 18 | } 19 | 20 | private readonly ITestOutputHelper Output; 21 | 22 | [Fact] 23 | public void Default() 24 | { 25 | var sql = @"select 26 | 1 27 | union all 28 | select 29 | 2 30 | union 31 | select 32 | * 33 | from table_a"; 34 | var clause = SqlParser.Parse(sql); 35 | 36 | Assert.Equal(sql, clause.ToQuery().CommandText); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/SqModelTest/AnalysisTest/ParseValueClauseTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestPlatform.Utilities; 2 | using SqModel.Analysis; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | 11 | namespace SqModelTest.AnalysisTest; 12 | 13 | public class ParseValueClauseTest 14 | { 15 | public ParseValueClauseTest(ITestOutputHelper output) 16 | { 17 | Output = output; 18 | } 19 | 20 | private readonly ITestOutputHelper Output; 21 | 22 | [Fact] 23 | public void Default() 24 | { 25 | var text = "t.column"; 26 | var val = ValueClauseParser.Parse(text); 27 | var sql = val.ToQuery().CommandText; 28 | 29 | Assert.Equal(text, sql); 30 | } 31 | 32 | [Fact] 33 | public void Value() 34 | { 35 | var text = "1"; 36 | var val = ValueClauseParser.Parse(text); 37 | var sql = val.ToQuery().CommandText; 38 | 39 | Assert.Equal(text, sql); 40 | } 41 | 42 | [Fact] 43 | public void Condition() 44 | { 45 | var text = "1 = 1"; 46 | var val = ValueClauseParser.Parse(text); 47 | var sql = val.ToQuery().CommandText; 48 | 49 | Assert.Equal("1 = 1", sql); 50 | } 51 | 52 | [Fact] 53 | public void Condition2() 54 | { 55 | var text = "1 + 1 = 2"; 56 | var val = ValueClauseParser.Parse(text); 57 | var sql = val.ToQuery().CommandText; 58 | 59 | Assert.Equal("1 + 1 = 2", sql); 60 | } 61 | 62 | [Fact] 63 | public void Expression() 64 | { 65 | var text = "(1 + 2) * 3.4"; 66 | var val = ValueClauseParser.Parse(text); 67 | var sql = val.ToQuery().CommandText; 68 | 69 | Assert.Equal("(1 + 2) * 3.4", sql); 70 | } 71 | 72 | [Fact] 73 | public void ExpressionUseColumn() 74 | { 75 | var text = "a.value * 3.4"; 76 | var val = ValueClauseParser.Parse(text); 77 | var sql = val.ToQuery().CommandText; 78 | 79 | Assert.Equal("a.value * 3.4", sql); 80 | } 81 | 82 | [Fact] 83 | public void CaseWhenExpression() 84 | { 85 | var text = "case when 1 = 1 then 1 else 2 end"; 86 | var val = ValueClauseParser.Parse(text); 87 | var sql = val.ToQuery().CommandText; 88 | 89 | Assert.Equal(text, sql); 90 | } 91 | 92 | [Fact] 93 | public void InlineQuery() 94 | { 95 | var text = "(select t.colmn from table as t)"; 96 | var val = ValueClauseParser.Parse(text); 97 | var sql = val.ToQuery().CommandText; 98 | 99 | Assert.Equal(text, sql); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /test/SqModelTest/AnalysisTest/ParseValuesTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestPlatform.Utilities; 2 | using SqModel.Analysis; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | 11 | namespace SqModelTest.AnalysisTest; 12 | 13 | public class ParseValuesTest 14 | { 15 | public ParseValuesTest(ITestOutputHelper output) 16 | { 17 | Output = output; 18 | } 19 | 20 | private readonly ITestOutputHelper Output; 21 | 22 | [Fact] 23 | public void Default() 24 | { 25 | var text = "values (1,2,3), (4,5,6)"; 26 | var c = ValuesClauseParser.Parse(text); 27 | var q = c.ToQuery(); 28 | 29 | Assert.Equal(@"values 30 | (1, 2, 3) 31 | , (4, 5, 6)", q.CommandText); 32 | } 33 | } -------------------------------------------------------------------------------- /test/SqModelTest/AnalysisTest/ParseWhereTest.cs: -------------------------------------------------------------------------------- 1 | using SqModel.Analysis; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | 10 | namespace SqModelTest.AnalysisTest; 11 | 12 | public class ParseWhereTest 13 | { 14 | public ParseWhereTest(ITestOutputHelper output) 15 | { 16 | Output = output; 17 | } 18 | 19 | private readonly ITestOutputHelper Output; 20 | 21 | [Fact] 22 | public void Default() 23 | { 24 | var condition = "where a.column_1 = 1 or a.column_2 = 2"; 25 | var clause = WhereClauseParser.Parse(condition); 26 | var q = clause.ToQuery(); 27 | var expect = @"where 28 | a.column_1 = 1 or a.column_2 = 2"; 29 | Assert.Equal(expect, q.CommandText); 30 | } 31 | 32 | 33 | [Fact] 34 | public void Exists() 35 | { 36 | var condition = "where exists (select * from table_b as b where b.id = a.id)"; 37 | var clause = WhereClauseParser.Parse(condition); 38 | //clause.IsOneLineFormat = true; 39 | //clause.ConditionGroup.IsOneLineFormat = true; 40 | var q = clause.ToQuery(); 41 | var expect = @"where 42 | exists (select * from table_b as b where b.id = a.id)"; 43 | Assert.Equal(expect, q.CommandText); 44 | } 45 | 46 | [Fact] 47 | public void NotExists() 48 | { 49 | var condition = "where not exists (select * from table_b as b where b.id = a.id)"; 50 | var clause = WhereClauseParser.Parse(condition); 51 | var q = clause.ToQuery(); 52 | var expect = @"where 53 | not exists (select * from table_b as b where b.id = a.id)"; 54 | Assert.Equal(expect, q.CommandText); 55 | } 56 | 57 | [Fact] 58 | public void Group() 59 | { 60 | var condition = "where (a.id = 1 or a.id = 2) and (a.value = 1 or (a.id = 3 and a.id = 4))"; 61 | var clause = WhereClauseParser.Parse(condition); 62 | var q = clause.ToQuery(); 63 | var expect = @"where 64 | (a.id = 1 or a.id = 2) and (a.value = 1 or (a.id = 3 and a.id = 4))"; 65 | Assert.Equal(expect, q.CommandText); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/SqModelTest/AnalysisTest/SelectItemTest.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using SqModel.Analysis; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | 11 | namespace SqModelTest.AnalysisTest; 12 | 13 | public class SelectItemTest 14 | { 15 | public SelectItemTest(ITestOutputHelper output) 16 | { 17 | Output = output; 18 | } 19 | 20 | private readonly ITestOutputHelper Output; 21 | 22 | private string ToString(List items) 23 | { 24 | var sb = new StringBuilder(); 25 | sb.AppendLine("| SQL | Command | Name |"); 26 | sb.AppendLine("| --- | ------- | ---- |"); 27 | 28 | foreach (var item in items) 29 | { 30 | sb.AppendLine($"| {item.ToQuery().CommandText} | {item.ToCommandText()} | {item.Name} |"); 31 | } 32 | Output.WriteLine(sb.ToString()); 33 | return sb.ToString(); 34 | } 35 | 36 | [Fact] 37 | public void ColumnInfo_Parse() 38 | { 39 | var sq = SqlParser.Parse(@"select 40 | a.val1 41 | , a.val2 as val2 42 | , a.val3 as value3 43 | , 1 44 | , 2 as num2 45 | , a.* 46 | from 47 | table as a"); 48 | 49 | var items = sq.GetSelectItems(); 50 | var s = ToString(items); 51 | 52 | var expect = @"| SQL | Command | Name | 53 | | --- | ------- | ---- | 54 | | a.val1 | a.val1 | val1 | 55 | | a.val2 | a.val2 | val2 | 56 | | a.val3 as value3 | a.val3 | value3 | 57 | | 1 | 1 | | 58 | | 2 as num2 | 2 | num2 | 59 | | a.* | a.* | | 60 | "; 61 | 62 | Assert.Equal(expect, s); 63 | Assert.Equal(6, sq.GetSelectItems().Count); 64 | 65 | Assert.True(sq.ColumnContains("value3")); 66 | var item = sq.GetSelectItemByName("value3"); 67 | Assert.Equal("value3", item.Name); 68 | Assert.Equal("a.val3", item.ToCommandText()); 69 | 70 | Assert.False(sq.ColumnContains("value99")); 71 | } 72 | 73 | [Fact] 74 | public void ColumnInfo_Build() 75 | { 76 | var sq = new SelectQuery(); 77 | var a = sq.From("table").As("a"); 78 | 79 | sq.Select.Add().Column(a, "val1"); 80 | sq.Select.Add().Column(a, "val2").As("val2"); 81 | sq.Select.Add().Column(a, "val3").As("value3"); 82 | sq.Select.Add().Value(1); 83 | sq.Select.Add().Value(2).As("num2"); 84 | sq.SelectAll(a); 85 | 86 | var items = sq.GetSelectItems(); 87 | var s = ToString(items); 88 | 89 | var expect = @"| SQL | Command | Name | 90 | | --- | ------- | ---- | 91 | | a.val1 | a.val1 | val1 | 92 | | a.val2 | a.val2 | val2 | 93 | | a.val3 as value3 | a.val3 | value3 | 94 | | 1 | 1 | | 95 | | 2 as num2 | 2 | num2 | 96 | | a.* | a.* | | 97 | "; 98 | 99 | Assert.Equal(expect, s); 100 | 101 | Assert.Equal(expect, s); 102 | Assert.Equal(6, sq.GetSelectItems().Count); 103 | 104 | Assert.True(sq.ColumnContains("value3")); 105 | var item = sq.GetSelectItemByName("value3"); 106 | Assert.Equal("value3", item.Name); 107 | Assert.Equal("a.val3", item.ToCommandText()); 108 | 109 | Assert.False(sq.ColumnContains("value99")); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /test/SqModelTest/AnalysisTest/TableClauseTest.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using SqModel.Analysis; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | 11 | namespace SqModelTest.AnalysisTest; 12 | 13 | public class TableClauseTest 14 | { 15 | public TableClauseTest(ITestOutputHelper output) 16 | { 17 | Output = output; 18 | } 19 | 20 | private readonly ITestOutputHelper Output; 21 | 22 | private string ToString(List items) 23 | { 24 | var sb = new StringBuilder(); 25 | sb.AppendLine("| Type | Table | Alias | Condition |"); 26 | sb.AppendLine("| --- | ------- | ---- | --------- |"); 27 | 28 | foreach (var item in items) 29 | { 30 | sb.AppendLine($"| {item.RelationType} | {item.TableName} | {item.AliasName} | {item.RelationClause.ToQuery().CommandText} |"); 31 | } 32 | Output.WriteLine(sb.ToString()); 33 | return sb.ToString(); 34 | } 35 | 36 | [Fact] 37 | public void TableInfo_Parse() 38 | { 39 | var sq = SqlParser.Parse(@"select 40 | * 41 | from 42 | a 43 | inner join table_b as b on a.table_a_id = b.table_a_id 44 | left join table_c as c1 on b.table_b_id = c1.table_b_id 45 | right join table_c as c2 on b.table_b_id = c2.table_b_id 46 | inner join (select * from e) as z on a.table_a_id = z.table_a_id"); 47 | 48 | var items = sq.GetTableClauses(); 49 | var s = ToString(items); 50 | 51 | var expect = @"| Type | Table | Alias | Condition | 52 | | --- | ------- | ---- | --------- | 53 | | From | a | a | | 54 | | Inner | table_b | b | a.table_a_id = b.table_a_id | 55 | | Left | table_c | c1 | b.table_b_id = c1.table_b_id | 56 | | Right | table_c | c2 | b.table_b_id = c2.table_b_id | 57 | | Inner | | z | a.table_a_id = z.table_a_id | 58 | "; 59 | 60 | Assert.Equal(expect, s); 61 | 62 | Assert.Equal(5, sq.GetTableClauses().Count); 63 | 64 | Assert.True(sq.TableAliasContains("c1")); 65 | var item = sq.GetTableClauseByName("c1"); 66 | Assert.Equal("c1", item.AliasName); 67 | Assert.Equal("table_c", item.TableName); 68 | 69 | Assert.False(sq.TableAliasContains("table_1")); 70 | 71 | Assert.Equal(2, sq.GetTableClausesByTable("table_c").Count); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /test/SqModelTest/CreateTable.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace SqModelTest; 10 | 11 | public class CreateTable 12 | { 13 | [Fact] 14 | public void Default() 15 | { 16 | var q = new SelectQuery(); 17 | var table_a = q.From("table_a"); 18 | q.Select(table_a, "*"); 19 | 20 | var tq = new CreateTableQuery() { SelectQuery = q, TableName = "tmp" }; 21 | 22 | var text = tq.ToQuery().CommandText; 23 | var expect = @"create table tmp 24 | as 25 | select 26 | table_a.* 27 | from table_a"; 28 | 29 | Assert.Equal(expect, text); 30 | } 31 | 32 | [Fact] 33 | public void Temporary() 34 | { 35 | var q = new SelectQuery(); 36 | var table_a = q.From("table_a"); 37 | q.Select(table_a, "*"); 38 | 39 | var tq = new CreateTableQuery() { SelectQuery = q, TableName = "tmp", IsTemporary = true }; 40 | 41 | var text = tq.ToQuery().CommandText; 42 | var expect = @"create temporary table tmp 43 | as 44 | select 45 | table_a.* 46 | from table_a"; 47 | 48 | Assert.Equal(expect, text); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/SqModelTest/CreateView.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace SqModelTest; 10 | 11 | public class CreateView 12 | { 13 | [Fact] 14 | public void Default() 15 | { 16 | var q = new SelectQuery(); 17 | var table_a = q.From("table_a"); 18 | q.Select(table_a, "*"); 19 | 20 | var tq = new CreateViewQuery() { SelectQuery = q, ViewName = "tmp" }; 21 | 22 | var text = tq.ToQuery().CommandText; 23 | var expect = @"create view tmp 24 | as 25 | select 26 | table_a.* 27 | from table_a"; 28 | 29 | Assert.Equal(expect, text); 30 | } 31 | 32 | [Fact] 33 | public void Temporary() 34 | { 35 | var q = new SelectQuery(); 36 | var table_a = q.From("table_a"); 37 | q.Select(table_a, "*"); 38 | 39 | var tq = new CreateViewQuery() { SelectQuery = q, ViewName = "tmp", IsTemporary = true }; 40 | 41 | var text = tq.ToQuery().CommandText; 42 | var expect = @"create temporary view tmp 43 | as 44 | select 45 | table_a.* 46 | from table_a"; 47 | 48 | Assert.Equal(expect, text); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/SqModelTest/CteQuery.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using SqModel.Analysis; 3 | using Xunit; 4 | 5 | namespace SqModelTest; 6 | 7 | public class CteQuery 8 | { 9 | [Fact] 10 | public void Default() 11 | { 12 | var q = new SelectQuery(); 13 | var ta = q.With("a").As(x => 14 | { 15 | x.From("table_a"); 16 | x.SelectAll(); 17 | }); 18 | 19 | q.From(ta); 20 | q.SelectAll(); 21 | 22 | var text = q.ToQuery().CommandText; 23 | var expect = @"with 24 | a as ( 25 | select 26 | * 27 | from table_a 28 | ) 29 | select 30 | * 31 | from a"; 32 | 33 | Assert.Equal(expect, text); 34 | } 35 | 36 | [Fact] 37 | public void Many() 38 | { 39 | var q = new SelectQuery(); 40 | var cta = q.With("a").As(x => 41 | { 42 | x.From("table_a"); 43 | x.SelectAll(); 44 | }); 45 | var ctb = q.With("b").As(x => 46 | { 47 | x.From("table_b"); 48 | x.SelectAll(); 49 | }); 50 | var ctc = q.With("c").As(x => 51 | { 52 | x.From("table_c"); 53 | x.SelectAll(); 54 | }); 55 | 56 | var ta = q.From(cta); 57 | var tb = ta.InnerJoin(ctb).On("table_a_id"); 58 | tb.InnerJoin(ctc).On("table_b_id"); 59 | 60 | q.SelectAll(); 61 | 62 | var text = q.ToQuery().CommandText; 63 | var expect = @"with 64 | a as ( 65 | select 66 | * 67 | from table_a 68 | ), 69 | b as ( 70 | select 71 | * 72 | from table_b 73 | ), 74 | c as ( 75 | select 76 | * 77 | from table_c 78 | ) 79 | select 80 | * 81 | from a 82 | inner join b on a.table_a_id = b.table_a_id 83 | inner join c on b.table_b_id = c.table_b_id"; 84 | 85 | Assert.Equal(expect, text); 86 | } 87 | 88 | [Fact] 89 | public void PushToCommonTable() 90 | { 91 | var q = new SelectQuery(); 92 | q.From("table_a"); 93 | q.SelectAll(); 94 | 95 | q = q.PushToCommonTable("a"); 96 | 97 | q.From("a"); 98 | q.SelectAll(); 99 | 100 | var text = q.ToQuery().CommandText; 101 | var expect = @"with 102 | a as ( 103 | select 104 | * 105 | from table_a 106 | ) 107 | select 108 | * 109 | from a"; 110 | 111 | Assert.Equal(expect, text); 112 | } 113 | 114 | [Fact] 115 | public void ValuesTest() 116 | { 117 | var sql = @"with 118 | v(c1, c2, c3) as ( 119 | values 120 | (1, 2, 3) 121 | , (4, 5, 6) 122 | ) 123 | select 124 | * 125 | from v"; 126 | var sq = SqlParser.Parse(sql); 127 | var q = sq.ToQuery(); 128 | 129 | Assert.Equal(sql, q.CommandText); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /test/SqModelTest/CteSubquery.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using Xunit; 3 | 4 | namespace SqModelTest; 5 | 6 | public class CteSubquery 7 | { 8 | private SelectQuery CreateCommonQuery(string tableName, string aliasName) 9 | { 10 | var q = new SelectQuery(); 11 | q.With(aliasName).As(x => 12 | { 13 | var t = x.From(tableName); 14 | x.Select(t, "*"); 15 | }); 16 | 17 | var table = q.From(aliasName); 18 | q.Select(table, "*"); 19 | 20 | return q; 21 | } 22 | 23 | [Fact] 24 | public void Default() 25 | { 26 | var commonA = CreateCommonQuery("table_a", "a"); 27 | 28 | var text = commonA.ToQuery().CommandText; 29 | var expect = @"with 30 | a as ( 31 | select 32 | table_a.* 33 | from table_a 34 | ) 35 | select 36 | a.* 37 | from a"; 38 | 39 | Assert.Equal(expect, text); 40 | } 41 | 42 | [Fact] 43 | public void Nest() 44 | { 45 | var commonA = CreateCommonQuery("table_a", "a"); 46 | 47 | var q = new SelectQuery(); 48 | var y = q.From(commonA).As("y"); 49 | q.Select(y, "*"); 50 | 51 | var text = q.ToQuery().CommandText; 52 | var expect = @"with 53 | a as ( 54 | select 55 | table_a.* 56 | from table_a 57 | ) 58 | select 59 | y.* 60 | from ( 61 | select 62 | a.* 63 | from a 64 | ) as y"; 65 | 66 | Assert.Equal(expect, text); 67 | } 68 | 69 | [Fact] 70 | public void Merge() 71 | { 72 | var commonA = CreateCommonQuery("table_a", "a"); 73 | 74 | var commonY = new SelectQuery(); 75 | var table_a = commonY.From(commonA).As("a"); 76 | commonY.Select(table_a, "*"); 77 | 78 | var text = commonY.ToQuery().CommandText; 79 | var expect = @"with 80 | a as ( 81 | select 82 | table_a.* 83 | from table_a 84 | ) 85 | select 86 | a.* 87 | from ( 88 | select 89 | a.* 90 | from a 91 | ) as a"; 92 | 93 | Assert.Equal(expect, text); 94 | 95 | 96 | var q2 = new SelectQuery(); 97 | q2.With("y").As(commonY); 98 | var y = q2.From("y"); 99 | q2.Select(y, "*"); 100 | 101 | text = q2.ToQuery().CommandText; 102 | expect = @"with 103 | a as ( 104 | select 105 | table_a.* 106 | from table_a 107 | ), 108 | y as ( 109 | select 110 | a.* 111 | from ( 112 | select 113 | a.* 114 | from a 115 | ) as a 116 | ) 117 | select 118 | y.* 119 | from y"; 120 | Assert.Equal(expect, text); 121 | } 122 | 123 | [Fact] 124 | public void Valiable() 125 | { 126 | var q1 = CreateCommonQuery("table_a", "a"); 127 | q1.Select(":val1").As("value1").AddParameter(":val1", 1); 128 | 129 | var q2 = new SelectQuery(); 130 | var y = q2.From(q1).As("y"); 131 | q2.Select(y, "*"); 132 | q2.Select(":val2").As("value2").AddParameter(":val2", 2); 133 | 134 | var prm = q2.ToQuery().Parameters; 135 | 136 | Assert.Equal(2, prm.Count); 137 | Assert.Equal(1, prm[":val1"]); 138 | Assert.Equal(2, prm[":val2"]); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /test/SqModelTest/DistinctQuery.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using Xunit; 3 | 4 | namespace SqModelTest; 5 | 6 | public class DistinctQuery 7 | { 8 | [Fact] 9 | public void Default() 10 | { 11 | var q = new SelectQuery(); 12 | var table_a = q.From("table_a").As("a"); 13 | q.Select(table_a, "name"); 14 | q.Select.IsDistinct = true; 15 | q.Distinct(); 16 | 17 | var text = q.ToQuery().CommandText; 18 | var expect = @"select distinct 19 | a.name 20 | from table_a as a"; 21 | 22 | Assert.Equal(expect, text); 23 | } 24 | 25 | [Fact] 26 | public void Ignore() 27 | { 28 | var q = new SelectQuery(); 29 | var table_a = q.From("table_a").As("a"); 30 | q.Select(table_a, "name"); 31 | q.Select.IsDistinct = true; 32 | q.Distinct(false); 33 | 34 | var text = q.ToQuery().CommandText; 35 | var expect = @"select 36 | a.name 37 | from table_a as a"; 38 | 39 | Assert.Equal(expect, text); 40 | } 41 | 42 | [Fact] 43 | public void SubQuery() 44 | { 45 | var q = new SelectQuery(); 46 | var x = q.From(sq => 47 | { 48 | var a = sq.From("table_a").As("a"); 49 | sq.Select(a, "name"); 50 | sq.Distinct(); 51 | }).As("x"); 52 | q.Select(x, "*"); 53 | 54 | var text = q.ToQuery().CommandText; 55 | var expect = @"select 56 | x.* 57 | from ( 58 | select distinct 59 | a.name 60 | from table_a as a 61 | ) as x"; 62 | 63 | Assert.Equal(expect, text); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/SqModelTest/Element.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestPlatform.Utilities; 2 | using SqModel; 3 | using SqModel.Analysis; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Xunit; 10 | using Xunit.Abstractions; 11 | 12 | namespace SqModelTest; 13 | 14 | public class Element 15 | { 16 | public Element(ITestOutputHelper output) 17 | { 18 | Output = output; 19 | } 20 | 21 | private readonly ITestOutputHelper Output; 22 | 23 | [Fact] 24 | public void Default() 25 | { 26 | //using SqModel; 27 | var sq = SqlParser.Parse("select a.id, a.id as col, 1+1 as val from a"); 28 | 29 | var a = sq.FromClause; 30 | sq.Select.Add().Column(a, "column1"); 31 | sq.Select.Add().Column(a, "column2").As("column2"); 32 | sq.Select.Add().Column(a, "column3").As("col3"); 33 | 34 | sq.GetSelectItems().ForEach(x => 35 | { 36 | Output.WriteLine($"> {x.ToQuery().CommandText}"); 37 | Output.WriteLine($" name : {x.Name}"); 38 | Output.WriteLine($" column name : {x.ColumnName}"); 39 | Output.WriteLine($" command text : {x.ToCommandText()}"); 40 | 41 | var expect = string.Empty; 42 | if (x.Command != null) 43 | { 44 | expect = x.Command.ToQuery().CommandText; 45 | } 46 | Assert.Equal(expect, x.ToCommandText()); 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/SqModelTest/Insert.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace SqModelTest; 10 | 11 | public class Insert 12 | { 13 | [Fact] 14 | public void Default() 15 | { 16 | var q = new SelectQuery(); 17 | var table_a = q.From("table_a").As("a"); 18 | q.Select(table_a, "id"); 19 | 20 | var tq = new InsertQuery() { SelectQuery = q, TableName = "table_b" }; 21 | 22 | var text = tq.ToQuery().CommandText; 23 | var expect = @"insert into table_b(id) 24 | select 25 | a.id 26 | from table_a as a"; 27 | 28 | Assert.Equal(expect, text); 29 | } 30 | 31 | [Fact] 32 | public void ColumnNameOmitted() 33 | { 34 | var q = new SelectQuery(); 35 | var table_a = q.From("table_a").As("a"); 36 | q.Select(table_a, "*"); 37 | 38 | var tq = new InsertQuery() { SelectQuery = q, TableName = "table_b" }; 39 | 40 | var text = tq.ToQuery().CommandText; 41 | var expect = @"insert into table_b 42 | select 43 | a.* 44 | from table_a as a"; 45 | 46 | Assert.Equal(expect, text); 47 | } 48 | 49 | [Fact] 50 | public void ColumnNameAlias() 51 | { 52 | var q = new SelectQuery(); 53 | var table_a = q.From("table_a").As("a"); 54 | q.Select(table_a, "id").As("index_value"); 55 | 56 | var tq = new InsertQuery() { SelectQuery = q, TableName = "table_b" }; 57 | 58 | var text = tq.ToQuery().CommandText; 59 | var expect = @"insert into table_b(index_value) 60 | select 61 | a.id as index_value 62 | from table_a as a"; 63 | 64 | Assert.Equal(expect, text); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/SqModelTest/ORMTest.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using SqModel.Analysis; 3 | using SqModel.Extension; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Xunit; 10 | 11 | namespace SqModelTest; 12 | 13 | public class ORMTest 14 | { 15 | public class Model 16 | { 17 | public int ModelId { get; set; } 18 | public string ModelName { get; set; } = string.Empty; 19 | public int? Price { get; set; } 20 | } 21 | 22 | [Fact] 23 | public void ObjectToSelectQueryTest() 24 | { 25 | var c = new Model() { ModelName = "test", Price = 10 }; 26 | 27 | var sq = SqlParser.Parse(c); 28 | var q = sq.ToQuery(); 29 | 30 | var sql = @"select 31 | :modelid as ModelId 32 | , :modelname as ModelName 33 | , :price as Price"; 34 | 35 | Assert.Equal(sql, q.CommandText); 36 | Assert.Equal(0, q.Parameters[":modelid"]); 37 | Assert.Equal("test", q.Parameters[":modelname"]); 38 | Assert.Equal(10, q.Parameters[":price"]); 39 | } 40 | 41 | [Fact] 42 | public void NullValueTest() 43 | { 44 | var c = new Model() { ModelName = "test", Price = null }; 45 | 46 | var sq = SqlParser.Parse(c); 47 | var q = sq.ToQuery(); 48 | 49 | var sql = @"select 50 | :modelid as ModelId 51 | , :modelname as ModelName 52 | , :price as Price"; 53 | 54 | Assert.Equal(sql, q.CommandText); 55 | Assert.Equal(0, q.Parameters[":modelid"]); 56 | Assert.Equal("test", q.Parameters[":modelname"]); 57 | Assert.Null(q.Parameters[":price"]); 58 | } 59 | 60 | [Fact] 61 | public void ObjectToInsertQueryTest() 62 | { 63 | var c = new Model() { ModelName = "test", Price = 10 }; 64 | 65 | var sq = SqlParser.Parse(c); 66 | 67 | //remove seq column 68 | var keys = new List(); 69 | keys.Add("ModelId"); 70 | var q = sq.ToInsertQuery("models", keys); 71 | 72 | var sql = @"insert into models(ModelName, Price) 73 | select 74 | :modelname as ModelName 75 | , :price as Price"; 76 | 77 | Assert.Equal(sql, q.CommandText); 78 | Assert.Equal("test", q.Parameters[":modelname"]); 79 | Assert.Equal(10, q.Parameters[":price"]); 80 | } 81 | 82 | [Fact] 83 | public void ObjectToInsertQueryTest2() 84 | { 85 | var c = new Model() { ModelName = "test", Price = 10 }; 86 | 87 | var sq = SqlParser.Parse(c, nameconverter: x => x.ToSnakeCase().ToLower()); 88 | 89 | //remove seq column 90 | var keys = new List(); 91 | keys.Add("model_id"); 92 | var q = sq.ToInsertQuery("models", keys); 93 | 94 | var sql = @"insert into models(model_name, price) 95 | select 96 | :modelname as model_name 97 | , :price as price"; 98 | 99 | Assert.Equal(sql, q.CommandText); 100 | Assert.Equal("test", q.Parameters[":modelname"]); 101 | Assert.Equal(10, q.Parameters[":price"]); 102 | } 103 | 104 | [Fact] 105 | public void ObjectToUpdateQueryTest() 106 | { 107 | var c = new Model() { ModelName = "test", Price = 10 }; 108 | 109 | var sq = SqlParser.Parse(c); 110 | 111 | var keys = new List(); 112 | keys.Add("ModelId"); 113 | var q = sq.ToUpdateQuery("models", keys); 114 | 115 | var sql = @"update models as t 116 | set 117 | ModelName = :modelname 118 | , Price = :price 119 | where 120 | t.ModelId = :modelid"; 121 | 122 | Assert.Equal(sql, q.CommandText); 123 | Assert.Equal(0, q.Parameters[":modelid"]); 124 | Assert.Equal("test", q.Parameters[":modelname"]); 125 | Assert.Equal(10, q.Parameters[":price"]); 126 | } 127 | 128 | [Fact] 129 | public void ObjectSelectQueryTest() 130 | { 131 | var sq = SqlParser.Parse(); 132 | 133 | var q = sq.ToQuery(); 134 | var sql = @"select 135 | t.modelid 136 | , t.modelname 137 | , t.price 138 | from model as t"; 139 | 140 | Assert.Equal(sql, q.CommandText); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /test/SqModelTest/SelectCaseWhen.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using SqModel.Expression; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | 10 | namespace SqModelTest; 11 | 12 | public class SelectCaseWhen 13 | { 14 | [Fact] 15 | public void DefaultCaseWhen() 16 | { 17 | var sq = new SelectQuery(); 18 | var a = sq.From("table_a").As("a"); 19 | 20 | sq.Select.Add().CaseWhen(x => 21 | { 22 | x.Add().When(w => w.Value("a").Equal(1)).Then(10); 23 | x.Add().When(w => w.Column("a", "id").Equal(2)).Then(20); 24 | x.Add().When(w => w.Column(a, "id").Equal(3)).Then(30); 25 | x.Add().WhenGroup(g => 26 | { 27 | g.Add().Column("a", "id").Equal(1); 28 | g.Add().Or().Column("b", "id").Equal(2); 29 | }).Then(40); 30 | }).As("case_1"); 31 | 32 | var q = sq.ToQuery().CommandText; 33 | var expect = @"select 34 | case when a = 1 then 10 when a.id = 2 then 20 when a.id = 3 then 30 when (a.id = 1 or b.id = 2) then 40 end as case_1 35 | from table_a as a"; 36 | 37 | Assert.Equal(expect, q); 38 | } 39 | 40 | [Fact] 41 | public void DefaultCase() 42 | { 43 | var sq = new SelectQuery(); 44 | var a = sq.From("table_a").As("a"); 45 | 46 | sq.Select.Add().Case("1", x => 47 | { 48 | x.Add().When("a").Then(10); 49 | x.Add().When("a", "id").Then(20); 50 | x.Add().When(a, "id").Then(30); 51 | x.Add().When(1).Then(30); 52 | x.Add().When(1).ThenNull(); 53 | x.Add().Else(100); 54 | }).As("case_2"); 55 | 56 | var q = sq.ToQuery().CommandText; 57 | var expect = @"select 58 | case 1 when a then 10 when a.id then 20 when a.id then 30 when 1 then 30 when 1 then null else 100 end as case_2 59 | from table_a as a"; 60 | 61 | Assert.Equal(expect, q); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/SqModelTest/SelectColumn.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using SqModel.Expression; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | 11 | namespace SqModelTest; 12 | 13 | public class SelectColumn 14 | { 15 | public SelectColumn(ITestOutputHelper output) 16 | { 17 | Output = output; 18 | } 19 | 20 | private readonly ITestOutputHelper Output; 21 | 22 | [Fact] 23 | public void Default() 24 | { 25 | var q = new SelectQuery(); 26 | var ta = q.From("table_a").As("a"); 27 | 28 | q.Select.Add().All(); 29 | q.Select.Add().All(ta); 30 | q.Select.Add().All("a"); 31 | 32 | //brief 33 | q.SelectAll(); 34 | q.SelectAll(ta); 35 | q.SelectAll("a"); 36 | 37 | q.Select.Add().Column(ta, "table_a_id").As("id"); 38 | q.Select.Add().Column("a", "table_a_id").As("id"); 39 | 40 | q.Select.Add().Strings(x => 41 | { 42 | x.Add().Column(ta, "table_a_id"); 43 | x.Add().Column(ta, "table_a_id"); 44 | x.Add().Column(ta, "table_a_id"); 45 | }).As("id"); 46 | 47 | q.Select.Add().Concat(x => 48 | { 49 | x.Add().Column(ta, "table_a_id"); 50 | x.Add().Column(ta, "table_a_id"); 51 | x.Add().Column(ta, "table_a_id"); 52 | }).As("id"); 53 | 54 | //brief 55 | q.Select(ta, "table_a_id").As("id"); 56 | q.Select("a", "table_a_id").As("id"); 57 | 58 | q.Select.Add().Value("a.table_a_id").As("id"); 59 | q.Select.Add().Value(10).As("id"); 60 | q.Select.Add().Value(":val1 + :val2").As("id").AddParameter(":val1", 10).AddParameter(":val2", 20); 61 | 62 | //brief 63 | q.Select(":val1 + :val2").As("id").AddParameter(":val1", 10).AddParameter(":val2", 20); 64 | 65 | q.Select.Add().InlineQuery(x => 66 | { 67 | x.From("table_b").As("b"); 68 | x.Select("b.id"); 69 | }).As("b_id"); 70 | 71 | q.Select.Add().CaseWhen(x => 72 | { 73 | x.Add().When(w => w.Value("a").Equal(1)).Then(10); 74 | x.Add().When(w => w.Column("a", "id").Equal(2)).Then(20); 75 | x.Add().When(w => w.Column(ta, "id").Equal(3)).Then(30); 76 | }).As("case_1"); 77 | 78 | q.Select.Add().Case("1", x => 79 | { 80 | x.Add().When("a").Then(10); 81 | x.Add().When("a", "id").Then(20); 82 | x.Add().When(ta, "id").Then(30); 83 | x.Add().When(1).Then(30); 84 | x.Add().Else(100); 85 | }).As("case_2"); 86 | 87 | var acutal = q.ToQuery(); 88 | // var expect = @"select table_a.* 89 | //from table_a 90 | //where 91 | // table_a.id = :id"; 92 | 93 | // Assert.Equal(expect, acutal.CommandText); 94 | // Assert.Single(acutal.Parameters); 95 | // Assert.Equal(1, acutal.Parameters[":id"]); 96 | 97 | q.GetSelectItems().ForEach(x => Output.WriteLine(x.Name)); 98 | } 99 | 100 | [Fact] 101 | public void AddRange() 102 | { 103 | var q = new SelectQuery(); 104 | var ta = q.From("table_a").As("ta"); 105 | var tb = ta.InnerJoin("table_b").As("tb").On("id"); 106 | q.Select.AddRangeOrDefault(ta, new List() { "a", "b", "c" }); 107 | q.Select.AddRangeOrDefault(tb, new List() { "a", "c", "d" }); 108 | var text = q.ToQuery().CommandText; 109 | 110 | var expect = @"select 111 | ta.a 112 | , ta.b 113 | , ta.c 114 | , tb.d 115 | from table_a as ta 116 | inner join table_b as tb on ta.id = tb.id"; 117 | 118 | Assert.Equal(expect, text); 119 | 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /test/SqModelTest/SelectRelationTable.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using System.Collections.Generic; 3 | using Xunit; 4 | using Xunit.Abstractions; 5 | 6 | namespace SqModelTest; 7 | 8 | public class SelectRelationTable 9 | { 10 | public SelectRelationTable(ITestOutputHelper output) 11 | { 12 | Output = output; 13 | } 14 | 15 | private readonly ITestOutputHelper Output; 16 | 17 | [Fact] 18 | public void Default() 19 | { 20 | var q = new SelectQuery(); 21 | var ta = q.From("table_a").As("a"); 22 | var tb = ta.InnerJoin("table_b").As("b").On("table_a_id"); 23 | tb.InnerJoin("table_c").As("c").On("table_b_id", "TABLE_B_ID"); 24 | 25 | q.SelectAll(); 26 | 27 | var text = q.ToQuery().CommandText; 28 | var expect = @"select 29 | * 30 | from table_a as a 31 | inner join table_b as b on a.table_a_id = b.table_a_id 32 | inner join table_c as c on b.table_b_id = c.TABLE_B_ID"; 33 | 34 | Assert.Equal(expect, text); 35 | } 36 | 37 | [Fact] 38 | public void CrossJoin() 39 | { 40 | var q = new SelectQuery(); 41 | var ta = q.From("table_a").As("a"); 42 | ta.CrossJoin("table_b").As("b"); 43 | 44 | q.SelectAll(); 45 | 46 | var text = q.ToQuery().CommandText; 47 | var expect = @"select 48 | * 49 | from table_a as a 50 | cross join table_b as b"; 51 | 52 | Assert.Equal(expect, text); 53 | } 54 | 55 | [Fact] 56 | public void Conditions() 57 | { 58 | var q = new SelectQuery(); 59 | var ta = q.From("table_a").As("a"); 60 | var tb = ta.InnerJoin("table_b").As("b").On(x => 61 | { 62 | x.Add().Value(10).Equal(10); 63 | x.Add().Column("a", "a_id").Equal("b", "b_id"); 64 | x.AddGroup(y => 65 | { 66 | y.Add().Value(10).Equal(10); 67 | y.Add().Or().Column("a", "a_id").Equal("b", "b_id"); 68 | }); 69 | }); 70 | 71 | q.SelectAll(); 72 | 73 | var text = q.ToQuery().CommandText; 74 | var expect = @"select 75 | * 76 | from table_a as a 77 | inner join table_b as b on 10 = 10 and a.a_id = b.b_id and (10 = 10 or a.a_id = b.b_id)"; 78 | 79 | Assert.Equal(expect, text); 80 | } 81 | 82 | [Fact] 83 | public void Conditions_brief() 84 | { 85 | var q = new SelectQuery(); 86 | var ta = q.From("table_a").As("a"); 87 | var tb = ta.InnerJoin("table_b").As("b").On(x => 88 | { 89 | x.Add().Equal("rel1_id"); 90 | x.Add().Equal("rel2_id"); 91 | }); 92 | 93 | q.SelectAll(); 94 | 95 | var text = q.ToQuery().CommandText; 96 | var expect = @"select 97 | * 98 | from table_a as a 99 | inner join table_b as b on a.rel1_id = b.rel1_id and a.rel2_id = b.rel2_id"; 100 | 101 | Assert.Equal(expect, text); 102 | } 103 | 104 | [Fact] 105 | public void Conditions_array() 106 | { 107 | var cols = new List() { "rel1_id", "rel2_id" }; 108 | 109 | var q = new SelectQuery(); 110 | var ta = q.From("table_a").As("a"); 111 | var tb = ta.InnerJoin("table_b").As("b").On(cols); 112 | 113 | q.SelectAll(); 114 | 115 | var text = q.ToQuery().CommandText; 116 | var expect = @"select 117 | * 118 | from table_a as a 119 | inner join table_b as b on a.rel1_id = b.rel1_id and a.rel2_id = b.rel2_id"; 120 | 121 | Assert.Equal(expect, text); 122 | } 123 | 124 | [Fact] 125 | public void Relations() 126 | { 127 | var q = new SelectQuery(); 128 | var ta = q.From("table_a").As("a"); 129 | var tb = ta.InnerJoin("table_b").As("b").On("table_a_id"); 130 | var tc = ta.LeftJoin("table_c").As("c").On("table_a_id"); 131 | var td = tc.LeftJoin("table_d").As("d").On("table_c_id"); 132 | var te = ta.RightJoin("table_e").As("e").On("table_a_id"); 133 | ta.CrossJoin("table_f").As("f"); 134 | 135 | q.SelectAll(); 136 | 137 | var text = q.ToQuery().CommandText; 138 | var expect = @"select 139 | * 140 | from table_a as a 141 | inner join table_b as b on a.table_a_id = b.table_a_id 142 | left join table_c as c on a.table_a_id = c.table_a_id 143 | left join table_d as d on c.table_c_id = d.table_c_id 144 | right join table_e as e on a.table_a_id = e.table_a_id 145 | cross join table_f as f"; 146 | 147 | Assert.Equal(expect, text); 148 | 149 | q.GetTableClauses().ForEach(x => Output.WriteLine(x.AliasName)); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /test/SqModelTest/SelectSingleTable.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using SqModel.Analysis; 3 | using Xunit; 4 | 5 | namespace SqModelTest; 6 | 7 | public class SelectSingleTable 8 | { 9 | [Fact] 10 | public void SelectAll() 11 | { 12 | var q = new SelectQuery(); 13 | var table_a = q.From("table_a"); 14 | q.Select(table_a, "*"); 15 | 16 | var text = q.ToQuery().CommandText; 17 | var expect = @"select 18 | table_a.* 19 | from table_a"; 20 | 21 | Assert.Equal(expect, text); 22 | } 23 | 24 | [Fact] 25 | public void TableNameAlias() 26 | { 27 | var q = new SelectQuery(); 28 | var table_a = q.From("table_a").As("a"); 29 | q.Select(table_a, "*"); 30 | 31 | var text = q.ToQuery().CommandText; 32 | var expect = @"select 33 | a.* 34 | from table_a as a"; 35 | 36 | Assert.Equal(expect, text); 37 | } 38 | 39 | [Fact] 40 | public void SelectColumn() 41 | { 42 | var q = new SelectQuery(); 43 | var table_a = q.From("table_a").As("a"); 44 | q.Select(table_a, "column_x"); 45 | 46 | var text = q.ToQuery().CommandText; 47 | var expect = @"select 48 | a.column_x 49 | from table_a as a"; 50 | 51 | Assert.Equal(expect, text); 52 | } 53 | 54 | [Fact] 55 | public void SelectColumnWithAlias() 56 | { 57 | var q = new SelectQuery(); 58 | var table_a = q.From("table_a").As("a"); 59 | q.Select(table_a, "column_x").As("x"); 60 | 61 | var text = q.ToQuery().CommandText; 62 | var expect = @"select 63 | a.column_x as x 64 | from table_a as a"; 65 | 66 | Assert.Equal(expect, text); 67 | } 68 | 69 | [Fact] 70 | public void SelectStaticValue() 71 | { 72 | var q = new SelectQuery(); 73 | var table_a = q.From("table_a").As("a"); 74 | q.Select("'test'").As("test"); 75 | 76 | var actual = q.ToQuery(); 77 | var text = actual.CommandText; 78 | var expect = @"select 79 | 'test' as test 80 | from table_a as a"; 81 | 82 | Assert.Equal(expect, text); 83 | } 84 | 85 | [Fact] 86 | public void SelectVariable() 87 | { 88 | var q = new SelectQuery(); 89 | var table_a = q.From("table_a").As("a"); 90 | q.Select(":val").As("value").AddParameter(":val", 1); 91 | 92 | var actual = q.ToQuery(); 93 | var text = actual.CommandText; 94 | var expect = @"select 95 | :val as value 96 | from table_a as a"; 97 | 98 | Assert.Equal(expect, text); 99 | Assert.Equal(1, actual.Parameters[":val"]); 100 | } 101 | 102 | [Fact] 103 | public void SelectColumns() 104 | { 105 | var q = new SelectQuery(); 106 | var table_a = q.From("table_a").As("a"); 107 | 108 | q.Select(table_a, "column_x").As("x"); 109 | q.Select(":val").AddParameter(":val", 1).As("value"); 110 | 111 | var actual = q.ToQuery(); 112 | var text = actual.CommandText; 113 | var expect = @"select 114 | a.column_x as x 115 | , :val as value 116 | from table_a as a"; 117 | 118 | Assert.Equal(expect, text); 119 | Assert.Equal(1, actual.Parameters[":val"]); 120 | } 121 | 122 | 123 | [Fact] 124 | public void ValuesTest() 125 | { 126 | var sql = @"select 127 | * 128 | from ( 129 | values 130 | (1, 2, 3) 131 | , (4, 5, 6) 132 | ) as v(c1, c2, c3)"; 133 | var sq = SqlParser.Parse(sql); 134 | var q = sq.ToQuery(); 135 | 136 | Assert.Equal(sql, q.CommandText); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /test/SqModelTest/SelectSubquery.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using Xunit; 3 | 4 | namespace SqModelTest; 5 | 6 | public class SelectSubquery 7 | { 8 | [Fact] 9 | public void Default() 10 | { 11 | var q = new SelectQuery(); 12 | q.From(x => 13 | { 14 | x.From("table_a").As("a"); 15 | x.SelectAll(); 16 | }).As("b"); 17 | q.SelectAll(); 18 | 19 | var text = q.ToQuery().CommandText; 20 | var expect = @"select 21 | * 22 | from ( 23 | select 24 | * 25 | from table_a as a 26 | ) as b"; 27 | 28 | Assert.Equal(expect, text); 29 | } 30 | 31 | [Fact] 32 | public void SubQueries() 33 | { 34 | var q = new SelectQuery(); 35 | var tb1 = q.From(x => 36 | { 37 | x.From("table_a1").As("a1"); 38 | x.SelectAll(); 39 | }).As("b1"); 40 | tb1.InnerJoin(x => 41 | { 42 | x.From("table_a2").As("a2"); 43 | x.SelectAll(); 44 | }).As("b2").On("id"); 45 | 46 | q.SelectAll(); 47 | 48 | var text = q.ToQuery().CommandText; 49 | var expect = @"select 50 | * 51 | from ( 52 | select 53 | * 54 | from table_a1 as a1 55 | ) as b1 56 | inner join ( 57 | select 58 | * 59 | from table_a2 as a2 60 | ) as b2 on b1.id = b2.id"; 61 | 62 | Assert.Equal(expect, text); 63 | } 64 | 65 | [Fact] 66 | public void Nest() 67 | { 68 | var q = new SelectQuery(); 69 | q.From(x => 70 | { 71 | x.From(y => 72 | { 73 | y.From(z => 74 | { 75 | z.From("table_z").As("z"); 76 | z.SelectAll(); 77 | }).As("y"); 78 | y.SelectAll(); 79 | }).As("x"); 80 | x.SelectAll(); 81 | }).As("a"); 82 | 83 | q.SelectAll(); 84 | 85 | var text = q.ToQuery().CommandText; 86 | var expect = @"select 87 | * 88 | from ( 89 | select 90 | * 91 | from ( 92 | select 93 | * 94 | from ( 95 | select 96 | * 97 | from table_z as z 98 | ) as y 99 | ) as x 100 | ) as a"; 101 | 102 | Assert.Equal(expect, text); 103 | } 104 | 105 | [Fact] 106 | public void Composition() 107 | { 108 | var q1 = new SelectQuery(); 109 | q1.From("table_a").As("a"); 110 | q1.SelectAll(); 111 | 112 | var q2 = new SelectQuery(); 113 | q2.From(q1).As("b"); 114 | q2.SelectAll(); 115 | 116 | var q3 = new SelectQuery(); 117 | q3.From(q2).As("c"); 118 | q3.SelectAll(); 119 | 120 | var text = q3.ToQuery().CommandText; 121 | var expect = @"select 122 | * 123 | from ( 124 | select 125 | * 126 | from ( 127 | select 128 | * 129 | from table_a as a 130 | ) as b 131 | ) as c"; 132 | 133 | Assert.Equal(expect, text); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /test/SqModelTest/SqModelTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | runtime; build; native; contentfiles; analyzers; buildtransitive 27 | all 28 | 29 | 30 | runtime; build; native; contentfiles; analyzers; buildtransitive 31 | all 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /test/SqModelTest/WhereQuery.cs: -------------------------------------------------------------------------------- 1 | using SqModel; 2 | using SqModel.Expression; 3 | using Xunit; 4 | 5 | namespace SqModelTest; 6 | 7 | public class WhereQuery 8 | { 9 | [Fact] 10 | public void Default() 11 | { 12 | var q = new SelectQuery(); 13 | var table_a = q.From("table_a"); 14 | q.Select(table_a, "*"); 15 | q.Where.Add().Column(table_a, "id").Equal(":id").Parameter(":id", 1); 16 | 17 | var acutal = q.ToQuery(); 18 | var expect = @"select 19 | table_a.* 20 | from table_a 21 | where 22 | table_a.id = :id"; 23 | 24 | Assert.Equal(expect, acutal.CommandText); 25 | Assert.Single(acutal.Parameters); 26 | Assert.Equal(1, acutal.Parameters[":id"]); 27 | } 28 | 29 | [Fact] 30 | public void CommandText() 31 | { 32 | var q = new SelectQuery(); 33 | var table_a = q.From("table_a"); 34 | q.Select(table_a, "*"); 35 | q.Where.Add().Value("table_a.id").NotEqual(":id").Parameter(":id", 1); 36 | 37 | var acutal = q.ToQuery(); 38 | var expect = @"select 39 | table_a.* 40 | from table_a 41 | where 42 | table_a.id <> :id"; 43 | 44 | Assert.Equal(expect, acutal.CommandText); 45 | Assert.Single(acutal.Parameters); 46 | Assert.Equal(1, acutal.Parameters[":id"]); 47 | } 48 | 49 | [Fact] 50 | public void OperatorAnd() 51 | { 52 | var q = new SelectQuery(); 53 | var table_a = q.From("table_a"); 54 | q.Select(table_a, "*"); 55 | q.Where.Add().Value("table_a.id").Equal(":id").Parameter(":id", 1); 56 | q.Where.Add().Value("table_a.sub_id").Equal(":sub_id").Parameter(":sub_id", 2); 57 | 58 | var acutal = q.ToQuery(); 59 | var expect = @"select 60 | table_a.* 61 | from table_a 62 | where 63 | table_a.id = :id 64 | and table_a.sub_id = :sub_id"; 65 | 66 | Assert.Equal(expect, acutal.CommandText); 67 | Assert.Equal(2, acutal.Parameters.Count); 68 | Assert.Equal(1, acutal.Parameters[":id"]); 69 | Assert.Equal(2, acutal.Parameters[":sub_id"]); 70 | } 71 | 72 | [Fact] 73 | public void OperatorOr() 74 | { 75 | var q = new SelectQuery(); 76 | var table_a = q.From("table_a"); 77 | q.Select(table_a, "*"); 78 | q.Where.AddGroup(x => 79 | { 80 | x.Add().Or().Value("table_a.id").Equal(":id1").Parameter(":id1", 1); 81 | x.Add().Or().Value("table_a.id").Equal(":id2").Parameter(":id2", 2); 82 | }); 83 | 84 | var acutal = q.ToQuery(); 85 | var expect = @"select 86 | table_a.* 87 | from table_a 88 | where 89 | (table_a.id = :id1 or table_a.id = :id2)"; 90 | 91 | Assert.Equal(expect, acutal.CommandText); 92 | Assert.Equal(2, acutal.Parameters.Count); 93 | Assert.Equal(1, acutal.Parameters[":id1"]); 94 | Assert.Equal(2, acutal.Parameters[":id2"]); 95 | } 96 | 97 | [Fact] 98 | public void OperatorComposite() 99 | { 100 | var q = new SelectQuery(); 101 | var table_a = q.From("table_a"); 102 | q.Select(table_a, "*"); 103 | 104 | q.Where.AddGroup(x => 105 | { 106 | x.Add().Or().Value("table_a.id").Equal(":id1").Parameter(":id1", 1); 107 | x.Add().Or().Value("table_a.id").Equal(":id2").Parameter(":id2", 2); 108 | }); 109 | q.Where.Add().Value("table_a.sub_id").Equal(":sub_id").Parameter(":sub_id", 2); 110 | 111 | var acutal = q.ToQuery(); 112 | var expect = @"select 113 | table_a.* 114 | from table_a 115 | where 116 | (table_a.id = :id1 or table_a.id = :id2) 117 | and table_a.sub_id = :sub_id"; 118 | 119 | Assert.Equal(expect, acutal.CommandText); 120 | Assert.Equal(3, acutal.Parameters.Count); 121 | Assert.Equal(1, acutal.Parameters[":id1"]); 122 | Assert.Equal(2, acutal.Parameters[":id2"]); 123 | Assert.Equal(2, acutal.Parameters[":sub_id"]); 124 | } 125 | 126 | [Fact] 127 | public void WhereOnly() 128 | { 129 | var q = new SelectQuery(); 130 | q.Where.AddGroup(x => 131 | { 132 | x.Add().Or().Value("table_a.id").Equal(":id1").Parameter(":id1", 1); 133 | x.Add().Or().Value("table_a.id").Equal(":id2").Parameter(":id2", 2); 134 | }); 135 | 136 | q.Where.Add().Column("table_a", "id").Equal("table_b", "id"); 137 | q.Where.Add().Column("table_a", "id").Equal(":sub_id").Parameter(":sub_id", 2); 138 | q.Where.Add().Column("table_a", "id").IsNull(); 139 | q.Where.Add().Column("table_a", "id").IsNotNull(); 140 | 141 | q.Where.AddGroup(x => 142 | { 143 | x.Add().Or().Value("table_a.id").Equal(":id1").Parameter(":id1", 1); 144 | x.Add().Or().Value("table_a.id").Equal(":id2").Parameter(":id2", 2); 145 | }); 146 | 147 | q.Where.Add().Exists(x => 148 | { 149 | x.From("table_x").As("x"); 150 | x.SelectAll(); 151 | x.Where.Add().Equal("x", "table_a", "id"); 152 | }); 153 | 154 | q.Where.Add().Not().Exists(x => 155 | { 156 | x.From("table_x").As("x"); 157 | x.SelectAll(); 158 | x.Where.Add().Column("x", "id").Equal("table_a", "id"); 159 | }); 160 | 161 | q.Where.Add().Case("table_a.id", x => 162 | { 163 | x.Add().When("1").Then("10"); 164 | x.Add().When("2").Then("20"); 165 | x.Add().When("3").Then("30"); 166 | }).Equal("1"); 167 | 168 | q.Where.Add().CaseWhen(x => 169 | { 170 | x.Add().When(w => w.Column("table_a", "id").Equal("1")).Then("10"); 171 | x.Add().When(w => w.Column("table_b", "id").Equal("2")).Then("20"); 172 | x.Add().When(w => w.Column("table_c", "id").Equal("3")).Then("30"); 173 | }).Equal("1"); 174 | 175 | var acutal = q.WhereClause.ToQuery(); 176 | var expect = @"where 177 | (table_a.id = :id1 or table_a.id = :id2) 178 | and table_a.id = table_b.id 179 | and table_a.id = :sub_id 180 | and table_a.id is null 181 | and table_a.id is not null 182 | and (table_a.id = :id1 or table_a.id = :id2) 183 | and exists (select * from table_x as x where x.id = table_a.id) 184 | and not exists (select * from table_x as x where x.id = table_a.id) 185 | and case table_a.id when 1 then 10 when 2 then 20 when 3 then 30 end = 1 186 | and case when table_a.id = 1 then 10 when table_b.id = 2 then 20 when table_c.id = 3 then 30 end = 1"; 187 | 188 | var text = acutal.CommandText; 189 | Assert.Equal(expect, text); 190 | Assert.Equal(3, acutal.Parameters.Count); 191 | Assert.Equal(1, acutal.Parameters[":id1"]); 192 | Assert.Equal(2, acutal.Parameters[":id2"]); 193 | Assert.Equal(2, acutal.Parameters[":sub_id"]); 194 | } 195 | } 196 | --------------------------------------------------------------------------------