diff --git a/.github/workflows/ExecuteQueryToFile_build_and_test_on_main.yml b/.github/workflows/ExecuteQueryToFile_build_and_test_on_main.yml new file mode 100644 index 0000000..8a69b12 --- /dev/null +++ b/.github/workflows/ExecuteQueryToFile_build_and_test_on_main.yml @@ -0,0 +1,19 @@ +name: ExecuteQueryToFile_build_main + +on: + push: + branches: + - main + paths: + - 'Frends.MicrosoftSQL.ExecuteQueryToFile/**' + workflow_dispatch: + +jobs: + build: + uses: FrendsPlatform/FrendsTasks/.github/workflows/linux_build_main.yml@main + with: + workdir: Frends.MicrosoftSQL.ExecuteQueryToFile + prebuild_command: docker-compose -f ./Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/docker-compose.yml up -d + secrets: + badge_service_api_key: ${{ secrets.BADGE_SERVICE_API_KEY }} + \ No newline at end of file diff --git a/.github/workflows/ExecuteQueryToFile_build_and_test_on_push.yml b/.github/workflows/ExecuteQueryToFile_build_and_test_on_push.yml new file mode 100644 index 0000000..78ca2eb --- /dev/null +++ b/.github/workflows/ExecuteQueryToFile_build_and_test_on_push.yml @@ -0,0 +1,19 @@ +name: ExecuteQueryToFile_build_test + +on: + push: + branches-ignore: + - main + paths: + - 'Frends.MicrosoftSQL.ExecuteQueryToFile/**' + workflow_dispatch: + +jobs: + build: + uses: FrendsPlatform/FrendsTasks/.github/workflows/linux_build_test.yml@main + with: + workdir: Frends.MicrosoftSQL.ExecuteQueryToFile + prebuild_command: docker-compose -f ./Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/docker-compose.yml up -d + secrets: + badge_service_api_key: ${{ secrets.BADGE_SERVICE_API_KEY }} + test_feed_api_key: ${{ secrets.TASKS_TEST_FEED_API_KEY }} diff --git a/.github/workflows/ExecuteQueryToFile_release.yml b/.github/workflows/ExecuteQueryToFile_release.yml new file mode 100644 index 0000000..78a8cf9 --- /dev/null +++ b/.github/workflows/ExecuteQueryToFile_release.yml @@ -0,0 +1,13 @@ +name: ExecuteQueryToFile_release + +on: + workflow_dispatch: + +jobs: + build: + uses: FrendsPlatform/FrendsTasks/.github/workflows/release.yml@main + with: + workdir: Frends.MicrosoftSQL.ExecuteQueryToFile + secrets: + feed_api_key: ${{ secrets.TASKS_FEED_API_KEY }} + \ No newline at end of file diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/.editorconfig b/Frends.MicrosoftSQL.ExecuteQueryToFile/.editorconfig new file mode 100644 index 0000000..1023159 --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# SA1000: Keywords should be spaced correctly +dotnet_diagnostic.SA1000.severity = none diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Apache-2.0 b/Frends.MicrosoftSQL.ExecuteQueryToFile/Apache-2.0 new file mode 100644 index 0000000..a20cf5a --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Apache-2.0 @@ -0,0 +1,93 @@ +Apache License 2.0 +SPDX identifier +Apache-2.0 +License text + +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Standard License Header + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Notes + +This license was released January 2004 +SPDX web page + + https://spdx.org/licenses/Apache-2.0.html + +Notice + +This license content is provided by the SPDX project. For more information about licenses.nuget.org, see our documentation. + +Data pulled from spdx/license-list-data on February 9, 2023. diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/CHANGELOG.md b/Frends.MicrosoftSQL.ExecuteQueryToFile/CHANGELOG.md new file mode 100644 index 0000000..78d07f6 --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## [1.0.0] - 2024-02-07 +### Changed +- Initial implementation diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests.csproj b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests.csproj new file mode 100644 index 0000000..923c177 --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests.csproj @@ -0,0 +1,41 @@ + + + + net6.0 + + false + + + + 1701;1702;SA0001 + + + + 1701;1702;SA0001 + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + + + \ No newline at end of file diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/GlobalSuppressions.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/GlobalSuppressions.cs new file mode 100644 index 0000000..d4a5ab6 --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.MicrosoftSQL.ExecuteQueryToFile.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.MicrosoftSQL.ExecuteQueryToFile.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends documentation guidelines")] +[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1309:Field names should not begin with underscore", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.MicrosoftSQL.ExecuteQueryToFile.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1629:Documentation text should end with a period", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.MicrosoftSQL.ExecuteQueryToFile.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.MicrosoftSQL.ExecuteQueryToFile.Tests")] diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/TestData/Test_image.png b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/TestData/Test_image.png new file mode 100644 index 0000000..7055e41 Binary files /dev/null and b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/TestData/Test_image.png differ diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/TestData/Test_text.txt b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/TestData/Test_text.txt new file mode 100644 index 0000000..0b3070a --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/TestData/Test_text.txt @@ -0,0 +1,103 @@ +Lorem Ipsum +"Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." +"There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain..." + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ultricies, nulla in dignissim pharetra, neque orci maximus ligula, ut fermentum diam justo sed lacus. Proin sed posuere dolor. Suspendisse potenti. Pellentesque in mauris venenatis neque ullamcorper porta non placerat felis. Aliquam eu neque rhoncus, rutrum felis in, facilisis est. Suspendisse eros magna, scelerisque et vehicula a, ornare sit amet urna. Nullam et neque nisl. Fusce nec pharetra neque, ac viverra massa. In hac habitasse platea dictumst. Vivamus augue orci, semper a sem ut, dignissim imperdiet diam. Maecenas turpis enim, lobortis fringilla malesuada id, blandit vel velit. In tempor nulla ut metus bibendum, rhoncus dictum justo condimentum. + +Fusce iaculis nisl at urna posuere commodo. Suspendisse iaculis justo neque, gravida sodales erat accumsan vel. In lacinia arcu at felis sagittis fringilla. Cras gravida tellus est. Sed quis ornare massa. Sed hendrerit risus dui. Phasellus feugiat augue eget velit mollis dignissim. Vivamus bibendum varius maximus. Morbi vehicula, odio vel finibus gravida, metus purus gravida nisi, vel laoreet urna nisi sit amet leo. Etiam risus erat, scelerisque ut sem non, semper egestas dui. Proin ipsum tellus, aliquet et dictum at, accumsan vitae lectus. Nunc imperdiet viverra sagittis. Mauris dignissim, ante vitae sollicitudin euismod, ex elit lobortis lectus, non pretium libero nibh et odio. Fusce quis quam eu lacus congue viverra. Nunc cursus odio vel magna aliquet, ac efficitur diam lacinia. + +Duis nibh eros, egestas aliquam bibendum vitae, mattis at augue. Sed turpis elit, ullamcorper in laoreet nec, elementum ac libero. Cras pharetra mauris a finibus eleifend. Pellentesque iaculis magna finibus enim condimentum tincidunt id et dolor. Suspendisse ultrices mauris nunc, sed fermentum velit vehicula eu. Vestibulum accumsan neque vel justo consequat porttitor. Ut sollicitudin suscipit vehicula. Maecenas et convallis magna. Nunc porta libero sed diam faucibus hendrerit. Vivamus et ex efficitur, consectetur lacus in, blandit justo. Fusce tristique dolor non ante ornare viverra. Nulla bibendum malesuada magna, at efficitur arcu pharetra in. Cras ac viverra arcu, et pulvinar enim. Integer auctor molestie feugiat. Maecenas congue dictum diam sit amet blandit. Cras a suscipit nisi, eu rutrum ipsum. + +Nulla sem ipsum, congue et facilisis eget, imperdiet ut justo. Vestibulum viverra augue magna, eget accumsan enim molestie a. Donec quis tortor quis dui rhoncus interdum. Phasellus vitae sodales orci. Nunc rutrum leo quis diam rutrum rhoncus. Nullam eu metus sem. Etiam dignissim, eros eu cursus laoreet, ex elit bibendum velit, mollis viverra purus lacus nec leo. Aliquam aliquam at orci et consectetur. Donec purus ex, commodo ac nulla in, varius mattis urna. Donec et arcu eu diam aliquam tincidunt. Integer hendrerit, ante sit amet vulputate feugiat, turpis ipsum pellentesque tellus, pellentesque consectetur lorem purus at mi. Aliquam dui nulla, pretium et risus vel, maximus facilisis metus. Donec molestie ac leo at interdum. Pellentesque lacus risus, semper nec fringilla et, accumsan sed tortor. Nulla facilisi. + +Etiam ac placerat nibh, nec feugiat est. Praesent sapien eros, lobortis eu dignissim id, ornare nec libero. Nulla facilisi. Fusce non accumsan quam. Fusce commodo sapien risus, eget pulvinar nibh commodo nec. Suspendisse semper diam ac lectus iaculis, vitae sagittis magna sagittis. Nulla ornare tortor sit amet congue accumsan. In imperdiet sodales augue, consectetur rutrum nunc suscipit quis. Pellentesque tristique, nibh consectetur accumsan mollis, enim massa porta diam, ac imperdiet enim sem ut eros. Integer viverra gravida quam at posuere. Sed a tristique sapien. Etiam vel arcu sagittis, consectetur nisi at, varius diam. Etiam vel nibh aliquam, aliquam velit in, elementum massa. Ut malesuada accumsan felis, sed tempus sem rutrum non. Vivamus porttitor egestas diam, sed aliquet lectus porttitor sed. + +Cras ac elit magna. Duis commodo, purus quis pellentesque laoreet, justo turpis ornare massa, dignissim congue odio purus nec lectus. Nullam sed vehicula turpis. In euismod rutrum nibh, dictum laoreet erat feugiat et. Nulla hendrerit mollis ante nec condimentum. Nulla vehicula odio vitae tincidunt scelerisque. Vivamus lacinia nisi vel leo dapibus, vel tempor tellus vulputate. Ut finibus, massa eu aliquet elementum, diam urna varius ante, vel tempor nisl ipsum et ex. Donec leo massa, commodo vitae risus ornare, vehicula pharetra est. Duis interdum risus in ligula pellentesque fringilla. Integer cursus dignissim tellus. Phasellus in dui quis risus fringilla ornare. Integer fringilla, enim ut efficitur aliquam, sem dolor finibus mauris, ac dignissim libero augue vel augue. Pellentesque eu mauris mauris. + +Morbi congue leo est, sed suscipit dolor cursus et. In ornare pretium fermentum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque placerat arcu sapien, a scelerisque elit efficitur at. Nam sed interdum magna. Aenean egestas in diam id congue. Cras diam sapien, faucibus non congue et, ultricies non nulla. Sed mollis nulla eget consequat varius. Nulla tincidunt quam et faucibus porttitor. Etiam quis libero mauris. + +Quisque vulputate lorem justo, eget rutrum felis porttitor bibendum. In vestibulum pretium sem sit amet suscipit. Sed sodales tellus mauris, et interdum odio hendrerit vel. Maecenas pulvinar diam a elit elementum hendrerit ut commodo arcu. Nunc ac eros vitae mi placerat gravida. Morbi sapien ante, elementum vel fermentum sit amet, pulvinar quis orci. Donec tempus felis sit amet tempor feugiat. Maecenas nec felis ut massa finibus consequat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Duis maximus, dui sit amet dapibus gravida, nibh ex faucibus velit, id rhoncus nulla lorem at magna. Ut non leo luctus, imperdiet orci sit amet, dictum neque. Nulla mattis sit amet diam id ullamcorper. Integer pellentesque quam a ante viverra lobortis. Suspendisse ac nibh vitae urna fringilla congue vel tempus arcu. Phasellus sit amet quam rhoncus, aliquam lectus ut, iaculis lacus. Donec mattis tristique ex ac elementum. + +Phasellus nec facilisis orci, quis vehicula velit. Nulla vehicula mauris eget sapien varius, nec aliquet lorem suscipit. Integer tincidunt est et metus scelerisque dapibus cursus eu augue. Maecenas orci risus, rutrum eu arcu et, auctor semper turpis. Praesent nulla mi, sagittis quis egestas ut, feugiat ac erat. Suspendisse lectus ligula, convallis sit amet erat id, tristique egestas sem. Proin sit amet lobortis massa, sit amet aliquam nisl. Quisque ullamcorper lorem a metus sollicitudin pulvinar. Nam egestas vulputate ante, ac molestie leo. Sed in enim tempor, porta mauris eget, egestas lectus. + +Sed vitae mi id lectus commodo consequat. In congue lorem at lectus ultrices, at auctor diam facilisis. Maecenas molestie mauris turpis, id tristique libero lacinia rhoncus. Integer faucibus blandit magna quis rhoncus. Aliquam hendrerit mi nec quam suscipit, eu maximus augue efficitur. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque in ligula in mauris pharetra sodales. Vivamus ac porttitor leo, ut pulvinar urna. Praesent eget nisi ut metus faucibus gravida. Aenean lorem libero, tincidunt ut tellus id, tempus tincidunt leo. Vestibulum mi justo, aliquet at hendrerit luctus, fringilla a elit. + +Suspendisse potenti. Aliquam eu mi viverra, dignissim lorem in, vulputate urna. Proin fringilla metus dolor, et molestie diam condimentum a. Suspendisse eget posuere augue. Cras venenatis, diam nec dapibus iaculis, mauris arcu elementum dui, cursus lobortis justo libero eget nunc. Etiam a viverra dolor, nec malesuada nibh. Donec gravida pharetra turpis, a dignissim nisi aliquam vitae. In feugiat pulvinar ornare. Morbi convallis odio mauris, in consectetur orci eleifend sed. Nulla vestibulum nibh et mauris faucibus, a lacinia nulla tristique. Ut sed turpis at erat rhoncus cursus. Vivamus semper, turpis ut interdum vehicula, sem arcu consequat nisl, sit amet ultricies nulla purus et ex. + +Suspendisse arcu ligula, pharetra rutrum sapien sit amet, laoreet egestas mi. Duis ultrices dictum ligula sed dapibus. Vivamus lorem turpis, fermentum vitae lorem ac, tristique sagittis ligula. Pellentesque consectetur dolor ac arcu tempor eleifend. Sed sed facilisis eros, quis bibendum dolor. Nam quis pharetra nunc, ac faucibus libero. Ut accumsan purus a orci pellentesque elementum. + +Donec sed ipsum finibus, molestie tortor vel, fermentum leo. Aliquam arcu velit, tempus tempus mi in, iaculis porta dui. Vivamus suscipit ultricies diam quis porttitor. Nulla porta massa sem, a pretium nunc pharetra ac. Aliquam erat volutpat. Duis a iaculis lorem. Mauris ut iaculis orci. Donec aliquet, lacus dignissim posuere euismod, quam nibh interdum lorem, ut tempus lorem ligula at nibh. Pellentesque sollicitudin sapien eget suscipit posuere. Morbi vestibulum ligula et lectus mollis mollis. Proin pulvinar, nibh euismod rutrum faucibus, sapien diam interdum ligula, a consequat nisi velit elementum libero. In id molestie nunc. + +Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Morbi dictum quam sit amet libero sodales, eu tincidunt lectus interdum. Phasellus convallis pulvinar ante at rutrum. Nunc cursus eros ac augue gravida tincidunt. Etiam lacinia quam sit amet lectus posuere elementum. Nam eget arcu eu dolor sodales congue in vel augue. Ut eget odio viverra, posuere dui non, luctus nisl. Nulla pellentesque, orci quis consequat sollicitudin, nisl felis eleifend tellus, at vulputate ligula sem et eros. Vivamus convallis ex vitae sapien semper dictum. + +Donec malesuada urna eu nisl lobortis auctor. Sed vel nunc vel tellus condimentum commodo ac in ipsum. Integer lectus odio, euismod quis felis ut, suscipit scelerisque mi. Praesent ante felis, sagittis et laoreet ut, pharetra quis massa. Donec in tortor malesuada neque gravida porttitor. Curabitur enim turpis, tristique a leo nec, vulputate iaculis enim. Proin ullamcorper nibh ut ex porttitor, nec malesuada ex cursus. Vivamus congue massa id laoreet iaculis. Pellentesque varius ipsum a leo congue venenatis in fringilla risus. Nulla scelerisque ipsum sed mi mollis dignissim. + +Proin malesuada risus sed finibus gravida. Praesent id tellus in tortor accumsan dapibus. Suspendisse lorem mi, feugiat ac rhoncus ac, sagittis vitae mi. Cras libero augue, condimentum lacinia consectetur scelerisque, fermentum vulputate ligula. Aliquam varius imperdiet magna sit amet iaculis. Pellentesque imperdiet sem dolor, eu vestibulum purus tempor at. Donec ultrices velit at eros mattis vehicula. Nulla auctor efficitur viverra. Cras fermentum tempus velit mollis porttitor. Nunc porttitor ac orci vel interdum. Duis pretium sagittis dolor, non condimentum leo porttitor et. + +Curabitur vulputate velit ultrices nulla pharetra, aliquet vestibulum elit pellentesque. Vivamus varius semper purus, id tincidunt velit ornare sit amet. Curabitur mauris nunc, tristique quis eleifend nec, consequat a metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut pulvinar vehicula tellus sagittis dictum. Mauris quis elit ipsum. In nulla lacus, dapibus quis tincidunt id, sagittis sit amet nunc. Aenean sollicitudin purus enim, at fringilla mi ultrices sit amet. Sed mi libero, ultrices nec suscipit sit amet, eleifend sit amet neque. Aliquam id vehicula augue. + +Sed tincidunt tempus lacinia. Cras eleifend massa eleifend luctus luctus. Pellentesque at nisi venenatis, tincidunt tellus nec, consequat purus. Integer id justo diam. Quisque eu elit a erat consectetur auctor. Pellentesque mollis commodo auctor. Quisque ac magna magna. In semper quis urna sed elementum. Donec non fermentum metus. + +Donec sollicitudin consequat viverra. Nulla tincidunt laoreet aliquet. Duis vitae urna eget lacus fringilla malesuada. Morbi non sagittis nunc, sit amet feugiat neque. Sed aliquet enim sed orci congue, vitae hendrerit neque rhoncus. Aliquam ullamcorper sapien consequat, pretium enim semper, congue ante. Suspendisse pellentesque in metus eget cursus. Integer in enim massa. Nullam laoreet ac ante vitae tempus. Ut hendrerit accumsan tellus efficitur sodales. Morbi venenatis nisl ipsum, vitae tincidunt quam consequat sed. In nec magna vehicula nisi hendrerit fringilla ac quis orci. Vivamus bibendum, libero sit amet tristique fermentum, quam urna ultrices dolor, eu pulvinar augue eros non turpis. Praesent sit amet fringilla diam. + +Ut eu feugiat diam. Suspendisse venenatis dui quam. Ut ut euismod erat. Sed eros odio, bibendum consequat dignissim ac, pellentesque ut metus. Donec nec porta mauris. Donec ac ante vitae erat faucibus rhoncus. Cras efficitur quis lectus porttitor blandit. Quisque lorem sapien, iaculis ac consequat eget, molestie non lacus. Ut erat sapien, facilisis at augue ut, mollis tempor urna. + +Mauris ut mi sed orci congue maximus. Nunc in feugiat sem, at dignissim ante. Nam iaculis ante iaculis, accumsan leo quis, porttitor magna. Proin tempus imperdiet ligula. Praesent consequat volutpat justo. Mauris a ullamcorper dolor. Praesent tincidunt lacus blandit, facilisis nunc sed, posuere ex. Cras quis justo mi. + +In fringilla nisi eget libero rhoncus tincidunt. In non elementum arcu. Nunc ac viverra leo. Aliquam id rhoncus enim. Duis rutrum justo non massa finibus, eu venenatis tortor rutrum. Integer quis sapien eu nibh ullamcorper tempor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse pellentesque, nulla vel iaculis molestie, lorem mauris cursus odio, eget mattis arcu quam in lorem. Vivamus mattis pretium nisl. Curabitur diam turpis, faucibus a accumsan a, ultricies nec ante. Fusce posuere quam sit amet turpis consequat rutrum. + +Cras sed mollis sem, ac auctor odio. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In blandit lacinia orci quis venenatis. Ut hendrerit ut leo sed tempus. Fusce pellentesque suscipit urna a sodales. Proin aliquet purus sed ultrices posuere. Nulla eget sodales dui. + +Etiam volutpat efficitur nibh in venenatis. Nam in ligula eget purus viverra laoreet. Nullam et vehicula odio. Nullam tincidunt ipsum a nisi tempor, ultrices imperdiet leo dapibus. Aenean lacus diam, dignissim sit amet egestas eu, auctor sed justo. Cras tempor bibendum mattis. Aenean tempor malesuada libero, id vestibulum massa condimentum eu. Morbi sed commodo turpis. Ut in nibh blandit, dictum velit ut, cursus libero. Pellentesque pellentesque risus id porttitor placerat. Suspendisse sed purus sed nulla fringilla pharetra eu eget diam. + +Ut orci erat, hendrerit in semper at, congue sed purus. Suspendisse nunc est, volutpat non tempus ut, convallis id urna. In vulputate metus ut lorem faucibus malesuada. Phasellus nec efficitur eros, sed tempus dui. Donec volutpat fringilla consectetur. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis varius sed purus vel hendrerit. Morbi aliquam pharetra est hendrerit feugiat. Nulla congue fermentum malesuada. + +Cras sit amet est porta, auctor dolor vel, porttitor nulla. Nam dignissim porttitor est, non efficitur metus blandit a. Praesent nisi mi, mollis a congue vitae, tristique sed mauris. Pellentesque porta, diam at pharetra porta, lectus elit placerat elit, a laoreet sem est vitae justo. Phasellus ac magna non urna imperdiet iaculis. Donec faucibus elementum consectetur. Integer eu sapien non augue lobortis tempor. Mauris lacinia nisi sed magna pharetra, nec viverra eros auctor. Nam eu euismod orci. Pellentesque iaculis nec ligula a lacinia. Sed luctus mollis faucibus. + +Sed nec tincidunt ligula. Integer et finibus magna. Praesent facilisis, arcu non imperdiet blandit, ante eros pellentesque tortor, a iaculis neque lacus id neque. Ut eleifend massa sed tortor aliquam, sed porta neque mollis. Vivamus vitae consequat felis. Etiam eros dui, bibendum id dui ut, accumsan facilisis ex. Morbi blandit odio a hendrerit mollis. Phasellus eu elit mauris. Donec est sapien, dapibus eget vulputate nec, posuere ac turpis. Duis consequat rhoncus orci, a fermentum sem. Integer vel lacus nibh. Etiam interdum, nunc in eleifend tempus, ipsum orci sollicitudin erat, ac vulputate nisi enim pharetra sem. Donec ac nibh suscipit, tristique justo nec, tristique metus. Quisque enim eros, rutrum nec justo sed, ultrices fringilla justo. In iaculis tortor et orci fringilla venenatis. Nunc sollicitudin lectus justo, id cursus est ornare mattis. + +Donec molestie orci sem, et euismod ipsum mattis nec. Duis rhoncus hendrerit odio, eu pellentesque metus cursus ut. Mauris risus purus, semper eget nisl ac, pulvinar tristique urna. Suspendisse potenti. Aliquam ultricies augue id volutpat imperdiet. Morbi ante ligula, aliquam sed sem sit amet, pulvinar interdum libero. Curabitur lobortis odio nec iaculis tincidunt. Nam eget tempor mi. Nullam vel ultricies nisl, vitae iaculis ex. + +Curabitur neque tellus, tincidunt at euismod et, tincidunt quis risus. Integer accumsan iaculis fermentum. Vivamus lobortis placerat nulla. Vestibulum facilisis lacinia bibendum. Suspendisse auctor tortor eros, ut scelerisque ante blandit quis. Nulla porta efficitur tortor, vitae sollicitudin eros. Nulla egestas ligula non lectus dignissim, ut tincidunt ante interdum. Aliquam et elit eget neque consequat rutrum eu eu nisi. Proin sodales faucibus eleifend. Nam condimentum mattis purus in lacinia. Sed tincidunt euismod tellus, at mollis dolor maximus nec. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut in dolor ac sem suscipit lobortis. + +Praesent purus orci, sagittis at lectus sed, tincidunt pulvinar orci. Pellentesque feugiat nisl ipsum, vel feugiat mi maximus at. Suspendisse blandit dui a ante mollis mollis. Nullam mollis laoreet erat, sed placerat felis aliquet sit amet. Praesent rhoncus ullamcorper ultricies. Ut lacinia lectus nec rutrum consequat. Pellentesque nec accumsan mauris. + +Curabitur risus eros, posuere sed porta in, laoreet ut turpis. Nullam condimentum maximus sapien, ut congue lectus accumsan eu. Morbi sagittis bibendum ligula, nec fermentum nulla mollis ut. Ut quis turpis ac nibh aliquam viverra. Maecenas placerat id augue vel condimentum. Nam sollicitudin nec nisl a tempor. Quisque lacus neque, laoreet et imperdiet id, suscipit a orci. Suspendisse lobortis porta est ac rhoncus. Aenean lacinia blandit libero a molestie. Maecenas ac venenatis sapien. Vivamus vel fringilla elit, vulputate imperdiet est. Suspendisse sit amet lorem elementum, efficitur leo a, facilisis ante. Suspendisse potenti. Vivamus ipsum urna, suscipit non euismod eget, hendrerit et magna. Morbi eu elementum nulla. + +Nulla ac lorem vel justo vulputate finibus. Pellentesque in tellus non enim commodo rutrum ac gravida dolor. Maecenas consectetur sem eu diam iaculis vestibulum. Cras sapien augue, gravida eget condimentum eget, dignissim eu tellus. Vestibulum id varius orci, quis imperdiet felis. In non congue quam. Donec venenatis in nisl et blandit. Vivamus fermentum eu ipsum vitae tempus. Nulla ultrices posuere vulputate. Duis efficitur ultrices tincidunt. Duis vel finibus diam. Proin porttitor placerat purus, at pretium leo pellentesque in. Maecenas dictum ligula id erat convallis, quis gravida odio laoreet. Aliquam et odio a enim fermentum egestas nec in neque. Donec vitae dolor at eros elementum pulvinar nec non diam. + +Quisque mattis, dolor sed porttitor condimentum, tellus urna vulputate sapien, sit amet fermentum enim odio eu lectus. Curabitur et lectus lacus. Nullam non dapibus eros. In nec ipsum non ligula hendrerit blandit. Quisque nec pretium nisi. Nulla scelerisque nunc non risus venenatis lobortis. Fusce id maximus augue, vitae varius erat. Sed id elit justo. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nullam vestibulum dictum turpis, ut pretium libero sollicitudin ornare. + +Quisque non tincidunt felis, quis pharetra enim. Curabitur pretium sapien vitae justo aliquam suscipit. Integer nec erat iaculis, condimentum velit fringilla, condimentum ligula. Nulla tristique bibendum urna vel tincidunt. Phasellus vel odio sit amet augue efficitur dignissim. Nulla tempor posuere nunc, eget tincidunt erat consequat eu. Proin fringilla nec nunc at mollis. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nulla sagittis augue nunc, ac dignissim nisi laoreet sed. + +Donec malesuada, eros et blandit mollis, arcu dui gravida turpis, at sagittis nulla ipsum eu ligula. In commodo id dui quis feugiat. Vestibulum malesuada risus ut est efficitur posuere. Proin ultricies, est vel placerat malesuada, dolor libero fringilla erat, at sollicitudin lacus lacus mattis eros. Sed efficitur neque nec gravida rutrum. Nulla vel ante id quam ullamcorper interdum eu ac neque. Nam eget lacinia ex, sit amet facilisis velit. Curabitur luctus elementum lectus id aliquet. Nullam nec rutrum massa. Suspendisse a lacus vitae mauris lacinia interdum. + +Donec sit amet tincidunt mauris. Aliquam at varius dolor. Sed tristique dapibus libero, ut cursus arcu accumsan vitae. Sed rutrum ipsum eget est vestibulum, nec accumsan metus feugiat. Sed at urna viverra, accumsan augue in, sollicitudin massa. Vivamus ipsum dolor, scelerisque non augue convallis, mollis suscipit est. Phasellus lacus lorem, ullamcorper at mauris sit amet, cursus facilisis erat. Donec semper mauris lacus, at pharetra libero ultricies non. Sed bibendum lectus a suscipit viverra. Quisque non dapibus lectus. Nunc pellentesque semper tortor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aliquam gravida ante ipsum, vitae gravida urna ornare vitae. Morbi pulvinar non lacus at pellentesque. Curabitur porttitor nisi ac est congue porttitor. + +Integer sed magna diam. Aliquam tempor turpis eu mollis auctor. Maecenas sed magna nibh. Nullam dictum metus leo, lacinia placerat ante semper lacinia. Suspendisse vel luctus lectus, sit amet auctor nulla. Curabitur ullamcorper porta justo eu porttitor. Nullam eget ipsum aliquam, accumsan nisl vel, mollis dui. Cras sed pharetra lectus, quis vestibulum libero. Pellentesque egestas ac sem tristique luctus. Integer nec lacinia risus. + +Proin vehicula scelerisque ullamcorper. Aenean auctor tristique nisl, id ornare elit efficitur ut. Duis dignissim faucibus porta. Phasellus consequat purus nec odio auctor pretium. Nam nisi lorem, feugiat ut enim non, aliquam rhoncus lorem. Pellentesque blandit dictum mi, non volutpat lectus luctus a. Aenean ultricies leo massa, ornare dignissim mauris accumsan vel. Donec mattis felis eu orci porttitor commodo. Praesent gravida hendrerit efficitur. + +Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vivamus sed nisi tortor. Ut vestibulum, tellus ac luctus accumsan, lectus enim facilisis turpis, vel euismod massa ante ac orci. In hac habitasse platea dictumst. Nullam mattis, nisi at molestie rutrum, nibh enim rhoncus odio, ac pharetra massa lorem et sapien. Aliquam vitae posuere odio, vel scelerisque mi. Maecenas auctor, turpis id pretium commodo, nunc nibh dictum lectus, sit amet consectetur velit massa sed dolor. Suspendisse potenti. Nam sodales arcu ac orci suscipit faucibus. Proin sagittis ex a neque imperdiet viverra. Aliquam pulvinar orci bibendum neque eleifend bibendum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; + +Maecenas ac lobortis ligula. Nullam vulputate, nisi eu luctus fringilla, justo velit finibus nisl, sed accumsan dui justo ut felis. Vivamus rutrum sem non tellus ullamcorper hendrerit. Nullam dapibus massa at leo molestie, in porttitor velit pretium. Quisque luctus lacus non enim condimentum, id rutrum diam varius. Aliquam lorem lacus, maximus eu ultrices sit amet, placerat eu sapien. Vivamus sit amet ex a odio feugiat vulputate. Aliquam hendrerit metus at finibus porta. + +Phasellus commodo erat quis turpis volutpat, non tempus nunc efficitur. Nulla elit mauris, scelerisque at aliquet eu, scelerisque a tellus. Quisque tristique tortor quam, vel varius dolor congue et. Pellentesque quis facilisis libero. Cras aliquet nisl ac quam congue, non ullamcorper odio tristique. Nullam dictum sed nunc in porta. Sed sit amet laoreet risus, sit amet dignissim ex. Curabitur quis felis ac turpis commodo malesuada. Vivamus arcu dui, feugiat sit amet odio et, pulvinar malesuada orci. Integer id faucibus magna, et placerat lacus. Donec ut ipsum ut ex auctor bibendum. Sed consequat maximus metus. Vestibulum non justo at neque pretium sodales. Vivamus ultricies magna ac bibendum interdum. Ut sed purus eu tortor sollicitudin feugiat vitae a nisl. Etiam sed dolor magna. + +Cras vitae imperdiet lorem, quis dignissim sapien. Proin laoreet odio eget urna semper, a commodo mi tempor. Curabitur et lacus id ante egestas fermentum quis tristique erat. Interdum et malesuada fames ac ante ipsum primis in faucibus. Curabitur vel nibh vel lorem efficitur malesuada ut sit amet tortor. Suspendisse tincidunt elementum mauris, non venenatis ipsum facilisis non. Praesent nunc mi, ullamcorper id lectus eget, fermentum dignissim ligula. Vivamus quis ultrices est. Sed pretium nulla sit amet bibendum fringilla. Sed hendrerit vulputate leo, et maximus lacus iaculis dapibus. Duis a nunc sed dui luctus rutrum non sit amet nunc. + +Maecenas aliquam mattis quam nec tincidunt. In ac metus mattis, ultrices lacus id, convallis ipsum. Etiam finibus aliquam tellus, vel mollis tortor tempus ac. Donec non tincidunt lorem, in feugiat est. Nullam sollicitudin, nisi ac pellentesque blandit, nisl ex vestibulum nisl, vel pharetra enim odio vitae justo. Morbi commodo cursus lacinia. Nulla at faucibus leo. Ut sed tristique magna. + +Morbi ac nibh lacus. Maecenas non ligula vitae neque ultrices elementum ac consectetur risus. Mauris mattis, velit sit amet consectetur bibendum, orci felis consectetur nisl, ac interdum elit nunc feugiat lectus. Mauris diam lectus, feugiat ac condimentum eu, tincidunt facilisis quam. Aenean massa nulla, vehicula condimentum risus imperdiet, congue tincidunt purus. Proin at metus id mi porttitor volutpat. Donec gravida laoreet pretium. Nulla consectetur justo eu sem cursus viverra. Aliquam erat volutpat. + +Vivamus consequat maximus massa. Donec suscipit nisi vitae bibendum sodales. Praesent lacinia accumsan mi sed elementum. Donec a massa turpis. Nulla eget tortor a eros dictum commodo in sit amet libero. Suspendisse convallis nunc ut nisl consequat, sit amet dapibus sapien pharetra. Quisque accumsan velit et turpis iaculis efficitur. Maecenas commodo ipsum ac arcu varius, in rutrum magna interdum. Duis fringilla ipsum auctor bibendum molestie. Sed at tincidunt eros, non laoreet metus. Maecenas tempus nisi risus, in mattis ligula hendrerit vitae. Vestibulum accumsan et erat nec faucibus. Quisque ut lectus nec neque sollicitudin luctus eu volutpat lectus. Cras porttitor, ipsum interdum gravida hendrerit, tellus quam consequat dolor, vel fermentum nisi ipsum sit amet augue. Donec tincidunt, magna vel placerat vehicula, ex sapien pulvinar orci, et tincidunt lacus ligula a eros. + +Mauris viverra placerat est. Suspendisse sed varius libero, condimentum tincidunt massa. Sed ultricies vel velit eu luctus. Ut pretium, nunc ut tincidunt porttitor, elit eros fermentum augue, sit amet fringilla diam libero nec eros. Maecenas vehicula urna sed orci lobortis congue. Praesent porta est tellus, sed condimentum lorem rhoncus eu. Maecenas consectetur magna neque, at rhoncus leo consectetur sit amet. Quisque eleifend id lorem sed scelerisque. Etiam faucibus neque non ex ultrices elementum. Suspendisse sodales mauris sed purus dignissim, non porttitor lorem lacinia. Sed eu facilisis nulla, et efficitur nisi. + +Donec vulputate fringilla augue eget lobortis. Donec porttitor fermentum est, sed sagittis dolor porttitor at. Ut euismod lobortis rhoncus. Integer nec consectetur libero. Nullam purus arcu, pellentesque in finibus non, eleifend sed turpis. Morbi suscipit orci eros, quis eleifend arcu posuere in. Donec vitae nibh lacinia, fringilla ante porttitor, finibus nunc. Suspendisse congue magna justo, quis dictum tortor tincidunt ac. Aenean sed lorem turpis. Duis blandit blandit tellus iaculis finibus. Maecenas in sapien id risus lacinia finibus. Nam nec ante at turpis hendrerit aliquet eget vel nisi. Quisque accumsan elit quis risus dictum dignissim. Cras vehicula, ante eget volutpat tempor, est arcu ornare velit, ac aliquet sapien eros dignissim orci. + +Donec vulputate elit eget mattis pharetra. Nunc enim nibh, elementum nec arcu id, malesuada ornare lorem. Duis vel lobortis urna. Ut diam mauris, blandit nec sapien vel, pretium varius sapien. Sed vitae efficitur mauris. Quisque laoreet bibendum massa id convallis. Vestibulum a feugiat ipsum. In hac habitasse platea dictumst. Maecenas tempus pulvinar lacus, non rhoncus erat bibendum eu. In laoreet quam tincidunt, ultrices nisi eget, accumsan mauris. Fusce non mauris porta, volutpat turpis nec, eleifend nisl. Quisque condimentum pharetra mollis. Nulla porttitor justo elit, non rhoncus turpis posuere nec. Donec pretium vestibulum dignissim. + +Curabitur maximus est vitae pellentesque consequat. In mattis ut mauris in facilisis. Vestibulum blandit dolor lacus, at blandit sapien commodo eu. Proin tincidunt, orci sit amet iaculis commodo, magna tortor blandit tellus, in sollicitudin lacus massa in ipsum. Mauris vitae leo nisl. Phasellus egestas, erat at volutpat laoreet, erat augue varius ligula, a consectetur sem ligula eu urna. Integer ultricies molestie iaculis. Suspendisse lobortis facilisis lectus, ac finibus tellus. Vestibulum iaculis eget orci a faucibus. In vel ullamcorper dolor. Nunc massa augue, fermentum sed sapien pellentesque, vulputate accumsan nisl. Sed et ante vitae diam malesuada sodales. Mauris consectetur facilisis tellus quis bibendum. Phasellus luctus turpis vel ipsum bibendum posuere. Praesent commodo pretium mauris, in porttitor magna scelerisque in. Sed cursus tellus quis arcu consectetur tempus. + +Sed faucibus justo a tortor molestie, non lobortis dolor fringilla. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Sed finibus ligula vel mattis ornare. Nullam et mattis sapien. Nam lacinia lectus eget mollis lacinia. Quisque mattis consectetur augue in ullamcorper. Aliquam vel felis interdum, sollicitudin urna volutpat, condimentum magna. Pellentesque pretium ex elit, ut mattis dui semper vitae. Proin arcu sapien, suscipit in ligula ut, tincidunt sollicitudin urna. Proin condimentum viverra ante id varius. Morbi nec ante justo. Nunc laoreet libero non mauris laoreet ornare. Donec tristique varius sem, sed laoreet purus blandit eget. In dapibus gravida justo sit amet lobortis. \ No newline at end of file diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/UnitTests.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/UnitTests.cs new file mode 100644 index 0000000..2887458 --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/UnitTests.cs @@ -0,0 +1,244 @@ +namespace Frends.MicrosoftSQL.ExecuteQueryToFile.Tests; + +using System; +using System.Data; +using System.Data.SqlClient; +using System.IO; +using System.Threading.Tasks; +using Frends.MicrosoftSQL.ExecuteQueryToFile.Definitions; +using Frends.MicrosoftSQL.ExecuteQueryToFile.Enums; +using NUnit.Framework; + +/// +/// docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=Salakala123!" -p 1433:1433 --name sql1 --hostname sql1 -d mcr.microsoft.com/mssql/server:2019-CU18-ubuntu-20.04 +/// with Git bash add winpty to the start of +/// winpty docker exec -it sql1 "bash" +/// /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "Salakala123!" +/// Check rows before CleanUp: +/// SELECT* FROM TestTable +/// GO +/// Optional queries: +/// SELECT Name FROM sys.Databases; +/// GO +/// SELECT* FROM INFORMATION_SCHEMA.TABLES; +/// GO +/// +[TestFixture] +public class UnitTests +{ + private static readonly string _connString = Helper.GetConnectionString(); + private static readonly string _tableName = "TestTable"; + private static readonly string _destination = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../../TestData/test.csv"); + + private Options _options; + + [SetUp] + public void Init() + { + _options = new Options() + { + TimeoutSeconds = 30, + CsvOptions = new CsvOptions + { + FieldDelimiter = CsvFieldDelimiter.Semicolon, + LineBreak = CsvLineBreak.CRLF, + FileEncoding = FileEncoding.UTF8, + EnableBom = false, + IncludeHeadersInOutput = false, + SanitizeColumnHeaders = false, + AddQuotesToDates = false, + AddQuotesToStrings = false, + DateFormat = "yyyy-MM-dd", + DateTimeFormat = "yyyy-MM-ddTHH:mm:ss", + }, + }; + + Helper.CreateTestTable(_connString, _tableName); + + var parameters = new System.Data.SqlClient.SqlParameter[] + { + new System.Data.SqlClient.SqlParameter("@Hash", SqlDbType.VarBinary) + { + Value = File.ReadAllBytes(Path.Combine(Path.GetDirectoryName(_destination), "Test_image.png")), + }, + new System.Data.SqlClient.SqlParameter("@TestText", SqlDbType.VarBinary) + { + Value = File.ReadAllBytes(Path.Combine(Path.GetDirectoryName(_destination), "Test_text.txt")), + }, + }; + + Helper.InsertTestData(_connString, $"Insert into {_tableName} (Id, LastName, FirstName, Salary, Image, TestText) values (1,'Meikalainen','Matti',1523.25, {parameters[0].ParameterName}, {parameters[1].ParameterName});", parameters); + } + + [TearDown] + public void CleanUp() + { + using (var connection = new SqlConnection(_connString)) + { + connection.Open(); + var createTable = connection.CreateCommand(); + createTable.CommandText = $@"IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='{_tableName}') BEGIN DROP TABLE IF EXISTS {_tableName}; END"; + createTable.ExecuteNonQuery(); + connection.Close(); + } + + // Clean and remove destination directory + File.Delete(_destination); + } + + [Test] + public async Task ExecuteQueryToFile_SqlParameters() + { + var query = new Input + { + Query = $"Select Id, LastName, FirstName, REPLACE(Salary, '.', ',') AS 'Salary' from {_tableName} WHERE Id = @param", + QueryParameters = new Definitions.SqlParameter[] + { + new Definitions.SqlParameter + { + Name = "@param", + Value = 1, + SqlDataType = SqlDataTypes.Int, + }, + }, + ConnectionString = _connString, + OutputFilePath = _destination, + }; + + _options.CsvOptions.AddQuotesToStrings = true; + _options.CsvOptions.IncludeHeadersInOutput = true; + + await MicrosoftSQL.ExecuteQueryToFile(query, _options, default); + var output = File.ReadAllText(_destination); + + Assert.AreEqual("Id;LastName;FirstName;Salary\r\n1;\"Meikalainen\";\"Matti\";\"1523,25\"\r\n", output); + } + + [Test] + public async Task ExecuteQueryToFile_StringWithApostrophe() + { + var query = new Input + { + Query = $"Select Id, LastName, FirstName, REPLACE(Salary, '.', ',') AS 'Salary' from {_tableName}", + QueryParameters = Array.Empty(), + ConnectionString = _connString, + OutputFilePath = _destination, + }; + + _options.CsvOptions.AddQuotesToStrings = true; + _options.CsvOptions.IncludeHeadersInOutput = true; + + await MicrosoftSQL.ExecuteQueryToFile(query, _options, default); + var output = File.ReadAllText(_destination); + + Assert.AreEqual("Id;LastName;FirstName;Salary\r\n1;\"Meikalainen\";\"Matti\";\"1523,25\"\r\n", output); + } + + [Test] + public async Task ExecuteQueryToFile_StringWithoutApostrophe() + { + var query = new Input + { + Query = $"Select Id, LastName, FirstName, REPLACE(Salary, '.', ',') AS 'Salary' from {_tableName}", + QueryParameters = Array.Empty(), + ConnectionString = _connString, + OutputFilePath = _destination, + }; + + _options.CsvOptions.IncludeHeadersInOutput = true; + + await MicrosoftSQL.ExecuteQueryToFile(query, _options, default); + + var output = File.ReadAllText(_destination); + + Assert.AreEqual("Id;LastName;FirstName;Salary\r\n1;Meikalainen;Matti;1523,25\r\n", output); + } + + [Test] + public async Task ExecuteQueryToFile_WithImageDBType() + { + var query = new Input + { + Query = $"SELECT Image from {_tableName}", + QueryParameters = Array.Empty(), + ConnectionString = _connString, + OutputFilePath = _destination, + }; + + await MicrosoftSQL.ExecuteQueryToFile(query, _options, default); + + var output = File.ReadAllText(_destination); + + Assert.AreEqual(BitConverter.ToString(File.ReadAllBytes(Path.Combine(Path.GetDirectoryName(_destination), "Test_image.png"))), output.TrimEnd(new char[] { '\r', '\n' })); + } + + [Test] + public async Task ExecuteQueryToFile_WithImageDBTypeWithWhereClause() + { + var query = new Input + { + Query = $"SELECT Image from {_tableName} WHERE CONVERT(varbinary(max), Image) = @param", + QueryParameters = new Definitions.SqlParameter[] + { + new Definitions.SqlParameter + { + Name = "@param", + Value = File.ReadAllBytes(Path.Combine(Path.GetDirectoryName(_destination), "Test_image.png")), + SqlDataType = SqlDataTypes.VarBinary, + }, + }, + ConnectionString = _connString, + OutputFilePath = _destination, + }; + + await MicrosoftSQL.ExecuteQueryToFile(query, _options, default); + + var output = File.ReadAllText(_destination); + + Assert.AreEqual(BitConverter.ToString(File.ReadAllBytes(Path.Combine(Path.GetDirectoryName(_destination), "Test_image.png"))), output.TrimEnd(new char[] { '\r', '\n' })); + } + + [Test] + public async Task ExecuteQueryToFile_WithBinaryDBType() + { + var query = new Input + { + Query = $"SELECT TestText from {_tableName}", + QueryParameters = Array.Empty(), + ConnectionString = _connString, + OutputFilePath = _destination, + }; + + await MicrosoftSQL.ExecuteQueryToFile(query, _options, default); + + var output = File.ReadAllText(_destination); + + Assert.AreEqual(BitConverter.ToString(File.ReadAllBytes(Path.Combine(Path.GetDirectoryName(_destination), "Test_text.txt"))), output.TrimEnd(new char[] { '\r', '\n' })); + } + + [Test] + public async Task ExecuteQueryToFile_WithBinaryDBTypeWithWhereClause() + { + var query = new Input + { + Query = $"SELECT TestText FROM {_tableName} WHERE TestText = @param", + QueryParameters = new Definitions.SqlParameter[] + { + new Definitions.SqlParameter + { + Name = "@param", + Value = File.ReadAllBytes(Path.Combine(Path.GetDirectoryName(_destination), "Test_text.txt")), + SqlDataType = SqlDataTypes.VarBinary, + }, + }, + ConnectionString = _connString, + OutputFilePath = _destination, + }; + + await MicrosoftSQL.ExecuteQueryToFile(query, _options, default); + + var output = File.ReadAllText(_destination); + + Assert.AreEqual(BitConverter.ToString(File.ReadAllBytes(Path.Combine(Path.GetDirectoryName(_destination), "Test_text.txt"))), output.TrimEnd(new char[] { '\r', '\n' })); + } +} \ No newline at end of file diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/docker-compose.yml b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/docker-compose.yml new file mode 100644 index 0000000..6a241cc --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3' + +services: + sql1: + container_name: sql1 + image: mcr.microsoft.com/mssql/server:2019-CU18-ubuntu-20.04 + hostname: + sql1 + environment: + MSSQL_SA_PASSWORD: "Salakala123!" + ACCEPT_EULA: "Y" + ports: + - "1433:1433" \ No newline at end of file diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/lib/Helper.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/lib/Helper.cs new file mode 100644 index 0000000..bd26167 --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.Tests/lib/Helper.cs @@ -0,0 +1,49 @@ +namespace Frends.MicrosoftSQL.ExecuteQueryToFile.Tests; + +using System.Data; +using System.Data.SqlClient; + +internal static class Helper +{ + internal static string GetConnectionString() + { + var user = "SA"; + var pwd = "Salakala123!"; + return $"Server=127.0.0.1,1433;Database=Master;User Id={user};Password={pwd}"; + } + + internal static void CreateTestTable(string connString, string tableName) + { + using var connection = new SqlConnection(connString); + connection.Open(); + var createTable = connection.CreateCommand(); + createTable.CommandText = $@"IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='{tableName}') BEGIN CREATE TABLE {tableName} ( Id int, LastName varchar(255), FirstName varchar(255), Salary decimal(6,2), Image Image, TestText VarBinary(MAX)); END"; + createTable.ExecuteNonQuery(); + connection.Close(); + } + + internal static void InsertTestData(string connString, string commandText, System.Data.SqlClient.SqlParameter[] parameters = null) + { + using var sqlConnection = new SqlConnection(connString); + sqlConnection.Open(); + + using (var command = new SqlCommand()) + { + command.CommandText = commandText; + command.CommandType = CommandType.Text; + command.CommandTimeout = 30; + command.Connection = sqlConnection; + if (parameters != null) + { + foreach (var param in parameters) + { + command.Parameters.Add(param); + } + } + + command.ExecuteNonQuery(); + } + + sqlConnection.Close(); + } +} diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.sln b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.sln new file mode 100644 index 0000000..df720a7 --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32112.339 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Frends.MicrosoftSQL.ExecuteQueryToFile", "Frends.MicrosoftSQL.ExecuteQueryToFile\Frends.MicrosoftSQL.ExecuteQueryToFile.csproj", "{35C305C0-8108-4A98-BB1D-AFE5C926239E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Frends.MicrosoftSQL.ExecuteQueryToFile.Tests", "Frends.MicrosoftSQL.ExecuteQueryToFile.Tests\Frends.MicrosoftSQL.ExecuteQueryToFile.Tests.csproj", "{8CA92187-8E4F-4414-803B-EC899479022E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{78F7F22E-6E20-4BCE-8362-0C558568B729}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + CHANGELOG.md = CHANGELOG.md + ..\.github\workflows\ExecuteQueryToFile_build_and_test_on_main.yml = ..\.github\workflows\ExecuteQueryToFile_build_and_test_on_main.yml + ..\.github\workflows\ExecuteQueryToFile_build_and_test_on_push.yml = ..\.github\workflows\ExecuteQueryToFile_build_and_test_on_push.yml + ..\.github\workflows\ExecuteQueryToFile_release.yml = ..\.github\workflows\ExecuteQueryToFile_release.yml + README.md = README.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {35C305C0-8108-4A98-BB1D-AFE5C926239E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35C305C0-8108-4A98-BB1D-AFE5C926239E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35C305C0-8108-4A98-BB1D-AFE5C926239E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35C305C0-8108-4A98-BB1D-AFE5C926239E}.Release|Any CPU.Build.0 = Release|Any CPU + {8CA92187-8E4F-4414-803B-EC899479022E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8CA92187-8E4F-4414-803B-EC899479022E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8CA92187-8E4F-4414-803B-EC899479022E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8CA92187-8E4F-4414-803B-EC899479022E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {55BC6629-85C9-48D8-8CA2-B0046AF1AF4B} + EndGlobalSection +EndGlobal diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/CsvFileWriter.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/CsvFileWriter.cs new file mode 100644 index 0000000..89647a7 --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/CsvFileWriter.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Data.SqlClient; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using CsvHelper; +using CsvHelper.Configuration; +using Frends.MicrosoftSQL.ExecuteQueryToFile.Enums; + +namespace Frends.MicrosoftSQL.ExecuteQueryToFile.Definitions; + +internal class CsvFileWriter +{ + internal CsvFileWriter(SqlCommand sqlCommand, Input input, CsvOptions options) + { + SqlCommand = sqlCommand; + Input = input; + Options = options; + } + + private SqlCommand SqlCommand { get; set; } + + private Input Input { get; set; } + + private CsvOptions Options { get; set; } + + public async Task SaveQueryToCSV(CancellationToken cancellationToken) + { + var output = 0; + var encoding = GetEncoding(Options.FileEncoding, Options.EnableBom, Options.EncodingInString); + + using (var writer = new StreamWriter(Input.OutputFilePath, false, encoding)) + using (var csvFile = CreateCsvWriter(Options.GetFieldDelimiterAsString(), writer)) + { + writer.NewLine = Options.GetLineBreakAsString(); + + var reader = await SqlCommand.ExecuteReaderAsync(cancellationToken); + output = DataReaderToCsv(reader, csvFile, Options, cancellationToken); + + csvFile.Flush(); + } + + return new Result(output, Input.OutputFilePath, Path.GetFileName(Input.OutputFilePath)); + } + + private static CsvWriter CreateCsvWriter(string delimiter, TextWriter writer) + { + var csvOptions = new CsvConfiguration(CultureInfo.InvariantCulture) + { + Delimiter = delimiter, + }; + + return new CsvWriter(writer, csvOptions); + } + + private static string FormatDbHeader(string header, bool forceSpecialFormatting) + { + if (!forceSpecialFormatting) return header; + + // First part of regex removes all non-alphanumeric ('_' also allowed) chars from the whole string. + // Second part removed any leading numbers or underscoress. + var rgx = new Regex("[^a-zA-Z0-9_-]|^[0-9_]+"); + header = rgx.Replace(header, string.Empty); + return header.ToLower(); + } + + private static string FormatDbValue(object value, string dbTypeName, Type dotnetType, CsvOptions options) + { + if (value == null || value == DBNull.Value) + { + if (dotnetType == typeof(string)) return "\"\""; + if (dotnetType == typeof(DateTime) && options.AddQuotesToDates) return "\"\""; + return string.Empty; + } + + if (dotnetType == typeof(string)) + { + var str = (string)value; + options.GetFieldDelimiterAsString(); + str = str.Replace("\"", "\\\""); + str = str.Replace("\r\n", " "); + str = str.Replace("\r", " "); + str = str.Replace("\n", " "); + if (options.AddQuotesToStrings) + return $"\"{str}\""; + return str; + } + + if (dotnetType == typeof(DateTime)) + { + var dateTime = (DateTime)value; + var dbType = dbTypeName?.ToLower(); + string output = dbType switch + { + "date" => dateTime.ToString(options.DateFormat, CultureInfo.InvariantCulture), + _ => dateTime.ToString(options.DateTimeFormat, CultureInfo.InvariantCulture), + }; + if (options.AddQuotesToDates) return $"\"{output}\""; + return output; + } + + if (dotnetType == typeof(float)) + return ((float)value).ToString("0.###########", CultureInfo.InvariantCulture); + + if (dotnetType == typeof(double)) + return ((double)value).ToString("0.###########", CultureInfo.InvariantCulture); + + if (dotnetType == typeof(decimal)) + return ((decimal)value).ToString("0.###########", CultureInfo.InvariantCulture); + + if (dotnetType == typeof(byte[])) + return BitConverter.ToString((byte[])value); + + return value.ToString(); + } + + private static int DataReaderToCsv( + DbDataReader reader, + CsvWriter csvWriter, + CsvOptions options, + CancellationToken cancellationToken) + { + // Write header and remember column indexes to include. + var columnIndexesToInclude = new List(); + for (var i = 0; i < reader.FieldCount; i++) + { + var columnName = reader.GetName(i); + var includeColumn = + options.ColumnsToInclude == null || + options.ColumnsToInclude.Length == 0 || + options.ColumnsToInclude.Contains(columnName); + + if (includeColumn) + { + if (options.IncludeHeadersInOutput) + { + var formattedHeader = FormatDbHeader(columnName, options.SanitizeColumnHeaders); + csvWriter.WriteField(formattedHeader); + } + + columnIndexesToInclude.Add(i); + } + + cancellationToken.ThrowIfCancellationRequested(); + } + + if (options.IncludeHeadersInOutput) csvWriter.NextRecord(); + + int count = 0; + while (reader.Read()) + { + foreach (var columnIndex in columnIndexesToInclude) + { + var value = reader.GetValue(columnIndex); + var dbTypeName = reader.GetDataTypeName(columnIndex); + var dotnetType = reader.GetFieldType(columnIndex); + var formattedValue = FormatDbValue(value, dbTypeName, dotnetType, options); + csvWriter.WriteField(formattedValue, false); + cancellationToken.ThrowIfCancellationRequested(); + } + + csvWriter.NextRecord(); + count++; + } + + return count; + } + + private static Encoding GetEncoding(FileEncoding optionsFileEncoding, bool optionsEnableBom, string optionsEncodingInString) + { + return optionsFileEncoding switch + { + FileEncoding.Other => Encoding.GetEncoding(optionsEncodingInString), + FileEncoding.ASCII => Encoding.ASCII, + FileEncoding.ANSI => Encoding.Default, + FileEncoding.UTF8 => optionsEnableBom ? new UTF8Encoding(true) : new UTF8Encoding(false), + FileEncoding.Unicode => Encoding.Unicode, + _ => Encoding.ASCII, + }; + } +} diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/CsvOptions.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/CsvOptions.cs new file mode 100644 index 0000000..dc11562 --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/CsvOptions.cs @@ -0,0 +1,138 @@ +using System; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Frends.MicrosoftSQL.ExecuteQueryToFile.Enums; + +namespace Frends.MicrosoftSQL.ExecuteQueryToFile.Definitions; + +/// +/// Options for CSV outout. +/// +public class CsvOptions +{ + /// + /// Columns to include in the CSV output. Leave empty to include all columns in output. + /// + /// [ column1, column2 ] + public string[] ColumnsToInclude { get; set; } + + /// + /// What to use as field separators. + /// + /// CsvDelimiter.SemiColon + [DefaultValue(CsvFieldDelimiter.Semicolon)] + public CsvFieldDelimiter FieldDelimiter { get; set; } = CsvFieldDelimiter.Semicolon; + + /// + /// Custom field delimiter as a string. + /// + /// ; + [UIHint(nameof(FieldDelimiter), "", CsvFieldDelimiter.Custom)] + public string CustomFieldDelimiter { get; set; } + + /// + /// What to use as line breaks. + /// + /// CsvLineBreak.CRLF + [DefaultValue(CsvLineBreak.CRLF)] + public CsvLineBreak LineBreak { get; set; } = CsvLineBreak.CRLF; + + /// + /// Output file encoding. + /// + /// FileEncoding.UTF8 + [DefaultValue(FileEncoding.UTF8)] + public FileEncoding FileEncoding { get; set; } + + /// + /// Enable Bom. + /// + /// true + [UIHint(nameof(FileEncoding), "", FileEncoding.UTF8)] + public bool EnableBom { get; set; } + + /// + /// File encoding to be used. A partial list of possible encodings: https://en.wikipedia.org/wiki/Windows_code_page#List + /// + /// utf-8 + [UIHint(nameof(FileEncoding), "", FileEncoding.Other)] + public string EncodingInString { get; set; } + + /// + /// Whether to include headers in output. + /// + /// false + [DefaultValue(true)] + public bool IncludeHeadersInOutput { get; set; } = true; + + /// + /// Whether to sanitize headers in output: + /// - Strip any chars that are not 0-9, a-z or _ + /// - Make sure that column does not start with a number or underscore. + /// - Force lower case. + /// + /// false + [DefaultValue(true)] + public bool SanitizeColumnHeaders { get; set; } = true; + + /// + /// Whether to add quotes around DATE and DATETIME fields. + /// + /// false + [DefaultValue(true)] + public bool AddQuotesToDates { get; set; } = true; + + /// + /// Whether to add quotes around string typed fields. + /// + /// false + [DefaultValue(true)] + public bool AddQuotesToStrings { get; set; } = true; + + /// + /// Date format to use for formatting DATE columns, use .NET formatting tokens. + /// Note that formatting is done using invariant culture. + /// + /// yyyy-MM-dd + [DefaultValue("\"yyyy-MM-dd\"")] + public string DateFormat { get; set; } = "yyyy-MM-dd"; + + /// + /// Date format to use for formatting DATETIME columns, use .NET formatting tokens. + /// Note that formatting is done using invariant culture. + /// + /// yyyy-MM-dd HH:mm:ss + [DefaultValue("\"yyyy-MM-dd HH:mm:ss\"")] + public string DateTimeFormat { get; set; } = "yyyy-MM-dd HH:mm:ss"; + + /// + /// Helper method to return the field delimiter as string. + /// + /// string + internal string GetFieldDelimiterAsString() + { + return FieldDelimiter switch + { + CsvFieldDelimiter.Comma => ",", + CsvFieldDelimiter.Pipe => "|", + CsvFieldDelimiter.Semicolon => ";", + CsvFieldDelimiter.Custom => CustomFieldDelimiter, + _ => throw new Exception($"Unknown field delimeter: {FieldDelimiter}"), + }; + } + + /// + /// Helper method to return the line break as string. + /// + /// string + internal string GetLineBreakAsString() + { + return LineBreak switch + { + CsvLineBreak.CRLF => "\r\n", + CsvLineBreak.CR => "\r", + CsvLineBreak.LF => "\n", + _ => throw new Exception($"Unknown field delimeter: {FieldDelimiter}"), + }; + } +} \ No newline at end of file diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/Input.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/Input.cs new file mode 100644 index 0000000..2308780 --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/Input.cs @@ -0,0 +1,39 @@ +namespace Frends.MicrosoftSQL.ExecuteQueryToFile.Definitions; + +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +/// +/// Input class usually contains parameters that are required. +/// +public class Input +{ + /// + /// Query to execute. + /// + /// SELECT * FROM table + [DisplayFormat(DataFormatString = "Sql")] + public string Query { get; set; } + + /// + /// Query parameters. + /// + /// [ { Name = test, Value = test_value, SqlDataType = SqlDataTypes.Auto } ] + public SqlParameter[] QueryParameters { get; set; } + + /// + /// Database connection string. + /// + /// Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword; + [DefaultValue("\"Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;\"")] + [PasswordPropertyText] + [DisplayFormat(DataFormatString = "Text")] + public string ConnectionString { get; set; } + + /// + /// Output file path. + /// + /// C:\path\tp\file.csv + [DefaultValue("")] + public string OutputFilePath { get; set; } +} \ No newline at end of file diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/Options.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/Options.cs new file mode 100644 index 0000000..91ba4f7 --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/Options.cs @@ -0,0 +1,31 @@ +namespace Frends.MicrosoftSQL.ExecuteQueryToFile.Definitions; + +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Frends.MicrosoftSQL.ExecuteQueryToFile.Enums; + +/// +/// Options class usually contains parameters that are required. +/// +public class Options +{ + /// + /// Operation timeout (seconds). + /// + /// 30 + [DefaultValue(30)] + public int TimeoutSeconds { get; set; } + + /// + /// Determines in what format the query is written. + /// + /// ReturnFormat.CSV + [DefaultValue(ReturnFormat.CSV)] + public ReturnFormat ReturnFormat { get; set; } + + /// + /// Csv options. + /// + [UIHint(nameof(ReturnFormat), "", ReturnFormat.CSV)] + public CsvOptions CsvOptions { get; set; } +} \ No newline at end of file diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/Result.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/Result.cs new file mode 100644 index 0000000..80cf3c7 --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/Result.cs @@ -0,0 +1,36 @@ +namespace Frends.MicrosoftSQL.ExecuteQueryToFile.Definitions; + +/// +/// Result class usually contains properties of the return object. +/// +public class Result +{ + internal Result(int entriesWritten, string path, string name) + { + EntriesWritten = entriesWritten; + Path = path; + FileName = name; + } + + internal Result() + { + } + + /// + /// Amount of entries written. + /// + /// 2 + public int EntriesWritten { get; private set; } + + /// + /// Path to the file. + /// + /// C:\test.csv + public string Path { get; set; } + + /// + /// Name of the file. + /// + /// test.csv + public string FileName { get; set; } +} diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/SqlParameter.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/SqlParameter.cs new file mode 100644 index 0000000..bea3d7f --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/SqlParameter.cs @@ -0,0 +1,31 @@ +using System.ComponentModel; +using Frends.MicrosoftSQL.ExecuteQueryToFile.Enums; + +namespace Frends.MicrosoftSQL.ExecuteQueryToFile.Definitions; + +/// +/// Sql query parameter class. +/// +public class SqlParameter +{ + /// + /// The name of the parameter. + /// + /// first_name + public string Name { get; set; } + + /// + /// The value of the parameter. + /// + /// FirstName + public object Value { get; set; } + + /// + /// SQL Server-specific data type. + /// Note! Use SqlDataType.Auto if not sure of the type. + /// See https://learn.microsoft.com/en-us/dotnet/api/system.data.sqldbtype?view=net-7.0 for more information. + /// + /// SqlDbTypes.Empty + [DefaultValue(SqlDataTypes.Auto)] + public SqlDataTypes SqlDataType { get; set; } +} \ No newline at end of file diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/CsvFieldDelimiter.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/CsvFieldDelimiter.cs new file mode 100644 index 0000000..1c1cf33 --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/CsvFieldDelimiter.cs @@ -0,0 +1,16 @@ +namespace Frends.MicrosoftSQL.ExecuteQueryToFile.Enums; + +#pragma warning disable CS1591 // Self-explanatory + +/// +/// CSV field delimeter options. +/// +public enum CsvFieldDelimiter +{ + Comma, + Semicolon, + Pipe, + Custom, +} + +#pragma warning restore CS1591 // Self-explanatory diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/CsvLineBreak.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/CsvLineBreak.cs new file mode 100644 index 0000000..a75f597 --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/CsvLineBreak.cs @@ -0,0 +1,15 @@ +namespace Frends.MicrosoftSQL.ExecuteQueryToFile.Enums; + +#pragma warning disable CS1591 // Self-explanatory + +/// +/// CSV line break options. +/// +public enum CsvLineBreak +{ + CRLF, + LF, + CR, +} + +#pragma warning restore CS1591 // Self-explanatory diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/FileEncoding.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/FileEncoding.cs new file mode 100644 index 0000000..cdc2840 --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/FileEncoding.cs @@ -0,0 +1,17 @@ +namespace Frends.MicrosoftSQL.ExecuteQueryToFile.Enums; + +#pragma warning disable CS1591 // Self-explanatory + +/// +/// File encoding used to encode the file. +/// +public enum FileEncoding +{ + UTF8, + ANSI, + ASCII, + Unicode, + Other, +} + +#pragma warning restore CS1591 // Self-explanatory \ No newline at end of file diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/ReturnFormat.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/ReturnFormat.cs new file mode 100644 index 0000000..f37af8c --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/ReturnFormat.cs @@ -0,0 +1,13 @@ +namespace Frends.MicrosoftSQL.ExecuteQueryToFile.Enums; + +#pragma warning disable CS1591 // Self-explanatory + +/// +/// Enumeration for output format. +/// +public enum ReturnFormat +{ + CSV, +} + +#pragma warning restore CS1591 // Self-explanatory \ No newline at end of file diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/SqlDataTypes.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/SqlDataTypes.cs new file mode 100644 index 0000000..6e78472 --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/SqlDataTypes.cs @@ -0,0 +1,42 @@ +namespace Frends.MicrosoftSQL.ExecuteQueryToFile.Enums; + +/// +/// SQL Server-specific data type. +/// +public enum SqlDataTypes +{ +#pragma warning disable CS1591 // self explanatory + Auto = -1, + BigInt = 0, + Binary = 1, + Bit = 2, + Char = 3, + DateTime = 4, + Decimal = 5, + Float = 6, + Image = 7, + Int = 8, + Money = 9, + NChar = 10, + NText = 11, + NVarChar = 12, + Real = 13, + UniqueIdentifier = 14, + SmallDateTime = 15, + SmallInt = 16, + SmallMoney = 17, + Text = 18, + Timestamp = 19, + TinyInt = 20, + VarBinary = 21, + VarChar = 22, + Variant = 23, + Xml = 25, + Udt = 29, + Structured = 30, + Date = 31, + Time = 32, + DateTime2 = 33, + DateTimeOffset = 34, +#pragma warning restore CS1591 // self explanatory +} \ No newline at end of file diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.cs new file mode 100644 index 0000000..3e47eab --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.cs @@ -0,0 +1,65 @@ +namespace Frends.MicrosoftSQL.ExecuteQueryToFile; + +using System; +using System.ComponentModel; +using System.Data; +using System.Data.SqlClient; +using System.Threading; +using System.Threading.Tasks; +using Frends.MicrosoftSQL.ExecuteQueryToFile.Definitions; +using Frends.MicrosoftSQL.ExecuteQueryToFile.Enums; + +/// +/// Main class of the Task. +/// +public static class MicrosoftSQL +{ + /// + /// Frends Task for executing Microsoft SQL queries into a file. + /// [Documentation](https://tasks.frends.com/tasks/frends-tasks/Frends.MicrosoftSQL.ExecuteQueryToFile). + /// + /// Input parameters. + /// Options parameters. + /// Cancellation token given by Frends. + /// Object { int EntriesWritten, string Path, string FileName } + public static async Task ExecuteQueryToFile([PropertyTab] Input input, [PropertyTab] Options options, CancellationToken cancellationToken) + { + Result result = new(); + using (var sqlConnection = new SqlConnection(input.ConnectionString)) + { + await sqlConnection.OpenAsync(cancellationToken); + + using var command = sqlConnection.CreateCommand(); + command.CommandTimeout = options.TimeoutSeconds; + command.CommandText = input.Query; + command.CommandType = CommandType.Text; + + if (input.QueryParameters != null) + { + foreach (var parameter in input.QueryParameters) + { + if (parameter.SqlDataType is SqlDataTypes.Auto) + { + command.Parameters.AddWithValue(parameterName: parameter.Name, value: parameter.Value); + } + else + { + var sqlDbType = (SqlDbType)Enum.Parse(typeof(SqlDbType), parameter.SqlDataType.ToString()); + var commandParameter = command.Parameters.Add(parameter.Name, sqlDbType); + commandParameter.Value = parameter.Value; + } + } + } + + switch (options.ReturnFormat) + { + case ReturnFormat.CSV: + var csvWriter = new CsvFileWriter(command, input, options.CsvOptions); + result = await csvWriter.SaveQueryToCSV(cancellationToken); + break; + } + } + + return result; + } +} diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.csproj b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.csproj new file mode 100644 index 0000000..0c69aec --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.csproj @@ -0,0 +1,48 @@ + + + + net6.0 + Latest + 1.0.0 + Frends + Frends + Frends + Frends + Frends + MIT + true + Frends Task for executing Microsoft SQL queries into a file. + https://frends.com/ + https://github.com/FrendsPlatform/Frends.MicrosoftSQL/tree/main/Frends.MicrosoftSQL.ExecuteQueryToFile + + + + + + PreserveNewest + + + + + + <_Parameter1>$(MSBuildProjectName).Tests + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/FrendsTaskMetadata.json b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/FrendsTaskMetadata.json new file mode 100644 index 0000000..9195abd --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/FrendsTaskMetadata.json @@ -0,0 +1,7 @@ +{ + "Tasks": [ + { + "TaskMethod": "Frends.MicrosoftSQL.ExecuteQueryToFile.MicrosoftSQL.ExecuteQueryToFile" + } + ] +} \ No newline at end of file diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/GlobalSuppressions.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/GlobalSuppressions.cs new file mode 100644 index 0000000..bbd0b8e --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/GlobalSuppressions.cs @@ -0,0 +1,12 @@ +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.MicrosoftSQL.ExecuteQueryToFile.Definitions")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1623:Property summary documentation should match accessors", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.MicrosoftSQL.ExecuteQueryToFile.Definitions")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.MicrosoftSQL.ExecuteQueryToFile")] +[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.MicrosoftSQL.ExecuteQueryToFile")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1629:Documentation text should end with a period", Justification = "Following Frends Tasks guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.MicrosoftSQL.ExecuteQueryToFile")] +[assembly: SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Following Frends guidelines", Scope = "type", Target = "~T:Frends.MicrosoftSQL.ExecuteQueryToFile.MicrosoftSQL")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends guidelines")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1602:Enumeration items should be documented", Justification = "Following Frends guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.MicrosoftSQL.ExecuteQueryToFile.Enums")] +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1200:Using directives should be placed correctly", Justification = "Following Frends guidelines")] +[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1503:Braces should not be omitted", Justification = "Following Frends guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.MicrosoftSQL.ExecuteQueryToFile.Definitions")] diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/README.md b/Frends.MicrosoftSQL.ExecuteQueryToFile/README.md new file mode 100644 index 0000000..5a9a438 --- /dev/null +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/README.md @@ -0,0 +1,34 @@ +# Frends.MicrosoftSQL.ExecuteQueryToFile +Frends Task for executing Microsoft SQL queries into a file. + +[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) +[![Build](https://github.com/FrendsPlatform/Frends.MicrosoftSQL/actions/workflows/ExecuteQueryToFile_build_and_test_on_main.yml/badge.svg)](https://github.com/FrendsPlatform/Frends.MicrosoftSQL/actions) +![Coverage](https://app-github-custom-badges.azurewebsites.net/Badge?key=FrendsPlatform/Frends.MicrosoftSQL/Frends.MicrosoftSQL.ExecuteQueryToFile|main) + +## Installing + +You can install the Task via frends UI Task View. + +## Building + +### Clone a copy of the repository + +`git clone https://github.com/FrendsPlatform/Frends.MicrosoftSQL.git` + +### Build the project + +`dotnet build` + +### Run tests + +Run the tests + +`dotnet test` + +### Create a NuGet package + +`dotnet pack --configuration Release` + +### Third party licenses + +StyleCop.Analyzer version (unmodified version 1.1.118) used to analyze code uses Apache-2.0 license, full text and source code can be found in https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/README.md diff --git a/README.md b/README.md index 6859424..1085b67 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Frends Task for MicrosoftSQL operations. - [Frends.MicrosoftSQL.ExecuteQuery](Frends.MicrosoftSQL.ExecuteQuery/README.md) - [Frends.MicrosoftSQL.ExecuteProcedure](Frends.MicrosoftSQL.ExecuteProcedure/README.md) - [Frends.MicrosoftSQL.BatchOperation](Frends.MicrosoftSQL.BatchOperation/README.md) +- [Frends.MicrosoftSQL.ExecuteQueryToFile](Frends.MicrosoftSQL.ExecuteQueryToFile/README.md) # Contributing When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change.