├── .gitattributes ├── .gitignore ├── ArchivedPatchNotes.md ├── Chocolatey ├── TSQLFlex.nuspec └── tools │ ├── chocolateyInstall.ps1 │ └── chocolateyUninstall.ps1 ├── Code ├── TSqlFlex.Core.Tests │ ├── CSVRendererTests.cs │ ├── CSharpRenderTests.cs │ ├── DataScriptingTests.cs │ ├── ExcelLauncherTests.cs │ ├── FlexResultSetTests.cs │ ├── InternationalTests.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── QueryRunnerTests.cs │ ├── RegistryTests.cs │ ├── SchemaScriptingTests.cs │ ├── TSqlFlex.Core.Tests.csproj │ ├── TSqlRulesTests.cs │ ├── XmlSpreadsheetRenderTests.cs │ └── packages.config ├── TSqlFlex.Core │ ├── CSVRenderer.cs │ ├── CSharpRenderer.cs │ ├── Config.cs │ ├── CustomExceptions.cs │ ├── ExcelLauncher.cs │ ├── ExternalProgramLauncher.cs │ ├── FieldScripting.cs │ ├── FlexResult.cs │ ├── FlexResultSet.cs │ ├── Logging.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── QueryWorker.cs │ ├── Resources │ │ └── XMLSpreadsheetTemplateHeader.txt │ ├── SQLColumn.cs │ ├── SqlRunParameters.cs │ ├── TSqlFlex.Core.csproj │ ├── TSqlRules.cs │ ├── TxtLauncher.cs │ ├── Utils.cs │ └── XmlSpreadsheetRenderer.cs ├── TSqlFlex.sln ├── TSqlFlex │ ├── AboutBox.Designer.cs │ ├── AboutBox.cs │ ├── AboutBox.resx │ ├── Extension.cs │ ├── FlexMainWindow.Designer.cs │ ├── FlexMainWindow.cs │ ├── FlexMainWindow.resx │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── RunCommand.cs │ ├── TSqlFlex.csproj │ └── packages.config └── packages │ └── repositories.config ├── Deploy └── prepDeploy.ps1 ├── InstallationInstructions.md ├── License.md ├── README.md └── Resources └── TSqlFlexScriptToInserts.gif /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Code/Packages/ 2 | Deploy/Binaries/ 3 | 4 | ################# 5 | ## Eclipse 6 | ################# 7 | 8 | *.pydevproject 9 | .project 10 | .metadata 11 | bin/ 12 | tmp/ 13 | *.tmp 14 | *.bak 15 | *.swp 16 | *~.nib 17 | local.properties 18 | .classpath 19 | .settings/ 20 | .loadpath 21 | 22 | # External tool builders 23 | .externalToolBuilders/ 24 | 25 | # Locally stored "Eclipse launch configurations" 26 | *.launch 27 | 28 | # CDT-specific 29 | .cproject 30 | 31 | # PDT-specific 32 | .buildpath 33 | 34 | 35 | ################# 36 | ## Visual Studio 37 | ################# 38 | 39 | ## Ignore Visual Studio temporary files, build results, and 40 | ## files generated by popular Visual Studio add-ons. 41 | 42 | # User-specific files 43 | *.suo 44 | *.user 45 | *.sln.docstates 46 | 47 | .vs/ 48 | 49 | # Build results 50 | 51 | [Dd]ebug/ 52 | [Rr]elease/ 53 | x64/ 54 | build/ 55 | [Bb]in/ 56 | [Oo]bj/ 57 | 58 | # MSTest test Results 59 | [Tt]est[Rr]esult*/ 60 | [Bb]uild[Ll]og.* 61 | 62 | *_i.c 63 | *_p.c 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.pch 68 | *.pdb 69 | *.pgc 70 | *.pgd 71 | *.rsp 72 | *.sbr 73 | *.tlb 74 | *.tli 75 | *.tlh 76 | *.tmp 77 | *.tmp_proj 78 | *.log 79 | *.vspscc 80 | *.vssscc 81 | .builds 82 | *.pidb 83 | *.log 84 | *.scc 85 | 86 | # Visual C++ cache files 87 | ipch/ 88 | *.aps 89 | *.ncb 90 | *.opensdf 91 | *.sdf 92 | *.cachefile 93 | 94 | # Visual Studio profiler 95 | *.psess 96 | *.vsp 97 | *.vspx 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | *.ncrunch* 114 | .*crunch*.local.xml 115 | 116 | # Installshield output folder 117 | [Ee]xpress/ 118 | 119 | # DocProject is a documentation generator add-in 120 | DocProject/buildhelp/ 121 | DocProject/Help/*.HxT 122 | DocProject/Help/*.HxC 123 | DocProject/Help/*.hhc 124 | DocProject/Help/*.hhk 125 | DocProject/Help/*.hhp 126 | DocProject/Help/Html2 127 | DocProject/Help/html 128 | 129 | # Click-Once directory 130 | publish/ 131 | 132 | # Publish Web Output 133 | *.Publish.xml 134 | *.pubxml 135 | 136 | # NuGet Packages Directory 137 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 138 | #packages/ 139 | 140 | # Windows Azure Build Output 141 | csx 142 | *.build.csdef 143 | 144 | # Windows Store app package directory 145 | AppPackages/ 146 | 147 | # Others 148 | sql/ 149 | *.Cache 150 | ClientBin/ 151 | [Ss]tyle[Cc]op.* 152 | ~$* 153 | *~ 154 | *.dbmdl 155 | *.[Pp]ublish.xml 156 | *.pfx 157 | *.publishsettings 158 | 159 | # RIA/Silverlight projects 160 | Generated_Code/ 161 | 162 | # Backup & report files from converting an old project file to a newer 163 | # Visual Studio version. Backup files are not needed, because we have git ;-) 164 | _UpgradeReport_Files/ 165 | Backup*/ 166 | UpgradeLog*.XML 167 | UpgradeLog*.htm 168 | 169 | # SQL Server files 170 | App_Data/*.mdf 171 | App_Data/*.ldf 172 | 173 | ############# 174 | ## Windows detritus 175 | ############# 176 | 177 | # Windows image file caches 178 | Thumbs.db 179 | ehthumbs.db 180 | 181 | # Folder config file 182 | Desktop.ini 183 | 184 | # Recycle Bin used on file shares 185 | $RECYCLE.BIN/ 186 | 187 | # Mac crap 188 | .DS_Store 189 | 190 | 191 | ############# 192 | ## Python 193 | ############# 194 | 195 | *.py[co] 196 | 197 | # Packages 198 | *.egg 199 | *.egg-info 200 | dist/ 201 | build/ 202 | eggs/ 203 | parts/ 204 | var/ 205 | sdist/ 206 | develop-eggs/ 207 | .installed.cfg 208 | 209 | # Installer logs 210 | pip-log.txt 211 | 212 | # Unit test / coverage reports 213 | .coverage 214 | .tox 215 | 216 | #Translations 217 | *.mo 218 | 219 | #Mr Developer 220 | .mr.developer.cfg 221 | 222 | Chocolatey/*.nupkg -------------------------------------------------------------------------------- /ArchivedPatchNotes.md: -------------------------------------------------------------------------------- 1 | 2 | **Archived patch notes:** 3 | * v0.0.11-alpha (2015-01-23): 4 | * Updated to use .NET 4.5 - this version is now required to run T-SQL Flex. 5 | * Improved scripter to continuously increment the "#Result" table number to prevent conflicts in a session. 6 | * Fixed lockup of SSMS when switching between database servers. (#33) 7 | * Fixed missing column header for anonymous columns in Excel export (#29) 8 | * Added WEIGHT, TARGET, and NONE as T-SQL keywords. 9 | * Implemented improved logging. 10 | * v0.0.10-alpha (2014-09-11): 11 | * Implemented drag and drop of database objects from object explorer. 12 | * Fix for launching Excel exported files when there is a space in the profile path name. 13 | * Fix for scripting a field name that includes spaces without bracketizing it. 14 | * v0.0.9-alpha (2014-09-11): 15 | * Fix for "Invalid class string" exception in SSMS 2008 or lower (via pull request from David Pond - many thanks!) 16 | * Fix for "unable to determine identity of domain" isolated storage initialization bug in SSMS 2008 or lower. 17 | * Fixes for CTRL+A and CTRL+C - "Select all" and "copy" keyboard shortcuts now work as expected with T-SQL Flex. 18 | * Additional exception handling during data scripting - hopefully will allow more graceful recovery from out of memory exceptions, in particular. 19 | * Some clarifications to the installation instructions including mentioning that the user must unblock the DLLs. 20 | * Fixed the T-SQL Flex metadata so that the add-in info appears in the "SSMS Add-ins" dialog. 21 | * v0.0.8-alpha (2014-09-06): 22 | * Added about box with version info and links for feedback, issues, and updates. 23 | * Added button to open the latest scripted XML spreadsheet in Excel. 24 | * Fix for some rare cross-thread UI update issues. 25 | * v0.0.7-alpha (2014-09-02): 26 | * Significant bug fixes for internationalization issues surrounding time and number formatting including tests. Cultures where . is used as the time separator and , as a decimal point should work OK now for both Excel and SQL INSERT scripts. Special thanks to Gianluca Sartori (@SpaghettiDBA) for assistance with troubleshooting these issues. 27 | * Bug fixes for incorrect columns and commas appearing in INSERT scripts due to hidden fields. 28 | * Added more line-feeds to the scripted XML Spreadsheet 2003 output. 29 | * Finished refactoring to "format" functions (this is an internal change only). 30 | * v0.0.6-alpha (2014-08-30): 31 | * Significant improvements to exception handling during all phases of querying and scripting 32 | * Significant changes to disk-based buffering. Now uses .NET IsolatedStorage. 33 | * Significant refactoring - moved query processing logic from UI to Core DLL. 34 | * Fixed bug where synthetic columns (select 'a' as [z]) were incorrectly hidden. 35 | * Fixed bug with scripting binary and other byte[] data fields to Excel. 36 | * Updated SIP Framework to 1.0.1.243 (from July 2014). 37 | * v0.0.5-alpha (2014-08-22): 38 | * Export to "XML Spreadsheet 2003" functionality added - this is very early alpha for this feature. 39 | * Started significant refactoring effort for data scripting in T-SQL field vs general presentation. 40 | * Started work to use a file stream rather than a string builder for scripting the data. Currently only used with the Excel XML export. 41 | * v0.0.4-alpha (2014-06-18): 42 | * Converted to background worker. Added cancel button, timer, and progress bar. 43 | * Additional scripted output "minification" improvements (dropping insignificant decimals for example). 44 | * Other improvements to quality of scripted output such as bracketing of keywords. 45 | * v0.0.3-alpha (2014-06-13): Fixed data script escaping bug for single quotes. 46 | * v0.0.2-alpha (2014-06-11): Data scripting implemented. Improved window. 47 | * v0.0.1-alpha (2014-06-01): Initial release. Schema scripting working. 48 | -------------------------------------------------------------------------------- /Chocolatey/TSQLFlex.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tsqlflex 6 | T-SQL Flex 7 | 0.2.1-beta 8 | Steve Ognibene 9 | NYCdotNet 10 | SSMS add-in for scripting and Excel export. 11 | SSMS add-in for scripting and Excel export. 12 | https://github.com/nycdotnet/TSqlFlex 13 | T-SQL Flex SQL Server SSMS 14 | 2017 15 | https://github.com/nycdotnet/TSqlFlex/blob/master/License.md 16 | false 17 | 18 | 19 | 20 | 21 | * v0.2.1-beta (2016-07-13): 22 | * Fixed scripting of TIME to XML Spreadsheet (no longer crashes) 23 | * Improved scripting of TIME to SQL scripts - can include up to 7 digits of scale. 24 | * Improved scripting of DATETIME2 to SQL scripts - now includes scale number if relevant. 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Chocolatey/tools/chocolateyInstall.ps1: -------------------------------------------------------------------------------- 1 | $packageName = "T-SQL Flex"; 2 | $url = "https://github.com/nycdotnet/TSqlFlex/releases/download/0.2.1-beta/TSqlFlex-0.2.1-beta.zip"; 3 | $installLocation = "$env:ProgramData\T-SQL Flex"; 4 | 5 | Install-ChocolateyZipPackage "$packageName" "$url" "$installLocation" 6 | 7 | function Create-RegistryKeyIfNotExists($parentKey, $testKey) { 8 | if ((test-path "$parentKey\$testKey") -eq $false) { 9 | New-Item -Path "$parentKey" -Name "$testKey" | Out-Null 10 | } 11 | if ((test-path "$parentKey\$testKey") -eq $true) { 12 | return "$parentKey\$testKey"; 13 | } 14 | throw "unable to create or confirm existence of '$parentKey\$testKey'"; 15 | } 16 | 17 | # thanks: http://stackoverflow.com/questions/5648931/test-if-registry-value-exists 18 | function Test-RegistryValue($regkey, $name) { 19 | $exists = Get-ItemProperty -Path "$regkey" -Name "$name" -ErrorAction SilentlyContinue 20 | If (($exists -ne $null) -and ($exists.Length -ne 0)) { 21 | Return $true 22 | } 23 | Return $false 24 | } 25 | 26 | function Get-RegistrySoftwareRootKey() { 27 | if (Get-ProcessorBits -eq 64) { 28 | Write-Debug "64-bit processor detected."; 29 | Return "HKLM:\Software\Wow6432Node"; 30 | } 31 | Write-Debug "32-bit processor detected."; 32 | Return "HKLM:\Software"; 33 | } 34 | 35 | 36 | $root = Get-RegistrySoftwareRootKey; 37 | $createdKey = $root; 38 | $addKeys = "Red Gate\SIPFramework\Plugins"; 39 | $addKeys.Split("\") | % { $createdKey = Create-RegistryKeyIfNotExists $createdKey $_ } 40 | 41 | if ((Test-RegistryValue $createdKey "TSQLFlex") -eq $false) { 42 | New-ItemProperty -Name "TSQLFlex" -Path $createdKey -Value "$installLocation\TSqlFlex.dll" | Out-Null 43 | } else { 44 | Set-ItemProperty -Name "TSQLFlex" -Path $createdKey -Value "$installLocation\TSqlFlex.dll" | Out-Null 45 | } 46 | -------------------------------------------------------------------------------- /Chocolatey/tools/chocolateyUninstall.ps1: -------------------------------------------------------------------------------- 1 | $packageName = "T-SQL Flex"; 2 | $installLocation = "$env:ProgramData\T-SQL Flex"; 3 | 4 | function Remove-ItemIfExists($itemName) { 5 | if ((Test-Path $itemName) -eq $true) { 6 | Remove-Item $itemName; 7 | } 8 | } 9 | 10 | # thanks: http://stackoverflow.com/questions/5648931/test-if-registry-value-exists 11 | function Test-RegistryValue($regkey, $name) { 12 | $exists = Get-ItemProperty -Path "$regkey" -Name "$name" -ErrorAction SilentlyContinue 13 | If (($exists -ne $null) -and ($exists.Length -ne 0)) { 14 | Return $true 15 | } 16 | Return $false 17 | } 18 | 19 | function Get-RegistrySoftwareRootKey() { 20 | if (Get-ProcessorBits -eq 64) { 21 | Return "HKLM:\Software\Wow6432Node"; 22 | } 23 | Return "HKLM:\Software"; 24 | } 25 | 26 | Remove-ItemIfExists "$installLocation\RedGate.SIPFrameworkShared.dll"; 27 | Remove-ItemIfExists "$installLocation\TSqlFlex.Core.dll"; 28 | Remove-ItemIfExists "$installLocation\TSqlFlex.dll"; 29 | Remove-ItemIfExists "$installLocation\InstallationInstructions.txt"; 30 | Remove-ItemIfExists "$installLocation\License.txt"; 31 | Remove-ItemIfExists "$installLocation\README.txt"; 32 | 33 | $root = Get-RegistrySoftwareRootKey; 34 | $pluginsPath = "$root\Red Gate\SIPFramework\Plugins"; 35 | if ((Test-RegistryValue $pluginsPath "TSQLFlex") -eq $true) { 36 | Remove-ItemProperty -Name "TSQLFlex" -Path $pluginsPath | Out-Null 37 | } 38 | 39 | try { 40 | Remove-ItemIfExists "$installLocation"; 41 | } catch { 42 | # Don't sweat it. 43 | } 44 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core.Tests/CSVRendererTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | using NUnit.Framework; 8 | 9 | namespace TSqlFlex.Core.Tests 10 | { 11 | [TestFixture()] 12 | class CSVRendererTests 13 | { 14 | [Test()] 15 | public void string_with_no_commas_is_not_enclosed() 16 | { 17 | string testit = "This is a test"; 18 | Assert.AreEqual(testit, TSqlFlex.Core.CSVRenderer.escapeForCSV(testit)); 19 | } 20 | 21 | [Test()] 22 | public void string_with_commas_is_enclosed() 23 | { 24 | string testit = "Developer, Jane Q."; 25 | Assert.AreEqual("\"" + testit + "\"", TSqlFlex.Core.CSVRenderer.escapeForCSV(testit)); 26 | } 27 | 28 | [Test()] 29 | public void string_starting_with_zero_is_enclosed() 30 | { 31 | string testit = "00000000"; 32 | Assert.AreEqual("\"" + testit + "\"", TSqlFlex.Core.CSVRenderer.escapeForCSV(testit)); 33 | } 34 | 35 | [Test()] 36 | public void string_starting_with_numeric_nonzero_is_not_enclosed() 37 | { 38 | string testit = "10000000"; 39 | Assert.AreEqual(testit, TSqlFlex.Core.CSVRenderer.escapeForCSV(testit)); 40 | } 41 | 42 | [Test()] 43 | public void string_that_is_lone_zero_is_not_enclosed() 44 | { 45 | string testit = "0"; 46 | Assert.AreEqual(testit, TSqlFlex.Core.CSVRenderer.escapeForCSV(testit)); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core.Tests/CSharpRenderTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Data.SqlClient; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace TSqlFlex.Core.Tests 9 | { 10 | [TestFixture()] 11 | class CSharpRenderTests 12 | { 13 | [Test()] 14 | public void two_simple_columns_returns_valid_class() 15 | { 16 | var resultSet = new FlexResultSet(); 17 | var result = new FlexResult(); 18 | result.schema = new List() { 19 | new SQLColumn() { ColumnName = "testa", DataType = "int" }, 20 | new SQLColumn() { ColumnName = "testb", DataType = "varchar" } 21 | }; 22 | result.data = new List(); 23 | resultSet.results.Add(result); 24 | 25 | var srp = new SqlRunParameters(new SqlConnectionStringBuilder(), SqlRunParameters.TO_CSHARP, ""); 26 | CSharpRenderer.renderAsCSharp(resultSet, srp); 27 | var expected = @"public class Result0 28 | { 29 | public int testa { get; set; } 30 | public string testb { get; set; } 31 | } 32 | "; 33 | Assert.AreEqual(srp.resultsText.ToString(), expected); 34 | } 35 | 36 | [Test()] 37 | public void handle_duplicate_column_names() 38 | { 39 | var resultSet = new FlexResultSet(); 40 | var result = new FlexResult(); 41 | result.schema = new List() { 42 | new SQLColumn() { ColumnName = "testa", DataType = "int" }, 43 | new SQLColumn() { ColumnName = "testa", DataType = "varchar" } 44 | }; 45 | result.data = new List(); 46 | resultSet.results.Add(result); 47 | 48 | var srp = new SqlRunParameters(new SqlConnectionStringBuilder(), SqlRunParameters.TO_CSHARP, ""); 49 | CSharpRenderer.renderAsCSharp(resultSet, srp); 50 | var expected = @"public class Result0 51 | { 52 | public int testa { get; set; } 53 | public string testa_2 { get; set; } 54 | } 55 | "; 56 | Assert.AreEqual(srp.resultsText.ToString(), expected); 57 | 58 | } 59 | 60 | [TestCase("aaaa", 'a')] 61 | [TestCase("AAAA", 'A')] 62 | [TestCase("zzzz", 'z')] 63 | [TestCase("ZZZZ", 'Z')] 64 | [TestCase("____", '_')] 65 | [TestCase("@_", '@')] 66 | public void FieldNameStartsWithAZazUnderscoreAt_WhenProcessed_FirstCharIsSame( 67 | string fieldName, char expectedFirstChar) 68 | { 69 | Assert.AreEqual(CSharpRenderer.FieldNameToCSharpPropertyName(fieldName)[0], expectedFirstChar); 70 | } 71 | 72 | [TestCase("@", "@")] 73 | [TestCase("@@", "@_")] 74 | [TestCase("@@@", "@__")] 75 | [TestCase("@a@", "@a_")] 76 | public void AtSignAfterFirstCharacter_WhenProcessed_IsReplaced( 77 | string fieldName, string expectedResult) 78 | { 79 | Assert.AreEqual(CSharpRenderer.FieldNameToCSharpPropertyName(fieldName), expectedResult); 80 | } 81 | 82 | 83 | [TestCase("-test", '_')] 84 | [TestCase(" test", '_')] 85 | [TestCase("!test", '_')] 86 | public void FieldNameStartsWithDashSpaceExclaim_WhenProcessed_FirstCharIsUnderscore( 87 | string fieldName, char expectedFirstChar) 88 | { 89 | Assert.AreEqual(CSharpRenderer.FieldNameToCSharpPropertyName(fieldName)[0], expectedFirstChar); 90 | } 91 | 92 | [Test()] 93 | public void EscapedNamesLeadingToACollisionAreHandledCorrectly() 94 | { 95 | var resultSet = new FlexResultSet(); 96 | var result = new FlexResult(); 97 | result.schema = new List() { 98 | new SQLColumn() { ColumnName = "test_1", DataType = "varchar" }, 99 | new SQLColumn() { ColumnName = "test?1", DataType = "varchar" } 100 | }; 101 | result.data = new List(); 102 | resultSet.results.Add(result); 103 | 104 | var srp = new SqlRunParameters(new SqlConnectionStringBuilder(), SqlRunParameters.TO_CSHARP, ""); 105 | CSharpRenderer.renderAsCSharp(resultSet, srp); 106 | var expected = @"public class Result0 107 | { 108 | public string test_1 { get; set; } 109 | public string test_1_2 { get; set; } 110 | } 111 | "; 112 | Assert.AreEqual(srp.resultsText.ToString(), expected); 113 | } 114 | 115 | [Test()] 116 | public void AnonymousColumnsAreHandledCorrectly() 117 | { 118 | var resultSet = new FlexResultSet(); 119 | var result = new FlexResult(); 120 | result.schema = new List() { 121 | new SQLColumn() { ColumnName = "", DataType = "varchar" }, 122 | new SQLColumn() { ColumnName = "", DataType = "int" } 123 | }; 124 | result.data = new List(); 125 | resultSet.results.Add(result); 126 | 127 | var srp = new SqlRunParameters(new SqlConnectionStringBuilder(), SqlRunParameters.TO_CSHARP, ""); 128 | CSharpRenderer.renderAsCSharp(resultSet, srp); 129 | var expected = @"public class Result0 130 | { 131 | public string anonymousProperty { get; set; } 132 | public int anonymousProperty_2 { get; set; } 133 | } 134 | "; 135 | Assert.AreEqual(expected, srp.resultsText.ToString()); 136 | } 137 | 138 | [Test()] 139 | public void NullabilityWorksCorrectly() 140 | { 141 | var resultSet = new FlexResultSet(); 142 | var result = new FlexResult(); 143 | result.schema = new List() { 144 | new SQLColumn() { ColumnName = "a", DataType = "varchar", AllowNulls = true }, 145 | new SQLColumn() { ColumnName = "b", DataType = "int", AllowNulls = true }, 146 | new SQLColumn() { ColumnName = "c", DataType = "smalldatetime", AllowNulls = true } 147 | }; 148 | result.data = new List(); 149 | resultSet.results.Add(result); 150 | 151 | var srp = new SqlRunParameters(new SqlConnectionStringBuilder(), SqlRunParameters.TO_CSHARP, ""); 152 | CSharpRenderer.renderAsCSharp(resultSet, srp); 153 | var expected = @"public class Result0 154 | { 155 | public string a { get; set; } 156 | public int? b { get; set; } 157 | public DateTime? c { get; set; } 158 | } 159 | "; 160 | Assert.AreEqual(expected, srp.resultsText.ToString()); 161 | } 162 | 163 | } 164 | 165 | 166 | } 167 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core.Tests/ExcelLauncherTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using NUnit.Framework; 6 | 7 | 8 | namespace TSqlFlex.Core.Tests 9 | { 10 | [TestFixture()] 11 | class ExcelLauncherTests 12 | { 13 | [Test()] 14 | public void quoteThePath_GivenAnUnquotedString_QuotesIt() 15 | { 16 | Assert.AreEqual("\"C:\\My Folder\\Something.xml\"", TSqlFlex.Core.ExcelLauncher.quoteThePath("C:\\My Folder\\Something.xml")); 17 | } 18 | 19 | [Test()] 20 | public void quoteThePath_GivenAQuotedString_DoesNotQuoteIt() 21 | { 22 | Assert.AreEqual("\"C:\\My Folder\\Something.xml\"", TSqlFlex.Core.ExcelLauncher.quoteThePath("\"C:\\My Folder\\Something.xml\"")); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core.Tests/FlexResultSetTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using NUnit.Framework; 3 | 4 | namespace TSqlFlex.Core.Tests 5 | { 6 | [TestFixture()] 7 | public class FlexResultSetTests 8 | { 9 | [Test()] 10 | public void CreatingEmptyFlexResultSet_ResultsInEmptyCollections() 11 | { 12 | FlexResultSet fsr = new FlexResultSet(); 13 | Assert.IsNotNull(fsr); 14 | 15 | Assert.IsNotNull(fsr.results); 16 | Assert.AreEqual(0, fsr.results.Count); 17 | } 18 | 19 | [Test()] 20 | public void ResultSet_WithNoReturnedSchema_ResultsInNoReturnedSchemaComment() 21 | { 22 | FlexResultSet fsr = new FlexResultSet(); 23 | 24 | var result = new FlexResult(); 25 | 26 | fsr.results.Add(result); 27 | 28 | Assert.AreEqual("--No schema for result from query.", FieldScripting.ScriptResultDataAsInsert(result, "#result0", FlexResultSet.SQL2008MaxRowsInValuesClause).ToString()); 29 | } 30 | 31 | [Test()] 32 | public void ResultSet_WithNoReturnedData_ResultsInNoReturnedDataComment() 33 | { 34 | FlexResultSet fsr = new FlexResultSet(); 35 | 36 | var dt = new List() { 37 | SchemaScriptingTests.FakeColumn("IntNotNull", "MyStuff", 32, "int", false, 255, 255), 38 | SchemaScriptingTests.FakeColumn("IntNull", "MyStuff", 32, "int", true, 255, 255) 39 | }; 40 | 41 | FlexResult result = new FlexResult(); 42 | 43 | fsr.results.Add(result); 44 | fsr.results[0].schema = dt; 45 | fsr.results[0].data = new List(); 46 | 47 | Assert.AreEqual("--No rows were returned from the query.", FieldScripting.ScriptResultDataAsInsert(result, "#result0", FlexResultSet.SQL2008MaxRowsInValuesClause).ToString()); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core.Tests/InternationalTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Threading; 4 | using NUnit.Framework; 5 | 6 | 7 | namespace TSqlFlex.Core.Tests 8 | { 9 | [TestFixture()] 10 | class InternationalTests 11 | { 12 | private CultureInfo originalCulture; 13 | private CultureInfo originalUICulture; 14 | 15 | [OneTimeSetUp()] 16 | public void StartCultureTesting() 17 | { 18 | originalCulture = Thread.CurrentThread.CurrentCulture; 19 | originalUICulture = Thread.CurrentThread.CurrentUICulture; 20 | } 21 | 22 | [OneTimeTearDown()] 23 | public void EndCultureTesting() 24 | { 25 | Thread.CurrentThread.CurrentCulture = originalCulture; 26 | Thread.CurrentThread.CurrentUICulture = originalUICulture; 27 | } 28 | 29 | [Test()] 30 | public void DATETIME2_WhenTimeSeparatorIsDot_ScriptsCorrectly() 31 | { 32 | 33 | setToItalianUsingDotTimeSeparator(); 34 | 35 | DateTime baseData = new DateTime(2000, 10, 31, 2, 33, 44); 36 | object data = baseData; 37 | 38 | Assert.AreEqual("'2000-10-31T02:33:44'", FieldScripting.formatDateTime(data, true), "we expect to have : as the time separator despite the set culture."); 39 | 40 | } 41 | 42 | [Test()] 43 | public void DECIMAL_WhenDecimalSeparatorIsPeriod_ScriptsCorrectly() 44 | { 45 | setToItalianUsingCommaDecimalSeparator(); 46 | 47 | Decimal baseData = 1.5M; 48 | object data = baseData; 49 | 50 | Assert.AreEqual("1.5", FieldScripting.formatDecimal(data), "we expect to have . as the decimal separator despite the set culture."); 51 | } 52 | 53 | [Test()] 54 | public void DOUBLE_WhenDecimalSeparatorIsPeriod_ScriptsCorrectly() 55 | { 56 | setToItalianUsingCommaDecimalSeparator(); 57 | 58 | double baseData = 1.5; 59 | object data = baseData; 60 | 61 | Assert.AreEqual("1.5", FieldScripting.formatDouble(data), "we expect to have . as the decimal separator despite the set culture."); 62 | } 63 | 64 | [Test()] 65 | public void SINGLE_WhenDecimalSeparatorIsPeriod_ScriptsCorrectly() 66 | { 67 | setToItalianUsingCommaDecimalSeparator(); 68 | 69 | float baseData = 1.5F; 70 | object data = baseData; 71 | 72 | Assert.AreEqual("1.5", FieldScripting.formatSingle(data), "we expect to have . as the decimal separator despite the set culture."); 73 | } 74 | 75 | private static void setToItalianUsingDotTimeSeparator() 76 | { 77 | var usePeriodsForTime = new DateTimeFormatInfo(); 78 | usePeriodsForTime.TimeSeparator = "."; 79 | 80 | var italyWithPeriods = new CultureInfo("it-IT"); 81 | italyWithPeriods.DateTimeFormat = usePeriodsForTime; 82 | 83 | Thread.CurrentThread.CurrentCulture = italyWithPeriods; 84 | Thread.CurrentThread.CurrentUICulture = italyWithPeriods; 85 | } 86 | 87 | 88 | private static void setToItalianUsingCommaDecimalSeparator() 89 | { 90 | var useCommaForDecimalSeparator = new NumberFormatInfo(); 91 | useCommaForDecimalSeparator.NumberDecimalSeparator = ","; 92 | 93 | var italyWithCommas = new CultureInfo("it-IT"); 94 | italyWithCommas.NumberFormat = useCommaForDecimalSeparator; 95 | 96 | Thread.CurrentThread.CurrentCulture = italyWithCommas; 97 | Thread.CurrentThread.CurrentUICulture = italyWithCommas; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TSqlFlex.Core.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TSqlFlex.Core.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2017 Steve Ognibene")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("60024be8-2ce5-43ca-bbbf-b215dee51df8")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core.Tests/QueryRunnerTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace TSqlFlex.Core.Tests 8 | { 9 | [TestFixture()] 10 | class QueryRunnerTests 11 | { 12 | 13 | [Test()] 14 | public void EmptyResultsProducesEmptySqlColumnList() 15 | { 16 | var sut = SQLColumnList.CreateFromSchemaTable(null); 17 | Assert.IsNotNull(sut); 18 | Assert.AreEqual(0, sut.Count); 19 | } 20 | 21 | [Test()] 22 | public void EmptyColumnList_NotRenderableAsCreateTable() 23 | { 24 | var sut = new FlexResult(); 25 | sut.schema = new List(); 26 | Assert.AreEqual(false, FieldScripting.ResultIsRenderableAsCreateTable(sut)); 27 | } 28 | 29 | [Test()] 30 | public void NullColumnList_NotRenderableAsCreateTable() 31 | { 32 | var sut = new FlexResult(); 33 | Assert.AreEqual(false, FieldScripting.ResultIsRenderableAsCreateTable(sut)); 34 | } 35 | 36 | [Test()] 37 | public void PopulatedColumnList_IsRenderableAsCreateTable() 38 | { 39 | var sut = new FlexResult(); 40 | sut.schema = new List() { new SQLColumn() { ColumnName = "test", DataType = "int" }}; 41 | Assert.AreEqual(true, FieldScripting.ResultIsRenderableAsCreateTable(sut)); 42 | } 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core.Tests/RegistryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using NUnit.Framework; 6 | 7 | namespace TSqlFlex.Core.Tests 8 | { 9 | [TestFixture()] 10 | public class RegistryTests 11 | { 12 | 13 | [Test()] 14 | public void GettingRegistryDWordFromKeyThatDoesNotExist_ReturnsNull() 15 | { 16 | var dword = Config.getLocalMachineDWORD("THIS\\Key\\DOES\\NOT\\Exist\\", "askldfhjaslkdfjhasdklfjhasldkf"); 17 | Assert.IsFalse(dword.HasValue); 18 | } 19 | 20 | /// 21 | /// This test assumes there is a registry key "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup" 22 | /// 23 | [Test()] 24 | public void GettingRegistryDWordFromKeyThatExistsButValueThatDoesNot_ReturnsNull() 25 | { 26 | var dword = Config.getLocalMachineDWORD("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup", "askldfhjaslkdfjhasdklfjhasldkf"); 27 | Assert.IsFalse(dword.HasValue); 28 | } 29 | 30 | /// 31 | /// This test assumes there is a registry key "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup" with DWORD LogLevel and a number > 0 32 | /// 33 | [Test()] 34 | public void GettingRegistryDWordFromKeyAndValueThatExistsWorks() 35 | { 36 | var dword = Config.getLocalMachineDWORD("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup", "LogLevel"); 37 | Assert.IsTrue(dword.HasValue); 38 | } 39 | 40 | [Test()] 41 | public void GettingRegistrySzFromKeyThatDoesNotExist_ReturnsNull() 42 | { 43 | var regsz = Config.getLocalMachineREGSZ("THIS\\Key\\DOES\\NOT\\Exist\\", "askldfhjaslkdfjhasdklfjhasldkf"); 44 | Assert.IsNull(regsz); 45 | } 46 | 47 | /// 48 | /// This test assumes there is a registry key "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup" 49 | /// 50 | [Test()] 51 | public void GettingRegistrySzFromKeyThatExistsButValueThatDoesNot_ReturnsNull() 52 | { 53 | var regsz = Config.getLocalMachineREGSZ("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup", "askldfhjaslkdfjhasdklfjhasldkf"); 54 | Assert.IsNull(regsz); 55 | } 56 | 57 | /// 58 | /// This test assumes there is a registry key "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup" with REG_SZ BootDir with content 59 | /// 60 | [Test()] 61 | public void GettingRegistrySzFromKeyAndValueThatExistsWorks() 62 | { 63 | var regsz = Config.getLocalMachineREGSZ("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup", "BootDir"); 64 | Assert.IsNotNull(regsz); 65 | Assert.IsTrue(regsz.Length > 0); 66 | } 67 | 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core.Tests/TSqlFlex.Core.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Debug 8 | AnyCPU 9 | {4BBD123B-0D08-4E16-A990-11A310C522BE} 10 | Library 11 | Properties 12 | TSqlFlex.Core.Tests 13 | TSqlFlex.Core.Tests 14 | v3.5 15 | 512 16 | 17 | 18 | 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | False 40 | ..\..\..\..\..\..\..\Program Files (x86)\Microsoft SQL Server\110\SDK\Assemblies\Microsoft.SqlServer.Types.dll 41 | 42 | 43 | ..\packages\NUnit.3.11.0\lib\net35\nunit.framework.dll 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | {6c3e3ef0-d9a2-4db8-94df-1d71bf3a7cdf} 69 | TSqlFlex.Core 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 82 | 83 | 84 | 85 | 86 | 93 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core.Tests/TSqlRulesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | 4 | namespace TSqlFlex.Core.Tests 5 | { 6 | 7 | [TestFixture()] 8 | class TSqlRulesTests 9 | { 10 | [Test()] 11 | public void ReservedWord_IsConsideredReserved() 12 | { 13 | Assert.AreEqual(true, TSqlRules.IsReservedWord("select")); 14 | } 15 | 16 | [Test()] 17 | public void NonReservedWord_IsNotConsideredReserved() 18 | { 19 | Assert.AreEqual(false, TSqlRules.IsReservedWord("SomeSillyThing")); 20 | } 21 | 22 | [Test()] 23 | public void Space_IsConsideredWhitespace() 24 | { 25 | Assert.AreEqual(true, TSqlRules.ContainsWhitespace("a b")); 26 | } 27 | 28 | [Test()] 29 | public void Tab_IsConsideredWhitespace() 30 | { 31 | Assert.AreEqual(true, TSqlRules.ContainsWhitespace("a\tb")); 32 | } 33 | 34 | [Test()] 35 | public void LineFeed_IsConsideredWhitespace() 36 | { 37 | Assert.AreEqual(true, TSqlRules.ContainsWhitespace("a\nb")); 38 | } 39 | 40 | [Test()] 41 | public void CarriageReturn_IsConsideredWhitespace() 42 | { 43 | Assert.AreEqual(true, TSqlRules.ContainsWhitespace("a\rb")); 44 | } 45 | 46 | [Test()] 47 | public void StringsWithSquareBrackets_AreConsideredAsHavingOne() 48 | { 49 | Assert.AreEqual(true, TSqlRules.ContainsSquareBracket("a[b")); 50 | Assert.AreEqual(true, TSqlRules.ContainsSquareBracket("a]b")); 51 | } 52 | 53 | [Test()] 54 | public void StringWithoutSquareBracket_IsNotConsideredAsHavingOne() 55 | { 56 | Assert.AreEqual(false, TSqlRules.ContainsSquareBracket("ab")); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core.Tests/XmlSpreadsheetRenderTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Data.SqlClient; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Xml; 8 | 9 | namespace TSqlFlex.Core.Tests 10 | { 11 | [TestFixture()] 12 | public class XmlSpreadsheetRenderTests 13 | { 14 | [Test()] 15 | public void RenderingTimeField_RendersCorrectly() 16 | { 17 | var frs = new FlexResultSet(); 18 | 19 | var dt = new List() { 20 | SchemaScriptingTests.FakeColumn("ColIntNotNull", "MyStuff", 32, "int", false, 255, 255), 21 | SchemaScriptingTests.FakeColumn("ColIntNull", "MyStuff", 32, "int", true, 255, 255), 22 | SchemaScriptingTests.FakeColumn("ColTimeOfDay", "MyStuff", 32, "time", true, 255, 255) 23 | }; 24 | 25 | var result = new FlexResult() { 26 | schema = dt, 27 | data = new List() { 28 | new object[] {99, 111, new TimeSpan(0, 00, 00, 00, 001)}, 29 | new object[] {42, null, new TimeSpan(23,59,30)}, 30 | } 31 | }; 32 | 33 | frs.results.Add(result); 34 | 35 | var tempFileName = Guid.NewGuid().ToString() + ".txt"; 36 | 37 | var srp = new SqlRunParameters(new SqlConnectionStringBuilder(), "", SqlRunParameters.TO_XML_SPREADSHEET, tempFileName); 38 | 39 | XmlSpreadsheetRenderer.renderAsXMLSpreadsheet(frs, srp); 40 | 41 | var xmlSpreadsheetContent = srp.getOutputStreamAsString(tempFileName); 42 | 43 | Assert.IsTrue(xmlSpreadsheetContent.Length > 1000, "expected more than 1000 characters of output"); 44 | 45 | Assert.IsTrue(xmlSpreadsheetContent.Contains(">99<")); 46 | Assert.IsTrue(xmlSpreadsheetContent.Contains(">111<")); 47 | Assert.IsTrue(xmlSpreadsheetContent.Contains(">1899-12-31T00:00:00.001<")); 48 | Assert.IsTrue(xmlSpreadsheetContent.Contains(">42<")); 49 | Assert.IsTrue(xmlSpreadsheetContent.Contains(">1899-12-31T23:59:30.000<")); 50 | 51 | XmlDocument doc = null; 52 | 53 | Assert.DoesNotThrow(() => { 54 | doc = new XmlDocument(); 55 | doc.LoadXml(xmlSpreadsheetContent); 56 | }, "expected no exception"); 57 | 58 | Assert.IsNotNull(doc, "expected valid XML"); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core/CSVRenderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace TSqlFlex.Core 7 | { 8 | public class CSVRenderer 9 | { 10 | private static HashSet numberLikeDataTypesForCSV = new HashSet() { "bigint", "numeric", "smallint", "decimal", "smallmoney", "int", "tinyint", "float", "real", "money", "bit" }; 11 | private static HashSet dateLikeDataTypesForCSV = new HashSet() { "date", "datetime2", "time", "datetime", "smalldatetime" }; 12 | 13 | public static void renderAsCSV(FlexResultSet resultSet, SqlRunParameters srp) 14 | { 15 | int writtenResultSets = 0; 16 | for (int i = 0; i < resultSet.results.Count; i++) 17 | { 18 | FlexResult result = resultSet.results[i]; 19 | if (result.schema != null && result.data != null) 20 | { 21 | writtenResultSets += 1; 22 | if (writtenResultSets > 1) 23 | { 24 | srp.openNewOutputStream(); 25 | } 26 | 27 | int columnCount = result.visibleColumnCount; 28 | 29 | //do header 30 | for (int colIndex = 0; colIndex < columnCount; colIndex += 1) 31 | { 32 | srp.WriteToStream(columnName(result, colIndex)); 33 | if (colIndex + 1 < columnCount) 34 | { 35 | srp.WriteToStream(","); 36 | } 37 | else 38 | { 39 | srp.WriteToStream("\r\n"); 40 | } 41 | } 42 | 43 | //do data rows 44 | for (int rowIndex = 0; rowIndex < result.data.Count; rowIndex += 1) 45 | { 46 | for (int colIndex = 0; colIndex < columnCount; colIndex += 1) 47 | { 48 | //todo: fix each of these items to work with the actual scripting stuff (requires finishing major refactoring work). 49 | object fieldData = result.data[rowIndex][colIndex]; 50 | SQLColumn fieldInfo = result.schema[colIndex]; 51 | 52 | if (fieldData == null || fieldData is DBNull) 53 | { 54 | //do nothing 55 | } 56 | else if (numberLikeDataTypesForCSV.Contains(fieldInfo.DataType)) 57 | { 58 | //todo: may be bug in German culture where they use , as the decimal separator. 59 | srp.WriteToStream(FieldScripting.valueAsTSQLLiteral(fieldData, fieldInfo, false)); 60 | } 61 | else if (dateLikeDataTypesForCSV.Contains(fieldInfo.DataType)) 62 | { 63 | srp.WriteToStream(escapeForCSV(String.Format("{0}.{1}", 64 | ((DateTime)fieldData).ToString("s"), 65 | ((DateTime)fieldData).ToString("fff") 66 | ))); 67 | } 68 | else if (fieldInfo.DataType == "binary" || fieldInfo.DataType == "rowversion" || fieldInfo.DataType == "timestamp") 69 | { 70 | byte[] d = (byte[])result.data[rowIndex][colIndex]; 71 | srp.WriteToStream(escapeForCSV(FieldScripting.formatBinary(d, d.Length))); 72 | } 73 | else if (fieldInfo.DataType == "varbinary" || fieldInfo.DataType == "image") 74 | { 75 | srp.WriteToStream(escapeForCSV(FieldScripting.formatVarbinary(fieldData))); 76 | } 77 | else 78 | { 79 | srp.WriteToStream(escapeForCSV(FieldScripting.valueAsTSQLLiteral(fieldData, fieldInfo, false))); 80 | } 81 | 82 | if (colIndex + 1 < columnCount) 83 | { 84 | srp.WriteToStream(","); 85 | } 86 | else 87 | { 88 | srp.WriteToStream("\r\n"); 89 | }; 90 | } 91 | 92 | } 93 | 94 | srp.worksheetIsValid = true; 95 | } 96 | } 97 | } 98 | 99 | private static string columnName(FlexResult result, int zeroBasedColumnIndex) 100 | { 101 | string headerName = (string)result.schema[zeroBasedColumnIndex].ColumnName; 102 | if (headerName == "") 103 | { 104 | return "anonymousColumn" + (zeroBasedColumnIndex + 1).ToString(); 105 | } 106 | return escapeForCSV(headerName); 107 | } 108 | 109 | public static string escapeForCSV(string input) 110 | { 111 | if (string.IsNullOrEmpty(input)) 112 | { 113 | return ""; 114 | } 115 | if (input == "0") 116 | { 117 | return input; 118 | } 119 | if (input.Substring(0, 1) == "0" || 120 | input.Contains('"') || 121 | input.Contains(',') || 122 | input.Contains('\n') || 123 | input.Contains('\r')) 124 | 125 | { 126 | return "\"" + input.Replace("\"", "\"\"") + "\""; 127 | } 128 | return input; 129 | } 130 | 131 | 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core/CSharpRenderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace TSqlFlex.Core 8 | { 9 | public class CSharpRenderer 10 | { 11 | 12 | public static void renderAsCSharp(FlexResultSet resultSet, SqlRunParameters srp) 13 | { 14 | StringBuilder sb = new StringBuilder(); 15 | for (int i = 0; i < resultSet.results.Count; i++) 16 | { 17 | FlexResult result = resultSet.results[i]; 18 | if (result.schema != null && result.data != null) 19 | { 20 | int columnCount = result.visibleColumnCount; 21 | var columnNamesAndCounts = new Dictionary(); 22 | sb.Append(String.Format(classHeader, generateValidCSharpClassName(i))); 23 | for (int colIndex = 0; colIndex < columnCount; colIndex += 1) 24 | { 25 | var s = result.schema[colIndex]; 26 | sb.Append(DisambiguateAndRenderCSharpProperty(s, columnNamesAndCounts)); 27 | } 28 | sb.Append(classFooter); 29 | } 30 | } 31 | srp.resultsText = sb; 32 | } 33 | 34 | private static string generateValidCSharpClassName(int i) 35 | { 36 | return "Result" + i.ToString(); 37 | } 38 | 39 | private static Dictionary SqlDataTypeToCSharp = new Dictionary() { 40 | {"int","int"}, 41 | {"smallint", "short"}, 42 | {"bigint", "long"}, 43 | {"varchar", "string"}, 44 | {"text", "string"}, 45 | {"nvarchar", "string"}, 46 | {"ntext", "string"}, 47 | {"char", "string"}, 48 | {"nchar", "string"}, 49 | {"xml", "string"}, 50 | {"numeric", "decimal"}, 51 | {"smallmoney", "decimal"}, 52 | {"decimal", "decimal"}, 53 | {"tinyint", "byte"}, 54 | {"float", "double"}, 55 | {"real", "single"}, 56 | {"money", "decimal"}, 57 | {"binary", "byte[]"}, 58 | {"varbinary", "byte[]"}, 59 | {"image", "byte[]"}, 60 | {"rowversion", "byte[]"}, 61 | {"timestamp", "byte[]"}, 62 | {"date", "DateTime"}, 63 | {"datetimeoffset", "DateTimeOffset"}, 64 | {"datetime", "DateTime"}, 65 | {"datetime2", "DateTime"}, 66 | {"time", "DateTime"}, 67 | {"smalldatetime", "DateTime"}, 68 | {"bit", "bool"}, 69 | {"uniqueidentifier", "Guid"}, 70 | {"sql_variant", "object"}, 71 | {"hierarchyid", "object"}, 72 | {"geography", "object"}, 73 | {"geometry", "object"} 74 | }; 75 | 76 | public static string DisambiguateAndRenderCSharpProperty(SQLColumn s, Dictionary columnNamesAndCounts) 77 | { 78 | string name = FieldNameToCSharpPropertyName(s.ColumnName); 79 | if (columnNamesAndCounts.ContainsKey(name)) 80 | { 81 | columnNamesAndCounts[name] += 1; 82 | name = string.Format("{0}_{1}", name, columnNamesAndCounts[name]); 83 | } 84 | else 85 | { 86 | columnNamesAndCounts.Add(name, 1); 87 | } 88 | 89 | return RenderCSharpProperty(s, name); 90 | } 91 | 92 | public static string RenderCSharpProperty(SQLColumn s, string name) 93 | { 94 | if (SqlDataTypeToCSharp.ContainsKey(s.DataType)) 95 | { 96 | return string.Format(propertyBoilerplate, 97 | SqlDataTypeToCSharp[s.DataType], 98 | name, 99 | QuestionMarkOrNot(s)); 100 | } 101 | else 102 | { 103 | return string.Format(propertyBoilerplate, "object", name); 104 | } 105 | } 106 | 107 | private static string QuestionMarkOrNot(SQLColumn s) 108 | { 109 | if (!s.AllowNulls) 110 | { 111 | return ""; 112 | } 113 | if (!SqlDataTypeToCSharp.TryGetValue(s.DataType, out var dataType) || dataType == "string") 114 | { 115 | return ""; 116 | } 117 | return "?"; 118 | } 119 | 120 | public static bool IsCSharpLetterCharacter(char value) 121 | { 122 | return char.IsLetter(value) || 123 | CharUnicodeInfo.GetUnicodeCategory(value) == UnicodeCategory.LetterNumber; 124 | } 125 | 126 | public static bool IsCSharpCombiningConnectingOrFormattingCharacter(char value) 127 | { 128 | var category = CharUnicodeInfo.GetUnicodeCategory(value); 129 | return category == UnicodeCategory.NonSpacingMark || 130 | category == UnicodeCategory.SpacingCombiningMark || 131 | category == UnicodeCategory.ConnectorPunctuation || 132 | category == UnicodeCategory.Format; 133 | } 134 | 135 | public static string FieldNameToCSharpPropertyName(string fieldName) 136 | { 137 | if (string.IsNullOrEmpty(fieldName)) 138 | { 139 | return "anonymousProperty"; 140 | } 141 | var fieldNameChars = fieldName.ToCharArray(); 142 | for (int i = 0; i < fieldNameChars.Length; i++) 143 | { 144 | char current = fieldNameChars[i]; 145 | if (i == 0) 146 | { 147 | if (!IsCSharpLetterCharacter(current) && current != '@') 148 | { 149 | fieldNameChars[i] = '_'; 150 | } 151 | } 152 | else 153 | { 154 | if (!IsCSharpLetterCharacter(current) && 155 | !char.IsDigit(current) && 156 | !IsCSharpCombiningConnectingOrFormattingCharacter(current) 157 | ) 158 | { 159 | fieldNameChars[i] = '_'; 160 | } 161 | } 162 | } 163 | return new string(fieldNameChars); 164 | 165 | } 166 | 167 | 168 | private const string classHeader = "public class {0}\r\n{{\r\n"; 169 | private const string classFooter = "}\r\n"; 170 | private const string propertyBoilerplate = " public {0}{2} {1} {{ get; set; }}\r\n"; 171 | 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Microsoft.Win32; 6 | 7 | namespace TSqlFlex.Core 8 | { 9 | public class Config 10 | { 11 | public int CommandTimeoutInSeconds { get; private set; } 12 | public bool LogToDebugger { get; private set; } 13 | public bool VerboseLogging { get; private set; } 14 | public string LogFileName { get; private set; } 15 | 16 | private const string TSqlFlexKey = "Software\\LegendaryApps.com\\T-SQL Flex"; 17 | 18 | public Config() 19 | { 20 | CommandTimeoutInSeconds = getLocalMachineDWORD(TSqlFlexKey, "CommandTimeoutInSeconds").GetValueOrDefault(60 * 5); 21 | LogToDebugger = getLocalMachineDWORD(TSqlFlexKey, "LogToDebugger") == 1; 22 | VerboseLogging = getLocalMachineDWORD(TSqlFlexKey, "VerboseLogging") == 1; 23 | LogFileName = getLocalMachineREGSZ(TSqlFlexKey, "LogFileName") ?? ""; 24 | } 25 | 26 | internal static string getLocalMachineREGSZ(string subKey, string value) 27 | { 28 | try 29 | { 30 | var rk = Registry.LocalMachine.OpenSubKey(subKey, RegistryKeyPermissionCheck.ReadSubTree); 31 | var result = rk?.GetValue(value) as string; 32 | rk?.Close(); 33 | return result; 34 | } 35 | catch (Exception) 36 | { 37 | return null; 38 | } 39 | } 40 | 41 | internal static int? getLocalMachineDWORD(string subKey, string value) 42 | { 43 | try 44 | { 45 | var rk = Registry.LocalMachine.OpenSubKey(subKey, RegistryKeyPermissionCheck.ReadSubTree); 46 | var result = rk?.GetValue(value) as int?; 47 | rk?.Close(); 48 | return result; 49 | } 50 | catch (Exception) 51 | { 52 | return null; 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core/CustomExceptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace TSqlFlex.Core 7 | { 8 | 9 | [Serializable] 10 | public class SqlExecutionException : Exception 11 | { 12 | public SqlExecutionException() { } 13 | public SqlExecutionException(Exception inner) : base(inner.Message, inner) { } 14 | public SqlExecutionException(string message) : base(message) { } 15 | public SqlExecutionException(string message, Exception inner) : base(message, inner) { } 16 | protected SqlExecutionException( 17 | System.Runtime.Serialization.SerializationInfo info, 18 | System.Runtime.Serialization.StreamingContext context) 19 | : base(info, context) { } 20 | } 21 | 22 | 23 | [Serializable] 24 | public class SqlResultProcessingException : Exception 25 | { 26 | public SqlResultProcessingException() { } 27 | public SqlResultProcessingException( string message ) : base( message ) { } 28 | public SqlResultProcessingException( string message, Exception inner ) : base( message, inner ) { } 29 | public SqlResultProcessingException(Exception inner) : base(inner.Message, inner) { } 30 | protected SqlResultProcessingException( 31 | System.Runtime.Serialization.SerializationInfo info, 32 | System.Runtime.Serialization.StreamingContext context ) : base( info, context ) { } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core/ExcelLauncher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Diagnostics; 7 | using Microsoft.Win32; 8 | 9 | 10 | namespace TSqlFlex.Core 11 | { 12 | public class ExcelLauncher : ExternalProgramLauncher 13 | { 14 | public ExcelLauncher() 15 | { 16 | } 17 | 18 | protected override void findProgram() 19 | { 20 | var currVerKey = Registry.ClassesRoot.OpenSubKey("Excel.Sheet\\CurVer"); 21 | if (currVerKey == null) 22 | { 23 | programError = "Can't detect Excel version - no Excel.Sheet\\CurVer in HKCR."; 24 | return; 25 | } 26 | 27 | string currVerCode = currVerKey.GetValue("").ToString(); 28 | if (string.IsNullOrEmpty(currVerCode)) 29 | { 30 | programError = "Can't detect Excel version - no version in Excel.Sheet\\CurVer."; 31 | return; 32 | } 33 | 34 | var cmdKey = Registry.ClassesRoot.OpenSubKey(currVerCode + "\\shell\\Edit\\command"); 35 | if (cmdKey == null) 36 | { 37 | programError = "Couldn't find shell command for Excel version " + currVerCode + "."; 38 | return; 39 | } 40 | 41 | string excelCommand = cmdKey.GetValue("").ToString(); 42 | 43 | int exeIndex = CultureInfo.InvariantCulture.CompareInfo 44 | .IndexOf(excelCommand, ".exe", CompareOptions.IgnoreCase); 45 | 46 | if (exeIndex <= "excel".Length) 47 | { 48 | programError = "Couldn't process Excel.exe name from registry."; 49 | return; 50 | } 51 | 52 | this.programLaunchPath = balanceDoubleQuotes(excelCommand.Substring(0, exeIndex + ".exe".Length)); 53 | 54 | this.programFound = (programLaunchPath.Length > 0 55 | && programError.Length == 0); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core/ExternalProgramLauncher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Diagnostics; 6 | 7 | namespace TSqlFlex.Core 8 | { 9 | public abstract class ExternalProgramLauncher 10 | { 11 | protected bool programFound; 12 | protected string programLaunchPath; 13 | protected string programError; 14 | 15 | protected ExternalProgramLauncher() 16 | { 17 | try 18 | { 19 | this.programFound = false; 20 | this.programError = ""; 21 | this.programLaunchPath = ""; 22 | 23 | findProgram(); 24 | } 25 | catch (Exception ex) 26 | { 27 | this.programFound = false; 28 | this.programLaunchPath = ""; 29 | this.programError = ex.Message; 30 | } 31 | } 32 | 33 | protected abstract void findProgram(); 34 | 35 | public void Launch(string FilePathToOpen) 36 | { 37 | Process.Start(this.programLaunchPath, quoteThePath(FilePathToOpen)); 38 | } 39 | 40 | protected static string balanceDoubleQuotes(string balanceThis) 41 | { 42 | int count = 0; 43 | char[] characters = balanceThis.ToCharArray(); 44 | for (int i = 0; i < characters.Length; i += 1) 45 | { 46 | if (characters[i] == '"') 47 | { 48 | count += 1; 49 | } 50 | } 51 | if (IsOdd(count)) 52 | { 53 | return balanceThis + "\""; 54 | } 55 | return balanceThis; 56 | } 57 | 58 | 59 | internal static string quoteThePath(string FilePath) 60 | { 61 | if (FilePath.Contains("\"")) 62 | { 63 | return FilePath; 64 | } 65 | return "\"" + FilePath + "\""; 66 | } 67 | 68 | protected static bool IsOdd(int value) 69 | { 70 | return value % 2 != 0; 71 | } 72 | 73 | public bool ProgramFound 74 | { 75 | get 76 | { 77 | return programFound; 78 | } 79 | } 80 | 81 | public string ProgramError 82 | { 83 | get 84 | { 85 | return programError; 86 | } 87 | } 88 | 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core/FlexResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Data; 5 | 6 | namespace TSqlFlex.Core 7 | { 8 | public class FlexResult 9 | { 10 | public List schema = null; 11 | public List exceptions = new List(); 12 | public List data = null; 13 | public Int64 recordsAffected = 0; 14 | public int visibleColumnCount { 15 | get { 16 | return schema.Where(col => !col.IsHidden).Count(); 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core/FlexResultSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Data.SqlClient; 5 | using System.Text; 6 | using Microsoft.SqlServer.Types; 7 | using System.ComponentModel; 8 | using System.Diagnostics; 9 | 10 | namespace TSqlFlex.Core 11 | { 12 | public class FlexResultSet 13 | { 14 | 15 | public const int SQL2008MaxRowsInValuesClause = 100; 16 | 17 | public List results = null; 18 | public List exceptions = null; 19 | 20 | public FlexResultSet() { 21 | results = new List(); 22 | exceptions = new List(); 23 | } 24 | 25 | public static FlexResultSet AnalyzeResultWithRollback(SqlConnection openConnection, SqlRunParameters srp, Config config, BackgroundWorker bw = null) 26 | { 27 | 28 | FlexResultSet resultSet = new FlexResultSet(); 29 | 30 | throwExceptionIfConnectionIsNotOpen(openConnection); 31 | 32 | SqlTransaction transaction = openConnection.BeginTransaction("Tran"); 33 | SqlDataReader reader = null; 34 | 35 | try 36 | { 37 | srp.command = new SqlCommand(srp.sqlToRun, openConnection, transaction); 38 | var cmd = srp.command; 39 | cmd.CommandTimeout = config.CommandTimeoutInSeconds; 40 | 41 | //todo: this is a bad way of doing this. Need to abstract further. 42 | bw.ReportProgress(5, "Running query..."); 43 | 44 | reader = executeSQL(resultSet, cmd, reader); 45 | int progress = 50; 46 | bw.ReportProgress(progress, "Processing results..."); 47 | do 48 | { 49 | FlexResult result = new FlexResult(); 50 | if (reader != null) 51 | { 52 | try 53 | { 54 | result.recordsAffected = reader.RecordsAffected; 55 | processSchemaInfo(reader, result); 56 | if (progress < 90) 57 | { 58 | progress += 5; 59 | } 60 | bw.ReportProgress(progress, "Processing results..."); 61 | processData(reader, result); 62 | 63 | if (progress < 90) 64 | { 65 | progress += 5; 66 | } 67 | bw.ReportProgress(progress, "Processing results..."); 68 | } 69 | catch (Exception ex) 70 | { 71 | resultSet.exceptions.Add(new SqlResultProcessingException(ex)); 72 | } 73 | } 74 | foreach (Exception ex in result.exceptions) 75 | { 76 | resultSet.exceptions.Add(ex); 77 | } 78 | resultSet.results.Add(result); 79 | 80 | } while (reader != null && reader.NextResult()); 81 | 82 | } 83 | catch (Exception ex) 84 | { 85 | resultSet.exceptions.Add(new SqlResultProcessingException(ex)); 86 | } 87 | finally 88 | { 89 | cleanupReader(reader); 90 | rollbackTransaction(transaction); 91 | } 92 | return resultSet; 93 | } 94 | 95 | private static void rollbackTransaction(SqlTransaction transaction) 96 | { 97 | if (transaction != null) 98 | { 99 | transaction.Rollback(); 100 | } 101 | } 102 | 103 | private static void cleanupReader(SqlDataReader reader) 104 | { 105 | if (reader != null) 106 | { 107 | if (!reader.IsClosed) 108 | { 109 | reader.Close(); 110 | } 111 | reader.Dispose(); 112 | } 113 | } 114 | 115 | private static SqlDataReader executeSQL(FlexResultSet resultSet, SqlCommand cmd, SqlDataReader reader) 116 | { 117 | try 118 | { 119 | reader = cmd.ExecuteReader(CommandBehavior.KeyInfo); 120 | } 121 | catch (Exception ex) 122 | { 123 | resultSet.exceptions.Add(new SqlExecutionException(ex)); 124 | } 125 | return reader; 126 | } 127 | 128 | private static void throwExceptionIfConnectionIsNotOpen(SqlConnection openConnection) 129 | { 130 | if (openConnection.State != System.Data.ConnectionState.Open) 131 | { 132 | var emptySqlConn = new ArgumentException("The SqlConnection must be open."); 133 | throw emptySqlConn; 134 | } 135 | } 136 | 137 | private static void processData(SqlDataReader reader, FlexResult result) 138 | { 139 | try 140 | { 141 | int fieldCount = reader.FieldCount; 142 | var data = new List(); 143 | while (reader.Read()) 144 | { 145 | var values = new object[fieldCount]; 146 | reader.GetValues(values); 147 | data.Add(values); 148 | } 149 | result.data = data; 150 | } 151 | catch (Exception ex) 152 | { 153 | result.exceptions.Add(ex); 154 | } 155 | } 156 | 157 | private static void processSchemaInfo(SqlDataReader reader, FlexResult result) 158 | { 159 | try 160 | { 161 | result.schema = SQLColumnList.CreateFromSchemaTable(reader.GetSchemaTable()); 162 | } 163 | catch (Exception ex) 164 | { 165 | result.exceptions.Add(ex); 166 | } 167 | } 168 | 169 | public string ScriptResultAsCreateTable(int resultIndex, string tableName) 170 | { 171 | //todo: columnnames must be unique in a table. It's possible to have a result set with duplicate column names, but not a table. 172 | //todo: bug with SELECT * FROM INFORMATION_SCHEMA.Tables - possibly hidden fields?? 173 | if (!FieldScripting.ResultIsRenderableAsCreateTable(results[resultIndex])) 174 | { 175 | return "--No schema for result from query."; 176 | } 177 | 178 | int visibleColumnCount = results[resultIndex].visibleColumnCount; 179 | var rows = results[resultIndex].schema; 180 | StringBuilder buffer = new StringBuilder("CREATE TABLE " + tableName + "(\r\n"); 181 | for (int fieldIndex = 0; fieldIndex < results[resultIndex].visibleColumnCount; fieldIndex++) 182 | { 183 | var fieldInfo = rows[fieldIndex]; 184 | buffer.Append(" " + 185 | FieldScripting.FieldNameOrDefault(fieldInfo, fieldIndex) + 186 | " " + 187 | FieldScripting.DataTypeName(fieldInfo) + 188 | FieldScripting.DataTypeParameterIfAny(fieldInfo) + 189 | " " + 190 | FieldScripting.NullOrNotNull(fieldInfo.AllowNulls) 191 | ); 192 | 193 | if (fieldIndex + 1 < visibleColumnCount) 194 | { 195 | buffer.Append(",\r\n"); 196 | } else { 197 | buffer.Append("\r\n"); 198 | } 199 | } 200 | buffer.Append(");\r\n"); 201 | return buffer.ToString(); 202 | } 203 | 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core/Logging.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace TSqlFlex.Core 9 | { 10 | public class Logging 11 | { 12 | public bool verboseLogging { get; set; } 13 | public bool logToDebugger { get; set; } 14 | public Config config; 15 | 16 | public Exception lastException = null; 17 | 18 | private StreamWriter outputLogStream; 19 | 20 | public Logging(Config instantiatedConfig) 21 | { 22 | config = instantiatedConfig; 23 | initializeLogging(); 24 | } 25 | 26 | ~Logging() 27 | { 28 | if (isLogging()) 29 | { 30 | outputLogStream.Flush(); 31 | outputLogStream.Close(); 32 | } 33 | } 34 | 35 | private void initializeLogging() 36 | { 37 | try 38 | { 39 | logToDebugger = logToDebuggerRegistryValue(); 40 | verboseLogging = verboseLoggingRegistryValue(); 41 | lastException = null; 42 | var lfn = logFileNameRegistryValue(); 43 | if (!String.IsNullOrEmpty(lfn)){ 44 | Stream logStream = new FileStream(lfn, FileMode.Append, FileAccess.Write, FileShare.Read); 45 | outputLogStream = new StreamWriter(logStream, Encoding.UTF8); 46 | LogVerbose("Opened outputLogStream at " + lfn + "."); 47 | } 48 | } 49 | catch (Exception ex) 50 | { 51 | Debug.Print("Exception trying to set up log. " + ex.Message); 52 | } 53 | 54 | } 55 | 56 | 57 | private bool logToDebuggerRegistryValue() 58 | { 59 | return config.LogToDebugger; 60 | } 61 | 62 | private bool verboseLoggingRegistryValue() 63 | { 64 | return config.VerboseLogging; 65 | } 66 | 67 | public string logFileNameRegistryValue() 68 | { 69 | return config.LogFileName; 70 | } 71 | 72 | public void LogVerbose(string TextToLog) 73 | { 74 | if (verboseLogging) 75 | { 76 | Log(TextToLog); 77 | } 78 | } 79 | 80 | public bool isLogging() 81 | { 82 | try 83 | { 84 | if (outputLogStream == null || outputLogStream.BaseStream == null) 85 | { 86 | return false; 87 | } 88 | return (outputLogStream.BaseStream.CanWrite); 89 | } 90 | catch (Exception ex) 91 | { 92 | lastException = ex; 93 | return false; 94 | } 95 | } 96 | 97 | public void Log(string TextToLog) 98 | { 99 | var logging = isLogging(); 100 | if (logging || logToDebugger) 101 | { 102 | string logEntry = "\"" + DateTime.Now.ToString("yyyy-MM-ddThh:mm:ss.fffffff") + "\",\"" + TextToLog.Replace("\"","\"\"") + "\""; 103 | if (logging) 104 | { 105 | try 106 | { 107 | outputLogStream.WriteLine(logEntry); 108 | outputLogStream.Flush(); 109 | } 110 | catch (Exception ex) 111 | { 112 | lastException = ex; 113 | Debug.Print("Exception trying to log: " + ex.Message); 114 | Debug.Print("log entry was: " + logEntry); 115 | } 116 | 117 | } 118 | if (logToDebugger) 119 | { 120 | Debug.Print(logEntry); 121 | } 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("TSqlFlex.Core")] 10 | [assembly: AssemblyDescription("Scripts SQL Server data.")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("TSqlFlex.Core")] 14 | [assembly: AssemblyCopyright("Copyright © 2017 Steve Ognibene")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | [assembly: AssemblyVersion("0.2.1.0")] 18 | [assembly: AssemblyFileVersion("0.2.1.0")] 19 | [assembly: AssemblySemverPrereleaseTag("beta")] 20 | 21 | // Setting ComVisible to false makes the types in this assembly not visible 22 | // to COM components. If you need to access a type in this assembly from 23 | // COM, set the ComVisible attribute to true on that type. 24 | [assembly: ComVisible(false)] 25 | 26 | // The following GUID is for the ID of the typelib if this project is exposed to COM 27 | [assembly: Guid("f28ef710-39c6-4254-a0db-e930d24f2372")] 28 | 29 | //Enable testing internal members of classes. 30 | [assembly: InternalsVisibleTo("TSqlFlex.Core.Tests")] 31 | 32 | [AttributeUsage(AttributeTargets.Assembly)] 33 | public class AssemblySemverPrereleaseTag : Attribute 34 | { 35 | public readonly string tag; 36 | public AssemblySemverPrereleaseTag() : this(string.Empty) { } 37 | public AssemblySemverPrereleaseTag(string value) 38 | { 39 | tag = value; 40 | } 41 | } -------------------------------------------------------------------------------- /Code/TSqlFlex.Core/QueryWorker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data.SqlClient; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace TSqlFlex.Core 9 | { 10 | public static class QueryWorker 11 | { 12 | public static void DoSqlQueryWork(DoWorkEventArgs e, BackgroundWorker bw, Config config) 13 | { 14 | FlexResultSet resultSet = null; 15 | var srp = (SqlRunParameters)e.Argument; 16 | 17 | bw.ReportProgress(1, "Opening connection..."); 18 | SqlConnection conn = null; 19 | string currentTask = ""; 20 | 21 | try 22 | { 23 | using (conn = new SqlConnection(srp.connStringBuilder.ConnectionString)) 24 | { 25 | if (bw.CancellationPending) 26 | { 27 | e.Cancel = true; 28 | return; 29 | } 30 | 31 | currentTask = "while opening SQL connection"; 32 | conn.Open(); 33 | 34 | bw.ReportProgress(2, "Running query..."); 35 | 36 | if (bw.CancellationPending) 37 | { 38 | e.Cancel = true; 39 | return; 40 | } 41 | 42 | currentTask = "while running the query or analyzing the data"; 43 | resultSet = FlexResultSet.AnalyzeResultWithRollback(conn, srp, config, bw); 44 | 45 | currentTask = "while closing the database connection"; 46 | conn.Close(); 47 | 48 | } 49 | } 50 | catch (Exception ex) 51 | { 52 | renderExceptionToSqlRunParameters(currentTask, srp, ex); 53 | } 54 | finally 55 | { 56 | if (conn != null && conn.State != System.Data.ConnectionState.Closed) 57 | { 58 | conn.Close(); 59 | } 60 | } 61 | 62 | if (bw.CancellationPending) 63 | { 64 | e.Cancel = true; 65 | return; 66 | } 67 | 68 | if (resultSet == null) 69 | { 70 | e.Result = srp; 71 | return; 72 | } 73 | 74 | try 75 | { 76 | bw.ReportProgress(90, "Scripting results..."); 77 | renderAndCountExceptions(resultSet, srp); 78 | } 79 | catch (Exception ex) 80 | { 81 | renderExceptionToSqlRunParameters("scripting results", srp, ex); 82 | e.Result = srp; 83 | return; 84 | } 85 | 86 | 87 | if (bw.CancellationPending) 88 | { 89 | e.Cancel = true; 90 | return; 91 | } 92 | 93 | bw.ReportProgress(92, "Scripting results..."); 94 | if (srp.outputType == SqlRunParameters.TO_INSERT_STATEMENTS) 95 | { 96 | try 97 | { 98 | renderSchemaAndData(resultSet, srp); 99 | } 100 | catch (Exception ex) 101 | { 102 | renderExceptionToSqlRunParameters("while rendering schema and dataset", srp, ex); 103 | } 104 | 105 | } 106 | else if (srp.outputType == SqlRunParameters.TO_XML_SPREADSHEET) 107 | { 108 | try 109 | { 110 | XmlSpreadsheetRenderer.renderAsXMLSpreadsheet(resultSet, srp); 111 | } 112 | catch (Exception ex) 113 | { 114 | srp.worksheetIsValid = false; 115 | srp.flushAndCloseOutputStreamIfNeeded(); 116 | renderExceptionToSqlRunParameters("while rendering spreadsheet", srp, ex); 117 | } 118 | } 119 | else if (srp.outputType == SqlRunParameters.TO_CSV) 120 | { 121 | try 122 | { 123 | CSVRenderer.renderAsCSV(resultSet, srp); 124 | } 125 | catch (Exception ex) 126 | { 127 | srp.worksheetIsValid = false; 128 | srp.flushAndCloseOutputStreamIfNeeded(); 129 | renderExceptionToSqlRunParameters("while rendering csv", srp, ex); 130 | } 131 | } 132 | else if (srp.outputType == SqlRunParameters.TO_CSHARP) 133 | { 134 | try 135 | { 136 | CSharpRenderer.renderAsCSharp(resultSet, srp); 137 | } 138 | catch (Exception ex) 139 | { 140 | renderExceptionToSqlRunParameters("while rendering C#", srp, ex); 141 | } 142 | } 143 | e.Result = srp; 144 | } 145 | 146 | private static void renderExceptionToSqlRunParameters(string generalDescriptionOfWhenTheErrorOccurred, SqlRunParameters srp, Exception ex) 147 | { 148 | srp.exceptionsText.Append("--Exception encountered "); 149 | srp.exceptionsText.Append(generalDescriptionOfWhenTheErrorOccurred); 150 | srp.exceptionsText.Append(".\r\n\r\n/* "); 151 | srp.exceptionsText.Append(ex.Message); 152 | srp.exceptionsText.Append("\r\n\r\n"); 153 | srp.exceptionsText.Append(ex.StackTrace); 154 | srp.exceptionsText.Append("\r\n*/"); 155 | } 156 | 157 | private static void renderAndCountExceptions(FlexResultSet resultSet, SqlRunParameters srp) 158 | { 159 | var sb = srp.exceptionsText; 160 | 161 | if (resultSet == null) 162 | { 163 | srp.exceptionCount = 0; 164 | } 165 | else 166 | { 167 | srp.exceptionCount = resultSet.exceptions.Count; 168 | } 169 | 170 | if (srp.exceptionCount > 0) 171 | { 172 | sb.Append(String.Format("--There were {0} exception(s) encountered while running the query.\r\n", resultSet.exceptions.Count)); 173 | } 174 | for (int i = 0; i < srp.exceptionCount; i++) 175 | { 176 | var ex = resultSet.exceptions[i]; 177 | if (ex is SqlResultProcessingException) 178 | { 179 | sb.Append(String.Format("--Error processing result: \"{0}\".\r\n", ex.Message)); 180 | } 181 | else if (ex is SqlExecutionException) 182 | { 183 | sb.Append(String.Format("--Error executing query: \"{0}\".\r\n", ex.Message)); 184 | } 185 | else 186 | { 187 | sb.Append(String.Format("--Error: \"{0}\".\r\n", ex.Message)); 188 | } 189 | } 190 | } 191 | 192 | private static void renderSchemaAndData(FlexResultSet resultSet, SqlRunParameters srp) 193 | { 194 | var sb = srp.resultsText; 195 | for (int i = 0; i < resultSet.results.Count; i++) 196 | { 197 | if (resultSet.results[i].recordsAffected > 0) 198 | { 199 | sb.AppendLine(String.Format("--Records affected: {0:G}\r\n\r\n", resultSet.results[i].recordsAffected)); 200 | } 201 | string resultTableName = "#Result" + (i + 1 + srp.completedResultsCount).ToString(); 202 | sb.AppendLine(resultSet.ScriptResultAsCreateTable(i, resultTableName)); 203 | sb.Append("\r\n"); 204 | 205 | if (FieldScripting.ResultIsRenderableAsScriptedData(resultSet.results[i])) 206 | { 207 | sb.AppendLine(FieldScripting.ScriptResultDataAsInsert(resultSet.results[i], resultTableName, FlexResultSet.SQL2008MaxRowsInValuesClause).ToString()); 208 | } 209 | 210 | srp.completedResultsCount += 1; 211 | 212 | sb.Append("\r\n"); 213 | } 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core/Resources/XMLSpreadsheetTemplateHeader.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 17 | 21 | 24 | 27 | 30 | 31 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core/SQLColumn.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace TSqlFlex.Core 8 | { 9 | public class SQLColumn 10 | { 11 | public string ColumnName { get; set; } 12 | public string BaseTableName { get; set; } 13 | public int ColumnSize { get; set; } 14 | public short NumericPrecision { get; set; } 15 | public short NumericScale { get; set; } 16 | public string DataType { get; set; } 17 | public bool AllowNulls { get; set; } 18 | public bool IsHidden { get; set; } 19 | } 20 | 21 | public static class SQLColumnList 22 | { 23 | public static List CreateFromSchemaTable(DataTable table) 24 | { 25 | if (table == null || table.Columns == null) 26 | { 27 | return new List(); 28 | } 29 | var result = new List(table.Columns.Count); 30 | foreach (DataRow fieldDefinition in table.Rows) 31 | { 32 | result.Add(new SQLColumn() 33 | { 34 | ColumnName = fieldDefinition[(int)FieldScripting.ADONetFieldInfo.Name].ToString(), 35 | BaseTableName = fieldDefinition[11].ToString(), 36 | ColumnSize = (int)fieldDefinition[(int)FieldScripting.ADONetFieldInfo.ColumnSize], 37 | NumericPrecision = (short)fieldDefinition[(int)FieldScripting.ADONetFieldInfo.NumericPrecision], 38 | NumericScale = (short)fieldDefinition[(int)FieldScripting.ADONetFieldInfo.NumericScale], 39 | DataType = fieldDefinition[(int)FieldScripting.ADONetFieldInfo.DataType].ToString(), 40 | AllowNulls = (bool)fieldDefinition[(int)FieldScripting.ADONetFieldInfo.AllowsNulls], 41 | IsHidden = fieldDefinition[(int)FieldScripting.ADONetFieldInfo.IsHidden] == DBNull.Value ? false : (bool)fieldDefinition[(int)FieldScripting.ADONetFieldInfo.IsHidden] 42 | }); 43 | } 44 | return result; 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core/SqlRunParameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.SqlClient; 4 | using System.IO; 5 | using System.IO.IsolatedStorage; 6 | using System.Text; 7 | 8 | namespace TSqlFlex.Core 9 | { 10 | public class SqlRunParameters : IDisposable 11 | { 12 | public const string TO_INSERT_STATEMENTS = "To INSERT Statements"; 13 | public const string TO_XML_SPREADSHEET = "To XML Spreadsheet (Excel)"; 14 | public const string TO_CSV = "To CSV file"; 15 | public const string TO_CSHARP = "To C#"; 16 | 17 | public SqlConnectionStringBuilder connStringBuilder; 18 | public string sqlToRun; 19 | public string outputType; 20 | public StringBuilder exceptionsText = new StringBuilder(); 21 | public StringBuilder resultsText = new StringBuilder(); 22 | private IsolatedStorageFile isolatedStore; 23 | private string currentFileName; 24 | private StreamWriter outputStream; 25 | public int exceptionCount = 0; 26 | public bool worksheetIsValid = false; 27 | public UInt32 completedResultsCount = 0; 28 | public List outputFiles = new List(); 29 | public SqlCommand command; 30 | 31 | public string outputFilename() 32 | { 33 | return this.currentFileName; 34 | } 35 | 36 | 37 | public SqlRunParameters(SqlConnectionStringBuilder csb, string sqlToRun, string outputType, string outputFileName = "") 38 | { 39 | this.connStringBuilder = csb; 40 | this.sqlToRun = sqlToRun; 41 | this.outputType = outputType; 42 | 43 | this.exceptionsText.EnsureCapacity(8000); //ensure there is plenty of reserved room for stack traces / error messages in case of an out of memory error. 44 | 45 | isolatedStore = Utils.getIsolatedStorageFile(); 46 | 47 | openNewOutputStream(outputFileName); 48 | } 49 | 50 | private string getNextFileName() 51 | { 52 | return "TSQLFlex_" + DateTime.UtcNow.ToString("yyyyMMddhhmmss.fffffff") + "_temp_" + outputFiles.Count + ".txt"; 53 | } 54 | 55 | public void openNewOutputStream(string outputFileName = "") 56 | { 57 | flushAndCloseOutputStreamIfNeeded(); 58 | this.currentFileName = String.IsNullOrEmpty(outputFileName) ? getNextFileName() : outputFileName; 59 | outputFiles.Add(this.currentFileName); 60 | 61 | Stream isoStream = new IsolatedStorageFileStream(this.currentFileName, FileMode.OpenOrCreate, FileAccess.Write, isolatedStore); 62 | this.outputStream = new StreamWriter(isoStream, Encoding.UTF8); 63 | } 64 | 65 | public void flushAndCloseOutputStreamIfNeeded() 66 | { 67 | try 68 | { 69 | if (outputStream != null && outputStream.BaseStream != null) 70 | { 71 | outputStream.Flush(); 72 | outputStream.Close(); 73 | } 74 | } 75 | catch (Exception) 76 | { 77 | //silently continue. 78 | } 79 | } 80 | 81 | public virtual void Dispose() 82 | { 83 | flushAndCloseOutputStreamIfNeeded(); 84 | outputStream.Dispose(); 85 | } 86 | 87 | public void WriteToStream(string dataToWrite) 88 | { 89 | outputStream.Write(dataToWrite); 90 | } 91 | 92 | public void saveOutputStreamsTo(string saveAsFileNameWithAsteriskForIndex) 93 | { 94 | if (saveAsFileNameWithAsteriskForIndex.IndexOf("*") == -1 && outputFiles.Count > 1) 95 | { 96 | throw new ArgumentException("Need an * in the file name passed to saveOutputStreamsTo."); 97 | } 98 | 99 | for (int i = 0; i < outputFiles.Count; i+= 1) { 100 | saveOutputStreamTo(outputFiles[i], saveAsFileNameWithAsteriskForIndex.Replace("*", i.ToString())); 101 | } 102 | 103 | } 104 | 105 | public void saveOutputStreamTo(string streamName, string saveAsFileName) 106 | { 107 | flushAndCloseOutputStreamIfNeeded(); 108 | 109 | Stream isoStream = new IsolatedStorageFileStream(streamName, FileMode.Open, FileAccess.Read, this.isolatedStore); 110 | 111 | var outputStreamWriter = new FileStream(saveAsFileName, FileMode.OpenOrCreate, FileAccess.Write); 112 | 113 | CopyStreamToEnd(isoStream, outputStreamWriter); 114 | 115 | outputStreamWriter.Flush(); 116 | outputStreamWriter.Close(); 117 | isoStream.Close(); 118 | } 119 | 120 | public string getOutputStreamAsString(string streamName) 121 | { 122 | flushAndCloseOutputStreamIfNeeded(); 123 | 124 | var isoStream = new IsolatedStorageFileStream(streamName, FileMode.Open, FileAccess.Read, this.isolatedStore); 125 | var reader = new StreamReader(isoStream); 126 | return reader.ReadToEnd(); 127 | } 128 | 129 | //thanks! http://stackoverflow.com/questions/12970957/when-shall-i-do-a-explicity-flush-while-copying-streams-in-c 130 | public static void CopyStreamToEnd(Stream source, Stream destination) 131 | { 132 | byte[] buffer = new byte[32768]; 133 | int bytesReadCount; 134 | while ((bytesReadCount = source.Read(buffer, 0, buffer.Length)) > 0) 135 | { 136 | destination.Write(buffer, 0, bytesReadCount); 137 | } 138 | } 139 | 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core/TSqlFlex.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {6C3E3EF0-D9A2-4DB8-94DF-1D71BF3A7CDF} 8 | Library 9 | Properties 10 | TSqlFlex.Core 11 | TSqlFlex.Core 12 | v3.5 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | false 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | false 34 | 35 | 36 | 37 | False 38 | ..\..\..\..\..\..\..\Program Files (x86)\Microsoft SQL Server\110\SDK\Assemblies\Microsoft.SqlServer.Types.dll 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 78 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core/TSqlRules.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace TSqlFlex.Core 7 | { 8 | public class TSqlRules 9 | { 10 | private static HashSet reservedWords = new HashSet(StringComparer.OrdinalIgnoreCase) { 11 | "action", 12 | "alter", 13 | "as", 14 | "asc", 15 | "auto", 16 | "by", 17 | "close", 18 | "clustered", 19 | "changes", 20 | "column", 21 | "constraint", 22 | "create", 23 | "dec", 24 | "default", 25 | "delete", 26 | "desc", 27 | "drop", 28 | "elements", 29 | "exec", 30 | "execute", 31 | "from", 32 | "function", 33 | "group", 34 | "insert", 35 | "index", 36 | "key", 37 | "level", 38 | "nonclustered", 39 | "none", 40 | "oct", 41 | "open", 42 | "order", 43 | "primary", 44 | "proc", 45 | "procedure", 46 | "raw", 47 | "root", 48 | "rule", 49 | "schema", 50 | "select", 51 | "shift", 52 | "statistics", 53 | "status", 54 | "sysname", 55 | "symmetric", 56 | "table", 57 | "target", 58 | "trigger", 59 | "unique", 60 | "update", 61 | "user", 62 | "version", 63 | "view", 64 | "weight", 65 | "with", 66 | "where", 67 | "xml", 68 | }; 69 | 70 | public static bool IsReservedWord(string word) 71 | { 72 | return reservedWords.Contains(word); 73 | } 74 | 75 | public static bool ContainsWhitespace(string fieldName) 76 | { 77 | return (fieldName.Contains(' ') 78 | || fieldName.Contains('\t') 79 | || fieldName.Contains('\n') 80 | || fieldName.Contains('\r')); 81 | } 82 | 83 | public static bool ContainsSquareBracket(string fieldName) 84 | { 85 | return (fieldName.Contains('[') || fieldName.Contains(']')); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core/TxtLauncher.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace TSqlFlex.Core 9 | { 10 | public class TxtLauncher : ExternalProgramLauncher 11 | { 12 | 13 | public TxtLauncher() { 14 | } 15 | 16 | protected override void findProgram() 17 | { 18 | var associationKey = Registry.ClassesRoot.OpenSubKey(".txt"); 19 | if (associationKey == null) 20 | { 21 | programError = "Can't detect .txt extension registration in HKCR."; 22 | return; 23 | } 24 | 25 | string association = associationKey.GetValue("").ToString(); 26 | if (string.IsNullOrEmpty(association)) 27 | { 28 | programError = "Can't detect .txt association in HKCR."; 29 | return; 30 | } 31 | 32 | var cmdKey = Registry.ClassesRoot.OpenSubKey(association + "\\shell\\open\\command"); 33 | if (cmdKey == null) 34 | { 35 | programError = "Couldn't find shell command for " + association + "."; 36 | return; 37 | } 38 | 39 | string txtCommand = cmdKey.GetValue("").ToString(); 40 | 41 | int exeIndex = CultureInfo.InvariantCulture.CompareInfo 42 | .IndexOf(txtCommand, ".exe", CompareOptions.IgnoreCase); 43 | 44 | this.programLaunchPath = balanceDoubleQuotes(txtCommand.Substring(0, exeIndex + ".exe".Length)); 45 | 46 | this.programFound = (programLaunchPath.Length > 0 47 | && programError.Length == 0); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.SqlClient; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.IO.IsolatedStorage; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Text; 10 | 11 | namespace TSqlFlex.Core 12 | { 13 | public static class Utils 14 | { 15 | public static bool IsValidConnectionStringBuilder(SqlConnectionStringBuilder builder) 16 | { 17 | if (builder != null) 18 | { 19 | if (!string.IsNullOrEmpty(builder.DataSource) && !(string.IsNullOrEmpty(builder.InitialCatalog))) { 20 | return (builder.IntegratedSecurity || !string.IsNullOrEmpty(builder.UserID)); //technically someone could have a blank password? 21 | } 22 | } 23 | return false; 24 | } 25 | 26 | public static string GetResourceByName(string resourceName) 27 | { 28 | string result; 29 | Assembly assembly = Assembly.GetExecutingAssembly(); 30 | using (Stream stream = assembly.GetManifestResourceStream(resourceName)) 31 | using (StreamReader reader = new StreamReader(stream)) 32 | { 33 | result = reader.ReadToEnd(); 34 | } 35 | return result; 36 | } 37 | 38 | //attempt to open all user stores in order of preference. 39 | public static IsolatedStorageFile getIsolatedStorageFile() 40 | { 41 | IsolatedStorageFile isf = null; 42 | try 43 | { 44 | isf = IsolatedStorageFile.GetUserStoreForDomain(); 45 | } 46 | catch (Exception) 47 | { 48 | isf = null; 49 | } 50 | 51 | if (isf == null) 52 | { 53 | try 54 | { 55 | isf = IsolatedStorageFile.GetUserStoreForAssembly(); 56 | } 57 | catch (Exception) 58 | { 59 | isf = null; 60 | } 61 | } 62 | 63 | if (isf == null) 64 | { 65 | try 66 | { 67 | isf = IsolatedStorageFile.GetUserStoreForApplication(); 68 | } 69 | catch (Exception) 70 | { 71 | //since this is the last one, give up and throw the exception. 72 | throw; 73 | } 74 | } 75 | 76 | return isf; 77 | } 78 | 79 | } 80 | 81 | public static class Info 82 | { 83 | 84 | private static string version = null; 85 | public static string Version() { 86 | 87 | if (version != null) 88 | { 89 | return version; 90 | } 91 | 92 | var tag = SemverPrereleaseTag(); 93 | 94 | version = string.Format("v{0}{1}", 95 | VersionNumbersOnly(), 96 | string.IsNullOrEmpty(tag) ? "" : "-" + tag); 97 | 98 | return version; 99 | 100 | } 101 | 102 | private static string versionNumbersOnly = null; 103 | public static string VersionNumbersOnly() 104 | { 105 | if (versionNumbersOnly != null) 106 | { 107 | return versionNumbersOnly; 108 | } 109 | 110 | var vi = VersionInfo(); 111 | versionNumbersOnly = string.Format("{0}.{1}.{2}", 112 | vi.FileMajorPart, 113 | vi.FileMinorPart, 114 | vi.FileBuildPart); 115 | return versionNumbersOnly; 116 | } 117 | 118 | public static FileVersionInfo VersionInfo() 119 | { 120 | Assembly assembly = Assembly.GetExecutingAssembly(); 121 | FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location); 122 | return fvi; 123 | } 124 | 125 | public static string SemverPrereleaseTag() 126 | { 127 | Assembly assembly = Assembly.GetExecutingAssembly(); 128 | 129 | var attributes = assembly 130 | .GetCustomAttributes(typeof(AssemblySemverPrereleaseTag), false) 131 | .Cast().ToArray(); 132 | 133 | if (attributes.Length == 0) 134 | { 135 | return ""; 136 | } 137 | else 138 | { 139 | return attributes[0].tag; 140 | } 141 | 142 | } 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /Code/TSqlFlex.Core/XmlSpreadsheetRenderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace TSqlFlex.Core 8 | { 9 | public class XmlSpreadsheetRenderer 10 | { 11 | public static void renderAsXMLSpreadsheet(FlexResultSet resultSet, SqlRunParameters srp) 12 | { 13 | //todo: refactor this and FlexResultSet to to share code and have test coverage. 14 | srp.WriteToStream(Utils.GetResourceByName("TSqlFlex.Core.Resources.XMLSpreadsheetTemplateHeader.txt")); 15 | for (int i = 0; i < resultSet.results.Count; i++) 16 | { 17 | FlexResult result = resultSet.results[i]; 18 | if (result.schema != null && result.data != null) 19 | { 20 | int columnCount = result.visibleColumnCount; 21 | 22 | srp.WriteToStream(String.Format("", i + 1)); 23 | srp.WriteToStream(String.Format("", 24 | columnCount, 25 | result.data.Count + 1 /* include header row */) 26 | ); 27 | 28 | //do header 29 | srp.WriteToStream(""); 30 | for (int colIndex = 0; colIndex < columnCount; colIndex += 1) 31 | { 32 | 33 | srp.WriteToStream(String.Format("{0}", columnName(result, colIndex))); 34 | } 35 | srp.WriteToStream("\r\n"); 36 | 37 | //do data rows 38 | for (int rowIndex = 0; rowIndex < result.data.Count; rowIndex += 1) 39 | { 40 | srp.WriteToStream(""); 41 | for (int colIndex = 0; colIndex < columnCount; colIndex += 1) 42 | { 43 | //todo: fix each of these items to work with the actual scripting stuff (requires finishing major refactoring work). 44 | object fieldData = result.data[rowIndex][colIndex]; 45 | SQLColumn fieldInfo = result.schema[colIndex]; 46 | 47 | if (fieldData == null || fieldData is DBNull) 48 | { 49 | srp.WriteToStream(""); 50 | } 51 | else if (fieldInfo.DataType == "bigint" || fieldInfo.DataType == "numeric" || fieldInfo.DataType == "smallint" || fieldInfo.DataType == "decimal" || fieldInfo.DataType == "smallmoney" || 52 | fieldInfo.DataType == "int" || fieldInfo.DataType == "tinyint" || fieldInfo.DataType == "float" || fieldInfo.DataType == "real" || fieldInfo.DataType == "money" || fieldInfo.DataType == "bit") 53 | { 54 | srp.WriteToStream(String.Format("{0}\r\n", escapeForXML(FieldScripting.valueAsTSQLLiteral(fieldData, fieldInfo, false)))); 55 | } 56 | else if (fieldInfo.DataType == "date" || fieldInfo.DataType == "datetime2" || fieldInfo.DataType == "datetime" || 57 | fieldInfo.DataType == "smalldatetime") 58 | { 59 | srp.WriteToStream(String.Format("{0}.{1}\r\n", 60 | escapeForXML(((DateTime)fieldData).ToString("s")), 61 | escapeForXML(((DateTime)fieldData).ToString("fff")) 62 | )); 63 | } 64 | else if (fieldInfo.DataType == "binary" || fieldInfo.DataType == "rowversion" || fieldInfo.DataType == "timestamp") 65 | { 66 | byte[] d = (byte[])result.data[rowIndex][colIndex]; 67 | srp.WriteToStream(String.Format("{0}\r\n", escapeForXML(FieldScripting.formatBinary(d,d.Length)))); 68 | } 69 | else if (fieldInfo.DataType == "varbinary" || fieldInfo.DataType == "image") 70 | { 71 | srp.WriteToStream(String.Format("{0}\r\n", escapeForXML(FieldScripting.formatVarbinary(fieldData)))); 72 | } 73 | else if (fieldInfo.DataType == "time") 74 | { 75 | srp.WriteToStream(String.Format("1899-12-31T{0}:{1}:{2}.{3}\r\n", 76 | ((TimeSpan)fieldData).Hours.ToString("00"), 77 | ((TimeSpan)fieldData).Minutes.ToString("00"), 78 | ((TimeSpan)fieldData).Seconds.ToString("00"), 79 | ((TimeSpan)fieldData).Milliseconds.ToString("000") 80 | )); 81 | } 82 | else 83 | { 84 | srp.WriteToStream(String.Format("{0}\r\n", escapeForXML(FieldScripting.valueAsTSQLLiteral(fieldData, fieldInfo, false)))); 85 | } 86 | 87 | } 88 | srp.WriteToStream("\r\n"); 89 | } 90 | 91 | srp.WriteToStream("
\r\n"); 92 | srp.worksheetIsValid = true; 93 | } 94 | } 95 | srp.WriteToStream("
\r\n"); 96 | } 97 | 98 | private static string columnName(FlexResult result, int zeroBasedColumnIndex) 99 | { 100 | if (String.IsNullOrEmpty(result.schema[zeroBasedColumnIndex].ColumnName)) 101 | { 102 | return "anonymousColumn" + (zeroBasedColumnIndex + 1).ToString(); 103 | } 104 | return escapeForXML(result.schema[zeroBasedColumnIndex].ColumnName); 105 | } 106 | 107 | private static string escapeForXML(string input) 108 | { 109 | return input.Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace("\"", """).Replace("'", "'"); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Code/TSqlFlex.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TSqlFlex", "TSqlFlex\TSqlFlex.csproj", "{5D764D2E-1BB8-41C7-8F82-428C687A2C6A}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TSqlFlex.Core", "TSqlFlex.Core\TSqlFlex.Core.csproj", "{6C3E3EF0-D9A2-4DB8-94DF-1D71BF3A7CDF}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TSqlFlex.Core.Tests", "TSqlFlex.Core.Tests\TSqlFlex.Core.Tests.csproj", "{4BBD123B-0D08-4E16-A990-11A310C522BE}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{731915D2-50C6-45D7-AF99-EE5D85083991}" 13 | ProjectSection(SolutionItems) = preProject 14 | ..\Chocolatey\tools\chocolateyInstall.ps1 = ..\Chocolatey\tools\chocolateyInstall.ps1 15 | ..\Chocolatey\tools\chocolateyUninstall.ps1 = ..\Chocolatey\tools\chocolateyUninstall.ps1 16 | ..\InstallationInstructions.md = ..\InstallationInstructions.md 17 | ..\License.md = ..\License.md 18 | ..\Deploy\prepDeploy.ps1 = ..\Deploy\prepDeploy.ps1 19 | ..\README.md = ..\README.md 20 | ..\Chocolatey\TSQLFlex.nuspec = ..\Chocolatey\TSQLFlex.nuspec 21 | EndProjectSection 22 | EndProject 23 | Global 24 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 25 | Debug|Any CPU = Debug|Any CPU 26 | Debug|Mixed Platforms = Debug|Mixed Platforms 27 | Debug|x86 = Debug|x86 28 | Release|Any CPU = Release|Any CPU 29 | Release|Mixed Platforms = Release|Mixed Platforms 30 | Release|x86 = Release|x86 31 | EndGlobalSection 32 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 33 | {5D764D2E-1BB8-41C7-8F82-428C687A2C6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {5D764D2E-1BB8-41C7-8F82-428C687A2C6A}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {5D764D2E-1BB8-41C7-8F82-428C687A2C6A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 36 | {5D764D2E-1BB8-41C7-8F82-428C687A2C6A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 37 | {5D764D2E-1BB8-41C7-8F82-428C687A2C6A}.Debug|x86.ActiveCfg = Debug|Any CPU 38 | {5D764D2E-1BB8-41C7-8F82-428C687A2C6A}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {5D764D2E-1BB8-41C7-8F82-428C687A2C6A}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {5D764D2E-1BB8-41C7-8F82-428C687A2C6A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 41 | {5D764D2E-1BB8-41C7-8F82-428C687A2C6A}.Release|Mixed Platforms.Build.0 = Release|Any CPU 42 | {5D764D2E-1BB8-41C7-8F82-428C687A2C6A}.Release|x86.ActiveCfg = Release|Any CPU 43 | {6C3E3EF0-D9A2-4DB8-94DF-1D71BF3A7CDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {6C3E3EF0-D9A2-4DB8-94DF-1D71BF3A7CDF}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {6C3E3EF0-D9A2-4DB8-94DF-1D71BF3A7CDF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 46 | {6C3E3EF0-D9A2-4DB8-94DF-1D71BF3A7CDF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 47 | {6C3E3EF0-D9A2-4DB8-94DF-1D71BF3A7CDF}.Debug|x86.ActiveCfg = Debug|Any CPU 48 | {6C3E3EF0-D9A2-4DB8-94DF-1D71BF3A7CDF}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {6C3E3EF0-D9A2-4DB8-94DF-1D71BF3A7CDF}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {6C3E3EF0-D9A2-4DB8-94DF-1D71BF3A7CDF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 51 | {6C3E3EF0-D9A2-4DB8-94DF-1D71BF3A7CDF}.Release|Mixed Platforms.Build.0 = Release|Any CPU 52 | {6C3E3EF0-D9A2-4DB8-94DF-1D71BF3A7CDF}.Release|x86.ActiveCfg = Release|Any CPU 53 | {4BBD123B-0D08-4E16-A990-11A310C522BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {4BBD123B-0D08-4E16-A990-11A310C522BE}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {4BBD123B-0D08-4E16-A990-11A310C522BE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 56 | {4BBD123B-0D08-4E16-A990-11A310C522BE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 57 | {4BBD123B-0D08-4E16-A990-11A310C522BE}.Debug|x86.ActiveCfg = Debug|Any CPU 58 | {4BBD123B-0D08-4E16-A990-11A310C522BE}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {4BBD123B-0D08-4E16-A990-11A310C522BE}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {4BBD123B-0D08-4E16-A990-11A310C522BE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 61 | {4BBD123B-0D08-4E16-A990-11A310C522BE}.Release|Mixed Platforms.Build.0 = Release|Any CPU 62 | {4BBD123B-0D08-4E16-A990-11A310C522BE}.Release|x86.ActiveCfg = Release|Any CPU 63 | EndGlobalSection 64 | GlobalSection(SolutionProperties) = preSolution 65 | HideSolutionNode = FALSE 66 | EndGlobalSection 67 | EndGlobal 68 | -------------------------------------------------------------------------------- /Code/TSqlFlex/AboutBox.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace TSqlFlex 2 | { 3 | partial class AboutBox 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | protected override void Dispose(bool disposing) 14 | { 15 | if (disposing && (components != null)) 16 | { 17 | components.Dispose(); 18 | } 19 | base.Dispose(disposing); 20 | } 21 | 22 | #region Windows Form Designer generated code 23 | 24 | /// 25 | /// Required method for Designer support - do not modify 26 | /// the contents of this method with the code editor. 27 | /// 28 | private void InitializeComponent() 29 | { 30 | this.labelProductName = new System.Windows.Forms.Label(); 31 | this.labelVersion = new System.Windows.Forms.Label(); 32 | this.labelCopyright = new System.Windows.Forms.Label(); 33 | this.labelCompanyName = new System.Windows.Forms.Label(); 34 | this.btnClose = new System.Windows.Forms.Button(); 35 | this.lblKudos = new System.Windows.Forms.Label(); 36 | this.btnTwitter = new System.Windows.Forms.Button(); 37 | this.lblIssue = new System.Windows.Forms.Label(); 38 | this.btnIssues = new System.Windows.Forms.Button(); 39 | this.label1 = new System.Windows.Forms.Label(); 40 | this.btnReleases = new System.Windows.Forms.Button(); 41 | this.SuspendLayout(); 42 | // 43 | // labelProductName 44 | // 45 | this.labelProductName.AutoSize = true; 46 | this.labelProductName.Location = new System.Drawing.Point(12, 9); 47 | this.labelProductName.Name = "labelProductName"; 48 | this.labelProductName.Size = new System.Drawing.Size(72, 13); 49 | this.labelProductName.TabIndex = 2; 50 | this.labelProductName.Text = "ProductName"; 51 | // 52 | // labelVersion 53 | // 54 | this.labelVersion.AutoSize = true; 55 | this.labelVersion.Location = new System.Drawing.Point(12, 33); 56 | this.labelVersion.Name = "labelVersion"; 57 | this.labelVersion.Size = new System.Drawing.Size(42, 13); 58 | this.labelVersion.TabIndex = 3; 59 | this.labelVersion.Text = "Version"; 60 | // 61 | // labelCopyright 62 | // 63 | this.labelCopyright.AutoSize = true; 64 | this.labelCopyright.Location = new System.Drawing.Point(12, 58); 65 | this.labelCopyright.Name = "labelCopyright"; 66 | this.labelCopyright.Size = new System.Drawing.Size(51, 13); 67 | this.labelCopyright.TabIndex = 4; 68 | this.labelCopyright.Text = "Copyright"; 69 | // 70 | // labelCompanyName 71 | // 72 | this.labelCompanyName.AutoSize = true; 73 | this.labelCompanyName.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Underline, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 74 | this.labelCompanyName.ForeColor = System.Drawing.SystemColors.HotTrack; 75 | this.labelCompanyName.Location = new System.Drawing.Point(12, 83); 76 | this.labelCompanyName.Name = "labelCompanyName"; 77 | this.labelCompanyName.Size = new System.Drawing.Size(79, 13); 78 | this.labelCompanyName.TabIndex = 5; 79 | this.labelCompanyName.Text = "CompanyName"; 80 | this.labelCompanyName.Click += new System.EventHandler(this.labelCompanyName_Click); 81 | // 82 | // btnClose 83 | // 84 | this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 85 | this.btnClose.DialogResult = System.Windows.Forms.DialogResult.OK; 86 | this.btnClose.Location = new System.Drawing.Point(206, 215); 87 | this.btnClose.Name = "btnClose"; 88 | this.btnClose.Size = new System.Drawing.Size(75, 23); 89 | this.btnClose.TabIndex = 6; 90 | this.btnClose.Text = "Close"; 91 | this.btnClose.UseVisualStyleBackColor = true; 92 | this.btnClose.Click += new System.EventHandler(this.btnClose_Click); 93 | // 94 | // lblKudos 95 | // 96 | this.lblKudos.AutoSize = true; 97 | this.lblKudos.Location = new System.Drawing.Point(12, 122); 98 | this.lblKudos.Name = "lblKudos"; 99 | this.lblKudos.Size = new System.Drawing.Size(153, 13); 100 | this.lblKudos.TabIndex = 7; 101 | this.lblKudos.Text = "Tell Steve you like T-SQL Flex:"; 102 | // 103 | // btnTwitter 104 | // 105 | this.btnTwitter.ForeColor = System.Drawing.SystemColors.HotTrack; 106 | this.btnTwitter.Location = new System.Drawing.Point(201, 116); 107 | this.btnTwitter.Name = "btnTwitter"; 108 | this.btnTwitter.Size = new System.Drawing.Size(80, 25); 109 | this.btnTwitter.TabIndex = 8; 110 | this.btnTwitter.Text = "@nycdotnet"; 111 | this.btnTwitter.UseVisualStyleBackColor = true; 112 | this.btnTwitter.Click += new System.EventHandler(this.btnTwitter_Click); 113 | // 114 | // lblIssue 115 | // 116 | this.lblIssue.AutoSize = true; 117 | this.lblIssue.Location = new System.Drawing.Point(12, 153); 118 | this.lblIssue.Name = "lblIssue"; 119 | this.lblIssue.Size = new System.Drawing.Size(85, 13); 120 | this.lblIssue.TabIndex = 9; 121 | this.lblIssue.Text = "Report an Issue:"; 122 | // 123 | // btnIssues 124 | // 125 | this.btnIssues.ForeColor = System.Drawing.SystemColors.HotTrack; 126 | this.btnIssues.Location = new System.Drawing.Point(167, 147); 127 | this.btnIssues.Name = "btnIssues"; 128 | this.btnIssues.Size = new System.Drawing.Size(113, 25); 129 | this.btnIssues.TabIndex = 10; 130 | this.btnIssues.Text = "TSqlFlex/issues"; 131 | this.btnIssues.UseVisualStyleBackColor = true; 132 | this.btnIssues.Click += new System.EventHandler(this.btnIssues_Click); 133 | // 134 | // label1 135 | // 136 | this.label1.AutoSize = true; 137 | this.label1.Location = new System.Drawing.Point(12, 184); 138 | this.label1.Name = "label1"; 139 | this.label1.Size = new System.Drawing.Size(107, 13); 140 | this.label1.TabIndex = 11; 141 | this.label1.Text = "Check for an update:"; 142 | // 143 | // btnReleases 144 | // 145 | this.btnReleases.ForeColor = System.Drawing.SystemColors.HotTrack; 146 | this.btnReleases.Location = new System.Drawing.Point(168, 178); 147 | this.btnReleases.Name = "btnReleases"; 148 | this.btnReleases.Size = new System.Drawing.Size(113, 25); 149 | this.btnReleases.TabIndex = 12; 150 | this.btnReleases.Text = "TSqlFlex/releases"; 151 | this.btnReleases.UseVisualStyleBackColor = true; 152 | this.btnReleases.Click += new System.EventHandler(this.btnReleases_Click); 153 | // 154 | // AboutBox 155 | // 156 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 157 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 158 | this.ClientSize = new System.Drawing.Size(288, 250); 159 | this.Controls.Add(this.btnReleases); 160 | this.Controls.Add(this.label1); 161 | this.Controls.Add(this.btnIssues); 162 | this.Controls.Add(this.lblIssue); 163 | this.Controls.Add(this.btnTwitter); 164 | this.Controls.Add(this.lblKudos); 165 | this.Controls.Add(this.btnClose); 166 | this.Controls.Add(this.labelCompanyName); 167 | this.Controls.Add(this.labelCopyright); 168 | this.Controls.Add(this.labelVersion); 169 | this.Controls.Add(this.labelProductName); 170 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 171 | this.MaximizeBox = false; 172 | this.MinimizeBox = false; 173 | this.Name = "AboutBox"; 174 | this.Padding = new System.Windows.Forms.Padding(9); 175 | this.ShowIcon = false; 176 | this.ShowInTaskbar = false; 177 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 178 | this.Text = "AboutBox"; 179 | this.ResumeLayout(false); 180 | this.PerformLayout(); 181 | 182 | } 183 | 184 | #endregion 185 | 186 | private System.Windows.Forms.Label labelProductName; 187 | private System.Windows.Forms.Label labelVersion; 188 | private System.Windows.Forms.Label labelCopyright; 189 | private System.Windows.Forms.Label labelCompanyName; 190 | private System.Windows.Forms.Button btnClose; 191 | private System.Windows.Forms.Label lblKudos; 192 | private System.Windows.Forms.Button btnTwitter; 193 | private System.Windows.Forms.Label lblIssue; 194 | private System.Windows.Forms.Button btnIssues; 195 | private System.Windows.Forms.Label label1; 196 | private System.Windows.Forms.Button btnReleases; 197 | 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /Code/TSqlFlex/AboutBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Windows.Forms; 9 | 10 | namespace TSqlFlex 11 | { 12 | partial class AboutBox : Form 13 | { 14 | public AboutBox() 15 | { 16 | InitializeComponent(); 17 | 18 | string productName = "T-SQL Flex"; 19 | string companyName = "LegendaryApps.com"; 20 | this.Text = String.Format("About {0}", productName); 21 | this.labelProductName.Text = productName; 22 | this.labelVersion.Text = String.Format("Version {0}", TSqlFlex.Core.Info.Version()); 23 | this.labelCopyright.Text = AssemblyCopyright; 24 | this.labelCompanyName.Text = companyName; 25 | } 26 | 27 | public string AssemblyCopyright 28 | { 29 | get 30 | { 31 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false); 32 | if (attributes.Length == 0) 33 | { 34 | return ""; 35 | } 36 | return ((AssemblyCopyrightAttribute)attributes[0]).Copyright; 37 | } 38 | } 39 | 40 | private void btnClose_Click(object sender, EventArgs e) 41 | { 42 | this.Close(); 43 | } 44 | 45 | private void btnTwitter_Click(object sender, EventArgs e) 46 | { 47 | launchUrl("https://twitter.com/nycdotnet/"); 48 | } 49 | 50 | private void btnIssues_Click(object sender, EventArgs e) 51 | { 52 | launchUrl("https://github.com/nycdotnet/TSqlFlex/issues"); 53 | } 54 | 55 | private void btnReleases_Click(object sender, EventArgs e) 56 | { 57 | launchUrl("https://github.com/nycdotnet/TSqlFlex/releases"); 58 | } 59 | 60 | private void launchUrl(string url) 61 | { 62 | Process proc = new Process(); 63 | proc.StartInfo.UseShellExecute = true; 64 | proc.StartInfo.FileName = url; 65 | proc.Start(); 66 | } 67 | 68 | private void labelCompanyName_Click(object sender, EventArgs e) 69 | { 70 | launchUrl("http://www.legendaryapps.com/"); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Code/TSqlFlex/AboutBox.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /Code/TSqlFlex/Extension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RedGate.SIPFrameworkShared; 3 | using TSqlFlex.Core; 4 | 5 | namespace TSqlFlex 6 | { 7 | public class Extension : ISsmsAddin4 8 | { 9 | private ObjectExplorerNodeDescriptorBase currentNode; 10 | private ISsmsFunctionalityProvider6 ssmsProvider; 11 | private RunCommand runCommand; 12 | public Config config; 13 | public Logging logger; 14 | 15 | public Extension() 16 | { 17 | config = new Config(); 18 | logger = new Logging(config); 19 | } 20 | 21 | public void OnLoad(ISsmsExtendedFunctionalityProvider provider) 22 | { 23 | logger.Log("Extension.OnLoad"); 24 | ssmsProvider = provider as ISsmsFunctionalityProvider6; 25 | if (ssmsProvider == null) 26 | { 27 | string error = "Could not initialize SIP provider for TSqlFlex extension."; 28 | logger.Log(error); 29 | throw new ArgumentException(error); 30 | } 31 | runCommand = new RunCommand(ssmsProvider, logger, config); 32 | runCommand.SetSelectedDBNode(currentNode); 33 | 34 | ssmsProvider.AddToolbarItem(runCommand); 35 | logger.LogVerbose("Extension.OnLoad complete."); 36 | } 37 | 38 | public void OnNodeChanged(ObjectExplorerNodeDescriptorBase node) 39 | { 40 | logger.LogVerbose("Extension.OnNodeChanged"); 41 | currentNode = node; 42 | if (runCommand != null) 43 | { 44 | runCommand.SetSelectedDBNode(currentNode); 45 | } 46 | logger.LogVerbose("Extension.OnNodeChanged complete."); 47 | } 48 | 49 | public void OnShutdown() 50 | { 51 | logger.Log("Extension.OnShutdown"); 52 | logger = null; 53 | } 54 | 55 | public string Author { get { return "Steve Ognibene"; } } 56 | public string Description { get { return "Scripts data to INSERT statements or Excel-compatible spreadsheets."; } } 57 | public string Name { get { return "T-SQL Flex"; } } 58 | public string Url { get { return @"https://github.com/nycdotnet/TSqlFlex/"; } } 59 | public string Version { get { return Info.VersionNumbersOnly(); } } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Code/TSqlFlex/FlexMainWindow.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace TSqlFlex 2 | { 3 | partial class FlexMainWindow 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.components = new System.ComponentModel.Container(); 32 | this.splitContainer1 = new System.Windows.Forms.SplitContainer(); 33 | this.panelTop = new System.Windows.Forms.Panel(); 34 | this.btnAbout = new System.Windows.Forms.Button(); 35 | this.lblConnectionInfo = new System.Windows.Forms.Label(); 36 | this.label1 = new System.Windows.Forms.Label(); 37 | this.txtSqlInput = new System.Windows.Forms.TextBox(); 38 | this.panelBottom = new System.Windows.Forms.Panel(); 39 | this.btnTxt = new System.Windows.Forms.Button(); 40 | this.btnExcel = new System.Windows.Forms.Button(); 41 | this.cmbResultsType = new System.Windows.Forms.ComboBox(); 42 | this.lblProgress = new System.Windows.Forms.Label(); 43 | this.cmdCancel = new System.Windows.Forms.Button(); 44 | this.queryProgress = new System.Windows.Forms.ProgressBar(); 45 | this.btnCopyToClipboard = new System.Windows.Forms.Button(); 46 | this.txtOutput = new System.Windows.Forms.TextBox(); 47 | this.cmdRunNRollback = new System.Windows.Forms.Button(); 48 | this.queryWorker = new System.ComponentModel.BackgroundWorker(); 49 | this.queryTimer = new System.Windows.Forms.Timer(this.components); 50 | this.splitContainer1.Panel1.SuspendLayout(); 51 | this.splitContainer1.Panel2.SuspendLayout(); 52 | this.splitContainer1.SuspendLayout(); 53 | this.panelTop.SuspendLayout(); 54 | this.panelBottom.SuspendLayout(); 55 | this.SuspendLayout(); 56 | // 57 | // splitContainer1 58 | // 59 | this.splitContainer1.BackColor = System.Drawing.Color.DarkSeaGreen; 60 | this.splitContainer1.Cursor = System.Windows.Forms.Cursors.SizeNS; 61 | this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; 62 | this.splitContainer1.Location = new System.Drawing.Point(0, 0); 63 | this.splitContainer1.Name = "splitContainer1"; 64 | this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; 65 | // 66 | // splitContainer1.Panel1 67 | // 68 | this.splitContainer1.Panel1.Controls.Add(this.panelTop); 69 | // 70 | // splitContainer1.Panel2 71 | // 72 | this.splitContainer1.Panel2.Controls.Add(this.panelBottom); 73 | this.splitContainer1.Size = new System.Drawing.Size(1112, 548); 74 | this.splitContainer1.SplitterDistance = 199; 75 | this.splitContainer1.SplitterWidth = 2; 76 | this.splitContainer1.TabIndex = 1; 77 | // 78 | // panelTop 79 | // 80 | this.panelTop.Controls.Add(this.btnAbout); 81 | this.panelTop.Controls.Add(this.lblConnectionInfo); 82 | this.panelTop.Controls.Add(this.label1); 83 | this.panelTop.Controls.Add(this.txtSqlInput); 84 | this.panelTop.Cursor = System.Windows.Forms.Cursors.Default; 85 | this.panelTop.Dock = System.Windows.Forms.DockStyle.Fill; 86 | this.panelTop.Location = new System.Drawing.Point(0, 0); 87 | this.panelTop.Name = "panelTop"; 88 | this.panelTop.Size = new System.Drawing.Size(1112, 199); 89 | this.panelTop.TabIndex = 0; 90 | // 91 | // btnAbout 92 | // 93 | this.btnAbout.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 94 | this.btnAbout.Location = new System.Drawing.Point(1048, 3); 95 | this.btnAbout.Name = "btnAbout"; 96 | this.btnAbout.Size = new System.Drawing.Size(61, 22); 97 | this.btnAbout.TabIndex = 6; 98 | this.btnAbout.Text = "&About..."; 99 | this.btnAbout.UseVisualStyleBackColor = true; 100 | this.btnAbout.Click += new System.EventHandler(this.btnAbout_Click); 101 | // 102 | // lblConnectionInfo 103 | // 104 | this.lblConnectionInfo.AutoSize = true; 105 | this.lblConnectionInfo.Font = new System.Drawing.Font("Arial", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 106 | this.lblConnectionInfo.Location = new System.Drawing.Point(3, 4); 107 | this.lblConnectionInfo.Name = "lblConnectionInfo"; 108 | this.lblConnectionInfo.Size = new System.Drawing.Size(452, 19); 109 | this.lblConnectionInfo.TabIndex = 4; 110 | this.lblConnectionInfo.Text = "Instance: [ServerNameAndInstance], DB: [DatabaseName]"; 111 | // 112 | // label1 113 | // 114 | this.label1.AutoSize = true; 115 | this.label1.Location = new System.Drawing.Point(4, 32); 116 | this.label1.Name = "label1"; 117 | this.label1.Size = new System.Drawing.Size(424, 13); 118 | this.label1.TabIndex = 2; 119 | this.label1.Text = "Enter a SQL command in the top section. Click \"Run \'n\' Rollback\" to script out t" + 120 | "he data."; 121 | // 122 | // txtSqlInput 123 | // 124 | this.txtSqlInput.AllowDrop = true; 125 | this.txtSqlInput.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 126 | | System.Windows.Forms.AnchorStyles.Left) 127 | | System.Windows.Forms.AnchorStyles.Right))); 128 | this.txtSqlInput.Font = new System.Drawing.Font("Consolas", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 129 | this.txtSqlInput.Location = new System.Drawing.Point(0, 48); 130 | this.txtSqlInput.Multiline = true; 131 | this.txtSqlInput.Name = "txtSqlInput"; 132 | this.txtSqlInput.ScrollBars = System.Windows.Forms.ScrollBars.Both; 133 | this.txtSqlInput.Size = new System.Drawing.Size(1112, 151); 134 | this.txtSqlInput.TabIndex = 0; 135 | this.txtSqlInput.WordWrap = false; 136 | this.txtSqlInput.DragDrop += new System.Windows.Forms.DragEventHandler(this.txtSqlInput_DragDrop); 137 | this.txtSqlInput.DragEnter += new System.Windows.Forms.DragEventHandler(this.txtSqlInput_DragEnter); 138 | // 139 | // panelBottom 140 | // 141 | this.panelBottom.Controls.Add(this.btnTxt); 142 | this.panelBottom.Controls.Add(this.btnExcel); 143 | this.panelBottom.Controls.Add(this.cmbResultsType); 144 | this.panelBottom.Controls.Add(this.lblProgress); 145 | this.panelBottom.Controls.Add(this.cmdCancel); 146 | this.panelBottom.Controls.Add(this.queryProgress); 147 | this.panelBottom.Controls.Add(this.btnCopyToClipboard); 148 | this.panelBottom.Controls.Add(this.txtOutput); 149 | this.panelBottom.Controls.Add(this.cmdRunNRollback); 150 | this.panelBottom.Cursor = System.Windows.Forms.Cursors.Default; 151 | this.panelBottom.Dock = System.Windows.Forms.DockStyle.Fill; 152 | this.panelBottom.Location = new System.Drawing.Point(0, 0); 153 | this.panelBottom.Name = "panelBottom"; 154 | this.panelBottom.Size = new System.Drawing.Size(1112, 347); 155 | this.panelBottom.TabIndex = 1; 156 | // 157 | // btnTxt 158 | // 159 | this.btnTxt.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 160 | this.btnTxt.Location = new System.Drawing.Point(831, 5); 161 | this.btnTxt.Name = "btnTxt"; 162 | this.btnTxt.Size = new System.Drawing.Size(75, 23); 163 | this.btnTxt.TabIndex = 12; 164 | this.btnTxt.Text = "Open as .&txt"; 165 | this.btnTxt.UseVisualStyleBackColor = true; 166 | this.btnTxt.Click += new System.EventHandler(this.btnTxt_Click); 167 | // 168 | // btnExcel 169 | // 170 | this.btnExcel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 171 | this.btnExcel.Location = new System.Drawing.Point(912, 5); 172 | this.btnExcel.Name = "btnExcel"; 173 | this.btnExcel.Size = new System.Drawing.Size(86, 23); 174 | this.btnExcel.TabIndex = 7; 175 | this.btnExcel.Text = "Open in &Excel"; 176 | this.btnExcel.UseVisualStyleBackColor = true; 177 | this.btnExcel.Click += new System.EventHandler(this.btnExcel_Click); 178 | // 179 | // cmbResultsType 180 | // 181 | this.cmbResultsType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; 182 | this.cmbResultsType.FormattingEnabled = true; 183 | this.cmbResultsType.Location = new System.Drawing.Point(3, 5); 184 | this.cmbResultsType.Name = "cmbResultsType"; 185 | this.cmbResultsType.Size = new System.Drawing.Size(192, 21); 186 | this.cmbResultsType.TabIndex = 11; 187 | // 188 | // lblProgress 189 | // 190 | this.lblProgress.AutoSize = true; 191 | this.lblProgress.Location = new System.Drawing.Point(460, 10); 192 | this.lblProgress.Name = "lblProgress"; 193 | this.lblProgress.Size = new System.Drawing.Size(41, 13); 194 | this.lblProgress.TabIndex = 9; 195 | this.lblProgress.Text = "Ready."; 196 | // 197 | // cmdCancel 198 | // 199 | this.cmdCancel.Enabled = false; 200 | this.cmdCancel.Location = new System.Drawing.Point(379, 5); 201 | this.cmdCancel.Name = "cmdCancel"; 202 | this.cmdCancel.Size = new System.Drawing.Size(75, 23); 203 | this.cmdCancel.TabIndex = 7; 204 | this.cmdCancel.Text = "Ca&ncel"; 205 | this.cmdCancel.UseVisualStyleBackColor = true; 206 | this.cmdCancel.Click += new System.EventHandler(this.cmdCancel_Click); 207 | // 208 | // queryProgress 209 | // 210 | this.queryProgress.Location = new System.Drawing.Point(308, 10); 211 | this.queryProgress.Name = "queryProgress"; 212 | this.queryProgress.Size = new System.Drawing.Size(65, 13); 213 | this.queryProgress.TabIndex = 6; 214 | // 215 | // btnCopyToClipboard 216 | // 217 | this.btnCopyToClipboard.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 218 | this.btnCopyToClipboard.Location = new System.Drawing.Point(1004, 5); 219 | this.btnCopyToClipboard.Name = "btnCopyToClipboard"; 220 | this.btnCopyToClipboard.Size = new System.Drawing.Size(105, 23); 221 | this.btnCopyToClipboard.TabIndex = 2; 222 | this.btnCopyToClipboard.Text = "&Copy to clipboard"; 223 | this.btnCopyToClipboard.UseVisualStyleBackColor = true; 224 | this.btnCopyToClipboard.Click += new System.EventHandler(this.btnCopyToClipboard_Click); 225 | // 226 | // txtOutput 227 | // 228 | this.txtOutput.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 229 | | System.Windows.Forms.AnchorStyles.Left) 230 | | System.Windows.Forms.AnchorStyles.Right))); 231 | this.txtOutput.BackColor = System.Drawing.SystemColors.Window; 232 | this.txtOutput.Font = new System.Drawing.Font("Consolas", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 233 | this.txtOutput.Location = new System.Drawing.Point(0, 32); 234 | this.txtOutput.Multiline = true; 235 | this.txtOutput.Name = "txtOutput"; 236 | this.txtOutput.ReadOnly = true; 237 | this.txtOutput.ScrollBars = System.Windows.Forms.ScrollBars.Both; 238 | this.txtOutput.Size = new System.Drawing.Size(1112, 315); 239 | this.txtOutput.TabIndex = 4; 240 | this.txtOutput.WordWrap = false; 241 | // 242 | // cmdRunNRollback 243 | // 244 | this.cmdRunNRollback.Location = new System.Drawing.Point(201, 5); 245 | this.cmdRunNRollback.Name = "cmdRunNRollback"; 246 | this.cmdRunNRollback.Size = new System.Drawing.Size(101, 23); 247 | this.cmdRunNRollback.TabIndex = 1; 248 | this.cmdRunNRollback.Text = "&Run \'n\' Rollback"; 249 | this.cmdRunNRollback.UseVisualStyleBackColor = true; 250 | this.cmdRunNRollback.Click += new System.EventHandler(this.cmdRunNRollback_Click); 251 | // 252 | // queryWorker 253 | // 254 | this.queryWorker.WorkerReportsProgress = true; 255 | this.queryWorker.WorkerSupportsCancellation = true; 256 | this.queryWorker.DoWork += new System.ComponentModel.DoWorkEventHandler(this.queryWorker_DoWork); 257 | this.queryWorker.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(this.queryWorker_ProgressChanged); 258 | this.queryWorker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.queryWorker_RunWorkerCompleted); 259 | // 260 | // queryTimer 261 | // 262 | this.queryTimer.Tick += new System.EventHandler(this.queryTimer_Tick); 263 | // 264 | // FlexMainWindow 265 | // 266 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 267 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 268 | this.Controls.Add(this.splitContainer1); 269 | this.Name = "FlexMainWindow"; 270 | this.Size = new System.Drawing.Size(1112, 548); 271 | this.splitContainer1.Panel1.ResumeLayout(false); 272 | this.splitContainer1.Panel2.ResumeLayout(false); 273 | this.splitContainer1.ResumeLayout(false); 274 | this.panelTop.ResumeLayout(false); 275 | this.panelTop.PerformLayout(); 276 | this.panelBottom.ResumeLayout(false); 277 | this.panelBottom.PerformLayout(); 278 | this.ResumeLayout(false); 279 | 280 | } 281 | 282 | #endregion 283 | 284 | private System.Windows.Forms.SplitContainer splitContainer1; 285 | private System.Windows.Forms.Panel panelTop; 286 | private System.Windows.Forms.Label lblConnectionInfo; 287 | private System.Windows.Forms.Label label1; 288 | private System.Windows.Forms.TextBox txtSqlInput; 289 | private System.Windows.Forms.Panel panelBottom; 290 | private System.Windows.Forms.Button btnCopyToClipboard; 291 | private System.Windows.Forms.TextBox txtOutput; 292 | private System.Windows.Forms.Button cmdRunNRollback; 293 | private System.ComponentModel.BackgroundWorker queryWorker; 294 | private System.Windows.Forms.Button cmdCancel; 295 | private System.Windows.Forms.ProgressBar queryProgress; 296 | private System.Windows.Forms.Label lblProgress; 297 | private System.Windows.Forms.Timer queryTimer; 298 | private System.Windows.Forms.ComboBox cmbResultsType; 299 | private System.Windows.Forms.Button btnAbout; 300 | private System.Windows.Forms.Button btnExcel; 301 | private System.Windows.Forms.Button btnTxt; 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /Code/TSqlFlex/FlexMainWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Data.SqlClient; 4 | using System.Diagnostics; 5 | using System.Drawing; 6 | using System.IO; 7 | using System.Runtime.InteropServices; 8 | using System.Text; 9 | using System.Windows.Forms; 10 | using TSqlFlex.Core; 11 | 12 | namespace TSqlFlex 13 | { 14 | [ComVisible(true)] 15 | public partial class FlexMainWindow : UserControl 16 | { 17 | private SqlConnectionStringBuilder connStringBuilder = null; 18 | private Stopwatch sqlStopwatch = null; 19 | private string progressText = ""; 20 | private string lastExportedFilePath = ""; 21 | private Logging logger; 22 | private Config config; 23 | private uint completedResultsCount = 0; 24 | 25 | public FlexMainWindow(Logging instantiatedLogger, Config instantiatedConfig) 26 | { 27 | logger = instantiatedLogger; 28 | config = instantiatedConfig; 29 | logger.LogVerbose("Initializing FlexMainWindow"); 30 | InitializeComponent(); 31 | lblProgress.Text = ""; 32 | cmbResultsType.Items.Add(SqlRunParameters.TO_INSERT_STATEMENTS); 33 | cmbResultsType.Items.Add(SqlRunParameters.TO_XML_SPREADSHEET); 34 | cmbResultsType.Items.Add(SqlRunParameters.TO_CSV); 35 | cmbResultsType.Items.Add(SqlRunParameters.TO_CSHARP); 36 | cmbResultsType.SelectedItem = SqlRunParameters.TO_INSERT_STATEMENTS; 37 | setUIState(false); 38 | } 39 | 40 | protected override bool ProcessCmdKey(ref Message msg, Keys keyData) 41 | { 42 | var control = FindFocusedControl(this); 43 | 44 | if (keyData == (Keys.Control | Keys.A)) 45 | { 46 | if (control == txtSqlInput || control == txtOutput) 47 | { 48 | var txt = control as TextBox; 49 | txt.SelectAll(); 50 | } 51 | return true; 52 | } 53 | else if (keyData == (Keys.Control | Keys.C)) 54 | { 55 | if (control == txtSqlInput || control == txtOutput) 56 | { 57 | var txt = control as TextBox; 58 | CopyThisTextOrShowErrorMessageBox(txt.SelectedText); 59 | } 60 | return true; 61 | } 62 | return base.ProcessCmdKey(ref msg, keyData); 63 | 64 | } 65 | 66 | //Thanks! http://stackoverflow.com/questions/435433/what-is-the-preferred-way-to-find-focused-control-in-winforms-app/ 67 | public static Control FindFocusedControl(Control control) 68 | { 69 | var container = control as ContainerControl; 70 | while (container != null) 71 | { 72 | control = container.ActiveControl; 73 | container = control as ContainerControl; 74 | } 75 | return control; 76 | } 77 | 78 | public void SetConnection(SqlConnectionStringBuilder theConnectionStringBuilder) 79 | { 80 | logger.LogVerbose("FlexMainWindow.SetConnection"); 81 | connStringBuilder = theConnectionStringBuilder; 82 | SetConnectionText(); 83 | logger.LogVerbose("FlexMainWindow.SetConnection Complete"); 84 | } 85 | 86 | private void SetConnectionText() 87 | { 88 | logger.LogVerbose("FlexMainWindow.SetConnectionText"); 89 | InvokeFireAndForgetOnFormThread(() => 90 | { 91 | lblConnectionInfo.Text = currentConnectionText(); 92 | }); 93 | logger.LogVerbose("FlexMainWindow.SetConnectionText Complete"); 94 | } 95 | 96 | private string currentConnectionText() 97 | { 98 | logger.LogVerbose("FlexMainWindow.currentConnectionText"); 99 | 100 | if (Utils.IsValidConnectionStringBuilder(connStringBuilder)) 101 | { 102 | logger.LogVerbose("FlexMainWindow.currentConnectionText valid builder"); 103 | return "Instance: " + connStringBuilder.DataSource + ", DB: " + connStringBuilder.InitialCatalog; 104 | } 105 | logger.LogVerbose("FlexMainWindow.currentConnectionText not connected"); 106 | return "Not connected."; 107 | } 108 | 109 | private void cmdRunNRollback_Click(object sender, EventArgs e) 110 | { 111 | if (!Utils.IsValidConnectionStringBuilder(connStringBuilder)) 112 | { 113 | MessageBox.Show("Select the target database in the object tree first."); 114 | return; 115 | } 116 | 117 | if (!queryWorker.IsBusy) { 118 | lastExportedFilePath = ""; 119 | sqlStopwatch = new Stopwatch(); 120 | sqlStopwatch.Start(); 121 | queryTimer.Enabled = true; 122 | setUIState(true); 123 | 124 | try 125 | { 126 | var srp = new SqlRunParameters(connStringBuilder, getSqlToRun(), cmbResultsType.SelectedItem.ToString()); 127 | srp.completedResultsCount = completedResultsCount; 128 | queryWorker.RunWorkerAsync(srp); 129 | } 130 | catch (Exception ex) 131 | { 132 | string error = "There was an exception when setting up the query run parameters. " + ex.Message; 133 | logger.Log(error + " " + ex.StackTrace); 134 | MessageBox.Show(error); 135 | sqlStopwatch.Stop(); 136 | queryTimer.Enabled = false; 137 | setUIState(false); 138 | } 139 | } 140 | } 141 | 142 | private string getSqlToRun() 143 | { 144 | if (txtSqlInput.SelectionLength > 0) 145 | { 146 | return txtSqlInput.SelectedText; 147 | } 148 | return txtSqlInput.Text; 149 | } 150 | 151 | private void btnCopyToClipboard_Click(object sender, EventArgs e) 152 | { 153 | CopyThisTextOrShowErrorMessageBox(txtOutput.Text); 154 | } 155 | 156 | private void CopyThisTextOrShowErrorMessageBox(string theTextToCopy) { 157 | try 158 | { 159 | if (!String.IsNullOrEmpty(theTextToCopy)) 160 | { 161 | Clipboard.SetText(theTextToCopy); 162 | } 163 | } 164 | catch (Exception ex) 165 | { 166 | logger.Log(ex.Message); 167 | logger.Log(ex.StackTrace); 168 | MessageBox.Show("Exception: " + ex.Message); 169 | } 170 | } 171 | 172 | private void btnCopyToNewWindow_Click(object sender, EventArgs e) 173 | { 174 | MessageBox.Show("Not yet implemented."); 175 | } 176 | 177 | private void queryWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) 178 | { 179 | QueryWorker.DoSqlQueryWork(e, queryWorker, config); 180 | } 181 | 182 | private void queryWorker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e) 183 | { 184 | Cursor.Current = Cursors.WaitCursor; 185 | if (e != null) 186 | { 187 | if (e.UserState is string && e.UserState != null) 188 | { 189 | progressText = (string)e.UserState; 190 | setProgressText( (e.ProgressPercentage == 100) ); 191 | } 192 | 193 | queryProgress.Value = e.ProgressPercentage; 194 | } 195 | } 196 | 197 | private void setProgressText(bool isComplete) 198 | { 199 | InvokeFireAndForgetOnFormThread(() => 200 | { 201 | lblProgress.Text = getFormattedElapsedSqlStopwatchTime(isComplete) + " " + progressText; 202 | lblProgress.Refresh(); 203 | }); 204 | } 205 | 206 | private string getFormattedElapsedSqlStopwatchTime(bool includeMilliseconds) 207 | { 208 | if (sqlStopwatch == null) 209 | { 210 | return ""; 211 | } 212 | TimeSpan t = sqlStopwatch.Elapsed; 213 | 214 | if (includeMilliseconds) 215 | { 216 | return String.Format("{0}:{1}:{2}.{3}", 217 | t.Hours.ToString(), 218 | t.Minutes.ToString().PadLeft(2, '0'), 219 | t.Seconds.ToString().PadLeft(2, '0'), 220 | t.Milliseconds.ToString().PadLeft(3, '0').TrimEnd('0')); 221 | } 222 | 223 | return String.Format("{0}:{1}:{2}", 224 | t.Hours.ToString(), 225 | t.Minutes.ToString().PadLeft(2, '0'), 226 | t.Seconds.ToString().PadLeft(2, '0')); 227 | } 228 | 229 | private void queryWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e) 230 | { 231 | queryTimer.Enabled = false; 232 | queryProgress.Value = 100; 233 | progressText = "Drawing results..."; //if there is a large amount of data, drawing it in the textbox can take a long time. 234 | sqlStopwatch.Stop(); 235 | setProgressText(true); //bug: This includes the time it took to read all of the results, etc. Should technically stop after data finishes coming in from SQL 236 | 237 | 238 | if (e.Cancelled) 239 | { 240 | progressText = "Cancelled."; 241 | txtOutput.Text = "--Query cancelled."; 242 | } 243 | else if (e.Error != null) 244 | { 245 | progressText = "Finished with error."; 246 | //todo: make this more consistent with what happens from SQL syntax errors, etc. 247 | txtOutput.Text = "--Error occurred while processing query.\r\n\r\n/* " + e.Error.Message + "\r\n*/"; 248 | } 249 | else 250 | { 251 | var srp = (SqlRunParameters)e.Result; 252 | 253 | completedResultsCount += srp.completedResultsCount - completedResultsCount; 254 | 255 | srp.flushAndCloseOutputStreamIfNeeded(); 256 | 257 | progressText = "Complete."; 258 | if (cmbResultsType.SelectedItem.ToString() == SqlRunParameters.TO_INSERT_STATEMENTS) 259 | { 260 | RenderInsertStatements(srp); 261 | } 262 | else if (cmbResultsType.SelectedItem.ToString() == SqlRunParameters.TO_XML_SPREADSHEET) 263 | { 264 | RenderXmlSpreadsheet(srp); 265 | } 266 | else if (cmbResultsType.SelectedItem.ToString() == SqlRunParameters.TO_CSV) 267 | { 268 | RenderCsv(srp); 269 | } 270 | else if (cmbResultsType.SelectedItem.ToString() == SqlRunParameters.TO_CSHARP) 271 | { 272 | RenderCSharp(srp); 273 | } 274 | } 275 | 276 | setProgressText(true); 277 | 278 | lblConnectionInfo.Text = currentConnectionText(); 279 | 280 | setUIState(false); 281 | } 282 | 283 | private void RenderCsv(SqlRunParameters srp) 284 | { 285 | if (srp.worksheetIsValid) 286 | { 287 | TryToSaveCSVs(srp); 288 | } 289 | else 290 | { 291 | drawExceptions(srp); 292 | } 293 | } 294 | 295 | private void RenderXmlSpreadsheet(SqlRunParameters srp) 296 | { 297 | if (srp.worksheetIsValid) 298 | { 299 | TryToSaveSpreadsheet(srp); 300 | } 301 | else 302 | { 303 | drawExceptions(srp); 304 | } 305 | } 306 | 307 | private void RenderCSharp(SqlRunParameters srp) 308 | { 309 | bool success = false; 310 | try 311 | { 312 | if (srp.exceptionsText.Length > 0) 313 | { 314 | if (srp.resultsText.Length > 0) 315 | { 316 | txtOutput.Text = srp.exceptionsText.ToString() + "\r\n\r\n" + srp.resultsText.ToString(); 317 | success = true; 318 | } 319 | else 320 | { 321 | drawExceptions(srp); 322 | success = true; 323 | } 324 | } 325 | else 326 | { 327 | txtOutput.Text = srp.resultsText.ToString(); 328 | success = true; 329 | } 330 | } 331 | catch (Exception ex) 332 | { 333 | srp.resultsText = null; 334 | srp.exceptionsText.Append("\r\n\r\n/*\r\n\r\nException while attempting to display results: "); 335 | srp.exceptionsText.Append(ex.Message); 336 | srp.exceptionsText.Append("\r\n\r\n"); 337 | srp.exceptionsText.Append(ex.StackTrace); 338 | srp.exceptionsText.Append("\r\n*/"); 339 | } 340 | 341 | if (!success) 342 | { 343 | GC.Collect(); 344 | txtOutput.Text = srp.exceptionsText.ToString(); 345 | } 346 | } 347 | 348 | 349 | private void RenderInsertStatements(SqlRunParameters srp) 350 | { 351 | bool success = false; 352 | try 353 | { 354 | if (srp.exceptionsText.Length > 0) 355 | { 356 | if (srp.resultsText.Length > 0) 357 | { 358 | txtOutput.Text = srp.exceptionsText.ToString() + "\r\n\r\n" + srp.resultsText.ToString(); 359 | success = true; 360 | } 361 | else 362 | { 363 | drawExceptions(srp); 364 | success = true; 365 | } 366 | } 367 | else 368 | { 369 | txtOutput.Text = srp.resultsText.ToString(); 370 | success = true; 371 | } 372 | } 373 | catch (Exception ex) 374 | { 375 | srp.resultsText = null; 376 | srp.exceptionsText.Append("\r\n\r\n/*\r\n\r\nException while attempting to display results: "); 377 | srp.exceptionsText.Append(ex.Message); 378 | srp.exceptionsText.Append("\r\n\r\n"); 379 | srp.exceptionsText.Append(ex.StackTrace); 380 | srp.exceptionsText.Append("\r\n*/"); 381 | } 382 | 383 | if (!success) 384 | { 385 | GC.Collect(); 386 | txtOutput.Text = srp.exceptionsText.ToString(); 387 | } 388 | } 389 | 390 | private void drawExceptions(SqlRunParameters srp, string header = "") 391 | { 392 | InvokeFireAndForgetOnFormThread(() => { 393 | if (header == "") 394 | { 395 | txtOutput.Text = srp.exceptionsText.ToString(); 396 | } 397 | else 398 | { 399 | txtOutput.Text = header + "\r\n\r\n" + srp.exceptionsText.ToString(); 400 | } 401 | }); 402 | } 403 | 404 | private void TryToSaveCSVs(SqlRunParameters srp) 405 | { 406 | txtOutput.Text = ""; 407 | string personalFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal); 408 | string timestamp = DateTime.Now.ToString("_yyyyMMddTHHmmss"); 409 | for (int csvIndex = 0; csvIndex < srp.outputFiles.Count; csvIndex += 1) 410 | { 411 | string fileName = ""; 412 | try 413 | { 414 | fileName = Path.Combine(personalFolder, "TSqlFlex" + timestamp + 415 | (srp.outputFiles.Count > 1 ? "_" + (csvIndex+1).ToString() : "") + ".csv"); 416 | } 417 | catch (Exception ex) 418 | { 419 | string error = "Exception when attempting to find personal documents folder for current user. Will not save file."; 420 | logger.Log(error + " " + ex.Message); 421 | logger.Log(ex.StackTrace); 422 | MessageBox.Show(error + " " + ex.Message); 423 | fileName = ""; 424 | break; 425 | } 426 | 427 | if (fileName != "") 428 | { 429 | srp.saveOutputStreamTo(srp.outputFiles[csvIndex], fileName); 430 | this.lastExportedFilePath = fileName; 431 | InvokeFireAndForgetOnFormThread(() => 432 | { 433 | txtOutput.Text += "--Results written to \"" + fileName + "\".\r\n--You can open this file in your text editor.\r\n\r\n"; 434 | }); 435 | } 436 | } 437 | } 438 | 439 | private void TryToSaveSpreadsheet(SqlRunParameters srp) 440 | { 441 | string fileName = ""; 442 | try 443 | { 444 | fileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "TSqlFlex" + DateTime.Now.ToString("_yyyyMMddTHHmmss") + ".xml"); 445 | } 446 | catch (Exception ex) 447 | { 448 | string error = "Exception when attempting to find personal documents folder for current user. Will not save file."; 449 | logger.Log(error + " " + ex.Message); 450 | logger.Log(ex.StackTrace); 451 | MessageBox.Show(error + " " + ex.Message); 452 | fileName = ""; 453 | } 454 | 455 | if (fileName != "") 456 | { 457 | srp.saveOutputStreamTo(srp.outputFiles[0], fileName); 458 | this.lastExportedFilePath = fileName; 459 | InvokeFireAndForgetOnFormThread(() => 460 | { 461 | txtOutput.Text = "--Results written to \"" + fileName + "\".\r\n--You can open this file in Excel.\r\n\r\n"; 462 | }); 463 | } 464 | } 465 | 466 | private void setUIState(bool queryIsRunning) { 467 | logger.LogVerbose("FlexMainWindow.setUIState"); 468 | InvokeFireAndForgetOnFormThread(() => 469 | { 470 | cmdCancel.Enabled = queryIsRunning; 471 | cmdRunNRollback.Enabled = !queryIsRunning; 472 | if (queryIsRunning) 473 | { 474 | Cursor.Current = Cursors.WaitCursor; 475 | } 476 | else 477 | { 478 | Cursor.Current = Cursors.Default; 479 | } 480 | btnExcel.Enabled = (lastExportedFilePath.Length > 0); 481 | btnTxt.Enabled = (lastExportedFilePath.Length > 0); 482 | }); 483 | logger.LogVerbose("FlexMainWindow.setUIState complete"); 484 | } 485 | 486 | private void cmdCancel_Click(object sender, EventArgs e) 487 | { 488 | queryWorker.CancelAsync(); 489 | cmdCancel.Enabled = false; 490 | } 491 | 492 | private void queryTimer_Tick(object sender, EventArgs e) 493 | { 494 | logger.LogVerbose("FlexMainWindow.queryTimerTick"); 495 | setProgressText(false); 496 | logger.LogVerbose("FlexMainWindow.queryTimerTick Complete"); 497 | } 498 | 499 | private void InvokeFireAndForgetOnFormThread(Action behavior) 500 | { 501 | logger.LogVerbose("FlexMainWindow.InvokeOnFormThread"); 502 | if (IsHandleCreated && InvokeRequired) 503 | { 504 | //Thanks to http://stackoverflow.com/questions/229554/whats-the-difference-between-invoke-and-begininvoke/229558#229558 505 | logger.LogVerbose("FlexMainWindow.InvokeOnFormThread (attempting to invoke)"); 506 | BeginInvoke(behavior); 507 | logger.LogVerbose("FlexMainWindow.InvokeOnFormThread (finished invoke)"); 508 | } 509 | else 510 | { 511 | logger.LogVerbose("FlexMainWindow.InvokeOnFormThread (attempting to do behavior)"); 512 | behavior(); 513 | logger.LogVerbose("FlexMainWindow.InvokeOnFormThread (finished behavior)"); 514 | } 515 | logger.LogVerbose("FlexMainWindow.InvokeOnFormThread Complete"); 516 | } 517 | 518 | private void btnAbout_Click(object sender, EventArgs e) 519 | { 520 | AboutBox ab = new AboutBox(); 521 | ab.ShowDialog(); 522 | } 523 | 524 | private void btnExcel_Click(object sender, EventArgs e) 525 | { 526 | var Excel = new ExcelLauncher(); 527 | if (Excel.ProgramFound) 528 | { 529 | Excel.Launch(this.lastExportedFilePath); 530 | } 531 | else 532 | { 533 | MessageBox.Show(Excel.ProgramError, "T-SQL Flex couldn't launch Excel"); 534 | } 535 | } 536 | 537 | private void txtSqlInput_DragDrop(object sender, DragEventArgs e) 538 | { 539 | try 540 | { 541 | string theObjectName = e.Data.GetData(DataFormats.Text).ToString(); 542 | Point pointOnTextbox = txtSqlInput.PointToClient(new Point(e.X, e.Y)); 543 | int charIndex = txtSqlInput.GetCharIndexFromPosition(pointOnTextbox); 544 | txtSqlInput.Text = txtSqlInput.Text.Insert(charIndex, FieldScripting.EscapeObjectNames(theObjectName)); 545 | } 546 | catch (Exception ex) 547 | { 548 | MessageBox.Show("Could not drag and drop.\r\n\r\n" + ex.Message, "T-SQL Flex"); 549 | } 550 | } 551 | 552 | private void txtSqlInput_DragEnter(object sender, DragEventArgs e) 553 | { 554 | if (e.Data.GetDataPresent(DataFormats.Text)) 555 | { 556 | e.Effect = DragDropEffects.Copy; 557 | } 558 | else 559 | { 560 | e.Effect = DragDropEffects.None; 561 | } 562 | } 563 | 564 | private void btnTxt_Click(object sender, EventArgs e) 565 | { 566 | var Txt = new TxtLauncher(); 567 | if (Txt.ProgramFound) 568 | { 569 | Txt.Launch(this.lastExportedFilePath); 570 | } 571 | else 572 | { 573 | MessageBox.Show(Txt.ProgramError, "T-SQL Flex couldn't launch your default text editor."); 574 | } 575 | } 576 | } 577 | } -------------------------------------------------------------------------------- /Code/TSqlFlex/FlexMainWindow.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | 124 | 139, 17 125 | 126 | -------------------------------------------------------------------------------- /Code/TSqlFlex/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("TSqlFlex")] 10 | [assembly: AssemblyDescription("Scripts SQL Server data.")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("TSqlFlex")] 14 | [assembly: AssemblyCopyright("Copyright © 2017 Steve Ognibene")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | [assembly: AssemblyVersion("0.2.1.0")] 18 | [assembly: AssemblyFileVersion("0.2.1.0")] 19 | [assembly: AssemblySemverPrereleaseTag("beta")] 20 | 21 | // Setting ComVisible to false makes the types in this assembly not visible 22 | // to COM components. If you need to access a type in this assembly from 23 | // COM, set the ComVisible attribute to true on that type. 24 | [assembly: ComVisible(false)] 25 | 26 | // The following GUID is for the ID of the typelib if this project is exposed to COM 27 | [assembly: Guid("aafe5ee1-f39f-4eee-9f37-ca4571ee5ea2")] 28 | 29 | -------------------------------------------------------------------------------- /Code/TSqlFlex/RunCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SqlClient; 3 | using RedGate.SIPFrameworkShared; 4 | using TSqlFlex.Core; 5 | 6 | namespace TSqlFlex 7 | { 8 | class RunCommand : ISharedCommand 9 | { 10 | private readonly ISsmsFunctionalityProvider6 ssmsProvider; 11 | private readonly ICommandImage commandImage = new CommandImageNone(); 12 | private ObjectExplorerNodeDescriptorBase currentNode = null; 13 | 14 | private IToolWindow formWindow; 15 | private FlexMainWindow flexMainWindow; 16 | private Guid formGuid = new Guid("579fa20c-38cb-4da6-9f57-6651d10e31d0"); 17 | private Logging logger; 18 | private Config config; 19 | 20 | private FlexMainWindow TheWindow() 21 | { 22 | return flexMainWindow; 23 | } 24 | 25 | public void SetSelectedDBNode(ObjectExplorerNodeDescriptorBase theSelectedNode) 26 | { 27 | currentNode = theSelectedNode; 28 | 29 | var objectExplorerNode = currentNode as IOeNode; 30 | IConnectionInfo ci = null; 31 | if (objectExplorerNode != null 32 | && objectExplorerNode.HasConnection 33 | && objectExplorerNode.TryGetConnection(out ci)) 34 | { 35 | var w = TheWindow(); 36 | if (w != null) 37 | { 38 | w.SetConnection(new SqlConnectionStringBuilder(ci.ConnectionString)); 39 | } 40 | } 41 | } 42 | 43 | public RunCommand(ISsmsFunctionalityProvider6 provider, Logging logger, Config config) 44 | { 45 | this.logger = logger; 46 | this.config = config; 47 | ssmsProvider = provider; 48 | if (ssmsProvider == null) 49 | { 50 | var error = "Could not initialize provider for RunCommand."; 51 | logger.Log(error); 52 | throw new ArgumentException(error); 53 | } 54 | } 55 | 56 | public void Execute() 57 | { 58 | if (formWindow == null) 59 | { 60 | flexMainWindow = new FlexMainWindow(logger, config); 61 | formWindow = ssmsProvider.ToolWindow.Create(flexMainWindow, Caption, formGuid, true); 62 | 63 | try 64 | { 65 | formWindow.Window.IsFloating = true; 66 | formWindow.Window.WindowState = WindowState.Maximize; 67 | } 68 | catch (Exception ex) 69 | { 70 | //This is copied code from a Red-Gate sample. If SIP is updated to address this better, it can be updated, but for now this works. 71 | var error = "Exception setting Window State."; 72 | logger.Log(error + " " + ex.Message); 73 | } 74 | formWindow.Window.IsFloating = false; // can't be docked 75 | formWindow.Window.Linkable = false; 76 | SetSelectedDBNode(currentNode); 77 | } 78 | 79 | formWindow.Activate(true); 80 | } 81 | 82 | public string Name { get { return "Open_TSQL_Flex"; } } 83 | public string Caption { get { return "T-SQL Flex"; } } 84 | public string Tooltip { get { return "Runs a command for scripting"; } } 85 | public ICommandImage Icon { get { return commandImage; } } 86 | public string[] DefaultBindings { get { return new string[] {}; } } 87 | public bool Visible { get { return true; } } 88 | public bool Enabled { get { return true; } } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Code/TSqlFlex/TSqlFlex.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {5D764D2E-1BB8-41C7-8F82-428C687A2C6A} 8 | Library 9 | Properties 10 | TSqlFlex 11 | TSqlFlex 12 | v3.5 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | false 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | false 34 | 35 | 36 | 37 | ..\packages\RedGate.SIPFrameworkShared.1.0.1.246\lib\RedGate.SIPFrameworkShared.dll 38 | True 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | Form 52 | 53 | 54 | AboutBox.cs 55 | 56 | 57 | 58 | UserControl 59 | 60 | 61 | FlexMainWindow.cs 62 | 63 | 64 | 65 | 66 | 67 | 68 | AboutBox.cs 69 | 70 | 71 | FlexMainWindow.cs 72 | 73 | 74 | 75 | 76 | {6c3e3ef0-d9a2-4db8-94df-1d71bf3a7cdf} 77 | TSqlFlex.Core 78 | 79 | 80 | 81 | 82 | 83 | 84 | 91 | -------------------------------------------------------------------------------- /Code/TSqlFlex/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /Code/packages/repositories.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Deploy/prepDeploy.ps1: -------------------------------------------------------------------------------- 1 | 2 | # thanks => https://stackoverflow.com/questions/1183183/path-of-currently-executing-powershell-script 3 | function Get-ScriptDirectory 4 | { 5 | $Invocation = (Get-Variable MyInvocation -Scope 1).Value 6 | Split-Path $Invocation.MyCommand.Path 7 | } 8 | 9 | # thanks => https://stackoverflow.com/questions/1153126/how-to-create-a-zip-archive-with-powershell/ 10 | function ZipFiles( $zipfilename, $sourcedir ) 11 | { 12 | Add-Type -Assembly System.IO.Compression.FileSystem 13 | $compressionLevel = ([System.IO.Compression.CompressionLevel]::Optimal) 14 | [System.IO.Compression.ZipFile]::CreateFromDirectory($sourcedir, $zipfilename, $compressionLevel, $false) 15 | } 16 | 17 | 18 | Push-Location 19 | $scriptPath = Get-ScriptDirectory 20 | CD $scriptPath 21 | 22 | Remove-Item -Recurse -Force Binaries | Out-Null 23 | New-Item -ItemType Directory -Force Binaries\All | Out-Null 24 | 25 | Copy-Item ..\Code\TSqlFlex\bin\Release\RedGate.SIPFrameworkShared.dll .\Binaries\All 26 | Copy-Item ..\Code\TSqlFlex\bin\Release\TSqlFlex.Core.dll .\Binaries\All 27 | Copy-Item ..\Code\TSqlFlex\bin\Release\TSqlFlex.dll .\Binaries\All 28 | Copy-Item ..\InstallationInstructions.md .\Binaries\All\InstallationInstructions.txt 29 | Copy-Item ..\License.md .\Binaries\All\License.txt 30 | Copy-Item ..\README.md .\Binaries\All\README.txt 31 | 32 | $outputZip = Join-Path $scriptPath (-Join("Binaries\TSqlFlex.zip")); 33 | $inputFolder = Join-Path $scriptPath "Binaries\All"; 34 | 35 | ZipFiles $outputZip $inputFolder; 36 | 37 | 38 | $TSqlFlexDllVersionInfo = (Get-Item (Join-Path $scriptPath "Binaries\All\TSqlFlex.Core.dll")).VersionInfo; 39 | $TSqlFlexDllVersion = -join($TSqlFlexDllVersionInfo.FileMajorPart, ".", $TSqlFlexDllVersionInfo.FileMinorPart, 40 | ".",$TSqlFlexDllVersionInfo.FilePrivatePart); 41 | $TSqlFlexDllVersion 42 | 43 | #read the AssemblySemverPrereleaseTag from the assembly in a new PowerShell instance. 44 | # then rename the zip file accordingly 45 | Invoke-Expression (-Join('cmd /c start powershell -Command { 46 | $assembly = [Reflection.Assembly]::ReflectionOnlyLoadFrom((Join-Path "', $scriptPath, '" "Binaries\All\TSqlFlex.Core.dll")); 47 | $atts = [reflection.customattributedata]::GetCustomAttributes($assembly); 48 | $ver = $atts | Where-Object {$_.AttributeType.ToString() -eq "AssemblySemverPrereleaseTag"} | % {$_.ConstructorArguments[0].ToString()} 49 | $ver = $ver.Replace("`"","") 50 | if ($ver.length -gt 0) { 51 | $ver = -Join("-", $ver); 52 | } 53 | $fromFileName = (Join-Path "', $scriptPath, '" "Binaries\TSqlFlex.zip"); 54 | $toFileName = (Join-Path "', $scriptPath, '" (-Join("Binaries\TSqlFlex-", "', $TSqlFlexDllVersion, '", $ver, ".zip"))); 55 | Rename-Item $fromFileName $toFileName; 56 | }')); 57 | 58 | 59 | & "C:\Program Files (x86)\Notepad++\notepad++.exe" "..\Chocolatey\TSQLFlex.nuspec" 60 | & "C:\Program Files (x86)\Notepad++\notepad++.exe" "..\Chocolatey\tools\chocolateyInstall.ps1" 61 | & "C:\Program Files (x86)\Notepad++\notepad++.exe" "..\README.md" 62 | 63 | start "https://github.com/nycdotnet/TSqlFlex/releases" 64 | 65 | explorer ".\Binaries" 66 | 67 | Pop-Location -------------------------------------------------------------------------------- /InstallationInstructions.md: -------------------------------------------------------------------------------- 1 | T-SQL Flex Installation Instructions 2 | ==================================== 3 | 4 | ## Best way 5 | T-SQL Flex is now [available](https://chocolatey.org/packages/tsqlflex) via Chocolatey! This is the best and easiest way to install T-SQL Flex and the Red Gate SIP Framework (which is configured as a dependent package and automatically downloaded and installed too). 6 | 7 | First, get Chocolatey from https://chocolatey.org/ . Once Chocolatey is installed, close all copies of SSMS and open an *administrative* PowerShell prompt (right-click, Run as Administrator). From that *administrative* PowerShell prompt, run one of the following commands: 8 | 9 | ```PowerShell 10 | #install the latest version of T-SQL Flex 11 | choco install tsqlflex -pre 12 | 13 | #install a specific version of T-SQL Flex (in this case, v 0.0.11) 14 | choco install tsqlflex -pre -version 0.0.11 15 | 16 | #upgrade T-SQL Flex (only if you installed it via Chocolatey) 17 | choco upgrade tsqlflex -pre 18 | 19 | #uninstall T-SQL Flex 20 | choco uninstall tsqlflex 21 | ``` 22 | 23 | Here's the code for the T-SQL Flex Chocolatey packages in case you want to know what you're running: https://github.com/nycdotnet/TSqlFlex/tree/master/Chocolatey/tools 24 | 25 | 26 | ## Manual Install Instructions 27 | * Download the latest release from the [GitHub releases page](https://github.com/nycdotnet/TSqlFlex/releases). 28 | * Extract the contents of the ZIP file somewhere, for example `C:\ProgramData\T-SQL Flex\` 29 | * Right-click on each of the extracted DLLs, choose properties, and click `Unblock` on the bottom of the general tab. This will allow SSMS to load the DLL. 30 | * Install the [Red Gate SIP framework](http://documentation.red-gate.com/display/MA/Redistributing+the+framework). 31 | * Figure out if you have 32-bit or 64-bit Windows; it affects the next step. 32 | * Open Regedit and create or navigate to the below registry key: 33 | * If 32-bit Windows: `HKLM\SOFTWARE\Red Gate\SIPFramework\Plugins` 34 | * If 64-bit Windows: `HKLM\SOFTWARE\Wow6432Node\Red Gate\SIPFramework\Plugins` 35 | * Create a new registry string value (REG_SZ) there to point to the extracted TSqlFlex.dll. 36 | * For example: 37 | * value name: `TSqlFlex` 38 | * value data: `C:\ProgramData\T-SQL Flex\TSqlFlex.dll` 39 | 40 | **Using T-SQL Flex:** 41 | * Launch SQL Server Management Studio and click the T-SQL Flex button. 42 | * Type one or more queries in the top panel and click the Run'n'Rollback button. 43 | * T-SQL Flex will run your query in the scope of an ADO.NET Transaction that is rolled-back when the batch completes. The schema returned from those queries will be scripted in the lower panel. 44 | * You can also have your query scripted into an Excel sheet by selecting that in the dropdown. 45 | 46 | **To uninstall T-SQL Flex:** 47 | * Simply delete the registry key and the extracted files and restart SSMS. 48 | 49 | Please create issues on GitHub or reach out to Steve on Twitter at [@nycdotnet](https://twitter.com/nycdotnet). 50 | 51 | 52 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Steve Ognibene 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | T-SQL Flex 2 | ========== 3 | 4 | T-SQL Flex is a scripting productivity tool for SQL Server Management Studio that uses the Red Gate SIP Framework. It is intended for use on non-production servers only. 5 | 6 | T-SQL Flex can script out the returned schema and data of any T-SQL query simply and with high accuracy. It can also export the data to the XML spreadsheet format which can be opened in Excel without having messed-up date formatting or losing leading zeros; multiple result sets are automatically placed on multiple worksheets. It can also export the result of queries to properly-escaped CSV files. 7 | 8 | T-SQL Flex is compatible with SQL Server Management Studio 2008 and higher, and requires .NET 3.5 on the server running SSMS. T-SQL Flex can be used when connecting to SQL Server 2000 as long as SSMS 2008 or higher is used; Note that the generated T-SQL scripts will only be compatible with SQL Server 2008 and higher. 9 | 10 | ![tsqlflexscripttoinserts](https://cloud.githubusercontent.com/assets/3755379/4175774/d1b0772e-35e4-11e4-975c-12df856bd9e2.gif) 11 | 12 | **To install T-SQL Flex:** 13 | 14 | Follow the instructions [here](https://github.com/nycdotnet/TSqlFlex/blob/master/InstallationInstructions.md). 15 | 16 | **For Support** 17 | 18 | Please either create issues on GitHub, or reach out to Steve on Twitter at [@nycdotnet](https://twitter.com/nycdotnet). 19 | 20 | **Patch notes:** 21 | * v0.2.1-beta (2016-07-13): 22 | * Fixed scripting of TIME to XML Spreadsheet (no longer crashes) 23 | * Improved scripting of TIME to SQL scripts - can include up to 7 digits of scale. 24 | * Improved scripting of DATETIME2 to SQL scripts - now includes scale number if relevant. 25 | * v0.2.0-beta (2016-09-23): 26 | * Implemented new script as C# feature. 27 | * Many behind-the-scenes code improvements. 28 | * Added escaping for several SQL keywords. 29 | * Updated to work with NUnit 3 and RedGate SIP FW 1.0.1.246. 30 | * v0.1.0-beta (2015-05-07): 31 | * Reverted to use .NET 3.5 to restore compatibility with SSMS 2008. 32 | * Added CSV export feature! T-SQL Flex can now export the results of any set of queries to properly-escaped CSV files that open correctly in Excel or any text editor. 33 | * Added a handful of additional T-SQL keywords. 34 | * Removed "This is alpha software" warning. Beta 1 will likely have only minimal further changes prior to the v1.0 release of T-SQL Flex. 35 | * v0.0.11-alpha (2015-01-23): 36 | * Updated to use .NET 4.5 - this version is now required to run T-SQL Flex. 37 | * Improved scripter to continuously increment the "#Result" table number to prevent conflicts in a session. 38 | * Fixed lockup of SSMS when switching between database servers. (#33) 39 | * Fixed missing column header for anonymous columns in Excel export (#29) 40 | * Added WEIGHT, TARGET, and NONE as T-SQL keywords. 41 | * Implemented improved logging. 42 | * Older patch notes are [available here](ArchivedPatchNotes.md). 43 | 44 | 45 | **Debugging an add-in:** 46 | * See the Red Gate document on this issue: http://documentation.red-gate.com/display/MA/Debugging+an+SSMS+addin 47 | 48 | 49 | **Build checklist** 50 | * Compiles and all tests pass. 51 | * Checked-in to master branch on GitHub. 52 | * Updated version in both AssemblyInfo.cs files. 53 | * Build in release mode, switch the registry to use release mode, and test it out. 54 | * Run .\Deploy\prepDeploy.ps1 55 | * Zip up the DLLs with the license, README, and installation instructions (rename all to .txt) and post to GitHub. 56 | * Add a screenshot via GitHub and edit the README and release FAQ. 57 | * Chocolatey 58 | * Quit SSMS. 59 | * edit .nuspec with version and patch notes. 60 | * edit chocolateyInstall.ps1 with new GitHub release URL. 61 | * Run from admin powershell (under TSqlFlex\Chocolatey): 62 | * `cpack` 63 | * `cinst tsqlflex -source $pwd -pre` ( use `-force` if already installed) 64 | * `cuninst tsqlflex` 65 | * `cpush tsqlflex.VERSION_NUMBER.nupkg` (you can type `cpush .\t`) 66 | 67 | -------------------------------------------------------------------------------- /Resources/TSqlFlexScriptToInserts.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nycdotnet/TSqlFlex/48496d873726b3ad4283fbc99173e95ace1d438f/Resources/TSqlFlexScriptToInserts.gif --------------------------------------------------------------------------------