diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..4e4275c2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,295 @@ +# EditorConfig file +root = true + +# General settings for all files +[*] +indent_style = space +spelling_exclusion_path = SpellingExclusions.dic + +# Code files +[*.{cs,csx,vb,vbx}] +end_of_line = lf +indent_size = 4 +insert_final_newline = true +charset = utf-8 + +# XML project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 4 +end_of_line = lf + +# XML config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# JSON files +[*.json] +indent_size = 2 + +# PowerShell files +[*.ps1] +indent_size = 2 + +# Shell scripts +[*.sh] +end_of_line = lf +indent_size = 2 + +########################################## +# C# and VB.NET style settings +[*.{cs,vb}] +########################################## + +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false +dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning + +# Qualification +dotnet_style_qualification_for_field = false:refactoring +dotnet_style_qualification_for_property = false:refactoring +dotnet_style_qualification_for_method = false:refactoring +dotnet_style_qualification_for_event = false:refactoring + +# Predefined type preference +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Modern language features +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion + +# Whitespace +dotnet_style_allow_multiple_blank_lines_experimental = false + +# API analyzer +dotnet_public_api_analyzer.require_api_files = true + +# IDE0055 formatting fix +dotnet_diagnostic.IDE0055.severity = warning + +########################################## +# Naming Rules +########################################## + +# Naming errors as warning +dotnet_diagnostic.IDE1006.severity = warning + +# === Naming Styles === +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +dotnet_naming_style.camel_case_style.capitalization = camel_case + +dotnet_naming_style.underscore_camel_case_style.capitalization = camel_case +dotnet_naming_style.underscore_camel_case_style.required_prefix = _ + +dotnet_naming_style.interface_style.capitalization = pascal_case +dotnet_naming_style.interface_style.required_prefix = I + +dotnet_naming_style.async_method_style.capitalization = pascal_case +dotnet_naming_style.async_method_style.required_suffix = Async + +dotnet_naming_style.underscore_pascalcase.capitalization = pascal_case +dotnet_naming_style.underscore_pascalcase.required_prefix = _ + +# === Symbols === +dotnet_naming_symbols.public_api_symbols.applicable_kinds = class, struct, enum, property, method, event, field, delegate, namespace +dotnet_naming_symbols.public_api_symbols.applicable_accessibilities = public, protected, protected_internal + +dotnet_naming_symbols.protected_fields.applicable_kinds = field +dotnet_naming_symbols.protected_fields.applicable_accessibilities = protected + +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private + +dotnet_naming_symbols.locals_and_parameters.applicable_kinds = local, parameter +dotnet_naming_symbols.locals_and_parameters.applicable_accessibilities = * + +dotnet_naming_symbols.constants.applicable_kinds = field +dotnet_naming_symbols.constants.required_modifiers = const + +dotnet_naming_symbols.interfaces.applicable_kinds = interface +dotnet_naming_symbols.interfaces.applicable_accessibilities = * + +dotnet_naming_symbols.async_methods.applicable_kinds = method +dotnet_naming_symbols.async_methods.required_modifiers = async + +# === Rules === +dotnet_naming_rule.public_api_should_be_pascal_case.symbols = public_api_symbols +dotnet_naming_rule.public_api_should_be_pascal_case.style = pascal_case_style +dotnet_naming_rule.public_api_should_be_pascal_case.severity = warning + +dotnet_naming_rule.protected_fields_should_be_prefixed_with_underscore_and_pascalcase.symbols = protected_fields +dotnet_naming_rule.protected_fields_should_be_prefixed_with_underscore_and_pascalcase.style = underscore_pascalcase +dotnet_naming_rule.protected_fields_should_be_prefixed_with_underscore_and_pascalcase.severity = warning + +dotnet_naming_rule.private_fields_should_be_underscore_camel.symbols = private_fields +dotnet_naming_rule.private_fields_should_be_underscore_camel.style = underscore_camel_case_style +dotnet_naming_rule.private_fields_should_be_underscore_camel.severity = warning + +dotnet_naming_rule.locals_and_parameters_should_be_camel_case.symbols = locals_and_parameters +dotnet_naming_rule.locals_and_parameters_should_be_camel_case.style = camel_case_style +dotnet_naming_rule.locals_and_parameters_should_be_camel_case.severity = warning + +dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants +dotnet_naming_rule.constants_should_be_pascal_case.style = pascal_case_style +dotnet_naming_rule.constants_should_be_pascal_case.severity = warning + +dotnet_naming_rule.interfaces_should_be_prefixed_with_i.symbols = interfaces +dotnet_naming_rule.interfaces_should_be_prefixed_with_i.style = interface_style +dotnet_naming_rule.interfaces_should_be_prefixed_with_i.severity = warning + +dotnet_naming_rule.async_methods_should_end_with_async.symbols = async_methods +dotnet_naming_rule.async_methods_should_end_with_async.style = async_method_style +dotnet_naming_rule.async_methods_should_end_with_async.severity = warning + +# Other style settings +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion +tab_width = 4 + +########################################## +# C# Specific settings +[*.cs] +########################################## + +# Namespaces +csharp_style_namespace_declarations = file_scoped:warning # Less indentation + +# Newlines +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left + +# Spacing +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Expression bodies +csharp_style_expression_bodied_methods = false:none +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_operators = false:none +csharp_style_expression_bodied_properties = true:none +csharp_style_expression_bodied_indexers = true:none +csharp_style_expression_bodied_accessors = true:none + +# Modern language features +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion +csharp_style_prefer_extended_property_pattern = true:suggestion +csharp_style_prefer_init_only_properties = true:suggestion + +# Braces +csharp_prefer_braces = true:silent +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true +dotnet_diagnostic.IDE0011.severity = warning + +# 'var' everywhere +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = true:warning +dotnet_diagnostic.IDE0007.severity = warning + +# XML documentation +dotnet_diagnostic.SA1600.severity = suggestion # Elements must be documented +dotnet_diagnostic.SA1623.severity = suggestion # Property summary must match accessor + +# Code quality +dotnet_diagnostic.IDE0005.severity = error # Unused usings +dotnet_diagnostic.IDE0040.severity = warning # Accessibility modifiers +dotnet_diagnostic.IDE0052.severity = warning # Remove unread private members +dotnet_diagnostic.IDE0059.severity = warning # Unused assignment +dotnet_diagnostic.IDE0055.severity = warning # Formatting issues (spacing, newlines, etc.) +dotnet_diagnostic.IDE0060.severity = warning # Unused parameters +dotnet_diagnostic.CA1012.severity = warning # Abstract types with public ctors +dotnet_diagnostic.CA1822.severity = none # Make member static +dotnet_diagnostic.IDE0032.severity = warning # Use auto-property +dotnet_diagnostic.CA2000.severity = warning # Dispose objects before losing scope +dotnet_diagnostic.CA1802.severity = warning # Use literals where appropriate +dotnet_diagnostic.CA1826.severity = warning # Use predicate in Any() instead of Where().Any() +dotnet_diagnostic.CA1828.severity = warning # Use Count property directly instead of LINQ Count() +dotnet_diagnostic.CA1829.severity = warning # Use Length/Count instead of LINQ Count() for performance +dotnet_diagnostic.CA1858.severity = warning # Avoid redundant type checks like (object)x is string +dotnet_diagnostic.CA1860.severity = warning # Use TryGetValue instead of ContainsKey followed by index +dotnet_diagnostic.CA1868.severity = warning # Use indexing instead of ElementAt() when possible +dotnet_diagnostic.CA1869.severity = warning # Avoid LINQ FirstOrDefault on arrays when index access is better +dotnet_diagnostic.CA1871.severity = warning # Prefer optimized collection initialization patterns +dotnet_diagnostic.CA1502.severity = warning # Avoid excessive complexity + +########################################## +# Experimental Visual Spacing Rules +########################################## + +dotnet_diagnostic.IDE2001.severity = warning +dotnet_diagnostic.IDE2002.severity = warning +dotnet_diagnostic.IDE2004.severity = warning +dotnet_diagnostic.IDE2005.severity = warning +dotnet_diagnostic.IDE2006.severity = warning +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_prefer_system_threading_lock = true:suggestion + +########################################## +# Exceptions by path +########################################## + +[src/{Compilers,ExpressionEvaluator,Scripting}/**Test**/*.{cs,vb}] +dotnet_diagnostic.IDE0060.severity = none + +[src/{Analyzers,CodeStyle,Features,Workspaces,EditorFeatures,VisualStudio}/**/*.{cs,vb}] +# Reserved for future path-specific rules + +[src/{VisualStudio}/**/*.{cs,vb}] +dotnet_code_quality.CA1822.api_surface = private \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..0f5d654d --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +github: jogibear9988 +patreon: jogibear9988 diff --git a/.github/workflows/dotnetpull.yml b/.github/workflows/dotnetpull.yml new file mode 100644 index 00000000..f25eef79 --- /dev/null +++ b/.github/workflows/dotnetpull.yml @@ -0,0 +1,108 @@ +name: .NET Pull Request + +on: + push: + branches: [master] + pull_request: + branches: [master] +jobs: + build: + runs-on: ubuntu-22.04 + services: + sqlserver: + image: mcr.microsoft.com/mssql/server:2019-latest + ports: + - 1433:1433 + env: + SA_PASSWORD: YourStrong@Passw0rd + ACCEPT_EULA: Y + options: >- + --health-cmd "bash -c '- + --health-cmd="pg_isready -U testuser" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + oracle: + image: gvenzl/oracle-free:latest + ports: + - 1521:1521 + env: + ORACLE_PASSWORD: adfkweflajdfglkj + options: >- + --health-cmd healthcheck.sh + --health-interval 10s + --health-timeout 5s + --health-retries 10 + mysql: + image: mysql:8.0 + ports: + - 3306:3306 + env: + MYSQL_ROOT_PASSWORD: rootpass + MYSQL_DATABASE: testdb + MYSQL_USER: testuser + MYSQL_PASSWORD: testpass + options: >- + --health-cmd="mysqladmin ping -h localhost -u root -prootpass" + --health-interval=10s + --health-timeout=5s + --health-retries=10 + steps: + - uses: actions/checkout@v4 + - uses: gvenzl/setup-oracle-sqlcl@v1 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 9.0.x + - name: Install Microsoft GPG apt-key + run: | + wget https://packages.microsoft.com/keys/microsoft.asc -O microsoft.asc + gpg --dearmor microsoft.asc + chmod 644 microsoft.asc.gpg + sudo mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/microsoft.gpg + - name: Add Microsoft SQL Server repo + run: | + echo "deb [arch=amd64] https://packages.microsoft.com/config/ubuntu/22.04/prod jammy main" + sudo apt-get update + - name: Install SQLCMD tools + run: | + sudo ACCEPT_EULA=Y apt-get install -y mssql-tools unixodbc-dev + echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc + source ~/.bashrc + - name: Create SQLServer database + run: | + /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'YourStrong@Passw0rd' -Q "CREATE DATABASE [Whatever];" + - name: Create Oracle user + run: | + sql sys/adfkweflajdfglkj@localhost/FREEPDB1 as sysdba < build - -This will run the nant build in default configuration. You can pass a target to the build.bat to run a specific -target in the default.build file. - -To zip the project into a zip file run build passing a zip argument like so: - -c:\> build zip - -To override any of the build properties copy local.properties-exmple to local.properties and override any of the -property values in the default.build. - -If you have any questions please go to the migrator google group located at: - -http://groups.google.com/group/migratordotnet-devel \ No newline at end of file diff --git a/Migrator.sln b/Migrator.sln deleted file mode 100644 index ba14ba53..00000000 --- a/Migrator.sln +++ /dev/null @@ -1,83 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.23107.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Runners", "Runners", "{1CC77E58-4B1E-4D3F-86EA-5078883434FC}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{9844714F-717A-4C16-97A9-9995BB1B4A8B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{8FF5F3DF-DF83-470C-ADFE-C0FF2B858F0F}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extras", "Extras", "{02B014BC-CC35-466F-A2F4-C5B5AC830653}" - ProjectSection(SolutionItems) = preProject - doc\CHANGES.txt = doc\CHANGES.txt - default.build = default.build - local.properties-example = local.properties-example - src\MigratorDotNet.snk = src\MigratorDotNet.snk - doc\README.txt = doc\README.txt - doc\TODO.txt = doc\TODO.txt - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetProjects.Migrator", "src\Migrator\DotNetProjects.Migrator.csproj", "{1FEE70A4-AAD7-4C60-BE60-3F7DC03A8C4D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetProjects.Migrator.Framework", "src\Migrator.Framework\DotNetProjects.Migrator.Framework.csproj", "{5270F048-E580-486C-B14C-E5B9F6E539D4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetProjects.Migrator.Providers", "src\Migrator.Providers\DotNetProjects.Migrator.Providers.csproj", "{D58C68E4-D789-40F7-9078-C9F587D4363C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Migrator.Console-vs2010", "src\Migrator.Console\Migrator.Console-vs2010.csproj", "{FBE3A83A-D0F8-4D72-AF8D-9EF772569A31}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Migrator.MSBuild-vs2010", "src\Migrator.MSBuild\Migrator.MSBuild-vs2010.csproj", "{A145FFA9-5FE6-4636-93B8-0C110D132BF3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Migrator.NAnt-vs2010", "src\Migrator.NAnt\Migrator.NAnt-vs2010.csproj", "{CDD39DB7-C9C0-4ECA-AD36-1B4D0BF59101}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Migrator.Tests-vs2010", "src\Migrator.Tests\Migrator.Tests-vs2010.csproj", "{882B6A93-67B8-45BF-8636-5796B1B1CBF8}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1FEE70A4-AAD7-4C60-BE60-3F7DC03A8C4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1FEE70A4-AAD7-4C60-BE60-3F7DC03A8C4D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1FEE70A4-AAD7-4C60-BE60-3F7DC03A8C4D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1FEE70A4-AAD7-4C60-BE60-3F7DC03A8C4D}.Release|Any CPU.Build.0 = Release|Any CPU - {5270F048-E580-486C-B14C-E5B9F6E539D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5270F048-E580-486C-B14C-E5B9F6E539D4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5270F048-E580-486C-B14C-E5B9F6E539D4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5270F048-E580-486C-B14C-E5B9F6E539D4}.Release|Any CPU.Build.0 = Release|Any CPU - {D58C68E4-D789-40F7-9078-C9F587D4363C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D58C68E4-D789-40F7-9078-C9F587D4363C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D58C68E4-D789-40F7-9078-C9F587D4363C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D58C68E4-D789-40F7-9078-C9F587D4363C}.Release|Any CPU.Build.0 = Release|Any CPU - {FBE3A83A-D0F8-4D72-AF8D-9EF772569A31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FBE3A83A-D0F8-4D72-AF8D-9EF772569A31}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FBE3A83A-D0F8-4D72-AF8D-9EF772569A31}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FBE3A83A-D0F8-4D72-AF8D-9EF772569A31}.Release|Any CPU.Build.0 = Release|Any CPU - {A145FFA9-5FE6-4636-93B8-0C110D132BF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A145FFA9-5FE6-4636-93B8-0C110D132BF3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A145FFA9-5FE6-4636-93B8-0C110D132BF3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A145FFA9-5FE6-4636-93B8-0C110D132BF3}.Release|Any CPU.Build.0 = Release|Any CPU - {CDD39DB7-C9C0-4ECA-AD36-1B4D0BF59101}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CDD39DB7-C9C0-4ECA-AD36-1B4D0BF59101}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CDD39DB7-C9C0-4ECA-AD36-1B4D0BF59101}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CDD39DB7-C9C0-4ECA-AD36-1B4D0BF59101}.Release|Any CPU.Build.0 = Release|Any CPU - {882B6A93-67B8-45BF-8636-5796B1B1CBF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {882B6A93-67B8-45BF-8636-5796B1B1CBF8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {882B6A93-67B8-45BF-8636-5796B1B1CBF8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {882B6A93-67B8-45BF-8636-5796B1B1CBF8}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {1FEE70A4-AAD7-4C60-BE60-3F7DC03A8C4D} = {9844714F-717A-4C16-97A9-9995BB1B4A8B} - {5270F048-E580-486C-B14C-E5B9F6E539D4} = {9844714F-717A-4C16-97A9-9995BB1B4A8B} - {D58C68E4-D789-40F7-9078-C9F587D4363C} = {9844714F-717A-4C16-97A9-9995BB1B4A8B} - {FBE3A83A-D0F8-4D72-AF8D-9EF772569A31} = {1CC77E58-4B1E-4D3F-86EA-5078883434FC} - {A145FFA9-5FE6-4636-93B8-0C110D132BF3} = {1CC77E58-4B1E-4D3F-86EA-5078883434FC} - {CDD39DB7-C9C0-4ECA-AD36-1B4D0BF59101} = {1CC77E58-4B1E-4D3F-86EA-5078883434FC} - {882B6A93-67B8-45BF-8636-5796B1B1CBF8} = {8FF5F3DF-DF83-470C-ADFE-C0FF2B858F0F} - EndGlobalSection -EndGlobal diff --git a/Migrator.slnx b/Migrator.slnx new file mode 100644 index 00000000..4cddff02 --- /dev/null +++ b/Migrator.slnx @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/README.markdown b/README.md similarity index 51% rename from README.markdown rename to README.md index 96b4516f..385358b3 100644 --- a/README.markdown +++ b/README.md @@ -18,12 +18,56 @@ Introduction This project is a fork of "ye olde trusty" Migrator.Net - the original project can be found [here on google code][1], and has since [moved to github][2] +Usage Example +------------- + +M_001_InitialSchema.cs +```cs +[Migration(1)] +public class M_001_InitialSchema : Migration +{ + public override void Up() + { + Database.AddTable( + "Users", + new Column("Id", DbType.Guid, ColumnProperty.NotNull | ColumnProperty.PrimaryKey), + new Column("CreationDate", DbType.DateTime, ColumnProperty.NotNull), + new Column("ModificationDate", DbType.DateTime, ColumnProperty.NotNull), + new Column("Name", DbType.String, 255), + new Column("Password", DbType.String, 255), + new Column("ExplicitRoles", DbType.String, int.MaxValue)); + } + + public override void Down() + { + } +} +``` + +Code to Apply Migration: +```cs +using (var p = ProviderFactory.Create(ProviderTypes.SQLite, connection, null)) +{ + var migrator = new Migrator(p, typeof(M_001_InitialSchema).Assembly, false)); + + if (migrator.LastAppliedMigrationVersion != null && migrator.LastAppliedMigrationVersion.Value > migrator.AssemblyLastMigrationVersion) + { + throw new Exception("Database has newer Migrations applied then the Software supports"); + } + else + { + migrator.MigrateToLastVersion(); + } +} +``` + What's different in the fork ---------------------------- In this fork the main changes are: * Now targets .Net Framework 4.0 instead of 2.0/3.5. +* NetStandart 2.0 will be supported when released * Support for reserved words. * Support for guid types across all databases. * Utility classes for removing all tables etc. from a database (to support migration integration tests). diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index ea18aa85..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,50 +0,0 @@ -version: 4.0.{build} - -branches: - only: - - master - -assembly_info: - patch: true - file: AssemblyInfo.* - assembly_version: "{version}" - assembly_file_version: "{version}" - assembly_informational_version: "{version}" - -configuration: Release - -before_build: - - nuget restore - -build: - project: Migrator.sln - -after_build: - - ps: .\nuget\pack.ps1 - -test: off - -artifacts: - - path: src\Migrator\bin\Migrator\Release\DotNetProjects.Migrator.dll - name: DotNetProjects.Migrator.dll - - path: src\Migrator\bin\Migrator\Release\DotNetProjects.Migrator.pdb - name: DotNetProjects.Migrator.pdb - - path: src\Migrator\bin\Migrator\Release\DotNetProjects.Migrator.Framework.dll - name: DotNetProjects.Migrator.Framework.dll - - path: src\Migrator\bin\Migrator\Release\DotNetProjects.Migrator.Framework.pdb - name: DotNetProjects.Migrator.Framework.pdb - - path: src\Migrator\bin\Migrator\Release\DotNetProjects.Migrator.Providers.dll - name: DotNetProjects.Migrator.Providers.dll - - path: src\Migrator\bin\Migrator\Release\DotNetProjects.Migrator.Providers.pdb - name: DotNetProjects.Migrator.Providers.pdb - - path: '**\DotNetProjects.Migrator*.nupkg' - -#uncomment to publish to NuGet -deploy: - provider: NuGet - api_key: - secure: OrhpK2cLXXcoWW+hU6xAv3eeKIbATEFbenteoFsi9EfM1yyDof6ZuNKAsA3Vy6vb - artifact: /.*\.nupkg/ - - - \ No newline at end of file diff --git a/build.bat b/build.bat deleted file mode 100644 index b45d5826..00000000 --- a/build.bat +++ /dev/null @@ -1,2 +0,0 @@ -@lib\nant\nant.exe -buildfile:default.build %* -pause \ No newline at end of file diff --git a/contrib/Migrator.Web/Default.aspx b/contrib/Migrator.Web/Default.aspx deleted file mode 100644 index 551d64ea..00000000 --- a/contrib/Migrator.Web/Default.aspx +++ /dev/null @@ -1,61 +0,0 @@ -<%@ Page - Language = "C#" - AutoEventWireup = "false" - Inherits = "Migrator.Web.Default" - ValidateRequest = "false" - EnableSessionState = "false" -%> - - - - - - Migrator.Web - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- Run Migration -
- Current Database Latest Version: - - -
- Migrate To: - - -
- -
- -
- - diff --git a/contrib/Migrator.Web/Default.aspx.cs b/contrib/Migrator.Web/Default.aspx.cs deleted file mode 100644 index aeb846e2..00000000 --- a/contrib/Migrator.Web/Default.aspx.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Configuration; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Reflection; -using System.Web; -using System.Web.SessionState; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.HtmlControls; -using Migrator.Framework; - -namespace Migrator.Web -{ - /// - /// Web form that can be used to run migrations in a web project. - /// It's recommended that you have some security in place. - /// - public class Default : Page - { - - protected Label _LatestVersion; - protected DropDownList _availableVersions; - protected Button _runMigration; - - protected void PageInit(object sender, EventArgs e) - { - } - - protected void PageExit(object sender, EventArgs e) - { - } - - - private void Page_Load(object sender, EventArgs e) - { - if(!IsPostBack) - { - this.BindForm(); - } - } - - private void RunMigration(object sender, EventArgs e) - { - Migrator mig = GetMigrator(); - mig.MigrateTo(int.Parse(this._availableVersions.SelectedValue)); - this.BindForm(); - } - - - protected override void OnInit(EventArgs e) - { - InitializeComponent(); - base.OnInit(e); - } - - private void InitializeComponent() - { - this.Load += new System.EventHandler(Page_Load); - this.Init += new System.EventHandler(PageInit); - this.Unload += new System.EventHandler(PageExit); - - this._runMigration.Click += new EventHandler(RunMigration); - } - - private void BindForm(){ - Migrator mig = GetMigrator(); - List appliedMigrations = mig.AppliedMigrations; - long latestMigration = 0; - if(appliedMigrations.Count > 0) { - latestMigration = appliedMigrations[appliedMigrations.Count - 1]; - } - this._LatestVersion.Text = latestMigration.ToString(); - - List availableMigrations = GetMigrationsList(mig); - this._availableVersions.DataSource = availableMigrations; - this._availableVersions.DataValueField = "ID"; - this._availableVersions.DataTextField = "ClassName"; - this._availableVersions.DataBind(); - } - - private Migrator GetMigrator() - { - Assembly asm = Assembly.LoadFrom(ConfigurationManager.AppSettings["MigrationAsembly"]); - string provider = ConfigurationManager.AppSettings["MigrationProvider"]; - string connectString = ConfigurationManager.AppSettings["ConnectionString"]; - - Migrator migrator = new Migrator(provider, connectString, asm, false); - return migrator; - } - - private List GetMigrationsList(Migrator mig) - { - List migrations = mig.MigrationsTypes; - migrations.Reverse(); - List list = new List(); - List.Enumerator en = migrations.GetEnumerator(); - while(en.MoveNext()){ - MigrationInfo info = new MigrationInfo(en.Current); - list.Add(info); - } - return list; - } - - public class MigrationInfo - { - private Type _type; - - public MigrationInfo(Type type) - { - this._type = type; - } - - public Type MigrationType - { - get{ return _type; } - } - - public long ID - { - get{ return MigrationLoader.GetMigrationVersion(_type); } - } - - public string ClassName - { - get{ return _type.ToString() + " (" + ID + ")"; } - } - } - } -} diff --git a/contrib/Migrator.Web/Global.asax b/contrib/Migrator.Web/Global.asax deleted file mode 100644 index 921e97a5..00000000 --- a/contrib/Migrator.Web/Global.asax +++ /dev/null @@ -1,3 +0,0 @@ -<%@ Application Codebehind="Global.cs" - Inherits="Migrator.Web.Global" -%> diff --git a/contrib/Migrator.Web/Global.asax.cs b/contrib/Migrator.Web/Global.asax.cs deleted file mode 100644 index 8dad9f2f..00000000 --- a/contrib/Migrator.Web/Global.asax.cs +++ /dev/null @@ -1,79 +0,0 @@ - -using System; -using System.Collections; -using System.ComponentModel; -using System.Web; -using System.Web.SessionState; - -namespace Migrator.Web -{ - /// - /// Summary description for Global. - /// - public class Global : HttpApplication - { - #region global - /// - /// Required designer variable. - /// - //private System.ComponentModel.IContainer components = null; - - public Global() - { - InitializeComponent(); - } - - #endregion - - protected void Application_Start(Object sender, EventArgs e) - { - - } - - protected void Session_Start(Object sender, EventArgs e) - { - - } - - protected void Application_BeginRequest(Object sender, EventArgs e) - { - - } - - protected void Application_EndRequest(Object sender, EventArgs e) - { - - } - - protected void Application_AuthenticateRequest(Object sender, EventArgs e) - { - - } - - protected void Application_Error(Object sender, EventArgs e) - { - - } - - protected void Session_End(Object sender, EventArgs e) - { - - } - - protected void Application_End(Object sender, EventArgs e) - { - - } - - #region Web Form Designer generated code - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - - } - #endregion - } -} diff --git a/contrib/Migrator.Web/Migrator.Web.csproj b/contrib/Migrator.Web/Migrator.Web.csproj deleted file mode 100644 index 7920c1a5..00000000 --- a/contrib/Migrator.Web/Migrator.Web.csproj +++ /dev/null @@ -1,56 +0,0 @@ - - - {25EE0010-081D-423F-AEE5-5B83ED235609} - Debug - AnyCPU - Library - Migrator.Web - Migrator.Web - bin\ - - - True - Full - False - True - DEBUG;TRACE - - - False - None - True - False - TRACE - - - - - - - - - - - - - - Default.aspx - - - Global.asax - - - - - - - - {5270F048-E580-486C-B14C-E5B9F6E539D4} - Migrator.Framework - - - {1FEE70A4-AAD7-4C60-BE60-3F7DC03A8C4D} - Migrator - - - diff --git a/contrib/Migrator.Web/Web.config b/contrib/Migrator.Web/Web.config deleted file mode 100644 index 58fa70bc..00000000 --- a/contrib/Migrator.Web/Web.config +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contrib/README.txt b/contrib/README.txt deleted file mode 100644 index 73104b14..00000000 --- a/contrib/README.txt +++ /dev/null @@ -1,6 +0,0 @@ -= Migrator DotNet Contrib -Projects and pieces contributed to Migrator DotNet that are outside of the core concepts or usecases. -Still interesting and might be useful. - -If they're useful enough, tell us to roll them into the core! - diff --git a/contrib/build.bat b/contrib/build.bat deleted file mode 100644 index a1eb951e..00000000 --- a/contrib/build.bat +++ /dev/null @@ -1,2 +0,0 @@ -@..\lib\nant\nant.exe -buildfile:default.build %* -pause diff --git a/contrib/default.build b/contrib/default.build deleted file mode 100644 index 8873a8c4..00000000 --- a/contrib/default.build +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/default.build b/default.build deleted file mode 100644 index 9bb91825..00000000 --- a/default.build +++ /dev/null @@ -1,263 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/CHANGES.txt b/doc/CHANGES.txt deleted file mode 100644 index ebd4d597..00000000 --- a/doc/CHANGES.txt +++ /dev/null @@ -1,77 +0,0 @@ -# Change history - -## Version 0.8.0 -- Implemented specific support for SQL Server 2005 (MAX parameter basically) -- Implemented specific support for SQL Server CE (Thanks Gustavo Ringel) - -- Changed the parameter names of AddForeignKey to try and make them easier to understand -- Made Version for MigrationAttribute non-optional -- Changed the Migration number to a long value so datestamps could be used. -- Added a SqlScriptFileLogger to log all of the SQL changes to a file (Thanks carl.lotz) - - Supported in MSBuild and NAnt tasks -- Implemented Fluent interface using SchemaBuilder - -- Breaking Change!: Changed the storage of versions applied to one that keeps all the versions. This - will help deal with branches during development. (Thanks evonzee) - You will need to update your SchemaInfo table by inserting a row for all previously applied versions. - -- Added contribs for extra interesting code - - Added Migration.Web to show how to run migrations from a Web App directly. - -## Version 0.7.0 -- Geoff's major refactorings fork re-merged back into the trunk -- Improved the build process allowing developers to override things in local.properties -- Added a packaging task to generate a zip file for a build -- SQL Server Default value now supports non-quoted functions and NULL -- Compound Primary Key bug was unsetting NotNull from all the columns has been fixed -- Added a Visual Studio Template that can be installed to help create new Migrations -- Various Small bug fixes - - Logger set after Migrations loaded in NAnt/MSBuild tasks - - Patch for wrong string format in SQLTransformationProvider - - One of the TranformationProvider.GenerateForeignKey methods ignores constraint - - No warning is issued if there are no migrations found - - Migration.InitializeOnce outputs to console - -## Version 0.6.0 -- Better API documentation -- Support declaring compound primary keys in the AddTable methods. -- Add support for Renaming Tables and Columns -- Add support for adding and removing Unique Constraints and Check Constraints -- Add support for changing a column definition -- Support compiling the migrations on the fly - -- Breaking Change!: Change the Insert and Update methods to separate the column names from the column values. -- Breaking Change!: Reversed the columns to have the table name first on ConstraintExists, PrimaryKeyExists and - RemovePrimaryKey - -## Version 0.5.0 -Forked the project to fix a bunch of issues. -- Major refactoring - - Breaking Change!: Changing to DBTypes instead of .NET Types for specifying column types - - Separated out Framework and Providers DLLs - - Made the Providers more of a Template model so they have to do less work and are more - declarative in nature. -- Fixed SQL Server Provider -- Added support for SQLite -- Added "Multi-DB" support for DB Specific SQL code - - Database["ProviderName"].ExecuteSQL() will only run if you are running against "ProviderName" -- Much more Unit Testing - -See http://code.macournoyer.com/migrator for further info - -## Version 0.2.0 -- Added support for char type on SQL Server by Luke Melia & Daniel Berlinger -- Fix some issues with SQL Server 2005 pointed out by Luke Melia & Daniel Berlinger -- Added migrate NAnt task -- Added basic schema dumper functionnality for MySql. -- Restructured project tree -- Applied patch from Tanner Burson to fix first run problem with SchemaInfo table creation - -## Version 0.1.0 -- Renamed "RemoveConstraint" to "RemoveForeignKey". We need to add Unique constraint support, but it's not in here yet. -- Merged most of the provider unit test code to a base class. -- Changed the hard dependencies on the ADO.NET providers to be a reflection-based load, just like NHibernate. -- Changed the MySQL provider "RemoveForeignKey" method to call two SQL calls to the DB before the constraint would actually be deleted. This is the wierd piece, and I am not sure if it's just my OS or version of MySQL that needs this. -- Added a few more assertions to the provider unit tests just to be sure the expectations are being met. -- Changed the build file to handle different platforms, since the Npgsql driver is so platform-specific. - diff --git a/doc/README.txt b/doc/README.txt deleted file mode 100644 index fd3b5615..00000000 --- a/doc/README.txt +++ /dev/null @@ -1,103 +0,0 @@ -= Migrator DotNet -Database Migrations implemented in .NET. -Supports rolling up and rolling back of migrations. - -A way to integrate database change management into your regular development and automation processes. -The migrations themselves are implemented in code and can be mostly done in a database independent way. - -Licensed under MPL 1.1 : http://www.mozilla.org/MPL/ - -== Supported Database -* MySQL (5.0, 5.1) -* PostgreSQL -* SQLite (tested on Mono) -* SQL Server (2000, 2005) -* SQL Server CE (3.5) - -== Untested Databases but in there -* Oracle -* Firebird -* Informix -* DB2 -* Ingres - -== Supported Modes -* MSBuild Task -* NAnt Task -* Console Application - - -= Development - -== Compiling -To build from source: - nant build - -== Testing -To run tests: - nant test - -You should have a database installed and setup: -* MySql -* SQL Server -* Oracle -* PostgreSQL -* or you can use SQLite with no setup -You can Test on each engine or change those by changing the 'exclude' properties in a nant build -file called 'local.properties'. To change the database connection strings see config\app.config. You -can make your own local version called 'local.config' to override these - -== SQL Server CE -To use SQL Server CE, you will need the proper tools installed. The current DLL that we are testing -against is the 3.5 version. -As of this writing you can download the installer for the SQL CE Runtime at: -http://www.microsoft.com/downloads/details.aspx?&FamilyID=7849b34f-67ab-481f-a5a5-4990597b0297&DisplayLang=en - -We have not confirmed if this will build on Mono yet. But it almost definitely won't run because SQL CE uses PInvoke -internally. - -= Usage - -1. Add bin/Migrator.Framework.dll to you project references - - All of the other DLLs are only needed for actually running the migrations. -2. Create a class for your migration like: - using Migrator.Framework; - [Migration(1)] - public class MyMigration : Migration - { - public override void Up() - { - // Create stuff - } - public override void Down() - { - // Remove the same stuff - } - } - -3. Compile your migrations and run the console (Migrator.Console.exe) or use the migrator - NAnt or MSBuild tasks: - - NAnt: - - - - - - - MSBuild: - - $(MSBuildProjectDirectory)\migrator - - - - - - - diff --git a/doc/TODO.txt b/doc/TODO.txt deleted file mode 100644 index 0926d759..00000000 --- a/doc/TODO.txt +++ /dev/null @@ -1,13 +0,0 @@ -Near Term: - -* Think about modeling a Table object as well as the Column object -* Think of a DSL to configure what the SQL syntax for a specific implementation looks like? -* Separate out Providers into separate DLLs and load them like plugins? - -Future: -* Look into using something like NHibernate to create our schema changes -* Look into directly supporting other kinds of SQL objects - * Stored Procedures - * User Defined Functions - * Would modeling these things as objects help? - diff --git a/doc/example/example-msbuild.proj b/doc/example/example-msbuild.proj deleted file mode 100644 index 34908ae5..00000000 --- a/doc/example/example-msbuild.proj +++ /dev/null @@ -1,57 +0,0 @@ - - - - $(MSBuildProjectDirectory)\..\..\build - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/example/example-nant.build b/doc/example/example-nant.build deleted file mode 100644 index 04d8ba80..00000000 --- a/doc/example/example-nant.build +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/doc/example/migrations/001_AddAddressTable.cs b/doc/example/migrations/001_AddAddressTable.cs deleted file mode 100644 index 9cbe18f0..00000000 --- a/doc/example/migrations/001_AddAddressTable.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Migrator.Framework; -using System.Data; - -[Migration(1)] -public class AddAddressTable : Migration -{ - override public void Up() - { - Database.AddTable("Address", - new Column("id", DbType.Int32, ColumnProperty.PrimaryKey), - new Column("street", DbType.String, 50), - new Column("city", DbType.String, 50), - new Column("state", DbType.StringFixedLength, 2), - new Column("postal_code", DbType.String, 10) - ); - } - override public void Down() - { - Database.RemoveTable("Address"); - } -} diff --git a/doc/example/migrations/002_AddAddressColumns.cs b/doc/example/migrations/002_AddAddressColumns.cs deleted file mode 100644 index d5cca3b1..00000000 --- a/doc/example/migrations/002_AddAddressColumns.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Migrator.Framework; -using System.Data; - -[Migration(2)] -public class AddAddressColumns : Migration -{ - override public void Up() - { - Database.AddColumn("Address", new Column("street2", DbType.String, 50)); - Database.AddColumn("Address", new Column("street3", DbType.String, 50)); - } - - override public void Down() - { - Database.RemoveColumn("Address", "street2"); - Database.RemoveColumn("Address", "street3"); - } -} \ No newline at end of file diff --git a/doc/example/migrations/003_AddPersonTable.cs b/doc/example/migrations/003_AddPersonTable.cs deleted file mode 100644 index 1b35b2e0..00000000 --- a/doc/example/migrations/003_AddPersonTable.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Migrator.Framework; -using System.Data; - -[Migration(3)] -public class AddPersonTable : Migration -{ - override public void Up() - { - Database.AddTable("Person", - new Column("id", DbType.Int32, ColumnProperty.PrimaryKey), - new Column("first_name", DbType.String, 50), - new Column("last_name", DbType.String, 50), - new Column("address_id", DbType.Int32, ColumnProperty.Unsigned) - ); - Database.AddForeignKey("FK_PERSON_ADDRESS", "Person", "address_id", "Address", "id"); - } - override public void Down() - { - Database.RemoveTable("Person"); - } -} diff --git a/doc/extras/VS.NET Template/Migration.zip b/doc/extras/VS.NET Template/Migration.zip deleted file mode 100644 index 231acabf..00000000 Binary files a/doc/extras/VS.NET Template/Migration.zip and /dev/null differ diff --git a/doc/extras/VS.NET Template/README.txt b/doc/extras/VS.NET Template/README.txt deleted file mode 100644 index 92dad2e3..00000000 --- a/doc/extras/VS.NET Template/README.txt +++ /dev/null @@ -1,2 +0,0 @@ -To install this, put the zip file in: -C:\Documents and Settings\\My Documents\Visual Studio 2008\Templates\ItemTemplates diff --git a/lib/NAnt.Core.dll b/lib/NAnt.Core.dll deleted file mode 100644 index 1083a424..00000000 Binary files a/lib/NAnt.Core.dll and /dev/null differ diff --git a/lib/NCover/CoverLib.dll b/lib/NCover/CoverLib.dll deleted file mode 100644 index e038b330..00000000 Binary files a/lib/NCover/CoverLib.dll and /dev/null differ diff --git a/lib/NCover/Coverage.xsl b/lib/NCover/Coverage.xsl deleted file mode 100644 index 164ff148..00000000 --- a/lib/NCover/Coverage.xsl +++ /dev/null @@ -1,339 +0,0 @@ - - - - - - - NCover Code Coverage Report - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- javascript:toggle( - ) - - - - - - - - -
- - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
VisitsLineEndColumnEndDocument
- - - exdatacell - hldatacell - datacell - - - - --- - - - - - - - - - - - - -
-
- - - - - - - - - - - - - - -
- javascript:toggle( - ) - - - - - - - - -
- - - -
- - - - - - - -
- - -
-
- - - - - - -
- - - -
- - - - - - - - -
- - -

Modules summary

- - - - - - - - -
-
-
-
- - - - - - -
- - - -
- - - - - - - - -
- - -

- NCover Code Coverage Report -

- - - - - - -
- Expand - | - Collapse -
-
-
- -
- Top -
- - - - - - - - - - - - - - - - - -
Excluded - - - - - - - - - -
-
-
\ No newline at end of file diff --git a/lib/NCover/Explorer/ActiproEULA.html b/lib/NCover/Explorer/ActiproEULA.html deleted file mode 100644 index 1f05a9df..00000000 --- a/lib/NCover/Explorer/ActiproEULA.html +++ /dev/null @@ -1,287 +0,0 @@ - - - End-User License Agreement (EULA) - - - - - -

END-USER LICENSE AGREEMENT FOR ACTIPRO SOFTWARE LLC SOFTWARE

- -
-IMPORTANT - READ CAREFULLY: This Actipro Software LLC ("Actipro") End-User License Agreement ("EULA") -is a legal agreement between you (“Licensee”), a developer of software applications, and Actipro for the Actipro software product -accompanying this EULA, which includes computer software and may include associated source code, media, printed materials, -and "on-line" or electronic documentation ("SOFTWARE PRODUCT"). By installing, copying, or otherwise using the SOFTWARE PRODUCT, -you agree to be bound by the terms of this EULA. If you do not agree to the terms of this EULA, do not install, use, distribute -in any manner, or replicate in any manner, any part, file or portion of the SOFTWARE PRODUCT. -
- -

-The SOFTWARE PRODUCT is protected by copyright laws and international copyright treaties, as well as other intellectual property laws and treaties. The SOFTWARE PRODUCT is licensed, not sold. -

- -

-The Licensee is considered to be an authorized licensee (“Authorized”) if the Licensee has legitimately obtained a registered license for the SOFTWARE PRODUCT from Actipro or an authorized Actipro reseller. -

- -

-RIGOROUS ENFORCEMENT OF INTELLECTUAL PROPERTY RIGHTS. -If the licensed right of use for this SOFTWARE PRODUCT is purchased by the Licensee with any intent to reverse engineer, -decompile, create derivative works, and the exploitation or unauthorized transfer of, any Actipro intellectual property and -trade secrets, to include any exposed methods or source code where provided, no licensed right of use shall exist, and any products -created as a result shall be judged illegal by definition of all applicable law. Any sale or resale of intellectual property or -created derivatives so obtained will be prosecuted to the fullest extent of all local, federal and international law. -

- -

-GRANT OF LICENSE. This EULA, if legally executed as defined herein, licenses and so grants the Licensee the following rights: -

- -

-Evaluation. If the downloaded SOFTWARE PRODUCT is designated as an Evaluation Release (“Evaluation Release”), -the Licensee is granted a license for a period of only fifteen (15) days after installation of the Evaluation Release -of the SOFTWARE PRODUCT ("Evaluation Period"). After the Evaluation Period, the Licensee must either: -

    -
  1. Delete the SOFTWARE PRODUCT and all related files from ALL computers onto which it was installed or copied, or
  2. -
  3. Contact Actipro or one of its authorized resellers to purchase the SOFTWARE PRODUCT.
  4. -
-

- -

-The Licensee may use the Evaluation Release of the SOFTWARE PRODUCT for evaluation purposes only. -The Licensee may not distribute ANY of the files, in any form or manner, provided with the Evaluation Release of the -SOFTWARE PRODUCT to ANY PARTIES. -

- -

-Development. Actipro grants the Licensee the non-exclusive license to install and use multiple copies of the -SOFTWARE PRODUCT or any prior version for the sole purpose of developing any number of end user applications that -operate in conjunction with the SOFTWARE PRODUCT. If the Licensee is not Authorized, the Licensee may not use the -SOFTWARE PRODUCT beyond the Evaluation Period. -

- -

-If the Licensee has purchased a single developer license (“Single Developer License”), the Licensee is Authorized -to use the SOFTWARE PRODUCT indefinitely beyond the Evaluation Period. A Single Developer License for the SOFTWARE PRODUCT -may not be shared or used concurrently by more than one individual developer. In a project that uses the SOFTWARE PRODUCT, -each individual developer on the project requires a separate Single Developer License, regardless of whether they directly -use the component or not. Single Developer Licenses may also be obtained in team discount packs. -

- -

-If the Licensee has purchased a site license (“Site License”), each of the developers at a single physical location is -considered Authorized according to the terms and conditions of the Single Developer License. Each additional physical -location requires an additional Site License to be considered Authorized. -

- -

-If the Licensee has purchased an enterprise license (“Enterprise License”), all developers in the Licensee's organization, -regardless of location, are considered Authorized according to the terms and conditions of the Single Developer License. -

- -

-If the Licensee has purchased a blueprint license (“Blueprint License”), each of the Authorized developers for the SOFTWARE PRODUCT is -considered Authorized to access source code for the SOFTWARE PRODUCT ("Source Code"). -The Blueprint License must be purchased at the same time as a Site License or Enterprise License. -Source Code may exclude Actipro proprietary licensing code. -The sale of Blueprint Licenses is considered final and neither the SOFTWARE PRODUCT nor Source Code may be returned under any circumstances. -

- -

-Duplication and Distribution. The SOFTWARE PRODUCT may include certain files ("Redistributables") intended for distribution -by the Licensee to the users of programs the Licensee creates. Redistributables include, for example, those files identified in -printed or electronic documentation as redistributable files, or those files pre-selected for deployment by an install utility -provided with the SOFTWARE PRODUCT (if any). In any event, the Redistributables for the SOFTWARE PRODUCT are only those files -specifically designated as such by Actipro. -

- -

-Subject to all of the terms and conditions in this EULA, if the Licensee is Authorized, Actipro grants the Licensee the non-exclusive, -royalty-free license to duplicate the Redistributables and to distribute them solely in conjunction with software products -developed by the Licensee that use them. The Licensee may not supply any means by which end users could incorporate the -SOFTWARE PRODUCT or portions thereof into their own products. -

- -

-Source Code. If the Licensee has purchased a Blueprint License and is Authorized, the Licensee is provided Source Code -for the SOFTWARE PRODUCT. The following stipulations and restrictions apply to Source Code: -

    -
  1. Source Code shall be considered as part the SOFTWARE PRODUCT and all requirements stated above still apply, - meaning that developers at a separate site from the one which purchased the Blueprint License are NOT able to work on - any project created that uses the Source Code, unless that site has also purchased a Blueprint License. - The only exception is when an Enterprise License has been purchased along with the Blueprint License, - in which case the Source Code may be used by developers at any site.
  2. -
  3. Actipro grants the Licensee the non-exclusive license to view and modify the Source Code for the sole purposes of education and troubleshooting. - If the Licensee troubleshoots the Source Code, the Licensee may compile the corrected source code and use and distribute the - resulting object code solely as a replacement for the corresponding Redistributables the Source Code compiles into.
  4. -
  5. The Licensee may NOT distribute or sell the Source Code, or portions or modifications or derivative works thereof, to any third party - not Authorized by the Licensee’s Blueprint License(s), without explicit permission by Actipro.
  6. -
  7. The Licensee may not compete against Actipro by repackaging, recompiling, or renaming the SOFTWARE PRODUCT for which the - Licensee purchased Source Code. Any derivative works based on the Source Code are illegal to be created or sold if they compete - in any way with the SOFTWARE PRODUCT or other Actipro products.
  8. -
  9. Any object code that is created by using the Source Code or derivative code based on the Source Code must be obfuscated.
  10. -
  11. Any object code that is created by using the Source Code or derivative code based on the Source Code may NOT bear "ActiproSoftware" - or the name of the SOFTWARE PRODUCT in the object code assembly name.
  12. -
  13. All Source Code must be kept in its proper "ActiproSoftware" namespace.
  14. -
  15. Actipro shall retain all rights, title and interest in and to all corrections, modifications and derivative works of the Source Code - created by the Licensee, including all copyrights subsisting therein, to the extent such corrections, modifications or - derivative works contain copyrightable code or expression derived from the Source Code.
  16. -
  17. The Licensee acknowledges that the Source Code contains valuable and proprietary trade secrets of Actipro, and agrees to expend - every effort to insure its confidentiality.
  18. -
  19. Source Code may be obtained by coordinating with Actipro during the support period for the Blueprint License, typically one year - in duration, starting on the date of purchase of the Blueprint License.
  20. -
-

- -

-Storage/Network Use. The Licensee may also store or install a copy of the SOFTWARE PRODUCT on a storage device, -such as a network server, used only to install or run the SOFTWARE PRODUCT on the the Licensee’s other computers over an internal network; -however, the Licensee must acquire and dedicate a Single Developer License for each separate individual developer who wishes to use -the SOFTWARE PRODUCT. -

- -

-DESCRIPTION OF OTHER RIGHTS AND LIMITATIONS. -

- -

-Not for Resale Software. If the SOFTWARE PRODUCT is labeled and provided as "Not for Resale" or "NFR", then, notwithstanding -other sections of this EULA, the Licensee may not resell, distribute, or otherwise transfer for value or benefit in any manner, -the SOFTWARE PRODUCT or any derivative work using the SOFTWARE PRODUCT. The Licensee may not transfer, rent, lease, lend, copy, -modify, translate, sublicense, time-share or electronically transmit the SOFTWARE PRODUCT, media or documentation. -This also applies to any and all intermediate files, source code, and compiled executables. -

- -

-Limitations on Reverse Engineering, Decompilation, and Disassembly. The Licensee may not reverse engineer, decompile, -create derivative works, modify, translate, or disassemble the SOFTWARE PRODUCT, and only to the extent that such activity is -expressly permitted by applicable law notwithstanding this limitation. The Licensee agrees to take all reasonable, legal and -appropriate measures to prohibit the illegal dissemination of the SOFTWARE PRODUCT or any of its constituent parts and redistributables -to the fullest extent of all applicable local, US Codes and International Laws and Treaties regarding anti-circumvention, including -but not limited to, the Geneva and Berne World Intellectual Property Organization (WIPO) Diplomatic Conferences. -

- -

-Rental. The Licensee may not rent, lease, or lend the SOFTWARE PRODUCT. -

- -

-Separation of Components, Their Constituent Parts and Redistributables. The SOFTWARE PRODUCT is licensed as a single product. -The SOFTWARE PRODUCT and its constituent parts and any provided redistributables may not be reverse engineered, decompiled, disassembled, -nor placed for distribution, sale, or resale as individual creations by the Licensee or any individual not expressly given -such permission by Actipro. The provision of Source Code, if included with the SOFTWARE PRODUCT, does not constitute transfer of any -legal rights to such code, and resale or distribution of all or any portion of all Source Code and intellectual property will be prosecuted -to the fullest extent of all applicable local, federal and international laws. All Actipro libraries, Source Code, Redistributables and -other files remain Actipro's exclusive property. The Licensee may not distribute any files, except those that Actipro has expressly -designated as Redistributable. -

- -

-Installation and Use. The license granted in this EULA for the Licensee to create his/her own compiled programs and distribute -the Licensee’s programs and the Redistributables (if any), is subject to all of the following conditions: -

    -
  1. All copies of the programs the Licensee creates must bear a valid copyright notice, either their own or the Actipro copyright - notice that appears on the SOFTWARE PRODUCT.
  2. -
  3. The Licensee may not remove or alter any Actipro copyright, trademark or other proprietary rights notice contained in any portion - of Actipro libraries, source code, Redistributables or other files that bear such a notice.
  4. -
  5. Actipro provides no warranty at all to any person, and the Licensee will remain solely responsible to anyone receiving the - Licensee’s programs for support, service, upgrades, or technical or other assistance, and such recipients will have no right - to contact Actipro for such services or assistance.
  6. -
  7. The Licensee will indemnify and hold Actipro, its related companies and its suppliers, harmless from and against any claims or - liabilities arising out of the use, reproduction or distribution of the Licensee’s programs.
  8. -
  9. The Licensee’s programs containing the SOFTWARE PRODUCT must be written using a licensed, registered copy of the SOFTWARE PRODUCT.
  10. -
  11. The Licensee’s programs must add primary and substantial functionality, and may not be merely a set or subset of any of the libraries, - Source Code, Redistributables or other files of the SOFTWARE PRODUCT.
  12. -
  13. The Licensee may not use Actipro's or any of its suppliers' names, logos, or trademarks to market the Licensee’s programs, - unless expressly given such permission by Actipro.
  14. -
-

- -

-Support Services. Actipro may provide the Licensee with support services related to the SOFTWARE PRODUCT ("Support Services"). -Use of Support Services is governed by Actipro policies and programs described in the user manual, in on-line documentation and/or other -Actipro provided materials. Any supplemental software code provided to the Licensee as part of the Support Services shall be considered -part of the SOFTWARE PRODUCT and subject to the terms and conditions of this EULA. With respect to technical information the Licensee -provides to Actipro as part of the Support Services, Actipro may use such information for its business purposes, including for product -support and development. -

- -

-Software Transfer. The Licensee may NOT permanently or temporarily transfer ANY of the Licensee’s rights under this EULA to any -individual or entity. Regardless of any modifications which the Licensee makes and regardless of how the Licensee might compile, link, -and/or package the Licensee’s programs, under no circumstances may the libraries, redistributables, and/or other files of the -SOFTWARE PRODUCT (including any portions thereof) be used for developing programs by anyone other than the Licensee. Only the Licensee -as the licensed end user has the right to use the libraries, redistributables, or other files of the SOFTWARE PRODUCT (or any portions thereof) -for developing programs created with the SOFTWARE PRODUCT. In particular, the Licensee may not share copies of the Source Code or -Redistributables with other co-developers. -

- -

-Termination. Without prejudice to any other rights or remedies, Actipro will terminate this EULA upon the Licensee’s failure to -comply with all the terms and conditions of this EULA. In such event, the Licensee must destroy all copies of the SOFTWARE PRODUCT and -all of its component parts including any related documentation, and must remove ANY and ALL use of such technology immediately from any -applications using technology contained in the SOFTWARE PRODUCT developed by the Licensee, whether in native, altered or compiled state. -

- -

-UPGRADES. If the SOFTWARE PRODUCT is labeled as an upgrade, the Licensee must be properly licensed to use the SOFTWARE PRODUCT -identified by Actipro as being eligible for the upgrade in order to use the SOFTWARE PRODUCT. A SOFTWARE PRODUCT labeled as an upgrade -replaces and/or supplements the SOFTWARE PRODUCT that formed the basis for the Licensee’s eligibility for the upgrade, and together -constitute a single SOFTWARE PRODUCT. The Licensee may use the resulting upgraded SOFTWARE PRODUCT only in accordance with all the -terms of this EULA. -

- -

-COPYRIGHT. All title and copyrights in and to the SOFTWARE PRODUCT (including but not limited to any images, demos, source code, -intermediate files, packages, photographs, animations, video, audio, music, text, and "applets" incorporated into the SOFTWARE PRODUCT), -the accompanying printed materials, and any copies of the SOFTWARE PRODUCT are owned by Actipro or its subsidiaries. -The SOFTWARE PRODUCT is protected by copyright laws and international treaty provisions. Therefore, the Licensee must treat the -SOFTWARE PRODUCT like any other copyrighted material except that the Licensee may install the SOFTWARE PRODUCT for use by the Licensee. -The Licensee may not copy any printed materials accompanying the SOFTWARE PRODUCT. -

- -

-GENERAL PROVISIONS. This EULA may only be modified in writing signed by the Licensee and an authorized officer of Actipro. -If any provision of this EULA is found void or unenforceable, the remainder will remain valid and enforceable according to its terms. -

- -

-MISCELLANEOUS. If the Licensee acquired this product in the United States, this EULA is governed by the laws of the State of Ohio. -

- -

-If this SOFTWARE PRODUCT was acquired outside the United States, then the Licensee, agrees and ascends to the adherence to all -applicable international treaties regarding copyright and intellectual property rights which shall also apply. -In addition, the Licensee agrees that any local law(s) to the benefit and protection of Actipro ownership of, and interest in, -its intellectual property and right of recovery for damages thereto will also apply. -

- -

-Should you have any questions concerning this EULA, or if you desire to contact Actipro for any reason, please contact us via our -support web pages at http://www.actiprosoftware.com. -

- -

-NO WARRANTIES. ACTIPRO EXPRESSLY DISCLAIMS ANY WARRANTY FOR THE SOFTWARE PRODUCT. THE PRODUCT AND ANY RELATED DOCUMENTATION IS PROVIDED -"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, OR NONINFRINGEMENT. THE ENTIRE RISK ARISING OUT OF USE OR PERFORMANCE OF THE PRODUCT REMAINS WITH THE LICENSEE. -

- -

-LIMITATION OF LIABILITY. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL ACTIPRO OR ITS SUPPLIERS BE LIABLE -FOR ANY SPECIAL, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF -BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR -INABILITY TO USE THE SOFTWARE PRODUCT OR THE PROVISION OF OR FAILURE TO PROVIDE SUPPORT SERVICES, EVEN IF ACTIPRO HAS BEEN ADVISED OF -THE POSSIBILITY OF SUCH DAMAGES. -

- -

-Copyright (c) 2002-2007 Actipro Software LLC. All rights reserved. -

- - - diff --git a/lib/NCover/Explorer/ActiproSoftware.Shared.Net11.dll b/lib/NCover/Explorer/ActiproSoftware.Shared.Net11.dll deleted file mode 100644 index 93f32bac..00000000 Binary files a/lib/NCover/Explorer/ActiproSoftware.Shared.Net11.dll and /dev/null differ diff --git a/lib/NCover/Explorer/ActiproSoftware.SyntaxEditor.Net11.dll b/lib/NCover/Explorer/ActiproSoftware.SyntaxEditor.Net11.dll deleted file mode 100644 index eafbdae3..00000000 Binary files a/lib/NCover/Explorer/ActiproSoftware.SyntaxEditor.Net11.dll and /dev/null differ diff --git a/lib/NCover/Explorer/ActiproSoftware.WinUICore.Net11.dll b/lib/NCover/Explorer/ActiproSoftware.WinUICore.Net11.dll deleted file mode 100644 index 85ae9ee3..00000000 Binary files a/lib/NCover/Explorer/ActiproSoftware.WinUICore.Net11.dll and /dev/null differ diff --git a/lib/NCover/Explorer/CommandBars.dll b/lib/NCover/Explorer/CommandBars.dll deleted file mode 100644 index 0c31bd50..00000000 Binary files a/lib/NCover/Explorer/CommandBars.dll and /dev/null differ diff --git a/lib/NCover/Explorer/ConsoleConfig.xsd b/lib/NCover/Explorer/ConsoleConfig.xsd deleted file mode 100644 index 533f659b..00000000 --- a/lib/NCover/Explorer/ConsoleConfig.xsd +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/lib/NCover/Explorer/ConsoleExample.config b/lib/NCover/Explorer/ConsoleExample.config deleted file mode 100644 index efd7f911..00000000 --- a/lib/NCover/Explorer/ConsoleExample.config +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - Example.Project - - - - *.Coverage.xml - - - - ModuleClassSummary - - C:\MyCoverageReport.html - - C:\MyCoverageReport.xml - - - - - - - - - - - - - - - - - - - - - - - - None - - - Name - - - - - Assembly - - *.Tests - false - - - - Namespace - *.My* - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/lib/NCover/Explorer/CoverageReport.xsl b/lib/NCover/Explorer/CoverageReport.xsl deleted file mode 100644 index e8b4fc90..00000000 --- a/lib/NCover/Explorer/CoverageReport.xsl +++ /dev/null @@ -1,468 +0,0 @@ - - - - - - - - Generated by NCoverExplorer (see http://www.kiwidude.com/blog/) - NCoverExplorer - Merged Report - - - - - - - -
- - -
- - - - - - - - - - - - - Unvisited Functions - Unvisited SeqPts - - - - - Function Coverage - Coverage - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

NCoverExplorer Coverage Report -   

- - - - - - - - - - - - - - - -
Report generated on: at 
NCoverExplorer version:
Filtering / Sorting: / 
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Project Statistics:Files: NCLOC:
Classes:  
Functions:Unvisited:
Seq Pts:Unvisited:
-
- - -
- - - - - - - - -   - - - Project - Acceptable - - - - - - - -   - - - - - - - - - - - - - - - - - - - - - - True - - - - - - - - - -   - - - Modules - Acceptable - - - - - - - - - - - True - - - - - - - - - -   - - - Module - Acceptable - Unvisited SeqPts - Coverage - - - - - - - - - - Namespaces - - - - - - - - - - - - - - - - - - - - -   - - - Module - Acceptable - - - - - - - - - - - - Namespace / Classes - - - - - - - - - padding-left:20px;font-weight:bold - - - - - - - - - padding-left:30px - 160 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - padding-left:20px - 180 - - - - - - - - - - - - - - - - - - -   - - - Excluded From Coverage Results - All Code Within - - - - - - - - - - - - -   - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - . - - - . - - graphBarSatisfactory - graphBarNotVisited - - - - .
-
-
\ No newline at end of file diff --git a/lib/NCover/Explorer/LicencePersonal.rtf b/lib/NCover/Explorer/LicencePersonal.rtf deleted file mode 100644 index ebf26543..00000000 --- a/lib/NCover/Explorer/LicencePersonal.rtf +++ /dev/null @@ -1,334 +0,0 @@ -{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff0\deff0\stshfdbch0\stshfloch0\stshfhich0\stshfbi0\deflang2057\deflangfe2057{\fonttbl{\f0\froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f1\fswiss\fcharset0\fprq2{\*\panose 020b0604020202020204}Arial;} -{\f36\fswiss\fcharset0\fprq2{\*\panose 020b0604030504040204}Tahoma;}{\f37\fswiss\fcharset0\fprq2{\*\panose 00000000000000000000}Verdana;}{\f38\froman\fcharset238\fprq2 Times New Roman CE;}{\f39\froman\fcharset204\fprq2 Times New Roman Cyr;} -{\f41\froman\fcharset161\fprq2 Times New Roman Greek;}{\f42\froman\fcharset162\fprq2 Times New Roman Tur;}{\f43\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f44\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} -{\f45\froman\fcharset186\fprq2 Times New Roman Baltic;}{\f46\froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f48\fswiss\fcharset238\fprq2 Arial CE;}{\f49\fswiss\fcharset204\fprq2 Arial Cyr;}{\f51\fswiss\fcharset161\fprq2 Arial Greek;} -{\f52\fswiss\fcharset162\fprq2 Arial Tur;}{\f53\fbidi \fswiss\fcharset177\fprq2 Arial (Hebrew);}{\f54\fbidi \fswiss\fcharset178\fprq2 Arial (Arabic);}{\f55\fswiss\fcharset186\fprq2 Arial Baltic;}{\f56\fswiss\fcharset163\fprq2 Arial (Vietnamese);} -{\f398\fswiss\fcharset238\fprq2 Tahoma CE;}{\f399\fswiss\fcharset204\fprq2 Tahoma Cyr;}{\f401\fswiss\fcharset161\fprq2 Tahoma Greek;}{\f402\fswiss\fcharset162\fprq2 Tahoma Tur;}{\f403\fbidi \fswiss\fcharset177\fprq2 Tahoma (Hebrew);} -{\f404\fbidi \fswiss\fcharset178\fprq2 Tahoma (Arabic);}{\f405\fswiss\fcharset186\fprq2 Tahoma Baltic;}{\f406\fswiss\fcharset163\fprq2 Tahoma (Vietnamese);}{\f407\fswiss\fcharset222\fprq2 Tahoma (Thai);}{\f408\fswiss\fcharset238\fprq2 Verdana CE;} -{\f409\fswiss\fcharset204\fprq2 Verdana Cyr;}{\f411\fswiss\fcharset161\fprq2 Verdana Greek;}{\f412\fswiss\fcharset162\fprq2 Verdana Tur;}{\f415\fswiss\fcharset186\fprq2 Verdana Baltic;}{\f416\fswiss\fcharset163\fprq2 Verdana (Vietnamese);}} -{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0; -\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}{\stylesheet{\qj \li0\ri0\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 -\af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext0 Normal;}{\s1\qc \li0\ri0\keepn\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af0\afs20\alang1025 -\ltrch\fcs0 \b\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 heading 1;}{\s2\qj \li0\ri0\sa120\keepn\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af0\afs20\alang1025 -\ltrch\fcs0 \b\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \sbasedon0 \snext0 heading 2;}{\s7\ql \fi-720\li720\ri0\keepn\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel6\adjustright\rin0\lin720\itap0 \rtlch\fcs1 -\ab\af1\afs24\alang1025 \ltrch\fcs0 \b\f1\fs24\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \sbasedon0 \snext0 heading 7;}{\*\cs10 \additive \ssemihidden Default Paragraph Font;}{\* -\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tscellwidthfts0\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv -\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs20 \ltrch\fcs0 \fs20\lang1024\langfe1024\cgrid\langnp1024\langfenp1024 \snext11 \ssemihidden Normal Table;}{ -\s15\qj \li0\ri0\sa220\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext15 \ssemihidden footnote text;}{\*\cs16 -\additive \rtlch\fcs1 \af0 \ltrch\fcs0 \super \sbasedon10 \ssemihidden footnote reference;}{ -\s17\qj \li2880\ri0\widctlpar\phpg\posxc\posyb\absh-1980\absw7920\dxfrtext180\dfrmtxtx180\dfrmtxty0\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin2880\itap0 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 -\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext17 envelope address;}{\s18\qj \li720\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin720\itap0 \rtlch\fcs1 \af0\afs16\alang1025 \ltrch\fcs0 -\scaps\fs16\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext18 envelope return;}{\s19\qj \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs20\alang1025 -\ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext19 header;}{\s20\qj \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 -\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext20 footer;}{\*\cs21 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \sbasedon10 page number;}{\s22\qj \li0\ri0\sa240\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 -\rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon23 \snext22 Num Continue;}{\s23\qj \li0\ri0\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 -\af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext23 Body Text;}{\s24\qj \li0\ri0\sa120\widctlpar\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls3\outlinelevel0\adjustright\rin0\lin0\itap0 -\rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext22 Legal2_L1;}{\s25\qj \fi720\li0\ri0\sa120\widctlpar -\jclisttab\tx1080\wrapdefault\aspalpha\aspnum\faauto\ls3\ilvl1\outlinelevel1\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon24 \snext22 Legal2_L2;}{ -\s26\qj \fi1440\li0\ri0\sa120\widctlpar\jclisttab\tx1800\wrapdefault\aspalpha\aspnum\faauto\ls3\ilvl2\outlinelevel2\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 -\sbasedon25 \snext22 Legal2_L3;}{\s27\qj \fi2160\li0\ri0\sa120\widctlpar\jclisttab\tx2880\wrapdefault\aspalpha\aspnum\faauto\ls3\ilvl3\outlinelevel3\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 -\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon26 \snext22 Legal2_L4;}{\s28\qj \fi2880\li0\ri0\sa120\widctlpar\jclisttab\tx3600\wrapdefault\aspalpha\aspnum\faauto\ls3\ilvl4\outlinelevel4\adjustright\rin0\lin0\itap0 \rtlch\fcs1 -\af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon27 \snext22 Legal2_L5;}{\s29\qj \fi3600\li0\ri0\sa120\widctlpar -\jclisttab\tx4320\wrapdefault\aspalpha\aspnum\faauto\ls3\ilvl5\outlinelevel5\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon28 \snext22 Legal2_L6;}{ -\s30\qj \fi4320\li0\ri0\sa120\widctlpar\jclisttab\tx5040\wrapdefault\aspalpha\aspnum\faauto\ls3\ilvl6\outlinelevel6\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 -\sbasedon29 \snext22 Legal2_L7;}{\s31\qj \fi720\li0\ri0\sa120\widctlpar\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls3\ilvl7\outlinelevel7\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 -\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon30 \snext22 Legal2_L8;}{\s32\qj \fi1440\li0\ri0\sa120\widctlpar\jclisttab\tx2160\wrapdefault\aspalpha\aspnum\faauto\ls3\ilvl8\outlinelevel8\adjustright\rin0\lin0\itap0 \rtlch\fcs1 -\af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon31 \snext22 Legal2_L9;}{\*\cs33 \additive \rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\caps\cf2 \sbasedon10 zzmpTCEntryL1;}{\*\cs34 \additive \rtlch\fcs1 \ab\af0 -\ltrch\fcs0 \b\cf2 \sbasedon10 zzmpTCEntryL2;}{\*\cs35 \additive \rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\cf2 \sbasedon10 zzmpTCEntryL3;}{\*\cs36 \additive \rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\cf2 \sbasedon10 zzmpTCEntryL4;}{\*\cs37 \additive \rtlch\fcs1 \af0 -\ltrch\fcs0 \cf2 \sbasedon10 zzmpTCEntryL5;}{\*\cs38 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf2 \sbasedon10 zzmpTCEntryL6;}{\*\cs39 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf2 \sbasedon10 zzmpTCEntryL7;}{\*\cs40 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 -\cf2 \sbasedon10 zzmpTCEntryL8;}{\*\cs41 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf2 \sbasedon10 zzmpTCEntryL9;}{\s42\ql \li0\ri0\sb100\sa100\sbauto1\saauto1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 -\af0\afs24\alang1025 \ltrch\fcs0 \fs24\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \sbasedon0 \snext42 Normal (Web);}{\*\cs43 \additive \rtlch\fcs1 \af37\afs17 \ltrch\fcs0 \f37\fs17 \sbasedon10 bodytext1;}{\*\cs44 \additive \rtlch\fcs1 \af0 -\ltrch\fcs0 \ul\cf2 \sbasedon10 Hyperlink;}{\s45\qj \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af1\afs20\alang1025 \ltrch\fcs0 \b\f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 -\sbasedon0 \snext45 Body Text 2;}{\*\cs46 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf12 \sbasedon10 FollowedHyperlink;}{\*\cs47 \additive \rtlch\fcs1 \af1 \ltrch\fcs0 \f1\cf0 \sbasedon10 text1;}{\*\cs48 \additive \rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b -\sbasedon10 \styrsid684987 Strong;}{\s49\qj \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af36\afs16\alang1025 \ltrch\fcs0 \f36\fs16\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 -\sbasedon0 \snext49 \slink50 \ssemihidden \styrsid4745031 Balloon Text;}{\*\cs50 \additive \rtlch\fcs1 \af36\afs16 \ltrch\fcs0 \f36\fs16\lang1033\langfe1033\langnp1033\langfenp1033 \sbasedon10 \slink49 \slocked \styrsid4745031 Balloon Text Char;}} -{\*\latentstyles\lsdstimax156\lsdlockeddef0{\lsdlockedexcept Normal;heading 1;heading 2;heading 3;heading 4;heading 5;heading 6;heading 7;heading 8;heading 9;toc 1;toc 2;toc 3;toc 4;toc 5;toc 6;toc 7;toc 8;toc 9;caption;Title;Subtitle;Strong;Emphasis;}} -{\*\listtable{\list\listtemplateid1586421402{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\'00;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-360\li360 -\jclisttab\tx360\lin360 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03\'00.\'01;}{\levelnumbers\'01\'03;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-360\li1260\jclisttab\tx1260\lin1260 -}{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'05\'00.\'01.\'02;}{\levelnumbers\'01\'03\'05;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-720\li2520\jclisttab\tx2520\lin2520 }{\listlevel -\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'07\'00.\'01.\'02.\'03;}{\levelnumbers\'01\'03\'05\'07;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-720\li3420\jclisttab\tx3420\lin3420 }{\listlevel -\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'04);}{\levelnumbers\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 \loch\af1\hich\af1\dbch\af0\fbias0 \fi-1080\li4680\jclisttab\tx4680\lin4680 }{\listlevel -\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'0b\'00.\'01.\'02.\'03.\'04.\'05;}{\levelnumbers\'01\'03\'05\'07\'09\'0b;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1080\li5580 -\jclisttab\tx5580\lin5580 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'0d\'00.\'01.\'02.\'03.\'04.\'05.\'06;}{\levelnumbers\'01\'03\'05\'07\'09\'0b\'0d;}\rtlch\fcs1 \af0 \ltrch\fcs0 -\fbias0 \fi-1440\li6840\jclisttab\tx6840\lin6840 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'0f\'00.\'01.\'02.\'03.\'04.\'05.\'06.\'07;}{\levelnumbers -\'01\'03\'05\'07\'09\'0b\'0d\'0f;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1440\li7740\jclisttab\tx7740\lin7740 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\'11\'00.\'01.\'02.\'03.\'04.\'05.\'06.\'07.\'08;}{\levelnumbers\'01\'03\'05\'07\'09\'0b\'0d\'0f\'11;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1800\li9000\jclisttab\tx9000\lin9000 }{\listname Legal22;}\listid187183174}{\list\listtemplateid-1494708814 -{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af1\afs22 \ltrch\fcs0 -\b\i0\strike0\outl0\shad0\embo0\impr0\caps\v0\f1\fs22\ulnone\cf0\nosupersub\animtext0\striked0\fbias0 \s24\jclisttab\tx360 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\'03\'00.\'01;}{\levelnumbers\'01\'03;}\rtlch\fcs1 \ab0\ai0\af1\afs22 \ltrch\fcs0 \b0\i0\strike0\outl0\shad0\embo0\impr0\caps0\v0\f1\fs22\ulnone\cf0\nosupersub\animtext0\striked0\fbias0 \s25\fi720\jclisttab\tx1080 }{\listlevel\levelnfc4\levelnfcn4 -\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'02);}{\levelnumbers\'02;}\rtlch\fcs1 \ab0\ai0\af0\afs20 \ltrch\fcs0 -\b0\i0\strike0\outl0\shad0\embo0\impr0\caps0\v0\f0\fs20\ulnone\cf0\nosupersub\animtext0\striked0\fbias0 \s26\fi1440\jclisttab\tx1800 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\'03(\'03);}{\levelnumbers\'02;}\rtlch\fcs1 \ab0\ai0\af0\afs20 \ltrch\fcs0 \b0\i0\strike0\outl0\shad0\embo0\impr0\caps0\v0\f0\fs20\ulnone\cf0\nosupersub\animtext0\striked0\fbias0 \s27\fi2160\jclisttab\tx2880 }{\listlevel\levelnfc0\levelnfcn0\leveljc0 -\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'04);}{\levelnumbers\'02;}\rtlch\fcs1 \ab0\ai0\af0\afs24 \ltrch\fcs0 -\b0\i0\strike0\outl0\shad0\embo0\impr0\scaps0\caps0\v0\f0\fs24\ulnone\cf0\nosupersub\animtext0\striked0\fbias0 \s28\fi2880\jclisttab\tx3600 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af0\afs24 \ltrch\fcs0 \b0\i0\strike0\outl0\shad0\embo0\impr0\scaps0\caps0\v0\f0\fs24\ulnone\cf0\nosupersub\animtext0\striked0\fbias0 \s29\fi3600\jclisttab\tx4320 }{\listlevel\levelnfc2\levelnfcn2 -\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af0\afs24 \ltrch\fcs0 -\b0\i0\strike0\outl0\shad0\embo0\impr0\scaps0\caps0\v0\f0\fs24\ulnone\cf0\nosupersub\animtext0\striked0\fbias0 \s30\fi4320\jclisttab\tx5040 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\'03(\'07);}{\levelnumbers\'02;}\rtlch\fcs1 \ab0\ai0\af0\afs24 \ltrch\fcs0 \b0\i0\strike0\outl0\shad0\embo0\impr0\scaps0\caps0\v0\f0\fs24\ulnone\cf0\nosupersub\animtext0\striked0\fbias0 \s31\fi720\jclisttab\tx1440 }{\listlevel\levelnfc2\levelnfcn2 -\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'08);}{\levelnumbers\'02;}\rtlch\fcs1 \ab0\ai0\af0\afs24 \ltrch\fcs0 -\b0\i0\strike0\outl0\shad0\embo0\impr0\scaps0\caps0\v0\f0\fs24\ulnone\cf0\nosupersub\animtext0\striked0\fbias0 \s32\fi1440\jclisttab\tx2160 }{\listname Legal2;}\listid589778925}{\list\listtemplateid1095382408{\listlevel\levelnfc0\levelnfcn0\leveljc0 -\leveljcn0\levelfollow0\levelstartat3\levelspace0\levelindent0{\leveltext\'01\'00;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-405\li405\jclisttab\tx405\lin405 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0 -\levelstartat2\levelspace0\levelindent0{\leveltext\'03\'00.\'01;}{\levelnumbers\'01\'03;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-405\li688\jclisttab\tx688\lin688 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 -\levelindent0{\leveltext\'05\'00.\'01.\'02;}{\levelnumbers\'01\'03\'05;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-720\li1286\jclisttab\tx1286\lin1286 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 -{\leveltext\'07\'00.\'01.\'02.\'03;}{\levelnumbers\'01\'03\'05\'07;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-720\li1569\jclisttab\tx1569\lin1569 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 -{\leveltext\'09\'00.\'01.\'02.\'03.\'04;}{\levelnumbers\'01\'03\'05\'07\'09;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-720\li1852\jclisttab\tx1852\lin1852 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 -\levelindent0{\leveltext\'0b\'00.\'01.\'02.\'03.\'04.\'05;}{\levelnumbers\'01\'03\'05\'07\'09\'0b;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1080\li2495\jclisttab\tx2495\lin2495 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0 -\levelstartat1\levelspace0\levelindent0{\leveltext\'0d\'00.\'01.\'02.\'03.\'04.\'05.\'06;}{\levelnumbers\'01\'03\'05\'07\'09\'0b\'0d;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1080\li2778\jclisttab\tx2778\lin2778 }{\listlevel\levelnfc0\levelnfcn0\leveljc0 -\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'0f\'00.\'01.\'02.\'03.\'04.\'05.\'06.\'07;}{\levelnumbers\'01\'03\'05\'07\'09\'0b\'0d\'0f;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1440\li3421\jclisttab\tx3421\lin3421 } -{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'11\'00.\'01.\'02.\'03.\'04.\'05.\'06.\'07.\'08;}{\levelnumbers\'01\'03\'05\'07\'09\'0b\'0d\'0f\'11;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 -\fi-1440\li3704\jclisttab\tx3704\lin3704 }{\listname ;}\listid899175523}{\list\listtemplateid-53593918{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat4\levelspace0\levelindent0{\leveltext\'01\'00;}{\levelnumbers\'01;} -\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\fbias0 \fi-360\li360\jclisttab\tx360\lin360 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03\'00.\'01;}{\levelnumbers\'01\'03;}\rtlch\fcs1 \af0 -\ltrch\fcs0 \fbias0 \fi-360\li360\jclisttab\tx360\lin360 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'05\'00.\'01.\'02;}{\levelnumbers\'01\'03\'05;}\rtlch\fcs1 \af0 \ltrch\fcs0 -\fbias0 \fi-720\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'07\'00.\'01.\'02.\'03;}{\levelnumbers\'01\'03\'05\'07;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 -\fi-720\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'09\'00.\'01.\'02.\'03.\'04;}{\levelnumbers\'01\'03\'05\'07\'09;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 -\fi-720\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'0b\'00.\'01.\'02.\'03.\'04.\'05;}{\levelnumbers\'01\'03\'05\'07\'09\'0b;}\rtlch\fcs1 \af0 -\ltrch\fcs0 \fbias0 \fi-1080\li1080\jclisttab\tx1080\lin1080 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'0d\'00.\'01.\'02.\'03.\'04.\'05.\'06;}{\levelnumbers -\'01\'03\'05\'07\'09\'0b\'0d;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1080\li1080\jclisttab\tx1080\lin1080 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\'0f\'00.\'01.\'02.\'03.\'04.\'05.\'06.\'07;}{\levelnumbers\'01\'03\'05\'07\'09\'0b\'0d\'0f;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1440\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1 -\levelspace0\levelindent0{\leveltext\'11\'00.\'01.\'02.\'03.\'04.\'05.\'06.\'07.\'08;}{\levelnumbers\'01\'03\'05\'07\'09\'0b\'0d\'0f\'11;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1440\li1440\jclisttab\tx1440\lin1440 }{\listname ;}\listid1303119334} -{\list\listtemplateid-1282876056{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat6\levelspace0\levelindent0{\leveltext\'01\'00;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\fbias0 \fi-360\li360\jclisttab\tx360\lin360 -}{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03\'00.\'01;}{\levelnumbers\'01\'03;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-360\li360\jclisttab\tx360\lin360 }{\listlevel\levelnfc0 -\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'05\'00.\'01.\'02;}{\levelnumbers\'01\'03\'05;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-720\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc0\levelnfcn0 -\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'07\'00.\'01.\'02.\'03;}{\levelnumbers\'01\'03\'05\'07;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-720\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc0\levelnfcn0\leveljc0 -\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'09\'00.\'01.\'02.\'03.\'04;}{\levelnumbers\'01\'03\'05\'07\'09;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-720\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc0\levelnfcn0\leveljc0 -\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'0b\'00.\'01.\'02.\'03.\'04.\'05;}{\levelnumbers\'01\'03\'05\'07\'09\'0b;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1080\li1080\jclisttab\tx1080\lin1080 }{\listlevel\levelnfc0 -\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'0d\'00.\'01.\'02.\'03.\'04.\'05.\'06;}{\levelnumbers\'01\'03\'05\'07\'09\'0b\'0d;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1080\li1080\jclisttab\tx1080\lin1080 -}{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'0f\'00.\'01.\'02.\'03.\'04.\'05.\'06.\'07;}{\levelnumbers\'01\'03\'05\'07\'09\'0b\'0d\'0f;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 -\fi-1440\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'11\'00.\'01.\'02.\'03.\'04.\'05.\'06.\'07.\'08;}{\levelnumbers -\'01\'03\'05\'07\'09\'0b\'0d\'0f\'11;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-1440\li1440\jclisttab\tx1440\lin1440 }{\listname ;}\listid1717701764}}{\*\listoverridetable{\listoverride\listid589778925\listoverridecount0\ls1}{\listoverride\listid899175523 -\listoverridecount0\ls2}{\listoverride\listid589778925\listoverridecount0\ls3}{\listoverride\listid1303119334\listoverridecount0\ls4}{\listoverride\listid1717701764\listoverridecount0\ls5}}{\*\rsidtbl \rsid11936\rsid88914\rsid265398\rsid684987\rsid1250516 -\rsid1846192\rsid2128574\rsid2506696\rsid3227523\rsid3348545\rsid3741615\rsid3868789\rsid3882300\rsid4082071\rsid4201573\rsid4354079\rsid4739009\rsid4745031\rsid5123650\rsid5189883\rsid5206122\rsid5249949\rsid5272592\rsid5397688\rsid5708036\rsid5796271 -\rsid5834546\rsid5907843\rsid5924216\rsid6182972\rsid6253249\rsid6776828\rsid6902420\rsid7296646\rsid7562558\rsid7621291\rsid7692188\rsid7735196\rsid7879489\rsid8065525\rsid8198028\rsid8260678\rsid8284163\rsid8537681\rsid8550639\rsid8985796\rsid9054116 -\rsid9119910\rsid9445259\rsid9722996\rsid9767071\rsid9795825\rsid9837062\rsid9900246\rsid9908984\rsid9969717\rsid10094441\rsid10252196\rsid10296700\rsid10425897\rsid10430804\rsid10703085\rsid10712577\rsid10769984\rsid10892103\rsid11489578\rsid11875457 -\rsid11883158\rsid12134897\rsid12517589\rsid12536041\rsid12598188\rsid12654351\rsid13001579\rsid13334799\rsid13654851\rsid13966861\rsid13969160\rsid14229932\rsid14902293\rsid14902778\rsid15223633\rsid15497150\rsid16004039\rsid16137090\rsid16201318 -\rsid16217207\rsid16322833\rsid16322878\rsid16406037}{\*\generator Microsoft Word 11.0.8134;}{\info{\title iKNOWLEDGE, INC}{\author Palmer & Dodge LLP}{\operator Grant Drake}{\creatim\yr2007\mo1\dy28\hr18\min29}{\revtim\yr2007\mo7\dy22\hr20} -{\printim\yr2006\mo5\dy31\hr11\min1}{\version5}{\edmins35}{\nofpages4}{\nofwords1407}{\nofchars8024}{\*\company Palmer & Dodge LLP}{\nofcharsws9413}{\vern24611}{\*\password 00000000}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/word -ml}{\xmlns2 urn:schemas-microsoft-com:office:smarttags}}\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect -\widowctrl\ftnbj\aenddoc\donotembedsysfont0\donotembedlingdata1\grfdocevents0\validatexml0\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors0\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\hyphcaps0\formshade\horzdoc\dgmargin -\dghspace100\dgvspace180\dghorigin1440\dgvorigin1440\dghshow0\dgvshow0\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\nolnhtadjtbl\nojkernpunct\rsidroot3741615 \fet0{\*\wgrffmtfilter 013f}\ilfomacatclnup0{\*\docvar {Document}{DOCUMENT}} -{\*\docvar {zzmpFixedCurrentTOCScheme}{Legal2}}{\*\docvar {zzmpFixedCurScheme}{Legal2}}{\*\ftnsep \ltrpar \pard\plain \ltrpar\qj \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs20\alang1025 -\ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid16322878 \chftnsep -\par }}{\*\ftnsepc \ltrpar \pard\plain \ltrpar\qj \li0\ri0\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af0 -\ltrch\fcs0 \insrsid16322878 \chftnsep -\par (continued...) -\par }}{\*\ftncn \ltrpar \pard\plain \ltrpar\qr \li0\ri0\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af0 -\ltrch\fcs0 \insrsid16322878 (continued...) -\par }}{\*\aftnsep \ltrpar \pard\plain \ltrpar\qj \li0\ri0\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af0 -\ltrch\fcs0 \insrsid16322878 \chftnsep -\par }}{\*\aftnsepc \ltrpar \pard\plain \ltrpar\qj \li0\ri0\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af0 -\ltrch\fcs0 \insrsid16322878 \chftnsepc -\par }}\ltrpar \sectd \ltrsect\binfsxn261\binsxn261\psz1\sbknone\linex0\footery432\endnhere\sectlinegrid272\sectdefaultcl\sectrsid3868789\sftnbj {\footerr \ltrpar \pard\plain \ltrpar\s20\qc \li0\ri0\widctlpar -\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid3868789 -}{\field{\*\fldinst { -\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \cs21\f1\fs22\insrsid3868789\charrsid16004039 PAGE }}{\fldrslt {\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \cs21\f1\fs22\lang1024\langfe1024\noproof\insrsid1846192 1}}}\sectd \linex0\endnhere\sectdefaultcl\sftnbj {\rtlch\fcs1 -\af0 \ltrch\fcs0 \insrsid3868789 - -\par }}{\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}} -{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8 -\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar -\s2\qc \li0\ri0\sa120\keepn\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin0\itap0\pararsid684987 \rtlch\fcs1 \ab\af0\afs20\alang1025 \ltrch\fcs0 \b\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {\rtlch\fcs1 -\af1\afs22 \ltrch\fcs0 \f1\fs22\ul\insrsid15497150 KIWI}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\ul\insrsid1846192 NOVA }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\ul\insrsid9767071\charrsid9767071 LTD -\par }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\ul\insrsid5272592 PERSONAL}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\ul\insrsid13001579 }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\ul\insrsid9767071 LICENCE}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\ul\insrsid9767071\charrsid9767071 AND SUPPORT AGREEMENT}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\ul\insrsid13966861\charrsid9767071 -\par }\pard\plain \ltrpar\qj \li0\ri0\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid9767071 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af1\afs22 -\ltrch\fcs0 \f1\fs22\lang2057\langfe1033\langnp2057\insrsid9767071\charrsid9767071 -\par }\pard\plain \ltrpar\s45\qj \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid14229932 \rtlch\fcs1 \ab\af1\afs20\alang1025 \ltrch\fcs0 \b\f1\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 -\af1\afs22 \ltrch\fcs0 \fs22\insrsid9767071\charrsid9767071 NOTICE TO USER: PLEASE READ THIS }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid4739009 AGREEMENT}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid9767071\charrsid9767071 CAREFULLY. }{ -\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid12598188 BY CLICKING \'93I ACCEPT\'94 AND/OR }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid9767071\charrsid9767071 BY DOWNLOADING AND/OR USING ALL OR ANY PORTION OF THE SOFTWARE }{\rtlch\fcs1 \af1\afs22 -\ltrch\fcs0 \fs22\insrsid12598188 YOU }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid9969717\charrsid9767071 (\'93LICEN}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid9969717 S}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid9969717\charrsid9767071 -EE\'94)}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid9969717 }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid12598188 ACCEPT}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid9767071\charrsid9767071 THE FOLLOWING TERMS FROM }{\rtlch\fcs1 -\af1\afs22 \ltrch\fcs0 \fs22\insrsid1846192 KIWINOVA }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid9767071\charrsid9767071 LTD OF }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid1846192 24 AEGEAN APARTMENTS}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\fs22\insrsid9767071\charrsid9767071 ,}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid1846192 19 WESTERN GATEWAY,}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid9767071\charrsid9767071 LONDON }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\fs22\insrsid15497150 E16 1}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid1846192 AR}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid9767071\charrsid9767071 (\'93}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid1846192 KIWINOVA}{\rtlch\fcs1 -\af1\afs22 \ltrch\fcs0 \fs22\insrsid12598188 \'94). }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid11489578 YOU}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid12598188 AGREE TO BE BOUND}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\fs22\insrsid9767071\charrsid9767071 BY ALL THE TERMS AND CONDITIONS OF THIS }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid4739009 AGREEMENT}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid9767071\charrsid9767071 -. YOU AGREE THAT IT IS ENFORCEABLE AS IF IT WERE A WRITTEN NEGOTIATED }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid4739009 AGREEMENT}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid9969717 SIGNED BY}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\fs22\insrsid16406037 YOU}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid9767071\charrsid9767071 . IF }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid9969717 YOU}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid9767071\charrsid9767071 - DO NOT AGREE TO THE TERMS OF THIS }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid4739009 AGREEMENT}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid9767071\charrsid9767071 DO NOT }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid11489578 CLICK \'93 -I ACCEPT\'94 AND DO NOT DOWNLOAD OR }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid9767071\charrsid9767071 USE THE SOFTWARE. }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid14229932\charrsid9767071 -\par }\pard \ltrpar\s45\qj \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 {\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \fs22\insrsid12598188\charrsid9767071 -\par {\listtext\pard\plain\ltrpar \s24 \rtlch\fcs1 \ab\af1\afs22 \ltrch\fcs0 \b\caps\f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid13966861\charrsid9767071 \hich\af1\dbch\af0\loch\f1 1.\tab}}\pard\plain \ltrpar -\s24\qj \fi-567\li567\ri0\sa120\keepn\widctlpar\jclisttab\tx567\wrapdefault\aspalpha\aspnum\faauto\ls3\outlinelevel0\adjustright\rin0\lin567\itap0\pararsid6776828 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 -\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \ab\af1\afs22 \ltrch\fcs0 \b\f1\fs22\insrsid13966861\charrsid9767071 DEFINITIONS}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 -\par {\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid14902293 \hich\af1\dbch\af0\loch\f1 1.1\tab}}\pard\plain \ltrpar\s25\qj \fi-567\li567\ri0\sa120\widctlpar -\jclisttab\tx567\wrapdefault\aspalpha\aspnum\faauto\ls1\ilvl1\outlinelevel1\adjustright\rin0\lin567\itap0\pararsid5189883 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af1\afs22 -\ltrch\fcs0 \f1\fs22\insrsid14902293 \'93Agreement\'94 means this Licence and Support Agreement. -\par {\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid13966861\charrsid9767071 \hich\af1\dbch\af0\loch\f1 1.2\tab}}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid13966861\charrsid9767071 \'93Documentation\'94 means the electronic user information }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid14902778\charrsid9767071 supplied }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid13966861\charrsid9767071 with the Software}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10252196 . -\par {\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid10252196\charrsid9767071 \hich\af1\dbch\af0\loch\f1 1.3\tab}}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid10252196\charrsid9767071 \'93Effective Date\'94 means the date }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid16406037 on which the Licensee accepts this Agreement}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid7562558 . -\par {\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid10252196\charrsid9767071 \hich\af1\dbch\af0\loch\f1 1.4\tab}}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid10252196\charrsid9767071 \'93Minimum Requirements\'94 means a min}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid12598188 imum technical specification of}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10252196\charrsid9767071 - the PC or laptop on which the Software is used which is required to enable the Software to function}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid12598188 , as set out in the Documentation}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid10252196\charrsid9767071 .}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10252196 -\par {\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid12598188\charrsid9767071 \hich\af1\dbch\af0\loch\f1 1.5\tab}}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid12598188\charrsid9767071 \'93Software\'94 means the }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid12598188 object code form }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid12598188\charrsid9767071 of }{\rtlch\fcs1 \af1\afs22 -\ltrch\fcs0 \f1\fs22\insrsid12598188 the }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843 personal}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid12598188 version of the software product entitled Testdriven.net}{\rtlch\fcs1 \af1\afs22 -\ltrch\fcs0 \f1\fs22\insrsid12598188\charrsid9767071 .}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid12598188 -\par }\pard\plain \ltrpar\s22\qj \li0\ri0\sa240\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid5907843 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af0 -\ltrch\fcs0 \insrsid5907843\charrsid5907843 -\par {\listtext\pard\plain\ltrpar \s24 \rtlch\fcs1 \ab\af1\afs22 \ltrch\fcs0 \b\caps\f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid13966861\charrsid9767071 \hich\af1\dbch\af0\loch\f1 2.\tab}}\pard\plain \ltrpar -\s24\qj \li0\ri0\sa120\keepn\widctlpar\jclisttab\tx567\wrapdefault\aspalpha\aspnum\faauto\ls3\outlinelevel0\adjustright\rin0\lin0\itap0\pararsid6776828 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 { -\rtlch\fcs1 \ab\af1\afs22 \ltrch\fcs0 \b\f1\fs22\insrsid13966861\charrsid9767071 GRANT OF RIGHTS; RESTRICTIONS -\par {\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid13966861\charrsid9767071 \hich\af1\dbch\af0\loch\f1 2.1\tab}}\pard\plain \ltrpar\s25\qj \fi-567\li567\ri0\sa120\widctlpar -\jclisttab\tx567\wrapdefault\aspalpha\aspnum\faauto\ls1\ilvl1\outlinelevel1\adjustright\rin0\lin567\itap0\pararsid5189883 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af1\afs22 -\ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 Subject to all the terms and conditions of this }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid14902293 Agreement}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 , }{ -\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid1846192 KiwiNova}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 hereby grants Licensee a}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid12598188 perpetual}{ -\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 , worldwide, none}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid9767071 xclusive, nontransferable licenc}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid13966861\charrsid9767071 e to install and use the Software on }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid265398 one}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 PC or laptop for}{\rtlch\fcs1 -\af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid265398 Licensee\rquote s}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 own use only. This licen}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid9767071 c}{\rtlch\fcs1 \af1\afs22 -\ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 e is in respect of use of the Software by the Licensee only and no subsidiaries or holding company of the Licensee may use the Software. -\par {\*\bkmkstart OEMS_OBLIGATIONS}{\*\bkmkstart OEM_SYSTEM_SUPPORT}{\*\bkmkstart TECHNICAL_SUPPORT_SERVICES}{\*\bkmkend OEMS_OBLIGATIONS}{\*\bkmkend OEM_SYSTEM_SUPPORT}{\*\bkmkend TECHNICAL_SUPPORT_SERVICES}{\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 -\af1\afs22 \ltrch\fcs0 \f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid13966861\charrsid9767071 \hich\af1\dbch\af0\loch\f1 2.2\tab}Except as expressly permitted in this }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid14902293 Agreement}{ -\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 , Licensee shall not, and shall not permit others to: (i)\~ -modify, translate, create derivative copies of or copy the Software (other than one backup copy which reproduces all proprietary notices), in whole or in part; (ii)\~ -reverse engineer, decompile, disassemble or otherwise reduce the Software to source code form; (iii)\~distribute, sublicense, assign, share, timeshare, sell, rent, lease, grant a security interest in, use for service bureau purposes, or othe -rwise transfer the Software or Licensee\rquote s right to use the Software; (iv)\~remove or modify any copyright, trademark, or other proprietary notices of }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid1846192 KiwiNova}{\rtlch\fcs1 \af1\afs22 -\ltrch\fcs0 \f1\fs22\insrsid15497150\charrsid9767071 }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 affixed to the media containing the Software or contained within the Software; or (v) use - the Software in any manner not expressly authorised by this }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid14902293 Agreement}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 . }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid13966861 -\par }\pard\plain \ltrpar\s22\qj \li0\ri0\sa240\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid265398 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af0 -\ltrch\fcs0 \insrsid265398\charrsid265398 -\par {\*\bkmkstart TERM_AND_TERMINATION}{\*\bkmkend TERM_AND_TERMINATION}{\listtext\pard\plain\ltrpar \s24 \rtlch\fcs1 \ab\af1\afs22 \ltrch\fcs0 \b\f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid10425897\charrsid9767071 \hich\af1\dbch\af0\loch\f1 3 -\tab}}\pard\plain \ltrpar\s24\qj \fi-567\li567\ri0\sa120\keepn\widctlpar\jclisttab\tx567\wrapdefault\aspalpha\aspnum\faauto\ls2\outlinelevel0\adjustright\rin0\lin567\itap0\pararsid10425897 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 -\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \ab\af1\afs22 \ltrch\fcs0 \b\f1\fs22\insrsid10425897\charrsid9767071 PROPRIETARY RIGHTS{\*\bkmkstart _REF426272673}{\*\bkmkend _REF426272673} -\par }\pard\plain \ltrpar\s23\qj \fi-567\li567\ri0\sa240\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin567\itap0\pararsid10425897 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 { -\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10425897 4.1\tab }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid1846192 KiwiNova}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid15497150\charrsid9767071 }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid10425897\charrsid9767071 has sole and exclusive ownership of all right, title, and interest in and to the Software, including all copyright and any other intellectual property rights therein. This }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid10425897 Agreement}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10425897\charrsid9767071 conveys a limited }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10425897 licence}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid10425897\charrsid9767071 to use the Software and shall not be construed to convey title to or ownership of the Software to Licensee. All rights in and to the Software not expressly granted to Licensee are reserved by }{\rtlch\fcs1 -\af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid1846192 KiwiNova}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10425897\charrsid9767071 .}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10425897 -\par }\pard\plain \ltrpar\s25\qj \fi-567\li567\ri0\sa120\widctlpar\jclisttab\tx567\wrapdefault\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin567\itap0\pararsid10425897 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 -\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10425897 4.2\tab For the avoidance of doubt, the Software does not include: -\par }\pard \ltrpar\s25\qj \fi-1276\li1276\ri0\sa120\widctlpar\jclisttab\tx567\wrapdefault\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin1276\itap0\pararsid10425897 {\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10425897 \tab 4.2.1\tab }{ -\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid16137090 Actipro Software}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid15497150 which }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid16137090 is}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid15497150 provided }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10425897 by }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid7735196\charrsid7735196 http://}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid16137090 -actiprosoftware.com}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid7735196\charrsid7735196 /}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid7735196 }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10425897 -on the terms and conditions set out at: }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid16137090 ActiproEULA}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid7735196 .}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid16137090 html}{\rtlch\fcs1 -\af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10425897 -\par }\pard\plain \ltrpar\s22\qj \fi-1276\li1276\ri0\sa240\widctlpar\jclisttab\tx567\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin1276\itap0\pararsid10425897 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 -\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid10425897 \tab }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10425897 4.2.4}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10425897\charrsid16322833 -\tab }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid15497150 CommandBars}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10425897 which is distributed with permission from }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid15497150 -Lutz Roeder }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10425897\charrsid13969160 <}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid15497150 roeder}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10425897\charrsid13969160 @}{\rtlch\fcs1 -\af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid15497150 aisto}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid7692188\charrsid7692188 .com}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10425897\charrsid13969160 >}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid10425897 -\par }\pard\plain \ltrpar\s23\qj \fi-567\li567\ri0\sa240\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin567\itap0\pararsid11489578 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 { -\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid10425897 -\par {\*\bkmkstart TRADEMARK}{\*\bkmkstart CALCULATION_OF_FEES}{\*\bkmkend TRADEMARK}{\*\bkmkend CALCULATION_OF_FEES}{\listtext\pard\plain\ltrpar \s24 \rtlch\fcs1 \ab\af1\afs22 \ltrch\fcs0 -\b\f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid13966861\charrsid4082071 \hich\af1\dbch\af0\loch\f1 4\tab}}\pard\plain \ltrpar\s24\qj \fi-567\li567\ri0\sa120\keepn\widctlpar -\jclisttab\tx567\wrapdefault\aspalpha\aspnum\faauto\ls2\outlinelevel0\adjustright\rin0\lin567\itap0\pararsid5189883 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \ab\af1\afs22 -\ltrch\fcs0 \b\f1\fs22\insrsid13966861\charrsid4082071 TERM AND TERMINATION{\*\bkmkstart _REF426272235}{\*\bkmkend _REF426272235} -\par {\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid13966861\charrsid4082071 \hich\af1\dbch\af0\loch\f1 4.1\tab}}\pard\plain \ltrpar\s25\qj \fi-600\li600\ri0\sa120\widctlpar -\jclisttab\tx600\wrapdefault\aspalpha\aspnum\faauto\ls4\ilvl1\outlinelevel1\adjustright\rin0\lin600\itap0\pararsid5907843 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af1\afs22 -\ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid4082071 This }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid14902293\charrsid4082071 Agreement}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid4082071 - shall commence on the Effective Date and continue in effect for consecutive annual periods, unless and until terminated in accordance with clause }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843 4}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid13966861\charrsid4082071 .2}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid9969717 , or unless terminated by Mutant on the provision of not less than thirty (30) days notice to the Licensee}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid9908984 , such notice to be provided to the Licensee via }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid1846192 KiwiNova}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid9908984 \rquote s website}{\rtlch\fcs1 \af1\afs22 -\ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid4082071 . -\par {\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid13966861\charrsid9767071 \hich\af1\dbch\af0\loch\f1 4.2\tab}}\pard \ltrpar\s25\qj \fi-567\li567\ri0\sa120\widctlpar -\jclisttab\tx567\wrapdefault\aspalpha\aspnum\faauto\ls4\ilvl1\outlinelevel1\adjustright\rin0\lin567\itap0\pararsid5189883 {\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 If either party breaches this }{\rtlch\fcs1 \af1\afs22 -\ltrch\fcs0 \f1\fs22\insrsid14902293 Agreement}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 in any material respect, the other party may give written notice to the breaching party of its intent to terminate, - and if such breach is not cured within thirty (30) days after the breaching party\rquote s receipt of such notice, this }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid14902293 Agreement}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid13966861\charrsid9767071 shall terminate without any further notice required (but no cure period is required for any breach that cannot be cured). -\par {\*\bkmkstart OBLIGATIONS_ON_TERMINATION}{\*\bkmkend OBLIGATIONS_ON_TERMINATION}{\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid13966861\charrsid9767071 -\hich\af1\dbch\af0\loch\f1 4.3\tab}Upon any termination of this }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid14902293 Agreement}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 , (a)\~the rights and }{\rtlch\fcs1 -\af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid9767071 licence}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 s granted to Licensee herein shall terminate; (b)\~Licensee shall cease all use of the Software; (c)\~Licensee shall }{ -\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid16322833 delete }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 all copies of the Software and Documentation in Licensee\rquote s possession or under its control; and (d)\~ -Licensee shall certify in writing to }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid1846192 KiwiNova}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 its compliance with the foregoing.{\*\bkmkstart _REF426272371} -{\*\bkmkend _REF426272371} Clauses\~1, }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid16406037 2.2, }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843 3}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 , }{ -\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843 4}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 .3, }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843 5}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid13966861\charrsid9767071 , }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843 6 }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 and }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843 7}{ -\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 shall survive any termination of this }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid14902293 Agreement}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid13966861\charrsid9767071 .}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861 -\par }\pard\plain \ltrpar\s24\qj \li0\ri0\sa120\keepn\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin0\itap0\pararsid5907843 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 { -\rtlch\fcs1 \ab\af1\afs22 \ltrch\fcs0 \b\f1\fs22\insrsid5907843 {\*\bkmkstart OWNERSHIP_OF_RIGHTS}{\*\bkmkstart _REF426271959}{\*\bkmkstart CONFIDENTIALITY}{\*\bkmkend OWNERSHIP_OF_RIGHTS}{\*\bkmkend _REF426271959}{\*\bkmkend CONFIDENTIALITY} -\par {\listtext\pard\plain\ltrpar \s24 \rtlch\fcs1 \ab\af1\afs22 \ltrch\fcs0 \b\f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid13966861\charrsid9767071 \hich\af1\dbch\af0\loch\f1 5\tab}}\pard \ltrpar\s24\qj \fi-360\li360\ri0\sa120\keepn\widctlpar -\jclisttab\tx567\wrapdefault\aspalpha\aspnum\faauto\ls4\outlinelevel0\adjustright\rin0\lin360\itap0\pararsid5189883 {\rtlch\fcs1 \ab\af1\afs22 \ltrch\fcs0 \b\f1\fs22\insrsid13966861\charrsid9767071 REPRESENTATIONS AND WARRANTIES -\par }\pard\plain \ltrpar\s25\qj \fi-567\li567\ri0\sa120\widctlpar\jclisttab\tx567\wrapdefault\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin567\itap0\pararsid6776828 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 -\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843 5.1}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 \tab }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid5907843 LICENSEE ACKNOWLEDGES AND}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843\charrsid5907843 AGREES THAT }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid7735196 KIWI}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid1846192 NOVA}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843\charrsid5907843 HAS PROVIDED NO EXPRESS OR IMPLIED WARRANTIES, ORAL OR WRITTEN, TO }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843 LICENSEE }{ -\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843\charrsid5907843 REGARDING THE SOFTWARE OR DOCUMENTATION AND THAT THEY ARE PROVIDED \'93AS IS\'94 WITHOUT WARRANTY OF ANY KIND. }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843 -TO THE MAXIMUM EXTENT PERMITTED BY LAW }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid7735196 KIWI}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid1846192 NOVA}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843\charrsid5907843 HEREBY } -{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843 EXCLUDES AND }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843\charrsid5907843 -DISCLAIMS ALL WARRANTIES WITH REGARD TO THE SOFTWARE AND DOCUMENTATION, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843\charrsid9767071 THE IMPLIED}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid5907843\charrsid5907843 WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843\charrsid9767071 AND ANY WARRANTIES ARISING BY STATUTE OR OTHERWISE IN LAW -OR FROM COURSE OF DEALING, COURSE }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid16406037 OF PERFORMANCE, OR USE OF TRADE}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843\charrsid5907843 .}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid5907843 -\par }\pard\plain \ltrpar\s22\qj \fi-567\li567\ri0\sa240\widctlpar\jclisttab\tx567\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin567\itap0\pararsid6776828 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 -\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid5907843 5.2}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 \tab -The Licensee hereby represents that it shall (i) comply with all applicable local and foreign laws and regulations which may govern the use of the Software, and (ii) use the Software only for lawful purposes and in accordance with the terms of this }{ -\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid14902293 Agreement}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 . -\par {\*\bkmkstart INDEMNIFICATION}{\*\bkmkstart NO_CONSEQUENTIAL_DAMAGES}{\*\bkmkend INDEMNIFICATION}{\*\bkmkend NO_CONSEQUENTIAL_DAMAGES}{\listtext\pard\plain\ltrpar \s24 \rtlch\fcs1 \ab\af1\afs22 \ltrch\fcs0 -\b\f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid13966861\charrsid9767071 \hich\af1\dbch\af0\loch\f1 6\tab}}\pard\plain \ltrpar\s24\qj \fi-360\li360\ri0\sa120\keepn\widctlpar -\jclisttab\tx567\wrapdefault\aspalpha\aspnum\faauto\ls4\outlinelevel0\adjustright\rin0\lin360\itap0\pararsid5189883 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \ab\af1\afs22 -\ltrch\fcs0 \b\f1\fs22\insrsid13966861\charrsid9767071 LIMITATION OF LIABILITY}{\rtlch\fcs1 \ab\af1\afs22 \ltrch\fcs0 \b\f1\fs22\insrsid16004039 /INDEMNITY}{\rtlch\fcs1 \ab\af1\afs22 \ltrch\fcs0 \b\f1\fs22\insrsid13966861\charrsid9767071 -\par {\*\bkmkstart LIMITATION_ON_LIABILITY}{\*\bkmkstart EQUITABLE_REMEDIES}{\*\bkmkend LIMITATION_ON_LIABILITY}{\*\bkmkend EQUITABLE_REMEDIES}{\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid7296646 \hich\af1\dbch\af0\loch\f1 6.1\tab}}\pard\plain \ltrpar\s25\qj \fi-567\li567\ri0\sa120\widctlpar -\jclisttab\tx567\wrapdefault\aspalpha\aspnum\faauto\ls4\ilvl1\outlinelevel1\adjustright\rin0\lin567\itap0\pararsid5189883 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af1\afs22 -\ltrch\fcs0 \f1\fs22\insrsid7296646 SAVE IN RESPECT OF DEATH OR PERSONAL INJURY, FOR WHICH THE LIABILITY OF THE PARTIES SHALL BE UNLIMITED, }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid16322833\charrsid9767071 IN NO EVENT SHALL }{\rtlch\fcs1 -\af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid1846192 KIWINOVA}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid16322833\charrsid9767071 BE LIABLE FOR ANY SPECIAL, }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid7296646 DIRECT, }{\rtlch\fcs1 \af1\afs22 -\ltrch\fcs0 \f1\fs22\insrsid16322833\charrsid9767071 INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOS -S OF PROFITS AND GOODWILL, BUSINESS OR BUSINESS BENEFIT, OR THE COST OF PROCUREMENT OF SUBSTITUTE PRODUCTS BY LICENSEE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. IN NO CIRCUMSTANCES SHALL }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid1846192 KIWINOVA}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid16322833\charrsid9767071 BE LIABLE FOR ANY FAILURE OF THE SOFTWARE - TO PERFORM IN ACCORDANCE WITH THE DOCUMENTATION, OR AT ALL, RESULTING FROM A FAILURE BY THE LICENSEE TO COMPLY WITH THE MINIMUM REQUIREMENTS. ADDITIONALLY, LICENSEE ACKNOWLEDGES TH}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid16322833\charrsid16004039 AT WHILST THE SOFTWARE MAY BE USED IN COMBINATION WITH THIRD PARTY SOFTWARE, }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid1846192 KIWINOVA}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid16322833\charrsid16004039 BEARS NO LIABILITY, HOWSOEVER ARISING, FOR ANY LOSS, DAMAGE OR COST THAT ARISES FROM A FAILURE OF THE SOFTWARE TO INTEGRATE WITH LICENSEE OR THIRD PARTY SOFTWARE.}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid13966861\charrsid16004039 -\par }\pard\plain \ltrpar\s22\qj \fi-567\li567\ri0\sa240\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin567\itap0\pararsid16004039 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 { -\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid16004039\charrsid16004039 8.3\tab }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid16004039 LICENSEE HEREBY INDEMNIFIES }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid1846192 KIWINOVA}{ -\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid16004039 IN FULL AND ON DEMAND IN RESPECT OF ALL COSTS, DAMAGES AND LIABILITIES ARISING FROM ANY BREACH BY THE LICENSEE OF ANY TERM OF THIS AGREEMENT.}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid16004039\charrsid16004039 -\par {\listtext\pard\plain\ltrpar \s24 \rtlch\fcs1 \ab\af1\afs22 \ltrch\fcs0 \b\f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid13966861\charrsid16004039 \hich\af1\dbch\af0\loch\f1 7\tab}}\pard\plain \ltrpar -\s24\qj \fi-360\li360\ri0\sa120\keepn\widctlpar\jclisttab\tx567\wrapdefault\aspalpha\aspnum\faauto\ls4\outlinelevel0\adjustright\rin0\lin360\itap0\pararsid5189883 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 -\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \ab\af1\afs22 \ltrch\fcs0 \b\f1\fs22\insrsid13966861\charrsid16004039 GENERAL}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid16004039 -\par {\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid13966861\charrsid16004039 \hich\af1\dbch\af0\loch\f1 7.1\tab}}\pard\plain \ltrpar\s25\qj \fi-567\li567\ri0\sa120\widctlpar -\jclisttab\tx567\wrapdefault\aspalpha\aspnum\faauto\ls4\ilvl1\outlinelevel1\adjustright\rin0\lin567\itap0\pararsid5189883 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af1\afs22 -\ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid16004039 Licensee shall not assign}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 this }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid14902293 Agreement}{\rtlch\fcs1 -\af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 , in whole or in part, without the written consent of }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid1846192 KiwiNova}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071 . - -\par {\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid4082071\charrsid9767071 \hich\af1\dbch\af0\loch\f1 7.2\tab}}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid4082071\charrsid9767071 Licensee consents to the use by }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid1846192 KiwiNova}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071\charrsid9767071 of Licensee\rquote -s name in customer lists and other publicity, including interviews, case studies, and conference discussions, provided that such publicity accurately describes the nature of the relationship between Licensee and }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid1846192 KiwiNova}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071\charrsid9767071 .}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071 -\par {\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid4082071\charrsid9767071 \hich\af1\dbch\af0\loch\f1 7.3\tab}}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid4082071\charrsid9767071 This }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071 Agreement}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071\charrsid9767071 - and its performance shall be governed by and construed in accordance with and the parties hereby submit to the exclusive jurisdiction of the laws of {\*\xmlopen\xmlns2{\factoidname country-region}}England{\*\xmlclose} and {\*\xmlopen\xmlns2{\factoidname -place}}{\*\xmlopen\xmlns2{\factoidname country-region}}Wales{\*\xmlclose}{\*\xmlclose}.}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071 -\par {\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid4082071\charrsid9767071 \hich\af1\dbch\af0\loch\f1 7.4\tab}}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid4082071\charrsid9767071 Licensee agrees that because of the unique nature of the Software and }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid1846192 KiwiNova}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid4082071\charrsid9767071 \rquote s proprietary rights therein, a demonstrated breach of this }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071 Agreement}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071\charrsid9767071 - by Licensee would irreparably harm }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid1846192 KiwiNova}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071\charrsid9767071 and monetary damages would be inadequate compensation. - Therefore, Licensee agrees that }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid1846192 KiwiNova}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071\charrsid9767071 - shall be entitled to preliminary and permanent injunctive relief, as determined by any court of competent jurisdiction to enforce the provisions of this }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071 Agreement}{\rtlch\fcs1 \af1\afs22 -\ltrch\fcs0 \f1\fs22\insrsid4082071\charrsid9767071 .}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071 -\par {\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid4082071\charrsid9767071 \hich\af1\dbch\af0\loch\f1 7.5\tab}}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid4082071\charrsid9767071 If any provision of this }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071 Agreement}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071\charrsid9767071 - or the Software thereof is declared void, illegal, or unenforceable, the remainder of this }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071 Agreement}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071\charrsid9767071 - will be valid and enforceable to the extent permitted by applicable law. In such event, the parties agree to use their best efforts to replace the invali -d or unenforceable provision by a provision that, to the extent permitted by the applicable law, achieves the purposes intended under the invalid or unenforceable provision}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071 . -\par {\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid4082071\charrsid9767071 \hich\af1\dbch\af0\loch\f1 7.6\tab}}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid4082071\charrsid9767071 Any failure by any party to this }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071 Agreement}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071\charrsid9767071 - to enforce at any time any term or condition under this }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071 Agreement}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071\charrsid9767071 will not be considered a waiver of that party -\rquote s right thereafter to enforce each and every term and condition of this }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071 Agreement. -\par {\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid4082071\charrsid9767071 \hich\af1\dbch\af0\loch\f1 7.7\tab}}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid4082071\charrsid9767071 Neither party will be responsible for delays resulting from circumstances beyond the reasona -ble control of such party, provided that the nonperforming party uses reasonable efforts to avoid or remove such causes of nonperformance and continues performance hereunder with reasonable dispatch whenever such causes are removed}{\rtlch\fcs1 -\af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071 . -\par {\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\lang1033\langfe1033\langnp1033\langfenp1033\insrsid11883158 \hich\af1\dbch\af0\loch\f1 7.8\tab}}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid11883158 T}{\rtlch\fcs1 -\af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071\charrsid9767071 his }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071 Agreement}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid11883158 }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid4082071\charrsid9767071 (i)\~ -constitutes the entire agreement and understanding between the parties with respect to the subject matter hereof and supersedes all prior agreements, oral and written, made with respect to the subject matter hereof, and (ii)\~ -cannot be altered except by agreement in writing executed by an authorised representative of each party. No purchase order and/or standard terms of purchase provided by Licensee shall supersede this }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid4082071 Agreement}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071\charrsid9767071 .}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid4082071 -\par {\listtext\pard\plain\ltrpar \s25 \rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\expnd0\expndtw-2\lang1033\langfe1033\langnp1033\langfenp1033\insrsid4082071\charrsid9767071 \hich\af1\dbch\af0\loch\f1 7.9\tab}}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\expnd0\expndtw-2\insrsid4082071\charrsid9767071 Nothing in this Agreement shall give, directly or indirectly, any third party any enforceable benefit or any right of action against }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\expnd0\expndtw-2\insrsid1846192 KiwiNova}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\expnd0\expndtw-2\insrsid4082071\charrsid9767071 and such third parties shall not be entitled to enforce any term of this Agreement against }{\rtlch\fcs1 -\af1\afs22 \ltrch\fcs0 \f1\fs22\expnd0\expndtw-2\insrsid1846192 KiwiNova}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\expnd0\expndtw-2\insrsid4082071\charrsid9767071 .}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid13966861\charrsid9767071 . -\par }\pard\plain \ltrpar\qj \li0\ri0\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid9767071 \rtlch\fcs1 \af0\afs20\alang1025 \ltrch\fcs0 \fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af1\afs22 -\ltrch\fcs0 \f1\fs22\insrsid8065525\charrsid9767071 If you have any questions regarding this Licence and Support Agreement or if you wish to discuss the terms and conditions contained herein please contact }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid1846192 KiwiNova}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid9767071\charrsid9767071 Ltd}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid8065525\charrsid9767071 using the contact details at}{\rtlch\fcs1 \af1\afs22 -\ltrch\fcs0 \f1\fs22\insrsid9445259 http://www.}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid7735196 kiwidude.com}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid8065525\charrsid9767071 or at }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 -\f1\fs22\insrsid1846192 24 Aegean Apartments}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid7735196 , }{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid1846192 19 Western Gateway}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid7735196 -, London E16 1}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid1846192 AR}{\rtlch\fcs1 \af1\afs22 \ltrch\fcs0 \f1\fs22\insrsid8065525\charrsid9767071 . -\par }\pard \ltrpar\qj \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 {\rtlch\fcs1 \ab\af1\afs22 \ltrch\fcs0 \b\f1\fs22\insrsid13966861\charrsid9767071 -\par }} \ No newline at end of file diff --git a/lib/NCover/Explorer/NCoverExplorer.Console.exe b/lib/NCover/Explorer/NCoverExplorer.Console.exe deleted file mode 100644 index 7dee5442..00000000 Binary files a/lib/NCover/Explorer/NCoverExplorer.Console.exe and /dev/null differ diff --git a/lib/NCover/Explorer/NCoverExplorer.Core.dll b/lib/NCover/Explorer/NCoverExplorer.Core.dll deleted file mode 100644 index 91dfff5c..00000000 Binary files a/lib/NCover/Explorer/NCoverExplorer.Core.dll and /dev/null differ diff --git a/lib/NCover/Explorer/NCoverExplorer.NAntTasks.dll b/lib/NCover/Explorer/NCoverExplorer.NAntTasks.dll deleted file mode 100644 index 552e4525..00000000 Binary files a/lib/NCover/Explorer/NCoverExplorer.NAntTasks.dll and /dev/null differ diff --git a/lib/NCover/Explorer/NCoverExplorer.NCoverRunner.dll b/lib/NCover/Explorer/NCoverExplorer.NCoverRunner.dll deleted file mode 100644 index fe686c99..00000000 Binary files a/lib/NCover/Explorer/NCoverExplorer.NCoverRunner.dll and /dev/null differ diff --git a/lib/NCover/Explorer/NCoverExplorer.WinForms.dll b/lib/NCover/Explorer/NCoverExplorer.WinForms.dll deleted file mode 100644 index 173d63f9..00000000 Binary files a/lib/NCover/Explorer/NCoverExplorer.WinForms.dll and /dev/null differ diff --git a/lib/NCover/Explorer/NCoverExplorer.exe b/lib/NCover/Explorer/NCoverExplorer.exe deleted file mode 100644 index 24fb51a4..00000000 Binary files a/lib/NCover/Explorer/NCoverExplorer.exe and /dev/null differ diff --git a/lib/NCover/Explorer/NCoverExplorer.exe.config b/lib/NCover/Explorer/NCoverExplorer.exe.config deleted file mode 100644 index 4bb5eb0d..00000000 --- a/lib/NCover/Explorer/NCoverExplorer.exe.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/lib/NCover/Explorer/NCoverExplorerFAQ.html b/lib/NCover/Explorer/NCoverExplorerFAQ.html deleted file mode 100644 index 1ee34b9c..00000000 --- a/lib/NCover/Explorer/NCoverExplorerFAQ.html +++ /dev/null @@ -1,303 +0,0 @@ - - - - - NCoverExplorer FAQ - - - - - -

NCoverExplorer FAQ

-

The latest version of this document is located here. -
For the latest NCoverExplorer news and updates, visit my blog.

-

Expand All | Collapse All

- 1. What is NCoverExplorer?
- - 2. What versions of the .NET Framework does it work with?
- - 2. What versions of NCover does it work with?
- - 3. Can I integrate it with my Visual Studio.Net version XXX IDE?
- - 4. Can I integrate it without TestDriven.Net?
- - 5. How does it compare to Visual Studio Team System?
- - 6. Why didn't you integrate NCoverExplorer directly into the IDE like VSTS or SharpDevelop?
- - 7. What do the tree node colours mean?
- - 8. What do the source code highlighting colours mean?
- -
9. What is that "Satisfaction Threshold" all about?
- - 10. I have a killer idea for XYZ feature - can you add it for me?
- - 11. Where can I download the latest version?
- - 12. What are the keyboard shortcuts?
- - 13. Where are my personal settings stored?
- - 14. Where can I download the custom NAnt/MSBuild tasks from?
- - 15. I get an "Illegal characters in path" exception - why?
- - 16. I get a "System.Format" exception - why?
- - 17. My module thresholds are not working - why?
- -

---------------------------------
FAQ last updated Jul 21st 2007.

- - diff --git a/lib/NCover/Explorer/NCoverExplorerReleaseNotes.html b/lib/NCover/Explorer/NCoverExplorerReleaseNotes.html deleted file mode 100644 index e9fc6d29..00000000 --- a/lib/NCover/Explorer/NCoverExplorerReleaseNotes.html +++ /dev/null @@ -1,874 +0,0 @@ - - - - NCoverExplorer Release Notes - - - - - -

NCoverExplorer Release Notes

-

The latest version of this document is located here. -
For the latest NCoverExplorer news and updates, visit my blog.

-
- -

v1.4.0 - Sep 16th 2007

-

The following new features were introduced:

-
    -
  • - Major rewrite of the underlying object design for future maintainability. Should improve treeview - performance for .NET 2.0 users (and load performance for all users) as well as make it easier to - add new features. -
  • -
  • - Changes to the project setting file format and location, both as used by the NCoverExplorer gui - and the NCoverExplorer.Console.exe application. If you use the /c argument supplying a configuration - file to NCoverExplorer then you must modify your project file format. See ConsoleExample.config - for details (replace the outer tag to be called ConsoleSetting). -
  • -
  • - Replaced ICSharpCode text editor with Actipro which offers far superior features, more attractive - appearance and provides a more flexible licensing model for the future of NCoverExplorer. -
  • -
  • - A new attribute added into the coverage report xml of "totalSeqPoints" which includes the - total of any excluded sequence points at that level. In response to a feature request in - this NCover forum thread to allow - people to report how much code was excluded from coverage. -
  • -
  • - Add a copy command to the right-click menu for the source code area. -
  • -
  • - Add a print preview command to the File menu. -
  • -
  • - Add support for profiling a specific process module to the Run NCover dialog. -
  • -
  • - Add a /fc (failCombinedMinimum) option to NCoverExplorer.Console.exe for emulating the - original behaviour of failing based on total coverage to supplement the /f option which - fails if an individual module is below the coverage threshold. -
  • -
-

The following minor changes were made:

-
    -
  • - Rewrite the options dialog to use a VS.Net style property pages approach. -
  • -
  • - Exclusions tab in Options dialog - delete key is now a shortcut to removing an exclusion. -
  • -
  • - Reorder the file menu slightly so Run NCover is separated. -
  • -
  • - Source code window now has a splitter bar. -
  • -
  • - Command line generated for NCover 1.5.7+ in NCover Runner dialog includes the //reg - option if choosing to register coverlib.dll. -
  • -
  • - Statistics pane auto-sizes the last column to fill the width of the listview. -
  • -
  • - Coverage exclusions now support '?' and more complex wildcard expressions such - as Test.*.Something*. -
  • -
-

The following bug fixes were made:

-
    -
  • - Line number foreground colour not displayed correctly in options dialog tab. -
  • -
  • - Directory not created if not existing when writing output report. -
  • -
  • - Corrected typo in full name of parameter when using /quiet option with NCoverExplorer.Console. -
  • -
  • - Reduce GDI usage by editor control. - Rnsure Actipro renderer is correctly utilised. - Turn off text margins in NCover run dialog for editor. - Ensure C++ code has whole line highlighted even though no sequence point values. - (Build 1.4.0.6) -
  • -
-
- -

v1.3.6 - Apr 5th 2007

-

Bundled with TestDriven.Net from build 2.5.2078.

-

The following new features were introduced:

-
    -
  • - Added a Find dialog (ctrl+F) to quickly navigate to a class. Wildcards are supported. -
  • -
  • - Added a /q or /quiet option to NCoverExplorer.Console.exe to minimise the output. -
  • -
-

The following minor changes were made:

-
    -
  • - Failing if less than a threshold now applies to any assembly not meeting the threshold - rather than comparing against the total coverage across all assemblies. -
  • -
  • - Add some examples to the NCoverExplorer.Console.exe output for the /help or /? (or no arguments). -
  • -
  • - Pressing ESC on the NCover Runner dialog will now close it. -
  • -
  • - Implement a workaround for poor treeview performance under .NET 2.0. -
  • -
  • - Rather than displaying validation errors automatically "fix" paths with matching trailing - slashes in the Change Source Path dialog. -
  • -
  • - Writing of coverage files should now match the schema for the relevant NCover version. - Later NCover versions like 1.5.7 have enhanced the schema, so the results of a merge or - save from NCoverExplorer should offer a comparative schema in the result. -
  • -
  • - Add a message indicating the return code to the output. -
  • -
-

The following bug fixes were made:

-
    -
  • - NCover 1.5.5/6 produce duplicate sequence points. To workaround this fix Jamie Cansdale implemented - a change for me to the way the methods are identified uniquely. The longer term fix is NCover version 1.5.7 - - this should keep things usable until that is released. -
  • -
  • - Another issue up to at least NCover 1.5.7 is that non-instrumented code does not have the sequence - points optimised. When merging multiple coverage files NCoverExplorer was incorrectly merging the noops with - valid instrumented sequence points, resulting in lower coverage information. -
  • -
  • - If CoverageReport.xsl stylesheet already exists in destination output folder for an xml report - and is marked as read-only then the replace would fail. -
  • -
  • - Drag/drop of coverage.xml files would add to the wrong end of the MRU menu once the maximum - number of items is reached. -
  • -
  • - If multiple classes in the same file then selecting a class node was not navigating to that - class in the source code tab. It will now jump to the first unvisited sequence point, or if - there are none of those the first sequence point in the class. -
  • -
  • - Wildcards for coverage exclusions were only working if placed at the ends, not in the middle - e.g. *.Tests or Testing.* would work, but xxx.*.yyy would not. -
  • -
  • - Prevent some of the nasty GDI errors in CommandBars code from disrupting the GUI. Longer - term will utilise another framework. -
  • -
  • - Replacing paths by typing them in had MaxLength set to 50 so impossible to edit long paths - in the Change Source Path dialog. -
  • -
  • - Merging property nodes under a parent in the tree has a dependency on the ordering of the coverage output - to ensure they appear properly. -
  • -
  • - When restoring form position from persisted values, ensure it appears on a visible screen, - catering for the user changing their display settings between sessions. -
  • -
  • - Ensure stylesheet cannot be copied over the top of itself. -
  • -
  • - Supplying a file pattern with no matches to NCoverExplorer.Console.exe was throwing an "Index was - outside the bounds of the array" exception. -
  • -
  • - Multiple coverage exclusion attributes not supplied correctly to NCover (build 26). -
  • -
  • - Check to make sure node is assigned to a TreeView before getting handle to set text (build 32). -
  • -
  • - Sort sequence point nodes when loading and handle merge case of multiple non-instrumented - sequence points becoming a single sequence point. (build 36). -
  • -
-
- -

v1.3.5 - Oct 23rd 2006

-

Bundled with TestDriven.Net from build 2.0.1921.

-

The following new features were introduced:

-
    -
  • - Added ability to run NCover from within NCoverExplorer (all versions). User Ctrl+N or - entries on File menu/toolbar to bring up configuration dialog. After successful - execution, the resultant coverage file is displayed in NCoverExplorer. -
  • -
  • - Added ability to generate MSBuild, NAnt and command-line scripts for running NCover - from within NCoverExplorer. See the NCover dialog above. -
  • -
  • - Added new function coverage viewing options and module/class coverage report. - Indicates the percentage of functions covered rather than the sequence points within each. - Supported by a new "satisfactory function threshold" and function % sorting options. -
  • -
  • - Background colours can now be customised for coverage nodes in the tree. -
  • -
  • - Reports will now have the current filtering applied, not just the sorting settings. -
  • -
  • - Reports using NCoverExplorer.Console can now have filtering and sorting applied. Use the - /sort: and /filter: command line arguments, or specify in a .config file (see example.config), - or use the sort/filter arguments to the NAnt/MSBuild tasks. -
  • -
  • - Sorting and filtering options applied are now persisted and reapplied to the next coverage - xml file loaded, both in this and future sessions. -
  • -
  • - Added ability to filter out all nodes exceeding coverage threshold. -
  • -
  • - Revamp to the NAnt/MSBuild tasks. Renamed assemblies and namespaces. Included new attribute of - "AssembliesList" as an alternative to the "Assemblies" group element to allow direct - specification of a list as you would on the command line. The "Version" attribute is now optional - - the task determines it from the NCover assembly instead if not specified. Tasks will automatically - register NCover coverlib.dll using the HKCU entry in the registry - no need for regsvr32 any more! - NCoverExplorer task now writes it's config file to temp folder for passing to the executable. -
  • -
  • - Added documentation for the NAnt and MSBuild tasks. This is included both in the NCoverExplorer.Extras.zip - file, as well as being available online for the custom MSBuild Task Help - and NAnt Task Help. - Links also available off the Help menu for NCoverExplorer. -
  • -
  • - Added a schema file ConsoleConfig.xsd to the distribution for people wanting to know the exact syntax - options for creating .config files to pass to NCoverExplorer.Console using the /config switch. -
  • -
  • - Added regular expression support to the coverage exclusions dialog for people wanting more complex queries. -
  • -
-

The following minor changes were made:

-
    -
  • - Configuration file change - the ModuleThresholds section in .config files passed to NCoverExplorer.Console now - uses propercase attribute names to be consistent with the rest of the configuration file. - i.e. "ModuleName" instead of "moduleName", and "SatisfactoryCoverage" instead of "satisfactoryCoverage". You must update - your NAnt/MSBuild tasks for NCoverExplorer if you use these. If you instead use the <exec> task with a .config - file then you should update the case of the entries in this file. This only affects people who have setup coverage exclusions - at the module level for reporting purposes. -
  • -
  • - If source code is out of date compared to the coverage results, the user is prompted with - the change source path dialog. -
  • -
  • - If the user chooses a new source code location, the tab is now automatically opened for - that location rather than requiring the user to click on the tree node again. -
  • -
  • - Added Help->NCoverExplorer Forum menu option to link to the NCover website. Also included - forum link information on the exception dialog. -
  • -
  • - Added a toolbar button for turning off filtering. -
  • -
  • - Keyboard shortcut change - Changed the keyboard shortcuts for next/previous unvisited class (ALT+UP/DOWN) and - next/previous unvisited line in class (ALT+LEFT/RIGHT). -
  • -
  • - Remember which tab was last opened in the NCoverExplorer options dialog during an NCoverExplorer session. -
  • -
  • - Replaced references to "transparent.gif" with "shim.gif" in the NCoverExplorerSummary.xsl. The "shim.gif" - file is a transparent 1x1 gif already distributed with CC.Net. -
  • -
  • - Coverage exclusions for assemblies are now case insensitive. -
  • -
  • - There are no longer two default coverage exclusions added of "*.Tests" and "*.My*" for first time users. - Intended for demo purposes only but stayed in until now. New users can manually add them if they desire them. -
  • -
-

The following bug fixes were made:

-
    -
  • - Overloaded constructors with class level variable declarations were being merged into a single - constructor in the coverage results as they had the same "start line" of the variable. Now uses - end line as part of the identifying key for each method. -
  • -
  • - Memory leak from opening and closing tabs displaying source code. -
  • -
  • - .Net 2.0 performance is pretty dire due to crap Microsoft changes to the TreeView control. - Change to default to .Net 1.1 in NCoverExplorer.exe.config and wrap updates to the tree - in BeginUpdate/EndUpdate. -
  • -
  • - Parsing Java code would blow up if an accessor had the same name as a nested class (illegal in C#). -
  • -
  • - Bugfix in NCover task where multiple assemblies were specified for NCover 1.5.4, which requires - separate <assembly> nodes. -
  • -
  • - Bugfix in trying to restore selected node text after refreshing file could raise - null reference exception. -
  • -
  • - Bugfix so that module names specified in module thresholds when using NCoverExplorer.Console - are no longer case sensitive for matching. -
  • -
  • - Added support for NCover 1.5.5 - the //q bug is fixed in NCover. Also changed parsing code so that modules - with a blank assembly name (through using TestDriven.Net) are ignored from the coverage. -
  • -
  • - Bugfix for merge functionality for NCover.Console when wildcards were used with relative paths. -
  • -
  • - Bugfix for naming of xml/html arguments for NCover.Console with relative file paths. -
  • -
  • - Bugfix for drag/drop broken while making the memory usage optimisations during the 1.3.5 beta release. -
  • -
  • - Print button was enabled when no source code displayed resulting in exception. -
  • -
-
- -

v1.3.4 - Jul 10th 2006

-

Bundled with TestDriven.Net from build 2.0.1702.

-

The following new features were introduced:

-
    -
  • - Added toolbar buttons which support moving to the next and previous unvisited code - within a class or namespace. Shortcut keys of N and P for next/previous unvisited line in the - current class (or mouse forward/back buttons). Use Ctrl+N and Ctrl+P to navigate to the - next/previous partially or unvisited class within the namespace (or Ctrl+forward/back mouse buttons). -
  • -
  • - NCoverExplorer.Console.exe now supports saving the merged results of the coverage xml file(s) with - a /s[ave] option. The NCoverExplorer NAnt and MSBuild tasks have also been enhanced to support this - with an optional "mergeFileName" attribute. -
  • -
  • - NCoverExplorer.Console.exe now supports wildcards for coverage xml filename(s). -
  • -
  • - NCoverExplorer.Console.exe now supports module level coverage thresholds, rather than just a project - coverage threshold. This feature allows finer tolerance for both output on the reports and to fail - a build. Specifying the module thresholds is done either through a .config file (see ConsoleExample.config) - or through parameters in the NAnt/MSBuild tasks. -
  • -
  • - Added a new summary report showing class coverage per namespace per module. -
  • -
  • - Enhanced the NCoverExplorerSummary.xsl to display summaries of each module. -
  • -
  • - Clicking on a class with non-existent source code displays a dialog allowing the user to specify an alternate - folder. For use when the source code location indicated within the coverage.xml file(s) loaded differs from - that on the local machine now (e.g. a different drive letter or folder path). -
  • -
-

The following minor changes were made:

-
    -
  • - NCoverExplorer release is compiled against .Net 1.1 rather than .Net 1.0 due to a dependency on the - FolderBrowserDialog not available in .Net 1.0. -
  • -
  • - Coverage file stylesheet modified to show coverage column and NCoverExplorer version information with - numerous other cosmetic enhancements. -
  • -
  • - Enrich error environment information to include .Net framework version and operating system. -
  • -
  • - Classes without a namespace are now shown under a namespace node of "-" like in Reflector. -
  • -
-

The following bug fixes were made:

-
    -
  • - Warnings about mismatches when merging xml files are no longer issued. NCover seems to inconsistently - produce xml file coverage of methods which caused some users problems when merging. -
  • -
  • - Nested classes without a namespace specified would cause the coverage.xml file to fail to load. -
  • -
  • - Parsing overloaded properties (overloads of this[]) would not show the separate overloads in the tree - and have incorrect coverage stats. -
  • -
  • - Fix memory leaks for when source code tabs are closed. -
  • -
  • - Minimum coverage threshold for NCoverExplorer.Console would sometimes be incorrect due to rounding. -
  • -
  • - Changed NCoverExplorerSummary.xsl to format to 1dp rather than rounding to 0. -
  • -
  • - Sorting by filename for a method then clicking on class node threw exception. -
  • -
  • - VB.Net source code keywords not highlighted with the correct ICSharpCode template. -
  • -
-
- -

v1.3.3 - Apr 4th 2006

-

Bundled with TestDriven.Net from build 2.0.1578.

-

The following new features were introduced:

-
    -
  • - Added NCoverExplorer.Console.exe for utilising NCoverExplorer features with automated - coverage builds and NAnt tasks. By default will load up all the specified coverage file(s), apply - any coverage exclusion(s) specified in the NCoverExplorer configuration and display total - coverage statistics in the console output. If all items processed successfully returns an exit code of 0, - if an exception occurs returns an exit code of 2. -
  • -
  • - Added /m:xx (or /minCoverage:xx) argument to NCoverExplorer.Console.exe. When used in conjunction with - /f (or /failMinimum) an exit code of 3 is returned if the min coverage is not reached. Can act - as a trigger for failing an automated build such as with CruiseControl.Net. -
  • -
  • - Added module & namespace summary xml report generation to NCoverExplorer (both the GUI and Console versions). - In the GUI, this is available via the "View->Reports" menu. The three reports that are offered currently are: -
     - Module Summary (Coverage totals for the project and per module); -
     - Namespace Summary (Coverage totals for the project and per namespace); -
     - Module Namespace Summary (Coverage totals for the project, per module and per namespace); -
  • -
  • - Reports can be generated in xml or html format. Native html may be useful for directly attaching to e-mails. - If xml format is chosen a "CoverageReport.xsl" stylesheet is copied from the NCoverExplorer installation - folder to the report directory and linked to the xml file similar to coverage.xml/coverage.xsl by NCover. -
  • -
  • - Reports can contain an "excluded nodes" footer section. This lists at the topmost level all of the items - excluded from coverage at the time the report was run. -
  • -
  • - Added "View->Filter" main menu and context menus, offering the ability to filter out nodes. Filtered - nodes are simply moved under a new "Filtered" tree node and do not alter the coverage statistics - (unlike excluded nodes which are effectively removed from the tree). Filters offered are either to - hide all 100% covered nodes, or hide all unvisited (0%) nodes. -
  • -
  • - Added "Include in Results" context menu option for when clicking on either the "Excluded" bin or one - of it's immediate child nodes. Offers a way to "undo" an exclusion without reloading the file. -
  • -
  • - Added "View->Summary Statistics" menu option (shortcut F3) to show dialog of totals of files, classes, members, - NCLOC (non-commented lines of code) and sequence points. Statistics do not include excluded nodes - (but will include filtered nodes). -
  • -
  • - Created NAnt and MSBuild tasks for execution of NCoverExplorer.Console as an alternative to the <exec> task. - These tasks offer a more developer friendly alternative such as <fileset> for coverage files and creating a - .config file on the fly based on specified parameters such as <exclusions> within the .build/.proj file. -
  • -
  • - Replaced menus with a lightly tweaked variant of Lutz Roeder's excellent CommandBar code to give a more modern - look and assign icons on the menus. -
  • -
  • - Added a toolbar. If not wanted the toolbar can be hidden using the "View->Show Toolbar" menu option. -
  • -
-

The following minor changes were made:

-
    -
  • - Options dialog shortcut changed to F2. -
  • -
  • - Excluding a node will now select the node after by default rather than the one previous. -
  • -
-

The following bug fixes were made:

-
    -
  • - Fix bug where delete key shortcut was active on the root coverage file node, causing an exception to be thrown. -
  • -
  • - Path was being truncated from the module name when saved. -
  • -
  • - Fix bug where changing theme without coverage file loaded caused error. -
  • -
-
- -

v1.3.2 - Mar 14th 2006

-

Bundled with TestDriven.Net from build 2.0.1545.

-

The following new features were introduced:

-
    -
  • - Added support for merging multiple coverage files. This can be triggered through a variety of ways: -
     - Selecting multiple test classes/fixtures/projects in TestDriven.Net; -
     - Passing multiple files in the command line arguments; -
     - Selecting multiple files in the Open dialog; -
     - Using a new "File->Merge..." menu option; -
     - Drag/dropping onto the NCoverExplorer application. -
  • -
  • - Added tabs for each source code file you open to explore coverage on. If you click on a partial class - then tabs will be opened for each of the source code files making up the class. -
  • -
  • - Added the ability to exclude assemblies, namespaces or classes from the coverage results by a wildcard capable - case-sensitive match on the name. By default NCoverExplorer includes two exclusions: -
     - Exclude all assemblies with the name ending in ".Tests". -
     - Exclude all namespaces with the name containing ".My" (for VB.Net exclusions). -
  • -
  • - Added support for the NCover 1.5.4 "excluded" attribute which can be found in the coverage.xml files when - the appropriate NCover command-line attributes are used. Note that TestDriven.Net still does not as yet - support this attribute so you need to use the NCover.Console command line for this feature - for more information see - here. NCoverExplorer - will not include nodes marked as 'excluded' by NCover in it's totals but will still display them in the tree. -
  • -
  • - Added an "Excluded" child bin node containing all nodes that have been excluded by the options dialog, by NCover - attributes or by the "Exclude From Results" context menu option (see next point). -
  • -
  • - Replaced the "Remove from Results" context menu feature with "Exclude from Results" (shortcut of the DEL key). - Achieves a similar result of removing nodes from coverage calculations, however the nodes are "moved" to the - Excluded bin rather than being deleted from the tree. -
  • -
  • - Added a custom "theme" capability along with further colour and font customisation options for the coverage tree, - statistics and source code panes. A number of predefined "themes" are supplied and users can add their own. - Users can switch between themes either in the Options dialog or via the "View->Themes" menu. -
  • -
  • - Added a new "View->Coverage" menu which has sub-options related to "Sequence Point Coverage" and - "Function Coverage", assigned shortcut keys ctrl+(1-4): -
     - Choosing one of the "Sequence Point" variants will display the tree nodes with differing naming - combinations of coverage percentage and # unvisited sequence points. -
     - Choosing "Function Coverage" will alter the coverage tree display so that only methods/classes that were - invoked are highlighted. Method nodes show the number of visits to that method. Class, namespace and module nodes - show the maximum visit count by any of their children. -
  • -
  • - Added a "View->Sort By" menu option and context menu on the tree, with sub-options for "Name" (default), - "Class name/line number", "Coverage %" (ascending/descending), "Uncovered Sequence Points" (ascending/descending) - and "Visit Counts" (ascending/descending). - Assigned shortcut keys of ctrl+shift+(1-8). Note that reloading the coverage file will remove the current sort - and default back to by "Name". -
  • -
  • - Added "Save" and "Save As" options to the File menu. These give you the option of overwriting/creating a - new coverage.xml file with the current values loaded in NCoverExplorer. Any coverage exclusions/removed - nodes will not appear in the saved coverage file. Note that the methods are written in the same order as - the sort order specified above. -
  • -
  • - Added an "Explore Coverage Folder" menu option to the file menu. -
  • -
  • - Added an "Expand All" context menu option on the tree (shortcut ctrl+L). -
  • -
  • - Enhanced the statistics pane. When a class node is selected you will now see additional columns of - coverage %, unvisited sequence points and sequence points. When clicking on a method node you will - now see the filename. -
  • -
  • - Implemented "smart expansion" in the tree. If when you expand a node there is only one child node - then that node will also be expanded and so on. Increases speed of tree navigation particularly - if using a style of "Nested" namespaces with deep hierarchies. -
  • -
  • - Display class file name in tab page header bar when a method node is clicked on. Tooltip shows the path. -
  • -
-

The following minor changes were made:

-
    -
  • - Optimised when reloads of the coverage file so it is now only required if you change a coverage exclusion - or the tree grouping/nesting styles in the options dialog. Makes for a snappier UI. -
  • -
  • - Added a "Close" menu option to remove any loaded coverage file(s) from display. -
  • -
  • - Moved all the "Recent Files" into a submenu to tidy up the File menu. -
  • -
  • - Pressing Tab/shift-tab while focus is in the TextEditor pane of source code will now - move focus out of the TextEditor. -
  • -
  • - If a source code file contains multiple classes (not nested), then only the highlighting relevant - to that particular class will be displayed in the editor window as each class tree node is clicked. -
  • -
  • - Excluding the My namespace is now done through the Exclusions feature. -
  • -
  • - Options dialog can be displayed using the F4 shortcut key. -
  • -
  • - Removed last remnants of "non VS.Net standard colors" from the C# ICSharpCode TextEditor template. -
  • -
  • - Make the GUI naming consistent to correctly reference "sequence points" rather than "lines" and "unvisited" - rather than "uncovered". -
  • -
  • - Removed "Edit in VS.Net" from the View menu. -
  • -
  • - Changed NCoverExplorer main form icon to one that includes 32x32 sizes so Alt-Tab switching looks - better than upscaled 16x16 icon. -
  • -
  • - User is now prompted to remove a non-existent coverage file from the "Recent" files list rather than - automatically being removed. -
  • -
-

The following bug fixes were made:

-
    -
  • - Serializing the configuration settings was not flushing the stream - resulting sometimes in a blank settings file - preventing people from loading NCoverExplorer. Will now revert to default settings if an error occurs. -
  • -
  • - Displaying a source code file that has been modified to have less lines of code than at the time of the coverage run - will now display a user friendly message box. -
  • -
  • - Compensation made for NCover not reporting column information when profiling C++ code. NCoverExplorer will now - highlight the entire line rather than throwing an error. -
  • -
  • - In some circumstances properties were not highlighted consistently due to a bug in the property node expansion. -
  • -
  • - Coverage greater than 99.5% will no longer be rounded up to 100% in the display. It is instead shown as ">99.5%". -
  • -
  • - Extremely high visit counts will no longer overflow the visit count. -
  • -
  • - Statistics pane for a class will now always consistently show the property nodes grouped, rather than only - after the class node has been expanded in the tree. -
  • -
  • - Recent file menu would display incorrectly for files numbered from 10 onwards truncating first character. -
  • -
  • - Release notes & FAQ were always directed to website rather than local versions when NCoverExplorer was started - from TestDriven.Net. -
  • -
-
- -

v1.3.1 - Feb 15th 2006

-

Bundled with TestDriven.Net from build 2.0.1435.

-

The following new features were introduced:

-
    -
  • - Namespaces are now "flattened" by default in the tree. This looks like the ClassView - browser in VS.Net 2005 (or Lutz Roeder's Reflector). You can retain the nested look by changing it in the View->Options dialog. -
  • -
  • - If you use the original "nested" namespace style (like the VS.Net 2003 class browser), then inner namespaces will now be - listed at the top of each branch with the classes listed underneath which is less confusing to navigate. -
  • -
  • - Option to exclude the "My" namespace for VB.Net projects (for use with with BCL 2.0 & NCover 1.5.x). -
  • -
  • - Right-click menu option on coverage tree (shortcut ctrl+R) to "Remove From Results" that selected node - and all it's children. Will force the coverage values to be recalculated. Intended for use where - you have undesired assemblies, namespaces, classes or methods included in the report that are skewing your - coverage results and you want them removed. -
  • -
  • - Option to specify a satisfactory coverage threshold as a number of lines instead/as well as a percentage. - If either of the conditions are met the node is coloured differently (provided the coverage is not zero). -
  • -
  • - Colours can now be customised for both the source code highlighting and the nodes in the tree. -
  • -
  • - Collapse all nodes context menu option on the coverage tree control (shortcut ctrl-A). Equivalent to reloading - the coverage file (but would preserve any changes you have made such as removing nodes). -
  • -
  • - By default the NCoverExplorer now attempts to restore your currently selected node/caret position after - reloading a coverage.xml file (either F5 or by execution of another "Test With Coverage" command in TestDriven.Net). - You can turn off this behaviour in the View->Options dialog. -
  • -
  • - Statistics pane is now sortable by method name (default), visit count and line number. -
  • -
  • - Statistics pane now summarises all the methods and their visit counts when a class node is clicked. - Can be used as a basic form of method invocation counting for a fairly rudimentary level of profiling. - The colouring used is the same as that of the tree to visually assist in identifying methods invoked. -
  • -
-

The following minor changes were made:

-
    -
  • - Restructured the Options dialog to have a tabbed interface. -
  • -
  • - Renamed the "Show Visit Pane" menu option to "Show Statistics". -
  • -
  • - The statistics pane now includes the method name. Widths of the columns are remembered each time you close NCoverExplorer. -
  • -
  • - Statistics pane now has icons and colouring to match those of the associated nodes in the coverage tree. -
  • -
  • - Inner nested classes now nested internally in the tree under the parent class, sorted to the top. -
  • -
  • - Added FAQ, Release Notes and Blog website to the Help menu. -
  • -
-

The following bug fixes were made:

-
    -
  • - Source code files now loading with "Encoding.Default" rather than previous default of UTF-8. -
  • -
  • - Coverage highlighting not working correctly on multiple line statements. -
  • -
  • - Now handles partial classes and yield statements correctly. -
  • -
-
- -

v1.3 - Feb 6th 2006

-

Bundled with TestDriven.Net from build 2.0.1373d.

-

The following new features were introduced:

-
    -
  • - Launching from VS.Net using TestDriven.Net will now re-use the NCoverExplorer instance - opened from a previous "Test with... Coverage" click. Each VS.Net instance has it's own - instance of NCoverExplorer. -
  • -
  • - Added "Edit in VS.Net" functionality (keyboard shortcut ctrl+ E) for classes and methods. - Will navigate to source code in your IDE at same point where your cursor resides in NCoverExplorer. - Replaces and enhances previous "Open File" right-click option which has been removed. -
  • -
  • - Added "Expand Covered" functionality (keyboard shortcut ctrl + Q) - recurses through the child - nodes of the current node and expands all those with partial or complete coverage. Useful when - using in conjunction with TestDriven.Net for isolated unit testing. -
  • -
  • - Added "coverage file" node at the top of the tree showing total coverage across all modules/namespaces. -
  • -
  • - Group by module option (default) to assist with navigating coverage for large solutions. -
  • -
  • - Configuration information for NCoverExplorer now written to Local Settings rather than registry. -
  • -
  • - Increase default number of "recent files" to 10, with ability to alter in the Options dialog. -
  • -
  • - Reload of the current coverage file now has a shortcut key of F5. -
  • -
  • - Display the path to the currently loaded coverage file in the title bar. -
  • -
-

The following minor changes were made:

-
    -
  • - Performance enhancements to improve loading times further for large files. -
  • -
  • - Static constructors now renamed from "cctor" to ".cctor" so as to be sorted at the top. -
  • -
  • - Running NCoverExplorer for first time ever will use a better starting form position. -
  • -
  • - Removed configuration option for "nesting properties" - default remains the same of "true". -
  • -
  • - Source code refactoring into separate assemblies to facilitate unit testing. -
  • -
-
- -

v1.2 - Feb 1st 2006

-

First public release, bundled with TestDriven.Net from build 2.0.1341d.

-

The following new features were introduced:

-
    -
  • - Block style highlighting option for both visited and unvisited code. -
  • -
  • - Satisfactory coverage threshold. -
  • -
  • - Nesting of properties as nodes are expanded. -
  • -
  • - Further speed improvements for initial file parsing. -
  • -
-
- -

v1.1 - Jan 1st 2006

-

Speed improvements.

-
- -

v1.0 - Dec 17th 2005

-

First version created.

- - diff --git a/lib/NCover/MSVCP80.dll b/lib/NCover/MSVCP80.dll deleted file mode 100644 index f0b52ebf..00000000 Binary files a/lib/NCover/MSVCP80.dll and /dev/null differ diff --git a/lib/NCover/MSVCR80.dll b/lib/NCover/MSVCR80.dll deleted file mode 100644 index 53c005ef..00000000 Binary files a/lib/NCover/MSVCR80.dll and /dev/null differ diff --git a/lib/NCover/Microsoft.VC80.CRT.manifest b/lib/NCover/Microsoft.VC80.CRT.manifest deleted file mode 100644 index 6a8a0e23..00000000 --- a/lib/NCover/Microsoft.VC80.CRT.manifest +++ /dev/null @@ -1,8 +0,0 @@ - - - - - n9On8FItNsK/DmT8UQxu6jYDtWQ= - 0KJ/VTwP4OUHx98HlIW2AdW1kuY= - YJuB+9Os2oxW4mY+2oC/r8lICZE= - \ No newline at end of file diff --git a/lib/NCover/NCover.Console.exe b/lib/NCover/NCover.Console.exe deleted file mode 100644 index 80330112..00000000 Binary files a/lib/NCover/NCover.Console.exe and /dev/null differ diff --git a/lib/NCover/NCover.Console.exe.config b/lib/NCover/NCover.Console.exe.config deleted file mode 100644 index eb0a31af..00000000 --- a/lib/NCover/NCover.Console.exe.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/lib/NCover/NCover.Framework.dll b/lib/NCover/NCover.Framework.dll deleted file mode 100644 index a9539e68..00000000 Binary files a/lib/NCover/NCover.Framework.dll and /dev/null differ diff --git a/lib/NCover/NCoverExplorer.MSBuildTasks.dll b/lib/NCover/NCoverExplorer.MSBuildTasks.dll deleted file mode 100644 index fa6e2bd4..00000000 Binary files a/lib/NCover/NCoverExplorer.MSBuildTasks.dll and /dev/null differ diff --git a/lib/NCover/NCoverExplorer.MSBuildTasks.xml b/lib/NCover/NCoverExplorer.MSBuildTasks.xml deleted file mode 100644 index dfa8c4fe..00000000 --- a/lib/NCover/NCoverExplorer.MSBuildTasks.xml +++ /dev/null @@ -1,1092 +0,0 @@ - - - - NCoverExplorer.MSBuildTasks - - - - - Logging levels to use within NCover task. - - - - No logging. - - - Writes standard log output (Default). - - - Writes verbose log output. - - - - New element option introduced in NCover 1.5.7 for use with //x2 argument. - - - - - Legacy xml format that is the default. - - - - - New xml format introduced in NCover 1.5.7 that nests method nodes with class nodes. - - - - - Sort order for displaying the coverage results in the tree. - - - - Sort by name (default). (0) - - - Sort by name( down to class level) then by line within the class. (1) - - - Sort by coverage percentage ascending. (2) - - - Sort by coverage percentage descending. (3) - - - Sort by unvisited lines ascending. (4) - - - Sort by unvisited lines descending. (5) - - - Sort by visit count ascending. (6) - - - Sort by visit count descending. (7) - - - Sort by function coverage ascending. (8) - - - Sort by function coverage descending. (9) - - - - Filter styles that can be applied to the results. Filtered nodes are not excluded from the coverage - statistics. - - - - No filter applied. (0) - - - Hide unvisited nodes. (1) - - - Hide 100% fully covered nodes. (2) - - - Hide nodes exceeding coverage threshold. (3) - - - - Potential report types. - - - - None. (0) - - - Modules summary only. (1) - - - Modules summary followed with a namespaces by module summary. (3) - - - Modules summary followed with a classes by namespace summary. (4) - - - Modules summary followed with a classes by namespace summary showing function coverage. (5) - - - - Common utility functions for working with NCover. - - - - - Registry key for registering NCover manually - this will all become unnecessary in future versions of NCover (post 1.5.5) hopefully. - - - - - Builds the temp settings XML file for NCover. - - The version. - The ncover path. - The settings file. - The command line exe. - The command line args. - The working directory. - The assembly list. - The assembly files. - The coverage file. - The log level. - The log file. - The exclude attributes. - If set to true profile IIS. - The profile service. - The XML format to write out (new feature in 1.5.7). - Name of the profiled process. - - Command line switch necessary for passing as an argument. - - - - - Creates the command line arguments. - - The version. - The ncover path. - The command line exe. - The command line args. - The working directory. - The assembly list. - The coverage file. - The log level. - The log file. - The exclude attributes. - if set to true [profile IIS]. - The profile service. - if set to true include formatting. - Whether to register CoverLib.dll. - The command line format token. - - - - - Registers the NCover coverlib.dll by writing directly into the registry under HKCU. - Keeps a reference count so only register if only NCover task currently running. - - - - - Unregisters the NCover coverlib.dll - Keeps a reference count so only unregister if last NCover task currently running. - - - - - Find path to NCover console and retrieve the version info. - - - - - Reads the file contents and returns as a string. - - - - - Build the Xml .ncoversettings file to pass to the NCover.Console executable using NCover 1.3.3 syntax. - - - - - Write assembly names as a semi-colon delimited unique assembly name list. - Assembly names do not have extensions (how NCover requires them) to match how the - CLR identifies them when being profiled. - - - - - Build the Xml .ncoversettings file to pass to the NCover.Console executable using NCover 1.5 syntax. - - - - - Writes assembly names as separate Assembly nodes in the settings file. Seems to be a - difference in how NCover 1.5.4 onwards handles from previous versions in the xml. - - - - - Build a command line using NCover 1.3.3 syntax. - - - - - Build a command line using NCover 1.5.x syntax. - - - - - Creates the necessary HKCU entries for the NCover coverlib.dll. - - The ncover path. - - - - Handles a control-C style event so we can cleanup our refcount. - - Type of control exit. - Whether to cancel the event. - - - - Utility class to scan for an executable in all available paths. - Based on CodeProject article at: http://www.codeproject.com/csharp/winwhich.asp - - - - - Initializes a new instance of the class. - - - - - Return the version information for the executable located at this path. If executable - not found at this location (e.g. not a fully qualified path) does a path search to see - if it can be found anywhere else. - - Path to executable to find. - Whether to throw exception if not found. - Version number in format Major.Minor.Build - - - - Searches for the specified executable name in all available paths. - - Name of the executable. - - - - - Form the regular expression string for the matching file. - - The name of the executable - string that is the regex pattern. - - - - MSBuild task for automating NCoverExplorer.Console. - Using this task you can merge coverage files from NCover, produce xml coverage reports for use - with CruiseControl.Net, produce html report files directly, fail automated builds if coverage - thresholds are not met and apply a range of detail to the reports produced such as sorting, - filtering and coverage exclusions. - - - This example shows producing an xml coverage report at Module/Namespace/Class detail level for - inclusion on a CC.Net build server. You would add a merge file task in the publishers section - of your CC.Net project file to merge in this "CoverageSummary.xml" file so that it can be - transformed by the NCoverExplorer xsl stylesheets you have copied into the CC.Net folder. Here - we have set a satisfactory coverage threshold at 80%. - - - - - - - ]]> - - - - This example shows producing an html function coverage report, excluding the test assemblies. The - assemblies excluded are being displayed at the bottom of the report. Note also that this time - the ReportType is specified by its enum name rather than numeric value - they are interchangable. - We have also "inlined" the "CoverageFiles" from the ItemGroup above to show this can be done. - - - - - Assembly - *.Tests - - - - - - ]]> - - - - This example shows producing an html module class summary coverage report with exclusions as above. - This time we have added applying specific sorting and filtering criteria. This report will show all - classes that do not have 100% coverage, sorted within their namespaces by descending coverage %. We - have also "inlined" the exclusions - - - ]]> - - - - This example shows the merging capability to produce a consolidated merge file from multiple - coverage test runs. The results are being stored in a single "MyApp.CoverageMerged.xml" file. - Note that you could additionally apply coverage exclusions at this point. Merging files can - be useful if your testing process requires multiple coverage runs and you want a single archive - which consolidates the results. - - - - - - - ]]> - - - - This example shows failing a build if the overall coverage % does not meet our threshold, without - producing a coverage report. - - - ]]> - - - - This example shows failing a build if either the overall coverage % does not meet our threshold, or - if one of the individual module thresholds is not met. Note that the ModuleThresholds - could have been "inlined" (just showing the MSBuild flexibility to place in a separate group). - - - - - - - - ]]> - - - - This example shows using virtually the whole range of attributes. Shown below is failing a build - if not reaching the overall or module level coverage thresholds. The results of merging multiple - NCover files together are stored as a separate file. We are producing xml and html Namespace per - module summary reports (with the exclusions show in the footer). Note that the module thresholds - will also be used in the reports. The reports are sorted by name with no filter applied. - We are excluding test assemblies and anything in a presentation layer namespace. - - - - - - - - - Class - MyApp.SomeNamespace.SomeClass - - - Namespace - MyApp\.(\w*\.)? - true - - - - - - ]]> - - - - - - Initializes a new instance of the class. - - - - - Validate the parameters supplied to this task. - - true if parameters are valid, false otherwise. - - - - Executes the task. - - if the task ran successfully; otherwise . - - - - Returns a string value containing the command line arguments to pass directly to the executable file. - - - A string value containing the command line arguments to pass directly to the executable file. - - - - - Logs the starting point of the run to all registered loggers. - - A descriptive message to provide loggers, usually the command line and switches. - - - - Returns the fully qualified path to the executable file. - - - The fully qualified path to the executable file. - - - - - Determine the path to NCoverExplorer. Either the user can specify it in the arguments to the task, - or we look in the registry, program files and finally just assume it is in the path. - - - - - Return a temporary filename for the config file for executing NCoverExplorer.Console. - - Configuration filename. - - - - Legacy NCoverExplorer.Console is considered prior to 1.4.0 (as 1.4.0 was when the settings - file format was changed). - - NCoverExplorer.Console.exe path. - true if version is prior to 1.4.0 - - - - Builds a temporary NCoverExplorer configuration file which we can pass in the command line. - We require this as the command line itself does not directly support all the argument combinations. - - Name of the settings file. - - - - Builds a temporary NCoverExplorer configuration file which we can pass in the command line. - We require this as the command line itself does not directly support all the argument combinations. - - Name of the settings file. - - - - The coverage exclusions have been inlined as type=pattern semi-colon delimited pairs. - Break apart and write to the temp config file. - - Current xml output stream. - - - - The coverage exclusions have been inlined as type=pattern semi-colon delimited pairs. - Break apart and write to the temp config file. - - Current xml output stream. - - - - Iterate through the module thresholds and write their values into the configuration file. - - Current xml output stream. - - - - Build command line for passing to legacy NCoverExplorer.Console versions. - - - - - Removes generated settings file after process has run. - - - - - Gets or sets the output directory for the reports. - - The output dir. - - - - Whether to fail the task if the minimumCoverage threshold is not reached on any module. - NCoverExplorer console application will return exit code 3. - - - - - Whether to fail the task if the minimumCoverage threshold is not reached on total coverage. - NCoverExplorer console application will return exit code 3. - - - - - The minimum coverage percentage to be used with the FailMinimum and FailCombinedMinimum options. - - - - - Gets or sets the name of the temporary XML config file being generated for coverage. - - The name of the XML config. - - - - The satisfactory coverage percentage for display in the reports. - - - - - The .config filename for containing any custom exclusions and parameters. - - - - - The type of report to produce (use numeric value or string name). - 0 / None, 1 / ModuleSummary, 3 / ModuleNamespaceSummary, - 4 / ModuleClassSummary, 5 / ModuleClassFunctionSummary - - - - - The sorting if any to apply (use numeric value or string name). - 0 / Name, 1 / ClassLine, - 2 / CoveragePercentageAscending, 3 / CoveragePercentageDescending, - 4 / UnvisitedSequencePointsAscending, 5 / UnvisitedSequencePointsDescending, - 6 / VisitCountAscending, 7 / VisitCountDescending, - 8 / FunctionCoverageAscending, 9 / FunctionCoverageDescending - - - - - The filtering if any to apply (use numeric value or string name). - 0 / None, 1 / HideUnvisited, 2 / HideFullyCovered, 3 / HideThresholdCovered - - The string or textual enum value. - - - - The filename for generating an xml report. - - - - - The filename for generating an html report. - - - - - The filename for the merge of the coverage xml files. - - - - - Determines whether to include the coverage exclusions in the report. The default is - . - - - - - Used to select the coverage xml files to merge into the report. - - - - - Coverage exclusions to apply, in one of two formats: - They can be semi-colon delimited "Type=Pattern" pairs, e.g. "Assembly=*.Tests;Class=My.*". - Alternatively they can be defined in a property group as a <CoverageExclusions> section. - See the examples for both formats. If you want to use regular expressions then you must - use the <PropertyGroup> approach. - - - This example shows a range of coverage exclusions using the <PropertyGroup> approach. - Note the optional use of wildcard characters in the pattern. You could set the exclusions - up within the gui and then paste the xml directly from the NCoverExplorer.config file located - in C:\Documents and Settings\user\Application Data\Gnoso\NCoverExplorer\ - - - - - Assembly - *.Tests - - - Namespace - MyNamespace.* - - - Class - MyNamespace.MyClass - - - Method - MyNamespace.MyClass.MyMethod - - - Namespace - MyApp\.(\w*\.)? - true - - - - - - ]]> - - - - This example shows inlining of three of the same exclusions above. Note with this approach - it is not possible to use regular expressions. - - - ]]> - - - - - - Module thresholds to apply, in format "AssemblyName=Percentage", e.g. "MyApp.Core=75" - - - - - Gets the name of the executable file to run. - - - The name of the executable file to run. - - - - Gets the with which to log errors. - - - The with which to log errors. - - - - MSBuild task for automating NCover.Console.exe, with NCover 1.5.x support. Note that this task - will self register CoverLib.dll by default using the registry (does not require local admin). - - - This example shows the standard profiling using NCover for standard nunit tests with minimal arguments. - Defaults are with logging to coverage.log, profiling all assemblies, output filename of coverage.xml and this - example specifies a path to where to find ncover.console.exe. - - - ]]> - - - - If you are using TypeMock, you may experience issues with the registration of coverlib.dll conflicting - due to overwriting the registered profiler. You should add the "registerProfiler" attribute below and set it to false. - - - ]]> - - - - This example for NCover 1.5.8 shows profiling a process which is launched by another process. - - - ]]> - - - - This example shows using an assembly list as ; delimited names rather than using the ability - of the NCover task to dynamically build from a list of files (shown in following example). - - - ]]> - - - - This example shows the standard profiling using NCover 1.5.x for a Windows application, specifying a coverage - exclusion, verbose logging to a named file, specifically named log and output xml files. It also shows - coverage exclusion attributes, overriding the NCover location to run from and a way of listing assemblies - to be included in the profiled NCover results. - - - - - - - ]]> - - - - - - Initializes a new instance of the class. - - - - - Executes the task. - - if the task ran successfully; otherwise . - - - - Returns a string value containing the command line arguments to pass directly to the executable file. - - - A string value containing the command line arguments to pass directly to the executable file. - - - - - Returns the fully qualified path to the executable file. - - - The fully qualified path to the executable file. - - - - - Logs the starting point of the run to all registered loggers. - - A descriptive message to provide loggers, usually the command line and switches. - - - - Check that we have a valid path to NCover. - - - - - Removes generated settings file after process has run. - - - - - Convert the MSBuild specific ITaskItem[] to a string array for use by NCoverUtilities. - - - - - - - The command line executable to be launched by NCover (such as nunit-console.exe). - - - - - The arguments to pass to the command line executable to be launched by NCover (such as nunit-console.exe). - - - - - The filename for the output coverage.xml file (default). - - - - - What level of NCover logging to provide. Values are "Normal" (default) and "Verbose". - Due to a bug in NCover 1.5.4 "Quiet" will result in NCover stopping abnormally - hence has been - defaulted to be "Normal" until the bug is fixed. - - - - - Gets or sets the logfile name to write to if logLevel is set to anything other than "Quiet". The default - is "coverage.log". - - - - - Gets or sets the working directory for the command line executable. - - - - - If coverage exclusion attributes have been applied (NCover 1.5.4 onwards) specify the full namespace - to the attribute including the "Attribute" suffix - e.g. "CoverageExcludeAttribute" if defined in no - namespace. Separate multiple attributes with semi-colons. - - - - - Determines whether to profile under IIS. Default value is . - - - - - The service name if profiling a windows service. - - - - - Alternative to specifying assembly names - you can instead list them as you would on the - command line as a semi-colon delimited list without any suffixes or paths. - - - - - Used to specify the assemblies to be profiled. Alternative to the AssemblyList property, - where instead you wat the list to be dynamically built using an itemgroup, for instance - to match all assemblies against a wildcard. The NCover task will take care of stripping - off the suffixes etc. - - - - - Determines whether to register NCover CoverLib.dll on each run. The default is true. You - would set this to false if using TypeMock due to a conflict in registered profilers. - If set to true, the NCover task uses a reference counting approach to minimise the chance - of issues when simultaneous builds. - - - - - Gets or sets a value indicating the xml output format to write (new in NCover 1.5.7). - Default value is "Xml1", alternat option is "Xml2" which nests method nodes within class - nodes. Note however that "Xml2" is for future use and is not yet supported by NCoverExplorer - as of version 1.3.6. - - - - - Gets or sets the profiled process module name. Use this argument when the executable being - launched is not the actual process you want to profile coverage for. - - - - - Gets the name of the executable file to run. - - - The name of the executable file to run. - - - - Gets the with which to log errors. - - - The with which to log errors. - - - - Create a .nunit project file for all the test assemblies matching the specified pattern. - This should be created in the bin folder where your test assemblies are located so that - the assemblies are within the AppDomain path. - The .nunit file can then be used by NUnit or NCover based tasks. - - - - Create a .nunit project file in output bin folder for a specified test assembly. - - - - - $(MSBuildProjectDirectory)\Build - - - - - - - - ]]> - - - - - Create a .nunit project file in output bin folder with an associated App.Config file for - all test assemblies matching a pattern. - - - - - - - - ]]> - - - - - - Default constructor. - - - - - Build the contents of the .nunit file using the test assemblies matching this pattern. - - - - - Create a .nunit project file listing the test assemblies. - - Full filename of the .nunit file. - Fileset containing the test assemblies. - Optional path to App.Config file to include in project. - Optional path to the nunit app base, when included full paths to each assembly (relative to the appbase) are included - - - - The nunit project file to create. - - - - - Optional path to an App.Config file to be specified in the .nunit project file. - - - - - Optional path to the nunit app base, when included full paths to each assembly - (relative to the appbase) are included. - - - - - Used to select the test assemblies to be included in the .nunit project. - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to Contents of config file:\r\n{0}. - - - - - Looks up a localized string similar to Contents of config file:\r\n{0}. - - - - - Looks up a localized string similar to Deleting config file: {0}. - - - - - Looks up a localized string similar to This line cannot be parsed: '{0}'. Coverage exclusions should be in format: Type=Pattern (e.g. 'Assembly=*.Tests'). - - - - - Looks up a localized string similar to Could not find the registry key for NCoverExplorer indicating the program location (set by installing with TestDriven.Net). Please specify the full path to NCoverExplorer.Console.exe using the ToolPath attribute.. - - - - - Looks up a localized string similar to Could not find NCover.Console.exe in C:\Program Files\NCover\. Specify an alternate path using the ToolPath attribute on your NCover target.. - - - - - Looks up a localized string similar to Detected NCover.Console v{0} in {1}. - - - - diff --git a/lib/NCover/NCoverFAQ.html b/lib/NCover/NCoverFAQ.html deleted file mode 100644 index f63b8f13..00000000 --- a/lib/NCover/NCoverFAQ.html +++ /dev/null @@ -1,429 +0,0 @@ - - - - - - - - - - -

NCover FAQ

-

If you have questions that this document does not address, contact - Peter Waldschmidt or try the NCover Forums.

-

1. What is code coverage analysis?

-

A code coverage analyzer monitors your code at runtime and - records information about which lines of code were executed. NCover shows each - sequence point in your application along with the number of times that point - was executed. Sequence points are generated by the compiler and stored in the - debug information (.pdb) files. A sequence point basically corresponds to a - single program statement (often a line of code) in your high-level language.

-

2. Why would I want to do code coverage analysis?

-

Unit test suites are often used as a quality tool during the - development process to keep the codebase stable as it changes and expands. - Tools such as NUnit are often used to run and - report on the test suites. However, when implementing unit testing in your - build process, you have no way of knowing how much of your code the unit tests - are actually testing. This is where code coverage comes in. You can run NUnit - within NCover and use the code coverage report to determine which code was not - tested by that particular test suite.

-

3. What versions of the CLR does NCover support?

-

- NCover 1.5.x requires the .NET framework version 2.0.50727 to be installed; however, - the application being profiled can be written against any shipping version of the - framework. NCover - has been tested profiling coverage of .NET 2.0, .NET 1.1 and .NET 1.0 applications.

-

4. Which version of NCover should I install?

-

- If you have the .NET 2.0 framework installed on your machine then you should use - the latest NCover version available. NCover as of version 1.5 can profile .NET 2.0, 1.1 and 1.0 applications.

-

- For development teams who do not have the .NET framework 2.0 installed but do have - the .NET framework version 1.1.4322, you can - try NCover 1.3.3. Note however that this version is no longer supported as - it has a number of known issues and limitations.

-

5. What is the command line syntax for NCover?

-

Here is the usage info from the NCover command line (for NCover versions from 1.5.6 - only):

-
NCover.Console [<command line> [<command args>]]
-               [//svc <service name>]
-               [//iis]
-               [//a <assembly list>]
-               [//w <working directory>]
-               [//ea <exclusion list>]
-               [//reg]
-               [//x <xml output file>]
-               [//s [<settings file>]] [//r [<settings file>]]
-               [//v] [//q] 
-               [//l <log file>]
-
-//svc  For profiling windows services
-//iis  For profiling web applications
-
-//a    List of assemblies to profile separated by semi-colons i.e. "MyAssembly1;MyAssembly2". Do not include paths or suffixes.
-//w    Working directory for profiled application 
-//ea   List of attributes marking classes or methods to exclude from coverage 
-
-//reg  Register profiler temporarily for user. (helps with xcopy deployment) 
-//x    Specify coverage output file. (default: coverage.xml).
-//pm   Specify name of process to profile (i.e. myapp.exe)
-
-//s    Save settings to a file (defaults: NCover.Settings) 
-//r    Use settings file, overriding other settings (default: NCover.Settings) 
-
-//l    Specify profiler log file (default: coverage.log).
-//q    No logging (quiet) 
-//v    Enable verbose logging (show instrumented code)
-        
-
    -
  • <command line> - This argument specifies the command-line of the .NET application - you want to analyze. - Any command line arguments not starting with // will be passed - through to that application. NCover will profile the running application until it has exited. See below for examples.
  • //svc - This option is an alternative to the <command line> - for profiling windows services, which cannot be run directly as executables. NCover - will start the service (stopping it first if already running) and profile coverage - until the windows service is stopped.
  • -
  • //iis - This option is an alternative to the <command line> for profiling - web applications. NCover will start the IISAdmin and W3C - services (stopping first if currently running) and profile coverage until the IISAdmin - service is stopped.
    -
  • -
  • //a - This command-line argument specifies the assemblies that you want to analyze. - NCover can only analyze assemblies that have .pdb files included with them. If - you do not specify the //a argument, NCover will attempt to analyze every loaded - assembly that has debug information available. Note that the assembly name arguments are - the module name within the assembly, not the physical file name. e.g. "MyAssembly" - rather than "MyAssembly.dll".
  • //w - If the application being profiled requires the - working directory to be set to something other than the current directory you are - executing the command line from then you can override it with this argument.
  • -
  • //ea - You can choose to exclude classes and methods - from coverage statistics by defining .NET attribute(s) and applying it to the affected - code. When using this argument you must specify the full type namespace of these - attribute(s) separated by semi-colons. See below for an example.
    -
  • -
  • //reg - NCover requires a COM registration of the CoverLib.dll assembly containing - the profiler, which is performed automatically by the default .msi installation. - If you require an xcopy style deployment of NCover like many other .NET tools, then - you can use this argument which will temporarily register the profiler while performing - coverage. This feature was added in NCover 1.5.6.
  • -
  • //x - The output of NCover is an xml file (example below). Use this argument to - specify an alternate filename to "coverage.xml" in the current directory.
    -
  • -
  • //pm - This setting tells NCover to ignore processes that don't have the specified process module name. - This is the name of the executable (i.e. myapp.exe). This setting is useful in cases, where your NCover - command spawns a series of child processes. Using this setting will help NCover determine which process to profile. -
  • -
  • //s - You may find it more convenient to use a settings file rather than specifying - a long list of command line arguments for running NCover. If you get the NCover - command line working as you would like it and then use the //s argument it will - save the required arguments as an xml file that can then be used by the //r argument - below.
  • -
  • //r - For use when you have used //s to construct an NCover settings file containing - your command line arguments. e.g. "ncover.console.exe //r NCover.Settings"
    -
  • -
  • //l - The coverage log file can provide an insight if the desired coverage output - is not obtained. Useful information you may find to assist you includes which assemblies - were loaded by NCover, their file paths and which of those it found the .pdb build - symbols for. Use this argument to specify an alternative log file name or location - to coverage.log in the current directory.
  • -
  • //q - Suppresses writing the coverage.log file.
  • -
  • //v - This command-line argument makes the profiler emit all the original IL and - modified IL instructions to the coverage log. This is useful for debugging - purposes. Beware that this can make your coverage log file very large! -
  • -
-

6. Does NCover required a special compilation step for my code?

-

No. Some code coverage tools change your source code and force - you to recompile it into a special build.  NCover is designed to work - on shipping code.  NCover uses the .NET Framework profiling API to monitor - your code. It does require build symbols, but can be run on release code - without any modifications.

-

7. How does NCover work?

-

NCover uses the .NET Framework profiler API to monitor an - application's execution. When a method is loaded by the CLR, NCover retrieves - the IL and replaces it with instrumented IL code.  NCover does not change - your original IL code, it simply inserts new code to update a visit - counter at each sequence point.  Upon - request, (usually after the .NET process has shut down) the profiler outputs statistics - to the coverage file. -

-

- 8. What is the output of NCover?

-

NCover generally writes out three files after analysis - completes. -

    -
  • - Coverage.log - This file is a log of the events and messages from the profiler - during the analysis process. Most of the time, error messages are recorded in - this log. If you enable verbose logging, the coverage log will contain - disassembly of the original and instrumented IL code.  Verbose logging is not recommended for - normal use.
  • - Coverage.xml - This file is the analysis output of NCover. You can see an - example of the output below. -
  • - Coverage.xsl - This file is a simple XML transformation that makes the XML - output easily readable. -
  • -
- Example XML output -
<method class="NCoverTest.ClassLoaded" name="HasDeadCode">
-    <seqpnt document="C:\Dev\Utilities\ncover\NCoverTest\NCoverTest.cs"
-            column="13" line="48" endcolumn="58" endline="48" visitcount="1" /> 
-    <seqpnt document="C:\Dev\Utilities\ncover\NCoverTest\NCoverTest.cs" 
-            column="13" line="49" endcolumn="22" endline="49" visitcount="1" /> 
-    <seqpnt document="C:\Dev\Utilities\ncover\NCoverTest\NCoverTest.cs" 
-            column="17" line="50" endcolumn="24" endline="50" visitcount="1" /> 
-    <seqpnt document="C:\Dev\Utilities\ncover\NCoverTest\NCoverTest.cs" 
-            column="13" line="51" endcolumn="48" endline="51" visitcount="0" /> 
-    <seqpnt document="C:\Dev\Utilities\ncover\NCoverTest\NCoverTest.cs" 
-            column="9" line="52"  endcolumn="10" endline="52" visitcount="0" /> 
-</method>
-
-

- Example transformed output -
-
NCoverTest.ClassLoaded.HasDeadCode
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Visit CountLineColumnEnd LineEnd ColumnDocument
148134858C:\Dev\Utilities\ncover\NCoverTest\NCoverTest.cs
149134922C:\Dev\Utilities\ncover\NCoverTest\NCoverTest.cs
150175024C:\Dev\Utilities\ncover\NCoverTest\NCoverTest.cs
051135148C:\Dev\Utilities\ncover\NCoverTest\NCoverTest.cs
05295210C:\Dev\Utilities\ncover\NCoverTest\NCoverTest.cs
-
-

Suggested usages of the coverage.xml output are to display it in the - NCoverExplorer gui with the source - code highlighted, to generate html reports, or to include it in your continuous build server reports such as CruiseControl.Net. - For more information on these options see below in the FAQ.

-

-

- 9. How do I use coverage exclusions?

-

- First you should define an attribute to markup your excluded code with. You will - likely want to put this in a common assembly to make it reusable, or indeed within - a "CommonAssemblyInfo.cs" that you include in all your application assemblies.

-

-
namespace MyNamespace {
-    class CoverageExcludeAttribute : System.Attribute { }
-}
-

- Apply the attribute to the C# classes and/or methods you wish to mark as excluded - from code coverage statistics:

-

-
    [CoverageExclude]
-    private void SomeMethodToExclude() {}    
-

- Finally, ensure you pass the full qualified attribute information in the NCover command line:

-

-
    NCover.Console MyApplication.exe //ea MyNamespace.CoverageExcludeAttribute    
-

- Note that if you are using the TestDriven.Net - VS.Net add-in to "Test with Coverage" it will automatically - pass through "//ea CoverageExcludeAttribute" - which you should define without a namespace like above. For further information refer to this - - blog entry.

-

- 10. Examples

-

- Coverage while running a simple executable until it exits:

-

-
    NCover.Console MyApplication.exe
-

- Coverage while running all the unit tests in an assembly using NUnit, profiling - all loaded assemblies with .pdb build symbols:

-

-
    NCover.Console nunit-console.exe MyApplication.Tests.dll
-

- Coverage of only a subset of loaded assemblies while running unit tests:

-

-
    NCover.Console nunit-console.exe MyApplication.Tests.dll //a MyApplication.Core;MyApplication.Utilities
-

- Coverage of a windows service. Stop the service to generate the coverage output:

-

-
    NCover.Console //svc MyServiceName
-

- Coverage of an ASP.Net application. Stop the IIS service to generate the coverage - output:

-

-
    NCover.Console //iis
-

- 11. Where can I get help or support?

-

- Your best approach is to browse the - NCover forums as well as the - blog by the author Peter Waldschmidt. If you cannot find a similar issue - mentioned feel free to post your query and perhaps someone can help.

-

- 12. How do I "xcopy deploy" NCover like my other build tools?

-

- Many developers prefer to have their build tools such as NUnit, NAnt etc stored - in source control in a Tools folder along with the source code. This ensures that - a new developer can obtain and build the application without having to install additional - tools on their own machines.

-

- NCover can also be deployed in this fashion. However the one gotcha with NCover - versus other tools is that the profiler within CoverLib.dll must be COM registered - on the local machine before you execute it. Prior to NCover 1.5.6 this was usually - achieved as part of your build script, which would call regsvr32 with the path to - the CoverLib.dll in your Tools folder. Alternatively the <ncover> NAnt and - MSBuild tasks described below will do this for you. As of NCover 1.5.6 you can also - use the //reg option in the command line arguments which will temporarily register - the profiler. Note that the //reg option will not work for IIS or Windows Service - profiling unless you are running NCover under the same Windows login account as - the IIS worker process, or your Windows Service.

-

- 13. How do I see my source code highlighted with the coverage results?

-

- NCoverExplorer is a gui and console-based - .NET application developed by Grant Drake. NCoverExplorer - parses the coverage.xml files output from NCover and displays the results integrated - with your source code. It also includes a number of additional features to merge, - filter, sort and generate html reports. The console version is - designed to be used as part of an automated build process. The support forums for - NCoverExplorer are located with the NCover ones at http://ncover.org/. 

-

- 14. How do I run NCover from within the Visual Studio.Net IDE?

-

- The TestDriven.Net add-in by - Jamie Cansdale offers a right-click capability within the IDE to execute - your unit tests with code coverage. The results of the NCover code coverage are - displayed with the bundled NCoverExplorer gui for analysis and reporting.

-

- 15. How do I run NCover from a NAnt or MSBuild task?

-

- You can use an <exec> task with NAnt - or an <Exec> task with MSBuild. Alternatively you may want to use the custom - <ncover> task for NAnt or <NCover> task for MSBuild developed by Grant - Drake for a more developer friendly syntax. The source code, compiled assemblies - and documentation are located in the NCoverExplorer.Extras.zip available from http://ncoverexplorer.org/.

-

- 16. How do I include NCover output in my CruiseControl.Net build reports?

-

- CruiseControl.Net is a continuous integration - build server which offers web-based reporting of the outputs of a build such as - unit test results and code coverage reporting. The default CruiseControl.Net installation - includes a basic stylesheet which works in combination with the standard coverage.xml - formatted output. So all you need to do is include the execution of NCover as part - of your build, then add a CruiseControl.Net merge file publisher task to integrate - the coverage.xml results into the build output.

-

- An improvement on the above to display more attractive and powerful reports as well - as minimize the build log size is to use NCoverExplorer. The NCoverExplorer.Console.exe - is designed to produce a more concise xml report summary that is combined with an - alternate xsl stylesheet for CruiseControl.Net. You can find more information and - screenshots in this - blog entry - all the necessary tasks, examples and documentation are located - within NCoverExplorer.Extras.zip available from - http://ncoverexplorer.org/

-

- 17. How do I merge multiple NCover coverage.xml results?

-

- You can can use NCoverExplorer to merge the results of multiple coverage runs. For - more information refer to this - blog entry.

-

- 18. Troubleshooting: Why is my coverage.xml file empty?

-
    -
  • If using the command-line, did you COM register CoverLib.dll (or use the //reg option - from NCover 1.5.6)?
  • -
  • Did you generate build symbol files (.pdbs) for the profiled application?
  • -
  • If using the //a option, did you correctly list just the assembly names without - paths or .dll suffixes?
  • -
-

- 19. Troubleshooting: I have coverage.xml output but my XYZ assembly is not included in it?

-
    -
  • NCover will only profile loaded assemblies - did your code execution path while - under coverage force that assembly to be loaded (e.g. by loading a type or calling - a method in that assembly)? 
  • -
  • Did you generate build symbol files (.pdb files) for the missing assembly?
  • -
  • If using the //a option, did you correctly list the assembly names including the - one that is missing?
  • -
  • Can you see information about the assembly being loaded within the coverage.log? - Is the correct assembly being loaded (check the path) - if you have a version in - the GAC it may possibly prevent the .pdb file from being loaded.
  • If using the NCoverExplorer gui, have you got a coverage exclusion defined which - is hiding it from the display?
  • -
-

- 20. Troubleshooting: After running NCover my coverage.log says "Failed to load symbols for module XYZ"?

-
    -
  • This message means that no .pdb build symbol file was found for that assembly so - it cannot be profiled for code coverage. If that assembly is part of the .NET framework - for instance like System.Data.dll, then this is an expected message and should not - cause concern. 
  • If however the assembly belongs to your application, did you generate the - build symbol files (.pdb files) for it?
  • -
-

- 21. Troubleshooting: I get a "Profiled process terminated. Profiler connection not - established" message?

-
    -
  • If using the command-line, did you COM register CoverLib.dll (or use the //reg option - from NCover 1.5.6)?
  • Are you running Windows XP 64-bit? You may want to take a look at - this thread
-

- 22. Troubleshooting: My coverage exclusions are not working?

-
    -
  • Have you put the full namespace type name to the exclusion including the Attribute suffix in the //ea argument? See the "How - do I use coverage exclusions?" question above.
-   - - diff --git a/lib/System.Data.SqlServerCe.dll b/lib/System.Data.SqlServerCe.dll deleted file mode 100644 index 33da7e95..00000000 Binary files a/lib/System.Data.SqlServerCe.dll and /dev/null differ diff --git a/lib/log4net.dll b/lib/log4net.dll deleted file mode 100644 index a70cd2b8..00000000 Binary files a/lib/log4net.dll and /dev/null differ diff --git a/local.properties-example b/local.properties-example deleted file mode 100644 index 473e1f60..00000000 --- a/local.properties-example +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/nuget/Package.nuspec b/nuget/Package.nuspec deleted file mode 100644 index a12d8a07..00000000 --- a/nuget/Package.nuspec +++ /dev/null @@ -1,22 +0,0 @@ - - - - DotNetProjects.Migrator - $version$ - DotNetProjects - DotNetProjects - https://www.mozilla.org/MPL/1.1/ - https://github.com/dotnetprojects/Migrator.NET - false - Framework Assembly for DotNetProjects.Migrator - Copyright 2015 - - - - - - - - - - diff --git a/nuget/nuget.exe b/nuget/nuget.exe deleted file mode 100644 index 324daa84..00000000 Binary files a/nuget/nuget.exe and /dev/null differ diff --git a/nuget/pack.ps1 b/nuget/pack.ps1 deleted file mode 100644 index 418a4a4f..00000000 --- a/nuget/pack.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -$root = (split-path -parent $MyInvocation.MyCommand.Definition) + '\..' - -Write-Host "root: $root" - -$version = [System.Reflection.Assembly]::LoadFile("$root\src\Migrator\bin\Migrator\Release\DotNetProjects.Migrator.dll").GetName().Version -$versionStr = "{0}.{1}.{2}" -f ($version.Major, $version.Minor, $version.Build) - -Write-Host "Setting .nuspec version tag to $versionStr" - -$content = (Get-Content $root\NuGet\Package.nuspec) -$content = $content -replace '\$version\$',$versionStr - -$content | Out-File $root\nuget\Package.compiled.nuspec - -& $root\NuGet\NuGet.exe pack $root\nuget\Package.compiled.nuspec \ No newline at end of file diff --git a/src/Migrator.Console/Boot.cs b/src/Migrator.Console/Boot.cs deleted file mode 100644 index ea223b11..00000000 --- a/src/Migrator.Console/Boot.cs +++ /dev/null @@ -1,30 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; - -namespace Migrator.MigratorConsole -{ - /// - /// Console application boostrap class. - /// - public class Boot - { - [STAThread] - public static int Main(string[] argv) - { - var con = new MigratorConsole(argv); - return con.Run(); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Console/Migrator.Console-vs2008.csproj b/src/Migrator.Console/Migrator.Console-vs2008.csproj deleted file mode 100644 index 28fd6649..00000000 --- a/src/Migrator.Console/Migrator.Console-vs2008.csproj +++ /dev/null @@ -1,105 +0,0 @@ - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {FBE3A83A-D0F8-4D72-AF8D-9EF772569A31} - Exe - Properties - Migrator.Console - Migrator.Console - - - 2.0 - - - true - http://localhost/Migrator.Console/ - true - Web - true - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - true - false - true - MigratorDotNet.snk - - - true - full - false - bin\Migrator.Console\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Migrator.Console\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - {1FEE70A4-AAD7-4C60-BE60-3F7DC03A8C4D} - Migrator-vs2008 - - - {5270F048-E580-486C-B14C-E5B9F6E539D4} - Migrator.Framework-vs2008 - - - - - False - .NET Framework Client Profile - false - - - False - .NET Framework 2.0 %28x86%29 - true - - - False - .NET Framework 3.0 %28x86%29 - false - - - False - .NET Framework 3.5 - false - - - False - .NET Framework 3.5 SP1 - false - - - - - - - \ No newline at end of file diff --git a/src/Migrator.Console/Migrator.Console-vs2010.csproj b/src/Migrator.Console/Migrator.Console-vs2010.csproj deleted file mode 100644 index 1e787332..00000000 --- a/src/Migrator.Console/Migrator.Console-vs2010.csproj +++ /dev/null @@ -1,115 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {FBE3A83A-D0F8-4D72-AF8D-9EF772569A31} - Exe - Properties - Migrator.Console - Migrator.Console - - - 3.5 - - - true - true - MigratorDotNet.snk - v4.0 - http://localhost/Migrator.Console/ - true - Web - true - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true - - - - true - full - false - bin\Migrator.Console\Debug\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - - - pdbonly - true - bin\Migrator.Console\Release\ - TRACE - prompt - 4 - AllRules.ruleset - - - - - - - - - - - - - - - - {d58c68e4-d789-40f7-9078-c9f587d4363c} - DotNetProjects.Migrator.Providers - - - {1FEE70A4-AAD7-4C60-BE60-3F7DC03A8C4D} - DotNetProjects.Migrator - - - {5270F048-E580-486C-B14C-E5B9F6E539D4} - DotNetProjects.Migrator.Framework - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 2.0 %28x86%29 - true - - - False - .NET Framework 3.0 %28x86%29 - false - - - False - .NET Framework 3.5 - false - - - False - .NET Framework 3.5 SP1 - false - - - - - - - - \ No newline at end of file diff --git a/src/Migrator.Console/MigratorConsole.cs b/src/Migrator.Console/MigratorConsole.cs deleted file mode 100644 index 50af4973..00000000 --- a/src/Migrator.Console/MigratorConsole.cs +++ /dev/null @@ -1,214 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.Collections.Generic; -using System.Reflection; -using Migrator.Framework; -using Migrator.Providers; -using Migrator.Tools; - -namespace Migrator.MigratorConsole -{ - /// - /// Commande line utility to run the migrations - /// - /// - public class MigratorConsole - { - readonly string[] args; - string _connectionString; - bool _dryrun; - string _dumpTo; - bool _list; - long _migrateTo = -1; - string _migrationsAssembly; - ProviderTypes _provider; - bool _trace; - string _defaultSchema; - - /// - /// Builds a new console - /// - /// Command line arguments - public MigratorConsole(string[] argv) - { - args = argv; - ParseArguments(argv); - } - - /// - /// Run the migrator's console - /// - /// -1 if error, else 0 - public int Run() - { - try - { - if (_list) - List(); - else if (_dumpTo != null) - Dump(); - else - Migrate(); - } - catch (ArgumentException aex) - { - Console.WriteLine("Invalid argument '{0}' : {1}", aex.ParamName, aex.Message); - Console.WriteLine(); - PrintUsage(); - return -1; - } - catch (Exception ex) - { - Console.WriteLine(ex); - return -1; - } - return 0; - } - - /// - /// Runs the migrations. - /// - public void Migrate() - { - CheckArguments(); - - Migrator mig = GetMigrator(); - if (mig.DryRun) - mig.Logger.Log("********** Dry run! Not actually applying changes. **********"); - - if (_migrateTo == -1) - mig.MigrateToLastVersion(); - else - mig.MigrateTo(_migrateTo); - } - - /// - /// List migrations. - /// - public void List() - { - CheckArguments(); - - Migrator mig = GetMigrator(); - List appliedMigrations = mig.AppliedMigrations; - - Console.WriteLine("Available migrations:"); - foreach (Type t in mig.MigrationsTypes) - { - long v = MigrationLoader.GetMigrationVersion(t); - Console.WriteLine("{0} {1} {2}", - appliedMigrations.Contains(v) ? "=>" : " ", - v.ToString().PadLeft(3), - StringUtils.ToHumanName(t.Name) - ); - } - } - - public void Dump() - { - CheckArguments(); - - var dumper = new SchemaDumper(_provider, _connectionString, _defaultSchema); - - dumper.DumpTo(_dumpTo); - } - - /// - /// Show usage information and help. - /// - public void PrintUsage() - { - int tab = 17; - Version ver = Assembly.GetExecutingAssembly().GetName().Version; - - Console.WriteLine("Database migrator - v{0}.{1}.{2}", ver.Major, ver.Minor, ver.Revision); - Console.WriteLine(); - Console.WriteLine("usage:\nMigrator.Console.exe provider connectionString migrationsAssembly [options]"); - Console.WriteLine(); - Console.WriteLine("\t{0} {1}", "provider".PadRight(tab), "The database provider (SqlServer, MySql, Postgre)"); - Console.WriteLine("\t{0} {1}", "connectionString".PadRight(tab), "Connection string to the database"); - Console.WriteLine("\t{0} {1}", "migrationAssembly".PadRight(tab), "Path to the assembly containing the migrations"); - Console.WriteLine("Options:"); - Console.WriteLine("\t-{0}{1}", "version NO".PadRight(tab), "To specific version to migrate the database to"); - Console.WriteLine("\t-{0}{1}", "defaultSchema ".PadRight(tab), "To specify the default schema"); - Console.WriteLine("\t-{0}{1}", "list".PadRight(tab), "List migrations"); - Console.WriteLine("\t-{0}{1}", "trace".PadRight(tab), "Show debug informations"); - Console.WriteLine("\t-{0}{1}", "dump FILE".PadRight(tab), "Dump the database schema as migration code"); - Console.WriteLine("\t-{0}{1}", "dryrun".PadRight(tab), "Simulation mode (don't actually apply/remove any migrations)"); - Console.WriteLine(); - } - - #region Private helper methods - - void CheckArguments() - { - if (_connectionString == null) - throw new ArgumentException("Connection string missing", "connectionString"); - if (_migrationsAssembly == null) - throw new ArgumentException("Migrations assembly missing", "migrationsAssembly"); - } - - Migrator GetMigrator() - { - Assembly asm = Assembly.LoadFrom(_migrationsAssembly); - - var migrator = new Migrator(_provider, _connectionString, _defaultSchema, asm, _trace); - migrator.args = args; - migrator.DryRun = _dryrun; - return migrator; - } - - void ParseArguments(string[] argv) - { - for (int i = 0; i < argv.Length; i++) - { - if (argv[i].Equals("-list")) - { - _list = true; - } - else if (argv[i].Equals("-trace")) - { - _trace = true; - } - else if (argv[i].Equals("-dryrun")) - { - _dryrun = true; - } - else if (argv[i].Equals("-version")) - { - _migrateTo = long.Parse(argv[i + 1]); - i++; - } - else if (argv[i].EndsWith("-defaultSchema")) - { - _defaultSchema = argv[i + 1]; - } - else if (argv[i].Equals("-dump")) - { - _dumpTo = argv[i + 1]; - i++; - } - else - { - if (i == 0) _provider = (ProviderTypes)Enum.Parse(typeof(ProviderTypes), argv[i]); - if (i == 1) _connectionString = argv[i]; - if (i == 2) _migrationsAssembly = argv[i]; - } - } - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Migrator.Console/MigratorDotNet.snk b/src/Migrator.Console/MigratorDotNet.snk deleted file mode 100644 index 5032d709..00000000 Binary files a/src/Migrator.Console/MigratorDotNet.snk and /dev/null differ diff --git a/src/Migrator.Console/app.config b/src/Migrator.Console/app.config deleted file mode 100644 index ae779258..00000000 --- a/src/Migrator.Console/app.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/Migrator.Framework/AssemblyInfo.cs b/src/Migrator.Framework/AssemblyInfo.cs deleted file mode 100644 index e06d25a4..00000000 --- a/src/Migrator.Framework/AssemblyInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ -using System.Reflection; - -[assembly: AssemblyTitle("DotNetProjects.Migrator.Framework")] -[assembly: AssemblyDescription("DotNetProjects.Migrator Framework")] \ No newline at end of file diff --git a/src/Migrator.Framework/Column.cs b/src/Migrator.Framework/Column.cs deleted file mode 100644 index 9b80f959..00000000 --- a/src/Migrator.Framework/Column.cs +++ /dev/null @@ -1,100 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System.Data; - -namespace Migrator.Framework -{ - /// - /// Represents a table column. - /// - public class Column : IColumn, IDbField - { - public Column(string name) - { - Name = name; - } - - public Column(string name, DbType type) - { - Name = name; - Type = type; - } - - public Column(string name, DbType type, int size) - { - Name = name; - Type = type; - Size = size; - } - - public Column(string name, DbType type, object defaultValue) - { - Name = name; - Type = type; - DefaultValue = defaultValue; - } - - public Column(string name, DbType type, ColumnProperty property) - { - Name = name; - Type = type; - ColumnProperty = property; - } - - public Column(string name, DbType type, int size, ColumnProperty property) - { - Name = name; - Type = type; - Size = size; - ColumnProperty = property; - } - - public Column(string name, DbType type, int size, ColumnProperty property, object defaultValue) - { - Name = name; - Type = type; - Size = size; - ColumnProperty = property; - DefaultValue = defaultValue; - } - - public Column(string name, DbType type, ColumnProperty property, object defaultValue) - { - Name = name; - Type = type; - ColumnProperty = property; - DefaultValue = defaultValue; - } - - public string Name { get; set; } - - public DbType Type { get; set; } - - public int Size { get; set; } - - public ColumnProperty ColumnProperty { get; set; } - - public object DefaultValue { get; set; } - - public bool IsIdentity - { - get { return (ColumnProperty & ColumnProperty.Identity) == ColumnProperty.Identity; } - } - - public bool IsPrimaryKey - { - get { return (ColumnProperty & ColumnProperty.PrimaryKey) == ColumnProperty.PrimaryKey; } - } - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/ColumnProperty.cs b/src/Migrator.Framework/ColumnProperty.cs deleted file mode 100644 index 79d515a5..00000000 --- a/src/Migrator.Framework/ColumnProperty.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; - -namespace Migrator.Framework -{ - /// - /// Represents a table column properties. - /// - [Flags] - public enum ColumnProperty - { - None = 0, - /// - /// Null is allowable - /// - Null = 1, - /// - /// Null is not allowable - /// - NotNull = 2, - /// - /// Identity column, autoinc - /// - Identity = 4, - /// - /// Unique Column - /// - Unique = 8, - /// - /// Indexed Column - /// - Indexed = 16, - /// - /// Unsigned Column - /// - Unsigned = 32, - - CaseSensitive = 64, - /// - /// Foreign Key - /// - ForeignKey = Unsigned | Null, - /// - /// Primary Key - /// - PrimaryKey = 128 | Unsigned | NotNull, - /// - /// Primary key. Make the column a PrimaryKey and unsigned - /// - PrimaryKeyWithIdentity = PrimaryKey | Identity - } - - public static class ColumnPropertyExtensions - { - public static bool IsSet(this ColumnProperty fruits, ColumnProperty flags) - { - return (fruits & flags) == flags; - } - - public static bool IsNotSet(this ColumnProperty fruits, ColumnProperty flags) - { - return (fruits & (~flags)) == 0; - } - - public static ColumnProperty Set(this ColumnProperty fruits, ColumnProperty flags) - { - return fruits | flags; - } - - public static ColumnProperty Clear(this ColumnProperty fruits, ColumnProperty flags) - { - return fruits & (~flags); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/DataRecordExtensions.cs b/src/Migrator.Framework/DataRecordExtensions.cs deleted file mode 100644 index 063dfe75..00000000 --- a/src/Migrator.Framework/DataRecordExtensions.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Data; - -namespace Migrator.Framework -{ - public static class DataRecordExtensions - { - public static T TryParse(this IDataRecord record, string name) - { - return TryParse(record, name, () => default(T)); - } - - public static T TryParse(this IDataRecord record, string name, Func defaultValue) - { - object value = record[name]; - - Type type = typeof (T); - - if (value == null || Convert.IsDBNull(value)) return defaultValue(); - - if (type == typeof (DateTime?) || type == typeof (DateTime)) - { - return (T) (object) (Convert.ToDateTime(value)); - } - - if (type == typeof (Guid) || type == typeof (Guid?)) - { - if (value is byte[]) return (T) (object) new Guid((byte[]) value); - return (T) ((object) new Guid(value.ToString())); - } - - if (type == typeof (string)) - { - return (T) ((object) value.ToString()); - } - - if (type == typeof (Int32?) || type == typeof (Int32)) - { - return (T) (object) Convert.ToInt32(value); - } - - if (type == typeof (Int64?) || type == typeof (Int64)) - { - return (T) (object) Convert.ToInt64(value); - } - - if (type == typeof (bool) || type == typeof (bool?)) - { - if (value is Int32 || value is Int64 || value is Int16 || value is UInt16 || value is UInt32 || value is UInt64) - { - long intValue = Convert.ToInt64(value); - return (T) (object) (intValue != 0); - } - - if (value is string) - { - bool result; - if (bool.TryParse((string) value, out result)) - { - return (T) (object) result; - } - } - - return (T) value; - } - - try - { - return (T) value; - } - catch (InvalidCastException ex) - { - throw new MigrationException(string.Format("Invalid cast exception of value: {0} of type: {1} to type: {2} (field name: {3})", value, value.GetType(), typeof (T), name), ex); - } - } - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/DotNetProjects.Migrator.Framework.csproj b/src/Migrator.Framework/DotNetProjects.Migrator.Framework.csproj deleted file mode 100644 index 530d1b96..00000000 --- a/src/Migrator.Framework/DotNetProjects.Migrator.Framework.csproj +++ /dev/null @@ -1,133 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {5270F048-E580-486C-B14C-E5B9F6E539D4} - Library - Migrator.Framework - DotNetProjects.Migrator.Framework - - - 3.5 - - - true - MigratorDotNet.snk - v4.0 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - AllRules.ruleset - - - - - - - - GlobalAssemblyInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - - - - - \ No newline at end of file diff --git a/src/Migrator.Framework/ForeignKeyConstraint.cs b/src/Migrator.Framework/ForeignKeyConstraint.cs deleted file mode 100644 index a4d5cd92..00000000 --- a/src/Migrator.Framework/ForeignKeyConstraint.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Migrator.Framework -{ - public class ForeignKeyConstraint : IDbField - { - public ForeignKeyConstraint() - { } - - public ForeignKeyConstraint(string name, string table, string[] columns, string pkTable, string[] pkColumns) - { - this.Name = name; - this.Table = table; - this.Columns = columns; - this.PkTable = pkTable; - this.PkColumns = pkColumns; - } - - public string Name { get; set; } - public string Table { get; set; } - public string[] Columns { get; set; } - public string PkTable { get; set; } - public string[] PkColumns { get; set; } - } -} diff --git a/src/Migrator.Framework/ForeignKeyConstraintType.cs b/src/Migrator.Framework/ForeignKeyConstraintType.cs deleted file mode 100644 index 802ff2a1..00000000 --- a/src/Migrator.Framework/ForeignKeyConstraintType.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Migrator.Framework -{ - public enum ForeignKeyConstraintType - { - Cascade, - SetNull, - NoAction, - Restrict, - SetDefault - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/IDbField.cs b/src/Migrator.Framework/IDbField.cs deleted file mode 100644 index 672c336c..00000000 --- a/src/Migrator.Framework/IDbField.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Migrator.Framework -{ - public interface IDbField - { - string Name { get; set; } - } -} diff --git a/src/Migrator.Framework/ILogger.cs b/src/Migrator.Framework/ILogger.cs deleted file mode 100644 index 06e0baad..00000000 --- a/src/Migrator.Framework/ILogger.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Migrator.Framework -{ - public interface ILogger - { - /// - /// Log that we have started a migration - /// - /// Start list of versions - /// Final Version - void Started(List currentVersion, long finalVersion); - - /// - /// Log that we are migrating up - /// - /// Version we are migrating to - /// Migration name - void MigrateUp(long version, string migrationName); - - /// - /// Log that we are migrating down - /// - /// Version we are migrating to - /// Migration name - void MigrateDown(long version, string migrationName); - - /// - /// Inform that a migration corresponding to the number of - /// version is untraceable (not found?) and will be ignored. - /// - /// Version we couldnt find - void Skipping(long version); - - /// - /// Log that we are rolling back to version - /// - /// - /// version - /// - void RollingBack(long originalVersion); - - /// - /// Log a Sql statement that changes the schema or content of the database as part of a migration - /// - /// - /// SELECT statements should not be logged using this method as they do not alter the data or schema of the - /// database. - /// - /// The Sql statement to log - void ApplyingDBChange(string sql); - - /// - /// Log that we had an exception on a migration - /// - /// The version of the migration that caused the exception. - /// The name of the migration that caused the exception. - /// The exception itself - void Exception(long version, string migrationName, Exception ex); - - /// - /// Log that we had an exception on a migration - /// - /// An informative message to show to the user. - /// The exception itself - void Exception(string message, Exception ex); - - /// - /// Log that we have finished a migration - /// - /// List of versions with which we started - /// Final Version - void Finished(List currentVersion, long finalVersion); - - /// - /// Log a message - /// - /// The format string ("{0}, blabla {1}"). - /// Parameters to apply to the format string. - void Log(string format, params object[] args); - - /// - /// Log a Warning - /// - /// The format string ("{0}, blabla {1}"). - /// Parameters to apply to the format string. - void Warn(string format, params object[] args); - - /// - /// Log a Trace Message - /// - /// The format string ("{0}, blabla {1}"). - /// Parameters to apply to the format string. - void Trace(string format, params object[] args); - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/IMigration.cs b/src/Migrator.Framework/IMigration.cs deleted file mode 100644 index f81653f9..00000000 --- a/src/Migrator.Framework/IMigration.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace Migrator.Framework -{ - public interface IMigration - { - string Name { get; } - - /// - /// Represents the database. - /// . - /// - /// Migration.Framework.ITransformationProvider - ITransformationProvider Database { get; set; } - - /// - /// Defines tranformations to port the database to the current version. - /// - void Up(); - - /// - /// This is run after the Up transaction has been committed - /// - void AfterUp(); - - /// - /// Defines transformations to revert things done in Up. - /// - void Down(); - - /// - /// This is run after the Down transaction has been committed - /// - void AfterDown(); - - /// - /// This gets called once on the first migration object. - /// - void InitializeOnce(string[] args); - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/ITransformationProvider.cs b/src/Migrator.Framework/ITransformationProvider.cs deleted file mode 100644 index cb79ecf6..00000000 --- a/src/Migrator.Framework/ITransformationProvider.cs +++ /dev/null @@ -1,632 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; - -namespace Migrator.Framework -{ - /// - /// The main interface to use in Migrations to make changes on a database schema. - /// - public interface ITransformationProvider : IDisposable - { - /// - /// Get this provider or a NoOp provider if you are not running in the context of 'provider'. - /// - ITransformationProvider this[string provider] { get; } - - string SchemaInfoTable { get; set; } - - /// - /// The list of Migrations currently applied to the database. - /// - List AppliedMigrations { get; } - - /// - /// Connection string to the database - /// - String ConnectionString { get; } - - /// - /// Logger used to log details of operations performed during migration - /// - ILogger Logger { get; set; } - - /// - /// Add a column to an existing table - /// - /// The name of the table that will get the new column - /// The name of the new column - /// The data type for the new columnd - /// The precision or size of the column - /// Properties that can be ORed together - /// The default value of the column if no value is given in a query - void AddColumn(string table, string column, DbType type, int size, ColumnProperty property, object defaultValue); - - /// - /// Add a column to an existing table - /// - /// The name of the table that will get the new column - /// The name of the new column - /// The data type for the new columnd - void AddColumn(string table, string column, DbType type); - - /// - /// Add a column to an existing table - /// - /// The name of the table that will get the new column - /// The name of the new column - /// The data type for the new columnd - /// The precision or size of the column - void AddColumn(string table, string column, DbType type, int size); - - /// - /// Add a column to an existing table - /// - /// The name of the table that will get the new column - /// The name of the new column - /// The data type for the new columnd - /// The precision or size of the column - /// Properties that can be ORed together - void AddColumn(string table, string column, DbType type, int size, ColumnProperty property); - - /// - /// Add a column to an existing table - /// - /// The name of the table that will get the new column - /// The name of the new column - /// The data type for the new columnd - /// Properties that can be ORed together - void AddColumn(string table, string column, DbType type, ColumnProperty property); - - /// - /// Add a column to an existing table with the default column size. - /// - /// The name of the table that will get the new column - /// The name of the new column - /// The data type for the new columnd - /// The default value of the column if no value is given in a query - void AddColumn(string table, string column, DbType type, object defaultValue); - - /// - /// Add a column to an existing table - /// - /// The name of the table that will get the new column - /// An instance of a Column with the specified properties - void AddColumn(string table, Column column); - - /// - /// Add a foreign key constraint - /// - /// The name of the foreign key. e.g. FK_TABLE_REF - /// The table that the foreign key will be created in (eg. Table.FK_id) - /// The columns that are the foreign keys (eg. FK_id) - /// The table that holds the primary keys (eg. Table.PK_id) - /// The columns that are the primary keys (eg. PK_id) - void AddForeignKey(string name, string foreignTable, string[] foreignColumns, string primaryTable, string[] primaryColumns); - - /// - /// Add a foreign key constraint - /// - /// The name of the foreign key. e.g. FK_TABLE_REF - /// The table that the foreign key will be created in (eg. Table.FK_id) - /// The columns that are the foreign keys (eg. FK_id) - /// The table that holds the primary keys (eg. Table.PK_id) - /// The columns that are the primary keys (eg. PK_id) - /// Constraint parameters - void AddForeignKey(string name, string foreignTable, string[] foreignColumns, string primaryTable, string[] primaryColumns, ForeignKeyConstraintType constraint); - - /// - /// Add a foreign key constraint - /// - /// - /// The name of the foreign key. e.g. FK_TABLE_REF - /// The table that the foreign key will be created in (eg. Table.FK_id) - /// The column that is the foreign key (eg. FK_id) - /// The table that holds the primary keys (eg. Table.PK_id) - /// The column that is the primary key (eg. PK_id) - void AddForeignKey(string name, string foreignTable, string foreignColumn, string primaryTable, string primaryColumn); - - /// - /// Add a foreign key constraint - /// - /// The name of the foreign key. e.g. FK_TABLE_REF - /// The table that the foreign key will be created in (eg. Table.FK_id) - /// The column that is the foreign key (eg. FK_id) - /// The table that holds the primary key (eg. Table.PK_id) - /// The column that is the primary key (eg. PK_id) - /// Constraint parameters - void AddForeignKey(string name, string foreignTable, string foreignColumn, string primaryTable, string primaryColumn, ForeignKeyConstraintType constraint); - - /// - /// Add a foreign key constraint when you don't care about the name of the constraint. - /// Warning: This will prevent you from dropping the constraint since you won't know the name. - /// - /// The table that the foreign key will be created in (eg. Table.FK_id) - /// The column that is the foreign key (eg. FK_id) - /// The table that holds the primary key (eg. Table.PK_id) - /// The column that is the primary key (eg. PK_id) - void GenerateForeignKey(string foreignTable, string foreignColumn, string primaryTable, string primaryColumn); - - /// - /// Add a foreign key constraint when you don't care about the name of the constraint. - /// Warning: This will prevent you from dropping the constraint since you won't know the name. - /// - /// The table that the foreign key will be created in (eg. Table.FK_id) - /// The columns that are the foreign keys (eg. FK_id) - /// The table that holds the primary key (eg. Table.PK_id) - /// The column that is the primary key (eg. PK_id) - void GenerateForeignKey(string foreignTable, string[] foreignColumns, string primaryTable, string[] primaryColumns); - - /// - /// Add a foreign key constraint when you don't care about the name of the constraint. - /// Warning: This will prevent you from dropping the constraint since you won't know the name. - /// - /// The table that the foreign key will be created in (eg. Table.FK_id) - /// The columns that are the foreign keys (eg. FK_id) - /// The table that holds the primary key (eg. Table.PK_id) - /// The columns that are the primary keys (eg. PK_id) - /// Constraint parameters - void GenerateForeignKey(string foreignTable, string[] foreignColumns, string primaryTable, string[] primaryColumns, ForeignKeyConstraintType constraint); - - /// - /// Add a foreign key constraint when you don't care about the name of the constraint. - /// Warning: This will prevent you from dropping the constraint since you won't know the name. - /// - /// The table that the foreign key will be created in (eg. Table.FK_id) - /// The columns that are the foreign keys (eg. FK_id) - /// The table that holds the primary key (eg. Table.PK_id) - /// The column that is the primary key (eg. PK_id) - /// Constraint parameters - void GenerateForeignKey(string foreignTable, string foreignColumn, string primaryTable, string primaryColumn, - ForeignKeyConstraintType constraint); - - /// - /// Add a foreign key constraint when you don't care about the name of the constraint. - /// Warning: This will prevent you from dropping the constraint since you won't know the name. - /// - /// The current expectations are that there is a column named the same as the foreignTable present in - /// the table. This is subject to change because I think it's not a good convention. - /// - /// The table that the foreign key will be created in (eg. Table.FK_id) - /// The table that holds the primary key (eg. Table.PK_id) - void GenerateForeignKey(string foreignTable, string primaryTable); - - /// - /// Add a foreign key constraint when you don't care about the name of the constraint. - /// Warning: This will prevent you from dropping the constraint since you won't know the name. - /// - /// The current expectations are that there is a column named the same as the foreignTable present in - /// the table. This is subject to change because I think it's not a good convention. - /// - /// The table that the foreign key will be created in (eg. Table.FK_id) - /// The table that holds the primary key (eg. Table.PK_id) - /// - void GenerateForeignKey(string foreignTable, string primaryTable, ForeignKeyConstraintType constraint); - - /// - /// Add a primary key to a table - /// - /// The name of the primary key to add. - /// The name of the table that will get the primary key. - /// The name of the column or columns that are in the primary key. - void AddPrimaryKey(string name, string table, params string[] columns); - - /// - /// Add a constraint to a table - /// - /// The name of the constraint to add. - /// The name of the table that will get the constraint - /// The name of the column or columns that will get the constraint. - void AddUniqueConstraint(string name, string table, params string[] columns); - - /// - /// Add a constraint to a table - /// - /// The name of the constraint to add. - /// The name of the table that will get the constraint - /// The check constraint definition. - void AddCheckConstraint(string name, string table, string checkSql); - - void AddView(string name, string tableName, params IViewField[] fields); - - /// - /// Add a table - /// - /// The name of the table to add. - /// The columns that are part of the table. - void AddTable(string name, params IDbField[] columns); - - /// - /// Add a table - /// - /// The name of the table to add. - /// The name of the database engine to use. (MySQL) - /// The columns that are part of the table. - void AddTable(string name, string engine, params IDbField[] columns); - - /// - /// Start a transction - /// - void BeginTransaction(); - - /// - /// Change the definition of an existing column. - /// - /// The name of the table that will get the new column - /// An instance of a Column with the specified properties and the name of an existing column - void ChangeColumn(string table, Column column); - - void RemoveColumnDefaultValue(string table, string column); - - /// - /// Check to see if a column exists - /// - /// - /// - /// - bool ColumnExists(string table, string column); - - /// - /// Commit the running transction - /// - void Commit(); - - /// - /// Check to see if a constraint exists - /// - /// The name of the constraint - /// The table that the constraint lives on. - /// - bool ConstraintExists(string table, string name); - - /// - /// Check to see if a primary key constraint exists on the table - /// - /// The name of the primary key - /// The table that the constraint lives on. - /// - bool PrimaryKeyExists(string table, string name); - - /// - /// Execute an arbitrary SQL query - /// - /// The SQL to execute. - /// timeout - /// Array of parameters of type object - /// - int ExecuteNonQuery(string sql, int timeout, object[] args); - - /// - /// Execute an arbitrary SQL query - /// - /// The SQL to execute. - /// timeout - /// - int ExecuteNonQuery(string sql,int timeout); - - int ExecuteNonQuery(string sql); - /// - /// Execute an arbitrary SQL query - /// - /// The SQL to execute. - /// - IDataReader ExecuteQuery(string sql); - - /// - /// Execute an arbitrary SQL query - /// - /// The SQL to execute. - /// A single value that is returned. - object ExecuteScalar(string sql); - - List ExecuteStringQuery(string sql, params object[] args); - - Index[] GetIndexes(string table); - - /// - /// Get the information about the columns in a table - /// - /// The table name that you want the columns for. - /// - Column[] GetColumns(string table); - - /// - /// Get information about a single column in a table - /// - /// The table name that you want the columns for. - /// The column name for which you want information. - /// - Column GetColumnByName(string table, string column); - - /// - /// Get the names of all of the tables - /// - /// The names of all the tables. - string[] GetTables(); - - ForeignKeyConstraint[] GetForeignKeyConstraints(string table); - - /// - /// Insert data into a table - /// - /// The table that will get the new data - /// The names of the columns - /// The values in the same order as the columns - /// - int Insert(string table, string[] columns, object[] values); - - /// - /// Delete data from a table - /// - /// The table that will have the data deleted - /// The names of the columns used in a where clause - /// The values in the same order as the columns - /// - int Delete(string table, string[] columns, string[] values); - - /// - /// Delete data from a table - /// - /// The table that will have the data deleted - /// The name of the column used in a where clause - /// The value for the where clause - /// - int Delete(string table, string whereColumn, string whereValue); - - /// - /// Truncate data from a table - /// - /// The table that will have the data deleted - /// - int TruncateTable(string table); - - /// - /// Marks a Migration version number as having been applied - /// - /// The version number of the migration that was applied - void MigrationApplied(long version, string scope); - - /// - /// Marks a Migration version number as having been rolled back from the database - /// - /// The version number of the migration that was removed - void MigrationUnApplied(long version, string scope); - - /// - /// Remove an existing column from a table - /// - /// The name of the table to remove the column from - /// The column to remove - void RemoveColumn(string table, string column); - - /// - /// Remove an existing foreign key constraint - /// - /// The table that contains the foreign key. - /// The name of the foreign key to remove - void RemoveForeignKey(string table, string name); - - /// - /// Remove an existing constraint - /// - /// The table that contains the foreign key. - /// The name of the constraint to remove - void RemoveConstraint(string table, string name); - - void RemoveAllConstraints(string table); - - /// - /// Remove an existing primary key - /// - /// The table that contains the primary key. - void RemovePrimaryKey(string table); - - /// - /// Remove an existing table - /// - /// The name of the table - void RemoveTable(string tableName); - - /// - /// Rename an existing table - /// - /// The old name of the table - /// The new name of the table - void RenameTable(string oldName, string newName); - - /// - /// Rename an existing table - /// - /// The name of the table - /// The old name of the column - /// The new name of the column - void RenameColumn(string tableName, string oldColumnName, string newColumnName); - - /// - /// Rollback the currently running transaction. - /// - void Rollback(); - - /// - /// Get values from a table - /// - /// The columns to select - /// The table to select from - /// The where clause to limit the selection - /// - IDataReader Select(string what, string from, string where); - - /// - /// Get values from a table - /// - /// The columns to select - /// The table to select from - /// - IDataReader Select(string what, string from); - - /// - /// Get a single value from a table - /// - /// The columns to select - /// The table to select from - /// - /// - object SelectScalar(string what, string from, string where); - - /// - /// Get a single value from a table - /// - /// The columns to select - /// The table to select from - /// - object SelectScalar(string what, string from); - - /// - /// Check if a table already exists - /// - /// The name of the table that you want to check on. - /// - bool TableExists(string tableName); - - /// - /// Update the values in a table - /// - /// The name of the table to update - /// The names of the columns. - /// The values for the columns in the same order as the names. - /// - int Update(string table, string[] columns, object[] values); - - /// - /// Update the values in a table - /// - /// The name of the table to update - /// The names of the columns. - /// The values for the columns in the same order as the names. - /// A where clause to limit the update - /// - int Update(string table, string[] columns, object[] values, string where); - - int Update(string table, string[] columns, object[] values, string[] whereColumns, object[] whereValues); - - /// - /// Get a command instance - /// - /// - IDbCommand GetCommand(); - - /// - /// Execute a schema builder - /// - /// - void ExecuteSchemaBuilder(SchemaBuilder.SchemaBuilder schemaBuilder); - - - void RemoveAllForeignKeys(string tableName, string columnName); - - bool IsThisProvider(string provider); - - /// - /// Quote a multiple column names, if required - /// - /// - /// - string[] QuoteColumnNamesIfRequired(params string[] columnNames); - - /// - /// Quaote column if required - /// - /// - /// - string QuoteColumnNameIfRequired(string name); - - /// - /// Quote table name if required - /// - /// - /// - string QuoteTableNameIfRequired(string name); - - /// - /// Encodes a guid value as a string, suitable for inclusion in sql statement - /// - /// - /// - string Encode(Guid guid); - - /// - /// Change the target database - /// - /// Name of the new target database - void SwitchDatabase(string databaseName); - - - /// - /// Get a list of databases available on the server - /// - List GetDatabases(); - - /// - /// Checks to see if a database with specific name exists on the server - /// - bool DatabaseExists(string name); - - /// - /// Create a new database on the server - /// - /// Name of the new database - void CreateDatabases(string databaseName); - - /// - /// Delete a database from the server - /// - /// Name of the database to delete - void DropDatabases(string databaseName); - - void AddIndex(string table, Index index); - - /// - /// Add a multi-column index to a table - /// - /// The name of the index to add. - /// The name of the table that will get the index. - /// The name of the column or columns that are in the index. - void AddIndex(string name, string table, params string[] columns); - - /// - /// Check to see if an index exists - /// - /// The name of the index - /// The table that the index lives on. - /// - bool IndexExists(string table, string name); - - /// - /// Remove an existing index - /// - /// The table that contains the index. - /// The name of the index to remove - void RemoveIndex(string table, string name); - - /// - /// Generate parameter name based on an index number - /// - /// The index number of the parameter - string GenerateParameterName(int index); - - /// - /// Remove all indexes of a table - /// - /// The table name - void RemoveAllIndexes(string table); - - string Concatenate(params string[] strings); - - IDbConnection Connection { get; } - - IEnumerable GetTables(string schema); - - IEnumerable GetColumns(string schema, string table); - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/IViewField.cs b/src/Migrator.Framework/IViewField.cs deleted file mode 100644 index 5a68a87a..00000000 --- a/src/Migrator.Framework/IViewField.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Migrator.Framework -{ - public interface IViewField - { - string TableName { get; set; } - string ColumnName { get; set; } - - string KeyColumnName { get; set; } - string ParentTableName { get; set; } - string ParentKeyColumnName { get; set; } - } -} diff --git a/src/Migrator.Framework/Index.cs b/src/Migrator.Framework/Index.cs deleted file mode 100644 index 5eb27c13..00000000 --- a/src/Migrator.Framework/Index.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Migrator.Framework -{ - public class Index : IDbField - { - public string Name { get; set; } - public bool Unique { get; set; } - public bool Clustered { get; set; } - public bool PrimaryKey { get; set; } - public string[] KeyColumns { get; set; } - public string[] IncludeColumns { get; set; } - } -} diff --git a/src/Migrator.Framework/JoiningTableTransformationProviderExtensions.cs b/src/Migrator.Framework/JoiningTableTransformationProviderExtensions.cs deleted file mode 100644 index 79eab098..00000000 --- a/src/Migrator.Framework/JoiningTableTransformationProviderExtensions.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Text; -using Migrator.Framework.Support; - -namespace Migrator.Framework -{ - /// - /// A set of extension methods for the transformation provider to make it easier to - /// build many-to-many joining tables (takes care of adding the joining table and foreign - /// key constraints as necessary. - /// This functionality was useful when bootstrapping a number of projects a few years ago, but - /// now that most changes are brown-field I'm thinking of removing these methods as it's easier to maintain - /// code that creates the tables etc. directly within migration. - /// - public static class JoiningTableTransformationProviderExtensions - { - public static ITransformationProvider AddManyToManyJoiningTable(this ITransformationProvider database, string schema, string lhsTableName, string lhsKey, string rhsTableName, string rhsKey) - { - string joiningTable = GetNameOfJoiningTable(lhsTableName, rhsTableName); - - return AddManyToManyJoiningTable(database, schema, lhsTableName, lhsKey, rhsTableName, rhsKey, joiningTable); - } - - static string GetNameOfJoiningTable(string lhsTableName, string rhsTableName) - { - return (Inflector.Singularize(lhsTableName) ?? lhsTableName) + (Inflector.Pluralize(rhsTableName) ?? rhsTableName); - } - - public static ITransformationProvider AddManyToManyJoiningTable(this ITransformationProvider database, string schema, string lhsTableName, string lhsKey, string rhsTableName, string rhsKey, string joiningTableName) - { - string joiningTableWithSchema = TransformationProviderUtility.FormatTableName(schema, joiningTableName); - - string joinLhsKey = Inflector.Singularize(lhsTableName) + "Id"; - string joinRhsKey = Inflector.Singularize(rhsTableName) + "Id"; - - database.AddTable(joiningTableWithSchema, - new Column(joinLhsKey, DbType.Guid, ColumnProperty.NotNull), - new Column(joinRhsKey, DbType.Guid, ColumnProperty.NotNull)); - - string pkName = "PK_" + joiningTableName; - - pkName = ShortenKeyNameToBeSuitableForOracle(pkName); - - database.AddPrimaryKey(pkName, joiningTableWithSchema, joinLhsKey, joinRhsKey); - - string lhsTableNameWithSchema = TransformationProviderUtility.FormatTableName(schema, lhsTableName); - string rhsTableNameWithSchema = TransformationProviderUtility.FormatTableName(schema, rhsTableName); - - string lhsFkName = TransformationProviderUtility.CreateForeignKeyName(lhsTableName, joiningTableName); - database.AddForeignKey(lhsFkName, joiningTableWithSchema, joinLhsKey, lhsTableNameWithSchema, lhsKey, ForeignKeyConstraintType.NoAction); - - string rhsFkName = TransformationProviderUtility.CreateForeignKeyName(rhsTableName, joiningTableName); - database.AddForeignKey(rhsFkName, joiningTableWithSchema, joinRhsKey, rhsTableNameWithSchema, rhsKey, ForeignKeyConstraintType.NoAction); - - return database; - } - - static string ShortenKeyNameToBeSuitableForOracle(string pkName) - { - return TransformationProviderUtility.AdjustNameToSize(pkName, TransformationProviderUtility.MaxLengthForForeignKeyInOracle, false); - } - - public static ITransformationProvider RemoveManyToManyJoiningTable(this ITransformationProvider database, string schema, string lhsTableName, string rhsTableName) - { - string joiningTable = GetNameOfJoiningTable(lhsTableName, rhsTableName); - return RemoveManyToManyJoiningTable(database, schema, lhsTableName, rhsTableName, joiningTable); - } - - public static ITransformationProvider RemoveManyToManyJoiningTable(this ITransformationProvider database, string schema, string lhsTableName, string rhsTableName, string joiningTableName) - { - string joiningTableNameWithSchema = TransformationProviderUtility.FormatTableName(schema, joiningTableName); - string lhsFkName = TransformationProviderUtility.CreateForeignKeyName(lhsTableName, joiningTableName); - string rhsFkName = TransformationProviderUtility.CreateForeignKeyName(rhsTableName, joiningTableName); - - database.RemoveForeignKey(joiningTableNameWithSchema, lhsFkName); - database.RemoveForeignKey(joiningTableNameWithSchema, rhsFkName); - database.RemoveTable(joiningTableNameWithSchema); - - return database; - } - } - -} diff --git a/src/Migrator.Framework/Loggers/IAttachableLogger.cs b/src/Migrator.Framework/Loggers/IAttachableLogger.cs deleted file mode 100644 index 45b82ed1..00000000 --- a/src/Migrator.Framework/Loggers/IAttachableLogger.cs +++ /dev/null @@ -1,35 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -namespace Migrator.Framework.Loggers -{ - /// - /// ILogger interface. - /// Implicit in this interface is that the logger will delegate actual - /// logging to the (s) that have been attached - /// - public interface IAttachableLogger : ILogger - { - /// - /// Attach an - /// - /// - void Attach(ILogWriter writer); - - /// - /// Detach an - /// - /// - void Detach(ILogWriter writer); - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/Loggers/ILogWriter.cs b/src/Migrator.Framework/Loggers/ILogWriter.cs deleted file mode 100644 index 8042c155..00000000 --- a/src/Migrator.Framework/Loggers/ILogWriter.cs +++ /dev/null @@ -1,35 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -namespace Migrator.Framework.Loggers -{ - /// - /// Handles writing a message to the log medium (i.e. file, console) - /// - public interface ILogWriter - { - /// - /// Write this message - /// - /// - /// - void Write(string message, params object[] args); - - /// - /// Write this message, as a line - /// - /// - /// - void WriteLine(string message, params object[] args); - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/Loggers/Logger.cs b/src/Migrator.Framework/Loggers/Logger.cs deleted file mode 100644 index ae093ddc..00000000 --- a/src/Migrator.Framework/Loggers/Logger.cs +++ /dev/null @@ -1,171 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.Collections.Generic; - -namespace Migrator.Framework.Loggers -{ - /// - /// Text logger for the migration mediator - /// - public class Logger : IAttachableLogger - { - readonly bool _trace; - readonly List _writers = new List(); - - public Logger(bool trace) - { - _trace = trace; - } - - public Logger(bool trace, params ILogWriter[] writers) - : this(trace) - { - _writers.AddRange(writers); - } - - public void Attach(ILogWriter writer) - { - _writers.Add(writer); - } - - public void Detach(ILogWriter writer) - { - _writers.Remove(writer); - } - - public void Started(List currentVersions, long finalVersion) - { - WriteLine("Latest version applied : {0}. Target version : {1}", LatestVersion(currentVersions), finalVersion); - } - - public void MigrateUp(long version, string migrationName) - { - WriteLine("Applying {0}: {1}", version.ToString(), migrationName); - } - - public void MigrateDown(long version, string migrationName) - { - WriteLine("Removing {0}: {1}", version.ToString(), migrationName); - } - - public void Skipping(long version) - { - WriteLine("{0} {1}", version.ToString(), ""); - } - - public void RollingBack(long originalVersion) - { - WriteLine("Rolling back to migration {0}", originalVersion); - } - - public void ApplyingDBChange(string sql) - { - Log(sql); - } - - public void Exception(long version, string migrationName, Exception ex) - { - WriteLine("============ Error Detail ============"); - WriteLine("Error in migration: {0}", version); - LogExceptionDetails(ex); - WriteLine("======================================"); - } - - public void Exception(string message, Exception ex) - { - WriteLine("============ Error Detail ============"); - WriteLine("Error: {0}", message); - LogExceptionDetails(ex); - WriteLine("======================================"); - } - - public void Finished(List originalVersions, long currentVersion) - { - WriteLine("Migrated to version {0}", currentVersion); - } - - public void Log(string format, params object[] args) - { - WriteLine(format, args); - } - - public void Warn(string format, params object[] args) - { - Write("Warning! : "); - WriteLine(format, args); - } - - public void Trace(string format, params object[] args) - { - if (_trace) - { - Log(format, args); - } - } - - public void Started(long currentVersion, long finalVersion) - { - WriteLine("Current version : {0}. Target version : {1}", currentVersion, finalVersion); - } - - void LogExceptionDetails(Exception ex) - { - WriteLine("{0}", ex.Message); - WriteLine("{0}", ex.StackTrace); - Exception iex = ex.InnerException; - while (iex != null) - { - WriteLine("Caused by: {0}", iex); - WriteLine("{0}", ex.StackTrace); - iex = iex.InnerException; - } - } - - public void Finished(long originalVersion, long currentVersion) - { - WriteLine("Migrated to version {0}", currentVersion); - } - - void Write(string message, params object[] args) - { - foreach (ILogWriter writer in _writers) - { - writer.Write(message, args); - } - } - - void WriteLine(string message, params object[] args) - { - foreach (ILogWriter writer in _writers) - { - writer.WriteLine(message, args); - } - } - - public static ILogger ConsoleLogger() - { - return new Logger(false, new ConsoleWriter()); - } - - string LatestVersion(List versions) - { - if (versions.Count > 0) - { - return versions[versions.Count - 1].ToString(); - } - return "No migrations applied yet!"; - } - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/Loggers/SqlScriptFileLogger.cs b/src/Migrator.Framework/Loggers/SqlScriptFileLogger.cs deleted file mode 100644 index fab28946..00000000 --- a/src/Migrator.Framework/Loggers/SqlScriptFileLogger.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace Migrator.Framework.Loggers -{ - public class SqlScriptFileLogger : ILogger, IDisposable - { - readonly ILogger _innerLogger; - TextWriter _streamWriter; - - public SqlScriptFileLogger(ILogger logger, TextWriter streamWriter) - { - _innerLogger = logger; - _streamWriter = streamWriter; - } - - #region IDisposable Members - - public void Dispose() - { - if (_streamWriter != null) - { - _streamWriter.Dispose(); - _streamWriter = null; - } - } - - #endregion - - public void Log(string format, params object[] args) - { - _innerLogger.Log(format, args); - } - - public void Warn(string format, params object[] args) - { - _innerLogger.Warn(format, args); - } - - public void Trace(string format, params object[] args) - { - _innerLogger.Trace(format, args); - } - - public void ApplyingDBChange(string sql) - { - _innerLogger.ApplyingDBChange(sql); - _streamWriter.WriteLine(sql); - } - - public void Started(List appliedVersions, long finalVersion) - { - _innerLogger.Started(appliedVersions, finalVersion); - } - - public void MigrateUp(long version, string migrationName) - { - _innerLogger.MigrateUp(version, migrationName); - } - - public void MigrateDown(long version, string migrationName) - { - _innerLogger.MigrateDown(version, migrationName); - } - - public void Skipping(long version) - { - _innerLogger.Skipping(version); - } - - public void RollingBack(long originalVersion) - { - _innerLogger.RollingBack(originalVersion); - } - - public void Exception(long version, string migrationName, Exception ex) - { - _innerLogger.Exception(version, migrationName, ex); - } - - public void Exception(string message, Exception ex) - { - _innerLogger.Exception(message, ex); - } - - public void Finished(List appliedVersions, long currentVersion) - { - _innerLogger.Finished(appliedVersions, currentVersion); - _streamWriter.Close(); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/Maximums.cs b/src/Migrator.Framework/Maximums.cs deleted file mode 100644 index 17af8a6a..00000000 --- a/src/Migrator.Framework/Maximums.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Migrator.Framework -{ - public static class Maximums - { - public const int NTextLength = 1073741823; - public const int BlobLength = 2147483647; - } -} diff --git a/src/Migrator.Framework/Migration.cs b/src/Migrator.Framework/Migration.cs deleted file mode 100644 index ca89eaa7..00000000 --- a/src/Migrator.Framework/Migration.cs +++ /dev/null @@ -1,113 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -namespace Migrator.Framework -{ - /// - /// A migration is a group of transformation applied to the database schema - /// (or sometimes data) to port the database from one version to another. - /// The Up() method must apply the modifications (eg.: create a table) - /// and the Down() method must revert, or rollback the modifications - /// (eg.: delete a table). - /// - /// Each migration must be decorated with the [Migration(0)] attribute. - /// Each migration number (0) must be unique, or else a - /// DuplicatedVersionException will be trown. - /// - /// - /// All migrations are executed inside a transaction. If an exception is - /// thrown, the transaction will be rolledback and transformations wont be - /// applied. - /// - /// - /// It is best to keep a limited number of transformation inside a migration - /// so you can easely move from one version of to another with fine grain - /// modifications. - /// You should give meaningful name to the migration class and prepend the - /// migration number to the filename so they keep ordered, eg.: - /// 002_CreateTableTest.cs. - /// - /// - /// Use the Database property to apply transformation and the - /// Logger property to output informations in the console (or other). - /// For more details on transformations see - /// ITransformationProvider. - /// - /// - /// - /// The following migration creates a new Customer table. - /// (File 003_AddCustomerTable.cs) - /// - /// [Migration(3)] - /// public class AddCustomerTable : Migration - /// { - /// public override void Up() - /// { - /// Database.AddTable("Customer", - /// new Column("Name", typeof(string), 50), - /// new Column("Address", typeof(string), 100) - /// ); - /// } - /// public override void Down() - /// { - /// Database.RemoveTable("Customer"); - /// } - /// } - /// - /// - public abstract class Migration : IMigration - { - public string Name - { - get { return StringUtils.ToHumanName(GetType().Name); } - } - - /// - /// Defines tranformations to port the database to the current version. - /// - public abstract void Up(); - - /// - /// This is run after the Up transaction has been committed - /// - public virtual void AfterUp() - { - } - - /// - /// Defines transformations to revert things done in Up. - /// - public abstract void Down(); - - /// - /// This is run after the Down transaction has been committed - /// - public virtual void AfterDown() - { - } - - /// - /// Represents the database. - /// . - /// - /// Migration.Framework.ITransformationProvider - public ITransformationProvider Database { get; set; } - - /// - /// This gets called once on the first migration object. - /// - public virtual void InitializeOnce(string[] args) - { - } - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/MigrationAttribute.cs b/src/Migrator.Framework/MigrationAttribute.cs deleted file mode 100644 index fc149678..00000000 --- a/src/Migrator.Framework/MigrationAttribute.cs +++ /dev/null @@ -1,59 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; - -namespace Migrator.Framework -{ - /// - /// Describe a migration - /// - public class MigrationAttribute : Attribute - { - private long _version; - private bool _ignore = false; - - public string Scope { get; set; } - - /// - /// Describe the migration - /// - /// The unique version of the migration. - public MigrationAttribute(long version) - { - Version = version; - } - public MigrationAttribute(int year, int month, int day, int hour, int minute,int second) - { - var combined = String.Format("{0:D4}{1:D2}{2:D2}{3:D2}{4:D2}{5:D2}", year, month, day, hour, minute,second); - Version = long.Parse(combined); - } - /// - /// The version reflected by the migration - /// - public long Version - { - get { return _version; } - private set { _version = value; } - } - - /// - /// Set to true to ignore this migration. - /// - public bool Ignore - { - get { return _ignore; } - set { _ignore = value; } - } - } -} diff --git a/src/Migrator.Framework/MigrationException.cs b/src/Migrator.Framework/MigrationException.cs deleted file mode 100644 index 395ca98e..00000000 --- a/src/Migrator.Framework/MigrationException.cs +++ /dev/null @@ -1,38 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; - -namespace Migrator.Framework -{ - /// - /// Base class for migration errors. - /// - public class MigrationException : Exception - { - public MigrationException(string message) - : base(message) - { - } - - public MigrationException(string message, Exception cause) - : base(message, cause) - { - } - - public MigrationException(string migration, int version, Exception innerException) - : base(String.Format("Exception in migration {0} (#{1})", migration, version), innerException) - { - } - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/MigratorDotNet.snk b/src/Migrator.Framework/MigratorDotNet.snk deleted file mode 100644 index 5032d709..00000000 Binary files a/src/Migrator.Framework/MigratorDotNet.snk and /dev/null differ diff --git a/src/Migrator.Framework/SchemaBuilder/AddColumnExpression.cs b/src/Migrator.Framework/SchemaBuilder/AddColumnExpression.cs deleted file mode 100644 index 02d3f049..00000000 --- a/src/Migrator.Framework/SchemaBuilder/AddColumnExpression.cs +++ /dev/null @@ -1,40 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -namespace Migrator.Framework.SchemaBuilder -{ - public class AddColumnExpression : ISchemaBuilderExpression - { - readonly IFluentColumn _column; - readonly string _toTable; - - public AddColumnExpression(string toTable, IFluentColumn column) - { - _column = column; - _toTable = toTable; - } - - public void Create(ITransformationProvider provider) - { - provider.AddColumn(_toTable, _column.Name, _column.Type, _column.Size, _column.ColumnProperty, _column.DefaultValue); - - if (_column.ForeignKey != null) - { - provider.AddForeignKey( - "FK_" + _toTable + "_" + _column.Name + "_" + _column.ForeignKey.PrimaryTable + "_" + - _column.ForeignKey.PrimaryKey, - _toTable, _column.Name, _column.ForeignKey.PrimaryTable, _column.ForeignKey.PrimaryKey, _column.Constraint); - } - } - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/SchemaBuilder/FluentColumn.cs b/src/Migrator.Framework/SchemaBuilder/FluentColumn.cs deleted file mode 100644 index e0ea1c6c..00000000 --- a/src/Migrator.Framework/SchemaBuilder/FluentColumn.cs +++ /dev/null @@ -1,71 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System.Data; - -namespace Migrator.Framework.SchemaBuilder -{ - public class FluentColumn : IFluentColumn - { - readonly Column _inner; - - public FluentColumn(string columnName) - { - _inner = new Column(columnName); - } - - public ColumnProperty ColumnProperty - { - get { return _inner.ColumnProperty; } - set { _inner.ColumnProperty = value; } - } - - public string Name - { - get { return _inner.Name; } - set { _inner.Name = value; } - } - - public DbType Type - { - get { return _inner.Type; } - set { _inner.Type = value; } - } - - public int Size - { - get { return _inner.Size; } - set { _inner.Size = value; } - } - - public bool IsIdentity - { - get { return _inner.IsIdentity; } - } - - public bool IsPrimaryKey - { - get { return _inner.IsPrimaryKey; } - } - - public object DefaultValue - { - get { return _inner.DefaultValue; } - set { _inner.DefaultValue = value; } - } - - public ForeignKeyConstraintType Constraint { get; set; } - - public ForeignKey ForeignKey { get; set; } - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/SchemaBuilder/ForeignKey.cs b/src/Migrator.Framework/SchemaBuilder/ForeignKey.cs deleted file mode 100644 index 793f2ec6..00000000 --- a/src/Migrator.Framework/SchemaBuilder/ForeignKey.cs +++ /dev/null @@ -1,28 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -namespace Migrator.Framework.SchemaBuilder -{ - public class ForeignKey - { - public ForeignKey(string primaryTable, string primaryKey) - { - PrimaryTable = primaryTable; - PrimaryKey = primaryKey; - } - - public string PrimaryTable { get; set; } - - public string PrimaryKey { get; set; } - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/SchemaBuilder/IColumnOptions.cs b/src/Migrator.Framework/SchemaBuilder/IColumnOptions.cs deleted file mode 100644 index 8fd8cb32..00000000 --- a/src/Migrator.Framework/SchemaBuilder/IColumnOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Data; - -namespace Migrator.Framework.SchemaBuilder -{ - public interface IColumnOptions - { - SchemaBuilder OfType(DbType dbType); - - SchemaBuilder WithSize(int size); - - IForeignKeyOptions AsForeignKey(); - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/SchemaBuilder/SchemaBuilder.cs b/src/Migrator.Framework/SchemaBuilder/SchemaBuilder.cs deleted file mode 100644 index 5fd35ad1..00000000 --- a/src/Migrator.Framework/SchemaBuilder/SchemaBuilder.cs +++ /dev/null @@ -1,169 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.Collections.Generic; -using System.Data; - -namespace Migrator.Framework.SchemaBuilder -{ - public class SchemaBuilder : IColumnOptions, IForeignKeyOptions, IDeleteTableOptions - { - readonly IList _exprs; - IFluentColumn _currentColumn; - string _currentTable; - - public SchemaBuilder() - { - _exprs = new List(); - } - - public IEnumerable Expressions - { - get { return _exprs; } - } - - public SchemaBuilder OfType(DbType columnType) - { - _currentColumn.Type = columnType; - - return this; - } - - public SchemaBuilder WithSize(int size) - { - if (size == 0) - throw new ArgumentNullException("size", "Size must be greater than zero"); - - _currentColumn.Size = size; - - return this; - } - - public IForeignKeyOptions AsForeignKey() - { - _currentColumn.ColumnProperty = ColumnProperty.ForeignKey; - - return this; - } - - /// - /// Adds a Table to be created to the Schema - /// - /// Table name to be created - /// SchemaBuilder for chaining - public SchemaBuilder AddTable(string name) - { - if (string.IsNullOrEmpty(name)) - throw new ArgumentNullException("name"); - - _exprs.Add(new AddTableExpression(name)); - _currentTable = name; - - return this; - } - - public IDeleteTableOptions DeleteTable(string name) - { - if (string.IsNullOrEmpty(name)) - throw new ArgumentNullException("name"); - _currentTable = ""; - _currentColumn = null; - - _exprs.Add(new DeleteTableExpression(name)); - - return this; - } - - /// - /// Reference an existing table. - /// - /// Table to reference - /// SchemaBuilder for chaining - public SchemaBuilder WithTable(string name) - { - if (string.IsNullOrEmpty(name)) - throw new ArgumentNullException("name"); - - _currentTable = name; - - return this; - } - - public SchemaBuilder ReferencedTo(string primaryKeyTable, string primaryKeyColumn) - { - _currentColumn.Constraint = ForeignKeyConstraintType.NoAction; - _currentColumn.ForeignKey = new ForeignKey(primaryKeyTable, primaryKeyColumn); - return this; - } - - /// - /// Reference an existing table. - /// - /// Table to reference - /// SchemaBuilder for chaining - public SchemaBuilder RenameTable(string newName) - { - if (string.IsNullOrEmpty(newName)) - throw new ArgumentNullException("newName"); - - _exprs.Add(new RenameTableExpression(_currentTable, newName)); - _currentTable = newName; - - return this; - } - - /// - /// Adds a Column to be created - /// - /// Column name to be added - /// IColumnOptions to restrict chaining - public IColumnOptions AddColumn(string name) - { - if (string.IsNullOrEmpty(name)) - throw new ArgumentNullException("name"); - if (string.IsNullOrEmpty(_currentTable)) - throw new ArgumentException("missing referenced table"); - - IFluentColumn column = new FluentColumn(name); - _currentColumn = column; - - _exprs.Add(new AddColumnExpression(_currentTable, column)); - return this; - } - - public SchemaBuilder WithProperty(ColumnProperty columnProperty) - { - _currentColumn.ColumnProperty = columnProperty; - - return this; - } - - public SchemaBuilder WithDefaultValue(object defaultValue) - { - if (defaultValue == null) - throw new ArgumentNullException("defaultValue", "DefaultValue cannot be null or empty"); - - _currentColumn.DefaultValue = defaultValue; - - return this; - } - - public SchemaBuilder WithConstraint(ForeignKeyConstraintType action) - { - _currentColumn.Constraint = action; - - return this; - } - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/StringUtils.cs b/src/Migrator.Framework/StringUtils.cs deleted file mode 100644 index 07a6dd84..00000000 --- a/src/Migrator.Framework/StringUtils.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Text; -using System.Text.RegularExpressions; - -namespace Migrator.Framework -{ - public class StringUtils - { - /// - /// Convert a classname to something more readable. - /// ex.: CreateATable => Create a table - /// - /// - /// - public static string ToHumanName(string className) - { - string name = Regex.Replace(className, "^[_0-9]*|[_0-9]*$", ""); - - name = Regex.Replace(name, "([A-Z])", " $1").Substring(1); - - return name.Substring(0, 1).ToUpper() + name.Substring(1).ToLower(); - } - - /// - /// - /// - /// - /// - /// - /// - public static string ReplaceOnce(string template, string placeholder, string replacement) - { - int loc = template.IndexOf(placeholder); - if (loc < 0) - { - return template; - } - else - { - return new StringBuilder(template.Substring(0, loc)) - .Append(replacement) - .Append(template.Substring(loc + placeholder.Length)) - .ToString(); - } - } - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/Support/Inflector.cs b/src/Migrator.Framework/Support/Inflector.cs deleted file mode 100644 index 8766edca..00000000 --- a/src/Migrator.Framework/Support/Inflector.cs +++ /dev/null @@ -1,167 +0,0 @@ -using System.Collections; -using System.Text.RegularExpressions; - -namespace Migrator.Framework.Support -{ - public class Inflector - { - private static readonly ArrayList plurals = new ArrayList(); - private static readonly ArrayList singulars = new ArrayList(); - private static readonly ArrayList uncountables = new ArrayList(); - - private Inflector() - { - } - - static Inflector() - { - AddPlural("$", "s"); - AddPlural("s$", "s"); - AddPlural("(ax|test)is$", "$1es"); - AddPlural("(octop|vir)us$", "$1i"); - AddPlural("(alias|status)$", "$1es"); - AddPlural("(bu)s$", "$1ses"); - AddPlural("(buffal|tomat)o$", "$1oes"); - AddPlural("([ti])um$", "$1a"); - AddPlural("sis$", "ses"); - AddPlural("(?:([^f])fe|([lr])f)$", "$1$2ves"); - AddPlural("(hive)$", "$1s"); - AddPlural("([^aeiouy]|qu)y$", "$1ies"); - AddPlural("(x|ch|ss|sh)$", "$1es"); - AddPlural("(matr|vert|ind)ix|ex$", "$1ices"); - AddPlural("([m|l])ouse$", "$1ice"); - AddPlural("^(ox)$", "$1en"); - AddPlural("(quiz)$", "$1zes"); - AddSingular("s$", ""); - AddSingular("(n)ews$", "$1ews"); - AddSingular("([ti])a$", "$1um"); - AddSingular("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$", "$1$2sis"); - AddSingular("(^analy)ses$", "$1sis"); - AddSingular("([^f])ves$", "$1fe"); - AddSingular("(hive)s$", "$1"); - AddSingular("(tive)s$", "$1"); - AddSingular("([lr])ves$", "$1f"); - AddSingular("([^aeiouy]|qu)ies$", "$1y"); - AddSingular("(s)eries$", "$1eries"); - AddSingular("(m)ovies$", "$1ovie"); - AddSingular("(x|ch|ss|sh)es$", "$1"); - AddSingular("([m|l])ice$", "$1ouse"); - AddSingular("(bus)es$", "$1"); - AddSingular("(o)es$", "$1"); - AddSingular("(shoe)s$", "$1"); - AddSingular("(cris|ax|test)es$", "$1is"); - AddSingular("(octop|vir)i$", "$1us"); - AddSingular("(alias|status)es$", "$1"); - AddSingular("^(ox)en", "$1"); - AddSingular("(vert|ind)ices$", "$1ex"); - AddSingular("(matr)ices$", "$1ix"); - AddSingular("(quiz)zes$", "$1"); - AddIrregular("person", "people"); - AddIrregular("man", "men"); - AddIrregular("child", "children"); - AddIrregular("sex", "sexes"); - AddIrregular("move", "moves"); - AddUncountable("equipment"); - AddUncountable("information"); - AddUncountable("rice"); - AddUncountable("money"); - AddUncountable("species"); - AddUncountable("series"); - AddUncountable("fish"); - AddUncountable("sheep"); - } - - private class Rule - { - private readonly Regex regex; - private readonly string replacement; - - public Rule(string pattern, string replacement) - { - regex = new Regex(pattern, RegexOptions.IgnoreCase); - this.replacement = replacement; - } - - public string Apply(string word) - { - if (!regex.IsMatch(word)) - { - return null; - } - - return regex.Replace(word, replacement); - } - } - - /// - /// Return the plural of a word. - /// - /// The singular form - /// The plural form of - public static string Pluralize(string word) - { - return ApplyRules(plurals, word); - } - - /// - /// Return the singular of a word. - /// - /// The plural form - /// The singular form of - public static string Singularize(string word) - { - return ApplyRules(singulars, word); - } - - /// - /// Capitalizes a word. - /// - /// The word to be capitalized. - /// capitalized. - public static string Capitalize(string word) - { - return word.Substring(0, 1).ToUpper() + word.Substring(1).ToLower(); - } - - private static void AddIrregular(string singular, string plural) - { - AddPlural("(" + singular[0] + ")" + singular.Substring(1) + "$", "$1" + plural.Substring(1)); - AddSingular("(" + plural[0] + ")" + plural.Substring(1) + "$", "$1" + singular.Substring(1)); - } - - private static void AddUncountable(string word) - { - uncountables.Add(word.ToLower()); - } - - private static void AddPlural(string rule, string replacement) - { - plurals.Add(new Rule(rule, replacement)); - } - - private static void AddSingular(string rule, string replacement) - { - singulars.Add(new Rule(rule, replacement)); - } - - private static string ApplyRules(IList rules, string word) - { - string result = word; - - if (!uncountables.Contains(word.ToLower())) - { - for (int i = rules.Count - 1; i >= 0; i--) - { - Rule rule = (Rule)rules[i]; - - if ((result = rule.Apply(word)) != null) - { - break; - } - } - } - - return result; - } - } -} diff --git a/src/Migrator.Framework/Support/TransformationProviderUtility.cs b/src/Migrator.Framework/Support/TransformationProviderUtility.cs deleted file mode 100644 index 10e0a907..00000000 --- a/src/Migrator.Framework/Support/TransformationProviderUtility.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; - -namespace Migrator.Framework.Support -{ - public static class TransformationProviderUtility - { - public const int MaxLengthForForeignKeyInOracle = 30; - //static readonly ILog log = LogManager.GetLogger(typeof (TransformationProviderUtility)); - static readonly string[] CommonWords = new[] {"Test"}; - - public static string CreateForeignKeyName(string tableName, string foreignKeyTableName) - { - string fkName = string.Format("FK_{0}_{1}", tableName, foreignKeyTableName); - - return AdjustNameToSize(fkName, MaxLengthForForeignKeyInOracle, true); - } - - public static string AdjustNameToSize(string name, int totalCharacters, bool removeCommmonWords) - { - string adjustedName = name; - - if (adjustedName.Length > totalCharacters) - { - if (removeCommmonWords) - { - adjustedName = RemoveCommonWords(adjustedName); - } - } - - if (adjustedName.Length > totalCharacters) adjustedName = adjustedName.Substring(0, totalCharacters); - - if (name != adjustedName) - { - //log.WarnFormat("Name has been truncated from: {0} to: {1}", name, adjustedName); - } - - return adjustedName; - } - - static string RemoveCommonWords(string adjustedName) - { - foreach (var word in CommonWords) - { - if (adjustedName.Contains(word)) - { - adjustedName = adjustedName.Replace(word, string.Empty); - } - } - return adjustedName; - } - - public static string FormatTableName(string schema, string tableName) - { - return string.IsNullOrEmpty(schema) ? tableName : string.Format("{0}.{1}", schema, tableName); - } - - public static string GetQualifiedResourcePath(Assembly assembly, string resourceName) - { - var resources = assembly.GetManifestResourceNames(); - - //resource full name is in format `namespace.resourceName` - var sqlScriptParts = resourceName.Split('.').Reverse().ToArray(); - Func isNameMatch = x => x.Split('.').Reverse().Take(sqlScriptParts.Length).SequenceEqual(sqlScriptParts, StringComparer.InvariantCultureIgnoreCase); - - string result = null; - var foundResources = resources.Where(isNameMatch).ToArray(); - - if (foundResources.Length == 0) throw new InvalidOperationException(string.Format("Could not find resource named {0} in assembly {1}", resourceName, assembly.FullName)); - - if (foundResources.Length > 1) throw new InvalidOperationException(string.Format(@"Could not find unique resource named {0} in assembly {1}.Possible candidates are: {2}", resourceName, assembly.FullName, string.Join(Environment.NewLine + "\t", foundResources))); - - return foundResources[0]; - } - } -} \ No newline at end of file diff --git a/src/Migrator.Framework/Unique.cs b/src/Migrator.Framework/Unique.cs deleted file mode 100644 index 0930acc1..00000000 --- a/src/Migrator.Framework/Unique.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Migrator.Framework -{ - public class Unique : IDbField - { - public string Name { get; set; } - public string[] KeyColumns { get; set; } - } -} diff --git a/src/Migrator.Framework/ViewField.cs b/src/Migrator.Framework/ViewField.cs deleted file mode 100644 index 9b848ac5..00000000 --- a/src/Migrator.Framework/ViewField.cs +++ /dev/null @@ -1,43 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System.Data; - -namespace Migrator.Framework -{ - /// - /// Represents a table column. - /// - public class ViewField : IViewField - { - public ViewField(string ColumnName) - { - this.ColumnName = ColumnName; - } - - public ViewField(string ColumnName, string TableName, string KeyColumnName, string ParentTableName, string ParentKeyColumnName) - { - this.ColumnName = ColumnName; - this.TableName = TableName; - this.KeyColumnName = KeyColumnName; - this.ParentTableName = ParentTableName; - this.ParentKeyColumnName = ParentKeyColumnName; - } - - public string TableName { get; set; } - public string ColumnName { get; set; } - public string KeyColumnName { get; set; } - public string ParentTableName { get; set; } - public string ParentKeyColumnName { get; set; } - } -} \ No newline at end of file diff --git a/src/Migrator.MSBuild/Logger/TaskLogger.cs b/src/Migrator.MSBuild/Logger/TaskLogger.cs deleted file mode 100644 index d6f13081..00000000 --- a/src/Migrator.MSBuild/Logger/TaskLogger.cs +++ /dev/null @@ -1,129 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.Collections.Generic; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using ILogger = Migrator.Framework.ILogger; - -namespace Migrator.MSBuild.Logger -{ - /// - /// MSBuild task logger for the migration mediator - /// - public class TaskLogger : ILogger - { - readonly Task _task; - - public TaskLogger(Task task) - { - _task = task; - } - - public void Started(List currentVersions, long finalVersion) - { - LogInfo("Latest version applied : {0}. Target version : {1}", LatestVersion(currentVersions), finalVersion); - } - - public void MigrateUp(long version, string migrationName) - { - LogInfo("Applying {0}: {1}", version.ToString(), migrationName); - } - - public void MigrateDown(long version, string migrationName) - { - LogInfo("Removing {0}: {1}", version.ToString(), migrationName); - } - - public void Skipping(long version) - { - MigrateUp(version, ""); - } - - public void RollingBack(long originalVersion) - { - LogInfo("Rolling back to migration {0}", originalVersion); - } - - public void ApplyingDBChange(string sql) - { - Log(sql); - } - - public void Exception(long version, string migrationName, Exception ex) - { - LogInfo("============ Error Detail ============"); - LogInfo("Error in migration: {0}", version); - _task.Log.LogErrorFromException(ex, true); - LogInfo("======================================"); - } - - public void Exception(string message, Exception ex) - { - LogInfo("============ Error Detail ============"); - LogInfo("Error: {0}", message); - _task.Log.LogErrorFromException(ex, true); - LogInfo("======================================"); - } - - public void Finished(List originalVersion, long currentVersion) - { - LogInfo("Migrated to version {0}", currentVersion); - } - - public void Log(string format, params object[] args) - { - LogInfo(format, args); - } - - public void Warn(string format, params object[] args) - { - _task.Log.LogWarning("[Warning] {0}", String.Format(format, args)); - } - - public void Trace(string format, params object[] args) - { - _task.Log.LogMessage(MessageImportance.Low, format, args); - } - - protected void LogInfo(string format, params object[] args) - { - _task.Log.LogMessage(format, args); - } - - protected void LogError(string format, params object[] args) - { - _task.Log.LogError(format, args); - } - - public void Started(long currentVersion, long finalVersion) - { - LogInfo("Current version : {0}", currentVersion); - } - - public void Finished(long originalVersion, long currentVersion) - { - LogInfo("Migrated to version {0}", currentVersion); - } - - string LatestVersion(List versions) - { - if (versions.Count > 0) - { - return versions[versions.Count - 1].ToString(); - } - return "No migrations applied yet!"; - } - } -} \ No newline at end of file diff --git a/src/Migrator.MSBuild/MigrateTask.cs b/src/Migrator.MSBuild/MigrateTask.cs deleted file mode 100644 index e45cab37..00000000 --- a/src/Migrator.MSBuild/MigrateTask.cs +++ /dev/null @@ -1,157 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.IO; -using System.Reflection; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using Migrator.Compile; -using Migrator.Framework.Loggers; -using Migrator.MSBuild.Logger; -using Migrator.Providers; - -namespace Migrator.MSBuild -{ - /// - /// Runs migrations on a database - /// - /// - /// To script the changes applied to the database via the migrations into a file, set the - /// flag and provide a file to write the changes to via the setting. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public class Migrate : Task - { - string _scriptFile; - long _to = -1; // To last revision - - [Required] - public ProviderTypes Provider { set; get; } - - public string DefaultSchema { get; set; } - - [Required] - public string ConnectionString { set; get; } - - /// - /// The paths to the assemblies that contain your migrations. - /// This will generally just be a single item. - /// - public ITaskItem[] Migrations { set; get; } - - /// - /// The paths to the directory that contains your migrations. - /// This will generally just be a single item. - /// - public string Directory { set; get; } - - public string Language { set; get; } - - public long To - { - set { _to = value; } - get { return _to; } - } - - public bool Trace { set; get; } - - public bool DryRun { set; get; } - - /// - /// Gets value indicating whether to script the changes made to the database - /// to the file indicated by . - /// - /// true if the changes should be scripted to a file; otherwise, false. - public bool ScriptChanges - { - get { return !String.IsNullOrEmpty(_scriptFile); } - } - - /// - /// Gets or sets the script file that will contain the Sql statements - /// that are executed as part of the migrations. - /// - public string ScriptFile - { - get { return _scriptFile; } - set { _scriptFile = value; } - } - - public override bool Execute() - { - if (! String.IsNullOrEmpty(Directory)) - { - var engine = new ScriptEngine(Language, null); - Execute(engine.Compile(Directory)); - } - - if (null != Migrations) - { - foreach (ITaskItem assembly in Migrations) - { - Assembly asm = Assembly.LoadFrom(assembly.GetMetadata("FullPath")); - Execute(asm); - } - } - - return true; - } - - void Execute(Assembly asm) - { - var mig = new Migrator(Provider, ConnectionString, DefaultSchema, asm, Trace, new TaskLogger(this)); - mig.DryRun = DryRun; - if (ScriptChanges) - { - using (var writer = new StreamWriter(ScriptFile)) - { - mig.Logger = new SqlScriptFileLogger(mig.Logger, writer); - RunMigration(mig); - } - } - else - { - RunMigration(mig); - } - } - - void RunMigration(Migrator mig) - { - if (mig.DryRun) - mig.Logger.Log("********** Dry run! Not actually applying changes. **********"); - - if (_to == -1) - mig.MigrateToLastVersion(); - else - mig.MigrateTo(_to); - } - } -} \ No newline at end of file diff --git a/src/Migrator.MSBuild/Migrator.MSBuild-vs2008.csproj b/src/Migrator.MSBuild/Migrator.MSBuild-vs2008.csproj deleted file mode 100644 index 7de5cb93..00000000 --- a/src/Migrator.MSBuild/Migrator.MSBuild-vs2008.csproj +++ /dev/null @@ -1,69 +0,0 @@ - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {A145FFA9-5FE6-4636-93B8-0C110D132BF3} - Library - Migrator.MSBuild - Migrator.MSBuild - - - 2.0 - - - true - MigratorDotNet.snk - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - {1FEE70A4-AAD7-4C60-BE60-3F7DC03A8C4D} - Migrator-vs2008 - - - {5270F048-E580-486C-B14C-E5B9F6E539D4} - Migrator.Framework-vs2008 - - - - - \ No newline at end of file diff --git a/src/Migrator.MSBuild/Migrator.MSBuild-vs2010.csproj b/src/Migrator.MSBuild/Migrator.MSBuild-vs2010.csproj deleted file mode 100644 index 2475b022..00000000 --- a/src/Migrator.MSBuild/Migrator.MSBuild-vs2010.csproj +++ /dev/null @@ -1,110 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {A145FFA9-5FE6-4636-93B8-0C110D132BF3} - Library - Migrator.MSBuild - Migrator.MSBuild - - - 3.5 - - - true - MigratorDotNet.snk - v4.0 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - AllRules.ruleset - - - - - - - - - - - - - - - - - - - {d58c68e4-d789-40f7-9078-c9f587d4363c} - DotNetProjects.Migrator.Providers - - - {1FEE70A4-AAD7-4C60-BE60-3F7DC03A8C4D} - DotNetProjects.Migrator - - - {5270F048-E580-486C-B14C-E5B9F6E539D4} - DotNetProjects.Migrator.Framework - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - - - - - \ No newline at end of file diff --git a/src/Migrator.MSBuild/Migrator.Targets b/src/Migrator.MSBuild/Migrator.Targets deleted file mode 100644 index 46d9de28..00000000 --- a/src/Migrator.MSBuild/Migrator.Targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - $(MSBuildExtensionsPath)\MigratorTasks - $(MigratorTasksPath)\Migrator.MSBuild.dll - - - - diff --git a/src/Migrator.MSBuild/MigratorDotNet.snk b/src/Migrator.MSBuild/MigratorDotNet.snk deleted file mode 100644 index 5032d709..00000000 Binary files a/src/Migrator.MSBuild/MigratorDotNet.snk and /dev/null differ diff --git a/src/Migrator.MSBuild/example-build.proj b/src/Migrator.MSBuild/example-build.proj deleted file mode 100644 index 0c8afde6..00000000 --- a/src/Migrator.MSBuild/example-build.proj +++ /dev/null @@ -1,28 +0,0 @@ - - - - Debug - bin\$(Configuration) - src\Migrations.csproj - - - - - - - - - - - - - - - - - - - - diff --git a/src/Migrator.NAnt/Loggers/TaskLogger.cs b/src/Migrator.NAnt/Loggers/TaskLogger.cs deleted file mode 100644 index 779051e3..00000000 --- a/src/Migrator.NAnt/Loggers/TaskLogger.cs +++ /dev/null @@ -1,141 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.Collections.Generic; -using Migrator.Framework; -using NAnt.Core; - -namespace Migrator.NAnt.Loggers -{ - /// - /// NAnt task logger for the migration mediator - /// - public class TaskLogger : ILogger - { - readonly Task _task; - - public TaskLogger(Task task) - { - _task = task; - } - - public void Started(List currentVersions, long finalVersion) - { - LogInfo("Latest version applied : {0}. Target version : {1}", LatestVersion(currentVersions), finalVersion); - } - - public void MigrateUp(long version, string migrationName) - { - LogInfo("Applying {0}: {1}", version.ToString(), migrationName); - } - - public void MigrateDown(long version, string migrationName) - { - LogInfo("Removing {0}: {1}", version.ToString(), migrationName); - } - - public void Skipping(long version) - { - MigrateUp(version, ""); - } - - public void RollingBack(long originalVersion) - { - LogInfo("Rolling back to migration {0}", originalVersion); - } - - public void ApplyingDBChange(string sql) - { - Log(sql); - } - - public void Exception(long version, string migrationName, Exception ex) - { - LogInfo("============ Error Detail ============"); - LogInfo("Error in migration: {0}", version); - LogExceptionDetails(ex); - LogInfo("======================================"); - } - - public void Exception(string message, Exception ex) - { - LogInfo("============ Error Detail ============"); - LogInfo("Error: {0}", message); - LogExceptionDetails(ex); - LogInfo("======================================"); - } - - public void Finished(List originalVersion, long currentVersion) - { - LogInfo("Migrated to version {0}", currentVersion); - } - - public void Log(string format, params object[] args) - { - LogInfo(format, args); - } - - public void Warn(string format, params object[] args) - { - LogInfo("[Warning] {0}", String.Format(format, args)); - } - - public void Trace(string format, params object[] args) - { - _task.Log(Level.Debug, format, args); - } - - protected void LogInfo(string format, params object[] args) - { - _task.Log(Level.Info, format, args); - } - - protected void LogError(string format, params object[] args) - { - _task.Log(Level.Error, format, args); - } - - public void Started(long currentVersion, long finalVersion) - { - LogInfo("Current version : {0}", currentVersion); - } - - void LogExceptionDetails(Exception ex) - { - LogInfo("{0}", ex.Message); - LogInfo("{0}", ex.StackTrace); - Exception iex = ex.InnerException; - while (iex != null) - { - LogInfo("Caused by: {0}", iex); - LogInfo("{0}", ex.StackTrace); - iex = iex.InnerException; - } - } - - public void Finished(long originalVersion, long currentVersion) - { - LogInfo("Migrated to version {0}", currentVersion); - } - - string LatestVersion(List versions) - { - if (versions.Count > 0) - { - return versions[versions.Count - 1].ToString(); - } - return "No migrations applied yet!"; - } - } -} \ No newline at end of file diff --git a/src/Migrator.NAnt/MigrateTask.cs b/src/Migrator.NAnt/MigrateTask.cs deleted file mode 100644 index 21d74d40..00000000 --- a/src/Migrator.NAnt/MigrateTask.cs +++ /dev/null @@ -1,147 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.IO; -using System.Reflection; -using Migrator.Compile; -using Migrator.Framework.Loggers; -using Migrator.NAnt.Loggers; -using Migrator.Providers; - -using NAnt.Core; -using NAnt.Core.Attributes; - -namespace Migrator.NAnt -{ - /// - /// Runs migrations on a database - /// - /// - /// - /// - /// - /// - /// - /// - [TaskName("migrate")] - public class MigrateTask : Task - { - string _scriptFile; - long _to = -1; // To last revision - - [TaskAttribute("defaultschema")] - public string DefaultSchema { get; set; } - - [TaskAttribute("provider", Required = true)] - public ProviderTypes Provider { set; get; } - - [TaskAttribute("connectionstring", Required = true)] - public string ConnectionString { set; get; } - - [TaskAttribute("migrations")] - public FileInfo MigrationsAssembly { set; get; } - - /// - /// The paths to the directory that contains your migrations. - /// This will generally just be a single item. - /// - [TaskAttribute("directory")] - public string Directory { set; get; } - - [TaskAttribute("language")] - public string Language { set; get; } - - [TaskAttribute("to")] - public long To - { - set { _to = value; } - get { return _to; } - } - - [TaskAttribute("trace")] - public bool Trace { set; get; } - - [TaskAttribute("dryrun")] - public bool DryRun { set; get; } - - /// - /// Gets value indicating whether to script the changes made to the database - /// to the file indicated by . - /// - /// true if the changes should be scripted to a file; otherwise, false. - public bool ScriptChanges - { - get { return !String.IsNullOrEmpty(_scriptFile); } - } - - /// - /// Gets or sets the script file that will contain the Sql statements - /// that are executed as part of the migrations. - /// - [TaskAttribute("scriptFile")] - public string ScriptFile - { - get { return _scriptFile; } - set { _scriptFile = value; } - } - - protected override void ExecuteTask() - { - if (! String.IsNullOrEmpty(Directory)) - { - var engine = new ScriptEngine(Language, null); - Execute(engine.Compile(Directory)); - } - - if (null != MigrationsAssembly) - { - Assembly asm = Assembly.LoadFrom(MigrationsAssembly.FullName); - Execute(asm); - } - } - - void Execute(Assembly asm) - { - var mig = new Migrator(Provider, ConnectionString, DefaultSchema, asm, Trace, new TaskLogger(this)); - mig.DryRun = DryRun; - if (ScriptChanges) - { - using (var writer = new StreamWriter(ScriptFile)) - { - mig.Logger = new SqlScriptFileLogger(mig.Logger, writer); - RunMigration(mig); - } - } - else - { - RunMigration(mig); - } - } - - void RunMigration(Migrator mig) - { - if (mig.DryRun) - mig.Logger.Log("********** Dry run! Not actually applying changes. **********"); - - if (_to == -1) - mig.MigrateToLastVersion(); - else - mig.MigrateTo(_to); - } - } -} \ No newline at end of file diff --git a/src/Migrator.NAnt/Migrator.NAnt-vs2008.csproj b/src/Migrator.NAnt/Migrator.NAnt-vs2008.csproj deleted file mode 100644 index 44f4d6f1..00000000 --- a/src/Migrator.NAnt/Migrator.NAnt-vs2008.csproj +++ /dev/null @@ -1,71 +0,0 @@ - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {CDD39DB7-C9C0-4ECA-AD36-1B4D0BF59101} - Library - Properties - Migrator.NAnt - Migrator.NAnt - - - 2.0 - - - false - MigratorDotNet.snk - - - true - full - false - bin\Migrator.NAnt\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Migrator.NAnt\Release\ - TRACE - prompt - 4 - - - - False - ..\..\lib\log4net.dll - - - - - - ..\..\lib\NAnt.Core.dll - False - - - - - - - - - - - - {1FEE70A4-AAD7-4C60-BE60-3F7DC03A8C4D} - Migrator-vs2008 - - - {5270F048-E580-486C-B14C-E5B9F6E539D4} - Migrator.Framework-vs2008 - - - - - - - \ No newline at end of file diff --git a/src/Migrator.NAnt/Migrator.NAnt-vs2010.csproj b/src/Migrator.NAnt/Migrator.NAnt-vs2010.csproj deleted file mode 100644 index 4fd0cdca..00000000 --- a/src/Migrator.NAnt/Migrator.NAnt-vs2010.csproj +++ /dev/null @@ -1,112 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {CDD39DB7-C9C0-4ECA-AD36-1B4D0BF59101} - Library - Properties - Migrator.NAnt - Migrator.NAnt - - - 3.5 - - - false - MigratorDotNet.snk - v4.0 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - true - full - false - bin\Migrator.NAnt\Debug\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - - - pdbonly - true - bin\Migrator.NAnt\Release\ - TRACE - prompt - 4 - AllRules.ruleset - - - - False - ..\..\lib\log4net.dll - - - False - ..\..\lib\NAnt.Core.dll - - - - - - - - - - - - - - - {d58c68e4-d789-40f7-9078-c9f587d4363c} - DotNetProjects.Migrator.Providers - - - {1FEE70A4-AAD7-4C60-BE60-3F7DC03A8C4D} - DotNetProjects.Migrator - - - {5270F048-E580-486C-B14C-E5B9F6E539D4} - DotNetProjects.Migrator.Framework - - - - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - - - - \ No newline at end of file diff --git a/src/Migrator.NAnt/MigratorDotNet.snk b/src/Migrator.NAnt/MigratorDotNet.snk deleted file mode 100644 index 5032d709..00000000 Binary files a/src/Migrator.NAnt/MigratorDotNet.snk and /dev/null differ diff --git a/src/Migrator.Providers/AssemblyInfo.cs b/src/Migrator.Providers/AssemblyInfo.cs deleted file mode 100644 index 037167e2..00000000 --- a/src/Migrator.Providers/AssemblyInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ -using System.Reflection; - -[assembly: AssemblyTitle("DotNetProjects.Migrator.Providers")] -[assembly: AssemblyDescription("Standard Migration Provider")] \ No newline at end of file diff --git a/src/Migrator.Providers/ColumnPropertiesMapper.cs b/src/Migrator.Providers/ColumnPropertiesMapper.cs deleted file mode 100644 index aabc05d5..00000000 --- a/src/Migrator.Providers/ColumnPropertiesMapper.cs +++ /dev/null @@ -1,226 +0,0 @@ -using System; -using System.Collections.Generic; -using Migrator.Framework; - -namespace Migrator.Providers -{ - /// - /// This is basically a just a helper base class - /// per-database implementors may want to override ColumnSql - /// - public class ColumnPropertiesMapper - { - /// - /// the type of the column - /// - protected string columnSql; - - /// - /// Sql if this column has a default value - /// - protected object defaultVal; - - protected Dialect dialect; - - /// - /// Sql if This column is Indexed - /// - protected bool indexed; - - /// The name of the column - protected string name; - - /// The SQL type - public string type { get; private set; } - - public ColumnPropertiesMapper(Dialect dialect, string type) - { - this.dialect = dialect; - this.type = type; - } - - /// - /// The sql for this column, override in database-specific implementation classes - /// - public virtual string ColumnSql - { - get { return columnSql; } - } - - public string Name - { - get { return name; } - set { name = value; } - } - - public object Default - { - get { return defaultVal; } - set { defaultVal = value; } - } - - public string QuotedName - { - get { return dialect.Quote(Name); } - } - - public string IndexSql - { - get - { - if (dialect.SupportsIndex && indexed) - return String.Format("INDEX({0})", dialect.Quote(name)); - return null; - } - } - - public virtual void MapColumnProperties(Column column) - { - Name = column.Name; - - indexed = PropertySelected(column.ColumnProperty, ColumnProperty.Indexed); - - var vals = new List(); - - AddName(vals); - - AddType(vals); - - AddCaseSensitive(column, vals); - - AddIdentity(column, vals); - - AddUnsigned(column, vals); - - AddNotNull(column, vals); - - AddNull(column, vals); - - AddPrimaryKey(column, vals); - - AddIdentityAgain(column, vals); - - AddUnique(column, vals); - - AddForeignKey(column, vals); - - AddDefaultValue(column, vals); - - columnSql = String.Join(" ", vals.ToArray()); - } - - public virtual void MapColumnPropertiesWithoutDefault(Column column) - { - Name = column.Name; - - indexed = PropertySelected(column.ColumnProperty, ColumnProperty.Indexed); - - var vals = new List(); - - AddName(vals); - - AddType(vals); - - AddCaseSensitive(column, vals); - - AddIdentity(column, vals); - - AddUnsigned(column, vals); - - AddNotNull(column, vals); - - AddNull(column, vals); - - AddPrimaryKey(column, vals); - - AddIdentityAgain(column, vals); - - AddUnique(column, vals); - - AddForeignKey(column, vals); - - columnSql = String.Join(" ", vals.ToArray()); - } - - protected virtual void AddCaseSensitive(Column column, List vals) - { - AddValueIfSelected(column, ColumnProperty.CaseSensitive, vals); - } - - protected virtual void AddDefaultValue(Column column, List vals) - { - if (column.DefaultValue != null) - vals.Add(dialect.Default(column.DefaultValue)); - } - - protected virtual void AddForeignKey(Column column, List vals) - { - AddValueIfSelected(column, ColumnProperty.ForeignKey, vals); - } - - protected virtual void AddUnique(Column column, List vals) - { - AddValueIfSelected(column, ColumnProperty.Unique, vals); - } - - protected virtual void AddIdentityAgain(Column column, List vals) - { - if (dialect.IdentityNeedsType) - AddValueIfSelected(column, ColumnProperty.Identity, vals); - } - - protected virtual void AddPrimaryKey(Column column, List vals) - { - AddValueIfSelected(column, ColumnProperty.PrimaryKey, vals); - } - - protected virtual void AddNull(Column column, List vals) - { - if (!PropertySelected(column.ColumnProperty, ColumnProperty.PrimaryKey)) - { - if (dialect.NeedsNullForNullableWhenAlteringTable) AddValueIfSelected(column, ColumnProperty.Null, vals); - } - } - - protected virtual void AddNotNull(Column column, List vals) - { - if (!PropertySelected(column.ColumnProperty, ColumnProperty.Null) && (!PropertySelected(column.ColumnProperty, ColumnProperty.PrimaryKey) || dialect.NeedsNotNullForIdentity)) - { - AddValueIfSelected(column, ColumnProperty.NotNull, vals); - } - } - - protected virtual void AddUnsigned(Column column, List vals) - { - if (dialect.IsUnsignedCompatible(column.Type)) - AddValueIfSelected(column, ColumnProperty.Unsigned, vals); - } - - protected virtual void AddIdentity(Column column, List vals) - { - if (!dialect.IdentityNeedsType) - AddValueIfSelected(column, ColumnProperty.Identity, vals); - } - - protected virtual void AddType(List vals) - { - vals.Add(type); - } - - protected virtual void AddName(List vals) - { - vals.Add(dialect.ColumnNameNeedsQuote || dialect.IsReservedWord(Name) ? QuotedName : Name); - } - - protected virtual void AddValueIfSelected(Column column, ColumnProperty property, ICollection vals) - { - if (PropertySelected(column.ColumnProperty, property)) - vals.Add(dialect.SqlForProperty(property)); - } - - public static bool PropertySelected(ColumnProperty source, ColumnProperty comparison) - { - return (source & comparison) == comparison; - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/DbProviderFactoriesHelper.cs b/src/Migrator.Providers/DbProviderFactoriesHelper.cs deleted file mode 100644 index f7d60531..00000000 --- a/src/Migrator.Providers/DbProviderFactoriesHelper.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.Common; -using System.Linq; -using System.Text; - -namespace Migrator.Providers -{ - public static class DbProviderFactoriesHelper - { - public static DbProviderFactory GetFactory(string providerName, string assemblyName, string factoryProviderType) - { - try - { - return DbProviderFactories.GetFactory(providerName); - } - catch(Exception) - { } - - return (DbProviderFactory)AppDomain.CurrentDomain.CreateInstanceAndUnwrap(assemblyName, factoryProviderType); - } - } -} diff --git a/src/Migrator.Providers/Dialect.cs b/src/Migrator.Providers/Dialect.cs deleted file mode 100644 index c860dd0b..00000000 --- a/src/Migrator.Providers/Dialect.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Globalization; -using Migrator.Framework; - -namespace Migrator.Providers -{ - /// - /// Defines the implementations specific details for a particular database. - /// - public abstract class Dialect - { - readonly Dictionary propertyMap = new Dictionary(); - readonly HashSet reservedWords = new HashSet(); - readonly TypeNames typeNames = new TypeNames(); - readonly List unsignedCompatibleTypes = new List(); - - protected Dialect() - { - RegisterProperty(ColumnProperty.Null, "NULL"); - RegisterProperty(ColumnProperty.NotNull, "NOT NULL"); - RegisterProperty(ColumnProperty.Unique, "UNIQUE"); - RegisterProperty(ColumnProperty.PrimaryKey, "PRIMARY KEY"); - } - - public virtual bool ColumnNameNeedsQuote - { - get { return false; } - } - - public virtual bool TableNameNeedsQuote - { - get { return false; } - } - - public virtual bool ConstraintNameNeedsQuote - { - get { return false; } - } - - public virtual bool IdentityNeedsType - { - get { return true; } - } - - public virtual bool NeedsNotNullForIdentity - { - get { return true; } - } - - public virtual bool SupportsIndex - { - get { return true; } - } - - public virtual string QuoteTemplate - { - get { return "\"{0}\""; } - } - - public virtual bool NeedsNullForNullableWhenAlteringTable - { - get { return false; } - } - - protected void AddReservedWord(string reservedWord) - { - reservedWords.Add(reservedWord.ToUpperInvariant()); - } - - protected void AddReservedWords(params string[] words) - { - if (words == null) return; - foreach (string word in words) reservedWords.Add(word); - } - - public virtual bool IsReservedWord(string reservedWord) - { - if (string.IsNullOrEmpty(reservedWord)) throw new ArgumentNullException("reservedWord"); - - if (reservedWords == null) return false; - - bool isReserved = reservedWords.Contains(reservedWord.ToUpperInvariant()); - - if (isReserved) - { - Console.WriteLine("Reserved word: {0}", reservedWord); - } - - return isReserved; - } - - public abstract ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName); - public abstract ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, string defaultSchema, string scope, string providerName); - - public ITransformationProvider NewProviderForDialect(string connectionString, string defaultSchema, string scope, string providerName) - { - return GetTransformationProvider(this, connectionString, defaultSchema, scope, providerName); - } - - public ITransformationProvider NewProviderForDialect(IDbConnection connection, string defaultSchema, string scope, string providerName) - { - return GetTransformationProvider(this, connection, defaultSchema, scope, providerName); - } - - /// - /// Subclasses register a typename for the given type code and maximum - /// column length. $l in the type name will be replaced by the column - /// length (if appropriate) - /// - /// The typecode - /// Maximum length of database type - /// The database type name - protected void RegisterColumnType(DbType code, int capacity, string name) - { - typeNames.Put(code, capacity, name); - } - - /// - /// Suclasses register a typename for the given type code. $l in the - /// typename will be replaced by the column length (if appropriate). - /// - /// The typecode - /// The database type name - protected void RegisterColumnType(DbType code, string name) - { - typeNames.Put(code, name); - } - - public virtual ColumnPropertiesMapper GetColumnMapper(Column column) - { - string type = column.Size > 0 ? GetTypeName(column.Type, column.Size) : GetTypeName(column.Type); - if (! IdentityNeedsType && column.IsIdentity) - type = String.Empty; - - return new ColumnPropertiesMapper(this, type); - } - - public virtual DbType GetDbTypeFromString(string type) - { - return typeNames.GetDbType(type); - } - - /// - /// Get the name of the database type associated with the given - /// - /// The DbType - /// The database type name used by ddl. - public virtual string GetTypeName(DbType type) - { - string result = typeNames.Get(type); - if (result == null) - { - throw new Exception(string.Format("No default type mapping for DbType {0}", type)); - } - - return result; - } - - /// - /// Get the name of the database type associated with the given - /// - /// The DbType - /// The database type name used by ddl. - /// - public virtual string GetTypeName(DbType type, int length) - { - return GetTypeName(type, length, 0, 0); - } - - /// - /// Get the name of the database type associated with the given - /// - /// The DbType - /// The database type name used by ddl. - /// - /// - /// - public virtual string GetTypeName(DbType type, int length, int precision, int scale) - { - string resultWithLength = typeNames.Get(type, length, precision, scale); - if (resultWithLength != null) - return resultWithLength; - - return GetTypeName(type); - } - - /// - /// Get the type from the specified database type name. - /// Note: This does not work perfectly, but it will do for most cases. - /// - /// The name of the type. - /// The . - public virtual DbType GetDbType(string databaseTypeName) - { - return typeNames.GetDbType(databaseTypeName); - } - - public void RegisterProperty(ColumnProperty property, string sql) - { - if (! propertyMap.ContainsKey(property)) - { - propertyMap.Add(property, sql); - } - propertyMap[property] = sql; - } - - public string SqlForProperty(ColumnProperty property) - { - if (propertyMap.ContainsKey(property)) - { - return propertyMap[property]; - } - return String.Empty; - } - - public virtual string Quote(string value) - { - return String.Format(QuoteTemplate, value); - } - - public virtual string Default(object defaultValue) - { - if (defaultValue is String && defaultValue.ToString() == String.Empty) - { - defaultValue = "''"; - } - else if (defaultValue is Guid) - { - return String.Format("DEFAULT '{0}'", defaultValue.ToString()); - } - else if (defaultValue is DateTime) - { - return String.Format("DEFAULT '{0}'", ((DateTime)defaultValue).ToString("yyyy-MM-dd HH:mm:ss")); - } - else if (defaultValue is String) - { - defaultValue = ((String)defaultValue).Replace("'", "''"); - defaultValue = "'" + defaultValue + "'"; - } - - return String.Format("DEFAULT {0}", defaultValue); - } - - public ColumnPropertiesMapper GetAndMapColumnProperties(Column column) - { - ColumnPropertiesMapper mapper = GetColumnMapper(column); - mapper.MapColumnProperties(column); - if (column.DefaultValue != null && column.DefaultValue != DBNull.Value) - mapper.Default = column.DefaultValue; - return mapper; - } - - public ColumnPropertiesMapper GetAndMapColumnPropertiesWithoutDefault(Column column) - { - ColumnPropertiesMapper mapper = GetColumnMapper(column); - mapper.MapColumnPropertiesWithoutDefault(column); - if (column.DefaultValue != null && column.DefaultValue != DBNull.Value) - mapper.Default = column.DefaultValue; - return mapper; - } - - /// - /// Subclasses register which DbTypes are unsigned-compatible (ie, available in signed and unsigned variants) - /// - /// - protected void RegisterUnsignedCompatible(DbType type) - { - unsignedCompatibleTypes.Add(type); - } - - /// - /// Determine if a particular database type has an unsigned variant - /// - /// The DbType - /// True if the database type has an unsigned variant, otherwise false - public bool IsUnsignedCompatible(DbType type) - { - return unsignedCompatibleTypes.Contains(type); - } - - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/DotNetProjects.Migrator.Providers.csproj b/src/Migrator.Providers/DotNetProjects.Migrator.Providers.csproj deleted file mode 100644 index a9a8aa64..00000000 --- a/src/Migrator.Providers/DotNetProjects.Migrator.Providers.csproj +++ /dev/null @@ -1,165 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {D58C68E4-D789-40F7-9078-C9F587D4363C} - Library - Migrator.Providers - DotNetProjects.Migrator.Providers - - - 3.5 - - - false - true - MigratorDotNet.snk - v4.0 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true - - - - true - full - false - bin\Debug\ - TRACE;DEBUG - prompt - 4 - AllRules.ruleset - default - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - AllRules.ruleset - - - - False - ..\..\lib\NAnt.Core.dll - - - - - - - - - GlobalAssemblyInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 2.0 %28x86%29 - true - - - False - .NET Framework 3.0 %28x86%29 - false - - - False - .NET Framework 3.5 - false - - - False - .NET Framework 3.5 SP1 - false - - - - - - - - {5270F048-E580-486C-B14C-E5B9F6E539D4} - DotNetProjects.Migrator.Framework - - - - - - \ No newline at end of file diff --git a/src/Migrator.Providers/ForeignKeyConstraintMapper.cs b/src/Migrator.Providers/ForeignKeyConstraintMapper.cs deleted file mode 100644 index 3df89f5f..00000000 --- a/src/Migrator.Providers/ForeignKeyConstraintMapper.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Migrator.Framework; - -namespace Migrator.Providers -{ - public class ForeignKeyConstraintMapper - { - public string SqlForConstraint(ForeignKeyConstraintType constraint) - { - switch (constraint) - { - case ForeignKeyConstraintType.Cascade: - return "CASCADE"; - case ForeignKeyConstraintType.Restrict: - return "RESTRICT"; - case ForeignKeyConstraintType.SetDefault: - return "SET DEFAULT"; - case ForeignKeyConstraintType.SetNull: - return "SET NULL"; - default: - return "NO ACTION"; - } - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/DB2/DB2Dialect.cs b/src/Migrator.Providers/Impl/DB2/DB2Dialect.cs deleted file mode 100644 index 48341675..00000000 --- a/src/Migrator.Providers/Impl/DB2/DB2Dialect.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Data; - -using Migrator.Framework; - -namespace Migrator.Providers.Impl.DB2 -{ - public class DB2Dialect : Dialect - { - public DB2Dialect() - { - this.RegisterColumnType(DbType.AnsiStringFixedLength, "CHAR(255)"); - this.RegisterColumnType(DbType.AnsiStringFixedLength, 255, "CHAR($l)"); - this.RegisterColumnType(DbType.AnsiStringFixedLength, 65535, "TEXT"); - this.RegisterColumnType(DbType.AnsiStringFixedLength, 16777215, "MEDIUMTEXT"); - this.RegisterColumnType(DbType.AnsiString, "VARCHAR(255)"); - this.RegisterColumnType(DbType.AnsiString, 255, "VARCHAR($l)"); - this.RegisterColumnType(DbType.AnsiString, 256, "VARCHAR(255)"); - this.RegisterColumnType(DbType.AnsiString, 65535, "TEXT"); - this.RegisterColumnType(DbType.AnsiString, 16777215, "MEDIUMTEXT"); - this.RegisterColumnType(DbType.Binary, "LONGBLOB"); - this.RegisterColumnType(DbType.Binary, 127, "TINYBLOB"); - this.RegisterColumnType(DbType.Binary, 65535, "BLOB"); - this.RegisterColumnType(DbType.Binary, 16777215, "MEDIUMBLOB"); - this.RegisterColumnType(DbType.Boolean, "TINYINT(1)"); - this.RegisterColumnType(DbType.Byte, "TINYINT UNSIGNED"); - this.RegisterColumnType(DbType.Currency, "MONEY"); - this.RegisterColumnType(DbType.Date, "DATE"); - this.RegisterColumnType(DbType.DateTime, "DATETIME"); - this.RegisterColumnType(DbType.DateTimeOffset, "DATETIME"); - this.RegisterColumnType(DbType.Decimal, "NUMERIC(19,5)"); - this.RegisterColumnType(DbType.Decimal, 19, "NUMERIC(19, $l)"); - this.RegisterColumnType(DbType.Double, "DOUBLE"); - this.RegisterColumnType(DbType.Guid, "VARCHAR(40)"); - this.RegisterColumnType(DbType.Int16, "SMALLINT"); - this.RegisterColumnType(DbType.Int32, "INTEGER"); - this.RegisterColumnType(DbType.Int64, "BIGINT"); - this.RegisterColumnType(DbType.Single, "FLOAT"); - this.RegisterColumnType(DbType.StringFixedLength, "CHAR(255)"); - this.RegisterColumnType(DbType.StringFixedLength, 255, "CHAR($l)"); - this.RegisterColumnType(DbType.StringFixedLength, 65535, "TEXT"); - this.RegisterColumnType(DbType.StringFixedLength, 16777215, "MEDIUMTEXT"); - this.RegisterColumnType(DbType.String, "VARCHAR(255)"); - this.RegisterColumnType(DbType.String, 255, "VARCHAR($l)"); - this.RegisterColumnType(DbType.String, 256, "VARCHAR(255)"); - this.RegisterColumnType(DbType.String, 65535, "TEXT"); - this.RegisterColumnType(DbType.String, 16777215, "MEDIUMTEXT"); - this.RegisterColumnType(DbType.String, 1073741823, "LONGTEXT"); - this.RegisterColumnType(DbType.Time, "TIME"); - - this.RegisterProperty(ColumnProperty.Unsigned, "UNSIGNED"); - this.RegisterProperty(ColumnProperty.Identity, "AUTO_INCREMENT"); - - this.RegisterUnsignedCompatible(DbType.Int16); - this.RegisterUnsignedCompatible(DbType.Int32); - this.RegisterUnsignedCompatible(DbType.Int64); - this.RegisterUnsignedCompatible(DbType.Decimal); - this.RegisterUnsignedCompatible(DbType.Double); - this.RegisterUnsignedCompatible(DbType.Single); - - this.AddReservedWords("KEY"); - } - - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, - string defaultSchema, string scope, string providerName) - { - return new DB2TransformationProvider(dialect, connectionString, scope, providerName); - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, - string defaultSchema, - string scope, string providerName) - { - return new DB2TransformationProvider(dialect, connection, scope, providerName); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/DB2/DB2TransformationProvider.cs b/src/Migrator.Providers/Impl/DB2/DB2TransformationProvider.cs deleted file mode 100644 index 66781912..00000000 --- a/src/Migrator.Providers/Impl/DB2/DB2TransformationProvider.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; - -namespace Migrator.Providers.Impl.DB2 -{ - /// - /// DB2 transformation provider - /// - public class DB2TransformationProvider : TransformationProvider - { - public DB2TransformationProvider(Dialect dialect, string connectionString, string scope, string providerName) - : base(dialect, connectionString, null, scope) - { - if (string.IsNullOrEmpty(providerName)) providerName = "IBM.Data.DB2"; - var fac = DbProviderFactories.GetFactory(providerName); - _connection = fac.CreateConnection(); - _connection.ConnectionString = _connectionString; - this._connection.Open(); - } - - public DB2TransformationProvider(Dialect dialect, IDbConnection connection, string scope, string providerName) - : base(dialect, connection, null, scope) - { - } - - public override List GetDatabases() - { - throw new NotImplementedException(); - } - - public override bool ConstraintExists(string table, string name) - { - throw new NotImplementedException(); - } - - public override bool IndexExists(string table, string name) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/Firebird/FirebirdColumnPropertiesMapper.cs b/src/Migrator.Providers/Impl/Firebird/FirebirdColumnPropertiesMapper.cs deleted file mode 100644 index 3b7eb296..00000000 --- a/src/Migrator.Providers/Impl/Firebird/FirebirdColumnPropertiesMapper.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using Migrator.Framework; - -namespace Migrator.Providers.Impl.Firebird -{ - public class FirebirdColumnPropertiesMapper : ColumnPropertiesMapper - { - public FirebirdColumnPropertiesMapper(Dialect dialect, string type) - : base(dialect, type) - { - } - - public override void MapColumnProperties(Column column) - { - Name = column.Name; - - indexed = PropertySelected(column.ColumnProperty, ColumnProperty.Indexed); - - var vals = new List(); - - AddName(vals); - - AddType(vals); - - AddIdentity(column, vals); - - //AddUnsigned(column, vals); - - AddPrimaryKey(column, vals); - - AddIdentityAgain(column, vals); - - AddUnique(column, vals); - - AddForeignKey(column, vals); - - AddDefaultValue(column, vals); - - AddNotNull(column, vals); - - AddNull(column, vals); - - columnSql = String.Join(" ", vals.ToArray()); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/Firebird/FirebirdDialect.cs b/src/Migrator.Providers/Impl/Firebird/FirebirdDialect.cs deleted file mode 100644 index 323ac4d5..00000000 --- a/src/Migrator.Providers/Impl/Firebird/FirebirdDialect.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Data; -using Migrator.Framework; - -namespace Migrator.Providers.Impl.Firebird -{ - public class FirebirdDialect : Dialect - { - public FirebirdDialect() - { - RegisterColumnType(DbType.AnsiStringFixedLength, 8000, "CHAR($l)"); - RegisterColumnType(DbType.AnsiString, 8000, "CHAR($l)"); - RegisterColumnType(DbType.Binary, "BLOB"); - RegisterColumnType(DbType.Binary, 8000, "CHAR"); - RegisterColumnType(DbType.Boolean, "SMALLINT"); - RegisterColumnType(DbType.Byte, "TINYINT"); - RegisterColumnType(DbType.Currency, "MONEY"); - RegisterColumnType(DbType.Date, "TIMESTAMP"); - RegisterColumnType(DbType.DateTime, "TIMESTAMP"); - RegisterColumnType(DbType.DateTimeOffset, "TIMESTAMP"); - RegisterColumnType(DbType.Decimal, "DECIMAL"); - RegisterColumnType(DbType.Double, "DOUBLE PRECISION"); //synonym for FLOAT(53) - RegisterColumnType(DbType.Guid, "CHAR(38)"); - RegisterColumnType(DbType.Int16, "SMALLINT"); - RegisterColumnType(DbType.Int32, "INT"); - RegisterColumnType(DbType.Int64, "BIGINT"); - RegisterColumnType(DbType.Single, "REAL"); //synonym for FLOAT(24) - RegisterColumnType(DbType.StringFixedLength, "NCHAR(255)"); - RegisterColumnType(DbType.String, "VARCHAR(255) CHARACTER SET UNICODE_FSS"); - RegisterColumnType(DbType.String, 4000, "VARCHAR($l) CHARACTER SET UNICODE_FSS"); - RegisterColumnType(DbType.String, int.MaxValue, "BLOB SUB_TYPE TEXT"); - RegisterColumnType(DbType.Time, "INTEGER"); - - this.RegisterProperty(ColumnProperty.Unsigned, "UNSIGNED"); - - this.RegisterUnsignedCompatible(DbType.Int16); - this.RegisterUnsignedCompatible(DbType.Int32); - this.RegisterUnsignedCompatible(DbType.Int64); - this.RegisterUnsignedCompatible(DbType.Decimal); - this.RegisterUnsignedCompatible(DbType.Double); - this.RegisterUnsignedCompatible(DbType.Single); - - this.AddReservedWords("KEY", "TIMESTAMP", "VALUE"); - } - - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) - { - return new FirebirdTransformationProvider(dialect, connectionString, scope, providerName); - } - - public override ColumnPropertiesMapper GetColumnMapper(Column column) - { - string type = column.Size > 0 ? GetTypeName(column.Type, column.Size) : GetTypeName(column.Type); - if (!IdentityNeedsType && column.IsIdentity) - type = String.Empty; - - return new FirebirdColumnPropertiesMapper(this, type); - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, - string defaultSchema, - string scope, string providerName) - { - return new FirebirdTransformationProvider(dialect, connection, scope, providerName); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/Firebird/FirebirdTransformationProvider.cs b/src/Migrator.Providers/Impl/Firebird/FirebirdTransformationProvider.cs deleted file mode 100644 index ee875b83..00000000 --- a/src/Migrator.Providers/Impl/Firebird/FirebirdTransformationProvider.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Linq; -using Migrator.Framework; - -namespace Migrator.Providers.Impl.Firebird -{ - /// - /// Firebird transformation provider - /// - public class FirebirdTransformationProvider : TransformationProvider - { - public FirebirdTransformationProvider(Dialect dialect, string connectionString, string scope, string providerName) - : base(dialect, connectionString, null, scope) - { - if (string.IsNullOrEmpty(providerName)) providerName = "FirebirdSql.Data.FirebirdClient"; - var fac = DbProviderFactoriesHelper.GetFactory(providerName, "FirebirdSql.Data.FirebirdClient", "FirebirdSql.Data.FirebirdClient.FirebirdClientFactory"); - _connection = fac.CreateConnection(); - _connection.ConnectionString = _connectionString; - this._connection.Open(); - } - - public FirebirdTransformationProvider(Dialect dialect, IDbConnection connection, string scope, string providerName) - : base(dialect, connection, null, scope) - { - } - - public override void AddColumn(string table, string sqlColumn) - { - table = QuoteTableNameIfRequired(table); - ExecuteNonQuery(String.Format("ALTER TABLE {0} ADD {1}", table, sqlColumn)); - } - - public override void DropDatabases(string databaseName) - { - if (string.IsNullOrEmpty(databaseName)) - ExecuteNonQuery(string.Format("DROP DATABASE")); - } - - /// - /// Execute an SQL query returning results. - /// - /// The SQL command. - /// A data iterator, IDataReader. - public override IDataReader ExecuteQuery(string sql) - { - Logger.Trace(sql); - IDbCommand cmd = BuildCommand(sql); - { - try - { - return cmd.ExecuteReader(); - } - catch (Exception ex) - { - Logger.Warn("query failed: {0}", cmd.CommandText); - throw new Exception("Failed to execute sql statement: " + sql, ex); - } - } - } - - public override Column[] GetColumns(string table) - { - var columns = new List(); - using ( - IDataReader reader = - ExecuteQuery( - String.Format("select RDB$FIELD_NAME, RDB$NULL_FLAG from RDB$RELATION_FIELDS where RDB$RELATION_NAME = '{0}'", table.ToUpper()))) - { - while (reader.Read()) - { - var column = new Column(reader.GetString(0).Trim(), DbType.String); - string nullableStr = reader.GetString(1); - bool isNullable = nullableStr == "1"; - column.ColumnProperty |= isNullable ? ColumnProperty.Null : ColumnProperty.NotNull; - - columns.Add(column); - } - } - - return columns.ToArray(); - } - - public override void AddTable(string name, params IDbField[] fields) - { - var columns = fields.Where(x => x is Column).Cast().ToArray(); - - base.AddTable(name, fields); - - if (columns.Any(c => c.ColumnProperty == ColumnProperty.PrimaryKeyWithIdentity)) - { - var identityColumn = columns.First(c => c.ColumnProperty == ColumnProperty.PrimaryKeyWithIdentity); - - var seqTName = name.Length > 21 ? name.Substring(0, 21) : name; - if (seqTName.EndsWith("_")) - seqTName = seqTName.Substring(0, seqTName.Length - 1); - - // Create a sequence for the table - ExecuteQuery(String.Format("CREATE GENERATOR {0}_SEQUENCE", seqTName)); - ExecuteQuery(String.Format("SET GENERATOR {0}_SEQUENCE TO 0", seqTName)); - - var sql = ""; // "set term !! ;"; - sql += "CREATE TRIGGER {1}_TRIGGER FOR {0}\n"; - sql += "ACTIVE BEFORE INSERT POSITION 0\n"; - sql += "AS\n"; - sql += "BEGIN\n"; - sql += "if (NEW.{2} is NULL) then NEW.{2} = GEN_ID({1}_SEQUENCE, 1);\n"; - sql += "END\n"; - - ExecuteQuery(String.Format(sql, name, seqTName, identityColumn.Name)); - } - } - - public override List GetDatabases() - { - throw new NotImplementedException(); - } - - public override bool ConstraintExists(string table, string name) - { - //todo, implement this!!! - - //http://edn.embarcadero.com/article/25259 field infos in FB - //http://www.felix-colibri.com/papers/db/interbase/using_interbase_system_tables/using_interbase_system_tables.html - - return false; - } - - public override bool IndexExists(string table, string name) - { - return false; - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/Informix/InformixDialect.cs b/src/Migrator.Providers/Impl/Informix/InformixDialect.cs deleted file mode 100644 index 3861070b..00000000 --- a/src/Migrator.Providers/Impl/Informix/InformixDialect.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Data; - -using Migrator.Framework; - -namespace Migrator.Providers.Impl.Informix -{ - public class InformixDialect : Dialect - { - public InformixDialect() - { - this.RegisterColumnType(DbType.AnsiStringFixedLength, "CHAR(255)"); - this.RegisterColumnType(DbType.AnsiStringFixedLength, 255, "CHAR($l)"); - this.RegisterColumnType(DbType.AnsiStringFixedLength, 65535, "TEXT"); - this.RegisterColumnType(DbType.AnsiStringFixedLength, 16777215, "MEDIUMTEXT"); - this.RegisterColumnType(DbType.AnsiString, "VARCHAR(255)"); - this.RegisterColumnType(DbType.AnsiString, 255, "VARCHAR($l)"); - this.RegisterColumnType(DbType.AnsiString, 256, "VARCHAR(255)"); - this.RegisterColumnType(DbType.AnsiString, 65535, "TEXT"); - this.RegisterColumnType(DbType.AnsiString, 16777215, "MEDIUMTEXT"); - this.RegisterColumnType(DbType.Binary, "LONGBLOB"); - this.RegisterColumnType(DbType.Binary, 127, "TINYBLOB"); - this.RegisterColumnType(DbType.Binary, 65535, "BLOB"); - this.RegisterColumnType(DbType.Binary, 16777215, "MEDIUMBLOB"); - this.RegisterColumnType(DbType.Boolean, "TINYINT(1)"); - this.RegisterColumnType(DbType.Byte, "TINYINT UNSIGNED"); - this.RegisterColumnType(DbType.Currency, "MONEY"); - this.RegisterColumnType(DbType.Date, "DATE"); - this.RegisterColumnType(DbType.DateTime, "DATETIME"); - this.RegisterColumnType(DbType.DateTimeOffset, "DATETIME"); - this.RegisterColumnType(DbType.Decimal, "NUMERIC(19,5)"); - this.RegisterColumnType(DbType.Decimal, 19, "NUMERIC(19, $l)"); - this.RegisterColumnType(DbType.Double, "DOUBLE"); - this.RegisterColumnType(DbType.Guid, "VARCHAR(40)"); - this.RegisterColumnType(DbType.Int16, "SMALLINT"); - this.RegisterColumnType(DbType.Int32, "INTEGER"); - this.RegisterColumnType(DbType.Int64, "BIGINT"); - this.RegisterColumnType(DbType.Single, "FLOAT"); - this.RegisterColumnType(DbType.StringFixedLength, "CHAR(255)"); - this.RegisterColumnType(DbType.StringFixedLength, 255, "CHAR($l)"); - this.RegisterColumnType(DbType.StringFixedLength, 65535, "TEXT"); - this.RegisterColumnType(DbType.StringFixedLength, 16777215, "MEDIUMTEXT"); - this.RegisterColumnType(DbType.String, "VARCHAR(255)"); - this.RegisterColumnType(DbType.String, 255, "VARCHAR($l)"); - this.RegisterColumnType(DbType.String, 256, "VARCHAR(255)"); - this.RegisterColumnType(DbType.String, 65535, "TEXT"); - this.RegisterColumnType(DbType.String, 16777215, "MEDIUMTEXT"); - this.RegisterColumnType(DbType.String, 1073741823, "LONGTEXT"); - this.RegisterColumnType(DbType.Time, "TIME"); - - this.RegisterProperty(ColumnProperty.Unsigned, "UNSIGNED"); - this.RegisterProperty(ColumnProperty.Identity, "AUTO_INCREMENT"); - - this.RegisterUnsignedCompatible(DbType.Int16); - this.RegisterUnsignedCompatible(DbType.Int32); - this.RegisterUnsignedCompatible(DbType.Int64); - this.RegisterUnsignedCompatible(DbType.Decimal); - this.RegisterUnsignedCompatible(DbType.Double); - this.RegisterUnsignedCompatible(DbType.Single); - - this.AddReservedWords("KEY"); - } - - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, - string defaultSchema, string scope, string providerName) - { - return new InformixTransformationProvider(dialect, connectionString, scope, providerName); - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, - string defaultSchema, - string scope, string providerName) - { - return new InformixTransformationProvider(dialect, connection, scope, providerName); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/Informix/InformixTransformationProvider.cs b/src/Migrator.Providers/Impl/Informix/InformixTransformationProvider.cs deleted file mode 100644 index f2fe1ed5..00000000 --- a/src/Migrator.Providers/Impl/Informix/InformixTransformationProvider.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; - -namespace Migrator.Providers.Impl.Informix -{ - /// - /// DB2 transformation provider - /// - public class InformixTransformationProvider : TransformationProvider - { - public InformixTransformationProvider(Dialect dialect, string connectionString, string scope, string providerName) - : base(dialect, connectionString, null, scope) - { - if (string.IsNullOrEmpty(providerName)) providerName = "IBM.Data.Informix.Client"; - var fac = DbProviderFactories.GetFactory(providerName); - _connection = fac.CreateConnection(); - _connection.ConnectionString = _connectionString; - this._connection.Open(); - } - - public InformixTransformationProvider(Dialect dialect, IDbConnection connection, string scope, string providerName) - : base(dialect, connection, null, scope) - { - } - - public override List GetDatabases() - { - throw new NotImplementedException(); - } - - public override bool ConstraintExists(string table, string name) - { - throw new NotImplementedException(); - } - - public override bool IndexExists(string table, string name) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/Ingres/IngresDialect.cs b/src/Migrator.Providers/Impl/Ingres/IngresDialect.cs deleted file mode 100644 index f8f1b1be..00000000 --- a/src/Migrator.Providers/Impl/Ingres/IngresDialect.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Data; -using Migrator.Framework; - -namespace Migrator.Providers.Impl.Ingres -{ - public class IngresDialect : Dialect - { - public IngresDialect() - { - this.RegisterColumnType(DbType.AnsiStringFixedLength, "CHAR(255)"); - this.RegisterColumnType(DbType.AnsiStringFixedLength, 255, "CHAR($l)"); - this.RegisterColumnType(DbType.AnsiStringFixedLength, 65535, "TEXT"); - this.RegisterColumnType(DbType.AnsiStringFixedLength, 16777215, "MEDIUMTEXT"); - this.RegisterColumnType(DbType.AnsiString, "VARCHAR(255)"); - this.RegisterColumnType(DbType.AnsiString, 255, "VARCHAR($l)"); - this.RegisterColumnType(DbType.AnsiString, 256, "VARCHAR(255)"); - this.RegisterColumnType(DbType.AnsiString, 65535, "TEXT"); - this.RegisterColumnType(DbType.AnsiString, 16777215, "MEDIUMTEXT"); - this.RegisterColumnType(DbType.Binary, "LONGBLOB"); - this.RegisterColumnType(DbType.Binary, 127, "TINYBLOB"); - this.RegisterColumnType(DbType.Binary, 65535, "BLOB"); - this.RegisterColumnType(DbType.Binary, 16777215, "MEDIUMBLOB"); - this.RegisterColumnType(DbType.Boolean, "TINYINT(1)"); - this.RegisterColumnType(DbType.Byte, "TINYINT UNSIGNED"); - this.RegisterColumnType(DbType.Currency, "MONEY"); - this.RegisterColumnType(DbType.Date, "DATE"); - this.RegisterColumnType(DbType.DateTime, "DATETIME"); - this.RegisterColumnType(DbType.DateTimeOffset, "DATETIME"); - this.RegisterColumnType(DbType.Decimal, "NUMERIC(19,5)"); - this.RegisterColumnType(DbType.Decimal, 19, "NUMERIC(19, $l)"); - this.RegisterColumnType(DbType.Double, "DOUBLE"); - this.RegisterColumnType(DbType.Guid, "VARCHAR(40)"); - this.RegisterColumnType(DbType.Int16, "SMALLINT"); - this.RegisterColumnType(DbType.Int32, "INTEGER"); - this.RegisterColumnType(DbType.Int64, "BIGINT"); - this.RegisterColumnType(DbType.Single, "FLOAT"); - this.RegisterColumnType(DbType.StringFixedLength, "CHAR(255)"); - this.RegisterColumnType(DbType.StringFixedLength, 255, "CHAR($l)"); - this.RegisterColumnType(DbType.StringFixedLength, 65535, "TEXT"); - this.RegisterColumnType(DbType.StringFixedLength, 16777215, "MEDIUMTEXT"); - this.RegisterColumnType(DbType.String, "VARCHAR(255)"); - this.RegisterColumnType(DbType.String, 255, "VARCHAR($l)"); - this.RegisterColumnType(DbType.String, 256, "VARCHAR(255)"); - this.RegisterColumnType(DbType.String, 65535, "TEXT"); - this.RegisterColumnType(DbType.String, 16777215, "MEDIUMTEXT"); - this.RegisterColumnType(DbType.String, 1073741823, "LONGTEXT"); - this.RegisterColumnType(DbType.Time, "TIME"); - - this.RegisterProperty(ColumnProperty.Unsigned, "UNSIGNED"); - this.RegisterProperty(ColumnProperty.Identity, "AUTO_INCREMENT"); - - this.RegisterUnsignedCompatible(DbType.Int16); - this.RegisterUnsignedCompatible(DbType.Int32); - this.RegisterUnsignedCompatible(DbType.Int64); - this.RegisterUnsignedCompatible(DbType.Decimal); - this.RegisterUnsignedCompatible(DbType.Double); - this.RegisterUnsignedCompatible(DbType.Single); - - this.AddReservedWords("KEY"); - } - - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, - string defaultSchema, string scope, string providerName) - { - return new IngresTransformationProvider(dialect, connectionString, scope, providerName); - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, - string defaultSchema, - string scope, string providerName) - { - return new IngresTransformationProvider(dialect, connection, scope, providerName); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/Ingres/IngresTransformationProvider.cs b/src/Migrator.Providers/Impl/Ingres/IngresTransformationProvider.cs deleted file mode 100644 index 6474d592..00000000 --- a/src/Migrator.Providers/Impl/Ingres/IngresTransformationProvider.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; - -namespace Migrator.Providers.Impl.Ingres -{ - public class IngresTransformationProvider : TransformationProvider - { - public IngresTransformationProvider(Dialect dialect, string connectionString, string scope, string providerName) - : base(dialect, connectionString, null, scope) - { - if (string.IsNullOrEmpty(providerName)) providerName = "Ingres.Client"; - var fac = DbProviderFactories.GetFactory(providerName); - _connection = fac.CreateConnection(); - _connection.ConnectionString = _connectionString; - this._connection.Open(); - } - - public IngresTransformationProvider(Dialect dialect, IDbConnection connection, string scope, string providerName) - : base(dialect, connection, null, scope) - { - } - - public override List GetDatabases() - { - throw new NotImplementedException(); - } - - public override bool ConstraintExists(string table, string name) - { - throw new NotImplementedException(); - } - - public override bool IndexExists(string table, string name) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/Mysql/MariaDBDialect.cs b/src/Migrator.Providers/Impl/Mysql/MariaDBDialect.cs deleted file mode 100644 index dc1fd98d..00000000 --- a/src/Migrator.Providers/Impl/Mysql/MariaDBDialect.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Data; -using Migrator.Framework; - -namespace Migrator.Providers.Mysql -{ - public class MariaDBDialect : MysqlDialect - { - public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) - { - return new MariaDBTransformationProvider(dialect, connectionString, scope, providerName); - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, - string defaultSchema, - string scope, string providerName) - { - return new MariaDBTransformationProvider(dialect, connection, scope, providerName); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/Mysql/MariaDBTransformationProvider.cs b/src/Migrator.Providers/Impl/Mysql/MariaDBTransformationProvider.cs deleted file mode 100644 index afec7e37..00000000 --- a/src/Migrator.Providers/Impl/Mysql/MariaDBTransformationProvider.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; - -using Migrator.Framework; - -namespace Migrator.Providers.Mysql -{ - /// - /// MySql transformation provider - /// - public class MariaDBTransformationProvider : MySqlTransformationProvider - { - public MariaDBTransformationProvider(Dialect dialect, string connectionString, string scope, string providerName) - : base(dialect, connectionString, scope, providerName) - { - if (string.IsNullOrEmpty(providerName)) providerName = "MySql.Data.MySqlClient"; - var fac = DbProviderFactories.GetFactory(providerName); - _connection = fac.CreateConnection(); - _connection.ConnectionString = _connectionString; - _connection.Open(); - } - - public MariaDBTransformationProvider(Dialect dialect, IDbConnection connection, string scope, string providerName) - : base(dialect, connection, scope, providerName) - { - } - - //public override void RenameColumn(string tableName, string oldColumnName, string newColumnName) - //{ - // if (ColumnExists(tableName, newColumnName)) - // { - // throw new MigrationException(String.Format("Table '{0}' has column named '{1}' already", tableName, newColumnName)); - // } - - // if (!ColumnExists(tableName, oldColumnName)) - // { - // throw new MigrationException(string.Format("The table '{0}' does not have a column named '{1}'", tableName, oldColumnName)); - // } - - // string definition = null; - // using (IDataReader reader = ExecuteQuery(String.Format("SHOW COLUMNS FROM {0} WHERE Field='{1}'", tableName, oldColumnName))) - // { - // if (reader.Read()) - // { - // // TODO: Could use something similar to construct the columns in GetColumns - // definition = reader["Type"].ToString(); - // if ("NO" == reader["Null"].ToString()) - // { - // definition += " " + "NOT NULL"; - // } - - // if (!reader.IsDBNull(reader.GetOrdinal("Key"))) - // { - // string key = reader["Key"].ToString(); - // if ("PRI" == key) - // { - // definition += " " + "PRIMARY KEY"; - // } - // else if ("UNI" == key) - // { - // definition += " " + "UNIQUE"; - // } - // } - - // if (!reader.IsDBNull(reader.GetOrdinal("Extra"))) - // { - // definition += " " + reader["Extra"]; - // } - // } - // } - - // if (!String.IsNullOrEmpty(definition)) - // { - // ExecuteNonQuery(String.Format("ALTER TABLE {0} CHANGE {1} {2} {3}", tableName, oldColumnName, newColumnName, definition)); - // } - //} - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/Mysql/MySqlTransformationProvider.cs b/src/Migrator.Providers/Impl/Mysql/MySqlTransformationProvider.cs deleted file mode 100644 index 26ce2fe5..00000000 --- a/src/Migrator.Providers/Impl/Mysql/MySqlTransformationProvider.cs +++ /dev/null @@ -1,336 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; - -using Migrator.Framework; - -namespace Migrator.Providers.Mysql -{ - /// - /// MySql transformation provider - /// - public class MySqlTransformationProvider : TransformationProvider - { - public MySqlTransformationProvider(Dialect dialect, string connectionString, string scope, string providerName) - : base(dialect, connectionString, null, scope) // we ignore schemas for MySql (schema == database for MySql) - { - if (string.IsNullOrEmpty(providerName)) providerName = "MySql.Data.MySqlClient"; - var fac = DbProviderFactoriesHelper.GetFactory(providerName, "MySql.Data", "MySql.Data.MySqlClient.MySqlClientFactory"); - _connection = fac.CreateConnection(); //new MySqlConnection(_connectionString) {ConnectionString = _connectionString}; - _connection.ConnectionString = _connectionString; - _connection.Open(); - } - - public MySqlTransformationProvider(Dialect dialect, IDbConnection connection, string scope, string providerName) - : base(dialect, connection, null, scope) - { - } - - public override void RemoveForeignKey(string table, string name) - { - if (ForeignKeyExists(table, name)) - { - ExecuteNonQuery(String.Format("ALTER TABLE {0} DROP FOREIGN KEY {1}", table, _dialect.Quote(name))); - } - } - - public override void RemoveAllIndexes(string table) - { - string qry = string.Format(@"SELECT k.TABLE_NAME, i.CONSTRAINT_NAME, i.CONSTRAINT_TYPE - FROM information_schema.KEY_COLUMN_USAGE k - INNER JOIN information_schema.TABLE_CONSTRAINTS i - ON i.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND i.TABLE_NAME = k.TABLE_NAME - WHERE k.REFERENCED_TABLE_SCHEMA='{0}' AND - (k.REFERENCED_TABLE_NAME='{1}') OR (k.TABLE_NAME='{1}')", GetDatabase(), table); - - var l = new List>(); - using (IDataReader reader = ExecuteQuery(qry)) - { - while (reader.Read()) - { - l.Add(new Tuple(reader.GetString(0), reader.GetString(1), reader.GetString(2))); - } - } - - foreach (var tuple in l) - { - if (tuple.Item3 == "FOREIGN KEY") - RemoveForeignKey(tuple.Item1, tuple.Item2); - else if (tuple.Item3 == "PRIMARY KEY") - { - try - { - ExecuteNonQuery(String.Format("ALTER TABLE {0} DROP PRIMARY KEY", table)); - } - catch (Exception) - { } - } - else if (tuple.Item3 == "UNIQUE") - RemoveIndex(tuple.Item1, tuple.Item2); - } - } - - public override void RemoveAllForeignKeys(string tableName, string columnName) - { - string qry = string.Format(@"SELECT k.TABLE_NAME, i.CONSTRAINT_NAME - FROM information_schema.KEY_COLUMN_USAGE k - INNER JOIN information_schema.TABLE_CONSTRAINTS i - ON i.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND i.TABLE_NAME = k.TABLE_NAME - WHERE k.REFERENCED_TABLE_SCHEMA='{0}' AND i.CONSTRAINT_TYPE = 'FOREIGN KEY' AND - (k.REFERENCED_TABLE_NAME='{1}' AND REFERENCED_COLUMN_NAME='{2}') OR (k.TABLE_NAME='{1}' AND COLUMN_NAME='{2}')", GetDatabase(), tableName, columnName); - - if (string.IsNullOrEmpty(columnName)) - { - qry = string.Format(@"SELECT k.TABLE_NAME, i.CONSTRAINT_NAME - FROM information_schema.KEY_COLUMN_USAGE k - INNER JOIN information_schema.TABLE_CONSTRAINTS i - ON i.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND i.TABLE_NAME = k.TABLE_NAME - WHERE k.REFERENCED_TABLE_SCHEMA='{0}' AND i.CONSTRAINT_TYPE = 'FOREIGN KEY' AND - (k.REFERENCED_TABLE_NAME='{1}') OR (k.TABLE_NAME='{1}')", GetDatabase(), tableName); - } - var l = new List> (); - using (IDataReader reader = ExecuteQuery(qry)) - { - while (reader.Read()) - { - l.Add(new Tuple(reader.GetString(0), reader.GetString(1))); - } - } - - foreach (var tuple in l) - { - RemoveForeignKey(tuple.Item1, tuple.Item2); - } - } - - public override void RemoveConstraint(string table, string name) - { - if (ConstraintExists(table, name)) - { - ExecuteNonQuery(String.Format("ALTER TABLE {0} DROP KEY {1}", table, _dialect.Quote(name))); - } - } - - public override bool ConstraintExists(string table, string name) - { - if (!TableExists(table)) - return false; - - string sqlConstraint = string.Format("SHOW KEYS FROM {0}", table); - - using (IDataReader reader = ExecuteQuery(sqlConstraint)) - { - while (reader.Read()) - { - if (reader["Key_name"].ToString().ToLower() == name.ToLower()) - { - return true; - } - } - } - - return false; - } - - public bool ForeignKeyExists(string table, string name) - { - if (!TableExists(table)) - return false; - - string sqlConstraint = string.Format(@"SELECT distinct i.CONSTRAINT_NAME - FROM information_schema.TABLE_CONSTRAINTS i - INNER JOIN information_schema.KEY_COLUMN_USAGE k - ON i.CONSTRAINT_NAME = k.CONSTRAINT_NAME - WHERE i.CONSTRAINT_TYPE = 'FOREIGN KEY' - AND i.TABLE_SCHEMA = '{1}' - AND i.TABLE_NAME = '{0}';", table, GetDatabase()); - - using (IDataReader reader = ExecuteQuery(sqlConstraint)) - { - while (reader.Read()) - { - if (reader["CONSTRAINT_NAME"].ToString().ToLower() == name.ToLower()) - { - return true; - } - } - } - - return false; - } - - public override Index[] GetIndexes(string table) - { - var retVal = new List(); - - var sql = @"SHOW INDEX FROM {0}"; - - using (var reader = ExecuteQuery(string.Format(sql, table))) - { - while (reader.Read()) - { - if (!reader.IsDBNull(1)) - { - var idx = new Index - { - Name = reader.GetString(2), - PrimaryKey = reader.GetString(2) == "PRIMARY", - Unique = !reader.GetBoolean(1), - }; - //var cols = reader.GetString(7); - //cols = cols.Substring(1, cols.Length - 2); - //idx.KeyColumns = cols.Split(','); - retVal.Add(idx); - } - } - } - - return retVal.ToArray(); - } - - public override bool PrimaryKeyExists(string table, string name) - { - return ConstraintExists(table, "PRIMARY"); - } - - public override Column[] GetColumns(string table) - { - var columns = new List(); - using ( - IDataReader reader = - ExecuteQuery( - String.Format("SHOW COLUMNS FROM {0}", table))) - { - while (reader.Read()) - { - var column = new Column(reader.GetString(0), DbType.String); - string nullableStr = reader.GetString(2); - bool isNullable = nullableStr == "YES"; - column.ColumnProperty |= isNullable ? ColumnProperty.Null : ColumnProperty.NotNull; - - columns.Add(column); - } - } - - return columns.ToArray(); - } - - public override string[] GetTables() - { - var tables = new List(); - using (IDataReader reader = ExecuteQuery("SHOW TABLES")) - { - while (reader.Read()) - { - tables.Add((string) reader[0]); - } - } - - return tables.ToArray(); - } - - public override void ChangeColumn(string table, string sqlColumn) - { - ExecuteNonQuery(String.Format("ALTER TABLE {0} MODIFY {1}", table, sqlColumn)); - } - - public override void AddTable(string name, params IDbField[] columns) - { - AddTable(name, "INNODB", columns); - } - - public override void AddTable(string name, string engine, string columns) - { - string sqlCreate = string.Format("CREATE TABLE {0} ({1}) ENGINE = {2}", name, columns, engine); - ExecuteNonQuery(sqlCreate); - } - - public override void RenameColumn(string tableName, string oldColumnName, string newColumnName) - { - if (ColumnExists(tableName, newColumnName)) - { - throw new MigrationException(String.Format("Table '{0}' has column named '{1}' already", tableName, newColumnName)); - } - - if (!ColumnExists(tableName, oldColumnName)) - { - throw new MigrationException(string.Format("The table '{0}' does not have a column named '{1}'", tableName, oldColumnName)); - } - - string definition = null; - - bool dropPrimary = false; - - using (IDataReader reader = ExecuteQuery(String.Format("SHOW COLUMNS FROM {0} WHERE Field='{1}'", tableName, oldColumnName))) - { - if (reader.Read()) - { - // TODO: Could use something similar to construct the columns in GetColumns - definition = reader["Type"].ToString(); - if ("NO" == reader["Null"].ToString()) - { - definition += " " + "NOT NULL"; - } - - if (!reader.IsDBNull(reader.GetOrdinal("Key"))) - { - string key = reader["Key"].ToString(); - if ("PRI" == key) - { - //definition += " " + "PRIMARY KEY"; - dropPrimary = true; - } - else if ("UNI" == key) - { - definition += " " + "UNIQUE"; - } - } - - if (!reader.IsDBNull(reader.GetOrdinal("Extra"))) - { - definition += " " + reader["Extra"]; - } - } - } - - if (!String.IsNullOrEmpty(definition)) - { - if (dropPrimary) - ExecuteNonQuery(String.Format("ALTER TABLE {0} DROP PRIMARY KEY", tableName)); - ExecuteNonQuery(String.Format("ALTER TABLE {0} CHANGE {1} {2} {3}", tableName, oldColumnName, newColumnName, definition)); - if (dropPrimary) - ExecuteNonQuery(String.Format("ALTER TABLE {0} ADD PRIMARY KEY({1});", tableName, newColumnName)); - - } - } - - public string GetDatabase() - { - return ExecuteScalar("SELECT DATABASE()") as string; - } - - public override void RemoveIndex(string table, string name) - { - if (IndexExists(table, name)) - { - ExecuteNonQuery(String.Format("DROP INDEX {1} ON {0}", table, _dialect.Quote(name))); - } - } - - public override List GetDatabases() - { - return ExecuteStringQuery("SHOW DATABASES"); - } - - public override bool IndexExists(string table, string name) - { - return ConstraintExists(table, name); - } - - public override string Concatenate(params string[] strings) - { - return "CONCAT(" + string.Join(", ", strings) + ")"; - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/Mysql/MysqlDialect.cs b/src/Migrator.Providers/Impl/Mysql/MysqlDialect.cs deleted file mode 100644 index 092b3438..00000000 --- a/src/Migrator.Providers/Impl/Mysql/MysqlDialect.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Data; -using Migrator.Framework; - -namespace Migrator.Providers.Mysql -{ - public class MysqlDialect : Dialect - { - public MysqlDialect() - { - // TODO: As per http://dev.mysql.com/doc/refman/5.0/en/char.html 5.0.3 and above - // can handle varchar(n) up to a length OF 65,535 - so the limit of 255 should no longer apply. - - RegisterColumnType(DbType.AnsiStringFixedLength, "CHAR(255)"); - RegisterColumnType(DbType.AnsiStringFixedLength, 255, "CHAR($l)"); - RegisterColumnType(DbType.AnsiStringFixedLength, 65535, "TEXT"); - RegisterColumnType(DbType.AnsiStringFixedLength, 16777215, "MEDIUMTEXT"); - RegisterColumnType(DbType.AnsiString, "VARCHAR(255)"); - RegisterColumnType(DbType.AnsiString, 255, "VARCHAR($l)"); - RegisterColumnType(DbType.AnsiString, 256, "VARCHAR(255)"); - RegisterColumnType(DbType.AnsiString, 65535, "TEXT"); - RegisterColumnType(DbType.AnsiString, 16777215, "MEDIUMTEXT"); - RegisterColumnType(DbType.Binary, "LONGBLOB"); - RegisterColumnType(DbType.Binary, 127, "TINYBLOB"); - RegisterColumnType(DbType.Binary, 65535, "BLOB"); - RegisterColumnType(DbType.Binary, 16777215, "MEDIUMBLOB"); - RegisterColumnType(DbType.Boolean, "TINYINT(1)"); - RegisterColumnType(DbType.Byte, "TINYINT UNSIGNED"); - RegisterColumnType(DbType.Currency, "MONEY"); - RegisterColumnType(DbType.Date, "DATE"); - RegisterColumnType(DbType.DateTime, "DATETIME"); - RegisterColumnType(DbType.DateTimeOffset, "DATETIME"); - RegisterColumnType(DbType.Decimal, "NUMERIC(19,5)"); - RegisterColumnType(DbType.Decimal, 19, "NUMERIC(19, $l)"); - RegisterColumnType(DbType.Double, "DOUBLE"); - RegisterColumnType(DbType.Guid, "VARCHAR(40)"); - RegisterColumnType(DbType.Int16, "SMALLINT"); - RegisterColumnType(DbType.Int32, "INTEGER"); - RegisterColumnType(DbType.Int64, "BIGINT"); - RegisterColumnType(DbType.UInt16, "INTEGER"); - RegisterColumnType(DbType.UInt32, "BIGINT"); - RegisterColumnType(DbType.UInt64, "NUMERIC(20,0)"); - RegisterColumnType(DbType.Single, "FLOAT"); - RegisterColumnType(DbType.StringFixedLength, "CHAR(255)"); - RegisterColumnType(DbType.StringFixedLength, 255, "CHAR($l)"); - RegisterColumnType(DbType.StringFixedLength, 65535, "TEXT"); - RegisterColumnType(DbType.StringFixedLength, 16777215, "MEDIUMTEXT"); - RegisterColumnType(DbType.String, "VARCHAR(255)"); - RegisterColumnType(DbType.String, 65535, "VARCHAR($l)"); - //RegisterColumnType(DbType.String, 256, "VARCHAR(255)"); - //RegisterColumnType(DbType.String, 256, "VARCHAR(255)"); - //RegisterColumnType(DbType.String, 65535, "TEXT"); - RegisterColumnType(DbType.String, 16777215, "MEDIUMTEXT"); - //RegisterColumnType(DbType.String, 1073741823, "LONGTEXT"); - RegisterColumnType(DbType.String, int.MaxValue, "LONGTEXT"); - RegisterColumnType(DbType.Time, "TIME"); - - RegisterProperty(ColumnProperty.Unsigned, "UNSIGNED"); - RegisterProperty(ColumnProperty.Identity, "AUTO_INCREMENT"); - RegisterProperty(ColumnProperty.CaseSensitive, "BINARY"); - - RegisterUnsignedCompatible(DbType.Int16); - RegisterUnsignedCompatible(DbType.Int32); - RegisterUnsignedCompatible(DbType.Int64); - RegisterUnsignedCompatible(DbType.Decimal); - RegisterUnsignedCompatible(DbType.Double); - RegisterUnsignedCompatible(DbType.Single); - - AddReservedWords("KEY", "MAXVALUE"); - } - - public override string QuoteTemplate - { - get { return "`{0}`"; } - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, - string defaultSchema, string scope, string providerName) - { - return new MySqlTransformationProvider(dialect, connectionString, scope, providerName); - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, - string defaultSchema, - string scope, string providerName) - { - return new MySqlTransformationProvider(dialect, connection, scope, providerName); - } - - public override string Default(object defaultValue) - { - if (defaultValue.GetType().Equals(typeof (bool))) - { - defaultValue = ((bool) defaultValue) ? 1 : 0; - } - - return base.Default(defaultValue); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/Oracle/MsOracleDialect.cs b/src/Migrator.Providers/Impl/Oracle/MsOracleDialect.cs deleted file mode 100644 index 138aaf57..00000000 --- a/src/Migrator.Providers/Impl/Oracle/MsOracleDialect.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Data; -using Migrator.Framework; -using Migrator.Providers.Impl.Oracle; - -namespace Migrator.Providers.Oracle -{ - public class MsOracleDialect : OracleDialect - { - public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) - { - return new MsOracleTransformationProvider(dialect, connectionString, defaultSchema, scope, providerName); - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, - string defaultSchema, - string scope, string providerName) - { - return new MsOracleTransformationProvider(dialect, connection, defaultSchema, scope, providerName); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/Oracle/MsOracleTransformationProvider.cs b/src/Migrator.Providers/Impl/Oracle/MsOracleTransformationProvider.cs deleted file mode 100644 index 0fb213a9..00000000 --- a/src/Migrator.Providers/Impl/Oracle/MsOracleTransformationProvider.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Linq; -using System.Text; -using Migrator.Framework; - -namespace Migrator.Providers.Oracle -{ - public class MsOracleTransformationProvider : OracleTransformationProvider - { - public MsOracleTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) - : base(dialect, connectionString, defaultSchema, scope, providerName) - { - - } - - public MsOracleTransformationProvider(Dialect dialect, IDbConnection connection, string defaultSchema, string scope, string providerName) - : base(dialect, connection, defaultSchema, scope, providerName) - { - } - - protected override void CreateConnection(string providerName) - { - if (string.IsNullOrEmpty(providerName)) providerName = "System.Data.OracleClient"; - var fac = DbProviderFactories.GetFactory(providerName); - _connection = fac.CreateConnection(); // new OracleConnection(); - _connection.ConnectionString = _connectionString; - _connection.Open(); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/Oracle/OracleColumnPropertiesMapper.cs b/src/Migrator.Providers/Impl/Oracle/OracleColumnPropertiesMapper.cs deleted file mode 100644 index bcc0590b..00000000 --- a/src/Migrator.Providers/Impl/Oracle/OracleColumnPropertiesMapper.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Generic; -using Migrator.Framework; - -namespace Migrator.Providers.Impl.Oracle -{ - public class OracleColumnPropertiesMapper : ColumnPropertiesMapper - { - public OracleColumnPropertiesMapper(Dialect dialect, string type) : base(dialect, type) - { - } - - public override void MapColumnProperties(Column column) - { - Name = column.Name; - - indexed = PropertySelected(column.ColumnProperty, ColumnProperty.Indexed); - - var vals = new List(); - - AddName(vals); - - AddType(vals); - - AddIdentity(column, vals); - - AddUnsigned(column, vals); - - AddPrimaryKey(column, vals); - - AddIdentityAgain(column, vals); - - AddUnique(column, vals); - - AddForeignKey(column, vals); - - AddDefaultValue(column, vals); - - // null / not-null comes last on Oracle - otherwise if use Null/Not-null + default, bad things happen - // (http://geekswithblogs.net/faizanahmad/archive/2009/08/07/add-new-columnfield-in-oracle-db-table---ora.aspx) - - AddNotNull(column, vals); - - AddNull(column, vals); - - columnSql = String.Join(" ", vals.ToArray()); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/Oracle/OracleDialect.cs b/src/Migrator.Providers/Impl/Oracle/OracleDialect.cs deleted file mode 100644 index fc3c03df..00000000 --- a/src/Migrator.Providers/Impl/Oracle/OracleDialect.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.Data; -using Migrator.Framework; -using Migrator.Providers.Impl.Oracle; - -namespace Migrator.Providers.Oracle -{ - public class OracleDialect : Dialect - { - public OracleDialect() - { - RegisterColumnType(DbType.AnsiStringFixedLength, "CHAR(255)"); - RegisterColumnType(DbType.AnsiStringFixedLength, 2000, "CHAR($l)"); - RegisterColumnType(DbType.AnsiString, "VARCHAR2(255)"); - RegisterColumnType(DbType.AnsiString, 2000, "VARCHAR2($l)"); - RegisterColumnType(DbType.AnsiString, 2147483647, "CLOB"); // should use the IType.ClobType - RegisterColumnType(DbType.Binary, "RAW(2000)"); - RegisterColumnType(DbType.Binary, 2000, "RAW($l)"); - RegisterColumnType(DbType.Binary, 2147483647, "BLOB"); - RegisterColumnType(DbType.Boolean, "NUMBER(1,0)"); - RegisterColumnType(DbType.Byte, "NUMBER(3,0)"); - RegisterColumnType(DbType.Currency, "NUMBER(19,1)"); - RegisterColumnType(DbType.Date, "DATE"); - RegisterColumnType(DbType.DateTime, "TIMESTAMP(4)"); - RegisterColumnType(DbType.DateTimeOffset, "TIMESTAMP(4)"); - RegisterColumnType(DbType.Decimal, "NUMBER(19,5)"); - RegisterColumnType(DbType.Decimal, 19, "NUMBER(19, $l)"); - // having problems with both ODP and OracleClient from MS not being able - // to read values out of a field that is DOUBLE PRECISION - RegisterColumnType(DbType.Double, "DOUBLE PRECISION"); //"FLOAT(53)" ); - //RegisterColumnType(DbType.Guid, "CHAR(38)"); - RegisterColumnType(DbType.Int16, "NUMBER(5,0)"); - RegisterColumnType(DbType.Int32, "NUMBER(10,0)"); - RegisterColumnType(DbType.Int64, "NUMBER(20,0)"); - RegisterColumnType(DbType.UInt16, "NUMBER(5,0)"); - RegisterColumnType(DbType.UInt32, "NUMBER(10,0)"); - RegisterColumnType(DbType.UInt64, "NUMBER(20,0)"); - RegisterColumnType(DbType.Single, "FLOAT(24)"); - RegisterColumnType(DbType.StringFixedLength, "NCHAR(255)"); - RegisterColumnType(DbType.StringFixedLength, 2000, "NCHAR($l)"); - RegisterColumnType(DbType.String, "NVARCHAR2(255)"); - RegisterColumnType(DbType.String, 2000, "NVARCHAR2($l)"); - //RegisterColumnType(DbType.String, 1073741823, "NCLOB"); - RegisterColumnType(DbType.String, int.MaxValue, "NCLOB"); - RegisterColumnType(DbType.Time, "DATE"); - RegisterColumnType(DbType.Guid, "RAW(16)"); - - // the original Migrator.Net code had this, but it's a bad idea - when - // apply a "null" migration to a "not-null" field, it just leaves it as "not-null" and silent fails - // because Oracle doesn't consider ALTER TABLE MODIFY (column ) as being a request to make the field null. - - //RegisterProperty(ColumnProperty.Null, String.Empty); - - AddReservedWords("ACCOUNT", "ACTIVATE", "ADMIN", "ADVISE", "AFTER", "ALL_ROWS", "ALLOCATE", "ANALYZE", "ARCHIVE", "ARCHIVELOG", "ARRAY", "AT", "AUTHENTICATED", "AUTHORIZATION", "AUTOEXTEND", "AUTOMATIC", "BACKUP", "BECOME", "BEFORE", "BEGIN", "BFILE", "BITMAP", "BLOB", "BLOCK", "BODY", "CACHE", "CACHE_INSTANCES", "CANCEL", "CASCADE", "CAST", "CFILE", "CHAINED", "CHANGE", "CHAR_CS", "CHARACTER", "CHECKPOINT", "CHOOSE", "CHUNK", "CLEAR", "CLOB", "CLONE", "CLOSE", "CLOSE_CACHED_OPEN_CURSORS", "COALESCE", "COLUMNS", "COMMIT", "COMMITTED", "COMPATIBILITY", "COMPILE", "COMPLETE", "COMPOSITE_LIMIT", "COMMENT", "COMPUTE", "CONNECT_TIME", "CONSTRAINT", "CONSTRAINTS", "CONTENTS", "CONTINUE", "CONTROLFILE", "CONVERT", "COST", "CPU_PER_CALL", "CPU_PER_SESSION", "CURRENT_SCHEMA", "CURREN_USER", "CURSOR", "CYCLE", "DANGLING", "DATABASE", "DATAFILE", "DATAFILES", "DATAOBJNO", "DBA", "DBHIGH", "DBLOW", "DBMAC", "DEALLOCATE", "DEBUG", "DEC", "DECLARE", "DEFERRABLE", "DEFERRED", "DEGREE", "DEREF", "DIRECTORY", "DISABLE", "DISCONNECT", "DISMOUNT", "DISTRIBUTED", "DML", "DOUBLE", "DUMP", "EACH", "ENABLE", "END", "ENFORCE", "ENTRY", "ESCAPE", "EXCEPT", "EXCEPTIONS", "EXCHANGE", "EXCLUDING", "EXECUTE", "EXPIRE", "EXPLAIN", "EXTENT", "EXTENTS", "EXTERNALLY", "FAILED_LOGIN_ATTEMPTS", "FALSE", "FAST", "FIRST_ROWS", "FLAGGER", "FLOB", "FLUSH", "FORCE", "FOREIGN", "FREELIST", "FREELISTS", "FULL", "FUNCTION", "GLOBAL", "GLOBALLY", "GLOBAL_NAME", "GROUPS", "HASH", "HASHKEYS", "HEADER", "HEAP", "IDGENERATORS", "IDLE_TIME", "IF", "INCLUDING", "INDEXED", "INDEXES", "INDICATOR", "IND_PARTITION", "INITIALLY", "INITRANS", "INSTANCE", "INSTANCES", "INSTEAD", "INT", "INTERMEDIATE", "ISOLATION", "ISOLATION_LEVEL", "KEEP", "KEY", "KILL", "LABEL", "LAYER", "LESS", "LIBRARY", "LIMIT", "LINK", "LIST", "LOB", "LOCAL", "LOCKED", "LOG", "LOGFILE", "LOGGING", "LOGICAL_READS_PER_CALL", "LOGICAL_READS_PER_SESSION", "MANAGE", "MASTER", "MAX", "MAXARCHLOGS", "MAXDATAFILES", "MAXINSTANCES", "MAXLOGFILES", "MAXLOGHISTORY", "MAXLOGMEMBERS", "MAXSIZE", "MAXTRANS", "MAXVALUE", "MIN", "MEMBER", "MINIMUM", "MINEXTENTS", "MINVALUE", "MLS_LABEL_FORMAT", "MOUNT", "MOVE", "MTS_DISPATCHERS", "MULTISET", "NATIONAL", "NCHAR", "NCHAR_CS", "NCLOB", "NEEDED", "NESTED", "NETWORK", "NEW", "NEXT", "NOARCHIVELOG", "NOCACHE", "NOCYCLE", "NOFORCE", "NOLOGGING", "NOMAXVALUE", "NOMINVALUE", "NONE", "NOORDER", "NOOVERRIDE", "NOPARALLEL", "NOPARALLEL", "NOREVERSE", "NORMAL", "NOSORT", "NOTHING", "NUMERIC", "NVARCHAR2", "OBJECT", "OBJNO", "OBJNO_REUSE", "OFF", "OID", "OIDINDEX", "OLD", "ONLY", "OPCODE", "OPEN", "OPTIMAL", "OPTIMIZER_GOAL", "ORGANIZATION", "OSLABEL", "OVERFLOW", "OWN", "PACKAGE", "PARALLEL", "PARTITION", "PASSWORD", "PASSWORD_GRACE_TIME", "PASSWORD_LIFE_TIME", "PASSWORD_LOCK_TIME", "PASSWORD_REUSE_MAX", "PASSWORD_REUSE_TIME", "PASSWORD_VERIFY_FUNCTION", "PCTINCREASE", "PCTTHRESHOLD", "PCTUSED", "PCTVERSION", "PERCENT", "PERMANENT", "PLAN", "PLSQL_DEBUG", "POST_TRANSACTION", "PRECISION", "PRESERVE", "PRIMARY", "PRIVATE", "PRIVATE_SGA", "PRIVILEGE", "PROCEDURE", "PROFILE", "PURGE", "QUEUE", "QUOTA", "RANGE", "RBA", "READ", "READUP", "REAL", "REBUILD", "RECOVER", "RECOVERABLE", "RECOVERY", "REF", "REFERENCES", "REFERENCING", "REFRESH", "REPLACE", "RESET", "RESETLOGS", "RESIZE", "RESTRICTED", "RETURN", "RETURNING", "REUSE", "REVERSE", "ROLE", "ROLES", "ROLLBACK", "RULE", "SAMPLE", "SAVEPOINT", "SB4", "SCAN_INSTANCES", "SCHEMA", "SCN", "SCOPE", "SD_ALL", "SD_INHIBIT", "SD_SHOW", "SEGMENT", "SEG_BLOCK", "SEG_FILE", "SEQUENCE", "SERIALIZABLE", "SESSION_CACHED_CURSORS", "SESSIONS_PER_USER", "SIZE", "SHARED", "SHARED_POOL", "SHRINK", "SKIP", "SKIP_UNUSABLE_INDEXES", "SNAPSHOT", "SOME", "SORT", "SPECIFICATION", "SPLIT", "SQL_TRACE", "STANDBY", "STATEMENT_ID", "STATISTICS", "STOP", "STORAGE", "STORE", "STRUCTURE", "SWITCH", "SYS_OP_ENFORCE_NOT_NULL$", "SYS_OP_NTCIMG$", "SYSDBA", "SYSOPER", "SYSTEM", "TABLES", "TABLESPACE", "TABLESPACE_NO", "TABNO", "TEMPORARY", "THAN", "THE", "THREAD", "TIMESTAMP", "TIME", "TOPLEVEL", "TRACE", "TRACING", "TRANSACTION", "TRANSITIONAL", "TRIGGERS", "TRUE", "TRUNCATE", "TX", "TYPE", "UB2", "UBA", "UNARCHIVED", "UNDO", "UNLIMITED", "UNLOCK", "UNRECOVERABLE", "UNTIL", "UNUSABLE", "UNUSED", "UPDATABLE", "USAGE", "USE", "USING", "VALIDATION", "VALUE", "VALUES", "VARYING", "WHEN", "WITHOUT", "WORK", "WRITE", "WRITEDOWN", "WRITEUP", "XID", "YEAR", "ZONE"); - } - - // in Oracle, this: ALTER TABLE EXTERNALSYSTEMREFERENCES MODIFY (TestScriptId RAW(16)) will no make the column nullable, it just leaves it at it's current null/not-null state - - public override bool NeedsNullForNullableWhenAlteringTable - { - get { return true; } - } - - public override bool ColumnNameNeedsQuote - { - get { return false; } - } - - public override bool ConstraintNameNeedsQuote - { - get { return false; } - } - public override bool TableNameNeedsQuote - { - get { return false; } - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) - { - return new OracleTransformationProvider(dialect, connectionString, defaultSchema, scope, providerName); - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, - string defaultSchema, - string scope, string providerName) - { - return new OracleTransformationProvider(dialect, connection, defaultSchema, scope, providerName); - } - - public override ColumnPropertiesMapper GetColumnMapper(Column column) - { - string type = column.Size > 0 ? GetTypeName(column.Type, column.Size) : GetTypeName(column.Type); - if (!IdentityNeedsType && column.IsIdentity) - type = String.Empty; - - return new OracleColumnPropertiesMapper(this, type); - } - - public override string Default(object defaultValue) - { - if (defaultValue.GetType().Equals(typeof(bool))) - { - return String.Format("DEFAULT {0}", (bool)defaultValue ? "1" : "0"); - } - else if (defaultValue is Guid) - { - return String.Format("DEFAULT HEXTORAW('{0}')", defaultValue.ToString().Replace("-","")); - } - else if (defaultValue is DateTime) - { - return String.Format("DEFAULT TO_TIMESTAMP('{0}', 'YYYY-MM-DD HH24:MI:SS.FF')", ((DateTime)defaultValue).ToString("yyyy-MM-dd HH:mm:ss.ff")); - } - - return base.Default(defaultValue); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/Oracle/OracleTransformationProvider.cs b/src/Migrator.Providers/Impl/Oracle/OracleTransformationProvider.cs deleted file mode 100644 index 4607abe9..00000000 --- a/src/Migrator.Providers/Impl/Oracle/OracleTransformationProvider.cs +++ /dev/null @@ -1,584 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Linq; -using System.Text; -using Migrator.Framework; - -namespace Migrator.Providers.Oracle -{ - public class OracleTransformationProvider : TransformationProvider - { - public const string TemporaryColumnName = "TEMPCOL"; - - public OracleTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) - : base(dialect, connectionString, defaultSchema, scope) - { - this.CreateConnection(providerName); - } - - public OracleTransformationProvider(Dialect dialect, IDbConnection connection, string defaultSchema, string scope, string providerName) - : base(dialect, connection, defaultSchema, scope) - { - } - - protected virtual void CreateConnection(string providerName) - { - if (string.IsNullOrEmpty(providerName)) providerName = "Oracle.DataAccess.Client"; - var fac = DbProviderFactories.GetFactory(providerName); - _connection = fac.CreateConnection(); // new OracleConnection(); - _connection.ConnectionString = _connectionString; - _connection.Open(); - } - - public override void DropDatabases(string databaseName) - { - if (string.IsNullOrEmpty(databaseName)) - ExecuteNonQuery(string.Format("DROP DATABASE")); - } - - public override void AddForeignKey(string name, string primaryTable, string[] primaryColumns, string refTable, - string[] refColumns, ForeignKeyConstraintType constraint) - { - GuardAgainstMaximumIdentifierLengthForOracle(name); - - if (ConstraintExists(primaryTable, name)) - { - Logger.Warn("Constraint {0} already exists", name); - return; - } - - primaryTable = QuoteTableNameIfRequired(primaryTable); - refTable = QuoteTableNameIfRequired(refTable); - string primaryColumnsSql = String.Join(",", primaryColumns.Select(col => QuoteColumnNameIfRequired(col)).ToArray()); - string refColumnsSql = String.Join(",", refColumns.Select(col => QuoteColumnNameIfRequired(col)).ToArray()); - - ExecuteNonQuery(String.Format("ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4})", primaryTable, name, primaryColumnsSql, refTable, refColumnsSql)); - } - - void GuardAgainstMaximumIdentifierLengthForOracle(string name) - { - if (name.Length > 30) - { - throw new ArgumentException(string.Format("The name \"{0}\" is {1} characters in length, bug maximum length for Oracle identifier is 30 characters.", name, name.Length), "name"); - } - } - - protected override string getPrimaryKeyname(string tableName) - { - return tableName.Length > 27 ? "PK_" + tableName.Substring(0, 27) : "PK_" + tableName; - } - - public override void ChangeColumn(string table, Column column) - { - if (!ColumnExists(table, column.Name)) - { - Logger.Warn("Column {0}.{1} does not exist", table, column.Name); - return; - } - - var existingColumn = GetColumnByName(table, column.Name); - - if (column.Type == DbType.String) - { - RenameColumn(table, column.Name, TemporaryColumnName); - - // check if this is not-null - bool isNotNull = (column.ColumnProperty & ColumnProperty.NotNull) == ColumnProperty.NotNull; - - // remove the not-null option - column.ColumnProperty = (column.ColumnProperty & ~ColumnProperty.NotNull); - - AddColumn(table, column); - CopyDataFromOneColumnToAnother(table, TemporaryColumnName, column.Name); - RemoveColumn(table, TemporaryColumnName); - //RenameColumn(table, TemporaryColumnName, column.Name); - - string columnName = QuoteColumnNameIfRequired(column.Name); - - // now set the column to not-null - if (isNotNull) ExecuteQuery(String.Format("ALTER TABLE {0} MODIFY ({1} NOT NULL)", table, columnName)); - } - else - { - if (((existingColumn.ColumnProperty & ColumnProperty.NotNull) == ColumnProperty.NotNull) - && ((column.ColumnProperty & ColumnProperty.NotNull) == ColumnProperty.NotNull)) - { - // was not null, and is being change to not-null - drop the not-null all together - column.ColumnProperty = column.ColumnProperty & ~ColumnProperty.NotNull; - } - else if - (((existingColumn.ColumnProperty & ColumnProperty.Null) == ColumnProperty.Null) - && ((column.ColumnProperty & ColumnProperty.Null) == ColumnProperty.Null)) - { - // was null, and is being changed to null - drop the null all together - column.ColumnProperty = column.ColumnProperty & ~ColumnProperty.Null; - } - - ColumnPropertiesMapper mapper = _dialect.GetAndMapColumnProperties(column); - - ChangeColumn(table, mapper.ColumnSql); - } - } - - void CopyDataFromOneColumnToAnother(string table, string fromColumn, string toColumn) - { - table = QuoteTableNameIfRequired(table); - fromColumn = QuoteColumnNameIfRequired(fromColumn); - toColumn = QuoteColumnNameIfRequired(toColumn); - - ExecuteNonQuery(string.Format("UPDATE {0} SET {1} = {2}", table, toColumn, fromColumn)); - } - - public override void RenameTable(string oldName, string newName) - { - GuardAgainstMaximumIdentifierLengthForOracle(newName); - GuardAgainstExistingTableWithSameName(newName, oldName); - - oldName = QuoteTableNameIfRequired(oldName); - newName = QuoteTableNameIfRequired(newName); - - ExecuteNonQuery(String.Format("ALTER TABLE {0} RENAME TO {1}", oldName, newName)); - } - - void GuardAgainstExistingTableWithSameName(string newName, string oldName) - { - if (TableExists(newName)) throw new MigrationException(string.Format("Can not rename table \"{0}\" to \"{1}\", a table with that name already exists", oldName, newName)); - } - - public override void RenameColumn(string tableName, string oldColumnName, string newColumnName) - { - GuardAgainstMaximumIdentifierLengthForOracle(newColumnName); - GuardAgainstExistingColumnWithSameName(newColumnName, tableName); - - tableName = QuoteTableNameIfRequired(tableName); - oldColumnName = QuoteColumnNameIfRequired(oldColumnName); - newColumnName = QuoteColumnNameIfRequired(newColumnName); - - ExecuteNonQuery(string.Format("ALTER TABLE {0} RENAME COLUMN {1} TO {2}", tableName, oldColumnName, newColumnName)); - } - - void GuardAgainstExistingColumnWithSameName(string newColumnName, string tableName) - { - if (ColumnExists(tableName, newColumnName)) throw new MigrationException(string.Format("A column with the name \"{0}\" already exists in the table \"{1}\"", newColumnName, tableName)); - } - - public override void ChangeColumn(string table, string sqlColumn) - { - if (string.IsNullOrEmpty(table)) throw new ArgumentNullException("table"); - if (string.IsNullOrEmpty(table)) throw new ArgumentNullException("sqlColumn"); - - table = QuoteTableNameIfRequired(table); - sqlColumn = QuoteColumnNameIfRequired(sqlColumn); - ExecuteNonQuery(String.Format("ALTER TABLE {0} MODIFY {1}", table, sqlColumn)); - } - - public override void AddColumn(string table, string sqlColumn) - { - GuardAgainstMaximumIdentifierLengthForOracle(table); - table = QuoteTableNameIfRequired(table); - sqlColumn = QuoteColumnNameIfRequired(sqlColumn); - ExecuteNonQuery(String.Format("ALTER TABLE {0} ADD {1}", table, sqlColumn)); - } - - public override string[] GetConstraints(string table) - { - var constraints = new List(); - //using ( - // IDataReader reader = - // ExecuteQuery( - // String.Format("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE LOWER(TABLE_NAME) = LOWER('{0}')", table))) - //{ - // while (reader.Read()) - // { - // constraints.Add(reader.GetString(0)); - // } - //} - - using ( - IDataReader reader = - ExecuteQuery( - String.Format("SELECT constraint_name FROM user_constraints WHERE lower(table_name) = '{0}'", table.ToLower()))) - { - while (reader.Read()) - { - constraints.Add(reader.GetString(0)); - } - } - - return constraints.ToArray(); - } - - protected override string GetPrimaryKeyConstraintName(string table) - { - var constraints = new List(); - - using ( - IDataReader reader = - ExecuteQuery( - String.Format("SELECT constraint_name FROM user_constraints WHERE lower(table_name) = '{0}' and constraint_type = 'P'", table.ToLower()))) - { - while (reader.Read()) - { - constraints.Add(reader.GetString(0)); - } - } - - return constraints.FirstOrDefault(); - } - - public override bool ConstraintExists(string table, string name) - { - string sql = - string.Format( - "SELECT COUNT(constraint_name) FROM user_constraints WHERE lower(constraint_name) = '{0}' AND lower(table_name) = '{1}'", - name.ToLower(), table.ToLower()); - Logger.Log(sql); - object scalar = ExecuteScalar(sql); - return Convert.ToInt32(scalar) == 1; - } - - public override bool ColumnExists(string table, string column) - { - if (!TableExists(table)) - return false; - - string sql = - string.Format( - "SELECT COUNT(column_name) FROM user_tab_columns WHERE lower(table_name) = '{0}' AND lower(column_name) = '{1}'", - table.ToLower(), column.ToLower()); - Logger.Log(sql); - object scalar = ExecuteScalar(sql); - return Convert.ToInt32(scalar) == 1; - } - - public override bool TableExists(string table) - { - string sql = string.Format("SELECT COUNT(table_name) FROM user_tables WHERE lower(table_name) = '{0}'", table.ToLower()); - - if (_defaultSchema != null) - sql = string.Format("SELECT COUNT(table_name) FROM user_tables WHERE lower(owner) = '{0}' and lower(table_name) = '{1}'", _defaultSchema.ToLower(), table.ToLower()); - - Logger.Log(sql); - object count = ExecuteScalar(sql); - return Convert.ToInt32(count) == 1; - } - - public override List GetDatabases() - { - throw new NotImplementedException(); - } - - public override string[] GetTables() - { - var tables = new List(); - - using (IDataReader reader = - ExecuteQuery("SELECT table_name FROM user_tables")) - { - while (reader.Read()) - { - tables.Add(reader[0].ToString()); - } - } - - return tables.ToArray(); - } - - //public override List AppliedMigrations - //{ - // get - // { - // if (_appliedMigrations == null) - // { - // _appliedMigrations = new List(); - // CreateSchemaInfoTable(); - // using (IDataReader reader = Select(QuoteColumnNameIfRequired("Version"), SchemaInfoTableName)) - // { - // while (reader.Read()) - // { - // _appliedMigrations.Add(Convert.ToInt64(reader.GetValue(0))); - // } - // } - // } - // return _appliedMigrations; - // } - //} - - public override Column[] GetColumns(string table) - { - var columns = new List(); - - using ( - IDataReader reader = - ExecuteQuery( - string.Format( - "select column_name, data_type, data_length, data_precision, data_scale, NULLABLE FROM USER_TAB_COLUMNS WHERE lower(table_name) = '{0}'", - table.ToLower()))) - { - while (reader.Read()) - { - string colName = reader[0].ToString(); - DbType colType = DbType.String; - string dataType = reader[1].ToString().ToLower(); - bool isNullable = ParseBoolean(reader.GetValue(5)); - - if (dataType.Equals("number")) - { - int precision = Convert.ToInt32(reader.GetValue(3)); - int scale = Convert.ToInt32(reader.GetValue(4)); - if (scale == 0) - { - colType = precision <= 10 ? DbType.Int16 : DbType.Int64; - } - else - { - colType = DbType.Decimal; - } - } - else if (dataType.StartsWith("timestamp") || dataType.Equals("date")) - { - colType = DbType.DateTime; - } - - var columnProperties = (isNullable) ? ColumnProperty.Null : ColumnProperty.NotNull; - - columns.Add(new Column(colName, colType, columnProperties)); - } - } - - return columns.ToArray(); - } - - bool ParseBoolean(object value) - { - if (value is string) - { - if ("N" == (string)value) return false; - if ("Y" == (string)value) return true; - } - - return Convert.ToBoolean(value); - } - - public override string GenerateParameterName(int index) - { - return ":p" + index; - } - - protected override void ConfigureParameterWithValue(IDbDataParameter parameter, int index, object value) - { - if (value is Guid || value is Guid?) - { - parameter.DbType = DbType.Binary; - - if (value is Guid? && !((Guid?) value).HasValue) - { - return; - } - - parameter.Value = ((Guid) value).ToByteArray(); - } - else if (value is bool || value is bool?) - { - parameter.DbType = DbType.Int32; - parameter.Value = ((bool) value) ? 1 : 0; - } - else if (value is UInt16) - { - parameter.DbType = DbType.Decimal; - parameter.Value = value; - } - else if (value is UInt32) - { - parameter.DbType = DbType.Decimal; - parameter.Value = value; - } - else if (value is UInt64) - { - parameter.DbType = DbType.Decimal; - parameter.Value = value; - } - else - { - base.ConfigureParameterWithValue(parameter, index, value); - } - } - - public override void RemoveColumnDefaultValue(string table, string column) - { - var sql = string.Format("ALTER TABLE {0} MODIFY {1} DEFAULT NULL", table, column); - ExecuteNonQuery(sql); - } - - public override void AddTable(string name, params IDbField[] fields) - { - GuardAgainstMaximumIdentifierLengthForOracle(name); - - var columns = fields.Where(x => x is Column).Cast().ToArray(); - - GuardAgainstMaximumColumnNameLengthForOracle(name, columns); - - base.AddTable(name, fields); - - if (columns.Any(c => c.ColumnProperty == ColumnProperty.PrimaryKeyWithIdentity)) - { - var identityColumn = columns.First(c => c.ColumnProperty == ColumnProperty.PrimaryKeyWithIdentity); - - var seqTName = name.Length > 21 ? name.Substring(0, 21) : name; - if (seqTName.EndsWith("_")) - seqTName = seqTName.Substring(0, seqTName.Length - 1); - - // Create a sequence for the table - ExecuteQuery(String.Format("CREATE SEQUENCE {0}_SEQUENCE", seqTName)); - - // Create identity trigger (This all has to be in one line (no whitespace), I learned the hard way :) ) - ExecuteQuery(String.Format( - @"CREATE OR REPLACE TRIGGER {0}_TRIGGER BEFORE INSERT ON {1} FOR EACH ROW BEGIN SELECT {0}_SEQUENCE.NEXTVAL INTO :NEW.{2} FROM DUAL; END;", seqTName, name, identityColumn.Name)); - } - } - public override void RemoveTable(string name) - { - base.RemoveTable(name); - try - { - ExecuteQuery(String.Format(@"DROP SEQUENCE {0}_SEQUENCE", name)); - } - catch (Exception e) - { - // swallow this because sequence may not have originally existed. - } - } - void GuardAgainstMaximumColumnNameLengthForOracle(string name, Column[] columns) - { - foreach (Column column in columns) - { - if (column.Name.Length > 30) - { - throw new ArgumentException( - string.Format("When adding table: \"{0}\", the column: \"{1}\", the name of the column is: {2} characters in length, but maximum length for an oracle identifier is 30 characters", name, - column.Name, column.Name.Length), "columns"); - } - } - } - - public override string Encode(Guid guid) - { - byte[] bytes = guid.ToByteArray(); - var hex = new StringBuilder(bytes.Length*2); - foreach (byte b in bytes) hex.AppendFormat("{0:X2}", b); - return hex.ToString(); - } - - public override bool IndexExists(string table, string name) - { - string sql = - string.Format( - "SELECT COUNT(index_name) FROM user_indexes WHERE lower(index_name) = '{0}' AND lower(table_name) = '{1}'", - name.ToLower(), table.ToLower()); - Logger.Log(sql); - object scalar = ExecuteScalar(sql); - return Convert.ToInt32(scalar) == 1; - } - - /*/// - /// Marks a Migration attribute as having been applied - /// - /// The migration attribute that was applied - public override void MigrationApplied(long version) - { - CreateSchemaInfoTable(); - Insert(SchemaInfoTableName, new[] { "version" }, new[] { version.ToString() }); - _appliedMigrations.Add(version); - } - - /// - /// Marks a Migration attribute as having been rolled back from the database - /// - /// The migration attribute that was removed - public override void MigrationUnApplied(long version) - { - CreateSchemaInfoTable(); - Delete(SchemaInfoTableName, "version", version.ToString()); - _appliedMigrations.Remove(version); - }*/ - - //protected override void CreateSchemaInfoTable() - //{ - // EnsureHasConnection(); - // if (!TableExists("SchemaInfo")) - // { - // AddTable(SchemaInfoTableName, new Column("Version", DbType.Int64, ColumnProperty.PrimaryKey)); - // } - //} - - private string SchemaInfoTableName - { - get - { - if (_defaultSchema == null) - return "SchemaInfo"; - return string.Format("{0}.{1}", _defaultSchema, "SchemaInfo"); - } - } - - //protected override string GetPrimaryKeyConstraintName(string table) - //{ - // var sql = "select constraint_name " + - // "from user_indexes join user_constraints on user_indexes.index_name = user_constraints.constraint_name " + - // "where lower(user_indexes.table_name) = lower('{0}') and constraint_type = 'P'"; - - // sql = string.Format(sql, table); - - // using (IDataReader reader = ExecuteQuery(sql)) - // { - // return reader.Read() ? reader.GetString(0) : null; - // } - //} - - public override Index[] GetIndexes(string table) - { - var sql = "select user_indexes.index_name, constraint_type, uniqueness " + - "from user_indexes left outer join user_constraints on user_indexes.index_name = user_constraints.constraint_name " + - "where lower(user_indexes.table_name) = lower('{0}') and index_type = 'NORMAL'"; - - sql = string.Format(sql, table); - - var indexes = new List(); - - using (IDataReader reader = ExecuteQuery(sql)) - { - while (reader.Read()) - { - var index = new Index - { - Name = reader.GetString(0), - Unique = reader.GetString(2) == "UNIQUE" ? true : false - }; - - if (!reader.IsDBNull(1)) - { - index.PrimaryKey = reader.GetString(1) == "P" ? true : false; - } - else - index.PrimaryKey = false; - - index.Clustered = false; //??? - - //if (!reader.IsDBNull(3)) index.KeyColumns = (reader.GetString(3).Split(',')); - //if (!reader.IsDBNull(4)) index.IncludeColumns = (reader.GetString(4).Split(',')); - - indexes.Add(index); - } - } - - return indexes.ToArray(); - } - - public override string Concatenate(params string[] strings) - { - return string.Join(" || ", strings); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/PostgreSQL/PostgreSQL82Dialect.cs b/src/Migrator.Providers/Impl/PostgreSQL/PostgreSQL82Dialect.cs deleted file mode 100644 index 4fe73add..00000000 --- a/src/Migrator.Providers/Impl/PostgreSQL/PostgreSQL82Dialect.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Data; - -namespace Migrator.Providers.PostgreSQL -{ - public class PostgreSQL82Dialect : PostgreSQLDialect - { - public PostgreSQL82Dialect() - { - RegisterColumnType(DbType.Guid, "uuid"); // Requires postgresql 8.2 and up - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/PostgreSQL/PostgreSQLDialect.cs b/src/Migrator.Providers/Impl/PostgreSQL/PostgreSQLDialect.cs deleted file mode 100644 index 0545c8a6..00000000 --- a/src/Migrator.Providers/Impl/PostgreSQL/PostgreSQLDialect.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Data; -using Migrator.Framework; -using Migrator.Providers.Oracle; - -namespace Migrator.Providers.PostgreSQL -{ - public class PostgreSQLDialect : Dialect - { - public PostgreSQLDialect() - { - RegisterColumnType(DbType.AnsiStringFixedLength, "char(255)"); - RegisterColumnType(DbType.AnsiStringFixedLength, 8000, "char($l)"); - RegisterColumnType(DbType.AnsiString, "varchar(255)"); - RegisterColumnType(DbType.AnsiString, 8000, "varchar($l)"); - RegisterColumnType(DbType.AnsiString, 2147483647, "text"); - RegisterColumnType(DbType.Binary, "bytea"); - RegisterColumnType(DbType.Binary, 2147483647, "bytea"); - RegisterColumnType(DbType.Boolean, "boolean"); - RegisterColumnType(DbType.Byte, "int2"); - RegisterColumnType(DbType.Currency, "decimal(16,4)"); - RegisterColumnType(DbType.Date, "date"); - RegisterColumnType(DbType.DateTime, "timestamp"); - RegisterColumnType(DbType.DateTimeOffset, "timestamp"); - RegisterColumnType(DbType.Decimal, "decimal(19,5)"); - RegisterColumnType(DbType.Decimal, 19, "decimal(18, $l)"); - RegisterColumnType(DbType.Double, "float8"); - RegisterColumnType(DbType.Int16, "int2"); - RegisterColumnType(DbType.Int32, "int4"); - RegisterColumnType(DbType.Int64, "int8"); - RegisterColumnType(DbType.UInt16, "int4"); - RegisterColumnType(DbType.UInt32, "int8"); - RegisterColumnType(DbType.UInt64, "decimal(20,0)"); - RegisterColumnType(DbType.Single, "float4"); - RegisterColumnType(DbType.StringFixedLength, "char(255)"); - RegisterColumnType(DbType.StringFixedLength, 4000, "char($l)"); - RegisterColumnType(DbType.String, "varchar(255)"); - RegisterColumnType(DbType.String, 4000, "varchar($l)"); - RegisterColumnType(DbType.String, 1073741823, "text"); - RegisterColumnType(DbType.Time, "time"); - RegisterColumnType(DbType.Guid, "uuid"); - - RegisterProperty(ColumnProperty.Identity, "serial"); - - AddReservedWords("ABS", "ABSOLUTE", "ACCESS", "ACTION", "ADA", "ADD", "ADMIN", "AFTER", "AGGREGATE", "ALIAS", "ALL", "ALLOCATE", "ALTER", "ANALYSE", "ANALYZE", "AND", "ANY", "ARE", - "ARRAY", "AS", "ASC", "ASENSITIVE", "ASSERTION", "ASSIGNMENT", "ASYMMETRIC", "AT", "ATOMIC", "AUTHORIZATION", "AVG", "BACKWARD", "BEFORE", "BEGIN", "BETWEEN", "BIGINT", "BINARY", - "BIT", "BITVAR", "BIT_LENGTH", "BLOB", "BOOLEAN", "BOTH", "BREADTH", "BY", "C", "CACHE", "CALL", "CALLED", "CARDINALITY", "CASCADE", "CASCADED", "CASE", "CAST", "CATALOG", - "CATALOG_NAME", "CHAIN", "CHAR", "CHARACTER", "CHARACTERISTICS", "CHARACTER_LENGTH", "CHARACTER_SET_CATALOG", "CHARACTER_SET_NAME", "CHARACTER_SET_SCHEMA", "CHAR_LENGTH", - "CHECK", "CHECKED", "CHECKPOINT", "CLASS", "CLASS_ORIGIN", "CLOB", "CLOSE", "CLUSTER", "COALESCE", "COBOL", "COLLATE", "COLLATION", "COLLATION_CATALOG", "COLLATION_NAME", - "COLLATION_SCHEMA", "COLUMN", "COLUMN_NAME", "COMMAND_FUNCTION", "COMMAND_FUNCTION_CODE", "COMMENT", "COMMIT", "COMMITTED", "COMPLETION", "CONDITION_NUMBER", "CONNECT", - "CONNECTION", "CONNECTION_NAME", "CONSTRAINT", "CONSTRAINTS", "CONSTRAINT_CATALOG", "CONSTRAINT_NAME", "CONSTRAINT_SCHEMA", "CONSTRUCTOR", "CONTAINS", "CONTENTS", "CONTINUE", - "CONVERSION", - "CONVERT", "COPY", "CORRESPONDING", "COUNT", "CREATE", "CREATEDB", "CREATEUSER", "CROSS", "CUBE", "CURRENT", "CURRENT_DATE", "CURRENT_PATH", "CURRENT_ROLE", "CURRENT_TIME", - "CURRENT_TIMESTAMP", "CURRENT_USER", "CURSOR", "CURSOR_NAME", "CYCLE", "DATABASE", "DATE", "DATETIME_INTERVAL_CODE", "DATETIME_INTERVAL_PRECISION", "DAY", "DEALLOCATE", - "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DEFINED", "DEFINER", "DELETE", "DELIMITER", "DELIMITERS", "DEPTH", "DEREF", "DESC", "DESCRIBE", "DESCRIPTOR", - "DESTROY", "DESTRUCTOR", "DETERMINISTIC", "DIAGNOSTICS", "DICTIONARY", "DISCONNECT", "DISPATCH", "DISTINCT", "DO", "DOMAIN", "DOUBLE", "DROP", "DYNAMIC", "DYNAMIC_FUNCTION", - "DYNAMIC_FUNCTION_CODE", "EACH", "ELSE", "ENCODING", "ENCRYPTED", "END", "END-EXEC", "EQUALS", "ESCAPE", "EVERY", "EXCEPT", "EXCEPTION", "EXCLUSIVE", "EXEC", "EXECUTE", - "EXISTING", "EXISTS", "EXPLAIN", "EXTERNAL", "EXTRACT", "FALSE", "FETCH", "FINAL", "FIRST", "FLOAT", "FOR", "FORCE", "FOREIGN", "FORTRAN", "FORWARD", "FOUND", "FREE", "FREEZE", - "FROM", "FULL", "FUNCTION", "G", "GENERAL", "GENERATED", "GET", "GLOBAL", "GO", "GOTO", "GRANT", "GRANTED", "GROUP", "GROUPING", "HANDLER", "HAVING", "HIERARCHY", "HOLD", "HOST", - "HOUR", "IDENTITY", "IGNORE", "ILIKE", "IMMEDIATE", "IMMUTABLE", "IMPLEMENTATION", "IMPLICIT", "IN", "INCREMENT", "INDEX", "INDICATOR", "INFIX", "INHERITS", "INITIALIZE", - "INITIALLY", "INNER", "INOUT", "INPUT", "INSENSITIVE", "INSERT", "INSTANCE", "INSTANTIABLE", "INSTEAD", "INT", "INTEGER", "INTERSECT", "INTERVAL", "INTO", "INVOKER", "IS", - "ISNULL", "ISOLATION", "ITERATE", "JOIN", "K", "KEY", "KEY_MEMBER", "KEY_TYPE", "LANCOMPILER", "LANGUAGE", "LARGE", "LAST", "LATERAL", "LEADING", "LEFT", "LENGTH", "LESS", - "LEVEL", "LIKE", "LIMIT", "LISTEN", "LOAD", "LOCAL", "LOCALTIME", "LOCALTIMESTAMP", "LOCATOR", "LOCK", "LOWER", "M", "MAP", "MATCH", "MAX", "MAXVALUE", - "MESSAGE_LENGTH", "MESSAGE_OCTET_LENGTH", "MESSAGE_TEXT", "METHOD", "MIN", "MINUTE", "MINVALUE", "MOD", "MODE", "MODIFIES", "MODIFY", "MODULE", "MONTH", "MORE", "MOVE", "MUMPS", - "NAMES", "NATIONAL", "NATURAL", "NCHAR", "NCLOB", "NEW", "NEXT", "NO", "NOCREATEDB", "NOCREATEUSER", "NONE", "NOT", "NOTHING", "NOTIFY", "NOTNULL", "NULL", "NULLABLE", - "NULLIF", "NUMBER", "NUMERIC", "OBJECT", "OCTET_LENGTH", "OF", "OFF", "OFFSET", "OIDS", "OLD", "ON", "ONLY", "OPEN", "OPERATION", "OPERATOR", "OPTION", "OPTIONS", "OR", "ORDER", - "ORDINALITY", "OUT", "OUTER", "OUTPUT", "OVERLAPS", "OVERLAY", "OVERRIDING", "OWNER", "PAD", "PARAMETER", "PARAMETERS", "PARAMETER_MODE", "PARAMETER_NAME", - "PARAMETER_ORDINAL_POSITION", "PARAMETER_SPECIFIC_CATALOG", "PARAMETER_SPECIFIC_NAME", "PARAMETER_SPECIFIC_SCHEMA", "PARTIAL", "PASCAL", "PATH", "PENDANT", "PLACING", - "PLI", "POSITION", "POSTFIX", "PRECISION", "PREFIX", "PREORDER", "PREPARE", "PRESERVE", "PRIMARY", "PRIOR", "PRIVILEGES", "PROCEDURAL", "PROCEDURE", "PUBLIC", "READ", "READS", - "REAL", "RECHECK", "RECURSIVE", "REF", "REFERENCES", "REFERENCING", "REINDEX", "RELATIVE", "RENAME", "REPEATABLE", "REPLACE", "RESET", "RESTRICT", "RESULT", "RETURN", - "RETURNED_LENGTH", "RETURNED_OCTET_LENGTH", "RETURNED_SQLSTATE", "RETURNS", "REVOKE", "RIGHT", "ROLE", "ROLLBACK", "ROLLUP", "ROUTINE", "ROUTINE_CATALOG", "ROUTINE_NAME", - "ROUTINE_SCHEMA", "ROW", "ROWS", "ROW_COUNT", "RULE", "SAVEPOINT", "SCALE", "SCHEMA", "SCHEMA_NAME", "SCOPE", "SCROLL", "SEARCH", "SECOND", "SECTION", "SECURITY", "SELECT", - "SELF", "SENSITIVE", "SEQUENCE", "SERIALIZABLE", "SERVER_NAME", "SESSION", "SESSION_USER", "SET", "SETOF", "SETS", "SHARE", "SHOW", "SIMILAR", "SIMPLE", "SIZE", "SMALLINT", - "SOME", "SPACE", "SPECIFIC", "SPECIFICTYPE", "SPECIFIC_NAME", "SQL", "SQLCODE", "SQLERROR", "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", "STABLE", "START", - "STATEMENT", "STATIC", "STATISTICS", "STDIN", "STDOUT", "STORAGE", "STRICT", "STRUCTURE", "STYLE", "SUBCLASS_ORIGIN", "SUBLIST", "SUBSTRING", "SUM", "SYMMETRIC", "SYSID", - "SYSTEM", "SYSTEM_USER", "TABLE", "TABLE_NAME", "TEMP", "TEMPLATE", "TEMPORARY", "TERMINATE", "THAN", "THEN", "TIME", "TIMESTAMP", "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TO", - "TOAST", "TRAILING", "TRANSACTION", "TRANSACTIONS_COMMITTED", "TRANSACTIONS_ROLLED_BACK", "TRANSACTION_ACTIVE", "TRANSFORM", "TRANSFORMS", "TRANSLATE", "TRANSLATION", "TREAT", - "TRIGGER", "TRIGGER_CATALOG", "TRIGGER_SCHEMA", "TRIM", "TRUE", "TRUNCATE", "TRUSTED", "UNCOMMITTED", "UNDER", "UNENCRYPTED", "UNION", "UNIQUE", - "UNKNOWN", "UNLISTEN", "UNNAMED", "UNNEST", "UNTIL", "UPDATE", "UPPER", "USAGE", "USER", "USER_DEFINED_TYPE_CATALOG", "USER_DEFINED_TYPE_NAME", "USER_DEFINED_TYPE_SCHEMA", - "USING", "VACUUM", "VALID", "VALIDATOR", "VALUES", "VARCHAR", "VARIABLE", "VARYING", "VERBOSE", "VERSION", "VIEW", "VOLATILE", "WHEN", "WHENEVER", "WHERE", "WITH", - "WITHOUT", "WORK", "WRITE", "XMAX", "XMIN", "YEAR", "ZONE"); - } - - public override bool TableNameNeedsQuote - { - get { return false; } - } - - public override bool ConstraintNameNeedsQuote - { - get { return false; } - } - - public override bool IdentityNeedsType - { - get { return false; } - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) - { - return new PostgreSQLTransformationProvider(dialect, connectionString, defaultSchema, scope, providerName); - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, - string defaultSchema, - string scope, string providerName) - { - return new PostgreSQLTransformationProvider(dialect, connection, defaultSchema, scope, providerName); - } - } -} diff --git a/src/Migrator.Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs b/src/Migrator.Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs deleted file mode 100644 index c0608164..00000000 --- a/src/Migrator.Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs +++ /dev/null @@ -1,303 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; - -using Migrator.Framework; - -namespace Migrator.Providers.PostgreSQL -{ - /// - /// Migration transformations provider for PostgreSql (using NPGSql .Net driver) - /// - public class PostgreSQLTransformationProvider : TransformationProvider - { - public PostgreSQLTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) - : base(dialect, connectionString, defaultSchema, scope) - { - if (string.IsNullOrEmpty(providerName)) providerName = "Npgsql"; - var fac = DbProviderFactoriesHelper.GetFactory(providerName, "Npgsql", "Npgsql.NpgsqlFactory"); - _connection = fac.CreateConnection(); //new NpgsqlConnection(); - _connection.ConnectionString = _connectionString; - _connection.Open(); - } - - public PostgreSQLTransformationProvider(Dialect dialect, IDbConnection connection, string defaultSchema, string scope, string providerName) - : base(dialect, connection, defaultSchema, scope) - { - } - - public override Index[] GetIndexes(string table) - { - var retVal = new List(); - - var sql = @" -SELECT * FROM ( -SELECT i.relname as indname, - idx.indisprimary, - idx.indisunique, - i.relowner as indowner, - cast(idx.indrelid::regclass as varchar) as tablenm, - am.amname as indam, - idx.indkey, - ARRAY( - SELECT pg_get_indexdef(idx.indexrelid, k + 1, true) - FROM generate_subscripts(idx.indkey, 1) as k - ORDER BY k - ) as indkey_names, - idx.indexprs IS NOT NULL as indexprs, - idx.indpred IS NOT NULL as indpred -FROM pg_index as idx -JOIN pg_class as i -ON i.oid = idx.indexrelid -JOIN pg_am as am -ON i.relam = am.oid -JOIN pg_namespace as ns -ON ns.oid = i.relnamespace -AND ns.nspname = ANY(current_schemas(false))) AS t -WHERE lower(tablenm) = lower('{0}') -;"; - - - - using (var reader = ExecuteQuery(string.Format(sql, table))) - { - while (reader.Read()) - { - if (!reader.IsDBNull(1)) - { - var idx = new Index - { - Name = reader.GetString(0), - PrimaryKey = reader.GetBoolean(1), - Unique = reader.GetBoolean(2), - }; - //var cols = reader.GetString(7); - //cols = cols.Substring(1, cols.Length - 2); - //idx.KeyColumns = cols.Split(','); - retVal.Add(idx); - } - } - } - - return retVal.ToArray(); - } - - public override void RemoveTable(string name) - { - if (!TableExists(name)) - { - throw new MigrationException(String.Format("Table with name '{0}' does not exist to rename", name)); - } - - ExecuteNonQuery(String.Format("DROP TABLE IF EXISTS {0} CASCADE", name)); - } - - public override bool ConstraintExists(string table, string name) - { - using (IDataReader reader = - ExecuteQuery(string.Format("SELECT constraint_name FROM information_schema.table_constraints WHERE table_schema = 'public' AND constraint_name = lower('{0}')", name))) - { - return reader.Read(); - } - } - - public override bool ColumnExists(string table, string column) - { - if (!TableExists(table)) - return false; - - using (IDataReader reader = - ExecuteQuery(String.Format("SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = lower('{0}') AND (column_name = lower('{1}') OR column_name = '{1}')", table, column))) - { - return reader.Read(); - } - } - - public override bool TableExists(string table) - { - using (IDataReader reader = - ExecuteQuery(String.Format("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = lower('{0}')", table))) - { - return reader.Read(); - } - } - - public override List GetDatabases() - { - return ExecuteStringQuery("SELECT datname FROM pg_database WHERE datistemplate = false"); - } - - //public override void ChangeColumn(string table, Column column) - //{ - // if (!ColumnExists(table, column.Name)) - // { - // Logger.Warn("Column {0}.{1} does not exist", table, column.Name); - // return; - // } - - // var existingColumn = GetColumnByName(table, column.Name); - - // column.Name = existingColumn.Name; // name might have different case. - - // string tempColumn = "temp_" + column.Name; - // RenameColumn(table, column.Name, tempColumn); - - // // check if this is not-null - // bool isNotNull = (column.ColumnProperty & ColumnProperty.NotNull) == ColumnProperty.NotNull; - - // // remove the not-null option - // column.ColumnProperty = (column.ColumnProperty & ~ColumnProperty.NotNull); - - // AddColumn(table, column); - // ExecuteQuery(String.Format("UPDATE {0} SET {1}={2}", table, Dialect.Quote(column.Name), Dialect.Quote(tempColumn))); - // RemoveColumn(table, tempColumn); - - // // if is not null, set that now - // if (isNotNull) ExecuteQuery(string.Format("ALTER TABLE {0} ALTER COLUMN {1} SET NOT NULL", table, Dialect.Quote(column.Name))); - //} - - public override void ChangeColumn(string table, Column column) - { - var oldColumn = GetColumnByName(table, column.Name); - - var isUniqueSet = column.ColumnProperty.IsSet(ColumnProperty.Unique); - - column.ColumnProperty = column.ColumnProperty.Clear(ColumnProperty.Unique); - - if (!ColumnExists(table, column.Name)) - { - Logger.Warn("Column {0}.{1} does not exist", table, column.Name); - return; - } - - ColumnPropertiesMapper mapper = _dialect.GetAndMapColumnProperties(column); - - string change1 = string.Format("{0} TYPE {1}", QuoteColumnNameIfRequired(mapper.Name), mapper.type); - - #region Field Type Converters... - if ((oldColumn.Type == DbType.Int16 || oldColumn.Type == DbType.Int32 || oldColumn.Type == DbType.Int64 || oldColumn.Type == DbType.Decimal) && column.Type == DbType.Boolean) - { - change1 += string.Format(" USING CASE {0} WHEN 1 THEN true ELSE false END", QuoteColumnNameIfRequired(mapper.Name)); - } - else if (column.Type == DbType.Boolean) - { - change1 += string.Format(" USING CASE {0} WHEN '1' THEN true ELSE false END", QuoteColumnNameIfRequired(mapper.Name)); - } - #endregion - ChangeColumn(table, change1); - - if (mapper.Default != null) - { - string change2 = string.Format("{0} SET {1}", QuoteColumnNameIfRequired(mapper.Name), _dialect.Default(mapper.Default)); - ChangeColumn(table, change2); - } - else - { - string change2 = string.Format("{0} DROP DEFAULT", QuoteColumnNameIfRequired(mapper.Name)); - ChangeColumn(table, change2); - } - - if (isUniqueSet) - { - AddUniqueConstraint(string.Format("UX_{0}_{1}", table, column.Name), table, new string[] { column.Name }); - } - } - - public override void CreateDatabases(string databaseName) - { - ExecuteNonQuery(string.Format("CREATE DATABASE {0}", _dialect.Quote(databaseName))); - } - - public override void SwitchDatabase(string databaseName) - { - _connection.ChangeDatabase(_dialect.Quote(databaseName)); - } - - public override void DropDatabases(string databaseName) - { - ExecuteNonQuery(string.Format("DROP DATABASE {0}", _dialect.Quote(databaseName))); - } - - public override string[] GetTables() - { - var tables = new List(); - using (IDataReader reader = ExecuteQuery("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'")) - { - while (reader.Read()) - { - tables.Add((string) reader[0]); - } - } - return tables.ToArray(); - } - - public override Column[] GetColumns(string table) - { - var columns = new List(); - using ( - IDataReader reader = - ExecuteQuery( - String.Format("select COLUMN_NAME, IS_NULLABLE from information_schema.columns where table_schema = 'public' AND table_name = lower('{0}');", table))) - { - // FIXME: Mostly duplicated code from the Transformation provider just to support stupid case-insensitivty of Postgre - while (reader.Read()) - { - var column = new Column(reader[0].ToString(), DbType.String); - bool isNullable = reader.GetString(1) == "YES"; - column.ColumnProperty |= isNullable ? ColumnProperty.Null : ColumnProperty.NotNull; - - columns.Add(column); - } - } - - return columns.ToArray(); - } - - public override Column GetColumnByName(string table, string columnName) - { - // Duplicate because of the lower case issue - return Array.Find(GetColumns(table), column => column.Name == columnName.ToLower() || column.Name == columnName); - } - - public override bool IndexExists(string table, string name) - { - using (IDataReader reader = - ExecuteQuery(string.Format("SELECT indexname FROM pg_catalog.pg_indexes WHERE indexname = lower('{0}')", name))) - { - return reader.Read(); - } - } - - protected override void ConfigureParameterWithValue(IDbDataParameter parameter, int index, object value) - { - if (value is UInt16) - { - parameter.DbType = DbType.Int32; - parameter.Value = Convert.ToInt32(value); - } - else if (value is UInt32) - { - parameter.DbType = DbType.Int64; - parameter.Value = Convert.ToInt64(value); - } - else - { - base.ConfigureParameterWithValue(parameter, index, value); - } - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/SQLite/SQLiteDialect.cs b/src/Migrator.Providers/Impl/SQLite/SQLiteDialect.cs deleted file mode 100644 index 0c7b0561..00000000 --- a/src/Migrator.Providers/Impl/SQLite/SQLiteDialect.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Data; -using Migrator.Framework; - -namespace Migrator.Providers.SQLite -{ - public class SQLiteDialect : Dialect - { - public SQLiteDialect() - { - RegisterColumnType(DbType.Binary, "BINARY"); - RegisterColumnType(DbType.Byte, "TINYINT"); - RegisterColumnType(DbType.Int16, "SMALLINT"); - RegisterColumnType(DbType.Int32, "INTEGER"); - RegisterColumnType(DbType.Int64, "INTEGER"); - RegisterColumnType(DbType.SByte, "INTEGER"); - RegisterColumnType(DbType.UInt16, "INTEGER"); - RegisterColumnType(DbType.UInt32, "INTEGER"); - RegisterColumnType(DbType.UInt64, "INTEGER"); - - RegisterColumnType(DbType.Currency, "CURRENCY"); - RegisterColumnType(DbType.Decimal, "DECIMAL"); - RegisterColumnType(DbType.Double, "DOUBLE"); - RegisterColumnType(DbType.Single, "REAL"); - RegisterColumnType(DbType.VarNumeric, "NUMERIC"); - - RegisterColumnType(DbType.String, "TEXT"); - RegisterColumnType(DbType.StringFixedLength, "TEXT"); - RegisterColumnType(DbType.AnsiString, "TEXT"); - RegisterColumnType(DbType.AnsiStringFixedLength, "TEXT"); - - RegisterColumnType(DbType.Date, "DATE"); - RegisterColumnType(DbType.DateTime, "DATETIME"); - RegisterColumnType(DbType.DateTimeOffset, "TEXT"); - RegisterColumnType(DbType.Time, "TIME"); - RegisterColumnType(DbType.Boolean, "BOOLEAN"); // Important for Dapper to know it should map to a bool - RegisterColumnType(DbType.Guid, "UNIQUEIDENTIFIER"); - - RegisterProperty(ColumnProperty.Identity, "AUTOINCREMENT"); - RegisterProperty(ColumnProperty.CaseSensitive, "COLLATE NOCASE"); - } - - public override string Default(object defaultValue) - { - if (defaultValue is bool) - { - return String.Format("DEFAULT {0}", (bool)defaultValue ? "1" : "0"); - } - - return base.Default(defaultValue); - } - - public override bool NeedsNotNullForIdentity - { - get { return false; } - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) - { - return new SQLiteTransformationProvider(dialect, connectionString, scope, providerName); - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, string defaultSchema, - string scope, string providerName) - { - return new SQLiteTransformationProvider(dialect, connection, scope, providerName); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/SQLite/SQLiteMonoDialect.cs b/src/Migrator.Providers/Impl/SQLite/SQLiteMonoDialect.cs deleted file mode 100644 index 10b41f0b..00000000 --- a/src/Migrator.Providers/Impl/SQLite/SQLiteMonoDialect.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Data; -using Migrator.Framework; - -namespace Migrator.Providers.SQLite -{ - public class SQLiteMonoDialect : SQLiteDialect - { - public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) - { - return new SQLiteMonoTransformationProvider(dialect, connectionString, scope, providerName); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/SQLite/SQLiteMonoTransformationProvider.cs b/src/Migrator.Providers/Impl/SQLite/SQLiteMonoTransformationProvider.cs deleted file mode 100644 index e18e98cc..00000000 --- a/src/Migrator.Providers/Impl/SQLite/SQLiteMonoTransformationProvider.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; - -using Migrator.Framework; - -namespace Migrator.Providers.SQLite -{ - /// - /// Summary description for SQLiteTransformationProvider. - /// - public class SQLiteMonoTransformationProvider : SQLiteTransformationProvider - { - public SQLiteMonoTransformationProvider(Dialect dialect, string connectionString, string scope, string providerName) - : base(dialect, connectionString, scope, providerName) - { - - } - - public SQLiteMonoTransformationProvider(Dialect dialect, IDbConnection connection, string scope, string providerName) - : base(dialect, connection, scope, providerName) - { - } - - protected override void CreateConnection(string providerName) - { - if (string.IsNullOrEmpty(providerName)) - providerName = "Mono.Data.Sqlite"; - var fac = DbProviderFactoriesHelper.GetFactory(providerName, "Mono.Data.Sqlite", "Mono.Data.Sqlite.SQLiteFactory"); - _connection = fac.CreateConnection(); // new SQLiteConnection(_connectionString); - _connection.ConnectionString = _connectionString; - _connection.Open(); - } - } -} diff --git a/src/Migrator.Providers/Impl/SQLite/SQLiteTransformationProvider.cs b/src/Migrator.Providers/Impl/SQLite/SQLiteTransformationProvider.cs deleted file mode 100644 index 64cec3d8..00000000 --- a/src/Migrator.Providers/Impl/SQLite/SQLiteTransformationProvider.cs +++ /dev/null @@ -1,591 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -//using System.Data.SQLite; -using System.Linq; - -using Migrator.Framework; - -using ForeignKeyConstraint = Migrator.Framework.ForeignKeyConstraint; - -namespace Migrator.Providers.SQLite -{ - /// - /// Summary description for SQLiteTransformationProvider. - /// - public class SQLiteTransformationProvider : TransformationProvider - { - public SQLiteTransformationProvider(Dialect dialect, string connectionString, string scope, string providerName) - : base(dialect, connectionString, null, scope) - { - this.CreateConnection(providerName); - } - - public SQLiteTransformationProvider(Dialect dialect, IDbConnection connection, string scope, string providerName) - : base(dialect, connection, null, scope) - { - } - - protected virtual void CreateConnection(string providerName) - { - if (string.IsNullOrEmpty(providerName)) - providerName = "System.Data.SQLite"; - var fac = DbProviderFactoriesHelper.GetFactory(providerName, "System.Data.SQLite", "System.Data.SQLite.SQLiteFactory"); - _connection = fac.CreateConnection(); // new SQLiteConnection(_connectionString); - _connection.ConnectionString = _connectionString; - _connection.Open(); - } - - public override void AddForeignKey(string name, string primaryTable, string[] primaryColumns, string refTable, - string[] refColumns, ForeignKeyConstraintType constraint) - { - // NOOP Because SQLite doesn't support foreign keys - } - - private string GetSqlForAddTable(string tableName, string colDefsSql, string compositeDefSql) - { - return compositeDefSql != null ? colDefsSql.TrimEnd(')') + "," + compositeDefSql : colDefsSql; - } - - public string[] GetColumnDefs(string table, out string compositeDefSql) - { - return ParseSqlColumnDefs(GetSqlDefString(table), out compositeDefSql); - } - - public string GetSqlDefString(string table) - { - string sqldef = null; - using (IDataReader reader = ExecuteQuery(String.Format("SELECT sql FROM sqlite_master WHERE type='table' AND name='{0}'", table))) - { - if (reader.Read()) - { - sqldef = (string)reader[0]; - } - } - return sqldef; - } - - public string[] ParseSqlColumnDefs(string sqldef, out string compositeDefSql) - { - if (String.IsNullOrEmpty(sqldef)) - { - compositeDefSql = null; - return null; - } - - sqldef = sqldef.Replace(Environment.NewLine, " "); - int start = sqldef.IndexOf("("); - - // Code to handle composite primary keys /mol - int compositeDefIndex = sqldef.IndexOf("PRIMARY KEY ("); // Not ideal to search for a string like this but I'm lazy - if (compositeDefIndex > -1) - { - compositeDefSql = sqldef.Substring(compositeDefIndex, sqldef.LastIndexOf(")") - compositeDefIndex); - sqldef = sqldef.Substring(0, compositeDefIndex).TrimEnd(',', ' ') + ")"; - } - else - compositeDefSql = null; - - int end = sqldef.LastIndexOf(")"); // Changed from 'IndexOf' to 'LastIndexOf' to handle foreign key definitions /mol - - sqldef = sqldef.Substring(0, end); - sqldef = sqldef.Substring(start + 1); - - string[] cols = sqldef.Split(new char[] { ',' }); - for (int i = 0; i < cols.Length; i++) - { - cols[i] = cols[i].Trim(); - } - return cols; - } - - /// - /// Turn something like 'columnName INTEGER NOT NULL' into just 'columnName' - /// - public string[] ParseSqlForColumnNames(string sqldef, out string compositeDefSql) - { - string[] parts = ParseSqlColumnDefs(sqldef, out compositeDefSql); - return ParseSqlForColumnNames(parts); - } - - public string[] ParseSqlForColumnNames(string[] parts) - { - if (null == parts) - return null; - - for (int i = 0; i < parts.Length; i++) - { - parts[i] = ExtractNameFromColumnDef(parts[i]); - } - return parts; - } - - /// - /// Name is the first value before the space. - /// - /// - /// - public string ExtractNameFromColumnDef(string columnDef) - { - int idx = columnDef.IndexOf(" "); - if (idx > 0) - { - return columnDef.Substring(0, idx); - } - return null; - } - - public DbType ExtractTypeFromColumnDef(string columnDef) - { - int idx = columnDef.IndexOf(" ") + 1; - if (idx > 0) - { - var idy = columnDef.IndexOf(" ", idx) - idx; - - if (idy > 0) - return _dialect.GetDbType(columnDef.Substring(idx, idy)); - else - return _dialect.GetDbType(columnDef.Substring(idx)); - } - else - throw new Exception("Error extracting type from column definition: '" + columnDef + "'"); - } - - public override void RemoveForeignKey(string table, string name) - { - //Check the impl... - return; - - // Generate new table definition with foreign key - string compositeDefSql; - string[] origColDefs = GetColumnDefs(table, out compositeDefSql); - List colDefs = new List(); - - foreach (string origdef in origColDefs) - { - // Strip the constraint part of the column definition - var constraintIndex = origdef.IndexOf(string.Format(" CONSTRAINT {0}", name), StringComparison.OrdinalIgnoreCase); - if (constraintIndex > -1) - colDefs.Add(origdef.Substring(0, constraintIndex)); - else - colDefs.Add(origdef); - } - - string[] newColDefs = colDefs.ToArray(); - string colDefsSql = String.Join(",", newColDefs); - - string[] colNames = ParseSqlForColumnNames(newColDefs); - string colNamesSql = String.Join(",", colNames); - - // Create new table with temporary name - AddTable(table + "_temp", null, GetSqlForAddTable(table, colDefsSql, compositeDefSql)); - - // Copy data from original table to temporary table - ExecuteNonQuery(String.Format("INSERT INTO {0}_temp SELECT {1} FROM {0}", table, colNamesSql)); - - // Add indexes from original table - MoveIndexesFromOriginalTable(table, table + "_temp"); - - //PerformForeignKeyAffectedAction(() => - //{ - // Remove original table - RemoveTable(table); - - // Rename temporary table to original table name - ExecuteNonQuery(String.Format("ALTER TABLE {0}_temp RENAME TO {0}", table)); - //}); - } - - public string[] GetCreateIndexSqlStrings(string table) - { - var sqlStrings = new List(); - - using (IDataReader reader = ExecuteQuery(String.Format("SELECT sql FROM sqlite_master WHERE type='index' AND sql NOT NULL AND tbl_name='{0}'", table))) - while (reader.Read()) - sqlStrings.Add((string)reader[0]); - - return sqlStrings.ToArray(); - } - - public void MoveIndexesFromOriginalTable(string origTable, string newTable) - { - var indexSqls = GetCreateIndexSqlStrings(origTable); - foreach (var indexSql in indexSqls) - { - var origTableStart = indexSql.IndexOf(" ON ", StringComparison.OrdinalIgnoreCase) + 4; - var origTableEnd = indexSql.IndexOf("(", origTableStart); - - // First remove original index, because names have to be unique - var createIndexDef = " INDEX "; - var indexNameStart = indexSql.IndexOf(createIndexDef, StringComparison.OrdinalIgnoreCase) + createIndexDef.Length; - ExecuteNonQuery("DROP INDEX " + indexSql.Substring(indexNameStart, (origTableStart - 4) - indexNameStart)); - - // Create index on new table - ExecuteNonQuery(indexSql.Substring(0, origTableStart) + newTable + " " + indexSql.Substring(origTableEnd)); - } - } - - public override void RemoveColumn(string table, string column) - { - if (! (TableExists(table) && ColumnExists(table, column))) - return; - - - var newColumns = GetColumns(table).Where(x => x.Name != column).ToArray(); - - AddTable(table + "_temp", null, newColumns); - var colNamesSql = string.Join(", ", newColumns.Select(x => x.Name)); - ExecuteQuery(String.Format("INSERT INTO {0}_temp SELECT {1} FROM {0}", table, colNamesSql)); - RemoveTable(table); - ExecuteQuery(String.Format("ALTER TABLE {0}_temp RENAME TO {0}", table)); - } - - public override void RenameColumn(string tableName, string oldColumnName, string newColumnName) - { - if (ColumnExists(tableName, newColumnName)) - throw new MigrationException(String.Format("Table '{0}' has column named '{1}' already", tableName, newColumnName)); - - if (ColumnExists(tableName, oldColumnName)) - { - var columnDef = GetColumns(tableName).First(x => x.Name == oldColumnName); - - //if (columnDef.IsPrimaryKey) - { - columnDef.Name = newColumnName; - this.changeColumnInternal(tableName, new[] { oldColumnName }, new[] { columnDef }); - } - /*else - { - columnDef.Name = newColumnName; - AddColumn(tableName, columnDef); - ExecuteQuery(String.Format("UPDATE {0} SET {1}={2}", tableName, newColumnName, oldColumnName)); - RemoveColumn(tableName, oldColumnName); - }*/ - } - } - - public override void RemoveColumnDefaultValue(string table, string column) - { - var columnDef = GetColumns(table).First(x => x.Name == column); - columnDef.DefaultValue = null; - changeColumnInternal(table, new[] { column }, new[] { columnDef }); - } - - public override void AddPrimaryKey(string name, string table, params string[] columns) - { - List newCol = new List(); - foreach (var column in columns) - { - var columnDef = GetColumns(table).First(x => x.Name == column); - columnDef.ColumnProperty |= ColumnProperty.PrimaryKey; - newCol.Add(columnDef); - } - this.changeColumnInternal(table, columns, newCol.ToArray()); - } - - public override void AddUniqueConstraint(string name, string table, params string[] columns) - { - var constr = new Unique() {KeyColumns = columns, Name = name}; - - this.changeColumnInternal(table, new string[] {}, new[] {constr}); - } - - private void changeColumnInternal(string table, string[] old, IDbField[] columns) - { - var newColumns = GetColumns(table).Where(x => !old.Any(y => x.Name.ToLower() == y.ToLower())).ToList(); - var oldColumnNames = newColumns.Select(x => x.Name).ToList(); - newColumns.AddRange(columns.Where(x => x is Column).Cast()); - oldColumnNames.AddRange(old); - - var newFieldsPlusUnique = newColumns.Cast().ToList(); - newFieldsPlusUnique.AddRange(columns.Where(x => x is Unique)); - - AddTable(table + "_temp", null, newFieldsPlusUnique.ToArray()); - var colNamesNewSql = string.Join(", ", newColumns.Select(x => x.Name)); - var colNamesSql = string.Join(", ", oldColumnNames); - ExecuteQuery(String.Format("INSERT INTO {1}_temp ({0}) SELECT {2} FROM {1}", colNamesNewSql, table, colNamesSql)); - RemoveTable(table); - ExecuteQuery(String.Format("ALTER TABLE {0}_temp RENAME TO {0}", table)); - } - - - public override void ChangeColumn(string table, Column column) - { - if (! ColumnExists(table, column.Name)) - { - Logger.Warn("Column {0}.{1} does not exist", table, column.Name); - return; - } - - if ( - (column.ColumnProperty & ColumnProperty.PrimaryKey) != ColumnProperty.PrimaryKey && - (column.ColumnProperty & ColumnProperty.Unique) != ColumnProperty.Unique && - ((column.ColumnProperty & ColumnProperty.NotNull) != ColumnProperty.NotNull || column.DefaultValue != null) && - (column.DefaultValue == null || (column.DefaultValue.ToString() != "'CURRENT_TIME'" && column.DefaultValue.ToString() != "'CURRENT_DATE'") && column.DefaultValue.ToString() != "'CURRENT_TIMESTAMP'") - ) - { - string tempColumn = "temp_" + column.Name; - RenameColumn(table, column.Name, tempColumn); - AddColumn(table, column); - ExecuteQuery(String.Format("UPDATE {0} SET {1}={2}", table, column.Name, tempColumn)); - RemoveColumn(table, tempColumn); - } - else - { - var newColumns = GetColumns(table).ToArray(); - - for (int i = 0; i < newColumns.Count(); i++) - { - if (newColumns[i].Name == column.Name) - { - newColumns[i] = column; - break; - } - } - - AddTable(table + "_temp", null, newColumns); - - var colNamesSql = string.Join(", ", newColumns.Select(x => x.Name)); - ExecuteQuery(String.Format("INSERT INTO {0}_temp SELECT {1} FROM {0}", table, colNamesSql)); - RemoveTable(table); - ExecuteQuery(String.Format("ALTER TABLE {0}_temp RENAME TO {0}", table)); - } - } - - public override int TruncateTable(string table) - { - return ExecuteNonQuery(String.Format("DELETE FROM {0} ", table)); - } - - public override bool TableExists(string table) - { - using (IDataReader reader = - ExecuteQuery(String.Format("SELECT name FROM sqlite_master WHERE type='table' and lower(name)=lower('{0}')", table))) - { - return reader.Read(); - } - } - - public override List GetDatabases() - { - throw new NotImplementedException(); - } - - public override bool ConstraintExists(string table, string name) - { - return false; - } - - public override string[] GetConstraints(string table) - { - return new string[] { }; - } - - public override string[] GetTables() - { - var tables = new List(); - - using (IDataReader reader = ExecuteQuery("SELECT name FROM sqlite_master WHERE type='table' AND name <> 'sqlite_sequence' ORDER BY name")) - { - while (reader.Read()) - { - tables.Add((string) reader[0]); - } - } - - return tables.ToArray(); - } - - public override Column[] GetColumns(string table) - { - var columns = new List(); - using (IDataReader reader = ExecuteQuery(String.Format("PRAGMA table_info('{0}')", table))) - { - while (reader.Read()) - { - var column = new Column((string)reader[1]); - - column.Type = _dialect.GetDbTypeFromString((string)reader[2]); - - if (Convert.ToBoolean(reader[3])) - { - column.ColumnProperty |= ColumnProperty.NotNull; - } - else - { - column.ColumnProperty |= ColumnProperty.Null; - } - - column.DefaultValue = reader[4] == DBNull.Value ? null : reader[4]; - - if (Convert.ToBoolean(reader[5])) - { - column.ColumnProperty |= ColumnProperty.PrimaryKey; - } - - columns.Add(column); - - } - } - - - - return columns.ToArray(); - } - - public bool IsNullable(string columnDef) - { - return ! columnDef.Contains("NOT NULL"); - } - - public bool ColumnMatch(string column, string columnDef) - { - return columnDef.StartsWith(column + " ") || columnDef.StartsWith(_dialect.Quote(column)); - } - - public override bool IndexExists(string table, string name) - { - using (IDataReader reader = - ExecuteQuery(String.Format("SELECT name FROM sqlite_master WHERE type='index' and name='{0}'", name))) - { - return reader.Read(); - } - } - - public override void AddTable(string name, string engine, params IDbField[] fields) - { - if (TableExists(name)) - { - Logger.Warn("Table {0} already exists", name); - return; - } - - var columns = fields.Where(x => x is Column).Cast().ToArray(); - - List pks = GetPrimaryKeys(columns); - bool compoundPrimaryKey = pks.Count > 1; - - var columnProviders = new List(columns.Length); - foreach (Column column in columns) - { - // Remove the primary key notation if compound primary key because we'll add it back later - if (compoundPrimaryKey && column.IsPrimaryKey) - { - column.ColumnProperty = column.ColumnProperty ^ ColumnProperty.PrimaryKey; - column.ColumnProperty = column.ColumnProperty | ColumnProperty.NotNull; // PK is always not-null - } - - ColumnPropertiesMapper mapper = _dialect.GetAndMapColumnProperties(column); - columnProviders.Add(mapper); - } - - string columnsAndIndexes = JoinColumnsAndIndexes(columnProviders); - - var table = _dialect.TableNameNeedsQuote ? _dialect.Quote(name) : name; - string sqlCreate; - - sqlCreate = String.Format("CREATE TABLE {0} ({1}", table, columnsAndIndexes); - - if (compoundPrimaryKey) - { - sqlCreate += String.Format(", PRIMARY KEY ({0}) ", String.Join(",", pks.ToArray())); - } - - var uniques = fields.Where(x => x is Unique).Cast().ToArray(); - foreach (var u in uniques) - { - var nm = ""; - if (!string.IsNullOrEmpty(u.Name)) - nm = string.Format(" CONSTRAINT {0}", u.Name); - sqlCreate += String.Format(",{0} UNIQUE ({1})", nm, String.Join(",", u.KeyColumns)); - } - - var foreignKeys = fields.Where(x => x is ForeignKeyConstraint).Cast().ToArray(); - foreach (var fk in foreignKeys) - { - var nm = ""; - if (!string.IsNullOrEmpty(fk.Name)) - nm = string.Format(" CONSTRAINT {0}", fk.Name); - sqlCreate += String.Format(",{0} FOREIGN KEY ({1}) REFERENCES {2}({3})", nm, String.Join(",", fk.Columns), fk.PkTable, String.Join(",", fk.PkColumns)); - } - - - - //table = QuoteTableNameIfRequired(table); - //ExecuteNonQuery(String.Format("ALTER TABLE {0} ADD CONSTRAINT {1} UNIQUE({2}) ", table, name, string.Join(", ", columns))); - - - - sqlCreate += ")"; - - ExecuteNonQuery(sqlCreate); - - var indexes = fields.Where(x => x is Index).Cast().ToArray(); - foreach (var index in indexes) - { - AddIndex(name, index); - } - } - - protected override string GetPrimaryKeyConstraintName(string table) - { - throw new NotImplementedException(); - } - - public override void RemovePrimaryKey(string table) - { - if (!TableExists(table)) return; - - var columnDefs = GetColumns(table); - - foreach (var columnDef in columnDefs.Where(columnDef => columnDef.IsPrimaryKey)) - { - columnDef.ColumnProperty = columnDef.ColumnProperty.Clear(ColumnProperty.PrimaryKey); - columnDef.ColumnProperty = columnDef.ColumnProperty.Clear(ColumnProperty.PrimaryKeyWithIdentity); - } - - changeColumnInternal(table, columnDefs.Select(x => x.Name).ToArray(), columnDefs); - } - - public override void RemoveAllIndexes(string table) - { - if (!TableExists(table)) return; - - var columnDefs = GetColumns(table); - - foreach (var columnDef in columnDefs.Where(columnDef => columnDef.IsPrimaryKey)) - { - columnDef.ColumnProperty = columnDef.ColumnProperty.Clear(ColumnProperty.PrimaryKey); - columnDef.ColumnProperty = columnDef.ColumnProperty.Clear(ColumnProperty.PrimaryKeyWithIdentity); - columnDef.ColumnProperty = columnDef.ColumnProperty.Clear(ColumnProperty.Unique); - columnDef.ColumnProperty = columnDef.ColumnProperty.Clear(ColumnProperty.Indexed); - } - - changeColumnInternal(table, columnDefs.Select(x => x.Name).ToArray(), columnDefs); - } - - protected override void ConfigureParameterWithValue(IDbDataParameter parameter, int index, object value) - { - if (value is UInt16) - { - parameter.DbType = DbType.Int32; - parameter.Value = Convert.ToInt32(value); - } - else if (value is UInt32) - { - parameter.DbType = DbType.Int64; - parameter.Value = Convert.ToInt64(value); - } - else if (value is Guid || value is Guid?) - { - parameter.DbType = DbType.Binary; - parameter.Value = ((Guid)value).ToByteArray(); - } - else - { - base.ConfigureParameterWithValue(parameter, index, value); - } - } - } -} diff --git a/src/Migrator.Providers/Impl/SqlServer/SqlServer2005Dialect.cs b/src/Migrator.Providers/Impl/SqlServer/SqlServer2005Dialect.cs deleted file mode 100644 index e583aa9f..00000000 --- a/src/Migrator.Providers/Impl/SqlServer/SqlServer2005Dialect.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Data; -using Migrator.Framework; - -namespace Migrator.Providers.SqlServer -{ - public class SqlServer2005Dialect : SqlServerDialect - { - public SqlServer2005Dialect() - { - RegisterColumnType(DbType.AnsiString, 2147483647, "VARCHAR(MAX)"); - RegisterColumnType(DbType.Binary, 2147483647, "VARBINARY(MAX)"); - RegisterColumnType(DbType.String, 1073741823, "NVARCHAR(MAX)"); - RegisterColumnType(DbType.Xml, "XML"); - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) - { - return new SqlServerTransformationProvider(dialect, connectionString, defaultSchema ?? DboSchemaName, scope, providerName); - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, - string defaultSchema, - string scope, string providerName) - { - return new SqlServerTransformationProvider(dialect, connection, defaultSchema ?? DboSchemaName, scope, providerName); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/SqlServer/SqlServerCeDialect.cs b/src/Migrator.Providers/Impl/SqlServer/SqlServerCeDialect.cs deleted file mode 100644 index c5113016..00000000 --- a/src/Migrator.Providers/Impl/SqlServer/SqlServerCeDialect.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Data; -using Migrator.Framework; - -namespace Migrator.Providers.SqlServer -{ - public class SqlServerCeDialect : SqlServerDialect - { - public SqlServerCeDialect() - { - RegisterColumnType(DbType.AnsiStringFixedLength, "NCHAR(255)"); - RegisterColumnType(DbType.AnsiStringFixedLength, 4000, "NCHAR($l)"); - RegisterColumnType(DbType.AnsiString, "NVARCHAR(255)"); - RegisterColumnType(DbType.AnsiString, 4000, "NVARCHAR($l)"); - RegisterColumnType(DbType.AnsiString, 1073741823, "TEXT"); - RegisterColumnType(DbType.Double, "FLOAT"); - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) - { - return new SqlServerCeTransformationProvider(dialect, connectionString, scope, providerName); - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, - string defaultSchema, - string scope, string providerName) - { - return new SqlServerCeTransformationProvider(dialect, connection, scope, providerName); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/SqlServer/SqlServerCeTransformationProvider.cs b/src/Migrator.Providers/Impl/SqlServer/SqlServerCeTransformationProvider.cs deleted file mode 100644 index b44866ea..00000000 --- a/src/Migrator.Providers/Impl/SqlServer/SqlServerCeTransformationProvider.cs +++ /dev/null @@ -1,116 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.Data; -using System.Data.Common; -//using System.Data.SqlServerCe; -using Migrator.Framework; - -namespace Migrator.Providers.SqlServer -{ - /// - /// Migration transformations provider for Microsoft SQL Server Compact Edition. - /// - public class SqlServerCeTransformationProvider : SqlServerTransformationProvider - { - public SqlServerCeTransformationProvider(Dialect dialect, string connectionString, string scope, string providerName) - : base(dialect, connectionString, null, scope, providerName) - { - } - - public SqlServerCeTransformationProvider(Dialect dialect, IDbConnection connection, string scope, string providerName) - : base(dialect, connection, null, scope, providerName) - { - } - - protected override void CreateConnection(string providerName) - { - if (string.IsNullOrEmpty(providerName)) providerName = "System.Data.SqlServerCe.3.5"; - var fac = DbProviderFactories.GetFactory(providerName); - _connection = fac.CreateConnection(); // new SqlConnection(); - _connection.ConnectionString = _connectionString; - _connection.Open(); - } - - public override bool ConstraintExists(string table, string name) - { - using (IDataReader reader = - ExecuteQuery(string.Format("SELECT cont.constraint_name FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS cont WHERE cont.Constraint_Name='{0}'", name))) - { - return reader.Read(); - } - } - - protected string GetSchemaName(string longTableName) - { - throw new MigrationException("SQL CE does not support database schemas."); - } - - public override bool TableExists(string table) - { - using (IDataReader reader = base.ExecuteQuery(string.Format("SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='{0}'", table))) - { - return reader.Read(); - } - } - - public override bool ColumnExists(string table, string column) - { - if (!TableExists(table)) - { - return false; - } - int firstIndex = table.IndexOf("."); - if (firstIndex >= 0) - { - table = table.Substring(firstIndex + 1); - } - - using ( - IDataReader reader = base.ExecuteQuery(string.Format("SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='{0}' AND COLUMN_NAME='{1}'", table, column))) - { - return reader.Read(); - } - } - - public override void RenameColumn(string tableName, string oldColumnName, string newColumnName) - { - if (ColumnExists(tableName, newColumnName)) - throw new MigrationException(String.Format("Table '{0}' has column named '{1}' already", tableName, newColumnName)); - - if (ColumnExists(tableName, oldColumnName)) - { - Column column = GetColumnByName(tableName, oldColumnName); - - AddColumn(tableName, new Column(newColumnName, column.Type, column.ColumnProperty, column.DefaultValue)); - ExecuteNonQuery(string.Format("UPDATE {0} SET {1}={2}", tableName, newColumnName, oldColumnName)); - RemoveColumn(tableName, oldColumnName); - } - } - - // Not supported by SQLCe when we have a better schemadumper which gives the exact sql construction including constraints we may use it to insert into a new table and then drop the old table...but this solution is dangerous for big tables. - public override void RenameTable(string oldName, string newName) - { - throw new NotSupportedException("Table Rename is not supported in SQL CE"); - } - - protected override string FindConstraints(string table, string column) - { - return - string.Format("SELECT cont.constraint_name FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE cont " - + "WHERE cont.Table_Name='{0}' AND cont.column_name = '{1}'", - table, column); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/SqlServer/SqlServerDialect.cs b/src/Migrator.Providers/Impl/SqlServer/SqlServerDialect.cs deleted file mode 100644 index fb827f34..00000000 --- a/src/Migrator.Providers/Impl/SqlServer/SqlServerDialect.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System; -using System.Data; -using Migrator.Framework; - -namespace Migrator.Providers.SqlServer -{ - public class SqlServerDialect : Dialect - { - public const string DboSchemaName = "dbo"; - - public SqlServerDialect() - { - RegisterColumnType(DbType.AnsiStringFixedLength, "CHAR(255)"); - RegisterColumnType(DbType.AnsiStringFixedLength, int.MaxValue - 1, "CHAR($l)"); - RegisterColumnType(DbType.AnsiStringFixedLength, int.MaxValue, "CHAR(max)"); - RegisterColumnType(DbType.AnsiString, "VARCHAR(255)"); - RegisterColumnType(DbType.AnsiString, 8000, "VARCHAR($l)"); - RegisterColumnType(DbType.AnsiString, 2147483647, "TEXT"); - RegisterColumnType(DbType.Binary, "VARBINARY(8000)"); - RegisterColumnType(DbType.Binary, int.MaxValue-1, "VARBINARY($l)"); - RegisterColumnType(DbType.Binary, int.MaxValue, "VARBINARY(max)"); - RegisterColumnType(DbType.Boolean, "BIT"); - RegisterColumnType(DbType.Byte, "TINYINT"); - RegisterColumnType(DbType.Currency, "MONEY"); - RegisterColumnType(DbType.Date, "DATETIME"); - RegisterColumnType(DbType.DateTime, "DATETIME"); - RegisterColumnType(DbType.DateTimeOffset, "DATETIMEOffset(7)"); - RegisterColumnType(DbType.Decimal, "DECIMAL(19,5)"); - RegisterColumnType(DbType.Decimal, 19, "DECIMAL(19, $l)"); - RegisterColumnType(DbType.Double, "DOUBLE PRECISION"); //synonym for FLOAT(53) - RegisterColumnType(DbType.Double, 24, "FLOAT(24)"); - RegisterColumnType(DbType.Double, 53, "FLOAT(53)"); - RegisterColumnType(DbType.Guid, "UNIQUEIDENTIFIER"); - RegisterColumnType(DbType.Int16, "SMALLINT"); - RegisterColumnType(DbType.Int32, "INT"); - RegisterColumnType(DbType.Int64, "BIGINT"); - RegisterColumnType(DbType.UInt16, "INT"); - RegisterColumnType(DbType.UInt32, "BIGINT"); - RegisterColumnType(DbType.UInt64, "DECIMAL(20,0)"); - RegisterColumnType(DbType.Single, "REAL"); //synonym for FLOAT(24) - RegisterColumnType(DbType.StringFixedLength, "NCHAR(255)"); - RegisterColumnType(DbType.StringFixedLength, int.MaxValue - 1, "NCHAR($l)"); - RegisterColumnType(DbType.StringFixedLength, int.MaxValue, "NCHAR(max)"); - RegisterColumnType(DbType.String, "NVARCHAR(255)"); - RegisterColumnType(DbType.String, int.MaxValue - 1, "NVARCHAR($l)"); - RegisterColumnType(DbType.String, int.MaxValue, "NVARCHAR(max)"); - //RegisterColumnType(DbType.String, 1073741823, "NTEXT"); - RegisterColumnType(DbType.Time, "DATETIME"); - RegisterColumnType(DbType.VarNumeric, "NUMERIC(18,0)"); - RegisterColumnType(DbType.VarNumeric, 38, "NUMERIC($l,0)"); - - RegisterProperty(ColumnProperty.Identity, "IDENTITY"); - - AddReservedWords("ADD", "EXCEPT", "PERCENT", "ALL", "EXEC", "PLAN", "ALTER", "EXECUTE", "PRECISION", "AND", "EXISTS", "PRIMARY", "ANY", "EXIT", "PRINT", "AS", "FETCH", "PROC", "ASC", "FILE", "PROCEDURE", "AUTHORIZATION", "FILLFACTOR", "PUBLIC", "BACKUP", "FOR", "RAISERROR", "BEGIN", "FOREIGN", "READ", "BETWEEN", "FREETEXT", "READTEXT", "BREAK", "FREETEXTTABLE", "RECONFIGURE", "BROWSE", "FROM", "REFERENCES", "BULK", "FULL", "REPLICATION", "BY", "FUNCTION", "RESTORE", "CASCADE", "GOTO", "RESTRICT", "CASE", "GRANT", "RETURN", "CHECK", "GROUP", "REVOKE", "CHECKPOINT", "HAVING", "RIGHT", "CLOSE", "HOLDLOCK", "ROLLBACK", "CLUSTERED", "IDENTITY", "ROWCOUNT", "COALESCE", "IDENTITY_INSERT", "ROWGUIDCOL", "COLLATE", "IDENTITYCOL", "RULE", "COLUMN", "IF", "SAVE", "COMMIT", "IN", "SCHEMA", "COMPUTE", "INDEX", "SELECT", "CONSTRAINT", "INNER", "SESSION_USER", "CONTAINS", "INSERT", "SET", "CONTAINSTABLE", "INTERSECT", "SETUSER", "CONTINUE", "INTO", "SHUTDOWN", "CONVERT", "IS", "SOME", "CREATE", "JOIN", "STATISTICS", "CROSS", "KEY", "SYSTEM_USER", "CURRENT", "KILL", "TABLE", "CURRENT_DATE", "LEFT", "TEXTSIZE", "CURRENT_TIME", "LIKE", "THEN", "CURRENT_TIMESTAMP", "LINENO", "TO", "CURRENT_USER", "LOAD", "TOP", "CURSOR", "NATIONAL", "TRAN", "DATABASE", "NOCHECK", "TRANSACTION", "DBCC", "NONCLUSTERED", "TRIGGER", "DEALLOCATE", "NOT", "TRUNCATE", "DECLARE", "NULL", "TSEQUAL", "DEFAULT", "NULLIF", "UNION", "DELETE", "OF", "UNIQUE", "DENY", "OFF", "UPDATE", "DESC", "OFFSETS", "UPDATETEXT", "DISK", "ON", "USE", "DISTINCT", "OPEN", "USER", "DISTRIBUTED", "OPENDATASOURCE", "VALUES", "DOUBLE", "OPENQUERY", "VARYING", "DROP", "OPENROWSET", "VIEW", "DUMMY", "OPENXML", "WAITFOR", "DUMP", "OPTION", "WHEN", "ELSE", "OR", "WHERE", "END", "ORDER", "WHILE", "ERRLVL", "OUTER", "WITH", "ESCAPE", "OVER", "WRITETEXT"); - } - - public override bool SupportsIndex - { - get { return false; } - } - - public override bool ColumnNameNeedsQuote - { - get { return true; } - } - - public override bool TableNameNeedsQuote - { - get { return true; } - } - - public override string QuoteTemplate - { - get { return "[{0}]"; } - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) - { - return new SqlServerTransformationProvider(dialect, connectionString, defaultSchema ?? DboSchemaName, scope, providerName); - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, - string defaultSchema, - string scope, string providerName) - { - return new SqlServerTransformationProvider(dialect, connection, defaultSchema ?? DboSchemaName, scope, providerName); - } - - public override string Quote(string value) - { - int firstDotIndex = value.IndexOf('.'); - if (firstDotIndex >= 0) - { - string owner = value.Substring(0, firstDotIndex); - string table = value.Substring(firstDotIndex + 1); - return (string.Format(QuoteTemplate, owner) + "." + string.Format(QuoteTemplate, table)); - } - return string.Format(QuoteTemplate, value); - } - - public override string Default(object defaultValue) - { - if (defaultValue.GetType().Equals(typeof (bool))) - { - return String.Format("DEFAULT {0}", (bool)defaultValue ? "1" : "0"); - } - else if (defaultValue.GetType().Equals(typeof(Guid))) - { - return "DEFAULT '" + ((Guid) defaultValue).ToString("D") + "'"; - } - else if (defaultValue.GetType().Equals(typeof(DateTime))) - { - return "DEFAULT CONVERT(DateTime,'" - + ((DateTime)defaultValue).Year.ToString("D4") + '-' - + ((DateTime)defaultValue).Month.ToString("D2") + '-' - + ((DateTime)defaultValue).Day.ToString("D2") + ' ' - + ((DateTime)defaultValue).Hour.ToString("D2") + ':' - + ((DateTime)defaultValue).Minute.ToString("D2") + ':' - + ((DateTime)defaultValue).Second.ToString("D2") + '.' - + ((DateTime)defaultValue).Millisecond.ToString("D3") - + "',121)"; - } - - return base.Default(defaultValue); - } - } -} diff --git a/src/Migrator.Providers/Impl/SqlServer/SqlServerTransformationProvider.cs b/src/Migrator.Providers/Impl/SqlServer/SqlServerTransformationProvider.cs deleted file mode 100644 index a7e11bdd..00000000 --- a/src/Migrator.Providers/Impl/SqlServer/SqlServerTransformationProvider.cs +++ /dev/null @@ -1,480 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Data.SqlClient; -using Migrator.Framework; - -namespace Migrator.Providers.SqlServer -{ - /// - /// Migration transformations provider for Microsoft SQL Server. - /// - public class SqlServerTransformationProvider : TransformationProvider - { - public SqlServerTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) - : base(dialect, connectionString, defaultSchema, scope) - { - CreateConnection(providerName); - } - - public SqlServerTransformationProvider(Dialect dialect, IDbConnection connection, string defaultSchema, string scope, string providerName) - : base(dialect, connection, defaultSchema, scope) - { - } - - - protected virtual void CreateConnection(string providerName) - { - if (string.IsNullOrEmpty(providerName)) - providerName = "System.Data.SqlClient"; - var fac = DbProviderFactories.GetFactory(providerName); - _connection = fac.CreateConnection(); // new SqlConnection(); - _connection.ConnectionString = _connectionString; - _connection.Open(); - - string collationString = null; - var collation = this.ExecuteScalar("SELECT DATABASEPROPERTYEX('" + _connection.Database + "', 'Collation')"); - if (collation != null) - collationString = collation.ToString(); - if (string.IsNullOrWhiteSpace(collationString)) - collationString = "Latin1_General_CI_AS"; - this.Dialect.RegisterProperty(ColumnProperty.CaseSensitive, "COLLATE " + collationString.Replace("_CI_", "_CS_")); - } - - public override bool ConstraintExists(string table, string name) - { - bool retVal = false; - using (IDataReader reader = ExecuteQuery(string.Format("SELECT TOP 1 * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_NAME ='{0}'", name))) - { - retVal = reader.Read(); - } - - if (!retVal) - using (IDataReader reader = ExecuteQuery(string.Format("SELECT TOP 1 * FROM SYS.DEFAULT_CONSTRAINTS WHERE PARENT_OBJECT_ID = OBJECT_ID('{0}') AND Name = '{1}'", table, name))) - { - return reader.Read(); - } - return true; - } - - public override void AddColumn(string table, string sqlColumn) - { - table = _dialect.TableNameNeedsQuote ? _dialect.Quote(table) : table; - ExecuteNonQuery(string.Format("ALTER TABLE {0} ADD {1}", table, sqlColumn)); - } - - public override void AddIndex(string table, Index index) - { - if (IndexExists(table, index.Name)) - { - Logger.Warn("Index {0} already exists", index.Name); - return; - } - - var name = QuoteConstraintNameIfRequired(index.Name); - - table = QuoteTableNameIfRequired(table); - - var columns = QuoteColumnNamesIfRequired(index.KeyColumns); - - if (index.IncludeColumns != null && index.IncludeColumns.Length > 0) - { - var include = QuoteColumnNamesIfRequired(index.IncludeColumns); - ExecuteNonQuery(String.Format("CREATE {0}{1} INDEX {2} ON {3} ({4}) INCLUDE ({5})", (index.Unique ? "UNIQUE " : ""), (index.Clustered ? "CLUSTERED" : "NONCLUSTERED"), name, table, string.Join(", ", columns), string.Join(", ", include))); - } - else - { - ExecuteNonQuery(String.Format("CREATE {0}{1} INDEX {2} ON {3} ({4})", (index.Unique ? "UNIQUE " : ""), (index.Clustered ? "CLUSTERED" : "NONCLUSTERED"), name, table, string.Join(", ", columns))); - } - } - - public override void ChangeColumn(string table, Column column) - { - if (column.DefaultValue == null || column.DefaultValue == DBNull.Value) - { - base.ChangeColumn(table, column); - } - else - { - var def = column.DefaultValue; - var notNull = column.ColumnProperty.IsSet(ColumnProperty.NotNull); - column.DefaultValue = null; - column.ColumnProperty = column.ColumnProperty.Set(ColumnProperty.Null); - column.ColumnProperty = column.ColumnProperty.Clear(ColumnProperty.NotNull); - - base.ChangeColumn(table,column); - - ColumnPropertiesMapper mapper = _dialect.GetAndMapColumnPropertiesWithoutDefault(column); - ExecuteNonQuery(string.Format("ALTER TABLE {0} ADD CONSTRAINT {1} {2} FOR {3}", this.QuoteTableNameIfRequired(table), "DF_" + table + "_" + column.Name, _dialect.Default(def), this.QuoteColumnNameIfRequired(column.Name))); - - if (notNull) - { - column.ColumnProperty = column.ColumnProperty.Set(ColumnProperty.NotNull); - column.ColumnProperty = column.ColumnProperty.Clear(ColumnProperty.Null); - base.ChangeColumn(table, column); - } - } - } - - public override bool ColumnExists(string table, string column) - { - string schema; - if (!TableExists(table)) - { - return false; - } - int firstIndex = table.IndexOf("."); - if (firstIndex >= 0) - { - schema = table.Substring(0, firstIndex); - table = table.Substring(firstIndex + 1); - } - else - { - schema = _defaultSchema; - } - using ( - IDataReader reader = base.ExecuteQuery(string.Format("SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '{0}' AND TABLE_NAME='{1}' AND COLUMN_NAME='{2}'", schema, table, column))) - { - return reader.Read(); - } - } - - public override void RemoveColumnDefaultValue(string table, string column) - { - var sql = string.Format("SELECT Name FROM SYS.DEFAULT_CONSTRAINTS WHERE PARENT_OBJECT_ID = OBJECT_ID('{0}') AND PARENT_COLUMN_ID = (SELECT column_id FROM sys.columns WHERE NAME = '{1}' AND object_id = OBJECT_ID('{0}'))", table, column); - var constraintName = ExecuteScalar(sql); - if (constraintName != null) - RemoveConstraint(table, constraintName.ToString()); - } - - - public override bool TableExists(string table) - { - string schema; - - int firstIndex = table.IndexOf("."); - if (firstIndex >= 0) - { - schema = table.Substring(0, firstIndex); - table = table.Substring(firstIndex + 1); - } - else - { - schema = _defaultSchema; - } - - using (IDataReader reader = base.ExecuteQuery(string.Format("SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='{0}' AND TABLE_SCHEMA='{1}'", table, schema))) - { - return reader.Read(); - } - } - - public override Index[] GetIndexes(string table) - { - var retVal = new List(); - - var sql = @"SELECT Tab.[name] AS TableName, - Ind.[name] AS IndexName, - Ind.[type_desc] AS IndexType, - Ind.[is_unique] AS IndexUnique, - SUBSTRING(( SELECT ',' + AC.name - FROM sys.[tables] AS T - INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id] - INNER JOIN sys.[index_columns] IC ON I.[object_id] = IC.[object_id] - AND I.[index_id] = IC.[index_id] - INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id] - AND IC.[column_id] = AC.[column_id] - WHERE Ind.[object_id] = I.[object_id] - AND Ind.index_id = I.index_id - AND IC.is_included_column = 0 - ORDER BY IC.key_ordinal - FOR - XML PATH('') ), 2, 8000) AS KeyCols, - SUBSTRING(( SELECT ',' + AC.name - FROM sys.[tables] AS T - INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id] - INNER JOIN sys.[index_columns] IC ON I.[object_id] = IC.[object_id] - AND I.[index_id] = IC.[index_id] - INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id] - AND IC.[column_id] = AC.[column_id] - WHERE Ind.[object_id] = I.[object_id] - AND Ind.index_id = I.index_id - AND IC.is_included_column = 1 - ORDER BY IC.key_ordinal - FOR - XML PATH('') ), 2, 8000) AS IncludeCols -FROM sys.[indexes] Ind - INNER JOIN sys.[tables] AS Tab ON Tab.[object_id] = Ind.[object_id] - WHERE LOWER(Tab.[name]) = LOWER('{0}')"; - - using (var reader=ExecuteQuery(string.Format(sql, table))) - { - while (reader.Read()) - { - if (!reader.IsDBNull(1)) - { - var idx = new Index - { - Name = reader.GetString(1), - Clustered = reader.GetString(2) == "CLUSTERED", - PrimaryKey = reader.GetString(2) == "CLUSTERED", - Unique = reader.GetBoolean(3) - }; - if (!reader.IsDBNull(4)) idx.KeyColumns = (reader.GetString(4).Split(',')); - if (!reader.IsDBNull(5)) idx.IncludeColumns = (reader.GetString(5).Split(',')); - retVal.Add(idx); - } - } - } - - return retVal.ToArray(); - } - - public override Column[] GetColumns(string table) - { - var pkColumns = new List(); - try - { - pkColumns = this.ExecuteStringQuery("SELECT cu.COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE cu WHERE EXISTS ( SELECT tc.* FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc WHERE tc.TABLE_NAME = '{0}' AND tc.CONSTRAINT_TYPE = 'PRIMARY KEY' AND tc.CONSTRAINT_NAME = cu.CONSTRAINT_NAME )", table); - } - catch (Exception ex) - { } - - var columns = new List(); - using ( - IDataReader reader = - ExecuteQuery( - String.Format("select COLUMN_NAME, IS_NULLABLE, DATA_TYPE, ISNULL(CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION), COLUMN_DEFAULT from INFORMATION_SCHEMA.COLUMNS where table_name = '{0}'", table))) - { - while (reader.Read()) - { - var column = new Column(reader.GetString(0), DbType.String); - - if (pkColumns.Contains(column.Name)) - column.ColumnProperty |= ColumnProperty.PrimaryKey; - - string nullableStr = reader.GetString(1); - bool isNullable = nullableStr == "YES"; - if (!reader.IsDBNull(2)) - { - string type = reader.GetString(2); - column.Type = Dialect.GetDbTypeFromString(type); - } - if (!reader.IsDBNull(3)) - { - column.Size = reader.GetInt32(3); - } - if (!reader.IsDBNull(4)) - { - column.DefaultValue = reader.GetValue(4); - - if (column.DefaultValue.ToString()[1] == '(' || column.DefaultValue.ToString()[1] == '\'') - column.DefaultValue = column.DefaultValue.ToString().Substring(2, column.DefaultValue.ToString().Length - 4); // Example "((10))" or "('false')" - else - column.DefaultValue = column.DefaultValue.ToString().Substring(1, column.DefaultValue.ToString().Length - 2); // Example "(CONVERT([datetime],'20000101',(112)))" - - if (column.Type == DbType.Int16 || column.Type == DbType.Int32 || column.Type == DbType.Int64) - column.DefaultValue = Int64.Parse(column.DefaultValue.ToString()); - - if (column.Type == DbType.UInt16 || column.Type == DbType.UInt32 || column.Type == DbType.UInt64) - column.DefaultValue = UInt64.Parse(column.DefaultValue.ToString()); - - if (column.Type == DbType.Double || column.Type == DbType.Single) - column.DefaultValue = double.Parse(column.DefaultValue.ToString()); - } - - column.ColumnProperty |= isNullable ? ColumnProperty.Null : ColumnProperty.NotNull; - - columns.Add(column); - } - } - - return columns.ToArray(); - } - - public override List GetDatabases() - { - return ExecuteStringQuery("SELECT name FROM sys.databases"); - } - - public override void DropDatabases(string databaseName) - { - ExecuteNonQuery(string.Format("USE [master]" + System.Environment.NewLine + "DROP DATABASE {0}", databaseName)); - } - - public override void RemoveColumn(string table, string column) - { - DeleteColumnConstraints(table, column); - DeleteColumnIndexes(table, column); - RemoveColumnDefaultValue(table, column); - base.RemoveColumn(table, column); - } - - public override void RenameColumn(string tableName, string oldColumnName, string newColumnName) - { - if (ColumnExists(tableName, newColumnName)) - throw new MigrationException(String.Format("Table '{0}' has column named '{1}' already", tableName, newColumnName)); - - if (ColumnExists(tableName, oldColumnName)) - ExecuteNonQuery(String.Format("EXEC sp_rename '{0}.{1}', '{2}', 'COLUMN'", tableName, oldColumnName, newColumnName)); - } - - public override void RenameTable(string oldName, string newName) - { - if (TableExists(newName)) - { - throw new MigrationException(String.Format("Table with name '{0}' already exists", newName)); - } - - if (!TableExists(oldName)) - { - throw new MigrationException(String.Format("Table with name '{0}' does not exist to rename", oldName)); - } - - ExecuteNonQuery(String.Format("EXEC sp_rename '{0}', '{1}'", oldName, newName)); - } - - // Deletes all constraints linked to a column. Sql Server - // doesn't seems to do this. - void DeleteColumnConstraints(string table, string column) - { - string sqlContrainte = FindConstraints(table, column); - var constraints = new List(); - using (IDataReader reader = ExecuteQuery(sqlContrainte)) - { - while (reader.Read()) - { - constraints.Add(reader.GetString(0)); - } - } - // Can't share the connection so two phase modif - foreach (string constraint in constraints) - { - RemoveForeignKey(table, constraint); - } - } - - void DeleteColumnIndexes(string table, string column) - { - string sqlIndex = this.FindIndexes(table, column); - var indexes = new List(); - using (IDataReader reader = ExecuteQuery(sqlIndex)) - { - while (reader.Read()) - { - indexes.Add(reader.GetString(0)); - } - } - // Can't share the connection so two phase modif - foreach (string index in indexes) - { - this.RemoveIndex(table, index); - } - } - - protected virtual string FindIndexes(string table, string column) - { - return string.Format(@" -select - i.name as IndexName -from sys.indexes i -join sys.objects o on i.object_id = o.object_id -join sys.index_columns ic on ic.object_id = i.object_id - and ic.index_id = i.index_id -join sys.columns co on co.object_id = i.object_id - and co.column_id = ic.column_id -where i.[type] = 2 -and o.[Name] = '{0}' -and co.[Name] = '{1}'", - table, column); - } - - // FIXME: We should look into implementing this with INFORMATION_SCHEMA if possible - // so that it would be usable by all the SQL Server implementations - protected virtual string FindConstraints(string table, string column) - { - return string.Format(@"SELECT DISTINCT CU.CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE CU -INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC -ON CU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME -WHERE TC.CONSTRAINT_TYPE = 'FOREIGN KEY' -AND CU.TABLE_NAME = '{0}' -AND CU.COLUMN_NAME = '{1}'", - table, column); - - /*return string.Format( - "SELECT cont.name FROM sysobjects cont, syscolumns col, sysconstraints cnt " - + "WHERE cont.parent_obj = col.id AND cnt.constid = cont.id AND cnt.colid=col.colid " - + "AND col.name = '{1}' AND col.id = object_id('{0}')", - table, column);*/ - } - - public override bool IndexExists(string table, string name) - { - using (IDataReader reader = - ExecuteQuery(string.Format("SELECT top 1 * FROM sys.indexes WHERE object_id = OBJECT_ID('{0}') AND name = '{1}'", table, name))) - { - return reader.Read(); - } - } - - public override void RemoveIndex(string table, string name) - { - if (TableExists(table) && IndexExists(table, name)) - { - ExecuteNonQuery(String.Format("DROP INDEX {0} ON {1}", QuoteConstraintNameIfRequired(name), QuoteTableNameIfRequired(table))); - } - } - - protected override string GetPrimaryKeyConstraintName(string table) - { - using (IDataReader reader = - ExecuteQuery(string.Format("SELECT name FROM sys.indexes WHERE object_id = OBJECT_ID('{0}') AND is_primary_key = 1", table))) - { - return reader.Read() ? reader.GetString(0) : null; - } - } - - protected override void ConfigureParameterWithValue(IDbDataParameter parameter, int index, object value) - { - if (value is UInt16) - { - parameter.DbType = DbType.Int32; - parameter.Value = value; - } - else if (value is UInt32) - { - parameter.DbType = DbType.Int64; - parameter.Value = value; - } - else if (value is UInt64) - { - parameter.DbType = DbType.Decimal; - parameter.Value = value; - } - else - { - base.ConfigureParameterWithValue(parameter, index, value); - } - } - - public override string Concatenate(params string[] strings) - { - return string.Join(" + ", strings); - } - } -} diff --git a/src/Migrator.Providers/Impl/Sybase/SybaseDialect.cs b/src/Migrator.Providers/Impl/Sybase/SybaseDialect.cs deleted file mode 100644 index c4575a65..00000000 --- a/src/Migrator.Providers/Impl/Sybase/SybaseDialect.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Data; -using Migrator.Framework; -using Migrator.Providers.Impl.DB2; -using Migrator.Providers.Impl.Ingres; - -namespace Migrator.Providers.Impl.Sybase -{ - public class SybaseDialect : Dialect - { - public SybaseDialect() - { - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) - { - return new SybaseTransformationProvider(dialect, connectionString, scope, providerName); - } - - public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, - string defaultSchema, - string scope, string providerName) - { - return new SybaseTransformationProvider(dialect, connection, scope, providerName); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Impl/Sybase/SybaseTransformationProvider.cs b/src/Migrator.Providers/Impl/Sybase/SybaseTransformationProvider.cs deleted file mode 100644 index 13fff94d..00000000 --- a/src/Migrator.Providers/Impl/Sybase/SybaseTransformationProvider.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; - -namespace Migrator.Providers.Impl.Sybase -{ - public class SybaseTransformationProvider : TransformationProvider - { - public SybaseTransformationProvider(Dialect dialect, string connectionString, string scope, string providerName) - : base(dialect, connectionString, null, scope) - { - if (string.IsNullOrEmpty(providerName)) providerName = "Sybase.Data.AseClient"; - var fac = DbProviderFactories.GetFactory(providerName); - _connection = fac.CreateConnection(); - _connection.ConnectionString = _connectionString; - this._connection.Open(); - } - - public SybaseTransformationProvider(Dialect dialect, IDbConnection connection, string scope, string providerName) - : base(dialect, connection, null, scope) - { - } - - public override List GetDatabases() - { - throw new NotImplementedException(); - } - - public override bool ConstraintExists(string table, string name) - { - throw new NotImplementedException(); - } - - public override bool IndexExists(string table, string name) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/MigratorDotNet.snk b/src/Migrator.Providers/MigratorDotNet.snk deleted file mode 100644 index 5032d709..00000000 Binary files a/src/Migrator.Providers/MigratorDotNet.snk and /dev/null differ diff --git a/src/Migrator.Providers/NoOpTransformationProvider.cs b/src/Migrator.Providers/NoOpTransformationProvider.cs deleted file mode 100644 index f54b6215..00000000 --- a/src/Migrator.Providers/NoOpTransformationProvider.cs +++ /dev/null @@ -1,508 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using Migrator.Framework; -using Migrator.Framework.SchemaBuilder; - -using ForeignKeyConstraint = Migrator.Framework.ForeignKeyConstraint; - -namespace Migrator.Providers -{ - /// - /// No Op (Null Object Pattern) implementation of the ITransformationProvider - /// - public class NoOpTransformationProvider : ITransformationProvider - { - public static readonly NoOpTransformationProvider Instance = new NoOpTransformationProvider(); - - NoOpTransformationProvider() - { - } - - public Dialect Dialect - { - get { return null; } - } - - public string ConnectionString - { - get { return String.Empty; } - } - - public virtual ILogger Logger - { - get { return null; } - set { } - } - - public string[] GetTables() - { - return null; - } - - public ForeignKeyConstraint[] GetForeignKeyConstraints(string table) - { - return null; - } - - public int Insert(string table, string[] columns, object[] values) - { - return 0; - } - - public List ExecuteStringQuery(string sql, params object[] args) - { - return new List(); - } - - public Index[] GetIndexes(string table) - { - return null; - } - - public Column[] GetColumns(string table) - { - return null; - } - - public Column GetColumnByName(string table, string column) - { - return null; - } - - public void RemoveForeignKey(string table, string name) - { - // No Op - } - - public void RemoveConstraint(string table, string name) - { - // No Op - } - - public void RemoveAllConstraints(string table) - { - // No Op - } - - public void RemovePrimaryKey(string table) - { - // No Op - } - - public void AddView(string name, string tableName, params IViewField[] fields) - { - // No Op - } - - public void AddTable(string name, params IDbField[] columns) - { - // No Op - } - - public void AddTable(string name, string engine, params IDbField[] columns) - { - // No Op - } - - public void RemoveTable(string name) - { - // No Op - } - - public void RenameTable(string oldName, string newName) - { - // No Op - } - - public void RenameColumn(string tableName, string oldColumnName, string newColumnName) - { - // No Op - } - - public void RemoveColumn(string table, string column) - { - // No Op - } - - public void RemoveColumnDefaultValue(string table, string column) - { - // No Op - } - - public bool ColumnExists(string table, string column) - { - return false; - } - - public bool TableExists(string table) - { - return false; - } - - public void AddColumn(string table, string column, DbType type, int size, ColumnProperty property, object defaultValue) - { - // No Op - } - - public void AddColumn(string table, string column, DbType type) - { - // No Op - } - - public void AddColumn(string table, string column, DbType type, object defaultValue) - { - // No Op - } - - public void AddColumn(string table, string column, DbType type, int size) - { - // No Op - } - - public void AddColumn(string table, string column, DbType type, ColumnProperty property) - { - // No Op - } - - public void AddColumn(string table, string column, DbType type, int size, ColumnProperty property) - { - // No Op - } - - public void AddPrimaryKey(string name, string table, params string[] columns) - { - // No Op - } - - public void GenerateForeignKey(string primaryTable, string primaryColumn, string refTable, string refColumn) - { - // No Op - } - - public void GenerateForeignKey(string primaryTable, string[] primaryColumns, string refTable, string[] refColumns) - { - // No Op - } - - public void GenerateForeignKey(string primaryTable, string primaryColumn, string refTable, string refColumn, ForeignKeyConstraintType constraint) - { - // No Op - } - - public void GenerateForeignKey(string primaryTable, string[] primaryColumns, string refTable, - string[] refColumns, ForeignKeyConstraintType constraint) - { - // No Op - } - - public void AddForeignKey(string name, string primaryTable, string primaryColumn, string refTable, - string refColumn) - { - // No Op - } - - public void AddForeignKey(string name, string primaryTable, string[] primaryColumns, string refTable, string[] refColumns) - { - // No Op - } - - public void AddForeignKey(string name, string primaryTable, string primaryColumn, string refTable, string refColumn, ForeignKeyConstraintType constraint) - { - // No Op - } - - public void AddForeignKey(string name, string primaryTable, string[] primaryColumns, string refTable, - string[] refColumns, ForeignKeyConstraintType constraint) - { - // No Op - } - - public void AddUniqueConstraint(string name, string table, params string[] columns) - { - // No Op - } - - public void AddCheckConstraint(string name, string table, string checkSql) - { - // No Op - } - - public bool ConstraintExists(string table, string name) - { - return false; - } - - public void ChangeColumn(string table, Column column) - { - // No Op - } - - public bool PrimaryKeyExists(string table, string name) - { - return false; - } - - public int ExecuteNonQuery(string sql) - { - return 0; - } - public int ExecuteNonQuery(string sql, int timeout) - { - return 0; - } - public int ExecuteNonQuery(string sql, int timeout, object[] parameters) - { - return 0; - } - - public IDataReader ExecuteQuery(string sql) - { - return null; - } - - public object ExecuteScalar(string sql) - { - return null; - } - - public IDataReader Select(string what, string from) - { - return null; - } - - public IDataReader Select(string what, string from, string where) - { - return null; - } - - public object SelectScalar(string what, string from) - { - return null; - } - - public object SelectScalar(string what, string from, string where) - { - return null; - } - - public int Update(string table, string[] columns, object[] values) - { - return 0; - } - - public int Update(string table, string[] columns, object[] values, string where) - { - return 0; - } - - public int Update(string table, string[] columns, object[] values, string[] whereColumns, object[] whereValues) - { - return 0; - } - - public int Delete(string table, string[] columns, string[] columnValues) - { - return 0; - } - - public int Delete(string table, string column, string value) - { - return 0; - } - - public int TruncateTable(string table) - { - return 0; - } - - public void BeginTransaction() - { - // No Op - } - - public void Rollback() - { - // No Op - } - - public void Commit() - { - // No Op - } - - public ITransformationProvider this[string provider] - { - get { return this; } - } - - public string SchemaInfoTable { get; set; } - - public void MigrationApplied(long version, string scope) - { - //no op - } - - public void MigrationUnApplied(long version, string scope) - { - //no op - } - - public List AppliedMigrations - { - get { return new List(); } - } - - public void AddColumn(string table, Column column) - { - // No Op - } - - public void GenerateForeignKey(string primaryTable, string refTable) - { - // No Op - } - - public void GenerateForeignKey(string primaryTable, string refTable, ForeignKeyConstraintType constraint) - { - // No Op - } - - public IDbCommand GetCommand() - { - return null; - } - - public void ExecuteSchemaBuilder(SchemaBuilder schemaBuilder) - { - // No Op - } - - public void RemoveAllForeignKeys(string tableName, string columnName) - { - - } - - public bool IsThisProvider(string provider) - { - return false; - } - - public string[] QuoteColumnNamesIfRequired(params string[] columnNames) - { - throw new NotImplementedException(); - } - - public string QuoteColumnNameIfRequired(string name) - { - throw new NotImplementedException(); - } - - public string QuoteTableNameIfRequired(string name) - { - throw new NotImplementedException(); - } - - public string Encode(Guid guid) - { - return guid.ToString(); - } - - public void SwitchDatabase(string databaseName) - { - - } - - public List GetDatabases() - { - return new List(); - } - - public bool DatabaseExists(string name) - { - return true; - } - - public void CreateDatabases(string databaseName) - { - - } - - public void DropDatabases(string databaseName) - { - - } - - public void AddIndex(string table, Index index) - { - - } - - public void Dispose() - { - //No Op - } - - public void AddColumn(string table, string sqlColumn) - { - // No Op - } - - public int Insert(string table, string[] columns, string[] columnValues) - { - return 0; - } - - protected void CreateSchemaInfoTable() - { - } - - public void RemoveIndex(string table, string name) - { - // No Op - } - - public void AddIndex(string name, string table, params string[] columns) - { - // No Op - } - - public bool IndexExists(string table, string name) - { - return false; - } - - public string GenerateParameterName(int index) - { - return "@p" + index; - } - - public void RemoveAllIndexes(string table) - { - // No Op - } - - public string Concatenate(params string[] strings) - { - return ""; - } - - public IDbConnection Connection - { - get - { - return null; - } - } - - public IEnumerable GetTables(string schema) - { - throw new NotImplementedException(); - } - - public IEnumerable GetColumns(string schema, string table) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/ProviderTypes.cs b/src/Migrator.Providers/ProviderTypes.cs deleted file mode 100644 index 80c3d459..00000000 --- a/src/Migrator.Providers/ProviderTypes.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Migrator.Providers -{ - public enum ProviderTypes - { - none, - SqlServer2005, - SqlServerCe, - SqlServer, - Mysql, - MariaDB, - SQLite, - MonoSQLite, - PostgreSQL82, - PostgreSQL, - Oracle, - MsOracle, - IBM_DB2, - IBM_Informix, - Firebird, - Ingres, - Sybase, - } -} diff --git a/src/Migrator.Providers/TransformationProvider.cs b/src/Migrator.Providers/TransformationProvider.cs deleted file mode 100644 index bc83be8a..00000000 --- a/src/Migrator.Providers/TransformationProvider.cs +++ /dev/null @@ -1,1630 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.IO; -using System.Linq; -using System.Text; -using Migrator.Framework; -using Migrator.Framework.Loggers; -using Migrator.Framework.SchemaBuilder; -using Migrator.Framework.Support; - -using ForeignKeyConstraintType = Migrator.Framework.ForeignKeyConstraintType; -using ForeignKeyConstraint = Migrator.Framework.ForeignKeyConstraint; - -namespace Migrator.Providers -{ - /// - /// Base class for every transformation providers. - /// A 'tranformation' is an operation that modifies the database. - /// - public abstract class TransformationProvider : ITransformationProvider - { - private string _scope; - protected readonly string _connectionString; - protected readonly string _defaultSchema; - readonly ForeignKeyConstraintMapper constraintMapper = new ForeignKeyConstraintMapper(); - protected List _appliedMigrations; - protected IDbConnection _connection; - protected bool _outsideConnection = false; - protected Dialect _dialect; - ILogger _logger; - IDbTransaction _transaction; - - protected TransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope) - { - _dialect = dialect; - _connectionString = connectionString; - _defaultSchema = defaultSchema; - _logger = new Logger(false); - _scope = scope; - } - - protected TransformationProvider(Dialect dialect, IDbConnection connection, string defaultSchema, string scope) - { - _dialect = dialect; - _connection = connection; - _outsideConnection = true; - _defaultSchema = defaultSchema; - _logger = new Logger(false); - _scope = scope; - } - - public IMigration CurrentMigration { get; set; } - - private string _schemaInfotable = "SchemaInfo"; - public string SchemaInfoTable - { - get - { - return _schemaInfotable; - } - set - { - _schemaInfotable = value; - } - } - - public Dialect Dialect - { - get { return _dialect; } - } - - public string ConnectionString { get { return _connectionString; }} - - /// - /// Returns the event logger - /// - public virtual ILogger Logger - { - get { return _logger; } - set { _logger = value; } - } - - public virtual ITransformationProvider this[string provider] - { - get - { - if (null != provider && IsThisProvider(provider)) - return this; - - return NoOpTransformationProvider.Instance; - } - } - - public virtual Index[] GetIndexes(string table) - { - throw new NotImplementedException(); - } - - public virtual Column[] GetColumns(string table) - { - var columns = new List(); - using ( - IDataReader reader = - ExecuteQuery( - String.Format("select COLUMN_NAME, IS_NULLABLE from INFORMATION_SCHEMA.COLUMNS where table_name = '{0}'", table))) - { - while (reader.Read()) - { - var column = new Column(reader.GetString(0), DbType.String); - string nullableStr = reader.GetString(1); - bool isNullable = nullableStr == "YES"; - column.ColumnProperty |= isNullable ? ColumnProperty.Null : ColumnProperty.NotNull; - - columns.Add(column); - } - } - - return columns.ToArray(); - } - - public ForeignKeyConstraint[] GetForeignKeyConstraints(string table) - { - var constraints = new List(); - using ( - IDataReader reader = - ExecuteQuery( - String.Format("SELECT K_Table = FK.TABLE_NAME, FK_Column = CU.COLUMN_NAME, PK_Table = PK.TABLE_NAME, PK_Column = PT.COLUMN_NAME, Constraint_Name = C.CONSTRAINT_NAME FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS C INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS FK ON C.CONSTRAINT_NAME = FK.CONSTRAINT_NAME INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS PK ON C.UNIQUE_CONSTRAINT_NAME = PK.CONSTRAINT_NAME INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE CU ON C.CONSTRAINT_NAME = CU.CONSTRAINT_NAME INNER JOIN ( SELECT i1.TABLE_NAME, i2.COLUMN_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS i1 INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE i2 ON i1.CONSTRAINT_NAME = i2.CONSTRAINT_NAME WHERE i1.CONSTRAINT_TYPE = 'PRIMARY KEY' ) PT ON PT.TABLE_NAME = PK.TABLE_NAME WHERE FK.table_name = '{0}'", table))) - { - while (reader.Read()) - { - var constraint = new ForeignKeyConstraint(); - constraint.Name = reader.GetString(4); - constraint.Table = reader.GetString(0); - constraint.Columns = new[] { reader.GetString(1) }; - constraint.PkTable = reader.GetString(2); - constraint.PkColumns = new[] { reader.GetString(3) }; - - constraints.Add(constraint); - } - } - - return constraints.ToArray(); - } - - public virtual string[] GetConstraints(string table) - { - var constraints = new List(); - using ( - IDataReader reader = - ExecuteQuery( - String.Format("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE LOWER(TABLE_NAME) = LOWER('{0}')", table))) - { - while (reader.Read()) - { - constraints.Add(reader.GetString(0)); - } - } - - return constraints.ToArray(); - } - - public virtual Column GetColumnByName(string table, string columnName) - { - var columns = GetColumns(table); - return columns.First(column => column.Name.Equals(columnName, StringComparison.OrdinalIgnoreCase)); - } - - public virtual string[] GetTables() - { - var tables = new List(); - using (IDataReader reader = ExecuteQuery("SELECT table_name FROM INFORMATION_SCHEMA.TABLES")) - { - while (reader.Read()) - { - tables.Add((string)reader[0]); - } - } - return tables.ToArray(); - } - - public virtual void RemoveForeignKey(string table, string name) - { - RemoveConstraint(table, name); - } - - public virtual void RemoveConstraint(string table, string name) - { - if (TableExists(table) && ConstraintExists(table, name)) - { - ExecuteNonQuery(String.Format("ALTER TABLE {0} DROP CONSTRAINT {1}", QuoteTableNameIfRequired(table), QuoteConstraintNameIfRequired(name))); - } - } - - public virtual void RemoveAllConstraints(string table) - { - foreach (var constraint in GetConstraints(table)) - { - this.RemoveConstraint(table, constraint); - } - } - - public virtual void AddView(string name, string tableName, params IViewField[] fields) - { - var lst = - fields.Where(x => string.IsNullOrEmpty(x.TableName) || x.TableName == tableName) - .Select(x => x.ColumnName) - .ToList(); - - int nr = 0; - string joins = ""; - foreach (var joinTable in fields.Where(x => !string.IsNullOrEmpty(x.TableName) && x.TableName != tableName).GroupBy(x=>x.TableName)) - { - foreach (var viewField in joinTable) - { - joins += string.Format("JOIN {0} {1} ON {1}.{2} = {3}.{4} ", viewField.TableName, " T" + nr, - viewField.KeyColumnName, viewField.ParentTableName, viewField.ParentKeyColumnName); - lst.Add(" T" + nr + "." + viewField.ColumnName); - } - } - - var select = string.Format("SELECT {0} FROM {1} {2}", string.Join(",", lst), tableName, joins); - - var sql = string.Format("CREATE VIEW {0} AS {1}", name, select); - - ExecuteNonQuery(sql); - } - - /// - /// Add a new table - /// - /// Table name - /// Columns - /// - /// Adds the Test table with two columns: - /// - /// Database.AddTable("Test", - /// new Column("Id", typeof(int), ColumnProperty.PrimaryKey), - /// new Column("Title", typeof(string), 100) - /// ); - /// - /// - public virtual void AddTable(string name, params IDbField[] columns) - { - // Most databases don't have the concept of a storage engine, so default is to not use it. - AddTable(name, null, columns); - } - - /// - /// Add a new table - /// - /// Table name - /// Columns - /// the database storage engine to use - /// - /// Adds the Test table with two columns: - /// - /// Database.AddTable("Test", "INNODB", - /// new Column("Id", typeof(int), ColumnProperty.PrimaryKey), - /// new Column("Title", typeof(string), 100) - /// ); - /// - /// - public virtual void AddTable(string name, string engine, params IDbField[] fields) - { - if (TableExists(name)) - { - Logger.Warn("Table {0} already exists", name); - return; - } - - if (name.Length > 30) - { - Logger.Warn("Tablename {0} is bigger then 30 char's This is a Problem if you want to use Oracle!", name); - } - - var columns = fields.Where(x => x is Column).Cast().ToArray(); - - List pks = GetPrimaryKeys(columns); - bool compoundPrimaryKey = pks.Count > 1; - - var columnProviders = new List(columns.Count()); - foreach (Column column in columns) - { - // Remove the primary key notation if compound primary key because we'll add it back later - if (compoundPrimaryKey && column.IsPrimaryKey) - { - column.ColumnProperty = column.ColumnProperty ^ ColumnProperty.PrimaryKey; - column.ColumnProperty = column.ColumnProperty | ColumnProperty.NotNull; // PK is always not-null - } - - ColumnPropertiesMapper mapper = _dialect.GetAndMapColumnProperties(column); - columnProviders.Add(mapper); - } - - string columnsAndIndexes = JoinColumnsAndIndexes(columnProviders); - AddTable(name, engine, columnsAndIndexes); - - if (compoundPrimaryKey) - { - AddPrimaryKey(getPrimaryKeyname(name), name, pks.ToArray()); - } - - var indexes = fields.Where(x => x is Index).Cast().ToArray(); - foreach (var index in indexes) - { - AddIndex(name, index); - } - - var foreignKeys = fields.Where(x => x is ForeignKeyConstraint).Cast().ToArray(); - foreach (var foreignKey in foreignKeys) - { - this.AddForeignKey(name, foreignKey); - } - } - - protected virtual string getPrimaryKeyname(string tableName) - { - return "PK_" + tableName; - } - public virtual void RemoveTable(string name) - { - if (!TableExists(name)) - { - throw new MigrationException(String.Format("Table with name '{0}' does not exist to rename", name)); - } - - ExecuteNonQuery(String.Format("DROP TABLE {0}", name)); - } - - public virtual void RenameTable(string oldName, string newName) - { - oldName = QuoteTableNameIfRequired(oldName); - newName = QuoteTableNameIfRequired(newName); - - if (TableExists(newName)) - { - throw new MigrationException(String.Format("Table with name '{0}' already exists", newName)); - } - - if (!TableExists(oldName)) - { - throw new MigrationException(String.Format("Table with name '{0}' does not exist to rename", oldName)); - } - - ExecuteNonQuery(String.Format("ALTER TABLE {0} RENAME TO {1}", oldName, newName)); - } - - public virtual void RenameColumn(string tableName, string oldColumnName, string newColumnName) - { - if (ColumnExists(tableName, newColumnName)) - { - throw new MigrationException(String.Format("Table '{0}' has column named '{1}' already", tableName, newColumnName)); - } - - if (!ColumnExists(tableName, oldColumnName)) - { - throw new MigrationException(string.Format("The table '{0}' does not have a column named '{1}'", tableName, oldColumnName)); - } - - var column = GetColumnByName(tableName, oldColumnName); - - var quotedNewColumnName = QuoteColumnNameIfRequired(newColumnName); - - ExecuteNonQuery(String.Format("ALTER TABLE {0} RENAME COLUMN {1} TO {2}", tableName, Dialect.Quote(column.Name), quotedNewColumnName)); - } - - public virtual void RemoveColumn(string table, string column) - { - if (!ColumnExists(table, column, true)) - { - throw new MigrationException(string.Format("The table '{0}' does not have a column named '{1}'", table, column)); - } - - var existingColumn = GetColumnByName(table, column); - - ExecuteNonQuery(String.Format("ALTER TABLE {0} DROP COLUMN {1} ", table, Dialect.Quote(existingColumn.Name))); - } - - public virtual bool ColumnExists(string table, string column) - { - return ColumnExists(table, column, true); - } - - public virtual bool ColumnExists(string table, string column, bool ignoreCase) - { - try - { - if (ignoreCase) - return GetColumns(table).Any(col => col.Name.ToLower() == column.ToLower()); - return GetColumns(table).Any(col => col.Name == column); - } - catch (Exception ex) - { - return false; - } - } - - public virtual void ChangeColumn(string table, Column column) - { - var isUniqueSet = column.ColumnProperty.IsSet(ColumnProperty.Unique); - - column.ColumnProperty = column.ColumnProperty.Clear(ColumnProperty.Unique); - - if (!ColumnExists(table, column.Name)) - { - Logger.Warn("Column {0}.{1} does not exist", table, column.Name); - return; - } - - ColumnPropertiesMapper mapper = _dialect.GetAndMapColumnProperties(column); - - ChangeColumn(table, mapper.ColumnSql); - - if (isUniqueSet) - { - AddUniqueConstraint(string.Format("UX_{0}_{1}", table, column.Name), table, new string[]{column.Name}); - } - } - - public virtual void RemoveColumnDefaultValue(string table, string column) - { - var sql = string.Format("ALTER TABLE {0} ALTER {1} DROP DEFAULT", table, column); - ExecuteNonQuery(sql); - } - - public virtual bool TableExists(string table) - { - try - { - ExecuteNonQuery("SELECT COUNT(*) FROM " + table); - return true; - } - catch (Exception) - { - return false; - } - } - - public virtual void SwitchDatabase(string databaseName) - { - _connection.ChangeDatabase(databaseName); - } - - public abstract List GetDatabases(); - - public bool DatabaseExists(string name) - { - return GetDatabases().Any(c => string.Equals(name, c, StringComparison.InvariantCultureIgnoreCase)); - } - - public virtual void CreateDatabases(string databaseName) - { - ExecuteNonQuery(string.Format("CREATE DATABASE {0}", databaseName)); - } - - public virtual void DropDatabases(string databaseName) - { - ExecuteNonQuery(string.Format("DROP DATABASE {0}", databaseName)); - } - - /// - /// Add a new column to an existing table. - /// - /// Table to which to add the column - /// Column name - /// Date type of the column - /// Max length of the column - /// Properties of the column, see ColumnProperty, - /// Default value - public virtual void AddColumn(string table, string column, DbType type, int size, ColumnProperty property, - object defaultValue) - { - if (ColumnExists(table, column)) - { - Logger.Warn("Column {0}.{1} already exists", table, column); - return; - } - - if (column.Length > 30) - { - Logger.Warn("Columnname {0} is bigger then 30 char's This is a Problem if you want to use Oracle!", column); - } - - ColumnPropertiesMapper mapper = - _dialect.GetAndMapColumnProperties(new Column(column, type, size, property, defaultValue)); - - AddColumn(table, mapper.ColumnSql); - } - - /// - /// - /// AddColumn(string, string, Type, int, ColumnProperty, object) - /// - /// - public virtual void AddColumn(string table, string column, DbType type) - { - AddColumn(table, column, type, 0, ColumnProperty.Null, null); - } - - /// - /// - /// AddColumn(string, string, Type, int, ColumnProperty, object) - /// - /// - public virtual void AddColumn(string table, string column, DbType type, int size) - { - AddColumn(table, column, type, size, ColumnProperty.Null, null); - } - - public virtual void AddColumn(string table, string column, DbType type, object defaultValue) - { - if (ColumnExists(table, column)) - { - Logger.Warn("Column {0}.{1} already exists", table, column); - return; - } - - ColumnPropertiesMapper mapper = - _dialect.GetAndMapColumnProperties(new Column(column, type, defaultValue)); - - AddColumn(table, mapper.ColumnSql); - } - - /// - /// - /// AddColumn(string, string, Type, int, ColumnProperty, object) - /// - /// - public virtual void AddColumn(string table, string column, DbType type, ColumnProperty property) - { - AddColumn(table, column, type, 0, property, null); - } - - /// - /// - /// AddColumn(string, string, Type, int, ColumnProperty, object) - /// - /// - public virtual void AddColumn(string table, string column, DbType type, int size, ColumnProperty property) - { - AddColumn(table, column, type, size, property, null); - } - - /// - /// Append a primary key to a table. - /// - /// Constraint name - /// Table name - /// Primary column names - public virtual void AddPrimaryKey(string name, string table, params string[] columns) - { - if (ConstraintExists(table, name)) - { - Logger.Warn("Primary key {0} already exists", name); - return; - } - - ExecuteNonQuery( - String.Format("ALTER TABLE {0} ADD CONSTRAINT {1} PRIMARY KEY ({2}) ", table, name, - String.Join(",", QuoteColumnNamesIfRequired(columns)))); - } - - public virtual void AddUniqueConstraint(string name, string table, params string[] columns) - { - if (ConstraintExists(table, name)) - { - Logger.Warn("Constraint {0} already exists", name); - return; - } - - QuoteColumnNames(columns); - - table = QuoteTableNameIfRequired(table); - - ExecuteNonQuery(String.Format("ALTER TABLE {0} ADD CONSTRAINT {1} UNIQUE({2}) ", table, name, string.Join(", ", columns))); - } - - public virtual void AddCheckConstraint(string name, string table, string checkSql) - { - if (ConstraintExists(table, name)) - { - Logger.Warn("Constraint {0} already exists", name); - return; - } - - table = QuoteTableNameIfRequired(table); - - ExecuteNonQuery(String.Format("ALTER TABLE {0} ADD CONSTRAINT {1} CHECK ({2}) ", table, name, checkSql)); - } - - /// - /// Guesses the name of the foreign key and add it - /// - public virtual void GenerateForeignKey(string primaryTable, string primaryColumn, string refTable, string refColumn) - { - AddForeignKey("FK_" + primaryTable + "_" + refTable, primaryTable, primaryColumn, refTable, refColumn); - } - - /// - /// Guesses the name of the foreign key and add it - /// - /// - public virtual void GenerateForeignKey(string primaryTable, string[] primaryColumns, string refTable, - string[] refColumns) - { - AddForeignKey("FK_" + primaryTable + "_" + refTable, primaryTable, primaryColumns, refTable, refColumns); - } - - /// - /// Guesses the name of the foreign key and add it - /// - public virtual void GenerateForeignKey(string primaryTable, string primaryColumn, string refTable, - string refColumn, ForeignKeyConstraintType constraint) - { - AddForeignKey("FK_" + primaryTable + "_" + refTable, primaryTable, primaryColumn, refTable, refColumn, - constraint); - } - - /// - /// Guesses the name of the foreign key and add it - /// - /// - public virtual void GenerateForeignKey(string primaryTable, string[] primaryColumns, string refTable, - string[] refColumns, ForeignKeyConstraintType constraint) - { - AddForeignKey("FK_" + primaryTable + "_" + refTable, primaryTable, primaryColumns, refTable, refColumns, - constraint); - } - - public virtual void AddForeignKey(string table, ForeignKeyConstraint fk) - { - AddForeignKey(fk.Name, table, fk.Columns, fk.PkTable, fk.PkColumns); - } - - public virtual void AddForeignKey(string name, string primaryTable, string primaryColumn, string refTable, string refColumn) - { - try - { - AddForeignKey(name, primaryTable, new[] { primaryColumn }, refTable, new[] { refColumn }); - } - catch (Exception ex) - { - throw new Exception(string.Format("Error occured while adding foreign key: \"{0}\" between table: \"{1}\" and table: \"{2}\" - see inner exception for details", name, primaryTable, refTable), ex); - } - } - - - /// - /// - /// AddForeignKey(string, string, string, string, string) - /// - /// - public virtual void AddForeignKey(string name, string primaryTable, string[] primaryColumns, string refTable, string[] refColumns) - { - AddForeignKey(name, primaryTable, primaryColumns, refTable, refColumns, ForeignKeyConstraintType.NoAction); - } - - public virtual void AddForeignKey(string name, string primaryTable, string primaryColumn, string refTable, string refColumn, ForeignKeyConstraintType constraint) - { - AddForeignKey(name, primaryTable, new[] { primaryColumn }, refTable, new[] { refColumn }, - constraint); - } - - public virtual void AddForeignKey(string name, string primaryTable, string[] primaryColumns, string refTable, - string[] refColumns, ForeignKeyConstraintType constraint) - { - if (ConstraintExists(primaryTable, name)) - { - Logger.Warn("Constraint {0} already exists", name); - return; - } - - refTable = QuoteTableNameIfRequired(refTable); - primaryTable = QuoteTableNameIfRequired(primaryTable); - QuoteColumnNames(primaryColumns); - QuoteColumnNames(refColumns); - - string constraintResolved = constraintMapper.SqlForConstraint(constraint); - - ExecuteNonQuery( - String.Format( - "ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4}) ON UPDATE {5} ON DELETE {6}", - primaryTable, name, String.Join(",", primaryColumns), - refTable, String.Join(",", refColumns), constraintResolved, constraintResolved)); - } - - /// - /// Determines if a constraint exists. - /// - /// Constraint name - /// Table owning the constraint - /// true if the constraint exists. - public abstract bool ConstraintExists(string table, string name); - - public virtual bool PrimaryKeyExists(string table, string name) - { - return ConstraintExists(table, name); - } - - public virtual int ExecuteNonQuery(string sql) - { - return ExecuteNonQuery(sql, 30); - } - - public virtual int ExecuteNonQuery(string sql, int timeout) - { - return this.ExecuteNonQuery(sql, timeout, null); - } - - public virtual int ExecuteNonQuery(string sql, int timeout, params object[] args) - { - if (args==null) - { - Logger.Trace(sql); - Logger.ApplyingDBChange(sql); - } - else - { - Logger.Trace(string.Format(sql, args)); - Logger.ApplyingDBChange(string.Format(sql, args)); - } - - using (IDbCommand cmd = BuildCommand(sql)) - { - try - { - cmd.CommandTimeout = timeout; - - if (args != null) - { - int index = 0; - foreach (object obj in args) - { - IDbDataParameter parameter = cmd.CreateParameter(); - this.ConfigureParameterWithValue(parameter, index, obj); - parameter.ParameterName = this.GenerateParameterName(index); - cmd.Parameters.Add((object)parameter); - ++index; - } - } - - return cmd.ExecuteNonQuery(); - } - catch (Exception ex) - { - Logger.Warn(ex.Message); - throw new Exception(string.Format("Error occured executing sql: {0}, see inner exception for details, error: " + ex, sql), ex); - } - } - } - - public List ExecuteStringQuery(string sql, params object[] args) - { - var values = new List(); - - using (var reader = ExecuteQuery(string.Format(sql, args))) - { - while (reader.Read()) - { - var value = reader[0]; - - if (value == null || value == DBNull.Value) - { - values.Add(null); - } - else - { - values.Add(value.ToString()); - } - } - } - - return values; - } - - public virtual void ExecuteScript(string fileName) - { - if (CurrentMigration != null) - { - var assembly = CurrentMigration.GetType().Assembly; - - string sqlText; - string file = (new System.Uri(assembly.CodeBase)).AbsolutePath; - using (var reader = File.OpenText(file)) - sqlText = reader.ReadToEnd(); - - ExecuteNonQuery(sqlText); - } - } - - public virtual void ExecuteEmbededScript(string resourceName) - { - if (CurrentMigration != null) - { - var assembly = CurrentMigration.GetType().Assembly; - - string sqlText; - string embeddedResourceName = TransformationProviderUtility.GetQualifiedResourcePath(assembly, resourceName); - - using (var stream = assembly.GetManifestResourceStream(embeddedResourceName)) - using (var reader = new StreamReader(stream)) - { - sqlText = reader.ReadToEnd(); - } - ExecuteNonQuery(sqlText); - } - } - - /// - /// Execute an SQL query returning results. - /// - /// The SQL command. - /// A data iterator, IDataReader. - public virtual IDataReader ExecuteQuery(string sql) - { - Logger.Trace(sql); - using (IDbCommand cmd = BuildCommand(sql)) - { - try - { - return cmd.ExecuteReader(); - } - catch (Exception ex) - { - Logger.Warn("query failed: {0}", cmd.CommandText); - throw new Exception("Failed to execute sql statement: " + sql, ex); - } - } - } - - public virtual object ExecuteScalar(string sql) - { - Logger.Trace(sql); - using (IDbCommand cmd = BuildCommand(sql)) - { - try - { - return cmd.ExecuteScalar(); - } - catch - { - Logger.Warn("Query failed: {0}", cmd.CommandText); - throw; - } - } - } - - public virtual IDataReader Select(string what, string from) - { - return Select(what, from, "1=1"); - } - - public virtual IDataReader Select(string what, string from, string where) - { - return ExecuteQuery(String.Format("SELECT {0} FROM {1} WHERE {2}", what, from, where)); - } - - public object SelectScalar(string what, string from) - { - return SelectScalar(what, from, "1=1"); - } - - public virtual object SelectScalar(string what, string from, string where) - { - return ExecuteScalar(String.Format("SELECT {0} FROM {1} WHERE {2}", what, from, where)); - } - - public virtual int Update(string table, string[] columns, object[] values) - { - return Update(table, columns, values, null); - } - - public virtual int Update(string table, string[] columns, object[] values, string where) - { - if (string.IsNullOrEmpty(table)) throw new ArgumentNullException("table"); - if (columns == null) throw new ArgumentNullException("columns"); - if (values == null) throw new ArgumentNullException("values"); - if (columns.Length != values.Length) throw new Exception(string.Format("The number of columns: {0} does not match the number of supplied values: {1}", columns.Length, values.Length)); - - table = QuoteTableNameIfRequired(table); - - var builder = new StringBuilder(); - for (int i = 0; i < values.Length; i++) - { - if (builder.Length > 0) builder.Append(", "); - builder.Append(QuoteColumnNameIfRequired(columns[i])); - builder.Append(" = "); - builder.Append(GenerateParameterName(i)); - } - - using (IDbCommand command = _connection.CreateCommand()) - { - command.Transaction = _transaction; - - var query = String.Format("UPDATE {0} SET {1}", table, builder.ToString()); - if (!String.IsNullOrEmpty(where)) - { - query += " WHERE " + where; - } - command.CommandText = query; - command.CommandType = CommandType.Text; - - int paramCount = 0; - - foreach (object value in values) - { - IDbDataParameter parameter = command.CreateParameter(); - - ConfigureParameterWithValue(parameter, paramCount, value); - - parameter.ParameterName = GenerateParameterName(paramCount); - - command.Parameters.Add(parameter); - - paramCount++; - } - - return command.ExecuteNonQuery(); - } - } - - public virtual int Update(string table, string[] columns, object[] values, string[] whereColumns, object[] whereValues) - { - if (string.IsNullOrEmpty(table)) throw new ArgumentNullException("table"); - if (columns == null) throw new ArgumentNullException("columns"); - if (values == null) throw new ArgumentNullException("values"); - if (columns.Length != values.Length) throw new Exception(string.Format("The number of columns: {0} does not match the number of supplied values: {1}", columns.Length, values.Length)); - - table = QuoteTableNameIfRequired(table); - - var builder = new StringBuilder(); - for (int i = 0; i < values.Length; i++) - { - if (builder.Length > 0) builder.Append(", "); - builder.Append(QuoteColumnNameIfRequired(columns[i])); - builder.Append(" = "); - builder.Append(GenerateParameterName(i)); - } - - var builder2 = new StringBuilder(); - for (int i = 0; i < whereColumns.Length; i++) - { - if (builder2.Length > 0) builder2.Append(" AND "); - builder2.Append(QuoteColumnNameIfRequired(whereColumns[i])); - builder2.Append(" = "); - builder2.Append(GenerateParameterName(i + values.Count())); - } - - using (IDbCommand command = _connection.CreateCommand()) - { - command.Transaction = _transaction; - - var query = String.Format("UPDATE {0} SET {1} WHERE {2}", table, builder.ToString(), builder2.ToString()); - - command.CommandText = query; - command.CommandType = CommandType.Text; - - int paramCount = 0; - - foreach (object value in values) - { - IDbDataParameter parameter = command.CreateParameter(); - - ConfigureParameterWithValue(parameter, paramCount, value); - - parameter.ParameterName = GenerateParameterName(paramCount); - - command.Parameters.Add(parameter); - - paramCount++; - } - - foreach (object value in whereValues) - { - - IDbDataParameter parameter = command.CreateParameter(); - - ConfigureParameterWithValue(parameter, paramCount, value); - - parameter.ParameterName = GenerateParameterName(paramCount); - - command.Parameters.Add(parameter); - - paramCount++; - } - - return command.ExecuteNonQuery(); - } - } - - public virtual int Insert(string table, string[] columns, object[] values) - { - if (string.IsNullOrEmpty(table)) throw new ArgumentNullException("table"); - if (columns == null) throw new ArgumentNullException("columns"); - if (values == null) throw new ArgumentNullException("values"); - if (columns.Length != values.Length) throw new Exception(string.Format("The number of columns: {0} does not match the number of supplied values: {1}", columns.Length, values.Length)); - - table = QuoteTableNameIfRequired(table); - - string columnNames = string.Join(", ", columns.Select(col => QuoteColumnNameIfRequired(col)).ToArray()); - - var builder = new StringBuilder(); - - for (int i = 0; i < values.Length; i++) - { - if (builder.Length > 0) builder.Append(", "); - builder.Append(GenerateParameterName(i)); - } - - string parameterNames = builder.ToString(); - - using (IDbCommand command = _connection.CreateCommand()) - { - command.Transaction = _transaction; - - command.CommandText = String.Format("INSERT INTO {0} ({1}) VALUES ({2})", table, columnNames, parameterNames); - command.CommandType = CommandType.Text; - - int paramCount = 0; - - foreach (object value in values) - { - IDbDataParameter parameter = command.CreateParameter(); - - ConfigureParameterWithValue(parameter, paramCount, value); - - parameter.ParameterName = GenerateParameterName(paramCount); - - command.Parameters.Add(parameter); - - paramCount++; - } - - return command.ExecuteNonQuery(); - } - } - - public virtual int Delete(string table, string[] columns, string[] values) - { - if (null == columns || null == values) - { - return ExecuteNonQuery(String.Format("DELETE FROM {0}", table)); - } - else - { - return ExecuteNonQuery(String.Format("DELETE FROM {0} WHERE ({1})", table, JoinColumnsAndValues(columns, values))); - } - } - - public virtual int Delete(string table, string wherecolumn, string wherevalue) - { - if (string.IsNullOrEmpty(wherecolumn) && string.IsNullOrEmpty(wherevalue)) - { - return Delete(table, (string[])null, null); - } - - return ExecuteNonQuery(String.Format("DELETE FROM {0} WHERE {1} = {2}", table, wherecolumn, QuoteValues(wherevalue))); - } - - public virtual int TruncateTable(string table) - { - return ExecuteNonQuery(String.Format("TRUNCATE TABLE {0} ", table)); - } - - /// - /// Starts a transaction. Called by the migration mediator. - /// - public virtual void BeginTransaction() - { - if (_transaction == null && _connection != null) - { - EnsureHasConnection(); - _transaction = _connection.BeginTransaction(IsolationLevel.ReadCommitted); - } - } - - /// - /// Rollback the current migration. Called by the migration mediator. - /// - public virtual void Rollback() - { - if (_transaction != null && _connection != null && _connection.State == ConnectionState.Open) - { - try - { - _transaction.Rollback(); - } - finally - { - if (!_outsideConnection) - { - _connection.Close(); - } - } - } - _transaction = null; - } - - /// - /// Commit the current transaction. Called by the migrations mediator. - /// - public virtual void Commit() - { - if (_transaction != null && _connection != null && _connection.State == ConnectionState.Open) - { - try - { - _transaction.Commit(); - } - finally - { - if (!_outsideConnection) - { - _connection.Close(); - } - } - } - _transaction = null; - } - - /// - /// The list of Migrations currently applied to the database. - /// - public virtual List AppliedMigrations - { - get - { - if (_appliedMigrations == null) - { - _appliedMigrations = new List(); - CreateSchemaInfoTable(); - - string versionColumn = "Version"; - string scopeColumn = "Scope"; - - versionColumn = QuoteColumnNameIfRequired(versionColumn); - scopeColumn = QuoteColumnNameIfRequired(scopeColumn); - - using (IDataReader reader = Select(versionColumn, _schemaInfotable, string.Format("{0} = '{1}'", scopeColumn, _scope))) - { - while (reader.Read()) - { - if (reader.GetFieldType(0) == typeof(Decimal)) - { - _appliedMigrations.Add((long)reader.GetDecimal(0)); - } - else - { - _appliedMigrations.Add(reader.GetInt64(0)); - } - } - } - } - return _appliedMigrations; - } - } - - /// - /// Marks a Migration version number as having been applied - /// - /// The version number of the migration that was applied - public virtual void MigrationApplied(long version, string scope) - { - CreateSchemaInfoTable(); - Insert(_schemaInfotable, new string[] { "Scope", "Version", "TimeStamp" }, new object[] { scope ?? _scope, version, DateTime.Now }); - _appliedMigrations.Add(version); - } - - /// - /// Marks a Migration version number as having been rolled back from the database - /// - /// The version number of the migration that was removed - public virtual void MigrationUnApplied(long version, string scope) - { - CreateSchemaInfoTable(); - Delete(_schemaInfotable, new[] { "Scope", "Version" }, new[] { scope ?? _scope, version.ToString() }); - _appliedMigrations.Remove(version); - } - - public virtual void AddColumn(string table, Column column) - { - AddColumn(table, column.Name, column.Type, column.Size, column.ColumnProperty, column.DefaultValue); - } - - public virtual void GenerateForeignKey(string primaryTable, string refTable) - { - GenerateForeignKey(primaryTable, refTable, ForeignKeyConstraintType.NoAction); - } - - public virtual void GenerateForeignKey(string primaryTable, string refTable, ForeignKeyConstraintType constraint) - { - GenerateForeignKey(primaryTable, refTable + "Id", refTable, "Id", constraint); - } - - public virtual IDbCommand GetCommand() - { - return BuildCommand(null); - } - - public virtual void ExecuteSchemaBuilder(SchemaBuilder builder) - { - foreach (ISchemaBuilderExpression expr in builder.Expressions) - expr.Create(this); - } - - public void Dispose() - { - if (_connection != null && _connection.State == ConnectionState.Open) - { - if (!_outsideConnection) - { - _connection.Close(); - } - } - - if (_connection != null) - { - if (!_outsideConnection) - { - _connection.Close(); - } - } - - _connection = null; - } - - public virtual string QuoteColumnNameIfRequired(string name) - { - if (Dialect.ColumnNameNeedsQuote || Dialect.IsReservedWord(name)) - { - return Dialect.Quote(name); - } - return name; - } - - public virtual string QuoteTableNameIfRequired(string name) - { - if (Dialect.TableNameNeedsQuote || Dialect.IsReservedWord(name)) - { - return Dialect.Quote(name); - } - return name; - } - - public virtual string Encode(Guid guid) - { - return guid.ToString(); - } - - public virtual string[] QuoteColumnNamesIfRequired(params string[] columnNames) - { - var quotedColumns = new string[columnNames.Length]; - - for (int i = 0; i < columnNames.Length; i++) - { - quotedColumns[i] = QuoteColumnNameIfRequired(columnNames[i]); - } - - return quotedColumns; - } - - public virtual bool IsThisProvider(string provider) - { - // XXX: This might need to be more sophisticated. Currently just a convention - return GetType().Name.ToLower().StartsWith(provider.ToLower()); - } - - public virtual void RemoveAllForeignKeys(string tableName, string columnName) - { } - - public virtual void AddTable(string table, string engine, string columns) - { - table = _dialect.TableNameNeedsQuote ? _dialect.Quote(table) : table; - string sqlCreate = String.Format("CREATE TABLE {0} ({1})", table, columns); - ExecuteNonQuery(sqlCreate); - } - - public virtual List GetPrimaryKeys(IEnumerable columns) - { - var pks = new List(); - foreach (Column col in columns) - { - if (col.IsPrimaryKey) - pks.Add(col.Name); - } - return pks; - } - - public virtual void AddColumnDefaultValue(string table, string column, object defaultValue) - { - table = QuoteTableNameIfRequired(table); - column = this.QuoteColumnNameIfRequired(column); - var def = Dialect.Default(defaultValue); - ExecuteNonQuery(String.Format("ALTER TABLE {0} ADD DEFAULT('{1}') FOR {2}", table, def, column)); - } - - public virtual void AddColumn(string table, string sqlColumn) - { - table = QuoteTableNameIfRequired(table); - ExecuteNonQuery(String.Format("ALTER TABLE {0} ADD COLUMN {1}", table, sqlColumn)); - } - - public virtual void ChangeColumn(string table, string sqlColumn) - { - table = QuoteTableNameIfRequired(table); - ExecuteNonQuery(String.Format("ALTER TABLE {0} ALTER COLUMN {1}", table, sqlColumn)); - } - - protected virtual string JoinColumnsAndIndexes(IEnumerable columns) - { - string indexes = JoinIndexes(columns); - string columnsAndIndexes = JoinColumns(columns) + (indexes != null ? "," + indexes : String.Empty); - return columnsAndIndexes; - } - - protected virtual string JoinIndexes(IEnumerable columns) - { - var indexes = new List(); - foreach (ColumnPropertiesMapper column in columns) - { - string indexSql = column.IndexSql; - if (indexSql != null) - indexes.Add(indexSql); - } - - if (indexes.Count == 0) - return null; - - return String.Join(", ", indexes.ToArray()); - } - - protected virtual string JoinColumns(IEnumerable columns) - { - var columnStrings = new List(); - foreach (ColumnPropertiesMapper column in columns) - columnStrings.Add(column.ColumnSql); - return String.Join(", ", columnStrings.ToArray()); - } - - protected IDbCommand BuildCommand(string sql) - { - EnsureHasConnection(); - IDbCommand cmd = _connection.CreateCommand(); - cmd.CommandText = sql; - cmd.CommandType = CommandType.Text; - if (_transaction != null) - { - cmd.Transaction = _transaction; - } - return cmd; - } - - public virtual int Delete(string table) - { - return Delete(table, null, (string[])null); - } - - protected void EnsureHasConnection() - { - if (_connection.State != ConnectionState.Open) - { - _connection.Open(); - } - } - - protected virtual void CreateSchemaInfoTable() - { - EnsureHasConnection(); - if (!TableExists(_schemaInfotable)) - { - AddTable(_schemaInfotable, - new Column("Version", DbType.Int64, ColumnProperty.NotNull | ColumnProperty.PrimaryKey), - new Column("Scope", DbType.String, 50, ColumnProperty.NotNull | ColumnProperty.PrimaryKey, "default"), - new Column("TimeStamp", DbType.DateTime)); - } - else - { - if (!ColumnExists(_schemaInfotable, "Scope")) - { - AddColumn(_schemaInfotable, "Scope", DbType.String, 50, ColumnProperty.NotNull, "default"); - RemoveAllConstraints(_schemaInfotable); - AddPrimaryKey("PK_SchemaInfo", _schemaInfotable, new[] { "Version", "Scope" }); - } - - if (!ColumnExists(_schemaInfotable, "TimeStamp")) - { - AddColumn(_schemaInfotable, "TimeStamp", DbType.DateTime); - } - } - } - - public virtual string QuoteValues(string values) - { - return QuoteValues(new[] { values })[0]; - } - - public virtual string[] QuoteValues(string[] values) - { - return Array.ConvertAll(values, - delegate(string val) - { - if (null == val) - return "null"; - else - return String.Format("'{0}'", val.Replace("'", "''")); - }); - } - - public virtual string JoinColumnsAndValues(string[] columns, string[] values) - { - string[] quotedValues = QuoteValues(values); - var namesAndValues = new string[columns.Length]; - for (int i = 0; i < columns.Length; i++) - { - namesAndValues[i] = String.Format("{0}={1}", columns[i], quotedValues[i]); - } - - return String.Join(", ", namesAndValues); - } - - public virtual string GenerateParameterName(int index) - { - return "@p" + index; - } - - protected virtual void ConfigureParameterWithValue(IDbDataParameter parameter, int index, object value) - { - if (value == null || value == DBNull.Value) - { - parameter.Value = DBNull.Value; - } - else if (value is Guid || value is Guid?) - { - parameter.DbType = DbType.Guid; - parameter.Value = (Guid)value; - } - else if (value is Int16) - { - parameter.DbType = DbType.Int16; - parameter.Value = value; - } - else if (value is Int32) - { - parameter.DbType = DbType.Int32; - parameter.Value = value; - } - else if (value is Int64) - { - parameter.DbType = DbType.Int64; - parameter.Value = value; - } - else if (value is UInt16) - { - parameter.DbType = DbType.UInt16; - parameter.Value = value; - } - else if (value is UInt32) - { - parameter.DbType = DbType.UInt32; - parameter.Value = value; - } - else if (value is UInt64) - { - parameter.DbType = DbType.UInt64; - parameter.Value = value; - } - else if (value is Double) - { - parameter.DbType = DbType.Double; - parameter.Value = value; - } - else if (value is Decimal) - { - parameter.DbType = DbType.Decimal; - parameter.Value = value; - } - else if (value is String) - { - parameter.DbType = DbType.String; - parameter.Value = value; - } - else if (value is DateTime || value is DateTime?) - { - parameter.DbType = DbType.DateTime; - parameter.Value = value; - } - else if (value is Boolean || value is Boolean?) - { - parameter.DbType = DbType.Boolean; - parameter.Value = value; - } - else - { - throw new NotSupportedException(string.Format("TransformationProvider does not support value: {0} of type: {1}", value, value.GetType())); - } - } - - string FormatValue(object value) - { - if (value == null) return null; - if (value is DateTime) return ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss:fff"); - return value.ToString(); - } - - void QuoteColumnNames(string[] primaryColumns) - { - for (int i = 0; i < primaryColumns.Length; i++) - { - primaryColumns[i] = QuoteColumnNameIfRequired(primaryColumns[i]); - } - } - - public virtual void RemoveIndex(string table, string name) - { - if (TableExists(table) && IndexExists(table, name)) - { - name = QuoteConstraintNameIfRequired(name); - ExecuteNonQuery(String.Format("DROP INDEX {0}", name)); - } - } - - public virtual void AddIndex(string table, Index index) - { - AddIndex(index.Name, table, index.KeyColumns); - } - - public virtual void AddIndex(string name, string table, params string[] columns) - { - if (IndexExists(table, name)) - { - Logger.Warn("Index {0} already exists", name); - return; - } - - name = QuoteConstraintNameIfRequired(name); - - table = QuoteTableNameIfRequired(table); - - columns = QuoteColumnNamesIfRequired(columns); - - ExecuteNonQuery(String.Format("CREATE INDEX {0} ON {1} ({2}) ", name, table, string.Join(", ", columns))); - } - - protected string QuoteConstraintNameIfRequired(string name) - { - return _dialect.ConstraintNameNeedsQuote ? _dialect.Quote(name) : name; - } - - public abstract bool IndexExists(string table, string name); - - protected virtual string GetPrimaryKeyConstraintName(string table) - { - return null; - } - - public virtual void RemovePrimaryKey(string table) - { - if (!TableExists(table)) return; - - var primaryKeyConstraintName = GetPrimaryKeyConstraintName(table); - - if (primaryKeyConstraintName == null || !ConstraintExists(table, primaryKeyConstraintName)) return; - - RemoveConstraint(table, primaryKeyConstraintName); - } - - public virtual void RemoveAllIndexes(string table) - { - if (!TableExists(table)) return; - - var indexes = GetIndexes(table); - - foreach (var index in indexes) - { - if (index.Name == null || !IndexExists(table, index.Name)) continue; - - if (index.PrimaryKey || index.Clustered || index.Unique) - RemoveConstraint(table, index.Name); - else - RemoveIndex(table, index.Name); - } - } - - public virtual string Concatenate(params string[] strings) - { - return string.Join(" || ", strings); - } - - public IDbConnection Connection - { - get { return _connection; } - } - - public IEnumerable GetTables(string schema) - { - var tableRestrictions = new string[4]; - tableRestrictions[1] = schema; - - var c = _connection as DbConnection; - var tables = c.GetSchema("Tables", tableRestrictions); - return from DataRow row in tables.Rows select row.Field("TABLE_NAME"); - } - - public IEnumerable GetColumns(string schema, string table) - { - var tableRestrictions = new string[4]; - tableRestrictions[1] = schema; - tableRestrictions[2] = table; - - var c = _connection as DbConnection; - var tables = c.GetSchema("Columns", tableRestrictions); - return from DataRow row in tables.Rows select row.Field("TABLE_NAME"); - } - } -} diff --git a/src/Migrator.Providers/TypeNames.cs b/src/Migrator.Providers/TypeNames.cs deleted file mode 100644 index a697ceb5..00000000 --- a/src/Migrator.Providers/TypeNames.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; - -using Migrator.Framework; - -namespace Migrator.Providers -{ - /// - /// This class maps a DbType to names. - /// - /// - /// Associations may be marked with a capacity. Calling the Get() - /// method with a type and actual size n will return the associated - /// name with smallest capacity >= n, if available and an unmarked - /// default type otherwise. - /// Eg, setting - /// - /// Names.Put(DbType, "TEXT" ); - /// Names.Put(DbType, 255, "VARCHAR($l)" ); - /// Names.Put(DbType, 65534, "LONGVARCHAR($l)" ); - /// - /// will give you back the following: - /// - /// Names.Get(DbType) // --> "TEXT" (default) - /// Names.Get(DbType,100) // --> "VARCHAR(100)" (100 is in [0:255]) - /// Names.Get(DbType,1000) // --> "LONGVARCHAR(1000)" (100 is in [256:65534]) - /// Names.Get(DbType,100000) // --> "TEXT" (default) - /// - /// On the other hand, simply putting - /// - /// Names.Put(DbType, "VARCHAR($l)" ); - /// - /// would result in - /// - /// Names.Get(DbType) // --> "VARCHAR($l)" (will cause trouble) - /// Names.Get(DbType,100) // --> "VARCHAR(100)" - /// Names.Get(DbType,1000) // --> "VARCHAR(1000)" - /// Names.Get(DbType,10000) // --> "VARCHAR(10000)" - /// - /// - public class TypeNames - { - public const string LengthPlaceHolder = "$l"; - public const string PrecisionPlaceHolder = "$p"; - public const string ScalePlaceHolder = "$s"; - - readonly Dictionary defaults = new Dictionary(); - - readonly Dictionary> weighted = - new Dictionary>(); - - public DbType GetDbType(string type) - { - type = type.Trim().ToLower(); - var retval = defaults.Where(x => x.Value.Trim().ToLower().StartsWith(type)).Select(x => x.Key); - if (retval.Any()) - return retval.First(); - return weighted.Where(x => x.Value.Where(y => y.Value.Trim().ToLower().StartsWith(type)).Any()).Select(x => x.Key).FirstOrDefault(); - } - - /// - /// Get default type name for specified type - /// - /// the type key - /// the default type name associated with the specified key - public string Get(DbType typecode) - { - string result; - if (!defaults.TryGetValue(typecode, out result)) - { - throw new ArgumentException("Dialect does not support DbType." + typecode, "typecode"); - } - return result; - } - - /// - /// Get the type name specified type and size - /// - /// the type key - /// the SQL length - /// the SQL scale - /// the SQL precision - /// - /// The associated name with smallest capacity >= size if available and the - /// default type name otherwise - /// - public string Get(DbType typecode, int size, int precision, int scale) - { - SortedList map; - weighted.TryGetValue(typecode, out map); - if (map != null && map.Count > 0) - { - foreach (var entry in map) - { - if (size <= entry.Key) - { - return Replace(entry.Value, size, precision, scale); - } - } - } - //Could not find a specific type for the size, using the default - return Get(typecode); - } - - static string Replace(string type, int size, int precision, int scale) - { - type = StringUtils.ReplaceOnce(type, LengthPlaceHolder, size.ToString()); - type = StringUtils.ReplaceOnce(type, ScalePlaceHolder, scale.ToString()); - return StringUtils.ReplaceOnce(type, PrecisionPlaceHolder, precision.ToString()); - } - - /// - /// Set a type name for specified type key and capacity - /// - /// the type key - /// the (maximum) type size/length - /// The associated name - public void Put(DbType typecode, int capacity, string value) - { - SortedList map; - if (!weighted.TryGetValue(typecode, out map)) - { - // add new ordered map - weighted[typecode] = map = new SortedList(); - } - map[capacity] = value; - } - - /// - /// - /// - /// - /// - public void Put(DbType typecode, string value) - { - defaults[typecode] = value; - } - } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Utility/MySqlServerUtility.cs b/src/Migrator.Providers/Utility/MySqlServerUtility.cs deleted file mode 100644 index 17b3de1b..00000000 --- a/src/Migrator.Providers/Utility/MySqlServerUtility.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Data; -//using MySql.Data.MySqlClient; - -namespace Migrator.Providers.Utility -{ -// public static class MySqlServerUtility -// { -// public static void RemoveAllTablesFromDefaultDatabase(string connectionString) -// { -// using (var connection = new MySqlConnection(connectionString)) -// { -// connection.Open(); - -// string dropAllTablesSql = null; - -// do -// { -// dropAllTablesSql = GetDropAllTablesSql(connection); - -// if (dropAllTablesSql == null) continue; - -// DisableForeignKeys(connection); - -// ExecuteDropCommand(connection, dropAllTablesSql); -// } while (dropAllTablesSql != null); -// } -// } - -// static void ExecuteDropCommand(MySqlConnection connection, string dropAllTablesSql) -// { -// using (var dropCmd = new MySqlCommand(dropAllTablesSql, connection)) -// { -// dropCmd.ExecuteNonQuery(); -// } -// } - -// public static void DisableForeignKeys(MySqlConnection connection) -// { -// using (var command = new MySqlCommand("Set foreign_key_checks=off;", connection)) -// { -// command.ExecuteNonQuery(); -// } -// } - -// public static string GetDropAllTablesSql(MySqlConnection connection) -// { -// const string query = -// @"set group_concat_max_len=10240; -//SELECT concat('DROP TABLE IF EXISTS ', group_concat(table_name)) drop_statement -//FROM information_schema.tables -//WHERE table_schema=database();"; - -// using (var getDropAllTablesCommand = new MySqlCommand(query, connection)) -// { -// getDropAllTablesCommand.CommandType = CommandType.Text; - -// using (MySqlDataReader reader = getDropAllTablesCommand.ExecuteReader()) -// { -// if (reader.Read() && (reader[0] != null && !Convert.IsDBNull(reader[0]))) -// { -// return reader[0].ToString(); -// } -// } -// } - -// return null; -// } -// } -} \ No newline at end of file diff --git a/src/Migrator.Providers/Utility/OracleServerUtility.cs b/src/Migrator.Providers/Utility/OracleServerUtility.cs deleted file mode 100644 index c56e42d6..00000000 --- a/src/Migrator.Providers/Utility/OracleServerUtility.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -//using Oracle.DataAccess.Client; - -namespace Migrator.Providers.Utility -{ - //public static class OracleServerUtility - //{ - // static readonly string[] _specialTableNames = new[] - // { - // "DEF$_AQCALL", - // "DEF$_AQERROR", - // "SQLPLUS_PRODUCT_PROFILE", - // "HELP", - // "MVIEW$_ADV_INDEX", - // "MVIEW$_ADV_PARTITION" - // }; - - // public static void RemoveAllTablesFromDefaultDatabase(string connectionString) - // { - // using (var connection = new OracleConnection(connectionString)) - // { - // connection.Open(); - - // string[] allTablesToDrop = GetTablesToDrop(connection).ToArray(); - - // foreach (string table in allTablesToDrop) - // { - // string statement = string.Format("drop table \"{0}\" cascade constraints", table); - - // ExecuteDropCommand(connection, statement); - // } - // } - // } - - // static void ExecuteDropCommand(OracleConnection connection, string statement) - // { - // using (var dropCmd = new OracleCommand(statement, connection)) - // { - // dropCmd.ExecuteNonQuery(); - // } - // } - - // public static int GetTableCount(string connectionString) - // { - // using (var connection = new OracleConnection(connectionString)) - // { - // connection.Open(); - - // return GetTablesToDrop(connection).Count(); - // } - // } - - // static string ExtractUserIDFromConnectionString(string connectionString) - // { - - // string[] values = connectionString.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries); - - // var match = values.FirstOrDefault(v => v.StartsWith("User ID=", StringComparison.InvariantCultureIgnoreCase)); - - // if (match != null) - // { - // string userName = match.Split(new[] { "=" }, StringSplitOptions.None)[1]; - // return userName; - // } - - // return null; - // } - - // public static IEnumerable GetTablesToDrop(OracleConnection connection) - // { - // var schema = ExtractUserIDFromConnectionString(connection.ConnectionString); - - // string query = string.Format(@"select * from user_tables where TABLESPACE_NAME = '{0}'", schema); - - // using (var getDropAllTablesCommand = new OracleCommand(query, connection)) - // { - // getDropAllTablesCommand.CommandType = CommandType.Text; - - // using (OracleDataReader reader = getDropAllTablesCommand.ExecuteReader()) - // { - // while (reader.Read() && (reader[0] != null && !Convert.IsDBNull(reader[0]))) - // { - // string tableName = reader[0].ToString(); - - // if (!_specialTableNames.Contains(tableName)) - // { - // yield return tableName; - // } - // } - // } - // } - // } - //} -} \ No newline at end of file diff --git a/src/Migrator.Providers/Utility/PostgreSqlServerUtility.cs b/src/Migrator.Providers/Utility/PostgreSqlServerUtility.cs deleted file mode 100644 index 1e0dc477..00000000 --- a/src/Migrator.Providers/Utility/PostgreSqlServerUtility.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Collections.Generic; -using System.Data; -using System.Linq; -//using Npgsql; - -namespace Migrator.Providers.Utility -{ - //public static class PostgreSqlServerUtility - //{ - // public static void RemoveAllTablesFromDefaultDatabase(string connectionString) - // { - // using (var connection = new NpgsqlConnection(connectionString)) - // { - // connection.Open(); - - // List tableNames = GetAllTableNames(connection).ToList(); - - // foreach (string table in tableNames) - // { - // using (var command = new NpgsqlCommand(string.Format("DROP TABLE IF EXISTS {0} CASCADE", table), connection)) - // { - // command.ExecuteNonQuery(); - // } - // } - - // connection.Close(); - // } - // } - - // static IEnumerable GetAllTableNames(NpgsqlConnection connection) - // { - // using (var command = new NpgsqlCommand("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'", connection)) - // { - // using (IDataReader reader = command.ExecuteReader(CommandBehavior.Default)) - // { - // while (reader.Read()) - // { - // yield return (string) reader[0]; - // } - // } - // } - // } - //} -} \ No newline at end of file diff --git a/src/Migrator.Providers/Utility/SqlServerUtility.cs b/src/Migrator.Providers/Utility/SqlServerUtility.cs deleted file mode 100644 index 54a06213..00000000 --- a/src/Migrator.Providers/Utility/SqlServerUtility.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Data; -using System.Data.SqlClient; - -namespace Migrator.Providers.Utility -{ - public static class SqlServerUtility - { - public static void RemoveAllTablesFromDefaultDatabase(string connectionString) - { - using (var connection = new SqlConnection(connectionString)) - { - connection.Open(); - RemoveAllForeignKeys(connection); - DropAllTables(connection); - connection.Close(); - } - } - - static void DropAllTables(SqlConnection connection) - { - ExecuteForEachTable(connection, "DROP TABLE ?"); - } - - static void RemoveAllForeignKeys(SqlConnection connection) - { - using ( - var dropConstraintsCommand = - new SqlCommand( - @"DECLARE @Sql NVARCHAR(500) DECLARE @Cursor CURSOR - -SET @Cursor = CURSOR FAST_FORWARD FOR - -SELECT DISTINCT sql = 'ALTER TABLE [' + tc2.TABLE_NAME + '] DROP [' + rc1.CONSTRAINT_NAME + ']' - -FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc1 - -LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc2 ON tc2.CONSTRAINT_NAME =rc1.CONSTRAINT_NAME - -OPEN @Cursor FETCH NEXT FROM @Cursor INTO @Sql - -WHILE (@@FETCH_STATUS = 0) - -BEGIN - -Exec sys.sp_executesql @Sql - -FETCH NEXT FROM @Cursor INTO @Sql - -END - -CLOSE @Cursor DEALLOCATE @Cursor", - connection)) - { - dropConstraintsCommand.CommandType = CommandType.Text; - dropConstraintsCommand.ExecuteNonQuery(); - } - } - - static void ExecuteForEachTable(SqlConnection connection, string command) - { - using (var forEachCommand = new SqlCommand("sp_MSforeachtable", connection)) - { - forEachCommand.CommandType = CommandType.StoredProcedure; - forEachCommand.Parameters.AddWithValue("@command1", command); - forEachCommand.ExecuteNonQuery(); - } - } - } -} \ No newline at end of file diff --git a/src/Migrator.Tests/ColumnPropertyMapperTest.cs b/src/Migrator.Tests/ColumnPropertyMapperTest.cs index 143a7c55..3add18d0 100644 --- a/src/Migrator.Tests/ColumnPropertyMapperTest.cs +++ b/src/Migrator.Tests/ColumnPropertyMapperTest.cs @@ -1,122 +1,121 @@ using System.Data; -using Migrator.Framework; -using Migrator.Providers; -using Migrator.Providers.Oracle; -using Migrator.Providers.PostgreSQL; -using Migrator.Providers.SQLite; -using Migrator.Providers.SqlServer; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers; +using DotNetProjects.Migrator.Providers.Impl.Oracle; +using DotNetProjects.Migrator.Providers.Impl.PostgreSQL; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using DotNetProjects.Migrator.Providers.Impl.SqlServer; using NUnit.Framework; -namespace Migrator.Tests +namespace Migrator.Tests; + +[TestFixture] +public class ColumnPropertyMapperTest { - [TestFixture] - public class ColumnPropertyMapperTest - { - [Test] - public void OracleCreatesNotNullSql() - { - var mapper = new ColumnPropertiesMapper(new OracleDialect(), "varchar(30)"); - mapper.MapColumnProperties(new Column("foo", DbType.String, ColumnProperty.NotNull)); - Assert.AreEqual("foo varchar(30) NOT NULL", mapper.ColumnSql); - } + [Test] + public void OracleCreatesNotNullSql() + { + var mapper = new ColumnPropertiesMapper(new OracleDialect(), "varchar(30)"); + mapper.MapColumnProperties(new Column("foo", DbType.String, ColumnProperty.NotNull)); + Assert.That("foo varchar(30) NOT NULL", Is.EqualTo(mapper.ColumnSql)); + } - [Test] - public void OracleCreatesSql() - { - var mapper = new ColumnPropertiesMapper(new OracleDialect(), "varchar(30)"); - mapper.MapColumnProperties(new Column("foo", DbType.String, 0)); - Assert.AreEqual("foo varchar(30)", mapper.ColumnSql); - } + [Test] + public void OracleCreatesSql() + { + var mapper = new ColumnPropertiesMapper(new OracleDialect(), "varchar(30)"); + mapper.MapColumnProperties(new Column("foo", DbType.String, 0)); + Assert.That("foo varchar(30)", Is.EqualTo(mapper.ColumnSql)); + } - [Test] - public void OracleIndexSqlIsNoNullWhenIndexed() - { - var mapper = new ColumnPropertiesMapper(new OracleDialect(), "char(1)"); - mapper.MapColumnProperties(new Column("foo", DbType.StringFixedLength, 1, ColumnProperty.Indexed)); - Assert.IsNotNull(mapper.IndexSql); - } + [Test] + public void OracleIndexSqlIsNoNullWhenIndexed() + { + var mapper = new ColumnPropertiesMapper(new OracleDialect(), "char(1)"); + mapper.MapColumnProperties(new Column("foo", DbType.StringFixedLength, 1, ColumnProperty.Indexed)); + Assert.That(mapper.IndexSql, Is.Not.Null); + } - [Test] - public void OracleIndexSqlIsNullWhenIndexedFalse() - { - var mapper = new ColumnPropertiesMapper(new OracleDialect(), "char(1)"); - mapper.MapColumnProperties(new Column("foo", DbType.StringFixedLength, 1, 0)); - Assert.IsNull(mapper.IndexSql); - } + [Test] + public void OracleIndexSqlIsNullWhenIndexedFalse() + { + var mapper = new ColumnPropertiesMapper(new OracleDialect(), "char(1)"); + mapper.MapColumnProperties(new Column("foo", DbType.StringFixedLength, 1, 0)); + Assert.That(mapper.IndexSql, Is.Null); + } - [Test] - public void PostgresIndexSqlIsNoNullWhenIndexed() - { - var mapper = new ColumnPropertiesMapper(new PostgreSQLDialect(), "char(1)"); - mapper.MapColumnProperties(new Column("foo", DbType.StringFixedLength, 1, ColumnProperty.Indexed)); - Assert.IsNotNull(mapper.IndexSql); - } + [Test] + public void PostgresIndexSqlIsNoNullWhenIndexed() + { + var mapper = new ColumnPropertiesMapper(new PostgreSQLDialect(), "char(1)"); + mapper.MapColumnProperties(new Column("foo", DbType.StringFixedLength, 1, ColumnProperty.Indexed)); + Assert.That(mapper.IndexSql, Is.Not.Null); + } - [Test] - public void PostgresIndexSqlIsNullWhenIndexedFalse() - { - var mapper = new ColumnPropertiesMapper(new PostgreSQLDialect(), "char(1)"); - mapper.MapColumnProperties(new Column("foo", DbType.StringFixedLength, 1, 0)); - Assert.IsNull(mapper.IndexSql); - } + [Test] + public void PostgresIndexSqlIsNullWhenIndexedFalse() + { + var mapper = new ColumnPropertiesMapper(new PostgreSQLDialect(), "char(1)"); + mapper.MapColumnProperties(new Column("foo", DbType.StringFixedLength, 1, 0)); + Assert.That(mapper.IndexSql, Is.Null); + } - [Test] - public void SqlServerCreatesNotNullSql() - { - var mapper = new ColumnPropertiesMapper(new SqlServerDialect(), "varchar(30)"); - mapper.MapColumnProperties(new Column("foo", DbType.String, ColumnProperty.NotNull)); - Assert.AreEqual("[foo] varchar(30) NOT NULL", mapper.ColumnSql); - } + [Test] + public void SqlServerCreatesNotNullSql() + { + var mapper = new ColumnPropertiesMapper(new SqlServerDialect(), "varchar(30)"); + mapper.MapColumnProperties(new Column("foo", DbType.String, ColumnProperty.NotNull)); + Assert.That("[foo] varchar(30) NOT NULL", Is.EqualTo(mapper.ColumnSql)); + } - [Test] - public void SqlServerCreatesSqWithBooleanDefault() - { - var mapper = new ColumnPropertiesMapper(new SqlServerDialect(), "bit"); - mapper.MapColumnProperties(new Column("foo", DbType.Boolean, 0, false)); - Assert.AreEqual("[foo] bit DEFAULT 0", mapper.ColumnSql); + [Test] + public void SqlServerCreatesSqWithBooleanDefault() + { + var mapper = new ColumnPropertiesMapper(new SqlServerDialect(), "bit"); + mapper.MapColumnProperties(new Column("foo", DbType.Boolean, 0, false)); + Assert.That("[foo] bit DEFAULT 0", Is.EqualTo(mapper.ColumnSql)); - mapper.MapColumnProperties(new Column("bar", DbType.Boolean, 0, true)); - Assert.AreEqual("[bar] bit DEFAULT 1", mapper.ColumnSql); - } + mapper.MapColumnProperties(new Column("bar", DbType.Boolean, 0, true)); + Assert.That("[bar] bit DEFAULT 1", Is.EqualTo(mapper.ColumnSql)); + } - [Test] - public void SqlServerCreatesSqWithDefault() - { - var mapper = new ColumnPropertiesMapper(new SqlServerDialect(), "varchar(30)"); - mapper.MapColumnProperties(new Column("foo", DbType.String, 0, "'NEW'")); - Assert.AreEqual("[foo] varchar(30) DEFAULT '''NEW'''", mapper.ColumnSql); - } + [Test] + public void SqlServerCreatesSqWithDefault() + { + var mapper = new ColumnPropertiesMapper(new SqlServerDialect(), "varchar(30)"); + mapper.MapColumnProperties(new Column("foo", DbType.String, 0, "'NEW'")); + Assert.That("[foo] varchar(30) DEFAULT '''NEW'''", Is.EqualTo(mapper.ColumnSql)); + } - [Test] - public void SqlServerCreatesSqWithNullDefault() - { - var mapper = new ColumnPropertiesMapper(new SqlServerDialect(), "varchar(30)"); - mapper.MapColumnProperties(new Column("foo", DbType.String, 0, "NULL")); - Assert.AreEqual("[foo] varchar(30) DEFAULT 'NULL'", mapper.ColumnSql); - } + [Test] + public void SqlServerCreatesSqWithNullDefault() + { + var mapper = new ColumnPropertiesMapper(new SqlServerDialect(), "varchar(30)"); + mapper.MapColumnProperties(new Column("foo", DbType.String, 0, "NULL")); + Assert.That("[foo] varchar(30) DEFAULT 'NULL'", Is.EqualTo(mapper.ColumnSql)); + } - [Test] - public void SqlServerCreatesSql() - { - var mapper = new ColumnPropertiesMapper(new SqlServerDialect(), "varchar(30)"); - mapper.MapColumnProperties(new Column("foo", DbType.String, 0)); - Assert.AreEqual("[foo] varchar(30)", mapper.ColumnSql); - } + [Test] + public void SqlServerCreatesSql() + { + var mapper = new ColumnPropertiesMapper(new SqlServerDialect(), "varchar(30)"); + mapper.MapColumnProperties(new Column("foo", DbType.String, 0)); + Assert.That("[foo] varchar(30)", Is.EqualTo(mapper.ColumnSql)); + } - [Test] - public void SqlServerIndexSqlIsNoNullWhenIndexed() - { - var mapper = new ColumnPropertiesMapper(new SqlServerDialect(), "char(1)"); - mapper.MapColumnProperties(new Column("foo", DbType.StringFixedLength, 1, ColumnProperty.Indexed)); - Assert.IsNull(mapper.IndexSql); - } + [Test] + public void SqlServerIndexSqlIsNoNullWhenIndexed() + { + var mapper = new ColumnPropertiesMapper(new SqlServerDialect(), "char(1)"); + mapper.MapColumnProperties(new Column("foo", DbType.StringFixedLength, 1, ColumnProperty.Indexed)); + Assert.That(mapper.IndexSql, Is.Null); + } - [Test] - public void SQLiteIndexSqlWithEmptyStringDefault() - { - var mapper = new ColumnPropertiesMapper(new SQLiteDialect(), "varchar(30)"); - mapper.MapColumnProperties(new Column("foo", DbType.String, 1, ColumnProperty.NotNull, string.Empty)); - Assert.AreEqual("foo varchar(30) NOT NULL DEFAULT ''", mapper.ColumnSql); - } - } + [Test] + public void SQLiteIndexSqlWithEmptyStringDefault() + { + var mapper = new ColumnPropertiesMapper(new SQLiteDialect(), "varchar(30)"); + mapper.MapColumnProperties(new Column("foo", DbType.String, 1, ColumnProperty.NotNull, string.Empty)); + Assert.That("foo varchar(30) NOT NULL DEFAULT ''", Is.EqualTo(mapper.ColumnSql)); + } } \ No newline at end of file diff --git a/src/Migrator.Tests/Data/TestMigrations.cs b/src/Migrator.Tests/Data/TestMigrations.cs index 7a3efcfb..a6541559 100644 --- a/src/Migrator.Tests/Data/TestMigrations.cs +++ b/src/Migrator.Tests/Data/TestMigrations.cs @@ -1,67 +1,66 @@ -using Migrator.Framework; +using DotNetProjects.Migrator.Framework; -namespace Migrator.Tests.Data +namespace Migrator.Tests.Data; + +[Migration(1)] +public class FirstTestMigration : Migration { - [Migration(1)] - public class FirstTestMigration : Migration - { - public override void Up() - { - } + public override void Up() + { + } - public override void Down() - { - } - } + public override void Down() + { + } +} - [Migration(2)] - public class SecondTestMigration : IMigration - { - public string Name - { - get { return StringUtils.ToHumanName(GetType().Name); } - } +[Migration(2)] +public class SecondTestMigration : IMigration +{ + public string Name + { + get { return StringUtils.ToHumanName(GetType().Name); } + } - /// - /// Defines tranformations to port the database to the current version. - /// - public void Up() - { - } + /// + /// Defines tranformations to port the database to the current version. + /// + public void Up() + { + } - /// - /// This is run after the Up transaction has been committed - /// - public virtual void AfterUp() - { - } + /// + /// This is run after the Up transaction has been committed + /// + public virtual void AfterUp() + { + } - /// - /// Defines transformations to revert things done in Up. - /// - public void Down() - { - } + /// + /// Defines transformations to revert things done in Up. + /// + public void Down() + { + } - /// - /// This is run after the Down transaction has been committed - /// - public virtual void AfterDown() - { - } + /// + /// This is run after the Down transaction has been committed + /// + public virtual void AfterDown() + { + } - /// - /// Represents the database. - /// . - /// - /// Migration.Framework.ITransformationProvider - public ITransformationProvider Database { get; set; } + /// + /// Represents the database. + /// . + /// + /// Migration.Framework.ITransformationProvider + public ITransformationProvider Database { get; set; } - /// - /// This gets called once on the first migration object. - /// - public virtual void InitializeOnce(string[] args) - { - } - } -} \ No newline at end of file + /// + /// This gets called once on the first migration object. + /// + public virtual void InitializeOnce(string[] args) + { + } +} diff --git a/src/Migrator.Tests/Database/Data/Common/EntityConfiguration.cs b/src/Migrator.Tests/Database/Data/Common/EntityConfiguration.cs new file mode 100644 index 00000000..2b3a5f18 --- /dev/null +++ b/src/Migrator.Tests/Database/Data/Common/EntityConfiguration.cs @@ -0,0 +1,15 @@ +using LinqToDB.Mapping; +using Migrator.Tests.Database.Data.Common.Interfaces; + +namespace Migrator.Tests.Database.Data.Common; + +public abstract class EntityConfiguration(FluentMappingBuilder fluentMappingBuilder) : IEntityConfiguration where T : class +{ + protected EntityMappingBuilder _EntityMappingBuilder = fluentMappingBuilder.Entity(); + protected FluentMappingBuilder _FluentMappingBuilder = fluentMappingBuilder; + + /// + /// Configures the entity in the fluent migrator of Linq2db + /// + public abstract void ConfigureEntity(); +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/Data/Common/Interfaces/IEntityConfiguration.cs b/src/Migrator.Tests/Database/Data/Common/Interfaces/IEntityConfiguration.cs new file mode 100644 index 00000000..7ada7a23 --- /dev/null +++ b/src/Migrator.Tests/Database/Data/Common/Interfaces/IEntityConfiguration.cs @@ -0,0 +1,9 @@ +namespace Migrator.Tests.Database.Data.Common.Interfaces; + +public interface IEntityConfiguration +{ + /// + /// Configure the entity mapping. + /// + void ConfigureEntity(); +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/Data/Common/Interfaces/IMappingSchemaFactory.cs b/src/Migrator.Tests/Database/Data/Common/Interfaces/IMappingSchemaFactory.cs new file mode 100644 index 00000000..7fb68e3b --- /dev/null +++ b/src/Migrator.Tests/Database/Data/Common/Interfaces/IMappingSchemaFactory.cs @@ -0,0 +1,8 @@ +using LinqToDB.Mapping; + +namespace Migrator.Tests.Database.Data.Common.Interfaces; + +public interface IMappingSchemaFactory +{ + MappingSchema CreateOracleMappingSchema(); +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/Data/Common/MappingSchemaFactory.cs b/src/Migrator.Tests/Database/Data/Common/MappingSchemaFactory.cs new file mode 100644 index 00000000..e044f899 --- /dev/null +++ b/src/Migrator.Tests/Database/Data/Common/MappingSchemaFactory.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using LinqToDB.Mapping; +using Migrator.Tests.Database.Data.Common.Interfaces; +using Migrator.Tests.Database.Data.Mappings.Oracle; + +namespace DotNetProjects.Migrator.Framework.Data.Common; + +public class MappingSchemaFactory() : IMappingSchemaFactory +{ + public MappingSchema CreateOracleMappingSchema() + { + var fluentMappingBuilder = new FluentMappingBuilder(); + + var configs = new List + { + new OracleAllConsColumnsConfiguration(fluentMappingBuilder), + new OracleAllConstraintsConfiguration(fluentMappingBuilder), + new OracleAllTabColumnsConfiguration(fluentMappingBuilder), + new OracleAllTabColumnsConfiguration(fluentMappingBuilder), + new OracleAllUsersConfiguration(fluentMappingBuilder), + new OracleDBADataFilesConfiguration(fluentMappingBuilder), + new OracleVSessionConfiguration(fluentMappingBuilder), + }; + + return Configure(fluentMappingBuilder, configs); + } + + private static MappingSchema Configure(FluentMappingBuilder fluentMappingBuilder, IEnumerable entityConfigurations) + { + foreach (var config in entityConfigurations) + { + config.ConfigureEntity(); + } + + fluentMappingBuilder.Build(); + + return fluentMappingBuilder.MappingSchema; + } +} diff --git a/src/Migrator.Tests/Database/Data/Mappings/Oracle/OracleAllConsColumnsConfiguration.cs b/src/Migrator.Tests/Database/Data/Mappings/Oracle/OracleAllConsColumnsConfiguration.cs new file mode 100644 index 00000000..ea016267 --- /dev/null +++ b/src/Migrator.Tests/Database/Data/Mappings/Oracle/OracleAllConsColumnsConfiguration.cs @@ -0,0 +1,29 @@ +using DotNetProjects.Migrator.Framework.Data.Models.Oracle; +using LinqToDB.Mapping; +using Migrator.Tests.Database.Data.Common; + +namespace Migrator.Tests.Database.Data.Mappings.Oracle; + +public class OracleAllConsColumnsConfiguration(FluentMappingBuilder fluentMappingBuilder) + : EntityConfiguration(fluentMappingBuilder) +{ + public override void ConfigureEntity() + { + _EntityMappingBuilder!.HasTableName("ALL_CONS_COLUMNS"); + + _EntityMappingBuilder.Property(x => x.ColumnName) + .HasColumnName("COLUMN_NAME"); + + _EntityMappingBuilder.Property(x => x.ConstraintName) + .HasColumnName("CONSTRAINT_NAME"); + + _EntityMappingBuilder.Property(x => x.Owner) + .HasColumnName("OWNER"); + + _EntityMappingBuilder.Property(x => x.Position) + .HasColumnName("POSITION"); + + _EntityMappingBuilder.Property(x => x.TableName) + .HasColumnName("TABLE_NAME"); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/Data/Mappings/Oracle/OracleAllConstraintsConfiguration.cs b/src/Migrator.Tests/Database/Data/Mappings/Oracle/OracleAllConstraintsConfiguration.cs new file mode 100644 index 00000000..88c22e53 --- /dev/null +++ b/src/Migrator.Tests/Database/Data/Mappings/Oracle/OracleAllConstraintsConfiguration.cs @@ -0,0 +1,35 @@ +using DotNetProjects.Migrator.Framework.Data.Models.Oracle; +using LinqToDB.Mapping; +using Migrator.Tests.Database.Data.Common; + +namespace Migrator.Tests.Database.Data.Mappings.Oracle; + +public class OracleAllConstraintsConfiguration(FluentMappingBuilder fluentMappingBuilder) + : EntityConfiguration(fluentMappingBuilder) +{ + public override void ConfigureEntity() + { + _EntityMappingBuilder!.HasTableName("ALL_CONSTRAINTS"); + + _EntityMappingBuilder.Property(x => x.ConstraintName) + .HasColumnName("CONSTRAINT_NAME"); + + _EntityMappingBuilder.Property(x => x.RConstraintName) + .HasColumnName("R_CONSTRAINT_NAME"); + + _EntityMappingBuilder.Property(x => x.ROwner) + .HasColumnName("R_OWNER"); + + _EntityMappingBuilder.Property(x => x.ConstraintType) + .HasColumnName("CONSTRAINT_TYPE"); + + _EntityMappingBuilder.Property(x => x.Owner) + .HasColumnName("OWNER"); + + _EntityMappingBuilder.Property(x => x.Status) + .HasColumnName("STATUS"); + + _EntityMappingBuilder.Property(x => x.TableName) + .HasColumnName("TABLE_NAME"); + } +} diff --git a/src/Migrator.Tests/Database/Data/Mappings/Oracle/OracleAllTabColumnsConfiguration.cs b/src/Migrator.Tests/Database/Data/Mappings/Oracle/OracleAllTabColumnsConfiguration.cs new file mode 100644 index 00000000..da72fa1a --- /dev/null +++ b/src/Migrator.Tests/Database/Data/Mappings/Oracle/OracleAllTabColumnsConfiguration.cs @@ -0,0 +1,38 @@ +using DotNetProjects.Migrator.Framework.Data.Models.Oracle; +using LinqToDB.Mapping; +using Migrator.Tests.Database.Data.Common; + +namespace Migrator.Tests.Database.Data.Mappings.Oracle; + +public class OracleAllTabColumnsConfiguration(FluentMappingBuilder fluentMappingBuilder) + : EntityConfiguration(fluentMappingBuilder) +{ + public override void ConfigureEntity() + { + _EntityMappingBuilder!.HasTableName("ALL_TAB_COLUMNS"); + + _EntityMappingBuilder.Property(x => x.ColumnName) + .HasColumnName("COLUMN_NAME"); + + _EntityMappingBuilder.Property(x => x.DataDefault) + .HasColumnName("DATA_DEFAULT"); + + _EntityMappingBuilder.Property(x => x.DataLength) + .HasColumnName("DATA_LENGTH"); + + _EntityMappingBuilder.Property(x => x.DataType) + .HasColumnName("DATA_TYPE"); + + _EntityMappingBuilder.Property(x => x.IdentityColumn) + .HasColumnName("IDENTITY_COLUMN"); + + _EntityMappingBuilder.Property(x => x.Nullable) + .HasColumnName("NULLABLE"); + + _EntityMappingBuilder.Property(x => x.Owner) + .HasColumnName("OWNER"); + + _EntityMappingBuilder.Property(x => x.TableName) + .HasColumnName("TABLE_NAME"); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/Data/Mappings/Oracle/OracleAllUsersConfiguration.cs b/src/Migrator.Tests/Database/Data/Mappings/Oracle/OracleAllUsersConfiguration.cs new file mode 100644 index 00000000..acdb6c69 --- /dev/null +++ b/src/Migrator.Tests/Database/Data/Mappings/Oracle/OracleAllUsersConfiguration.cs @@ -0,0 +1,17 @@ +using DotNetProjects.Migrator.Framework.Data.Models.Oracle; +using LinqToDB.Mapping; +using Migrator.Tests.Database.Data.Common; + +namespace Migrator.Tests.Database.Data.Mappings.Oracle; + +public class OracleAllUsersConfiguration(FluentMappingBuilder fluentMappingBuilder) + : EntityConfiguration(fluentMappingBuilder) +{ + public override void ConfigureEntity() + { + _EntityMappingBuilder!.HasTableName("ALL_USERS"); + + _EntityMappingBuilder.Property(x => x.UserName) + .HasColumnName("USERNAME"); + } +} diff --git a/src/Migrator.Tests/Database/Data/Mappings/Oracle/OracleDBADataFilesConfiguration.cs b/src/Migrator.Tests/Database/Data/Mappings/Oracle/OracleDBADataFilesConfiguration.cs new file mode 100644 index 00000000..f7e5b767 --- /dev/null +++ b/src/Migrator.Tests/Database/Data/Mappings/Oracle/OracleDBADataFilesConfiguration.cs @@ -0,0 +1,20 @@ +using DotNetProjects.Migrator.Framework.Data.Models.Oracle; +using LinqToDB.Mapping; +using Migrator.Tests.Database.Data.Common; + +namespace Migrator.Tests.Database.Data.Mappings.Oracle; + +public class OracleDBADataFilesConfiguration(FluentMappingBuilder fluentMappingBuilder) + : EntityConfiguration(fluentMappingBuilder) +{ + public override void ConfigureEntity() + { + _EntityMappingBuilder!.HasTableName("DBA_DATA_FILES"); + + _EntityMappingBuilder.Property(x => x.FileName) + .HasColumnName("FILE_NAME"); + + _EntityMappingBuilder.Property(x => x.TablespaceName) + .HasColumnName("TABLESPACE_NAME"); + } +} diff --git a/src/Migrator.Tests/Database/Data/Mappings/Oracle/OracleVSessionConfiguration.cs b/src/Migrator.Tests/Database/Data/Mappings/Oracle/OracleVSessionConfiguration.cs new file mode 100644 index 00000000..a5a08343 --- /dev/null +++ b/src/Migrator.Tests/Database/Data/Mappings/Oracle/OracleVSessionConfiguration.cs @@ -0,0 +1,23 @@ +using DotNetProjects.Migrator.Framework.Data.Models.Oracle; +using LinqToDB.Mapping; +using Migrator.Tests.Database.Data.Common; + +namespace Migrator.Tests.Database.Data.Mappings.Oracle; + +public class OracleVSessionConfiguration(FluentMappingBuilder fluentMappingBuilder) + : EntityConfiguration(fluentMappingBuilder) +{ + public override void ConfigureEntity() + { + _EntityMappingBuilder!.HasTableName("V$SESSION"); + + _EntityMappingBuilder.Property(x => x.SerialHashTag) + .HasColumnName("SERIAL#"); + + _EntityMappingBuilder.Property(x => x.SID) + .HasColumnName("SID"); + + _EntityMappingBuilder.Property(x => x.UserName) + .HasColumnName("USERNAME"); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/Data/Models/Oracle/AllConsColumns.cs b/src/Migrator.Tests/Database/Data/Models/Oracle/AllConsColumns.cs new file mode 100644 index 00000000..cd75ffc7 --- /dev/null +++ b/src/Migrator.Tests/Database/Data/Models/Oracle/AllConsColumns.cs @@ -0,0 +1,32 @@ +namespace DotNetProjects.Migrator.Framework.Data.Models.Oracle; + +/// +/// Represents the Oracle system table ALL_CONS_COLUMNS +/// +public class AllConsColumns +{ + /// + /// Gets or sets the column name. + /// + public string ColumnName { get; set; } + + /// + /// Gets or sets the name of the constraint definition. + /// + public string ConstraintName { get; set; } + + /// + /// Gets or sets + /// + public int Position { get; set; } + + /// + /// Gets or sets the name of the table with the constraint definition. + /// + public string TableName { get; set; } + + /// + /// Gets or sets the owner of the constraint definition. + /// + public string Owner { get; set; } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/Data/Models/Oracle/AllConstraints.cs b/src/Migrator.Tests/Database/Data/Models/Oracle/AllConstraints.cs new file mode 100644 index 00000000..cae0f3aa --- /dev/null +++ b/src/Migrator.Tests/Database/Data/Models/Oracle/AllConstraints.cs @@ -0,0 +1,55 @@ +namespace DotNetProjects.Migrator.Framework.Data.Models.Oracle; + +/// +/// Represents the Oracle system table ALL_CONSTRAINTS +/// +public class AllConstraints +{ + /// + /// Gets or sets the name of the constraint definition. + /// + public string ConstraintName { get; set; } + + /// + /// Gets or sets the name of the unique constraint definition for the referenced table (R_CONSTRAINT_NAME) + /// + public string RConstraintName { get; set; } + + /// + /// Gets or sets the constraint type. + /// + /// Type of the constraint definition: + /// + /// C - Check constraint on a table + /// P - Primary key + /// U - Unique key + /// R - Referential integrity + /// V - With check option, on a view + /// O - With read only, on a view + /// H - Hash expression + /// F - Constraint that involves a REF column + /// S - Supplemental logging + /// + /// + public string ConstraintType { get; set; } + + /// + /// Gets or sets the owner of the constraint definition. + /// + public string Owner { get; set; } + + /// + /// Gets or sets the owner of the table referred to in a referential constraint (R_OWNER) + /// + public string ROwner { get; set; } + + /// + /// Gets or set the status. Enforcement status of the constraint: ENABLED / DISABLED + /// + public string Status { get; set; } + + /// + /// Gets or sets the name associated with the table (or view) with the constraint definition. + /// + public string TableName { get; set; } +} diff --git a/src/Migrator.Tests/Database/Data/Models/Oracle/AllTabColumns.cs b/src/Migrator.Tests/Database/Data/Models/Oracle/AllTabColumns.cs new file mode 100644 index 00000000..c9fa72e8 --- /dev/null +++ b/src/Migrator.Tests/Database/Data/Models/Oracle/AllTabColumns.cs @@ -0,0 +1,48 @@ +namespace DotNetProjects.Migrator.Framework.Data.Models.Oracle; + +/// +/// Represents the Oracle system table ALL_TAB_COLUMNS +/// +public class AllTabColumns +{ + /// + /// Gets or sets the column name + /// + public string ColumnName { get; set; } + + /// + /// Gets or sets the DATA_DEFAULT. This returns sth. like "SCHEMA"."ISEQ$$_1234".nextval + /// + public string DataDefault { get; set; } + + /// + /// Gets or sets the length of the column (in bytes) + /// + public string DataLength { get; set; } + + /// + /// Gets or sets the data type of the column + /// + public string DataType { get; set; } + + /// + /// Indicates whether this is an identity column (YES) or not (NO) + /// + public string IdentityColumn { get; set; } + + /// + /// Indicates whether a column allows NULLs. The value is N if there is a NOT NULL constraint on the column or if the column is part of a + /// PRIMARY KEY. The constraint should be in an ENABLE VALIDATE state. + /// + public string Nullable { get; set; } + + /// + /// Gets or sets the name of the table, view, or cluster + /// + public string TableName { get; set; } + + /// + /// Gets or sets the owner of the table, view, or cluster + /// + public string Owner { get; set; } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/Data/Models/Oracle/AllUsers.cs b/src/Migrator.Tests/Database/Data/Models/Oracle/AllUsers.cs new file mode 100644 index 00000000..eadf4c6d --- /dev/null +++ b/src/Migrator.Tests/Database/Data/Models/Oracle/AllUsers.cs @@ -0,0 +1,12 @@ +namespace DotNetProjects.Migrator.Framework.Data.Models.Oracle; + +/// +/// Represents the Oracle system table ALL_USERS. +/// +public class AllUsers +{ + /// + /// Gets or sets the name of the user. + /// + public string UserName { get; set; } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/Data/Models/Oracle/DBADataFiles.cs b/src/Migrator.Tests/Database/Data/Models/Oracle/DBADataFiles.cs new file mode 100644 index 00000000..a7aa3775 --- /dev/null +++ b/src/Migrator.Tests/Database/Data/Models/Oracle/DBADataFiles.cs @@ -0,0 +1,17 @@ +namespace DotNetProjects.Migrator.Framework.Data.Models.Oracle; + +/// +/// Represents the Oracle system table DBA_DATA_FILES. +/// +public class DBADataFiles +{ + /// + /// Gets or sets the file name. (FILE_NAME) + /// + public string FileName { get; set; } + + /// + /// Gets or sets the tablespace name. (TABLESPACE_NAME) + /// + public string TablespaceName { get; set; } +} diff --git a/src/Migrator.Tests/Database/Data/Models/Oracle/VSession.cs b/src/Migrator.Tests/Database/Data/Models/Oracle/VSession.cs new file mode 100644 index 00000000..a3709d11 --- /dev/null +++ b/src/Migrator.Tests/Database/Data/Models/Oracle/VSession.cs @@ -0,0 +1,19 @@ +namespace DotNetProjects.Migrator.Framework.Data.Models.Oracle; + +public class VSession +{ + /// + /// Gets or sets the "serial#" + /// + public string SerialHashTag { get; set; } + + /// + /// Gets or sets the session id (SID). + /// + public string SID { get; set; } + + /// + /// Gets or sets the user name. + /// + public string UserName { get; set; } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/DatabaseIntegrationTestServiceBase.cs b/src/Migrator.Tests/Database/DatabaseIntegrationTestServiceBase.cs new file mode 100644 index 00000000..00450410 --- /dev/null +++ b/src/Migrator.Tests/Database/DatabaseIntegrationTestServiceBase.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Migrator.Tests.Database.DatabaseName.Interfaces; +using Migrator.Tests.Database.Interfaces; +using Migrator.Tests.Database.Models; +using Migrator.Tests.Settings.Models; + +namespace Migrator.Tests.Database; + +public abstract class DatabaseIntegrationTestServiceBase(IDatabaseNameService databaseNameService) : IDatabaseIntegrationTestService +{ + /// + /// Deletes all integration test databases older than the given time span. + /// + // TODO CK time span! + protected readonly TimeSpan _MinTimeSpanBeforeDatabaseDeletion = TimeSpan.FromMinutes(1); // TimeSpan.FromMinutes(60); + + protected IDatabaseNameService DatabaseNameService { get; private set; } = databaseNameService; + + abstract public Task CreateTestDatabaseAsync(DatabaseConnectionConfig databaseConnectionConfig, CancellationToken cancellationToken); + + abstract public Task DropDatabaseAsync(DatabaseInfo databaseInfo, CancellationToken cancellationToken); + + protected DateTime ReadTimeStampFromDatabaseName(string name) + { + var creationDate = DatabaseNameService.ReadTimeStampFromString(name); + + if (!creationDate.HasValue) + { + throw new Exception("You tried to drop a database that was not created by this service. For safety reasons we deny your request."); + } + + return creationDate.Value; + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/DatabaseIntegrationTestServiceFactory.cs b/src/Migrator.Tests/Database/DatabaseIntegrationTestServiceFactory.cs new file mode 100644 index 00000000..424f5a27 --- /dev/null +++ b/src/Migrator.Tests/Database/DatabaseIntegrationTestServiceFactory.cs @@ -0,0 +1,12 @@ +using DryIoc; +using Migrator.Tests.Database.Interfaces; + +namespace Migrator.Tests.Database; + +public class DatabaseIntegrationTestServiceFactory(IResolver resolver) : IDatabaseIntegrationTestServiceFactory +{ + public IDatabaseIntegrationTestService Create(DatabaseProviderType providerType) + { + return resolver.Resolve(serviceKey: providerType); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/DatabaseIntegrationTestServiceRegistry.cs b/src/Migrator.Tests/Database/DatabaseIntegrationTestServiceRegistry.cs new file mode 100644 index 00000000..268fd50a --- /dev/null +++ b/src/Migrator.Tests/Database/DatabaseIntegrationTestServiceRegistry.cs @@ -0,0 +1,28 @@ +using Migrator.Tests.Database.DatabaseName.Interfaces; +using Migrator.Tests.Database.GuidServices.Interfaces; +using Migrator.Tests.Database.GuidServices; +using Migrator.Tests.Database.Interfaces; +using Migrator.Tests.Database.DerivedDatabaseIntegrationTestServices; +using System; +using DryIoc; +using Migrator.Test.Shared.Database; +using Migrator.Tests.Settings.Interfaces; +using Migrator.Tests.Settings; + +namespace Migrator.Tests.Database; + +public static class DatabaseCreationServiceRegistry +{ + public static void RegisterDatabaseIntegrationTestService(this IRegistrator container) + { + container.Register(reuse: Reuse.Transient); + container.Register(reuse: Reuse.Transient); + container.RegisterInstance(TimeProvider.System, ifAlreadyRegistered: IfAlreadyRegistered.Keep); + container.Register(reuse: Reuse.Transient, ifAlreadyRegistered: IfAlreadyRegistered.Keep); + container.Register(serviceKey: DatabaseProviderType.Oracle); + container.Register(serviceKey: DatabaseProviderType.SQLite); + container.Register(serviceKey: DatabaseProviderType.Postgres); + container.Register(serviceKey: DatabaseProviderType.SQLServer); + container.Register(reuse: Reuse.Singleton, ifAlreadyRegistered: IfAlreadyRegistered.Keep); + } +} diff --git a/src/Migrator.Tests/Database/DatabaseName/DatabaseNameService.cs b/src/Migrator.Tests/Database/DatabaseName/DatabaseNameService.cs new file mode 100644 index 00000000..505e396c --- /dev/null +++ b/src/Migrator.Tests/Database/DatabaseName/DatabaseNameService.cs @@ -0,0 +1,56 @@ +using System; +using System.Globalization; +using System.IO; +using System.Security.Cryptography; +using System.Text.RegularExpressions; +using Migrator.Tests.Database.DatabaseName.Interfaces; + +namespace Migrator.Test.Shared.Database; + +public partial class DatabaseNameService(TimeProvider timeProvider) : IDatabaseNameService +{ + private const string TestDatabaseString = "T"; + private const string TimeStampPattern = "yyyyMMddHHmmssfff"; + + public DateTime? ReadTimeStampFromString(string name) + { + name = Path.GetFileNameWithoutExtension(name); + + var regex = DateTimeRegex(); + var match = regex.Match(name); + + if (match.Success && DateTime.TryParseExact(match.Value, TimeStampPattern, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var res)) + { + return res; + } + + return null; + } + + public string CreateDatabaseName() + { + var dateTimePattern = timeProvider.GetUtcNow() + .ToString(TimeStampPattern); + + var randomString = CreateRandomChars(7); + + return $"{dateTimePattern}{TestDatabaseString}{randomString}"; + } + + private string CreateRandomChars(int length) + { + var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + var stringChars = new char[length]; + + for (var i = 0; i < length; i++) + { + var index = RandomNumberGenerator.GetInt32(chars.Length); + stringChars[i] = chars[index]; + } + + return new string(stringChars); + } + + [GeneratedRegex(@"^([\d]+)(?=T.{7}$)")] + private static partial Regex DateTimeRegex(); +} diff --git a/src/Migrator.Tests/Database/DatabaseName/Interfaces/IDatabaseNameService.cs b/src/Migrator.Tests/Database/DatabaseName/Interfaces/IDatabaseNameService.cs new file mode 100644 index 00000000..c5be42e6 --- /dev/null +++ b/src/Migrator.Tests/Database/DatabaseName/Interfaces/IDatabaseNameService.cs @@ -0,0 +1,22 @@ +using System; + +namespace Migrator.Tests.Database.DatabaseName.Interfaces; + +/// +/// Used for integration tests. During integration tests we need to create unique database names for parallel testing. +/// +public interface IDatabaseNameService +{ + /// + /// Reads the date time from the date part of the database or user name (in Oracle we use the user name/schema name). + /// + /// + /// + DateTime? ReadTimeStampFromString(string name); + + /// + /// Creates a database name + /// + /// + string CreateDatabaseName(); +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/DatabaseProviderType.cs b/src/Migrator.Tests/Database/DatabaseProviderType.cs new file mode 100644 index 00000000..e477f508 --- /dev/null +++ b/src/Migrator.Tests/Database/DatabaseProviderType.cs @@ -0,0 +1,21 @@ +namespace Migrator.Tests.Database; + +public enum DatabaseProviderType +{ + // Do not use in any case not even as default + None = 0, + + Unknown, + + // Postgre SQL + Postgres, + + // SQL Server + SQLServer, + + // SQLite + SQLite, + + // Oracle + Oracle +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/DerivedDatabaseIntegrationTestServices/OracleDatabaseIntegrationTestService.cs b/src/Migrator.Tests/Database/DerivedDatabaseIntegrationTestServices/OracleDatabaseIntegrationTestService.cs new file mode 100644 index 00000000..6d41ace4 --- /dev/null +++ b/src/Migrator.Tests/Database/DerivedDatabaseIntegrationTestServices/OracleDatabaseIntegrationTestService.cs @@ -0,0 +1,210 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Framework.Data.Common; +using DotNetProjects.Migrator.Framework.Data.Models.Oracle; +using LinqToDB; +using LinqToDB.Async; +using LinqToDB.Data; +using LinqToDB.Mapping; +using Mapster; +using Migrator.Tests.Database.DatabaseName.Interfaces; +using Migrator.Tests.Database.Interfaces; +using Migrator.Tests.Database.Models; +using Migrator.Tests.Settings.Models; +using Oracle.ManagedDataAccess.Client; + +namespace Migrator.Tests.Database.DerivedDatabaseIntegrationTestServices; + + +/// +/// We use the tablespace users since the server container is recreated before the test runs (once per github workflow run) +/// +/// +/// +public class OracleDatabaseIntegrationTestService( + TimeProvider timeProvider, + IDatabaseNameService databaseNameService) + : DatabaseIntegrationTestServiceBase(databaseNameService), IDatabaseIntegrationTestService +{ + private const string UserStringKey = "User Id"; + private const string PasswordStringKey = "Password"; + private const string ReplaceString = "RandomStringThatIsNotQuotedByTheBuilderDoNotChange"; + private readonly MappingSchema _mappingSchema = new MappingSchemaFactory().CreateOracleMappingSchema(); + + /// + /// Creates an oracle database for test purposes. + /// + /// + /// For the creation of the Oracle user used in this method follow these steps: + /// + /// Use a SYSDBA user, connect or switch to the default PDB. + /// On the free docker container the name of the default PDB is "FREEPDB1" use it as the service name or alternatively switch containers. For installations other than the "FREE" Oracle + /// Docker image find out the (default) PDB and switch to it then create grant privileges listed below. Having all set you can create a connection string using the newly created user + /// and password and add it to appsettings.Development (for dev environment) + /// + /// ALTER SESSION SET CONTAINER = FREEPDB1 + /// CREATE USER myuser IDENTIFIED BY mypassword + /// GRANT CREATE USER TO myuser + /// GRANT DROP USER TO myuser + /// GRANT CREATE SESSION TO myuser WITH ADMIN OPTION + /// GRANT RESOURCE TO myuser WITH ADMIN OPTION + /// GRANT CONNECT TO myuser WITH ADMIN OPTION + /// GRANT UNLIMITED TABLESPACE TO myuser with ADMIN OPTION + /// GRANT SELECT ON V_$SESSION TO myuser with GRANT OPTION + /// GRANT ALTER SYSTEM TO myuser + /// + /// Having all set you can create a connection string using the newly created user and password and add it into appsettings.development + /// + /// + /// + /// + /// + public override async Task CreateTestDatabaseAsync(DatabaseConnectionConfig databaseConnectionConfig, CancellationToken cancellationToken) + { + var tempDatabaseConnectionConfig = databaseConnectionConfig.Adapt(); + + var connectionStringBuilder = new OracleConnectionStringBuilder() + { + ConnectionString = tempDatabaseConnectionConfig.ConnectionString + }; + + if (!connectionStringBuilder.TryGetValue(UserStringKey, out var user)) + { + throw new Exception($"Cannot find key '{UserStringKey}'"); + } + + if (!connectionStringBuilder.TryGetValue(PasswordStringKey, out var password)) + { + throw new Exception($"Cannot find key '{PasswordStringKey}'"); + } + + var tempUserName = DatabaseNameService.CreateDatabaseName(); + + var dataOptions = new DataOptions().UseOracle(databaseConnectionConfig.ConnectionString) + .UseMappingSchema(_mappingSchema); + + using var context = new DataConnection(dataOptions); + + var userNames = await context.GetTable().Select(x => x.UserName).ToListAsync(cancellationToken); + + var toBeDeletedUsers = userNames.Where(x => + { + var creationDate = DatabaseNameService.ReadTimeStampFromString(x); + + return creationDate.HasValue && creationDate.Value < timeProvider.GetUtcNow().Subtract(_MinTimeSpanBeforeDatabaseDeletion); + }).ToList(); + + await Parallel.ForEachAsync( + toBeDeletedUsers, + new ParallelOptions { MaxDegreeOfParallelism = 3, CancellationToken = cancellationToken }, + async (x, cancellationTokenInner) => + { + var databaseInfoToBeDeleted = new DatabaseInfo + { + DatabaseConnectionConfig = databaseConnectionConfig.Adapt(), + DatabaseConnectionConfigMaster = databaseConnectionConfig.Adapt(), + SchemaName = x + }; + + await DropDatabaseAsync(databaseInfoToBeDeleted, cancellationTokenInner); + }); + + var stringBuilder = new StringBuilder(); + stringBuilder.Append($"CREATE USER \"{tempUserName}\" IDENTIFIED BY \"{tempUserName}\""); + stringBuilder.AppendLine($"DEFAULT TABLESPACE users"); + stringBuilder.AppendLine($"TEMPORARY TABLESPACE TEMP"); + stringBuilder.AppendLine($"QUOTA UNLIMITED ON users"); + + await context.ExecuteAsync(stringBuilder.ToString(), cancellationToken); + + var privileges = new[] + { + "CONNECT", + "CREATE SESSION", + "RESOURCE", + "UNLIMITED TABLESPACE" + }; + + await context.ExecuteAsync($"GRANT {string.Join(", ", privileges)} TO \"{tempUserName}\"", cancellationToken); + await context.ExecuteAsync($"GRANT SELECT ON SYS.GV_$SESSION TO \"{tempUserName}\"", cancellationToken); + + connectionStringBuilder.Add(UserStringKey, ReplaceString); + connectionStringBuilder.Add(PasswordStringKey, ReplaceString); + + tempDatabaseConnectionConfig.ConnectionString = connectionStringBuilder.ConnectionString; + tempDatabaseConnectionConfig.ConnectionString = tempDatabaseConnectionConfig.ConnectionString.Replace(ReplaceString, $"\"{tempUserName}\""); + tempDatabaseConnectionConfig.Schema = tempUserName; + + var databaseInfo = new DatabaseInfo + { + DatabaseConnectionConfigMaster = databaseConnectionConfig.Adapt(), + DatabaseConnectionConfig = tempDatabaseConnectionConfig, + SchemaName = tempUserName, + }; + + return databaseInfo; + } + + public override async Task DropDatabaseAsync(DatabaseInfo databaseInfo, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(databaseInfo); + + var creationDate = ReadTimeStampFromDatabaseName(databaseInfo.SchemaName); + + var dataOptions = new DataOptions().UseOracle(databaseInfo.DatabaseConnectionConfigMaster.ConnectionString) + .UseMappingSchema(_mappingSchema); + + using var context = new DataConnection(dataOptions); + + var maxAttempts = 4; + var delayBetweenAttempts = TimeSpan.FromSeconds(1); + + for (var i = 0; i < maxAttempts; i++) + { + try + { + var vSessions = await context.GetTable() + .Where(x => x.UserName == databaseInfo.SchemaName) + .ToListAsync(cancellationToken); + + foreach (var session in vSessions) + { + var killStatement = $"ALTER SYSTEM KILL SESSION '{session.SID},{session.SerialHashTag}' IMMEDIATE"; + await context.ExecuteAsync(killStatement, cancellationToken); + } + + var userExists = context.GetTable().Any(x => x.UserName == databaseInfo.SchemaName); + + if (!userExists) + { + break; + } + + await context.ExecuteAsync($"DROP USER \"{databaseInfo.SchemaName}\" CASCADE", cancellationToken); + } + catch + { + if (i + 1 == maxAttempts) + { + throw; + } + + var userExists = await context.GetTable().AnyAsync(x => x.UserName == databaseInfo.SchemaName, token: cancellationToken); + + if (!userExists) + { + break; + } + + await Task.Delay(delayBetweenAttempts, cancellationToken); + + delayBetweenAttempts = delayBetweenAttempts.Add(TimeSpan.FromSeconds(1)); + } + } + + await context.ExecuteAsync($"PURGE RECYCLEBIN", cancellationToken); + } +} diff --git a/src/Migrator.Tests/Database/DerivedDatabaseIntegrationTestServices/PostgreSqlDatabaseIntegrationTestService.cs b/src/Migrator.Tests/Database/DerivedDatabaseIntegrationTestServices/PostgreSqlDatabaseIntegrationTestService.cs new file mode 100644 index 00000000..72847417 --- /dev/null +++ b/src/Migrator.Tests/Database/DerivedDatabaseIntegrationTestServices/PostgreSqlDatabaseIntegrationTestService.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using LinqToDB; +using LinqToDB.Data; +using Mapster; +using Migrator.Tests.Database.DatabaseName.Interfaces; +using Migrator.Tests.Database.Interfaces; +using Migrator.Tests.Database.Models; +using Migrator.Tests.Settings.Models; +using Npgsql; + +namespace Migrator.Tests.Database.DerivedDatabaseIntegrationTestServices; + +public class PostgreSqlDatabaseIntegrationTestService(TimeProvider timeProvider, IDatabaseNameService databaseNameService) + : DatabaseIntegrationTestServiceBase(databaseNameService), IDatabaseIntegrationTestService +{ + public override async Task CreateTestDatabaseAsync(DatabaseConnectionConfig databaseConnectionConfig, CancellationToken cancellationToken) + { + var clonedDatabaseConnectionConfig = databaseConnectionConfig.Adapt(); + + var builder = new NpgsqlConnectionStringBuilder + { + ConnectionString = clonedDatabaseConnectionConfig.ConnectionString, + Database = "postgres" + }; + + List databaseNames; + + using (var context = new DataConnection(new DataOptions().UsePostgreSQL(builder.ConnectionString))) + { + databaseNames = await context.QueryToListAsync("SELECT datname from pg_database WHERE datistemplate = false", cancellationToken); + } + + var toBeDeletedDatabaseNames = databaseNames.Where(x => + { + var creationDate = DatabaseNameService.ReadTimeStampFromString(x); + + return creationDate.HasValue && creationDate.Value < timeProvider.GetUtcNow().Subtract(_MinTimeSpanBeforeDatabaseDeletion); + }).ToList(); + + foreach (var databaseName in toBeDeletedDatabaseNames) + { + var databaseInfoToBeDeleted = new DatabaseInfo { DatabaseConnectionConfig = databaseConnectionConfig, DatabaseName = databaseName }; + await DropDatabaseAsync(databaseInfoToBeDeleted, cancellationToken); + } + + var newDatabaseName = DatabaseNameService.CreateDatabaseName(); + using (var context = new DataConnection(new DataOptions().UsePostgreSQL(builder.ConnectionString))) + { + await context.ExecuteAsync($"CREATE DATABASE \"{newDatabaseName}\"", cancellationToken); + } + + var connectionStringBuilder2 = new NpgsqlConnectionStringBuilder(clonedDatabaseConnectionConfig.ConnectionString) + { + Database = newDatabaseName + }; + + clonedDatabaseConnectionConfig.ConnectionString = connectionStringBuilder2.ConnectionString; + + var databaseInfo = new DatabaseInfo + { + DatabaseConnectionConfig = clonedDatabaseConnectionConfig, + DatabaseName = newDatabaseName + }; + + return databaseInfo; + } + + public override async Task DropDatabaseAsync(DatabaseInfo databaseInfo, CancellationToken cancellationToken) + { + var creationDate = DatabaseNameService.ReadTimeStampFromString(databaseInfo.DatabaseName); + + if (!creationDate.HasValue) + { + throw new Exception("You tried to drop a database that was not created by this service. For safety reasons we deny your request."); + } + + var builder = new NpgsqlConnectionStringBuilder(databaseInfo.DatabaseConnectionConfig.ConnectionString) + { + Database = "postgres" + }; + + var dataOptions = new DataOptions().UsePostgreSQL(builder.ConnectionString); + + using (var context = new DataConnection(dataOptions)) + { + + try + { + await context.ExecuteAsync($"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '{databaseInfo.DatabaseName}'", cancellationToken); + await context.ExecuteAsync($"DROP DATABASE \"{databaseInfo.DatabaseName}\"", cancellationToken); + } + catch + { + await Task.Delay(2000, cancellationToken); + + var count = await context.ExecuteAsync($"SELECT COUNT(*) from pg_database WHERE datistemplate = false AND datname = '{databaseInfo.DatabaseName}'", cancellationToken); + + if (count == 1) + { + throw; + } + else + { + // The database was removed by another asynchronously running test that kicked in earlier. + // That's ok for us as we have achieved our objective. + } + } + } + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/DerivedDatabaseIntegrationTestServices/SQLiteDatabaseIntegrationTestService.cs b/src/Migrator.Tests/Database/DerivedDatabaseIntegrationTestServices/SQLiteDatabaseIntegrationTestService.cs new file mode 100644 index 00000000..7b25159f --- /dev/null +++ b/src/Migrator.Tests/Database/DerivedDatabaseIntegrationTestServices/SQLiteDatabaseIntegrationTestService.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Data.SQLite; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using LinqToDB; +using LinqToDB.Data; +using Mapster; +using System.Linq; +using Microsoft.Data.Sqlite; +using Migrator.Tests.Database.DatabaseName.Interfaces; +using Migrator.Tests.Database.Interfaces; +using Migrator.Tests.Settings.Models; +using Migrator.Tests.Database.Models; + +namespace Migrator.Tests.Database.DerivedDatabaseIntegrationTestServices; + +public class SQLiteDatabaseIntegrationTestService(TimeProvider timeProvider, IDatabaseNameService databaseNameService) + : DatabaseIntegrationTestServiceBase(databaseNameService), IDatabaseIntegrationTestService +{ + private const string SqliteDataSourceName = "data source"; + private static readonly string[] _sqliteFileExtensions = ["*.sqlite", "*.db", "*.sqlite3", "*.db3", "*.sqlitedb", "*.*wal", "*.*shm", "*.*journal"]; + + public override async Task CreateTestDatabaseAsync(DatabaseConnectionConfig databaseConnectionConfig, CancellationToken cancellationToken) + { + var builder = new SQLiteConnectionStringBuilder { ConnectionString = databaseConnectionConfig.ConnectionString }; + + if (!builder.TryGetValue(SqliteDataSourceName, out var dataSource)) + { + throw new Exception($@"No {SqliteDataSourceName} given in your SQLite connection string. Use a fully qualified path, e.g. Data Source=C:\bla\bla.db"); + } + + var dataSourceString = (string)dataSource; + + if (dataSourceString.Contains("memory", StringComparison.InvariantCultureIgnoreCase)) + { + throw new Exception("You are using an 'in memory' SQLite database connection string."); + } + + if (!Path.IsPathFullyQualified(dataSourceString)) + { + throw new Exception("You need to use a fully qualified path in your SQLite connection string."); + } + + var directory = Path.GetDirectoryName(dataSourceString); + + var filePaths = _sqliteFileExtensions.Select(x => Directory.EnumerateFiles(directory, x, SearchOption.TopDirectoryOnly)) + .SelectMany(x => x) + .ToList(); + + List toBeDeletedDatabases = []; + + foreach (var filePath in filePaths) + { + var fileName = Path.GetFileName(filePath); + + var creationDate = DatabaseNameService.ReadTimeStampFromString(fileName); + + if (creationDate.HasValue && creationDate.Value < timeProvider.GetUtcNow().Subtract(_MinTimeSpanBeforeDatabaseDeletion)) + { + var builderExistingFile = new SqliteConnectionStringBuilder { DataSource = filePath }; + var dataConnectionConfigExistingFile = databaseConnectionConfig.Adapt(); + dataConnectionConfigExistingFile.ConnectionString = builderExistingFile.ConnectionString; + + var databaseInfo = new DatabaseInfo + { + DatabaseConnectionConfig = dataConnectionConfigExistingFile, + DatabaseName = fileName + }; + + toBeDeletedDatabases.Add(databaseInfo); + } + } + + foreach (var toBeDeletedDatabase in toBeDeletedDatabases) + { + await DropDatabaseAsync(toBeDeletedDatabase, cancellationToken); + } + + builder.Remove(SqliteDataSourceName); + + var newSqliteDatabaseName = $"{DatabaseNameService.CreateDatabaseName()}.db"; + var fullSqliteDatabaseName = Path.Combine(directory, newSqliteDatabaseName); + + builder.Add(SqliteDataSourceName, fullSqliteDatabaseName); + + var newDatabaseConnectionConfig = databaseConnectionConfig.Adapt(); + newDatabaseConnectionConfig.ConnectionString = builder.ConnectionString; + + // Create the database file physically + using var context = new DataConnection(new DataOptions().UseSQLite(newDatabaseConnectionConfig.ConnectionString)); + + var databaseInfoNew = new DatabaseInfo + { + DatabaseConnectionConfig = newDatabaseConnectionConfig, + DatabaseConnectionConfigMaster = databaseConnectionConfig.Adapt(), + DatabaseName = newSqliteDatabaseName, + }; + + return databaseInfoNew; + } + + public override async Task DropDatabaseAsync(DatabaseInfo databaseInfo, CancellationToken cancellationToken) + { + var builder = new DbConnectionStringBuilder { ConnectionString = databaseInfo.DatabaseConnectionConfig.ConnectionString }; + + if (!builder.TryGetValue(SqliteDataSourceName, out var dataSource)) + { + throw new Exception(); + } + + var dataSourceString = (string)dataSource; + + if (!Path.IsPathFullyQualified(dataSourceString)) + { + throw new Exception("Path is not fully qualified."); + } + + var fileName = Path.GetFileName(dataSourceString); + + var creationDate = DatabaseNameService.ReadTimeStampFromString(fileName); + + if (!creationDate.HasValue) + { + throw new Exception("You tried to drop a database that was not created by this service. For safety reasons we deny your request."); + } + + File.Delete(dataSourceString); + + await Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/DerivedDatabaseIntegrationTestServices/SqlServerDatabaseIntegrationTestService.cs b/src/Migrator.Tests/Database/DerivedDatabaseIntegrationTestServices/SqlServerDatabaseIntegrationTestService.cs new file mode 100644 index 00000000..29aaaf6a --- /dev/null +++ b/src/Migrator.Tests/Database/DerivedDatabaseIntegrationTestServices/SqlServerDatabaseIntegrationTestService.cs @@ -0,0 +1,111 @@ +using System; +using System.Data.Common; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using LinqToDB; +using LinqToDB.Data; +using Mapster; +using Microsoft.Data.SqlClient; +using Migrator.Tests.Database.DatabaseName.Interfaces; +using Migrator.Tests.Database.Interfaces; +using Migrator.Tests.Database.Models; +using Migrator.Tests.Settings.Models; + +namespace Migrator.Tests.Database.DerivedDatabaseIntegrationTestServices; + +public class SqlServerDatabaseIntegrationTestService(TimeProvider timeProvider, IDatabaseNameService databaseNameService) + : DatabaseIntegrationTestServiceBase(databaseNameService), IDatabaseIntegrationTestService +{ + private const string SqlServerInitialCatalogString = "Initial Catalog"; + + public override async Task CreateTestDatabaseAsync(DatabaseConnectionConfig databaseConnectionConfig, CancellationToken cancellationToken) + { + using var context = new DataConnection(new DataOptions().UseSqlServer(databaseConnectionConfig.ConnectionString)); + await context.ExecuteAsync("use master", cancellationToken); + + var databaseNames = context.Query($"SELECT name FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')").ToList(); + + var toBeDeletedDatabaseNames = databaseNames.Where(x => + { + var creationDate = DatabaseNameService.ReadTimeStampFromString(x); + return creationDate.HasValue && creationDate.Value < timeProvider.GetUtcNow().Subtract(_MinTimeSpanBeforeDatabaseDeletion); + }).ToList(); + + foreach (var databaseName in toBeDeletedDatabaseNames) + { + var databaseInfoToBeDeleted = new DatabaseInfo { DatabaseConnectionConfig = databaseConnectionConfig, DatabaseName = databaseName }; + await DropDatabaseAsync(databaseInfoToBeDeleted, cancellationToken); + } + + var newDatabaseName = DatabaseNameService.CreateDatabaseName(); + + await context.ExecuteAsync($"CREATE DATABASE [{newDatabaseName}]", cancellationToken); + + var clonedDatabaseConnectionConfig = databaseConnectionConfig.Adapt(); + + var builder = new DbConnectionStringBuilder + { + ConnectionString = clonedDatabaseConnectionConfig.ConnectionString + }; + + if (builder.TryGetValue(SqlServerInitialCatalogString, out var value)) + { + builder.Remove(SqlServerInitialCatalogString); + builder.Add(SqlServerInitialCatalogString, newDatabaseName); + } + + clonedDatabaseConnectionConfig.ConnectionString = builder.ConnectionString; + + var databaseInfo = new DatabaseInfo + { + DatabaseConnectionConfig = clonedDatabaseConnectionConfig, + DatabaseName = newDatabaseName + }; + + return databaseInfo; + } + + public override async Task DropDatabaseAsync(DatabaseInfo databaseInfo, CancellationToken cancellationToken) + { + var creationDate = DatabaseNameService.ReadTimeStampFromString(databaseInfo.DatabaseName); + + if (!creationDate.HasValue) + { + throw new Exception("You tried to drop a database that was not created by this service. For safety reasons we deny your request."); + } + + using var context = new DataConnection(new DataOptions().UseSqlServer(databaseInfo.DatabaseConnectionConfig.ConnectionString)); + await context.ExecuteAsync("use master", cancellationToken); + + try + { + await context.ExecuteAsync($"ALTER DATABASE [{databaseInfo.DatabaseName}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE", cancellationToken); + await context.ExecuteAsync($"DROP DATABASE [{databaseInfo.DatabaseName}]", cancellationToken); + } + catch (SqlException ex) + { + // 3701: "Cannot drop the database because it does not exist or you do not have permission" + if (ex.Errors.Count > 0 && ex.Errors.Cast().Any(x => x.Number == 3701)) + { + await Task.Delay(5000, cancellationToken); + + var count = await context.ExecuteAsync($"SELECT COUNT(*) FROM sys.databases WHERE name = '{databaseInfo.DatabaseName}'"); + + if (count == 1) + { + throw new UnauthorizedAccessException($"The database '{databaseInfo.DatabaseName}' cannot be dropped but it still exists so we assume you do not have sufficient privileges to drop databases or this database.", ex); + } + else + { + // The database was removed by another (asynchronously) running test that kicked in earlier. + // That's ok for us as we have achieved the goal. + } + } + else + { + throw; + } + } + } +} diff --git a/src/Migrator.Tests/Database/GuidServices/GuidService.cs b/src/Migrator.Tests/Database/GuidServices/GuidService.cs new file mode 100644 index 00000000..12662802 --- /dev/null +++ b/src/Migrator.Tests/Database/GuidServices/GuidService.cs @@ -0,0 +1,12 @@ +using System; +using Migrator.Tests.Database.GuidServices.Interfaces; + +namespace Migrator.Tests.Database.GuidServices; + +public class GuidService : IGuidService +{ + public Guid NewGuid() + { + return Guid.NewGuid(); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/GuidServices/Interfaces/IGuidService.cs b/src/Migrator.Tests/Database/GuidServices/Interfaces/IGuidService.cs new file mode 100644 index 00000000..16b66068 --- /dev/null +++ b/src/Migrator.Tests/Database/GuidServices/Interfaces/IGuidService.cs @@ -0,0 +1,13 @@ +using System; + +namespace Migrator.Tests.Database.GuidServices.Interfaces; + +public interface IGuidService +{ + /// + /// Creates a new database friendly Guid depending on the given database type. + /// + /// + /// + Guid NewGuid(); +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/Interfaces/IDatabaseIntegrationTestService.cs b/src/Migrator.Tests/Database/Interfaces/IDatabaseIntegrationTestService.cs new file mode 100644 index 00000000..cea8aee9 --- /dev/null +++ b/src/Migrator.Tests/Database/Interfaces/IDatabaseIntegrationTestService.cs @@ -0,0 +1,27 @@ +using System.Threading; +using System.Threading.Tasks; +using Migrator.Tests.Database.Models; +using Migrator.Tests.Settings.Models; + +namespace Migrator.Tests.Database.Interfaces; + +public interface IDatabaseIntegrationTestService +{ + /// + /// Creates a new test database. The database name contains a timestamp and some random alphanumeric chars to increase uniqueness of the name. + /// It also removes old databases that could be leftovers from broken unit tests. + /// + /// + /// + /// + Task CreateTestDatabaseAsync(DatabaseConnectionConfig databaseConnectionConfig, CancellationToken cancellationToken); + + /// + /// Drops a test database. The should hold the of the user with elevated privileges and the + /// Oracle: Schema should hold the name of the user (in Oracle the schema is equal to user) + /// + /// + /// + /// + Task DropDatabaseAsync(DatabaseInfo databaseInfo, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/Interfaces/IDatabaseIntegrationTestServiceFactory.cs b/src/Migrator.Tests/Database/Interfaces/IDatabaseIntegrationTestServiceFactory.cs new file mode 100644 index 00000000..cc604348 --- /dev/null +++ b/src/Migrator.Tests/Database/Interfaces/IDatabaseIntegrationTestServiceFactory.cs @@ -0,0 +1,13 @@ + + +namespace Migrator.Tests.Database.Interfaces; + +public interface IDatabaseIntegrationTestServiceFactory +{ + /// + /// Creates a depending on the provider type (Oracle, PostgreSQL etc.). + /// + /// + /// + IDatabaseIntegrationTestService Create(DatabaseProviderType providerType); +} diff --git a/src/Migrator.Tests/Database/Models/DatabaseInfo.cs b/src/Migrator.Tests/Database/Models/DatabaseInfo.cs new file mode 100644 index 00000000..d66c475a --- /dev/null +++ b/src/Migrator.Tests/Database/Models/DatabaseInfo.cs @@ -0,0 +1,26 @@ +using Migrator.Tests.Settings.Models; + +namespace Migrator.Tests.Database.Models; + +public class DatabaseInfo +{ + /// + /// Gets or sets the master + /// + public DatabaseConnectionConfig DatabaseConnectionConfigMaster { get; set; } + + /// + /// Cloned with manipulated connection string. The connection string contains the new database name. + /// + public DatabaseConnectionConfig DatabaseConnectionConfig { get; set; } + + /// + /// Gets or sets the name of the created test database. + /// + public string DatabaseName { get; set; } + + /// + /// Gets or sets the schema name. In Oracle the user name is equal to the schema name. + /// + public string SchemaName { get; set; } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Database/Parsers/Interfaces/ILinq2DBNameToDatabaseServerTypeParser.cs b/src/Migrator.Tests/Database/Parsers/Interfaces/ILinq2DBNameToDatabaseServerTypeParser.cs new file mode 100644 index 00000000..48888d9c --- /dev/null +++ b/src/Migrator.Tests/Database/Parsers/Interfaces/ILinq2DBNameToDatabaseServerTypeParser.cs @@ -0,0 +1,11 @@ +namespace Migrator.Tests.Database.Parsers.Interfaces; + +public interface ILinq2DBNameToDatabaseServerTypeParser +{ + /// + /// Parses the Linq2Db provider name to . + /// + /// + /// + DatabaseProviderType Parse(string linq2DbName); +} \ No newline at end of file diff --git a/src/Migrator.Tests/Dialects/PostgreSQLDialectTests.cs b/src/Migrator.Tests/Dialects/PostgreSQLDialectTests.cs new file mode 100644 index 00000000..11951b71 --- /dev/null +++ b/src/Migrator.Tests/Dialects/PostgreSQLDialectTests.cs @@ -0,0 +1,45 @@ +using DotNetProjects.Migrator.Providers.Impl.PostgreSQL; +using DotNetProjects.Migrator.Providers.Models.Indexes.Enums; +using NUnit.Framework; + +namespace Migrator.Tests.Dialects; + +[TestFixture] +[Category("Postgre")] +public class PostgreDialectTests +{ + private PostgreSQLDialect _postgreSQLDialect; + + [SetUp] + public void SetUp() + { + // Since Dialect is abstract we use PostgreSQLDialect + _postgreSQLDialect = new PostgreSQLDialect(); + } + + [TestCase(FilterType.EqualTo, "=")] + [TestCase(FilterType.GreaterThanOrEqualTo, ">=")] + [TestCase(FilterType.SmallerThanOrEqualTo, "<=")] + [TestCase(FilterType.SmallerThan, "<")] + [TestCase(FilterType.GreaterThan, ">")] + [TestCase(FilterType.NotEqualTo, "<>")] + public void GetComparisonStringByFilterType_Success(FilterType filterType, string expectedString) + { + var result = _postgreSQLDialect.GetComparisonStringByFilterType(filterType); + + Assert.That(result, Is.EqualTo(expectedString)); + } + + [TestCase("=", FilterType.EqualTo)] + [TestCase(">=", FilterType.GreaterThanOrEqualTo)] + [TestCase("<=", FilterType.SmallerThanOrEqualTo)] + [TestCase("<", FilterType.SmallerThan)] + [TestCase(">", FilterType.GreaterThan)] + [TestCase("<>", FilterType.NotEqualTo)] + public void GetFilterTypeByComparisonString_Success(string comparisonString, FilterType expectedFilterType) + { + var result = _postgreSQLDialect.GetFilterTypeByComparisonString(comparisonString); + + Assert.That(result, Is.EqualTo(expectedFilterType)); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Framework/ColumnProperties/ColumnPropertyExtensionTests.cs b/src/Migrator.Tests/Framework/ColumnProperties/ColumnPropertyExtensionTests.cs new file mode 100644 index 00000000..d2f822d6 --- /dev/null +++ b/src/Migrator.Tests/Framework/ColumnProperties/ColumnPropertyExtensionTests.cs @@ -0,0 +1,90 @@ +using NUnit.Framework; +using DotNetProjects.Migrator.Framework; +using System; +using System.Linq; + +namespace Migrator.Tests.Framework.ColumnProperties; + +public class ColumnPropertyExtensionsTests +{ + [Test] + public void Clear() + { + // Arrange + var columnProperty = ColumnProperty.PrimaryKey | ColumnProperty.NotNull; + + // Act + columnProperty = columnProperty.Clear(ColumnProperty.PrimaryKey); + + // Assert + Assert.That(columnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.False); + } + + [Test] + public void IsSet() + { + // Arrange + var columnProperty = ColumnProperty.PrimaryKeyWithIdentity | ColumnProperty.NotNull; + + // Act + var actualData = GetAllSingleColumnProperties().Select(x => new + { + ColumnPropertyString = x.ToString(), + IsSet = columnProperty.IsSet(x), + IsNotSet = columnProperty.IsNotSet(x) + }) + .ToList(); + + // Assert + string[] expectedSet = [nameof(ColumnProperty.PrimaryKey), nameof(ColumnProperty.NotNull), nameof(ColumnProperty.Identity)]; + var actualDataShouldBeTrue = actualData.Where(x => expectedSet.Any(y => y == x.ColumnPropertyString)).ToList(); + var actualDataShouldBeFalse = actualData.Where(x => !expectedSet.Any(y => y == x.ColumnPropertyString)).ToList(); + + Assert.That(actualDataShouldBeTrue.Select(x => x.IsSet), Has.All.True); + Assert.That(actualDataShouldBeFalse.Select(x => x.IsSet), Has.All.False); + } + + [Test] + public void IsNotSet() + { + // Arrange + var columnProperty = ColumnProperty.PrimaryKeyWithIdentity | ColumnProperty.NotNull; + + // Act + var actualData = GetAllSingleColumnProperties().Select(x => new + { + ColumnPropertyString = x.ToString(), + IsSet = columnProperty.IsNotSet(x), + IsNotSet = columnProperty.IsNotSet(x) + }) + .ToList(); + + // Assert + string[] expectedSet = [nameof(ColumnProperty.PrimaryKey), nameof(ColumnProperty.NotNull), nameof(ColumnProperty.Identity)]; + var actualDataShouldBeFalse = actualData.Where(x => expectedSet.Any(y => y == x.ColumnPropertyString)).ToList(); + var actualDataShouldBeTrue = actualData.Where(x => !expectedSet.Any(y => y == x.ColumnPropertyString)).ToList(); + + Assert.That(actualDataShouldBeTrue.Select(x => x.IsNotSet), Has.All.True); + Assert.That(actualDataShouldBeFalse.Select(x => x.IsNotSet), Has.All.False); + } + + [Test] + public void Set_Success() + { + // Arrange + var columnProperty = ColumnProperty.NotNull; + + // Act + var result = columnProperty.Set(ColumnProperty.PrimaryKeyWithIdentity); + + // Assert + var expected = ColumnProperty.NotNull | ColumnProperty.PrimaryKeyWithIdentity; + + Assert.That(result, Is.EqualTo(expected)); + } + + private ColumnProperty[] GetAllSingleColumnProperties() + { + return [.. Enum.GetValues().Where(x => x == 0 || (x & (x - 1)) == 0)]; + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/JoiningTableTransformationProviderExtensionsTests.cs b/src/Migrator.Tests/JoiningTableTransformationProviderExtensionsTests.cs index f31286ec..dfdaabff 100644 --- a/src/Migrator.Tests/JoiningTableTransformationProviderExtensionsTests.cs +++ b/src/Migrator.Tests/JoiningTableTransformationProviderExtensionsTests.cs @@ -1,157 +1,216 @@ -using System.Data; -using Migrator.Framework; -using NUnit.Framework; -using Rhino.Mocks; - -namespace Migrator.Tests -{ - [TestFixture] - public class JoiningTableTransformationProviderExtensionsTests - { - #region Setup/Teardown - - [SetUp] - public void SetUp() - { - provider = MockRepository.GenerateStub(); - } - - #endregion - - ITransformationProvider provider; - - [Test] - public void AddManyToManyJoiningTable_AddsPrimaryKey() - { - provider.AddManyToManyJoiningTable("dbo", "TestScenarios", "Id", "Versions", "Id"); - - object[] args = provider.GetArgumentsForCallsMadeOn(stub => stub.AddPrimaryKey(null, null, null))[0]; - - Assert.AreEqual("PK_TestScenarioVersions", args[0]); - Assert.AreEqual("dbo.TestScenarioVersions", args[1]); - - var columns = (string[]) args[2]; - - Assert.Contains("TestScenarioId", columns); - Assert.Contains("VersionId", columns); - } - - [Test] - public void AddManyToManyJoiningTable_CreatesLeftHandSideColumn_WithCorrectName() - { - provider.AddManyToManyJoiningTable("dbo", "TestScenarios", "Id", "Versions", "Id"); - - object[] args = provider.GetArgumentsForCallsMadeOn(stub => stub.AddTable(null, (Column[]) null))[0]; - - Column lhsColumn = ((IDbField[]) args[1])[0] as Column; - - Assert.AreEqual(lhsColumn.Name, "TestScenarioId"); - Assert.AreEqual(DbType.Guid, lhsColumn.Type); - Assert.AreEqual(ColumnProperty.NotNull, lhsColumn.ColumnProperty); - } - - [Test] - public void AddManyToManyJoiningTable_CreatesLeftHandSideForeignKey_WithCorrectAttributes() - { - provider.AddManyToManyJoiningTable("dbo", "TestScenarios", "Id", "Versions", "Id"); - - object[] args = provider.GetArgumentsForCallsMadeOn(stub => stub.AddForeignKey(null, null, "", null, null, ForeignKeyConstraintType.NoAction))[0]; - - Assert.AreEqual("dbo.TestScenarioVersions", args[1]); - Assert.AreEqual("TestScenarioId", args[2]); - Assert.AreEqual("dbo.TestScenarios", args[3]); - Assert.AreEqual("Id", args[4]); - Assert.AreEqual(ForeignKeyConstraintType.NoAction, args[5]); - } - - [Test] - public void AddManyToManyJoiningTable_CreatesLeftHandSideForeignKey_WithCorrectName() - { - provider.AddManyToManyJoiningTable("dbo", "TestScenarios", "Id", "Versions", "Id"); - - object[] args = provider.GetArgumentsForCallsMadeOn(stub => stub.AddForeignKey(null, null, "", null, null, ForeignKeyConstraintType.NoAction))[0]; - - Assert.AreEqual("FK_Scenarios_ScenarioVersions", args[0]); - } - - [Test] - public void AddManyToManyJoiningTable_CreatesRightHandSideColumn_WithCorrectName() - { - provider.AddManyToManyJoiningTable("dbo", "TestScenarios", "Id", "Versions", "Id"); - - object[] args = provider.GetArgumentsForCallsMadeOn(stub => stub.AddTable(null, (Column[]) null))[0]; - - Column rhsColumn = ((IDbField[]) args[1])[1] as Column; - - Assert.AreEqual(rhsColumn.Name, "VersionId"); - Assert.AreEqual(DbType.Guid, rhsColumn.Type); - Assert.AreEqual(ColumnProperty.NotNull, rhsColumn.ColumnProperty); - } - - [Test] - public void AddManyToManyJoiningTable_CreatesRightHandSideForeignKey_WithCorrectAttributes() - { - provider.AddManyToManyJoiningTable("dbo", "TestScenarios", "Id", "Versions", "Id"); - - object[] args = provider.GetArgumentsForCallsMadeOn(stub => stub.AddForeignKey(null, null, "", null, null, ForeignKeyConstraintType.NoAction))[1]; - - Assert.AreEqual("dbo.TestScenarioVersions", args[1]); - Assert.AreEqual("VersionId", args[2]); - Assert.AreEqual("dbo.Versions", args[3]); - Assert.AreEqual("Id", args[4]); - Assert.AreEqual(ForeignKeyConstraintType.NoAction, args[5]); - } - - [Test] - public void AddManyToManyJoiningTable_CreatesRightHandSideForeignKey_WithCorrectName() - { - provider.AddManyToManyJoiningTable("dbo", "TestScenarios", "Id", "Versions", "Id"); - - object[] args = provider.GetArgumentsForCallsMadeOn(stub => stub.AddForeignKey(null, null, "", null, null, ForeignKeyConstraintType.NoAction))[1]; - - Assert.AreEqual("FK_Versions_ScenarioVersions", args[0]); - } - - [Test] - public void AddManyToManyJoiningTable_CreatesTableWithCorrectName() - { - provider.AddManyToManyJoiningTable("dbo", "TestScenarios", "Id", "Versions", "Id"); - - object[] args = provider.GetArgumentsForCallsMadeOn(stub => stub.AddTable(null, (Column[]) null))[0]; - - Assert.AreEqual("dbo.TestScenarioVersions", args[0]); - } - - [Test] - public void RemoveManyToManyJoiningTable_RemovesLhsForeignKey() - { - provider.RemoveManyToManyJoiningTable("dbo", "TestScenarios", "Versions"); - - object[] args = provider.GetArgumentsForCallsMadeOn(stub => stub.RemoveForeignKey(null, null))[0]; - - Assert.AreEqual("dbo.TestScenarioVersions", args[0]); - Assert.AreEqual("FK_Scenarios_ScenarioVersions", args[1]); - } - - [Test] - public void RemoveManyToManyJoiningTable_RemovesRhsForeignKey() - { - provider.RemoveManyToManyJoiningTable("dbo", "TestScenarios", "Versions"); - - object[] args = provider.GetArgumentsForCallsMadeOn(stub => stub.RemoveForeignKey(null, null))[1]; - - Assert.AreEqual("dbo.TestScenarioVersions", args[0]); - Assert.AreEqual("FK_Versions_ScenarioVersions", args[1]); - } - - [Test] - public void RemoveManyToManyJoiningTable_RemovesTable() - { - provider.RemoveManyToManyJoiningTable("dbo", "TestScenarios", "Versions"); - - object[] args = provider.GetArgumentsForCallsMadeOn(stub => stub.RemoveTable(null))[0]; - - Assert.AreEqual("dbo.TestScenarioVersions", args[0]); - } - } -} \ No newline at end of file +using System.Data; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Framework.Support; +using NSubstitute; +using NUnit.Framework; + +namespace Migrator.Tests; + +[TestFixture] +public class JoiningTableTransformationProviderExtensionsTests +{ + #region Setup/Teardown + + [SetUp] + public void SetUp() + { + _provider = Substitute.For(); + } + + #endregion + + private ITransformationProvider _provider; + + [Test] + public void AddManyToManyJoiningTable_AddsPrimaryKey() + { + _provider + .When(x => x.AddPrimaryKey(Arg.Any(), Arg.Any(), Arg.Any())) + .Do(callInfo => + { + var capturedName = callInfo[0] as string; + var capturedTable = callInfo[1] as string; + var columns = callInfo[2] as string[]; + Assert.That(capturedName, Is.EqualTo("PK_TestScenarioVersions")); + Assert.That(capturedTable, Is.EqualTo("dbo.TestScenarioVersions")); + Assert.That(columns, Does.Contain("TestScenarioId")); + Assert.That(columns, Does.Contain("VersionId")); + }); + + _provider.AddManyToManyJoiningTable("dbo", "TestScenarios", "Id", "Versions", "Id"); + } + + [Test] + public void AddManyToManyJoiningTable_CreatesLeftHandSideColumn_WithCorrectName() + { + _provider + .When(x => x.AddTable(Arg.Any(), Arg.Any())) + .Do(callInfo => + { + var lhsColumn = ((IDbField[])callInfo[1])[0] as Column; + + Assert.That(lhsColumn.Name, Is.EqualTo("TestScenarioId")); + Assert.That(lhsColumn.Type, Is.EqualTo(DbType.Guid)); + Assert.That(ColumnProperty.NotNull, Is.EqualTo(lhsColumn.ColumnProperty)); + }); + + _provider.AddManyToManyJoiningTable("dbo", "TestScenarios", "Id", "Versions", "Id"); + } + + [Test] + public void AddManyToManyJoiningTable_CreatesLeftHandSideForeignKey_WithCorrectAttributes() + { + _provider + .When(x => x.AddForeignKey(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())) + .Do(callInfo => + { + var lhsColumn = ((IDbField[])callInfo[1])[0] as Column; + + Assert.That(callInfo[1] as string, Is.EqualTo("dbo.TestScenarioVersions")); + Assert.That(callInfo[2] as string, Is.EqualTo("TestScenarioId")); + Assert.That(callInfo[3] as string, Is.EqualTo("dbo.TestScenarios")); + Assert.That(callInfo[4] as string, Is.EqualTo("Id")); + Assert.That((ForeignKeyConstraintType)callInfo[5], Is.EqualTo(ForeignKeyConstraintType.NoAction)); + }); + + _provider.AddManyToManyJoiningTable("dbo", "TestScenarios", "Id", "Versions", "Id"); + } + + [Test] + public void AddManyToManyJoiningTable_CreatesLeftHandSideForeignKey_WithCorrectName() + { + _provider + .When(x => x.AddForeignKey(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())) + .Do(callInfo => + { + var lhsColumn = ((IDbField[])callInfo[1])[0] as Column; + + Assert.That(callInfo[0] as string, Is.EqualTo("FK_Scenarios_ScenarioVersions")); + }); + + _provider.AddManyToManyJoiningTable("dbo", "TestScenarios", "Id", "Versions", "Id"); + } + + [Test] + public void AddManyToManyJoiningTable_CreatesRightHandSideColumn_WithCorrectName() + { + _provider + .When(x => x.AddTable(Arg.Any(), Arg.Any())) + .Do(callInfo => + { + var rhsColumn = ((IDbField[])callInfo[1])[0] as Column; + + Assert.That(rhsColumn.Name, Is.EqualTo("VersionId")); + Assert.That(DbType.Guid, Is.EqualTo(rhsColumn.Type)); + Assert.That(ColumnProperty.NotNull, Is.EqualTo(rhsColumn.ColumnProperty)); + }); + + _provider.AddManyToManyJoiningTable("dbo", "TestScenarios", "Id", "Versions", "Id"); + } + + [Test] + public void AddManyToManyJoiningTable_CreatesRightHandSideForeignKey_WithCorrectAttributes() + { + _provider + .When(x => x.AddTable(Arg.Any(), Arg.Any())) + .Do(callInfo => + { + var rhsColumn = ((IDbField[])callInfo[1])[0] as Column; + + Assert.That(rhsColumn.Name, Is.EqualTo("VersionId")); + Assert.That(DbType.Guid, Is.EqualTo(rhsColumn.Type)); + Assert.That(ColumnProperty.NotNull, Is.EqualTo(rhsColumn.ColumnProperty)); + + Assert.That(callInfo[1] as string, Is.EqualTo("dbo.TestScenarioVersions")); + Assert.That(callInfo[2] as string, Is.EqualTo("VersionId")); + Assert.That(callInfo[3] as string, Is.EqualTo("dbo.Versions")); + Assert.That(callInfo[4] as string, Is.EqualTo("Id")); + Assert.That((ForeignKeyConstraintType)callInfo[5], Is.EqualTo(ForeignKeyConstraintType.NoAction)); + }); + + _provider.AddManyToManyJoiningTable("dbo", "TestScenarios", "Id", "Versions", "Id"); + } + + [Test] + public void AddManyToManyJoiningTable_CreatesRightHandSideForeignKey_WithCorrectName() + { + _provider + .When(x => x.AddForeignKey(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())) + .Do(callInfo => + { + var lhsColumn = ((IDbField[])callInfo[1])[0] as Column; + + Assert.That(callInfo[0] as string, Is.EqualTo("FK_Scenarios_ScenarioVersions")); + }); + + _provider.AddManyToManyJoiningTable("dbo", "TestScenarios", "Id", "Versions", "Id"); + } + + [Test] + public void AddManyToManyJoiningTable_CreatesTableWithCorrectName() + { + _provider + .When(x => x.AddTable(Arg.Any(), Arg.Any())) + .Do(callInfo => + { + var rhsColumn = ((IDbField[])callInfo[1])[0] as Column; + + Assert.That(callInfo[1] as string, Is.EqualTo("dbo.TestScenarioVersions")); + }); + + _provider.AddManyToManyJoiningTable("dbo", "TestScenarios", "Id", "Versions", "Id"); + } + + [Test] + public void RemoveManyToManyJoiningTable_RemovesLhsForeignKey() + { + var callCount = 0; + + _provider + .When(x => x.RemoveForeignKey(Arg.Any(), Arg.Any())) + .Do(callInfo => + { + callCount++; + if (callCount == 1) + { + Assert.That(callInfo[0] as string, Is.EqualTo("dbo.TestScenarioVersions")); + Assert.That(callInfo[1] as string, Is.EqualTo("FK_Scenarios_ScenarioVersions")); + } + }); + + _provider.RemoveManyToManyJoiningTable("dbo", "TestScenarios", "Versions"); + } + + [Test] + public void RemoveManyToManyJoiningTable_RemovesRhsForeignKey() + { + var callCount = 0; + + _provider + .When(x => x.RemoveForeignKey(Arg.Any(), Arg.Any())) + .Do(callInfo => + { + callCount++; + if (callCount == 2) + { + Assert.That(callInfo[0] as string, Is.EqualTo("dbo.TestScenarioVersions")); + Assert.That(callInfo[1] as string, Is.EqualTo("FK_Versions_ScenarioVersions")); + } + }); + + _provider.RemoveManyToManyJoiningTable("dbo", "TestScenarios", "Versions"); + } + + [Test] + public void RemoveManyToManyJoiningTable_RemovesTable() + { + _provider + .When(x => x.RemoveTable(Arg.Any())) + .Do(callInfo => + { + Assert.That(callInfo[0] as string, Is.EqualTo("dbo.TestScenarioVersions")); + }); + + _provider.RemoveManyToManyJoiningTable("dbo", "TestScenarios", "Versions"); + } +} diff --git a/src/Migrator.Tests/MigrationLoaderTest.cs b/src/Migrator.Tests/MigrationLoaderTest.cs index 01b197f1..fec13989 100644 --- a/src/Migrator.Tests/MigrationLoaderTest.cs +++ b/src/Migrator.Tests/MigrationLoaderTest.cs @@ -1,72 +1,80 @@ -using System.Reflection; -using Migrator.Framework; -using Migrator.Framework.Loggers; -using NUnit.Framework; -using NUnit.Mocks; - -namespace Migrator.Tests -{ - [TestFixture] - public class MigrationLoaderTest - { - #region Setup/Teardown - - [SetUp] - public void SetUp() - { - SetUpCurrentVersion(0, false); - } - - #endregion - - MigrationLoader _migrationLoader; - - void SetUpCurrentVersion(int version, bool assertRollbackIsCalled) - { - var providerMock = new DynamicMock(typeof (ITransformationProvider)); - - providerMock.SetReturnValue("get_CurrentVersion", version); - providerMock.SetReturnValue("get_Logger", new Logger(false)); - if (assertRollbackIsCalled) - providerMock.Expect("Rollback"); - else - providerMock.ExpectNoCall("Rollback"); - - _migrationLoader = new MigrationLoader((ITransformationProvider) providerMock.MockInstance, Assembly.GetExecutingAssembly(), true); - _migrationLoader.MigrationsTypes.Add(typeof (MigratorTest.FirstMigration)); - _migrationLoader.MigrationsTypes.Add(typeof (MigratorTest.SecondMigration)); - _migrationLoader.MigrationsTypes.Add(typeof (MigratorTest.ThirdMigration)); - _migrationLoader.MigrationsTypes.Add(typeof (MigratorTest.ForthMigration)); - _migrationLoader.MigrationsTypes.Add(typeof (MigratorTest.BadMigration)); - _migrationLoader.MigrationsTypes.Add(typeof (MigratorTest.SixthMigration)); - _migrationLoader.MigrationsTypes.Add(typeof (MigratorTest.NonIgnoredMigration)); - } - - [Test] - [ExpectedException(typeof (DuplicatedVersionException))] - public void CheckForDuplicatedVersion() - { - _migrationLoader.MigrationsTypes.Add(typeof (MigratorTest.FirstMigration)); - _migrationLoader.CheckForDuplicatedVersion(); - } - - [Test] - public void LastVersion() - { - Assert.AreEqual(7, _migrationLoader.LastVersion); - } - - [Test] - public void NullIfNoMigrationForVersion() - { - Assert.IsNull(_migrationLoader.GetMigration(99999999)); - } - - [Test] - public void ZeroIfNoMigrations() - { - _migrationLoader.MigrationsTypes.Clear(); - Assert.AreEqual(0, _migrationLoader.LastVersion); - } - } -} \ No newline at end of file +using System.Reflection; +using DotNetProjects.Migrator; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Framework.Loggers; +using NSubstitute; +using NUnit.Framework; + +namespace Migrator.Tests; + +[TestFixture] +public class MigrationLoaderTest +{ + #region Setup/Teardown + + [SetUp] + public void SetUp() + { + SetUpCurrentVersion(0, false); + } + + #endregion + + private MigrationLoader _migrationLoader; + + private void SetUpCurrentVersion(int version, bool assertRollbackIsCalled) + { + var providerMock = Substitute.For(); + + providerMock.Logger = new Logger(false); + providerMock.When(x => x.Dispose()).Do(_ => + { + if (assertRollbackIsCalled) + { + providerMock.Received().Rollback(); + } + else + { + providerMock.DidNotReceive().Rollback(); + } + }); + + _migrationLoader = new MigrationLoader(providerMock, Assembly.GetExecutingAssembly(), true); + _migrationLoader.MigrationsTypes.Add(typeof(MigratorTest.FirstMigration)); + _migrationLoader.MigrationsTypes.Add(typeof(MigratorTest.SecondMigration)); + _migrationLoader.MigrationsTypes.Add(typeof(MigratorTest.ThirdMigration)); + _migrationLoader.MigrationsTypes.Add(typeof(MigratorTest.ForthMigration)); + _migrationLoader.MigrationsTypes.Add(typeof(MigratorTest.BadMigration)); + _migrationLoader.MigrationsTypes.Add(typeof(MigratorTest.SixthMigration)); + _migrationLoader.MigrationsTypes.Add(typeof(MigratorTest.NonIgnoredMigration)); + } + + [Test] + public void CheckForDuplicatedVersion() + { + _migrationLoader.MigrationsTypes.Add(typeof(MigratorTest.FirstMigration)); + Assert.Throws(() => + { + _migrationLoader.CheckForDuplicatedVersion(); + }); + } + + [Test] + public void LastVersion() + { + Assert.That(7, Is.EqualTo(_migrationLoader.LastVersion)); + } + + [Test] + public void NullIfNoMigrationForVersion() + { + Assert.That(_migrationLoader.GetMigration(99999999), Is.Null); + } + + [Test] + public void ZeroIfNoMigrations() + { + _migrationLoader.MigrationsTypes.Clear(); + Assert.That(0, Is.EqualTo(_migrationLoader.LastVersion)); + } +} diff --git a/src/Migrator.Tests/MigrationTestCase.cs b/src/Migrator.Tests/MigrationTestCase.cs index e1627c8e..106d7cc6 100644 --- a/src/Migrator.Tests/MigrationTestCase.cs +++ b/src/Migrator.Tests/MigrationTestCase.cs @@ -12,49 +12,48 @@ #endregion using System.Reflection; -using Migrator.Providers; +using DotNetProjects.Migrator.Providers; using NUnit.Framework; -namespace Migrator.Tests +namespace Migrator.Tests; + +/// +/// Extend this classe to test your migrations +/// +public abstract class MigrationsTestCase { - /// - /// Extend this classe to test your migrations - /// - public abstract class MigrationsTestCase - { - Migrator _migrator; - - protected abstract TransformationProvider TransformationProvider { get; } - protected abstract string ConnectionString { get; } - protected abstract Assembly MigrationAssembly { get; } - - [SetUp] - public void SetUp() - { - _migrator = new Migrator(TransformationProvider, MigrationAssembly, true); - - Assert.IsTrue(_migrator.MigrationsTypes.Count > 0, "No migrations in assembly " + MigrationAssembly.Location); - - _migrator.MigrateTo(0); - } - - [TearDown] - public void TearDown() - { - _migrator.MigrateTo(0); - } - - [Test] - public void Up() - { - _migrator.MigrateToLastVersion(); - } - - [Test] - public void Down() - { - _migrator.MigrateToLastVersion(); - _migrator.MigrateTo(0); - } - } + private DotNetProjects.Migrator.Migrator _migrator; + + protected abstract TransformationProvider TransformationProvider { get; } + protected abstract string ConnectionString { get; } + protected abstract Assembly MigrationAssembly { get; } + + [SetUp] + public void SetUp() + { + _migrator = new DotNetProjects.Migrator.Migrator(TransformationProvider, MigrationAssembly, true); + + Assert.That(_migrator.MigrationsTypes.Count > 0, Is.True, "No migrations in assembly " + MigrationAssembly.Location); + + _migrator.MigrateTo(0); + } + + [TearDown] + public void TearDown() + { + _migrator.MigrateTo(0); + } + + [Test] + public void Up() + { + _migrator.MigrateToLastVersion(); + } + + [Test] + public void Down() + { + _migrator.MigrateToLastVersion(); + _migrator.MigrateTo(0); + } } \ No newline at end of file diff --git a/src/Migrator.Tests/MigrationTypeComparerTest.cs b/src/Migrator.Tests/MigrationTypeComparerTest.cs index ead0f9cb..ae57d1d7 100644 --- a/src/Migrator.Tests/MigrationTypeComparerTest.cs +++ b/src/Migrator.Tests/MigrationTypeComparerTest.cs @@ -13,88 +13,88 @@ using System; using System.Collections.Generic; -using Migrator.Framework; +using DotNetProjects.Migrator; +using DotNetProjects.Migrator.Framework; using NUnit.Framework; -namespace Migrator.Tests +namespace Migrator.Tests; + +[TestFixture] +public class MigrationTypeComparerTest { - [TestFixture] - public class MigrationTypeComparerTest - { - readonly Type[] _types = { - typeof (Migration1), - typeof (Migration2), - typeof (Migration3) - }; - - [Migration(1, Ignore = true)] - internal class Migration1 : Migration - { - public override void Up() - { - } - - public override void Down() - { - } - } - - [Migration(2, Ignore = true)] - internal class Migration2 : Migration - { - public override void Up() - { - } - - public override void Down() - { - } - } - - [Migration(3, Ignore = true)] - internal class Migration3 : Migration - { - public override void Up() - { - } - - public override void Down() - { - } - } - - [Test] - public void SortAscending() - { - var list = new List(); - - list.Add(_types[1]); - list.Add(_types[0]); - list.Add(_types[2]); - - list.Sort(new MigrationTypeComparer(true)); - - for (int i = 0; i < 3; i++) - { - Assert.AreSame(_types[i], list[i]); - } - } - - [Test] - public void SortDescending() - { - var list = new List(); - - list.Add(_types[1]); - list.Add(_types[0]); - list.Add(_types[2]); - - list.Sort(new MigrationTypeComparer(false)); - - for (int i = 0; i < 3; i++) - { - Assert.AreSame(_types[2 - i], list[i]); - } - } - } + private readonly Type[] _types = [ + typeof (Migration1), + typeof (Migration2), + typeof (Migration3) + ]; + + [Migration(1, Ignore = true)] + internal class Migration1 : Migration + { + public override void Up() + { + } + + public override void Down() + { + } + } + + [Migration(2, Ignore = true)] + internal class Migration2 : Migration + { + public override void Up() + { + } + + public override void Down() + { + } + } + + [Migration(3, Ignore = true)] + internal class Migration3 : Migration + { + public override void Up() + { + } + + public override void Down() + { + } + } + + [Test] + public void SortAscending() + { + var list = new List(); + + list.Add(_types[1]); + list.Add(_types[0]); + list.Add(_types[2]); + + list.Sort(new MigrationTypeComparer(true)); + + for (var i = 0; i < 3; i++) + { + Assert.That(_types[i], Is.SameAs(list[i])); + } + } + + [Test] + public void SortDescending() + { + var list = new List(); + + list.Add(_types[1]); + list.Add(_types[0]); + list.Add(_types[2]); + + list.Sort(new MigrationTypeComparer(false)); + + for (var i = 0; i < 3; i++) + { + Assert.That(_types[2 - i], Is.SameAs(list[i])); + } + } } \ No newline at end of file diff --git a/src/Migrator.Tests/Migrator.Tests-vs2008.csproj b/src/Migrator.Tests/Migrator.Tests-vs2008.csproj deleted file mode 100644 index bf400ca3..00000000 --- a/src/Migrator.Tests/Migrator.Tests-vs2008.csproj +++ /dev/null @@ -1,141 +0,0 @@ - - - Debug - AnyCPU - 9.0.21022 - 2.0 - {882B6A93-67B8-45BF-8636-5796B1B1CBF8} - Library - Properties - Migrator.Tests - Migrator.Tests - - - 2.0 - - - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - true - full - false - bin\Migrator.Tests\Debug\ - TRACE;DEBUG;DOTNET2 - prompt - 4 - - - pdbonly - true - bin\Migrator.Tests\Release\ - TRACE;DOTNET2 - prompt - 4 - - - - False - ..\..\lib\NUnit\nunit.framework.dll - - - False - ..\..\lib\NUnit\nunit.mocks.dll - - - - - - ..\..\lib\System.Data.SqlServerCe.dll - False - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {1FEE70A4-AAD7-4C60-BE60-3F7DC03A8C4D} - Migrator-vs2008 - - - {5270F048-E580-486C-B14C-E5B9F6E539D4} - Migrator.Framework-vs2008 - - - {D58C68E4-D789-40F7-9078-C9F587D4363C} - Migrator.Providers-vs2008 - - - - - app.config - - - - - False - .NET Framework Client Profile - false - - - False - .NET Framework 2.0 %28x86%29 - true - - - False - .NET Framework 3.0 %28x86%29 - false - - - False - .NET Framework 3.5 - false - - - False - .NET Framework 3.5 SP1 - false - - - - \ No newline at end of file diff --git a/src/Migrator.Tests/Migrator.Tests-vs2010.csproj b/src/Migrator.Tests/Migrator.Tests-vs2010.csproj deleted file mode 100644 index 901836c6..00000000 --- a/src/Migrator.Tests/Migrator.Tests-vs2010.csproj +++ /dev/null @@ -1,185 +0,0 @@ - - - - Debug - AnyCPU - 9.0.21022 - 2.0 - {882B6A93-67B8-45BF-8636-5796B1B1CBF8} - Library - Properties - Migrator.Tests - Migrator.Tests - - - 3.5 - - - false - v4.0 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true - - - - - - true - full - false - bin\Migrator.Tests\Debug\ - TRACE;DEBUG;DOTNET2 - prompt - 4 - AllRules.ruleset - - - pdbonly - true - bin\Migrator.Tests\Release\ - TRACE;DOTNET2 - prompt - 4 - AllRules.ruleset - - - - ..\..\packages\FirebirdSql.Data.FirebirdClient.4.7.0.0\lib\net40-client\FirebirdSql.Data.FirebirdClient.dll - True - - - ..\..\packages\Npgsql.2.2.5\lib\net40\Mono.Security.dll - True - - - ..\..\packages\MySql.Data.6.9.7\lib\net40\MySql.Data.dll - True - - - ..\..\packages\Npgsql.2.2.5\lib\net40\Npgsql.dll - True - - - ..\..\packages\NUnit.2.6.4\lib\nunit.framework.dll - True - - - ..\..\packages\NUnit.Mocks.2.6.4\lib\nunit.mocks.dll - True - - - ..\..\lib\Oracle.DataAccess.dll - - - ..\..\packages\RhinoMocks.3.6.1\lib\net\Rhino.Mocks.dll - True - - - - - - ..\..\packages\System.Data.SQLite.Core.1.0.97.0\lib\net40\System.Data.SQLite.dll - True - - - ..\..\lib\System.Data.SqlServerCe.dll - False - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {1FEE70A4-AAD7-4C60-BE60-3F7DC03A8C4D} - DotNetProjects.Migrator - - - {5270F048-E580-486C-B14C-E5B9F6E539D4} - DotNetProjects.Migrator.Framework - - - {D58C68E4-D789-40F7-9078-C9F587D4363C} - DotNetProjects.Migrator.Providers - - - - - app.config - Designer - - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 2.0 %28x86%29 - true - - - False - .NET Framework 3.0 %28x86%29 - false - - - False - .NET Framework 3.5 - false - - - False - .NET Framework 3.5 SP1 - false - - - - - - - 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}. - - - - \ No newline at end of file diff --git a/src/Migrator.Tests/Migrator.Tests.csproj b/src/Migrator.Tests/Migrator.Tests.csproj new file mode 100644 index 00000000..456eae07 --- /dev/null +++ b/src/Migrator.Tests/Migrator.Tests.csproj @@ -0,0 +1,44 @@ + + + + net9.0 + false + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Migrator.Tests/MigratorTest.cs b/src/Migrator.Tests/MigratorTest.cs index b883bf83..881cd519 100644 --- a/src/Migrator.Tests/MigratorTest.cs +++ b/src/Migrator.Tests/MigratorTest.cs @@ -1,250 +1,248 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - using System; using System.Collections.Generic; using System.Reflection; -using Migrator.Framework; -using Migrator.Framework.Loggers; +using DotNetProjects.Migrator; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Framework.Loggers; +using NSubstitute; using NUnit.Framework; -using NUnit.Mocks; -namespace Migrator.Tests +namespace Migrator.Tests; + +[TestFixture] +public class MigratorTest { - [TestFixture] - public class MigratorTest - { - #region Setup/Teardown - - [SetUp] - public void SetUp() - { - SetUpCurrentVersion(0); - } - - #endregion - - Migrator _migrator; - - // Collections that contain the version that are called migrating up and down - static readonly List _upCalled = new List(); - static readonly List _downCalled = new List(); - - void SetUpCurrentVersion(long version) - { - SetUpCurrentVersion(version, false); - } - - void SetUpCurrentVersion(long version, bool assertRollbackIsCalled) - { - SetUpCurrentVersion(version, assertRollbackIsCalled, true); - } - - void SetUpCurrentVersion(long version, bool assertRollbackIsCalled, bool includeBad) - { - var providerMock = new DynamicMock(typeof (ITransformationProvider)); - - var appliedVersions = new List(); - for (long i = 1; i <= version; i++) - { - appliedVersions.Add(i); - } - providerMock.SetReturnValue("get_AppliedMigrations", appliedVersions); - providerMock.SetReturnValue("get_Logger", new Logger(false)); - if (assertRollbackIsCalled) - providerMock.Expect("Rollback"); - else - providerMock.ExpectNoCall("Rollback"); - - _migrator = new Migrator((ITransformationProvider) providerMock.MockInstance, Assembly.GetExecutingAssembly(), false); - - // Enlève toutes les migrations trouvée automatiquement - _migrator.MigrationsTypes.Clear(); - _upCalled.Clear(); - _downCalled.Clear(); - - _migrator.MigrationsTypes.Add(typeof (FirstMigration)); - _migrator.MigrationsTypes.Add(typeof (SecondMigration)); - _migrator.MigrationsTypes.Add(typeof (ThirdMigration)); - _migrator.MigrationsTypes.Add(typeof (ForthMigration)); - _migrator.MigrationsTypes.Add(typeof (SixthMigration)); - - if (includeBad) - _migrator.MigrationsTypes.Add(typeof (BadMigration)); - } - - public class AbstractTestMigration : Migration - { - public override void Up() - { - _upCalled.Add(MigrationLoader.GetMigrationVersion(GetType())); - } - - public override void Down() - { - _downCalled.Add(MigrationLoader.GetMigrationVersion(GetType())); - } - } - - [Migration(1, Ignore = true)] - public class FirstMigration : AbstractTestMigration - { - } - - [Migration(2, Ignore = true)] - public class SecondMigration : AbstractTestMigration - { - } - - [Migration(3, Ignore = true)] - public class ThirdMigration : AbstractTestMigration - { - } - - [Migration(4, Ignore = true)] - public class ForthMigration : AbstractTestMigration - { - } - - [Migration(5, Ignore = true)] - public class BadMigration : AbstractTestMigration - { - public override void Up() - { - throw new Exception("oh uh!"); - } - - public override void Down() - { - throw new Exception("oh uh!"); - } - } - - [Migration(6, Ignore = true)] - public class SixthMigration : AbstractTestMigration - { - } - - [Migration(7)] - public class NonIgnoredMigration : AbstractTestMigration - { - } - - [Test] - public void MigrateBackward() - { - SetUpCurrentVersion(3); - _migrator.MigrateTo(1); - - Assert.AreEqual(0, _upCalled.Count); - Assert.AreEqual(2, _downCalled.Count); - - Assert.AreEqual(3, _downCalled[0]); - Assert.AreEqual(2, _downCalled[1]); - } - - [Test] - public void MigrateDownwardWithRollback() - { - SetUpCurrentVersion(6, true); - - try - { - _migrator.MigrateTo(3); - Assert.Fail("La migration 5 devrait lancer une exception"); - } - catch (Exception) - { - } - - Assert.AreEqual(0, _upCalled.Count); - Assert.AreEqual(1, _downCalled.Count); - - Assert.AreEqual(6, _downCalled[0]); - } - - [Test] - public void MigrateToCurrentVersion() - { - SetUpCurrentVersion(3); - - _migrator.MigrateTo(3); - - Assert.AreEqual(0, _upCalled.Count); - Assert.AreEqual(0, _downCalled.Count); - } - - [Test] - public void MigrateToLastVersion() - { - SetUpCurrentVersion(3, false, false); - - _migrator.MigrateToLastVersion(); - - Assert.AreEqual(2, _upCalled.Count); - Assert.AreEqual(0, _downCalled.Count); - } - - [Test] - public void MigrateUpward() - { - SetUpCurrentVersion(1); - _migrator.MigrateTo(3); - - Assert.AreEqual(2, _upCalled.Count); - Assert.AreEqual(0, _downCalled.Count); - - Assert.AreEqual(2, _upCalled[0]); - Assert.AreEqual(3, _upCalled[1]); - } - - [Test] - public void MigrateUpwardFrom0() - { - _migrator.MigrateTo(3); - - Assert.AreEqual(3, _upCalled.Count); - Assert.AreEqual(0, _downCalled.Count); - - Assert.AreEqual(1, _upCalled[0]); - Assert.AreEqual(2, _upCalled[1]); - Assert.AreEqual(3, _upCalled[2]); - } - - [Test] - public void MigrateUpwardWithRollback() - { - SetUpCurrentVersion(3, true); - - try - { - _migrator.MigrateTo(6); - Assert.Fail("La migration 5 devrait lancer une exception"); - } - catch (Exception) - { - } - - Assert.AreEqual(1, _upCalled.Count); - Assert.AreEqual(0, _downCalled.Count); - - Assert.AreEqual(4, _upCalled[0]); - } - - [Test] - public void ToHumanName() - { - Assert.AreEqual("Create a table", StringUtils.ToHumanName("CreateATable")); - } - } -} \ No newline at end of file + #region Setup/Teardown + + [SetUp] + public void SetUp() + { + SetUpCurrentVersion(0); + } + + #endregion + + private DotNetProjects.Migrator.Migrator _migrator; + + // Collections that contain the version that are called migrating up and down + private static readonly List _upCalled = new List(); + private static readonly List _downCalled = new List(); + + private void SetUpCurrentVersion(long version) + { + SetUpCurrentVersion(version, false); + } + + private void SetUpCurrentVersion(long version, bool assertRollbackIsCalled) + { + SetUpCurrentVersion(version, assertRollbackIsCalled, true); + } + + private void SetUpCurrentVersion(long version, bool assertRollbackIsCalled, bool includeBad) + { + var providerMock = Substitute.For(); + + var appliedVersions = new List(); + + for (long i = 1; i <= version; i++) + { + appliedVersions.Add(i); + } + + providerMock.AppliedMigrations.Returns(appliedVersions); + providerMock.Logger.Returns(new Logger(false)); + + providerMock.When(x => x.Dispose()).Do(_ => + { + if (assertRollbackIsCalled) + { + providerMock.Received().Rollback(); + } + else + { + providerMock.DidNotReceive().Rollback(); + } + }); + + _migrator = new DotNetProjects.Migrator.Migrator((ITransformationProvider)providerMock, Assembly.GetExecutingAssembly(), false); + + _migrator.MigrationsTypes.Clear(); + _upCalled.Clear(); + _downCalled.Clear(); + + _migrator.MigrationsTypes.Add(typeof(FirstMigration)); + _migrator.MigrationsTypes.Add(typeof(SecondMigration)); + _migrator.MigrationsTypes.Add(typeof(ThirdMigration)); + _migrator.MigrationsTypes.Add(typeof(ForthMigration)); + _migrator.MigrationsTypes.Add(typeof(SixthMigration)); + + if (includeBad) + { + _migrator.MigrationsTypes.Add(typeof(BadMigration)); + } + } + + public class AbstractTestMigration : Migration + { + public override void Up() + { + _upCalled.Add(MigrationLoader.GetMigrationVersion(GetType())); + } + + public override void Down() + { + _downCalled.Add(MigrationLoader.GetMigrationVersion(GetType())); + } + } + + [Migration(1, Ignore = true)] + public class FirstMigration : AbstractTestMigration + { + } + + [Migration(2, Ignore = true)] + public class SecondMigration : AbstractTestMigration + { + } + + [Migration(3, Ignore = true)] + public class ThirdMigration : AbstractTestMigration + { + } + + [Migration(4, Ignore = true)] + public class ForthMigration : AbstractTestMigration + { + } + + [Migration(5, Ignore = true)] + public class BadMigration : AbstractTestMigration + { + public override void Up() + { + throw new Exception("oh uh!"); + } + + public override void Down() + { + throw new Exception("oh uh!"); + } + } + + [Migration(6, Ignore = true)] + public class SixthMigration : AbstractTestMigration + { + } + + [Migration(7)] + public class NonIgnoredMigration : AbstractTestMigration + { + } + + [Test] + public void MigrateBackward() + { + SetUpCurrentVersion(3); + _migrator.MigrateTo(1); + + Assert.That(0, Is.EqualTo(_upCalled.Count)); + Assert.That(2, Is.EqualTo(_downCalled.Count)); + + Assert.That(3, Is.EqualTo(_downCalled[0])); + Assert.That(2, Is.EqualTo(_downCalled[1])); + } + + [Test] + public void MigrateDownwardWithRollback() + { + SetUpCurrentVersion(6, true); + + try + { + _migrator.MigrateTo(3); + Assert.Fail("La migration 5 devrait lancer une exception"); + } + catch (Exception) + { + } + + Assert.That(0, Is.EqualTo(_upCalled.Count)); + Assert.That(1, Is.EqualTo(_downCalled.Count)); + + Assert.That(6, Is.EqualTo(_downCalled[0])); + } + + [Test] + public void MigrateToCurrentVersion() + { + SetUpCurrentVersion(3); + + _migrator.MigrateTo(3); + + Assert.That(0, Is.EqualTo(_upCalled.Count)); + Assert.That(0, Is.EqualTo(_downCalled.Count)); + } + + [Test] + public void MigrateToLastVersion() + { + SetUpCurrentVersion(3, false, false); + + _migrator.MigrateToLastVersion(); + + Assert.That(2, Is.EqualTo(_upCalled.Count)); + Assert.That(0, Is.EqualTo(_downCalled.Count)); + } + + [Test] + public void MigrateUpward() + { + SetUpCurrentVersion(1); + _migrator.MigrateTo(3); + + Assert.That(2, Is.EqualTo(_upCalled.Count)); + Assert.That(0, Is.EqualTo(_downCalled.Count)); + + Assert.That(2, Is.EqualTo(_upCalled[0])); + Assert.That(3, Is.EqualTo(_upCalled[1])); + } + + [Test] + public void MigrateUpwardFrom0() + { + _migrator.MigrateTo(3); + + Assert.That(3, Is.EqualTo(_upCalled.Count)); + Assert.That(0, Is.EqualTo(_downCalled.Count)); + + Assert.That(1, Is.EqualTo(_upCalled[0])); + Assert.That(2, Is.EqualTo(_upCalled[1])); + Assert.That(3, Is.EqualTo(_upCalled[2])); + } + + [Test] + public void MigrateUpwardWithRollback() + { + SetUpCurrentVersion(3, true); + + try + { + _migrator.MigrateTo(6); + Assert.Fail("La migration 5 devrait lancer une exception"); + } + catch (Exception) + { + } + + Assert.That(1, Is.EqualTo(_upCalled.Count)); + Assert.That(0, Is.EqualTo(_downCalled.Count)); + + Assert.That(4, Is.EqualTo(_upCalled[0])); + } + + [Test] + public void ToHumanName() + { + Assert.That("Create a table", Is.EqualTo(StringUtils.ToHumanName("CreateATable"))); + } +} diff --git a/src/Migrator.Tests/MigratorTestDates.cs b/src/Migrator.Tests/MigratorTestDates.cs index edda6314..0065ef3f 100644 --- a/src/Migrator.Tests/MigratorTestDates.cs +++ b/src/Migrator.Tests/MigratorTestDates.cs @@ -1,312 +1,305 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - using System; using System.Collections.Generic; using System.Reflection; -using Migrator.Framework; -using Migrator.Framework.Loggers; +using DotNetProjects.Migrator; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Framework.Loggers; +using NSubstitute; using NUnit.Framework; -using NUnit.Mocks; -namespace Migrator.Tests +namespace Migrator.Tests; + +[TestFixture] +public class MigratorTestDates { - [TestFixture] - public class MigratorTestDates - { - #region Setup/Teardown - - [SetUp] - public void SetUp() - { - SetUpCurrentVersion(0); - } - - #endregion - - Migrator _migrator; - - // Collections that contain the version that are called migrating up and down - static readonly List _upCalled = new List(); - static readonly List _downCalled = new List(); - - void SetUpCurrentVersion(long version) - { - SetUpCurrentVersion(version, false); - } - - void SetUpCurrentVersion(long version, bool assertRollbackIsCalled) - { - SetUpCurrentVersion(version, assertRollbackIsCalled, true); - } - - void SetUpCurrentVersion(long version, bool assertRollbackIsCalled, bool includeBad) - { - var appliedVersions = new List(); - for (long i = 2008010195; i <= version; i += 10000) - { - appliedVersions.Add(i); - } - SetUpCurrentVersion(version, appliedVersions, assertRollbackIsCalled, includeBad); - } - - void SetUpCurrentVersion(long version, List appliedVersions, bool assertRollbackIsCalled, bool includeBad) - { - var providerMock = new DynamicMock(typeof (ITransformationProvider)); - - providerMock.SetReturnValue("get_MaxVersion", version); - providerMock.SetReturnValue("get_AppliedMigrations", appliedVersions); - providerMock.SetReturnValue("get_Logger", new Logger(false)); - if (assertRollbackIsCalled) - providerMock.Expect("Rollback"); - else - providerMock.ExpectNoCall("Rollback"); - - _migrator = new Migrator((ITransformationProvider) providerMock.MockInstance, Assembly.GetExecutingAssembly(), false); - - // Enlève toutes les migrations trouvée automatiquement - _migrator.MigrationsTypes.Clear(); - _upCalled.Clear(); - _downCalled.Clear(); - - _migrator.MigrationsTypes.Add(typeof (FirstMigration)); - _migrator.MigrationsTypes.Add(typeof (SecondMigration)); - _migrator.MigrationsTypes.Add(typeof (ThirdMigration)); - _migrator.MigrationsTypes.Add(typeof (FourthMigration)); - _migrator.MigrationsTypes.Add(typeof (SixthMigration)); - - if (includeBad) - _migrator.MigrationsTypes.Add(typeof (BadMigration)); - } - - public class AbstractTestMigration : Migration - { - public override void Up() - { - _upCalled.Add(MigrationLoader.GetMigrationVersion(GetType())); - } - - public override void Down() - { - _downCalled.Add(MigrationLoader.GetMigrationVersion(GetType())); - } - } - - [Migration(2008010195, Ignore = true)] - public class FirstMigration : AbstractTestMigration - { - } - - [Migration(2008020195, Ignore = true)] - public class SecondMigration : AbstractTestMigration - { - } - - [Migration(2008030195, Ignore = true)] - public class ThirdMigration : AbstractTestMigration - { - } - - [Migration(2008040195, Ignore = true)] - public class FourthMigration : AbstractTestMigration - { - } - - [Migration(2008050195, Ignore = true)] - public class BadMigration : AbstractTestMigration - { - public override void Up() - { - throw new Exception("oh uh!"); - } - - public override void Down() - { - throw new Exception("oh uh!"); - } - } - - [Migration(2008060195, Ignore = true)] - public class SixthMigration : AbstractTestMigration - { - } - - [Migration(2008070195)] - public class NonIgnoredMigration : AbstractTestMigration - { - } - - [Test] - public void MigrateBackward() - { - SetUpCurrentVersion(2008030195); - _migrator.MigrateTo(2008010195); - - Assert.AreEqual(0, _upCalled.Count); - Assert.AreEqual(2, _downCalled.Count); - - Assert.AreEqual(2008030195, _downCalled[0]); - Assert.AreEqual(2008020195, _downCalled[1]); - } - - [Test] - public void MigrateDownWithHoles() - { - var migs = new List(); - migs.Add(2008010195); - migs.Add(2008030195); - migs.Add(2008040195); - SetUpCurrentVersion(2008040195, migs, false, false); - _migrator.MigrateTo(2008030195); - - Assert.AreEqual(1, _upCalled.Count); - Assert.AreEqual(1, _downCalled.Count); - - Assert.AreEqual(2008020195, _upCalled[0]); - Assert.AreEqual(2008040195, _downCalled[0]); - } - - [Test] - public void MigrateDownwardWithRollback() - { - SetUpCurrentVersion(2008060195, true); - - try - { - _migrator.MigrateTo(3); - Assert.Fail("La migration 5 devrait lancer une exception"); - } - catch (Exception) - { - } - - Assert.AreEqual(0, _upCalled.Count); - Assert.AreEqual(1, _downCalled.Count); - - Assert.AreEqual(2008060195, _downCalled[0]); - } - - [Test] - public void MigrateToCurrentVersion() - { - SetUpCurrentVersion(2008030195); - - _migrator.MigrateTo(2008030195); - - Assert.AreEqual(0, _upCalled.Count); - Assert.AreEqual(0, _downCalled.Count); - } - - [Test] - public void MigrateToLastVersion() - { - SetUpCurrentVersion(2008030195, false, false); - - _migrator.MigrateToLastVersion(); - - Assert.AreEqual(2, _upCalled.Count); - Assert.AreEqual(0, _downCalled.Count); - } - - [Test] - public void MigrateUpWithHoles() - { - var migs = new List(); - migs.Add(2008010195); - migs.Add(2008030195); - SetUpCurrentVersion(2008030195, migs, false, false); - _migrator.MigrateTo(2008040195); - - Assert.AreEqual(2, _upCalled.Count); - Assert.AreEqual(0, _downCalled.Count); - - Assert.AreEqual(2008020195, _upCalled[0]); - Assert.AreEqual(2008040195, _upCalled[1]); - } - - [Test] - public void MigrateUpward() - { - SetUpCurrentVersion(2008010195); - _migrator.MigrateTo(2008030195); - - Assert.AreEqual(2, _upCalled.Count); - Assert.AreEqual(0, _downCalled.Count); - - Assert.AreEqual(2008020195, _upCalled[0]); - Assert.AreEqual(2008030195, _upCalled[1]); - } - - [Test] - public void MigrateUpwardWithRollback() - { - SetUpCurrentVersion(2008030195, true); - - try - { - _migrator.MigrateTo(2008060195); - Assert.Fail("La migration 5 devrait lancer une exception"); - } - catch (Exception) - { - } - - Assert.AreEqual(1, _upCalled.Count); - Assert.AreEqual(0, _downCalled.Count); - - Assert.AreEqual(2008040195, _upCalled[0]); - } - - [Test] - public void PostMergeMigrateDown() - { - // Assume trunk had versions 1 2 and 4. A branch is merged with 3, then - // rollback to version 2. v3 should be untouched, and v4 should be rolled back - var migs = new List(); - migs.Add(2008010195); - migs.Add(2008020195); - migs.Add(2008040195); - SetUpCurrentVersion(2008040195, migs, false, false); - _migrator.MigrateTo(2008020195); - - Assert.AreEqual(0, _upCalled.Count); - Assert.AreEqual(1, _downCalled.Count); - - Assert.AreEqual(2008040195, _downCalled[0]); - } - - [Test] - public void PostMergeOldAndMigrateLatest() - { - // Assume trunk had versions 1 2 and 4. A branch is merged with 3, then - // we migrate to Latest. v3 should be applied and nothing else done. - var migs = new List(); - migs.Add(2008010195); - migs.Add(2008020195); - migs.Add(2008040195); - SetUpCurrentVersion(2008040195, migs, false, false); - _migrator.MigrateTo(2008040195); - - Assert.AreEqual(1, _upCalled.Count); - Assert.AreEqual(0, _downCalled.Count); - - Assert.AreEqual(2008030195, _upCalled[0]); - } - - [Test] - public void ToHumanName() - { - Assert.AreEqual("Create a table", StringUtils.ToHumanName("CreateATable")); - } - } -} \ No newline at end of file + [SetUp] + public void SetUp() + { + SetUpCurrentVersion(0); + } + + private DotNetProjects.Migrator.Migrator _migrator; + + // Collections that contain the version that are called migrating up and down + private static readonly List _upCalled = []; + private static readonly List _downCalled = []; + + private void SetUpCurrentVersion(long version) + { + SetUpCurrentVersion(version, false); + } + + private void SetUpCurrentVersion(long version, bool assertRollbackIsCalled) + { + SetUpCurrentVersion(version, assertRollbackIsCalled, true); + } + + private void SetUpCurrentVersion(long version, bool assertRollbackIsCalled, bool includeBad) + { + var appliedVersions = new List(); + + for (long i = 2008010195; i <= version; i += 10000) + { + appliedVersions.Add(i); + } + + SetUpCurrentVersion(version, appliedVersions, assertRollbackIsCalled, includeBad); + } + + private void SetUpCurrentVersion(long version, List appliedVersions, bool assertRollbackIsCalled, bool includeBad) + { + var providerMock = Substitute.For(); + + providerMock.AppliedMigrations.Returns(appliedVersions); + providerMock.Logger.Returns(new Logger(false)); + + providerMock.When(x => x.Dispose()).Do(_ => + { + if (assertRollbackIsCalled) + { + providerMock.Received().Rollback(); + } + else + { + providerMock.DidNotReceive().Rollback(); + } + }); + + _migrator = new DotNetProjects.Migrator.Migrator((ITransformationProvider)providerMock, Assembly.GetExecutingAssembly(), false); + + _migrator.MigrationsTypes.Clear(); + _upCalled.Clear(); + _downCalled.Clear(); + + _migrator.MigrationsTypes.Add(typeof(FirstMigration)); + _migrator.MigrationsTypes.Add(typeof(SecondMigration)); + _migrator.MigrationsTypes.Add(typeof(ThirdMigration)); + _migrator.MigrationsTypes.Add(typeof(FourthMigration)); + _migrator.MigrationsTypes.Add(typeof(SixthMigration)); + + if (includeBad) + { + _migrator.MigrationsTypes.Add(typeof(BadMigration)); + } + } + + public class AbstractTestMigration : Migration + { + public override void Up() + { + _upCalled.Add(MigrationLoader.GetMigrationVersion(GetType())); + } + + public override void Down() + { + _downCalled.Add(MigrationLoader.GetMigrationVersion(GetType())); + } + } + + [Migration(2008010195, Ignore = true)] + public class FirstMigration : AbstractTestMigration + { + } + + [Migration(2008020195, Ignore = true)] + public class SecondMigration : AbstractTestMigration + { + } + + [Migration(2008030195, Ignore = true)] + public class ThirdMigration : AbstractTestMigration + { + } + + [Migration(2008040195, Ignore = true)] + public class FourthMigration : AbstractTestMigration + { + } + + [Migration(2008050195, Ignore = true)] + public class BadMigration : AbstractTestMigration + { + public override void Up() + { + throw new Exception("oh uh!"); + } + + public override void Down() + { + throw new Exception("oh uh!"); + } + } + + [Migration(2008060195, Ignore = true)] + public class SixthMigration : AbstractTestMigration + { + } + + [Migration(2008070195)] + public class NonIgnoredMigration : AbstractTestMigration + { + } + + [Test] + public void MigrateBackward() + { + SetUpCurrentVersion(2008030195); + _migrator.MigrateTo(2008010195); + + Assert.That(0, Is.EqualTo(_upCalled.Count)); + Assert.That(2, Is.EqualTo(_downCalled.Count)); + + Assert.That(2008030195, Is.EqualTo(_downCalled[0])); + Assert.That(2008020195, Is.EqualTo(_downCalled[1])); + } + + [Test] + public void MigrateDownWithHoles() + { + var migs = new List(); + migs.Add(2008010195); + migs.Add(2008030195); + migs.Add(2008040195); + SetUpCurrentVersion(2008040195, migs, false, false); + _migrator.MigrateTo(2008030195); + + Assert.That(1, Is.EqualTo(_upCalled.Count)); + Assert.That(1, Is.EqualTo(_downCalled.Count)); + + Assert.That(2008020195, Is.EqualTo(_upCalled[0])); + Assert.That(2008040195, Is.EqualTo(_downCalled[0])); + } + + [Test] + public void MigrateDownwardWithRollback() + { + SetUpCurrentVersion(2008060195, true); + + try + { + _migrator.MigrateTo(3); + Assert.Fail("La migration 5 devrait lancer une exception"); + } + catch (Exception) + { + } + + Assert.That(0, Is.EqualTo(_upCalled.Count)); + Assert.That(1, Is.EqualTo(_downCalled.Count)); + + Assert.That(2008060195, Is.EqualTo(_downCalled[0])); + } + + [Test] + public void MigrateToCurrentVersion() + { + SetUpCurrentVersion(2008030195); + + _migrator.MigrateTo(2008030195); + + Assert.That(0, Is.EqualTo(_upCalled.Count)); + Assert.That(0, Is.EqualTo(_downCalled.Count)); + } + + [Test] + public void MigrateToLastVersion() + { + SetUpCurrentVersion(2008030195, false, false); + + _migrator.MigrateToLastVersion(); + + Assert.That(2, Is.EqualTo(_upCalled.Count)); + Assert.That(0, Is.EqualTo(_downCalled.Count)); + } + + [Test] + public void MigrateUpWithHoles() + { + var migs = new List(); + migs.Add(2008010195); + migs.Add(2008030195); + SetUpCurrentVersion(2008030195, migs, false, false); + _migrator.MigrateTo(2008040195); + + Assert.That(2, Is.EqualTo(_upCalled.Count)); + Assert.That(0, Is.EqualTo(_downCalled.Count)); + + Assert.That(2008020195, Is.EqualTo(_upCalled[0])); + Assert.That(2008040195, Is.EqualTo(_upCalled[1])); + } + + [Test] + public void MigrateUpward() + { + SetUpCurrentVersion(2008010195); + _migrator.MigrateTo(2008030195); + + Assert.That(2, Is.EqualTo(_upCalled.Count)); + Assert.That(0, Is.EqualTo(_downCalled.Count)); + + Assert.That(2008020195, Is.EqualTo(_upCalled[0])); + Assert.That(2008030195, Is.EqualTo(_upCalled[1])); + } + + [Test] + public void MigrateUpwardWithRollback() + { + SetUpCurrentVersion(2008030195, true); + + try + { + _migrator.MigrateTo(2008060195); + Assert.Fail("La migration 5 devrait lancer une exception"); + } + catch (Exception) + { + } + + Assert.That(1, Is.EqualTo(_upCalled.Count)); + Assert.That(0, Is.EqualTo(_downCalled.Count)); + + Assert.That(2008040195, Is.EqualTo(_upCalled[0])); + } + + [Test] + public void PostMergeMigrateDown() + { + // Assume trunk had versions 1 2 and 4. A branch is merged with 3, then + // rollback to version 2. v3 should be untouched, and v4 should be rolled back + var migs = new List(); + migs.Add(2008010195); + migs.Add(2008020195); + migs.Add(2008040195); + SetUpCurrentVersion(2008040195, migs, false, false); + _migrator.MigrateTo(2008020195); + + Assert.That(0, Is.EqualTo(_upCalled.Count)); + Assert.That(1, Is.EqualTo(_downCalled.Count)); + + Assert.That(2008040195, Is.EqualTo(_downCalled[0])); + } + + [Test] + public void PostMergeOldAndMigrateLatest() + { + // Assume trunk had versions 1 2 and 4. A branch is merged with 3, then + // we migrate to Latest. v3 should be applied and nothing else done. + var migs = new List(); + migs.Add(2008010195); + migs.Add(2008020195); + migs.Add(2008040195); + SetUpCurrentVersion(2008040195, migs, false, false); + _migrator.MigrateTo(2008040195); + + Assert.That(1, Is.EqualTo(_upCalled.Count)); + Assert.That(0, Is.EqualTo(_downCalled.Count)); + + Assert.That(2008030195, Is.EqualTo(_upCalled[0])); + } + + [Test] + public void ToHumanName() + { + Assert.That("Create a table", Is.EqualTo(StringUtils.ToHumanName("CreateATable"))); + } +} diff --git a/src/Migrator.Tests/ProviderFactoryTest.cs b/src/Migrator.Tests/ProviderFactoryTest.cs index 6db76322..8c9c3c81 100644 --- a/src/Migrator.Tests/ProviderFactoryTest.cs +++ b/src/Migrator.Tests/ProviderFactoryTest.cs @@ -1,94 +1,90 @@ -using System; -using System.Configuration; -using System.Linq; -using Migrator.Framework; -using Migrator.Providers; +using System; +using System.Linq; +using DotNetProjects.Migrator; +using DotNetProjects.Migrator.Providers; +using Migrator.Tests.Settings; +using Migrator.Tests.Settings.Config; +using Npgsql; +using NUnit.Framework; -using NUnit.Framework; - -namespace Migrator.Tests -{ - [TestFixture] - public class ProviderFactoryTest - { - [Test] - public void CanGetDialectsForProvider() - { - foreach (ProviderTypes provider in Enum.GetValues(typeof(ProviderTypes)).Cast().Where(x=>x!=ProviderTypes.none)) - { - Assert.IsNotNull(ProviderFactory.DialectForProvider(provider)); - } - Assert.IsNull(ProviderFactory.DialectForProvider(ProviderTypes.none)); - } - - [Test] - [Category("MySql")] - public void CanLoad_MySqlProvider() - { - ITransformationProvider provider = ProviderFactory.Create(ProviderTypes.Mysql, - ConfigurationManager.AppSettings[ - "MySqlConnectionString"], null); - Assert.IsNotNull(provider); - } - - [Test] - [Category("Oracle")] - public void CanLoad_OracleProvider() - { - ITransformationProvider provider = ProviderFactory.Create(ProviderTypes.Oracle, - ConfigurationManager.AppSettings[ - "OracleConnectionString"], null); - Assert.IsNotNull(provider); - } - - [Test] - [Category("Postgre")] - public void CanLoad_PostgreSQLProvider() - { - ITransformationProvider provider = ProviderFactory.Create(ProviderTypes.PostgreSQL, - ConfigurationManager.AppSettings[ - "NpgsqlConnectionString"], null); - Assert.IsNotNull(provider); - } - - [Test] - [Category("SQLite")] - public void CanLoad_SQLiteProvider() - { - ITransformationProvider provider = ProviderFactory.Create(ProviderTypes.SQLite, - ConfigurationManager.AppSettings[ - "SQLiteConnectionString"], null); - Assert.IsNotNull(provider); - } - - [Test] - [Category("SqlServer2005")] - public void CanLoad_SqlServer2005Provider() - { - ITransformationProvider provider = ProviderFactory.Create(ProviderTypes.SqlServer2005, - ConfigurationManager.AppSettings[ - "SqlServer2005ConnectionString"], null); - Assert.IsNotNull(provider); - } - - [Test] - [Category("SqlServerCe")] - public void CanLoad_SqlServerCeProvider() - { - ITransformationProvider provider = ProviderFactory.Create(ProviderTypes.SqlServerCe, - ConfigurationManager.AppSettings[ - "SqlServerCeConnectionString"], null); - Assert.IsNotNull(provider); - } - - [Test] - [Category("SqlServer")] - public void CanLoad_SqlServerProvider() - { - ITransformationProvider provider = ProviderFactory.Create(ProviderTypes.SqlServer, - ConfigurationManager.AppSettings[ - "SqlServerConnectionString"], null); - Assert.IsNotNull(provider); - } - } -} \ No newline at end of file +namespace Migrator.Tests; + +[TestFixture] +public class ProviderFactoryTest +{ + [Test] + public void CanGetDialectsForProvider() + { + foreach (var provider in Enum.GetValues(typeof(ProviderTypes)).Cast().Where(x => x != ProviderTypes.none)) + { + Assert.That(ProviderFactory.DialectForProvider(provider), Is.Not.Null); + } + + Assert.That(ProviderFactory.DialectForProvider(ProviderTypes.none), Is.Null); + } + + [SetUp] + public void SetUp() + { + DbProviderFactories.RegisterFactory("Npgsql", () => NpgsqlFactory.Instance); + DbProviderFactories.RegisterFactory("MySql.Data.MySqlClient", () => MySql.Data.MySqlClient.MySqlClientFactory.Instance); + DbProviderFactories.RegisterFactory("Oracle.DataAccess.Client", () => Oracle.ManagedDataAccess.Client.OracleClientFactory.Instance); + DbProviderFactories.RegisterFactory("System.Data.SqlClient", () => Microsoft.Data.SqlClient.SqlClientFactory.Instance); + DbProviderFactories.RegisterFactory("System.Data.SQLite", () => System.Data.SQLite.SQLiteFactory.Instance); + } + + [Test] + [Category("MySql")] + public void CanLoad_MySqlProvider() + { + var configReader = new ConfigurationReader(); + var connectionString = configReader.GetDatabaseConnectionConfigById(DatabaseConnectionConfigIds.MySQLId)?.ConnectionString; + + using var provider = ProviderFactory.Create(ProviderTypes.Mysql, connectionString, null); + Assert.That(provider, Is.Not.Null); + } + + [Test] + [Category("Oracle")] + public void CanLoad_OracleProvider() + { + var configReader = new ConfigurationReader(); + var connectionString = configReader.GetDatabaseConnectionConfigById(DatabaseConnectionConfigIds.OracleId)?.ConnectionString; + + using var provider = ProviderFactory.Create(ProviderTypes.Oracle, connectionString, null); + Assert.That(provider, Is.Not.Null); + } + + [Test] + [Category("Postgre")] + public void CanLoad_PostgreSQLProvider() + { + var configReader = new ConfigurationReader(); + var connectionString = configReader.GetDatabaseConnectionConfigById(DatabaseConnectionConfigIds.PostgreSQL)?.ConnectionString; + + using var provider = ProviderFactory.Create(ProviderTypes.PostgreSQL, connectionString, null); + Assert.That(provider, Is.Not.Null); + } + + [Test] + [Category("SQLite")] + public void CanLoad_SQLiteProvider() + { + var configReader = new ConfigurationReader(); + var connectionString = configReader.GetDatabaseConnectionConfigById(DatabaseConnectionConfigIds.SQLiteId)?.ConnectionString; + + using var provider = ProviderFactory.Create(ProviderTypes.SQLite, connectionString, null); + Assert.That(provider, Is.Not.Null); + } + + [Test] + [Category("SqlServer")] + public void CanLoad_SqlServerProvider() + { + var configReader = new ConfigurationReader(); + var connectionString = configReader.GetDatabaseConnectionConfigById(DatabaseConnectionConfigIds.SQLServerId)?.ConnectionString; + + using var provider = ProviderFactory.Create(ProviderTypes.SqlServer, connectionString, null); + Assert.That(provider, Is.Not.Null); + } +} diff --git a/src/Migrator.Tests/Providers/Base/TransformationProviderBase.cs b/src/Migrator.Tests/Providers/Base/TransformationProviderBase.cs new file mode 100644 index 00000000..f12b7f27 --- /dev/null +++ b/src/Migrator.Tests/Providers/Base/TransformationProviderBase.cs @@ -0,0 +1,162 @@ +using System; +using System.Data; +using System.Threading; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers; +using DotNetProjects.Migrator.Providers.Impl.Oracle; +using DotNetProjects.Migrator.Providers.Impl.PostgreSQL; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using DotNetProjects.Migrator.Providers.Impl.SqlServer; +using DryIoc; +using Migrator.Tests.Database; +using Migrator.Tests.Database.Interfaces; +using Migrator.Tests.Settings; +using Migrator.Tests.Settings.Config; +using Migrator.Tests.Settings.Models; +using Npgsql; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.Base; + +/// +/// Base class for provider tests. +/// +public abstract class TransformationProviderBase +{ + private IDbConnection _dbConnection; + protected ITransformationProvider Provider; + + [TearDown] + public virtual void TearDown() + { + DropTestTables(); + + Provider?.Rollback(); + + _dbConnection?.Dispose(); + } + + protected void DropTestTables() + { + // Because MySql doesn't support schema transaction + // we got to remove the tables manually... sad... + try + { + Provider.RemoveTable("TestTwo"); + } + catch (Exception) + { + } + try + { + Provider.RemoveTable("Test"); + } + catch (Exception) + { + } + try + { + Provider.RemoveTable("SchemaInfo"); + } + catch (Exception) + { + } + } + + protected async Task BeginOracleTransactionAsync() + { + using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1)); + var configReader = new ConfigurationReader(); + + var databaseConnectionConfig = configReader.GetDatabaseConnectionConfigById(DatabaseConnectionConfigIds.OracleId); + + var connectionString = databaseConnectionConfig?.ConnectionString; + + if (string.IsNullOrEmpty(connectionString)) + { + throw new IgnoreException($"No Oracle {nameof(DatabaseConnectionConfig.ConnectionString)} is set."); + } + + DbProviderFactories.RegisterFactory("Oracle.ManagedDataAccess.Client", () => Oracle.ManagedDataAccess.Client.OracleClientFactory.Instance); + + using var container = new Container(); + container.RegisterDatabaseIntegrationTestService(); + var databaseIntegrationTestServiceFactory = container.Resolve(); + var oracleIntegrationTestService = databaseIntegrationTestServiceFactory.Create(DatabaseProviderType.Oracle); + var databaseInfo = await oracleIntegrationTestService.CreateTestDatabaseAsync(databaseConnectionConfig, cts.Token); + + Provider = new OracleTransformationProvider(new OracleDialect(), databaseInfo.DatabaseConnectionConfig.ConnectionString, null, "default", "Oracle.ManagedDataAccess.Client"); + + Provider.BeginTransaction(); + } + + protected async Task BeginPostgreSQLTransactionAsync() + { + using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1)); + var configReader = new ConfigurationReader(); + + var databaseConnectionConfig = configReader.GetDatabaseConnectionConfigById(DatabaseConnectionConfigIds.PostgreSQL); + + var connectionString = databaseConnectionConfig?.ConnectionString; + + if (string.IsNullOrEmpty(connectionString)) + { + throw new IgnoreException("No Postgre SQL connection string is set."); + } + + DbProviderFactories.RegisterFactory("Npgsql", () => Npgsql.NpgsqlFactory.Instance); + + using var container = new Container(); + container.RegisterDatabaseIntegrationTestService(); + var databaseIntegrationTestServiceFactory = container.Resolve(); + var postgreIntegrationTestService = databaseIntegrationTestServiceFactory.Create(DatabaseProviderType.Postgres); + var databaseInfo = await postgreIntegrationTestService.CreateTestDatabaseAsync(databaseConnectionConfig, cts.Token); + + + _dbConnection = new NpgsqlConnection(databaseInfo.DatabaseConnectionConfig.ConnectionString); + + Provider = new PostgreSQLTransformationProvider(new PostgreSQLDialect(), _dbConnection, null, "default", "Npgsql"); + Provider.BeginTransaction(); + + await Task.CompletedTask; + } + + protected async Task BeginSQLiteTransactionAsync() + { + var configReader = new ConfigurationReader(); + var connectionString = configReader.GetDatabaseConnectionConfigById(DatabaseConnectionConfigIds.SQLiteId) + .ConnectionString; + + Provider = new SQLiteTransformationProvider(new SQLiteDialect(), connectionString, "default", null); + Provider.BeginTransaction(); + + await Task.CompletedTask; + } + + protected async Task BeginSQLServerTransactionAsync() + { + var configReader = new ConfigurationReader(); + + var databaseConnectionConfig = configReader.GetDatabaseConnectionConfigById(DatabaseConnectionConfigIds.SQLServerId); + + var connectionString = databaseConnectionConfig?.ConnectionString; + + if (string.IsNullOrEmpty(connectionString)) + { + throw new IgnoreException($"No SQL Server {nameof(DatabaseConnectionConfig.ConnectionString)} is set."); + } + + DbProviderFactories.RegisterFactory("Microsoft.Data.SqlClient", () => Microsoft.Data.SqlClient.SqlClientFactory.Instance); + + using var container = new Container(); + container.RegisterDatabaseIntegrationTestService(); + var databaseIntegrationTestServiceFactory = container.Resolve(); + var sqlServerIntegrationTestService = databaseIntegrationTestServiceFactory.Create(DatabaseProviderType.SQLServer); + var databaseInfo = await sqlServerIntegrationTestService.CreateTestDatabaseAsync(databaseConnectionConfig, CancellationToken.None); + + Provider = new SqlServerTransformationProvider(new SqlServerDialect(), databaseInfo.DatabaseConnectionConfig.ConnectionString, "dbo", "default", "Microsoft.Data.SqlClient"); + + Provider.BeginTransaction(); + } +} diff --git a/src/Migrator.Tests/Providers/Base/TransformationProviderSimpleBase.cs b/src/Migrator.Tests/Providers/Base/TransformationProviderSimpleBase.cs new file mode 100644 index 00000000..94050506 --- /dev/null +++ b/src/Migrator.Tests/Providers/Base/TransformationProviderSimpleBase.cs @@ -0,0 +1,44 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; + +namespace Migrator.Tests.Providers.Base; + +public abstract class TransformationProviderSimpleBase : TransformationProviderBase +{ + public void AddDefaultTable() + { + Provider.AddTable("TestTwo", + new Column("Id", DbType.Int32, ColumnProperty.PrimaryKey), + new Column("TestId", DbType.Int32) + ); + } + + public void AddTable() + { + Provider.AddTable("Test", + new Column("Id", DbType.Int32, ColumnProperty.NotNull), + new Column("Title", DbType.String, 100, ColumnProperty.Null), + new Column("name", DbType.String, 50, ColumnProperty.Null), + new Column("blobVal", DbType.Binary, ColumnProperty.Null), + new Column("boolVal", DbType.Boolean, ColumnProperty.Null), + new Column("bigstring", DbType.String, 50000, ColumnProperty.Null) + ); + } + + public void AddTableWithPrimaryKey() + { + Provider.AddTable("Test", + new Column("Id", DbType.Int32, ColumnProperty.PrimaryKeyWithIdentity), + new Column("Title", DbType.String, 100, ColumnProperty.Null), + new Column("name", DbType.String, 50, ColumnProperty.NotNull), + new Column("blobVal", DbType.Binary), + new Column("boolVal", DbType.Boolean), + new Column("bigstring", DbType.String, 50000) + ); + } + + public void AddPrimaryKey() + { + Provider.AddPrimaryKey("PK_Test", "Test", "Id"); + } +} diff --git a/src/Migrator.Tests/Providers/Generic/Generic_AddIndexTestsBase.cs b/src/Migrator.Tests/Providers/Generic/Generic_AddIndexTestsBase.cs new file mode 100644 index 00000000..1076d801 --- /dev/null +++ b/src/Migrator.Tests/Providers/Generic/Generic_AddIndexTestsBase.cs @@ -0,0 +1,123 @@ +using System.Data; +using System.Linq; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Models.Indexes; +using DotNetProjects.Migrator.Providers.Models.Indexes.Enums; +using Migrator.Tests.Providers.Base; +using NUnit.Framework; +using Index = DotNetProjects.Migrator.Framework.Index; + +namespace Migrator.Tests.Providers.Generic; + +public abstract class Generic_AddIndexTestsBase : TransformationProviderBase +{ + [Test] + public void AddIndex_TableDoesNotExist() + { + // Act + Assert.Throws(() => Provider.AddIndex("NotExistingTable", new Index())); + Assert.Throws(() => Provider.AddIndex("NotExistingIndex", "NotExistingTable", "column")); + } + + [Test] + public void AddIndex_AddAlreadyExistingIndex_Throws() + { + // Arrange + const string tableName = "TestTable"; + const string columnName = "TestColumn"; + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, new Column(columnName, DbType.Int32)); + Provider.AddIndex(tableName, new Index { Name = indexName, KeyColumns = [columnName] }); + + // Act/Assert + // Add already existing index + Assert.Throws(() => Provider.AddIndex(tableName, new Index { Name = indexName, KeyColumns = [columnName] })); + } + + [Test] + public void AddIndex_IncludeColumnsContainsColumnThatExistInKeyColumns_Throws() + { + // Arrange + const string tableName = "TestTable"; + const string columnName1 = "TestColumn1"; + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, new Column(columnName1, DbType.Int32)); + + Assert.Throws(() => Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [columnName1], + IncludeColumns = [columnName1] + })); + } + + [Test] + public void AddIndex_ColumnNameUsedInFilterItemDoesNotExistInKeyColumns_Throws() + { + // Arrange + const string tableName = "TestTable"; + const string columnName1 = "TestColumn1"; + const string columnName2 = "TestColumn2"; + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, + new Column(columnName1, DbType.Int32), + new Column(columnName2, DbType.Int32) + ); + + Assert.Throws(() => Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [columnName1], + FilterItems = [new FilterItem { Filter = FilterType.GreaterThan, ColumnName = columnName2, Value = 12 }] + })); + } + + [Test] + public void AddIndex_UsingIndexInstanceOverload_NonUnique_ShouldBeReadable() + { + // Arrange + const string tableName = "TestTable"; + const string columnName = "TestColumn"; + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, new Column(columnName, DbType.Int32)); + + // Act + Provider.AddIndex(tableName, new Index { Name = indexName, KeyColumns = [columnName] }); + + // Assert + var indexes = Provider.GetIndexes(tableName); + + var index = indexes.Single(); + + Assert.That(index.Name, Is.EqualTo(indexName).IgnoreCase); + Assert.That(index.KeyColumns.Single(), Is.EqualTo(columnName).IgnoreCase); + } + + [Test] + public void AddIndex_UsingNonIndexInstanceOverload_NonUnique_ShouldBeReadable() + { + // Arrange + const string tableName = "TestTable"; + const string columnName = "TestColumn"; + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, new Column(columnName, DbType.Int32)); + + // Act + Provider.AddIndex(indexName, tableName, columnName); + + // Assert + var indexes = Provider.GetIndexes(tableName); + + var index = indexes.Single(); + + Assert.That(index.Name, Is.EqualTo(indexName).IgnoreCase); + Assert.That(index.KeyColumns.Single(), Is.EqualTo(columnName).IgnoreCase); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/Generic/Generic_AddPrimaryKey.cs b/src/Migrator.Tests/Providers/Generic/Generic_AddPrimaryKey.cs new file mode 100644 index 00000000..b203a11c --- /dev/null +++ b/src/Migrator.Tests/Providers/Generic/Generic_AddPrimaryKey.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.Data; +using System.Linq; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.Generic; + +[TestFixture] +public abstract class Generic_AddPrimaryTestsBase : TransformationProviderBase +{ + [Test] + public void AddPrimaryKey_IdentityColumnWithData_Success() + { + // Arrange + const string tableName = "TestTable"; + const string columnName1 = "TestColumn1"; + const string columnName2 = "TestColumn2"; + + Provider.AddTable(tableName, + new Column(columnName1, DbType.Int32, property: ColumnProperty.Identity | ColumnProperty.PrimaryKey), + new Column(columnName2, DbType.String) + ); + + // Act + Provider.Insert(tableName, [columnName2], ["Hello"]); + Provider.Insert(tableName, [columnName2], ["Hello2"]); + + // Assert + + List<(int, string)> list = []; + + using var cmd = Provider.CreateCommand(); + using var reader = Provider.Select(cmd, tableName, [columnName1, columnName2]); + + while (reader.Read()) + { + list.Add((reader.GetInt32(0), reader.GetString(1))); + } + + list = list.OrderBy(x => x.Item1).ToList(); + + Assert.That(list[0].Item1, Is.EqualTo(1)); + Assert.That(list[1].Item1, Is.EqualTo(2)); + } + + [Test] + public void AddPrimaryKey_AddPrimaryKey_ShouldStillBeNotNull() + { + // Arrange + const string tableName = "TestTable"; + const string columnName1 = "TestColumn1"; + const string columnName2 = "TestColumn2"; + + Provider.AddTable(tableName, + new Column(columnName1, DbType.Int32, property: ColumnProperty.NotNull), + new Column(columnName2, DbType.DateTime, property: ColumnProperty.NotNull) + ); + + // Act + Provider.AddPrimaryKey(name: "MyPkName", table: tableName, columnName1); + + // Assert + var column1 = Provider.GetColumnByName(table: tableName, column: columnName1); + var column2 = Provider.GetColumnByName(table: tableName, column: columnName2); + + Assert.That(column1.ColumnProperty.HasFlag(ColumnProperty.NotNull), Is.True); + Assert.That(column2.ColumnProperty.HasFlag(ColumnProperty.NotNull), Is.True); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/Generic/Generic_AddTableTestsBase.cs b/src/Migrator.Tests/Providers/Generic/Generic_AddTableTestsBase.cs new file mode 100644 index 00000000..0c78c68b --- /dev/null +++ b/src/Migrator.Tests/Providers/Generic/Generic_AddTableTestsBase.cs @@ -0,0 +1,145 @@ +using System.Collections.Generic; +using System.Data; +using System.Linq; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.Generic; + +[TestFixture] +public abstract class Generic_AddTableTestsBase : TransformationProviderBase +{ + [Test] + public void AddTable_PrimaryKeyWithIdentity_Success() + { + // Arrange + var tableName = "TableName"; + var column1Name = "Column1"; + var column2Name = "Column2"; + + // Act + Provider.AddTable(tableName, + new Column(column1Name, DbType.Int32, ColumnProperty.NotNull | ColumnProperty.PrimaryKeyWithIdentity), + new Column(column2Name, DbType.Int32, ColumnProperty.NotNull) + ); + + // Assert + var column1 = Provider.GetColumnByName(tableName, column1Name); + var column2 = Provider.GetColumnByName(tableName, column2Name); + + Assert.That(column1.ColumnProperty.HasFlag(ColumnProperty.PrimaryKeyWithIdentity), Is.True); + Assert.That(column2.ColumnProperty.HasFlag(ColumnProperty.NotNull), Is.True); + } + + [Test] + public void AddTable_PrimaryKeyAndIdentity_Success() + { + // Arrange + var tableName = "TableName"; + var column1Name = "Column1"; + var column2Name = "Column2"; + + // Act + Provider.AddTable(tableName, + new Column(column1Name, DbType.Int32, ColumnProperty.NotNull | ColumnProperty.PrimaryKey | ColumnProperty.Identity), + new Column(column2Name, DbType.Int32, ColumnProperty.NotNull) + ); + + // Assert + var column1 = Provider.GetColumnByName(tableName, column1Name); + var column2 = Provider.GetColumnByName(tableName, column2Name); + + Assert.That(column1.ColumnProperty.HasFlag(ColumnProperty.PrimaryKeyWithIdentity), Is.True); + Assert.That(column2.ColumnProperty.HasFlag(ColumnProperty.NotNull), Is.True); + } + + [Test] + public void AddTable_PrimaryKeyAndIdentityWithInsertNull_Success() + { + // Arrange + var tableName = "TableName"; + var column1Name = "Column1"; + var column2Name = "Column2"; + + // Act + Provider.AddTable(tableName, + new Column(column1Name, DbType.Int32, ColumnProperty.NotNull | ColumnProperty.PrimaryKey | ColumnProperty.Identity), + new Column(column2Name, DbType.Int32, ColumnProperty.NotNull) + ); + + Provider.Insert(table: tableName, [column2Name], [999]); + + // Assert + var column1 = Provider.GetColumnByName(tableName, column1Name); + var column2 = Provider.GetColumnByName(tableName, column2Name); + + using var cmd = Provider.CreateCommand(); + using var reader = Provider.Select(cmd: cmd, table: tableName, columns: [column1Name, column2Name]); + + List<(int, int)> records = []; + + while (reader.Read()) + { + records.Add((reader.GetInt32(0), reader.GetInt32(1))); + } + + Assert.That(records.Single().Item1, Is.EqualTo(1)); + + Assert.That(column1.ColumnProperty.HasFlag(ColumnProperty.PrimaryKeyWithIdentity), Is.True); + Assert.That(column2.ColumnProperty.HasFlag(ColumnProperty.NotNull), Is.True); + } + + [Test] + public void AddTable_PrimaryKeyAndIdentityWithoutNotNull_Success() + { + // Arrange + var tableName = "TableName"; + var column1Name = "Column1"; + var column2Name = "Column2"; + + // Act + Provider.AddTable(tableName, + new Column(column1Name, DbType.Int32, ColumnProperty.PrimaryKey | ColumnProperty.Identity), + new Column(column2Name, DbType.Int32, ColumnProperty.NotNull) + ); + + // Assert + var column1 = Provider.GetColumnByName(tableName, column1Name); + var column2 = Provider.GetColumnByName(tableName, column2Name); + + Assert.That(column1.ColumnProperty.HasFlag(ColumnProperty.PrimaryKeyWithIdentity), Is.True); + Assert.That(column2.ColumnProperty.HasFlag(ColumnProperty.NotNull), Is.True); + } + + [Test] + public void AddTable_NotNull_Success() + { + // Arrange + var tableName = "TableName"; + var column1Name = "Column1"; + + // Act + Provider.AddTable(tableName, + new Column(column1Name, DbType.Int32, ColumnProperty.NotNull) + ); + + // Assert + var column1 = Provider.GetColumnByName(tableName, column1Name); + + Assert.That(column1.ColumnProperty.HasFlag(ColumnProperty.NotNull), Is.True); + } + + + [Test] + public void AddTableWithCompoundPrimaryKey() + { + Provider.AddTable("Test", + new Column("PersonId", DbType.Int32, ColumnProperty.PrimaryKey), + new Column("AddressId", DbType.Int32, ColumnProperty.PrimaryKey) + ); + + Assert.That(Provider.TableExists("Test"), Is.True, "Table doesn't exist"); + Assert.That(Provider.PrimaryKeyExists("Test", "PK_Test"), Is.True, "Constraint doesn't exist"); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/Generic/Generic_ChangeColumnTestsBase.cs b/src/Migrator.Tests/Providers/Generic/Generic_ChangeColumnTestsBase.cs new file mode 100644 index 00000000..b20ad58b --- /dev/null +++ b/src/Migrator.Tests/Providers/Generic/Generic_ChangeColumnTestsBase.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.Generic; + +public abstract class Generic_ChangeColumnTestsBase : TransformationProviderBase +{ + [Test] + public void ChangeColumn_NotNullAndNullToNotNull_Success() + { + // Arrange + var tableName = "TableName"; + var column1Name = "Column1"; + var column2Name = "Column2"; + + // Act + Provider.AddTable(tableName, + new Column(column1Name, DbType.DateTime, ColumnProperty.NotNull), + new Column(column2Name, DbType.DateTime, ColumnProperty.Null) + ); + + // Assert + Provider.ChangeColumn(tableName, new Column(column1Name, DbType.DateTime2, ColumnProperty.NotNull)); + Provider.ChangeColumn(tableName, new Column(column2Name, DbType.DateTime2, ColumnProperty.NotNull)); + var column1 = Provider.GetColumnByName(tableName, column1Name); + var column2 = Provider.GetColumnByName(tableName, column2Name); + + Assert.That(column1.ColumnProperty.HasFlag(ColumnProperty.NotNull), Is.True); + Assert.That(column2.ColumnProperty.HasFlag(ColumnProperty.NotNull), Is.True); + } + + [Test, Ignore("Not yet implemented. See issue https://github.com/dotnetprojects/Migrator.NET/issues/139")] + public void ChangeColumn_RemoveDefaultValue_Success() + { + // Arrange + var tableName = "TableName"; + var column1Name = "Column1"; + var column2Name = "Column2"; + + var testTime = new DateTime(2025, 5, 5, 5, 5, 5, DateTimeKind.Utc); + + Provider.AddTable(tableName, + new Column(name: column1Name, type: DbType.Int32, property: ColumnProperty.NotNull), + new Column(name: column2Name, type: DbType.DateTime2, property: ColumnProperty.Null, defaultValue: testTime) + ); + + // Act + Provider.Insert(table: tableName, [column1Name], [1]); + Provider.ChangeColumn(table: tableName, column: new Column(name: column2Name, type: DbType.DateTime2, property: ColumnProperty.Null)); + + // Assert + Provider.Insert(table: tableName, [column1Name], [2]); + + using var cmd = Provider.CreateCommand(); + using var reader = Provider.Select(cmd: cmd, table: tableName, columns: [column1Name, column2Name]); + + List<(int, DateTime)> records = []; + + while (reader.Read()) + { + records.Add((reader.GetInt32(0), reader.GetDateTime(1))); + } + + Assert.That(records.Count, Is.EqualTo(2)); + Assert.That(records.Single(x => x.Item1 == 1).Item2, Is.EqualTo(testTime)); + Assert.That(records.Single(x => x.Item1 == 2).Item2, Is.Null); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/Generic/Generic_ConstraintExistsBase.cs b/src/Migrator.Tests/Providers/Generic/Generic_ConstraintExistsBase.cs new file mode 100644 index 00000000..a55e1aaa --- /dev/null +++ b/src/Migrator.Tests/Providers/Generic/Generic_ConstraintExistsBase.cs @@ -0,0 +1,38 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.Generic; + +[TestFixture] +public abstract class Generic_ConstraintExistsBase : TransformationProviderBase +{ + /// + /// Should return true if foreign key exists. + /// + [Test] + public void ConstraintExists_ForeignKeyExists_ReturnsTrue() + { + // Arrange + var tableName = "Task"; + var fkName = "FK_Task_TaskGroup"; + + Provider.AddTable("Task", + new Column(name: "Id", type: DbType.Int32, property: ColumnProperty.PrimaryKey), + new Column(name: "TaskGroupId", type: DbType.Int32, property: ColumnProperty.Null) + ); + + Provider.AddTable("TaskGroup", + new Column(name: "Id", type: DbType.Int32, property: ColumnProperty.PrimaryKey) + ); + + Provider.AddForeignKey(name: fkName, childTable: tableName, childColumn: "TaskGroupId", parentTable: "TaskGroup", parentColumn: "Id"); + + // Act + var result = Provider.ConstraintExists(table: tableName, name: fkName); + + // Assert + Assert.That(result, Is.True); + } +} diff --git a/src/Migrator.Tests/Providers/Generic/Generic_CopyDataFromTableToTableBase.cs b/src/Migrator.Tests/Providers/Generic/Generic_CopyDataFromTableToTableBase.cs new file mode 100644 index 00000000..b06e059f --- /dev/null +++ b/src/Migrator.Tests/Providers/Generic/Generic_CopyDataFromTableToTableBase.cs @@ -0,0 +1,138 @@ +using System.Collections.Generic; +using System.Data; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.Base; +using Migrator.Tests.Providers.Generic.Models; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.Generic; + +public abstract class Generic_CopyDataFromTableToTableBase : TransformationProviderBase +{ + [Test] + public void CopyDataFromTableToTable_UsingOrderBy_Success() + { + // Arrange + const string tableNameSource = "SourceTable"; + const string columnName1Source = "SourceColumn1"; + const string columnName2Source = "SourceColumn2"; + const string columnName3Source = "SourceColumn3"; + + const string tableNameTarget = "TargetTable"; + const string columnName1Target = "TargetColumn1"; + const string columnName2Target = "TargetColumn2"; + const string columnName3Target = "TargetColumn3"; + + Provider.AddTable(tableNameSource, + new Column(columnName1Source, DbType.Int32), + new Column(columnName2Source, DbType.String), + new Column(columnName3Source, DbType.Int32) + ); + + Provider.AddTable(tableNameTarget, + new Column(columnName1Target, DbType.Int32), + new Column(columnName2Target, DbType.String), + new Column(columnName3Target, DbType.Int32) + ); + + Provider.Insert(tableNameSource, [columnName1Source, columnName2Source, columnName3Source], [2, "Hello2", 22]); + Provider.Insert(tableNameSource, [columnName1Source, columnName2Source, columnName3Source], [1, "Hello1", 11]); + + // Act + Provider.CopyDataFromTableToTable( + tableNameSource, + [columnName1Source, columnName2Source, columnName3Source], + tableNameTarget, + [columnName1Target, columnName2Target, columnName3Target], + [columnName1Source]); + + // Assert + List targetRows = []; + using (var cmd = Provider.CreateCommand()) + using (var reader = Provider.Select(cmd, tableNameTarget, [columnName1Target, columnName2Target, columnName3Target])) + { + while (reader.Read()) + { + targetRows.Add(new CopyDataFromTableToTableModel + { + Column1 = reader.GetInt32(0), + Column2 = reader.GetString(1), + Column3 = reader.GetInt32(2), + }); + } + } + + List expectedTargetRows = [ + new CopyDataFromTableToTableModel{ Column1 = 1, Column2 = "Hello1", Column3 = 11 }, + new CopyDataFromTableToTableModel{ Column1 = 2, Column2 = "Hello2", Column3 = 22 }, + ]; + + Assert.That(targetRows, Is.EquivalentTo(expectedTargetRows).Using((x, y) => + x.Column1 == y.Column1 && + x.Column2 == y.Column2 && + x.Column3 == y.Column3)); + } + + [Test] + public void CopyDataFromTableToTable_NotUsingOrderBy_Success() + { + // Arrange + const string tableNameSource = "SourceTable"; + const string columnName1Source = "SourceColumn1"; + const string columnName2Source = "SourceColumn2"; + const string columnName3Source = "SourceColumn3"; + + const string tableNameTarget = "TargetTable"; + const string columnName1Target = "TargetColumn1"; + const string columnName2Target = "TargetColumn2"; + const string columnName3Target = "TargetColumn3"; + + Provider.AddTable(tableNameSource, + new Column(columnName1Source, DbType.Int32), + new Column(columnName2Source, DbType.String), + new Column(columnName3Source, DbType.Int32) + ); + + Provider.AddTable(tableNameTarget, + new Column(columnName1Target, DbType.Int32), + new Column(columnName2Target, DbType.String), + new Column(columnName3Target, DbType.Int32) + ); + + Provider.Insert(tableNameSource, [columnName1Source, columnName2Source, columnName3Source], [2, "Hello2", 22]); + Provider.Insert(tableNameSource, [columnName1Source, columnName2Source, columnName3Source], [1, "Hello1", 11]); + + // Act + Provider.CopyDataFromTableToTable( + tableNameSource, + [columnName1Source, columnName2Source, columnName3Source], + tableNameTarget, + [columnName1Target, columnName2Target, columnName3Target]); + + // Assert + List targetRows = []; + using (var cmd = Provider.CreateCommand()) + using (var reader = Provider.Select(cmd, tableNameTarget, [columnName1Target, columnName2Target, columnName3Target])) + { + while (reader.Read()) + { + targetRows.Add(new CopyDataFromTableToTableModel + { + Column1 = reader.GetInt32(0), + Column2 = reader.GetString(1), + Column3 = reader.GetInt32(2), + }); + } + } + + List expectedTargetRows = [ + new CopyDataFromTableToTableModel{ Column1 = 1, Column2 = "Hello1", Column3 = 11 }, + new CopyDataFromTableToTableModel{ Column1 = 2, Column2 = "Hello2", Column3 = 22 }, + ]; + + Assert.That(targetRows, Is.EquivalentTo(expectedTargetRows).Using((x, y) => + x.Column1 == y.Column1 && + x.Column2 == y.Column2 && + x.Column3 == y.Column3)); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/Generic/Generic_DefaultValueTestsBase.cs b/src/Migrator.Tests/Providers/Generic/Generic_DefaultValueTestsBase.cs new file mode 100644 index 00000000..58d525bb --- /dev/null +++ b/src/Migrator.Tests/Providers/Generic/Generic_DefaultValueTestsBase.cs @@ -0,0 +1,50 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.Generic; + +public abstract class Generic_DefaultValueTestsBase : TransformationProviderBase +{ + [Test] + public void DefaultValue_Null_Success() + { + const string tableNameSource = "SourceTable"; + const string columnName1Target = "TargetColumn1"; + + Provider.AddTable(tableNameSource, + new Column(columnName1Target, DbType.Int32, ColumnProperty.Null, null) + ); + + Provider.ChangeColumn(tableNameSource, new Column(columnName1Target, DbType.Int32, ColumnProperty.NotNull)); + } + + [Test] + public void DefaultValue_ConvertStringToNotNull_DoesNotThrow() + { + const string tableNameSource = "SourceTable"; + const string columnName1Target = "TargetColumn1"; + + Provider.AddTable(tableNameSource, + new Column(columnName1Target, DbType.String, 32, ColumnProperty.NotNull) + ); + + Provider.ChangeColumn(tableNameSource, new Column(columnName1Target, DbType.String, ColumnProperty.Null)); + } + + [Test] + public void RemoveColumnDefaultValue_DoesNotThrow() + { + const string tableNameSource = "TableName"; + const string columnName1 = "ColumnName1"; + + Provider.AddTable(tableNameSource, + new Column(columnName1, DbType.Int32, ColumnProperty.NotNull, 10) + ); + + Provider.RemoveColumnDefaultValue(tableNameSource, columnName1); + + Provider.ChangeColumn(tableNameSource, new Column(columnName1, DbType.Int32, ColumnProperty.Null)); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/Generic/Generic_GetColumnsTestsBase.cs b/src/Migrator.Tests/Providers/Generic/Generic_GetColumnsTestsBase.cs new file mode 100644 index 00000000..e51a6a07 --- /dev/null +++ b/src/Migrator.Tests/Providers/Generic/Generic_GetColumnsTestsBase.cs @@ -0,0 +1,7 @@ +using Migrator.Tests.Providers.Base; + +namespace Migrator.Tests.Providers.Generic; + +public abstract class Generic_GetColumnsTestsBase : TransformationProviderBase +{ +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/Generic/Generic_GetIndexesTestsBase.cs b/src/Migrator.Tests/Providers/Generic/Generic_GetIndexesTestsBase.cs new file mode 100644 index 00000000..ffceaf9d --- /dev/null +++ b/src/Migrator.Tests/Providers/Generic/Generic_GetIndexesTestsBase.cs @@ -0,0 +1,55 @@ +using System.Data; +using System.Linq; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Models.Indexes.Enums; +using Migrator.Tests.Providers.Base; +using NUnit.Framework; +using Index = DotNetProjects.Migrator.Framework.Index; + +namespace Migrator.Tests.Providers.Generic; + +public abstract class Generic_GetIndexesTestsBase : TransformationProviderBase +{ + [Test] + public void AddIndex_FilteredIndexGreaterOrEqualThanNumber_Success() + { + // Arrange + const string tableName = "TestTable"; + const string columnName = "TestColumn"; + const string columnName2 = "TestColumn2"; + const string columnName3 = "TestColumn3"; + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, + new Column(columnName, DbType.Int32), + new Column(columnName2, DbType.String), + new Column(columnName3, DbType.Int32) + ); + + Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [columnName, columnName2], + Unique = true, + FilterItems = [ + new() { Filter = FilterType.GreaterThanOrEqualTo, ColumnName = columnName, Value = 100 }, + new() { Filter = FilterType.EqualTo, ColumnName = columnName2, Value = "Hello" }, + ] + }); + + // Act + var indexes = Provider.GetIndexes(table: tableName); + + var index = indexes.Single(); + + var filterItem1 = index.FilterItems.Single(x => x.ColumnName == columnName); + var filterItem2 = index.FilterItems.Single(x => x.ColumnName == columnName2); + + Assert.That(filterItem1.Filter, Is.EqualTo(FilterType.GreaterThanOrEqualTo)); + Assert.That((long)filterItem1.Value, Is.EqualTo(100)); + + Assert.That(filterItem2.Filter, Is.EqualTo(FilterType.EqualTo)); + Assert.That((string)filterItem2.Value, Is.EqualTo("Hello")); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/Generic/Generic_UpdateFromTableToTableTestsBase.cs b/src/Migrator.Tests/Providers/Generic/Generic_UpdateFromTableToTableTestsBase.cs new file mode 100644 index 00000000..dce4047e --- /dev/null +++ b/src/Migrator.Tests/Providers/Generic/Generic_UpdateFromTableToTableTestsBase.cs @@ -0,0 +1,103 @@ +using System.Collections.Generic; +using System.Data; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.Base; +using Migrator.Tests.Providers.Generic.Models; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.Generic; + +public abstract class Generic_UpdateFromTableToTableTestsBase : TransformationProviderBase +{ + [Test] + public void UpdateFromTableToTable_Success() + { + // Arrange + const string tableNameSource = "TableSource"; + const string tableNameTarget = "TableTarget"; + const string columnName1Source = "ColumnName1Source"; + const string columnName2Source = "ColumnName2Source"; + const string columnName3Source = "ColumnName3Source"; + const string columnName4Source = "ColumnName4Source"; + const string columnName5Source = "ColumnName5Source"; + + const string columnName1Target = "ColumnName1Target"; + const string columnName2Target = "ColumnName2Target"; + const string columnName3Target = "ColumnName3Target"; + const string columnName4Target = "ColumnName4Target"; + const string columnName5Target = "ColumnName5Target"; + + + Provider.AddTable(tableNameSource, + new Column(columnName1Source, DbType.Int32, ColumnProperty.NotNull), + new Column(columnName2Source, DbType.Int32, ColumnProperty.NotNull), + new Column(columnName3Source, DbType.String), + new Column(columnName4Source, DbType.String), + new Column(columnName5Source, DbType.String) + ); + + Provider.AddPrimaryKey("PK_Source", tableNameSource, [columnName1Source, columnName2Source]); + + Provider.AddTable(tableNameTarget, + new Column(columnName1Target, DbType.Int32, ColumnProperty.NotNull), + new Column(columnName2Target, DbType.Int32, ColumnProperty.NotNull), + new Column(columnName3Target, DbType.String), + new Column(columnName4Target, DbType.String), + new Column(columnName5Target, DbType.String) + ); + + Provider.AddPrimaryKey("PK_Target", tableNameTarget, [columnName1Target, columnName2Target]); + + Provider.Insert(tableNameSource, [columnName1Source, columnName2Source, columnName3Source, columnName4Source, columnName5Source], [1, 2, "source 1", "source 2", "source 3"]); + Provider.Insert(tableNameSource, [columnName1Source, columnName2Source, columnName3Source, columnName4Source, columnName5Source], [2, 3, "source 11", "source 22", "source 33"]); + + Provider.Insert(tableNameTarget, [columnName1Target, columnName2Target, columnName3Target, columnName4Target, columnName5Target], [1, 2, "target 1", "target 2", "target 3"]); + Provider.Insert(tableNameTarget, [columnName1Target, columnName2Target, columnName3Target, columnName4Target, columnName5Target], [1, 3, "target no update", "target no update", "target no update"]); + + // Act + Provider.UpdateTargetFromSource( + tableNameSource: tableNameSource, + tableNameTarget: tableNameTarget, + copyColumnPairs: + [ + new () { ColumnNameSource = columnName3Source, ColumnNameTarget = columnName3Target }, + new () { ColumnNameSource = columnName4Source, ColumnNameTarget = columnName4Target }, + new () { ColumnNameSource = columnName5Source, ColumnNameTarget = columnName5Target } + ], + matchColumnPairs: + [ + new () { ColumnNameSource = columnName1Source, ColumnNameTarget = columnName1Target }, + new () { ColumnNameSource = columnName2Source, ColumnNameTarget = columnName2Target } + ]); + + // Assert + List targetRows = []; + using (var cmd = Provider.CreateCommand()) + using (var reader = Provider.Select(cmd, tableNameTarget, [columnName1Target, columnName2Target, columnName3Target, columnName4Target, columnName5Target])) + { + while (reader.Read()) + { + targetRows.Add(new UpdateFromTableToTableModel + { + Column1 = reader.GetInt32(0), + Column2 = reader.GetInt32(1), + Column3 = reader.GetString(2), + Column4 = reader.GetString(3), + Column5 = reader.GetString(4) + }); + } + } + + List expectedTargetRows = [ + new UpdateFromTableToTableModel{ Column1 = 1, Column2 = 2, Column3 = "source 1", Column4 = "source 2", Column5 = "source 3"}, + new UpdateFromTableToTableModel{ Column1 = 1, Column2 = 3, Column3 = "target no update", Column4 = "target no update", Column5 = "target no update"}, + ]; + + Assert.That(targetRows, Is.EquivalentTo(expectedTargetRows).Using((x, y) => + x.Column1 == y.Column1 && + x.Column2 == y.Column2 && + x.Column3 == y.Column3 && + x.Column4 == y.Column4 && + x.Column5 == y.Column5)); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/Generic/Models/CopyDataFromTableToTableModel.cs b/src/Migrator.Tests/Providers/Generic/Models/CopyDataFromTableToTableModel.cs new file mode 100644 index 00000000..f1904b58 --- /dev/null +++ b/src/Migrator.Tests/Providers/Generic/Models/CopyDataFromTableToTableModel.cs @@ -0,0 +1,8 @@ +namespace Migrator.Tests.Providers.Generic.Models; + +public class CopyDataFromTableToTableModel +{ + public int Column1 { get; set; } + public string Column2 { get; set; } + public int Column3 { get; set; } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/Generic/Models/UpdateFromTableToTableModel.cs b/src/Migrator.Tests/Providers/Generic/Models/UpdateFromTableToTableModel.cs new file mode 100644 index 00000000..6f99f0c5 --- /dev/null +++ b/src/Migrator.Tests/Providers/Generic/Models/UpdateFromTableToTableModel.cs @@ -0,0 +1,10 @@ +namespace Migrator.Tests.Providers.Generic.Models; + +public class UpdateFromTableToTableModel +{ + public int Column1 { get; set; } + public int Column2 { get; set; } + public string Column3 { get; set; } + public string Column4 { get; set; } + public string Column5 { get; set; } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/Generic/TransformationProviderGenericMiscConstraintBase.cs b/src/Migrator.Tests/Providers/Generic/TransformationProviderGenericMiscConstraintBase.cs new file mode 100644 index 00000000..d2d2a180 --- /dev/null +++ b/src/Migrator.Tests/Providers/Generic/TransformationProviderGenericMiscConstraintBase.cs @@ -0,0 +1,244 @@ +using System; +using System.Data; +using System.Linq; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.Generic; + +/// +/// Base class for provider tests for all tests including constraint oriented tests. +/// +public abstract class TransformationProviderGenericMiscConstraintBase : TransformationProviderGenericMiscTests +{ + public void AddForeignKey() + { + AddTableWithPrimaryKey(); + Provider.AddForeignKey("FK_Test_TestTwo", "TestTwo", "TestId", "Test", "Id"); + } + + public void AddPrimaryKey() + { + AddTable(); + Provider.AddPrimaryKey("PK_Test", "Test", "Id"); + } + + public void AddUniqueConstraint() + { + Provider.AddUniqueConstraint("UN_Test_TestTwo", "TestTwo", "TestId"); + } + + public void AddMultipleUniqueConstraint() + { + Provider.AddUniqueConstraint("UN_Test_TestTwo", "TestTwo", "Id", "TestId"); + } + + public void AddTestCheckConstraint() + { + Provider.AddCheckConstraint("CK_TestTwo_TestId", "TestTwo", "TestId>5"); + } + + [Test] + public void CanAddPrimaryKey() + { + AddPrimaryKey(); + + Assert.That(Provider.PrimaryKeyExists("Test", "PK_Test"), Is.True); + } + + // [Test] + // public void AddIndexedColumn() + // { + // Provider.AddColumn("TestTwo", "Test", DbType.String, 50, ColumnProperty.Indexed); + // } + + [Test] + public void AddUniqueColumn() + { + Provider.AddColumn("TestTwo", "Test", DbType.String, 50, ColumnProperty.Unique); + } + + [Test] + public void CanAddForeignKey() + { + AddForeignKey(); + Assert.That(Provider.ConstraintExists("TestTwo", "FK_Test_TestTwo"), Is.True); + } + + [Test] + public virtual void CanAddUniqueConstraint() + { + AddUniqueConstraint(); + Assert.That(Provider.ConstraintExists("TestTwo", "UN_Test_TestTwo"), Is.True); + } + + [Test] + public virtual void CanAddMultipleUniqueConstraint() + { + AddMultipleUniqueConstraint(); + Assert.That(Provider.ConstraintExists("TestTwo", "UN_Test_TestTwo"), Is.True); + } + + [Test] + public virtual void CanAddCheckConstraint() + { + AddTestCheckConstraint(); + var constraintExists = Provider.ConstraintExists("TestTwo", "CK_TestTwo_TestId"); + + Assert.That(constraintExists, Is.True); + } + + [Test] + public virtual void RemoveForeignKey() + { + Console.WriteLine($"Test running in class: {TestContext.CurrentContext.Test.ClassName}"); + AddForeignKey(); + Provider.RemoveForeignKey("TestTwo", "FK_Test_TestTwo"); + Assert.That(Provider.ConstraintExists("TestTwo", "FK_Test_TestTwo"), Is.False); + } + + [Test] + public void RemoveUniqueConstraint() + { + AddUniqueConstraint(); + Provider.RemoveConstraint("TestTwo", "UN_Test_TestTwo"); + Assert.That(Provider.ConstraintExists("TestTwo", "UN_Test_TestTwo"), Is.False); + } + + [Test] + public virtual void RemoveCheckConstraint() + { + AddTestCheckConstraint(); + Provider.RemoveConstraint("TestTwo", "CK_TestTwo_TestId"); + Assert.That(Provider.ConstraintExists("TestTwo", "CK_TestTwo_TestId"), Is.False); + } + + [Test] + public void RemoveUnexistingForeignKey() + { + // Arrange + AddForeignKey(); + + // Act/Assert + // Table does not exist. + Assert.Throws(() => Provider.RemoveForeignKey("NotExistingTable", "FK_Test_TestTwo")); + + // Table exists but foreign key does not exist. + if (Provider is SQLiteTransformationProvider) + { + Assert.Throws(() => Provider.RemoveForeignKey("Test", "NotExistingForeignKey")); + } + else + { + Assert.That(() => Provider.RemoveForeignKey("Test", "NotExistingForeignKey"), Throws.Exception); + } + } + + [Test] + public void ConstraintExist() + { + AddForeignKey(); + Assert.That(Provider.ConstraintExists("TestTwo", "FK_Test_TestTwo"), Is.True); + Assert.That(Provider.ConstraintExists("TestTwo", "abc"), Is.False); + } + + [Test] + public void AddTableWithCompoundPrimaryKeyShouldKeepNullForOtherProperties() + { + var testTableName = "Test"; + + Provider.AddTable(testTableName, + new Column("PersonId", DbType.Int32, ColumnProperty.PrimaryKey), + new Column("AddressId", DbType.Int32, ColumnProperty.PrimaryKey), + new Column("Name", DbType.String, 30, ColumnProperty.Null) + ); + + Assert.That(Provider.TableExists("Test"), Is.True, "Table doesn't exist"); + + var column = Provider.GetColumnByName("Test", "Name"); + + Assert.That(column, Is.Not.Null); + Assert.That((column.ColumnProperty & ColumnProperty.Null) == ColumnProperty.Null, Is.True); + } + + [Test] + public void GetForeignKeyConstraints_SingleColumn_Success() + { + // Arrange + const string fkName = "MyForeignKey"; + const string childTableName = "ChildTable"; + const string parentTableName = "ParentTable"; + const string idColumn = "Id"; + const string parentIdColumn = "ParentId"; + + Provider.AddTable(parentTableName, + new Column(idColumn, DbType.Int32, ColumnProperty.PrimaryKey) + ); + + Provider.AddTable(childTableName, + new Column(idColumn, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(parentIdColumn, DbType.Int32) + ); + + Provider.AddForeignKey(fkName, childTableName, parentIdColumn, parentTableName, idColumn); + + // Act + var foreignKeyConstraints = Provider.GetForeignKeyConstraints(childTableName); + + // Assert + var resultSingle = foreignKeyConstraints.Single(); + + Assert.That(resultSingle.Name.ToLowerInvariant(), Is.EqualTo(fkName.ToLowerInvariant())); + Assert.That(resultSingle.ChildTable.ToLowerInvariant(), Is.EqualTo(childTableName.ToLowerInvariant())); + Assert.That(resultSingle.ParentTable.ToLowerInvariant(), Is.EqualTo(parentTableName.ToLowerInvariant())); + Assert.That(resultSingle.ChildColumns.Select(x => x.ToLowerInvariant()).Single(), Is.EqualTo(parentIdColumn.ToLowerInvariant())); + Assert.That(resultSingle.ParentColumns.Select(x => x.ToLowerInvariant()).Single(), Is.EqualTo(idColumn.ToLowerInvariant())); + } + + [Test] + public void GetForeignKeyConstraints_MultiColumnColumn_Success() + { + // Arrange + const string fkName = "MyForeignKey"; + const string childTableName = "ChildTable"; + const string parentTableName = "ParentTable"; + + const string parentColumnId = "Id"; + const string parentColumnTest = "Test"; + const string childColumnParentId = "ParentId"; + const string childColumnParentTest = "ParentTest"; + + Provider.AddTable(parentTableName, + new Column(parentColumnId, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(parentColumnTest, DbType.Int32, ColumnProperty.NotNull) + ); + + Provider.AddTable(childTableName, + new Column(childColumnParentId, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(childColumnParentTest, DbType.Int32) + ); + + Provider.AddUniqueConstraint("MyUniqueConstraint", parentTableName, [parentColumnId, parentColumnTest]); + + Provider.AddForeignKey(fkName, childTableName, [childColumnParentId, childColumnParentTest], parentTableName, [parentColumnId, parentColumnTest]); + + // Act + var foreignKeyConstraints = Provider.GetForeignKeyConstraints(childTableName); + + // Assert + var resultSingle = foreignKeyConstraints.Single(); + + Assert.That(resultSingle.Name.ToLowerInvariant(), Is.EqualTo(fkName.ToLowerInvariant())); + Assert.That(resultSingle.ChildTable.ToLowerInvariant(), Is.EqualTo(childTableName.ToLowerInvariant())); + Assert.That(resultSingle.ParentTable.ToLowerInvariant(), Is.EqualTo(parentTableName.ToLowerInvariant())); + + var childColumns = resultSingle.ChildColumns.Select(x => x.ToLowerInvariant()).ToList(); + var parentColumns = resultSingle.ParentColumns.Select(x => x.ToLowerInvariant()).ToList(); + + Assert.That(childColumns[0], Is.EqualTo(childColumnParentId.ToLowerInvariant())); + Assert.That(childColumns[1], Is.EqualTo(childColumnParentTest.ToLowerInvariant())); + Assert.That(parentColumns[0], Is.EqualTo(parentColumnId.ToLowerInvariant())); + Assert.That(parentColumns[1], Is.EqualTo(parentColumnTest.ToLowerInvariant())); + } +} diff --git a/src/Migrator.Tests/Providers/Generic/TransformationProviderGenericMiscTests.cs b/src/Migrator.Tests/Providers/Generic/TransformationProviderGenericMiscTests.cs new file mode 100644 index 00000000..62249ae1 --- /dev/null +++ b/src/Migrator.Tests/Providers/Generic/TransformationProviderGenericMiscTests.cs @@ -0,0 +1,473 @@ +using System; +using System.Data; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.Generic; + +/// +/// Base class for provider tests. +/// +public abstract class TransformationProviderGenericMiscTests : TransformationProviderSimpleBase +{ + [Test] + public void TableExistsWorks() + { + Assert.That(Provider.TableExists("gadadadadseeqwe"), Is.False); + Assert.That(Provider.TableExists("TestTwo"), Is.True); + } + + [Test] + public void ColumnExistsWorks() + { + Assert.That(Provider.ColumnExists("gadadadadseeqwe", "eqweqeq"), Is.False); + Assert.That(Provider.ColumnExists("TestTwo", "eqweqeq"), Is.False); + Assert.That(Provider.ColumnExists("TestTwo", "Id"), Is.True); + } + + [Test] + public void CanExecuteBadSqlForNonCurrentProvider() + { + Provider["foo"].ExecuteNonQuery("select foo from bar 123"); + } + + [Test] + public void TableCanBeAdded() + { + AddTable(); + Assert.That(Provider.TableExists("Test"), Is.True); + } + + [Test] + public void GetTablesWorks() + { + var tables = Provider.GetTables(); + + foreach (var name in tables) + { + Provider.Logger.Log("Table: {0}", name); + } + + Assert.That(1, Is.EqualTo(tables.Length)); + AddTable(); + + tables = Provider.GetTables(); + + Assert.That(2, Is.EqualTo(tables.Length)); + } + + [Test] + public void GetColumnsReturnsProperCount() + { + AddTable(); + var cols = Provider.GetColumns("Test"); + + Assert.That(cols, Is.Not.Null); + Assert.That(6, Is.EqualTo(cols.Length)); + } + + [Test] + public void GetColumnsContainsProperNullInformation() + { + AddTableWithPrimaryKey(); + var cols = Provider.GetColumns("Test"); + Assert.That(cols, Is.Not.Null); + + foreach (var column in cols) + { + if (column.Name == "name") + { + Assert.That((column.ColumnProperty & ColumnProperty.NotNull) == ColumnProperty.NotNull, Is.True); + } + else if (column.Name == "Title") + { + Assert.That((column.ColumnProperty & ColumnProperty.Null) == ColumnProperty.Null, Is.True); + } + } + } + + [Test] + public void CanAddTableWithPrimaryKey() + { + AddTableWithPrimaryKey(); + Assert.That(Provider.TableExists("Test"), Is.True); + } + + [Test] + public void RemoveTable() + { + AddTable(); + Provider.RemoveTable("Test"); + Assert.That(Provider.TableExists("Test"), Is.False); + } + + [Test] + public virtual void RenameTableThatExists() + { + AddTable(); + Provider.RenameTable("Test", "Test_Rename"); + + Assert.That(Provider.TableExists("Test_Rename"), Is.True); + Assert.That(Provider.TableExists("Test"), Is.False); + Provider.RemoveTable("Test_Rename"); + } + + [Test] + public void RenameTableToExistingTable() + { + AddTable(); + Assert.Throws(() => + { + Provider.RenameTable("Test", "TestTwo"); + }); + } + + [Test] + public void RenameColumnThatExists() + { + AddTable(); + Provider.RenameColumn("Test", "name", "name_rename"); + + Assert.That(Provider.ColumnExists("Test", "name_rename"), Is.True); + Assert.That(Provider.ColumnExists("Test", "name"), Is.False); + } + + [Test] + public void RenameColumnToExistingColumn() + { + AddTable(); + Assert.Throws(() => + { + Provider.RenameColumn("Test", "Title", "name"); + }); + } + + [Test] + public void RemoveUnexistingTable() + { + var exception = Assert.Catch(() => Provider.RemoveTable("abc")); + var expectedMessage = "Table with name 'abc' does not exist to rename"; + + Assert.That(exception.Message, Is.EqualTo(expectedMessage)); + } + + [Test] + public void AddColumn() + { + Provider.AddColumn("TestTwo", "Test", DbType.String, 50); + Assert.That(Provider.ColumnExists("TestTwo", "Test"), Is.True); + } + + [Test] + public void ChangeColumn() + { + Provider.ChangeColumn("TestTwo", new Column("TestId", DbType.String, 50)); + Assert.That(Provider.ColumnExists("TestTwo", "TestId"), Is.True); + Provider.Insert("TestTwo", ["Id", "TestId"], [1, "Not an Int val."]); + } + + [Test] + public void ChangeColumn_FromNullToNull() + { + Provider.ChangeColumn("TestTwo", new Column("TestId", DbType.String, 50, ColumnProperty.Null)); + Provider.ChangeColumn("TestTwo", new Column("TestId", DbType.String, 50, ColumnProperty.Null)); + Provider.ChangeColumn("TestTwo", new Column("TestId", DbType.String, 50, ColumnProperty.Null)); + Provider.Insert("TestTwo", ["Id", "TestId"], [2, "Not an Int val."]); + } + + [Test] + public void AddDecimalColumn() + { + Provider.AddColumn("TestTwo", "TestDecimal", DbType.Decimal, 38); + Assert.That(Provider.ColumnExists("TestTwo", "TestDecimal"), Is.True); + } + + [Test] + public void AddColumnWithDefault() + { + Provider.AddColumn("TestTwo", "TestWithDefault", DbType.Int32, 50, 0, 10); + Assert.That(Provider.ColumnExists("TestTwo", "TestWithDefault"), Is.True); + } + + [Test] + public void AddColumnWithDefaultButNoSize() + { + Provider.AddColumn("TestTwo", "TestWithDefault", DbType.Int32, 10); + Assert.That(Provider.ColumnExists("TestTwo", "TestWithDefault"), Is.True); + + Provider.AddColumn("TestTwo", "TestWithDefaultString", DbType.String, "'foo'"); + Assert.That(Provider.ColumnExists("TestTwo", "TestWithDefaultString"), Is.True); + } + + [Test] + public void AddBooleanColumnWithDefault() + { + Provider.AddColumn("TestTwo", "TestBoolean", DbType.Boolean, 0, 0, false); + Assert.That(Provider.ColumnExists("TestTwo", "TestBoolean"), Is.True); + } + + [Test] + public void CanGetNullableFromProvider() + { + Provider.AddColumn("TestTwo", "NullableColumn", DbType.String, 30, ColumnProperty.Null); + var columns = Provider.GetColumns("TestTwo"); + + foreach (var column in columns) + { + if (column.Name == "NullableColumn") + { + Assert.That((column.ColumnProperty & ColumnProperty.Null) == ColumnProperty.Null, Is.True); + } + } + } + + [Test] + public void RemoveColumn() + { + AddColumn(); + Provider.RemoveColumn("TestTwo", "Test"); + Assert.That(Provider.ColumnExists("TestTwo", "Test"), Is.False); + } + + [Test] + public void RemoveColumnWithDefault() + { + AddColumnWithDefault(); + Provider.RemoveColumn("TestTwo", "TestWithDefault"); + Assert.That(Provider.ColumnExists("TestTwo", "TestWithDefault"), Is.False); + } + + [Test] + public void RemoveUnexistingColumn() + { + var exception1 = Assert.Throws(() => Provider.RemoveColumn("TestTwo", "abc")); + var exception2 = Assert.Throws(() => Provider.RemoveColumn("abc", "abc")); + + Assert.That(exception1.Message, Is.EqualTo("The table 'TestTwo' does not have a column named 'abc'")); + Assert.That(exception2.Message, Is.EqualTo("The table 'abc' does not exist")); + } + + /// + /// Supprimer une colonne bit causait une erreur à cause + /// de la valeur par défaut. + /// + [Test] + public void RemoveBoolColumn() + { + AddTable(); + Provider.AddColumn("Test", "Inactif", DbType.Boolean); + Assert.That(Provider.ColumnExists("Test", "Inactif"), Is.True); + + Provider.RemoveColumn("Test", "Inactif"); + Assert.That(Provider.ColumnExists("Test", "Inactif"), Is.False); + } + + [Test] + public void HasColumn() + { + AddColumn(); + Assert.That(Provider.ColumnExists("TestTwo", "Test"), Is.True); + Assert.That(Provider.ColumnExists("TestTwo", "TestPasLa"), Is.False); + } + + [Test] + public void HasTable() + { + Assert.That(Provider.TableExists("TestTwo"), Is.True); + } + + [Test] + public void AppliedMigrations() + { + Assert.That(Provider.TableExists("SchemaInfo"), Is.False); + + // Check that a "get" call works on the first run. + Assert.That(0, Is.EqualTo(Provider.AppliedMigrations.Count)); + Assert.That(Provider.TableExists("SchemaInfo"), Is.True, "No SchemaInfo table created"); + + // Check that a "set" called after the first run works. + Provider.MigrationApplied(1, null); + Assert.That(1, Is.EqualTo(Provider.AppliedMigrations[0])); + + Provider.RemoveTable("SchemaInfo"); + // Check that a "set" call works on the first run. + Provider.MigrationApplied(1, null); + Assert.That(1, Is.EqualTo(Provider.AppliedMigrations[0])); + Assert.That(Provider.TableExists("SchemaInfo"), Is.True, "No SchemaInfo table created"); + } + + + [Test] + public void CommitTwice() + { + Provider.Commit(); + Assert.That(0, Is.EqualTo(Provider.AppliedMigrations.Count)); + Provider.Commit(); + } + + [Test] + public void InsertData() + { + Provider.Insert("TestTwo", ["Id", "TestId"], [1, 1]); + Provider.Insert("TestTwo", ["Id", "TestId"], [2, 2]); + + using var cmd = Provider.CreateCommand(); + using var reader = Provider.Select(cmd, "TestId", "TestTwo"); + var vals = GetVals(reader); + + Assert.That(Array.Exists(vals, delegate (int val) { return val == 1; }), Is.True); + Assert.That(Array.Exists(vals, delegate (int val) { return val == 2; }), Is.True); + } + + [Test] + public void CanInsertNullData() + { + AddTable(); + + Provider.Insert("Test", ["Id", "Title"], [1, "foo"]); + Provider.Insert("Test", ["Id", "Title"], [2, null]); + + using var cmd = Provider.CreateCommand(); + using var reader = Provider.Select(cmd, "Title", "Test"); + var vals = GetStringVals(reader); + + Assert.That(Array.Exists(vals, delegate (string val) { return val == "foo"; }), Is.True); + Assert.That(Array.Exists(vals, delegate (string val) { return val == null; }), Is.True); + } + + [Test] + public void CanInsertDataWithSingleQuotes() + { + // Arrange + const string testString = "Test string with ' (single quote)"; + AddTable(); + Provider.Insert("Test", ["Id", "Title"], [1, testString]); + + using var cmd = Provider.CreateCommand(); + using var reader = Provider.Select(cmd, "Title", "Test"); + + Assert.That(reader.Read(), Is.True); + Assert.That(testString, Is.EqualTo(reader.GetString(0))); + Assert.That(reader.Read(), Is.False); + } + + [Test] + public void DeleteData() + { + InsertData(); + Provider.Delete("TestTwo", "TestId", "1"); + using var cmd = Provider.CreateCommand(); + using var reader = Provider.Select(cmd, "TestId", "TestTwo"); + Assert.That(reader.Read(), Is.True); + Assert.That(2, Is.EqualTo(Convert.ToInt32(reader[0]))); + Assert.That(reader.Read(), Is.False); + } + + [Test] + public void DeleteDataWithArrays() + { + InsertData(); + + Provider.Delete("TestTwo", ["TestId"], [1]); + + using var cmd = Provider.CreateCommand(); + using var reader = Provider.Select(cmd, "TestId", "TestTwo"); + + Assert.That(reader.Read(), Is.True); + Assert.That(2, Is.EqualTo(Convert.ToInt32(reader[0]))); + Assert.That(reader.Read(), Is.False); + } + + [Test] + public void UpdateData() + { + Provider.Insert("TestTwo", ["Id", "TestId"], [20, 1]); + Provider.Insert("TestTwo", ["Id", "TestId"], [21, 2]); + + Provider.Update("TestTwo", ["TestId"], [3]); + using var cmd = Provider.CreateCommand(); + using var reader = Provider.Select(cmd, "TestId", "TestTwo"); + var vals = GetVals(reader); + + Assert.That(Array.Exists(vals, delegate (int val) { return val == 3; }), Is.True); + Assert.That(Array.Exists(vals, delegate (int val) { return val == 1; }), Is.False); + Assert.That(Array.Exists(vals, delegate (int val) { return val == 2; }), Is.False); + } + + [Test] + public void CanUpdateWithNullData() + { + AddTable(); + Provider.Insert("Test", ["Id", "Title"], [1, "foo"]); + Provider.Insert("Test", ["Id", "Title"], [2, null]); + + Provider.Update("Test", ["Title"], [null]); + using var cmd = Provider.CreateCommand(); + using var reader = Provider.Select(cmd, "Title", "Test"); + var vals = GetStringVals(reader); + + Assert.That(vals[0], Is.Null); + Assert.That(vals[1], Is.Null); + } + + [Test] + public void UpdateDataWithWhere() + { + Provider.Insert("TestTwo", ["Id", "TestId"], [10, 1]); + Provider.Insert("TestTwo", ["Id", "TestId"], [11, 2]); + + Provider.Update("TestTwo", ["TestId"], [3], "TestId='1'"); + using var cmd = Provider.CreateCommand(); + using var reader = Provider.Select(cmd, "TestId", "TestTwo"); + var vals = GetVals(reader); + + Assert.That(Array.Exists(vals, delegate (int val) { return val == 3; }), Is.True); + Assert.That(Array.Exists(vals, delegate (int val) { return val == 2; }), Is.True); + Assert.That(Array.Exists(vals, delegate (int val) { return val == 1; }), Is.False); + } + + [Test] + public void AddIndex() + { + var indexName = "test_index"; + + Assert.That(Provider.IndexExists("TestTwo", indexName), Is.False); + Provider.AddIndex(indexName, "TestTwo", "Id", "TestId"); + Assert.That(Provider.IndexExists("TestTwo", indexName), Is.True); + } + + [Test] + public void RemoveIndex() + { + var indexName = "test_index"; + + Assert.That(Provider.IndexExists("TestTwo", indexName), Is.False); + Provider.AddIndex(indexName, "TestTwo", "Id", "TestId"); + Provider.RemoveIndex("TestTwo", indexName); + Assert.That(Provider.IndexExists("TestTwo", indexName), Is.False); + } + + + private int[] GetVals(IDataReader reader) + { + var vals = new int[2]; + Assert.That(reader.Read(), Is.True); + vals[0] = Convert.ToInt32(reader[0]); + Assert.That(reader.Read(), Is.True); + vals[1] = Convert.ToInt32(reader[0]); + + return vals; + } + + private string[] GetStringVals(IDataReader reader) + { + var vals = new string[2]; + Assert.That(reader.Read(), Is.True); + vals[0] = reader[0] as string; + Assert.That(reader.Read(), Is.True); + vals[1] = reader[0] as string; + + return vals; + } +} diff --git a/src/Migrator.Tests/Providers/GenericProviderTests.cs b/src/Migrator.Tests/Providers/GenericProviderTests.cs index eb64877e..9cdce313 100644 --- a/src/Migrator.Tests/Providers/GenericProviderTests.cs +++ b/src/Migrator.Tests/Providers/GenericProviderTests.cs @@ -1,42 +1,40 @@ -using System.Collections.Generic; - -using Migrator.Providers; +using System.Collections.Generic; +using DotNetProjects.Migrator.Providers; using NUnit.Framework; -namespace Migrator.Tests.Providers +namespace Migrator.Tests.Providers; + +[TestFixture] +public class GenericProviderTests +{ + [Test] + public void CanJoinColumnsAndValues() + { + var provider = new GenericTransformationProvider(); + var result = provider.JoinColumnsAndValues(["foo", "bar"], ["123", "456"]); + + Assert.That("foo='123', bar='456'", Is.EqualTo(result)); + } +} + +internal class GenericTransformationProvider : TransformationProvider { - [TestFixture] - public class GenericProviderTests - { - [Test] - public void CanJoinColumnsAndValues() - { - var provider = new GenericTransformationProvider(); - string result = provider.JoinColumnsAndValues(new[] {"foo", "bar"}, new[] {"123", "456"}); + public GenericTransformationProvider() : base(null, null as string, null, "default") + { + } - Assert.AreEqual("foo='123', bar='456'", result); - } - } + public override bool ConstraintExists(string table, string name) + { + return false; + } - internal class GenericTransformationProvider : TransformationProvider - { - public GenericTransformationProvider() : base(null, null as string, null, "default") - { - } + public override List GetDatabases() + { + throw new System.NotImplementedException(); + } - public override bool ConstraintExists(string table, string name) - { - return false; - } - - public override List GetDatabases() - { - throw new System.NotImplementedException(); - } - - public override bool IndexExists(string table, string name) - { - return false; - } - } + public override bool IndexExists(string table, string name) + { + return false; + } } \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/MySQL/MySqlTransformationProviderTest.cs b/src/Migrator.Tests/Providers/MySQL/MySqlTransformationProviderTest.cs new file mode 100644 index 00000000..a8ee884a --- /dev/null +++ b/src/Migrator.Tests/Providers/MySQL/MySqlTransformationProviderTest.cs @@ -0,0 +1,61 @@ +// using System; +// using System.Data; +// using Migrator.Framework; +// using Migrator.Providers; +// using Migrator.Providers.Mysql; +// using Migrator.Tests.Settings; +// using Migrator.Tests.Settings.Config; +// using NUnit.Framework; + +// namespace Migrator.Tests.Providers.MySQL; + +// [TestFixture] +// [Category("MySql")] +// public class MySqlTransformationProviderTest : TransformationProviderConstraintBase +// { +// [SetUp] +// public void SetUp() +// { +// var configReader = new ConfigurationReader(); +// var connectionString = configReader.GetDatabaseConnectionConfigById(DatabaseConnectionConfigIds.MySQLId) +// ?.ConnectionString; + +// if (string.IsNullOrEmpty(connectionString)) +// { +// throw new IgnoreException("No MySQL ConnectionString is Set."); +// } + +// DbProviderFactories.RegisterFactory("MySql.Data.MySqlClient", () => MySql.Data.MySqlClient.MySqlClientFactory.Instance); + +// Provider = new MySqlTransformationProvider(new MysqlDialect(), connectionString, "default", null); + +// AddDefaultTable(); +// } + +// [TearDown] +// public override void TearDown() +// { +// DropTestTables(); +// } + +// // [Test,Ignore("MySql doesn't support check constraints")] +// public override void CanAddCheckConstraint() +// { +// } + +// [Test] +// public void AddTableWithMyISAMEngine() +// { +// Provider.AddTable("Test", "MyISAM", +// new Column("Id", DbType.Int32, ColumnProperty.NotNull), +// new Column("name", DbType.String, 50) +// ); +// } + +// [Test] +// [Ignore("needs to be fixed")] +// public override void RemoveForeignKey() +// { +// //Foreign Key exists method seems not to return the key, but the ConstraintExists does +// } +// } diff --git a/src/Migrator.Tests/Providers/MySqlTransformationProviderTest.cs b/src/Migrator.Tests/Providers/MySqlTransformationProviderTest.cs deleted file mode 100644 index 878d82db..00000000 --- a/src/Migrator.Tests/Providers/MySqlTransformationProviderTest.cs +++ /dev/null @@ -1,63 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.Configuration; -using System.Data; -using Migrator.Framework; -using Migrator.Providers.Mysql; -using NUnit.Framework; - -namespace Migrator.Tests.Providers -{ - [TestFixture] - [Category("MySql")] - public class MySqlTransformationProviderTest : TransformationProviderConstraintBase - { - #region Setup/Teardown - - [SetUp] - public void SetUp() - { - string constr = ConfigurationManager.AppSettings["MySqlConnectionString"]; - if (constr == null) - throw new ArgumentNullException("MySqlConnectionString", "No config file"); - _provider = new MySqlTransformationProvider(new MysqlDialect(), constr, "default", null); - // _provider.Logger = new Logger(true, new ConsoleWriter()); - - AddDefaultTable(); - } - - [TearDown] - public override void TearDown() - { - DropTestTables(); - } - - #endregion - - // [Test,Ignore("MySql doesn't support check constraints")] - public override void CanAddCheckConstraint() - { - } - - [Test] - public void AddTableWithMyISAMEngine() - { - _provider.AddTable("Test", "MyISAM", - new Column("Id", DbType.Int32, ColumnProperty.NotNull), - new Column("name", DbType.String, 50) - ); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/OracleProvider/Base/OracleTransformationProviderTestBase.cs b/src/Migrator.Tests/Providers/OracleProvider/Base/OracleTransformationProviderTestBase.cs new file mode 100644 index 00000000..f9a368d7 --- /dev/null +++ b/src/Migrator.Tests/Providers/OracleProvider/Base/OracleTransformationProviderTestBase.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.OracleProvider.Base; + +public class OracleTransformationProviderTestBase : TransformationProviderSimpleBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginOracleTransactionAsync(); + + AddDefaultTable(); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProviderGenericTests.cs b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProviderGenericTests.cs new file mode 100644 index 00000000..5f56b686 --- /dev/null +++ b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProviderGenericTests.cs @@ -0,0 +1,30 @@ +using System.Data; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.OracleProvider; + +[TestFixture] +[Category("Oracle")] +public class OracleTransformationProviderGenericTests : TransformationProviderGenericMiscConstraintBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginOracleTransactionAsync(); + + AddDefaultTable(); + } + + [Test] + public void ChangeColumn_FromNotNullToNotNull() + { + Provider.ExecuteNonQuery("DELETE FROM TestTwo"); + Provider.ChangeColumn("TestTwo", new Column("TestId", DbType.String, 50, ColumnProperty.Null)); + Provider.Insert("TestTwo", ["Id", "TestId"], [3, "Not an Int val."]); + Provider.ChangeColumn("TestTwo", new Column("TestId", DbType.String, 50, ColumnProperty.NotNull)); + Provider.ChangeColumn("TestTwo", new Column("TestId", DbType.String, 50, ColumnProperty.NotNull)); + } +} diff --git a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_AddColumnTests.cs b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_AddColumnTests.cs new file mode 100644 index 00000000..6675de98 --- /dev/null +++ b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_AddColumnTests.cs @@ -0,0 +1,43 @@ +using System.Data; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.OracleProvider; + +[TestFixture] +[Category("Oracle")] +public class OracleTransformationProvider_AddColumn_Tests : TransformationProviderBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginOracleTransactionAsync(); + } + + [Test] + public void AddTable_NotNull_OtherColumnStillNotNull() + { + // Arrange + var tableName = "TableName"; + var column1Name = "Column1"; + var column2Name = "Column2"; + + + Provider.AddTable(tableName, + new Column(column1Name, DbType.Int32, ColumnProperty.NotNull) + ); + + // Act + Provider.AddColumn(table: tableName, column: new Column(column2Name, DbType.DateTime, ColumnProperty.NotNull)); + + + // Assert + var column1 = Provider.GetColumnByName(tableName, column1Name); + var column2 = Provider.GetColumnByName(tableName, column2Name); + + Assert.That(column1.ColumnProperty.HasFlag(ColumnProperty.NotNull), Is.True); + Assert.That(column2.ColumnProperty.HasFlag(ColumnProperty.NotNull), Is.True); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_AddIndexTests.cs b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_AddIndexTests.cs new file mode 100644 index 00000000..c9613a5b --- /dev/null +++ b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_AddIndexTests.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Models.Indexes; +using DotNetProjects.Migrator.Providers.Models.Indexes.Enums; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; +using Oracle.ManagedDataAccess.Client; +using Index = DotNetProjects.Migrator.Framework.Index; + +namespace Migrator.Tests.Providers.OracleProvider; + +[TestFixture] +[Category("Oracle")] +public class OracleTransformationProvider_AddIndex_Tests : Generic_AddIndexTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginOracleTransactionAsync(); + } + + [Test] + public void AddIndex_Unique_Success() + { + // Arrange + const string tableName = "TestTable"; + const string columnName = "TestColumn"; + const string columnName2 = "TestColumn2"; + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, new Column(columnName, DbType.Int32), new Column(columnName2, DbType.String)); + + // Act + Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [columnName], + Unique = true, + }); + + // Assert + Provider.Insert(tableName, [columnName, columnName2], [1, "Hello"]); + var ex = Assert.Throws(() => Provider.Insert(tableName, [columnName, columnName2], [1, "Some other string"])); + var index = Provider.GetIndexes(tableName).Single(); + + Assert.That(index.Unique, Is.True); + Assert.That(ex.Number, Is.EqualTo(1)); + } + + /// + /// This test is located in the dedicated database type folder not in the base class since + /// cannot read filter items for Oracle and Oracle does not allow + /// Unique = true for indexes with functional expressions + /// + [Test] + public void AddIndex_FilteredIndexMiscellaneousFilterTypesAndDataTypes_Success() + { + // Arrange + const string tableName = "TestTable"; + const string columnName1 = "TestColumn1"; + const string columnName2 = "TestColumn2"; + const string columnName3 = "TestColumn3"; + const string columnName4 = "TestColumn4"; + const string columnName5 = "TestColumn5"; + const string columnName6 = "TestColumn6"; + const string columnName7 = "TestColumn7"; + const string columnName8 = "TestColumn8"; + const string columnName9 = "TestColumn9"; + const string columnName10 = "TestColumn10"; + const string columnName11 = "TestColumn11"; + const string columnName12 = "TestColumn12"; + const string columnName13 = "TestColumn13"; + + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, + new Column(columnName1, DbType.Int16), + new Column(columnName2, DbType.Int32), + new Column(columnName3, DbType.Int64), + new Column(columnName4, DbType.UInt16), + new Column(columnName5, DbType.UInt32), + new Column(columnName6, DbType.UInt64), + new Column(columnName7, DbType.String), + new Column(columnName8, DbType.Int32), + new Column(columnName9, DbType.Int32), + new Column(columnName10, DbType.Int32), + new Column(columnName11, DbType.Int32), + new Column(columnName12, DbType.Int32), + new Column(columnName13, DbType.Int32) + ); + + List filterItems = [ + new() { Filter = FilterType.EqualTo, ColumnName = columnName1, Value = 1 }, + new() { Filter = FilterType.GreaterThan, ColumnName = columnName2, Value = 2 }, + new() { Filter = FilterType.GreaterThanOrEqualTo, ColumnName = columnName3, Value = 2323 }, + new() { Filter = FilterType.NotEqualTo, ColumnName = columnName4, Value = 3434 }, + new() { Filter = FilterType.NotEqualTo, ColumnName = columnName5, Value = -3434 }, + new() { Filter = FilterType.SmallerThan, ColumnName = columnName6, Value = 3434345345 }, + new() { Filter = FilterType.NotEqualTo, ColumnName = columnName7, Value = "asdf" }, + new() { Filter = FilterType.EqualTo, ColumnName = columnName8, Value = 11 }, + new() { Filter = FilterType.GreaterThan, ColumnName = columnName9, Value = 22 }, + new() { Filter = FilterType.GreaterThanOrEqualTo, ColumnName = columnName10, Value = 33 }, + new() { Filter = FilterType.NotEqualTo, ColumnName = columnName11, Value = 44 }, + new() { Filter = FilterType.SmallerThan, ColumnName = columnName12, Value = 55 }, + new() { Filter = FilterType.SmallerThanOrEqualTo, ColumnName = columnName13, Value = 66 } + ]; + + // Act + var addIndexSql = Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [ + columnName1, + columnName2, + columnName3, + columnName4, + columnName5, + columnName6, + columnName7, + columnName8, + columnName9, + columnName10, + columnName11, + columnName12, + columnName13 + ], + Unique = false, + FilterItems = filterItems + }); + + Provider.Insert(table: tableName, [columnName1], [1]); + + // Assert + var indexesFromDatabase = Provider.GetIndexes(table: tableName); + + // In Oracle it seems that functional expressions are stored as column with generated column name. FilterItems are not + // implemented in Provider.GetIndexes() for Oracle. No further assert possible at this point in time. + Assert.That(indexesFromDatabase.Single().KeyColumns.Count, Is.EqualTo(13)); + + + var expectedSql = "CREATE INDEX TestIndexName ON TestTable (CASE WHEN TestColumn1 = 1 THEN TestColumn1 ELSE NULL END, CASE WHEN TestColumn2 > 2 THEN TestColumn2 ELSE NULL END, CASE WHEN TestColumn3 >= 2323 THEN TestColumn3 ELSE NULL END, CASE WHEN TestColumn4 <> 3434 THEN TestColumn4 ELSE NULL END, CASE WHEN TestColumn5 <> -3434 THEN TestColumn5 ELSE NULL END, CASE WHEN TestColumn6 < 3434345345 THEN TestColumn6 ELSE NULL END, CASE WHEN TestColumn7 <> 'asdf' THEN TestColumn7 ELSE NULL END, CASE WHEN TestColumn8 = 11 THEN TestColumn8 ELSE NULL END, CASE WHEN TestColumn9 > 22 THEN TestColumn9 ELSE NULL END, CASE WHEN TestColumn10 >= 33 THEN TestColumn10 ELSE NULL END, CASE WHEN TestColumn11 <> 44 THEN TestColumn11 ELSE NULL END, CASE WHEN TestColumn12 < 55 THEN TestColumn12 ELSE NULL END, CASE WHEN TestColumn13 <= 66 THEN TestColumn13 ELSE NULL END)"; + + Assert.That(addIndexSql, Is.EqualTo(expectedSql)); + } + + /// + /// Migrator throws if UNIQUE is used with functional expressions. + /// + [Test] + public void AddIndex_FilterItemsCombinedWithUnique_Throws() + { + // Arrange + const string tableName = "TestTable"; + const string columnName1 = "TestColumn1"; + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, + new Column(columnName1, DbType.Int16) + ); + + List filterItems = [ + new() { Filter = FilterType.EqualTo, ColumnName = columnName1, Value = 1 }, + ]; + + // Act/Assert + Assert.Throws(() => Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [ + columnName1 + ], + Unique = true, + FilterItems = filterItems + })); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_AddPrimaryKeyTests.cs b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_AddPrimaryKeyTests.cs new file mode 100644 index 00000000..bfb884e9 --- /dev/null +++ b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_AddPrimaryKeyTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.OracleProvider; + +[TestFixture] +[Category("Oracle")] +public class OracleTransformationProvider_AddPrimaryKeyTests : Generic_AddPrimaryTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginOracleTransactionAsync(); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_AddTable_Tests.cs b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_AddTable_Tests.cs new file mode 100644 index 00000000..c5e9f53e --- /dev/null +++ b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_AddTable_Tests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.OracleProvider; + +[TestFixture] +[Category("Oracle")] +public class OracleTransformationProvider_AddTable_Tests : Generic_AddTableTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginOracleTransactionAsync(); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_ChangeColumnTests.cs b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_ChangeColumnTests.cs new file mode 100644 index 00000000..9b753c9b --- /dev/null +++ b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_ChangeColumnTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.OracleProvider; + +[TestFixture] +[Category("Oracle")] +public class OracleTransformationProvider_ChangeColumn_Tests : Generic_ChangeColumnTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginOracleTransactionAsync(); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_ConstraintExistsTests.cs b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_ConstraintExistsTests.cs new file mode 100644 index 00000000..cd5f9a04 --- /dev/null +++ b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_ConstraintExistsTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.OracleProvider; + +[TestFixture] +[Category("Oracle")] +public class OracleTransformationProvider_ConstraintExists_Tests : Generic_ConstraintExistsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginOracleTransactionAsync(); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_CopyDataFromTableToTableTests.cs b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_CopyDataFromTableToTableTests.cs new file mode 100644 index 00000000..9652a6b3 --- /dev/null +++ b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_CopyDataFromTableToTableTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.OracleProvider; + +[TestFixture] +[Category("Oracle")] +public class OracleTransformationProvider_CopyDataFromTableToTableTests : Generic_CopyDataFromTableToTableBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginOracleTransactionAsync(); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_DefaultValueTests.cs b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_DefaultValueTests.cs new file mode 100644 index 00000000..28097140 --- /dev/null +++ b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_DefaultValueTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.OracleProvider; + +[TestFixture] +[Category("Oracle")] +public class OracleTransformationProvider_DefaultValueTests : Generic_DefaultValueTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginOracleTransactionAsync(); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_GetColumns_Tests.cs b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_GetColumns_Tests.cs new file mode 100644 index 00000000..fbc6fed7 --- /dev/null +++ b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_GetColumns_Tests.cs @@ -0,0 +1,142 @@ +using System; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.OracleProvider; + +[TestFixture] +[Category("Oracle")] +public class OracleTransformationProvider_GetColumns_Tests : Generic_GetColumnsTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginOracleTransactionAsync(); + } + + /// + /// Since SQLite does not support binary default values in the generic file a separate test is needed for Oracle + /// Find the generic test in the base class. + /// + [Test] + public void GetColumns_Oracle_DefaultValues_Succeeds() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string binaryColumnName1 = "binarycolumn1"; + + // Should be extended by remaining types + Provider.AddTable(testTableName, + new Column(binaryColumnName1, DbType.Binary, defaultValue: new byte[] { 12, 32, 34 }) + ); + + // Act + var columns = Provider.GetColumns(testTableName); + + // Assert + var binarycolumn1 = columns.Single(x => x.Name.Equals(binaryColumnName1, StringComparison.OrdinalIgnoreCase)); + + Assert.That(binarycolumn1.DefaultValue, Is.EqualTo(new byte[] { 12, 32, 34 })); + } + + [Test] + public void GetColumns_DefaultValues_Succeeds() + { + // Arrange + var dateTimeDefaultValue = new DateTime(2000, 1, 2, 3, 4, 5, DateTimeKind.Utc); + var guidDefaultValue = Guid.NewGuid(); + var decimalDefaultValue = 14.56565m; + + const string testTableName = "MyDefaultTestTable"; + + const string dateTimeColumnName1 = "datetimecolumn1"; + const string dateTimeColumnName2 = "datetimecolumn2"; + const string decimalColumnName1 = "decimalcolumn"; + const string guidColumnName1 = "guidcolumn1"; + const string booleanColumnName1 = "booleancolumn1"; + const string int32ColumnName1 = "int32column1"; + const string int64ColumnName1 = "int64column1"; + const string int64ColumnName2 = "int64column2"; + const string stringColumnName1 = "stringcolumn1"; + const string binaryColumnName1 = "binarycolumn1"; + const string doubleColumnName1 = "doublecolumn1"; + + // Should be extended by remaining types + Provider.AddTable(testTableName, + new Column(dateTimeColumnName1, DbType.DateTime, dateTimeDefaultValue), + new Column(dateTimeColumnName2, DbType.DateTime2, dateTimeDefaultValue), + new Column(decimalColumnName1, DbType.Decimal, decimalDefaultValue), + new Column(guidColumnName1, DbType.Guid, guidDefaultValue), + + // other boolean default values are tested in another test + new Column(booleanColumnName1, DbType.Boolean, true), + + new Column(int32ColumnName1, DbType.Int32, defaultValue: 43), + new Column(int64ColumnName1, DbType.Int64, defaultValue: 88), + new Column(int64ColumnName2, DbType.Int64, defaultValue: 0), + new Column(stringColumnName1, DbType.String, defaultValue: "Hello"), + new Column(binaryColumnName1, DbType.Binary, defaultValue: new byte[] { 12, 32, 34 }), + new Column(doubleColumnName1, DbType.Double, defaultValue: 84.874596567) { Precision = 19, Scale = 10 } + ); + + // Act + var columns = Provider.GetColumns(testTableName); + + // Assert + var dateTimeColumn1 = columns.Single(x => x.Name.Equals(dateTimeColumnName1, StringComparison.OrdinalIgnoreCase)); + var dateTimeColumn2 = columns.Single(x => x.Name.Equals(dateTimeColumnName2, StringComparison.OrdinalIgnoreCase)); + var decimalColumn1 = columns.Single(x => x.Name.Equals(decimalColumnName1, StringComparison.OrdinalIgnoreCase)); + var guidColumn1 = columns.Single(x => x.Name.Equals(guidColumnName1, StringComparison.OrdinalIgnoreCase)); + var booleanColumn1 = columns.Single(x => x.Name.Equals(booleanColumnName1, StringComparison.OrdinalIgnoreCase)); + var int32Column1 = columns.Single(x => x.Name.Equals(int32ColumnName1, StringComparison.OrdinalIgnoreCase)); + var int64Column1 = columns.Single(x => x.Name.Equals(int64ColumnName1, StringComparison.OrdinalIgnoreCase)); + var int64Column2 = columns.Single(x => x.Name.Equals(int64ColumnName2, StringComparison.OrdinalIgnoreCase)); + var stringColumn1 = columns.Single(x => x.Name.Equals(stringColumnName1, StringComparison.OrdinalIgnoreCase)); + var binarycolumn1 = columns.Single(x => x.Name.Equals(binaryColumnName1, StringComparison.OrdinalIgnoreCase)); + var doubleColumn1 = columns.Single(x => x.Name.Equals(doubleColumnName1, StringComparison.OrdinalIgnoreCase)); + + Assert.That(dateTimeColumn1.DefaultValue, Is.EqualTo(dateTimeDefaultValue)); + Assert.That(dateTimeColumn2.DefaultValue, Is.EqualTo(dateTimeDefaultValue)); + Assert.That(decimalColumn1.DefaultValue, Is.EqualTo(decimalDefaultValue)); + Assert.That(guidColumn1.DefaultValue, Is.EqualTo(guidDefaultValue)); + Assert.That(booleanColumn1.DefaultValue, Is.True); + Assert.That(int32Column1.DefaultValue, Is.EqualTo(43)); + Assert.That(int64Column1.DefaultValue, Is.EqualTo(88)); + Assert.That(stringColumn1.DefaultValue, Is.EqualTo("Hello")); + Assert.That(binarycolumn1.DefaultValue, Is.EqualTo(new byte[] { 12, 32, 34 })); + Assert.That(doubleColumn1.DefaultValue, Is.EqualTo(84.874596567)); + } + + [Test] + public void GetColumns_GetIdentity_Succeeds() + { + // Arrange + var tableName1 = "Table1"; + var tableName2 = "Table2"; + var tableName3 = "Table3"; + var tableName4 = "Table4"; + var columnName1 = "ColumnName1"; + + Provider.ExecuteNonQuery($"CREATE TABLE {tableName1} ({columnName1} NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY)"); + Provider.ExecuteNonQuery($"CREATE TABLE {tableName2} ({columnName1} NUMBER PRIMARY KEY)"); + + Provider.AddTable(name: tableName3, new Column(columnName1, DbType.Int32, ColumnProperty.Identity | ColumnProperty.PrimaryKey)); + Provider.AddTable(name: tableName4, new Column(columnName1, DbType.Int32, ColumnProperty.PrimaryKey)); + + // Act + var columnTable1 = Provider.GetColumnByName(table: tableName1, column: columnName1); + var columnTable2 = Provider.GetColumnByName(table: tableName2, column: columnName1); + var columnTable3 = Provider.GetColumnByName(table: tableName3, column: columnName1); + var columnTable4 = Provider.GetColumnByName(table: tableName4, column: columnName1); + + // Assert + Assert.That(columnTable1.IsIdentity, Is.True); + Assert.That(columnTable2.IsIdentity, Is.False); + Assert.That(columnTable3.IsIdentity, Is.True); + Assert.That(columnTable4.IsIdentity, Is.False); + } +} diff --git a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_PrimaryKeyExists.cs b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_PrimaryKeyExists.cs new file mode 100644 index 00000000..54d35731 --- /dev/null +++ b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_PrimaryKeyExists.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.OracleProvider; + +[TestFixture] +[Category("Oracle")] +public class OracleTransformationProvider_PrimaryKeyExistsTests : TransformationProviderSimpleBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginOracleTransactionAsync(); + } + + [Test] + public void CanAddPrimaryKey() + { + AddTable(); + AddPrimaryKey(); + Assert.That(Provider.PrimaryKeyExists("Test", "PK_Test"), Is.True); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_TableExistsTests.cs b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_TableExistsTests.cs new file mode 100644 index 00000000..e6d20358 --- /dev/null +++ b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_TableExistsTests.cs @@ -0,0 +1,42 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.OracleProvider.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.OracleProvider; + +[TestFixture] +[Category("Oracle")] +public class OracleTransformationProvider_TableExistsTests : OracleTransformationProviderTestBase +{ + [Test] + public void TableExists_TableExists_Returns() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string propertyName1 = "Color1"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32) + ); + + // Act + var tableExists = Provider.TableExists(testTableName); + + // Assert + Assert.That(tableExists, Is.True); + } + + [Test] + public void TableExists_TableDoesNotExist_ReturnsFalse() + { + // Arrange + const string myTableName = "MyTable"; + + // Act + var tableExists = Provider.TableExists(myTableName); + + // Assert + Assert.That(tableExists, Is.False); + } +} diff --git a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_UpdateFromTableToTableTests.cs b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_UpdateFromTableToTableTests.cs new file mode 100644 index 00000000..a8d297d6 --- /dev/null +++ b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_UpdateFromTableToTableTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.OracleProvider; + +[TestFixture] +[Category("Oracle")] +public class OracleTransformationProvider_UpdateFromTableToTableTests : Generic_UpdateFromTableToTableTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginOracleTransactionAsync(); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_ViewExistsTests.cs b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_ViewExistsTests.cs new file mode 100644 index 00000000..726e0704 --- /dev/null +++ b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_ViewExistsTests.cs @@ -0,0 +1,45 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.OracleProvider.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.OracleProvider; + +[TestFixture] +[Category("Oracle")] +public class OracleTransformationProvider_ViewExistsTests : OracleTransformationProviderTestBase +{ + [Test] + public void ViewExists_ViewExists_Returns() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string myViewName = "MyView"; + const string propertyName1 = "Color1"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32) + ); + + Provider.ExecuteNonQuery($"CREATE VIEW {myViewName} AS SELECT {propertyName1} FROM {testTableName}"); + + // Act + var viewExists = Provider.ViewExists(myViewName); + + // Assert + Assert.That(viewExists, Is.True); + } + + [Test] + public void ViewExists_ViewDoesNotExist_ReturnsFalse() + { + // Arrange + const string myViewName = "MyView"; + + // Act + var viewExists = Provider.ViewExists(myViewName); + + // Assert + Assert.That(viewExists, Is.False); + } +} diff --git a/src/Migrator.Tests/Providers/OracleTransformationProviderTest.cs b/src/Migrator.Tests/Providers/OracleTransformationProviderTest.cs deleted file mode 100644 index 6dc8c77b..00000000 --- a/src/Migrator.Tests/Providers/OracleTransformationProviderTest.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Configuration; -using System.Data; -using Migrator.Framework; -using Migrator.Providers.Oracle; -using NUnit.Framework; - -namespace Migrator.Tests.Providers -{ - [TestFixture] - [Category("Oracle")] - public class OracleTransformationProviderTest : TransformationProviderConstraintBase - { - #region Setup/Teardown - - [SetUp] - public void SetUp() - { - string constr = ConfigurationManager.AppSettings["OracleConnectionString"]; - if (constr == null) - throw new ArgumentNullException("OracleConnectionString", "No config file"); - _provider = new OracleTransformationProvider(new OracleDialect(), constr, null, "default", null); - _provider.BeginTransaction(); - - AddDefaultTable(); - } - - #endregion - - [Test] - public void ChangeColumn_FromNotNullToNotNull() - { - _provider.ExecuteNonQuery("DELETE FROM TestTwo"); - _provider.ChangeColumn("TestTwo", new Column("TestId", DbType.String, 50, ColumnProperty.Null)); - _provider.Insert("TestTwo", new[] {"Id", "TestId"}, new object[] {3, "Not an Int val."}); - _provider.ChangeColumn("TestTwo", new Column("TestId", DbType.String, 50, ColumnProperty.NotNull)); - _provider.ChangeColumn("TestTwo", new Column("TestId", DbType.String, 50, ColumnProperty.NotNull)); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/PostgreSQL/Base/PostgreSQLTransformationProviderTestBase.cs b/src/Migrator.Tests/Providers/PostgreSQL/Base/PostgreSQLTransformationProviderTestBase.cs new file mode 100644 index 00000000..9e8de2ac --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/Base/PostgreSQLTransformationProviderTestBase.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL.Base; + +[TestFixture] +[Category("Postgre")] +public abstract class PostgreSQLTransformationProviderTestBase : TransformationProviderSimpleBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginPostgreSQLTransactionAsync(); + } +} diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProviderGenericTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProviderGenericTests.cs new file mode 100644 index 00000000..5fce7dfd --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProviderGenericTests.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProviderGenericTests : TransformationProviderGenericMiscConstraintBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginPostgreSQLTransactionAsync(); + + AddDefaultTable(); + } +} diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_AddIndexTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_AddIndexTests.cs new file mode 100644 index 00000000..0f981653 --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_AddIndexTests.cs @@ -0,0 +1,344 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Models.Indexes; +using DotNetProjects.Migrator.Providers.Models.Indexes.Enums; +using Migrator.Tests.Providers.Generic; +using Npgsql; +using NUnit.Framework; +using Index = DotNetProjects.Migrator.Framework.Index; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProvider_AddIndexTests : Generic_AddIndexTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginPostgreSQLTransactionAsync(); + } + + [Test] + public void AddTableWithCompoundPrimaryKey() + { + Provider.AddTable("Test", + new Column("PersonId", DbType.Int32, ColumnProperty.PrimaryKey), + new Column("AddressId", DbType.Int32, ColumnProperty.PrimaryKey) + ); + + Assert.That(Provider.TableExists("Test"), Is.True, "Table doesn't exist"); + Assert.That(Provider.PrimaryKeyExists("Test", "PK_Test"), Is.True, "Constraint doesn't exist"); + } + + [Test] + public void AddIndex_Unique_Success() + { + // Arrange + const string tableName = "TestTable"; + const string columnName = "TestColumn"; + const string columnName2 = "TestColumn2"; + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, new Column(columnName, DbType.Int32), new Column(columnName2, DbType.String)); + + // Act + Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [columnName], + Unique = true, + }); + + // Assert + var indexes = Provider.GetIndexes(tableName); + Provider.Insert(tableName, [columnName, columnName2], [1, "Hello"]); + var ex = Assert.Throws(() => Provider.Insert(tableName, [columnName, columnName2], [1, "Some other string"])); + var index = indexes.Single(); + + Assert.That(index.Unique, Is.True); + Assert.That(ex.SqlState, Is.EqualTo("23505")); + } + + [Test] + public void AddIndex_FilteredIndexGreaterOrEqualThanNumber_Success() + { + // Arrange + const string tableName = "TestTable"; + const string columnName = "TestColumn"; + const string columnName2 = "TestColumn2"; + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, new Column(columnName, DbType.Int32), new Column(columnName2, DbType.String)); + + // Act + Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [columnName, columnName2], + Unique = true, + FilterItems = [ + new() { Filter = FilterType.GreaterThanOrEqualTo, ColumnName = columnName, Value = 100 }, + new() { Filter = FilterType.EqualTo, ColumnName = columnName2, Value = "Hello" }, + ] + }); + + // Assert + var index = Provider.GetIndexes(tableName).Single(); + Provider.Insert(tableName, [columnName, columnName2], [1, "Hello"]); + // Unique but no exception is thrown since smaller than 100 + Provider.Insert(tableName, [columnName, columnName2], [1, "Hello"]); + + Provider.Insert(tableName, [columnName, columnName2], [100, "Hello"]); + var ex = Assert.Throws(() => Provider.Insert(tableName, [columnName, columnName2], [100, "Hello"])); + + Assert.That(index.Unique, Is.True); + Assert.That(ex.SqlState, Is.EqualTo("23505")); + } + + [Test] + public void AddIndex_IncludeColumnsSingle_Success() + { + // Arrange + const string tableName = "TestTable"; + const string columnName = "TestColumn"; + const string columnName2 = "TestColumn2"; + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, new Column(columnName, DbType.Int32), new Column(columnName2, DbType.String)); + + // Act + Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [columnName], + Unique = true, + IncludeColumns = [columnName2] + }); + + // Assert + var index = Provider.GetIndexes(tableName).Single(); + + Assert.That(index.Unique, Is.True); + Assert.That(index.KeyColumns.Single, Is.EqualTo(columnName).IgnoreCase); + Assert.That(index.IncludeColumns.Single, Is.EqualTo(columnName2).IgnoreCase); + } + + [Test] + public void AddIndex_IncludeColumnsMultiple_Success() + { + // Arrange + const string tableName = "TestTable"; + const string columnName = "TestColumn"; + const string columnName2 = "TestColumn2"; + const string columnName3 = "TestColumn3"; + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, new Column(columnName, DbType.Int32), new Column(columnName2, DbType.String), new Column(columnName3, DbType.Boolean)); + + // Act + Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [columnName], + Unique = true, + IncludeColumns = [columnName2, columnName3] + }); + + // Assert + var index = Provider.GetIndexes(tableName).Single(); + + Assert.That(index.Unique, Is.True); + Assert.That(index.KeyColumns.Single, Is.EqualTo(columnName).IgnoreCase); + Assert.That(index.IncludeColumns, Is.EquivalentTo([columnName2, columnName3]) + .Using((x, y) => string.Compare(x, y, ignoreCase: true))); + } + + [Test] + public void AddIndex_FilteredIndexSingle_Success() + { + // Arrange + const string tableName = "TestTable"; + const string columnName1 = "TestColumn1"; + + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, + new Column(columnName1, DbType.Int16) + ); + + List filterItems = [ + new() { Filter = FilterType.EqualTo, ColumnName = columnName1, Value = 1 }, + ]; + + // Act + Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [columnName1], + Unique = true, + FilterItems = filterItems + }); + + // Assert + + var indexesFromDatabase = Provider.GetIndexes(table: tableName); + var filteredItemsFromDatabase = indexesFromDatabase.Single().FilterItems; + + // We cannot find out the exact DbType so we compare strings. + foreach (var filteredItemFromDatabase in filteredItemsFromDatabase) + { + var expected = filterItems.Single(x => x.ColumnName.Equals(filteredItemFromDatabase.ColumnName, StringComparison.OrdinalIgnoreCase)); + Assert.That(filteredItemFromDatabase.Filter, Is.EqualTo(expected.Filter)); + Assert.That(Convert.ToString(filteredItemFromDatabase.Value, CultureInfo.InvariantCulture), Is.EqualTo(Convert.ToString(expected.Value, CultureInfo.InvariantCulture))); + } + + Assert.That( + filteredItemsFromDatabase.Select(x => x.ColumnName.ToLowerInvariant()), + Is.EquivalentTo(filterItems.Select(x => x.ColumnName.ToLowerInvariant())) + ); + } + + /// + /// This test is located in the dedicated database type folder not in the base class since + /// cannot read filter items for Oracle. + /// + [Test] + public void AddIndex_FilteredIndexMiscellaneousFilterTypesAndDataTypes_Success() + { + // Arrange + const string tableName = "TestTable"; + const string columnName1 = "TestColumn1"; + const string columnName2 = "TestColumn2"; + const string columnName3 = "TestColumn3"; + const string columnName4 = "TestColumn4"; + const string columnName5 = "TestColumn5"; + const string columnName6 = "TestColumn6"; + const string columnName7 = "TestColumn7"; + const string columnName8 = "TestColumn8"; + const string columnName9 = "TestColumn9"; + const string columnName10 = "TestColumn10"; + const string columnName11 = "TestColumn11"; + const string columnName12 = "TestColumn12"; + const string columnName13 = "TestColumn13"; + + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, + new Column(columnName1, DbType.Int16), + new Column(columnName2, DbType.Int32), + new Column(columnName3, DbType.Int64), + new Column(columnName4, DbType.UInt16), + new Column(columnName5, DbType.UInt32), + new Column(columnName6, DbType.UInt64), + new Column(columnName7, DbType.String), + new Column(columnName8, DbType.Int32), + new Column(columnName9, DbType.Int32), + new Column(columnName10, DbType.Int32), + new Column(columnName11, DbType.Int32), + new Column(columnName12, DbType.Int32), + new Column(columnName13, DbType.Int32) + ); + + List filterItems = [ + new() { Filter = FilterType.EqualTo, ColumnName = columnName1, Value = 1 }, + new() { Filter = FilterType.GreaterThan, ColumnName = columnName2, Value = 2 }, + new() { Filter = FilterType.GreaterThanOrEqualTo, ColumnName = columnName3, Value = 2323 }, + new() { Filter = FilterType.NotEqualTo, ColumnName = columnName4, Value = 3434 }, + new() { Filter = FilterType.NotEqualTo, ColumnName = columnName5, Value = -3434 }, + new() { Filter = FilterType.SmallerThan, ColumnName = columnName6, Value = 3434345345 }, + new() { Filter = FilterType.NotEqualTo, ColumnName = columnName7, Value = "asdf" }, + new() { Filter = FilterType.EqualTo, ColumnName = columnName8, Value = 11 }, + new() { Filter = FilterType.GreaterThan, ColumnName = columnName9, Value = 22 }, + new() { Filter = FilterType.GreaterThanOrEqualTo, ColumnName = columnName10, Value = 33 }, + new() { Filter = FilterType.NotEqualTo, ColumnName = columnName11, Value = 44 }, + new() { Filter = FilterType.SmallerThan, ColumnName = columnName12, Value = 55 }, + new() { Filter = FilterType.SmallerThanOrEqualTo, ColumnName = columnName13, Value = 66 } + ]; + + // Act + var addIndexSql = Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [ + columnName1, + columnName2, + columnName3, + columnName4, + columnName5, + columnName6, + columnName7, + columnName8, + columnName9, + columnName10, + columnName11, + columnName12, + columnName13 + ], + Unique = true, + FilterItems = filterItems + }); + + // Assert + + var indexesFromDatabase = Provider.GetIndexes(table: tableName); + var filteredItemsFromDatabase = indexesFromDatabase.Single().FilterItems; + + // We cannot find out the exact DbType so we compare strings. + foreach (var filteredItemFromDatabase in filteredItemsFromDatabase) + { + var expected = filterItems.Single(x => x.ColumnName.Equals(filteredItemFromDatabase.ColumnName, StringComparison.OrdinalIgnoreCase)); + Assert.That(filteredItemFromDatabase.Filter, Is.EqualTo(expected.Filter)); + Assert.That(Convert.ToString(filteredItemFromDatabase.Value, CultureInfo.InvariantCulture), Is.EqualTo(Convert.ToString(expected.Value, CultureInfo.InvariantCulture))); + } + + Assert.That( + filteredItemsFromDatabase.Select(x => x.ColumnName.ToLowerInvariant()), + Is.EquivalentTo(filterItems.Select(x => x.ColumnName.ToLowerInvariant())) + ); + + var expectedSql = "CREATE UNIQUE INDEX TestIndexName ON TestTable (TestColumn1, TestColumn2, TestColumn3, TestColumn4, TestColumn5, TestColumn6, TestColumn7, TestColumn8, TestColumn9, TestColumn10, TestColumn11, TestColumn12, TestColumn13) WHERE TestColumn1 = 1 AND TestColumn2 > 2 AND TestColumn3 >= 2323 AND TestColumn4 <> 3434 AND TestColumn5 <> -3434 AND TestColumn6 < 3434345345 AND TestColumn7 <> 'asdf' AND TestColumn8 = 11 AND TestColumn9 > 22 AND TestColumn10 >= 33 AND TestColumn11 <> 44 AND TestColumn12 < 55 AND TestColumn13 <= 66"; + + Assert.That(addIndexSql, Is.EqualTo(expectedSql)); + } + + /// + /// Reserved word used as table name. + /// + [Test] + public void AddIndex_TableNameIsReservedWord_Succeeds() + { + // Arrange + Provider.AddTable("trigger", + new Column(name: "id", type: DbType.Int32, ColumnProperty.PrimaryKeyWithIdentity), + new Column(name: "test_run_id", type: DbType.Int32, ColumnProperty.NotNull) + ); + + Provider.AddTable("statistics", + new Column(name: "id", type: DbType.Int32, ColumnProperty.PrimaryKeyWithIdentity), + new Column(name: "test_run_id", type: DbType.Int32, ColumnProperty.NotNull) + ); + + // Act + var addIndexTriggerSql = Provider.AddIndex(name: "IX_trigger__test_run_id", table: "trigger", "test_run_id"); + var addIndexStatisticsSql = Provider.AddIndex(name: "IX_statistics__test_run_id", table: "statistics", "test_run_id"); + + // Assert + var expectedSQLTableTrigger = "CREATE INDEX IX_trigger__test_run_id ON \"trigger\" (test_run_id)"; + var expectedSQLTableStatistics = "CREATE INDEX IX_statistics__test_run_id ON \"statistics\" (test_run_id)"; + + Assert.That(addIndexTriggerSql, Is.EqualTo(expectedSQLTableTrigger)); + Assert.That(addIndexStatisticsSql, Is.EqualTo(expectedSQLTableStatistics)); + } +} diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_AddPrimaryKeyTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_AddPrimaryKeyTests.cs new file mode 100644 index 00000000..9649a621 --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_AddPrimaryKeyTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProvider_AddPrimaryKeyTests : Generic_AddPrimaryTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginPostgreSQLTransactionAsync(); + } +} diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_AddTableTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_AddTableTests.cs new file mode 100644 index 00000000..d09187b1 --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_AddTableTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProvider_AddTableTests : Generic_AddTableTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginPostgreSQLTransactionAsync(); + } +} diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_ChangeColumnTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_ChangeColumnTests.cs new file mode 100644 index 00000000..7185210c --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_ChangeColumnTests.cs @@ -0,0 +1,107 @@ +using System; +using System.Data; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProvider_ChangeColumnTests : Generic_ChangeColumnTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginPostgreSQLTransactionAsync(); + } + + [Test] + public void ChangeColumn_DateTimeOffsetToDateTime_Success() + { + // Arrange + var tableName = "TableName"; + var column1Name = "Column1"; + var column2Name = "Column2"; + var dateTimeDefaultValue = new DateTime(2025, 5, 4, 3, 2, 1, DateTimeKind.Utc); + var dateTimeInsert = new DateTime(2001, 2, 3, 4, 5, 6, 7, DateTimeKind.Utc); + + // Act + Provider.AddTable(tableName, + new Column(column1Name, DbType.Int32, ColumnProperty.Null), + new Column(column2Name, DbType.DateTimeOffset, ColumnProperty.Null, defaultValue: dateTimeDefaultValue) + ); + + Provider.Insert(table: tableName, columns: [column2Name], values: [dateTimeInsert]); + + // Assert + Provider.ChangeColumn(tableName, new Column(column2Name, DbType.DateTime2, ColumnProperty.NotNull)); + var column2 = Provider.GetColumnByName(tableName, column2Name); + + Assert.That(column2.MigratorDbType, Is.EqualTo(MigratorDbType.DateTime2)); + Assert.That(column2.DefaultValue, Is.Null); + } + + [Test] + public void ChangeColumn_DateTimeOffsetToDateTimeGetDefaultValueAndReuseIt_DefaultValueIsEqualAndValueIsEqual() + { + // Arrange + var tableName = "TableName"; + var column1Name = "Column1"; + var column2Name = "Column2"; + var dateTimeOffsetDefaultValue = new DateTimeOffset(2022, 2, 3, 4, 5, 6, TimeSpan.FromHours(2)); + var dateTimeOffsetInsert = new DateTimeOffset(2001, 2, 3, 4, 5, 6, TimeSpan.FromHours(2)); + + Provider.AddTable(tableName, + new Column(column1Name, DbType.Int32, ColumnProperty.Null), + new Column(column2Name, DbType.DateTimeOffset, ColumnProperty.Null, defaultValue: dateTimeOffsetDefaultValue) + ); + + Provider.Insert(table: tableName, columns: [column2Name], values: [dateTimeOffsetInsert]); + // Act + + var column2 = Provider.GetColumnByName(tableName, column2Name); + Assert.That(((DateTimeOffset)column2.DefaultValue).UtcDateTime, Is.EqualTo(dateTimeOffsetDefaultValue.UtcDateTime)); + Provider.ChangeColumn(tableName, new Column(column2Name, DbType.DateTime2, ColumnProperty.NotNull, defaultValue: column2.DefaultValue)); + + + // Assert + column2 = Provider.GetColumnByName(tableName, column2Name); + + // using var reader = Provider.Select(Provider.GetCommand(), what: column2Name, from: tableName); + // var valueFromDatabase = reader.GetDateTime(0); + + Assert.That(column2.MigratorDbType, Is.EqualTo(MigratorDbType.DateTime2)); + Assert.That(column2.DefaultValue, Is.EqualTo(dateTimeOffsetDefaultValue.UtcDateTime)); + } + + [Test] + public void GetColumns_GetIdentity_Succeeds() + { + // Arrange + var tableName1 = "Table1"; + var tableName2 = "Table2"; + var tableName3 = "Table3"; + var tableName4 = "Table4"; + var columnName1 = "ColumnName1"; + + Provider.ExecuteNonQuery($"CREATE TABLE {tableName1} ({columnName1} INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY)"); + Provider.ExecuteNonQuery($"CREATE TABLE {tableName2} ({columnName1} INT PRIMARY KEY)"); + + Provider.AddTable(name: tableName3, new Column(columnName1, DbType.Int32, ColumnProperty.Identity | ColumnProperty.PrimaryKey)); + Provider.AddTable(name: tableName4, new Column(columnName1, DbType.Int32, ColumnProperty.PrimaryKey)); + + // Act + var columnTable1 = Provider.GetColumnByName(table: tableName1, column: columnName1); + var columnTable2 = Provider.GetColumnByName(table: tableName2, column: columnName1); + var columnTable3 = Provider.GetColumnByName(table: tableName3, column: columnName1); + var columnTable4 = Provider.GetColumnByName(table: tableName4, column: columnName1); + + // Assert + Assert.That(columnTable1.IsIdentity, Is.True); + Assert.That(columnTable2.IsIdentity, Is.False); + Assert.That(columnTable3.IsIdentity, Is.True); + Assert.That(columnTable4.IsIdentity, Is.False); + } +} diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_ConstraintExists.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_ConstraintExists.cs new file mode 100644 index 00000000..358859be --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_ConstraintExists.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProvider_ConstraintExistsTests : Generic_ConstraintExistsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginPostgreSQLTransactionAsync(); + } +} diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_CopyDataFromTableToTableTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_CopyDataFromTableToTableTests.cs new file mode 100644 index 00000000..4bf485d8 --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_CopyDataFromTableToTableTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProvider_CopyDataFromTableToTableTests : Generic_CopyDataFromTableToTableBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginPostgreSQLTransactionAsync(); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_DefaultValueTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_DefaultValueTests.cs new file mode 100644 index 00000000..a985d3ab --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_DefaultValueTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProvider_DefaultValueTests : Generic_DefaultValueTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginPostgreSQLTransactionAsync(); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnContent_SizeTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnContent_SizeTests.cs new file mode 100644 index 00000000..16c9deda --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnContent_SizeTests.cs @@ -0,0 +1,52 @@ +using System; +using System.Data; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.PostgreSQL.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProvider_GetColumnContentSize_Tests : PostgreSQLTransformationProviderTestBase +{ + [Test] + public void GetColumnContentSize_UseStringColumn_MaxContentLengthIsCorrect() + { + // Arrange + const string testTableName = "testtable"; + const string stringColumnName = "stringcolumn"; + + Provider.AddTable(testTableName, + new Column(stringColumnName, DbType.String, 5000) + ); + + Provider.Insert(testTableName, [stringColumnName], [new string('A', 44)]); + Provider.Insert(testTableName, [stringColumnName], [new string('B', 444)]); + Provider.Insert(testTableName, [stringColumnName], [new string('C', 4444)]); + + // Act + var columnContentSize = Provider.GetColumnContentSize(testTableName, stringColumnName); + + // Assert + Assert.That(columnContentSize, Is.EqualTo(4444)); + } + + [Test] + public void GetColumnContentSize_UseOnNonStringColumn_ThrowsSpeakingException() + { + // Arrange + const string testTableName = "testtable"; + const string stringColumnName = "nonstringcolumn"; + + Provider.AddTable(testTableName, + new Column(stringColumnName, DbType.Int32) + ); + + // Act + var exception = Assert.Throws(() => Provider.GetColumnContentSize(testTableName, stringColumnName)); + + // Assert + Assert.That(exception.Message, Does.Contain("is not of type string")); + } +} diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs new file mode 100644 index 00000000..dd9e1e53 --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs @@ -0,0 +1,84 @@ +using System.Data; +using System.Linq; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.PostgreSQL.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProvider_GetColumnTypeTests : PostgreSQLTransformationProviderTestBase +{ + [Test] + public void GetColumns_DataTypeResolveSucceeds() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string dateTimeColumnName1 = "datetimecolumn1"; + const string dateTimeColumnName2 = "datetimecolumn2"; + const string decimalColumnName1 = "decimalcolumn"; + const string guidColumnName1 = "guidcolumn1"; + const string booleanColumnName1 = "booleancolumn1"; + const string int32ColumnName1 = "int32column1"; + const string int64ColumnName1 = "int64column1"; + const string stringColumnName1 = "stringcolumn1"; + const string stringColumnName2 = "stringcolumn2"; + const string binaryColumnName1 = "binarycolumn"; + const string doubleColumnName1 = "doublecolumn"; + const string intervalColumnName1 = "intervalcolumn"; + + // Should be extended by remaining types + Provider.AddTable(testTableName, + new Column(dateTimeColumnName1, DbType.DateTime), + new Column(dateTimeColumnName2, DbType.DateTime2), + new Column(decimalColumnName1, DbType.Decimal), + new Column(guidColumnName1, DbType.Guid), + new Column(booleanColumnName1, DbType.Boolean), + new Column(int32ColumnName1, DbType.Int32), + new Column(int64ColumnName1, DbType.Int64), + new Column(stringColumnName1, DbType.String), + new Column(stringColumnName2, DbType.String) { Size = 30 }, + new Column(binaryColumnName1, DbType.Binary), + new Column(doubleColumnName1, DbType.Double), + new Column(intervalColumnName1, MigratorDbType.Interval) + ); + + + // Act + var columns = Provider.GetColumns(testTableName); + + var dateTimeColumn1 = columns.Single(x => x.Name == dateTimeColumnName1); + var dateTimeColumn2 = columns.Single(x => x.Name == dateTimeColumnName2); + var decimalColumn1 = columns.Single(x => x.Name == decimalColumnName1); + var guidColumn1 = columns.Single(x => x.Name == guidColumnName1); + var booleanColumn1 = columns.Single(x => x.Name == booleanColumnName1); + var int32Column1 = columns.Single(x => x.Name == int32ColumnName1); + var int64column1 = columns.Single(x => x.Name == int64ColumnName1); + var stringColumn1 = columns.Single(x => x.Name == stringColumnName1); + var stringColumn2 = columns.Single(x => x.Name == stringColumnName2); + var binaryColumn1 = columns.Single(x => x.Name == binaryColumnName1); + var doubleColumn1 = columns.Single(x => x.Name == doubleColumnName1); + var intervalColumn1 = columns.Single(x => x.Name == intervalColumnName1); + + + // Assert + Assert.That(dateTimeColumn1.MigratorDbType, Is.EqualTo(MigratorDbType.DateTime)); + Assert.That(dateTimeColumn1.Precision, Is.EqualTo(3)); + Assert.That(dateTimeColumn2.MigratorDbType, Is.EqualTo(MigratorDbType.DateTime2)); + Assert.That(dateTimeColumn2.Precision, Is.EqualTo(6)); + Assert.That(decimalColumn1.MigratorDbType, Is.EqualTo(MigratorDbType.Decimal)); + Assert.That(decimalColumn1.Precision, Is.EqualTo(19)); + Assert.That(decimalColumn1.Scale, Is.EqualTo(5)); + Assert.That(guidColumn1.MigratorDbType, Is.EqualTo(MigratorDbType.Guid)); + Assert.That(booleanColumn1.MigratorDbType, Is.EqualTo(MigratorDbType.Boolean)); + Assert.That(int32Column1.MigratorDbType, Is.EqualTo(MigratorDbType.Int32)); + Assert.That(int64column1.MigratorDbType, Is.EqualTo(MigratorDbType.Int64)); + Assert.That(stringColumn1.MigratorDbType, Is.EqualTo(MigratorDbType.String)); + Assert.That(stringColumn2.MigratorDbType, Is.EqualTo(MigratorDbType.String)); + Assert.That(stringColumn2.Size, Is.EqualTo(30)); + Assert.That(binaryColumn1.MigratorDbType, Is.EqualTo(MigratorDbType.Binary)); + Assert.That(doubleColumn1.MigratorDbType, Is.EqualTo(MigratorDbType.Double)); + Assert.That(intervalColumn1.MigratorDbType, Is.EqualTo(MigratorDbType.Interval)); + } +} diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumns_DefaultValueTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumns_DefaultValueTests.cs new file mode 100644 index 00000000..20b1e2e1 --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumns_DefaultValueTests.cs @@ -0,0 +1,169 @@ +using System; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProvider_GetColumns_DefaultValuesTests : TransformationProviderBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginPostgreSQLTransactionAsync(); + } + + /// + /// More tests for GetColumns in + /// + [Test] + public void GetColumns_Postgres_DefaultValues_Succeeds() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string intervalColumnName1 = "intervalcolumn1"; + const string intervalColumnName2 = "intervalcolumn2"; + const string binaryColumnName1 = "binarycolumn1"; + + // Should be extended by remaining types + Provider.AddTable(testTableName, + new Column(intervalColumnName1, MigratorDbType.Interval, defaultValue: new TimeSpan(100000, 3, 4, 5, 666)), + new Column(intervalColumnName2, MigratorDbType.Interval, defaultValue: new TimeSpan(0, 0, 0, 0, 666)), + new Column(binaryColumnName1, DbType.Binary, defaultValue: new byte[] { 12, 32, 34 }) + ); + + // Act + var columns = Provider.GetColumns(testTableName); + + // Assert + var intervalColumn1 = columns.Single(x => x.Name == intervalColumnName1); + var intervalColumn2 = columns.Single(x => x.Name == intervalColumnName2); + var binarycolumn1 = columns.Single(x => x.Name.Equals(binaryColumnName1, StringComparison.OrdinalIgnoreCase)); + + Assert.That(intervalColumn1.DefaultValue, Is.EqualTo(new TimeSpan(100000, 3, 4, 5, 666))); + Assert.That(intervalColumn2.DefaultValue, Is.EqualTo(new TimeSpan(0, 0, 0, 0, 666))); + Assert.That(binarycolumn1.DefaultValue, Is.EqualTo(new byte[] { 12, 32, 34 })); + } + + [Test] + public void GetColumns_DefaultValues_Succeeds() + { + // Arrange + var dateTimeDefaultValue = new DateTime(2000, 1, 2, 3, 4, 5, DateTimeKind.Utc); + var dateTimeOffsetDefaultValue = new DateTimeOffset(2022, 2, 2, 3, 3, 4, 4, TimeSpan.FromHours(1)); + var guidDefaultValue = Guid.NewGuid(); + var decimalDefaultValue = 14.56565m; + + const string testTableName = "MyDefaultTestTable"; + + const string dateTimeColumnName1 = "datetimecolumn1"; + const string dateTimeColumnName2 = "datetimecolumn2"; + const string dateTimeOffsetColumnName1 = "datetimeoffset1"; + const string decimalColumnName1 = "decimalcolumn"; + const string guidColumnName1 = "guidcolumn1"; + const string booleanColumnName1 = "booleancolumn1"; + const string int32ColumnName1 = "int32column1"; + const string int64ColumnName1 = "int64column1"; + const string int64ColumnName2 = "int64column2"; + const string int64ColumnName3 = "int64column3"; + const string stringColumnName1 = "stringcolumn1"; + const string binaryColumnName1 = "binarycolumn1"; + const string doubleColumnName1 = "doublecolumn1"; + + // Should be extended by remaining types + Provider.AddTable(testTableName, + new Column(dateTimeColumnName1, DbType.DateTime, dateTimeDefaultValue), + new Column(dateTimeColumnName2, DbType.DateTime2, dateTimeDefaultValue), + new Column(dateTimeOffsetColumnName1, DbType.DateTimeOffset, dateTimeOffsetDefaultValue), + new Column(decimalColumnName1, DbType.Decimal, decimalDefaultValue), + new Column(guidColumnName1, DbType.Guid, guidDefaultValue), + + // other boolean default values are tested in another test + new Column(booleanColumnName1, DbType.Boolean, true), + + new Column(int32ColumnName1, DbType.Int32, defaultValue: 43), + new Column(int64ColumnName1, DbType.Int64, defaultValue: 88), + new Column(int64ColumnName2, DbType.Int64, defaultValue: 0), + // converted in postgre to ''0'::bigint' + new Column(int64ColumnName3, DbType.Int64, defaultValue: "0"), + new Column(stringColumnName1, DbType.String, defaultValue: "Hello"), + new Column(binaryColumnName1, DbType.Binary, defaultValue: new byte[] { 12, 32, 34 }), + new Column(doubleColumnName1, DbType.Double, defaultValue: 84.874596567) { Precision = 19, Scale = 10 } + ); + + // Act + var columns = Provider.GetColumns(testTableName); + + // Assert + var dateTimeColumn1 = columns.Single(x => x.Name.Equals(dateTimeColumnName1, StringComparison.OrdinalIgnoreCase)); + var dateTimeColumn2 = columns.Single(x => x.Name.Equals(dateTimeColumnName2, StringComparison.OrdinalIgnoreCase)); + var dateTimeOffsetColumn1 = columns.Single(x => x.Name.Equals(dateTimeOffsetColumnName1, StringComparison.OrdinalIgnoreCase)); + var decimalColumn1 = columns.Single(x => x.Name.Equals(decimalColumnName1, StringComparison.OrdinalIgnoreCase)); + var guidColumn1 = columns.Single(x => x.Name.Equals(guidColumnName1, StringComparison.OrdinalIgnoreCase)); + var booleanColumn1 = columns.Single(x => x.Name.Equals(booleanColumnName1, StringComparison.OrdinalIgnoreCase)); + var int32Column1 = columns.Single(x => x.Name.Equals(int32ColumnName1, StringComparison.OrdinalIgnoreCase)); + var int64Column1 = columns.Single(x => x.Name.Equals(int64ColumnName1, StringComparison.OrdinalIgnoreCase)); + var int64Column2 = columns.Single(x => x.Name.Equals(int64ColumnName2, StringComparison.OrdinalIgnoreCase)); + var stringColumn1 = columns.Single(x => x.Name.Equals(stringColumnName1, StringComparison.OrdinalIgnoreCase)); + var binarycolumn1 = columns.Single(x => x.Name.Equals(binaryColumnName1, StringComparison.OrdinalIgnoreCase)); + var doubleColumn1 = columns.Single(x => x.Name.Equals(doubleColumnName1, StringComparison.OrdinalIgnoreCase)); + + Assert.That(dateTimeColumn1.DefaultValue, Is.EqualTo(dateTimeDefaultValue)); + Assert.That(dateTimeColumn2.DefaultValue, Is.EqualTo(dateTimeDefaultValue)); + Assert.That(dateTimeOffsetColumn1.DefaultValue, Is.EqualTo(dateTimeOffsetDefaultValue)); + Assert.That(decimalColumn1.DefaultValue, Is.EqualTo(decimalDefaultValue)); + Assert.That(guidColumn1.DefaultValue, Is.EqualTo(guidDefaultValue)); + Assert.That(booleanColumn1.DefaultValue, Is.True); + Assert.That(int32Column1.DefaultValue, Is.EqualTo(43)); + Assert.That(int64Column1.DefaultValue, Is.EqualTo(88)); + Assert.That(stringColumn1.DefaultValue, Is.EqualTo("Hello")); + Assert.That(binarycolumn1.DefaultValue, Is.EqualTo(new byte[] { 12, 32, 34 })); + Assert.That(doubleColumn1.DefaultValue, Is.EqualTo(84.874596567)); + } + + // 1 will coerce to true on inserts but not for default values in Postgre SQL - same for 0 to false + // so we do not test it here + [TestCase("true", true)] + [TestCase("TRUE", true)] + [TestCase("t", true)] + [TestCase("T", true)] + [TestCase("yes", true)] + [TestCase("YES", true)] + [TestCase("y", true)] + [TestCase("Y", true)] + [TestCase("on", true)] + [TestCase("ON", true)] + [TestCase("false", false)] + [TestCase("FALSE", false)] + [TestCase("f", false)] + [TestCase("F", false)] + [TestCase("false", false)] + [TestCase("FALSE", false)] + [TestCase("n", false)] + [TestCase("N", false)] + [TestCase("off", false)] + [TestCase("OFF", false)] + public void GetColumns_DefaultValueBooleanValues_Succeeds(object inboundBooleanDefaultValue, bool outboundBooleanDefaultValue) + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string booleanColumnName1 = "booleancolumn1"; + + Provider.AddTable(testTableName, + new Column(booleanColumnName1, DbType.Boolean) { DefaultValue = inboundBooleanDefaultValue } + ); + + // Act + var columns = Provider.GetColumns(testTableName); + + // Assert + var booleanColumn1 = columns.Single(x => x.Name == booleanColumnName1); + + Assert.That(booleanColumn1.DefaultValue, Is.EqualTo(outboundBooleanDefaultValue)); + } +} diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumns_Tests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumns_Tests.cs new file mode 100644 index 00000000..fe18719f --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumns_Tests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProvider_GetColumns_Tests : Generic_GetColumnsTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginPostgreSQLTransactionAsync(); + } +} diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_PrimaryKeyExistsTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_PrimaryKeyExistsTests.cs new file mode 100644 index 00000000..48462699 --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_PrimaryKeyExistsTests.cs @@ -0,0 +1,17 @@ +using Migrator.Tests.Providers.PostgreSQL.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProvider_PrimaryKeyExistsTests : PostgreSQLTransformationProviderTestBase +{ + [Test] + public void CanAddPrimaryKey() + { + AddTable(); + AddPrimaryKey(); + Assert.That(Provider.PrimaryKeyExists("Test", "PK_Test"), Is.True); + } +} diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_PrimaryKeyWithIdentityTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_PrimaryKeyWithIdentityTests.cs new file mode 100644 index 00000000..4b17cb02 --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_PrimaryKeyWithIdentityTests.cs @@ -0,0 +1,46 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.PostgreSQL.Base; +using Npgsql; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProvider_PrimaryKeyWithIdentityTests : PostgreSQLTransformationProviderTestBase +{ + [Test] + public void AddTableWithPrimaryKeyIdentity_Succeeds() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string propertyName1 = "Color1"; + const string propertyName2 = "Color2"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32, ColumnProperty.PrimaryKeyWithIdentity), + new Column(propertyName2, DbType.Int32, ColumnProperty.Unsigned) + ); + + // Act + Provider.Insert(testTableName, [propertyName2], [1]); + Provider.Insert(testTableName, [propertyName2], [1]); + + // Assert + using (var command = Provider.GetCommand()) + { + using var reader = Provider.ExecuteQuery(command, $"SELECT max({propertyName1}) as max from {testTableName}"); + reader.Read(); + + var primaryKeyValue = reader.GetInt32(reader.GetOrdinal("max")); + Assert.That(primaryKeyValue, Is.EqualTo(2)); + } + + // Act II + var exception = Assert.Throws(() => Provider.Insert(testTableName, [propertyName1, propertyName2], [1, 888])); + + // Assert II + Assert.That(exception.SqlState, Is.EqualTo("428C9")); + } +} diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_ReservedWordsTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_ReservedWordsTests.cs new file mode 100644 index 00000000..efa156c5 --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_ReservedWordsTests.cs @@ -0,0 +1,35 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.PostgreSQL.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProvider_ReservedWordsTests : PostgreSQLTransformationProviderTestBase +{ + [Test] + public void AddIndex_IncludeColumnsWithReservedWord_Succeeds() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string propertyName1 = "Color1"; + const string propertyName2 = "Host"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32, ColumnProperty.PrimaryKeyWithIdentity), + new Column(propertyName2, DbType.Int32, ColumnProperty.Unsigned) + ); + + // Act/Assert + Provider.AddIndex(testTableName, new Index + { + Name = "IX_WMS_OTO_Sta_OT_Pri_OTOPos", + Unique = false, + Clustered = false, + KeyColumns = [propertyName1], + IncludeColumns = [propertyName2] + }); + } +} diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_TableExistsTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_TableExistsTests.cs new file mode 100644 index 00000000..fa53a604 --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_TableExistsTests.cs @@ -0,0 +1,42 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.PostgreSQL.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProvider_TableExistsTests : PostgreSQLTransformationProviderTestBase +{ + [Test] + public void TableExists_TableExists_Returns() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string propertyName1 = "Color1"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32) + ); + + // Act + var tableExists = Provider.TableExists(testTableName); + + // Assert + Assert.That(tableExists, Is.True); + } + + [Test] + public void TableExists_TableDoesNotExist_ReturnsFalse() + { + // Arrange + const string myTableName = "MyTable"; + + // Act + var tableExists = Provider.TableExists(myTableName); + + // Assert + Assert.That(tableExists, Is.False); + } +} diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_ViewExistsTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_ViewExistsTests.cs new file mode 100644 index 00000000..b34dbcc2 --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_ViewExistsTests.cs @@ -0,0 +1,45 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.PostgreSQL.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProvider_ViewExistsTests : PostgreSQLTransformationProviderTestBase +{ + [Test] + public void ViewExists_ViewExists_Returns() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string myViewName = "MyView"; + const string propertyName1 = "Color1"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32) + ); + + Provider.ExecuteNonQuery($"CREATE VIEW {myViewName} AS SELECT {propertyName1} FROM {testTableName}"); + + // Act + var viewExists = Provider.ViewExists(myViewName); + + // Assert + Assert.That(viewExists, Is.True); + } + + [Test] + public void ViewExists_ViewDoesNotExist_ReturnsFalse() + { + // Arrange + const string myViewName = "MyView"; + + // Act + var viewExists = Provider.ViewExists(myViewName); + + // Assert + Assert.That(viewExists, Is.False); + } +} diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgresSQLTransformationProvider_UpdateFromTableToTableTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgresSQLTransformationProvider_UpdateFromTableToTableTests.cs new file mode 100644 index 00000000..2b39e27f --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgresSQLTransformationProvider_UpdateFromTableToTableTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProvider_UpdateFromTableToTableTests : Generic_UpdateFromTableToTableTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginPostgreSQLTransactionAsync(); + } +} diff --git a/src/Migrator.Tests/Providers/PostgreSQLTransformationProviderTest.cs b/src/Migrator.Tests/Providers/PostgreSQLTransformationProviderTest.cs deleted file mode 100644 index c08caf13..00000000 --- a/src/Migrator.Tests/Providers/PostgreSQLTransformationProviderTest.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Configuration; -using Migrator.Providers.PostgreSQL; -using NUnit.Framework; - -namespace Migrator.Tests.Providers -{ - [TestFixture] - [Category("Postgre")] - public class PostgreSQLTransformationProviderTest : TransformationProviderConstraintBase - { - #region Setup/Teardown - - [SetUp] - public void SetUp() - { - string constr = ConfigurationManager.AppSettings["NpgsqlConnectionString"]; - if (constr == null) - throw new ArgumentNullException("ConnectionString", "No config file"); - - _provider = new PostgreSQLTransformationProvider(new PostgreSQLDialect(), constr, null, "default", null); - _provider.BeginTransaction(); - - AddDefaultTable(); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/SQLServer/Base/SQLServerTransformationProviderTestBase.cs b/src/Migrator.Tests/Providers/SQLServer/Base/SQLServerTransformationProviderTestBase.cs new file mode 100644 index 00000000..2c76169a --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLServer/Base/SQLServerTransformationProviderTestBase.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLServer.Base; + +[TestFixture] +[Category("SQLServer")] +public abstract class SQLServerTransformationProviderTestBase : TransformationProviderSimpleBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLServerTransactionAsync(); + + AddDefaultTable(); + } +} diff --git a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_AddIndexTests.cs b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_AddIndexTests.cs new file mode 100644 index 00000000..91fb83f2 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_AddIndexTests.cs @@ -0,0 +1,300 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Models.Indexes; +using DotNetProjects.Migrator.Providers.Models.Indexes.Enums; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; +using Index = DotNetProjects.Migrator.Framework.Index; + +namespace Migrator.Tests.Providers.SQLServer; + +[TestFixture] +[Category("SqlServer")] +public class SQLServerTransformationProvider_AddIndexTests : Generic_AddIndexTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLServerTransactionAsync(); + } + + [Test] + public void AddIndex_Unique_Success() + { + // Arrange + const string tableName = "TestTable"; + const string columnName = "TestColumn"; + const string columnName2 = "TestColumn2"; + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, new Column(columnName, DbType.Int32), new Column(columnName2, DbType.String)); + + // Act + Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [columnName], + Unique = true, + }); + + // Assert + Provider.Insert(tableName, [columnName, columnName2], [1, "Hello"]); + var sqlException = Assert.Throws(() => Provider.Insert(tableName, [columnName, columnName2], [1, "Some other string"])); + var index = Provider.GetIndexes(tableName).Single(); + + Assert.That(index.Unique, Is.True); + Assert.That(sqlException.Number, Is.EqualTo(2601)); + } + + [Test] + public void AddIndex_FilteredIndexGreaterOrEqualThanNumber_PartialIndexThrowsOnConditionMet() + { + // Arrange + const string tableName = "TestTable"; + const string columnName = "TestColumn"; + const string columnName2 = "TestColumn2"; + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, new Column(columnName, DbType.Int32), new Column(columnName2, DbType.String)); + + // Act + Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [columnName, columnName2], + Unique = true, + FilterItems = [ + new() { Filter = FilterType.GreaterThanOrEqualTo, ColumnName = columnName, Value = 100 }, + new() { Filter = FilterType.EqualTo, ColumnName = columnName2, Value = "Hello" }, + ] + }); + + // Assert + Provider.Insert(tableName, [columnName, columnName2], [1, "Hello"]); + // Unique but no exception is thrown since smaller than 100 + Provider.Insert(tableName, [columnName, columnName2], [1, "Hello"]); + + Provider.Insert(tableName, [columnName, columnName2], [100, "Hello"]); + var sqlException = Assert.Throws(() => Provider.Insert(tableName, [columnName, columnName2], [100, "Hello"])); + var index = Provider.GetIndexes(tableName).Single(); + + Assert.That(index.Unique, Is.True); + Assert.That(sqlException.Number, Is.EqualTo(2601)); + } + + [Test] + public void AddIndex_IncludeColumnsSingle_Success() + { + // Arrange + const string tableName = "TestTable"; + const string columnName = "TestColumn"; + const string columnName2 = "TestColumn2"; + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, new Column(columnName, DbType.Int32), new Column(columnName2, DbType.String)); + + // Act + Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [columnName], + Unique = true, + IncludeColumns = [columnName2] + }); + + // Assert + var index = Provider.GetIndexes(tableName).Single(); + + Assert.That(index.Unique, Is.True); + Assert.That(index.KeyColumns.Single, Is.EqualTo(columnName).IgnoreCase); + Assert.That(index.IncludeColumns.Single, Is.EqualTo(columnName2).IgnoreCase); + } + + [Test] + public void AddIndex_IncludeColumnsMultiple_Success() + { + // Arrange + const string tableName = "TestTable"; + const string columnName = "TestColumn"; + const string columnName2 = "TestColumn2"; + const string columnName3 = "TestColumn3"; + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, new Column(columnName, DbType.Int32), new Column(columnName2, DbType.String), new Column(columnName3, DbType.Boolean)); + + // Act + Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [columnName], + Unique = true, + IncludeColumns = [columnName2, columnName3] + }); + + // Assert + var index = Provider.GetIndexes(tableName).Single(); + + Assert.That(index.Unique, Is.True); + Assert.That(index.KeyColumns.Single, Is.EqualTo(columnName).IgnoreCase); + Assert.That(index.IncludeColumns, Is.EquivalentTo([columnName2, columnName3]) + .Using((x, y) => string.Compare(x, y, ignoreCase: true))); + } + + [Test] + public void AddIndex_FilteredIndexSingle_Success() + { + // Arrange + const string tableName = "TestTable"; + const string columnName1 = "TestColumn1"; + + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, + new Column(columnName1, DbType.Int16) + ); + + List filterItems = [ + new() { Filter = FilterType.EqualTo, ColumnName = columnName1, Value = 1 }, + ]; + + // Act + Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [columnName1], + Unique = true, + FilterItems = filterItems + }); + + // Assert + + var indexesFromDatabase = Provider.GetIndexes(table: tableName); + var filteredItemsFromDatabase = indexesFromDatabase.Single().FilterItems; + + // We cannot find out the exact DbType so we compare strings. + foreach (var filteredItemFromDatabase in filteredItemsFromDatabase) + { + var expected = filterItems.Single(x => x.ColumnName.Equals(filteredItemFromDatabase.ColumnName, StringComparison.OrdinalIgnoreCase)); + Assert.That(filteredItemFromDatabase.Filter, Is.EqualTo(expected.Filter)); + Assert.That(Convert.ToString(filteredItemFromDatabase.Value, CultureInfo.InvariantCulture), Is.EqualTo(Convert.ToString(expected.Value, CultureInfo.InvariantCulture))); + } + + Assert.That( + filteredItemsFromDatabase.Select(x => x.ColumnName.ToLowerInvariant()), + Is.EquivalentTo(filterItems.Select(x => x.ColumnName.ToLowerInvariant())) + ); + } + + /// + /// This test is located in the dedicated database type folder not in the base class since + /// cannot read filter items for Oracle. + /// + [Test] + public void AddIndex_FilteredIndexMiscellaneousFilterTypesAndDataTypes_Success() + { + // Arrange + const string tableName = "TestTable"; + const string columnName1 = "TestColumn1"; + const string columnName2 = "TestColumn2"; + const string columnName3 = "TestColumn3"; + const string columnName4 = "TestColumn4"; + const string columnName5 = "TestColumn5"; + const string columnName6 = "TestColumn6"; + const string columnName7 = "TestColumn7"; + const string columnName8 = "TestColumn8"; + const string columnName9 = "TestColumn9"; + const string columnName10 = "TestColumn10"; + const string columnName11 = "TestColumn11"; + const string columnName12 = "TestColumn12"; + const string columnName13 = "TestColumn13"; + + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, + new Column(columnName1, DbType.Int16), + new Column(columnName2, DbType.Int32), + new Column(columnName3, DbType.Int64), + new Column(columnName4, DbType.UInt16), + new Column(columnName5, DbType.UInt32), + new Column(columnName6, DbType.UInt64), + new Column(columnName7, DbType.String), + new Column(columnName8, DbType.Int32), + new Column(columnName9, DbType.Int32), + new Column(columnName10, DbType.Int32), + new Column(columnName11, DbType.Int32), + new Column(columnName12, DbType.Int32), + new Column(columnName13, DbType.Int32) + ); + + List filterItems = [ + new() { Filter = FilterType.EqualTo, ColumnName = columnName1, Value = 1 }, + new() { Filter = FilterType.GreaterThan, ColumnName = columnName2, Value = 2 }, + new() { Filter = FilterType.GreaterThanOrEqualTo, ColumnName = columnName3, Value = 2323 }, + new() { Filter = FilterType.NotEqualTo, ColumnName = columnName4, Value = 3434 }, + new() { Filter = FilterType.NotEqualTo, ColumnName = columnName5, Value = -3434 }, + new() { Filter = FilterType.SmallerThan, ColumnName = columnName6, Value = 3434345345 }, + new() { Filter = FilterType.NotEqualTo, ColumnName = columnName7, Value = "asdf" }, + new() { Filter = FilterType.EqualTo, ColumnName = columnName8, Value = 11 }, + new() { Filter = FilterType.GreaterThan, ColumnName = columnName9, Value = 22 }, + new() { Filter = FilterType.GreaterThanOrEqualTo, ColumnName = columnName10, Value = 33 }, + new() { Filter = FilterType.NotEqualTo, ColumnName = columnName11, Value = 44 }, + new() { Filter = FilterType.SmallerThan, ColumnName = columnName12, Value = 55 }, + new() { Filter = FilterType.SmallerThanOrEqualTo, ColumnName = columnName13, Value = 66 } + ]; + + // Act + var addIndexSql = Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [ + columnName1, + columnName2, + columnName3, + columnName4, + columnName5, + columnName6, + columnName7, + columnName8, + columnName9, + columnName10, + columnName11, + columnName12, + columnName13 + ], + Unique = true, + FilterItems = filterItems + }); + + // Assert + var indexesFromDatabase = Provider.GetIndexes(table: tableName); + var filteredItemsFromDatabase = indexesFromDatabase.Single().FilterItems; + + // We cannot find out the exact DbType so we compare strings. + foreach (var filteredItemFromDatabase in filteredItemsFromDatabase) + { + var expected = filterItems.Single(x => x.ColumnName.Equals(filteredItemFromDatabase.ColumnName, StringComparison.OrdinalIgnoreCase)); + Assert.That(filteredItemFromDatabase.Filter, Is.EqualTo(expected.Filter)); + Assert.That(Convert.ToString(filteredItemFromDatabase.Value, CultureInfo.InvariantCulture), Is.EqualTo(Convert.ToString(expected.Value, CultureInfo.InvariantCulture))); + } + + Assert.That( + filteredItemsFromDatabase.Select(x => x.ColumnName.ToLowerInvariant()), + Is.EquivalentTo(filterItems.Select(x => x.ColumnName.ToLowerInvariant())) + ); + + var expectedSql = @"CREATE UNIQUE NONCLUSTERED INDEX [TestIndexName] ON [TestTable] ([TestColumn1], [TestColumn2], [TestColumn3], [TestColumn4], [TestColumn5], [TestColumn6], [TestColumn7], [TestColumn8], [TestColumn9], [TestColumn10], [TestColumn11], [TestColumn12], [TestColumn13]) WHERE [TestColumn1] = 1 AND [TestColumn2] > 2 AND [TestColumn3] >= 2323 AND [TestColumn4] <> 3434 AND [TestColumn5] <> -3434 AND [TestColumn6] < 3434345345 AND [TestColumn7] <> 'asdf' AND [TestColumn8] = 11 AND [TestColumn9] > 22 AND [TestColumn10] >= 33 AND [TestColumn11] <> 44 AND [TestColumn12] < 55 AND [TestColumn13] <= 66"; + + Assert.That(addIndexSql, Is.EqualTo(expectedSql)); + } +} diff --git a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_AddPrimaryKeyTests.cs b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_AddPrimaryKeyTests.cs new file mode 100644 index 00000000..32f4b308 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_AddPrimaryKeyTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLServer; + +[TestFixture] +[Category("SqlServer")] +public class SQLServerTransformationProvider_AddPrimaryKeyTests : Generic_AddPrimaryTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLServerTransactionAsync(); + } +} diff --git a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_AddTableTests.cs b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_AddTableTests.cs new file mode 100644 index 00000000..1632e892 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_AddTableTests.cs @@ -0,0 +1,54 @@ +using System.Data; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLServer; + +[TestFixture] +[Category("SqlServer")] +public class SQLServerTransformationProvider_AddTableTests : Generic_AddTableTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLServerTransactionAsync(); + } + + [Test] + public void AddTableWithCompoundPrimaryKey() + { + Provider.AddTable("Test", + new Column("PersonId", DbType.Int32, ColumnProperty.PrimaryKey), + new Column("AddressId", DbType.Int32, ColumnProperty.PrimaryKey) + ); + + Assert.That(Provider.TableExists("Test"), Is.True, "Table doesn't exist"); + Assert.That(Provider.PrimaryKeyExists("Test", "PK_Test"), Is.True, "Constraint doesn't exist"); + } + + [Test] + public void AddTableDateTime() + { + var tableName = "Table1"; + var columnName = "Column1"; + + Provider.AddTable(tableName, new Column(columnName, DbType.DateTime, ColumnProperty.NotNull)); + var column = Provider.GetColumnByName(tableName, columnName); + + Assert.That(column.Type, Is.EqualTo(DbType.DateTime)); + } + + [Test] + public void AddTableDateTime2() + { + var tableName = "Table1"; + var columnName = "Column1"; + + Provider.AddTable(tableName, new Column(columnName, DbType.DateTime2, ColumnProperty.NotNull)); + var column = Provider.GetColumnByName(tableName, columnName); + + Assert.That(column.Type, Is.EqualTo(DbType.DateTime2)); + } +} diff --git a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_ChangeColumnTests.cs b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_ChangeColumnTests.cs new file mode 100644 index 00000000..754fad52 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_ChangeColumnTests.cs @@ -0,0 +1,56 @@ +using System.Data; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLServer; + +[TestFixture] +[Category("SqlServer")] +public class SQLServerTransformationProvider_ChangeColumnTests : Generic_ChangeColumnTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLServerTransactionAsync(); + } + + [Test] + public void ChangeColumn_DateTimeToDateTime2_Success() + { + // Arrange + const string tableName = "TestTable"; + const string columnName = "TestColumn"; + + Provider.AddTable(tableName, new Column(columnName, DbType.DateTime, ColumnProperty.NotNull)); + var columnBefore = Provider.GetColumnByName(tableName, columnName); + + // Act + Provider.ChangeColumn(tableName, new Column(columnName, DbType.DateTime2, ColumnProperty.NotNull)); + + // Assert + var columnAfter = Provider.GetColumnByName(tableName, columnName); + + Assert.That(columnBefore.Type == DbType.DateTime); + Assert.That(columnAfter.Type == DbType.DateTime2); + } + + [Test, Ignore("This issue is not yet fixed. See https://github.com/dotnetprojects/Migrator.NET/issues/132")] + public void ChangeColumn_WithUniqueThenReChangeToNonUnique_UniqueConstraintShouldBeRemoved() + { + // Arrange + const string tableName = "TestTable"; + const string columnName = "TestColumn"; + + Provider.AddTable(tableName, new Column(columnName, DbType.Int32, ColumnProperty.NotNull)); + + // Act + Provider.ChangeColumn(tableName, new Column(columnName, DbType.Int32, ColumnProperty.NotNull | ColumnProperty.Unique)); + Provider.ChangeColumn(tableName, new Column(columnName, DbType.Int32, ColumnProperty.NotNull)); + + // Assert + var indexes = Provider.GetIndexes(tableName); + Assert.That(indexes, Is.Empty); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_ConstraintExistsTests.cs b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_ConstraintExistsTests.cs new file mode 100644 index 00000000..5be60184 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_ConstraintExistsTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLServer; + +[TestFixture] +[Category("SqlServer")] +public class SQLServerTransformationProvider_ConstraintExistsTests : Generic_ConstraintExistsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLServerTransactionAsync(); + } +} diff --git a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_CopyDataFromTableToTableTests.cs b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_CopyDataFromTableToTableTests.cs new file mode 100644 index 00000000..dcab7bf4 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_CopyDataFromTableToTableTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLServer; + +[TestFixture] +[Category("SqlServer")] +public class SQLServerTransformationProvider_CopyDataFromTableToTableTests : Generic_CopyDataFromTableToTableBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLServerTransactionAsync(); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_DefaultValueTests.cs b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_DefaultValueTests.cs new file mode 100644 index 00000000..73caec5d --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_DefaultValueTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLServer; + +[TestFixture] +[Category("SqlServer")] +public class SQLServerTransformationProvider_DefaultValueTests : Generic_DefaultValueTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLServerTransactionAsync(); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_GetColumnsTests.cs b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_GetColumnsTests.cs new file mode 100644 index 00000000..19204913 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_GetColumnsTests.cs @@ -0,0 +1,47 @@ +using System.Data; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLServer; + +[TestFixture] +[Category("SqlServer")] +public class SQLServerTransformationProvider_GetColumnsTests : Generic_GetColumnsTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLServerTransactionAsync(); + } + + [Test] + public void GetColumns_GetIdentity_Succeeds() + { + // Arrange + var tableName1 = "Table1"; + var tableName2 = "Table2"; + var tableName3 = "Table3"; + var tableName4 = "Table4"; + var columnName1 = "ColumnName1"; + + Provider.ExecuteNonQuery($"CREATE TABLE {tableName1} ({columnName1} INT IDENTITY(1,1) PRIMARY KEY)"); + Provider.ExecuteNonQuery($"CREATE TABLE {tableName2} ({columnName1} INT PRIMARY KEY)"); + + Provider.AddTable(name: tableName3, new Column(columnName1, DbType.Int32, ColumnProperty.Identity | ColumnProperty.PrimaryKey)); + Provider.AddTable(name: tableName4, new Column(columnName1, DbType.Int32, ColumnProperty.PrimaryKey)); + + // Act + var columnTable1 = Provider.GetColumnByName(table: tableName1, column: columnName1); + var columnTable2 = Provider.GetColumnByName(table: tableName2, column: columnName1); + var columnTable3 = Provider.GetColumnByName(table: tableName3, column: columnName1); + var columnTable4 = Provider.GetColumnByName(table: tableName4, column: columnName1); + + // Assert + Assert.That(columnTable1.IsIdentity, Is.True); + Assert.That(columnTable2.IsIdentity, Is.False); + Assert.That(columnTable3.IsIdentity, Is.True); + Assert.That(columnTable4.IsIdentity, Is.False); + } +} diff --git a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_GetColumns_DefaultValues_Tests.cs b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_GetColumns_DefaultValues_Tests.cs new file mode 100644 index 00000000..03c60f2b --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_GetColumns_DefaultValues_Tests.cs @@ -0,0 +1,96 @@ +using System; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLServer; + +[TestFixture] +[Category("SqlServer")] +public class SQLServerTransformationProvider_GetColumns_DefaultValues_Tests : TransformationProviderBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLServerTransactionAsync(); + } + + [Test] + public void GetColumns_DefaultValues_Succeeds() + { + // Arrange + var dateTimeDefaultValue = new DateTime(2000, 1, 2, 3, 4, 5, DateTimeKind.Utc); + var guidDefaultValue = Guid.NewGuid(); + var decimalDefaultValue = 14.56565m; + + const string testTableName = "MyDefaultTestTable"; + + const string dateTimeColumnName1 = "datetimecolumn1"; + const string dateTimeColumnName2 = "datetimecolumn2"; + const string decimalColumnName1 = "decimalcolumn"; + const string guidColumnName1 = "guidcolumn1"; + const string booleanColumnName1 = "booleancolumn1"; + const string booleanColumnName2 = "booleancolumn2"; + const string int32ColumnName1 = "int32column1"; + const string int64ColumnName1 = "int64column1"; + const string int64ColumnName2 = "int64column2"; + const string stringColumnName1 = "stringcolumn1"; + const string binaryColumnName1 = "binarycolumn1"; + const string doubleColumnName1 = "doublecolumn1"; + const string byteColumnName1 = "byteColumn1"; + + // Should be extended by remaining types + Provider.AddTable(testTableName, + new Column(dateTimeColumnName1, DbType.DateTime, dateTimeDefaultValue), + new Column(dateTimeColumnName2, DbType.DateTime2, dateTimeDefaultValue), + new Column(decimalColumnName1, DbType.Decimal, decimalDefaultValue), + new Column(guidColumnName1, DbType.Guid, guidDefaultValue), + + // other boolean default values are tested in another test + new Column(booleanColumnName1, DbType.Boolean, true), + new Column(booleanColumnName2, DbType.Boolean, false), + + new Column(int32ColumnName1, DbType.Int32, defaultValue: 43), + new Column(int64ColumnName1, DbType.Int64, defaultValue: 88), + new Column(int64ColumnName2, DbType.Int64, defaultValue: 0), + new Column(stringColumnName1, DbType.String, defaultValue: "Hello"), + new Column(binaryColumnName1, DbType.Binary, defaultValue: new byte[] { 12, 32, 34 }), + new Column(doubleColumnName1, DbType.Double, defaultValue: 84.874596567) { Precision = 19, Scale = 10 }, + new Column(byteColumnName1, DbType.Byte, defaultValue: 233) + ); + + // Act + var columns = Provider.GetColumns(testTableName); + + // Assert + var dateTimeColumn1 = columns.Single(x => x.Name.Equals(dateTimeColumnName1, StringComparison.OrdinalIgnoreCase)); + var dateTimeColumn2 = columns.Single(x => x.Name.Equals(dateTimeColumnName2, StringComparison.OrdinalIgnoreCase)); + var decimalColumn1 = columns.Single(x => x.Name.Equals(decimalColumnName1, StringComparison.OrdinalIgnoreCase)); + var guidColumn1 = columns.Single(x => x.Name.Equals(guidColumnName1, StringComparison.OrdinalIgnoreCase)); + var booleanColumn1 = columns.Single(x => x.Name.Equals(booleanColumnName1, StringComparison.OrdinalIgnoreCase)); + var booleanColumn2 = columns.Single(x => x.Name.Equals(booleanColumnName2, StringComparison.OrdinalIgnoreCase)); + var int32Column1 = columns.Single(x => x.Name.Equals(int32ColumnName1, StringComparison.OrdinalIgnoreCase)); + var int64Column1 = columns.Single(x => x.Name.Equals(int64ColumnName1, StringComparison.OrdinalIgnoreCase)); + var int64Column2 = columns.Single(x => x.Name.Equals(int64ColumnName2, StringComparison.OrdinalIgnoreCase)); + var stringColumn1 = columns.Single(x => x.Name.Equals(stringColumnName1, StringComparison.OrdinalIgnoreCase)); + var binarycolumn1 = columns.Single(x => x.Name.Equals(binaryColumnName1, StringComparison.OrdinalIgnoreCase)); + var doubleColumn1 = columns.Single(x => x.Name.Equals(doubleColumnName1, StringComparison.OrdinalIgnoreCase)); + var byteColumn1 = columns.Single(x => x.Name.Equals(byteColumnName1, StringComparison.OrdinalIgnoreCase)); + + Assert.That(dateTimeColumn1.DefaultValue, Is.EqualTo(dateTimeDefaultValue)); + Assert.That(dateTimeColumn2.DefaultValue, Is.EqualTo(dateTimeDefaultValue)); + Assert.That(decimalColumn1.DefaultValue, Is.EqualTo(decimalDefaultValue)); + Assert.That(guidColumn1.DefaultValue, Is.EqualTo(guidDefaultValue)); + Assert.That(booleanColumn1.DefaultValue, Is.True); + Assert.That(booleanColumn2.DefaultValue, Is.False); + Assert.That(int32Column1.DefaultValue, Is.EqualTo(43)); + Assert.That(int64Column1.DefaultValue, Is.EqualTo(88)); + Assert.That(stringColumn1.DefaultValue, Is.EqualTo("Hello")); + Assert.That(binarycolumn1.DefaultValue, Is.EqualTo(new byte[] { 12, 32, 34 })); + Assert.That(doubleColumn1.DefaultValue, Is.EqualTo(84.874596567)); + Assert.That(byteColumn1.DefaultValue, Is.EqualTo(233)); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_NVARCHARnTests.cs b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_NVARCHARnTests.cs new file mode 100644 index 00000000..0cce1e4f --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_NVARCHARnTests.cs @@ -0,0 +1,49 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; +using Microsoft.Data.SqlClient; +using Migrator.Tests.Providers.SQLServer.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLServer; + +[TestFixture] +[Category("SqlServer")] +public class SqlServerTransformationProvider_NVARCHARnTests : SQLServerTransformationProviderTestBase +{ + [Test] + public void AddTableWithFixedLengthEqualTo4000Characters_ShouldCreateNVARCHAR4000() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string propertyName1 = "Color1"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.String, 4000) + ); + + var stringLength4001 = new string('A', 4001); + + // Act + var exception = Assert.Throws(() => Provider.Insert(testTableName, [propertyName1], [stringLength4001])); + + Assert.That(exception.Errors[0].Message, Does.Contain("String or binary data would be truncated")); + } + + [Test] + public void AddTableWithFixedLengthGreaterThan4000Characters_ShouldCreateNVARCHARMAX() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string propertyName1 = "Color1"; + + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.String, 4001) + ); + + var stringLength5000 = new string('A', 5000); + + // Act + Provider.Insert(testTableName, [propertyName1], [stringLength5000]); + } +} diff --git a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_TableExists.cs b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_TableExists.cs new file mode 100644 index 00000000..b9b59e27 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_TableExists.cs @@ -0,0 +1,60 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.SQLServer.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLServer; + +[TestFixture] +[Category("SqlServer")] +public class SQLServerTransformationProvider_TableExistsTests : SQLServerTransformationProviderTestBase +{ + [Test] + public void TableExists_WithSchemaNameTableExists_Returns() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string propertyName1 = "Color1"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32) + ); + + // Act + var tableExists = Provider.TableExists($"dbo.{testTableName}"); + + // Assert + Assert.That(tableExists, Is.True); + } + + [Test] + public void TableExists_NoSchemaNameTableExists_Returns() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string propertyName1 = "Color1"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32) + ); + + // Act + var tableExists = Provider.TableExists(testTableName); + + // Assert + Assert.That(tableExists, Is.True); + } + + [Test] + public void TableExists_TableDoesNotExist_ReturnsFalse() + { + // Arrange + const string myTableName = "MyTableName"; + + // Act + var tableExists = Provider.TableExists(myTableName); + + // Assert + Assert.That(tableExists, Is.False); + } +} diff --git a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_UpdateFromTableToTableTests.cs b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_UpdateFromTableToTableTests.cs new file mode 100644 index 00000000..7015e781 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_UpdateFromTableToTableTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLServer; + +[TestFixture] +[Category("SqlServer")] +public class SQLServerTransformationProvider_UpdateFromTableToTableTests : Generic_UpdateFromTableToTableTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLServerTransactionAsync(); + } +} diff --git a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_ViewExistsTests.cs b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_ViewExistsTests.cs new file mode 100644 index 00000000..de0784e0 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_ViewExistsTests.cs @@ -0,0 +1,66 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.SQLServer.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLServer; + +[TestFixture] +[Category("SqlServer")] +public class SQLServerTransformationProvider_ViewExistsTests : SQLServerTransformationProviderTestBase +{ + [Test] + public void ViewExists_WithSchemaNameViewExists_Returns() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string myViewName = "MyView"; + const string propertyName1 = "Color1"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32) + ); + + Provider.ExecuteNonQuery($"CREATE VIEW dbo.{myViewName} AS SELECT {propertyName1} FROM dbo.{testTableName}"); + + // Act + var viewExists = Provider.ViewExists($"dbo.{myViewName}"); + + // Assert + Assert.That(viewExists, Is.True); + } + + [Test] + public void ViewExists_NoSchemaNameViewExists_Returns() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string myViewName = "MyView"; + const string propertyName1 = "Color1"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32) + ); + + Provider.ExecuteNonQuery($"CREATE VIEW dbo.{myViewName} AS SELECT {propertyName1} FROM dbo.{testTableName}"); + + // Act + var viewExists = Provider.ViewExists(myViewName); + + // Assert + Assert.That(viewExists, Is.True); + } + + [Test] + public void ViewExists_ViewDoesNotExist_ReturnsFalse() + { + // Arrange + const string myViewName = "MyView"; + + // Act + var viewExists = Provider.ViewExists(myViewName); + + // Assert + Assert.That(viewExists, Is.False); + } +} diff --git a/src/Migrator.Tests/Providers/SQLServer/SqlServerTransformationProviderGenericTests.cs b/src/Migrator.Tests/Providers/SQLServer/SqlServerTransformationProviderGenericTests.cs new file mode 100644 index 00000000..f89967d6 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLServer/SqlServerTransformationProviderGenericTests.cs @@ -0,0 +1,64 @@ +using System.Data; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Providers; +using DotNetProjects.Migrator.Providers.Impl.SqlServer; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLServer; + +[TestFixture] +[Category("SqlServer")] +public class SqlServerTransformationProviderGenericTests : TransformationProviderGenericMiscConstraintBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLServerTransactionAsync(); + + AddDefaultTable(); + } + + [Test] + public void ByteColumnWillBeCreatedAsBlob() + { + Provider.AddColumn("TestTwo", "BlobColumn", DbType.Byte); + Assert.That(Provider.ColumnExists("TestTwo", "BlobColumn"), Is.True); + } + + [Test] + public void InstanceForProvider() + { + var localProv = Provider["sqlserver"]; + Assert.That(localProv is SqlServerTransformationProvider, Is.True); + + var localProv2 = Provider["foo"]; + Assert.That(localProv2 is NoOpTransformationProvider, Is.True); + } + + [Test] + public void QuoteCreatesProperFormat() + { + var dialect = new SqlServerDialect(); + + Assert.That("[foo]", Is.EqualTo(dialect.Quote("foo"))); + } + + [Test] + public void TableExistsShouldWorkWithBracketsAndSchemaNameAndTableName() + { + Assert.That(Provider.TableExists("[dbo].[TestTwo]"), Is.True); + } + + [Test] + public void TableExistsShouldWorkWithSchemaNameAndTableName() + { + Assert.That(Provider.TableExists("dbo.TestTwo"), Is.True); + } + + [Test] + public void TableExistsShouldWorkWithTableNamesWithBracket() + { + Assert.That(Provider.TableExists("[TestTwo]"), Is.True); + } +} diff --git a/src/Migrator.Tests/Providers/SQLServer/SqlServerTransformationProviderTests.cs b/src/Migrator.Tests/Providers/SQLServer/SqlServerTransformationProviderTests.cs new file mode 100644 index 00000000..b9b03fd9 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLServer/SqlServerTransformationProviderTests.cs @@ -0,0 +1,55 @@ +using System.Data; +using DotNetProjects.Migrator.Providers; +using DotNetProjects.Migrator.Providers.Impl.SqlServer; +using Migrator.Tests.Providers.SQLServer.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLServer; + +[TestFixture] +[Category("SqlServer")] +public class SqlServerTransformationProviderTests : SQLServerTransformationProviderTestBase +{ + [Test] + public void ByteColumnWillBeCreatedAsBlob() + { + Provider.AddColumn("TestTwo", "BlobColumn", DbType.Byte); + Assert.That(Provider.ColumnExists("TestTwo", "BlobColumn"), Is.True); + } + + [Test] + public void InstanceForProvider() + { + var localProv = Provider["sqlserver"]; + Assert.That(localProv is SqlServerTransformationProvider, Is.True); + + var localProv2 = Provider["foo"]; + Assert.That(localProv2 is NoOpTransformationProvider, Is.True); + } + + [Test] + public void QuoteCreatesProperFormat() + { + var dialect = new SqlServerDialect(); + + Assert.That("[foo]", Is.EqualTo(dialect.Quote("foo"))); + } + + [Test] + public void TableExistsShouldWorkWithBracketsAndSchemaNameAndTableName() + { + Assert.That(Provider.TableExists("[dbo].[TestTwo]"), Is.True); + } + + [Test] + public void TableExistsShouldWorkWithSchemaNameAndTableName() + { + Assert.That(Provider.TableExists("dbo.TestTwo"), Is.True); + } + + [Test] + public void TableExistsShouldWorkWithTableNamesWithBracket() + { + Assert.That(Provider.TableExists("[TestTwo]"), Is.True); + } +} diff --git a/src/Migrator.Tests/Providers/SQLite/Base/SQLiteTransformationProviderTestBase.cs b/src/Migrator.Tests/Providers/SQLite/Base/SQLiteTransformationProviderTestBase.cs new file mode 100644 index 00000000..995b0c28 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/Base/SQLiteTransformationProviderTestBase.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite.Base; + +[TestFixture] +[Category("SQLite")] +public abstract class SQLiteTransformationProviderTestBase : TransformationProviderSimpleBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLiteTransactionAsync(); + AddDefaultTable(); + } +} diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteReader/SQLiteReaderTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteReader/SQLiteReaderTests.cs new file mode 100644 index 00000000..e9eaa2ac --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteReader/SQLiteReaderTests.cs @@ -0,0 +1,24 @@ +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite.SQLiteReader; + +[TestFixture] +[Category("SQLite")] +public class SQLiteReaderTests +{ + private SQLiteCreateTableScriptReader _sqliteCreateTableScriptReader = new SQLiteCreateTableScriptReader(); + + [Test] + public void GetParenthesisContent() + { + // Arrange + var testScript = "CREATE TABLE \"TestTwo\" (Id INTEGER NOT NULL PRIMARY KEY, TestId INTEGER NULL, CONSTRAINT FKName FOREIGN KEY (TestId) REFERENCES Test(IdNew))"; + + // Act + var parenthesisContent = _sqliteCreateTableScriptReader.GetParenthesisContent(testScript); + + // Assert + Assert.That(parenthesisContent, Is.EqualTo("Id INTEGER NOT NULL PRIMARY KEY, TestId INTEGER NULL, CONSTRAINT FKName FOREIGN KEY (TestId) REFERENCES Test(IdNew)")); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProviderGenericTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProviderGenericTests.cs new file mode 100644 index 00000000..2a7ba7ba --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProviderGenericTests.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProviderGenericTests : TransformationProviderGenericMiscConstraintBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLiteTransactionAsync(); + + AddDefaultTable(); + } +} diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProviderTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProviderTests.cs new file mode 100644 index 00000000..3d26a7c4 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProviderTests.cs @@ -0,0 +1,220 @@ +using System.Data; +using System.Linq; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using Migrator.Tests.Providers.SQLite.Base; +using NUnit.Framework; +using NUnit.Framework.Legacy; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProviderTests : SQLiteTransformationProviderTestBase +{ + [Test] + public void GetTables() + { + var tables = Provider.GetTables(); + + Assert.That("TestTwo", Is.EqualTo(tables.Single())); + } + + [Test] + public void CanParseColumnDefForNotNull() + { + const string nullString = "bar TEXT"; + const string notNullString = "baz INTEGER NOT NULL"; + + Assert.That(((SQLiteTransformationProvider)Provider).IsNullable(nullString), Is.True); + Assert.That(((SQLiteTransformationProvider)Provider).IsNullable(notNullString), Is.False); + } + + [Test] + public void RemoveDefaultValue_Succeeds() + { + // Arrange + var testTableName = "MyDefaultTestTable"; + var columnName = "Bla"; + + Provider.AddTable(testTableName, new Column(columnName, DbType.Int32, (object)55)); + var tableInfoBefore = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + var createScriptBefore = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(testTableName); + + // Act + Provider.RemoveColumnDefaultValue(testTableName, columnName); + + // Assert + var tableInfoAfter = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + var createScriptAfter = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(testTableName); + var tableNames = ((SQLiteTransformationProvider)Provider).GetTables(); + + Assert.That(tableInfoBefore.Columns.Single().DefaultValue, Is.EqualTo(55)); + Assert.That(tableInfoAfter.Columns.Single().DefaultValue, Is.Null); + Assert.That(createScriptBefore, Does.Contain("DEFAULT 55")); + Assert.That(createScriptAfter, Does.Not.Contain("DEFAULT")); + + // Check for intermediate table residues. + Assert.That(tableNames.Where(x => x.Contains(testTableName)), Has.Exactly(1).Items); + } + + [Test] + public void AddPrimaryKey_CompositePrimaryKey_Succeeds() + { + // Arrange + var testTableName = "MyDefaultTestTable"; + + Provider.AddTable(testTableName, + new Column("Id", DbType.Int32), + new Column("Color", DbType.Int32), + new Column("NotAPrimaryKey", DbType.Int32) + ); + + var tableInfoBefore = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + + // Act + Provider.AddPrimaryKey("MyPrimaryKeyName", testTableName, "Id", "Color"); + + // Assert + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == "Id").ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.False); + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == "Color").ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.False); + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == "NotAPrimaryKey").ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.False); + + var tableInfoAfter = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + var tableNames = ((SQLiteTransformationProvider)Provider).GetTables(); + + Assert.That(tableInfoAfter.Columns.Single(x => x.Name == "Id").ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.True); + Assert.That(tableInfoAfter.Columns.Single(x => x.Name == "Color").ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.True); + Assert.That(tableInfoAfter.Columns.Single(x => x.Name == "NotAPrimaryKey").ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.False); + + // Check for intermediate table residues. + Assert.That(tableNames.Where(x => x.Contains(testTableName)), Has.Exactly(1).Items); + } + + [Test] + public void AddPrimaryKey_HavingColumnPropertyUniqueAndIndex_RebuildSucceeds() + { + // Arrange + var testTableName = "MyDefaultTestTable"; + var propertyName1 = "Color1"; + var propertyName2 = "Color2"; + var indexName = "MyIndexName"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32, ColumnProperty.Unique | ColumnProperty.NotNull), + new Column(propertyName2, DbType.Int32) + ); + + Provider.AddIndex(indexName, testTableName, [propertyName1, propertyName2]); + var tableInfoBefore = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + + Provider.ExecuteNonQuery($"INSERT INTO {testTableName} ({propertyName1}, {propertyName2}) VALUES (1, 2)"); + + // Act + ((SQLiteTransformationProvider)Provider).AddPrimaryKey("MyPrimaryKeyName", testTableName, [propertyName1]); + + // Assert + using var command = Provider.GetCommand(); + using var reader = Provider.ExecuteQuery(command, $"SELECT COUNT(*) as Count from {testTableName}"); + reader.Read(); + var count = reader.GetInt32(reader.GetOrdinal("Count")); + Assert.That(count, Is.EqualTo(1)); + + var tableInfoAfter = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == propertyName1).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.False); + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == propertyName2).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.False); + + Assert.That(tableInfoAfter.Columns.Single(x => x.Name == propertyName1).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.True); + Assert.That(tableInfoAfter.Columns.Single(x => x.Name == propertyName2).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.False); + + var indexAfter = tableInfoAfter.Indexes.Single(); + Assert.That(indexAfter.Name, Is.EqualTo(indexName)); + CollectionAssert.AreEquivalent(indexAfter.KeyColumns, new string[] { propertyName1, propertyName2 }); + } + + [Test] + public void RemovePrimaryKey_HavingColumnPropertyUniqueAndIndex_RebuildSucceeds() + { + // Arrange + var testTableName = "MyDefaultTestTable"; + var propertyName1 = "Color1"; + var propertyName2 = "Color2"; + var indexName = "MyIndexName"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(propertyName2, DbType.Int32, ColumnProperty.Unique) + ); + + Provider.AddIndex(indexName, testTableName, [propertyName1, propertyName2]); + var tableInfoBefore = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + + Provider.ExecuteNonQuery($"INSERT INTO {testTableName} ({propertyName1}, {propertyName2}) VALUES (1, 2)"); + + // Act + ((SQLiteTransformationProvider)Provider).RemovePrimaryKey(tableName: testTableName); + + // Assert + using var command = Provider.GetCommand(); + using var reader = Provider.ExecuteQuery(command, $"SELECT COUNT(*) as Count from {testTableName}"); + reader.Read(); + var count = reader.GetInt32(reader.GetOrdinal("Count")); + Assert.That(count, Is.EqualTo(1)); + + var tableInfoAfter = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == propertyName1).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.True); + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == propertyName2).ColumnProperty.HasFlag(ColumnProperty.Unique), Is.True); + + Assert.That(tableInfoAfter.Columns.Single(x => x.Name == propertyName1).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.False); + Assert.That(tableInfoAfter.Columns.Single(x => x.Name == propertyName2).ColumnProperty.HasFlag(ColumnProperty.Unique), Is.True); + + var indexAfter = tableInfoAfter.Indexes.Single(); + Assert.That(indexAfter.Name, Is.EqualTo(indexName)); + CollectionAssert.AreEquivalent(indexAfter.KeyColumns, new string[] { propertyName1, propertyName2 }); + } + + [Test] + public void RemoveAllIndexes_HavingIndexAndUnique_RebuildSucceeds() + { + // Arrange + var testTableName = "MyDefaultTestTable"; + var propertyName1 = "Color1"; + var propertyName2 = "Color2"; + var indexName = "MyIndexName"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(propertyName2, DbType.Int32) + ); + + Provider.AddIndex(indexName, testTableName, [propertyName1, propertyName2]); + Provider.AddUniqueConstraint("MyConstraint", testTableName, [propertyName1, propertyName2]); + var tableInfoBefore = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + + Provider.AddUniqueConstraint("MyUniqueConstraintName", testTableName, [propertyName1, propertyName2]); + + Provider.ExecuteNonQuery($"INSERT INTO {testTableName} ({propertyName1}, {propertyName2}) VALUES (1, 2)"); + + // Act + ((SQLiteTransformationProvider)Provider).RemoveAllIndexes(tableName: testTableName); + + // Assert + using var command = Provider.GetCommand(); + using var reader = Provider.ExecuteQuery(command, $"SELECT COUNT(*) as Count from {testTableName}"); + reader.Read(); + var count = reader.GetInt32(reader.GetOrdinal("Count")); + Assert.That(count, Is.EqualTo(1)); + + var tableInfoAfter = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == propertyName1).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.True); + Assert.That(tableInfoAfter.Columns.Single(x => x.Name == propertyName1).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.True); + + Assert.That(tableInfoBefore.Uniques, Is.Not.Empty); + Assert.That(tableInfoBefore.Indexes, Is.Not.Empty); + Assert.That(tableInfoAfter.Uniques, Is.Empty); + Assert.That(tableInfoAfter.Indexes, Is.Empty); + } +} diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddColumnTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddColumnTests.cs new file mode 100644 index 00000000..4b9c6453 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddColumnTests.cs @@ -0,0 +1,117 @@ +using System.Data; +using System.Linq; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using Migrator.Tests.Providers.SQLite.Base; +using NUnit.Framework; +using NUnit.Framework.Legacy; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_AddColumnTests : SQLiteTransformationProviderTestBase +{ + /// + /// We use a NULL column as new column here. NOT NULL will fail as expected. The user should handle that on his own. + /// + [Test] + public void AddColumn_HavingColumnPropertyUniqueAndIndex_RebuildSucceeds() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string propertyName1 = "Color1"; + const string propertyName2 = "Color2"; + const string newColumn = "NewColumn"; + const string indexName = "MyIndexName"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(propertyName2, DbType.Int32, ColumnProperty.Unique) + ); + + Provider.AddIndex(indexName, testTableName, [propertyName1, propertyName2]); + var tableInfoBefore = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + + Provider.ExecuteNonQuery($"INSERT INTO {testTableName} ({propertyName1}, {propertyName2}) VALUES (1, 2)"); + + // Act + Provider.AddColumn(table: testTableName, new Column(newColumn, DbType.String, ColumnProperty.Null)); + Provider.ExecuteNonQuery($"INSERT INTO {testTableName} ({propertyName1}, {propertyName2}, {newColumn}) VALUES (2, 3, 'Hello')"); + + // Assert + using var command = Provider.GetCommand(); + using var reader = Provider.ExecuteQuery(command, $"SELECT COUNT(*) as Count from {testTableName}"); + reader.Read(); + var count = reader.GetInt32(reader.GetOrdinal("Count")); + Assert.That(count, Is.EqualTo(2)); + + var tableInfoAfter = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == propertyName1).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.True); + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == propertyName2).ColumnProperty.HasFlag(ColumnProperty.Unique), Is.True); + + Assert.That(tableInfoAfter.Columns.Single(x => x.Name == propertyName1).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.True); + Assert.That(tableInfoAfter.Columns.Single(x => x.Name == propertyName2).ColumnProperty.HasFlag(ColumnProperty.Unique), Is.True); + + var indexAfter = tableInfoAfter.Indexes.Single(); + Assert.That(indexAfter.Name, Is.EqualTo(indexName)); + CollectionAssert.AreEquivalent(indexAfter.KeyColumns, new string[] { propertyName1, propertyName2 }); + } + + /// + /// NOT NULL is implicitly set by the migrator for non-composite primary key + /// + [Test] + public void AddColumn_HavingNullInPrimaryKey_HasNotNullAfterAddAnotherColumn() + { + // Arrange/Act + Provider.ExecuteNonQuery("CREATE TABLE Common_Language (LanguageID TEXT PRIMARY KEY)"); + + Provider.AddColumn("Common_Language", "Enabled", DbType.Boolean); + + var tableInfo = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo("Common_Language"); + var script = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript("Common_Language"); + + var columnProperty = tableInfo.Columns.Single(x => x.Name == "LanguageID").ColumnProperty; + + // Assert + Assert.That(script, Does.Contain("LanguageID TEXT NOT NULL PRIMARY KEY")); + } + + [Test] + public void AddColumn_HavingNullInPrimaryKey_HasNOTNULLAfterAddAnotherColumn() + { + // Arrange/Act + Provider.ExecuteNonQuery("CREATE TABLE Common_Language (LanguageID TEXT NOT NULL PRIMARY KEY)"); + + Provider.AddColumn("Common_Language", "Enabled", DbType.Boolean); + + var tableInfo = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo("Common_Language"); + var script = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript("Common_Language"); + + var columnProperty = tableInfo.Columns.Single(x => x.Name == "LanguageID").ColumnProperty; + + // Assert + Assert.That(script, Does.Contain("LanguageID TEXT NOT NULL PRIMARY KEY")); + } + + [Test] + public void AddColumn_HavingNotNullInPrimaryKey_Succeds() + { + // Arrange/Act + Provider.ExecuteNonQuery("CREATE TABLE Common_Language (LanguageID INT NOT NULL PRIMARY KEY)"); + + Provider.AddColumn("Common_Language", "Enabled", DbType.Boolean); + + var tableInfo = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo("Common_Language"); + var script = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript("Common_Language"); + + var columnProperty = tableInfo.Columns.Single(x => x.Name == "LanguageID").ColumnProperty; + var hasNull = columnProperty.IsSet(ColumnProperty.Null); + + // Assert + Assert.That(script, Does.Contain("LanguageID INTEGER NOT NULL PRIMARY KEY")); + Assert.That(hasNull, Is.False); + } +} diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddForeignKeyTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddForeignKeyTests.cs new file mode 100644 index 00000000..ad2f3f6c --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddForeignKeyTests.cs @@ -0,0 +1,92 @@ +using System.Data; +using System.Linq; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using Migrator.Tests.Providers.SQLite.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_AddForeignKeyTests : SQLiteTransformationProviderTestBase +{ + [Test] + public void AddForeignKey() + { + // Arrange + AddTableWithPrimaryKey(); + Provider.ExecuteNonQuery("INSERT INTO Test (Id, name) VALUES (1, 'my name')"); + Provider.ExecuteNonQuery("INSERT INTO TestTwo (TestId) VALUES (1)"); + + // Act + Provider.AddForeignKey(name: "FKName", childTable: "TestTwo", childColumn: "TestId", parentTable: "Test", parentColumn: "Id", constraint: ForeignKeyConstraintType.Cascade); + + // Assert + var foreignKeyConstraints = ((SQLiteTransformationProvider)Provider).GetForeignKeyConstraints("TestTwo"); + var tableSQLCreateScript = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript("TestTwo"); + + Assert.That(foreignKeyConstraints.Single().Name, Is.EqualTo("FKName")); + Assert.That(foreignKeyConstraints.Single().ChildTable, Is.EqualTo("TestTwo")); + Assert.That(foreignKeyConstraints.Single().ParentTable, Is.EqualTo("Test")); + Assert.That(foreignKeyConstraints.Single().ChildColumns.Single(), Is.EqualTo("TestId")); + Assert.That(foreignKeyConstraints.Single().ParentColumns.Single(), Is.EqualTo("Id")); + + // Cascade is not supported in this migrator see https://github.com/dotnetprojects/Migrator.NET/issues/33 + // TODO add cascade tests as soon as it is supported. + + Assert.That(tableSQLCreateScript, Does.Contain("CREATE TABLE \"TestTwo\"")); + Assert.That(tableSQLCreateScript, Does.Contain(", CONSTRAINT FKName FOREIGN KEY (TestId) REFERENCES Test(Id))")); + + var result = ((SQLiteTransformationProvider)Provider).CheckForeignKeyIntegrity(); + Assert.That(result, Is.True); + } + + [Test] + public void AddForeignKey_RenameParentColumWithForeignKeyAndData_ForeignKeyPointsToRenamedColumn() + { + // Arrange + AddTableWithPrimaryKey(); + Provider.ExecuteNonQuery("INSERT INTO Test (Id, name) VALUES (1, 'my name')"); + Provider.ExecuteNonQuery("INSERT INTO TestTwo (TestId) VALUES (1)"); + + // Act + Provider.AddForeignKey(name: "FKName", childTable: "TestTwo", childColumn: "TestId", parentTable: "Test", parentColumn: "Id", constraint: ForeignKeyConstraintType.Cascade); + + // Rename column in parent + Provider.RenameColumn("Test", "Id", "IdNew"); + + // Assert + var foreignKeyConstraints = ((SQLiteTransformationProvider)Provider).GetForeignKeyConstraints("TestTwo"); + var tableSQLCreateScript = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript("TestTwo"); + + Assert.That(tableSQLCreateScript, Does.Contain("CREATE TABLE \"TestTwo\"")); + Assert.That(tableSQLCreateScript, Does.Contain(", CONSTRAINT FKName FOREIGN KEY (TestId) REFERENCES Test(IdNew))")); + Assert.That(foreignKeyConstraints.Single().ParentColumns.Single(), Is.EqualTo("IdNew")); + + var result = ((SQLiteTransformationProvider)Provider).CheckForeignKeyIntegrity(); + Assert.That(result, Is.True); + } + + [Test] + public void AddForeignKey_3_Success() + { + Provider.AddTable("Task", + new Column(name: "BinId", type: DbType.Int32, property: ColumnProperty.NotNull), + new Column(name: "CreationTimeStamp", type: DbType.DateTime2, property: ColumnProperty.NotNull), + new Column(name: "EstimatedPickTime", type: DbType.Int32, property: ColumnProperty.Null), + new Column(name: "Id", type: DbType.Int32, property: ColumnProperty.NotNull), + new Column(name: "Item", type: DbType.Int32, property: ColumnProperty.Null), + new Column(name: "Order", type: DbType.Int32, property: ColumnProperty.Null), + new Column(name: "TaskGroupId", type: DbType.Int32, property: ColumnProperty.Null) + ); + + Provider.AddTable("TaskGroup", + new Column(name: "CreationTimeStamp", type: DbType.DateTime2, property: ColumnProperty.NotNull), + new Column(name: "Id", type: DbType.Int32) + ); + + // TODO CK add more columns. + Provider.AddForeignKey(name: "FK_Task_TaskGroup", childTable: "Task", childColumn: "TaskGroupId", parentTable: "TaskGroup", parentColumn: "Id"); + } +} diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddIndexTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddIndexTests.cs new file mode 100644 index 00000000..868a17b2 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddIndexTests.cs @@ -0,0 +1,269 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.SQLite; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Models.Indexes; +using DotNetProjects.Migrator.Providers.Models.Indexes.Enums; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; +using Index = DotNetProjects.Migrator.Framework.Index; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_AddIndexTests : Generic_AddIndexTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLiteTransactionAsync(); + } + + [Test] + public void AddIndex_Unique_Success() + { + // Arrange + const string columnName1 = "TestColumn"; + const string columnName2 = "TestColumn2"; + const string indexName = "TestIndexName"; + const string tableName = "TestTable"; + + Provider.AddTable(tableName, new Column(columnName1, DbType.Int32), new Column(columnName2, DbType.String)); + + // Act + Provider.AddIndex(tableName, + new Index + { + KeyColumns = [columnName1], + Name = indexName, + Unique = true, + }); + + // Assert + Provider.Insert(tableName, [columnName1, columnName2], [1, "Hello"]); + var ex = Assert.Throws(() => Provider.Insert(tableName, [columnName1, columnName2], [1, "Some other string"])); + var index = Provider.GetIndexes(tableName).Single(); + + Assert.That(index.Unique, Is.True); + + // Unique violation + Assert.That(ex.ErrorCode, Is.EqualTo(19)); + } + + [Test] + public void AddIndex_FilteredIndexGreaterOrEqualThanNumber_Success() + { + // Arrange + const string columnName1 = "TestColumn"; + const string columnName2 = "TestColumn2"; + const string columnName3 = "TestColumn3"; + const string columnName4 = "TestColumn4"; + const string indexName = "TestIndexName"; + const string tableName = "TestTable"; + + Provider.AddTable(tableName, + new Column(columnName1, DbType.Int32), + new Column(columnName2, DbType.String), + new Column(columnName3, DbType.Boolean), + new Column(columnName4, DbType.Int32) + ); + + // Act + Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [columnName1, columnName2, columnName3], + Unique = true, + FilterItems = [ + new() { Filter = FilterType.GreaterThanOrEqualTo, ColumnName = columnName1, Value = 100 }, + new() { Filter = FilterType.EqualTo, ColumnName = columnName2, Value = "Hello" }, + new() { Filter = FilterType.EqualTo, ColumnName = columnName3, Value = true }, + ] + }); + + // We remove column to invoke a recreation of the table. + Provider.RemoveColumn(tableName, columnName4); + + // Assert + Provider.Insert(tableName, [columnName1, columnName2, columnName3], [1, "Hello", true]); + // Unique but no exception should be thrown since the integer value is smaller than 100 - not within the filter restriction. + Provider.Insert(tableName, [columnName1, columnName2, columnName3], [1, "Hello", true]); + + Provider.Insert(tableName, [columnName1, columnName2, columnName3], [100, "Hello", true]); + var sqliteException = Assert.Throws(() => Provider.Insert(tableName, [columnName1, columnName2, columnName3], [100, "Hello", true])); + var index = Provider.GetIndexes(tableName).Single(); + + Assert.That(index.Unique, Is.True); + // Unique violation + Assert.That(sqliteException.ErrorCode, Is.EqualTo(19)); + + var indexScriptFromDatabase = GetCreateIndexSqlString(indexName); + + Assert.That(indexScriptFromDatabase, Is.EqualTo("CREATE UNIQUE INDEX TestIndexName ON TestTable (TestColumn, TestColumn2, TestColumn3) WHERE TestColumn >= 100 AND TestColumn2 = 'Hello' AND TestColumn3 = 1")); + } + + [Test] + public void AddIndex_FilteredIndexSingle_Success() + { + // Arrange + const string tableName = "TestTable"; + const string columnName1 = "TestColumn1"; + + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, + new Column(columnName1, DbType.Int16) + ); + + List filterItems = [ + new() { Filter = FilterType.EqualTo, ColumnName = columnName1, Value = 1 }, + ]; + + // Act + Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [columnName1], + Unique = true, + FilterItems = filterItems + }); + + // Assert + + var indexesFromDatabase = Provider.GetIndexes(table: tableName); + var filteredItemsFromDatabase = indexesFromDatabase.Single().FilterItems; + + // We cannot find out the exact DbType so we compare strings. + foreach (var filteredItemFromDatabase in filteredItemsFromDatabase) + { + var expected = filterItems.Single(x => x.ColumnName.Equals(filteredItemFromDatabase.ColumnName, StringComparison.OrdinalIgnoreCase)); + Assert.That(filteredItemFromDatabase.Filter, Is.EqualTo(expected.Filter)); + Assert.That(Convert.ToString(filteredItemFromDatabase.Value, CultureInfo.InvariantCulture), Is.EqualTo(Convert.ToString(expected.Value, CultureInfo.InvariantCulture))); + } + + Assert.That( + filteredItemsFromDatabase.Select(x => x.ColumnName.ToLowerInvariant()), + Is.EquivalentTo(filterItems.Select(x => x.ColumnName.ToLowerInvariant())) + ); + } + + /// + /// This test is located in the dedicated database type folder not in the base class since + /// cannot read filter items for Oracle. + /// + [Test] + public void AddIndex_FilteredIndexMiscellaneousFilterTypesAndDataTypes_Success() + { + // Arrange + const string tableName = "TestTable"; + const string columnName1 = "TestColumn1"; + const string columnName2 = "TestColumn2"; + const string columnName3 = "TestColumn3"; + const string columnName4 = "TestColumn4"; + const string columnName5 = "TestColumn5"; + const string columnName6 = "TestColumn6"; + const string columnName7 = "TestColumn7"; + const string columnName8 = "TestColumn8"; + const string columnName9 = "TestColumn9"; + const string columnName10 = "TestColumn10"; + const string columnName11 = "TestColumn11"; + const string columnName12 = "TestColumn12"; + const string columnName13 = "TestColumn13"; + + const string indexName = "TestIndexName"; + + Provider.AddTable(tableName, + new Column(columnName1, DbType.Int16), + new Column(columnName2, DbType.Int32), + new Column(columnName3, DbType.Int64), + new Column(columnName4, DbType.UInt16), + new Column(columnName5, DbType.UInt32), + new Column(columnName6, DbType.UInt64), + new Column(columnName7, DbType.String), + new Column(columnName8, DbType.Int32), + new Column(columnName9, DbType.Int32), + new Column(columnName10, DbType.Int32), + new Column(columnName11, DbType.Int32), + new Column(columnName12, DbType.Int32), + new Column(columnName13, DbType.Int32) + ); + + List filterItems = [ + new() { Filter = FilterType.EqualTo, ColumnName = columnName1, Value = 1 }, + new() { Filter = FilterType.GreaterThan, ColumnName = columnName2, Value = 2 }, + new() { Filter = FilterType.GreaterThanOrEqualTo, ColumnName = columnName3, Value = 2323 }, + new() { Filter = FilterType.NotEqualTo, ColumnName = columnName4, Value = 3434 }, + new() { Filter = FilterType.NotEqualTo, ColumnName = columnName5, Value = -3434 }, + new() { Filter = FilterType.SmallerThan, ColumnName = columnName6, Value = 3434345345 }, + new() { Filter = FilterType.NotEqualTo, ColumnName = columnName7, Value = "asdf" }, + new() { Filter = FilterType.EqualTo, ColumnName = columnName8, Value = 11 }, + new() { Filter = FilterType.GreaterThan, ColumnName = columnName9, Value = 22 }, + new() { Filter = FilterType.GreaterThanOrEqualTo, ColumnName = columnName10, Value = 33 }, + new() { Filter = FilterType.NotEqualTo, ColumnName = columnName11, Value = 44 }, + new() { Filter = FilterType.SmallerThan, ColumnName = columnName12, Value = 55 }, + new() { Filter = FilterType.SmallerThanOrEqualTo, ColumnName = columnName13, Value = 66 } + ]; + + // Act + var addIndexSql = Provider.AddIndex(tableName, + new Index + { + Name = indexName, + KeyColumns = [ + columnName1, + columnName2, + columnName3, + columnName4, + columnName5, + columnName6, + columnName7, + columnName8, + columnName9, + columnName10, + columnName11, + columnName12, + columnName13 + ], + Unique = true, + FilterItems = filterItems + }); + + // Assert + + var indexesFromDatabase = Provider.GetIndexes(table: tableName); + var filteredItemsFromDatabase = indexesFromDatabase.Single().FilterItems; + + // We cannot find out the exact DbType so we compare strings. + foreach (var filteredItemFromDatabase in filteredItemsFromDatabase) + { + var expected = filterItems.Single(x => x.ColumnName.Equals(filteredItemFromDatabase.ColumnName, StringComparison.OrdinalIgnoreCase)); + Assert.That(filteredItemFromDatabase.Filter, Is.EqualTo(expected.Filter)); + Assert.That(Convert.ToString(filteredItemFromDatabase.Value, CultureInfo.InvariantCulture), Is.EqualTo(Convert.ToString(expected.Value, CultureInfo.InvariantCulture))); + } + + Assert.That( + filteredItemsFromDatabase.Select(x => x.ColumnName.ToLowerInvariant()), + Is.EquivalentTo(filterItems.Select(x => x.ColumnName.ToLowerInvariant())) + ); + + var expectedSql = "CREATE UNIQUE INDEX TestIndexName ON TestTable (TestColumn1, TestColumn2, TestColumn3, TestColumn4, TestColumn5, TestColumn6, TestColumn7, TestColumn8, TestColumn9, TestColumn10, TestColumn11, TestColumn12, TestColumn13) WHERE TestColumn1 = 1 AND TestColumn2 > 2 AND TestColumn3 >= 2323 AND TestColumn4 <> 3434 AND TestColumn5 <> -3434 AND TestColumn6 < 3434345345 AND TestColumn7 <> 'asdf' AND TestColumn8 = 11 AND TestColumn9 > 22 AND TestColumn10 >= 33 AND TestColumn11 <> 44 AND TestColumn12 < 55 AND TestColumn13 <= 66"; + + Assert.That(addIndexSql, Is.EqualTo(expectedSql)); + } + + private string GetCreateIndexSqlString(string indexName) + { + using var cmd = Provider.CreateCommand(); + using var reader = Provider.ExecuteQuery(cmd, $"SELECT sql FROM sqlite_master WHERE type='index' AND lower(name)=lower('{indexName}')"); + reader.Read(); + + return reader.IsDBNull(0) ? null : (string)reader[0]; + } +} diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddPrimaryKeyTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddPrimaryKeyTests.cs new file mode 100644 index 00000000..23c217e8 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddPrimaryKeyTests.cs @@ -0,0 +1,96 @@ +using System; +using System.Data; +using System.Data.SQLite; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_AddPrimaryTests : Generic_AddPrimaryTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLiteTransactionAsync(); + } + + [Test] + public void AddPrimaryKey_ColumnsInOtherOrderThanInColumnsList_Success() + { + // Arrange + const string columnName1 = "TestColumn"; + const string columnName2 = "TestColumn2"; + const string columnName3 = "TestColumn3"; + const string tableName = "TestTable"; + const string primaryKeyName = $"PK_{tableName}"; + + Provider.AddTable(tableName, + new Column(columnName1, DbType.String), + new Column(columnName2, DbType.Int32), + new Column(columnName3, DbType.Int32)); + + // Act + Provider.AddPrimaryKey(name: primaryKeyName, table: tableName, columns: [columnName3, columnName2]); + + // Assert + var createTableScript = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(tableName); + + Assert.That(createTableScript, Does.Contain("PRIMARY KEY (TestColumn3, TestColumn2))")); + } + + [Test] + public void AddPrimaryKey_ColumnGuidNonComposite_ThrowsOnDuplicatesAndNulls() + { + const string tableName = "MyTableName"; + const string columnName1 = "Column1"; + var guid = Guid.NewGuid(); + + // Arrange/Act + Provider.AddTable(tableName, + new Column(columnName1, DbType.Guid, ColumnProperty.PrimaryKey) + ); + + Provider.Insert(tableName, [columnName1], [guid]); + Assert.Throws(() => Provider.Insert(tableName, [columnName1], [guid])); + Assert.Throws(() => Provider.Insert(tableName, [columnName1], [null])); + } + + [Test] + public void AddPrimaryKey_ColumnGuidComposite_ThrowsOnDuplicatesAndNulls() + { + // Arrange + const string columnName1 = "TestColumn1"; + const string columnName2 = "TestColumn2"; + const string tableName = "TestTable"; + const string primaryKeyName = $"PK_{tableName}"; + var guid = Guid.NewGuid(); + var guid2 = Guid.NewGuid(); + + Provider.AddTable(tableName, + new Column(columnName1, DbType.Guid), + new Column(columnName2, DbType.Guid)); + + // Act + Provider.AddPrimaryKey(name: primaryKeyName, table: tableName, columns: [columnName1, columnName2]); + + // This is a normal SQLite behavior! + // NULL != NULL + // (A, NULL) != (A, NULL) + // Duplicates! You need to set NotNull if you want to prevent it! + Provider.Insert(tableName, [columnName1, columnName2], [guid, null]); + + Provider.Insert(tableName, [columnName1, columnName2], [guid, null]); + Provider.Insert(tableName, [columnName1, columnName2], [guid, null]); + + Provider.Insert(tableName, [columnName1, columnName2], [null, guid]); + Provider.Insert(tableName, [columnName1, columnName2], [null, guid]); + + Provider.Insert(tableName, [columnName1, columnName2], [guid2, guid2]); + Assert.Throws(() => Provider.Insert(tableName, [columnName1, columnName2], [guid2, guid2])); + } +} diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddTableTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddTableTests.cs new file mode 100644 index 00000000..1cc48749 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddTableTests.cs @@ -0,0 +1,182 @@ +using System; +using System.Data.SQLite; +using System.Linq; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_AddTableTests : Generic_AddTableTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLiteTransactionAsync(); + } + + [Test] + public void AddTable_UniqueOnlyOnColumnLevel_Obsolete_UniquesListIsEmpty() + { + const string tableName = "MyTableName"; + const string columnName = "MyColumnName"; + + // Arrange/Act + Provider.AddTable(tableName, new Column(columnName, System.Data.DbType.Int32, ColumnProperty.Unique)); + + // Assert + var createScript = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(tableName); + Assert.That(createScript, Is.EqualTo("CREATE TABLE MyTableName (MyColumnName INTEGER NULL UNIQUE)")); + + var sqliteInfo = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(tableName); + + // It is no named unique so it is not listed in the Uniques list. Unique on column level is marked as obsolete. + Assert.That(sqliteInfo.Uniques, Is.Empty); + } + + [Test] + public void AddTable_CompositePrimaryKey_ContainsNull() + { + const string tableName = "MyTableName"; + const string columnName1 = "Column1"; + const string columnName2 = "Column2"; + + // Arrange/Act + Provider.AddTable(tableName, + new Column(columnName1, System.Data.DbType.Int32, ColumnProperty.PrimaryKey), + new Column(columnName2, System.Data.DbType.Int32, ColumnProperty.PrimaryKey | ColumnProperty.NotNull) + ); + + Provider.Insert(tableName, [columnName1, columnName2], [1, 1]); + var ex = Assert.Throws(() => Provider.Insert(tableName, [columnName1, columnName2], [1, 1])); + + // Assert + var createScript = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(tableName); + Assert.That(createScript, Is.EqualTo("CREATE TABLE MyTableName (Column1 INTEGER NULL, Column2 INTEGER NOT NULL, PRIMARY KEY (Column1, Column2))")); + + var pragmaTableInfos = ((SQLiteTransformationProvider)Provider).GetPragmaTableInfoItems(tableName); + Assert.That(pragmaTableInfos.Single(x => x.Name == columnName1).NotNull, Is.False); + Assert.That(pragmaTableInfos.Single(x => x.Name == columnName2).NotNull, Is.True); + + var sqliteInfo = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(tableName); + Assert.That(sqliteInfo.Columns.First().Name, Is.EqualTo(columnName1)); + Assert.That(sqliteInfo.Columns[1].Name, Is.EqualTo(columnName2)); + + // 19 = UNIQUE constraint failed + Assert.That(ex.ErrorCode, Is.EqualTo(19)); + } + + [Test] + public void AddTable_SinglePrimaryKey_ContainsNull() + { + const string tableName = "MyTableName"; + const string columnName1 = "Column1"; + const string columnName2 = "Column2"; + + // Arrange/Act + Provider.AddTable(tableName, + new Column(columnName1, System.Data.DbType.Int32, ColumnProperty.PrimaryKey), + new Column(columnName2, System.Data.DbType.Int32, ColumnProperty.NotNull) + ); + + Provider.Insert(tableName, [columnName1, columnName2], [1, 1]); + Assert.Throws(() => Provider.Insert(tableName, [columnName1, columnName2], [1, 2])); + + // Assert + var createScript = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(tableName); + + // In SQLite an INTEGER PRIMARY KEY column is NOT NULL implicitly (see insert asserts above) + Assert.That(createScript, Is.EqualTo("CREATE TABLE MyTableName (Column1 INTEGER NOT NULL PRIMARY KEY, Column2 INTEGER NOT NULL)")); + + var sqliteInfo = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(tableName); + Assert.That(sqliteInfo.Columns.First().Name, Is.EqualTo(columnName1)); + Assert.That(sqliteInfo.Columns[1].Name, Is.EqualTo(columnName2)); + } + + [Test] + public void AddTable_MiscellaneousColumns_Succeeds() + { + const string tableName = "MyTableName"; + const string columnName1 = "Column1"; + const string columnName2 = "Column2"; + + // Arrange/Act + Provider.AddTable(tableName, + new Column(columnName1, System.Data.DbType.Int32, ColumnProperty.NotNull | ColumnProperty.Identity | ColumnProperty.PrimaryKey), + new Column(columnName2, System.Data.DbType.Int32, ColumnProperty.Null | ColumnProperty.Unique) + ); + + Provider.Insert(tableName, [columnName1, columnName2], [1, 1]); + Assert.Throws(() => Provider.Insert(tableName, [columnName1, columnName2], [1, 1])); + + // Assert + var createScript = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(tableName); + Assert.That(createScript, Is.EqualTo("CREATE TABLE MyTableName (Column1 INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, Column2 INTEGER NULL UNIQUE)")); + + var pragmaTableInfos = ((SQLiteTransformationProvider)Provider).GetPragmaTableInfoItems(tableName); + Assert.That(pragmaTableInfos.First().NotNull, Is.True); + Assert.That(pragmaTableInfos[1].NotNull, Is.False); + + var sqliteInfo = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(tableName); + Assert.That(sqliteInfo.Columns.First().Name, Is.EqualTo(columnName1)); + Assert.That(sqliteInfo.Columns[1].Name, Is.EqualTo(columnName2)); + } + + /// + /// NOT NULL is implicitly set by SQLite + /// + [Test] + public void AddTable_GuidPrimaryKeyOneColumnPKImplicitlyUsingNotNull_ThrowsOnNullAndOnDuplicates() + { + const string tableName = "MyTableName"; + const string columnName1 = "Column1"; + var guid = Guid.NewGuid(); + + // Arrange/Act + Provider.AddTable(tableName, + new Column(columnName1, System.Data.DbType.Guid, ColumnProperty.PrimaryKey) + ); + + Provider.Insert(tableName, [columnName1], [guid]); + Assert.Throws(() => Provider.Insert(tableName, [columnName1], [guid])); + + // The migrator sets NotNull on PrimaryKey (non composite) so this line throws. + Assert.Throws(() => Provider.Insert(tableName, [columnName1], [null])); + } + + /// + /// Composite PK with Guids + /// + [Test] + public void AddTable_GuidPrimaryKeyCompositeWithGuid_DoesNotThrowOnDuplicateNULLEntries() + { + const string tableName = "MyTableName"; + const string columnName1 = "Column1"; + const string columnName2 = "Column2"; + var guid = Guid.NewGuid(); + var guid2 = Guid.NewGuid(); + + // Arrange/Act + Provider.AddTable(tableName, + new Column(columnName1, System.Data.DbType.Guid, ColumnProperty.PrimaryKey), + new Column(columnName2, System.Data.DbType.Guid, ColumnProperty.PrimaryKey) + ); + + // This is a normal SQLite behavior! + // NULL != NULL + // (A, NULL) != (A, NULL) + // Duplicates! You need to set NotNull if you want to prevent it! + Provider.Insert(tableName, [columnName1, columnName2], [guid, null]); + Provider.Insert(tableName, [columnName1, columnName2], [guid, null]); + + Provider.Insert(tableName, [columnName1, columnName2], [null, guid]); + Provider.Insert(tableName, [columnName1, columnName2], [null, guid]); + + Provider.Insert(tableName, [columnName1, columnName2], [guid2, guid2]); + Assert.Throws(() => Provider.Insert(tableName, [columnName1, columnName2], [guid2, guid2])); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_ChangeColumnTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_ChangeColumnTests.cs new file mode 100644 index 00000000..6819f858 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_ChangeColumnTests.cs @@ -0,0 +1,93 @@ +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; +using NUnit.Framework.Legacy; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_ChangeColumnTests : Generic_ChangeColumnTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLiteTransactionAsync(); + } + + [Test] + public void ChangeColumn_HavingColumnPropertyUniqueAndIndex_RebuildSucceeds() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string propertyName1 = "Color1"; + const string propertyName2 = "Color2"; + const string indexName = "MyIndexName"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(propertyName2, DbType.Int32, ColumnProperty.NotNull) + ); + + Provider.AddIndex(indexName, testTableName, [propertyName1, propertyName2]); + var tableInfoBefore = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + + Provider.ExecuteNonQuery($"INSERT INTO {testTableName} ({propertyName1}, {propertyName2}) VALUES (1, 2)"); + + // Act + Provider.ChangeColumn(table: testTableName, new Column(propertyName2, DbType.String, ColumnProperty.Unique | ColumnProperty.Null)); + Provider.ExecuteNonQuery($"INSERT INTO {testTableName} ({propertyName1}, {propertyName2}) VALUES (2, 3)"); + + // Assert + var createScriptAfter = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(testTableName); + Assert.That(createScriptAfter, Does.Contain("Color2 TEXT NULL UNIQUE")); + + using var command = Provider.GetCommand(); + using var reader = Provider.ExecuteQuery(command, $"SELECT COUNT(*) as Count from {testTableName}"); + reader.Read(); + var count = reader.GetInt32(reader.GetOrdinal("Count")); + Assert.That(count, Is.EqualTo(2)); + + var tableInfoAfter = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == propertyName1).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.True); + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == propertyName2).ColumnProperty.HasFlag(ColumnProperty.Unique), Is.False); + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == propertyName2).ColumnProperty.HasFlag(ColumnProperty.NotNull), Is.True); + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == propertyName2).ColumnProperty.HasFlag(ColumnProperty.Null), Is.False); + + Assert.That(tableInfoAfter.Columns.Single(x => x.Name == propertyName1).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.True); + Assert.That(tableInfoAfter.Columns.Single(x => x.Name == propertyName2).ColumnProperty.HasFlag(ColumnProperty.Unique), Is.True); + Assert.That(tableInfoAfter.Columns.Single(x => x.Name == propertyName2).ColumnProperty.HasFlag(ColumnProperty.NotNull), Is.False); + Assert.That(tableInfoAfter.Columns.Single(x => x.Name == propertyName2).ColumnProperty.HasFlag(ColumnProperty.Null), Is.True); + + var indexAfter = tableInfoAfter.Indexes.Single(); + Assert.That(indexAfter.Name, Is.EqualTo(indexName)); + CollectionAssert.AreEquivalent(indexAfter.KeyColumns, new string[] { propertyName1, propertyName2 }); + } + + [Test] + public void ChangeColumn_StringFromNullToNotNull_StillNotNull() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string propertyName1 = "Color1"; + const string propertyName2 = "Color2"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(propertyName2, DbType.String, 100, ColumnProperty.Null) + ); + + // Act + Provider.ChangeColumn(table: testTableName, new Column(propertyName2, DbType.String, ColumnProperty.NotNull)); + + + // Assert + var createScriptAfter = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(testTableName); + Assert.That(createScriptAfter, Does.Contain("Color2 TEXT NOT NULL")); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_CheckForeignKeyIntegrityTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_CheckForeignKeyIntegrityTests.cs new file mode 100644 index 00000000..e1c6b5fb --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_CheckForeignKeyIntegrityTests.cs @@ -0,0 +1,43 @@ +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using Migrator.Tests.Providers.SQLite.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_CheckForeignKeyIntegrityTests : SQLiteTransformationProviderTestBase +{ + [Test] + public void CheckForeignKeyIntegrity_IntegrityViolated_ReturnsFalse() + { + // Arrange + AddTableWithPrimaryKey(); + Provider.ExecuteNonQuery("INSERT INTO Test (Id, name) VALUES (1, 'my name')"); + Provider.ExecuteNonQuery("INSERT INTO TestTwo (TestId) VALUES (44444)"); + Provider.AddForeignKey(name: "FKName", childTable: "TestTwo", childColumn: "TestId", parentTable: "Test", parentColumn: "Id", constraint: ForeignKeyConstraintType.Cascade); + + // Act + var result = ((SQLiteTransformationProvider)Provider).CheckForeignKeyIntegrity(); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public void CheckForeignKeyIntegrity_IntegrityOk_ReturnsTrue() + { + // Arrange + AddTableWithPrimaryKey(); + Provider.ExecuteNonQuery("INSERT INTO Test (Id, name) VALUES (1, 'my name')"); + Provider.ExecuteNonQuery("INSERT INTO TestTwo (TestId) VALUES (1)"); + Provider.AddForeignKey(name: "FKName", childTable: "TestTwo", childColumn: "TestId", parentTable: "Test", parentColumn: "Id", constraint: ForeignKeyConstraintType.Cascade); + + // Act + var result = ((SQLiteTransformationProvider)Provider).CheckForeignKeyIntegrity(); + + // Assert + Assert.That(result, Is.True); + } +} diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_ConstraintExistsTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_ConstraintExistsTests.cs new file mode 100644 index 00000000..a53f6f8e --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_ConstraintExistsTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.SQLite.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_ConstraintExistsTests : SQLiteTransformationProviderTestBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLiteTransactionAsync(); + } +} diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_CopyDataFromTableToTableTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_CopyDataFromTableToTableTests.cs new file mode 100644 index 00000000..87647cfb --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_CopyDataFromTableToTableTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_CopyDataFromTableToTableTests : Generic_CopyDataFromTableToTableBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLiteTransactionAsync(); + } +} diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_DefaultValueTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_DefaultValueTests.cs new file mode 100644 index 00000000..e6bac437 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_DefaultValueTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_DefaultValueTests : Generic_AddIndexTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLiteTransactionAsync(); + } +} diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetCheckConstraintsTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetCheckConstraintsTests.cs new file mode 100644 index 00000000..c9e20eca --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetCheckConstraintsTests.cs @@ -0,0 +1,44 @@ +using System.Data.SQLite; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using Migrator.Tests.Providers.SQLite.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_GetCheckConstraintsTests : SQLiteTransformationProviderTestBase +{ + [Test] + public void GetCheckConstraints_AddCheckConstraintsViaAddTable_CreatesTableCorrectly() + { + const string tableName = "MyTableName"; + const string columnName = "MyColumnName"; + const string checkConstraint1 = "MyCheckConstraint1"; + const string checkConstraint2 = "MyCheckConstraint2"; + + // Arrange/Act + Provider.AddTable(tableName, + new Column(columnName, System.Data.DbType.Int32), + new CheckConstraint(checkConstraint1, $"{columnName} > 10"), + new CheckConstraint(checkConstraint2, $"{columnName} < 100") + ); + + var checkConstraints = ((SQLiteTransformationProvider)Provider).GetCheckConstraints(tableName); + + // Assert + Assert.That(checkConstraints[0].Name, Is.EqualTo(checkConstraint1)); + Assert.That(checkConstraints[0].CheckConstraintString, Is.EqualTo($"{columnName} > 10")); + + Assert.That(checkConstraints[1].Name, Is.EqualTo(checkConstraint2)); + Assert.That(checkConstraints[1].CheckConstraintString, Is.EqualTo($"{columnName} < 100")); + + Provider.Insert(tableName, [columnName], [11]); + Assert.Throws(() => Provider.Insert(tableName, [columnName], [1])); + Assert.Throws(() => Provider.Insert(tableName, [columnName], [200])); + + var createScript = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(tableName); + Assert.That(createScript, Is.EqualTo("CREATE TABLE MyTableName (MyColumnName INTEGER NULL, CONSTRAINT MyCheckConstraint1 CHECK (MyColumnName > 10), CONSTRAINT MyCheckConstraint2 CHECK (MyColumnName < 100))")); + } +} diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetColumnsTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetColumnsTests.cs new file mode 100644 index 00000000..9b7c576a --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetColumnsTests.cs @@ -0,0 +1,125 @@ +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_GetColumnsTests : Generic_GetColumnsTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLiteTransactionAsync(); + } + + [Test] + public void GetColumns_UniqueButNotPrimaryKey_ReturnsFalse() + { + // Arrange + const string tableName = "GetColumnsTest"; + Provider.AddTable(tableName, new Column("Id", DbType.Int32, ColumnProperty.Unique)); + + // Act + var columns = Provider.GetColumns(tableName); + + // Assert + Assert.That(columns.Single().ColumnProperty, Is.EqualTo(ColumnProperty.Null | ColumnProperty.Unique)); + } + + [Test] + public void GetColumns_PrimaryAndUnique_ReturnsFalse() + { + // Arrange + const string tableName = "GetColumnsTest"; + Provider.AddTable(tableName, new Column("Id", DbType.Int32, ColumnProperty.Unique | ColumnProperty.PrimaryKey)); + + // Act + var columns = Provider.GetColumns(tableName); + + // Assert + Assert.That(columns.Single().ColumnProperty, Is.EqualTo( + ColumnProperty.NotNull | + ColumnProperty.Identity | + ColumnProperty.Unique | + ColumnProperty.PrimaryKey)); + } + + [Test] + public void GetColumns_Primary_ColumnPropertyOk() + { + // Arrange + const string tableName = "GetColumnsTest"; + Provider.AddTable(tableName, new Column("Id", DbType.Int32, ColumnProperty.PrimaryKey)); + Provider.GetColumns(tableName); + + // Act + var columns = Provider.GetColumns(tableName); + + // Assert + Assert.That(columns.Single().ColumnProperty, Is.EqualTo(ColumnProperty.NotNull | + ColumnProperty.PrimaryKeyWithIdentity)); + } + + [Test] + public void GetColumns_PrimaryKeyOnTwoColumns_BothColumnsHavePrimaryKeyAndAreNotNull() + { + // Arrange + const string tableName = "GetColumnsTest"; + + Provider.AddTable(tableName, + new Column("Id", DbType.Int32, ColumnProperty.PrimaryKey), + new Column("Id2", DbType.Int32, ColumnProperty.PrimaryKey) + ); + + // Act + var columns = Provider.GetColumns(tableName); + + // Assert + Assert.That(columns[0].ColumnProperty, Is.EqualTo(ColumnProperty.PrimaryKey | ColumnProperty.NotNull)); + Assert.That(columns[1].ColumnProperty, Is.EqualTo(ColumnProperty.PrimaryKey | ColumnProperty.NotNull)); + } + + [Test] + public void GetColumns_AddUniqueConstraintWithTwoColumns_NoUniqueOnColumnLevel() + { + // Arrange + const string tableName = "GetColumnsTest"; + const string column1Name = "Column1"; + const string column2Name = "Column2"; + const string constraintName = "ConstraintName"; + + Provider.AddTable(tableName, new Column(column1Name, DbType.Int32), new Column(column2Name, DbType.Int32)); + + Provider.AddUniqueConstraint(constraintName, tableName, column1Name, column2Name); + + // Act + var columns = Provider.GetColumns(tableName); + + // Assert + Assert.That(columns[0].ColumnProperty, Is.EqualTo(ColumnProperty.Null)); + } + + [Test, Description("Add index. The index should be added and then being detected as index.")] + public void GetSQLiteTableInfo_GetIndexesAndColumnsWithIndex_NoUniqueOnTheColumnsAndIndexExists() + { + // Arrange + const string tableName = "GetColumnsTest"; + Provider.AddTable(tableName, new Column("Bla1", DbType.Int32), new Column("Bla2", DbType.Int32)); + Provider.AddIndex("IndexName", tableName, ["Bla1", "Bla2"]); + + // Act + var sqliteInfo = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(tableName); + + // Assert + Assert.That(sqliteInfo.Columns[0].ColumnProperty, Is.EqualTo(ColumnProperty.Null)); + Assert.That(sqliteInfo.Columns[1].ColumnProperty, Is.EqualTo(ColumnProperty.Null)); + Assert.That(sqliteInfo.Uniques, Is.Empty); + Assert.That(sqliteInfo.Indexes.Single().Unique, Is.False); + } +} diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetForeignKeysTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetForeignKeysTests.cs new file mode 100644 index 00000000..17b431b1 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetForeignKeysTests.cs @@ -0,0 +1,56 @@ +using System.Data; +using System.Linq; +using DotNetProjects.Migrator.Framework; +using Migrator.Tests.Providers.SQLite.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_GetForeignKeysTests : SQLiteTransformationProviderTestBase +{ + [Test] + public void RenameColumn_HavingASingleForeignKeyPointingToTheTargetColumn_SingleColumnForeignKeyIsRemoved() + { + // Arrange + const string parentA = "TableA"; + const string parentAProperty1 = "ParentBProperty1"; + const string parentB = "TableB"; + const string parentBProperty1 = "ParentBProperty1"; + const string parentBProperty2 = "ParentBProperty2"; + const string child = "TableChild"; + const string childColumnFKToParentAProperty1 = "ChildColumnFKToParentAProperty1"; + const string childColumnFKToParentBProperty1 = "ChildColumnFKToParentBProperty1"; + const string childColumnFKToParentBProperty2 = "ChildColumnFKToParentBProperty2"; + const string foreignKeyStringA = "ForeignKeyStringA"; + const string foreignKeyStringB = "ForeignKeyStringB"; + + Provider.AddTable(parentA, new Column(parentAProperty1, DbType.Int32, ColumnProperty.PrimaryKey)); + + Provider.AddTable(parentB, + new Column(parentBProperty1, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(parentBProperty2, DbType.Int32, ColumnProperty.Unique) + ); + + Provider.AddTable(child, + new Column("Id", DbType.Int32, ColumnProperty.PrimaryKey), + new Column(childColumnFKToParentAProperty1, DbType.Int32, ColumnProperty.Unique), + new Column(childColumnFKToParentBProperty1, DbType.Int32), + new Column(childColumnFKToParentBProperty2, DbType.Int32) + ); + + Provider.AddForeignKey(foreignKeyStringA, child, childColumnFKToParentAProperty1, parentA, parentAProperty1); + Provider.AddForeignKey(foreignKeyStringB, child, [childColumnFKToParentBProperty1, childColumnFKToParentBProperty2], parentB, [parentBProperty1, parentBProperty2]); + + // Act + var foreignKeyConstraints = Provider.GetForeignKeyConstraints(child); + + // Assert + Assert.That(foreignKeyConstraints.Single(x => x.Name == foreignKeyStringA).ChildColumns, Is.EqualTo([childColumnFKToParentAProperty1])); + Assert.That(foreignKeyConstraints.Single(x => x.Name == foreignKeyStringA).ParentColumns, Is.EqualTo([parentAProperty1])); + + Assert.That(foreignKeyConstraints.Single(x => x.Name == foreignKeyStringB).ChildColumns, Is.EqualTo([childColumnFKToParentBProperty1, childColumnFKToParentBProperty2])); + Assert.That(foreignKeyConstraints.Single(x => x.Name == foreignKeyStringB).ParentColumns, Is.EqualTo([parentBProperty1, parentBProperty2])); + } +} diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetIndexesTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetIndexesTests.cs new file mode 100644 index 00000000..f55ab749 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetIndexesTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_GetIndexesTests : Generic_GetIndexesTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLiteTransactionAsync(); + } +} diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetPragmaTableInfoItemsTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetPragmaTableInfoItemsTests.cs new file mode 100644 index 00000000..e4418b28 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetPragmaTableInfoItemsTests.cs @@ -0,0 +1,46 @@ +using System.Linq; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using Migrator.Tests.Providers.SQLite.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_GetPragmaTableInfoItemsTests : SQLiteTransformationProviderTestBase +{ + [Test] + public void AddTable_NoNotNullColumn_NotNullIsFalse() + { + const string tableName = "MyTableName"; + const string columnName = "MyColumnName"; + + // Arrange + Provider.AddTable(tableName, new Column(columnName, System.Data.DbType.Int32)); + var createScript = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(tableName); + + // Act + var tableInfoItems = ((SQLiteTransformationProvider)Provider).GetPragmaTableInfoItems(tableName); + + + Assert.That(tableInfoItems.First(x => x.Name == columnName).NotNull, Is.False); + } + + [Test] + public void AddTable_NotNullColumn_NotNullIsTrue() + { + const string tableName = "MyTableName"; + const string columnName = "MyColumnName"; + + // Arrange + Provider.AddTable(tableName, new Column(columnName, System.Data.DbType.Int32, ColumnProperty.NotNull)); + var createScript = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(tableName); + + // Act + var tableInfoItems = ((SQLiteTransformationProvider)Provider).GetPragmaTableInfoItems(tableName); + + + Assert.That(tableInfoItems.First(x => x.Name == columnName).NotNull, Is.True); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetUniques.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetUniques.cs new file mode 100644 index 00000000..d4727474 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetUniques.cs @@ -0,0 +1,68 @@ +using System.Data; +using System.Linq; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using Migrator.Tests.Providers.SQLite.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_GetUniquesTests : SQLiteTransformationProviderTestBase +{ + [Test] + public void GetUniques_Success() + { + // Arrange + const string tableNameA = "TableA"; + const string property1 = "Property1"; + const string property2 = "Property2"; + const string property3 = "Property3"; + const string property4 = "Property4"; + const string property5 = "Property5"; + const string uniqueConstraintName1 = "UniqueConstraint1"; + const string uniqueConstraintName2 = "UniqueConstraint2"; + const string uniqueIndexName1 = "IndexUnique1"; + const string nonUniqueIndexName1 = "IndexNonUnique1"; + + Provider.AddTable(tableNameA, + new Column(property1, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(property2, DbType.Int32, ColumnProperty.Unique), + new Column(property3, DbType.Int32), + new Column(property4, DbType.Int32), + new Column(property5, DbType.Int32) + ); + + Provider.AddUniqueConstraint(uniqueConstraintName1, tableNameA, property3); + Provider.AddUniqueConstraint(uniqueConstraintName2, tableNameA, property4, property5); + + // Add unique index in order to assert there is no interference with unique constraints + Provider.AddIndex(tableNameA, new Index { Name = nonUniqueIndexName1, KeyColumns = [property4], Unique = false }); + Provider.AddIndex(tableNameA, new Index { Name = uniqueIndexName1, KeyColumns = [property5], Unique = true }); + + // Act + var uniqueConstraints = ((SQLiteTransformationProvider)Provider).GetUniques(tableNameA); + var indexes = Provider.GetIndexes(tableNameA); + + // Assert + var sql = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(tableNameA); + + Assert.That(uniqueConstraints.Count, Is.EqualTo(3)); + Assert.That(uniqueConstraints.Single(x => x.Name == uniqueConstraintName1).KeyColumns, Is.EqualTo([property3])); + Assert.That(uniqueConstraints.Single(x => x.Name == uniqueConstraintName2).KeyColumns, Is.EqualTo([property4, property5])); + + Assert.That(sql, Does.Contain("CONSTRAINT UniqueConstraint1 UNIQUE (Property3)")); + Assert.That(sql, Does.Contain("CONSTRAINT UniqueConstraint2 UNIQUE (Property4, Property5)")); + Assert.That(sql, Does.Contain("CONSTRAINT sqlite_autoindex_TableA_1 UNIQUE (Property2)")); + + var retrievedUniqueIndex1 = indexes.Single(x => x.Name == uniqueIndexName1); + + Assert.That(retrievedUniqueIndex1.Unique, Is.True); + Assert.That(retrievedUniqueIndex1.Name, Is.EqualTo(uniqueIndexName1)); + + var retrievedNonUniqueIndex1 = indexes.Single(x => x.Name == nonUniqueIndexName1); + + Assert.That(retrievedNonUniqueIndex1.Unique, Is.False); + } +} diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_PRAGMAForeignKeys.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_PRAGMAForeignKeys.cs new file mode 100644 index 00000000..189791ce --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_PRAGMAForeignKeys.cs @@ -0,0 +1,53 @@ + + + + +// Does not work because we cannot reuse the connection at this point in time. + + + + + + +// using System.Data; +// using DotNetProjects.Migrator.Providers.Impl.SQLite; +// using Migrator.Framework; +// using Migrator.Tests.Providers.SQLite.Base; +// using NUnit.Framework; + +// namespace Migrator.Tests.Providers.SQLite; + +// [TestFixture] +// [Category("SQLite")] +// public class SQLiteTransformationProvider_PRAGMAForeignKeysTests : SQLiteTransformationProviderTestBase +// { +// [Test, Description("Tests the set ON indirectly. Integrity violation should throw.")] +// public void PragmaForeignKeys_IntegrityViolation_Throws() +// { +// const string parentTableName = "ParentTable"; +// const string childTableName = "ChildTable"; +// const string propertyIdName = "Id"; +// const string foreignKeyColumnName = "ParentId"; + +// Provider.AddTable(parentTableName, new Column(propertyIdName, DbType.Int32, ColumnProperty.PrimaryKey)); +// Provider.AddTable(childTableName, new Column(propertyIdName, DbType.Int32, ColumnProperty.PrimaryKey), new Column(foreignKeyColumnName, DbType.Int32)); + +// ((SQLiteTransformationProvider)Provider).BeginTransaction(); +// ((SQLiteTransformationProvider)Provider).SetPragmaForeignKeys(false); +// var pragmaForeignKeyState1 = ((SQLiteTransformationProvider)Provider).IsPragmaForeignKeysOn(); + +// Provider.ExecuteNonQuery($"INSERT INTO {parentTableName} ({propertyIdName}) VALUES (1)"); + +// // Integrity violation does not throw due to set OFF validation +// Provider.ExecuteNonQuery($"INSERT INTO {childTableName} ({propertyIdName}, {foreignKeyColumnName}) VALUES (1, 999)"); + +// Provider.ExecuteNonQuery($"DELETE FROM {childTableName}"); + +// ((SQLiteTransformationProvider)Provider).SetPragmaForeignKeys(true); +// var pragmaForeignKeyState2 = ((SQLiteTransformationProvider)Provider).IsPragmaForeignKeysOn(); + + +// Assert.That(pragmaForeignKeyState1, Is.False); +// Assert.That(pragmaForeignKeyState2, Is.True); +// } +// } diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_PropertyColumnIdentityTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_PropertyColumnIdentityTests.cs new file mode 100644 index 00000000..7ba49518 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_PropertyColumnIdentityTests.cs @@ -0,0 +1,31 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using Migrator.Tests.Providers.SQLite.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_PropertyColumnIdentityTests : SQLiteTransformationProviderTestBase +{ + [Test] + public void AddPrimaryIdentity_Succeeds() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string propertyName1 = "Color1"; + const string propertyName2 = "Color2"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32, ColumnProperty.PrimaryKey | ColumnProperty.Identity), + new Column(propertyName2, DbType.Int32, ColumnProperty.NotNull) + ); + + var sql = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(testTableName); + + // NOT NULL implicitly set in SQLite + Assert.That(sql, Does.Contain("Color1 INTEGER NOT NULL PRIMARY KEY")); + } +} diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_RecreateTable.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_RecreateTable.cs new file mode 100644 index 00000000..ffb2efe8 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_RecreateTable.cs @@ -0,0 +1,34 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using Migrator.Tests.Providers.SQLite.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_RecreateTableTests : SQLiteTransformationProviderTestBase +{ + [Test] + public void RecreateTable_HavingACompoundPrimaryKey_Success() + { + // Arrange + Provider.AddTable("Common_Availability_EvRef", + new Column("EventId", DbType.Int64, ColumnProperty.NotNull | ColumnProperty.PrimaryKey), + new Column("AvailabilityGroupId", DbType.Guid, ColumnProperty.NotNull | ColumnProperty.PrimaryKey)); + + var sqliteInfo = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo("Common_Availability_EvRef"); + var sql = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript("Common_Availability_EvRef"); + + // Act/Assert + ((SQLiteTransformationProvider)Provider).RecreateTable(sqliteInfo); + var sql2 = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript("Common_Availability_EvRef"); + + + Assert.That(sql, Is.EqualTo("CREATE TABLE Common_Availability_EvRef (EventId INTEGER NOT NULL, AvailabilityGroupId UNIQUEIDENTIFIER NOT NULL, PRIMARY KEY (EventId, AvailabilityGroupId))")); + + // The quotes around the table name are added by SQLite on ALTER TABLE in RecreateTable + Assert.That(sql2, Is.EqualTo("CREATE TABLE \"Common_Availability_EvRef\" (EventId INTEGER NOT NULL, AvailabilityGroupId UNIQUEIDENTIFIER NOT NULL, PRIMARY KEY (EventId, AvailabilityGroupId))")); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_RemoveAllConstraintsTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_RemoveAllConstraintsTests.cs new file mode 100644 index 00000000..6494b426 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_RemoveAllConstraintsTests.cs @@ -0,0 +1,66 @@ +using System; +using System.Data; +using System.Linq; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using Migrator.Tests.Providers.SQLite.Base; +using NUnit.Framework; +using NUnit.Framework.Legacy; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_RemoveAllConstraints : SQLiteTransformationProviderTestBase +{ + [Test] + public void RemoveColumn_HavingNoCompositeIndexAndNoCompositeUniqueConstraint_Succeeds() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string propertyName1 = "Color1"; + const string propertyName2 = "Color2"; + const string propertyName3 = "Color3"; + const string indexName = "MyIndexName"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(propertyName2, DbType.Int32, ColumnProperty.Unique), + new Column(propertyName3, DbType.Int32, ColumnProperty.Unique) + ); + + Provider.AddIndex(indexName, testTableName, [propertyName1]); + var tableInfoBefore = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + + Provider.ExecuteNonQuery($"INSERT INTO {testTableName} ({propertyName1}, {propertyName2}) VALUES (1, 2)"); + + // Act + Provider.RemoveAllConstraints(testTableName); + Provider.ExecuteNonQuery($"INSERT INTO {testTableName} ({propertyName1}) VALUES (2)"); + + // Assert + using var command = Provider.GetCommand(); + using var reader = Provider.ExecuteQuery(command, $"SELECT COUNT(*) as Count from {testTableName}"); + reader.Read(); + var count = reader.GetInt32(reader.GetOrdinal("Count")); + Assert.That(count, Is.EqualTo(2)); + + var tableInfoAfter = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + var sqlAfter = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(testTableName); + + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == propertyName1).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.True); + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == propertyName2).ColumnProperty.HasFlag(ColumnProperty.Unique), Is.True); + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == propertyName3).ColumnProperty.HasFlag(ColumnProperty.Unique), Is.True); + + Assert.That(tableInfoAfter.Columns.Single(x => x.Name == propertyName1).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.False); + Assert.That(tableInfoAfter.Columns.Single(x => x.Name == propertyName2).ColumnProperty.HasFlag(ColumnProperty.Unique), Is.False); + Assert.That(tableInfoAfter.Columns.Single(x => x.Name == propertyName3).ColumnProperty.HasFlag(ColumnProperty.Unique), Is.False); + + Assert.That(sqlAfter.Contains("unique", StringComparison.OrdinalIgnoreCase), Is.False); + + var indexAfter = tableInfoAfter.Indexes.Single(); + + Assert.That(indexAfter.Name, Is.EqualTo(indexName)); + CollectionAssert.AreEquivalent(indexAfter.KeyColumns, new string[] { propertyName1 }); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_RemoveColumnTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_RemoveColumnTests.cs new file mode 100644 index 00000000..5fd05fa4 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_RemoveColumnTests.cs @@ -0,0 +1,288 @@ +using System; +using System.Data; +using System.Linq; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using Migrator.Tests.Providers.SQLite.Base; +using NUnit.Framework; +using NUnit.Framework.Legacy; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_RemoveColumn : SQLiteTransformationProviderTestBase +{ + [Test] + public void RemoveColumn_HavingNoCompositeIndexAndNoCompositeUniqueConstraint_Succeeds() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string propertyName1 = "Color1"; + const string propertyName2 = "Color2"; + const string propertyName3 = "Color3"; + const string indexName = "MyIndexName"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(propertyName2, DbType.Int32, ColumnProperty.Unique), + new Column(propertyName3, DbType.Int32, ColumnProperty.Unique) + ); + + Provider.AddIndex(indexName, testTableName, [propertyName1]); + var tableInfoBefore = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + + Provider.ExecuteNonQuery($"INSERT INTO {testTableName} ({propertyName1}, {propertyName2}) VALUES (1, 2)"); + + // Act + Provider.RemoveColumn(testTableName, propertyName2); + Provider.ExecuteNonQuery($"INSERT INTO {testTableName} ({propertyName1}) VALUES (2)"); + + // Assert + using var command = Provider.GetCommand(); + using var reader = Provider.ExecuteQuery(command, $"SELECT COUNT(*) as Count from {testTableName}"); + reader.Read(); + var count = reader.GetInt32(reader.GetOrdinal("Count")); + Assert.That(count, Is.EqualTo(2)); + + var tableInfoAfter = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == propertyName1).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.True); + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == propertyName2).ColumnProperty.HasFlag(ColumnProperty.Unique), Is.True); + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == propertyName3).ColumnProperty.HasFlag(ColumnProperty.Unique), Is.True); + + Assert.That(tableInfoAfter.Columns.Single(x => x.Name == propertyName1).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.True); + Assert.That(tableInfoAfter.Columns.Single(x => x.Name == propertyName3).ColumnProperty.HasFlag(ColumnProperty.Unique), Is.True); + + var indexAfter = tableInfoAfter.Indexes.Single(); + Assert.That(indexAfter.Name, Is.EqualTo(indexName)); + CollectionAssert.AreEquivalent(indexAfter.KeyColumns, new string[] { propertyName1 }); + } + + [Test] + public void RemoveColumn_HavingASingleForeignKeyPointingToTheTargetColumn_SingleColumnForeignKeyIsRemoved() + { + // Arrange + const string parentTableName = "Parent"; + const string propertyName1 = "Id"; + const string propertyName2 = "OtherProperty"; + const string childTestTableName = "Child"; + const string childTestTableName2 = "ChildTable2"; + const string propertyChildTableName1 = "ColorId"; + + Provider.AddTable(parentTableName, + new Column(propertyName1, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(propertyName2, DbType.Int32, ColumnProperty.Unique) + ); + + Provider.AddTable(childTestTableName, new Column(propertyChildTableName1, DbType.Int32)); + Provider.AddForeignKey("FKName1", childTestTableName, propertyChildTableName1, parentTableName, propertyName1); + var script = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(childTestTableName); + + Provider.AddTable(childTestTableName2, new Column(propertyChildTableName1, DbType.Int32)); + Provider.AddForeignKey(name: "FKName2", childTable: childTestTableName2, childColumn: propertyChildTableName1, parentTable: parentTableName, parentColumn: propertyName2); + + var tableInfoBefore = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(parentTableName); + var tableInfoChildBefore = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(childTestTableName); + + Provider.ExecuteNonQuery($"INSERT INTO {parentTableName} ({propertyName1}, {propertyName2}) VALUES (1, 2)"); + Provider.ExecuteNonQuery($"INSERT INTO {childTestTableName} ({propertyChildTableName1}) VALUES (1)"); + Provider.ExecuteNonQuery($"INSERT INTO {childTestTableName2} ({propertyChildTableName1}) VALUES (2)"); + + // Act + Provider.RemoveColumn(parentTableName, propertyName1); + + // Assert + Provider.ExecuteNonQuery($"INSERT INTO {parentTableName} ({propertyName2}) VALUES (3)"); + using var command = Provider.GetCommand(); + using var reader = Provider.ExecuteQuery(command, $"SELECT COUNT(*) as Count from {parentTableName}"); + reader.Read(); + var count = reader.GetInt32(reader.GetOrdinal("Count")); + Assert.That(count, Is.EqualTo(2)); + + var tableInfoAfter = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(parentTableName); + + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == propertyName1).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.True); + Assert.That(tableInfoBefore.Columns.Single(x => x.Name == propertyName2).ColumnProperty.HasFlag(ColumnProperty.Unique), Is.True); + + Assert.That(tableInfoAfter.Columns.FirstOrDefault(x => x.Name == propertyName1), Is.Null); + Assert.That(tableInfoAfter.ForeignKeys, Is.Empty); + + var valid = ((SQLiteTransformationProvider)Provider).CheckForeignKeyIntegrity(); + Assert.That(valid, Is.True); + } + + /// + /// If there is a composite index (more than one key columns) that contains the target column it should throw. + /// + [Test] + public void RemoveColumn_HavingIndexWithTwoColumnsOneOfThemIsTheTargetColumn_Throws() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string propertyName1 = "Color1"; + const string propertyName2 = "Color2"; + const string propertyName3 = "Color3"; + const string indexName = "MyIndexName"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(propertyName2, DbType.Int32, ColumnProperty.Unique), + new Column(propertyName3, DbType.Int32, ColumnProperty.Unique) + ); + + Provider.AddIndex(indexName, testTableName, [propertyName1, propertyName2]); + var tableInfoBefore = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + + Provider.ExecuteNonQuery($"INSERT INTO {testTableName} ({propertyName1}, {propertyName2}) VALUES (1, 2)"); + + // Act/Assert + var exception = Assert.Throws(() => Provider.RemoveColumn(testTableName, propertyName2)); + + Assert.That(exception.Message, Does.StartWith("Found composite index")); + } + + /// + /// If there is a composite unique constraint (more than one key columns) that contains the target column it should throw. + /// + [Test] + public void RemoveColumn_HavingUniqueConstraintWithTwoColumnsOneOfThemTargetColumn_Throws() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string propertyName1 = "Color1"; + const string propertyName2 = "Color2"; + const string propertyName3 = "Color3"; + const string indexName = "MyIndexName"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(propertyName2, DbType.Int32, ColumnProperty.Unique), + new Column(propertyName3, DbType.Int32, ColumnProperty.Unique) + ); + + Provider.AddUniqueConstraint("UniqueConstraintName", testTableName, [propertyName2, propertyName3]); + + Provider.AddIndex(indexName, testTableName, [propertyName1, propertyName2]); + var tableInfoBefore = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + + Provider.ExecuteNonQuery($"INSERT INTO {testTableName} ({propertyName1}, {propertyName2}) VALUES (1, 2)"); + + // Act/Assert + var exception = Assert.Throws(() => Provider.RemoveColumn(testTableName, propertyName2)); + + Assert.That(exception.Message, Does.StartWith("Found composite unique constraint")); + } + + /// + /// If there are multiple single uniques and only single column indexes (or no index) it should succeed. + /// + [Test] + public void RemoveColumn_HavingMultipleSingleUniques_Succeeds() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string propertyName1 = "Color1"; + const string propertyName2 = "Color2"; + const string propertyName3 = "Color3"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(propertyName2, DbType.Int32, ColumnProperty.Unique), + new Column(propertyName3, DbType.Int32, ColumnProperty.Unique) + ); + + var tableInfoBefore = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + + // Act + Provider.RemoveColumn(testTableName, propertyName2); + var tableInfoAfter = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(testTableName); + + // We do not support not named uniques in SQLite any more. + Assert.That(tableInfoBefore.Uniques.Count, Is.EqualTo(0)); + Assert.That(tableInfoAfter.Uniques.Count, Is.EqualTo(0)); + } + + [Test] + public void RemoveColumn_HavingAForeignKeyPointingFromTableToParentAndForeignKeyPointingToTable_SingleColumnForeignKeyIsRemoved() + { + // Arrange + const string tableNameLevel1 = "Level1"; + const string tableNameLevel2 = "Level2"; + const string tableNameLevel3 = "Level3"; + const string propertyId = "Id"; + const string propertyLevel1Id = "Level1Id"; + const string propertyLevel2Id = "Level2Id"; + + Provider.AddTable(tableNameLevel1, new Column(propertyId, DbType.Int32, ColumnProperty.PrimaryKey)); + + Provider.AddTable(tableNameLevel2, + new Column(propertyId, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(propertyLevel1Id, DbType.Int32, ColumnProperty.Unique) + ); + + Provider.AddTable(tableNameLevel3, + new Column(propertyId, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(propertyLevel2Id, DbType.Int32) + ); + + Provider.AddForeignKey("Level2ToLevel1", tableNameLevel2, propertyLevel1Id, tableNameLevel1, propertyId); + Provider.AddForeignKey("Level3ToLevel2", tableNameLevel3, propertyLevel2Id, tableNameLevel2, propertyId); + + var script = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(tableNameLevel2); + + var tableInfoLevel2Before = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(tableNameLevel2); + var tableInfoLevel3Before = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(tableNameLevel3); + + Provider.ExecuteNonQuery($"INSERT INTO {tableNameLevel1} ({propertyId}) VALUES (1)"); + Provider.ExecuteNonQuery($"INSERT INTO {tableNameLevel1} ({propertyId}) VALUES (2)"); + Provider.ExecuteNonQuery($"INSERT INTO {tableNameLevel2} ({propertyId}, {propertyLevel1Id}) VALUES (1, 1)"); + Provider.ExecuteNonQuery($"INSERT INTO {tableNameLevel3} ({propertyId}, {propertyLevel2Id}) VALUES (1, 1)"); + + // Act + Provider.RemoveColumn(tableNameLevel2, propertyLevel1Id); + + // Assert + Provider.ExecuteNonQuery($"INSERT INTO {tableNameLevel2} ({propertyId}) VALUES (2)"); + using var command = Provider.GetCommand(); + + using var reader = Provider.ExecuteQuery(command, $"SELECT COUNT(*) as Count from {tableNameLevel2}"); + reader.Read(); + var count = reader.GetInt32(reader.GetOrdinal("Count")); + Assert.That(count, Is.EqualTo(2)); + + var tableInfoLevel2After = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(tableNameLevel2); + + Assert.That(tableInfoLevel2Before.Columns.Single(x => x.Name == propertyId).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.True); + Assert.That(tableInfoLevel2Before.Columns.Single(x => x.Name == propertyLevel1Id).ColumnProperty.HasFlag(ColumnProperty.Unique), Is.True); + Assert.That(tableInfoLevel2Before.ForeignKeys.Single().ChildColumns.Single(), Is.EqualTo(propertyLevel1Id)); + + Assert.That(tableInfoLevel2After.Columns.FirstOrDefault(x => x.Name == propertyId), Is.Not.Null); + Assert.That(tableInfoLevel2After.Columns.FirstOrDefault(x => x.Name == propertyLevel1Id), Is.Null); + Assert.That(tableInfoLevel2After.Columns.FirstOrDefault(x => x.Name == propertyId), Is.Not.Null); + Assert.That(tableInfoLevel2After.Columns.FirstOrDefault(x => x.Name == propertyLevel1Id), Is.Null); + Assert.That(tableInfoLevel2After.ForeignKeys, Is.Empty); + + var valid = ((SQLiteTransformationProvider)Provider).CheckForeignKeyIntegrity(); + Assert.That(valid, Is.True); + } + + [Test] + public void RemoveColumn_ColumnExistsInCheckConstraintString_Throws() + { + const string tableName = "MyTableName"; + const string columnName = "MyColumnName"; + const string checkConstraint1 = "MyCheckConstraint1"; + + // Arrange + Provider.AddTable(tableName, + new Column(columnName, System.Data.DbType.Int32), + new CheckConstraint(checkConstraint1, $"{columnName} > 10") + ); + + var checkConstraints = ((SQLiteTransformationProvider)Provider).GetCheckConstraints(tableName); + + // Act/Assert + Assert.Throws(() => Provider.RemoveColumn(tableName, columnName)); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_RenameColumnTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_RenameColumnTests.cs new file mode 100644 index 00000000..c801913a --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_RenameColumnTests.cs @@ -0,0 +1,80 @@ +using System.Data; +using System.Linq; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using Migrator.Tests.Providers.SQLite.Base; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_RenameColumnTests : SQLiteTransformationProviderTestBase +{ + [Test] + public void RenameColumn_HavingASingleForeignKeyPointingToTheTargetColumn_SingleColumnForeignKeyIsRemoved() + { + // Arrange + const string tableNameLevel1 = "Level1"; + const string tableNameLevel2 = "Level2"; + const string tableNameLevel3 = "Level3"; + const string propertyId = "Id"; + const string propertyIdRenamed = "IdRenamed"; + const string propertyLevel1Id = "Level1Id"; + const string propertyLevel1IdRenamed = "Level1IdRenamed"; + const string propertyLevel2Id = "Level2Id"; + + Provider.AddTable(tableNameLevel1, new Column(propertyId, DbType.Int32, ColumnProperty.PrimaryKey)); + + Provider.AddTable(tableNameLevel2, + new Column(propertyId, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(propertyLevel1Id, DbType.Int32, ColumnProperty.Unique) + ); + + Provider.AddTable(tableNameLevel3, + new Column(propertyId, DbType.Int32, ColumnProperty.PrimaryKey), + new Column(propertyLevel2Id, DbType.Int32) + ); + + Provider.AddForeignKey("Level2ToLevel1", tableNameLevel2, propertyLevel1Id, tableNameLevel1, propertyId); + Provider.AddForeignKey("Level3ToLevel2", tableNameLevel3, propertyLevel2Id, tableNameLevel2, propertyId); + + var script = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(tableNameLevel2); + + var tableInfoLevel2Before = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(tableNameLevel2); + var tableInfoLevel3Before = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(tableNameLevel3); + + Provider.ExecuteNonQuery($"INSERT INTO {tableNameLevel1} ({propertyId}) VALUES (1)"); + Provider.ExecuteNonQuery($"INSERT INTO {tableNameLevel1} ({propertyId}) VALUES (2)"); + Provider.ExecuteNonQuery($"INSERT INTO {tableNameLevel2} ({propertyId}, {propertyLevel1Id}) VALUES (1, 1)"); + Provider.ExecuteNonQuery($"INSERT INTO {tableNameLevel3} ({propertyId}, {propertyLevel2Id}) VALUES (1, 1)"); + + // Act + Provider.RenameColumn(tableNameLevel2, propertyId, propertyIdRenamed); + Provider.RenameColumn(tableNameLevel2, propertyLevel1Id, propertyLevel1IdRenamed); + + // Assert + Provider.ExecuteNonQuery($"INSERT INTO {tableNameLevel2} ({propertyIdRenamed}, {propertyLevel1IdRenamed}) VALUES (2,2)"); + using var command = Provider.GetCommand(); + + using var reader = Provider.ExecuteQuery(command, $"SELECT COUNT(*) as Count from {tableNameLevel2}"); + reader.Read(); + var count = reader.GetInt32(reader.GetOrdinal("Count")); + Assert.That(count, Is.EqualTo(2)); + + var tableInfoLevel2After = ((SQLiteTransformationProvider)Provider).GetSQLiteTableInfo(tableNameLevel2); + + Assert.That(tableInfoLevel2Before.Columns.Single(x => x.Name == propertyId).ColumnProperty.HasFlag(ColumnProperty.PrimaryKey), Is.True); + Assert.That(tableInfoLevel2Before.Columns.Single(x => x.Name == propertyLevel1Id).ColumnProperty.HasFlag(ColumnProperty.Unique), Is.True); + Assert.That(tableInfoLevel2Before.ForeignKeys.Single().ChildColumns.Single(), Is.EqualTo(propertyLevel1Id)); + + Assert.That(tableInfoLevel2After.Columns.FirstOrDefault(x => x.Name == propertyId), Is.Null); + Assert.That(tableInfoLevel2After.Columns.FirstOrDefault(x => x.Name == propertyLevel1Id), Is.Null); + Assert.That(tableInfoLevel2After.Columns.FirstOrDefault(x => x.Name == propertyIdRenamed), Is.Not.Null); + Assert.That(tableInfoLevel2After.Columns.FirstOrDefault(x => x.Name == propertyLevel1IdRenamed), Is.Not.Null); + Assert.That(tableInfoLevel2After.ForeignKeys.Single().ChildColumns.Single(), Is.EqualTo(propertyLevel1IdRenamed)); + + var valid = ((SQLiteTransformationProvider)Provider).CheckForeignKeyIntegrity(); + Assert.That(valid, Is.True); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_UpdateFromTableToTableTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_UpdateFromTableToTableTests.cs new file mode 100644 index 00000000..aac17b01 --- /dev/null +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_UpdateFromTableToTableTests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Migrator.Tests.Providers.Generic; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.SQLite; + +[TestFixture] +[Category("SQLite")] +public class SQLiteTransformationProvider_UpdateFromTableToTableTests : Generic_UpdateFromTableToTableTestsBase +{ + [SetUp] + public async Task SetUpAsync() + { + await BeginSQLiteTransactionAsync(); + } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/SQLiteTransformationProviderTest.cs b/src/Migrator.Tests/Providers/SQLiteTransformationProviderTest.cs deleted file mode 100644 index bdf888ae..00000000 --- a/src/Migrator.Tests/Providers/SQLiteTransformationProviderTest.cs +++ /dev/null @@ -1,84 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.Configuration; -using Migrator.Providers.SQLite; -using NUnit.Framework; - -namespace Migrator.Tests.Providers -{ - [TestFixture] - [Category("SQLite")] - public class SQLiteTransformationProviderTest : TransformationProviderBase - { - #region Setup/Teardown - - [SetUp] - public void SetUp() - { - string constr = ConfigurationManager.AppSettings["SQLiteConnectionString"]; - if (constr == null) - throw new ArgumentNullException("SQLiteConnectionString", "No config file"); - - _provider = new SQLiteTransformationProvider(new SQLiteDialect(), constr, "default", null); - _provider.BeginTransaction(); - - AddDefaultTable(); - } - - #endregion - - [Test] - public void CanParseColumnDefForName() - { - const string nullString = "bar TEXT"; - const string notNullString = "baz INTEGER NOT NULL"; - //Assert.AreEqual("bar", ((SQLiteTransformationProvider) _provider).ExtractNameFromColumnDef(nullString)); - //Assert.AreEqual("baz", ((SQLiteTransformationProvider) _provider).ExtractNameFromColumnDef(notNullString)); - } - - [Test] - public void CanParseColumnDefForNotNull() - { - const string nullString = "bar TEXT"; - const string notNullString = "baz INTEGER NOT NULL"; - Assert.IsTrue(((SQLiteTransformationProvider) _provider).IsNullable(nullString)); - Assert.IsFalse(((SQLiteTransformationProvider) _provider).IsNullable(notNullString)); - } - - [Test] - public void CanParseSqlDefinitions() - { - //const string testSql = "CREATE TABLE bar ( id INTEGER PRIMARY KEY AUTOINCREMENT, bar TEXT, baz INTEGER NOT NULL )"; - //string[] columns = ((SQLiteTransformationProvider) _provider).ParseSqlColumnDefs(testSql); - //Assert.IsNotNull(columns); - //Assert.AreEqual(3, columns.Length); - //Assert.AreEqual("id INTEGER PRIMARY KEY AUTOINCREMENT", columns[0]); - //Assert.AreEqual("bar TEXT", columns[1]); - //Assert.AreEqual("baz INTEGER NOT NULL", columns[2]); - } - - [Test] - public void CanParseSqlDefinitionsForColumnNames() - { - //const string testSql = "CREATE TABLE bar ( id INTEGER PRIMARY KEY AUTOINCREMENT, bar TEXT, baz INTEGER NOT NULL )"; - //string[] columns = ((SQLiteTransformationProvider) _provider).ParseSqlForColumnNames(testSql); - //Assert.IsNotNull(columns); - //Assert.AreEqual(3, columns.Length); - //Assert.AreEqual("id", columns[0]); - //Assert.AreEqual("bar", columns[1]); - //Assert.AreEqual("baz", columns[2]); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/SqlServer2005TransformationProviderTest.cs b/src/Migrator.Tests/Providers/SqlServer2005TransformationProviderTest.cs deleted file mode 100644 index 1c9b7cf9..00000000 --- a/src/Migrator.Tests/Providers/SqlServer2005TransformationProviderTest.cs +++ /dev/null @@ -1,44 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.Configuration; -using Migrator.Providers.SqlServer; -using NUnit.Framework; -using Migrator.Providers.Utility; - -namespace Migrator.Tests.Providers -{ - [TestFixture] - [Category("SqlServer2005")] - public class SqlServer2005TransformationProviderTest : TransformationProviderConstraintBase - { - #region Setup/Teardown - - [SetUp] - public void SetUp() - { - string constr = ConfigurationManager.AppSettings["SqlServer2005ConnectionString"]; - - if (constr == null) - throw new ArgumentNullException("SqlServer2005ConnectionString", "No config file"); - - _provider = new SqlServerTransformationProvider(new SqlServer2005Dialect(), constr, null, "default", null); - _provider.BeginTransaction(); - - AddDefaultTable(); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/SqlServerCeTransformationProviderTest.cs b/src/Migrator.Tests/Providers/SqlServerCeTransformationProviderTest.cs deleted file mode 100644 index cd2ece36..00000000 --- a/src/Migrator.Tests/Providers/SqlServerCeTransformationProviderTest.cs +++ /dev/null @@ -1,67 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.Configuration; -using System.Data.SqlServerCe; -using System.IO; -using Migrator.Providers.SqlServer; -using NUnit.Framework; - -namespace Migrator.Tests.Providers -{ - [TestFixture] - [Category("SqlServerCe")] - public class SqlServerCeTransformationProviderTest : TransformationProviderConstraintBase - { - #region Setup/Teardown - - [SetUp] - public void SetUp() - { - string constr = ConfigurationManager.AppSettings["SqlServerCeConnectionString"]; - if (constr == null) - throw new ArgumentNullException("SqlServerCeConnectionString", "No config file"); - - EnsureDatabase(constr); - - _provider = new SqlServerCeTransformationProvider(new SqlServerCeDialect(), constr, "default", null); - _provider.BeginTransaction(); - - AddDefaultTable(); - } - - #endregion - - void EnsureDatabase(string constr) - { - var connection = new SqlCeConnection(constr); - if (!File.Exists(connection.Database)) - { - var engine = new SqlCeEngine(constr); - engine.CreateDatabase(); - } - } - - // [Test,Ignore("SqlServerCe doesn't support check constraints")] - public override void CanAddCheckConstraint() - { - } - - // [Test,Ignore("SqlServerCe doesn't support table renaming")] - // see: http://www.pocketpcdn.com/articles/articles.php?&atb.set(c_id)=74&atb.set(a_id)=8145&atb.perform(details)=& - public override void RenameTableThatExists() - { - } - } -} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/SqlServerTransformationProviderTest.cs b/src/Migrator.Tests/Providers/SqlServerTransformationProviderTest.cs deleted file mode 100644 index f461cd29..00000000 --- a/src/Migrator.Tests/Providers/SqlServerTransformationProviderTest.cs +++ /dev/null @@ -1,87 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.Configuration; -using System.Data; -using Migrator.Framework; -using Migrator.Providers; -using Migrator.Providers.SqlServer; -using NUnit.Framework; - -namespace Migrator.Tests.Providers -{ - [TestFixture] - [Category("SqlServer")] - public class SqlServerTransformationProviderTest : TransformationProviderConstraintBase - { - #region Setup/Teardown - - [SetUp] - public void SetUp() - { - string constr = ConfigurationManager.AppSettings["SqlServerConnectionString"]; - if (constr == null) - throw new ArgumentNullException("SqlServerConnectionString", "No config file"); - - _provider = new SqlServerTransformationProvider(new SqlServerDialect(), constr, null, "default", null); - _provider.BeginTransaction(); - - AddDefaultTable(); - } - - #endregion - - [Test] - public void ByteColumnWillBeCreatedAsBlob() - { - _provider.AddColumn("TestTwo", "BlobColumn", DbType.Byte); - Assert.IsTrue(_provider.ColumnExists("TestTwo", "BlobColumn")); - } - - [Test] - public void InstanceForProvider() - { - ITransformationProvider localProv = _provider["sqlserver"]; - Assert.IsTrue(localProv is SqlServerTransformationProvider); - - ITransformationProvider localProv2 = _provider["foo"]; - Assert.IsTrue(localProv2 is NoOpTransformationProvider); - } - - [Test] - public void QuoteCreatesProperFormat() - { - Dialect dialect = new SqlServerDialect(); - Assert.AreEqual("[foo]", dialect.Quote("foo")); - } - - [Test] - public void TableExistsShouldWorkWithBracketsAndSchemaNameAndTableName() - { - Assert.IsTrue(_provider.TableExists("[dbo].[TestTwo]")); - } - - [Test] - public void TableExistsShouldWorkWithSchemaNameAndTableName() - { - Assert.IsTrue(_provider.TableExists("dbo.TestTwo")); - } - - [Test] - public void TableExistsShouldWorkWithTableNamesWithBracket() - { - Assert.IsTrue(_provider.TableExists("[TestTwo]")); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/TransformationProviderBase.cs b/src/Migrator.Tests/Providers/TransformationProviderBase.cs deleted file mode 100644 index 27778090..00000000 --- a/src/Migrator.Tests/Providers/TransformationProviderBase.cs +++ /dev/null @@ -1,523 +0,0 @@ -using System; -using System.Data; -using Migrator.Framework; -using NUnit.Framework; - -namespace Migrator.Tests.Providers -{ - /// - /// Base class for Provider tests for all non-constraint oriented tests. - /// - public class TransformationProviderBase - { - protected ITransformationProvider _provider; - - [TearDown] - public virtual void TearDown() - { - DropTestTables(); - - _provider.Rollback(); - } - - protected void DropTestTables() - { - // Because MySql doesn't support schema transaction - // we got to remove the tables manually... sad... - try - { - _provider.RemoveTable("TestTwo"); - } - catch (Exception) - { - } - try - { - _provider.RemoveTable("Test"); - } - catch (Exception) - { - } - try - { - _provider.RemoveTable("SchemaInfo"); - } - catch (Exception) - { - } - } - - public void AddDefaultTable() - { - _provider.AddTable("TestTwo", - new Column("Id", DbType.Int32, ColumnProperty.PrimaryKey), - new Column("TestId", DbType.Int32, ColumnProperty.ForeignKey) - ); - } - - public void AddTable() - { - _provider.AddTable("Test", - new Column("Id", DbType.Int32, ColumnProperty.NotNull), - new Column("Title", DbType.String, 100, ColumnProperty.Null), - new Column("name", DbType.String, 50, ColumnProperty.Null), - new Column("blobVal", DbType.Binary, ColumnProperty.Null), - new Column("boolVal", DbType.Boolean, ColumnProperty.Null), - new Column("bigstring", DbType.String, 50000, ColumnProperty.Null) - ); - } - - public void AddTableWithPrimaryKey() - { - _provider.AddTable("Test", - new Column("Id", DbType.Int32, ColumnProperty.PrimaryKeyWithIdentity), - new Column("Title", DbType.String, 100, ColumnProperty.Null), - new Column("name", DbType.String, 50, ColumnProperty.NotNull), - new Column("blobVal", DbType.Binary), - new Column("boolVal", DbType.Boolean), - new Column("bigstring", DbType.String, 50000) - ); - } - - [Test] - public void TableExistsWorks() - { - Assert.IsFalse(_provider.TableExists("gadadadadseeqwe")); - Assert.IsTrue(_provider.TableExists("TestTwo")); - } - - [Test] - public void ColumnExistsWorks() - { - Assert.IsFalse(_provider.ColumnExists("gadadadadseeqwe", "eqweqeq")); - Assert.IsFalse(_provider.ColumnExists("TestTwo", "eqweqeq")); - Assert.IsTrue(_provider.ColumnExists("TestTwo", "Id")); - } - - [Test] - public void CanExecuteBadSqlForNonCurrentProvider() - { - _provider["foo"].ExecuteNonQuery("select foo from bar 123"); - } - - [Test] - public void TableCanBeAdded() - { - AddTable(); - Assert.IsTrue(_provider.TableExists("Test")); - } - - [Test] - public void GetTablesWorks() - { - foreach (string name in _provider.GetTables()) - { - _provider.Logger.Log("Table: {0}", name); - } - Assert.AreEqual(1, _provider.GetTables().Length); - AddTable(); - Assert.AreEqual(2, _provider.GetTables().Length); - } - - [Test] - public void GetColumnsReturnsProperCount() - { - AddTable(); - Column[] cols = _provider.GetColumns("Test"); - Assert.IsNotNull(cols); - Assert.AreEqual(6, cols.Length); - } - - [Test] - public void GetColumnsContainsProperNullInformation() - { - AddTableWithPrimaryKey(); - Column[] cols = _provider.GetColumns("Test"); - Assert.IsNotNull(cols); - foreach (Column column in cols) - { - if (column.Name == "name") - Assert.IsTrue((column.ColumnProperty & ColumnProperty.NotNull) == ColumnProperty.NotNull); - else if (column.Name == "Title") - Assert.IsTrue((column.ColumnProperty & ColumnProperty.Null) == ColumnProperty.Null); - } - } - - [Test] - public void CanAddTableWithPrimaryKey() - { - AddTableWithPrimaryKey(); - Assert.IsTrue(_provider.TableExists("Test")); - } - - [Test] - public void RemoveTable() - { - AddTable(); - _provider.RemoveTable("Test"); - Assert.IsFalse(_provider.TableExists("Test")); - } - - [Test] - public virtual void RenameTableThatExists() - { - AddTable(); - _provider.RenameTable("Test", "Test_Rename"); - - Assert.IsTrue(_provider.TableExists("Test_Rename")); - Assert.IsFalse(_provider.TableExists("Test")); - _provider.RemoveTable("Test_Rename"); - } - - [Test] - [ExpectedException(typeof (MigrationException))] - public void RenameTableToExistingTable() - { - AddTable(); - _provider.RenameTable("Test", "TestTwo"); - } - - [Test] - public void RenameColumnThatExists() - { - AddTable(); - _provider.RenameColumn("Test", "name", "name_rename"); - - Assert.IsTrue(_provider.ColumnExists("Test", "name_rename")); - Assert.IsFalse(_provider.ColumnExists("Test", "name")); - } - - [Test] - [ExpectedException(typeof (MigrationException))] - public void RenameColumnToExistingColumn() - { - AddTable(); - _provider.RenameColumn("Test", "Title", "name"); - } - - [Test] - public void RemoveUnexistingTable() - { - _provider.RemoveTable("abc"); - } - - [Test] - public void AddColumn() - { - _provider.AddColumn("TestTwo", "Test", DbType.String, 50); - Assert.IsTrue(_provider.ColumnExists("TestTwo", "Test")); - } - - [Test] - public void ChangeColumn() - { - _provider.ChangeColumn("TestTwo", new Column("TestId", DbType.String, 50)); - Assert.IsTrue(_provider.ColumnExists("TestTwo", "TestId")); - _provider.Insert("TestTwo", new[] {"Id", "TestId"}, new object[] {1, "Not an Int val."}); - } - - [Test] - public void ChangeColumn_FromNullToNull() - { - _provider.ChangeColumn("TestTwo", new Column("TestId", DbType.String, 50, ColumnProperty.Null)); - _provider.ChangeColumn("TestTwo", new Column("TestId", DbType.String, 50, ColumnProperty.Null)); - _provider.ChangeColumn("TestTwo", new Column("TestId", DbType.String, 50, ColumnProperty.Null)); - _provider.Insert("TestTwo", new[] {"Id", "TestId"}, new object[] {2, "Not an Int val."}); - } - - [Test] - public void AddDecimalColumn() - { - _provider.AddColumn("TestTwo", "TestDecimal", DbType.Decimal, 38); - Assert.IsTrue(_provider.ColumnExists("TestTwo", "TestDecimal")); - } - - [Test] - public void AddColumnWithDefault() - { - _provider.AddColumn("TestTwo", "TestWithDefault", DbType.Int32, 50, 0, 10); - Assert.IsTrue(_provider.ColumnExists("TestTwo", "TestWithDefault")); - } - - [Test] - public void AddColumnWithDefaultButNoSize() - { - _provider.AddColumn("TestTwo", "TestWithDefault", DbType.Int32, 10); - Assert.IsTrue(_provider.ColumnExists("TestTwo", "TestWithDefault")); - - _provider.AddColumn("TestTwo", "TestWithDefaultString", DbType.String, "'foo'"); - Assert.IsTrue(_provider.ColumnExists("TestTwo", "TestWithDefaultString")); - } - - [Test] - public void AddBooleanColumnWithDefault() - { - _provider.AddColumn("TestTwo", "TestBoolean", DbType.Boolean, 0, 0, false); - Assert.IsTrue(_provider.ColumnExists("TestTwo", "TestBoolean")); - } - - [Test] - public void CanGetNullableFromProvider() - { - _provider.AddColumn("TestTwo", "NullableColumn", DbType.String, 30, ColumnProperty.Null); - Column[] columns = _provider.GetColumns("TestTwo"); - foreach (Column column in columns) - { - if (column.Name == "NullableColumn") - { - Assert.IsTrue((column.ColumnProperty & ColumnProperty.Null) == ColumnProperty.Null); - } - } - } - - [Test] - public void RemoveColumn() - { - AddColumn(); - _provider.RemoveColumn("TestTwo", "Test"); - Assert.IsFalse(_provider.ColumnExists("TestTwo", "Test")); - } - - [Test] - public void RemoveColumnWithDefault() - { - AddColumnWithDefault(); - _provider.RemoveColumn("TestTwo", "TestWithDefault"); - Assert.IsFalse(_provider.ColumnExists("TestTwo", "TestWithDefault")); - } - - [Test] - public void RemoveUnexistingColumn() - { - _provider.RemoveColumn("TestTwo", "abc"); - _provider.RemoveColumn("abc", "abc"); - } - - /// - /// Supprimer une colonne bit causait une erreur à cause - /// de la valeur par défaut. - /// - [Test] - public void RemoveBoolColumn() - { - AddTable(); - _provider.AddColumn("Test", "Inactif", DbType.Boolean); - Assert.IsTrue(_provider.ColumnExists("Test", "Inactif")); - - _provider.RemoveColumn("Test", "Inactif"); - Assert.IsFalse(_provider.ColumnExists("Test", "Inactif")); - } - - [Test] - public void HasColumn() - { - AddColumn(); - Assert.IsTrue(_provider.ColumnExists("TestTwo", "Test")); - Assert.IsFalse(_provider.ColumnExists("TestTwo", "TestPasLa")); - } - - [Test] - public void HasTable() - { - Assert.IsTrue(_provider.TableExists("TestTwo")); - } - - [Test] - public void AppliedMigrations() - { - Assert.IsFalse(_provider.TableExists("SchemaInfo")); - - // Check that a "get" call works on the first run. - Assert.AreEqual(0, _provider.AppliedMigrations.Count); - Assert.IsTrue(_provider.TableExists("SchemaInfo"), "No SchemaInfo table created"); - - // Check that a "set" called after the first run works. - _provider.MigrationApplied(1, null); - Assert.AreEqual(1, _provider.AppliedMigrations[0]); - - _provider.RemoveTable("SchemaInfo"); - // Check that a "set" call works on the first run. - _provider.MigrationApplied(1, null); - Assert.AreEqual(1, _provider.AppliedMigrations[0]); - Assert.IsTrue(_provider.TableExists("SchemaInfo"), "No SchemaInfo table created"); - } - - /// - /// Reproduce bug reported by Luke Melia & Daniel Berlinger : - /// http://macournoyer.wordpress.com/2006/10/15/migrate-nant-task/#comment-113 - /// - [Test] - public void CommitTwice() - { - _provider.Commit(); - Assert.AreEqual(0, _provider.AppliedMigrations.Count); - _provider.Commit(); - } - - [Test] - public void InsertData() - { - _provider.Insert("TestTwo", new[] {"Id", "TestId"}, new object[] {1, "1"}); - _provider.Insert("TestTwo", new[] {"Id", "TestId"}, new object[] {2, "2"}); - using (IDataReader reader = _provider.Select("TestId", "TestTwo")) - { - int[] vals = GetVals(reader); - - Assert.IsTrue(Array.Exists(vals, delegate(int val) { return val == 1; })); - Assert.IsTrue(Array.Exists(vals, delegate(int val) { return val == 2; })); - } - } - - [Test] - public void CanInsertNullData() - { - AddTable(); - _provider.Insert("Test", new[] {"Id", "Title"}, new[] {"1", "foo"}); - _provider.Insert("Test", new[] {"Id", "Title"}, new[] {"2", null}); - using (IDataReader reader = _provider.Select("Title", "Test")) - { - string[] vals = GetStringVals(reader); - - Assert.IsTrue(Array.Exists(vals, delegate(string val) { return val == "foo"; })); - Assert.IsTrue(Array.Exists(vals, delegate(string val) { return val == null; })); - } - } - - [Test] - public void CanInsertDataWithSingleQuotes() - { - AddTable(); - _provider.Insert("Test", new[] {"Id", "Title"}, new[] {"1", "Muad'Dib"}); - using (IDataReader reader = _provider.Select("Title", "Test")) - { - Assert.IsTrue(reader.Read()); - Assert.AreEqual("Muad'Dib", reader.GetString(0)); - Assert.IsFalse(reader.Read()); - } - } - - [Test] - public void DeleteData() - { - InsertData(); - _provider.Delete("TestTwo", "TestId", "1"); - - using (IDataReader reader = _provider.Select("TestId", "TestTwo")) - { - Assert.IsTrue(reader.Read()); - Assert.AreEqual(2, Convert.ToInt32(reader[0])); - Assert.IsFalse(reader.Read()); - } - } - - [Test] - public void DeleteDataWithArrays() - { - InsertData(); - _provider.Delete("TestTwo", new[] {"TestId"}, new[] {"1"}); - - using (IDataReader reader = _provider.Select("TestId", "TestTwo")) - { - Assert.IsTrue(reader.Read()); - Assert.AreEqual(2, Convert.ToInt32(reader[0])); - Assert.IsFalse(reader.Read()); - } - } - - [Test] - public void UpdateData() - { - _provider.Insert("TestTwo", new[] {"Id", "TestId"}, new object[] {20, "1"}); - _provider.Insert("TestTwo", new[] {"Id", "TestId"}, new object[] {21, "2"}); - - _provider.Update("TestTwo", new[] {"TestId"}, new[] {"3"}); - - using (IDataReader reader = _provider.Select("TestId", "TestTwo")) - { - int[] vals = GetVals(reader); - - Assert.IsTrue(Array.Exists(vals, delegate(int val) { return val == 3; })); - Assert.IsFalse(Array.Exists(vals, delegate(int val) { return val == 1; })); - Assert.IsFalse(Array.Exists(vals, delegate(int val) { return val == 2; })); - } - } - - [Test] - public void CanUpdateWithNullData() - { - AddTable(); - _provider.Insert("Test", new[] {"Id", "Title"}, new[] {"1", "foo"}); - _provider.Insert("Test", new[] {"Id", "Title"}, new[] {"2", null}); - - _provider.Update("Test", new[] {"Title"}, new string[] {null}); - - using (IDataReader reader = _provider.Select("Title", "Test")) - { - string[] vals = GetStringVals(reader); - - Assert.IsNull(vals[0]); - Assert.IsNull(vals[1]); - } - } - - [Test] - public void UpdateDataWithWhere() - { - _provider.Insert("TestTwo", new[] {"Id", "TestId"}, new object[] {10, "1"}); - _provider.Insert("TestTwo", new[] {"Id", "TestId"}, new object[] {11, "2"}); - - _provider.Update("TestTwo", new[] {"TestId"}, new[] {"3"}, "TestId='1'"); - - using (IDataReader reader = _provider.Select("TestId", "TestTwo")) - { - int[] vals = GetVals(reader); - - Assert.IsTrue(Array.Exists(vals, delegate(int val) { return val == 3; })); - Assert.IsTrue(Array.Exists(vals, delegate(int val) { return val == 2; })); - Assert.IsFalse(Array.Exists(vals, delegate(int val) { return val == 1; })); - } - } - - [Test] - public void AddIndex() - { - string indexName = "test_index"; - - Assert.IsFalse(_provider.IndexExists("TestTwo", indexName)); - _provider.AddIndex(indexName, "TestTwo", "Id", "TestId"); - Assert.IsTrue(_provider.IndexExists("TestTwo", indexName)); - } - - [Test] - public void RemoveIndex() - { - string indexName = "test_index"; - - Assert.IsFalse(_provider.IndexExists("TestTwo", indexName)); - _provider.AddIndex(indexName, "TestTwo", "Id", "TestId"); - _provider.RemoveIndex("TestTwo", indexName); - Assert.IsFalse(_provider.IndexExists("TestTwo", indexName)); - } - - - int[] GetVals(IDataReader reader) - { - var vals = new int[2]; - Assert.IsTrue(reader.Read()); - vals[0] = Convert.ToInt32(reader[0]); - Assert.IsTrue(reader.Read()); - vals[1] = Convert.ToInt32(reader[0]); - return vals; - } - - string[] GetStringVals(IDataReader reader) - { - var vals = new string[2]; - Assert.IsTrue(reader.Read()); - vals[0] = reader[0] as string; - Assert.IsTrue(reader.Read()); - vals[1] = reader[0] as string; - return vals; - } - } -} \ No newline at end of file diff --git a/src/Migrator.Tests/Providers/TransformationProviderConstraintBase.cs b/src/Migrator.Tests/Providers/TransformationProviderConstraintBase.cs deleted file mode 100644 index ce499d9c..00000000 --- a/src/Migrator.Tests/Providers/TransformationProviderConstraintBase.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System.Data; -using Migrator.Framework; -using NUnit.Framework; - -namespace Migrator.Tests.Providers -{ - /// - /// Base class for Provider tests for all tests including constraint oriented tests. - /// - public class TransformationProviderConstraintBase : TransformationProviderBase - { - public void AddForeignKey() - { - AddTableWithPrimaryKey(); - _provider.AddForeignKey("FK_Test_TestTwo", "TestTwo", "TestId", "Test", "Id"); - } - - public void AddPrimaryKey() - { - AddTable(); - _provider.AddPrimaryKey("PK_Test", "Test", "Id"); - } - - public void AddUniqueConstraint() - { - _provider.AddUniqueConstraint("UN_Test_TestTwo", "TestTwo", "TestId"); - } - - public void AddMultipleUniqueConstraint() - { - _provider.AddUniqueConstraint("UN_Test_TestTwo", "TestTwo", "Id", "TestId"); - } - - public void AddCheckConstraint() - { - _provider.AddCheckConstraint("CK_TestTwo_TestId", "TestTwo", "TestId>5"); - } - - [Test] - public void CanAddPrimaryKey() - { - AddPrimaryKey(); - Assert.IsTrue(_provider.PrimaryKeyExists("Test", "PK_Test")); - } - - [Test] - public void AddIndexedColumn() - { - _provider.AddColumn("TestTwo", "Test", DbType.String, 50, ColumnProperty.Indexed); - } - - [Test] - public void AddUniqueColumn() - { - _provider.AddColumn("TestTwo", "Test", DbType.String, 50, ColumnProperty.Unique); - } - - [Test] - public void CanAddForeignKey() - { - AddForeignKey(); - Assert.IsTrue(_provider.ConstraintExists("TestTwo", "FK_Test_TestTwo")); - } - - [Test] - public virtual void CanAddUniqueConstraint() - { - AddUniqueConstraint(); - Assert.IsTrue(_provider.ConstraintExists("TestTwo", "UN_Test_TestTwo")); - } - - [Test] - public virtual void CanAddMultipleUniqueConstraint() - { - AddMultipleUniqueConstraint(); - Assert.IsTrue(_provider.ConstraintExists("TestTwo", "UN_Test_TestTwo")); - } - - [Test] - public virtual void CanAddCheckConstraint() - { - AddCheckConstraint(); - Assert.IsTrue(_provider.ConstraintExists("TestTwo", "CK_TestTwo_TestId")); - } - - [Test] - public void RemoveForeignKey() - { - AddForeignKey(); - _provider.RemoveForeignKey("TestTwo", "FK_Test_TestTwo"); - Assert.IsFalse(_provider.ConstraintExists("TestTwo", "FK_Test_TestTwo")); - } - - [Test] - public void RemoveUniqueConstraint() - { - AddUniqueConstraint(); - _provider.RemoveConstraint("TestTwo", "UN_Test_TestTwo"); - Assert.IsFalse(_provider.ConstraintExists("TestTwo", "UN_Test_TestTwo")); - } - - [Test] - public virtual void RemoveCheckConstraint() - { - AddCheckConstraint(); - _provider.RemoveConstraint("TestTwo", "CK_TestTwo_TestId"); - Assert.IsFalse(_provider.ConstraintExists("TestTwo", "CK_TestTwo_TestId")); - } - - [Test] - public void RemoveUnexistingForeignKey() - { - AddForeignKey(); - _provider.RemoveForeignKey("abc", "FK_Test_TestTwo"); - _provider.RemoveForeignKey("abc", "abc"); - _provider.RemoveForeignKey("Test", "abc"); - } - - [Test] - public void ConstraintExist() - { - AddForeignKey(); - Assert.IsTrue(_provider.ConstraintExists("TestTwo", "FK_Test_TestTwo")); - Assert.IsFalse(_provider.ConstraintExists("abc", "abc")); - } - - [Test] - public void AddTableWithCompoundPrimaryKey() - { - _provider.AddTable("Test", - new Column("PersonId", DbType.Int32, ColumnProperty.PrimaryKey), - new Column("AddressId", DbType.Int32, ColumnProperty.PrimaryKey) - ); - Assert.IsTrue(_provider.TableExists("Test"), "Table doesn't exist"); - Assert.IsTrue(_provider.PrimaryKeyExists("Test", "PK_Test"), "Constraint doesn't exist"); - } - - [Test] - public void AddTableWithCompoundPrimaryKeyShouldKeepNullForOtherProperties() - { - _provider.AddTable("Test", - new Column("PersonId", DbType.Int32, ColumnProperty.PrimaryKey), - new Column("AddressId", DbType.Int32, ColumnProperty.PrimaryKey), - new Column("Name", DbType.String, 30, ColumnProperty.Null) - ); - Assert.IsTrue(_provider.TableExists("Test"), "Table doesn't exist"); - Assert.IsTrue(_provider.PrimaryKeyExists("Test", "PK_Test"), "Constraint doesn't exist"); - - Column column = _provider.GetColumnByName("Test", "Name"); - Assert.IsNotNull(column); - Assert.IsTrue((column.ColumnProperty & ColumnProperty.Null) == ColumnProperty.Null); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Tests/SchemaBuilderTests.cs b/src/Migrator.Tests/SchemaBuilderTests.cs index 5413b558..f0adc61c 100644 --- a/src/Migrator.Tests/SchemaBuilderTests.cs +++ b/src/Migrator.Tests/SchemaBuilderTests.cs @@ -1,93 +1,91 @@ using System.Data; -using Migrator.Framework; -using Migrator.Framework.SchemaBuilder; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Framework.SchemaBuilder; using NUnit.Framework; -using ForeignKeyConstraint = Migrator.Framework.ForeignKeyConstraint; -namespace Migrator.Tests +namespace Migrator.Tests; + +[TestFixture] +public class SchemaBuilderTests { - [TestFixture] - public class SchemaBuilderTests - { - private SchemaBuilder _schemaBuilder; - - [SetUp] - public void Setup() - { - _schemaBuilder = new SchemaBuilder(); - _schemaBuilder.AddTable("SomeTable"); - } - - [Test] - public void Can_AddTable() - { - _schemaBuilder.AddTable("MyUserTable"); - //Assert.AreEqual("MyUserTable", _schemaBuilder.Expressions.ElementAt(0)); - } - - [Test] - public void Can_AddColumn() - { - string columnName = "MyUserId"; - - _schemaBuilder - .AddColumn(columnName); - - //Assert.IsTrue(_schemaBuilder.Columns.Count == 1); - //Assert.AreEqual(columnName, _schemaBuilder.Columns[0].Name); - } - - [Test] - public void Can_chain_AddColumn_OfType() - { - _schemaBuilder - .AddColumn("SomeColumn") - .OfType(DbType.Int32); - - //Assert.AreEqual(DbType.Int32, _schemaBuilder.Columns[0].Type, "Column.Type was not as expected"); - } - - [Test] - public void Can_chain_AddColumn_WithProperty() - { - _schemaBuilder - .AddColumn("MyColumn") - .OfType(DbType.Int32) - .WithProperty(ColumnProperty.PrimaryKey); - - //Assert.IsTrue(_schemaBuilder.Columns[0].IsPrimaryKey); - } - - [Test] - public void Can_chain_AddColumn_WithSize() - { - _schemaBuilder - .AddColumn("column") - .WithSize(100); - - //Assert.AreEqual(100, _schemaBuilder.Columns[0].Size); - } - - [Test] - public void Can_chain_AddColumn_WithDefaultValue() - { - _schemaBuilder - .AddColumn("something") - .OfType(DbType.Int32) - .WithDefaultValue("default value"); - - //Assert.AreEqual("default value", _schemaBuilder.Columns[0].DefaultValue); - } - - [Test] - public void Can_chain_AddTable_WithForeignKey() - { - _schemaBuilder - .AddColumn("MyColumnThatIsForeignKey") - .AsForeignKey().ReferencedTo("PrimaryKeyTable", "PrimaryKeyColumn").WithConstraint(ForeignKeyConstraint.NoAction); - - //Assert.IsTrue(_schemaBuilder.Columns[0].ColumnProperty == ColumnProperty.ForeignKey); - } - } + private SchemaBuilder _schemaBuilder; + + [SetUp] + public void Setup() + { + _schemaBuilder = new SchemaBuilder(); + _schemaBuilder.AddTable("SomeTable"); + } + + [Test] + public void Can_AddTable() + { + _schemaBuilder.AddTable("MyUserTable"); + //Assert.That("MyUserTable", _schemaBuilder.Expressions.ElementAt(0)); + } + + [Test] + public void Can_AddColumn() + { + var columnName = "MyUserId"; + + _schemaBuilder + .AddColumn(columnName); + + //Assert.IsTrue(_schemaBuilder.Columns.Count == 1); + //Assert.That(columnName, _schemaBuilder.Columns[0].Name); + } + + [Test] + public void Can_chain_AddColumn_OfType() + { + _schemaBuilder + .AddColumn("SomeColumn") + .OfType(DbType.Int32); + + //Assert.That(DbType.Int32, _schemaBuilder.Columns[0].Type, "Column.Type was not as expected"); + } + + [Test] + public void Can_chain_AddColumn_WithProperty() + { + _schemaBuilder + .AddColumn("MyColumn") + .OfType(DbType.Int32) + .WithProperty(ColumnProperty.PrimaryKey); + + //Assert.IsTrue(_schemaBuilder.Columns[0].IsPrimaryKey); + } + + [Test] + public void Can_chain_AddColumn_WithSize() + { + _schemaBuilder + .AddColumn("column") + .WithSize(100); + + //Assert.That(100, _schemaBuilder.Columns[0].Size); + } + + [Test] + public void Can_chain_AddColumn_WithDefaultValue() + { + _schemaBuilder + .AddColumn("something") + .OfType(DbType.Int32) + .WithDefaultValue("default value"); + + //Assert.That("default value", _schemaBuilder.Columns[0].DefaultValue); + } + + [Test] + public void Can_chain_AddTable_WithForeignKey() + { + _schemaBuilder + .AddColumn("MyColumnThatIsForeignKey") + .AsForeignKey().ReferencedTo("PrimaryKeyTable", "PrimaryKeyColumn").WithConstraint(ForeignKeyConstraintType.NoAction); + + //Assert.IsTrue(_schemaBuilder.Columns[0].ColumnProperty == ColumnProperty.ForeignKey); + } } diff --git a/src/Migrator.Tests/ScriptEngineTests.cs b/src/Migrator.Tests/ScriptEngineTests.cs deleted file mode 100644 index 568c5458..00000000 --- a/src/Migrator.Tests/ScriptEngineTests.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.IO; -using System.Reflection; -using Migrator.Compile; -using NUnit.Framework; - -namespace Migrator.Tests -{ - [TestFixture] - public class ScriptEngineTests - { - [Test] - public void CanCompileAssemblies() - { - var engine = new ScriptEngine(); - - // This should let it work on windows or mono/unix I hope - string dataPath = Path.Combine(Path.Combine("..", Path.Combine("src", "Migrator.Tests")), "Data"); - - Assembly asm = engine.Compile(dataPath); - Assert.IsNotNull(asm); - - var loader = new MigrationLoader(null, asm, false); - Assert.AreEqual(2, loader.LastVersion); - - Assert.AreEqual(2, MigrationLoader.GetMigrationTypes(asm).Count); - } - } -} \ No newline at end of file diff --git a/src/Migrator.Tests/Settings/Config/ConnectionIds.cs b/src/Migrator.Tests/Settings/Config/ConnectionIds.cs new file mode 100644 index 00000000..e4b2deb9 --- /dev/null +++ b/src/Migrator.Tests/Settings/Config/ConnectionIds.cs @@ -0,0 +1,10 @@ +namespace Migrator.Tests.Settings.Config; + +public static class DatabaseConnectionConfigIds +{ + public const string OracleId = "Oracle"; + public const string MySQLId = "MySQL"; + public const string PostgreSQL = "PostgreSQL"; + public const string SQLiteId = "SQLite"; + public const string SQLServerId = "SQLServer"; +} diff --git a/src/Migrator.Tests/Settings/ConfigurationReader.cs b/src/Migrator.Tests/Settings/ConfigurationReader.cs new file mode 100644 index 00000000..0767fff8 --- /dev/null +++ b/src/Migrator.Tests/Settings/ConfigurationReader.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Configuration; +using Migrator.Tests.Settings.Interfaces; +using Migrator.Tests.Settings.Models; + +namespace Migrator.Tests.Settings; + +/// +/// Reads the configuration from appsettings. +/// +public class ConfigurationReader() : IConfigurationReader +{ + private const string AspnetCoreVariableString = "ASPNETCORE_ENVIRONMENT"; + + /// + /// Gets the database connection config by its ID. + /// + /// Use one of the IDs in + /// + public DatabaseConnectionConfig GetDatabaseConnectionConfigById(string id) + { + var configurationRoot = GetConfigurationRoot(); + var aspNetCoreVariable = GetAspNetCoreEnvironmentVariable(); + + var databaseConnectionConfigs = configurationRoot.GetSection("DatabaseConnectionConfigs") + .Get>() ?? throw new KeyNotFoundException(); + + return databaseConnectionConfigs.SingleOrDefault(x => x.Id == id); + } + + /// + /// Gets the configuration root. Currently it is not used for production therefore we do not use appsettings.json. + /// Your personal appsettings.Development.json will be used if your ASPNETCORE_ENVIRONMENT env variable is set to "Development". + /// + /// + public IConfigurationRoot GetConfigurationRoot() + { + + var builder = new ConfigurationBuilder() + .SetBasePath(AppDomain.CurrentDomain.BaseDirectory) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: false); + var aspNetCoreVariableName = GetAspNetCoreEnvironmentVariable(); + + if (!string.IsNullOrEmpty(aspNetCoreVariableName)) + { + builder = builder.AddJsonFile($"appsettings.{aspNetCoreVariableName}.json", optional: true, reloadOnChange: false); + } + + return builder.Build(); + } + + private static string GetAspNetCoreEnvironmentVariable() + { + var aspNetCoreVariable = Environment.GetEnvironmentVariable(AspnetCoreVariableString, EnvironmentVariableTarget.Process); + + if (string.IsNullOrEmpty(aspNetCoreVariable)) + { + aspNetCoreVariable = Environment.GetEnvironmentVariable(AspnetCoreVariableString, EnvironmentVariableTarget.User); + } + else if (string.IsNullOrEmpty(aspNetCoreVariable)) + { + aspNetCoreVariable = Environment.GetEnvironmentVariable(AspnetCoreVariableString, EnvironmentVariableTarget.Machine); + } + + return aspNetCoreVariable; + } +} diff --git a/src/Migrator.Tests/Settings/Interfaces/IConfigurationReader.cs b/src/Migrator.Tests/Settings/Interfaces/IConfigurationReader.cs new file mode 100644 index 00000000..20a535ca --- /dev/null +++ b/src/Migrator.Tests/Settings/Interfaces/IConfigurationReader.cs @@ -0,0 +1,8 @@ +using Migrator.Tests.Settings.Models; + +namespace Migrator.Tests.Settings.Interfaces; + +public interface IConfigurationReader +{ + DatabaseConnectionConfig GetDatabaseConnectionConfigById(string id); +} \ No newline at end of file diff --git a/src/Migrator.Tests/Settings/Models/DatabaseConnectionConfig.cs b/src/Migrator.Tests/Settings/Models/DatabaseConnectionConfig.cs new file mode 100644 index 00000000..e41c7ea8 --- /dev/null +++ b/src/Migrator.Tests/Settings/Models/DatabaseConnectionConfig.cs @@ -0,0 +1,19 @@ +namespace Migrator.Tests.Settings.Models; + +public class DatabaseConnectionConfig +{ + /// + /// Gets or sets the connection string. + /// + public string ConnectionString { get; set; } + + /// + /// Gets or sets the connection identifier. + /// + public string Id { get; set; } + + /// + /// Gets or sets the schema name. + /// + public string Schema { get; set; } +} \ No newline at end of file diff --git a/src/Migrator.Tests/Support/Inflector.cs b/src/Migrator.Tests/Support/Inflector.cs new file mode 100644 index 00000000..012b3ab9 --- /dev/null +++ b/src/Migrator.Tests/Support/Inflector.cs @@ -0,0 +1,167 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace DotNetProjects.Migrator.Framework.Support; + +public class Inflector +{ + private static readonly List plurals = new List(); + private static readonly List singulars = new List(); + private static readonly List uncountables = new List(); + + private Inflector() + { + } + + static Inflector() + { + AddPlural("$", "s"); + AddPlural("s$", "s"); + AddPlural("(ax|test)is$", "$1es"); + AddPlural("(octop|vir)us$", "$1i"); + AddPlural("(alias|status)$", "$1es"); + AddPlural("(bu)s$", "$1ses"); + AddPlural("(buffal|tomat)o$", "$1oes"); + AddPlural("([ti])um$", "$1a"); + AddPlural("sis$", "ses"); + AddPlural("(?:([^f])fe|([lr])f)$", "$1$2ves"); + AddPlural("(hive)$", "$1s"); + AddPlural("([^aeiouy]|qu)y$", "$1ies"); + AddPlural("(x|ch|ss|sh)$", "$1es"); + AddPlural("(matr|vert|ind)ix|ex$", "$1ices"); + AddPlural("([m|l])ouse$", "$1ice"); + AddPlural("^(ox)$", "$1en"); + AddPlural("(quiz)$", "$1zes"); + AddSingular("s$", ""); + AddSingular("(n)ews$", "$1ews"); + AddSingular("([ti])a$", "$1um"); + AddSingular("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$", "$1$2sis"); + AddSingular("(^analy)ses$", "$1sis"); + AddSingular("([^f])ves$", "$1fe"); + AddSingular("(hive)s$", "$1"); + AddSingular("(tive)s$", "$1"); + AddSingular("([lr])ves$", "$1f"); + AddSingular("([^aeiouy]|qu)ies$", "$1y"); + AddSingular("(s)eries$", "$1eries"); + AddSingular("(m)ovies$", "$1ovie"); + AddSingular("(x|ch|ss|sh)es$", "$1"); + AddSingular("([m|l])ice$", "$1ouse"); + AddSingular("(bus)es$", "$1"); + AddSingular("(o)es$", "$1"); + AddSingular("(shoe)s$", "$1"); + AddSingular("(cris|ax|test)es$", "$1is"); + AddSingular("(octop|vir)i$", "$1us"); + AddSingular("(alias|status)es$", "$1"); + AddSingular("^(ox)en", "$1"); + AddSingular("(vert|ind)ices$", "$1ex"); + AddSingular("(matr)ices$", "$1ix"); + AddSingular("(quiz)zes$", "$1"); + AddIrregular("person", "people"); + AddIrregular("man", "men"); + AddIrregular("child", "children"); + AddIrregular("sex", "sexes"); + AddIrregular("move", "moves"); + AddUncountable("equipment"); + AddUncountable("information"); + AddUncountable("rice"); + AddUncountable("money"); + AddUncountable("species"); + AddUncountable("series"); + AddUncountable("fish"); + AddUncountable("sheep"); + } + + private class Rule + { + private readonly Regex regex; + private readonly string replacement; + + public Rule(string pattern, string replacement) + { + regex = new Regex(pattern, RegexOptions.IgnoreCase); + this.replacement = replacement; + } + + public string Apply(string word) + { + if (!regex.IsMatch(word)) + { + return null; + } + + return regex.Replace(word, replacement); + } + } + + /// + /// Return the plural of a word. + /// + /// The singular form + /// The plural form of + public static string Pluralize(string word) + { + return ApplyRules(plurals, word); + } + + /// + /// Return the singular of a word. + /// + /// The plural form + /// The singular form of + public static string Singularize(string word) + { + return ApplyRules(singulars, word); + } + + /// + /// Capitalizes a word. + /// + /// The word to be capitalized. + /// capitalized. + public static string Capitalize(string word) + { + return word.Substring(0, 1).ToUpper() + word.Substring(1).ToLower(); + } + + private static void AddIrregular(string singular, string plural) + { + AddPlural("(" + singular[0] + ")" + singular.Substring(1) + "$", "$1" + plural.Substring(1)); + AddSingular("(" + plural[0] + ")" + plural.Substring(1) + "$", "$1" + singular.Substring(1)); + } + + private static void AddUncountable(string word) + { + uncountables.Add(word.ToLower()); + } + + private static void AddPlural(string rule, string replacement) + { + plurals.Add(new Rule(rule, replacement)); + } + + private static void AddSingular(string rule, string replacement) + { + singulars.Add(new Rule(rule, replacement)); + } + + private static string ApplyRules(IList rules, string word) + { + var result = word; + + if (!uncountables.Contains(word.ToLower())) + { + for (var i = rules.Count - 1; i >= 0; i--) + { + var rule = (Rule)rules[i]; + + if ((result = rule.Apply(word)) != null) + { + break; + } + } + } + + return result; + } +} diff --git a/src/Migrator.Tests/Support/JoiningTableTransformationProviderExtensions.cs b/src/Migrator.Tests/Support/JoiningTableTransformationProviderExtensions.cs new file mode 100644 index 00000000..ec11d4eb --- /dev/null +++ b/src/Migrator.Tests/Support/JoiningTableTransformationProviderExtensions.cs @@ -0,0 +1,79 @@ +using System.Data; + +namespace DotNetProjects.Migrator.Framework.Support; + +/// +/// A set of extension methods for the transformation provider to make it easier to +/// build many-to-many joining tables (takes care of adding the joining table and foreign +/// key constraints as necessary. +/// This functionality was useful when bootstrapping a number of projects a few years ago, but +/// now that most changes are brown-field I'm thinking of removing these methods as it's easier to maintain +/// code that creates the tables etc. directly within migration. +/// +public static class JoiningTableTransformationProviderExtensions +{ + public static ITransformationProvider AddManyToManyJoiningTable(this ITransformationProvider database, string schema, string lhsTableName, string lhsKey, string rhsTableName, string rhsKey) + { + var joiningTable = GetNameOfJoiningTable(lhsTableName, rhsTableName); + + return database.AddManyToManyJoiningTable(schema, lhsTableName, lhsKey, rhsTableName, rhsKey, joiningTable); + } + + private static string GetNameOfJoiningTable(string lhsTableName, string rhsTableName) + { + return (Inflector.Singularize(lhsTableName) ?? lhsTableName) + (Inflector.Pluralize(rhsTableName) ?? rhsTableName); + } + + public static ITransformationProvider AddManyToManyJoiningTable(this ITransformationProvider database, string schema, string lhsTableName, string lhsKey, string rhsTableName, string rhsKey, string joiningTableName) + { + var joiningTableWithSchema = TransformationProviderUtility.FormatTableName(schema, joiningTableName); + + var joinLhsKey = Inflector.Singularize(lhsTableName) + "Id"; + var joinRhsKey = Inflector.Singularize(rhsTableName) + "Id"; + + database.AddTable(joiningTableWithSchema, + new Column(joinLhsKey, DbType.Guid, ColumnProperty.NotNull), + new Column(joinRhsKey, DbType.Guid, ColumnProperty.NotNull)); + + var pkName = "PK_" + joiningTableName; + + pkName = ShortenKeyNameToBeSuitableForOracle(pkName); + + database.AddPrimaryKey(pkName, joiningTableWithSchema, joinLhsKey, joinRhsKey); + + var lhsTableNameWithSchema = TransformationProviderUtility.FormatTableName(schema, lhsTableName); + var rhsTableNameWithSchema = TransformationProviderUtility.FormatTableName(schema, rhsTableName); + + var lhsFkName = TransformationProviderUtility.CreateForeignKeyName(lhsTableName, joiningTableName); + database.AddForeignKey(lhsFkName, joiningTableWithSchema, joinLhsKey, lhsTableNameWithSchema, lhsKey, ForeignKeyConstraintType.NoAction); + + var rhsFkName = TransformationProviderUtility.CreateForeignKeyName(rhsTableName, joiningTableName); + database.AddForeignKey(rhsFkName, joiningTableWithSchema, joinRhsKey, rhsTableNameWithSchema, rhsKey, ForeignKeyConstraintType.NoAction); + + return database; + } + + private static string ShortenKeyNameToBeSuitableForOracle(string pkName) + { + return TransformationProviderUtility.AdjustNameToSize(pkName, TransformationProviderUtility.MaxLengthForForeignKeyInOracle, false); + } + + public static ITransformationProvider RemoveManyToManyJoiningTable(this ITransformationProvider database, string schema, string lhsTableName, string rhsTableName) + { + var joiningTable = GetNameOfJoiningTable(lhsTableName, rhsTableName); + return database.RemoveManyToManyJoiningTable(schema, lhsTableName, rhsTableName, joiningTable); + } + + public static ITransformationProvider RemoveManyToManyJoiningTable(this ITransformationProvider database, string schema, string lhsTableName, string rhsTableName, string joiningTableName) + { + var joiningTableNameWithSchema = TransformationProviderUtility.FormatTableName(schema, joiningTableName); + var lhsFkName = TransformationProviderUtility.CreateForeignKeyName(lhsTableName, joiningTableName); + var rhsFkName = TransformationProviderUtility.CreateForeignKeyName(rhsTableName, joiningTableName); + + database.RemoveForeignKey(joiningTableNameWithSchema, lhsFkName); + database.RemoveForeignKey(joiningTableNameWithSchema, rhsFkName); + database.RemoveTable(joiningTableNameWithSchema); + + return database; + } +} diff --git a/src/Migrator.Tests/Support/TransformationProviderUtility.cs b/src/Migrator.Tests/Support/TransformationProviderUtility.cs new file mode 100644 index 00000000..17b75b70 --- /dev/null +++ b/src/Migrator.Tests/Support/TransformationProviderUtility.cs @@ -0,0 +1,89 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace DotNetProjects.Migrator.Framework.Support; + +public static class TransformationProviderUtility +{ + public const int MaxLengthForForeignKeyInOracle = 30; + //static readonly ILog log = LogManager.GetLogger(typeof (TransformationProviderUtility)); + private static readonly string[] CommonWords = ["Test"]; + + public static string CreateForeignKeyName(string tableName, string foreignKeyTableName) + { + var fkName = string.Format("FK_{0}_{1}", tableName, foreignKeyTableName); + + return AdjustNameToSize(fkName, MaxLengthForForeignKeyInOracle, true); + } + + public static string AdjustNameToSize(string name, int totalCharacters, bool removeCommmonWords) + { + var adjustedName = name; + + if (adjustedName.Length > totalCharacters) + { + if (removeCommmonWords) + { + adjustedName = RemoveCommonWords(adjustedName); + } + } + + if (adjustedName.Length > totalCharacters) + { + adjustedName = adjustedName.Substring(0, totalCharacters); + } + + if (name != adjustedName) + { + //log.WarnFormat("Name has been truncated from: {0} to: {1}", name, adjustedName); + } + + return adjustedName; + } + + private static string RemoveCommonWords(string adjustedName) + { + foreach (var word in CommonWords) + { + if (adjustedName.Contains(word)) + { + adjustedName = adjustedName.Replace(word, string.Empty); + } + } + return adjustedName; + } + + public static string FormatTableName(string schema, string tableName) + { + return string.IsNullOrEmpty(schema) ? tableName : string.Format("{0}.{1}", schema, tableName); + } + + public static string GetQualifiedResourcePath(Assembly assembly, string resourceName) + { + var resources = assembly.GetManifestResourceNames(); + + //resource full name is in format `namespace.resourceName` + var sqlScriptParts = resourceName.Split('.').Reverse().ToArray(); +#if NETSTANDARD + Func isNameMatch = x => x.Split('.').Reverse().Take(sqlScriptParts.Length).SequenceEqual(sqlScriptParts, StringComparer.CurrentCultureIgnoreCase); +#else + Func isNameMatch = x => x.Split('.').Reverse().Take(sqlScriptParts.Length).SequenceEqual(sqlScriptParts, StringComparer.InvariantCultureIgnoreCase); +#endif + + //string result = null; + var foundResources = resources.Where(isNameMatch).ToArray(); + + if (foundResources.Length == 0) + { + throw new InvalidOperationException(string.Format("Could not find resource named {0} in assembly {1}", resourceName, assembly.FullName)); + } + + if (foundResources.Length > 1) + { + throw new InvalidOperationException(string.Format(@"Could not find unique resource named {0} in assembly {1}.Possible candidates are: {2}", resourceName, assembly.FullName, string.Join(Environment.NewLine + "\t", foundResources))); + } + + return foundResources[0]; + } +} diff --git a/src/Migrator.Tests/Tools/SchemaDumperTest.cs b/src/Migrator.Tests/Tools/SchemaDumperTest.cs index d982f4ef..aa6e13d1 100644 --- a/src/Migrator.Tests/Tools/SchemaDumperTest.cs +++ b/src/Migrator.Tests/Tools/SchemaDumperTest.cs @@ -1,59 +1,48 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.Configuration; - -using Migrator.Providers; -using Migrator.Tools; -using NUnit.Framework; - -namespace Migrator.Tests.Tools -{ - [TestFixture] - [Category("MySql")] - public class SchemaDumperTest - { - [Test] - public void Dump() - { - string constr = ConfigurationManager.AppSettings["MySqlConnectionString"]; - - if (constr == null) - throw new ArgumentNullException("MySqlConnectionString", "No config file"); - - var dumper = new SchemaDumper(ProviderTypes.Mysql, constr, null); - string output = dumper.Dump(); - - Assert.IsNotNull(output); - } - } - [TestFixture, Category("SqlServer2005")] - public class SchemaDumperSqlServerTest - { - [Test] - public void Dump() - { - - string constr = ConfigurationManager.AppSettings["SqlServerConnectionString"]; - - if (constr == null) - throw new ArgumentNullException("SqlServerConnectionString", "No config file"); - - SchemaDumper dumper = new SchemaDumper(ProviderTypes.SqlServer, constr, ""); - string output = dumper.Dump(); - - Assert.IsNotNull(output); - } - } -} \ No newline at end of file +//using System; +//using System.Configuration; +//using Migrator.Providers; +//using Migrator.Tools; +//using NUnit.Framework; + +//namespace Migrator.Tests.Tools; + +//[TestFixture] +//[Category("MySql")] +//public class SchemaDumperTest +//{ +// [Test] +// public void Dump() +// { +// var constr = ConfigurationManager.AppSettings["MySqlConnectionString"]; + +// if (constr == null) +// { +// throw new ArgumentNullException("MySqlConnectionString", "No config file"); +// } + +// var dumper = new SchemaDumper(ProviderTypes.Mysql, constr, null); +// var output = dumper.GetDump(); + +// Assert.That(output, Is.Not.Null); +// } +//} + +//[TestFixture, Category("SqlServer2005")] +//public class SchemaDumperSqlServerTest +//{ +// [Test] +// public void Dump() +// { +// var constr = ConfigurationManager.AppSettings["SqlServerConnectionString"]; + +// if (constr == null) +// { +// throw new ArgumentNullException("SqlServerConnectionString", "No config file"); +// } + +// var dumper = new SchemaDumper(ProviderTypes.SqlServer, constr, ""); +// var output = dumper.GetDump(); + +// Assert.That(output, Is.Not.Null); +// } +//} diff --git a/src/Migrator.Tests/Tools/SqlFileLoggerTest.cs b/src/Migrator.Tests/Tools/SqlFileLoggerTest.cs index 6d6801bf..01424cd6 100644 --- a/src/Migrator.Tests/Tools/SqlFileLoggerTest.cs +++ b/src/Migrator.Tests/Tools/SqlFileLoggerTest.cs @@ -2,56 +2,55 @@ using System.Collections.Generic; using System.IO; using System.Text; -using Migrator.Framework.Loggers; +using DotNetProjects.Migrator.Framework.Loggers; using NUnit.Framework; -namespace Migrator.Tests.Tools +namespace Migrator.Tests.Tools; + +[TestFixture] +public class SqlFileLoggerTest { - [TestFixture] - public class SqlFileLoggerTest - { - #region Setup/Teardown - - [SetUp] - public void Setup() - { - _sb = new StringBuilder(); - _logger = new SqlScriptFileLogger(Logger.ConsoleLogger(), new StringWriter(_sb)); - } - - #endregion - - public SqlScriptFileLogger _logger; - public StringBuilder _sb; - - [Test] - public void CanRunTheRest() - { - var appliedVersions = new List(); - appliedVersions.Add(1L); - appliedVersions.Add(2L); - appliedVersions.Add(3L); - - _logger.ApplyingDBChange("some_change"); - _logger.Log("log something"); - _logger.Warn("danger will"); - _logger.Trace("trace"); - _logger.Started(appliedVersions, 123L); - _logger.MigrateUp(123L, "foo"); - _logger.MigrateDown(123L, "bar"); - _logger.Skipping(123L); - _logger.RollingBack(123L); - _logger.Exception(123L, "baz", new Exception()); - _logger.Finished(appliedVersions, 123L); - - Assert.AreEqual("some_change" + Environment.NewLine, _sb.ToString()); - } - - [Test] - public void CanWriteSql() - { - _logger.ApplyingDBChange("some_change"); - Assert.AreEqual("some_change" + Environment.NewLine, _sb.ToString()); - } - } + #region Setup/Teardown + + [SetUp] + public void Setup() + { + _sb = new StringBuilder(); + _logger = new SqlScriptFileLogger(Logger.ConsoleLogger(), new StringWriter(_sb)); + } + + #endregion + + public SqlScriptFileLogger _logger; + public StringBuilder _sb; + + [Test] + public void CanRunTheRest() + { + var appliedVersions = new List(); + appliedVersions.Add(1L); + appliedVersions.Add(2L); + appliedVersions.Add(3L); + + _logger.ApplyingDBChange("some_change"); + _logger.Log("log something"); + _logger.Warn("danger will"); + _logger.Trace("trace"); + _logger.Started(appliedVersions, 123L); + _logger.MigrateUp(123L, "foo"); + _logger.MigrateDown(123L, "bar"); + _logger.Skipping(123L); + _logger.RollingBack(123L); + _logger.Exception(123L, "baz", new Exception()); + _logger.Finished(appliedVersions, 123L); + + Assert.That("some_change" + Environment.NewLine, Is.EqualTo(_sb.ToString())); + } + + [Test] + public void CanWriteSql() + { + _logger.ApplyingDBChange("some_change"); + Assert.That("some_change" + Environment.NewLine, Is.EqualTo(_sb.ToString())); + } } \ No newline at end of file diff --git a/src/Migrator.Tests/app.config b/src/Migrator.Tests/app.config deleted file mode 100644 index c05a0346..00000000 --- a/src/Migrator.Tests/app.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/Migrator.Tests/appsettings.json b/src/Migrator.Tests/appsettings.json new file mode 100644 index 00000000..b2173754 --- /dev/null +++ b/src/Migrator.Tests/appsettings.json @@ -0,0 +1,24 @@ +{ + "DatabaseConnectionConfigs": [ + { + "Id": "SQLite", + "ConnectionString": "Data Source=:memory:;version=3" + }, + { + "Id": "SQLServer", + "ConnectionString": "Data Source=localhost;Initial Catalog=Whatever;user=sa;pwd=YourStrong@Passw0rd;encrypt=false" + }, + { + "Id": "PostgreSQL", + "ConnectionString": "Server=localhost;Port=5432;Database=postgres;User Id=testuser;Password=testpass;Pooling=false;" + }, + { + "Id": "Oracle", + "ConnectionString": "Data Source=//localhost:1521/FREEPDB1;User Id=k;Password=k;" + }, + { + "Id": "MySQL", + "ConnectionString": "Server=127.0.0.1;Port=3306;Database=testdb;User Id=testuser;Password=testpass;" + } + ] +} \ No newline at end of file diff --git a/src/Migrator.Tests/packages.config b/src/Migrator.Tests/packages.config deleted file mode 100644 index fe5c4b4c..00000000 --- a/src/Migrator.Tests/packages.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/Migrator/AssemblyInfo.cs b/src/Migrator/AssemblyInfo.cs index 534cbb60..93009494 100644 --- a/src/Migrator/AssemblyInfo.cs +++ b/src/Migrator/AssemblyInfo.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; [assembly: AssemblyTitle("DotNetProjects.Migrator")] [assembly: AssemblyDescription("DotNetProjects.Migrator Core")] diff --git a/src/Migrator/BaseMigrate.cs b/src/Migrator/BaseMigrate.cs index d02cff68..a8d9ea69 100644 --- a/src/Migrator/BaseMigrate.cs +++ b/src/Migrator/BaseMigrate.cs @@ -1,112 +1,111 @@ using System.Collections.Generic; -using Migrator.Framework; +using DotNetProjects.Migrator.Framework; -namespace Migrator +namespace DotNetProjects.Migrator; + +public abstract class BaseMigrate { - public abstract class BaseMigrate - { - protected readonly ITransformationProvider _provider; - protected List _availableMigrations; - protected long _current; - protected bool _dryrun; - protected ILogger _logger; - protected List _original; - - protected BaseMigrate(List availableMigrations, ITransformationProvider provider, ILogger logger) - { - _provider = provider; - _availableMigrations = availableMigrations; - _original = new List(_provider.AppliedMigrations.ToArray()); //clone - _logger = logger; - } - - public List AppliedVersions - { - get { return _original; } - } - - public virtual long Current - { - get { return _current; } - protected set { _current = value; } - } - - public virtual bool DryRun - { - get { return _dryrun; } - set { _dryrun = value; } - } - - public abstract long Previous { get; } - public abstract long Next { get; } - - public static BaseMigrate GetInstance(List availableMigrations, ITransformationProvider provider, ILogger logger) - { - return new MigrateAnywhere(availableMigrations, provider, logger); - } - - public void Iterate() - { - Current = Next; - } - - public abstract bool Continue(long targetVersion); - - public abstract void Migrate(IMigration migration); - - /// - /// Finds the next migration available to be applied. Only returns - /// migrations that have NOT already been applied. - /// - /// The migration number of the next available Migration. - protected long NextMigration() - { - // Start searching at the current index - int migrationSearch = _availableMigrations.IndexOf(Current) + 1; - - // See if we can find a migration that matches the requirement - while (migrationSearch < _availableMigrations.Count - && _provider.AppliedMigrations.Contains(_availableMigrations[migrationSearch])) - { - migrationSearch++; - } - - // did we exhaust the list? - if (migrationSearch == _availableMigrations.Count) - { - // we're at the last one. Done! - return _availableMigrations[migrationSearch - 1] + 1; - } - // found one. - return _availableMigrations[migrationSearch]; - } - - /// - /// Finds the previous migration that has been applied. Only returns - /// migrations that HAVE already been applied. - /// - /// The most recently applied Migration. - protected long PreviousMigration() - { - // Start searching at the current index - int migrationSearch = _availableMigrations.IndexOf(Current) - 1; - - // See if we can find a migration that matches the requirement - while (migrationSearch > -1 - && !_provider.AppliedMigrations.Contains(_availableMigrations[migrationSearch])) - { - migrationSearch--; - } - - // did we exhaust the list? - if (migrationSearch < 0) - { - // we're at the first one. Done! - return 0; - } - - // found one. - return _availableMigrations[migrationSearch]; - } - } + protected readonly ITransformationProvider _provider; + protected List _availableMigrations; + protected long _current; + protected bool _dryrun; + protected ILogger _logger; + protected List _original; + + protected BaseMigrate(List availableMigrations, ITransformationProvider provider, ILogger logger) + { + _provider = provider; + _availableMigrations = availableMigrations; + _original = new List(_provider.AppliedMigrations.ToArray()); //clone + _logger = logger; + } + + public List AppliedVersions + { + get { return _original; } + } + + public virtual long Current + { + get { return _current; } + protected set { _current = value; } + } + + public virtual bool DryRun + { + get { return _dryrun; } + set { _dryrun = value; } + } + + public abstract long Previous { get; } + public abstract long Next { get; } + + public static BaseMigrate GetInstance(List availableMigrations, ITransformationProvider provider, ILogger logger) + { + return new MigrateAnywhere(availableMigrations, provider, logger); + } + + public void Iterate() + { + Current = Next; + } + + public abstract bool Continue(long targetVersion); + + public abstract void Migrate(IMigration migration); + + /// + /// Finds the next migration available to be applied. Only returns + /// migrations that have NOT already been applied. + /// + /// The migration number of the next available Migration. + protected long NextMigration() + { + // Start searching at the current index + var migrationSearch = _availableMigrations.IndexOf(Current) + 1; + + // See if we can find a migration that matches the requirement + while (migrationSearch < _availableMigrations.Count + && _provider.AppliedMigrations.Contains(_availableMigrations[migrationSearch])) + { + migrationSearch++; + } + + // did we exhaust the list? + if (migrationSearch == _availableMigrations.Count) + { + // we're at the last one. Done! + return _availableMigrations[migrationSearch - 1] + 1; + } + // found one. + return _availableMigrations[migrationSearch]; + } + + /// + /// Finds the previous migration that has been applied. Only returns + /// migrations that HAVE already been applied. + /// + /// The most recently applied Migration. + protected long PreviousMigration() + { + // Start searching at the current index + var migrationSearch = _availableMigrations.IndexOf(Current) - 1; + + // See if we can find a migration that matches the requirement + while (migrationSearch > -1 + && !_provider.AppliedMigrations.Contains(_availableMigrations[migrationSearch])) + { + migrationSearch--; + } + + // did we exhaust the list? + if (migrationSearch < 0) + { + // we're at the first one. Done! + return 0; + } + + // found one. + return _availableMigrations[migrationSearch]; + } } \ No newline at end of file diff --git a/src/Migrator/Compile/ScriptEngine.cs b/src/Migrator/Compile/ScriptEngine.cs deleted file mode 100644 index 01a10d4b..00000000 --- a/src/Migrator/Compile/ScriptEngine.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.CodeDom.Compiler; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using Migrator.Framework; - -namespace Migrator.Compile -{ - public class ScriptEngine - { - readonly string _codeType = "csharp"; - readonly CodeDomProvider _provider; - public readonly string[] extraReferencedAssemblies; - - public ScriptEngine() : this(null, null) - { - } - - public ScriptEngine(string[] extraReferencedAssemblies) - : this(null, extraReferencedAssemblies) - { - } - - public ScriptEngine(string codeType, string[] extraReferencedAssemblies) - { - if (!String.IsNullOrEmpty(codeType)) - _codeType = codeType; - this.extraReferencedAssemblies = extraReferencedAssemblies; - - // There is currently no way to generically create a CodeDomProvider and have it work with .NET 3.5 - _provider = CodeDomProvider.CreateProvider(_codeType); - } - - public Assembly Compile(string directory) - { - string[] files = GetFilesRecursive(directory); - Console.Out.WriteLine("Compiling:"); - Array.ForEach(files, file => Console.Out.WriteLine(file)); - - return Compile(files); - } - - string[] GetFilesRecursive(string directory) - { - FileInfo[] files = GetFilesRecursive(new DirectoryInfo(directory)); - var fileNames = new string[files.Length]; - for (int i = 0; i < files.Length; i ++) - { - fileNames[i] = files[i].FullName; - } - return fileNames; - } - - FileInfo[] GetFilesRecursive(DirectoryInfo d) - { - var files = new List(); - files.AddRange(d.GetFiles(String.Format("*.{0}", _provider.FileExtension))); - DirectoryInfo[] subDirs = d.GetDirectories(); - if (subDirs.Length > 0) - { - foreach (DirectoryInfo subDir in subDirs) - { - files.AddRange(GetFilesRecursive(subDir)); - } - } - - return files.ToArray(); - } - - public Assembly Compile(params string[] files) - { - CompilerParameters parms = SetupCompilerParams(); - - CompilerResults compileResult = _provider.CompileAssemblyFromFile(parms, files); - if (compileResult.Errors.Count != 0) - { - foreach (CompilerError err in compileResult.Errors) - { - Console.Error.WriteLine("{0} ({1}:{2}) {3}", err.FileName, err.Line, err.Column, err.ErrorText); - } - } - return compileResult.CompiledAssembly; - } - - CompilerParameters SetupCompilerParams() - { - string migrationFrameworkPath = FrameworkAssemblyPath(); - var parms = new CompilerParameters(); - parms.CompilerOptions = "/t:library"; - parms.GenerateInMemory = true; - parms.IncludeDebugInformation = true; - parms.OutputAssembly = Path.Combine(Path.GetDirectoryName(migrationFrameworkPath), "MyMigrations.dll"); - - Console.Out.WriteLine("Output assembly: " + parms.OutputAssembly); - - // Add Default referenced assemblies - parms.ReferencedAssemblies.Add("mscorlib.dll"); - parms.ReferencedAssemblies.Add("System.dll"); - parms.ReferencedAssemblies.Add("System.Data.dll"); - parms.ReferencedAssemblies.Add(FrameworkAssemblyPath()); - if (null != extraReferencedAssemblies && extraReferencedAssemblies.Length > 0) - { - Array.ForEach(extraReferencedAssemblies, - assembly => parms.ReferencedAssemblies.Add(assembly)); - } - return parms; - } - - static string FrameworkAssemblyPath() - { - string path = typeof (MigrationAttribute).Module.FullyQualifiedName; - Console.Out.WriteLine("Framework DLL: " + path); - return path; - } - } -} \ No newline at end of file diff --git a/src/Migrator/DotNetProjects.Migrator.csproj b/src/Migrator/DotNetProjects.Migrator.csproj index 062dab0f..83bd3d3c 100644 --- a/src/Migrator/DotNetProjects.Migrator.csproj +++ b/src/Migrator/DotNetProjects.Migrator.csproj @@ -1,115 +1,35 @@ - - + + - Debug - AnyCPU - 9.0.30729 - 2.0 - {1FEE70A4-AAD7-4C60-BE60-3F7DC03A8C4D} - Library - Properties - Migrator + net9.0 + false DotNetProjects.Migrator - - - 3.5 - - - true - MigratorDotNet.snk - v4.0 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - + DotNetProjects.Migrator + latest - - true - full - false - bin\Migrator\Debug\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - - - pdbonly - true - bin\Migrator\Release\ - TRACE - prompt - 4 - AllRules.ruleset + + + true + true + true + snupkg + True + https://github.com/dotnetprojects/Migrator.NET + MPL-1.1 + 9.0.0.0 + 9.0.0.0 + 9.0.0.0 + - - - - - - - GlobalAssemblyInfo.cs - - - - - - - - - - - - - - - - default.build - - + - + - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - - - - - {5270F048-E580-486C-B14C-E5B9F6E539D4} - DotNetProjects.Migrator.Framework - - - {D58C68E4-D789-40F7-9078-C9F587D4363C} - DotNetProjects.Migrator.Providers - - - + + + $(DefineConstants);NETSTANDARD + + \ No newline at end of file diff --git a/src/Migrator/DuplicatedVersionException.cs b/src/Migrator/DuplicatedVersionException.cs index 7df9427b..370ebb68 100644 --- a/src/Migrator/DuplicatedVersionException.cs +++ b/src/Migrator/DuplicatedVersionException.cs @@ -13,17 +13,19 @@ using System; -namespace Migrator +namespace DotNetProjects.Migrator; + +/// +/// Exception thrown when a migration number is not unique. +/// +#if NETSTANDARD +#else +[Serializable] +#endif +public class DuplicatedVersionException : Exception { - /// - /// Exception thrown when a migration number is not unique. - /// - [Serializable] - public class DuplicatedVersionException : Exception - { - public DuplicatedVersionException(long version) - : base(String.Format("Migration version #{0} is duplicated", version)) - { - } - } + public DuplicatedVersionException(long version) + : base(string.Format("Migration version #{0} is duplicated", version)) + { + } } \ No newline at end of file diff --git a/src/Migrator/Framework/CheckConstraint.cs b/src/Migrator/Framework/CheckConstraint.cs new file mode 100644 index 00000000..d682fa9a --- /dev/null +++ b/src/Migrator/Framework/CheckConstraint.cs @@ -0,0 +1,26 @@ +namespace DotNetProjects.Migrator.Framework; + +/// +/// Currently only used for SQLite +/// +public class CheckConstraint : IDbField +{ + public CheckConstraint() + { } + + public CheckConstraint(string name, string checkConstraintText) + { + CheckConstraintString = checkConstraintText; + Name = name; + } + + /// + /// Gets or sets the CheckConstraintString. Add it without the braces they will be added by the migrator. + /// + public string CheckConstraintString { get; set; } + + /// + /// Gets or sets the name of the CHECK constraint. + /// + public string Name { get; set; } +} diff --git a/src/Migrator/Framework/Column.cs b/src/Migrator/Framework/Column.cs new file mode 100644 index 00000000..3a444a20 --- /dev/null +++ b/src/Migrator/Framework/Column.cs @@ -0,0 +1,196 @@ +#region License + +//The contents of this file are subject to the Mozilla Public License +//Version 1.1 (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.mozilla.org/MPL/ +//Software distributed under the License is distributed on an "AS IS" +//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +//License for the specific language governing rights and limitations +//under the License. + +#endregion + +using System; +using System.Data; + +namespace DotNetProjects.Migrator.Framework; + +/// +/// Represents a table column. +/// +public class Column : IColumn, IDbField +{ + private object _defaultValue; + + public Column(string name) + { + Name = name; + } + + public Column(string name, DbType type) + { + Name = name; + Type = type; + } + + public Column(string name, DbType type, int size) + { + Name = name; + Type = type; + Size = size; + } + + public Column(string name, DbType type, object defaultValue) + { + Name = name; + Type = type; + DefaultValue = defaultValue; + } + + public Column(string name, DbType type, ColumnProperty property) + { + Name = name; + Type = type; + ColumnProperty = property; + } + + public Column(string name, DbType type, int size, ColumnProperty property) + { + Name = name; + Type = type; + Size = size; + ColumnProperty = property; + } + + public Column(string name, DbType type, int size, ColumnProperty property, object defaultValue) + { + Name = name; + Type = type; + Size = size; + ColumnProperty = property; + DefaultValue = defaultValue; + } + + public Column(string name, DbType type, ColumnProperty property, object defaultValue) + { + Name = name; + Type = type; + ColumnProperty = property; + DefaultValue = defaultValue; + } + + public Column(string name, MigratorDbType type) + { + Name = name; + MigratorDbType = type; + } + + public Column(string name, MigratorDbType type, int size) + { + Name = name; + MigratorDbType = type; + Size = size; + } + + public Column(string name, MigratorDbType type, object defaultValue) + { + Name = name; + MigratorDbType = type; + DefaultValue = defaultValue; + } + + public Column(string name, MigratorDbType type, ColumnProperty property) + { + Name = name; + MigratorDbType = type; + ColumnProperty = property; + } + + public Column(string name, MigratorDbType type, int size, ColumnProperty property) + { + Name = name; + MigratorDbType = type; + Size = size; + ColumnProperty = property; + } + + public Column(string name, MigratorDbType type, int size, ColumnProperty property, object defaultValue) + { + Name = name; + MigratorDbType = type; + Size = size; + ColumnProperty = property; + DefaultValue = defaultValue; + } + + public Column(string name, MigratorDbType type, ColumnProperty property, object defaultValue) + { + Name = name; + MigratorDbType = type; + ColumnProperty = property; + DefaultValue = defaultValue; + } + + public string Name { get; set; } + + public DbType Type + { + get + { + return (DbType)MigratorDbType; + } + set + { + MigratorDbType = (MigratorDbType)value; + } + } + + public MigratorDbType MigratorDbType { get; set; } + + public int Size { get; set; } + + /// + /// Gets or sets the precision for NUMERIC/DECIMAL + /// + public int? Precision { get; set; } + + /// + /// Gets or sets the scale for NUMERIC/DECIMAL + /// + public int? Scale { get; set; } + + public ColumnProperty ColumnProperty { get; set; } + + public object DefaultValue + { + get => _defaultValue; + set + { + if (value is DateTime defaultValueDateTime) + { + if (defaultValueDateTime.Kind != DateTimeKind.Utc) + { + throw new Exception("Only UTC values are accepted as default DateTime values."); + } + } + + _defaultValue = value; + } + } + + public bool IsIdentity + { + get { return (ColumnProperty & ColumnProperty.Identity) == ColumnProperty.Identity; } + } + + public bool IsPrimaryKey + { + get { return (ColumnProperty & ColumnProperty.PrimaryKey) == ColumnProperty.PrimaryKey; } + } + + public bool IsPrimaryKeyNonClustered + { + get { return (ColumnProperty & ColumnProperty.PrimaryKeyNonClustered) == ColumnProperty.PrimaryKeyNonClustered; } + } +} diff --git a/src/Migrator/Framework/ColumnProperty.cs b/src/Migrator/Framework/ColumnProperty.cs new file mode 100644 index 00000000..75daca10 --- /dev/null +++ b/src/Migrator/Framework/ColumnProperty.cs @@ -0,0 +1,71 @@ +using System; + +namespace DotNetProjects.Migrator.Framework; + +/// +/// Represents a table column properties. +/// +[Flags] +public enum ColumnProperty +{ + None = 0, + + /// + /// Null is allowable + /// + Null = 1 << 0, + + /// + /// Null is not allowable + /// + NotNull = 1 << 1, + + /// + /// Identity column, autoinc + /// + Identity = 1 << 2, + + /// + /// Unique Column. This is marked being obsolete since you cannot add a name for the constraint which makes it difficult to remove the constraint again. + /// + [Obsolete("Use method 'AddUniqueConstraint' instead. This is marked being obsolete since you cannot add a name for the constraint which makes it difficult to remove the constraint again.")] + Unique = 1 << 3, + + /// + /// Indexed Column + /// + [Obsolete("Use method 'AddIndex'")] + Indexed = 1 << 4, + + /// + /// Unsigned Column. Not used in SQLite there is only one integer data type => INTEGER. + /// + Unsigned = 1 << 5, + + /// + /// CaseSensitive. Currently only used in SQLite, MySQL and SQL Server + /// + CaseSensitive = 1 << 6, + + // /// + // /// Foreign Key + // /// + // [Obsolete("Use method 'AddForeignKey' instead. The flag does not make sense on column level.")] + // ForeignKey = 1 << 7, + + /// + /// Primary Key. For compound PKs use AddPrimaryKey instead. + /// + [Obsolete("Use AddPrimaryKey instead.")] + PrimaryKey = 1 << 8, + + /// + /// Primary key with identity. This is shorthand for and + /// + PrimaryKeyWithIdentity = PrimaryKey | Identity, + + /// + /// Primary key non clustered. + /// + PrimaryKeyNonClustered = 1 << 10 | PrimaryKey +} diff --git a/src/Migrator/Framework/ColumnPropertyExtensions.cs b/src/Migrator/Framework/ColumnPropertyExtensions.cs new file mode 100644 index 00000000..989b11fb --- /dev/null +++ b/src/Migrator/Framework/ColumnPropertyExtensions.cs @@ -0,0 +1,24 @@ +namespace DotNetProjects.Migrator.Framework; + +public static class ColumnPropertyExtensions +{ + public static bool IsSet(this ColumnProperty columnProperty, ColumnProperty flags) + { + return flags != 0 && columnProperty.HasFlag(flags); + } + + public static bool IsNotSet(this ColumnProperty columnProperty, ColumnProperty flags) + { + return flags == 0 || !columnProperty.HasFlag(flags); + } + + public static ColumnProperty Set(this ColumnProperty columnProperty, ColumnProperty flags) + { + return columnProperty | flags; + } + + public static ColumnProperty Clear(this ColumnProperty columnProperty, ColumnProperty flags) + { + return columnProperty & ~flags; + } +} \ No newline at end of file diff --git a/src/Migrator/Framework/DataRecordExtensions.cs b/src/Migrator/Framework/DataRecordExtensions.cs new file mode 100644 index 00000000..be83a34e --- /dev/null +++ b/src/Migrator/Framework/DataRecordExtensions.cs @@ -0,0 +1,83 @@ +using System; +using System.Data; + +namespace DotNetProjects.Migrator.Framework; + +public static class DataRecordExtensions +{ + public static T TryParse(this IDataRecord record, string name) + { + return TryParse(record, name, () => default(T)); + } + + public static T TryParse(this IDataRecord record, string name, Func defaultValue) + { + var value = record[name]; + + var type = typeof(T); + + if (value == null || value == DBNull.Value) + { + return defaultValue(); + } + + if (type == typeof(DateTime?) || type == typeof(DateTime)) + { + return (T)(object)(Convert.ToDateTime(value)); + } + + if (type == typeof(Guid) || type == typeof(Guid?)) + { + if (value is byte[]) + { + return (T)(object)new Guid((byte[])value); + } + + return (T)((object)new Guid(value.ToString())); + } + + if (type == typeof(string)) + { + return (T)((object)value.ToString()); + } + + if (type == typeof(int?) || type == typeof(int)) + { + return (T)(object)Convert.ToInt32(value); + } + + if (type == typeof(long?) || type == typeof(long)) + { + return (T)(object)Convert.ToInt64(value); + } + + if (type == typeof(bool) || type == typeof(bool?)) + { + if (value is int || value is long || value is short || value is ushort || value is uint || value is ulong) + { + var intValue = Convert.ToInt64(value); + return (T)(object)(intValue != 0); + } + + if (value is string) + { + bool result; + if (bool.TryParse((string)value, out result)) + { + return (T)(object)result; + } + } + + return (T)value; + } + + try + { + return (T)value; + } + catch (InvalidCastException ex) + { + throw new MigrationException(string.Format("Invalid cast exception of value: {0} of type: {1} to type: {2} (field name: {3})", value, value.GetType(), typeof(T), name), ex); + } + } +} \ No newline at end of file diff --git a/src/Migrator/Framework/Extensions/LinqExtensions.cs b/src/Migrator/Framework/Extensions/LinqExtensions.cs new file mode 100644 index 00000000..d8bdcb4d --- /dev/null +++ b/src/Migrator/Framework/Extensions/LinqExtensions.cs @@ -0,0 +1,18 @@ +using System; + +namespace DotNetProjects.Migrator.Framework.Extensions; + +public static class LinqExtensions +{ + /// + /// Is equal to the Contains method in .NET 9. Please remove it after .NET upgrade. + /// + /// + /// + /// + /// + public static bool Contains(this string source, string toBeChecked, StringComparison stringComparison) + { + return source?.IndexOf(toBeChecked, stringComparison) >= 0; + } +} \ No newline at end of file diff --git a/src/Migrator/Framework/ForeignKeyConstraint.cs b/src/Migrator/Framework/ForeignKeyConstraint.cs new file mode 100644 index 00000000..1a5af872 --- /dev/null +++ b/src/Migrator/Framework/ForeignKeyConstraint.cs @@ -0,0 +1,42 @@ +namespace DotNetProjects.Migrator.Framework; + +public class ForeignKeyConstraint : IDbField +{ + public ForeignKeyConstraint() + { } + + public ForeignKeyConstraint(string name, string parentTable, string[] parentcolumns, string childTable, string[] childColumns) + { + Name = name; + ParentTable = parentTable; + ParentColumns = parentcolumns; + ChildTable = childTable; + ChildColumns = childColumns; + } + + /// + /// Gets or sets the Id of the FK. This is not the name of the FK. + /// Currently used for SQLite + /// + public int? Id { get; set; } + public string Name { get; set; } + public string ParentTable { get; set; } + public string[] ParentColumns { get; set; } + public string ChildTable { get; set; } + public string[] ChildColumns { get; set; } + + /// + /// Gets or sets the on delete text. Currently only used for SQLite. + /// + public string OnDelete { get; set; } + + /// + /// Gets or sets the on update text. Currently only used for SQLite. + /// + public string OnUpdate { get; set; } + + /// + /// /// Gets or sets the match text. Currently only used for SQLite. + /// + public string Match { get; set; } +} diff --git a/src/Migrator/Framework/ForeignKeyConstraintType.cs b/src/Migrator/Framework/ForeignKeyConstraintType.cs new file mode 100644 index 00000000..c7ae9333 --- /dev/null +++ b/src/Migrator/Framework/ForeignKeyConstraintType.cs @@ -0,0 +1,10 @@ +namespace DotNetProjects.Migrator.Framework; + +public enum ForeignKeyConstraintType +{ + Cascade, + SetNull, + NoAction, + Restrict, + SetDefault +} \ No newline at end of file diff --git a/src/Migrator/Framework/IColumn.cs b/src/Migrator/Framework/IColumn.cs new file mode 100644 index 00000000..14b49729 --- /dev/null +++ b/src/Migrator/Framework/IColumn.cs @@ -0,0 +1,36 @@ +#region License + +//The contents of this file are subject to the Mozilla Public License +//Version 1.1 (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.mozilla.org/MPL/ +//Software distributed under the License is distributed on an "AS IS" +//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +//License for the specific language governing rights and limitations +//under the License. + +#endregion + +using System.Data; + +namespace DotNetProjects.Migrator.Framework; + +public interface IColumn +{ + ColumnProperty ColumnProperty { get; set; } + + string Name { get; set; } + + DbType Type { get; set; } + + MigratorDbType MigratorDbType { get; set; } + + int Size { get; set; } + + bool IsIdentity { get; } + + bool IsPrimaryKey { get; } + bool IsPrimaryKeyNonClustered { get; } + + object DefaultValue { get; set; } +} diff --git a/src/Migrator/Framework/IDbField.cs b/src/Migrator/Framework/IDbField.cs new file mode 100644 index 00000000..79cdaa03 --- /dev/null +++ b/src/Migrator/Framework/IDbField.cs @@ -0,0 +1,6 @@ +namespace DotNetProjects.Migrator.Framework; + +public interface IDbField +{ + string Name { get; set; } +} diff --git a/src/Migrator/Framework/IDialect.cs b/src/Migrator/Framework/IDialect.cs new file mode 100644 index 00000000..ffe0cf78 --- /dev/null +++ b/src/Migrator/Framework/IDialect.cs @@ -0,0 +1,86 @@ +using System.Data; + +namespace DotNetProjects.Migrator.Framework; + +public interface IDialect +{ + int MaxKeyLength { get; } + int MaxFieldNameLength { get; } + bool ColumnNameNeedsQuote { get; } + bool TableNameNeedsQuote { get; } + bool ConstraintNameNeedsQuote { get; } + bool IdentityNeedsType { get; } + bool NeedsNotNullForIdentity { get; } + bool SupportsIndex { get; } + string QuoteTemplate { get; } + bool NeedsNullForNullableWhenAlteringTable { get; } + bool IsReservedWord(string reservedWord); + DbType GetDbTypeFromString(string type); + + /// + /// Get the name of the database type associated with the given + /// + /// The DbType + /// The database type name used by ddl. + string GetTypeName(DbType type); + + /// + /// Get the name of the database type associated with the given + /// + /// The DbType + /// The database type name used by ddl. + /// + string GetTypeName(DbType type, int length); + + /// + /// Get the name of the database type associated with the given + /// + /// The DbType + /// The database type name used by ddl. + /// + /// + /// + string GetTypeName(DbType type, int length, int precision, int scale); + + /// + /// Get the type from the specified database type name. + /// Note: This does not work perfectly, but it will do for most cases. + /// + /// The name of the type. + /// The . + DbType GetDbType(string databaseTypeName); + + void RegisterProperty(ColumnProperty property, string sql); + + string SqlForProperty(ColumnProperty property, Column column); + + string Default(object defaultValue); + + /// + /// Determine if a particular database type has an unsigned variant + /// + /// The DbType + /// True if the database type has an unsigned variant, otherwise false + bool IsUnsignedCompatible(DbType type); + + /// + /// Quotes the string. + /// + /// + /// + string Quote(string value); + + /// + /// Quotes the table name if necessary. + /// + /// + /// + string QuoteTableNameIfRequired(string tableName); + + /// + /// Quotes the column name if necessary. + /// + /// + /// + string QuoteColumnNameIfRequired(string columnName); +} diff --git a/src/Migrator/Framework/ILogger.cs b/src/Migrator/Framework/ILogger.cs new file mode 100644 index 00000000..3d36e265 --- /dev/null +++ b/src/Migrator/Framework/ILogger.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; + +namespace DotNetProjects.Migrator.Framework; + +public interface ILogger +{ + /// + /// Log that we have started a migration + /// + /// Start list of versions + /// Final Version + void Started(List currentVersion, long finalVersion); + + /// + /// Log that we are migrating up + /// + /// Version we are migrating to + /// Migration name + void MigrateUp(long version, string migrationName); + + /// + /// Log that we are migrating down + /// + /// Version we are migrating to + /// Migration name + void MigrateDown(long version, string migrationName); + + /// + /// Inform that a migration corresponding to the number of + /// version is untraceable (not found?) and will be ignored. + /// + /// Version we couldnt find + void Skipping(long version); + + /// + /// Log that we are rolling back to version + /// + /// + /// version + /// + void RollingBack(long originalVersion); + + /// + /// Log a Sql statement that changes the schema or content of the database as part of a migration + /// + /// + /// SELECT statements should not be logged using this method as they do not alter the data or schema of the + /// database. + /// + /// The Sql statement to log + void ApplyingDBChange(string sql); + + /// + /// Log that we had an exception on a migration + /// + /// The version of the migration that caused the exception. + /// The name of the migration that caused the exception. + /// The exception itself + void Exception(long version, string migrationName, Exception ex); + + /// + /// Log that we had an exception on a migration + /// + /// An informative message to show to the user. + /// The exception itself + void Exception(string message, Exception ex); + + /// + /// Log that we have finished a migration + /// + /// List of versions with which we started + /// Final Version + void Finished(List currentVersion, long finalVersion); + + /// + /// Log a message + /// + /// The format string ("{0}, blabla {1}"). + /// Parameters to apply to the format string. + void Log(string format, params object[] args); + + /// + /// Log a Warning + /// + /// The format string ("{0}, blabla {1}"). + /// Parameters to apply to the format string. + void Warn(string format, params object[] args); + + /// + /// Log a Trace Message + /// + /// The format string ("{0}, blabla {1}"). + /// Parameters to apply to the format string. + void Trace(string format, params object[] args); +} \ No newline at end of file diff --git a/src/Migrator/Framework/IMigration.cs b/src/Migrator/Framework/IMigration.cs new file mode 100644 index 00000000..f8a53e50 --- /dev/null +++ b/src/Migrator/Framework/IMigration.cs @@ -0,0 +1,38 @@ +namespace DotNetProjects.Migrator.Framework; + +public interface IMigration +{ + string Name { get; } + + /// + /// Represents the database. + /// . + /// + /// Migration.Framework.ITransformationProvider + ITransformationProvider Database { get; set; } + + /// + /// Defines tranformations to port the database to the current version. + /// + void Up(); + + /// + /// This is run after the Up transaction has been committed + /// + void AfterUp(); + + /// + /// Defines transformations to revert things done in Up. + /// + void Down(); + + /// + /// This is run after the Down transaction has been committed + /// + void AfterDown(); + + /// + /// This gets called once on the first migration object. + /// + void InitializeOnce(string[] args); +} \ No newline at end of file diff --git a/src/Migrator/Framework/ITransformationProvider.cs b/src/Migrator/Framework/ITransformationProvider.cs new file mode 100644 index 00000000..63fd04cd --- /dev/null +++ b/src/Migrator/Framework/ITransformationProvider.cs @@ -0,0 +1,794 @@ +using System; +using System.Collections.Generic; +using System.Data; +using DotNetProjects.Migrator.Framework.Models; + +namespace DotNetProjects.Migrator.Framework; + +/// +/// The main interface to use in Migrations to make changes on a database schema. +/// +public interface ITransformationProvider : IDisposable +{ + /// + /// Get this provider or a NoOp provider if you are not running in the context of 'provider'. + /// + ITransformationProvider this[string provider] { get; } + + string SchemaInfoTable { get; set; } + + int? CommandTimeout { get; set; } + + IDialect Dialect { get; } + + /// + /// The list of Migrations currently applied to the database. + /// + List AppliedMigrations { get; } + + bool IsMigrationApplied(long version, string scope); + + /// + /// Connection string to the database + /// + string ConnectionString { get; } + + /// + /// Logger used to log details of operations performed during migration + /// + ILogger Logger { get; set; } + + /// + /// Add a column to an existing table + /// + /// The name of the table that will get the new column + /// The name of the new column + /// The data type for the new columnd + /// The precision or size of the column + /// Properties that can be ORed together + /// The default value of the column if no value is given in a query + void AddColumn(string table, string column, DbType type, int size, ColumnProperty property, object defaultValue); + + /// + /// Add a column to an existing table + /// + /// The name of the table that will get the new column + /// The name of the new column + /// The data type for the new columnd + /// The precision or size of the column + /// Properties that can be ORed together + /// The default value of the column if no value is given in a query + void AddColumn(string table, string column, MigratorDbType type, int size, ColumnProperty property, object defaultValue); + + /// + /// Add a column to an existing table + /// + /// The name of the table that will get the new column + /// The name of the new column + /// The data type for the new columnd + void AddColumn(string table, string column, DbType type); + + /// + /// Add a column to an existing table + /// + /// The name of the table that will get the new column + /// The name of the new column + /// The data type for the new columnd + void AddColumn(string table, string column, MigratorDbType type); + + /// + /// Add a column to an existing table + /// + /// The name of the table that will get the new column + /// The name of the new column + /// The data type for the new columnd + /// The precision or size of the column + void AddColumn(string table, string column, DbType type, int size); + + /// + /// Add a column to an existing table + /// + /// The name of the table that will get the new column + /// The name of the new column + /// The data type for the new columnd + /// The precision or size of the column + void AddColumn(string table, string column, MigratorDbType type, int size); + + /// + /// Add a column to an existing table + /// + /// The name of the table that will get the new column + /// The name of the new column + /// The data type for the new columnd + /// The precision or size of the column + /// Properties that can be ORed together + void AddColumn(string table, string column, DbType type, int size, ColumnProperty property); + + /// + /// Add a column to an existing table + /// + /// The name of the table that will get the new column + /// The name of the new column + /// The data type for the new columnd + /// The precision or size of the column + /// Properties that can be ORed together + void AddColumn(string table, string column, MigratorDbType type, int size, ColumnProperty property); + + /// + /// Add a column to an existing table + /// + /// The name of the table that will get the new column + /// The name of the new column + /// The data type for the new columnd + /// Properties that can be ORed together + void AddColumn(string table, string column, DbType type, ColumnProperty property); + + /// + /// Add a column to an existing table + /// + /// The name of the table that will get the new column + /// The name of the new column + /// The data type for the new columnd + /// Properties that can be ORed together + void AddColumn(string table, string column, MigratorDbType type, ColumnProperty property); + + /// + /// Add a column to an existing table with the default column size. + /// + /// The name of the table that will get the new column + /// The name of the new column + /// The data type for the new columnd + /// The default value of the column if no value is given in a query + void AddColumn(string table, string column, DbType type, object defaultValue); + + /// + /// Add a column to an existing table with the default column size. + /// + /// The name of the table that will get the new column + /// The name of the new column + /// The data type for the new columnd + /// The default value of the column if no value is given in a query + void AddColumn(string table, string column, MigratorDbType type, object defaultValue); + + /// + /// Add a column to an existing table + /// + /// The name of the table that will get the new column + /// An instance of a Column with the specified properties + void AddColumn(string table, Column column); + + /// + /// Add a foreign key constraint + /// + /// The name of the foreign key. e.g. FK_TABLE_REF + /// The table that the foreign key will be created in (e.g. Child) + /// The columns that are the foreign keys (e.g. ParentId) + /// The table that holds the primary keys (e.g. Parent) + /// The columns that are the primary keys in the parent table (e.g. Id) + void AddForeignKey(string name, string childTable, string[] childColumns, string parentTable, string[] parentColumns); + + /// + /// Add a foreign key constraint + /// + /// The name of the foreign key. e.g. FK_TABLE_REF + /// The table that the foreign key will be created in (e.g. Child) + /// The columns that are the foreign keys (e.g. ParentId) + /// The table that holds the primary keys (e.g. Parent) + /// The columns that are the primary keys in the parent table(e.g. Id) + /// Constraint parameters + void AddForeignKey(string name, string childTable, string[] childColumns, string parentTable, string[] parentColumns, ForeignKeyConstraintType constraint); + + /// + /// Add a foreign key constraint + /// + /// + /// The name of the foreign key. e.g. FK_TABLE_REF + /// The table that the foreign key will be created in (e.g. Child) + /// The column that is the foreign key (e.g. ParentId) + /// The table that holds the primary keys (e.g. Parent) + /// The column that is the primary key int the parent table (e.g. Id) + void AddForeignKey(string name, string childTable, string childColumn, string parentTable, string parentColumn); + + /// + /// Add a foreign key constraint + /// + /// The name of the foreign key. e.g. FK_CHILD_PARENT + /// The table that the foreign key will be created in (e.g. ChildTable) + /// The column that is the foreign key (e.g. ParentId) + /// The table that holds the primary key (e.g. Parent) + /// The column that is the primary key in the parent table(e.g. Id) + /// Constraint parameters + void AddForeignKey(string name, string childTable, string childColumn, string parentTable, string parentColumn, ForeignKeyConstraintType constraint); + + /// + /// Add a foreign key constraint when you don't care about the name of the constraint. + /// Warning: This will prevent you from dropping the constraint since you won't know the name. + /// + /// The table that the foreign key will be created in (e.g. ChildTable) + /// The column that is the foreign key (e.g. ParentId) + /// The table that holds the primary key (e.g. Parent) + /// The column that is the primary key in the parent table(e.g. Id) + void GenerateForeignKey(string childTable, string childColumn, string parentTable, string parentColumn); + + /// + /// Add a foreign key constraint when you don't care about the name of the constraint. + /// Warning: This will prevent you from dropping the constraint since you won't know the name. + /// + /// The table that the foreign key will be created in (e.g. ChildTable) + /// The columns that are the foreign keys (e.g. ParentId) + /// The table that holds the primary key (e.g. Parent) + /// The column that is the primary key in the parent table (e.g. Id) + void GenerateForeignKey(string foreignTable, string[] foreignColumns, string primaryTable, string[] primaryColumns); + + /// + /// Add a foreign key constraint when you don't care about the name of the constraint. + /// Warning: This will prevent you from dropping the constraint since you won't know the name. + /// + /// The table that the foreign key will be created in (e.g. ChildTable) + /// The columns that are the foreign keys (e.g. ParentId) + /// The table that holds the primary key (e.g. Parent) + /// The columns that are the primary keys in the parent table (e.g. Id) + /// Constraint parameters + void GenerateForeignKey(string childTable, string[] childColumns, string parentTable, string[] parentColumns, ForeignKeyConstraintType constraint); + + /// + /// Add a foreign key constraint when you don't care about the name of the constraint. + /// Warning: This will prevent you from dropping the constraint since you won't know the name. + /// + /// The table that the foreign key will be created in (e.g. ChildTable) + /// The columns that are the foreign keys (e.g. ParentId) + /// The table that holds the primary key (e.g. Parent) + /// The column that is the primary key in the parent table (e.g. Id) + /// Constraint parameters + void GenerateForeignKey(string childTable, string childColumn, string parentTable, string parentColumn, ForeignKeyConstraintType constraint); + + /// + /// Add a foreign key constraint when you don't care about the name of the constraint. + /// Warning: This will prevent you from dropping the constraint since you won't know the name. + /// + /// The current expectations are that there is a column named the same as the foreignTable present in + /// the table. This is subject to change because I think it's not a good convention. + /// + /// The table that the foreign key will be created in (eg. ChildTable.ParentId) + /// The table that holds the primary key (eg. Table.PK_id) + void GenerateForeignKey(string childTable, string parentTable); + + /// + /// Add a foreign key constraint when you don't care about the name of the constraint. + /// Warning: This will prevent you from dropping the constraint since you won't know the name. + /// + /// The current expectations are that there is a column named the same as the foreignTable present in + /// the table. This is subject to change because I think it's not a good convention. + /// + /// The table that the foreign key will be created in (eg. ChildTable.ParentId) + /// The table that holds the primary key (eg. Table.PK_id) + /// + void GenerateForeignKey(string foreignTable, string primaryTable, ForeignKeyConstraintType constraint); + + /// + /// Add a primary key to a table + /// + /// The name of the primary key to add. + /// The name of the table that will get the primary key. + /// The name of the column or columns that are in the primary key. + void AddPrimaryKey(string name, string table, params string[] columns); + + void AddPrimaryKeyNonClustered(string name, string table, params string[] columns); + /// + /// Add a constraint to a table + /// + /// The name of the constraint to add. + /// The name of the table that will get the constraint + /// The name of the column or columns that will get the constraint. + void AddUniqueConstraint(string name, string table, params string[] columns); + + /// + /// Add a constraint to a table + /// + /// The name of the constraint to add. + /// The name of the table that will get the constraint + /// The check constraint definition. + void AddCheckConstraint(string name, string table, string checkSql); + + void AddView(string name, string tableName, params IViewElement[] viewElements); + + void AddView(string name, string tableName, params IViewField[] fields); + + /// + /// Add a table + /// + /// The name of the table to add. + /// The columns that are part of the table. + void AddTable(string name, params IDbField[] columns); + + /// + /// Add a table + /// + /// The name of the table to add. + /// The name of the database engine to use. (MySQL) + /// The columns that are part of the table. + void AddTable(string name, string engine, params IDbField[] columns); + + /// + /// Start a transction + /// + void BeginTransaction(); + + /// + /// Change the definition of an existing column. + /// + /// The name of the table that will get the new column + /// An instance of a Column with the specified properties and the name of an existing column + void ChangeColumn(string table, Column column); + + void RemoveColumnDefaultValue(string table, string column); + + /// + /// Check to see if a column exists + /// + /// + /// + /// + bool ColumnExists(string table, string column); + + /// + /// Commit the running transction + /// + void Commit(); + + /// + /// Check to see if a constraint exists + /// + /// The name of the constraint + /// The table that the constraint lives on. + /// + bool ConstraintExists(string table, string name); + + /// + /// Copies data from source table to target table using INSERT INTO...SELECT..FROM + /// Be aware that the order of and matters. + /// + /// + /// + /// + /// + /// Sort source by these columns. must contain the . + void CopyDataFromTableToTable(string sourceTableName, List sourceColumnNames, string targetTableName, List targetColumnNames, List orderBySourceColumns = null); + + /// + /// Check to see if a primary key constraint exists on the table + /// + /// The name of the primary key + /// The table that the constraint lives on. + /// + bool PrimaryKeyExists(string table, string name); + + /// + /// Execute an arbitrary SQL query + /// + /// The SQL to execute. + /// timeout + /// Array of parameters of type object + /// + int ExecuteNonQuery(string sql, int timeout, object[] args); + + /// + /// Execute an arbitrary SQL query + /// + /// The SQL to execute. + /// timeout + /// + int ExecuteNonQuery(string sql, int timeout); + + int ExecuteNonQuery(string sql); + /// + /// Execute an arbitrary SQL query + /// + /// The SQL to execute. + /// + IDataReader ExecuteQuery(IDbCommand cmd, string sql); + + /// + /// Creates a DbCommand + /// + /// + IDbCommand CreateCommand(); + + /// + /// Execute an arbitrary SQL query + /// + /// The SQL to execute. + /// A single value that is returned. + object ExecuteScalar(string sql); + + List ExecuteStringQuery(string sql, params object[] args); + + /// + /// Oracle: The retrieval of filter items is not supported in this migrator. If functional expressions are used: they seem to be stored as separate columns (with generated names). + /// + /// + /// + Index[] GetIndexes(string table); + + /// + /// Get the information about the columns in a table. + /// and can in some cases only be guessed. Do not rely on them. Same for + /// + /// The table name that you want the columns for. + /// + [Obsolete("We cannot resolve the DbType or MigratorDbType exactly so the result is just a guess. Also the default value in the result is depending on DbType and therefore also a guess. Do not use this method any more. Look up the type in your migration history.")] + Column[] GetColumns(string table); + + /// + /// Reads the MaxLength of the Data in the Column + /// + /// + /// + /// + int GetColumnContentSize(string table, string columnName); + + /// + /// Gets information about a single column in a table. + /// and can in some cases only be guessed. Do not rely on them. Same for + /// + /// The table name that you want the columns for. + /// The column name for which you want information. + /// + [Obsolete("We cannot resolve the DbType or MigratorDbType exactly so the result is just a guess. Also the default value in the result is depending on DbType and therefore also a guess. Do not use this method any more. Look up the type in your migration history.")] + Column GetColumnByName(string table, string column); + + /// + /// Get the names of all of the tables + /// + /// The names of all the tables. + string[] GetTables(); + + /// + /// Get all foreign keys by the given table name. + /// ATTENTION: For Postgre SQL the result will be lower case if the names were not quoted on table creation of on FK creation! For Oracle they are uppercase! + /// + /// + /// + ForeignKeyConstraint[] GetForeignKeyConstraints(string table); + + /// + /// Insert data into a table + /// + /// The table that will get the new data + /// The names of the columns + /// The values in the same order as the columns + /// + int Insert(string table, string[] columns, object[] values); + + /// + /// Insert data into a table (if it not exists) + /// + /// The table that will get the new data + /// The names of the columns + /// The values in the same order as the columns + /// + int InsertIfNotExists(string table, string[] columns, object[] values, string[] whereColumns, object[] whereValues); + + /// + /// Delete data from a table + /// + /// The table that will have the data deleted + /// The names of the columns used in a where clause + /// The values in the same order as the columns + /// + int Delete(string table, string[] whereColumns = null, object[] whereValues = null); + + /// + /// Delete data from a table + /// + /// The table that will have the data deleted + /// The name of the column used in a where clause + /// The value for the where clause + /// + int Delete(string table, string whereColumn, string whereValue); + + /// + /// Truncate data from a table + /// + /// The table that will have the data deleted + /// + int TruncateTable(string table); + + /// + /// Marks a Migration version number as having been applied + /// + /// The version number of the migration that was applied + void MigrationApplied(long version, string scope); + + /// + /// Marks a Migration version number as having been rolled back from the database + /// + /// The version number of the migration that was removed + void MigrationUnApplied(long version, string scope); + + /// + /// Remove an existing column from a table + /// + /// The name of the table to remove the column from + /// The column to remove + void RemoveColumn(string table, string column); + + /// + /// Remove an existing foreign key constraint. + /// + /// The table that contains the foreign key. + /// The name of the foreign key to remove + void RemoveForeignKey(string table, string name); + + /// + /// Remove an existing constraint. + /// + /// The table that contains the foreign key. + /// The name of the constraint to remove + void RemoveConstraint(string table, string name); + + /// + /// Removes PK, FKs, Unique and CHECK constraints. + /// + /// + [Obsolete("Drop all constraints separately.")] + void RemoveAllConstraints(string table); + + /// + /// Remove an existing primary key. + /// + /// The table that contains the primary key. + void RemovePrimaryKey(string table); + + /// + /// Drops an existing table. + /// + /// The name of the table + void RemoveTable(string tableName); + + /// + /// Rename an existing table + /// + /// The old name of the table + /// The new name of the table + void RenameTable(string oldName, string newName); + + /// + /// Rename an existing table + /// + /// The name of the table + /// The old name of the column + /// The new name of the column + void RenameColumn(string tableName, string oldColumnName, string newColumnName); + + /// + /// Rollback the currently running transaction. + /// + void Rollback(); + + /// + /// Get values from a table + /// + /// The columns to select + /// The table to select from + /// The where clause to limit the selection + /// + IDataReader Select(IDbCommand cmd, string what, string from, string where); + + /// + /// Get values from a table + /// + /// + /// + /// + /// + /// + IDataReader Select(IDbCommand cmd, string table, string[] columns, string[] whereColumns = null, object[] whereValues = null); + + /// + /// Get values from a table + /// + /// + /// + /// + /// + /// + /// + /// + IDataReader SelectComplex(IDbCommand cmd, string table, string[] columns, string[] whereColumns = null, + object[] whereValues = null, string[] nullWhereColumns = null, string[] notNullWhereColumns = null); + + /// + /// Get values from a table + /// + /// The columns to select + /// The table to select from + /// + IDataReader Select(IDbCommand cmd, string what, string from); + + /// + /// Get a single value from a table + /// + /// The columns to select + /// The table to select from + /// + /// + object SelectScalar(string what, string from, string where); + + /// + /// Get a single value from a table + /// + /// The columns to select + /// The table to select from + /// + object SelectScalar(string what, string from); + + /// + /// Check if a table already exists + /// + /// The name of the table that you want to check on. + /// + bool TableExists(string tableName); + + /// + /// Check if a view already exists + /// + /// The name of the view that you want to check on. + /// + bool ViewExists(string viewName); + + /// + /// Update the values in a table + /// + /// The name of the table to update + /// The names of the columns. + /// The values for the columns in the same order as the names. + /// + int Update(string table, string[] columns, object[] values); + + /// + /// Update the values in a table + /// + /// The name of the table to update + /// The names of the columns. + /// The values for the columns in the same order as the names. + /// A where clause to limit the update + /// + int Update(string table, string[] columns, object[] values, string where); + + int Update(string table, string[] columns, object[] values, string[] whereColumns, object[] whereValues); + + /// + /// Updates the target table with data from the source table. Make sure to use primary key or unique columns in + /// + /// Source table name (unquoted). + /// Target table name (unquoted). + /// Pairs of columns that are used to copy data from column in source table to column in target table. + /// Pairs of columns that are used to match rows in source and target table. + void UpdateTargetFromSource(string tableNameSource, string tableNameTarget, ColumnPair[] copyColumnPairs, ColumnPair[] matchColumnPairs); + + /// + /// Get a command instance + /// + /// + IDbCommand GetCommand(); + + /// + /// Execute a schema builder + /// + /// + void ExecuteSchemaBuilder(SchemaBuilder.SchemaBuilder schemaBuilder); + + + void RemoveAllForeignKeys(string tableName, string columnName); + + bool IsThisProvider(string provider); + + /// + /// Quote a multiple column names, if required + /// + /// + /// + string[] QuoteColumnNamesIfRequired(params string[] columnNames); + + /// + /// Quaote column if required + /// + /// + /// + string QuoteColumnNameIfRequired(string name); + + /// + /// Quote table name if required + /// + /// + /// + string QuoteTableNameIfRequired(string name); + + /// + /// Encodes a guid value as a string, suitable for inclusion in sql statement + /// + /// + /// + string Encode(Guid guid); + + /// + /// Change the target database + /// + /// Name of the new target database + void SwitchDatabase(string databaseName); + + + /// + /// Get a list of databases available on the server + /// + List GetDatabases(); + + /// + /// Checks to see if a database with specific name exists on the server + /// + bool DatabaseExists(string name); + + /// + /// Create a new database on the server + /// + /// Name of the new database + void CreateDatabases(string databaseName); + + /// + /// Close all Connections to the Database. Sometimes needed for DropDatabase or redefine PrimaryKey. + /// + /// Name of the database to close all Connections + void KillDatabaseConnections(string databaseName); + + /// + /// Delete a database from the server + /// + /// Name of the database to delete + void DropDatabases(string databaseName); + + string AddIndex(string table, Index index); + + /// + /// Add a multi-column index to a table + /// + /// The name of the index to add. + /// The name of the table that will get the index. + /// The name of the column or columns that are in the index. + string AddIndex(string name, string table, params string[] columns); + + /// + /// Check to see if an index exists + /// + /// The name of the index + /// The table that the index lives on. + /// + bool IndexExists(string table, string name); + + /// + /// Remove an existing index + /// + /// The table that contains the index. + /// The name of the index to remove + void RemoveIndex(string table, string name); + + /// + /// Generate parameter name based on an index number + /// + /// The index number of the parameter + string GenerateParameterName(int index); + + /// + /// Remove all indexes of a table + /// + /// The table name + void RemoveAllIndexes(string table); + + string Concatenate(params string[] strings); + + IDbConnection Connection { get; } + + IEnumerable GetTables(string schema); + + IEnumerable GetColumns(string schema, string table); +} diff --git a/src/Migrator/Framework/IViewElement.cs b/src/Migrator/Framework/IViewElement.cs new file mode 100644 index 00000000..ad010949 --- /dev/null +++ b/src/Migrator/Framework/IViewElement.cs @@ -0,0 +1,5 @@ +namespace DotNetProjects.Migrator.Framework; + +public interface IViewElement +{ +} diff --git a/src/Migrator/Framework/IViewField.cs b/src/Migrator/Framework/IViewField.cs new file mode 100644 index 00000000..5fa20741 --- /dev/null +++ b/src/Migrator/Framework/IViewField.cs @@ -0,0 +1,11 @@ +namespace DotNetProjects.Migrator.Framework; + +public interface IViewField +{ + string TableName { get; set; } + string ColumnName { get; set; } + + string KeyColumnName { get; set; } + string ParentTableName { get; set; } + string ParentKeyColumnName { get; set; } +} diff --git a/src/Migrator/Framework/Index.cs b/src/Migrator/Framework/Index.cs new file mode 100644 index 00000000..5376a944 --- /dev/null +++ b/src/Migrator/Framework/Index.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using DotNetProjects.Migrator.Providers.Models.Indexes; +using DotNetProjects.Migrator.Providers; + +namespace DotNetProjects.Migrator.Framework; + +public class Index : IDbField +{ + public string Name { get; set; } + + public bool Unique { get; set; } + + /// + /// Indicates whether the index is clustered (false for NONCLUSTERED). + /// Please mind that this is ignored in Oracle and SQLite (supported in SQLite but not in this migrator) + /// + public bool Clustered { get; set; } + + /// + /// Indicates whether it is a primary key constraint. If you want to set a primary key use in + /// + public bool PrimaryKey { get; internal set; } + + /// + /// Indicates whether it is a unique constraint. If you want to set a unique constraint use the method + /// + public bool UniqueConstraint { get; internal set; } + + /// + /// Gets or sets the column names in the index (not included columns). + /// + public string[] KeyColumns { get; set; } = []; + + /// + /// Gets or sets the included columns. Not supported in SQLite and Oracle. + /// + public string[] IncludeColumns { get; set; } = []; + + /// + /// Gets or sets items that represent filter expressions in filtered indexes. Currently string, integer and boolean values are supported. + /// Attention: In SQL Server the column used in the filter must be NOT NULL. + /// + public List FilterItems { get; set; } = []; +} diff --git a/src/Migrator/Framework/JoinType.cs b/src/Migrator/Framework/JoinType.cs new file mode 100644 index 00000000..64f0adf6 --- /dev/null +++ b/src/Migrator/Framework/JoinType.cs @@ -0,0 +1,7 @@ +namespace DotNetProjects.Migrator.Framework; + +public enum JoinType +{ + Join, + LeftJoin +} diff --git a/src/Migrator.Framework/Loggers/ConsoleWriter.cs b/src/Migrator/Framework/Loggers/ConsoleWriter.cs similarity index 61% rename from src/Migrator.Framework/Loggers/ConsoleWriter.cs rename to src/Migrator/Framework/Loggers/ConsoleWriter.cs index 8cb2980f..84d8ca70 100644 --- a/src/Migrator.Framework/Loggers/ConsoleWriter.cs +++ b/src/Migrator/Framework/Loggers/ConsoleWriter.cs @@ -13,18 +13,17 @@ using System; -namespace Migrator.Framework.Loggers +namespace DotNetProjects.Migrator.Framework.Loggers; + +public class ConsoleWriter : ILogWriter { - public class ConsoleWriter : ILogWriter - { - public void Write(string message, params object[] args) - { - Console.Write(message, args); - } + public void Write(string message, params object[] args) + { + Console.Write(message, args); + } - public void WriteLine(string message, params object[] args) - { - Console.WriteLine(message, args); - } - } + public void WriteLine(string message, params object[] args) + { + Console.WriteLine(message, args); + } } \ No newline at end of file diff --git a/src/Migrator/Framework/Loggers/IAttachableLogger.cs b/src/Migrator/Framework/Loggers/IAttachableLogger.cs new file mode 100644 index 00000000..f6f480d4 --- /dev/null +++ b/src/Migrator/Framework/Loggers/IAttachableLogger.cs @@ -0,0 +1,34 @@ +#region License + +//The contents of this file are subject to the Mozilla Public License +//Version 1.1 (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.mozilla.org/MPL/ +//Software distributed under the License is distributed on an "AS IS" +//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +//License for the specific language governing rights and limitations +//under the License. + +#endregion + +namespace DotNetProjects.Migrator.Framework.Loggers; + +/// +/// ILogger interface. +/// Implicit in this interface is that the logger will delegate actual +/// logging to the (s) that have been attached +/// +public interface IAttachableLogger : ILogger +{ + /// + /// Attach an + /// + /// + void Attach(ILogWriter writer); + + /// + /// Detach an + /// + /// + void Detach(ILogWriter writer); +} \ No newline at end of file diff --git a/src/Migrator/Framework/Loggers/ILogWriter.cs b/src/Migrator/Framework/Loggers/ILogWriter.cs new file mode 100644 index 00000000..ee84f9bc --- /dev/null +++ b/src/Migrator/Framework/Loggers/ILogWriter.cs @@ -0,0 +1,34 @@ +#region License + +//The contents of this file are subject to the Mozilla Public License +//Version 1.1 (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.mozilla.org/MPL/ +//Software distributed under the License is distributed on an "AS IS" +//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +//License for the specific language governing rights and limitations +//under the License. + +#endregion + +namespace DotNetProjects.Migrator.Framework.Loggers; + +/// +/// Handles writing a message to the log medium (i.e. file, console) +/// +public interface ILogWriter +{ + /// + /// Write this message + /// + /// + /// + void Write(string message, params object[] args); + + /// + /// Write this message, as a line + /// + /// + /// + void WriteLine(string message, params object[] args); +} \ No newline at end of file diff --git a/src/Migrator/Framework/Loggers/Logger.cs b/src/Migrator/Framework/Loggers/Logger.cs new file mode 100644 index 00000000..0389fc34 --- /dev/null +++ b/src/Migrator/Framework/Loggers/Logger.cs @@ -0,0 +1,170 @@ +#region License + +//The contents of this file are subject to the Mozilla Public License +//Version 1.1 (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.mozilla.org/MPL/ +//Software distributed under the License is distributed on an "AS IS" +//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +//License for the specific language governing rights and limitations +//under the License. + +#endregion + +using System; +using System.Collections.Generic; + +namespace DotNetProjects.Migrator.Framework.Loggers; + +/// +/// Text logger for the migration mediator +/// +public class Logger : IAttachableLogger +{ + private readonly bool _trace; + private readonly List _writers = new List(); + + public Logger(bool trace) + { + _trace = trace; + } + + public Logger(bool trace, params ILogWriter[] writers) + : this(trace) + { + _writers.AddRange(writers); + } + + public void Attach(ILogWriter writer) + { + _writers.Add(writer); + } + + public void Detach(ILogWriter writer) + { + _writers.Remove(writer); + } + + public void Started(List currentVersions, long finalVersion) + { + WriteLine("Latest version applied : {0}. Target version : {1}", LatestVersion(currentVersions), finalVersion); + } + + public void MigrateUp(long version, string migrationName) + { + WriteLine("Applying {0}: {1}", version.ToString(), migrationName); + } + + public void MigrateDown(long version, string migrationName) + { + WriteLine("Removing {0}: {1}", version.ToString(), migrationName); + } + + public void Skipping(long version) + { + WriteLine("{0} {1}", version.ToString(), ""); + } + + public void RollingBack(long originalVersion) + { + WriteLine("Rolling back to migration {0}", originalVersion); + } + + public void ApplyingDBChange(string sql) + { + Log(sql); + } + + public void Exception(long version, string migrationName, Exception ex) + { + WriteLine("============ Error Detail ============"); + WriteLine("Error in migration: {0}", version); + LogExceptionDetails(ex); + WriteLine("======================================"); + } + + public void Exception(string message, Exception ex) + { + WriteLine("============ Error Detail ============"); + WriteLine("Error: {0}", message); + LogExceptionDetails(ex); + WriteLine("======================================"); + } + + public void Finished(List originalVersions, long currentVersion) + { + WriteLine("Migrated to version {0}", currentVersion); + } + + public void Log(string format, params object[] args) + { + WriteLine(format, args); + } + + public void Warn(string format, params object[] args) + { + Write("Warning! : "); + WriteLine(format, args); + } + + public void Trace(string format, params object[] args) + { + if (_trace) + { + Log(format, args); + } + } + + public void Started(long currentVersion, long finalVersion) + { + WriteLine("Current version : {0}. Target version : {1}", currentVersion, finalVersion); + } + + private void LogExceptionDetails(Exception ex) + { + WriteLine("{0}", ex.Message); + WriteLine("{0}", ex.StackTrace); + var iex = ex.InnerException; + while (iex != null) + { + WriteLine("Caused by: {0}", iex); + WriteLine("{0}", ex.StackTrace); + iex = iex.InnerException; + } + } + + public void Finished(long originalVersion, long currentVersion) + { + WriteLine("Migrated to version {0}", currentVersion); + } + + private void Write(string message, params object[] args) + { + foreach (var writer in _writers) + { + writer.Write(message, args); + } + } + + private void WriteLine(string message, params object[] args) + { + foreach (var writer in _writers) + { + writer.WriteLine(message, args); + } + } + + public static ILogger ConsoleLogger() + { + return new Logger(false, new ConsoleWriter()); + } + + private string LatestVersion(List versions) + { + if (versions.Count > 0) + { + return versions[versions.Count - 1].ToString(); + } + return "No migrations applied yet!"; + } +} \ No newline at end of file diff --git a/src/Migrator/Framework/Loggers/SqlScriptFileLogger.cs b/src/Migrator/Framework/Loggers/SqlScriptFileLogger.cs new file mode 100644 index 00000000..0d81c179 --- /dev/null +++ b/src/Migrator/Framework/Loggers/SqlScriptFileLogger.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace DotNetProjects.Migrator.Framework.Loggers; + +public class SqlScriptFileLogger : ILogger, IDisposable +{ + private readonly ILogger _innerLogger; + private TextWriter _streamWriter; + + public SqlScriptFileLogger(ILogger logger, TextWriter streamWriter) + { + _innerLogger = logger; + _streamWriter = streamWriter; + } + + #region IDisposable Members + + public void Dispose() + { + if (_streamWriter != null) + { + _streamWriter.Dispose(); + _streamWriter = null; + } + } + + #endregion + + public void Log(string format, params object[] args) + { + _innerLogger.Log(format, args); + } + + public void Warn(string format, params object[] args) + { + _innerLogger.Warn(format, args); + } + + public void Trace(string format, params object[] args) + { + _innerLogger.Trace(format, args); + } + + public void ApplyingDBChange(string sql) + { + _innerLogger.ApplyingDBChange(sql); + _streamWriter.WriteLine(sql); + } + + public void Started(List appliedVersions, long finalVersion) + { + _innerLogger.Started(appliedVersions, finalVersion); + } + + public void MigrateUp(long version, string migrationName) + { + _innerLogger.MigrateUp(version, migrationName); + } + + public void MigrateDown(long version, string migrationName) + { + _innerLogger.MigrateDown(version, migrationName); + } + + public void Skipping(long version) + { + _innerLogger.Skipping(version); + } + + public void RollingBack(long originalVersion) + { + _innerLogger.RollingBack(originalVersion); + } + + public void Exception(long version, string migrationName, Exception ex) + { + _innerLogger.Exception(version, migrationName, ex); + } + + public void Exception(string message, Exception ex) + { + _innerLogger.Exception(message, ex); + } + + public void Finished(List appliedVersions, long currentVersion) + { + _innerLogger.Finished(appliedVersions, currentVersion); + _streamWriter.Dispose(); + } +} \ No newline at end of file diff --git a/src/Migrator/Framework/Maximums.cs b/src/Migrator/Framework/Maximums.cs new file mode 100644 index 00000000..5727b84b --- /dev/null +++ b/src/Migrator/Framework/Maximums.cs @@ -0,0 +1,7 @@ +namespace DotNetProjects.Migrator.Framework; + +public static class Maximums +{ + public const int NTextLength = 1073741823; + public const int BlobLength = 2147483647; +} diff --git a/src/Migrator/Framework/Migration.cs b/src/Migrator/Framework/Migration.cs new file mode 100644 index 00000000..c0909789 --- /dev/null +++ b/src/Migrator/Framework/Migration.cs @@ -0,0 +1,112 @@ +#region License + +//The contents of this file are subject to the Mozilla Public License +//Version 1.1 (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.mozilla.org/MPL/ +//Software distributed under the License is distributed on an "AS IS" +//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +//License for the specific language governing rights and limitations +//under the License. + +#endregion + +namespace DotNetProjects.Migrator.Framework; + +/// +/// A migration is a group of transformation applied to the database schema +/// (or sometimes data) to port the database from one version to another. +/// The Up() method must apply the modifications (eg.: create a table) +/// and the Down() method must revert, or rollback the modifications +/// (eg.: delete a table). +/// +/// Each migration must be decorated with the [Migration(0)] attribute. +/// Each migration number (0) must be unique, or else a +/// DuplicatedVersionException will be trown. +/// +/// +/// All migrations are executed inside a transaction. If an exception is +/// thrown, the transaction will be rolledback and transformations wont be +/// applied. +/// +/// +/// It is best to keep a limited number of transformation inside a migration +/// so you can easely move from one version of to another with fine grain +/// modifications. +/// You should give meaningful name to the migration class and prepend the +/// migration number to the filename so they keep ordered, eg.: +/// 002_CreateTableTest.cs. +/// +/// +/// Use the Database property to apply transformation and the +/// Logger property to output informations in the console (or other). +/// For more details on transformations see +/// ITransformationProvider. +/// +/// +/// +/// The following migration creates a new Customer table. +/// (File 003_AddCustomerTable.cs) +/// +/// [Migration(3)] +/// public class AddCustomerTable : Migration +/// { +/// public override void Up() +/// { +/// Database.AddTable("Customer", +/// new Column("Name", typeof(string), 50), +/// new Column("Address", typeof(string), 100) +/// ); +/// } +/// public override void Down() +/// { +/// Database.RemoveTable("Customer"); +/// } +/// } +/// +/// +public abstract class Migration : IMigration +{ + public string Name + { + get { return StringUtils.ToHumanName(GetType().Name); } + } + + /// + /// Defines tranformations to port the database to the current version. + /// + public abstract void Up(); + + /// + /// This is run after the Up transaction has been committed + /// + public virtual void AfterUp() + { + } + + /// + /// Defines transformations to revert things done in Up. + /// + public abstract void Down(); + + /// + /// This is run after the Down transaction has been committed + /// + public virtual void AfterDown() + { + } + + /// + /// Represents the database. + /// . + /// + /// Migration.Framework.ITransformationProvider + public ITransformationProvider Database { get; set; } + + /// + /// This gets called once on the first migration object. + /// + public virtual void InitializeOnce(string[] args) + { + } +} diff --git a/src/Migrator/Framework/MigrationAttribute.cs b/src/Migrator/Framework/MigrationAttribute.cs new file mode 100644 index 00000000..483e2f3d --- /dev/null +++ b/src/Migrator/Framework/MigrationAttribute.cs @@ -0,0 +1,58 @@ +#region License + +//The contents of this file are subject to the Mozilla Public License +//Version 1.1 (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.mozilla.org/MPL/ +//Software distributed under the License is distributed on an "AS IS" +//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +//License for the specific language governing rights and limitations +//under the License. + +#endregion + +using System; + +namespace DotNetProjects.Migrator.Framework; + +/// +/// Describe a migration +/// +public class MigrationAttribute : Attribute +{ + private long _version; + private bool _ignore = false; + + public string Scope { get; set; } + + /// + /// Describe the migration + /// + /// The unique version of the migration. + public MigrationAttribute(long version) + { + Version = version; + } + public MigrationAttribute(int year, int month, int day, int hour, int minute, int second) + { + var combined = string.Format("{0:D4}{1:D2}{2:D2}{3:D2}{4:D2}{5:D2}", year, month, day, hour, minute, second); + Version = long.Parse(combined); + } + /// + /// The version reflected by the migration + /// + public long Version + { + get { return _version; } + private set { _version = value; } + } + + /// + /// Set to true to ignore this migration. + /// + public bool Ignore + { + get { return _ignore; } + set { _ignore = value; } + } +} diff --git a/src/Migrator/Framework/MigrationException.cs b/src/Migrator/Framework/MigrationException.cs new file mode 100644 index 00000000..176a099a --- /dev/null +++ b/src/Migrator/Framework/MigrationException.cs @@ -0,0 +1,37 @@ +#region License + +//The contents of this file are subject to the Mozilla Public License +//Version 1.1 (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.mozilla.org/MPL/ +//Software distributed under the License is distributed on an "AS IS" +//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +//License for the specific language governing rights and limitations +//under the License. + +#endregion + +using System; + +namespace DotNetProjects.Migrator.Framework; + +/// +/// Base class for migration errors. +/// +public class MigrationException : Exception +{ + public MigrationException(string message) + : base(message) + { + } + + public MigrationException(string message, Exception cause) + : base(message, cause) + { + } + + public MigrationException(string migration, int version, Exception innerException) + : base(string.Format("Exception in migration {0} (#{1})", migration, version), innerException) + { + } +} \ No newline at end of file diff --git a/src/Migrator/Framework/MigratorDbType.cs b/src/Migrator/Framework/MigratorDbType.cs new file mode 100644 index 00000000..6e5e09f2 --- /dev/null +++ b/src/Migrator/Framework/MigratorDbType.cs @@ -0,0 +1,35 @@ +namespace DotNetProjects.Migrator.Framework; + +public enum MigratorDbType +{ + AnsiString = 0, + Binary = 1, + Byte = 2, + Boolean = 3, + Currency = 4, + Date = 5, + DateTime = 6, + Decimal = 7, + Double = 8, + Guid = 9, + Int16 = 10, + Int32 = 11, + Int64 = 12, + Object = 13, + SByte = 14, + Single = 15, + String = 16, + Time = 17, + UInt16 = 18, + UInt32 = 19, + UInt64 = 20, + VarNumeric = 21, + AnsiStringFixedLength = 22, + StringFixedLength = 23, + Xml = 25, + DateTime2 = 26, + DateTimeOffset = 27, + + Json = 9000, + Interval = 9001 +} diff --git a/src/Migrator/Framework/SchemaBuilder/AddColumnExpression.cs b/src/Migrator/Framework/SchemaBuilder/AddColumnExpression.cs new file mode 100644 index 00000000..3a070dd9 --- /dev/null +++ b/src/Migrator/Framework/SchemaBuilder/AddColumnExpression.cs @@ -0,0 +1,39 @@ +#region License + +//The contents of this file are subject to the Mozilla Public License +//Version 1.1 (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.mozilla.org/MPL/ +//Software distributed under the License is distributed on an "AS IS" +//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +//License for the specific language governing rights and limitations +//under the License. + +#endregion + +namespace DotNetProjects.Migrator.Framework.SchemaBuilder; + +public class AddColumnExpression : ISchemaBuilderExpression +{ + private readonly IFluentColumn _column; + private readonly string _toTable; + + public AddColumnExpression(string toTable, IFluentColumn column) + { + _column = column; + _toTable = toTable; + } + + public void Create(ITransformationProvider provider) + { + provider.AddColumn(_toTable, _column.Name, _column.Type, _column.Size, _column.ColumnProperty, _column.DefaultValue); + + if (_column.ForeignKey != null) + { + provider.AddForeignKey( + "FK_" + _toTable + "_" + _column.Name + "_" + _column.ForeignKey.PrimaryTable + "_" + + _column.ForeignKey.PrimaryKey, + _toTable, _column.Name, _column.ForeignKey.PrimaryTable, _column.ForeignKey.PrimaryKey, _column.Constraint); + } + } +} \ No newline at end of file diff --git a/src/Migrator.Framework/SchemaBuilder/AddTableExpression.cs b/src/Migrator/Framework/SchemaBuilder/AddTableExpression.cs similarity index 58% rename from src/Migrator.Framework/SchemaBuilder/AddTableExpression.cs rename to src/Migrator/Framework/SchemaBuilder/AddTableExpression.cs index 7b816784..c86ec384 100644 --- a/src/Migrator.Framework/SchemaBuilder/AddTableExpression.cs +++ b/src/Migrator/Framework/SchemaBuilder/AddTableExpression.cs @@ -11,20 +11,19 @@ #endregion -namespace Migrator.Framework.SchemaBuilder +namespace DotNetProjects.Migrator.Framework.SchemaBuilder; + +public class AddTableExpression : ISchemaBuilderExpression { - public class AddTableExpression : ISchemaBuilderExpression - { - readonly string _newTable; + private readonly string _newTable; - public AddTableExpression(string newTable) - { - _newTable = newTable; - } + public AddTableExpression(string newTable) + { + _newTable = newTable; + } - public void Create(ITransformationProvider provider) - { - provider.AddTable(_newTable); - } - } + public void Create(ITransformationProvider provider) + { + provider.AddTable(_newTable); + } } \ No newline at end of file diff --git a/src/Migrator.Framework/SchemaBuilder/DeleteTableExpression.cs b/src/Migrator/Framework/SchemaBuilder/DeleteTableExpression.cs similarity index 57% rename from src/Migrator.Framework/SchemaBuilder/DeleteTableExpression.cs rename to src/Migrator/Framework/SchemaBuilder/DeleteTableExpression.cs index c6159a67..f6e928f0 100644 --- a/src/Migrator.Framework/SchemaBuilder/DeleteTableExpression.cs +++ b/src/Migrator/Framework/SchemaBuilder/DeleteTableExpression.cs @@ -11,20 +11,19 @@ #endregion -namespace Migrator.Framework.SchemaBuilder +namespace DotNetProjects.Migrator.Framework.SchemaBuilder; + +public class DeleteTableExpression : ISchemaBuilderExpression { - public class DeleteTableExpression : ISchemaBuilderExpression - { - readonly string _tableName; + private readonly string _tableName; - public DeleteTableExpression(string tableName) - { - _tableName = tableName; - } + public DeleteTableExpression(string tableName) + { + _tableName = tableName; + } - public void Create(ITransformationProvider provider) - { - provider.RemoveTable(_tableName); - } - } + public void Create(ITransformationProvider provider) + { + provider.RemoveTable(_tableName); + } } \ No newline at end of file diff --git a/src/Migrator/Framework/SchemaBuilder/FluentColumn.cs b/src/Migrator/Framework/SchemaBuilder/FluentColumn.cs new file mode 100644 index 00000000..f6a39776 --- /dev/null +++ b/src/Migrator/Framework/SchemaBuilder/FluentColumn.cs @@ -0,0 +1,81 @@ +#region License + +//The contents of this file are subject to the Mozilla Public License +//Version 1.1 (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.mozilla.org/MPL/ +//Software distributed under the License is distributed on an "AS IS" +//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +//License for the specific language governing rights and limitations +//under the License. + +#endregion + +using System.Data; + +namespace DotNetProjects.Migrator.Framework.SchemaBuilder; + +public class FluentColumn : IFluentColumn +{ + private readonly Column _inner; + + public FluentColumn(string columnName) + { + _inner = new Column(columnName); + } + + public ColumnProperty ColumnProperty + { + get { return _inner.ColumnProperty; } + set { _inner.ColumnProperty = value; } + } + + public string Name + { + get { return _inner.Name; } + set { _inner.Name = value; } + } + + public DbType Type + { + get { return _inner.Type; } + set { _inner.Type = value; } + } + + public MigratorDbType MigratorDbType + { + get { return _inner.MigratorDbType; } + set { _inner.MigratorDbType = value; } + } + + public int Size + { + get { return _inner.Size; } + set { _inner.Size = value; } + } + + public bool IsIdentity + { + get { return _inner.IsIdentity; } + } + + public bool IsPrimaryKey + { + get { return _inner.IsPrimaryKey; } + } + + public object DefaultValue + { + get { return _inner.DefaultValue; } + set { _inner.DefaultValue = value; } + } + + public ForeignKeyConstraintType Constraint { get; set; } + + public ForeignKey ForeignKey { get; set; } + + public bool IsPrimaryKeyNonClustered + { + get { return _inner.IsPrimaryKeyNonClustered; } + } +} diff --git a/src/Migrator.Framework/IColumn.cs b/src/Migrator/Framework/SchemaBuilder/ForeignKey.cs similarity index 61% rename from src/Migrator.Framework/IColumn.cs rename to src/Migrator/Framework/SchemaBuilder/ForeignKey.cs index aa2a4d18..e3318f82 100644 --- a/src/Migrator.Framework/IColumn.cs +++ b/src/Migrator/Framework/SchemaBuilder/ForeignKey.cs @@ -11,24 +11,17 @@ #endregion -using System.Data; +namespace DotNetProjects.Migrator.Framework.SchemaBuilder; -namespace Migrator.Framework +public class ForeignKey { - public interface IColumn - { - ColumnProperty ColumnProperty { get; set; } + public ForeignKey(string primaryTable, string primaryKey) + { + PrimaryTable = primaryTable; + PrimaryKey = primaryKey; + } - string Name { get; set; } + public string PrimaryTable { get; set; } - DbType Type { get; set; } - - int Size { get; set; } - - bool IsIdentity { get; } - - bool IsPrimaryKey { get; } - - object DefaultValue { get; set; } - } + public string PrimaryKey { get; set; } } \ No newline at end of file diff --git a/src/Migrator/Framework/SchemaBuilder/IColumnOptions.cs b/src/Migrator/Framework/SchemaBuilder/IColumnOptions.cs new file mode 100644 index 00000000..0b28b6aa --- /dev/null +++ b/src/Migrator/Framework/SchemaBuilder/IColumnOptions.cs @@ -0,0 +1,12 @@ +using System.Data; + +namespace DotNetProjects.Migrator.Framework.SchemaBuilder; + +public interface IColumnOptions +{ + SchemaBuilder OfType(DbType dbType); + + SchemaBuilder WithSize(int size); + + IForeignKeyOptions AsForeignKey(); +} \ No newline at end of file diff --git a/src/Migrator.Framework/SchemaBuilder/IDeleteTableOptions.cs b/src/Migrator/Framework/SchemaBuilder/IDeleteTableOptions.cs similarity index 68% rename from src/Migrator.Framework/SchemaBuilder/IDeleteTableOptions.cs rename to src/Migrator/Framework/SchemaBuilder/IDeleteTableOptions.cs index 17f63963..9b3581fd 100644 --- a/src/Migrator.Framework/SchemaBuilder/IDeleteTableOptions.cs +++ b/src/Migrator/Framework/SchemaBuilder/IDeleteTableOptions.cs @@ -11,14 +11,13 @@ #endregion -namespace Migrator.Framework.SchemaBuilder +namespace DotNetProjects.Migrator.Framework.SchemaBuilder; + +public interface IDeleteTableOptions { - public interface IDeleteTableOptions - { - SchemaBuilder WithTable(string name); + SchemaBuilder WithTable(string name); - SchemaBuilder AddTable(string name); + SchemaBuilder AddTable(string name); - IDeleteTableOptions DeleteTable(string name); - } + IDeleteTableOptions DeleteTable(string name); } \ No newline at end of file diff --git a/src/Migrator.Framework/SchemaBuilder/IFluentColumn.cs b/src/Migrator/Framework/SchemaBuilder/IFluentColumn.cs similarity index 72% rename from src/Migrator.Framework/SchemaBuilder/IFluentColumn.cs rename to src/Migrator/Framework/SchemaBuilder/IFluentColumn.cs index 0d7debdf..e5a90e0a 100644 --- a/src/Migrator.Framework/SchemaBuilder/IFluentColumn.cs +++ b/src/Migrator/Framework/SchemaBuilder/IFluentColumn.cs @@ -11,12 +11,11 @@ #endregion -namespace Migrator.Framework.SchemaBuilder +namespace DotNetProjects.Migrator.Framework.SchemaBuilder; + +public interface IFluentColumn : IColumn { - public interface IFluentColumn : IColumn - { - ForeignKeyConstraintType Constraint { get; set; } + ForeignKeyConstraintType Constraint { get; set; } - ForeignKey ForeignKey { get; set; } - } + ForeignKey ForeignKey { get; set; } } \ No newline at end of file diff --git a/src/Migrator.Framework/SchemaBuilder/IForeignKeyOptions.cs b/src/Migrator/Framework/SchemaBuilder/IForeignKeyOptions.cs similarity index 74% rename from src/Migrator.Framework/SchemaBuilder/IForeignKeyOptions.cs rename to src/Migrator/Framework/SchemaBuilder/IForeignKeyOptions.cs index 3cb52cbf..ecd4ecd8 100644 --- a/src/Migrator.Framework/SchemaBuilder/IForeignKeyOptions.cs +++ b/src/Migrator/Framework/SchemaBuilder/IForeignKeyOptions.cs @@ -11,10 +11,9 @@ #endregion -namespace Migrator.Framework.SchemaBuilder +namespace DotNetProjects.Migrator.Framework.SchemaBuilder; + +public interface IForeignKeyOptions { - public interface IForeignKeyOptions - { - SchemaBuilder ReferencedTo(string primaryKeyTable, string primaryKeyColumn); - } + SchemaBuilder ReferencedTo(string primaryKeyTable, string primaryKeyColumn); } \ No newline at end of file diff --git a/src/Migrator.Framework/SchemaBuilder/ISchemaBuilderExpression.cs b/src/Migrator/Framework/SchemaBuilder/ISchemaBuilderExpression.cs similarity index 76% rename from src/Migrator.Framework/SchemaBuilder/ISchemaBuilderExpression.cs rename to src/Migrator/Framework/SchemaBuilder/ISchemaBuilderExpression.cs index 0db89f88..fe616f96 100644 --- a/src/Migrator.Framework/SchemaBuilder/ISchemaBuilderExpression.cs +++ b/src/Migrator/Framework/SchemaBuilder/ISchemaBuilderExpression.cs @@ -11,10 +11,9 @@ #endregion -namespace Migrator.Framework.SchemaBuilder +namespace DotNetProjects.Migrator.Framework.SchemaBuilder; + +public interface ISchemaBuilderExpression { - public interface ISchemaBuilderExpression - { - void Create(ITransformationProvider provider); - } + void Create(ITransformationProvider provider); } \ No newline at end of file diff --git a/src/Migrator.Framework/SchemaBuilder/RenameTableExpression.cs b/src/Migrator/Framework/SchemaBuilder/RenameTableExpression.cs similarity index 52% rename from src/Migrator.Framework/SchemaBuilder/RenameTableExpression.cs rename to src/Migrator/Framework/SchemaBuilder/RenameTableExpression.cs index 63e5c682..15625861 100644 --- a/src/Migrator.Framework/SchemaBuilder/RenameTableExpression.cs +++ b/src/Migrator/Framework/SchemaBuilder/RenameTableExpression.cs @@ -11,22 +11,21 @@ #endregion -namespace Migrator.Framework.SchemaBuilder +namespace DotNetProjects.Migrator.Framework.SchemaBuilder; + +public class RenameTableExpression : ISchemaBuilderExpression { - public class RenameTableExpression : ISchemaBuilderExpression - { - readonly string _newName; - readonly string _oldName; + private readonly string _newName; + private readonly string _oldName; - public RenameTableExpression(string oldName, string newName) - { - _oldName = oldName; - _newName = newName; - } + public RenameTableExpression(string oldName, string newName) + { + _oldName = oldName; + _newName = newName; + } - public void Create(ITransformationProvider provider) - { - provider.RenameTable(_oldName, _newName); - } - } + public void Create(ITransformationProvider provider) + { + provider.RenameTable(_oldName, _newName); + } } \ No newline at end of file diff --git a/src/Migrator/Framework/SchemaBuilder/SchemaBuilder.cs b/src/Migrator/Framework/SchemaBuilder/SchemaBuilder.cs new file mode 100644 index 00000000..675e446b --- /dev/null +++ b/src/Migrator/Framework/SchemaBuilder/SchemaBuilder.cs @@ -0,0 +1,184 @@ +#region License + +//The contents of this file are subject to the Mozilla Public License +//Version 1.1 (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.mozilla.org/MPL/ +//Software distributed under the License is distributed on an "AS IS" +//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +//License for the specific language governing rights and limitations +//under the License. + +#endregion + +using System; +using System.Collections.Generic; +using System.Data; + +namespace DotNetProjects.Migrator.Framework.SchemaBuilder; + +public class SchemaBuilder : IColumnOptions, IForeignKeyOptions, IDeleteTableOptions +{ + private readonly IList _exprs; + private IFluentColumn _currentColumn; + private string _currentTable; + + public SchemaBuilder() + { + _exprs = new List(); + } + + public IEnumerable Expressions + { + get { return _exprs; } + } + + public SchemaBuilder OfType(DbType columnType) + { + _currentColumn.Type = columnType; + + return this; + } + + public SchemaBuilder WithSize(int size) + { + if (size == 0) + { + throw new ArgumentNullException("size", "Size must be greater than zero"); + } + + _currentColumn.Size = size; + + return this; + } + + public IForeignKeyOptions AsForeignKey() + { + return this; + } + + /// + /// Adds a Table to be created to the Schema + /// + /// Table name to be created + /// SchemaBuilder for chaining + public SchemaBuilder AddTable(string name) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentNullException("name"); + } + + _exprs.Add(new AddTableExpression(name)); + _currentTable = name; + + return this; + } + + public IDeleteTableOptions DeleteTable(string name) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentNullException("name"); + } + + _currentTable = ""; + _currentColumn = null; + + _exprs.Add(new DeleteTableExpression(name)); + + return this; + } + + /// + /// Reference an existing table. + /// + /// Table to reference + /// SchemaBuilder for chaining + public SchemaBuilder WithTable(string name) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentNullException("name"); + } + + _currentTable = name; + + return this; + } + + public SchemaBuilder ReferencedTo(string primaryKeyTable, string primaryKeyColumn) + { + _currentColumn.Constraint = ForeignKeyConstraintType.NoAction; + _currentColumn.ForeignKey = new ForeignKey(primaryKeyTable, primaryKeyColumn); + return this; + } + + /// + /// Reference an existing table. + /// + /// Table to reference + /// SchemaBuilder for chaining + public SchemaBuilder RenameTable(string newName) + { + if (string.IsNullOrEmpty(newName)) + { + throw new ArgumentNullException("newName"); + } + + _exprs.Add(new RenameTableExpression(_currentTable, newName)); + _currentTable = newName; + + return this; + } + + /// + /// Adds a Column to be created + /// + /// Column name to be added + /// IColumnOptions to restrict chaining + public IColumnOptions AddColumn(string name) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentNullException("name"); + } + + if (string.IsNullOrEmpty(_currentTable)) + { + throw new ArgumentException("missing referenced table"); + } + + IFluentColumn column = new FluentColumn(name); + _currentColumn = column; + + _exprs.Add(new AddColumnExpression(_currentTable, column)); + return this; + } + + public SchemaBuilder WithProperty(ColumnProperty columnProperty) + { + _currentColumn.ColumnProperty = columnProperty; + + return this; + } + + public SchemaBuilder WithDefaultValue(object defaultValue) + { + if (defaultValue == null) + { + throw new ArgumentNullException("defaultValue", "DefaultValue cannot be null or empty"); + } + + _currentColumn.DefaultValue = defaultValue; + + return this; + } + + public SchemaBuilder WithConstraint(ForeignKeyConstraintType action) + { + _currentColumn.Constraint = action; + + return this; + } +} \ No newline at end of file diff --git a/src/Migrator/Framework/StringUtils.cs b/src/Migrator/Framework/StringUtils.cs new file mode 100644 index 00000000..5e2be26f --- /dev/null +++ b/src/Migrator/Framework/StringUtils.cs @@ -0,0 +1,45 @@ +using System.Text; +using System.Text.RegularExpressions; + +namespace DotNetProjects.Migrator.Framework; + +public class StringUtils +{ + /// + /// Convert a classname to something more readable. + /// ex.: CreateATable => Create a table + /// + /// + /// + public static string ToHumanName(string className) + { + var name = Regex.Replace(className, "^[_0-9]*|[_0-9]*$", ""); + + name = Regex.Replace(name, "([A-Z])", " $1").Substring(1); + + return name.Substring(0, 1).ToUpper() + name.Substring(1).ToLower(); + } + + /// + /// + /// + /// + /// + /// + /// + public static string ReplaceOnce(string template, string placeholder, string replacement) + { + var loc = template.IndexOf(placeholder); + if (loc < 0) + { + return template; + } + else + { + return new StringBuilder(template.Substring(0, loc)) + .Append(replacement) + .Append(template.Substring(loc + placeholder.Length)) + .ToString(); + } + } +} diff --git a/src/Migrator/Framework/Unique.cs b/src/Migrator/Framework/Unique.cs new file mode 100644 index 00000000..6ad3ce93 --- /dev/null +++ b/src/Migrator/Framework/Unique.cs @@ -0,0 +1,8 @@ +namespace DotNetProjects.Migrator.Framework; + +public class Unique : IDbField +{ + public string Name { get; set; } + + public string[] KeyColumns { get; set; } +} diff --git a/src/Migrator/Framework/ViewColumn.cs b/src/Migrator/Framework/ViewColumn.cs new file mode 100644 index 00000000..5ed6b67a --- /dev/null +++ b/src/Migrator/Framework/ViewColumn.cs @@ -0,0 +1,13 @@ +namespace DotNetProjects.Migrator.Framework; + +public class ViewColumn : IViewElement +{ + public string Prefix { get; } + public string ColumnName { get; } + + public ViewColumn(string prefix, string columnName) + { + Prefix = prefix; + ColumnName = columnName; + } +} diff --git a/src/Migrator/Framework/ViewField.cs b/src/Migrator/Framework/ViewField.cs new file mode 100644 index 00000000..3d3a8289 --- /dev/null +++ b/src/Migrator/Framework/ViewField.cs @@ -0,0 +1,40 @@ +#region License + +//The contents of this file are subject to the Mozilla Public License +//Version 1.1 (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.mozilla.org/MPL/ +//Software distributed under the License is distributed on an "AS IS" +//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +//License for the specific language governing rights and limitations +//under the License. + +#endregion + +namespace DotNetProjects.Migrator.Framework; + +/// +/// Represents a table column. +/// +public class ViewField : IViewField +{ + public ViewField(string ColumnName) + { + this.ColumnName = ColumnName; + } + + public ViewField(string ColumnName, string TableName, string KeyColumnName, string ParentTableName, string ParentKeyColumnName) + { + this.ColumnName = ColumnName; + this.TableName = TableName; + this.KeyColumnName = KeyColumnName; + this.ParentTableName = ParentTableName; + this.ParentKeyColumnName = ParentKeyColumnName; + } + + public string TableName { get; set; } + public string ColumnName { get; set; } + public string KeyColumnName { get; set; } + public string ParentTableName { get; set; } + public string ParentKeyColumnName { get; set; } +} diff --git a/src/Migrator/Framework/ViewJoin.cs b/src/Migrator/Framework/ViewJoin.cs new file mode 100644 index 00000000..69dee6e3 --- /dev/null +++ b/src/Migrator/Framework/ViewJoin.cs @@ -0,0 +1,37 @@ +using System.Linq.Expressions; + +namespace DotNetProjects.Migrator.Framework; + +public class ViewJoin : IViewElement +{ + public string TableName { get; } + public string TableAlias { get; } + public string ColumnName { get; } + public string ParentTableName { get; } + public string ParentTableAlias { get; } + public string ParentColumnName { get; } + public JoinType JoinType { get; } + + public ViewJoin(string tableName, string columnName, string parentTableName, string parentColumnName, JoinType joinType) + : this(tableName, string.Empty, columnName, parentTableName, string.Empty, parentColumnName, joinType) + => Expression.Empty(); + + public ViewJoin(string tableName, string tableAlias, string columnName, string parentTableName, string parentColumnName, JoinType joinType) + : this(tableName, tableAlias, columnName, parentTableName, string.Empty, parentColumnName, joinType) + => Expression.Empty(); + + public ViewJoin(JoinType joinType, string tableName, string columnName, string parentTableName, string parentTableAlias, string parentColumnName) + : this(tableName, string.Empty, columnName, parentTableName, parentTableAlias, parentColumnName, joinType) + => Expression.Empty(); + + public ViewJoin(string tableName, string tableAlias, string columnName, string parentTableName, string parentTableAlias, string parentColumnName, JoinType joinType) + { + TableName = tableName; + TableAlias = tableAlias; + ColumnName = columnName; + ParentTableName = parentTableName; + ParentTableAlias = parentTableAlias; + ParentColumnName = parentColumnName; + JoinType = joinType; + } +} diff --git a/src/Migrator/IrreversibleMigrationException.cs b/src/Migrator/IrreversibleMigrationException.cs index 25b3401f..f57e1b36 100644 --- a/src/Migrator/IrreversibleMigrationException.cs +++ b/src/Migrator/IrreversibleMigrationException.cs @@ -13,17 +13,19 @@ using System; -namespace Migrator +namespace DotNetProjects.Migrator; + +/// +/// Exception thrown in a migration Down() method +/// when changes can't be undone. +/// +#if NETSTANDARD +#else +[Serializable] +#endif +public class IrreversibleMigrationException : Exception { - /// - /// Exception thrown in a migration Down() method - /// when changes can't be undone. - /// - [Serializable] - public class IrreversibleMigrationException : Exception - { - public IrreversibleMigrationException() : base("Irreversible migration") - { - } - } + public IrreversibleMigrationException() : base("Irreversible migration") + { + } } \ No newline at end of file diff --git a/src/Migrator/MigrateAnywhere.cs b/src/Migrator/MigrateAnywhere.cs index 6505b8fe..2c374902 100644 --- a/src/Migrator/MigrateAnywhere.cs +++ b/src/Migrator/MigrateAnywhere.cs @@ -1,112 +1,135 @@ -using System; -using System.Collections.Generic; -using Migrator.Framework; -using Migrator.Providers; +using System; +using System.Collections.Generic; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers; +using DotNetProjects.Migrator.Providers.Impl.SQLite; -namespace Migrator -{ - /// - /// Description of MigrateAnywhere. - /// - public class MigrateAnywhere : BaseMigrate - { - bool _goForward; - - public MigrateAnywhere(List availableMigrations, ITransformationProvider provider, ILogger logger) - : base(availableMigrations, provider, logger) - { - _current = 0; - if (provider.AppliedMigrations.Count > 0) - { - _current = provider.AppliedMigrations[provider.AppliedMigrations.Count - 1]; - } - _goForward = false; - } - - public override long Next - { - get - { - return _goForward - ? NextMigration() - : PreviousMigration(); - } - } - - public override long Previous - { - get - { - return _goForward - ? PreviousMigration() - : NextMigration(); - } - } - - public override bool Continue(long version) - { - // If we're going backwards and our current is less than the target, - // reverse direction. Also, start over at zero to make sure we catch - // any merged migrations that are less than the current target. - if (!_goForward && version >= Current) - { - _goForward = true; - Current = 0; - Iterate(); - } - - // We always finish on going forward. So continue if we're still - // going backwards, or if there are no migrations left in the forward direction. - return !_goForward || Current <= version; - } - - public override void Migrate(IMigration migration) - { - _provider.BeginTransaction(); - var attr = (MigrationAttribute) Attribute.GetCustomAttribute(migration.GetType(), typeof (MigrationAttribute)); - - if (_provider.AppliedMigrations.Contains(attr.Version)) - { - RemoveMigration(migration, attr); - } - else - { - ApplyMigration(migration, attr); - } - } - - void ApplyMigration(IMigration migration, MigrationAttribute attr) - { - // we're adding this one - _logger.MigrateUp(Current, migration.Name); - if (! DryRun) - { - var tProvider = _provider as TransformationProvider; - if (tProvider != null) - tProvider.CurrentMigration = migration; - - migration.Up(); - _provider.MigrationApplied(attr.Version, attr.Scope); - _provider.Commit(); - migration.AfterUp(); - } - } +namespace DotNetProjects.Migrator; + +/// +/// Description of MigrateAnywhere. +/// +public class MigrateAnywhere : BaseMigrate +{ + private bool _goForward; + + public MigrateAnywhere(List availableMigrations, ITransformationProvider provider, ILogger logger) + : base(availableMigrations, provider, logger) + { + _current = 0; + if (provider.AppliedMigrations.Count > 0) + { + _current = provider.AppliedMigrations[provider.AppliedMigrations.Count - 1]; + } + _goForward = false; + } + + public override long Next + { + get + { + return _goForward + ? NextMigration() + : PreviousMigration(); + } + } + + public override long Previous + { + get + { + return _goForward + ? PreviousMigration() + : NextMigration(); + } + } + + public override bool Continue(long version) + { + // If we're going backwards and our current is less than the target, + // reverse direction. Also, start over at zero to make sure we catch + // any merged migrations that are less than the current target. + if (!_goForward && version >= Current) + { + _goForward = true; + Current = 0; + Iterate(); + } + + // We always finish on going forward. So continue if we're still + // going backwards, or if there are no migrations left in the forward direction. + return !_goForward || Current <= version; + } + + public override void Migrate(IMigration migration) + { +#if NETSTANDARD + var attr = migration.GetType().GetTypeInfo().GetCustomAttribute(); +#else + var attr = (MigrationAttribute)Attribute.GetCustomAttribute(migration.GetType(), typeof(MigrationAttribute)); +#endif + var foreignKeysWasOn = false; + if (_provider is SQLiteTransformationProvider sqlite) + { + foreignKeysWasOn = sqlite.IsPragmaForeignKeysOn(); + if (foreignKeysWasOn) + { + sqlite.SetPragmaForeignKeys(false); + } + } - void RemoveMigration(IMigration migration, MigrationAttribute attr) - { - // we're removing this one - _logger.MigrateDown(Current, migration.Name); - if (! DryRun) - { - var tProvider = _provider as TransformationProvider; - if (tProvider != null) - tProvider.CurrentMigration = migration; + _provider.BeginTransaction(); + + if (_provider.AppliedMigrations.Contains(attr.Version)) + { + RemoveMigration(migration, attr); + } + else + { + ApplyMigration(migration, attr); + } - migration.Down(); - _provider.MigrationUnApplied(attr.Version, attr.Scope); - _provider.Commit(); - migration.AfterDown(); - } - } - } -} \ No newline at end of file + if (foreignKeysWasOn && _provider is SQLiteTransformationProvider sqlite2) + { + sqlite2.SetPragmaForeignKeys(true); + } + } + + private void ApplyMigration(IMigration migration, MigrationAttribute attr) + { + // we're adding this one + _logger.MigrateUp(Current, migration.Name); + if (!DryRun) + { + var tProvider = _provider as TransformationProvider; + if (tProvider != null) + { + tProvider.CurrentMigration = migration; + } + + migration.Up(); + _provider.MigrationApplied(attr.Version, attr.Scope); + _provider.Commit(); + migration.AfterUp(); + } + } + + private void RemoveMigration(IMigration migration, MigrationAttribute attr) + { + // we're removing this one + _logger.MigrateDown(Current, migration.Name); + if (!DryRun) + { + var tProvider = _provider as TransformationProvider; + if (tProvider != null) + { + tProvider.CurrentMigration = migration; + } + + migration.Down(); + _provider.MigrationUnApplied(attr.Version, attr.Scope); + _provider.Commit(); + migration.AfterDown(); + } + } +} diff --git a/src/Migrator/MigrateDown.cs b/src/Migrator/MigrateDown.cs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Migrator/MigrateUp.cs b/src/Migrator/MigrateUp.cs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Migrator/MigrationComparer.cs b/src/Migrator/MigrationComparer.cs index 284132ae..65a51d6b 100644 --- a/src/Migrator/MigrationComparer.cs +++ b/src/Migrator/MigrationComparer.cs @@ -13,31 +13,34 @@ using System; using System.Collections.Generic; -using Migrator.Framework; +using DotNetProjects.Migrator.Framework; -namespace Migrator +namespace DotNetProjects.Migrator; + +/// +/// Comparer of Migration by their version attribute. +/// +public class MigrationTypeComparer : IComparer { - /// - /// Comparer of Migration by their version attribute. - /// - public class MigrationTypeComparer : IComparer - { - readonly bool _ascending = true; - - public MigrationTypeComparer(bool ascending) - { - _ascending = ascending; - } - - public int Compare(Type x, Type y) - { - var attribOfX = (MigrationAttribute) Attribute.GetCustomAttribute(x, typeof (MigrationAttribute)); - var attribOfY = (MigrationAttribute) Attribute.GetCustomAttribute(y, typeof (MigrationAttribute)); - - if (_ascending) - return attribOfX.Version.CompareTo(attribOfY.Version); - else - return attribOfY.Version.CompareTo(attribOfX.Version); - } - } -} \ No newline at end of file + private readonly bool _ascending = true; + + public MigrationTypeComparer(bool ascending) + { + _ascending = ascending; + } + + public int Compare(Type x, Type y) + { + var attribOfX = (MigrationAttribute)Attribute.GetCustomAttribute(x, typeof(MigrationAttribute)); + var attribOfY = (MigrationAttribute)Attribute.GetCustomAttribute(y, typeof(MigrationAttribute)); + + if (_ascending) + { + return attribOfX.Version.CompareTo(attribOfY.Version); + } + else + { + return attribOfY.Version.CompareTo(attribOfX.Version); + } + } +} diff --git a/src/Migrator/MigrationLoader.cs b/src/Migrator/MigrationLoader.cs index 1f32985b..b3ef87bd 100644 --- a/src/Migrator/MigrationLoader.cs +++ b/src/Migrator/MigrationLoader.cs @@ -1,136 +1,170 @@ using System; using System.Collections.Generic; using System.Reflection; -using Migrator.Framework; +using System.Linq; +using DotNetProjects.Migrator.Framework; -namespace Migrator +namespace DotNetProjects.Migrator; + +/// +/// Handles inspecting code to find all of the Migrations in assemblies and reading +/// other metadata such as the last revision, etc. +/// +public class MigrationLoader { - /// - /// Handles inspecting code to find all of the Migrations in assemblies and reading - /// other metadata such as the last revision, etc. - /// - public class MigrationLoader - { - readonly List _migrationsTypes = new List(); - readonly ITransformationProvider _provider; - - public MigrationLoader(ITransformationProvider provider, Assembly migrationAssembly, bool trace) - { - _provider = provider; - AddMigrations(migrationAssembly); - - if (trace) - { - provider.Logger.Trace("Loaded migrations:"); - foreach (Type t in _migrationsTypes) - { - provider.Logger.Trace("{0} {1}", GetMigrationVersion(t).ToString().PadLeft(5), StringUtils.ToHumanName(t.Name)); - } - } - } - - /// - /// Returns registered migration types. - /// - public List MigrationsTypes - { - get { return _migrationsTypes; } - } - - /// - /// Returns the last version of the migrations. - /// - public long LastVersion - { - get - { - if (_migrationsTypes.Count == 0) - return 0; - return GetMigrationVersion(_migrationsTypes[_migrationsTypes.Count - 1]); - } - } - - public void AddMigrations(Assembly migrationAssembly) - { - if (migrationAssembly != null) - _migrationsTypes.AddRange(GetMigrationTypes(migrationAssembly)); - } - - /// - /// Check for duplicated version in migrations. - /// - /// CheckForDuplicatedVersion - public void CheckForDuplicatedVersion() - { - var versions = new List(); - foreach (Type t in _migrationsTypes) - { - long version = GetMigrationVersion(t); - - if (versions.Contains(version)) - throw new DuplicatedVersionException(version); - - versions.Add(version); - } - } - - /// - /// Collect migrations in one Assembly. - /// - /// The Assembly to browse. - /// The migrations collection - public static List GetMigrationTypes(Assembly asm) - { - var migrations = new List(); - foreach (Type t in asm.GetExportedTypes()) - { - var attrib = - (MigrationAttribute) Attribute.GetCustomAttribute(t, typeof (MigrationAttribute)); - - if (attrib != null && typeof (IMigration).IsAssignableFrom(t) && !attrib.Ignore) - { - migrations.Add(t); - } - } - - migrations.Sort(new MigrationTypeComparer(true)); - return migrations; - } - - /// - /// Returns the version of the migration - /// MigrationAttribute. - /// - /// Migration type. - /// Version number sepcified in the attribute - public static long GetMigrationVersion(Type t) - { - var attrib = (MigrationAttribute) - Attribute.GetCustomAttribute(t, typeof (MigrationAttribute)); - - return attrib.Version; - } - - public List GetAvailableMigrations() - { - //List availableMigrations = new List(); - _migrationsTypes.Sort(new MigrationTypeComparer(true)); - return _migrationsTypes.ConvertAll(GetMigrationVersion); - } - - public IMigration GetMigration(long version) - { - foreach (Type t in _migrationsTypes) - { - if (GetMigrationVersion(t) == version) - { - var migration = (IMigration) Activator.CreateInstance(t); - migration.Database = _provider; - return migration; - } - } - - return null; - } - } -} \ No newline at end of file + private readonly List _migrationsTypes = new List(); + private readonly ITransformationProvider _provider; + + public MigrationLoader(ITransformationProvider provider, Assembly migrationAssembly, bool trace) + { + _provider = provider; + AddMigrations(migrationAssembly); + + if (trace) + { + provider.Logger.Trace("Loaded migrations:"); + foreach (var t in _migrationsTypes) + { + provider.Logger.Trace("{0} {1}", GetMigrationVersion(t).ToString().PadLeft(5), StringUtils.ToHumanName(t.Name)); + } + } + } + + public MigrationLoader(ITransformationProvider provider, bool trace, params Type[] migrationTypes) + { + _provider = provider; + _migrationsTypes.AddRange(migrationTypes); + + if (trace) + { + provider.Logger.Trace("Loaded migrations:"); + foreach (var t in _migrationsTypes) + { + provider.Logger.Trace("{0} {1}", GetMigrationVersion(t).ToString().PadLeft(5), StringUtils.ToHumanName(t.Name)); + } + } + } + + /// + /// Returns registered migration types. + /// + public virtual List MigrationsTypes + { + get { return _migrationsTypes; } + } + + /// + /// Returns the last version of the migrations. + /// + public virtual long LastVersion + { + get + { + if (_migrationsTypes.Count == 0) + { + return 0; + } + + return GetMigrationVersion(_migrationsTypes[_migrationsTypes.Count - 1]); + } + } + + public virtual void AddMigrations(Assembly migrationAssembly) + { + if (migrationAssembly != null) + { + _migrationsTypes.AddRange(GetMigrationTypes(migrationAssembly)); + } + } + + /// + /// Check for duplicated version in migrations. + /// + /// CheckForDuplicatedVersion + public virtual void CheckForDuplicatedVersion() + { + var versions = new List(); + foreach (var t in _migrationsTypes) + { + var version = GetMigrationVersion(t); + + if (versions.Contains(version)) + { + throw new DuplicatedVersionException(version); + } + + versions.Add(version); + } + } + + /// + /// Collect migrations in one Assembly. + /// + /// The Assembly to browse. + /// The migrations collection + public static List GetMigrationTypes(Assembly asm) + { + var migrations = new List(); + foreach (var t in asm.GetExportedTypes()) + { + + +#if NETSTANDARD + var attrib = t.GetTypeInfo().GetCustomAttribute(); + if (attrib != null && typeof(IMigration).GetTypeInfo().IsAssignableFrom(t) && !attrib.Ignore) + { + migrations.Add(t); + } +#else + var attrib = (MigrationAttribute)Attribute.GetCustomAttribute(t, typeof(MigrationAttribute)); + if (attrib != null && typeof(IMigration).IsAssignableFrom(t) && !attrib.Ignore) + { + migrations.Add(t); + } +#endif + + + } + + migrations.Sort(new MigrationTypeComparer(true)); + return migrations; + } + + /// + /// Returns the version of the migration + /// MigrationAttribute. + /// + /// Migration type. + /// Version number sepcified in the attribute + public static long GetMigrationVersion(Type t) + { + var attrib = (MigrationAttribute)Attribute.GetCustomAttribute(t, typeof(MigrationAttribute)); + return attrib.Version; + } + + public List GetAvailableMigrations() + { + _migrationsTypes.Sort(new MigrationTypeComparer(true)); + return _migrationsTypes.Select(x => GetMigrationVersion(x)).ToList(); + } + + public virtual IMigration GetMigration(long version) + { + foreach (var t in _migrationsTypes) + { + if (GetMigrationVersion(t) == version) + { + var migration = CreateInstance(t); + migration.Database = _provider; + return migration; + } + } + + return null; + } + + public virtual IMigration CreateInstance(Type migrationType) + { + return (IMigration)Activator.CreateInstance(migrationType); + } +} diff --git a/src/Migrator/Migrator.cs b/src/Migrator/Migrator.cs index 94f685ac..46fbd20b 100644 --- a/src/Migrator/Migrator.cs +++ b/src/Migrator/Migrator.cs @@ -1,192 +1,248 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.Collections.Generic; -using System.Reflection; -using Migrator.Framework; -using Migrator.Framework.Loggers; -using Migrator.Providers; - -namespace Migrator -{ - /// - /// Migrations mediator. - /// - public class Migrator - { - readonly MigrationLoader _migrationLoader; - readonly ITransformationProvider _provider; - - string[] _args; - protected bool _dryrun; - ILogger _logger = new Logger(false); - - public Migrator(ProviderTypes provider, string connectionString, string defaultSchema, Assembly migrationAssembly) - : this(provider, connectionString, defaultSchema, migrationAssembly, false) - { - } - - public Migrator(ProviderTypes provider, string connectionString, string defaultSchema, Assembly migrationAssembly, bool trace) - : this(ProviderFactory.Create(provider, connectionString, defaultSchema), migrationAssembly, trace) - { - } - - public Migrator(ProviderTypes provider, string connectionString, string defaultSchema, Assembly migrationAssembly, bool trace, ILogger logger) - : this(ProviderFactory.Create(provider, connectionString, defaultSchema), migrationAssembly, trace, logger) - { - } - - public Migrator(ITransformationProvider provider, Assembly migrationAssembly, bool trace) - : this(provider, migrationAssembly, trace, new Logger(trace, new ConsoleWriter())) - { - } - - public Migrator(ITransformationProvider provider, Assembly migrationAssembly, bool trace, ILogger logger) - { - _provider = provider; - Logger = logger; - - _migrationLoader = new MigrationLoader(provider, migrationAssembly, trace); - _migrationLoader.CheckForDuplicatedVersion(); - } - - public string[] args - { - get { return _args; } - set { _args = value; } - } - - /// - /// Returns registered migration types. - /// - public List MigrationsTypes - { - get { return _migrationLoader.MigrationsTypes; } - } - - /// - /// Set or get the Schema Info table name, where the migration applied are saved - /// Default is: SchemaInfo - /// - public string SchemaInfoTableName +#region License + +//The contents of this file are subject to the Mozilla Public License +//Version 1.1 (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.mozilla.org/MPL/ +//Software distributed under the License is distributed on an "AS IS" +//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +//License for the specific language governing rights and limitations +//under the License. + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Framework.Loggers; +using DotNetProjects.Migrator.Providers; + +namespace DotNetProjects.Migrator; + +/// +/// Migrations mediator. +/// +public class Migrator +{ + private readonly MigrationLoader _migrationLoader; + private readonly ITransformationProvider _provider; + + private string[] _args; + protected bool _dryrun; + private ILogger _logger = new Logger(false); + + public Migrator(ProviderTypes provider, string connectionString, string defaultSchema, Assembly migrationAssembly) + : this(provider, connectionString, defaultSchema, migrationAssembly, false) + { + } + + public Migrator(ProviderTypes provider, string connectionString, string defaultSchema, params Type[] migrationTypes) + : this(provider, connectionString, defaultSchema, false, migrationTypes) + { + } + + public Migrator(ProviderTypes provider, string connectionString, string defaultSchema, Assembly migrationAssembly, bool trace) + : this(ProviderFactory.Create(provider, connectionString, defaultSchema), migrationAssembly, trace) + { + } + + public Migrator(ProviderTypes provider, string connectionString, string defaultSchema, bool trace, params Type[] migrationTypes) + : this(ProviderFactory.Create(provider, connectionString, defaultSchema), trace, migrationTypes) + { + } + + public Migrator(ProviderTypes provider, string connectionString, string defaultSchema, Assembly migrationAssembly, bool trace, ILogger logger) + : this(ProviderFactory.Create(provider, connectionString, defaultSchema), migrationAssembly, trace, logger) + { + } + + public Migrator(ProviderTypes provider, string connectionString, string defaultSchema, bool trace, ILogger logger, params Type[] migrationTypes) + : this(ProviderFactory.Create(provider, connectionString, defaultSchema), trace, logger, migrationTypes) + { + } + + public Migrator(ITransformationProvider provider, Assembly migrationAssembly, bool trace) + : this(provider, migrationAssembly, trace, new Logger(trace, new ConsoleWriter())) + { + } + + public Migrator(ITransformationProvider provider, bool trace, params Type[] migrationTypes) + : this(provider, trace, new Logger(trace, new ConsoleWriter()), migrationTypes) + { + } + + public Migrator(ITransformationProvider provider, Assembly migrationAssembly, bool trace, ILogger logger) + { + _provider = provider; + Logger = logger; + + _migrationLoader = new MigrationLoader(provider, migrationAssembly, trace); + _migrationLoader.CheckForDuplicatedVersion(); + } + + public Migrator(ITransformationProvider provider, bool trace, ILogger logger, params Type[] migrationTypes) + { + _provider = provider; + Logger = logger; + + _migrationLoader = new MigrationLoader(provider, trace, migrationTypes); + _migrationLoader.CheckForDuplicatedVersion(); + } + + public Migrator(ITransformationProvider provider, ILogger logger, MigrationLoader migrationLoader) + { + _provider = provider; + Logger = logger; + + _migrationLoader = migrationLoader; + _migrationLoader.CheckForDuplicatedVersion(); + } + + public string[] args + { + get { return _args; } + set { _args = value; } + } + + /// + /// Returns registered migration types. + /// + public List MigrationsTypes + { + get { return _migrationLoader.MigrationsTypes; } + } + + /// + /// Set or get the Schema Info table name, where the migration applied are saved + /// Default is: SchemaInfo + /// + public string SchemaInfoTableName + { + get + { + return _provider.SchemaInfoTable; + } + + set + { + _provider.SchemaInfoTable = value; + } + } + + /// + /// Returns the current migrations applied to the database. + /// + public List AppliedMigrations + { + get { return _provider.AppliedMigrations; } + } + + /// + /// Get or set the event logger. + /// + public ILogger Logger + { + get { return _logger; } + set + { + _logger = value; + _provider.Logger = value; + } + } + + public virtual bool DryRun + { + get { return _dryrun; } + set { _dryrun = value; } + } + + public long AssemblyLastMigrationVersion + { + get { return _migrationLoader.LastVersion; } + } + + public long? LastAppliedMigrationVersion + { + get + { + if (AppliedMigrations.Count() == 0) + { + return null; + } + + return AppliedMigrations.Max(); + } + } + + /// + /// Run all migrations up to the latest. Make no changes to database if + /// dryrun is true. + /// + public void MigrateToLastVersion() + { + MigrateTo(_migrationLoader.LastVersion); + } + + /// + /// Migrate the database to a specific version. + /// Runs all migration between the actual version and the + /// specified version. + /// If version is greater then the current version, + /// the Up() method will be invoked. + /// If version lower then the current version, + /// the Down() method of previous migration will be invoked. + /// If dryrun is set, don't write any changes to the database. + /// + /// The version that must became the current one + public void MigrateTo(long version) + { + if (_migrationLoader.MigrationsTypes.Count == 0) + { + _logger.Warn("No public classes with the Migration attribute were found."); + return; + } + + var firstRun = true; + var migrate = BaseMigrate.GetInstance(_migrationLoader.GetAvailableMigrations(), _provider, _logger); + migrate.DryRun = DryRun; + Logger.Started(migrate.AppliedVersions, version); + + while (migrate.Continue(version)) { - get + var migration = _migrationLoader.GetMigration(migrate.Current); + if (null == migration) { - return _provider.SchemaInfoTable; + _logger.Skipping(migrate.Current); + migrate.Iterate(); + continue; } - set + try + { + if (firstRun) + { + migration.InitializeOnce(_args); + firstRun = false; + } + + migrate.Migrate(migration); + } + catch (Exception ex) { - _provider.SchemaInfoTable = value; + Logger.Exception(migrate.Current, migration.Name, ex); + + // Oho! error! We rollback changes. + Logger.RollingBack(migrate.Previous); + _provider.Rollback(); + + throw; } - } - - /// - /// Returns the current migrations applied to the database. - /// - public List AppliedMigrations - { - get { return _provider.AppliedMigrations; } - } - - /// - /// Get or set the event logger. - /// - public ILogger Logger - { - get { return _logger; } - set - { - _logger = value; - _provider.Logger = value; - } - } - - public virtual bool DryRun - { - get { return _dryrun; } - set { _dryrun = value; } - } - - /// - /// Run all migrations up to the latest. Make no changes to database if - /// dryrun is true. - /// - public void MigrateToLastVersion() - { - MigrateTo(_migrationLoader.LastVersion); - } - - /// - /// Migrate the database to a specific version. - /// Runs all migration between the actual version and the - /// specified version. - /// If version is greater then the current version, - /// the Up() method will be invoked. - /// If version lower then the current version, - /// the Down() method of previous migration will be invoked. - /// If dryrun is set, don't write any changes to the database. - /// - /// The version that must became the current one - public void MigrateTo(long version) - { - if (_migrationLoader.MigrationsTypes.Count == 0) - { - _logger.Warn("No public classes with the Migration attribute were found."); - return; - } - - bool firstRun = true; - BaseMigrate migrate = BaseMigrate.GetInstance(_migrationLoader.GetAvailableMigrations(), _provider, _logger); - migrate.DryRun = DryRun; - Logger.Started(migrate.AppliedVersions, version); - - while (migrate.Continue(version)) - { - IMigration migration = _migrationLoader.GetMigration(migrate.Current); - if (null == migration) - { - _logger.Skipping(migrate.Current); - migrate.Iterate(); - continue; - } - - try - { - if (firstRun) - { - migration.InitializeOnce(_args); - firstRun = false; - } - - migrate.Migrate(migration); - } - catch (Exception ex) - { - Logger.Exception(migrate.Current, migration.Name, ex); - - // Oho! error! We rollback changes. - Logger.RollingBack(migrate.Previous); - _provider.Rollback(); - - throw; - } - - migrate.Iterate(); - } - - Logger.Finished(migrate.AppliedVersions, version); - } - } -} \ No newline at end of file + + migrate.Iterate(); + } + + Logger.Finished(migrate.AppliedVersions, version); + } +} diff --git a/src/Migrator/MigratorDotNet.snk b/src/Migrator/MigratorDotNet.snk deleted file mode 100644 index 5032d709..00000000 Binary files a/src/Migrator/MigratorDotNet.snk and /dev/null differ diff --git a/src/Migrator/ProviderFactory.cs b/src/Migrator/ProviderFactory.cs index 09af8a0a..acb1fc2b 100644 --- a/src/Migrator/ProviderFactory.cs +++ b/src/Migrator/ProviderFactory.cs @@ -1,105 +1,87 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.Collections.Generic; -using System.Data; -using System.Reflection; -using Migrator.Framework; -using Migrator.Providers; -using Migrator.Providers.Impl.DB2; -using Migrator.Providers.Impl.Firebird; -using Migrator.Providers.Impl.Informix; -using Migrator.Providers.Impl.Ingres; -using Migrator.Providers.Impl.Sybase; -using Migrator.Providers.Mysql; -using Migrator.Providers.Oracle; -using Migrator.Providers.PostgreSQL; -using Migrator.Providers.SQLite; -using Migrator.Providers.SqlServer; +using System; +using System.Data; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers; +using DotNetProjects.Migrator.Providers.Impl.DB2; +using DotNetProjects.Migrator.Providers.Impl.Firebird; +using DotNetProjects.Migrator.Providers.Impl.Informix; +using DotNetProjects.Migrator.Providers.Impl.Ingres; +using DotNetProjects.Migrator.Providers.Impl.Mysql; +using DotNetProjects.Migrator.Providers.Impl.Oracle; +using DotNetProjects.Migrator.Providers.Impl.PostgreSQL; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using DotNetProjects.Migrator.Providers.Impl.SqlServer; +using DotNetProjects.Migrator.Providers.Impl.Sybase; -namespace Migrator -{ - /// - /// Handles loading Provider implementations - /// - public class ProviderFactory - { - static ProviderFactory() - { } +namespace DotNetProjects.Migrator; - /// - /// - /// - /// - /// - /// - /// - /// for Example: System.Data.SqlClient - /// - public static ITransformationProvider Create(ProviderTypes providerType, string connectionString, string defaultSchema, string scope = "default", string providerName = "") - { - Dialect dialectInstance = DialectForProvider(providerType); +/// +/// Handles loading Provider implementations +/// +public class ProviderFactory +{ + static ProviderFactory() + { } - return dialectInstance.NewProviderForDialect(connectionString, defaultSchema, scope, providerName); - } + /// + /// + /// + /// + /// + /// + /// + /// for Example: System.Data.SqlClient + /// + public static ITransformationProvider Create(ProviderTypes providerType, string connectionString, string defaultSchema, string scope = "default", string providerName = "") + { + var dialectInstance = DialectForProvider(providerType); - public static ITransformationProvider Create(ProviderTypes providerType, IDbConnection connection, string defaultSchema, string scope = "default", string providerName = "") - { - Dialect dialectInstance = DialectForProvider(providerType); + return dialectInstance.NewProviderForDialect(connectionString, defaultSchema, scope, providerName); + } - return dialectInstance.NewProviderForDialect(connection, defaultSchema, scope, providerName); - } + public static ITransformationProvider Create(ProviderTypes providerType, IDbConnection connection, string defaultSchema, string scope = "default", string providerName = "") + { + var dialectInstance = DialectForProvider(providerType); + + return dialectInstance.NewProviderForDialect(connection, defaultSchema, scope, providerName); + } - public static Dialect DialectForProvider(ProviderTypes providerType) + public static Dialect DialectForProvider(ProviderTypes providerType) + { + switch (providerType) { - switch (providerType) - { - case ProviderTypes.SQLite: - return (Dialect)Activator.CreateInstance(typeof(SQLiteDialect)); - case ProviderTypes.MonoSQLite: - return (Dialect)Activator.CreateInstance(typeof(SQLiteMonoDialect)); - case ProviderTypes.Mysql: - return (Dialect)Activator.CreateInstance(typeof(MysqlDialect)); - case ProviderTypes.MariaDB: - return (Dialect)Activator.CreateInstance(typeof(MariaDBDialect)); - case ProviderTypes.Oracle: - return (Dialect)Activator.CreateInstance(typeof(OracleDialect)); - case ProviderTypes.PostgreSQL: - return (Dialect)Activator.CreateInstance(typeof(PostgreSQLDialect)); - case ProviderTypes.PostgreSQL82: - return (Dialect)Activator.CreateInstance(typeof(PostgreSQL82Dialect)); - case ProviderTypes.SqlServer: - return (Dialect)Activator.CreateInstance(typeof(SqlServerDialect)); - case ProviderTypes.SqlServer2005: - return (Dialect)Activator.CreateInstance(typeof(SqlServer2005Dialect)); - case ProviderTypes.SqlServerCe: - return (Dialect)Activator.CreateInstance(typeof(SqlServerCeDialect)); - case ProviderTypes.MsOracle: - return (Dialect)Activator.CreateInstance(typeof(MsOracleDialect)); - case ProviderTypes.IBM_DB2: - return (Dialect)Activator.CreateInstance(typeof(DB2Dialect)); - case ProviderTypes.IBM_Informix: - return (Dialect)Activator.CreateInstance(typeof(InformixDialect)); - case ProviderTypes.Firebird: - return (Dialect)Activator.CreateInstance(typeof(FirebirdDialect)); - case ProviderTypes.Ingres: - return (Dialect)Activator.CreateInstance(typeof(IngresDialect)); - case ProviderTypes.Sybase: - return (Dialect)Activator.CreateInstance(typeof(SybaseDialect)); - } + case ProviderTypes.SQLite: + return (Dialect)Activator.CreateInstance(typeof(SQLiteDialect)); + case ProviderTypes.MonoSQLite: + return (Dialect)Activator.CreateInstance(typeof(SQLiteMonoDialect)); + case ProviderTypes.Mysql: + return (Dialect)Activator.CreateInstance(typeof(MysqlDialect)); + case ProviderTypes.MariaDB: + return (Dialect)Activator.CreateInstance(typeof(MariaDBDialect)); + case ProviderTypes.Oracle: + return (Dialect)Activator.CreateInstance(typeof(OracleDialect)); + case ProviderTypes.PostgreSQL: + return (Dialect)Activator.CreateInstance(typeof(PostgreSQLDialect)); + case ProviderTypes.PostgreSQL82: + return (Dialect)Activator.CreateInstance(typeof(PostgreSQL82Dialect)); + case ProviderTypes.SqlServer: + return (Dialect)Activator.CreateInstance(typeof(SqlServerDialect)); + case ProviderTypes.SqlServer2005: + return (Dialect)Activator.CreateInstance(typeof(SqlServer2005Dialect)); + case ProviderTypes.MsOracle: + return (Dialect)Activator.CreateInstance(typeof(MsOracleDialect)); + case ProviderTypes.IBM_DB2: + return (Dialect)Activator.CreateInstance(typeof(DB2Dialect)); + case ProviderTypes.IBM_Informix: + return (Dialect)Activator.CreateInstance(typeof(InformixDialect)); + case ProviderTypes.Firebird: + return (Dialect)Activator.CreateInstance(typeof(FirebirdDialect)); + case ProviderTypes.Ingres: + return (Dialect)Activator.CreateInstance(typeof(IngresDialect)); + case ProviderTypes.Sybase: + return (Dialect)Activator.CreateInstance(typeof(SybaseDialect)); + } - return null; - } - } -} \ No newline at end of file + return null; + } +} diff --git a/src/Migrator/Providers/ColumnPropertiesMapper.cs b/src/Migrator/Providers/ColumnPropertiesMapper.cs new file mode 100644 index 00000000..da3e6213 --- /dev/null +++ b/src/Migrator/Providers/ColumnPropertiesMapper.cs @@ -0,0 +1,251 @@ +using System.Collections.Generic; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers; + +/// +/// This is basically a just a helper base class +/// per-database implementors may want to override ColumnSql +/// +public class ColumnPropertiesMapper +{ + /// + /// the type of the column + /// + protected string _ColumnSql; + + /// + /// Sql if this column has a default value + /// + protected object _DefaultVal; + + protected Dialect _Dialect; + + /// + /// Sql if This column is Indexed + /// + protected bool _Indexed; + + /// The name of the column + protected string _Name; + + /// The SQL type + public string Type { get; private set; } + + public ColumnPropertiesMapper(Dialect dialect, string typeString) + { + _Dialect = dialect; + Type = typeString; + } + + /// + /// The sql for this column, override in database-specific implementation classes + /// + public virtual string ColumnSql + { + get { return _ColumnSql; } + } + + public string Name + { + get { return _Name; } + set { _Name = value; } + } + + public object Default + { + get { return _DefaultVal; } + set { _DefaultVal = value; } + } + + public string QuotedName + { + get { return _Dialect.Quote(Name); } + } + + public string IndexSql + { + get + { + if (_Dialect.SupportsIndex && _Indexed) + { + return string.Format("INDEX({0})", _Dialect.Quote(_Name)); + } + + return null; + } + } + + public virtual void MapColumnProperties(Column column) + { + Name = column.Name; + + _Indexed = PropertySelected(column.ColumnProperty, ColumnProperty.Indexed); + + var vals = new List(); + + AddName(vals); + + AddType(vals); + + AddCaseSensitive(column, vals); + + AddIdentity(column, vals); + + AddUnsigned(column, vals); + + AddNotNull(column, vals); + + AddNull(column, vals); + + AddPrimaryKey(column, vals); + + AddPrimaryKeyNonClustered(column, vals); + + AddIdentityAgain(column, vals); + + AddUnique(column, vals); + + AddForeignKey(column, vals); + + AddDefaultValue(column, vals); + + _ColumnSql = string.Join(" ", vals.ToArray()); + } + + public virtual void MapColumnPropertiesWithoutDefault(Column column) + { + Name = column.Name; + + _Indexed = PropertySelected(column.ColumnProperty, ColumnProperty.Indexed); + + var vals = new List(); + + AddName(vals); + + AddType(vals); + + AddCaseSensitive(column, vals); + + AddIdentity(column, vals); + + AddUnsigned(column, vals); + + AddNotNull(column, vals); + + AddNull(column, vals); + + AddPrimaryKey(column, vals); + + AddIdentityAgain(column, vals); + + AddPrimaryKeyNonClustered(column, vals); + + AddUnique(column, vals); + + AddForeignKey(column, vals); + + _ColumnSql = string.Join(" ", vals.ToArray()); + } + + protected virtual void AddCaseSensitive(Column column, List vals) + { + AddValueIfSelected(column, ColumnProperty.CaseSensitive, vals); + } + + protected virtual void AddDefaultValue(Column column, List vals) + { + if (column.DefaultValue != null) + { + vals.Add(_Dialect.Default(column.DefaultValue)); + } + } + + protected virtual void AddForeignKey(Column column, List vals) + { + // TODO Does that really make sense? + // AddValueIfSelected(column, ColumnProperty.ForeignKey, vals); + } + + protected virtual void AddUnique(Column column, List vals) + { + AddValueIfSelected(column, ColumnProperty.Unique, vals); + } + + protected virtual void AddIdentityAgain(Column column, List vals) + { + if (_Dialect.IdentityNeedsType) + { + AddValueIfSelected(column, ColumnProperty.Identity, vals); + } + } + protected virtual void AddPrimaryKeyNonClustered(Column column, List vals) + { + if (_Dialect.SupportsNonClustered) + { + AddValueIfSelected(column, ColumnProperty.PrimaryKeyNonClustered, vals); + } + } + protected virtual void AddPrimaryKey(Column column, List vals) + { + AddValueIfSelected(column, ColumnProperty.PrimaryKey, vals); + } + + protected virtual void AddNull(Column column, List vals) + { + if (!PropertySelected(column.ColumnProperty, ColumnProperty.PrimaryKey)) + { + if (_Dialect.NeedsNullForNullableWhenAlteringTable) + { + AddValueIfSelected(column, ColumnProperty.Null, vals); + } + } + } + + protected virtual void AddNotNull(Column column, List vals) + { + if (!PropertySelected(column.ColumnProperty, ColumnProperty.Null) && (!PropertySelected(column.ColumnProperty, ColumnProperty.PrimaryKey) || _Dialect.NeedsNotNullForIdentity)) + { + AddValueIfSelected(column, ColumnProperty.NotNull, vals); + } + } + + protected virtual void AddUnsigned(Column column, List vals) + { + if (_Dialect.IsUnsignedCompatible(column.Type)) + { + AddValueIfSelected(column, ColumnProperty.Unsigned, vals); + } + } + + protected virtual void AddIdentity(Column column, List vals) + { + if (!_Dialect.IdentityNeedsType) + { + AddValueIfSelected(column, ColumnProperty.Identity, vals); + } + } + + protected virtual void AddType(List vals) + { + vals.Add(Type); + } + + protected virtual void AddName(List vals) + { + vals.Add(_Dialect.ColumnNameNeedsQuote || _Dialect.IsReservedWord(Name) ? QuotedName : Name); + } + + protected virtual void AddValueIfSelected(Column column, ColumnProperty property, ICollection vals) + { + if (PropertySelected(column.ColumnProperty, property)) + { + vals.Add(_Dialect.SqlForProperty(property, column)); + } + } + + public static bool PropertySelected(ColumnProperty source, ColumnProperty comparison) + { + return (source & comparison) == comparison; + } +} diff --git a/src/Migrator/Providers/DbProviderFactoriesHelper.cs b/src/Migrator/Providers/DbProviderFactoriesHelper.cs new file mode 100644 index 00000000..651d8f78 --- /dev/null +++ b/src/Migrator/Providers/DbProviderFactoriesHelper.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; + +namespace DotNetProjects.Migrator.Providers; + +public static class DbProviderFactoriesHelper +{ + public static DbProviderFactory GetFactory(string providerName, string assemblyName, string factoryProviderType) + { + try + { + var factory = DbProviderFactories.GetFactory(providerName); + if (factory != null) + { + return factory; + } + } + catch (Exception) + { } + + +#if !NETSTANDARD + try + { + var factory = System.Data.Common.DbProviderFactories.GetFactory(providerName); + if (factory != null) + { + return factory; + } + } + catch (Exception) + { } +#endif + +#if NETSTANDARD + return null; +#else + return (DbProviderFactory)AppDomain.CurrentDomain.CreateInstanceAndUnwrap(assemblyName, factoryProviderType); +#endif + } +} + +public abstract class DbProviderFactories +{ + + internal static readonly Dictionary> _configs = new Dictionary>(); + + public static DbProviderFactory GetFactory(string providerInvariantName) + { + if (_configs.ContainsKey(providerInvariantName)) + { + return _configs[providerInvariantName](); + } + + throw new Exception("ConfigProviderNotFound"); + } + + public static void RegisterFactory(string providerInvariantName, Func factory) + { + _configs[providerInvariantName] = factory; + } + + public static IEnumerable GetFactoryProviderNames() + { + return _configs.Keys.ToArray(); + } +} diff --git a/src/Migrator/Providers/Dialect.cs b/src/Migrator/Providers/Dialect.cs new file mode 100644 index 00000000..8a3fcf11 --- /dev/null +++ b/src/Migrator/Providers/Dialect.cs @@ -0,0 +1,487 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using System.Linq; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Models; +using DotNetProjects.Migrator.Providers.Models.Indexes.Enums; + +namespace DotNetProjects.Migrator.Providers; + +/// +/// Defines the implementations specific details for a particular database. +/// +public abstract class Dialect : IDialect +{ + private readonly Dictionary _propertyMap = []; + private readonly HashSet _reservedWords = []; + private readonly TypeNames _typeNames = new(); + private readonly List _unsignedCompatibleTypes = []; + + private readonly List _filterTypeToStrings = [ + new() { FilterType = FilterType.EqualTo, FilterString = "=" }, + new() { FilterType = FilterType.GreaterThan, FilterString = ">" }, + new() { FilterType = FilterType.GreaterThanOrEqualTo, FilterString = ">=" }, + new() { FilterType = FilterType.SmallerThan, FilterString = "<" }, + new() { FilterType = FilterType.SmallerThanOrEqualTo, FilterString = "<=" }, + new() { FilterType = FilterType.NotEqualTo, FilterString = "<>"} + ]; + + protected Dialect() + { + RegisterProperty(ColumnProperty.Null, "NULL"); + RegisterProperty(ColumnProperty.NotNull, "NOT NULL"); + RegisterProperty(ColumnProperty.Unique, "UNIQUE"); + RegisterProperty(ColumnProperty.PrimaryKey, "PRIMARY KEY"); + RegisterProperty(ColumnProperty.PrimaryKeyNonClustered, " NONCLUSTERED"); + } + + public virtual int MaxKeyLength + { + get { return 900; } + } + + public virtual int MaxFieldNameLength + { + get { return int.MaxValue; } + } + + public virtual bool ColumnNameNeedsQuote + { + get { return false; } + } + + public virtual bool TableNameNeedsQuote + { + get { return false; } + } + + public virtual bool ConstraintNameNeedsQuote + { + get { return false; } + } + + public virtual bool IdentityNeedsType + { + get { return true; } + } + public virtual bool SupportsNonClustered + { + get { return false; } + } + + public virtual bool NeedsNotNullForIdentity + { + get { return true; } + } + + public virtual bool SupportsIndex + { + get { return true; } + } + + public virtual string QuoteTemplate + { + get { return "\"{0}\""; } + } + + public virtual bool NeedsNullForNullableWhenAlteringTable + { + get { return false; } + } + + protected void AddReservedWord(string reservedWord) + { + _reservedWords.Add(reservedWord.ToUpperInvariant()); + } + + protected void AddReservedWords(params string[] words) + { + if (words == null) + { + return; + } + + foreach (var word in words) + { + _reservedWords.Add(word); + } + } + + public virtual bool IsReservedWord(string reservedWord) + { + if (string.IsNullOrEmpty(reservedWord)) + { + throw new ArgumentNullException("reservedWord"); + } + + if (_reservedWords == null) + { + return false; + } + + var isReserved = _reservedWords.Contains(reservedWord.ToUpperInvariant()); + + if (isReserved) + { + //Console.WriteLine("Reserved word: {0}", reservedWord); + } + + return isReserved; + } + + public abstract ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName); + public abstract ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, string defaultSchema, string scope, string providerName); + + public ITransformationProvider NewProviderForDialect(string connectionString, string defaultSchema, string scope, string providerName) + { + return GetTransformationProvider(this, connectionString, defaultSchema, scope, providerName); + } + + public ITransformationProvider NewProviderForDialect(IDbConnection connection, string defaultSchema, string scope, string providerName) + { + return GetTransformationProvider(this, connection, defaultSchema, scope, providerName); + } + + /// + /// Subclasses register a typename for the given type code and maximum + /// column length. $l in the type name will be replaced by the column + /// length (if appropriate) + /// + /// The typecode + /// Maximum length of database type + /// The database type name + protected void RegisterColumnType(DbType code, int capacity, string name) + { + _typeNames.Put(code, capacity, name); + } + + /// + /// Subclasses register a typename for the given type code and maximum + /// column length. $l in the type name will be replaced by the column + /// length (if appropriate) + /// + /// The typecode + /// Maximum length of database type + /// The database type name + protected void RegisterColumnType(MigratorDbType code, int capacity, string name) + { + _typeNames.Put(code, capacity, name); + } + + /// + /// Subclasses register a typename for the given type code and maximum + /// column length. $l in the type name will be replaced by the column + /// length (if appropriate) + /// $2 in the type name will be replaced by the column + /// precision (if appropriate) + /// + /// The typecode + /// Maximum length of database type + /// The database type name + protected void RegisterColumnTypeWithPrecision(DbType code, string name) + { + _typeNames.Put(code, -1, name); + } + + /// + /// Suclasses register a typename for the given type code. $l in the + /// typename will be replaced by the column length (if appropriate). + /// + /// The typecode + /// The database type name + protected void RegisterColumnType(MigratorDbType code, string name) + { + _typeNames.Put(code, name); + } + + /// + /// Suclasses register a typename for the given type code. $l in the + /// typename will be replaced by the column length (if appropriate). + /// + /// The typecode + /// The database type name + protected void RegisterColumnType(DbType code, string name) + { + _typeNames.Put(code, name); + } + + /// + /// Suclasses register a typename for the given type code. + /// {length}, {precision} & {scale} in the + /// typename will be replaced. + // /// + /// The typecode + /// The database type name + protected void RegisterColumnTypeWithParameters(DbType code, string name) + { + _typeNames.PutParametrized(code, name); + } + + + protected void RegisterColumnTypeAlias(DbType code, string alias) + { + _typeNames.PutAlias(code, alias); + } + + public virtual ColumnPropertiesMapper GetColumnMapper(Column column) + { + var type = column.Size > 0 ? GetTypeName(column.Type, column.Size) : GetTypeName(column.Type); + + if (column.Precision.HasValue || column.Scale.HasValue) + { + type = GetTypeNameParametrized(column.Type, column.Size, column.Precision ?? 0, column.Scale ?? 0); + } + + if (!IdentityNeedsType && column.IsIdentity) + { + type = string.Empty; + } + + return new ColumnPropertiesMapper(this, type); + } + + public virtual DbType GetDbTypeFromString(string type) + { + return _typeNames.GetDbType(type); + } + + /// + /// Get the name of the database type associated with the given + /// + /// The DbType + /// The database type name used by ddl. + public virtual string GetTypeName(DbType type) + { + var result = _typeNames.Get(type); + + if (result == null) + { + throw new Exception(string.Format("No default type mapping for DbType {0}", type)); + } + + return result; + } + + /// + /// Get the name of the database type associated with the given + /// + /// The DbType + /// The database type name used by ddl. + /// + public virtual string GetTypeName(DbType type, int length) + { + return GetTypeName(type, length, 0, 0); + } + + /// + /// Get the name of the database type associated with the given + /// + /// The DbType + /// The database type name used by ddl. + /// + /// + /// + public virtual string GetTypeName(DbType type, int length, int precision, int scale) + { + var resultWithLength = _typeNames.Get(type, length, precision, scale); + if (resultWithLength != null) + { + return resultWithLength; + } + + return GetTypeName(type); + } + + /// + /// Get the name of the database type associated with the given + /// + /// The DbType + /// The database type name used by ddl. + /// + /// + /// + public virtual string GetTypeNameParametrized(DbType type, int length, int precision, int scale) + { + var result = _typeNames.GetParametrized(type); + if (result != null) + { + return result.Replace("{length}", length.ToString()) + .Replace("{precision}", precision.ToString()) + .Replace("{scale}", scale.ToString()); + } + + return GetTypeName(type, length, precision, scale); + } + + /// + /// Get the type from the specified database type name. + /// Note: This does not work perfectly, but it will do for most cases. + /// + /// The name of the type. + /// The . + public virtual DbType GetDbType(string databaseTypeName) + { + return _typeNames.GetDbType(databaseTypeName); + } + + public void RegisterProperty(ColumnProperty property, string sql) + { + if (!_propertyMap.ContainsKey(property)) + { + _propertyMap.Add(property, sql); + } + _propertyMap[property] = sql; + } + + public virtual string SqlForProperty(ColumnProperty property, Column column) + { + if (_propertyMap.ContainsKey(property)) + { + return _propertyMap[property]; + } + return string.Empty; + } + + public virtual string Quote(string value) + { + return string.Format(QuoteTemplate, value); + } + + public virtual string QuoteColumnNameIfRequired(string columnName) + { + if (ColumnNameNeedsQuote || IsReservedWord(columnName)) + { + return Quote(columnName); + } + + return columnName; + } + + public virtual string QuoteTableNameIfRequired(string tableName) + { + if (TableNameNeedsQuote || IsReservedWord(tableName)) + { + return Quote(tableName); + } + + return tableName; + } + + public virtual string Default(object defaultValue) + { + if (defaultValue is string && defaultValue.ToString() == string.Empty) + { + defaultValue = "''"; + } + else if (defaultValue is Guid) + { + var guidValue = string.Format("DEFAULT '{0}'", defaultValue.ToString()); + + return guidValue; + } + else if (defaultValue is DateTime dateTime) + { + if (dateTime.Kind != DateTimeKind.Utc) + { + throw new Exception("Use DateTimeKind.Utc for default date time values."); + } + + return string.Format("DEFAULT '{0}'", ((DateTime)defaultValue).ToString("yyyy-MM-dd HH:mm:ss")); + } + else if (defaultValue is string) + { + defaultValue = ((string)defaultValue).Replace("'", "''"); + defaultValue = "'" + defaultValue + "'"; + } + else if (defaultValue is decimal) + { + // .ToString("N") does not exist in old .NET version + defaultValue = Convert.ToString(defaultValue, CultureInfo.InvariantCulture); + } + else if (defaultValue is byte[] byteArray) + { + var convertedString = BitConverter.ToString(byteArray).Replace("-", "").ToLower(); + defaultValue = $"0x{convertedString}"; + } + else if (defaultValue is double doubleValue) + { + defaultValue = Convert.ToString(doubleValue, CultureInfo.InvariantCulture); + } + + return string.Format("DEFAULT {0}", defaultValue); + } + + public ColumnPropertiesMapper GetAndMapColumnProperties(Column column) + { + var mapper = GetColumnMapper(column); + mapper.MapColumnProperties(column); + + if (column.DefaultValue != null && column.DefaultValue != DBNull.Value) + { + mapper.Default = column.DefaultValue; + } + + return mapper; + } + + public ColumnPropertiesMapper GetAndMapColumnPropertiesWithoutDefault(Column column) + { + var mapper = GetColumnMapper(column); + mapper.MapColumnPropertiesWithoutDefault(column); + if (column.DefaultValue != null && column.DefaultValue != DBNull.Value) + { + mapper.Default = column.DefaultValue; + } + + return mapper; + } + + public string GetComparisonStringByFilterType(FilterType filterType) + { + var exceptionString = $"The {nameof(FilterType)} '{filterType}' is not implemented."; + var result = _filterTypeToStrings.FirstOrDefault(x => x.FilterType == filterType) ?? throw new NotImplementedException(exceptionString); + + return result.FilterString; + } + + public string[] GetComparisonStrings() + { + return _filterTypeToStrings.Select(x => x.FilterString).ToArray(); + } + + /// + /// Resolves the comparison string for filtered indexes. + /// + /// + /// + /// + public FilterType GetFilterTypeByComparisonString(string comparisonString) + { + var exceptionString = $"The {comparisonString} cannot be resolved."; + var result = _filterTypeToStrings.FirstOrDefault(x => x.FilterString == comparisonString) ?? throw new Exception(exceptionString); + + return result.FilterType; + } + + /// + /// Subclasses register which DbTypes are unsigned-compatible (ie, available in signed and unsigned variants) + /// + /// + protected void RegisterUnsignedCompatible(DbType type) + { + _unsignedCompatibleTypes.Add(type); + } + + /// + /// Determine if a particular database type has an unsigned variant + /// + /// The DbType + /// True if the database type has an unsigned variant, otherwise false + public bool IsUnsignedCompatible(DbType type) + { + return _unsignedCompatibleTypes.Contains(type); + } + +} diff --git a/src/Migrator/Providers/ForeignKeyConstraintMapper.cs b/src/Migrator/Providers/ForeignKeyConstraintMapper.cs new file mode 100644 index 00000000..2249b28d --- /dev/null +++ b/src/Migrator/Providers/ForeignKeyConstraintMapper.cs @@ -0,0 +1,23 @@ +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers; + +public class ForeignKeyConstraintMapper +{ + public string SqlForConstraint(ForeignKeyConstraintType constraint) + { + switch (constraint) + { + case ForeignKeyConstraintType.Cascade: + return "CASCADE"; + case ForeignKeyConstraintType.Restrict: + return "RESTRICT"; + case ForeignKeyConstraintType.SetDefault: + return "SET DEFAULT"; + case ForeignKeyConstraintType.SetNull: + return "SET NULL"; + default: + return "NO ACTION"; + } + } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/DB2/DB2Dialect.cs b/src/Migrator/Providers/Impl/DB2/DB2Dialect.cs new file mode 100644 index 00000000..2c0f6923 --- /dev/null +++ b/src/Migrator/Providers/Impl/DB2/DB2Dialect.cs @@ -0,0 +1,74 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.DB2; + +public class DB2Dialect : Dialect +{ + public DB2Dialect() + { + this.RegisterColumnType(DbType.AnsiStringFixedLength, "CHAR(255)"); + this.RegisterColumnType(DbType.AnsiStringFixedLength, 255, "CHAR($l)"); + this.RegisterColumnType(DbType.AnsiStringFixedLength, 65535, "TEXT"); + this.RegisterColumnType(DbType.AnsiStringFixedLength, 16777215, "MEDIUMTEXT"); + this.RegisterColumnType(DbType.AnsiString, "VARCHAR(255)"); + this.RegisterColumnType(DbType.AnsiString, 255, "VARCHAR($l)"); + this.RegisterColumnType(DbType.AnsiString, 256, "VARCHAR(255)"); + this.RegisterColumnType(DbType.AnsiString, 65535, "TEXT"); + this.RegisterColumnType(DbType.AnsiString, 16777215, "MEDIUMTEXT"); + this.RegisterColumnType(DbType.Binary, "LONGBLOB"); + this.RegisterColumnType(DbType.Binary, 127, "TINYBLOB"); + this.RegisterColumnType(DbType.Binary, 65535, "BLOB"); + this.RegisterColumnType(DbType.Binary, 16777215, "MEDIUMBLOB"); + this.RegisterColumnType(DbType.Boolean, "TINYINT(1)"); + this.RegisterColumnType(DbType.Byte, "TINYINT UNSIGNED"); + this.RegisterColumnType(DbType.Currency, "MONEY"); + this.RegisterColumnType(DbType.Date, "DATE"); + this.RegisterColumnType(DbType.DateTime, "DATETIME"); + this.RegisterColumnType(DbType.DateTimeOffset, "DATETIME"); + this.RegisterColumnType(DbType.Decimal, "NUMERIC(19,5)"); + this.RegisterColumnType(DbType.Decimal, 19, "NUMERIC(19, $l)"); + this.RegisterColumnType(DbType.Double, "DOUBLE"); + this.RegisterColumnType(DbType.Guid, "VARCHAR(40)"); + this.RegisterColumnType(DbType.Int16, "SMALLINT"); + this.RegisterColumnType(DbType.Int32, "INTEGER"); + this.RegisterColumnType(DbType.Int64, "BIGINT"); + this.RegisterColumnType(DbType.Single, "FLOAT"); + this.RegisterColumnType(DbType.StringFixedLength, "CHAR(255)"); + this.RegisterColumnType(DbType.StringFixedLength, 255, "CHAR($l)"); + this.RegisterColumnType(DbType.StringFixedLength, 65535, "TEXT"); + this.RegisterColumnType(DbType.StringFixedLength, 16777215, "MEDIUMTEXT"); + this.RegisterColumnType(DbType.String, "VARCHAR(255)"); + this.RegisterColumnType(DbType.String, 255, "VARCHAR($l)"); + this.RegisterColumnType(DbType.String, 256, "VARCHAR(255)"); + this.RegisterColumnType(DbType.String, 65535, "TEXT"); + this.RegisterColumnType(DbType.String, 16777215, "MEDIUMTEXT"); + this.RegisterColumnType(DbType.String, 1073741823, "LONGTEXT"); + this.RegisterColumnType(DbType.Time, "TIME"); + + this.RegisterProperty(ColumnProperty.Unsigned, "UNSIGNED"); + this.RegisterProperty(ColumnProperty.Identity, "AUTO_INCREMENT"); + + this.RegisterUnsignedCompatible(DbType.Int16); + this.RegisterUnsignedCompatible(DbType.Int32); + this.RegisterUnsignedCompatible(DbType.Int64); + this.RegisterUnsignedCompatible(DbType.Decimal); + this.RegisterUnsignedCompatible(DbType.Double); + this.RegisterUnsignedCompatible(DbType.Single); + + this.AddReservedWords("KEY"); + } + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, + string defaultSchema, string scope, string providerName) + { + return new DB2TransformationProvider(dialect, connectionString, scope, providerName); + } + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, + string defaultSchema, + string scope, string providerName) + { + return new DB2TransformationProvider(dialect, connection, scope, providerName); + } +} diff --git a/src/Migrator/Providers/Impl/DB2/DB2TransformationProvider.cs b/src/Migrator/Providers/Impl/DB2/DB2TransformationProvider.cs new file mode 100644 index 00000000..81be053f --- /dev/null +++ b/src/Migrator/Providers/Impl/DB2/DB2TransformationProvider.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Data; + +namespace DotNetProjects.Migrator.Providers.Impl.DB2; + +/// +/// DB2 transformation provider +/// +public class DB2TransformationProvider : TransformationProvider +{ + public DB2TransformationProvider(Dialect dialect, string connectionString, string scope, string providerName) + : base(dialect, connectionString, null, scope) + { + if (string.IsNullOrEmpty(providerName)) + { + providerName = "IBM.Data.DB2"; + } + + var fac = DbProviderFactoriesHelper.GetFactory(providerName, null, null); + _connection = fac.CreateConnection(); + _connection.ConnectionString = _connectionString; + this._connection.Open(); + } + + public DB2TransformationProvider(Dialect dialect, IDbConnection connection, string scope, string providerName) + : base(dialect, connection, null, scope) + { + } + + public override List GetDatabases() + { + throw new NotImplementedException(); + } + + public override bool ConstraintExists(string table, string name) + { + throw new NotImplementedException(); + } + + public override bool IndexExists(string table, string name) + { + throw new NotImplementedException(); + } +} diff --git a/src/Migrator/Providers/Impl/Firebird/FirebirdColumnPropertiesMapper.cs b/src/Migrator/Providers/Impl/Firebird/FirebirdColumnPropertiesMapper.cs new file mode 100644 index 00000000..38ea3a26 --- /dev/null +++ b/src/Migrator/Providers/Impl/Firebird/FirebirdColumnPropertiesMapper.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.Firebird; + +public class FirebirdColumnPropertiesMapper : ColumnPropertiesMapper +{ + public FirebirdColumnPropertiesMapper(Dialect dialect, string type) + : base(dialect, type) + { + } + + public override void MapColumnProperties(Column column) + { + Name = column.Name; + + _Indexed = PropertySelected(column.ColumnProperty, ColumnProperty.Indexed); + + var vals = new List(); + + AddName(vals); + + AddType(vals); + + AddIdentity(column, vals); + + AddPrimaryKey(column, vals); + + AddIdentityAgain(column, vals); + + AddUnique(column, vals); + + AddForeignKey(column, vals); + + AddDefaultValue(column, vals); + + AddNotNull(column, vals); + + AddNull(column, vals); + + _ColumnSql = string.Join(" ", vals.ToArray()); + } +} diff --git a/src/Migrator/Providers/Impl/Firebird/FirebirdDialect.cs b/src/Migrator/Providers/Impl/Firebird/FirebirdDialect.cs new file mode 100644 index 00000000..e00c9ff5 --- /dev/null +++ b/src/Migrator/Providers/Impl/Firebird/FirebirdDialect.cs @@ -0,0 +1,73 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.Firebird; + +public class FirebirdDialect : Dialect +{ + public FirebirdDialect() + { + RegisterColumnType(DbType.AnsiStringFixedLength, 8000, "CHAR($l)"); + RegisterColumnType(DbType.AnsiString, 8000, "CHAR($l)"); + RegisterColumnType(DbType.Binary, "BLOB"); + RegisterColumnType(DbType.Binary, 8000, "CHAR"); + RegisterColumnType(DbType.Boolean, "SMALLINT"); + RegisterColumnType(DbType.Byte, "TINYINT"); + RegisterColumnType(DbType.Currency, "MONEY"); + RegisterColumnType(DbType.Date, "TIMESTAMP"); + RegisterColumnType(DbType.DateTime, "TIMESTAMP"); + RegisterColumnType(DbType.DateTimeOffset, "TIMESTAMP"); + RegisterColumnType(DbType.Decimal, "DECIMAL"); + RegisterColumnType(DbType.Double, "DOUBLE PRECISION"); //synonym for FLOAT(53) + RegisterColumnType(DbType.Guid, "CHAR(38)"); + RegisterColumnType(DbType.Int16, "SMALLINT"); + RegisterColumnType(DbType.Int32, "INT"); + RegisterColumnType(DbType.Int64, "BIGINT"); + RegisterColumnType(DbType.Single, "REAL"); //synonym for FLOAT(24) + RegisterColumnType(DbType.StringFixedLength, "NCHAR(255)"); + RegisterColumnType(DbType.String, "VARCHAR(255) CHARACTER SET UNICODE_FSS"); + RegisterColumnType(DbType.String, 4000, "VARCHAR($l) CHARACTER SET UNICODE_FSS"); + RegisterColumnType(DbType.String, int.MaxValue, "BLOB SUB_TYPE TEXT"); + RegisterColumnType(DbType.Time, "INTEGER"); + + this.RegisterProperty(ColumnProperty.Unsigned, "UNSIGNED"); + + this.RegisterUnsignedCompatible(DbType.Int16); + this.RegisterUnsignedCompatible(DbType.Int32); + this.RegisterUnsignedCompatible(DbType.Int64); + this.RegisterUnsignedCompatible(DbType.Decimal); + this.RegisterUnsignedCompatible(DbType.Double); + this.RegisterUnsignedCompatible(DbType.Single); + + this.AddReservedWords("KEY", "TIMESTAMP", "VALUE"); + } + + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) + { + return new FirebirdTransformationProvider(dialect, connectionString, scope, providerName); + } + + public override ColumnPropertiesMapper GetColumnMapper(Column column) + { + var type = column.Size > 0 ? GetTypeName(column.Type, column.Size) : GetTypeName(column.Type); + if (column.Precision.HasValue || column.Scale.HasValue) + { + type = GetTypeNameParametrized(column.Type, column.Size, column.Precision ?? 0, column.Scale ?? 0); + } + + if (!IdentityNeedsType && column.IsIdentity) + { + type = string.Empty; + } + + return new FirebirdColumnPropertiesMapper(this, type); + } + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, + string defaultSchema, + string scope, string providerName) + { + return new FirebirdTransformationProvider(dialect, connection, scope, providerName); + } +} diff --git a/src/Migrator/Providers/Impl/Firebird/FirebirdTransformationProvider.cs b/src/Migrator/Providers/Impl/Firebird/FirebirdTransformationProvider.cs new file mode 100644 index 00000000..878667fb --- /dev/null +++ b/src/Migrator/Providers/Impl/Firebird/FirebirdTransformationProvider.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.Firebird; + +/// +/// Firebird transformation provider +/// +public class FirebirdTransformationProvider : TransformationProvider +{ + public FirebirdTransformationProvider(Dialect dialect, string connectionString, string scope, string providerName) + : base(dialect, connectionString, null, scope) + { + if (string.IsNullOrEmpty(providerName)) + { + providerName = "FirebirdSql.Data.FirebirdClient"; + } + + var fac = DbProviderFactoriesHelper.GetFactory(providerName, "FirebirdSql.Data.FirebirdClient", "FirebirdSql.Data.FirebirdClient.FirebirdClientFactory"); + _connection = fac.CreateConnection(); + _connection.ConnectionString = _connectionString; + this._connection.Open(); + } + + public FirebirdTransformationProvider(Dialect dialect, IDbConnection connection, string scope, string providerName) + : base(dialect, connection, null, scope) + { + } + + public override void AddColumn(string table, string sqlColumn) + { + table = QuoteTableNameIfRequired(table); + ExecuteNonQuery(string.Format("ALTER TABLE {0} ADD {1}", table, sqlColumn)); + } + + public override void DropDatabases(string databaseName) + { + if (string.IsNullOrEmpty(databaseName)) + { + ExecuteNonQuery(string.Format("DROP DATABASE")); + } + } + + /// + /// Execute an SQL query returning results. + /// + /// The SQL command. + /// A data iterator, IDataReader. + public override IDataReader ExecuteQuery(IDbCommand cmd, string sql) + { + Logger.Trace(sql); + //IDbCommand cmd = BuildCommand(sql); + { + try + { + return cmd.ExecuteReader(); + } + catch (Exception ex) + { + Logger.Warn("query failed: {0}", cmd.CommandText); + throw new Exception("Failed to execute sql statement: " + sql, ex); + } + } + } + + public override Column[] GetColumns(string table) + { + var columns = new List(); + using (var cmd = CreateCommand()) + using ( + var reader = + ExecuteQuery(cmd, + string.Format("select RDB$FIELD_NAME, RDB$NULL_FLAG from RDB$RELATION_FIELDS where RDB$RELATION_NAME = '{0}'", table.ToUpper()))) + { + while (reader.Read()) + { + var column = new Column(reader.GetString(0).Trim(), DbType.String); + var nullableStr = reader.GetString(1); + var isNullable = nullableStr == "1"; + column.ColumnProperty |= isNullable ? ColumnProperty.Null : ColumnProperty.NotNull; + + columns.Add(column); + } + } + + return columns.ToArray(); + } + + public override void AddTable(string name, params IDbField[] fields) + { + var columns = fields.Where(x => x is Column).Cast().ToArray(); + + base.AddTable(name, fields); + + if (columns.Any(c => c.ColumnProperty == ColumnProperty.PrimaryKeyWithIdentity)) + { + var identityColumn = columns.First(c => c.ColumnProperty == ColumnProperty.PrimaryKeyWithIdentity); + + var seqTName = name.Length > 21 ? name.Substring(0, 21) : name; + if (seqTName.EndsWith("_")) + { + seqTName = seqTName.Substring(0, seqTName.Length - 1); + } + + // Create a sequence for the table + using (var cmd = CreateCommand()) + { + ExecuteQuery(cmd, string.Format("CREATE GENERATOR {0}_SEQUENCE", seqTName)); + } + + using (var cmd = CreateCommand()) + { + ExecuteQuery(cmd, string.Format("SET GENERATOR {0}_SEQUENCE TO 0", seqTName)); + } + + var sql = ""; // "set term !! ;"; + sql += "CREATE TRIGGER {1}_TRIGGER FOR {0}\n"; + sql += "ACTIVE BEFORE INSERT POSITION 0\n"; + sql += "AS\n"; + sql += "BEGIN\n"; + sql += "if (NEW.{2} is NULL) then NEW.{2} = GEN_ID({1}_SEQUENCE, 1);\n"; + sql += "END\n"; + + using (var cmd = CreateCommand()) + { + ExecuteQuery(cmd, string.Format(sql, name, seqTName, identityColumn.Name)); + } + } + } + + public override List GetDatabases() + { + throw new NotImplementedException(); + } + + public override bool ConstraintExists(string table, string name) + { + //todo, implement this!!! + + //http://edn.embarcadero.com/article/25259 field infos in FB + //http://www.felix-colibri.com/papers/db/interbase/using_interbase_system_tables/using_interbase_system_tables.html + + return false; + } + + public override bool IndexExists(string table, string name) + { + return false; + } +} diff --git a/src/Migrator/Providers/Impl/Informix/InformixDialect.cs b/src/Migrator/Providers/Impl/Informix/InformixDialect.cs new file mode 100644 index 00000000..9a93fff3 --- /dev/null +++ b/src/Migrator/Providers/Impl/Informix/InformixDialect.cs @@ -0,0 +1,75 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.Informix; + +public class InformixDialect : Dialect +{ + public InformixDialect() + { + this.RegisterColumnType(DbType.AnsiStringFixedLength, "CHAR(255)"); + this.RegisterColumnType(DbType.AnsiStringFixedLength, 255, "CHAR($l)"); + this.RegisterColumnType(DbType.AnsiStringFixedLength, 65535, "TEXT"); + this.RegisterColumnType(DbType.AnsiStringFixedLength, 16777215, "MEDIUMTEXT"); + this.RegisterColumnType(DbType.AnsiString, "VARCHAR(255)"); + this.RegisterColumnType(DbType.AnsiString, 255, "VARCHAR($l)"); + this.RegisterColumnType(DbType.AnsiString, 256, "VARCHAR(255)"); + this.RegisterColumnType(DbType.AnsiString, 65535, "TEXT"); + this.RegisterColumnType(DbType.AnsiString, 16777215, "MEDIUMTEXT"); + this.RegisterColumnType(DbType.Binary, "LONGBLOB"); + this.RegisterColumnType(DbType.Binary, 127, "TINYBLOB"); + this.RegisterColumnType(DbType.Binary, 65535, "BLOB"); + this.RegisterColumnType(DbType.Binary, 16777215, "MEDIUMBLOB"); + this.RegisterColumnType(DbType.Boolean, "TINYINT(1)"); + this.RegisterColumnType(DbType.Byte, "TINYINT UNSIGNED"); + this.RegisterColumnType(DbType.Currency, "MONEY"); + this.RegisterColumnType(DbType.Date, "DATE"); + this.RegisterColumnType(DbType.DateTime, "DATETIME"); + this.RegisterColumnType(DbType.DateTimeOffset, "DATETIME"); + this.RegisterColumnType(DbType.Decimal, "NUMERIC(19,5)"); + this.RegisterColumnType(DbType.Decimal, 19, "NUMERIC(19, $l)"); + this.RegisterColumnType(DbType.Double, "DOUBLE"); + this.RegisterColumnType(DbType.Guid, "VARCHAR(40)"); + this.RegisterColumnType(DbType.Int16, "SMALLINT"); + this.RegisterColumnType(DbType.Int32, "INTEGER"); + this.RegisterColumnType(DbType.Int64, "BIGINT"); + this.RegisterColumnType(DbType.Single, "FLOAT"); + this.RegisterColumnType(DbType.StringFixedLength, "CHAR(255)"); + this.RegisterColumnType(DbType.StringFixedLength, 255, "CHAR($l)"); + this.RegisterColumnType(DbType.StringFixedLength, 65535, "TEXT"); + this.RegisterColumnType(DbType.StringFixedLength, 16777215, "MEDIUMTEXT"); + this.RegisterColumnType(DbType.String, "VARCHAR(255)"); + this.RegisterColumnType(DbType.String, 255, "VARCHAR($l)"); + this.RegisterColumnType(DbType.String, 256, "VARCHAR(255)"); + this.RegisterColumnType(DbType.String, 65535, "TEXT"); + this.RegisterColumnType(DbType.String, 16777215, "MEDIUMTEXT"); + this.RegisterColumnType(DbType.String, 1073741823, "LONGTEXT"); + this.RegisterColumnType(DbType.Time, "TIME"); + + this.RegisterProperty(ColumnProperty.Unsigned, "UNSIGNED"); + this.RegisterProperty(ColumnProperty.Identity, "AUTO_INCREMENT"); + + this.RegisterUnsignedCompatible(DbType.Int16); + this.RegisterUnsignedCompatible(DbType.Int32); + this.RegisterUnsignedCompatible(DbType.Int64); + this.RegisterUnsignedCompatible(DbType.Decimal); + this.RegisterUnsignedCompatible(DbType.Double); + this.RegisterUnsignedCompatible(DbType.Single); + + this.AddReservedWords("KEY"); + } + + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, + string defaultSchema, string scope, string providerName) + { + return new InformixTransformationProvider(dialect, connectionString, scope, providerName); + } + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, + string defaultSchema, + string scope, string providerName) + { + return new InformixTransformationProvider(dialect, connection, scope, providerName); + } +} diff --git a/src/Migrator/Providers/Impl/Informix/InformixTransformationProvider.cs b/src/Migrator/Providers/Impl/Informix/InformixTransformationProvider.cs new file mode 100644 index 00000000..4ab49fa7 --- /dev/null +++ b/src/Migrator/Providers/Impl/Informix/InformixTransformationProvider.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Data; + +namespace DotNetProjects.Migrator.Providers.Impl.Informix; + +/// +/// DB2 transformation provider +/// +public class InformixTransformationProvider : TransformationProvider +{ + public InformixTransformationProvider(Dialect dialect, string connectionString, string scope, string providerName) + : base(dialect, connectionString, null, scope) + { + if (string.IsNullOrEmpty(providerName)) + { + providerName = "IBM.Data.Informix.Client"; + } + + var fac = DbProviderFactoriesHelper.GetFactory(providerName, null, null); + _connection = fac.CreateConnection(); + _connection.ConnectionString = _connectionString; + this._connection.Open(); + } + + public InformixTransformationProvider(Dialect dialect, IDbConnection connection, string scope, string providerName) + : base(dialect, connection, null, scope) + { + } + + public override List GetDatabases() + { + throw new NotImplementedException(); + } + + public override bool ConstraintExists(string table, string name) + { + throw new NotImplementedException(); + } + + public override bool IndexExists(string table, string name) + { + throw new NotImplementedException(); + } +} diff --git a/src/Migrator/Providers/Impl/Ingres/IngresDialect.cs b/src/Migrator/Providers/Impl/Ingres/IngresDialect.cs new file mode 100644 index 00000000..a6ce8103 --- /dev/null +++ b/src/Migrator/Providers/Impl/Ingres/IngresDialect.cs @@ -0,0 +1,75 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.Ingres; + +public class IngresDialect : Dialect +{ + public IngresDialect() + { + this.RegisterColumnType(DbType.AnsiStringFixedLength, "CHAR(255)"); + this.RegisterColumnType(DbType.AnsiStringFixedLength, 255, "CHAR($l)"); + this.RegisterColumnType(DbType.AnsiStringFixedLength, 65535, "TEXT"); + this.RegisterColumnType(DbType.AnsiStringFixedLength, 16777215, "MEDIUMTEXT"); + this.RegisterColumnType(DbType.AnsiString, "VARCHAR(255)"); + this.RegisterColumnType(DbType.AnsiString, 255, "VARCHAR($l)"); + this.RegisterColumnType(DbType.AnsiString, 256, "VARCHAR(255)"); + this.RegisterColumnType(DbType.AnsiString, 65535, "TEXT"); + this.RegisterColumnType(DbType.AnsiString, 16777215, "MEDIUMTEXT"); + this.RegisterColumnType(DbType.Binary, "LONGBLOB"); + this.RegisterColumnType(DbType.Binary, 127, "TINYBLOB"); + this.RegisterColumnType(DbType.Binary, 65535, "BLOB"); + this.RegisterColumnType(DbType.Binary, 16777215, "MEDIUMBLOB"); + this.RegisterColumnType(DbType.Boolean, "TINYINT(1)"); + this.RegisterColumnType(DbType.Byte, "TINYINT UNSIGNED"); + this.RegisterColumnType(DbType.Currency, "MONEY"); + this.RegisterColumnType(DbType.Date, "DATE"); + this.RegisterColumnType(DbType.DateTime, "DATETIME"); + this.RegisterColumnType(DbType.DateTimeOffset, "DATETIME"); + this.RegisterColumnType(DbType.Decimal, "NUMERIC(19,5)"); + this.RegisterColumnType(DbType.Decimal, 19, "NUMERIC(19, $l)"); + this.RegisterColumnType(DbType.Double, "DOUBLE"); + this.RegisterColumnType(DbType.Guid, "VARCHAR(40)"); + this.RegisterColumnType(DbType.Int16, "SMALLINT"); + this.RegisterColumnType(DbType.Int32, "INTEGER"); + this.RegisterColumnType(DbType.Int64, "BIGINT"); + this.RegisterColumnType(DbType.Single, "FLOAT"); + this.RegisterColumnType(DbType.StringFixedLength, "CHAR(255)"); + this.RegisterColumnType(DbType.StringFixedLength, 255, "CHAR($l)"); + this.RegisterColumnType(DbType.StringFixedLength, 65535, "TEXT"); + this.RegisterColumnType(DbType.StringFixedLength, 16777215, "MEDIUMTEXT"); + this.RegisterColumnType(DbType.String, "VARCHAR(255)"); + this.RegisterColumnType(DbType.String, 255, "VARCHAR($l)"); + this.RegisterColumnType(DbType.String, 256, "VARCHAR(255)"); + this.RegisterColumnType(DbType.String, 65535, "TEXT"); + this.RegisterColumnType(DbType.String, 16777215, "MEDIUMTEXT"); + this.RegisterColumnType(DbType.String, 1073741823, "LONGTEXT"); + this.RegisterColumnType(DbType.Time, "TIME"); + + this.RegisterProperty(ColumnProperty.Unsigned, "UNSIGNED"); + this.RegisterProperty(ColumnProperty.Identity, "AUTO_INCREMENT"); + + this.RegisterUnsignedCompatible(DbType.Int16); + this.RegisterUnsignedCompatible(DbType.Int32); + this.RegisterUnsignedCompatible(DbType.Int64); + this.RegisterUnsignedCompatible(DbType.Decimal); + this.RegisterUnsignedCompatible(DbType.Double); + this.RegisterUnsignedCompatible(DbType.Single); + + this.AddReservedWords("KEY"); + } + + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, + string defaultSchema, string scope, string providerName) + { + return new IngresTransformationProvider(dialect, connectionString, scope, providerName); + } + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, + string defaultSchema, + string scope, string providerName) + { + return new IngresTransformationProvider(dialect, connection, scope, providerName); + } +} diff --git a/src/Migrator/Providers/Impl/Ingres/IngresTransformationProvider.cs b/src/Migrator/Providers/Impl/Ingres/IngresTransformationProvider.cs new file mode 100644 index 00000000..ac69a823 --- /dev/null +++ b/src/Migrator/Providers/Impl/Ingres/IngresTransformationProvider.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Data; + +namespace DotNetProjects.Migrator.Providers.Impl.Ingres; + +public class IngresTransformationProvider : TransformationProvider +{ + public IngresTransformationProvider(Dialect dialect, string connectionString, string scope, string providerName) + : base(dialect, connectionString, null, scope) + { + if (string.IsNullOrEmpty(providerName)) + { + providerName = "Ingres.Client"; + } + + var fac = DbProviderFactoriesHelper.GetFactory(providerName, null, null); + _connection = fac.CreateConnection(); + _connection.ConnectionString = _connectionString; + this._connection.Open(); + } + + public IngresTransformationProvider(Dialect dialect, IDbConnection connection, string scope, string providerName) + : base(dialect, connection, null, scope) + { + } + + public override List GetDatabases() + { + throw new NotImplementedException(); + } + + public override bool ConstraintExists(string table, string name) + { + throw new NotImplementedException(); + } + + public override bool IndexExists(string table, string name) + { + throw new NotImplementedException(); + } +} diff --git a/src/Migrator/Providers/Impl/Mysql/MariaDBDialect.cs b/src/Migrator/Providers/Impl/Mysql/MariaDBDialect.cs new file mode 100644 index 00000000..2f8f614b --- /dev/null +++ b/src/Migrator/Providers/Impl/Mysql/MariaDBDialect.cs @@ -0,0 +1,19 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.Mysql; + +public class MariaDBDialect : MysqlDialect +{ + public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) + { + return new MariaDBTransformationProvider(dialect, connectionString, scope, providerName); + } + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, + string defaultSchema, + string scope, string providerName) + { + return new MariaDBTransformationProvider(dialect, connection, scope, providerName); + } +} diff --git a/src/Migrator/Providers/Impl/Mysql/MariaDBTransformationProvider.cs b/src/Migrator/Providers/Impl/Mysql/MariaDBTransformationProvider.cs new file mode 100644 index 00000000..a8ef9397 --- /dev/null +++ b/src/Migrator/Providers/Impl/Mysql/MariaDBTransformationProvider.cs @@ -0,0 +1,28 @@ +using System.Data; + +namespace DotNetProjects.Migrator.Providers.Impl.Mysql; + +/// +/// MySql transformation provider +/// +public class MariaDBTransformationProvider : MySqlTransformationProvider +{ + public MariaDBTransformationProvider(Dialect dialect, string connectionString, string scope, string providerName) + : base(dialect, connectionString, scope, providerName) + { + if (string.IsNullOrEmpty(providerName)) + { + providerName = "MySql.Data.MySqlClient"; + } + + var fac = DbProviderFactoriesHelper.GetFactory(providerName, "MySql.Data", "MySql.Data.MySqlClient.MySqlClientFactory"); + _connection = fac.CreateConnection(); + _connection.ConnectionString = _connectionString; + _connection.Open(); + } + + public MariaDBTransformationProvider(Dialect dialect, IDbConnection connection, string scope, string providerName) + : base(dialect, connection, scope, providerName) + { + } +} diff --git a/src/Migrator/Providers/Impl/Mysql/MySqlTransformationProvider.cs b/src/Migrator/Providers/Impl/Mysql/MySqlTransformationProvider.cs new file mode 100644 index 00000000..7491d2d8 --- /dev/null +++ b/src/Migrator/Providers/Impl/Mysql/MySqlTransformationProvider.cs @@ -0,0 +1,410 @@ +using DotNetProjects.Migrator.Framework; +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using Index = DotNetProjects.Migrator.Framework.Index; + +namespace DotNetProjects.Migrator.Providers.Impl.Mysql; + +/// +/// MySql transformation provider +/// +public class MySqlTransformationProvider : TransformationProvider +{ + public MySqlTransformationProvider(Dialect dialect, string connectionString, string scope, string providerName) + : base(dialect, connectionString, null, scope) // we ignore schemas for MySql (schema == database for MySql) + { + if (string.IsNullOrEmpty(providerName)) + { + providerName = "MySql.Data.MySqlClient"; + } + + var fac = DbProviderFactoriesHelper.GetFactory(providerName, "MySql.Data", "MySql.Data.MySqlClient.MySqlClientFactory"); + _connection = fac.CreateConnection(); //new MySqlConnection(_connectionString) {ConnectionString = _connectionString}; + _connection.ConnectionString = _connectionString; + _connection.Open(); + } + + public MySqlTransformationProvider(Dialect dialect, IDbConnection connection, string scope, string providerName) + : base(dialect, connection, null, scope) + { + } + + public override void RemoveForeignKey(string table, string name) + { + if (ForeignKeyExists(table, name)) + { + ExecuteNonQuery(string.Format("ALTER TABLE {0} DROP FOREIGN KEY {1}", table, _dialect.Quote(name))); + } + } + + public override void RemoveAllIndexes(string table) + { + var qry = string.Format(@"SELECT k.TABLE_NAME, i.CONSTRAINT_NAME, i.CONSTRAINT_TYPE + FROM information_schema.KEY_COLUMN_USAGE k + INNER JOIN information_schema.TABLE_CONSTRAINTS i + ON i.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND i.TABLE_NAME = k.TABLE_NAME + WHERE k.REFERENCED_TABLE_SCHEMA='{0}' AND + (k.REFERENCED_TABLE_NAME='{1}') OR (k.TABLE_NAME='{1}')", GetDatabase(), table); + + var l = new List>(); + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, qry)) + { + while (reader.Read()) + { + l.Add(new Tuple(reader.GetString(0), reader.GetString(1), reader.GetString(2))); + } + } + + foreach (var tuple in l) + { + if (tuple.Item3 == "FOREIGN KEY") + { + RemoveForeignKey(tuple.Item1, tuple.Item2); + } + else if (tuple.Item3 == "PRIMARY KEY") + { + try + { + ExecuteNonQuery(string.Format("ALTER TABLE {0} DROP PRIMARY KEY", table)); + } + catch (Exception) + { } + } + else if (tuple.Item3 == "UNIQUE") + { + RemoveIndex(tuple.Item1, tuple.Item2); + } + } + } + + public override void RemoveAllForeignKeys(string tableName, string columnName) + { + var qry = string.Format(@"SELECT k.TABLE_NAME, i.CONSTRAINT_NAME + FROM information_schema.KEY_COLUMN_USAGE k + INNER JOIN information_schema.TABLE_CONSTRAINTS i + ON i.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND i.TABLE_NAME = k.TABLE_NAME + WHERE k.REFERENCED_TABLE_SCHEMA='{0}' AND i.CONSTRAINT_TYPE = 'FOREIGN KEY' AND + (k.REFERENCED_TABLE_NAME='{1}' AND REFERENCED_COLUMN_NAME='{2}') OR (k.TABLE_NAME='{1}' AND COLUMN_NAME='{2}')", GetDatabase(), tableName, columnName); + + if (string.IsNullOrEmpty(columnName)) + { + qry = string.Format(@"SELECT k.TABLE_NAME, i.CONSTRAINT_NAME + FROM information_schema.KEY_COLUMN_USAGE k + INNER JOIN information_schema.TABLE_CONSTRAINTS i + ON i.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND i.TABLE_NAME = k.TABLE_NAME + WHERE k.REFERENCED_TABLE_SCHEMA='{0}' AND i.CONSTRAINT_TYPE = 'FOREIGN KEY' AND + (k.REFERENCED_TABLE_NAME='{1}') OR (k.TABLE_NAME='{1}')", GetDatabase(), tableName); + } + var l = new List>(); + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, qry)) + { + while (reader.Read()) + { + l.Add(new Tuple(reader.GetString(0), reader.GetString(1))); + } + } + + foreach (var tuple in l) + { + RemoveForeignKey(tuple.Item1, tuple.Item2); + } + } + + public override void RemoveConstraint(string table, string name) + { + if (ConstraintExists(table, name)) + { + ExecuteNonQuery(string.Format("ALTER TABLE {0} DROP KEY {1}", table, _dialect.Quote(name))); + } + } + + public override bool ConstraintExists(string table, string name) + { + if (!TableExists(table)) + { + return false; + } + + var sqlConstraint = string.Format("SHOW KEYS FROM {0}", table); + + using var cmd = CreateCommand(); + using var reader = ExecuteQuery(cmd, sqlConstraint); + + while (reader.Read()) + { + if (reader["Key_name"].ToString().ToLower() == name.ToLower()) + { + return true; + } + } + + return false; + } + + public bool ForeignKeyExists(string table, string name) + { + if (!TableExists(table)) + { + return false; + } + + var sqlConstraint = string.Format(@"SELECT distinct i.CONSTRAINT_NAME + FROM information_schema.TABLE_CONSTRAINTS i + INNER JOIN information_schema.KEY_COLUMN_USAGE k + ON i.CONSTRAINT_NAME = k.CONSTRAINT_NAME + WHERE i.CONSTRAINT_TYPE = 'FOREIGN KEY' + AND i.TABLE_SCHEMA = '{1}' + AND i.TABLE_NAME = '{0}';", table, GetDatabase()); + + using var cmd = CreateCommand(); + using var reader = ExecuteQuery(cmd, sqlConstraint); + + while (reader.Read()) + { + if (reader["CONSTRAINT_NAME"].ToString().ToLower() == name.ToLower()) + { + return true; + } + } + + return false; + } + + public override Index[] GetIndexes(string table) + { + var retVal = new List(); + + var sql = @"SHOW INDEX FROM {0}"; + + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, string.Format(sql, table))) + { + while (reader.Read()) + { + if (!reader.IsDBNull(1)) + { + var idx = new Index + { + Name = reader.GetString(2), + PrimaryKey = reader.GetString(2) == "PRIMARY", + Unique = !reader.GetBoolean(1), + }; + //var cols = reader.GetString(7); + //cols = cols.Substring(1, cols.Length - 2); + //idx.KeyColumns = cols.Split(','); + retVal.Add(idx); + } + } + } + + return retVal.ToArray(); + } + + public override bool PrimaryKeyExists(string table, string name) + { + return ConstraintExists(table, "PRIMARY"); + } + + public override Column[] GetColumns(string table) + { + var columns = new List(); + using (var cmd = CreateCommand()) + using ( + var reader = + ExecuteQuery(cmd, + string.Format("SHOW COLUMNS FROM {0}", table))) + { + while (reader.Read()) + { + var column = new Column(reader.GetString(0), DbType.String); + var nullableStr = reader.GetString(2); + var isNullable = nullableStr == "YES"; + var defaultValue = reader.GetValue(4); + column.ColumnProperty |= isNullable ? ColumnProperty.Null : ColumnProperty.NotNull; + + if (defaultValue != null && defaultValue != DBNull.Value) + { + column.DefaultValue = defaultValue; + } + + if (column.DefaultValue != null) + { + if (column.Type == DbType.Int16 || column.Type == DbType.Int32 || column.Type == DbType.Int64) + { + column.DefaultValue = long.Parse(column.DefaultValue.ToString()); + } + else if (column.Type == DbType.UInt16 || column.Type == DbType.UInt32 || column.Type == DbType.UInt64) + { + column.DefaultValue = ulong.Parse(column.DefaultValue.ToString()); + } + else if (column.Type == DbType.Double || column.Type == DbType.Single) + { + column.DefaultValue = double.Parse(column.DefaultValue.ToString()); + } + else if (column.Type == DbType.Boolean) + { + column.DefaultValue = column.DefaultValue.ToString().Trim() == "1" || column.DefaultValue.ToString().Trim().ToUpper() == "TRUE" || column.DefaultValue.ToString().Trim() == "YES"; + } + else if (column.Type == DbType.DateTime || column.Type == DbType.DateTime2) + { + if (column.DefaultValue is string defVal) + { + var dt = defVal; + if (defVal.StartsWith("'")) + { + dt = defVal.Substring(1, defVal.Length - 2); + } + + var d = DateTime.ParseExact(dt, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture); + column.DefaultValue = d; + } + } + else if (column.Type == DbType.Guid) + { + if (column.DefaultValue is string defVal) + { + var dt = defVal; + if (defVal.StartsWith("'")) + { + dt = defVal.Substring(1, defVal.Length - 2); + } + + var d = Guid.Parse(dt); + column.DefaultValue = d; + } + } + } + + columns.Add(column); + } + } + + return columns.ToArray(); + } + + public override string[] GetTables() + { + var tables = new List(); + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, "SHOW TABLES")) + { + while (reader.Read()) + { + tables.Add((string)reader[0]); + } + } + + return tables.ToArray(); + } + + public override void ChangeColumn(string table, string sqlColumn) + { + ExecuteNonQuery(string.Format("ALTER TABLE {0} MODIFY {1}", table, sqlColumn)); + } + + public override void AddTable(string name, params IDbField[] columns) + { + AddTable(name, "INNODB", columns); + } + + public override void AddTable(string name, string engine, string columns) + { + var sqlCreate = string.Format("CREATE TABLE {0} ({1}) ENGINE = {2}", name, columns, engine); + ExecuteNonQuery(sqlCreate); + } + + public override void RenameColumn(string tableName, string oldColumnName, string newColumnName) + { + if (ColumnExists(tableName, newColumnName)) + { + throw new MigrationException(string.Format("Table '{0}' has column named '{1}' already", tableName, newColumnName)); + } + + if (!ColumnExists(tableName, oldColumnName)) + { + throw new MigrationException(string.Format("The table '{0}' does not have a column named '{1}'", tableName, oldColumnName)); + } + + string definition = null; + + var dropPrimary = false; + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, string.Format("SHOW COLUMNS FROM {0} WHERE Field='{1}'", tableName, oldColumnName))) + { + if (reader.Read()) + { + // TODO: Could use something similar to construct the columns in GetColumns + definition = reader["Type"].ToString(); + if ("NO" == reader["Null"].ToString()) + { + definition += " " + "NOT NULL"; + } + + if (!reader.IsDBNull(reader.GetOrdinal("Key"))) + { + var key = reader["Key"].ToString(); + if ("PRI" == key) + { + //definition += " " + "PRIMARY KEY"; + dropPrimary = true; + } + else if ("UNI" == key) + { + definition += " " + "UNIQUE"; + } + } + + if (!reader.IsDBNull(reader.GetOrdinal("Extra"))) + { + definition += " " + reader["Extra"]; + } + } + } + + if (!string.IsNullOrEmpty(definition)) + { + if (dropPrimary) + { + ExecuteNonQuery(string.Format("ALTER TABLE {0} DROP PRIMARY KEY", tableName)); + } + + ExecuteNonQuery(string.Format("ALTER TABLE {0} CHANGE {1} {2} {3}", tableName, QuoteColumnNameIfRequired(oldColumnName), QuoteColumnNameIfRequired(newColumnName), definition)); + if (dropPrimary) + { + ExecuteNonQuery(string.Format("ALTER TABLE {0} ADD PRIMARY KEY({1});", tableName, QuoteColumnNameIfRequired(newColumnName))); + } + } + } + + public string GetDatabase() + { + return ExecuteScalar("SELECT DATABASE()") as string; + } + + public override void RemoveIndex(string table, string name) + { + if (IndexExists(table, name)) + { + ExecuteNonQuery(string.Format("DROP INDEX {1} ON {0}", table, _dialect.Quote(name))); + } + } + + public override List GetDatabases() + { + return ExecuteStringQuery("SHOW DATABASES"); + } + + public override bool IndexExists(string table, string name) + { + return ConstraintExists(table, name); + } + + public override string Concatenate(params string[] strings) + { + return "CONCAT(" + string.Join(", ", strings) + ")"; + } +} diff --git a/src/Migrator/Providers/Impl/Mysql/MysqlDialect.cs b/src/Migrator/Providers/Impl/Mysql/MysqlDialect.cs new file mode 100644 index 00000000..fa51e466 --- /dev/null +++ b/src/Migrator/Providers/Impl/Mysql/MysqlDialect.cs @@ -0,0 +1,290 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.Mysql; + +public class MysqlDialect : Dialect +{ + public MysqlDialect() + { + // TODO: As per http://dev.mysql.com/doc/refman/5.0/en/char.html 5.0.3 and above + // can handle varchar(n) up to a length OF 65,535 - so the limit of 255 should no longer apply. + + RegisterColumnType(DbType.AnsiStringFixedLength, "CHAR(255)"); + RegisterColumnType(DbType.AnsiStringFixedLength, 255, "CHAR($l)"); + RegisterColumnType(DbType.AnsiStringFixedLength, 65535, "TEXT"); + RegisterColumnType(DbType.AnsiStringFixedLength, 16777215, "MEDIUMTEXT"); + RegisterColumnType(DbType.AnsiString, "VARCHAR(255)"); + RegisterColumnType(DbType.AnsiString, 255, "VARCHAR($l)"); + RegisterColumnType(DbType.AnsiString, 256, "VARCHAR(255)"); + RegisterColumnType(DbType.AnsiString, 65535, "TEXT"); + RegisterColumnType(DbType.AnsiString, 16777215, "MEDIUMTEXT"); + RegisterColumnType(DbType.Binary, "LONGBLOB"); + RegisterColumnType(DbType.Binary, 127, "TINYBLOB"); + RegisterColumnType(DbType.Binary, 65535, "BLOB"); + RegisterColumnType(DbType.Binary, 16777215, "MEDIUMBLOB"); + RegisterColumnType(DbType.Boolean, "TINYINT(1)"); + RegisterColumnType(DbType.Byte, "TINYINT UNSIGNED"); + RegisterColumnType(DbType.Currency, "MONEY"); + RegisterColumnType(DbType.Date, "DATE"); + RegisterColumnType(DbType.DateTime, "DATETIME"); + RegisterColumnType(DbType.DateTime2, "DATETIME"); + RegisterColumnType(DbType.DateTimeOffset, "DATETIME"); + RegisterColumnType(DbType.Decimal, "NUMERIC(19,5)"); + RegisterColumnType(DbType.Decimal, 19, "NUMERIC(19, $l)"); + RegisterColumnType(DbType.Double, "DOUBLE"); + RegisterColumnType(DbType.Guid, "VARCHAR(40)"); + RegisterColumnType(DbType.Int16, "SMALLINT"); + RegisterColumnType(DbType.Int32, "INTEGER"); + RegisterColumnType(DbType.Int64, "BIGINT"); + RegisterColumnType(MigratorDbType.Interval, "BIGINT"); + RegisterColumnType(DbType.UInt16, "INTEGER"); + RegisterColumnType(DbType.UInt32, "BIGINT"); + RegisterColumnType(DbType.UInt64, "NUMERIC(20,0)"); + RegisterColumnType(DbType.Single, "FLOAT"); + RegisterColumnType(DbType.StringFixedLength, "CHAR(255)"); + RegisterColumnType(DbType.StringFixedLength, 255, "CHAR($l)"); + RegisterColumnType(DbType.StringFixedLength, 16383, "TEXT"); + RegisterColumnType(DbType.StringFixedLength, 5592415, "MEDIUMTEXT"); + RegisterColumnType(DbType.String, "VARCHAR(255)"); + RegisterColumnType(DbType.String, 16383, "VARCHAR($l)"); + RegisterColumnType(DbType.String, 5592415, "MEDIUMTEXT"); + RegisterColumnType(DbType.String, int.MaxValue, "LONGTEXT"); + RegisterColumnType(DbType.Time, "TIME"); + + RegisterProperty(ColumnProperty.Unsigned, "UNSIGNED"); + RegisterProperty(ColumnProperty.Identity, "AUTO_INCREMENT"); + RegisterProperty(ColumnProperty.CaseSensitive, "BINARY"); + + RegisterUnsignedCompatible(DbType.Int16); + RegisterUnsignedCompatible(DbType.Int32); + RegisterUnsignedCompatible(DbType.Int64); + RegisterUnsignedCompatible(DbType.Decimal); + RegisterUnsignedCompatible(DbType.Double); + RegisterUnsignedCompatible(DbType.Single); + + AddReservedWords("ACCESSIBLE", "ACTION", "ADD", + "AFTER", "AGAINST", "AGGREGATE", + "ALGORITHM", "ALL", "ALTER", + "ANALYZE", "AND", "ANY", + "AS", "ASC", "ASCII", + "ASENSITIVE", "AT", "AUTHORS", + "AUTOEXTEND_SIZE", "AUTO_INCREMENT", "AVG", + "AVG_ROW_LENGTH", "BACKUP", "BEFORE", + "BEGIN", "BETWEEN", "BIGINT", + "BINARY", "BINLOG", "BIT", + "BLOB", "BLOCK", "BOOL", + "BOOLEAN", "BOTH", "BTREE", + "BY", "BYTE", "CACHE", + "CALL", "CASCADE", "CASCADED", + "CASE", "CATALOG_NAME", "CHAIN", + "CHANGE", "CHANGED", "CHAR", + "CHARACTER", "CHARSET", "CHECK", + "CHECKSUM", "CIPHER", "CLASS_ORIGIN", + "CLIENT", "CLOSE", "COALESCE", + "CODE", "COLLATE", "COLLATION", + "COLUMN", "COLUMNS", "COLUMN_NAME", + "COMMENT", "COMMIT", "COMMITTED", + "COMPACT", "COMPLETION", "COMPRESSED", + "CONCURRENT", "CONDITION", "CONNECTION", + "CONSISTENT", "CONSTRAINT", "CONSTRAINT_CATALOG", + "CONSTRAINT_NAME", "CONSTRAINT_SCHEMA", "CONTAINS", + "CONTEXT", "CONTINUE", "CONTRIBUTORS", + "CONVERT", "CPU", "CREATE", + "CROSS", "CUBE", "CURRENT_DATE", + "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", + "CURSOR", "CURSOR_NAME", "DATA", + "DATABASE", "DATABASES", "DATAFILE", + "DATE", "DATETIME", "DAY", + "DAY_HOUR", "DAY_MICROSECOND", "DAY_MINUTE", + "DAY_SECOND", "DEALLOCATE", "DEC", + "DECIMAL", "DECLARE", "DEFAULT", + "DEFINER", "DELAYED", "DELAY_KEY_WRITE", + "DELETE", "DESC", "DESCRIBE", + "DES_KEY_FILE", "DETERMINISTIC", "DIRECTORY", + "DISABLE", "DISCARD", "DISK", + "DISTINCT", "DISTINCTROW", "DIV", + "DO", "DOUBLE", "DROP", + "DUAL", "DUMPFILE", "DUPLICATE", + "DYNAMIC", "EACH", "ELSE", + "ELSEIF", "ENABLE", "ENCLOSED", + "END", "ENDS", "ENGINE", + "ENGINES", "ENUM", "ERROR", + "ERRORS", "ESCAPE", "ESCAPED", + "EVENT", "EVENTS", "EVERY", + "EXECUTE", "EXISTS", "EXIT", + "EXPANSION", "EXPLAIN", "EXTENDED", + "EXTENT_SIZE", "FALSE", "FAST", + "FAULTS", "FETCH", "FIELDS", + "FILE", "FIRST", "FIXED", + "FLOAT", "FLOAT4", "FLOAT8", + "FLUSH", "FOR", "FORCE", + "FOREIGN", "FOUND", "FRAC_SECOND", + "FROM", "FULL", "FULLTEXT", + "FUNCTION", "GENERAL", "GEOMETRY", + "GEOMETRYCOLLECTION", "GET_FORMAT", "GLOBAL", + "GRANT", "GRANTS", "GROUP", + "HANDLER", "HASH", "HAVING", + "HELP", "HIGH_PRIORITY", "HOST", + "HOSTS", "HOUR", "HOUR_MICROSECOND", + "HOUR_MINUTE", "HOUR_SECOND", "IDENTIFIED", + "IF", "IGNORE", "IGNORE_SERVER_IDS", + "IMPORT", "IN", "INDEX", + "INDEXES", "INFILE", "INITIAL_SIZE", + "INNER", "INNOBASE", "INNODB", + "INOUT", "INSENSITIVE", "INSERT", + "INSERT_METHOD", "INSTALL", "INT", + "INT1", "INT2", "INT3", + "INT4", "INT8", "INTEGER", + "INTERVAL", "INTO", "INVOKER", + "IO", "IO_THREAD", "IPC", + "IS", "ISOLATION", "ISSUER", + "ITERATE", "JOIN", "KEY", + "KEYS", "KEY_BLOCK_SIZE", "KILL", + "LANGUAGE", "LAST", "LEADING", + "LEAVE", "LEAVES", "LEFT", + "LESS", "LEVEL", "LIKE", + "LIMIT", "LINEAR", "LINES", + "LINESTRING", "LIST", "LOAD", + "LOCAL", "LOCALTIME", "LOCALTIMESTAMP", + "LOCK", "LOCKS", "LOGFILE", + "LOGS", "LONG", "LONGBLOB", + "LONGTEXT", "LOOP", "LOW_PRIORITY", + "MASTER", "MASTER_CONNECT_RETRY", "MASTER_HEARTBEAT_PERIOD", + "MASTER_HOST", "MASTER_LOG_FILE", "MASTER_LOG_POS", + "MASTER_PASSWORD", "MASTER_PORT", "MASTER_SERVER_ID", + "MASTER_SSL", "MASTER_SSL_CA", "MASTER_SSL_CAPATH", + "MASTER_SSL_CERT", "MASTER_SSL_CIPHER", "MASTER_SSL_KEY", + "MASTER_SSL_VERIFY_SERVER_CERT", "MASTER_USER", "MATCH", + "MAXVALUE", "MAX_CONNECTIONS_PER_HOUR", "MAX_QUERIES_PER_HOUR", + "MAX_ROWS", "MAX_SIZE", "MAX_UPDATES_PER_HOUR", + "MAX_USER_CONNECTIONS", "MEDIUM", "MEDIUMBLOB", + "MEDIUMINT", "MEDIUMTEXT", "MEMORY", + "MERGE", "MESSAGE_TEXT", "MICROSECOND", + "MIDDLEINT", "MIGRATE", "MINUTE", + "MINUTE_MICROSECOND", "MINUTE_SECOND", "MIN_ROWS", + "MOD", "MODE", "MODIFIES", + "MODIFY", "MONTH", "MULTILINESTRING", + "MULTIPOINT", "MULTIPOLYGON", "MUTEX", + "MYSQL_ERRNO", "NAME", "NAMES", + "NATIONAL", "NATURAL", "NCHAR", + "NDB", "NDBCLUSTER", "NEW", + "NEXT", "NO", "NODEGROUP", + "NONE", "NOT", "NO_WAIT", + "NO_WRITE_TO_BINLOG", "NULL", "NUMERIC", + "NVARCHAR", "OFFSET", "OLD_PASSWORD", + "ON", "ONE", "ONE_SHOT", + "OPEN", "OPTIMIZE", "OPTION", + "OPTIONALLY", "OPTIONS", "OR", + "ORDER", "OUT", "OUTER", + "OUTFILE", "OWNER", "PACK_KEYS", + "PAGE", "PARSER", "PARTIAL", + "PARTITION", "PARTITIONING", "PARTITIONS", + "PASSWORD", "PHASE", "PLUGIN", + "PLUGINS", "POINT", "POLYGON", + "PORT", "PRECISION", "PREPARE", + "PRESERVE", "PREV", "PRIMARY", + "PRIVILEGES", "PROCEDURE", "PROCESSLIST", + "PROFILE", "PROFILES", "PROXY", + "PURGE", "QUARTER", "QUERY", + "QUICK", "RANGE", "READ", + "READS", "READ_ONLY", "READ_WRITE", + "REAL", "REBUILD", "RECOVER", + "REDOFILE", "REDO_BUFFER_SIZE", "REDUNDANT", + "REFERENCES", "REGEXP", "RELAY", + "RELAYLOG", "RELAY_LOG_FILE", "RELAY_LOG_POS", + "RELAY_THREAD", "RELEASE", "RELOAD", + "REMOVE", "RENAME", "REORGANIZE", + "REPAIR", "REPEAT", "REPEATABLE", + "REPLACE", "REPLICATION", "REQUIRE", + "RESET", "RESIGNAL", "RESTORE", + "RESTRICT", "RESUME", "RETURN", + "RETURNS", "REVOKE", "RIGHT", + "RLIKE", "ROLLBACK", "ROLLUP", + "ROUTINE", "ROW", "ROWS", + "ROW_FORMAT", "RTREE", "SAVEPOINT", + "SCHEDULE", "SCHEMA", "SCHEMAS", + "SCHEMA_NAME", "SECOND", "SECOND_MICROSECOND", + "SECURITY", "SELECT", "SENSITIVE", + "SEPARATOR", "SERIAL", "SERIALIZABLE", + "SERVER", "SESSION", "SET", + "SHARE", "SHOW", "SHUTDOWN", + "SIGNAL", "SIGNED", "SIMPLE", + "SLAVE", "SLOW", "SMALLINT", + "SNAPSHOT", "SOCKET", "SOME", + "SONAME", "SOUNDS", "SOURCE", + "SPATIAL", "SPECIFIC", "SQL", + "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", + "SQL_BIG_RESULT", "SQL_BUFFER_RESULT", "SQL_CACHE", + "SQL_CALC_FOUND_ROWS", "SQL_NO_CACHE", "SQL_SMALL_RESULT", + "SQL_THREAD", "SQL_TSI_DAY", "SQL_TSI_FRAC_SECOND", + "SQL_TSI_HOUR", "SQL_TSI_MINUTE", "SQL_TSI_MONTH", + "SQL_TSI_QUARTER", "SQL_TSI_SECOND", "SQL_TSI_WEEK", + "SQL_TSI_YEAR", "SSL", "START", + "STARTING", "STARTS", "STATUS", + "STOP", "STORAGE", "STRAIGHT_JOIN", + "STRING", "SUBCLASS_ORIGIN", "SUBJECT", + "SUBPARTITION", "SUBPARTITIONS", "SUPER", + "SUSPEND", "SWAPS", "SWITCHES", + "TABLE", "TABLES", "TABLESPACE", + "TABLE_CHECKSUM", "TABLE_NAME", "TEMPORARY", + "TEMPTABLE", "TERMINATED", "TEXT", + "THAN", "THEN", "TIME", + "TIMESTAMP", "TIMESTAMPADD", "TIMESTAMPDIFF", + "TINYBLOB", "TINYINT", "TINYTEXT", + "TO", "TRAILING", "TRANSACTION", + "TRIGGER", "TRIGGERS", "TRUE", + "TRUNCATE", "TYPE", "TYPES", + "UNCOMMITTED", "UNDEFINED", "UNDO", + "UNDOFILE", "UNDO_BUFFER_SIZE", "UNICODE", + "UNINSTALL", "UNION", "UNIQUE", + "UNKNOWN", "UNLOCK", "UNSIGNED", + "UNTIL", "UPDATE", "UPGRADE", + "USAGE", "USE", "USER", + "USER_RESOURCES", "USE_FRM", "USING", + "UTC_DATE", "UTC_TIME", "UTC_TIMESTAMP", + "VALUE", "VALUES", "VARBINARY", + "VARCHAR", "VARCHARACTER", "VARIABLES", + "VARYING", "VIEW", "WAIT", + "WARNINGS", "WEEK", "WHEN", + "WHERE", "WHILE", "WITH", + "WORK", "WRAPPER", "WRITE", + "X509", "XA", "XML", + "XOR", "YEAR", "YEAR_MONTH", + "ZEROFILL" + ); + } + + public override int MaxKeyLength + { + get { return 767; } + } + + public override string QuoteTemplate + { + get { return "`{0}`"; } + } + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, + string defaultSchema, string scope, string providerName) + { + return new MySqlTransformationProvider(dialect, connectionString, scope, providerName); + } + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, + string defaultSchema, + string scope, string providerName) + { + return new MySqlTransformationProvider(dialect, connection, scope, providerName); + } + + public override string Default(object defaultValue) + { + if (defaultValue.GetType().Equals(typeof(bool))) + { + defaultValue = ((bool)defaultValue) ? 1 : 0; + } + + return base.Default(defaultValue); + } +} diff --git a/src/Migrator/Providers/Impl/Oracle/Data/Interfaces/IOracleSystemDataLoader.cs b/src/Migrator/Providers/Impl/Oracle/Data/Interfaces/IOracleSystemDataLoader.cs new file mode 100644 index 00000000..80d53dff --- /dev/null +++ b/src/Migrator/Providers/Impl/Oracle/Data/Interfaces/IOracleSystemDataLoader.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using DotNetProjects.Migrator.Providers.Impl.Oracle.Models; +using DotNetProjects.Migrator.Providers.Models; +using DotNetProjects.Migrator.Providers.Models.Indexes; + +namespace DotNetProjects.Migrator.Providers.Impl.Oracle.Data.Interfaces; + +public interface IOracleSystemDataLoader +{ + /// + /// Gets s for given table name. + /// + /// + /// + List GetForeignKeyConstraintItems(string tableName); + + /// + /// Gets the USER_TAB_IDENTITY_COLS records for the given table name. + /// + /// + /// + List GetUserTabIdentityCols(string tableName); + + /// + /// Gets the primary key items from user_constraints and user_cons_columns + /// + /// + /// + List GetPrimaryKeyItems(string tableName); + + /// + /// Gets index items from USER_INDEXES, USER_IND_COLUMNS and USER_CONSTRAINTS + /// + /// + /// + List GetIndexItems(string tableName); +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/Oracle/Data/OracleSystemDataLoader.cs b/src/Migrator/Providers/Impl/Oracle/Data/OracleSystemDataLoader.cs new file mode 100644 index 00000000..287389a9 --- /dev/null +++ b/src/Migrator/Providers/Impl/Oracle/Data/OracleSystemDataLoader.cs @@ -0,0 +1,198 @@ +using System.Collections.Generic; +using System.Text; +using DotNetProjects.Migrator.Providers.Impl.Oracle.Data.Interfaces; +using DotNetProjects.Migrator.Providers.Impl.Oracle.Interfaces; +using DotNetProjects.Migrator.Providers.Impl.Oracle.Models; +using DotNetProjects.Migrator.Providers.Models; +using DotNetProjects.Migrator.Providers.Models.Indexes; + +namespace DotNetProjects.Migrator.Providers.Impl.Oracle.Data; + +public class OracleSystemDataLoader(IOracleTransformationProvider oracleTransformationProvider) : IOracleSystemDataLoader +{ + private readonly IOracleTransformationProvider _oracleTransformationProvider = oracleTransformationProvider; + + public List GetUserTabIdentityCols(string tableName) + { + List userTabIdentityCols = []; + + var tableNameQuoted = _oracleTransformationProvider.QuoteTableNameIfRequired(tableName); + + var sql = $"SELECT TABLE_NAME, COLUMN_NAME, GENERATION_TYPE, SEQUENCE_NAME FROM USER_TAB_IDENTITY_COLS WHERE TABLE_NAME = '{tableNameQuoted.ToUpperInvariant()}'"; + + using var cmd = _oracleTransformationProvider.CreateCommand(); + using var reader = _oracleTransformationProvider.ExecuteQuery(cmd, sql); + + while (reader.Read()) + { + var tableNameOrdinal = reader.GetOrdinal("TABLE_NAME"); + var columnNameOrdinal = reader.GetOrdinal("COLUMN_NAME"); + var generationTypeOrdinal = reader.GetOrdinal("GENERATION_TYPE"); + var sequenceNameOrdinal = reader.GetOrdinal("SEQUENCE_NAME"); + + var userTablIdentityColsItem = new UserTabIdentityCols + { + ColumnName = reader.GetString(columnNameOrdinal), + GenerationType = reader.GetString(generationTypeOrdinal), + SequenceName = reader.GetString(sequenceNameOrdinal), + TableName = reader.GetString(tableNameOrdinal), + }; + + userTabIdentityCols.Add(userTablIdentityColsItem); + } + + return userTabIdentityCols; + } + + public List GetForeignKeyConstraintItems(string tableName) + { + var tableNameQuoted = _oracleTransformationProvider.QuoteTableNameIfRequired(tableName); + + var sb = new StringBuilder(); + sb.AppendLine("SELECT"); + sb.AppendLine(" a.OWNER AS TABLE_SCHEMA,"); + sb.AppendLine(" c.CONSTRAINT_NAME AS FK_KEY,"); + sb.AppendLine(" a.TABLE_NAME AS CHILD_TABLE,"); + sb.AppendLine(" a.COLUMN_NAME AS CHILD_COLUMN,"); + sb.AppendLine(" c_pk.TABLE_NAME AS PARENT_TABLE,"); + sb.AppendLine(" col_pk.COLUMN_NAME AS PARENT_COLUMN"); + sb.AppendLine("FROM "); + sb.AppendLine(" USER_CONS_COLUMNS a "); + sb.AppendLine("JOIN USER_CONSTRAINTS c"); + sb.AppendLine(" ON a.owner = c.owner AND a.CONSTRAINT_NAME = c.CONSTRAINT_NAME"); + sb.AppendLine("JOIN USER_CONSTRAINTS c_pk"); + sb.AppendLine(" ON c.R_OWNER = c_pk.OWNER AND c.R_CONSTRAINT_NAME = c_pk.CONSTRAINT_NAME"); + sb.AppendLine("JOIN USER_CONS_COLUMNS col_pk"); + sb.AppendLine(" ON c_pk.CONSTRAINT_NAME = col_pk.CONSTRAINT_NAME AND c_pk.OWNER = col_pk.OWNER AND a.POSITION = col_pk.POSITION"); + sb.AppendLine($"WHERE LOWER(a.TABLE_NAME) = LOWER('{tableNameQuoted}') AND c.CONSTRAINT_TYPE = 'R'"); + sb.AppendLine("ORDER BY a.POSITION"); + + var sql = sb.ToString(); + List foreignKeyConstraintItems = []; + + using var cmd = _oracleTransformationProvider.CreateCommand(); + using var reader = _oracleTransformationProvider.ExecuteQuery(cmd, sql); + + while (reader.Read()) + { + var constraintItem = new ForeignKeyConstraintItem + { + SchemaName = reader.GetString(reader.GetOrdinal("TABLE_SCHEMA")), + ForeignKeyName = reader.GetString(reader.GetOrdinal("FK_KEY")), + ChildTableName = reader.GetString(reader.GetOrdinal("CHILD_TABLE")), + ChildColumnName = reader.GetString(reader.GetOrdinal("CHILD_COLUMN")), + ParentTableName = reader.GetString(reader.GetOrdinal("PARENT_TABLE")), + ParentColumnName = reader.GetString(reader.GetOrdinal("PARENT_COLUMN")) + }; + + foreignKeyConstraintItems.Add(constraintItem); + } + + return foreignKeyConstraintItems; + } + + public List GetPrimaryKeyItems(string tableName) + { + var tableNameQuoted = _oracleTransformationProvider.QuoteTableNameIfRequired(tableName); + + var sql = $@" + SELECT + ucc.TABLE_NAME, + ucc.COLUMN_NAME, + ucc.POSITION, + uc.CONSTRAINT_NAME, + uc.STATUS + FROM + USER_CONSTRAINTS uc + JOIN + USER_CONS_COLUMNS ucc + ON uc.CONSTRAINT_NAME = ucc.CONSTRAINT_NAME + WHERE + uc.CONSTRAINT_TYPE = 'P' + AND ucc.TABLE_NAME = '{tableNameQuoted.ToUpperInvariant()}' + ORDER BY + ucc.POSITION + "; + + List primaryKeyItems = []; + + using var cmd = _oracleTransformationProvider.CreateCommand(); + using var reader = _oracleTransformationProvider.ExecuteQuery(cmd, sql); + + while (reader.Read()) + { + var constraintItem = new PrimaryKeyItem + { + TableName = reader.GetString(reader.GetOrdinal("TABLE_NAME")), + ColumnName = reader.GetString(reader.GetOrdinal("COLUMN_NAME")), + Position = reader.GetInt32(reader.GetOrdinal("POSITION")), + ConstraintName = reader.GetString(reader.GetOrdinal("CONSTRAINT_NAME")), + Status = reader.GetString(reader.GetOrdinal("STATUS")) + }; + + primaryKeyItems.Add(constraintItem); + } + + return primaryKeyItems; + } + + public List GetIndexItems(string tableName) + { + var tableNameQuoted = _oracleTransformationProvider.QuoteTableNameIfRequired(tableName); + + var sql = @$" + SELECT + i.table_name, + i.index_name, + i.uniqueness, + ic.column_position, + ic.column_name, + CASE WHEN c.constraint_type = 'P' THEN 'YES' ELSE 'NO' END AS is_primary_key, + CASE WHEN c.constraint_type = 'U' THEN 'YES' ELSE 'NO' END AS is_unique_key + FROM + user_indexes i + JOIN + user_ind_columns ic ON i.index_name = ic.index_name AND + i.table_name = ic.table_name + LEFT JOIN + user_constraints c ON i.index_name = c.index_name AND + i.table_name = c.table_name + WHERE + UPPER(i.table_name) = '{tableNameQuoted.ToUpperInvariant()}' + -- AND + -- i.index_type = 'NORMAL' + ORDER BY + i.table_name, i.index_name, ic.column_position"; + + List indexItems = []; + + using var cmd = _oracleTransformationProvider.CreateCommand(); + using var reader = _oracleTransformationProvider.ExecuteQuery(cmd, sql); + + while (reader.Read()) + { + var tableNameOrdinal = reader.GetOrdinal("table_name"); + var indexNameOrdinal = reader.GetOrdinal("index_name"); + var uniquenessOrdinal = reader.GetOrdinal("uniqueness"); + var columnPositionOrdinal = reader.GetOrdinal("column_position"); + var columnNameOrdinal = reader.GetOrdinal("column_name"); + var isPrimaryKeyOrdinal = reader.GetOrdinal("is_primary_key"); + var isUniqueConstraintOrdinal = reader.GetOrdinal("is_unique_key"); + + var indexItem = new IndexItem + { + ColumnName = reader.GetString(columnNameOrdinal), + ColumnOrder = reader.GetInt32(columnPositionOrdinal), + Name = reader.GetString(indexNameOrdinal), + PrimaryKey = reader.GetString(isPrimaryKeyOrdinal) == "YES", + TableName = reader.GetString(tableNameOrdinal), + Unique = reader.GetString(uniquenessOrdinal) == "UNIQUE", + UniqueConstraint = reader.GetString(isUniqueConstraintOrdinal) == "YES" + }; + + indexItems.Add(indexItem); + } + + return indexItems; + } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/Oracle/Interfaces/IOracleTransformationProvider.cs b/src/Migrator/Providers/Impl/Oracle/Interfaces/IOracleTransformationProvider.cs new file mode 100644 index 00000000..6b2aa91e --- /dev/null +++ b/src/Migrator/Providers/Impl/Oracle/Interfaces/IOracleTransformationProvider.cs @@ -0,0 +1,7 @@ +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.Oracle.Interfaces; + +public interface IOracleTransformationProvider : ITransformationProvider +{ +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/Oracle/Models/AllTablIdentityCols.cs b/src/Migrator/Providers/Impl/Oracle/Models/AllTablIdentityCols.cs new file mode 100644 index 00000000..b419a04d --- /dev/null +++ b/src/Migrator/Providers/Impl/Oracle/Models/AllTablIdentityCols.cs @@ -0,0 +1,27 @@ +namespace DotNetProjects.Migrator.Providers.Impl.Oracle.Models; + +/// +/// Represents USER_TAB_IDENTITY_COLS partly +/// +public class UserTabIdentityCols +{ + /// + /// Gets or sets the name of the identity column. Column: COLUMN_NAME + /// + public string ColumnName { get; set; } + + /// + /// Gets or sets the generation type of the identity column. Possible values are ALWAYS or BY DEFAULT. Column: GENERATION_TYPE + /// + public string GenerationType { get; set; } + + /// + /// Gets or sets the name of the sequence associated with the identity column. Column: SEQUENCE_NAME + /// + public string SequenceName { get; set; } + + /// + /// Gets or sets the name of the table. Column: TABLE_NAME + /// + public string TableName { get; set; } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/Oracle/Models/PrimaryKeyItem.cs b/src/Migrator/Providers/Impl/Oracle/Models/PrimaryKeyItem.cs new file mode 100644 index 00000000..754a1d62 --- /dev/null +++ b/src/Migrator/Providers/Impl/Oracle/Models/PrimaryKeyItem.cs @@ -0,0 +1,29 @@ +namespace DotNetProjects.Migrator.Providers.Impl.Oracle.Models; + +public class PrimaryKeyItem +{ + /// + /// Gets or sets the table name USER_CONS_COLUMNS.TABLE_NAME + /// + public string TableName { get; set; } + + /// + /// Gets or sets the column name USER_CONS_COLUMNS.COLUMN_NAME + /// + public string ColumnName { get; set; } + + /// + /// Gets or sets USER_CONS_COLUMNS.POSITION + /// + public int Position { get; set; } + + /// + /// Gets or sets USER_CONSTRAINTS.STATUS Enforcement status of the constraint: ENABLED, DISABLED + /// + public string Status { get; set; } + + /// + /// Gets or sets the USER_CONSTRAINTS.CONSTRAINT_NAME + /// + public string ConstraintName { get; set; } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/Oracle/Models/UserTabColumns.cs b/src/Migrator/Providers/Impl/Oracle/Models/UserTabColumns.cs new file mode 100644 index 00000000..413a3d79 --- /dev/null +++ b/src/Migrator/Providers/Impl/Oracle/Models/UserTabColumns.cs @@ -0,0 +1,7 @@ +namespace DotNetProjects.Migrator.Providers.Impl.Oracle.Models; + +public class UserTabColumns +{ + public string ColumnName { get; set; } + public string DataDefault { get; set; } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/Oracle/MsOracleDialect.cs b/src/Migrator/Providers/Impl/Oracle/MsOracleDialect.cs new file mode 100644 index 00000000..4aa8f94c --- /dev/null +++ b/src/Migrator/Providers/Impl/Oracle/MsOracleDialect.cs @@ -0,0 +1,19 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.Oracle; + +public class MsOracleDialect : OracleDialect +{ + public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) + { + return new MsOracleTransformationProvider(dialect, connectionString, defaultSchema, scope, providerName); + } + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, + string defaultSchema, + string scope, string providerName) + { + return new MsOracleTransformationProvider(dialect, connection, defaultSchema, scope, providerName); + } +} diff --git a/src/Migrator/Providers/Impl/Oracle/MsOracleTransformationProvider.cs b/src/Migrator/Providers/Impl/Oracle/MsOracleTransformationProvider.cs new file mode 100644 index 00000000..04ff1094 --- /dev/null +++ b/src/Migrator/Providers/Impl/Oracle/MsOracleTransformationProvider.cs @@ -0,0 +1,30 @@ +using System.Data; + +namespace DotNetProjects.Migrator.Providers.Impl.Oracle; + +public class MsOracleTransformationProvider : OracleTransformationProvider +{ + public MsOracleTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) + : base(dialect, connectionString, defaultSchema, scope, providerName) + { + + } + + public MsOracleTransformationProvider(Dialect dialect, IDbConnection connection, string defaultSchema, string scope, string providerName) + : base(dialect, connection, defaultSchema, scope, providerName) + { + } + + protected override void CreateConnection(string providerName) + { + if (string.IsNullOrEmpty(providerName)) + { + providerName = "System.Data.OracleClient"; + } + + var fac = DbProviderFactoriesHelper.GetFactory(providerName, null, null); + _connection = fac.CreateConnection(); // new OracleConnection(); + _connection.ConnectionString = _connectionString; + _connection.Open(); + } +} diff --git a/src/Migrator/Providers/Impl/Oracle/OracleColumnPropertiesMapper.cs b/src/Migrator/Providers/Impl/Oracle/OracleColumnPropertiesMapper.cs new file mode 100644 index 00000000..56f672d4 --- /dev/null +++ b/src/Migrator/Providers/Impl/Oracle/OracleColumnPropertiesMapper.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.Oracle; + +public class OracleColumnPropertiesMapper : ColumnPropertiesMapper +{ + public OracleColumnPropertiesMapper(Dialect dialect, string typeString) : base(dialect, typeString) + { + } + + public override void MapColumnProperties(Column column) + { + Name = column.Name; + + _Indexed = PropertySelected(column.ColumnProperty, ColumnProperty.Indexed); + + var vals = new List(); + + AddName(vals); + + AddType(vals); + + AddIdentity(column, vals); + + AddUnsigned(column, vals); + + AddPrimaryKey(column, vals); + + AddIdentityAgain(column, vals); + + AddUnique(column, vals); + + AddForeignKey(column, vals); + + AddDefaultValue(column, vals); + + // null / not-null comes last on Oracle - otherwise if use Null/Not-null + default, bad things happen + // (http://geekswithblogs.net/faizanahmad/archive/2009/08/07/add-new-columnfield-in-oracle-db-table---ora.aspx) + + AddNotNull(column, vals); + + AddNull(column, vals); + + _ColumnSql = string.Join(" ", vals.ToArray()); + } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/Oracle/OracleDialect.cs b/src/Migrator/Providers/Impl/Oracle/OracleDialect.cs new file mode 100644 index 00000000..4d5a2b32 --- /dev/null +++ b/src/Migrator/Providers/Impl/Oracle/OracleDialect.cs @@ -0,0 +1,157 @@ +using System; +using System.Data; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.Oracle; + +public class OracleDialect : Dialect +{ + public OracleDialect() + { + RegisterColumnType(DbType.AnsiStringFixedLength, "CHAR(255)"); + RegisterColumnType(DbType.AnsiStringFixedLength, 2000, "CHAR($l)"); + RegisterColumnType(DbType.AnsiString, "VARCHAR2(255)"); + RegisterColumnType(DbType.AnsiString, 2000, "VARCHAR2($l)"); + RegisterColumnType(DbType.AnsiString, 2147483647, "CLOB"); // should use the IType.ClobType + RegisterColumnType(DbType.Binary, "RAW(2000)"); + RegisterColumnType(DbType.Binary, 2000, "RAW($l)"); + RegisterColumnType(DbType.Binary, 2147483647, "BLOB"); + + // 23ai now has a native boolean data type but for backwards compatibility we keep using NUMBER(1,0) + RegisterColumnType(DbType.Boolean, "NUMBER(1,0)"); + RegisterColumnType(DbType.Byte, "NUMBER(3,0)"); + RegisterColumnType(DbType.Currency, "NUMBER(19,1)"); + RegisterColumnType(DbType.Date, "DATE"); + RegisterColumnType(DbType.DateTime, "TIMESTAMP(4)"); + RegisterColumnType(DbType.DateTime2, "TIMESTAMP(7)"); + RegisterColumnType(DbType.DateTimeOffset, "TIMESTAMP(4)"); + RegisterColumnType(DbType.Decimal, "NUMBER(19,5)"); + RegisterColumnType(DbType.Decimal, 19, "NUMBER(19, $l)"); + RegisterColumnTypeWithParameters(DbType.Decimal, "NUMBER({precision}, {scale})"); + // having problems with both ODP and OracleClient from MS not being able + // to read values out of a field that is DOUBLE PRECISION + RegisterColumnType(DbType.Double, "DOUBLE PRECISION"); //"FLOAT(53)" ); + //RegisterColumnType(DbType.Guid, "CHAR(38)"); + RegisterColumnType(DbType.Int16, "NUMBER(5,0)"); + RegisterColumnType(DbType.Int32, "NUMBER(10,0)"); + RegisterColumnType(DbType.Int64, "NUMBER(20,0)"); + RegisterColumnType(DbType.UInt16, "NUMBER(5,0)"); + RegisterColumnType(DbType.UInt32, "NUMBER(10,0)"); + RegisterColumnType(DbType.UInt64, "NUMBER(20,0)"); + RegisterColumnType(DbType.Single, "FLOAT(24)"); + RegisterColumnType(DbType.Double, "BINARY_DOUBLE"); + RegisterColumnType(DbType.StringFixedLength, "NCHAR(255)"); + RegisterColumnType(DbType.StringFixedLength, 2000, "NCHAR($l)"); + RegisterColumnType(DbType.String, "NVARCHAR2(255)"); + RegisterColumnType(DbType.String, 2000, "NVARCHAR2($l)"); + //RegisterColumnType(DbType.String, 1073741823, "NCLOB"); + RegisterColumnType(DbType.String, int.MaxValue, "NCLOB"); + RegisterColumnType(DbType.Time, "DATE"); + RegisterColumnType(DbType.Guid, "RAW(16)"); + RegisterColumnType(MigratorDbType.Interval, "interval day (9) to second (9)"); + + RegisterProperty(ColumnProperty.Identity, "GENERATED ALWAYS AS IDENTITY"); + + // the original Migrator.Net code had this, but it's a bad idea - when + // apply a "null" migration to a "not-null" field, it just leaves it as "not-null" and it silently fails + // because Oracle doesn't consider ALTER TABLE
MODIFY (column ) as being a request to make the field null. + + //RegisterProperty(ColumnProperty.Null, String.Empty); + + AddReservedWords("ACCOUNT", "ACTIVATE", "ADMIN", "ADVISE", "AFTER", "ALL_ROWS", "ALLOCATE", "ANALYZE", "ARCHIVE", "ARCHIVELOG", "ARRAY", "AT", "AUTHENTICATED", "AUTHORIZATION", "AUTOEXTEND", "AUTOMATIC", "BACKUP", "BECOME", "BEFORE", "BEGIN", "BFILE", "BITMAP", "BLOB", "BLOCK", "BODY", "CACHE", "CACHE_INSTANCES", "CANCEL", "CASCADE", "CAST", "CFILE", "CHAINED", "CHANGE", "CHAR_CS", "CHARACTER", "CHECKPOINT", "CHOOSE", "CHUNK", "CLEAR", "CLOB", "CLONE", "CLOSE", "CLOSE_CACHED_OPEN_CURSORS", "COALESCE", "COLUMNS", "COMMIT", "COMMITTED", "COMPATIBILITY", "COMPILE", "COMPLETE", "COMPOSITE_LIMIT", "COMMENT", "COMPUTE", "CONNECT_TIME", "CONSTRAINT", "CONSTRAINTS", "CONTENTS", "CONTINUE", "CONTROLFILE", "CONVERT", "COST", "CPU_PER_CALL", "CPU_PER_SESSION", "CURRENT_SCHEMA", "CURREN_USER", "CURSOR", "CYCLE", "DANGLING", "DATABASE", "DATAFILE", "DATAFILES", "DATAOBJNO", "DBA", "DBHIGH", "DBLOW", "DBMAC", "DEALLOCATE", "DEBUG", "DEC", "DECLARE", "DEFERRABLE", "DEFERRED", "DEGREE", "DEREF", "DIRECTORY", "DISABLE", "DISCONNECT", "DISMOUNT", "DISTRIBUTED", "DML", "DOUBLE", "DUMP", "EACH", "ENABLE", "END", "ENFORCE", "ENTRY", "ESCAPE", "EXCEPT", "EXCEPTIONS", "EXCHANGE", "EXCLUDING", "EXECUTE", "EXPIRE", "EXPLAIN", "EXTENT", "EXTENTS", "EXTERNALLY", "FAILED_LOGIN_ATTEMPTS", "FALSE", "FAST", "FIRST_ROWS", "FLAGGER", "FLOB", "FLUSH", "FORCE", "FOREIGN", "FREELIST", "FREELISTS", "FULL", "FUNCTION", "GLOBAL", "GLOBALLY", "GLOBAL_NAME", "GROUPS", "HASH", "HASHKEYS", "HEADER", "HEAP", "IDGENERATORS", "IDLE_TIME", "IF", "INCLUDING", "INCREMENT", "INDEXED", "INDEXES", "INDICATOR", "IND_PARTITION", "INITIALLY", "INITRANS", "INSTANCE", "INSTANCES", "INSTEAD", "INT", "INTERMEDIATE", "ISOLATION", "ISOLATION_LEVEL", "KEEP", "KEY", "KILL", "LABEL", "LAYER", "LESS", "LIBRARY", "LIMIT", "LINK", "LIST", "LOB", "LOCAL", "LOCKED", "LOG", "LOGFILE", "LOGGING", "LOGICAL_READS_PER_CALL", "LOGICAL_READS_PER_SESSION", "MANAGE", "MASTER", "MAX", "MAXARCHLOGS", "MAXDATAFILES", "MAXINSTANCES", "MAXLOGFILES", "MAXLOGHISTORY", "MAXLOGMEMBERS", "MAXSIZE", "MAXTRANS", "MAXVALUE", "MIN", "MEMBER", "MINIMUM", "MINEXTENTS", "MINVALUE", "MLS_LABEL_FORMAT", "MOUNT", "MOVE", "MTS_DISPATCHERS", "MULTISET", "NATIONAL", "NCHAR", "NCHAR_CS", "NCLOB", "NEEDED", "NESTED", "NETWORK", "NEW", "NEXT", "NOARCHIVELOG", "NOCACHE", "NOCYCLE", "NOFORCE", "NOLOGGING", "NOMAXVALUE", "NOMINVALUE", "NONE", "NOORDER", "NOOVERRIDE", "NOPARALLEL", "NOPARALLEL", "NOREVERSE", "NORMAL", "NOSORT", "NOTHING", "NUMBER", "NUMERIC", "NVARCHAR2", "OBJECT", "OBJNO", "OBJNO_REUSE", "OFF", "OID", "OIDINDEX", "OLD", "ONLY", "OPCODE", "OPEN", "OPTIMAL", "OPTIMIZER_GOAL", "ORGANIZATION", "OSLABEL", "OVERFLOW", "OWN", "ORDER", "PACKAGE", "PARALLEL", "PARTITION", "PASSWORD", "PASSWORD_GRACE_TIME", "PASSWORD_LIFE_TIME", "PASSWORD_LOCK_TIME", "PASSWORD_REUSE_MAX", "PASSWORD_REUSE_TIME", "PASSWORD_VERIFY_FUNCTION", "PCTINCREASE", "PCTTHRESHOLD", "PCTUSED", "PCTVERSION", "PERCENT", "PERMANENT", "PLAN", "PLSQL_DEBUG", "POST_TRANSACTION", "PRECISION", "PRESERVE", "PRIMARY", "PRIVATE", "PRIVATE_SGA", "PRIVILEGE", "PROCEDURE", "PROFILE", "PURGE", "QUEUE", "QUOTA", "RANGE", "RBA", "READ", "READUP", "REAL", "REBUILD", "RECOVER", "RECOVERABLE", "RECOVERY", "REF", "REFERENCES", "REFERENCING", "REFRESH", "REPLACE", "RESET", "RESETLOGS", "RESIZE", "RESTRICTED", "RETURN", "RETURNING", "REUSE", "REVERSE", "ROLE", "ROLES", "ROLLBACK", "RULE", "SAMPLE", "SAVEPOINT", "SB4", "SCAN_INSTANCES", "SCHEMA", "SCN", "SCOPE", "SD_ALL", "SD_INHIBIT", "SD_SHOW", "SEGMENT", "SEG_BLOCK", "SEG_FILE", "SEQUENCE", "SERIALIZABLE", "SESSION_CACHED_CURSORS", "SESSIONS_PER_USER", "SIZE", "SHARED", "SHARED_POOL", "SHRINK", "SKIP", "SKIP_UNUSABLE_INDEXES", "SNAPSHOT", "SOME", "SORT", "SPECIFICATION", "SPLIT", "SQL_TRACE", "STANDBY", "STATEMENT_ID", "STATISTICS", "STOP", "STORAGE", "STORE", "STRUCTURE", "SWITCH", "SYS_OP_ENFORCE_NOT_NULL$", "SYS_OP_NTCIMG$", "SYSDBA", "SYSOPER", "SYSTEM", "TABLES", "TABLESPACE", "TABLESPACE_NO", "TABNO", "TEMPORARY", "THAN", "THE", "THREAD", "TIMESTAMP", "TIME", "TOPLEVEL", "TRACE", "TRACING", "TRANSACTION", "TRANSITIONAL", "TRIGGERS", "TRUE", "TRUNCATE", "TX", "TYPE", "UB2", "UBA", "UNARCHIVED", "UNDO", "UNLIMITED", "UNLOCK", "UNRECOVERABLE", "UNTIL", "UNUSABLE", "UNUSED", "UPDATABLE", "USAGE", "USE", "USING", "VALIDATION", "VALUE", "VALUES", "VARYING", "VIEW", "WHEN", "WITHOUT", "WORK", "WRITE", "WRITEDOWN", "WRITEUP", "XID", "YEAR", "ZONE"); + } + + // in Oracle, this: ALTER TABLE EXTERNALSYSTEMREFERENCES MODIFY (TestScriptId RAW(16)) will no make the column nullable, it just leaves it at it's current null/not-null state + + + public override bool ColumnNameNeedsQuote => false; + public override bool ConstraintNameNeedsQuote => false; + public override bool IdentityNeedsType => false; + public override bool NeedsNullForNullableWhenAlteringTable => true; + public override bool TableNameNeedsQuote => false; + public override int MaxFieldNameLength => 30; + public override int MaxKeyLength => 767; + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) + { + return new OracleTransformationProvider(dialect, connectionString, defaultSchema, scope, providerName); + } + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, + string defaultSchema, + string scope, string providerName) + { + return new OracleTransformationProvider(dialect, connection, defaultSchema, scope, providerName); + } + + public override ColumnPropertiesMapper GetColumnMapper(Column column) + { + var typeString = column.Size > 0 ? GetTypeName(column.Type, column.Size) : GetTypeName(column.Type); + + if (column.Precision.HasValue || column.Scale.HasValue) + { + typeString = GetTypeNameParametrized(column.Type, column.Size, column.Precision ?? 0, column.Scale ?? 0); + } + + + return new OracleColumnPropertiesMapper(this, typeString); + } + + public override string Default(object defaultValue) + { + if (defaultValue == null) + { + return string.Empty; + } + + if (defaultValue is bool booleanValue) + { + return string.Format("DEFAULT {0}", booleanValue ? "1" : "0"); + } + else if (defaultValue is Guid guid) + { + var bytes = guid.ToByteArray(); + + // Convert to big-endian format in Oracle + var oracleBytes = new byte[16]; + + // Reverse first 4 bytes + Array.Copy(bytes, 0, oracleBytes, 0, 4); + Array.Reverse(oracleBytes, 0, 4); + + // Reverse next 2 bytes + Array.Copy(bytes, 4, oracleBytes, 4, 2); + Array.Reverse(oracleBytes, 4, 2); + + // Reverse next 2 bytes + Array.Copy(bytes, 6, oracleBytes, 6, 2); + Array.Reverse(oracleBytes, 6, 2); + + // Copy remaining 8bytes + Array.Copy(bytes, 8, oracleBytes, 8, 8); + + // Convert to hex string + var hex = BitConverter.ToString(oracleBytes).Replace("-", ""); + + return $"DEFAULT HEXTORAW('{hex}')"; + } + else if (defaultValue is DateTime dateTime) + { + // We use 4 because we have no access data type and therefore no access to the real n in TIMESTAMP(n) in this method. Needs refactoring. + var dateTimeString = dateTime.ToString("yyyy-MM-dd HH:mm:ss.ffff"); + return $"DEFAULT TO_TIMESTAMP('{dateTimeString}', 'YYYY-MM-DD HH24:MI:SS.FF4')"; + } + else if (defaultValue is string stringValue) + { + stringValue = stringValue.Replace("'", "''"); + return $"DEFAULT '{stringValue}'"; + } + else if (defaultValue is byte[] byteArray) + { + var convertedString = BitConverter.ToString(byteArray).Replace("-", "").ToLower(); + return $"DEFAULT HEXTORAW('{convertedString}')"; + } + + return base.Default(defaultValue); + } +} diff --git a/src/Migrator/Providers/Impl/Oracle/OracleTransformationProvider.cs b/src/Migrator/Providers/Impl/Oracle/OracleTransformationProvider.cs new file mode 100644 index 00000000..6a5a35ed --- /dev/null +++ b/src/Migrator/Providers/Impl/Oracle/OracleTransformationProvider.cs @@ -0,0 +1,1073 @@ +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Framework.Models; +using DotNetProjects.Migrator.Providers.Impl.Oracle.Data; +using DotNetProjects.Migrator.Providers.Impl.Oracle.Data.Interfaces; +using DotNetProjects.Migrator.Providers.Impl.Oracle.Interfaces; +using DotNetProjects.Migrator.Providers.Impl.Oracle.Models; +using DotNetProjects.Migrator.Providers.Models.Indexes; +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using ForeignKeyConstraint = DotNetProjects.Migrator.Framework.ForeignKeyConstraint; +using Index = DotNetProjects.Migrator.Framework.Index; + +namespace DotNetProjects.Migrator.Providers.Impl.Oracle; + +public class OracleTransformationProvider : TransformationProvider, IOracleTransformationProvider +{ + private IOracleSystemDataLoader _oracleSystemDataLoader; + public const string TemporaryColumnName = "TEMPCOL"; + + public OracleTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) + : base(dialect, connectionString, defaultSchema, scope) + { + CreateConnection(providerName); + Initialize(); + } + + public OracleTransformationProvider(Dialect dialect, IDbConnection connection, string defaultSchema, string scope, string providerName) + : base(dialect, connection, defaultSchema, scope) + { + Initialize(); + } + + protected virtual void CreateConnection(string providerName) + { + if (string.IsNullOrEmpty(providerName)) + { + providerName = "Oracle.DataAccess.Client"; + } + + var fac = DbProviderFactoriesHelper.GetFactory(providerName, null, null); + _connection = fac.CreateConnection(); // new OracleConnection(); + _connection.ConnectionString = _connectionString; + _connection.Open(); + } + + public override void DropDatabases(string databaseName) + { + if (string.IsNullOrEmpty(databaseName)) + { + ExecuteNonQuery(string.Format("DROP DATABASE")); + } + } + + public override ForeignKeyConstraint[] GetForeignKeyConstraints(string table) + { + var constraints = new List(); + var foreignKeyConstraintItems = _oracleSystemDataLoader.GetForeignKeyConstraintItems(table); + + var schemaChildTableGroups = foreignKeyConstraintItems.GroupBy(x => new { x.SchemaName, x.ChildTableName }).Count(); + + if (schemaChildTableGroups > 1) + { + throw new MigrationException($"Duplicates found (grouping by schema name and child table name). Since we do not offer schemas in '{nameof(GetForeignKeyConstraints)}' at this moment in time we cannot filter your target schema. Your database use the same table name in different schemas."); + } + + var groups = foreignKeyConstraintItems.GroupBy(x => x.ForeignKeyName); + + foreach (var group in groups) + { + var first = group.First(); + + var foreignKeyConstraint = new ForeignKeyConstraint + { + Name = first.ForeignKeyName, + ParentTable = first.ParentTableName, + ParentColumns = [.. group.Select(x => x.ParentColumnName).Distinct()], + ChildTable = first.ChildTableName, + ChildColumns = [.. group.Select(x => x.ChildColumnName).Distinct()] + }; + + constraints.Add(foreignKeyConstraint); + } + + return [.. constraints]; + } + + public override void AddForeignKey(string name, string primaryTable, string[] primaryColumns, string refTable, + string[] refColumns, ForeignKeyConstraintType constraint) + { + GuardAgainstMaximumIdentifierLengthForOracle(name); + + primaryTable = QuoteTableNameIfRequired(primaryTable); + refTable = QuoteTableNameIfRequired(refTable); + var primaryColumnsSql = string.Join(",", primaryColumns.Select(col => QuoteColumnNameIfRequired(col)).ToArray()); + var refColumnsSql = string.Join(",", refColumns.Select(col => QuoteColumnNameIfRequired(col)).ToArray()); + + ExecuteNonQuery(string.Format("ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4})", primaryTable, name, primaryColumnsSql, refTable, refColumnsSql)); + } + + public override string AddIndex(string table, Index index) + { + ValidateIndex(tableName: table, index: index); + var hasFilterItems = index.FilterItems != null && index.FilterItems.Count > 0; + + // Oracle does not support included columns and clustered indexes. We ignore the values given in the properties SILENTLY for backwards compatibility. + + if (index.Unique && hasFilterItems) + { + throw new MigrationException($"You cannot use unique together with functional expressions in Oracle ({nameof(FilterItem)})."); + } + + var name = QuoteConstraintNameIfRequired(index.Name); + table = QuoteTableNameIfRequired(table); + + List singleFilterStrings = []; + + + if (hasFilterItems) + { + // In Oracle functional expressions replace the normal columns so we need to remove them + if (index.KeyColumns != null && index.KeyColumns.Length > 0) + { + var keyColumnsList = index.KeyColumns.ToList(); + + for (var i = keyColumnsList.Count - 1; i >= 0; i--) + { + if (index.FilterItems.Any(x => keyColumnsList[i].Equals(x.ColumnName, StringComparison.OrdinalIgnoreCase))) + { + keyColumnsList.RemoveAt(i); + } + } + + index.KeyColumns = keyColumnsList.ToArray(); + } + + foreach (var filterItem in index.FilterItems) + { + var comparisonString = _dialect.GetComparisonStringByFilterType(filterItem.Filter); + + var filterColumnQuoted = QuoteColumnNameIfRequired(filterItem.ColumnName); + string value = null; + + value = filterItem.Value switch + { + bool booleanValue => booleanValue ? "TRUE" : "FALSE", + string stringValue => $"'{stringValue}'", + byte or short or int or long => Convert.ToInt64(filterItem.Value).ToString(), + sbyte or ushort or uint or ulong => Convert.ToUInt64(filterItem.Value).ToString(), + _ => throw new NotImplementedException($"Given type in '{nameof(FilterItem)}' is not implemented. Please file an issue."), + }; + + var singleFilterString = $"CASE WHEN {filterColumnQuoted} {comparisonString} {value} THEN {filterColumnQuoted} ELSE NULL END"; + + singleFilterStrings.Add(singleFilterString); + } + } + + var mixedColumnNamesAndFilters = QuoteColumnNamesIfRequired(index.KeyColumns).ToList(); + mixedColumnNamesAndFilters.AddRange(singleFilterStrings); + var columnNamesAndFiltersString = $"({string.Join(", ", mixedColumnNamesAndFilters)})"; + + var uniqueString = index.Unique ? "UNIQUE" : null; + + List list = []; + list.Add("CREATE"); + list.Add(uniqueString); + list.Add("INDEX"); + list.Add(name); + list.Add("ON"); + list.Add(table); + list.Add(columnNamesAndFiltersString); + + list = [.. list.Where(x => !string.IsNullOrWhiteSpace(x))]; + + var sql = string.Join(" ", list); + + ExecuteNonQuery(sql); + + return sql; + } + + private void GuardAgainstMaximumIdentifierLengthForOracle(string name) + { + var utf8Bytes = Encoding.UTF8.GetBytes(name); + + if (utf8Bytes.Length > 128) + { + throw new MigrationException($"The name '{name}' is {utf8Bytes.Length} bytes in length, but maximum length for Oracle identifiers is 128 bytes for Oracle versions 12.1+."); + } + } + + protected override string GetPrimaryKeyname(string tableName) + { + return tableName.Length > 27 ? "PK_" + tableName.Substring(0, 27) : "PK_" + tableName; + } + + public override void ChangeColumn(string table, Column column) + { + var existingColumn = GetColumnByName(table, column.Name); + + if (column.Type == DbType.String) + { + RenameColumn(table, column.Name, TemporaryColumnName); + + // check if this is not-null + var isNotNull = (column.ColumnProperty & ColumnProperty.NotNull) == ColumnProperty.NotNull; + + // remove the not-null option + column.ColumnProperty = (column.ColumnProperty & ~ColumnProperty.NotNull); + + AddColumn(table, column); + CopyDataFromOneColumnToAnother(table, TemporaryColumnName, column.Name); + RemoveColumn(table, TemporaryColumnName); + //RenameColumn(table, TemporaryColumnName, column.Name); + + var columnName = QuoteColumnNameIfRequired(column.Name); + + // now set the column to not-null + if (isNotNull) + { + using var cmd = CreateCommand(); + ExecuteQuery(cmd, string.Format("ALTER TABLE {0} MODIFY ({1} NOT NULL)", table, columnName)); + } + } + else + { + if (((existingColumn.ColumnProperty & ColumnProperty.NotNull) == ColumnProperty.NotNull) + && ((column.ColumnProperty & ColumnProperty.NotNull) == ColumnProperty.NotNull)) + { + // was not null, and is being change to not-null - drop the not-null all together + column.ColumnProperty = column.ColumnProperty & ~ColumnProperty.NotNull; + } + else if + (((existingColumn.ColumnProperty & ColumnProperty.Null) == ColumnProperty.Null) + && ((column.ColumnProperty & ColumnProperty.Null) == ColumnProperty.Null)) + { + // was null, and is being changed to null - drop the null all together + column.ColumnProperty = column.ColumnProperty & ~ColumnProperty.Null; + } + + var mapper = _dialect.GetAndMapColumnProperties(column); + + ChangeColumn(table, mapper.ColumnSql); + } + } + + private void CopyDataFromOneColumnToAnother(string table, string fromColumn, string toColumn) + { + table = QuoteTableNameIfRequired(table); + fromColumn = QuoteColumnNameIfRequired(fromColumn); + toColumn = QuoteColumnNameIfRequired(toColumn); + + ExecuteNonQuery(string.Format("UPDATE {0} SET {1} = {2}", table, toColumn, fromColumn)); + } + + public override void RenameTable(string oldName, string newName) + { + GuardAgainstMaximumIdentifierLengthForOracle(newName); + GuardAgainstExistingTableWithSameName(newName, oldName); + + oldName = QuoteTableNameIfRequired(oldName); + newName = QuoteTableNameIfRequired(newName); + + ExecuteNonQuery(string.Format("ALTER TABLE {0} RENAME TO {1}", oldName, newName)); + } + + private void GuardAgainstExistingTableWithSameName(string newName, string oldName) + { + if (TableExists(newName)) + { + throw new MigrationException(string.Format("Can not rename table \"{0}\" to \"{1}\", a table with that name already exists", oldName, newName)); + } + } + + public override void RenameColumn(string tableName, string oldColumnName, string newColumnName) + { + GuardAgainstMaximumIdentifierLengthForOracle(newColumnName); + GuardAgainstExistingColumnWithSameName(newColumnName, tableName); + + tableName = QuoteTableNameIfRequired(tableName); + oldColumnName = QuoteColumnNameIfRequired(oldColumnName); + newColumnName = QuoteColumnNameIfRequired(newColumnName); + + ExecuteNonQuery(string.Format("ALTER TABLE {0} RENAME COLUMN {1} TO {2}", tableName, oldColumnName, newColumnName)); + } + + private void GuardAgainstExistingColumnWithSameName(string newColumnName, string tableName) + { + if (ColumnExists(tableName, newColumnName)) + { + throw new MigrationException(string.Format("A column with the name \"{0}\" already exists in the table \"{1}\"", newColumnName, tableName)); + } + } + + public override void ChangeColumn(string table, string sqlColumn) + { + if (string.IsNullOrEmpty(table)) + { + throw new ArgumentNullException(nameof(table)); + } + + if (string.IsNullOrEmpty(table)) + { + throw new ArgumentNullException(nameof(sqlColumn)); + } + + table = QuoteTableNameIfRequired(table); + sqlColumn = QuoteColumnNameIfRequired(sqlColumn); + + ExecuteNonQuery(string.Format("ALTER TABLE {0} MODIFY {1}", table, sqlColumn)); + } + + public override void AddColumn(string table, string sqlColumn) + { + GuardAgainstMaximumIdentifierLengthForOracle(table); + table = QuoteTableNameIfRequired(table); + sqlColumn = QuoteColumnNameIfRequired(sqlColumn); + + ExecuteNonQuery(string.Format("ALTER TABLE {0} ADD {1}", table, sqlColumn)); + } + + public override string[] GetConstraints(string table) + { + var constraints = new List(); + using (var cmd = CreateCommand()) + using ( + var reader = + ExecuteQuery(cmd, + string.Format("SELECT constraint_name FROM user_constraints WHERE lower(table_name) = '{0}'", table.ToLower()))) + { + while (reader.Read()) + { + constraints.Add(reader.GetString(0)); + } + } + + return constraints.ToArray(); + } + + protected override string GetPrimaryKeyConstraintName(string table) + { + var constraints = new List(); + + using (var cmd = CreateCommand()) + using ( + var reader = + ExecuteQuery(cmd, + string.Format("SELECT constraint_name FROM user_constraints WHERE lower(table_name) = '{0}' and constraint_type = 'P'", table.ToLower()))) + { + while (reader.Read()) + { + constraints.Add(reader.GetString(0)); + } + } + + return constraints.FirstOrDefault(); + } + + public override bool ConstraintExists(string table, string name) + { + var sql = + string.Format( + "SELECT COUNT(constraint_name) FROM user_constraints WHERE lower(constraint_name) = '{0}' AND lower(table_name) = '{1}'", + name.ToLower(), table.ToLower()); + + Logger.Log(sql); + var scalar = ExecuteScalar(sql); + + return Convert.ToInt32(scalar) == 1; + } + + public override bool ColumnExists(string table, string column) + { + if (!TableExists(table)) + { + return false; + } + + var sql = + string.Format( + "SELECT COUNT(column_name) FROM user_tab_columns WHERE lower(table_name) = '{0}' AND lower(column_name) = '{1}'", + table.ToLower(), column.ToLower()); + Logger.Log(sql); + var scalar = ExecuteScalar(sql); + return Convert.ToInt32(scalar) == 1; + } + + public override bool TableExists(string table) + { + var sql = string.Format("SELECT COUNT(table_name) FROM user_tables WHERE lower(table_name) = '{0}'", table.ToLower()); + + if (_defaultSchema != null) + { + sql = string.Format("SELECT COUNT(table_name) FROM user_tables WHERE lower(owner) = '{0}' and lower(table_name) = '{1}'", _defaultSchema.ToLower(), table.ToLower()); + } + + Logger.Log(sql); + var count = ExecuteScalar(sql); + return Convert.ToInt32(count) == 1; + } + + public override bool ViewExists(string view) + { + var sql = string.Format("SELECT COUNT(view_name) FROM user_views WHERE lower(view_name) = '{0}'", view.ToLower()); + + if (_defaultSchema != null) + { + sql = string.Format("SELECT COUNT(view_name) FROM user_views WHERE lower(owner) = '{0}' and lower(view_name) = '{1}'", _defaultSchema.ToLower(), view.ToLower()); + } + + Logger.Log(sql); + var count = ExecuteScalar(sql); + return Convert.ToInt32(count) == 1; + } + + public override List GetDatabases() + { + throw new NotImplementedException(); + } + + public override string[] GetTables() + { + var tables = new List(); + + using (var cmd = CreateCommand()) + using (var reader = + ExecuteQuery(cmd, "SELECT table_name FROM user_tables")) + { + while (reader.Read()) + { + tables.Add(reader[0].ToString()); + } + } + + return tables.ToArray(); + } + + public override Column[] GetColumns(string table) + { + var timestampRegex = new Regex(@"(?<=^TIMESTAMP\s+')[^']+(?=')", RegexOptions.IgnoreCase); + var hexToRawRegex = new Regex(@"(?<=^HEXTORAW\s*\(')[^']+(?=')", RegexOptions.IgnoreCase); + var timestampBaseFormat = "yyyy-MM-dd HH:mm:ss"; + + var stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("SELECT"); + stringBuilder.AppendLine(" COLUMN_NAME,"); + stringBuilder.AppendLine(" NULLABLE,"); + stringBuilder.AppendLine(" DATA_DEFAULT,"); + stringBuilder.AppendLine(" DATA_TYPE,"); + stringBuilder.AppendLine(" DATA_LENGTH,"); + stringBuilder.AppendLine(" DATA_PRECISION,"); + stringBuilder.AppendLine(" DATA_SCALE,"); + stringBuilder.AppendLine(" CHAR_COL_DECL_LENGTH"); + stringBuilder.AppendLine($"FROM USER_TAB_COLUMNS WHERE LOWER(TABLE_NAME) = LOWER('{table}')"); + + var stringBuilder2 = new StringBuilder(); + stringBuilder2.AppendLine("SELECT x.column_name, x.data_default"); + stringBuilder2.AppendLine("FROM XMLTABLE("); + stringBuilder2.AppendLine(" '/ROWSET/ROW'"); + stringBuilder2.AppendLine(" PASSING DBMS_XMLGEN.GETXMLTYPE("); + stringBuilder2.AppendLine($" 'SELECT column_name, data_default FROM user_tab_columns WHERE table_name = ''{table.ToUpperInvariant()}'''"); + stringBuilder2.AppendLine(" )"); + stringBuilder2.AppendLine(" COLUMNS"); + stringBuilder2.AppendLine(" column_name VARCHAR2(4000) PATH 'COLUMN_NAME',"); + stringBuilder2.AppendLine(" data_default VARCHAR2(4000) PATH 'DATA_DEFAULT'"); + stringBuilder2.AppendLine(") x"); + + var userTabIdentityCols = _oracleSystemDataLoader.GetUserTabIdentityCols(tableName: table); + var primaryKeyItems = _oracleSystemDataLoader.GetPrimaryKeyItems(tableName: table); + + List userTabColumns = []; + + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, stringBuilder2.ToString())) + { + while (reader.Read()) + { + var columnNameOrdinal = reader.GetOrdinal("COLUMN_NAME"); + var dataDefaultOrdinal = reader.GetOrdinal("DATA_DEFAULT"); + + var userTabColumnsItem = new UserTabColumns + { + ColumnName = reader.IsDBNull(columnNameOrdinal) ? null : reader.GetString(columnNameOrdinal), + DataDefault = reader.IsDBNull(dataDefaultOrdinal) ? null : reader.GetString(dataDefaultOrdinal).Trim() + }; + + userTabColumns.Add(userTabColumnsItem); + } + } + + var columns = new List(); + + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, stringBuilder.ToString())) + { + while (reader.Read()) + { + var columnNameOrdinal = reader.GetOrdinal("COLUMN_NAME"); + var nullableOrdinal = reader.GetOrdinal("NULLABLE"); + var dataTypeOrdinal = reader.GetOrdinal("DATA_TYPE"); + var dataLengthOrdinal = reader.GetOrdinal("DATA_LENGTH"); + var dataPrecisionOrdinal = reader.GetOrdinal("DATA_PRECISION"); + var dataScaleOrdinal = reader.GetOrdinal("DATA_SCALE"); + var charColDeclLengthOrdinal = reader.GetOrdinal("CHAR_COL_DECL_LENGTH"); + + var columnName = reader.GetString(columnNameOrdinal); + var isNullable = reader.GetString(nullableOrdinal) == "Y"; + var dataTypeString = reader.GetString(dataTypeOrdinal).ToUpperInvariant(); + var dataLength = reader.IsDBNull(dataLengthOrdinal) ? (int?)null : reader.GetInt32(dataLengthOrdinal); + var dataPrecision = reader.IsDBNull(dataPrecisionOrdinal) ? (int?)null : reader.GetInt32(dataPrecisionOrdinal); + var dataScale = reader.IsDBNull(dataScaleOrdinal) ? (int?)null : reader.GetInt32(dataScaleOrdinal); + var charColDeclLength = reader.IsDBNull(charColDeclLengthOrdinal) ? (int?)null : reader.GetInt32(charColDeclLengthOrdinal); + var dataDefaultString = userTabColumns.FirstOrDefault(x => x.ColumnName.Equals(columnName, StringComparison.OrdinalIgnoreCase))?.DataDefault; + + var column = new Column(columnName, DbType.String) + { + ColumnProperty = isNullable ? ColumnProperty.Null : ColumnProperty.NotNull + }; + + var isIdentity = userTabIdentityCols.Any(x => x.ColumnName.Equals(columnName, StringComparison.OrdinalIgnoreCase)); + var isPrimaryKey = primaryKeyItems.Any(x => x.ColumnName.Equals(columnName, StringComparison.OrdinalIgnoreCase)); + + if (isIdentity && isPrimaryKey) + { + column.ColumnProperty = column.ColumnProperty.Set(ColumnProperty.PrimaryKeyWithIdentity); + } + else if (isIdentity) + { + column.ColumnProperty.Set(ColumnProperty.Identity); + } + else if (isPrimaryKey) + { + column.ColumnProperty.Set(ColumnProperty.PrimaryKey); + } + + // Oracle does not have unsigned types. All NUMBER types can hold positive or negative values so we do not return DbType.UIntX types. + if (dataTypeString.StartsWith("NUMBER") || dataTypeString.StartsWith("FLOAT")) + { + column.Precision = dataPrecision; + + if (dataScale > 0) + { + // Could also be Double + column.MigratorDbType = MigratorDbType.Decimal; + column.Scale = dataScale; + } + else + { + if (dataPrecision.HasValue && dataPrecision == 1) + { + column.MigratorDbType = MigratorDbType.Boolean; + } + else if (dataPrecision.HasValue && (dataPrecision == 0 || (2 <= dataPrecision && dataPrecision <= 5))) + { + column.MigratorDbType = MigratorDbType.Int16; + } + else if (dataPrecision.HasValue && 6 <= dataPrecision && dataPrecision <= 10) + { + column.MigratorDbType = MigratorDbType.Int32; + } + else if (dataPrecision == null || 11 <= dataPrecision) + { + // Oracle allows up to 38 digits but in C# the maximum is Int64 and in Oracle there is no unsigned data type. + column.MigratorDbType = MigratorDbType.Int64; + } + else + { + throw new NotSupportedException(); + } + } + } + else if (dataTypeString.StartsWith("TIMESTAMP")) + { + var timestampNumberRegex = new Regex(@"(?<=^Timestamp\()[\d]+(?=\)$)", RegexOptions.IgnoreCase); + var timestampNumberMatch = timestampNumberRegex.Match(dataTypeString); + + if (timestampNumberMatch.Success) + { + // n in TIMESTAMP(n) is not retrievable using system tables so we need to extract it via regex. + column.Precision = int.Parse(timestampNumberMatch.Value); + column.MigratorDbType = column.Precision < 3 ? MigratorDbType.DateTime : MigratorDbType.DateTime2; + } + else + { + // 6 is the standard if we use TIMESTAMP without n like in TIMESTAMP(n) + column.Precision = 6; + column.MigratorDbType = MigratorDbType.DateTime2; + } + } + else if (dataTypeString == "DATE") + { + column.MigratorDbType = MigratorDbType.Date; + } + else if (dataTypeString == "RAW" && dataLength == 16) + { + // ambiguity - cannot distinguish between guid and binary + column.MigratorDbType = MigratorDbType.Guid; + } + else if (dataTypeString.StartsWith("RAW") || dataTypeString == "BLOB") + { + column.MigratorDbType = MigratorDbType.Binary; + } + else if (dataTypeString == "NVARCHAR2") + { + column.MigratorDbType = MigratorDbType.String; + } + else if (dataTypeString == "BINARY_FLOAT") + { + column.MigratorDbType = MigratorDbType.Single; + } + else if (dataTypeString == "BINARY_DOUBLE") + { + column.MigratorDbType = MigratorDbType.Double; + } + else if (dataTypeString == "BOOLEAN") + { + column.MigratorDbType = MigratorDbType.Boolean; + } + else if (dataTypeString == "NCLOB") + { + column.MigratorDbType = MigratorDbType.String; + } + else if (dataTypeString.StartsWith("INTERVAL")) + { + column.MigratorDbType = MigratorDbType.Interval; + } + else + { + throw new NotImplementedException($"The data type '{dataTypeString}' is not implemented yet. Please file an issue."); + } + + // dataDefaultString contains ISEQ$$ if the column is an identity column + if ( + !string.IsNullOrWhiteSpace(dataDefaultString) && + (column.Type == DbType.String || !dataDefaultString.Equals("null", StringComparison.OrdinalIgnoreCase)) && + !dataDefaultString.Contains("ISEQ$$") && + !dataDefaultString.Contains(".nextval")) + { + // This is only necessary because older versions of this migrator added single quotes for numerics. + var singleQuoteStrippedString = dataDefaultString.Replace("'", ""); + + if (column.Type == DbType.Int16 || column.Type == DbType.Int32 || column.Type == DbType.Int64) + { + column.DefaultValue = long.Parse(singleQuoteStrippedString, CultureInfo.InvariantCulture); + } + else if (column.Type == DbType.Double) + { + column.DefaultValue = double.Parse(singleQuoteStrippedString, CultureInfo.InvariantCulture); + } + else if (column.Type == DbType.Single) + { + column.DefaultValue = float.Parse(singleQuoteStrippedString, CultureInfo.InvariantCulture); + } + else if (column.Type == DbType.Decimal) + { + column.DefaultValue = decimal.Parse(singleQuoteStrippedString, CultureInfo.InvariantCulture); + } + else if (column.Type == DbType.Boolean) + { + column.DefaultValue = dataDefaultString == "1" || dataDefaultString.ToUpper() == "TRUE"; + } + else if (column.Type == DbType.DateTime || column.Type == DbType.DateTime2) + { + if (dataDefaultString.StartsWith("TO_TIMESTAMP(")) + { + var expectedOracleToTimestampPattern = "YYYY-MM-DD HH24:MI:SS"; + + if (!dataDefaultString.Contains(expectedOracleToTimestampPattern)) + { + throw new NotSupportedException($"Not supported 'TO_TIMESTAMP' pattern. Expected pattern: {expectedOracleToTimestampPattern}"); + } + + var toTimestampRegex = new Regex(@"(?<=^TO_TIMESTAMP\(')[^']+(?=')", RegexOptions.IgnoreCase); + var toTimestampMatch = toTimestampRegex.Match(dataDefaultString); + var toTimestampDateTimeString = toTimestampMatch.Value; + + List formats = []; + + // add formats with .F, .FF, .FFF etc. + formats = Enumerable.Range(0, 20).Select((x, y) => $"{timestampBaseFormat}.{new string('F', y + 1)}").ToList(); + formats.Add(timestampBaseFormat); + + column.DefaultValue = DateTime.ParseExact(toTimestampDateTimeString, [.. formats], CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); + } + else if (timestampRegex.Match(dataDefaultString) is Match timestampMatch && timestampMatch.Success) + { + var millisecondsPattern = column.Size == 0 ? string.Empty : $".{new string('F', column.Size)}"; + column.DefaultValue = DateTime.ParseExact(timestampMatch.Value, $"yyyy-MM-dd HH:mm:ss{millisecondsPattern}", CultureInfo.InvariantCulture); + } + else + { + // Could be system time in many variants + column.DefaultValue = dataDefaultString; + } + } + else if (column.Type == DbType.Guid) + { + if (hexToRawRegex.Match(dataDefaultString) is Match hexToRawMatch && hexToRawMatch.Success) + { + var bytes = Enumerable.Range(0, hexToRawMatch.Value.Length / 2) + .Select(x => Convert.ToByte(hexToRawMatch.Value.Substring(x * 2, 2), 16)) + .ToArray(); + + // Oracle uses Big-Endian + Array.Reverse(bytes, 0, 4); + Array.Reverse(bytes, 4, 2); + Array.Reverse(bytes, 6, 2); + + column.DefaultValue = new Guid(bytes); + } + else if (dataDefaultString.StartsWith("'")) + { + var guidString = dataDefaultString.Substring(1, dataDefaultString.Length - 2); + + column.DefaultValue = Guid.Parse(guidString); + } + else + { + column.DefaultValue = dataDefaultString; + } + } + else if (column.Type == DbType.String) + { + var contentRegex = new Regex(@"(?<=^').*(?='$)"); + + if (contentRegex.Match(dataDefaultString) is Match contentMatch && contentMatch.Success) + { + column.DefaultValue = contentMatch.Value; + } + else + { + throw new Exception($"Cannot parse string column '{column.Name}'"); + } + } + else if (column.Type == DbType.Binary) + { + if (hexToRawRegex.Match(dataDefaultString) is Match hexToRawMatch && hexToRawMatch.Success) + { + column.DefaultValue = Enumerable.Range(0, hexToRawMatch.Value.Length / 2) + .Select(x => Convert.ToByte(hexToRawMatch.Value.Substring(x * 2, 2), 16)) + .ToArray(); + } + else + { + throw new NotImplementedException($"Cannot parse default value in column '{column.Name}'"); + } + } + else + { + column.DefaultValue = dataDefaultString; + } + } + + columns.Add(column); + } + } + + return columns.ToArray(); + } + + public override string GenerateParameterNameParameter(int index) + { + return "p" + index; + } + + public override string GenerateParameterName(int index) + { + return ":p" + index; + } + + protected override void ConfigureParameterWithValue(IDbDataParameter parameter, int index, object value) + { + if (value is Guid || value is Guid?) + { + parameter.DbType = DbType.Binary; + + if (value is Guid? && !((Guid?)value).HasValue) + { + return; + } + + parameter.Value = ((Guid)value).ToByteArray(); + } + else if (value is bool || value is bool?) + { + parameter.DbType = DbType.Int32; + parameter.Value = ((bool)value) ? 1 : 0; + } + else if (value is ushort) + { + parameter.DbType = DbType.Decimal; + parameter.Value = value; + } + else if (value is uint) + { + parameter.DbType = DbType.Decimal; + parameter.Value = value; + } + else if (value is ulong) + { + parameter.DbType = DbType.Decimal; + parameter.Value = value; + } + else + { + base.ConfigureParameterWithValue(parameter, index, value); + } + } + + public override void CopyDataFromTableToTable(string sourceTableName, List sourceColumnNames, string targetTableName, List targetColumnNames, List orderBySourceColumns = null) + { + orderBySourceColumns ??= []; + + if (!TableExists(sourceTableName)) + { + throw new Exception($"Source table '{QuoteTableNameIfRequired(sourceTableName)}' does not exist"); + } + + if (!TableExists(targetTableName)) + { + throw new Exception($"Target table '{QuoteTableNameIfRequired(targetTableName)}' does not exist"); + } + + var sourceColumnsConcatenated = sourceColumnNames.Concat(orderBySourceColumns); + + foreach (var column in sourceColumnsConcatenated) + { + if (!ColumnExists(sourceTableName, column)) + { + throw new Exception($"Column {column} in source table does not exist."); + } + } + + foreach (var column in targetColumnNames) + { + if (!ColumnExists(targetTableName, column)) + { + throw new Exception($"Column {column} in target table does not exist."); + } + } + + if (!orderBySourceColumns.All(x => sourceColumnNames.Contains(x))) + { + throw new Exception($"All columns in {nameof(orderBySourceColumns)} must be in {nameof(sourceColumnNames)}"); + } + + var sourceTableNameQuoted = QuoteTableNameIfRequired(sourceTableName); + var targetTableNameQuoted = QuoteTableNameIfRequired(targetTableName); + + var sourceColumnNamesQuoted = sourceColumnNames.Select(QuoteColumnNameIfRequired).ToList(); + var targetColumnNamesQuoted = targetColumnNames.Select(QuoteColumnNameIfRequired).ToList(); + var orderBySourceColumnsQuoted = orderBySourceColumns.Select(QuoteColumnNameIfRequired).ToList(); + + var sourceColumnsJoined = string.Join(", ", sourceColumnNamesQuoted); + var targetColumnsJoined = string.Join(", ", targetColumnNamesQuoted); + var orderBySourceColumnsJoined = string.Join(", ", orderBySourceColumnsQuoted); + + var orderByComponent = !string.IsNullOrWhiteSpace(orderBySourceColumnsJoined) ? $"ORDER BY {orderBySourceColumnsJoined}" : null; + + List sqlComponents = + [ + $"INSERT INTO {targetTableNameQuoted} ({targetColumnsJoined}) SELECT {sourceColumnsJoined} FROM {sourceTableNameQuoted}", + orderByComponent + ]; + + var sql = string.Join(" ", sqlComponents.Where(x => x != null)); + ExecuteNonQuery(sql); + } + + public override void RemoveColumnDefaultValue(string table, string column) + { + var sql = string.Format("ALTER TABLE {0} MODIFY {1} DEFAULT NULL", table, column); + ExecuteNonQuery(sql); + } + + public override void AddTable(string name, params IDbField[] fields) + { + GuardAgainstMaximumIdentifierLengthForOracle(name); + name = QuoteTableNameIfRequired(name); + + var columns = fields.Where(x => x is Column).Cast().ToArray(); + + GuardAgainstMaximumColumnNameLengthForOracle(name, columns); + + base.AddTable(name, fields); + + // Should be refactored + if (columns.Any(c => c.ColumnProperty == ColumnProperty.PrimaryKeyWithIdentity || + (c.ColumnProperty.HasFlag(ColumnProperty.Identity) && c.ColumnProperty.HasFlag(ColumnProperty.PrimaryKey)))) + { + var identityColumn = columns.First(x => x.ColumnProperty.HasFlag(ColumnProperty.Identity) && x.ColumnProperty.HasFlag(ColumnProperty.PrimaryKey)); + + List allowedIdentityDbTypes = [DbType.Int16, DbType.Int32, DbType.Int64, DbType.UInt16, DbType.UInt32, DbType.UInt64]; + + if (!allowedIdentityDbTypes.Contains(identityColumn.Type)) + { + var allowedIdentityDbTypesStringList = allowedIdentityDbTypes.Select(x => x.ToString()).ToList(); + var allowedIdentityDbTypesString = $"{string.Join(", ", allowedIdentityDbTypesStringList[..^1])} and {allowedIdentityDbTypesStringList[^1..]}"; + + throw new MigrationException($"Identity columns can only be used with {allowedIdentityDbTypesString}"); + } + + var identityColumnNameQuoted = QuoteColumnNameIfRequired(identityColumn.Name); + + using var cmd = CreateCommand(); + // We use ALWAYS in order to prevent sequence problems in cases of misuse of the column by an unexperienced user. Inserting data will result in an exception. + ExecuteQuery(cmd, $"ALTER TABLE {name} MODIFY {identityColumnNameQuoted} GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE)"); + } + else if (columns.Any(x => x.ColumnProperty.HasFlag(ColumnProperty.Identity) && !x.ColumnProperty.HasFlag(ColumnProperty.PrimaryKey))) + { + throw new MigrationException("Identity without Primary is currently not supported by this migrator"); + } + } + + public override void RemoveTable(string name) + { + base.RemoveTable(name); + + try + { + using var cmd = CreateCommand(); + ExecuteQuery(cmd, string.Format(@"DROP SEQUENCE {0}_SEQUENCE", name)); + } + catch (Exception) + { + // swallow this because sequence may not have existed. + } + } + + private void GuardAgainstMaximumColumnNameLengthForOracle(string name, Column[] columns) + { + foreach (var column in columns) + { + if (column.Name.Length > 30) + { + throw new ArgumentException( + string.Format("When adding table: \"{0}\", the column: \"{1}\", the name of the column is: {2} characters in length, but maximum length for an oracle identifier is 30 characters", name, + column.Name, column.Name.Length), "columns"); + } + } + } + + public override string Encode(Guid guid) + { + var bytes = guid.ToByteArray(); + var hex = new StringBuilder(bytes.Length * 2); + foreach (var b in bytes) + { + hex.AppendFormat("{0:X2}", b); + } + + return hex.ToString(); + } + + public override bool IndexExists(string table, string name) + { + var sql = + string.Format( + "SELECT COUNT(index_name) FROM user_indexes WHERE lower(index_name) = '{0}' AND lower(table_name) = '{1}'", + name.ToLower(), table.ToLower()); + Logger.Log(sql); + var scalar = ExecuteScalar(sql); + return Convert.ToInt32(scalar) == 1; + } + + public override void UpdateTargetFromSource(string tableSourceNotQuoted, string tableTargetNotQuoted, ColumnPair[] fromSourceToTargetColumnPairs, ColumnPair[] conditionColumnPairs) + { + if (!TableExists(tableSourceNotQuoted)) + { + throw new Exception($"Table '{tableSourceNotQuoted}' given in '{nameof(tableSourceNotQuoted)}' does not exist"); + } + + if (!TableExists(tableTargetNotQuoted)) + { + throw new Exception($"Table '{tableTargetNotQuoted}' given in '{nameof(tableTargetNotQuoted)}' does not exist"); + } + + if (fromSourceToTargetColumnPairs.Length == 0) + { + throw new Exception($"{nameof(fromSourceToTargetColumnPairs)} is empty."); + } + + if (fromSourceToTargetColumnPairs.Any(x => string.IsNullOrWhiteSpace(x.ColumnNameSource) || string.IsNullOrWhiteSpace(x.ColumnNameTarget))) + { + throw new Exception($"One of the strings in {nameof(fromSourceToTargetColumnPairs)} is null or empty"); + } + + if (conditionColumnPairs.Length == 0) + { + throw new Exception($"{nameof(conditionColumnPairs)} is empty."); + } + + if (conditionColumnPairs.Any(x => string.IsNullOrWhiteSpace(x.ColumnNameSource) || string.IsNullOrWhiteSpace(x.ColumnNameTarget))) + { + throw new Exception($"One of the strings in {nameof(conditionColumnPairs)} is null or empty"); + } + + var tableNameSource = QuoteTableNameIfRequired(tableSourceNotQuoted); + var tableNameTarget = QuoteTableNameIfRequired(tableTargetNotQuoted); + + var conditionStrings = conditionColumnPairs.Select(x => $"t.{QuoteColumnNameIfRequired(x.ColumnNameTarget)} = s.{QuoteColumnNameIfRequired(x.ColumnNameSource)}"); + + var assignStrings = fromSourceToTargetColumnPairs.Select(x => $"{QuoteColumnNameIfRequired(x.ColumnNameTarget)} = s.{QuoteColumnNameIfRequired(x.ColumnNameSource)}").ToList(); + + var conditionStringsJoined = string.Join(" AND ", conditionStrings); + var assignStringsJoined = string.Join(", ", assignStrings); + + var sql = $"MERGE INTO {tableNameTarget} t USING {tableNameSource} s ON ({conditionStringsJoined}) WHEN MATCHED THEN UPDATE SET {assignStringsJoined}"; + ExecuteNonQuery(sql); + } + + private string SchemaInfoTableName + { + get + { + if (_defaultSchema == null) + { + return "SchemaInfo"; + } + + return string.Format("{0}.{1}", _defaultSchema, "SchemaInfo"); + } + } + + public override Index[] GetIndexes(string table) + { + var indexItems = _oracleSystemDataLoader.GetIndexItems(table); + + var indexGroups = indexItems.GroupBy(x => new { x.SchemaName, x.TableName, x.Name }); + List indexes = []; + + foreach (var indexGroup in indexGroups) + { + var first = indexGroup.First(); + + var index = new Index + { + KeyColumns = [.. indexGroup.OrderBy(x => x.ColumnOrder).Select(x => x.ColumnName).Distinct()], + Name = first.Name, + PrimaryKey = first.PrimaryKey, + UniqueConstraint = first.UniqueConstraint, + Unique = first.Unique, + + // Oracle does not support clustered indexes at this point in time. + Clustered = false, + + // Oracle does not support include columns at this point in time. + IncludeColumns = null, + }; + + // FilterItems is not supported in this migrator at this point in time. + + indexes.Add(index); + } + + return indexes.ToArray(); + } + + public override string Concatenate(params string[] strings) + { + return string.Join(" || ", strings); + } + + private void Initialize() + { + _oracleSystemDataLoader = new OracleSystemDataLoader(this); + } +} diff --git a/src/Migrator/Providers/Impl/PostgreSQL/Data/Interfaces/IPostgreSQLSystemDataLoader.cs b/src/Migrator/Providers/Impl/PostgreSQL/Data/Interfaces/IPostgreSQLSystemDataLoader.cs new file mode 100644 index 00000000..97a35085 --- /dev/null +++ b/src/Migrator/Providers/Impl/PostgreSQL/Data/Interfaces/IPostgreSQLSystemDataLoader.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using DotNetProjects.Migrator.Providers.Impl.PostgreSQL.Models; + +namespace DotNetProjects.Migrator.Providers.Impl.PostgreSQL.Data.Interfaces; + +public interface IPostgreSQLSystemDataLoader +{ + /// + /// Gets column infos. + /// + /// + /// + /// + List GetColumnInfos(string tableName, string schemaName = "public"); + + /// + /// Gets table constraints. + /// + /// + /// + /// + List GetTableConstraints(string tableName, string schemaName = "public"); +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/PostgreSQL/Data/PostgreSQLSystemDataLoader.cs b/src/Migrator/Providers/Impl/PostgreSQL/Data/PostgreSQLSystemDataLoader.cs new file mode 100644 index 00000000..a4cbfa0a --- /dev/null +++ b/src/Migrator/Providers/Impl/PostgreSQL/Data/PostgreSQLSystemDataLoader.cs @@ -0,0 +1,127 @@ +using System.Collections.Generic; +using DotNetProjects.Migrator.Providers.Impl.PostgreSQL.Data.Interfaces; +using DotNetProjects.Migrator.Providers.Impl.PostgreSQL.Interfaces; +using DotNetProjects.Migrator.Providers.Impl.PostgreSQL.Models; + +namespace DotNetProjects.Migrator.Providers.Impl.PostgreSQL.Data; + +public class PostgreSQLSystemDataLoader(IPostgreSQLTransformationProvider postgreTransformationProvider) : IPostgreSQLSystemDataLoader +{ + private readonly IPostgreSQLTransformationProvider _postgreSQLTransformationProvider = postgreTransformationProvider; + + public List GetTableConstraints(string tableName, string schemaName = "public") + { + var quotedTableName = _postgreSQLTransformationProvider.QuoteTableNameIfRequired(tableName); + + var sql = $@" + SELECT + tc.TABLE_SCHEMA, + tc.TABLE_NAME, + tc.CONSTRAINT_NAME, + tc.CONSTRAINT_TYPE, + kcu.COLUMN_NAME + FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc + JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu + ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME + AND tc.TABLE_SCHEMA = kcu.TABLE_SCHEMA + AND tc.TABLE_NAME = kcu.TABLE_NAME + WHERE + LOWER(tc.table_name) = '{quotedTableName.ToLowerInvariant()}' + AND tc.TABLE_SCHEMA = '{schemaName}' + "; + + List tableConstraints = []; + + using var cmd = _postgreSQLTransformationProvider.CreateCommand(); + using var reader = _postgreSQLTransformationProvider.ExecuteQuery(cmd, sql); + + while (reader.Read()) + { + var constraintNameOrdinal = reader.GetOrdinal("CONSTRAINT_NAME"); + var constraintTypeOrdinal = reader.GetOrdinal("CONSTRAINT_TYPE"); + var columnNameOrdinal = reader.GetOrdinal("COLUMN_NAME"); + var tableNameOrdinal = reader.GetOrdinal("TABLE_NAME"); + var tableSchemaOrdinal = reader.GetOrdinal("TABLE_SCHEMA"); + + var tableConstraint = new TableConstraint + { + ConstraintName = !reader.IsDBNull(constraintNameOrdinal) ? reader.GetString(constraintNameOrdinal) : null, + ConstraintType = !reader.IsDBNull(constraintTypeOrdinal) ? reader.GetString(constraintTypeOrdinal) : null, + ColumnName = reader.GetString(columnNameOrdinal), + TableName = reader.GetString(tableNameOrdinal), + TableSchema = reader.GetString(tableSchemaOrdinal), + }; + + tableConstraints.Add(tableConstraint); + } + + return tableConstraints; + } + + public List GetColumnInfos(string tableName, string schemaName = "public") + { + var sql = $@" + SELECT + c.CHARACTER_MAXIMUM_LENGTH, + c.COLUMN_DEFAULT, + c.COLUMN_NAME, + c.DATA_TYPE, + c.DATETIME_PRECISION, + c.IDENTITY_GENERATION, + c.IS_IDENTITY, + c.IS_NULLABLE, + c.NUMERIC_PRECISION, + c.NUMERIC_SCALE, + c.ORDINAL_POSITION, + c.TABLE_SCHEMA, + c.TABLE_NAME + FROM information_schema.columns c + WHERE + LOWER(c.table_name) = '{tableName.ToLowerInvariant()}' AND + c.TABLE_SCHEMA = '{schemaName}' + "; + + List columns = []; + + using var cmd = _postgreSQLTransformationProvider.CreateCommand(); + using var reader = _postgreSQLTransformationProvider.ExecuteQuery(cmd, sql); + + while (reader.Read()) + { + var characterMaximumLength = reader.GetOrdinal("CHARACTER_MAXIMUM_LENGTH"); + var columnDefaultOrdinal = reader.GetOrdinal("COLUMN_DEFAULT"); + var columnNameOrdinal = reader.GetOrdinal("COLUMN_NAME"); + var dataTypeOrdinal = reader.GetOrdinal("DATA_TYPE"); + var dateTimePrecisionOrdinal = reader.GetOrdinal("DATETIME_PRECISION"); + var identityGenerationOrdinal = reader.GetOrdinal("IDENTITY_GENERATION"); + var isIdentityOrdinal = reader.GetOrdinal("IS_IDENTITY"); + var isNullableOrdinal = reader.GetOrdinal("IS_NULLABLE"); + var numericPrecisionOrdinal = reader.GetOrdinal("NUMERIC_PRECISION"); + var numericScaleOrdinal = reader.GetOrdinal("NUMERIC_SCALE"); + var ordinalPositionOrdinal = reader.GetOrdinal("ORDINAL_POSITION"); + var tableNameOrdinal = reader.GetOrdinal("TABLE_NAME"); + var tableSchemaOrdinal = reader.GetOrdinal("TABLE_SCHEMA"); + + var columnInfo = new ColumnInfo + { + CharacterMaximumLength = !reader.IsDBNull(characterMaximumLength) ? reader.GetInt32(characterMaximumLength) : null, + ColumnDefault = !reader.IsDBNull(columnDefaultOrdinal) ? reader.GetString(columnDefaultOrdinal) : null, + ColumnName = reader.GetString(columnNameOrdinal), + DataType = reader.GetString(dataTypeOrdinal), + DateTimePrecision = !reader.IsDBNull(dateTimePrecisionOrdinal) ? reader.GetInt32(dateTimePrecisionOrdinal) : null, + IdentityGeneration = !reader.IsDBNull(identityGenerationOrdinal) ? reader.GetString(identityGenerationOrdinal) : null, + IsIdentity = reader.GetString(isIdentityOrdinal), + IsNullable = reader.GetString(isNullableOrdinal), + NumericPrecision = !reader.IsDBNull(numericPrecisionOrdinal) ? reader.GetInt32(numericPrecisionOrdinal) : null, + NumericScale = !reader.IsDBNull(numericScaleOrdinal) ? reader.GetInt32(numericScaleOrdinal) : null, + OrdinalPosition = reader.GetInt32(ordinalPositionOrdinal), + TableName = reader.GetString(tableNameOrdinal), + TableSchema = reader.GetString(tableSchemaOrdinal), + }; + + columns.Add(columnInfo); + } + + return columns; + } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/PostgreSQL/Interfaces/IPostgreSQLTransformationProvider.cs b/src/Migrator/Providers/Impl/PostgreSQL/Interfaces/IPostgreSQLTransformationProvider.cs new file mode 100644 index 00000000..5b98b8bb --- /dev/null +++ b/src/Migrator/Providers/Impl/PostgreSQL/Interfaces/IPostgreSQLTransformationProvider.cs @@ -0,0 +1,7 @@ +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.PostgreSQL.Interfaces; + +public interface IPostgreSQLTransformationProvider : ITransformationProvider +{ +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/PostgreSQL/Models/ColumnInfo.cs b/src/Migrator/Providers/Impl/PostgreSQL/Models/ColumnInfo.cs new file mode 100644 index 00000000..5ca2e845 --- /dev/null +++ b/src/Migrator/Providers/Impl/PostgreSQL/Models/ColumnInfo.cs @@ -0,0 +1,73 @@ +namespace DotNetProjects.Migrator.Providers.Impl.PostgreSQL.Models; + +/// +/// Represents the INFORMATIONSCHEMA.COLUMNS +/// +public class ColumnInfo +{ + /// + /// Gets or sets the date time precision. + /// + public int? DateTimePrecision { get; set; } + + /// + /// Gets or sets the character maximum length. + /// If data_type identifies a character or bit string type, the declared maximum length; null for all other data types or if no maximum length was declared. + /// + public int? CharacterMaximumLength { get; set; } + + /// + /// Gets or sets the schema name. + /// + public string TableSchema { get; set; } + + /// + /// Gets or sets the table name. + /// + public string TableName { get; set; } + + /// + /// Gets or sets the column name. + /// + public string ColumnName { get; set; } + + /// + /// Gets or sets the data type. Data type of the column, if it is a built-in type, or ARRAY if it is some array (in that case, see the view element_types), else USER-DEFINED (in that case, the type is identified in udt_name and associated columns). If the column is based on a domain, this column refers to the type underlying the domain (and the domain is identified in domain_name and associated columns). + /// + public string DataType { get; set; } + + /// + /// Gets or sets the is nullable string. + /// + public string IsNullable { get; set; } + + /// + /// Gets or sets the column default. + /// + public string ColumnDefault { get; set; } + + /// + /// Gets or sets the is identity string. YES or NO. + /// + public string IsIdentity { get; set; } + + /// + /// Gets or sets the identity generation. + /// + public string IdentityGeneration { get; set; } + + /// + /// Gets or sets the ordinal position + /// + public int OrdinalPosition { get; set; } + + /// + /// Gets or sets th numeric scale. + /// + public int? NumericScale { get; set; } + + /// + /// Gets or sets the numeric precision. + /// + public int? NumericPrecision { get; set; } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/PostgreSQL/Models/TableConstraint.cs b/src/Migrator/Providers/Impl/PostgreSQL/Models/TableConstraint.cs new file mode 100644 index 00000000..848028bf --- /dev/null +++ b/src/Migrator/Providers/Impl/PostgreSQL/Models/TableConstraint.cs @@ -0,0 +1,29 @@ +namespace DotNetProjects.Migrator.Providers.Impl.PostgreSQL.Models; + +public class TableConstraint +{ + /// + /// Gets or sets the schema name. + /// + public string TableSchema { get; set; } + + /// + /// Gets or sets the table name. + /// + public string TableName { get; set; } + + /// + /// Gets or sets the column name. + /// + public string ColumnName { get; set; } + + /// + /// Gets or sets the constraint name. + /// + public string ConstraintName { get; set; } + + /// + /// Gets or sets the constraint type. + /// + public string ConstraintType { get; set; } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQL82Dialect.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQL82Dialect.cs new file mode 100644 index 00000000..08b87424 --- /dev/null +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQL82Dialect.cs @@ -0,0 +1,11 @@ +using System.Data; + +namespace DotNetProjects.Migrator.Providers.Impl.PostgreSQL; + +public class PostgreSQL82Dialect : PostgreSQLDialect +{ + public PostgreSQL82Dialect() + { + RegisterColumnType(DbType.Guid, "uuid"); // Requires postgresql 8.2 and up + } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLDialect.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLDialect.cs new file mode 100644 index 00000000..2987d1d8 --- /dev/null +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLDialect.cs @@ -0,0 +1,147 @@ +using DotNetProjects.Migrator.Framework; +using System; +using System.Data; + +namespace DotNetProjects.Migrator.Providers.Impl.PostgreSQL; + +public class PostgreSQLDialect : Dialect +{ + public PostgreSQLDialect() + { + RegisterColumnType(DbType.AnsiStringFixedLength, "char(255)"); + RegisterColumnType(DbType.AnsiStringFixedLength, 1073741823, "char($l)"); + RegisterColumnType(DbType.AnsiString, "varchar(255)"); + RegisterColumnType(DbType.AnsiString, 8000, "varchar($l)"); + RegisterColumnType(DbType.AnsiString, int.MaxValue, "text"); + RegisterColumnType(DbType.Binary, "bytea"); + RegisterColumnType(DbType.Binary, 2147483647, "bytea"); + RegisterColumnType(DbType.Boolean, "boolean"); + RegisterColumnType(DbType.Byte, "int2"); + RegisterColumnType(DbType.Currency, "decimal(16,4)"); + RegisterColumnType(DbType.Date, "date"); + + // 8 bytes - resolution 1 microsecond + RegisterColumnType(DbType.DateTime, "timestamp(3)"); + + // 8 bytes - resolution 1 microsecond + // We do not use timezone any more - this is near a datetime2 in SQL Server + RegisterColumnType(DbType.DateTime2, "timestamp(6)"); + RegisterColumnType(DbType.DateTimeOffset, "timestamptz"); + RegisterColumnType(DbType.Decimal, "decimal(19,5)"); + RegisterColumnType(DbType.Decimal, 19, "decimal(18, $l)"); + RegisterColumnTypeWithParameters(DbType.Decimal, "decimal({precision}, {scale})"); + RegisterColumnType(DbType.Double, "float8"); + RegisterColumnType(DbType.Int16, "int2"); + RegisterColumnType(DbType.Int32, "int4"); + RegisterColumnType(DbType.Int64, "int8"); + RegisterColumnType(DbType.UInt16, "int4"); + RegisterColumnType(DbType.UInt32, "int8"); + RegisterColumnType(DbType.UInt64, "decimal(20,0)"); + RegisterColumnType(DbType.Single, "float4"); + RegisterColumnType(DbType.StringFixedLength, "char(255)"); + RegisterColumnType(DbType.StringFixedLength, 1073741823, "char($l)"); + RegisterColumnType(DbType.String, "varchar(255)"); + RegisterColumnType(DbType.String, 4000, "varchar($l)"); + RegisterColumnType(DbType.String, int.MaxValue, "text"); + RegisterColumnType(DbType.Time, "time"); + RegisterColumnType(DbType.Guid, "uuid"); + RegisterColumnType(MigratorDbType.Interval, "interval"); + + RegisterProperty(ColumnProperty.Identity, "GENERATED ALWAYS AS IDENTITY"); + + AddReservedWords("ABS", "ABSOLUTE", "ACCESS", "ACTION", "ADA", "ADD", "ADMIN", "AFTER", "AGGREGATE", "ALIAS", "ALL", "ALLOCATE", "ALTER", "ANALYSE", "ANALYZE", "AND", "ANY", "ARE", + "ARRAY", "AS", "ASC", "ASENSITIVE", "ASSERTION", "ASSIGNMENT", "ASYMMETRIC", "AT", "ATOMIC", "AUTHORIZATION", "AVG", "BACKWARD", "BEFORE", "BEGIN", "BETWEEN", "BIGINT", "BINARY", + "BIT", "BITVAR", "BIT_LENGTH", "BLOB", "BOOLEAN", "BOTH", "BREADTH", "BY", "C", "CACHE", "CALL", "CALLED", "CARDINALITY", "CASCADE", "CASCADED", "CASE", "CAST", "CATALOG", + "CATALOG_NAME", "CHAIN", "CHAR", "CHARACTER", "CHARACTERISTICS", "CHARACTER_LENGTH", "CHARACTER_SET_CATALOG", "CHARACTER_SET_NAME", "CHARACTER_SET_SCHEMA", "CHAR_LENGTH", + "CHECK", "CHECKED", "CHECKPOINT", "CLASS", "CLASS_ORIGIN", "CLOB", "CLOSE", "CLUSTER", "COALESCE", "COBOL", "COLLATE", "COLLATION", "COLLATION_CATALOG", "COLLATION_NAME", + "COLLATION_SCHEMA", "COLUMN", "COLUMN_NAME", "COMMAND_FUNCTION", "COMMAND_FUNCTION_CODE", "COMMENT", "COMMIT", "COMMITTED", "COMPLETION", "CONDITION_NUMBER", "CONNECT", + "CONNECTION", "CONNECTION_NAME", "CONSTRAINT", "CONSTRAINTS", "CONSTRAINT_CATALOG", "CONSTRAINT_NAME", "CONSTRAINT_SCHEMA", "CONSTRUCTOR", "CONTAINS", "CONTENTS", "CONTINUE", + "CONVERSION", + "CONVERT", "COPY", "CORRESPONDING", "COUNT", "CREATE", "CREATEDB", "CREATEUSER", "CROSS", "CUBE", "CURRENT", "CURRENT_DATE", "CURRENT_PATH", "CURRENT_ROLE", "CURRENT_TIME", + "CURRENT_TIMESTAMP", "CURRENT_USER", "CURSOR", "CURSOR_NAME", "CYCLE", "DATABASE", "DATE", "DATETIME_INTERVAL_CODE", "DATETIME_INTERVAL_PRECISION", "DAY", "DEALLOCATE", + "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DEFINED", "DEFINER", "DELETE", "DELIMITER", "DELIMITERS", "DEPTH", "DEREF", "DESC", "DESCRIBE", "DESCRIPTOR", + "DESTROY", "DESTRUCTOR", "DETERMINISTIC", "DIAGNOSTICS", "DICTIONARY", "DISCONNECT", "DISPATCH", "DISTINCT", "DO", "DOMAIN", "DOUBLE", "DROP", "DYNAMIC", "DYNAMIC_FUNCTION", + "DYNAMIC_FUNCTION_CODE", "EACH", "ELSE", "ENCODING", "ENCRYPTED", "END", "END-EXEC", "EQUALS", "ESCAPE", "EVERY", "EXCEPT", "EXCEPTION", "EXCLUSIVE", "EXEC", "EXECUTE", + "EXISTING", "EXISTS", "EXPLAIN", "EXTERNAL", "EXTRACT", "FALSE", "FETCH", "FINAL", "FIRST", "FLOAT", "FOR", "FORCE", "FOREIGN", "FORTRAN", "FORWARD", "FOUND", "FREE", "FREEZE", + "FROM", "FULL", "FUNCTION", "G", "GENERAL", "GENERATED", "GET", "GLOBAL", "GO", "GOTO", "GRANT", "GRANTED", "GROUP", "GROUPING", "HANDLER", "HAVING", "HIERARCHY", "HOLD", "HOST", + "HOUR", "IDENTITY", "IGNORE", "ILIKE", "IMMEDIATE", "IMMUTABLE", "IMPLEMENTATION", "IMPLICIT", "IN", "INCREMENT", "INDEX", "INDICATOR", "INFIX", "INHERITS", "INITIALIZE", + "INITIALLY", "INNER", "INOUT", "INPUT", "INSENSITIVE", "INSERT", "INSTANCE", "INSTANTIABLE", "INSTEAD", "INT", "INTEGER", "INTERSECT", "INTERVAL", "INTO", "INVOKER", "IS", + "ISNULL", "ISOLATION", "ITERATE", "JOIN", "K", "KEY", "KEY_MEMBER", "KEY_TYPE", "LANCOMPILER", "LANGUAGE", "LARGE", "LAST", "LATERAL", "LEADING", "LEFT", "LENGTH", "LESS", + "LEVEL", "LIKE", "LIMIT", "LISTEN", "LOAD", "LOCAL", "LOCALTIME", "LOCALTIMESTAMP", "LOCATOR", "LOCK", "LOWER", "M", "MAP", "MATCH", "MAX", "MAXVALUE", + "MESSAGE_LENGTH", "MESSAGE_OCTET_LENGTH", "MESSAGE_TEXT", "METHOD", "MIN", "MINUTE", "MINVALUE", "MOD", "MODE", "MODIFIES", "MODIFY", "MODULE", "MONTH", "MORE", "MOVE", "MUMPS", + "NAMES", "NATIONAL", "NATURAL", "NCHAR", "NCLOB", "NEW", "NEXT", "NO", "NOCREATEDB", "NOCREATEUSER", "NONE", "NOT", "NOTHING", "NOTIFY", "NOTNULL", "NULL", "NULLABLE", + "NULLIF", "NUMBER", "NUMERIC", "OBJECT", "OCTET_LENGTH", "OF", "OFF", "OFFSET", "OIDS", "OLD", "ON", "ONLY", "OPEN", "OPERATION", "OPERATOR", "OPTION", "OPTIONS", "OR", "ORDER", + "ORDINALITY", "OUT", "OUTER", "OUTPUT", "OVERLAPS", "OVERLAY", "OVERRIDING", "OWNER", "PAD", "PARAMETER", "PARAMETERS", "PARAMETER_MODE", "PARAMETER_NAME", + "PARAMETER_ORDINAL_POSITION", "PARAMETER_SPECIFIC_CATALOG", "PARAMETER_SPECIFIC_NAME", "PARAMETER_SPECIFIC_SCHEMA", "PARTIAL", "PASCAL", "PATH", "PENDANT", "PLACING", + "PLI", "POSITION", "POSTFIX", "PRECISION", "PREFIX", "PREORDER", "PREPARE", "PRESERVE", "PRIMARY", "PRIOR", "PRIVILEGES", "PROCEDURAL", "PROCEDURE", "PUBLIC", "READ", "READS", + "REAL", "RECHECK", "RECURSIVE", "REF", "REFERENCES", "REFERENCING", "REINDEX", "RELATIVE", "RENAME", "REPEATABLE", "REPLACE", "RESET", "RESTRICT", "RESULT", "RETURN", + "RETURNED_LENGTH", "RETURNED_OCTET_LENGTH", "RETURNED_SQLSTATE", "RETURNS", "REVOKE", "RIGHT", "ROLE", "ROLLBACK", "ROLLUP", "ROUTINE", "ROUTINE_CATALOG", "ROUTINE_NAME", + "ROUTINE_SCHEMA", "ROW", "ROWS", "ROW_COUNT", "RULE", "SAVEPOINT", "SCALE", "SCHEMA", "SCHEMA_NAME", "SCOPE", "SCROLL", "SEARCH", "SECOND", "SECTION", "SECURITY", "SELECT", + "SELF", "SENSITIVE", "SEQUENCE", "SERIALIZABLE", "SERVER_NAME", "SESSION", "SESSION_USER", "SET", "SETOF", "SETS", "SHARE", "SHOW", "SIMILAR", "SIMPLE", "SIZE", "SMALLINT", + "SOME", "SPACE", "SPECIFIC", "SPECIFICTYPE", "SPECIFIC_NAME", "SQL", "SQLCODE", "SQLERROR", "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", "STABLE", "START", + "STATEMENT", "STATIC", "STATISTICS", "STDIN", "STDOUT", "STORAGE", "STRICT", "STRUCTURE", "STYLE", "SUBCLASS_ORIGIN", "SUBLIST", "SUBSTRING", "SUM", "SYMMETRIC", "SYSID", + "SYSTEM", "SYSTEM_USER", "TABLE", "TABLE_NAME", "TEMP", "TEMPLATE", "TEMPORARY", "TERMINATE", "THAN", "THEN", "TIME", "TIMESTAMP", "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TO", + "TOAST", "TRAILING", "TRANSACTION", "TRANSACTIONS_COMMITTED", "TRANSACTIONS_ROLLED_BACK", "TRANSACTION_ACTIVE", "TRANSFORM", "TRANSFORMS", "TRANSLATE", "TRANSLATION", "TREAT", + "TRIGGER", "TRIGGER_CATALOG", "TRIGGER_SCHEMA", "TRIM", "TRUE", "TRUNCATE", "TRUSTED", "UNCOMMITTED", "UNDER", "UNENCRYPTED", "UNION", "UNIQUE", + "UNKNOWN", "UNLISTEN", "UNNAMED", "UNNEST", "UNTIL", "UPDATE", "UPPER", "USAGE", "USER", "USER_DEFINED_TYPE_CATALOG", "USER_DEFINED_TYPE_NAME", "USER_DEFINED_TYPE_SCHEMA", + "USING", "VACUUM", "VALID", "VALIDATOR", "VALUES", "VARCHAR", "VARIABLE", "VARYING", "VERBOSE", "VERSION", "VIEW", "VOLATILE", "WHEN", "WHENEVER", "WHERE", "WITH", + "WITHOUT", "WORK", "WRITE", "XMAX", "XMIN", "YEAR", "ZONE"); + } + + public override bool TableNameNeedsQuote => false; + + public override bool ConstraintNameNeedsQuote => false; + + public override bool IdentityNeedsType => false; + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) + { + return new PostgreSQLTransformationProvider(dialect, connectionString, defaultSchema, scope, providerName); + } + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, string defaultSchema, string scope, string providerName) + { + return new PostgreSQLTransformationProvider(dialect, connection, defaultSchema, scope, providerName); + } + + public override ColumnPropertiesMapper GetColumnMapper(Column column) + { + var type = column.Size > 0 ? GetTypeName(column.Type, column.Size) : GetTypeName(column.Type); + + if (column.Precision.HasValue || column.Scale.HasValue) + { + type = GetTypeNameParametrized(column.Type, column.Size, column.Precision ?? 0, column.Scale ?? 0); + } + + return new ColumnPropertiesMapper(this, type); + } + + public override string Default(object defaultValue) + { + if (defaultValue is TimeSpan timeSpan) + { + var intervalPostgreNotation = $"{(int)timeSpan.TotalHours:D2}:{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}.{timeSpan.Milliseconds:D3}"; + + return $"DEFAULT '{intervalPostgreNotation}'"; + } + else if (defaultValue is byte[] byteArray) + { + var convertedString = BitConverter.ToString(byteArray).Replace("-", "").ToLower(); + return @$"DEFAULT E'\\x{convertedString}'"; + } + else if (defaultValue is DateTimeOffset offset) + { + var convertedString = offset.ToString("yyyy-MM-dd HH:mm:ss.fffzzz"); + return @$"DEFAULT '{convertedString}'"; + } + + return base.Default(defaultValue); + } + + //public override string SqlForProperty(ColumnProperty property, Column column) + //{ + // if (property == ColumnProperty.Identity && (column.Type == DbType.Int64 || column.Type == DbType.UInt32 || column.Type == DbType.UInt64)) + // return "bigserial"; + // return base.SqlForProperty(property, column); + //} +} diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs new file mode 100644 index 00000000..1f55e5c3 --- /dev/null +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs @@ -0,0 +1,1035 @@ +#region License + +//The contents of this file are subject to the Mozilla Public License +//Version 1.1 (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.mozilla.org/MPL/ +//Software distributed under the License is distributed on an "AS IS" +//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +//License for the specific language governing rights and limitations +//under the License. + +#endregion + +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Framework.Models; +using DotNetProjects.Migrator.Providers.Impl.PostgreSQL.Data; +using DotNetProjects.Migrator.Providers.Impl.PostgreSQL.Data.Interfaces; +using DotNetProjects.Migrator.Providers.Impl.PostgreSQL.Interfaces; +using DotNetProjects.Migrator.Providers.Models.Indexes; +using DotNetProjects.Migrator.Providers.Models.Indexes.Enums; +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; +using Index = DotNetProjects.Migrator.Framework.Index; + +namespace DotNetProjects.Migrator.Providers.Impl.PostgreSQL; + +/// +/// Migration transformations provider for PostgreSql (using NPGSql .Net driver) +/// +public class PostgreSQLTransformationProvider : TransformationProvider, IPostgreSQLTransformationProvider +{ + private Regex stripSingleQuoteRegEx = new("(?<=')[^']*(?=')"); + private IPostgreSQLSystemDataLoader _postgreSQLSystemDataLoader; + + public PostgreSQLTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) + : base(dialect, connectionString, defaultSchema, scope) + { + Initialize(); + + if (string.IsNullOrEmpty(providerName)) + { + providerName = "Npgsql"; + } + + var fac = DbProviderFactoriesHelper.GetFactory(providerName, "Npgsql", "Npgsql.NpgsqlFactory"); + _connection = fac.CreateConnection(); //new NpgsqlConnection(); + _connection.ConnectionString = _connectionString; + _connection.Open(); + } + + public PostgreSQLTransformationProvider(Dialect dialect, IDbConnection connection, string defaultSchema, string scope, string providerName) + : base(dialect, connection, defaultSchema, scope) + { + Initialize(); + } + + protected override string GetPrimaryKeyConstraintName(string table) + { + using var cmd = CreateCommand(); + using var reader = + ExecuteQuery(cmd, string.Format("SELECT conname FROM pg_constraint WHERE contype = 'p' AND conrelid = (SELECT oid FROM pg_class WHERE relname = lower('{0}'));", table)); + + return reader.Read() ? reader.GetString(0) : null; + } + + public override string AddIndex(string table, Index index) + { + ValidateIndex(tableName: table, index: index); + + var hasIncludedColumns = index.IncludeColumns != null && index.IncludeColumns.Length > 0; + var name = QuoteConstraintNameIfRequired(index.Name); + table = QuoteTableNameIfRequired(table); + var columns = QuoteColumnNamesIfRequired(index.KeyColumns); + + var uniqueString = index.Unique ? "UNIQUE" : null; + var columnsString = $"({string.Join(", ", columns)})"; + var filterString = string.Empty; + var includeString = string.Empty; + + if (index.IncludeColumns != null && index.IncludeColumns.Length > 0) + { + var includeColumnsQuoted = index.IncludeColumns.Select(x => QuoteColumnNameIfRequired(x)).ToList(); + + includeString = $"INCLUDE ({string.Join(", ", includeColumnsQuoted)})"; + } + + if (index.FilterItems != null && index.FilterItems.Count > 0) + { + List singleFilterStrings = []; + + foreach (var filterItem in index.FilterItems) + { + var comparisonString = _dialect.GetComparisonStringByFilterType(filterItem.Filter); + + var filterColumnQuoted = QuoteColumnNameIfRequired(filterItem.ColumnName); + string value = null; + + value = filterItem.Value switch + { + bool booleanValue => booleanValue ? "TRUE" : "FALSE", + string stringValue => $"'{stringValue}'", + byte or short or int or long => Convert.ToInt64(filterItem.Value).ToString(), + sbyte or ushort or uint or ulong => Convert.ToUInt64(filterItem.Value).ToString(), + _ => throw new NotImplementedException($"Given type in '{nameof(FilterItem)}' is not implemented. Please file an issue."), + }; + + var singleFilterString = $"{filterColumnQuoted} {comparisonString} {value}"; + + singleFilterStrings.Add(singleFilterString); + } + + filterString = $"WHERE {string.Join(" AND ", singleFilterStrings)}"; + } + + List list = []; + list.Add("CREATE"); + list.Add(uniqueString); + list.Add("INDEX"); + list.Add(name); + list.Add("ON"); + list.Add(table); + list.Add(columnsString); + list.Add(filterString); + list.Add(includeString); + + var sql = string.Join(" ", list.Where(x => !string.IsNullOrWhiteSpace(x))); + + ExecuteNonQuery(sql); + + return sql; + } + + public override Index[] GetIndexes(string table) + { + var columns = GetColumns(table); + + // Since the migrator does not support schemas at this point in time we set the schema to "public" + var schemaName = "public"; + + var indexes = new List(); + + var sql = @$" + SELECT + nsp.nspname AS schema_name, + tbl.relname AS table_name, + cls.relname AS index_name, + idx.indisunique AS is_unique, + idx.indisclustered AS is_clustered, + con.contype = 'u' AS is_unique_constraint, + con.contype = 'p' AS is_primary_constraint, + pg_get_indexdef(idx.indexrelid) AS index_definition, + ( + SELECT string_agg(att.attname, ', ') + FROM unnest(idx.indkey) WITH ORDINALITY AS cols(attnum, ord) + JOIN pg_attribute att + ON att.attrelid = idx.indrelid + AND att.attnum = cols.attnum + WHERE cols.ord <= idx.indnkeyatts + ) AS index_columns, + ( + SELECT string_agg(att.attname, ', ') + FROM unnest(idx.indkey) WITH ORDINALITY AS cols(attnum, ord) + JOIN pg_attribute att + ON att.attrelid = idx.indrelid + AND att.attnum = cols.attnum + WHERE cols.ord > idx.indnkeyatts + ) AS include_columns, + pg_get_expr(idx.indpred, idx.indrelid) AS partial_filter + FROM pg_index idx + JOIN pg_class cls ON cls.oid = idx.indexrelid + JOIN pg_class tbl ON tbl.oid = idx.indrelid + JOIN pg_namespace nsp ON nsp.oid = tbl.relnamespace + LEFT JOIN pg_constraint con ON con.conindid = idx.indexrelid + WHERE + lower(tbl.relname) = '{table.ToLowerInvariant()}' AND + nsp.nspname = '{schemaName}'"; + + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, string.Format(sql, table))) + { + var includeColumnsOrdinal = reader.GetOrdinal("include_columns"); + var indexColumnsOrdinal = reader.GetOrdinal("index_columns"); + var indexDefinitionOrdinal = reader.GetOrdinal("index_definition"); + var indexNameOrdinal = reader.GetOrdinal("index_name"); + var isClusteredOrdinal = reader.GetOrdinal("is_clustered"); + var isPrimaryConstraintOrdinal = reader.GetOrdinal("is_primary_constraint"); + var isUniqueConstraintOrdinal = reader.GetOrdinal("is_unique_constraint"); + var isUniqueOrdinal = reader.GetOrdinal("is_unique"); + var partialFilterOrdinal = reader.GetOrdinal("partial_filter"); + var schemaNameOrdinal = reader.GetOrdinal("schema_name"); + var tableNameOrdinal = reader.GetOrdinal("table_name"); + + while (reader.Read()) + { + if (!reader.IsDBNull(1)) + { + var includeColumns = !reader.IsDBNull(includeColumnsOrdinal) ? reader.GetString(includeColumnsOrdinal) : null; + var indexColumns = !reader.IsDBNull(indexColumnsOrdinal) ? reader.GetString(indexColumnsOrdinal) : null; + var indexDefinition = reader.GetString(indexDefinitionOrdinal); + var partialColumns = !reader.IsDBNull(partialFilterOrdinal) ? reader.GetString(partialFilterOrdinal) : null; + List filterItems = []; + + if (!string.IsNullOrWhiteSpace(partialColumns)) + { + partialColumns = partialColumns.Substring(1, partialColumns.Length - 2); + var comparisonStrings = _dialect.GetComparisonStrings(); + var partialSplitted = Regex.Split(partialColumns, " AND ").Select(x => x.Trim()).ToList(); + + if (partialSplitted.Count > 1) + { + partialSplitted = partialSplitted.Select(x => x.Substring(1, x.Length - 2)).ToList(); + } + + foreach (var partialItemString in partialSplitted) + { + string[] splits = []; + var filterType = FilterType.None; + + foreach (var comparisonString in comparisonStrings.OrderByDescending(x => x)) + { + splits = Regex.Split(partialItemString, $" {comparisonString} "); + + if (splits.Length == 2) + { + filterType = _dialect.GetFilterTypeByComparisonString(comparisonString); + break; + } + } + + if (splits.Length != 2) + { + throw new NotImplementedException($"Comparison string not found in '{partialItemString}'"); + } + + var columnNameString = splits[0]; + var columnNameRegex = new Regex(@"(?<=^\().+(?=\)::(text|boolean|integer)$)"); + + if (columnNameRegex.Match(columnNameString) is Match matchColumnName && matchColumnName.Success) + { + columnNameString = matchColumnName.Value; + } + + var column = columns.First(x => columnNameString.Equals(x.Name, StringComparison.OrdinalIgnoreCase)); + var valueAsString = splits[1]; + var stringValueNumericRegex = new Regex(@"(?<=^\()[^\)]+(?=\)::numeric$)"); + + if (stringValueNumericRegex.Match(valueAsString) is Match valueNumericMatch && valueNumericMatch.Success) + { + valueAsString = valueNumericMatch.Value; + } + + var stringValueRegex = new Regex("(?<=^').+(?='::(text|boolean|integer|bigint)$)"); + + if (stringValueRegex.Match(valueAsString) is Match match && match.Success) + { + valueAsString = match.Value; + } + + var filterItem = new FilterItem + { + ColumnName = column.Name, + Filter = filterType, + Value = column.MigratorDbType switch + { + MigratorDbType.Int16 => short.Parse(valueAsString), + MigratorDbType.Int32 => int.Parse(valueAsString), + MigratorDbType.Int64 => long.Parse(valueAsString), + MigratorDbType.UInt16 => ushort.Parse(valueAsString), + MigratorDbType.UInt32 => uint.Parse(valueAsString), + MigratorDbType.UInt64 => ulong.Parse(valueAsString), + MigratorDbType.Decimal => decimal.Parse(valueAsString), + MigratorDbType.Boolean => valueAsString == "1" || valueAsString.Equals("true", StringComparison.OrdinalIgnoreCase), + MigratorDbType.String => valueAsString, + _ => throw new NotImplementedException($"Type '{column.MigratorDbType}' not yet supported - there are many variations. Please file an issue."), + } + }; + + filterItems.Add(filterItem); + } + } + + var index = new Index + { + Clustered = !reader.IsDBNull(isClusteredOrdinal) && reader.GetBoolean(isClusteredOrdinal), + FilterItems = filterItems, + IncludeColumns = !string.IsNullOrWhiteSpace(includeColumns) ? [.. includeColumns.Split(',').Select(x => x.Trim())] : null, + KeyColumns = !string.IsNullOrWhiteSpace(indexColumns) ? [.. indexColumns.Split(',').Select(x => x.Trim())] : null, + Name = reader.GetString(indexNameOrdinal), + PrimaryKey = !reader.IsDBNull(isPrimaryConstraintOrdinal) && reader.GetBoolean(isPrimaryConstraintOrdinal), + Unique = !reader.IsDBNull(isUniqueOrdinal) && reader.GetBoolean(isUniqueOrdinal), + UniqueConstraint = !reader.IsDBNull(isUniqueConstraintOrdinal) && reader.GetBoolean(isUniqueConstraintOrdinal), + }; + + indexes.Add(index); + } + } + } + + return [.. indexes]; + } + + public override void RemoveTable(string name) + { + if (!TableExists(name)) + { + throw new MigrationException(string.Format("Table with name '{0}' does not exist to rename", name)); + } + + ExecuteNonQuery(string.Format("DROP TABLE IF EXISTS {0} CASCADE", name)); + } + + public override bool ConstraintExists(string table, string name) + { + using var cmd = CreateCommand(); + using var reader = + ExecuteQuery(cmd, string.Format("SELECT constraint_name FROM information_schema.table_constraints WHERE table_schema = 'public' AND constraint_name = lower('{0}')", name)); + + return reader.Read(); + } + + public override bool ColumnExists(string table, string column) + { + if (!TableExists(table)) + { + return false; + } + + using var cmd = CreateCommand(); + using var reader = + ExecuteQuery(cmd, string.Format("SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = lower('{0}') AND (column_name = lower('{1}') OR column_name = '{1}')", table, column)); + return reader.Read(); + } + + public override bool TableExists(string table) + { + using var cmd = CreateCommand(); + using var reader = + ExecuteQuery(cmd, string.Format("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = lower('{0}')", table)); + return reader.Read(); + } + + public override bool ViewExists(string view) + { + using var cmd = CreateCommand(); + using var reader = + ExecuteQuery(cmd, string.Format("SELECT table_name FROM information_schema.views WHERE table_schema = 'public' AND table_name = lower('{0}')", view)); + + return reader.Read(); + } + + public override List GetDatabases() + { + return ExecuteStringQuery("SELECT datname FROM pg_database WHERE datistemplate = false"); + } + + public override void ChangeColumn(string table, Column column) + { + var oldColumn = GetColumnByName(table, column.Name); + + var isUniqueSet = column.ColumnProperty.IsSet(ColumnProperty.Unique); + + column.ColumnProperty = column.ColumnProperty.Clear(ColumnProperty.Unique); + + var mapper = _dialect.GetAndMapColumnProperties(column); + + var change1 = string.Format("{0} TYPE {1}", QuoteColumnNameIfRequired(mapper.Name), mapper.Type); + + if ( + (oldColumn.MigratorDbType == MigratorDbType.Int16 || + oldColumn.MigratorDbType == MigratorDbType.Int32 || + oldColumn.MigratorDbType == MigratorDbType.Int64 || + oldColumn.MigratorDbType == MigratorDbType.Decimal) && + column.MigratorDbType == MigratorDbType.Boolean) + { + change1 += string.Format(" USING CASE {0} WHEN 1 THEN true ELSE false END", QuoteColumnNameIfRequired(mapper.Name)); + } + else if (column.MigratorDbType == MigratorDbType.Boolean) + { + change1 += string.Format(" USING CASE {0} WHEN '1' THEN true ELSE false END", QuoteColumnNameIfRequired(mapper.Name)); + } + + ChangeColumn(table, change1); + + if (mapper.Default != null) + { + var change2 = string.Format("{0} SET {1}", QuoteColumnNameIfRequired(mapper.Name), _dialect.Default(mapper.Default)); + ChangeColumn(table, change2); + } + else + { + var change2 = string.Format("{0} DROP DEFAULT", QuoteColumnNameIfRequired(mapper.Name)); + ChangeColumn(table, change2); + } + + if (column.ColumnProperty.HasFlag(ColumnProperty.NotNull)) + { + var change3 = string.Format("{0} SET NOT NULL", QuoteColumnNameIfRequired(mapper.Name)); + ChangeColumn(table, change3); + } + else + { + var change3 = string.Format("{0} DROP NOT NULL", QuoteColumnNameIfRequired(mapper.Name)); + ChangeColumn(table, change3); + } + + if (isUniqueSet) + { + AddUniqueConstraint(string.Format("UX_{0}_{1}", table, column.Name), table, [column.Name]); + } + } + + public override void CreateDatabases(string databaseName) + { + ExecuteNonQuery(string.Format("CREATE DATABASE {0}", _dialect.Quote(databaseName))); + } + + public override void SwitchDatabase(string databaseName) + { + _connection.ChangeDatabase(_dialect.Quote(databaseName)); + } + + public override void DropDatabases(string databaseName) + { + ExecuteNonQuery(string.Format("DROP DATABASE {0}", _dialect.Quote(databaseName))); + } + + public override string[] GetTables() + { + var tables = new List(); + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'")) + { + while (reader.Read()) + { + tables.Add((string)reader[0]); + } + } + return [.. tables]; + } + + public override int GetColumnContentSize(string table, string columnName) + { + if (!TableExists(table)) + { + throw new Exception($"Table '{table}' not found."); + } + + if (!ColumnExists(table, columnName, true)) + { + throw new Exception($"Column '{columnName}' does not exist"); + } + + var column = GetColumnByName(table, columnName); + + if (column.MigratorDbType != MigratorDbType.String) + { + throw new Exception($"Column '{columnName}' in table {table} is not of type string"); + } + + var result = ExecuteScalar($"SELECT MAX(LENGTH({QuoteColumnNameIfRequired(columnName)})) FROM {QuoteTableNameIfRequired(table)}"); + + if (result == DBNull.Value) + { + return 0; + } + + return Convert.ToInt32(result); + } + + public override Column[] GetColumns(string table) + { + var columnInfos = _postgreSQLSystemDataLoader.GetColumnInfos(table, "public"); + var columns = new List(); + var tableConstraints = _postgreSQLSystemDataLoader.GetTableConstraints(table); + + foreach (var columnInfo in columnInfos) + { + var isNullable = columnInfo.IsNullable == "YES"; + var isIdentity = columnInfo.IsIdentity == "YES"; + var isPrimaryKey = tableConstraints.Any(x => x.ColumnName.Equals(columnInfo.ColumnName, StringComparison.OrdinalIgnoreCase) && x.ConstraintType == "PRIMARY KEY"); + + MigratorDbType dbType = 0; + int? precision = null; + int? scale = null; + int? size = null; + + if (new[] { "timestamptz", "timestamp with time zone" }.Contains(columnInfo.DataType)) + { + dbType = MigratorDbType.DateTimeOffset; + precision = columnInfo.DateTimePrecision; + } + else if (columnInfo.DataType == "double precision") + { + dbType = MigratorDbType.Double; + scale = columnInfo.NumericScale; + precision = columnInfo.NumericPrecision; + } + else if (columnInfo.DataType == "timestamp" || columnInfo.DataType == "timestamp without time zone") + { + // 6 is the maximum in PostgreSQL + if (columnInfo.DateTimePrecision > 5) + { + dbType = MigratorDbType.DateTime2; + } + else + { + dbType = MigratorDbType.DateTime; + } + + precision = columnInfo.DateTimePrecision; + } + else if (columnInfo.DataType == "smallint") + { + dbType = MigratorDbType.Int16; + } + else if (columnInfo.DataType == "integer") + { + dbType = MigratorDbType.Int32; + } + else if (columnInfo.DataType == "bigint") + { + dbType = MigratorDbType.Int64; + } + else if (columnInfo.DataType == "numeric") + { + dbType = MigratorDbType.Decimal; + precision = columnInfo.NumericPrecision; + scale = columnInfo.NumericScale; + } + else if (columnInfo.DataType == "real") + { + dbType = MigratorDbType.Single; + } + else if (columnInfo.DataType == "interval") + { + dbType = MigratorDbType.Interval; + } + else if (columnInfo.DataType == "money") + { + dbType = MigratorDbType.Currency; + } + else if (columnInfo.DataType == "date") + { + dbType = MigratorDbType.Date; + } + else if (columnInfo.DataType == "byte") + { + dbType = MigratorDbType.Binary; + } + else if (columnInfo.DataType == "uuid") + { + dbType = MigratorDbType.Guid; + } + else if (columnInfo.DataType == "xml") + { + dbType = MigratorDbType.Xml; + } + else if (columnInfo.DataType == "time") + { + dbType = MigratorDbType.Time; + } + else if (columnInfo.DataType == "boolean") + { + dbType = MigratorDbType.Boolean; + } + else if (columnInfo.DataType == "text" || columnInfo.DataType == "character varying") + { + dbType = MigratorDbType.String; + size = columnInfo.CharacterMaximumLength; + } + else if (columnInfo.DataType == "bytea") + { + dbType = MigratorDbType.Binary; + } + else if (columnInfo.DataType == "character" || columnInfo.DataType.StartsWith("character(")) + { + throw new NotSupportedException("Data type 'character' detected. 'character' is not supported. Use 'text' or 'character varying' instead."); + } + else + { + throw new NotImplementedException("The data type is not implemented. Please file an issue."); + } + + var column = new Column(columnInfo.ColumnName, dbType) + { + Precision = precision, + Scale = scale, + // Size should be nullable + Size = size ?? 0 + }; + + column.ColumnProperty |= isNullable ? ColumnProperty.Null : ColumnProperty.NotNull; + + if (isPrimaryKey) + { + column.ColumnProperty = column.ColumnProperty.Set(ColumnProperty.PrimaryKey); + } + + if (isIdentity) + { + column.ColumnProperty = column.ColumnProperty.Set(ColumnProperty.Identity); + } + + if (columnInfo.ColumnDefault != null) + { + if (column.MigratorDbType == MigratorDbType.Int16 || column.MigratorDbType == MigratorDbType.Int32 || column.MigratorDbType == MigratorDbType.Int64) + { + var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); + if (match.Success) + { + columnInfo.ColumnDefault = match.Value; + } + if (!columnInfo.ColumnDefault.Contains(table, StringComparison.OrdinalIgnoreCase)) + { + column.DefaultValue = long.Parse(columnInfo.ColumnDefault.ToString()); + } + } + else if (column.MigratorDbType == MigratorDbType.UInt16 || column.MigratorDbType == MigratorDbType.UInt32 || column.MigratorDbType == MigratorDbType.UInt64) + { + var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); + if (match.Success) + { + columnInfo.ColumnDefault = match.Value; + } + if (!columnInfo.ColumnDefault.Contains(table, StringComparison.OrdinalIgnoreCase)) + { + column.DefaultValue = ulong.Parse(columnInfo.ColumnDefault.ToString()); + } + } + else if (column.MigratorDbType == MigratorDbType.Double || column.MigratorDbType == MigratorDbType.Single) + { + var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); + if (match.Success) + { + columnInfo.ColumnDefault = match.Value; + } + if (!columnInfo.ColumnDefault.Contains(table, StringComparison.OrdinalIgnoreCase)) + { + column.DefaultValue = double.Parse(columnInfo.ColumnDefault.ToString(), CultureInfo.InvariantCulture); + } + } + else if (column.MigratorDbType == MigratorDbType.Interval) + { + if (columnInfo.ColumnDefault.StartsWith("'")) + { + var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); + + if (!match.Success) + { + throw new Exception("Postgre default value for interval: Single quotes around the interval string are expected."); + } + + column.DefaultValue = match.Value; + var splitted = match.Value.Split(':'); + if (splitted.Length != 3) + { + throw new NotImplementedException($"Cannot interpret {columnInfo.ColumnDefault} in column '{column.Name}' unexpected pattern."); + } + + var hours = int.Parse(splitted[0], CultureInfo.InvariantCulture); + var minutes = int.Parse(splitted[1], CultureInfo.InvariantCulture); + var splitted2 = splitted[2].Split('.'); + var seconds = int.Parse(splitted2[0], CultureInfo.InvariantCulture); + var milliseconds = int.Parse(splitted2[1], CultureInfo.InvariantCulture); + + column.DefaultValue = new TimeSpan(0, hours, minutes, seconds, milliseconds); + } + else + { + // We assume that the value was added using this migrator so we do not interpret things like '2 days 01:02:03' if you + // added such format you will run into this exception. + throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}' unexpected pattern."); + } + } + else if (column.MigratorDbType == MigratorDbType.Boolean) + { + var truthy = new[] { "TRUE", "YES", "'true'", "on", "'on'", "t", "'t'" }; + var falsy = new[] { "FALSE", "NO", "'false'", "off", "'off'", "f", "'f'" }; + + if (truthy.Any(x => x.Equals(columnInfo.ColumnDefault.Trim(), StringComparison.OrdinalIgnoreCase))) + { + column.DefaultValue = true; + } + else if (falsy.Any(x => x.Equals(columnInfo.ColumnDefault.Trim(), StringComparison.OrdinalIgnoreCase))) + { + column.DefaultValue = false; + } + else + { + throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'"); + } + } + else if (column.MigratorDbType == MigratorDbType.DateTime || column.MigratorDbType == MigratorDbType.DateTime2) + { + if (columnInfo.ColumnDefault.StartsWith("'")) + { + var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); + + if (!match.Success) + { + throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'"); + } + + var timeString = match.Value; + + // We convert to UTC since we restrict date time default values to UTC on default value definition. + var dateTimeExtracted = DateTime.ParseExact(timeString, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); + + column.DefaultValue = dateTimeExtracted; + } + else + { + throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'"); + } + } + else if (column.MigratorDbType == MigratorDbType.Guid) + { + if (columnInfo.ColumnDefault.StartsWith("'")) + { + var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); + + if (!match.Success) + { + throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'"); + } + + column.DefaultValue = Guid.Parse(match.Value); + } + else + { + throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'"); + } + } + else if (column.MigratorDbType == MigratorDbType.Decimal) + { + var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); + if (match.Success) + { + columnInfo.ColumnDefault = match.Value; + } + if (!columnInfo.ColumnDefault.Contains(table, StringComparison.OrdinalIgnoreCase)) + { + column.DefaultValue = decimal.Parse(columnInfo.ColumnDefault, CultureInfo.InvariantCulture); + } + } + else if (column.MigratorDbType == MigratorDbType.String) + { + if (columnInfo.ColumnDefault.StartsWith("'")) + { + var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); + + if (!match.Success) + { + throw new Exception("Postgre default value for date time: Single quotes around the date time string are expected."); + } + + column.DefaultValue = match.Value; + } + else + { + throw new NotImplementedException(); + } + } + else if (column.MigratorDbType == MigratorDbType.Binary) + { + if (columnInfo.ColumnDefault.StartsWith("'")) + { + var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); + + if (!match.Success) + { + throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'"); + } + + var singleQuoteString = match.Value; + + if (!singleQuoteString.StartsWith("\\x")) + { + throw new Exception(@"Postgre \x notation expected."); + } + + var hexString = singleQuoteString.Substring(2); + + // Not available in old .NET version: Convert.FromHexString(hexString); + + column.DefaultValue = Enumerable.Range(0, hexString.Length / 2) + .Select(x => Convert.ToByte(hexString.Substring(x * 2, 2), 16)) + .ToArray(); + } + else + { + throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'"); + } + } + else if (column.MigratorDbType == MigratorDbType.DateTimeOffset) + { + if (columnInfo.ColumnDefault.StartsWith("'")) + { + var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); + + if (!match.Success) + { + throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'"); + } + + var singleQuoteString = match.Value; + + // 1) Normalize "Z" at the end → "+00:00" + singleQuoteString = Regex.Replace(singleQuoteString, @"Z$", "+00:00"); + + // 2) Normalize offset at the end of the string + // Cases handled: + // +HH → +HH:00 + // +HHMM → +HH:MM + // +HH:MM → stays unchanged + // -HH / -HHMM → same logic + singleQuoteString = Regex.Replace( + singleQuoteString, + @"([+-])(\d{2})(?::?(\d{2}))?$", + m => + { + var sign = m.Groups[1].Value; // "+" or "-" + var hh = m.Groups[2].Value; // hours + var hasMm = m.Groups[3].Success; // minutes present? + var mm = hasMm ? m.Groups[3].Value : "00"; + return $"{sign}{hh}:{mm}"; + } + ); + + // 3) Parse using multiple possible formats + // Supports both space and "T" separator, with/without milliseconds + var formats = new[] + { + "yyyy-MM-dd HH:mm:ss.fffzzz", // space separator, with ms + "yyyy-MM-dd HH:mm:sszzz", // space separator, no ms + "yyyy-MM-ddTHH:mm:ss.fffzzz", // ISO8601, with ms + "yyyy-MM-ddTHH:mm:sszzz" // ISO8601, no ms + }; + + var dateTimeOffset = DateTimeOffset.ParseExact( + singleQuoteString, + formats, + CultureInfo.InvariantCulture, + DateTimeStyles.None + ); + + column.DefaultValue = dateTimeOffset; + } + else + { + throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'"); + } + } + else + { + throw new NotImplementedException($"{nameof(DbType)} {column.MigratorDbType} not implemented."); + } + } + + columns.Add(column); + } + + return columns.ToArray(); + } + + public override string[] GetConstraints(string table) + { + var constraints = new List(); + + using (var cmd = CreateCommand()) + using ( + var reader = + ExecuteQuery( + cmd, string.Format(@"select c.conname as constraint_name +from pg_constraint c +join pg_class t on c.conrelid = t.oid +where LOWER(t.relname) = LOWER('{0}')", table))) + { + while (reader.Read()) + { + constraints.Add(reader.GetString(0)); + } + } + + return constraints.ToArray(); + } + + public override Column GetColumnByName(string table, string columnName) + { + // Duplicate because of the lower case issue + return Array.Find(GetColumns(table), x => x.Name.Equals(columnName, StringComparison.OrdinalIgnoreCase) || x.Name == columnName); + } + + public override bool IndexExists(string table, string name) + { + using var cmd = CreateCommand(); + using var reader = + ExecuteQuery(cmd, string.Format("SELECT indexname FROM pg_catalog.pg_indexes WHERE indexname = lower('{0}')", name)); + + return reader.Read(); + } + + public override void UpdateTargetFromSource(string tableSourceNotQuoted, string tableTargetNotQuoted, ColumnPair[] fromSourceToTargetColumnPairs, ColumnPair[] conditionColumnPairs) + { + if (!TableExists(tableSourceNotQuoted)) + { + throw new Exception($"Table '{tableSourceNotQuoted}' given in '{nameof(tableSourceNotQuoted)}' does not exist"); + } + + if (!TableExists(tableTargetNotQuoted)) + { + throw new Exception($"Table '{tableTargetNotQuoted}' given in '{nameof(tableTargetNotQuoted)}' does not exist"); + } + + if (fromSourceToTargetColumnPairs.Length == 0) + { + throw new Exception($"{nameof(fromSourceToTargetColumnPairs)} is empty."); + } + + if (fromSourceToTargetColumnPairs.Any(x => string.IsNullOrWhiteSpace(x.ColumnNameSource) || string.IsNullOrWhiteSpace(x.ColumnNameTarget))) + { + throw new Exception($"One of the strings in {nameof(fromSourceToTargetColumnPairs)} is null or empty"); + } + + if (conditionColumnPairs.Length == 0) + { + throw new Exception($"{nameof(conditionColumnPairs)} is empty."); + } + + if (conditionColumnPairs.Any(x => string.IsNullOrWhiteSpace(x.ColumnNameSource) || string.IsNullOrWhiteSpace(x.ColumnNameTarget))) + { + throw new Exception($"One of the strings in {nameof(conditionColumnPairs)} is null or empty"); + } + + var tableNameSource = QuoteTableNameIfRequired(tableSourceNotQuoted); + var tableNameTarget = QuoteTableNameIfRequired(tableTargetNotQuoted); + + var assignStrings = fromSourceToTargetColumnPairs.Select(x => $"{QuoteColumnNameIfRequired(x.ColumnNameTarget)} = {tableNameSource}.{QuoteColumnNameIfRequired(x.ColumnNameSource)}").ToList(); + + var conditionStrings = conditionColumnPairs.Select(x => $"{tableNameSource}.{QuoteColumnNameIfRequired(x.ColumnNameSource)} = {tableNameTarget}.{QuoteColumnNameIfRequired(x.ColumnNameTarget)}"); + + var assignStringsJoined = string.Join(", ", assignStrings); + var conditionStringsJoined = string.Join(" AND ", conditionStrings); + + var sql = $"UPDATE {tableNameTarget} SET {assignStringsJoined} FROM {tableNameSource} WHERE {conditionStringsJoined}"; + ExecuteNonQuery(sql); + } + + public override void CopyDataFromTableToTable(string sourceTableName, List sourceColumnNames, string targetTableName, List targetColumnNames, List orderBySourceColumns = null) + { + orderBySourceColumns ??= []; + + if (!TableExists(sourceTableName)) + { + throw new Exception($"Source table '{QuoteTableNameIfRequired(sourceTableName)}' does not exist"); + } + + if (!TableExists(targetTableName)) + { + throw new Exception($"Target table '{QuoteTableNameIfRequired(targetTableName)}' does not exist"); + } + + var sourceColumnsConcatenated = sourceColumnNames.Concat(orderBySourceColumns); + + foreach (var column in sourceColumnsConcatenated) + { + if (!ColumnExists(sourceTableName, column)) + { + throw new Exception($"Column {column} in source table does not exist."); + } + } + + foreach (var column in targetColumnNames) + { + if (!ColumnExists(targetTableName, column)) + { + throw new Exception($"Column {column} in target table does not exist."); + } + } + + if (!orderBySourceColumns.All(x => sourceColumnNames.Contains(x))) + { + throw new Exception($"All columns in {nameof(orderBySourceColumns)} must be in {nameof(sourceColumnNames)}"); + } + + var sourceTableNameQuoted = QuoteTableNameIfRequired(sourceTableName); + var targetTableNameQuoted = QuoteTableNameIfRequired(targetTableName); + + var sourceColumnNamesQuoted = sourceColumnNames.Select(QuoteColumnNameIfRequired).ToList(); + var targetColumnNamesQuoted = targetColumnNames.Select(QuoteColumnNameIfRequired).ToList(); + var orderBySourceColumnsQuoted = orderBySourceColumns.Select(QuoteColumnNameIfRequired).ToList(); + + var sourceColumnsJoined = string.Join(", ", sourceColumnNamesQuoted); + var targetColumnsJoined = string.Join(", ", targetColumnNamesQuoted); + var orderBySourceColumnsJoined = string.Join(", ", orderBySourceColumnsQuoted); + + var orderByComponent = !string.IsNullOrWhiteSpace(orderBySourceColumnsJoined) ? $"ORDER BY {orderBySourceColumnsJoined}" : null; + + List sqlComponents = + [ + $"INSERT INTO {targetTableNameQuoted} ({targetColumnsJoined}) SELECT {sourceColumnsJoined} FROM {sourceTableNameQuoted}", + orderByComponent + ]; + + var sql = string.Join(" ", sqlComponents.Where(x => x != null)); + ExecuteNonQuery(sql); + } + + protected override void ConfigureParameterWithValue(IDbDataParameter parameter, int index, object value) + { + if (value is ushort) + { + parameter.DbType = DbType.Int32; + parameter.Value = Convert.ToInt32(value); + } + else if (value is uint) + { + parameter.DbType = DbType.Int64; + parameter.Value = Convert.ToInt64(value); + } + else + { + base.ConfigureParameterWithValue(parameter, index, value); + } + } + + private void Initialize() + { + _postgreSQLSystemDataLoader = new PostgreSQLSystemDataLoader(this); + } +} diff --git a/src/Migrator/Providers/Impl/SQLite/Models/ForeignKeyExtract.cs b/src/Migrator/Providers/Impl/SQLite/Models/ForeignKeyExtract.cs new file mode 100644 index 00000000..1cea29ab --- /dev/null +++ b/src/Migrator/Providers/Impl/SQLite/Models/ForeignKeyExtract.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace DotNetProjects.Migrator.Providers.Impl.SQLite.Models; + +public class ForeignKeyExtract +{ + /// + /// Gets or sets the complete foreign key string - CONSTRAINT MyFKName FOREIGN KEY (asdf, asdf) REFERENCES ParentTable(asdf,asdf) + /// + public string ForeignKeyString { get; set; } + + /// + /// Gets or sets the foreign key name + /// + public string ForeignKeyName { get; set; } + + /// + /// Gets or sets the child column names. + /// + public List ChildColumnNames { get; set; } + + /// + /// Gets or sets the parent column names. + /// + public List ParentColumnNames { get; set; } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/SQLite/Models/MappingInfo.cs b/src/Migrator/Providers/Impl/SQLite/Models/MappingInfo.cs new file mode 100644 index 00000000..23a9d9c0 --- /dev/null +++ b/src/Migrator/Providers/Impl/SQLite/Models/MappingInfo.cs @@ -0,0 +1,14 @@ +namespace DotNetProjects.Migrator.Providers.Impl.SQLite.Models; + +public class MappingInfo +{ + /// + /// Gets or sets the old name. + /// + public string OldName { get; set; } + + /// + /// Gets or sets the new name. + /// + public string NewName { get; set; } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/SQLite/Models/PragmaForeignKeyListItem.cs b/src/Migrator/Providers/Impl/SQLite/Models/PragmaForeignKeyListItem.cs new file mode 100644 index 00000000..76675428 --- /dev/null +++ b/src/Migrator/Providers/Impl/SQLite/Models/PragmaForeignKeyListItem.cs @@ -0,0 +1,47 @@ +namespace DotNetProjects.Migrator.Providers.Impl.SQLite.Models; + +/// +/// Represents a row of pragma_foreign_key_list() in SQLite. +/// +public class PragmaForeignKeyListItem +{ + /// + /// Gets or sets the foreign key id. Name: id + /// + public int Id { get; set; } + + /// + /// Gets or sets the sequence number of the foreign key. Name: seq + /// + public int Seq { get; set; } + + /// + /// Gets or sets the name of the referenced table. Name: table + /// + public string Table { get; set; } + + /// + /// Gets or sets the column in the current table that acts as the FK. Name: from + /// + public string From { get; set; } + + /// + /// Gets or sets the column in the referenced table. Name: to + /// + public string To { get; set; } + + /// + /// Gets or sets on update. Name: on_update + /// + public string OnUpdate { get; set; } + + /// + /// Gets or sets on delete. Name: on_delete + /// + public string OnDelete { get; set; } + + /// + /// Gets or sets match. Name: match + /// + public string Match { get; set; } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/SQLite/Models/PragmaIndexInfoItem.cs b/src/Migrator/Providers/Impl/SQLite/Models/PragmaIndexInfoItem.cs new file mode 100644 index 00000000..a091f574 --- /dev/null +++ b/src/Migrator/Providers/Impl/SQLite/Models/PragmaIndexInfoItem.cs @@ -0,0 +1,19 @@ +namespace DotNetProjects.Migrator.Providers.Impl.SQLite.Models; + +public class PragmaIndexInfoItem +{ + /// + /// Gets or sets the sequence number of the column in the index (zero-based) + /// + public int SeqNo { get; set; } + + /// + /// Gets or sets the column ID. -1 if expression + /// + public int Cid { get; set; } + + /// + /// Gets or sets the name of the column (expression if no not column related) + /// + public string Name { get; set; } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/SQLite/Models/PragmaIndexListItem.cs b/src/Migrator/Providers/Impl/SQLite/Models/PragmaIndexListItem.cs new file mode 100644 index 00000000..e95ac5f8 --- /dev/null +++ b/src/Migrator/Providers/Impl/SQLite/Models/PragmaIndexListItem.cs @@ -0,0 +1,14 @@ +namespace DotNetProjects.Migrator.Providers.Impl.SQLite.Models; + +public class PragmaIndexListItem +{ + public int Seq { get; set; } + + public string Name { get; set; } + + public bool Unique { get; set; } + + public string Origin { get; set; } + + public bool Partial { get; set; } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/SQLite/Models/PragmaTableInfoItem.cs b/src/Migrator/Providers/Impl/SQLite/Models/PragmaTableInfoItem.cs new file mode 100644 index 00000000..7684220b --- /dev/null +++ b/src/Migrator/Providers/Impl/SQLite/Models/PragmaTableInfoItem.cs @@ -0,0 +1,34 @@ +namespace DotNetProjects.Migrator.Providers.Impl.SQLite.Models; + +public class PragmaTableInfoItem +{ + /// + /// Gets or sets the column index (zero-based) + /// + public int Cid { get; set; } + + /// + /// Gets or sets the name of the column + /// + public string Name { get; set; } + + /// + /// Gets or sets the declared data type (INTEGER, TEXT, REAL etc.) + /// + public string Type { get; set; } + + /// + /// Gets or sets if is not null. + /// + public bool NotNull { get; set; } + + /// + /// Gets or sets the default value or NULL + /// + public object DfltValue { get; set; } + + /// + /// Gets or set the position in the primary key (1-based) 0 if not part of the primary key. + /// + public int Pk { get; set; } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/SQLite/Models/SQLiteTableInfo.cs b/src/Migrator/Providers/Impl/SQLite/Models/SQLiteTableInfo.cs new file mode 100644 index 00000000..c839b034 --- /dev/null +++ b/src/Migrator/Providers/Impl/SQLite/Models/SQLiteTableInfo.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.SQLite.Models; + +public class SQLiteTableInfo +{ + /// + /// Gets or sets the table name. + /// + public MappingInfo TableNameMapping { get; set; } + + /// + /// Gets or sets the columns of a table + /// + public List Columns { get; set; } = []; + + /// + /// Gets or sets the indexes of a table. + /// + public List Indexes { get; set; } = []; + + /// + /// Gets or sets the foreign keys of a table. + /// + public List ForeignKeys { get; set; } = []; + + /// + /// Gets or sets the column mappings. + /// + public List ColumnMappings { get; set; } = []; + + /// + /// Gets or sets the unique definitions. + /// + public List Uniques { get; set; } = []; + + /// + /// Gets or sets the check constraint definitions. + /// + public List CheckConstraints { get; set; } = []; +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/SQLite/SQLiteColumnPropertiesMapper.cs b/src/Migrator/Providers/Impl/SQLite/SQLiteColumnPropertiesMapper.cs new file mode 100644 index 00000000..442a7834 --- /dev/null +++ b/src/Migrator/Providers/Impl/SQLite/SQLiteColumnPropertiesMapper.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.SQLite; + +public class SQLiteColumnPropertiesMapper : ColumnPropertiesMapper +{ + public SQLiteColumnPropertiesMapper(Dialect dialect, string type) : base(dialect, type) + { + } + + protected override void AddNull(Column column, List vals) + { + var isPrimaryKeySelected = PropertySelected(column.ColumnProperty, ColumnProperty.PrimaryKey); + var isNullSelected = PropertySelected(column.ColumnProperty, ColumnProperty.Null); + var isNotNullSelected = PropertySelected(column.ColumnProperty, ColumnProperty.NotNull); + + if (isNullSelected || (!isNotNullSelected && !isPrimaryKeySelected)) + { + AddValueIfSelected(column, ColumnProperty.Null, vals); + } + } + + protected override void AddNotNull(Column column, List vals) + { + if (column.ColumnProperty.HasFlag(ColumnProperty.NotNull)) + { + AddValueIfSelected(column, ColumnProperty.NotNull, vals); + } + } + + protected virtual void AddValueIfSelected(Column column, ColumnProperty property, ICollection vals) + { + vals.Add(_Dialect.SqlForProperty(property, column)); + } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/SQLite/SQLiteCreateTableScriptReader.cs b/src/Migrator/Providers/Impl/SQLite/SQLiteCreateTableScriptReader.cs new file mode 100644 index 00000000..7fcf5145 --- /dev/null +++ b/src/Migrator/Providers/Impl/SQLite/SQLiteCreateTableScriptReader.cs @@ -0,0 +1,28 @@ +using System; +using System.Text.RegularExpressions; + +namespace DotNetProjects.Migrator.Providers.Impl.SQLite; + +public class SQLiteCreateTableScriptReader +{ + /// + /// Returns the content of the parenthesis. Ensures that the content between the first parenthesis and the last parenthesis is extracted. + /// + /// + /// + /// + public string GetParenthesisContent(string createTableScript) + { + // No GeneratedRegexAttribute due to old .NET version + var regEx = new Regex(@"(?<=\()[\s\S]*(?=\)(?![\s\S]*\)))"); + + var match = regEx.Match(createTableScript); + + if (!match.Success) + { + throw new Exception("Cannot parse script"); + } + + return match.Value; + } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Impl/SQLite/SQLiteDialect.cs b/src/Migrator/Providers/Impl/SQLite/SQLiteDialect.cs new file mode 100644 index 00000000..d4036da3 --- /dev/null +++ b/src/Migrator/Providers/Impl/SQLite/SQLiteDialect.cs @@ -0,0 +1,102 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.SQLite; + +public class SQLiteDialect : Dialect +{ + public SQLiteDialect() + { + RegisterColumnType(DbType.Binary, "BINARY"); + RegisterColumnType(DbType.Byte, "TINYINT"); + RegisterColumnType(DbType.Int16, "SMALLINT"); + RegisterColumnType(DbType.Int32, "INTEGER"); + RegisterColumnType(DbType.Int64, "INTEGER"); + RegisterColumnType(DbType.SByte, "INTEGER"); + RegisterColumnType(DbType.UInt16, "INTEGER"); + RegisterColumnType(DbType.UInt32, "INTEGER"); + RegisterColumnType(DbType.UInt64, "INTEGER"); + RegisterColumnType(MigratorDbType.Interval, "INTEGER"); + + RegisterColumnType(DbType.Currency, "CURRENCY"); + RegisterColumnType(DbType.Decimal, "DECIMAL"); + RegisterColumnType(DbType.Double, "DOUBLE"); + RegisterColumnType(DbType.Single, "REAL"); + RegisterColumnType(DbType.VarNumeric, "NUMERIC"); + + RegisterColumnType(DbType.String, "TEXT"); + RegisterColumnType(DbType.StringFixedLength, "TEXT"); + RegisterColumnType(DbType.AnsiString, "TEXT"); + RegisterColumnType(DbType.AnsiStringFixedLength, "TEXT"); + + RegisterColumnType(DbType.Date, "DATE"); + RegisterColumnType(DbType.DateTime, "DATETIME"); + RegisterColumnType(DbType.DateTime2, "DATETIME"); + RegisterColumnType(DbType.DateTimeOffset, "TEXT"); + RegisterColumnType(DbType.Time, "TIME"); + RegisterColumnType(DbType.Boolean, "BOOLEAN"); // Important for Dapper to know it should map to a bool + RegisterColumnType(DbType.Guid, "UNIQUEIDENTIFIER"); + + RegisterProperty(ColumnProperty.Identity, "AUTOINCREMENT"); + RegisterProperty(ColumnProperty.CaseSensitive, "COLLATE NOCASE"); + + AddReservedWords("ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ANALYZE", "AND", "AS", "ASC", "ATTACH", + "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "BY", "CASCADE", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", + "COMMIT", "CONFLICT", "CONSTRAINT", "CREATE", "CROSS", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", + "DATABASE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DELETE", "DESC", "DETACH", "DISTINCT", "DROP", "EACH", "ELSE", + "END", "ESCAPE", "EXCEPT", "EXCLUSIVE", "EXISTS", "EXPLAIN", "FAIL", "FOR", "FOREIGN", "FROM", "FULL", "GLOB", + "GROUP", "HAVING", "IF", "IGNORE", "IMMEDIATE", "IN", "INDEX", "INDEXED", "INITIALLY", "INNER", "INSERT", "INSTEAD", + "INTERSECT", "INTO", "IS", "ISNULL", "JOIN", "KEY", "LEFT", "LIKE", "LIMIT", "MATCH", "NATURAL", "NO", "NOT", + "NOTNULL", "NULL", "OF", "OFFSET", "ON", "OR", "ORDER", "OUTER", "PLAN", "PRAGMA", "PRIMARY", "QUERY", "RAISE", + "RECURSIVE", "REFERENCES", "REGEXP", "REINDEX", "RELEASE", "RENAME", "REPLACE", "RESTRICT", "RIGHT", "ROLLBACK", + "ROW", "SAVEPOINT", "SELECT", "SET", "TABLE", "TEMP", "TEMPORARY", "THEN", "TO", "TRANSACTION", "TRIGGER", "UNION", + "UNIQUE", "UPDATE", "USING", "VACUUM", "VALUES", "VIEW", "VIRTUAL", "WHEN", "WHERE", "WITH", "WITHOUT" + ); + } + + public override string QuoteTemplate => "\"{0}\""; + + public override string Default(object defaultValue) + { + if (defaultValue is bool) + { + return string.Format("DEFAULT {0}", (bool)defaultValue ? "1" : "0"); + } + + return base.Default(defaultValue); + } + + public override bool NeedsNotNullForIdentity + { + get { return false; } + } + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) + { + return new SQLiteTransformationProvider(dialect, connectionString, scope, providerName); + } + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, string defaultSchema, + string scope, string providerName) + { + return new SQLiteTransformationProvider(dialect, connection, scope, providerName); + } + + public override ColumnPropertiesMapper GetColumnMapper(Column column) + { + // Copied from base + var type = column.Size > 0 ? GetTypeName(column.Type, column.Size) : GetTypeName(column.Type); + + if (column.Precision.HasValue || column.Scale.HasValue) + { + type = GetTypeNameParametrized(column.Type, column.Size, column.Precision ?? 0, column.Scale ?? 0); + } + + if (!IdentityNeedsType && column.IsIdentity) + { + type = string.Empty; + } + + return new SQLiteColumnPropertiesMapper(this, type); + } +} diff --git a/src/Migrator/Providers/Impl/SQLite/SQLiteMonoDialect.cs b/src/Migrator/Providers/Impl/SQLite/SQLiteMonoDialect.cs new file mode 100644 index 00000000..1efaf015 --- /dev/null +++ b/src/Migrator/Providers/Impl/SQLite/SQLiteMonoDialect.cs @@ -0,0 +1,11 @@ +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.SQLite; + +public class SQLiteMonoDialect : SQLiteDialect +{ + public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) + { + return new SQLiteMonoTransformationProvider(dialect, connectionString, scope, providerName); + } +} diff --git a/src/Migrator/Providers/Impl/SQLite/SQLiteMonoTransformationProvider.cs b/src/Migrator/Providers/Impl/SQLite/SQLiteMonoTransformationProvider.cs new file mode 100644 index 00000000..442ce083 --- /dev/null +++ b/src/Migrator/Providers/Impl/SQLite/SQLiteMonoTransformationProvider.cs @@ -0,0 +1,33 @@ +using System.Data; + +namespace DotNetProjects.Migrator.Providers.Impl.SQLite; + +/// +/// Summary description for SQLiteTransformationProvider. +/// +public class SQLiteMonoTransformationProvider : SQLiteTransformationProvider +{ + public SQLiteMonoTransformationProvider(Dialect dialect, string connectionString, string scope, string providerName) + : base(dialect, connectionString, scope, providerName) + { + + } + + public SQLiteMonoTransformationProvider(Dialect dialect, IDbConnection connection, string scope, string providerName) + : base(dialect, connection, scope, providerName) + { + } + + protected override void CreateConnection(string providerName) + { + if (string.IsNullOrEmpty(providerName)) + { + providerName = "Mono.Data.Sqlite"; + } + + var fac = DbProviderFactoriesHelper.GetFactory(providerName, "Mono.Data.Sqlite", "Mono.Data.Sqlite.SQLiteFactory"); + _connection = fac.CreateConnection(); // new SQLiteConnection(_connectionString); + _connection.ConnectionString = _connectionString; + _connection.Open(); + } +} diff --git a/src/Migrator/Providers/Impl/SQLite/SQLiteTransformationProvider.cs b/src/Migrator/Providers/Impl/SQLite/SQLiteTransformationProvider.cs new file mode 100644 index 00000000..ab80622b --- /dev/null +++ b/src/Migrator/Providers/Impl/SQLite/SQLiteTransformationProvider.cs @@ -0,0 +1,1932 @@ +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Impl.SQLite.Models; +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using ForeignKeyConstraint = DotNetProjects.Migrator.Framework.ForeignKeyConstraint; +using Index = DotNetProjects.Migrator.Framework.Index; +using DotNetProjects.Migrator.Framework.Extensions; +using DotNetProjects.Migrator.Providers.Models.Indexes; +using DotNetProjects.Migrator.Providers.Models.Indexes.Enums; +using DotNetProjects.Migrator.Framework.Models; + +namespace DotNetProjects.Migrator.Providers.Impl.SQLite; + +/// +/// Summary description for SQLiteTransformationProvider. +/// +public partial class SQLiteTransformationProvider : TransformationProvider +{ + private const string IntermediateTableSuffix = "Temp"; + + public SQLiteTransformationProvider(Dialect dialect, string connectionString, string scope, string providerName) + : base(dialect, connectionString, null, scope) + { + CreateConnection(providerName); + } + + public SQLiteTransformationProvider(Dialect dialect, IDbConnection connection, string scope, string providerName) + : base(dialect, connection, null, scope) + { + } + + protected virtual void CreateConnection(string providerName) + { + if (string.IsNullOrEmpty(providerName)) + { + providerName = "System.Data.SQLite"; + } + + var fac = DbProviderFactoriesHelper.GetFactory(providerName, "System.Data.SQLite", "System.Data.SQLite.SQLiteFactory"); + _connection = fac.CreateConnection(); // new SQLiteConnection(_connectionString); + _connection.ConnectionString = _connectionString; + _connection.Open(); + } + + public override void AddForeignKey( + string name, + string childTable, + string[] childColumns, + string parentTable, + string[] parentColumns, + ForeignKeyConstraintType constraint) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new Exception("The foreign key name is mandatory"); + } + + var sqliteTableInfo = GetSQLiteTableInfo(childTable); + + // Get all unique constraint names if available + var uniqueConstraintNames = sqliteTableInfo.Uniques.Select(x => x.Name).ToList(); + + // Get all FK constraint names if available + var foreignKeyNames = sqliteTableInfo.ForeignKeys.Select(x => x.Name).ToList(); + + var names = uniqueConstraintNames.Concat(foreignKeyNames) + .Distinct() + .Where(x => !string.IsNullOrWhiteSpace(x)) + .ToList(); + + if (names.Any(x => x.Equals(name, StringComparison.OrdinalIgnoreCase))) + { + throw new Exception($"Constraint name {name} already exists"); + } + + var foreignKey = new ForeignKeyConstraint + { + ChildColumns = childColumns, + ChildTable = childTable, + Name = name, + ParentColumns = parentColumns, + ParentTable = parentTable, + }; + + sqliteTableInfo.ForeignKeys + .Add(foreignKey); + + RecreateTable(sqliteTableInfo); + } + + public string[] GetColumnDefs(string table, out string compositeDefSql) + { + return ParseSqlColumnDefs(GetSqlCreateTableScript(table), out compositeDefSql); + } + + /// + /// Gets the SQL CREATE TABLE script. Case-insensitive + /// + /// + /// + public string GetSqlCreateTableScript(string table) + { + string sqlCreateTableScript = null; + + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, string.Format("SELECT sql FROM sqlite_master WHERE type='table' AND lower(name)=lower('{0}')", table))) + { + if (reader.Read()) + { + sqlCreateTableScript = reader.IsDBNull(0) ? null : (string)reader[0]; + } + } + + return sqlCreateTableScript; + } + + public override ForeignKeyConstraint[] GetForeignKeyConstraints(string tableName) + { + List foreignKeyConstraints = []; + + var pragmaForeignKeyListItems = GetForeignKeyListItems(tableName); + var groups = pragmaForeignKeyListItems.GroupBy(x => x.Id); + + foreach (var group in groups) + { + var foreignKeyConstraint = new ForeignKeyConstraint + { + Id = group.First().Id, + // SQLite does not support FK names. + ChildColumns = group.OrderBy(x => x.Seq).Select(x => x.From).ToArray(), + ChildTable = tableName, + Match = group.First().Match, + Name = null, + OnDelete = group.First().OnDelete, + OnUpdate = group.First().OnUpdate, + ParentColumns = group.OrderBy(x => x.Seq).Select(x => x.To).ToArray(), + ParentTable = group.First().Table, + }; + + foreignKeyConstraints.Add(foreignKeyConstraint); + } + + if (foreignKeyConstraints.Count == 0) + { + return []; + } + + var createTableScript = GetSqlCreateTableScript(tableName); + // GeneratedRegex + var regEx = new Regex(@"CONSTRAINT\s+\w+\s+FOREIGN\s+KEY\s*\([^)]+\)\s+REFERENCES\s+[\w""]+\s*\([^)]+\)"); + var matchesCollection = regEx.Matches(createTableScript); + var fkParts = matchesCollection.Cast().ToList().Where(x => x.Success).Select(x => x.Value).ToList(); + + if (fkParts.Count != foreignKeyConstraints.Count) + { + throw new Exception($"Cannot extract all foreign keys out of the create table script in SQLite. Did you use a name as foreign key constraint for all constraints in table '{tableName}' in this or older migrations?"); + } + + List foreignKeyExtracts = []; + + foreach (var fkPart in fkParts) + { + var regexParenthesis = new Regex(@"\(([^)]+)\)"); + var parenthesisContents = regexParenthesis.Matches(fkPart).Cast().Select(x => x.Groups[1].Value).ToList(); + + if (parenthesisContents.Count != 2) + { + throw new Exception("Cannot extract parenthesis of foreign key constraint"); + } + + var foreignKeyExtract = new ForeignKeyExtract() + { + ChildColumnNames = parenthesisContents[0].Split(',').Select(x => x.Trim()).ToList(), + ForeignKeyString = fkPart, + ParentColumnNames = parenthesisContents[1].Split(',').Select(x => x.Trim()).ToList(), + }; + + var foreignKeyConstraintNameRegex = new Regex(@"CONSTRAINT\s+(\w+)\s+FOREIGN\s+KEY"); + var foreignKeyNameMatch = foreignKeyConstraintNameRegex.Match(fkPart); + + if (!foreignKeyNameMatch.Success) + { + throw new Exception("Could not extract the foreign key constraint name"); + } + + foreignKeyExtract.ForeignKeyName = foreignKeyNameMatch.Groups[1].Value; + + foreignKeyExtracts.Add(foreignKeyExtract); + } + + foreach (var foreignKeyConstraint in foreignKeyConstraints) + { + foreach (var foreignKeyExtract in foreignKeyExtracts) + { + if ( + foreignKeyExtract.ChildColumnNames.SequenceEqual(foreignKeyConstraint.ChildColumns) && + foreignKeyExtract.ParentColumnNames.SequenceEqual(foreignKeyConstraint.ParentColumns) + ) + { + foreignKeyConstraint.Name = foreignKeyExtract.ForeignKeyName; + } + } + } + + return foreignKeyConstraints.ToArray(); + } + + public override void UpdateTargetFromSource(string tableSourceNotQuoted, string tableTargetNotQuoted, ColumnPair[] fromSourceToTargetColumnPairs, ColumnPair[] conditionColumnPairs) + { + if (!TableExists(tableSourceNotQuoted)) + { + throw new Exception($"Table '{tableSourceNotQuoted}' given in '{nameof(tableSourceNotQuoted)}' does not exist"); + } + + if (!TableExists(tableTargetNotQuoted)) + { + throw new Exception($"Table '{tableTargetNotQuoted}' given in '{nameof(tableTargetNotQuoted)}' does not exist"); + } + + if (fromSourceToTargetColumnPairs.Length == 0) + { + throw new Exception($"{nameof(fromSourceToTargetColumnPairs)} is empty."); + } + + if (fromSourceToTargetColumnPairs.Any(x => string.IsNullOrWhiteSpace(x.ColumnNameSource) || string.IsNullOrWhiteSpace(x.ColumnNameTarget))) + { + throw new Exception($"One of the strings in {nameof(fromSourceToTargetColumnPairs)} is null or empty"); + } + + if (conditionColumnPairs.Length == 0) + { + throw new Exception($"{nameof(conditionColumnPairs)} is empty."); + } + + if (conditionColumnPairs.Any(x => string.IsNullOrWhiteSpace(x.ColumnNameSource) || string.IsNullOrWhiteSpace(x.ColumnNameTarget))) + { + throw new Exception($"One of the strings in {nameof(conditionColumnPairs)} is null or empty"); + } + + var tableNameSource = QuoteTableNameIfRequired(tableSourceNotQuoted); + var tableNameTarget = QuoteTableNameIfRequired(tableTargetNotQuoted); + + var assignStrings = fromSourceToTargetColumnPairs.Select(x => $"{QuoteColumnNameIfRequired(x.ColumnNameTarget)} = {tableNameSource}.{QuoteColumnNameIfRequired(x.ColumnNameSource)}").ToList(); + + var conditionStrings = conditionColumnPairs.Select(x => $"{tableNameSource}.{QuoteColumnNameIfRequired(x.ColumnNameSource)} = {tableNameTarget}.{QuoteColumnNameIfRequired(x.ColumnNameTarget)}"); + + var assignStringsJoined = string.Join(", ", assignStrings); + var conditionStringsJoined = string.Join(" AND ", conditionStrings); + + var sql = $"UPDATE {tableNameTarget} SET {assignStringsJoined} FROM {tableNameSource} WHERE {conditionStringsJoined}"; + ExecuteNonQuery(sql); + } + + private List GetForeignKeyListItems(string tableNameNotQuoted) + { + List pragmaForeignKeyListItems = []; + + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, $"PRAGMA foreign_key_list('{QuoteTableNameIfRequired(tableNameNotQuoted)}')")) + { + while (reader.Read()) + { + var pragmaForeignKeyListItem = new PragmaForeignKeyListItem + { + Id = reader.GetInt32(reader.GetOrdinal("id")), + Seq = reader.GetInt32(reader.GetOrdinal("seq")), + Table = reader.GetString(reader.GetOrdinal("table")), + From = reader.GetString(reader.GetOrdinal("from")), + To = reader.GetString(reader.GetOrdinal("to")), + OnUpdate = reader.GetString(reader.GetOrdinal("on_update")), + OnDelete = reader.GetString(reader.GetOrdinal("on_delete")), + Match = reader.GetString(reader.GetOrdinal("match")), + }; + + pragmaForeignKeyListItems.Add(pragmaForeignKeyListItem); + } + } + + return pragmaForeignKeyListItems; + } + + public string[] ParseSqlColumnDefs(string sqldef, out string compositeDefSql) + { + if (string.IsNullOrEmpty(sqldef)) + { + compositeDefSql = null; + + return null; + } + + sqldef = sqldef.Replace(Environment.NewLine, " "); + var start = sqldef.IndexOf("("); + + // Code to handle composite primary keys /mol + var compositeDefIndex = sqldef.IndexOf("PRIMARY KEY ("); // Not ideal to search for a string like this but I'm lazy + + if (compositeDefIndex > -1) + { + compositeDefSql = sqldef.Substring(compositeDefIndex, sqldef.LastIndexOf(")") - compositeDefIndex); + sqldef = sqldef.Substring(0, compositeDefIndex).TrimEnd(',', ' ') + ")"; + } + else + { + compositeDefSql = null; + } + + var end = sqldef.LastIndexOf(")"); // Changed from 'IndexOf' to 'LastIndexOf' to handle foreign key definitions /mol + + sqldef = sqldef.Substring(0, end); + sqldef = sqldef.Substring(start + 1); + + var cols = sqldef.Split([',']); + + for (var i = 0; i < cols.Length; i++) + { + cols[i] = cols[i].Trim(); + } + + return cols; + } + + /// + /// Turn something like 'columnName INTEGER NOT NULL' into just 'columnName' + /// + public string[] ParseSqlForColumnNames(string sqldef, out string compositeDefSql) + { + var parts = ParseSqlColumnDefs(sqldef, out compositeDefSql); + + return ParseSqlForColumnNames(parts); + } + + public string[] ParseSqlForColumnNames(string[] parts) + { + if (null == parts) + { + return null; + } + + for (var i = 0; i < parts.Length; i++) + { + parts[i] = ExtractNameFromColumnDef(parts[i]); + } + + return parts; + } + + /// + /// Name is the first value before the space. + /// + /// + /// + public static string ExtractNameFromColumnDef(string columnDef) + { + var idx = columnDef.IndexOf(" "); + + if (idx > 0) + { + return columnDef.Substring(0, idx); + } + return null; + } + + public DbType ExtractTypeFromColumnDef(string columnDef) + { + var idx = columnDef.IndexOf(" ") + 1; + + if (idx > 0) + { + var idy = columnDef.IndexOf(" ", idx) - idx; + + if (idy > 0) + { + return _dialect.GetDbType(columnDef.Substring(idx, idy)); + } + else + { + return _dialect.GetDbType(columnDef.Substring(idx)); + } + } + else + { + throw new Exception("Error extracting type from column definition: '" + columnDef + "'"); + } + } + + public override void RemoveForeignKey(string table, string name) + { + if (!TableExists(table)) + { + throw new MigrationException($"Table '{table}' does not exist."); + } + + var sqliteTableInfo = GetSQLiteTableInfo(table); + if (!sqliteTableInfo.ForeignKeys.Any(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase))) + { + throw new MigrationException($"Foreign key '{name}' does not exist."); + } + + sqliteTableInfo.ForeignKeys.RemoveAll(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); + + RecreateTable(sqliteTableInfo); + } + + public string[] GetCreateIndexSqlStrings(string table) + { + var sqlStrings = new List(); + + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, string.Format("SELECT sql FROM sqlite_master WHERE type='index' AND sql NOT NULL AND lower(tbl_name)=lower('{0}')", table))) + { + while (reader.Read()) + { + sqlStrings.Add((string)reader[0]); + } + } + + return [.. sqlStrings]; + } + + public void MoveIndexesFromOriginalTable(string origTable, string newTable) + { + var indexSqls = GetCreateIndexSqlStrings(origTable); + + foreach (var indexSql in indexSqls) + { + var origTableStart = indexSql.IndexOf(" ON ", StringComparison.OrdinalIgnoreCase) + 4; + var origTableEnd = indexSql.IndexOf("(", origTableStart); + + // First remove original index, because names have to be unique + var createIndexDef = " INDEX "; + var indexNameStart = indexSql.IndexOf(createIndexDef, StringComparison.OrdinalIgnoreCase) + createIndexDef.Length; + ExecuteNonQuery("DROP INDEX " + indexSql.Substring(indexNameStart, origTableStart - 4 - indexNameStart)); + + // Create index on new table + ExecuteNonQuery(indexSql.Substring(0, origTableStart) + newTable + " " + indexSql.Substring(origTableEnd)); + } + } + + public override void RemoveColumn(string tableName, string column) + { + // In SQLite we need to recreate the table even if we only want to add, alter or drop a foreign key. So we not only recreate the table given + // as parameter but also the tables with FKs pointing to the column you want to remove. + // In order to perform it smoothly, the PRAGMA foreign keys should be set off. + + var isPragmaForeignKeysOn = IsPragmaForeignKeysOn(); + + if (isPragmaForeignKeysOn) + { + throw new Exception($"{nameof(RemoveColumn)} requires foreign keys off."); + } + + if (!TableExists(tableName)) + { + throw new MigrationException($"The table '{tableName}' does not exist"); + } + + if (!ColumnExists(tableName, column)) + { + throw new MigrationException($"The table '{tableName}' does not have a column named '{column}'"); + } + + var sqliteInfoMainTable = GetSQLiteTableInfo(tableName); + + var checkConstraints = sqliteInfoMainTable.CheckConstraints; + + if (checkConstraints.Any(x => x.CheckConstraintString.Contains(column, StringComparison.OrdinalIgnoreCase))) + { + throw new MigrationException("A check constraint contains the column you want to remove. Remove the check constraint first"); + } + + if (!sqliteInfoMainTable.ColumnMappings.Any(x => x.OldName == column)) + { + throw new MigrationException("Column not found"); + } + + // We throw if all of the conditions are fulfilled: + // - the unique constraint is a composite constraint (more than one column) + // - the column to be removed is part of the constraint + // In case of single constraint we remove it silently as it is not needed any more + var isColumnInUniqueConstraint = sqliteInfoMainTable.Uniques + .Where(x => x.KeyColumns.Length > 1) + .SelectMany(x => x.KeyColumns) + .Distinct() + .Any(x => x.Equals(column, StringComparison.OrdinalIgnoreCase)); + + if (isColumnInUniqueConstraint) + { + StringBuilder stringBuilder = new(); + stringBuilder.Append("Found composite unique constraint where the column that you want to remove is part of. Remove the unique constraints first before you remove the column."); + stringBuilder.Append("Other unique constraints(if exists) that contains only the column to be removed are dropped silently."); + + throw new Exception(stringBuilder.ToString()); + } + + var isColumnInIndex = sqliteInfoMainTable.Indexes + .Where(x => x.KeyColumns.Length > 1) + .SelectMany(x => x.KeyColumns) + .Distinct() + .Any(x => x.Equals(column, StringComparison.OrdinalIgnoreCase)); + + if (isColumnInIndex) + { + StringBuilder stringBuilder = new(); + stringBuilder.Append("Found composite index where the column that you want to remove is part of. Remove the indexes first before you remove the column."); + stringBuilder.Append("Other indexes(if exists) that contains only the column to be removed are dropped silently."); + + throw new Exception(stringBuilder.ToString()); + } + + var isColumnInForeignKey = sqliteInfoMainTable.ForeignKeys + .Where(x => x.ChildColumns.Length > 1) + .SelectMany(x => x.ChildColumns) + .Distinct() + .Any(x => x.Equals(column, StringComparison.OrdinalIgnoreCase)); + + if (isColumnInForeignKey) + { + StringBuilder stringBuilder = new(); + stringBuilder.Append("Found foreign key with more than two columns with one column is the column you want to remove. Remove the foreign key before you "); + stringBuilder.Append("remove the column. Other foreign keys (if exists) that contain only the column to be removed are dropped silently."); + + throw new Exception(stringBuilder.ToString()); + } + + var allTableNames = GetTables(); + + // Remove foreign keys with single parent column pointing to the column to be removed. + foreach (var allTableName in allTableNames) + { + if (allTableName == tableName) + { + continue; + } + + var sqliteTableInfoOther = GetSQLiteTableInfo(allTableName); + var recreateOtherTable = false; + + for (var i = sqliteTableInfoOther.ForeignKeys.Count - 1; i >= 0; i--) + { + if (!sqliteTableInfoOther.ForeignKeys[i].ParentTable.Equals(tableName, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (sqliteTableInfoOther.ForeignKeys[i].ParentColumns.Contains(column) && sqliteTableInfoOther.ForeignKeys[i].ParentColumns.Length > 1) + { + StringBuilder stringBuilder = new(); + stringBuilder.Append($"You need to delete/adjust the FK in table {allTableName} pointing to {tableName}."); + stringBuilder.Append("Other foreign key if exists with just one parent column we adjust silently."); + + throw new Exception(stringBuilder.ToString()); + } + + if (sqliteTableInfoOther.ForeignKeys[i].ParentColumns.Contains(column) && sqliteTableInfoOther.ForeignKeys[i].ParentColumns.Length == 1) + { + recreateOtherTable = true; + sqliteTableInfoOther.ForeignKeys.RemoveAt(i); + } + } + + if (recreateOtherTable) + { + RecreateTable(sqliteTableInfoOther); + } + } + + sqliteInfoMainTable.Uniques.RemoveAll(x => x.KeyColumns.Length == 1 && x.KeyColumns[0].Equals(column, StringComparison.OrdinalIgnoreCase)); + sqliteInfoMainTable.ColumnMappings.RemoveAll(x => x.OldName.Equals(column, StringComparison.OrdinalIgnoreCase)); + sqliteInfoMainTable.Columns.RemoveAll(x => x.Name.Equals(column, StringComparison.OrdinalIgnoreCase)); + sqliteInfoMainTable.Indexes.RemoveAll(x => x.KeyColumns.Length == 1 && x.KeyColumns[0].Equals(column, StringComparison.OrdinalIgnoreCase)); + sqliteInfoMainTable.ForeignKeys.RemoveAll(x => x.ChildColumns.Length == 1 && x.ChildColumns[0].Equals(column, StringComparison.OrdinalIgnoreCase)); + + RecreateTable(sqliteInfoMainTable); + } + + public override void RenameColumn(string tableName, string oldColumnName, string newColumnName) + { + if (!TableExists(tableName)) + { + throw new Exception($"Table {tableName} does not exist"); + } + + var isPragmaForeignKeysOn = IsPragmaForeignKeysOn(); + + if (isPragmaForeignKeysOn) + { + throw new Exception($"{nameof(RenameColumn)} requires foreign keys off."); + } + + // Due to old .Net versions we cannot use ThrowIfNullOrWhitespace + if (string.IsNullOrWhiteSpace(newColumnName)) + { + throw new Exception("New column name is null or empty"); + } + + if (ColumnExists(tableName, newColumnName)) + { + throw new MigrationException(string.Format("Table '{0}' has column named '{1}' already", tableName, newColumnName)); + } + + if (ColumnExists(tableName, oldColumnName)) + { + var sqliteTableInfo = GetSQLiteTableInfo(tableName); + + var columnMapping = sqliteTableInfo.ColumnMappings.First(x => x.OldName.Equals(oldColumnName, StringComparison.OrdinalIgnoreCase)); + columnMapping.NewName = newColumnName; + + var column = sqliteTableInfo.Columns.First(x => x.Name.Equals(oldColumnName, StringComparison.OrdinalIgnoreCase)); + column.Name = newColumnName; + + foreach (var foreignKey in sqliteTableInfo.ForeignKeys) + { + foreignKey.ChildColumns = [.. foreignKey.ChildColumns.Select(x => x.Equals(oldColumnName, StringComparison.OrdinalIgnoreCase) ? newColumnName : x)]; + } + + foreach (var index in sqliteTableInfo.Indexes) + { + index.KeyColumns = [.. index.KeyColumns.Select(x => x.Equals(oldColumnName, StringComparison.OrdinalIgnoreCase) ? newColumnName : x)]; + } + + foreach (var unique in sqliteTableInfo.Uniques) + { + unique.KeyColumns = [.. unique.KeyColumns.Select(x => x.Equals(oldColumnName, StringComparison.OrdinalIgnoreCase) ? newColumnName : x)]; + } + + RecreateTable(sqliteTableInfo); + + var allTables = GetTables(); + + // Rename in foreign keys of depending tables + foreach (var allTablesItem in allTables) + { + if (allTablesItem == tableName) + { + continue; + } + + var sqliteTableInfoOther = GetSQLiteTableInfo(allTablesItem); + + foreach (var foreignKey in sqliteTableInfoOther.ForeignKeys) + { + if (foreignKey.ParentTable != tableName) + { + continue; + } + + foreignKey.ParentColumns = foreignKey.ParentColumns.Select(x => x == oldColumnName ? newColumnName : x).ToArray(); + + RecreateTable(sqliteTableInfoOther); + } + } + } + else + { + throw new MigrationException(string.Format("The table '{0}' does not have a column named '{1}'", tableName, oldColumnName)); + } + } + + public override void RemoveColumnDefaultValue(string tableName, string columnName) + { + if (!TableExists(tableName)) + { + throw new Exception("Table does not exist"); + } + + if (!ColumnExists(table: tableName, column: columnName)) + { + throw new Exception("Column does not exist"); + } + + var sqliteTableInfo = GetSQLiteTableInfo(tableName); + + var column = sqliteTableInfo.Columns.First(x => x.Name == columnName); + column.DefaultValue = null; + + RecreateTable(sqliteTableInfo); + } + + public override void AddPrimaryKey(string name, string tableName, params string[] columnNames) + { + if (!TableExists(tableName)) + { + throw new Exception("Table does not exist"); + } + + var sqliteTableInfo = GetSQLiteTableInfo(tableName); + + foreach (var column in sqliteTableInfo.Columns) + { + if (columnNames.Any(x => x.Equals(column.Name, StringComparison.OrdinalIgnoreCase))) + { + column.ColumnProperty = column.ColumnProperty.Set(ColumnProperty.PrimaryKey); + } + else + { + column.ColumnProperty = column.ColumnProperty.Clear(ColumnProperty.PrimaryKey); + } + } + + var columnNamesList = columnNames.ToList(); + + var columnsReordered = sqliteTableInfo.Columns.OrderBy(x => + { + var index = columnNamesList.IndexOf(x.Name); + return index >= 0 ? index : int.MaxValue; + }).ToList(); + + sqliteTableInfo.Columns = columnsReordered; + + RecreateTable(sqliteTableInfo); + } + + public override bool PrimaryKeyExists(string table, string name) + { + var sqliteTableInfo = GetSQLiteTableInfo(table); + + // SQLite does not offer named primary keys BUT since there can only be one primary key per table we return true if there is any primary key. + + var hasPrimaryKey = sqliteTableInfo.Columns.Any(x => x.ColumnProperty.IsSet(ColumnProperty.PrimaryKey)); + + return hasPrimaryKey; + } + + public override void AddUniqueConstraint(string name, string table, params string[] columns) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new MigrationException("Providing a constraint name is obligatory."); + } + + var sqliteTableInfo = GetSQLiteTableInfo(table); + + if (sqliteTableInfo.Uniques.Any(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase))) + { + throw new MigrationException("A unique constraint with the same name already exists."); + } + + var uniqueConstraint = new Unique() { KeyColumns = columns, Name = name }; + sqliteTableInfo.Uniques.Add(uniqueConstraint); + + RecreateTable(sqliteTableInfo); + } + + public override void RemoveConstraint(string table, string name) + { + var sqliteTableInfo = GetSQLiteTableInfo(table); + sqliteTableInfo.Uniques.RemoveAll(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); + sqliteTableInfo.CheckConstraints.RemoveAll(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); + + RecreateTable(sqliteTableInfo); + } + + public SQLiteTableInfo GetSQLiteTableInfo(string tableName) + { + if (!TableExists(tableName)) + { + return null; + } + + var sqliteTable = new SQLiteTableInfo + { + TableNameMapping = new MappingInfo { OldName = tableName, NewName = tableName }, + Columns = GetColumns(tableName).ToList(), + ForeignKeys = GetForeignKeyConstraints(tableName).ToList(), + Indexes = GetIndexes(tableName).ToList(), + Uniques = GetUniques(tableName).ToList(), + CheckConstraints = GetCheckConstraints(tableName) + }; + + sqliteTable.ColumnMappings = sqliteTable.Columns + .Select(x => + new MappingInfo + { + OldName = x.Name, + NewName = x.Name + }) + .ToList(); + + return sqliteTable; + } + + public bool CheckForeignKeyIntegrity() + { + ExecuteNonQuery("PRAGMA foreign_keys = ON"); + + using var cmd = CreateCommand(); + using var reader = ExecuteQuery(cmd, "PRAGMA foreign_key_check"); + + if (reader.Read()) + { + return false; + } + + return true; + } + + public bool IsPragmaForeignKeysOn() + { + using var cmd = CreateCommand(); + using var reader = ExecuteQuery(cmd, "PRAGMA foreign_keys"); + reader.Read(); + var isOn = reader.GetInt32(0) == 1; + + return isOn; + } + + public void SetPragmaForeignKeys(bool isOn) + { + var onOffString = isOn ? "ON" : "OFF"; + + using var cmd = CreateCommand(); + ExecuteQuery(cmd, $"PRAGMA foreign_keys = {onOffString}"); + } + + public void RecreateTable(SQLiteTableInfo sqliteTableInfo) + { + var sourceTableQuoted = QuoteTableNameIfRequired(sqliteTableInfo.TableNameMapping.OldName); + var targetIntermediateTableQuoted = QuoteTableNameIfRequired($"{sqliteTableInfo.TableNameMapping.NewName}{IntermediateTableSuffix}"); + var targetTableQuoted = QuoteTableNameIfRequired($"{sqliteTableInfo.TableNameMapping.NewName}"); + + var columnDbFields = sqliteTableInfo.Columns.Cast(); + var foreignKeyDbFields = sqliteTableInfo.ForeignKeys.Cast(); + var indexDbFields = sqliteTableInfo.Indexes.Cast(); + var uniqueDbFields = sqliteTableInfo.Uniques.Cast(); + var checkConstraintDbFields = sqliteTableInfo.CheckConstraints.Cast(); + + var dbFields = columnDbFields.Concat(foreignKeyDbFields) + .Concat(uniqueDbFields) + .Concat(checkConstraintDbFields) + .ToArray(); + + // ToHashSet() not available in older .NET versions so we create it old-fashioned. + var uniqueColumnNames = new HashSet(sqliteTableInfo.Uniques + .SelectMany(x => x.KeyColumns) + .Distinct() + ); + + // ToHashSet() not available in older .NET versions so we create it old-fashioned. + var columnNames = new HashSet(sqliteTableInfo.Columns + .Select(x => x.Name) + ); + + // ToHashSet() not available in older .NET versions so we create it old-fashioned. + var newColumnNamesInMapping = new HashSet(sqliteTableInfo.ColumnMappings + .Select(x => x.NewName) + ); + + if (!columnNames.SetEquals(newColumnNamesInMapping)) + { + throw new Exception($"{nameof(columnNames)} and {nameof(newColumnNamesInMapping)} are not equal regarding length and content"); + } + + if (uniqueColumnNames.Except(columnNames).Any()) + { + var firstMissing = uniqueColumnNames.Except(columnNames).First(); + throw new Exception($"Detected missing column names OR unique key columns that do not exist in the column list/column mapping. E.g. {firstMissing}"); + } + + AddTable(targetIntermediateTableQuoted, null, dbFields); + + var columnMappings = sqliteTableInfo.ColumnMappings + .Where(x => x.OldName != null) + .OrderBy(x => x.OldName) + .ToList(); + + var sourceColumnsQuotedString = string.Join(", ", columnMappings.Select(x => QuoteColumnNameIfRequired(x.OldName))); + var targetColumnsQuotedString = string.Join(", ", columnMappings.Select(x => QuoteColumnNameIfRequired(x.NewName))); + + using (var cmd = CreateCommand()) + { + var sql = $"INSERT INTO {targetIntermediateTableQuoted} ({targetColumnsQuotedString}) SELECT {sourceColumnsQuotedString} FROM {sourceTableQuoted}"; + ExecuteQuery(cmd, sql); + } + + RemoveTable(sourceTableQuoted); + + using (var cmd = CreateCommand()) + { + // Rename to original name + var sql = $"ALTER TABLE {targetIntermediateTableQuoted} RENAME TO {targetTableQuoted}"; + ExecuteQuery(cmd, sql); + } + + foreach (var index in sqliteTableInfo.Indexes) + { + AddIndex(sqliteTableInfo.TableNameMapping.NewName, index); + } + } + + [Obsolete] + public override void AddTable(string table, string engine, string columns) + { + throw new NotSupportedException(); + } + + public override void AddColumn(string table, Column column) + { + if (!TableExists(table)) + { + throw new Exception("Table does not exist."); + } + + var sqliteInfo = GetSQLiteTableInfo(table); + + if (sqliteInfo.ColumnMappings.Select(x => x.OldName).ToList().Contains(column.Name)) + { + throw new Exception("Column already exists."); + } + + sqliteInfo.ColumnMappings.Add(new MappingInfo { OldName = null, NewName = column.Name }); + sqliteInfo.Columns.Add(column); + + RecreateTable(sqliteInfo); + } + + public override void AddColumn(string table, string columnName, DbType type, int size) + { + var column = new Column(columnName, type, size); + + AddColumn(table, column); + } + + public override void AddColumn(string table, string columnName, MigratorDbType type, int size) + { + var column = new Column(columnName, type, size); + + AddColumn(table, column); + } + + public override void AddColumn(string table, string columnName, DbType type, ColumnProperty property) + { + var column = new Column(columnName, type, property); + + AddColumn(table, column); + } + + public override void AddColumn(string table, string columnName, MigratorDbType type, ColumnProperty property) + { + var column = new Column(columnName, type, property); + + AddColumn(table, column); + } + + public override void AddColumn(string table, string columnName, MigratorDbType type, int size, ColumnProperty property, + object defaultValue) + { + var column = new Column(columnName, type, property) { Size = size, DefaultValue = defaultValue }; + + AddColumn(table, column); + } + + public override void AddColumn(string table, string columnName, DbType type) + { + var column = new Column(columnName, type); + + AddColumn(table, column); + } + + public override void AddColumn(string table, string columnName, MigratorDbType type) + { + var column = new Column(columnName, type); + + AddColumn(table, column); + } + + public override void AddColumn(string table, string columnName, DbType type, int size, ColumnProperty property) + { + var column = new Column(columnName, type, size, property); + + AddColumn(table, column); + } + + public override void AddColumn(string table, string columnName, MigratorDbType type, int size, ColumnProperty property) + { + var column = new Column(columnName, type, size, property); + + AddColumn(table, column); + } + + public override void AddColumn(string table, string columnName, DbType type, object defaultValue) + { + var column = new Column(columnName, type, defaultValue); + + AddColumn(table, column); + } + + public override void AddColumn(string table, string sqlColumn) + { + var column = new Column(sqlColumn); + AddColumn(table, column); + } + + public override void ChangeColumn(string table, Column column) + { + if (!TableExists(table)) + { + throw new Exception("Table does not exist."); + } + + var sqliteInfo = GetSQLiteTableInfo(table); + + if (!sqliteInfo.ColumnMappings.Select(x => x.OldName).ToList().Contains(column.Name)) + { + throw new Exception("Column does not exists."); + } + + sqliteInfo.Columns = sqliteInfo.Columns + .Where(x => !x.Name.Equals(column.Name, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + sqliteInfo.Columns.Add(column); + + RecreateTable(sqliteInfo); + } + + public override int TruncateTable(string table) + { + return ExecuteNonQuery(string.Format("DELETE FROM {0} ", table)); + } + + public override bool TableExists(string table) + { + using var cmd = CreateCommand(); + using var reader = ExecuteQuery(cmd, string.Format("SELECT name FROM sqlite_master WHERE type='table' and lower(name)=lower('{0}')", table)); + + return reader.Read(); + } + + public override bool ViewExists(string view) + { + using var cmd = CreateCommand(); + using var reader = ExecuteQuery(cmd, string.Format("SELECT name FROM sqlite_master WHERE type='view' and lower(name)=lower('{0}')", view)); + + return reader.Read(); + } + + public override List GetDatabases() + { + throw new NotSupportedException("SQLite is a file-based database. You cannot list other databases."); + } + + public override bool ConstraintExists(string table, string name) + { + if (!TableExists(table)) + { + throw new Exception($"Table '{table}' does not exist."); + } + + var constraintNames = GetConstraints(table); + + var exists = constraintNames.Any(x => x.Equals(name, StringComparison.OrdinalIgnoreCase)); + + return exists; + } + + public override string[] GetConstraints(string table) + { + if (!TableExists(table)) + { + throw new Exception($"Table '{table}' does not exist."); + } + + var sqliteInfo = GetSQLiteTableInfo(table); + + var foreignKeyNames = sqliteInfo.ForeignKeys + .Select(x => x.Name) + .ToList(); + + var uniqueConstraints = sqliteInfo.Uniques + .Select(x => x.Name) + .ToList(); + + var checkConstraints = sqliteInfo.CheckConstraints + .Select(x => x.Name) + .ToList(); + + var names = foreignKeyNames.Concat(uniqueConstraints) + .Concat(checkConstraints) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .ToArray(); + + var distinctNames = names.Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); + + if (names.Length != distinctNames.Length) + { + throw new Exception($"There are duplicate constraint names in table {table}'"); + } + + return distinctNames; + } + + public override string[] GetTables() + { + var tables = new List(); + + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name")) + { + while (reader.Read()) + { + tables.Add((string)reader[0]); + } + } + + return [.. tables]; + } + + public override Column[] GetColumns(string tableName) + { + var pragmaTableInfoItems = GetPragmaTableInfoItems(tableName); + + // Column provides no way to store the primary key sequence number and we do not want to change the class for all database types for now + // so we sort the columns. + var tableInfoPrimaryKeys = pragmaTableInfoItems.Where(x => x.Pk > 0) + .OrderBy(x => x.Pk) + .ToList(); + + var tableInfoNonPrimaryKeys = pragmaTableInfoItems.Where(x => x.Pk < 1) + .OrderBy(x => x.Cid) + .ToList(); + + var pragmaTableInfoItemsSorted = tableInfoPrimaryKeys.Concat(tableInfoNonPrimaryKeys).ToList(); + + var columns = new List(); + + foreach (var pragmaTableInfoItem in pragmaTableInfoItemsSorted) + { + var column = new Column(pragmaTableInfoItem.Name) + { + Type = _dialect.GetDbTypeFromString(pragmaTableInfoItem.Type) + }; + + if (pragmaTableInfoItem.NotNull) + { + column.ColumnProperty |= ColumnProperty.NotNull; + } + else + { + column.ColumnProperty |= ColumnProperty.Null; + } + + var defValue = pragmaTableInfoItem.DfltValue == DBNull.Value ? null : pragmaTableInfoItem.DfltValue; + + if (defValue is string v && v.StartsWith("'") && v.EndsWith("'")) + { + column.DefaultValue = v.Substring(1, v.Length - 2); + } + else + { + column.DefaultValue = defValue; + } + + if (column.DefaultValue != null) + { + if (column.Type == DbType.Int16 || column.Type == DbType.Int32 || column.Type == DbType.Int64) + { + column.DefaultValue = long.Parse(column.DefaultValue.ToString()); + } + else if (column.Type == DbType.UInt16 || column.Type == DbType.UInt32 || column.Type == DbType.UInt64) + { + column.DefaultValue = ulong.Parse(column.DefaultValue.ToString()); + } + else if (column.Type == DbType.Double || column.Type == DbType.Single) + { + column.DefaultValue = double.Parse(column.DefaultValue.ToString()); + } + else if (column.Type == DbType.Boolean) + { + column.DefaultValue = column.DefaultValue.ToString().Trim() == "1" || column.DefaultValue.ToString().Trim().ToUpper() == "TRUE"; + } + else if (column.Type == DbType.DateTime || column.Type == DbType.DateTime2) + { + if (column.DefaultValue is string defVal) + { + var dt = defVal; + + if (defVal.StartsWith("'")) + { + dt = defVal.Substring(1, defVal.Length - 2); + } + + var d = DateTime.ParseExact(dt, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); + column.DefaultValue = d; + } + } + else if (column.Type == DbType.Guid) + { + if (column.DefaultValue is string defVal) + { + var dt = defVal; + + if (defVal.StartsWith("'")) + { + dt = defVal.Substring(1, defVal.Length - 2); + } + + var d = Guid.Parse(dt); + column.DefaultValue = d; + } + } + else if (column.Type == DbType.Boolean) + { + throw new NotSupportedException("SQLite does not support default values for BLOB columns."); + } + } + + if (pragmaTableInfoItem.Pk > 0) + { + if (new[] { DbType.UInt16, DbType.UInt32, DbType.UInt64, DbType.Int16, DbType.Int32, DbType.Int64 }.Contains(column.Type)) + { + column.ColumnProperty |= ColumnProperty.PrimaryKey; + column.ColumnProperty |= ColumnProperty.NotNull; + column.ColumnProperty = column.ColumnProperty.Clear(ColumnProperty.Null); + } + else + { + column.ColumnProperty |= ColumnProperty.PrimaryKey; + } + } + + var indexListItems = GetPragmaIndexListItems(tableName); + var uniqueConstraints = indexListItems.Where(x => x.Unique && x.Origin == "u"); + + foreach (var uniqueConstraint in uniqueConstraints) + { + var indexInfos = GetPragmaIndexInfo(uniqueConstraint.Name); + + if (indexInfos.Count == 1 && indexInfos.First().Name.Equals(column.Name, StringComparison.OrdinalIgnoreCase)) + { + column.ColumnProperty |= ColumnProperty.Unique; + + break; + } + } + + var tableScript = GetSqlCreateTableScript(tableName); + + var columnTableInfoItem = pragmaTableInfoItems.First(x => x.Name.Equals(column.Name, StringComparison.OrdinalIgnoreCase)); + + var hasCompoundPrimaryKey = tableInfoPrimaryKeys.Count > 1; + + // Implicit in SQLite + if (columnTableInfoItem.Type == "INTEGER" && columnTableInfoItem.Pk == 1 && !hasCompoundPrimaryKey) + { + column.ColumnProperty |= ColumnProperty.Identity; + } + + columns.Add(column); + } + + + return [.. columns]; + } + + public bool IsNullable(string columnDef) + { + return !columnDef.Contains("NOT NULL"); + } + + public bool ColumnMatch(string column, string columnDef) + { + return columnDef.StartsWith(column + " ") || columnDef.StartsWith(_dialect.Quote(column)); + } + + public override bool IndexExists(string table, string name) + { + using var cmd = CreateCommand(); + using var reader = ExecuteQuery(cmd, string.Format("SELECT name FROM sqlite_master WHERE type='index' and lower(name)=lower('{0}')", name)); + + return reader.Read(); + } + + public override Index[] GetIndexes(string table) + { + var afterWhereRegex = new Regex("(?<= WHERE ).+"); + List indexes = []; + + var indexCreateScripts = GetCreateIndexSqlStrings(table); + + var pragmaIndexListItems = GetPragmaIndexListItems(table).Where(x => x.Origin == "c"); + + var columns = GetColumns(table); + + foreach (var pragmaIndexListItem in pragmaIndexListItems) + { + var indexInfos = GetPragmaIndexInfo(pragmaIndexListItem.Name); + + var columnNames = indexInfos.OrderBy(x => x.SeqNo) + .Select(x => x.Name) + .ToArray(); + + var index = new Index + { + // At this moment in time the migrator does not support clustered indexes for SQLITE + // Since SQLite 3.8.2 WITHOUT ROWID is supported but not in this migrator + Clustered = false, + + // SQLite does not support include colums + IncludeColumns = [], + KeyColumns = columnNames, + Name = pragmaIndexListItem.Name, + Unique = pragmaIndexListItem.Unique + }; + + var script = indexCreateScripts.FirstOrDefault(x => x.Contains(pragmaIndexListItem.Name, StringComparison.OrdinalIgnoreCase)); + + if (script != null) + { + if (afterWhereRegex.Match(script) is Match match && match.Success) + { + // We cannot use GeneratedRegexAttribute due to old .NET version + var andSplitted = Regex.Split(match.Value, " AND "); + + var filterSingleStrings = andSplitted + .Select(x => x.Trim()) + .ToList(); + + foreach (var filterSingleString in filterSingleStrings) + { + var splitted = filterSingleString.Split(' ') + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => x.Trim()) + .ToList(); + + var filterItem = new FilterItem { ColumnName = splitted[0], Filter = _dialect.GetFilterTypeByComparisonString(splitted[1]) }; + + var column = columns.Single(x => x.Name.Equals(splitted[0], StringComparison.OrdinalIgnoreCase)); + + var sqliteIntegerDataTypes = new[] { + MigratorDbType.Int16, + MigratorDbType.Int32, + MigratorDbType.Int64, + MigratorDbType.UInt16, + MigratorDbType.UInt32, + MigratorDbType.UInt64 + }; + + if (sqliteIntegerDataTypes.Contains(column.MigratorDbType)) + { + if (long.TryParse(splitted[2], out var longValue)) + { + filterItem.Value = longValue; + } + else if (ulong.TryParse(splitted[2], out var uLongValue)) + { + filterItem.Value = uLongValue; + } + else + { + throw new Exception(); + } + } + else + { + filterItem.Value = column.MigratorDbType switch + { + MigratorDbType.Boolean => splitted[2] == "1" || splitted[2].Equals("true", StringComparison.OrdinalIgnoreCase), + MigratorDbType.String => splitted[2].Substring(1, splitted[2].Length - 2), + _ => throw new NotImplementedException("Type not yet supported. Please file an issue."), + }; + } + + index.FilterItems.Add(filterItem); + } + } + } + + indexes.Add(index); + } + + return [.. indexes]; + } + + public override void AddTable(string name, string engine, params IDbField[] fields) + { + var columns = fields.Where(x => x is Column) + .Cast() + .ToArray(); + + var pks = GetPrimaryKeys(columns); + var hasCompoundPrimaryKey = pks.Count > 1; + + var columnProviders = new List(columns.Length); + + foreach (var column in columns) + { + if (!hasCompoundPrimaryKey && column.IsPrimaryKey) + { + // We implicitly set NOT NULL for non-composite primary keys like in other RDBMS. + column.ColumnProperty = column.ColumnProperty.Clear(ColumnProperty.Null); + column.ColumnProperty = column.ColumnProperty.Set(ColumnProperty.NotNull); + } + + if (hasCompoundPrimaryKey && column.IsPrimaryKey) + { + // We remove PrimaryKey here and readd it as compound later ("...PRIMARY KEY(column1,column2)"); + column.ColumnProperty &= ~ColumnProperty.PrimaryKey; + + // AUTOINCREMENT cannot be used in compound primary keys in SQLite so we remove Identity here + column.ColumnProperty &= ~ColumnProperty.Identity; + } + + var mapper = _dialect.GetAndMapColumnProperties(column); + columnProviders.Add(mapper); + } + + var columnsAndIndexes = JoinColumnsAndIndexes(columnProviders); + + var table = _dialect.TableNameNeedsQuote ? _dialect.Quote(name) : QuoteTableNameIfRequired(name); + StringBuilder stringBuilder = new(); + + stringBuilder.Append(string.Format("CREATE TABLE {0} ({1}", table, columnsAndIndexes)); + + if (hasCompoundPrimaryKey) + { + stringBuilder.Append(string.Format(", PRIMARY KEY ({0})", string.Join(", ", pks.ToArray()))); + } + + + // Uniques + var uniques = fields.Where(x => x is Unique).Cast().ToArray(); + + foreach (var u in uniques) + { + if (!string.IsNullOrEmpty(u.Name)) + { + stringBuilder.Append($", CONSTRAINT {u.Name}"); + } + else + { + stringBuilder.Append(", "); + } + + var uniqueColumnsCommaSeparated = string.Join(", ", u.KeyColumns); + stringBuilder.Append($" UNIQUE ({uniqueColumnsCommaSeparated})"); + } + + // Foreign keys + var foreignKeys = fields.Where(x => x is ForeignKeyConstraint).Cast().ToArray(); + + List foreignKeyStrings = []; + + foreach (var fk in foreignKeys) + { + var sourceColumnNamesQuotedString = string.Join(", ", fk.ChildColumns.Select(QuoteColumnNameIfRequired)); + var parentColumnNamesQuotedString = string.Join(", ", fk.ParentColumns.Select(QuoteColumnNameIfRequired)); + var parentTableNameQuoted = QuoteTableNameIfRequired(fk.ParentTable); + + if (string.IsNullOrWhiteSpace(fk.Name)) + { + throw new Exception("No foreign key constraint name given"); + } + + foreignKeyStrings.Add($"CONSTRAINT {fk.Name} FOREIGN KEY ({sourceColumnNamesQuotedString}) REFERENCES {parentTableNameQuoted}({parentColumnNamesQuotedString})"); + } + + if (foreignKeyStrings.Count > 0) + { + stringBuilder.Append(", "); + stringBuilder.Append(string.Join(", ", foreignKeyStrings)); + } + + // Check Constraints + var checkConstraints = fields.Where(x => x is CheckConstraint).OfType().ToArray(); + List checkConstraintStrings = []; + + foreach (var checkConstraint in checkConstraints) + { + checkConstraintStrings.Add($"CONSTRAINT {checkConstraint.Name} CHECK ({checkConstraint.CheckConstraintString})"); + } + + if (checkConstraintStrings.Count > 0) + { + stringBuilder.Append($", {string.Join(", ", checkConstraintStrings)}"); + } + + stringBuilder.Append(')'); + + ExecuteNonQuery(stringBuilder.ToString()); + + var indexes = fields.Where(x => x is Index) + .Cast() + .ToArray(); + + foreach (var index in indexes) + { + AddIndex(name, index); + } + } + + public override string AddIndex(string table, Index index) + { + ValidateIndex(table, index); + + var hasIncludedColumns = index.IncludeColumns != null && index.IncludeColumns.Length > 0; + + if (hasIncludedColumns) + { + // This will be actived in the future. + // throw new MigrationException($"SQLite does not support included columns. Use 'if(Provider is {nameof(SQLiteTransformationProvider)}' if necessary."); + } + + if (index.Clustered) + { + throw new MigrationException($"For SQLite this migrator does not support clustered indexes at this point in time, sorry. File an issue if needed. Use 'if(Provider is {nameof(SQLiteTransformationProvider)}' if necessary."); + } + + var name = QuoteConstraintNameIfRequired(index.Name); + table = QuoteTableNameIfRequired(table); + var columns = QuoteColumnNamesIfRequired(index.KeyColumns); + + var uniqueString = index.Unique ? "UNIQUE" : null; + var columnsString = $"({string.Join(", ", columns)})"; + var filterString = string.Empty; + + if (index.FilterItems != null && index.FilterItems.Count > 0) + { + List singleFilterStrings = []; + + foreach (var filterItem in index.FilterItems) + { + var comparisonString = _dialect.GetComparisonStringByFilterType(filterItem.Filter); + + var filterColumnQuoted = QuoteColumnNameIfRequired(filterItem.ColumnName); + string value = null; + + value = filterItem.Value switch + { + bool booleanValue => booleanValue ? "1" : "0", + string stringValue => $"'{stringValue}'", + byte or short or int or long => Convert.ToInt64(filterItem.Value).ToString(), + sbyte or ushort or uint or ulong => Convert.ToUInt64(filterItem.Value).ToString(), + _ => throw new NotImplementedException("Given type is not implemented. Please file an issue."), + }; + + if ((filterItem.Value is string || filterItem.Value is bool) && filterItem.Filter != FilterType.EqualTo && filterItem.Filter != FilterType.NotEqualTo) + { + throw new MigrationException($"Bool and string in {nameof(FilterItem)} can only be used with '{nameof(FilterType.EqualTo)}' or '{nameof(FilterType.EqualTo)}'."); + } + + var singleFilterString = $"{filterColumnQuoted} {comparisonString} {value}"; + + singleFilterStrings.Add(singleFilterString); + } + + filterString = $"WHERE {string.Join(" AND ", singleFilterStrings)}"; + } + + List list = ["CREATE", uniqueString, "INDEX", name, "ON", table, columnsString, filterString]; + + var sql = string.Join(" ", list.Where(x => !string.IsNullOrWhiteSpace(x))); + + ExecuteNonQuery(sql); + + return sql; + } + + protected override string GetPrimaryKeyConstraintName(string table) + { + throw new NotImplementedException(); + } + + public override void RemoveAllConstraints(string table) + { + RemovePrimaryKey(table); + + var sqliteTableInfo = GetSQLiteTableInfo(table); + + // Remove unique constraints + sqliteTableInfo.Uniques = []; + + foreach (var column in sqliteTableInfo.Columns) + { + column.ColumnProperty &= ~ColumnProperty.PrimaryKey; + column.ColumnProperty &= ~ColumnProperty.Unique; + } + + // TODO CHECK is not implemented yet + // https://github.com/dotnetprojects/Migrator.NET/issues/64 + + RecreateTable(sqliteTableInfo); + } + + public override void RemovePrimaryKey(string tableName) + { + if (!TableExists(tableName)) + { + return; + } + + var sqliteInfoTable = GetSQLiteTableInfo(tableName); + + foreach (var column in sqliteInfoTable.Columns) + { + if (column.IsPrimaryKey) + { + column.ColumnProperty = column.ColumnProperty.Clear(ColumnProperty.PrimaryKey); + column.ColumnProperty = column.ColumnProperty.Clear(ColumnProperty.PrimaryKeyWithIdentity); + } + } + + RecreateTable(sqliteInfoTable); + } + + public override void RemoveAllIndexes(string tableName) + { + if (!TableExists(tableName)) + { + return; + } + + var sqliteInfoTable = GetSQLiteTableInfo(tableName); + + sqliteInfoTable.Uniques = []; + sqliteInfoTable.Indexes = []; + + RecreateTable(sqliteInfoTable); + } + + public List GetUniques(string tableName) + { + if (!TableExists(tableName)) + { + throw new Exception($"Table '{tableName}' does not exist."); + } + + var regEx = new Regex(@"(?<=,)\s*(CONSTRAINT\s+\w+\s+)?UNIQUE\s*\(\s*[\w\s,]+\s*\)\s*(?=,|\s*\))"); + var regExConstraintName = new Regex(@"(?<=CONSTRAINT\s+)\w+(?=\s+)"); + var regExParenthesis = new Regex(@"(?<=\().+(?=\))"); + + List uniques = []; + + var pragmaIndexListItems = GetPragmaIndexListItems(tableName); + + // Here we filter for origin u and unique while in "GetIndexes()" we exclude them. + // If "pk" is set then it was added by using a primary key. If so this is handled by "GetColumns()". + // If "c" is set it was created by using CREATE INDEX. + var uniqueConstraints = pragmaIndexListItems.Where(x => x.Unique && x.Origin == "u") + .ToList(); + + foreach (var uniqueConstraint in uniqueConstraints) + { + var indexInfos = GetPragmaIndexInfo(uniqueConstraint.Name); + + var columns = indexInfos.OrderBy(x => x.SeqNo) + .Select(x => x.Name) + .ToArray(); + + var unique = new Unique + { + Name = uniqueConstraint.Name, + KeyColumns = columns + }; + + uniques.Add(unique); + } + + var createScript = GetSqlCreateTableScript(tableName); + + var matches = regEx.Matches(createScript); + if (matches.Count == 0) + { + return []; + } + + var constraintNames = matches + .OfType() + .Where(x => x.Success && !string.IsNullOrWhiteSpace(x.Value)) + .Select(x => x.Value.Trim()) + .ToList(); + + // We can only use the ones containing a starting with CONSTRAINT + var matchesHavingName = constraintNames.Where(x => x.StartsWith("CONSTRAINT")).ToList(); + + foreach (var constraintString in matchesHavingName) + { + var constraintNameMatch = regExConstraintName.Match(constraintString); + + if (!constraintNameMatch.Success) + { + throw new Exception("Cannot extract constraint name. Please file an issue"); + } + + var constraintName = constraintNameMatch.Value; + + var parenthesisMatch = regExParenthesis.Match(constraintString); + + if (!parenthesisMatch.Success) + { + throw new Exception("Cannot extract parenthesis content for UNIQUE constraint. Please file an issue"); + } + + var columns = parenthesisMatch.Value.Split(',').Select(x => x.Trim()).ToList(); + + var unique = uniques.Where(x => x.KeyColumns.SequenceEqual(columns)).SingleOrDefault(); + + if (unique != null) + { + unique.Name = constraintName; + } + } + + return uniques; + } + + public List GetPragmaIndexInfo(string indexNameNotQuoted) + { + List pragmaIndexInfoItems = []; + + var quotedIndexName = QuoteTableNameIfRequired(indexNameNotQuoted); + + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, $"PRAGMA index_info({quotedIndexName})")) + { + while (reader.Read()) + { + var pragmaIndexInfoItem = new PragmaIndexInfoItem + { + SeqNo = reader.GetInt32(reader.GetOrdinal("seqno")), + Cid = reader.GetInt32(reader.GetOrdinal("cid")), + Name = reader.GetString(reader.GetOrdinal("name")), + }; + + pragmaIndexInfoItems.Add(pragmaIndexInfoItem); + } + } + + return pragmaIndexInfoItems; + } + + public List GetPragmaIndexListItems(string tableNameNotQuoted) + { + List pragmaIndexListItems = []; + + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, $"PRAGMA index_list({QuoteTableNameIfRequired(tableNameNotQuoted)})")) + { + while (reader.Read()) + { + var pragmaIndexListItem = new PragmaIndexListItem + { + Seq = reader.GetInt32(reader.GetOrdinal("seq")), + Name = reader.GetString(reader.GetOrdinal("name")), + Unique = reader.GetInt32(reader.GetOrdinal("unique")) == 1, + Origin = reader.GetString(reader.GetOrdinal("origin")), + Partial = reader.GetInt32(reader.GetOrdinal("partial")) == 1 + }; + + pragmaIndexListItems.Add(pragmaIndexListItem); + } + } + + return pragmaIndexListItems; + } + + public List GetPragmaTableInfoItems(string tableNameNotQuoted) + { + List pragmaTableInfoItems = []; + + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, $"PRAGMA table_info({QuoteTableNameIfRequired(tableNameNotQuoted)})")) + { + while (reader.Read()) + { + var pragmaTableInfoItem = new PragmaTableInfoItem + { + Cid = reader.GetInt32(reader.GetOrdinal("cid")), + DfltValue = reader[reader.GetOrdinal("dflt_value")], + Name = reader.GetString(reader.GetOrdinal("name")), + NotNull = reader.GetInt32(reader.GetOrdinal("notnull")) == 1, + Pk = reader.GetInt32(reader.GetOrdinal("pk")), + Type = reader.GetString(reader.GetOrdinal("type")), + }; + + pragmaTableInfoItems.Add(pragmaTableInfoItem); + } + } + + return pragmaTableInfoItems; + } + + public override void AddCheckConstraint(string constraintName, string tableName, string checkSql) + { + var sqliteTableInfo = GetSQLiteTableInfo(tableName); + + var checkConstraint = new CheckConstraint(constraintName, checkSql); + sqliteTableInfo.CheckConstraints.Add(checkConstraint); + + RecreateTable(sqliteTableInfo); + } + + public override void CopyDataFromTableToTable(string sourceTableName, List sourceColumnNames, string targetTableName, List targetColumnNames, List orderBySourceColumns = null) + { + orderBySourceColumns ??= []; + + if (!TableExists(sourceTableName)) + { + throw new Exception($"Source table '{QuoteTableNameIfRequired(sourceTableName)}' does not exist"); + } + + if (!TableExists(targetTableName)) + { + throw new Exception($"Target table '{QuoteTableNameIfRequired(targetTableName)}' does not exist"); + } + + var sourceColumnsConcatenated = sourceColumnNames.Concat(orderBySourceColumns); + + foreach (var column in sourceColumnsConcatenated) + { + if (!ColumnExists(sourceTableName, column)) + { + throw new Exception($"Column {column} in source table does not exist."); + } + } + + foreach (var column in targetColumnNames) + { + if (!ColumnExists(targetTableName, column)) + { + throw new Exception($"Column {column} in target table does not exist."); + } + } + + if (!orderBySourceColumns.All(x => sourceColumnNames.Contains(x))) + { + throw new Exception($"All columns in {nameof(orderBySourceColumns)} must be in {nameof(sourceColumnNames)}"); + } + + var sourceTableNameQuoted = QuoteTableNameIfRequired(sourceTableName); + var targetTableNameQuoted = QuoteTableNameIfRequired(targetTableName); + + var sourceColumnNamesQuoted = sourceColumnNames.Select(QuoteColumnNameIfRequired).ToList(); + var targetColumnNamesQuoted = targetColumnNames.Select(QuoteColumnNameIfRequired).ToList(); + var orderBySourceColumnsQuoted = orderBySourceColumns.Select(QuoteColumnNameIfRequired).ToList(); + + var sourceColumnsJoined = string.Join(", ", sourceColumnNamesQuoted); + var targetColumnsJoined = string.Join(", ", targetColumnNamesQuoted); + var orderBySourceColumnsJoined = string.Join(", ", orderBySourceColumnsQuoted); + + var orderByComponent = !string.IsNullOrWhiteSpace(orderBySourceColumnsJoined) ? $"ORDER BY {orderBySourceColumnsJoined}" : null; + + List sqlComponents = + [ + $"INSERT INTO {targetTableNameQuoted} ({targetColumnsJoined}) SELECT {sourceColumnsJoined} FROM {sourceTableNameQuoted}", + orderByComponent + ]; + + var sql = string.Join(" ", sqlComponents.Where(x => x != null)); + ExecuteNonQuery(sql); + } + + public List GetCheckConstraints(string tableName) + { + if (!TableExists(tableName)) + { + throw new Exception($"Table '{tableName}' does not exist."); + } + + var checkConstraintRegex = new Regex(@"(?<=,)[^,]+\s+[^,]+check[^,]+(?=[,|\)])", RegexOptions.IgnoreCase); + var braceContentRegex = new Regex(@"(?<=^\().+(?=\)$)"); + + var script = GetSqlCreateTableScript(tableName); + + var matches = checkConstraintRegex.Matches(script); + + if (matches == null) + { + return []; + } + + var checkStrings = matches.OfType() + .Where(x => x.Success) + .Select(x => x.Value) + .ToList(); + + List checkConstraints = []; + + foreach (var checkString in checkStrings) + { + var splitted = checkString.Trim().Split(' ') + .Select(x => x.Trim()) + .ToList(); + + if (!splitted[0].Equals("CONSTRAINT", StringComparison.OrdinalIgnoreCase) || !splitted[2].Equals("CHECK", StringComparison.OrdinalIgnoreCase)) + { + throw new Exception($"Cannot parse check constraint in table {tableName}"); + } + + var checkConstraintStringWithBraces = string.Join(" ", splitted.Skip(3)).Trim(); + var checkConstraintString = braceContentRegex.Match(checkConstraintStringWithBraces); + + var checkConstraint = new CheckConstraint + { + Name = splitted[1], + CheckConstraintString = checkConstraintString.Value + }; + + checkConstraints.Add(checkConstraint); + } + + return checkConstraints; + } + + protected override void ConfigureParameterWithValue(IDbDataParameter parameter, int index, object value) + { + if (value is ushort) + { + parameter.DbType = DbType.Int32; + parameter.Value = Convert.ToInt32(value); + } + else if (value is uint) + { + parameter.DbType = DbType.Int64; + parameter.Value = Convert.ToInt64(value); + } + else if (value is Guid || value is Guid?) + { + parameter.DbType = DbType.Binary; + parameter.Value = ((Guid)value).ToByteArray(); + } + else + { + base.ConfigureParameterWithValue(parameter, index, value); + } + } +} diff --git a/src/Migrator/Providers/Impl/SqlServer/SqlServer2005Dialect.cs b/src/Migrator/Providers/Impl/SqlServer/SqlServer2005Dialect.cs new file mode 100644 index 00000000..c5bf9285 --- /dev/null +++ b/src/Migrator/Providers/Impl/SqlServer/SqlServer2005Dialect.cs @@ -0,0 +1,27 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.SqlServer; + +public class SqlServer2005Dialect : SqlServerDialect +{ + public SqlServer2005Dialect() + { + RegisterColumnType(DbType.AnsiString, 2147483647, "VARCHAR(MAX)"); + RegisterColumnType(DbType.Binary, 2147483647, "VARBINARY(MAX)"); + RegisterColumnType(DbType.String, 1073741823, "NVARCHAR(MAX)"); + RegisterColumnType(DbType.Xml, "XML"); + } + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) + { + return new SqlServerTransformationProvider(dialect, connectionString, defaultSchema ?? DboSchemaName, scope, providerName); + } + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, + string defaultSchema, + string scope, string providerName) + { + return new SqlServerTransformationProvider(dialect, connection, defaultSchema ?? DboSchemaName, scope, providerName); + } +} diff --git a/src/Migrator/Providers/Impl/SqlServer/SqlServerDialect.cs b/src/Migrator/Providers/Impl/SqlServer/SqlServerDialect.cs new file mode 100644 index 00000000..2f2885a9 --- /dev/null +++ b/src/Migrator/Providers/Impl/SqlServer/SqlServerDialect.cs @@ -0,0 +1,150 @@ +using System; +using System.Data; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.SqlServer; + +public class SqlServerDialect : Dialect +{ + public const string DboSchemaName = "dbo"; + + public SqlServerDialect() + { + RegisterColumnType(DbType.AnsiStringFixedLength, "CHAR(255)"); + RegisterColumnType(DbType.AnsiStringFixedLength, int.MaxValue - 1, "CHAR($l)"); + RegisterColumnType(DbType.AnsiStringFixedLength, int.MaxValue, "CHAR(max)"); + RegisterColumnType(DbType.AnsiString, "VARCHAR(255)"); + RegisterColumnType(DbType.AnsiString, 8000, "VARCHAR($l)"); + RegisterColumnType(DbType.AnsiString, int.MaxValue, "TEXT"); + RegisterColumnType(DbType.Binary, "VARBINARY(8000)"); + RegisterColumnType(DbType.Binary, int.MaxValue - 1, "VARBINARY($l)"); + RegisterColumnType(DbType.Binary, int.MaxValue, "VARBINARY(max)"); + RegisterColumnType(DbType.Boolean, "BIT"); + RegisterColumnType(DbType.Byte, "TINYINT"); + RegisterColumnType(DbType.Currency, "MONEY"); + RegisterColumnType(DbType.Date, "DATETIME"); + RegisterColumnType(DbType.DateTime, "DATETIME"); + RegisterColumnType(DbType.DateTime2, "DATETIME2"); + RegisterColumnTypeAlias(DbType.DateTime, "SMALLDATETIME"); + RegisterColumnType(DbType.DateTimeOffset, "DATETIMEOffset(7)"); + RegisterColumnType(DbType.Decimal, "DECIMAL(19,5)"); + RegisterColumnType(DbType.Decimal, 19, "DECIMAL(19, $l)"); + RegisterColumnTypeWithParameters(DbType.Decimal, "DECIMAL({precision}, {scale})"); + RegisterColumnType(DbType.Double, "DOUBLE PRECISION"); //synonym for FLOAT(53) + RegisterColumnType(DbType.Double, 24, "FLOAT(24)"); + RegisterColumnType(DbType.Double, 53, "FLOAT(53)"); + RegisterColumnType(DbType.Guid, "UNIQUEIDENTIFIER"); + RegisterColumnType(DbType.Int16, "SMALLINT"); + RegisterColumnType(DbType.Int32, "INT"); + RegisterColumnType(DbType.Int64, "BIGINT"); + RegisterColumnType(DbType.UInt16, "INT"); + RegisterColumnType(DbType.UInt32, "BIGINT"); + RegisterColumnType(DbType.UInt64, "DECIMAL(20,0)"); + RegisterColumnType(DbType.Single, "REAL"); //synonym for FLOAT(24) + RegisterColumnType(DbType.StringFixedLength, "NCHAR(255)"); + RegisterColumnType(DbType.StringFixedLength, int.MaxValue - 1, "NCHAR($l)"); + RegisterColumnType(DbType.StringFixedLength, int.MaxValue, "NCHAR(max)"); + RegisterColumnType(DbType.String, "NVARCHAR(255)"); + RegisterColumnType(DbType.String, 4000, "NVARCHAR($l)"); + RegisterColumnType(DbType.String, int.MaxValue, "NVARCHAR(max)"); + //RegisterColumnType(DbType.String, 1073741823, "NTEXT"); + RegisterColumnType(DbType.Time, "DATETIME"); + RegisterColumnType(DbType.VarNumeric, "NUMERIC(18,0)"); + RegisterColumnType(DbType.VarNumeric, 38, "NUMERIC($l,0)"); + RegisterColumnType(MigratorDbType.Interval, "BIGINT"); + + RegisterProperty(ColumnProperty.Identity, "IDENTITY"); + + AddReservedWords("ADD", "EXCEPT", "PERCENT", "ALL", "EXEC", "PLAN", "ALTER", "EXECUTE", "PRECISION", "AND", "EXISTS", "PRIMARY", "ANY", "EXIT", "PRINT", "AS", "FETCH", "PROC", "ASC", "FILE", "PROCEDURE", "AUTHORIZATION", "FILLFACTOR", "PUBLIC", "BACKUP", "FOR", "RAISERROR", "BEGIN", "FOREIGN", "READ", "BETWEEN", "FREETEXT", "READTEXT", "BREAK", "FREETEXTTABLE", "RECONFIGURE", "BROWSE", "FROM", "REFERENCES", "BULK", "FULL", "REPLICATION", "BY", "FUNCTION", "RESTORE", "CASCADE", "GOTO", "RESTRICT", "CASE", "GRANT", "RETURN", "CHECK", "GROUP", "REVOKE", "CHECKPOINT", "HAVING", "RIGHT", "CLOSE", "HOLDLOCK", "ROLLBACK", "CLUSTERED", "IDENTITY", "ROWCOUNT", "COALESCE", "IDENTITY_INSERT", "ROWGUIDCOL", "COLLATE", "IDENTITYCOL", "RULE", "COLUMN", "IF", "SAVE", "COMMIT", "IN", "SCHEMA", "COMPUTE", "INDEX", "SELECT", "CONSTRAINT", "INNER", "SESSION_USER", "CONTAINS", "INSERT", "SET", "CONTAINSTABLE", "INTERSECT", "SETUSER", "CONTINUE", "INTO", "SHUTDOWN", "CONVERT", "IS", "SOME", "CREATE", "JOIN", "STATISTICS", "CROSS", "KEY", "SYSTEM_USER", "CURRENT", "KILL", "TABLE", "CURRENT_DATE", "LEFT", "TEXTSIZE", "CURRENT_TIME", "LIKE", "THEN", "CURRENT_TIMESTAMP", "LINENO", "TO", "CURRENT_USER", "LOAD", "TOP", "CURSOR", "NATIONAL", "TRAN", "DATABASE", "NOCHECK", "TRANSACTION", "DBCC", "NONCLUSTERED", "TRIGGER", "DEALLOCATE", "NOT", "TRUNCATE", "DECLARE", "NULL", "TSEQUAL", "DEFAULT", "NULLIF", "UNION", "DELETE", "OF", "UNIQUE", "DENY", "OFF", "UPDATE", "DESC", "OFFSETS", "UPDATETEXT", "DISK", "ON", "USE", "DISTINCT", "OPEN", "USER", "DISTRIBUTED", "OPENDATASOURCE", "VALUES", "DOUBLE", "OPENQUERY", "VARYING", "DROP", "OPENROWSET", "VIEW", "DUMMY", "OPENXML", "WAITFOR", "DUMP", "OPTION", "WHEN", "ELSE", "OR", "WHERE", "END", "ORDER", "WHILE", "ERRLVL", "OUTER", "WITH", "ESCAPE", "OVER", "WRITETEXT"); + } + public override bool SupportsNonClustered + { + get { return true; } + } + public override bool SupportsIndex + { + get { return false; } + } + + public override bool ColumnNameNeedsQuote + { + get { return true; } + } + + public override bool TableNameNeedsQuote + { + get { return true; } + } + + public override bool ConstraintNameNeedsQuote + { + get { return true; } + } + + public override string QuoteTemplate + { + get { return "[{0}]"; } + } + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) + { + return new SqlServerTransformationProvider(dialect, connectionString, defaultSchema ?? DboSchemaName, scope, providerName); + } + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, + string defaultSchema, + string scope, string providerName) + { + return new SqlServerTransformationProvider(dialect, connection, defaultSchema ?? DboSchemaName, scope, providerName); + } + + public override string Quote(string value) + { + var firstDotIndex = value.IndexOf('.'); + if (firstDotIndex >= 0) + { + var owner = value.Substring(0, firstDotIndex); + var table = value.Substring(firstDotIndex + 1); + return (string.Format(QuoteTemplate, owner) + "." + string.Format(QuoteTemplate, table)); + } + return string.Format(QuoteTemplate, value); + } + + public override string Default(object defaultValue) + { + if (defaultValue.GetType().Equals(typeof(bool))) + { + return string.Format("DEFAULT {0}", (bool)defaultValue ? "1" : "0"); + } + else if (defaultValue.GetType().Equals(typeof(Guid))) + { + return "DEFAULT '" + ((Guid)defaultValue).ToString("D") + "'"; + } + else if (defaultValue.GetType().Equals(typeof(DateTime))) + { + return "DEFAULT CONVERT(DateTime,'" + + ((DateTime)defaultValue).Year.ToString("D4") + '-' + + ((DateTime)defaultValue).Month.ToString("D2") + '-' + + ((DateTime)defaultValue).Day.ToString("D2") + ' ' + + ((DateTime)defaultValue).Hour.ToString("D2") + ':' + + ((DateTime)defaultValue).Minute.ToString("D2") + ':' + + ((DateTime)defaultValue).Second.ToString("D2") + '.' + + ((DateTime)defaultValue).Millisecond.ToString("D3") + + "',121)"; + } + else if (defaultValue.GetType().Equals(typeof(DateTimeOffset))) + { + return "DEFAULT CONVERT(DateTime,'" + + ((DateTimeOffset)defaultValue).Year.ToString("D4") + '-' + + ((DateTimeOffset)defaultValue).Month.ToString("D2") + '-' + + ((DateTimeOffset)defaultValue).Day.ToString("D2") + ' ' + + ((DateTimeOffset)defaultValue).Hour.ToString("D2") + ':' + + ((DateTimeOffset)defaultValue).Minute.ToString("D2") + ':' + + ((DateTimeOffset)defaultValue).Second.ToString("D2") + '.' + + ((DateTimeOffset)defaultValue).Millisecond.ToString("D3") + + "',121)"; + } + + return base.Default(defaultValue); + } +} diff --git a/src/Migrator/Providers/Impl/SqlServer/SqlServerTransformationProvider.cs b/src/Migrator/Providers/Impl/SqlServer/SqlServerTransformationProvider.cs new file mode 100644 index 00000000..871c2cb8 --- /dev/null +++ b/src/Migrator/Providers/Impl/SqlServer/SqlServerTransformationProvider.cs @@ -0,0 +1,1028 @@ +#region License + +//The contents of this file are subject to the Mozilla Public License +//Version 1.1 (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.mozilla.org/MPL/ +//Software distributed under the License is distributed on an "AS IS" +//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +//License for the specific language governing rights and limitations +//under the License. + +#endregion + +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Framework.Models; +using DotNetProjects.Migrator.Providers.Models.Indexes; +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; +using Index = DotNetProjects.Migrator.Framework.Index; + +namespace DotNetProjects.Migrator.Providers.Impl.SqlServer; + +/// +/// Migration transformations provider for Microsoft SQL Server. +/// +public class SqlServerTransformationProvider : TransformationProvider +{ + public SqlServerTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) + : base(dialect, connectionString, defaultSchema, scope) + { + CreateConnection(providerName); + } + + public SqlServerTransformationProvider(Dialect dialect, IDbConnection connection, string defaultSchema, string scope, string providerName) + : base(dialect, connection, defaultSchema, scope) + { + } + + + protected virtual void CreateConnection(string providerName) + { + if (string.IsNullOrEmpty(providerName)) + { + providerName = "System.Data.SqlClient"; + } + + var fac = DbProviderFactoriesHelper.GetFactory(providerName, null, null); + _connection = fac.CreateConnection(); + _connection.ConnectionString = _connectionString; + _connection.Open(); + + string collationString = null; + var collation = ExecuteScalar("SELECT DATABASEPROPERTYEX('" + _connection.Database + "', 'Collation')"); + + if (collation != null) + { + collationString = collation.ToString(); + } + + if (string.IsNullOrWhiteSpace(collationString)) + { + collationString = "Latin1_General_CI_AS"; + } + + Dialect.RegisterProperty(ColumnProperty.CaseSensitive, "COLLATE " + collationString.Replace("_CI_", "_CS_")); + } + + public override void CopyDataFromTableToTable(string sourceTableName, List sourceColumnNames, string targetTableName, List targetColumnNames, List orderBySourceColumns = null) + { + orderBySourceColumns ??= []; + + if (!TableExists(sourceTableName)) + { + throw new Exception($"Source table '{QuoteTableNameIfRequired(sourceTableName)}' does not exist"); + } + + if (!TableExists(targetTableName)) + { + throw new Exception($"Target table '{QuoteTableNameIfRequired(targetTableName)}' does not exist"); + } + + var sourceColumnsConcatenated = sourceColumnNames.Concat(orderBySourceColumns); + + foreach (var column in sourceColumnsConcatenated) + { + if (!ColumnExists(sourceTableName, column)) + { + throw new Exception($"Column {column} in source table does not exist."); + } + } + + foreach (var column in targetColumnNames) + { + if (!ColumnExists(targetTableName, column)) + { + throw new Exception($"Column {column} in target table does not exist."); + } + } + + if (!orderBySourceColumns.All(x => sourceColumnNames.Contains(x))) + { + throw new Exception($"All columns in {nameof(orderBySourceColumns)} must be in {nameof(sourceColumnNames)}"); + } + + var sourceTableNameQuoted = QuoteTableNameIfRequired(sourceTableName); + var targetTableNameQuoted = QuoteTableNameIfRequired(targetTableName); + + var sourceColumnNamesQuoted = sourceColumnNames.Select(QuoteColumnNameIfRequired).ToList(); + var targetColumnNamesQuoted = targetColumnNames.Select(QuoteColumnNameIfRequired).ToList(); + var orderBySourceColumnsQuoted = orderBySourceColumns.Select(QuoteColumnNameIfRequired).ToList(); + + var sourceColumnsJoined = string.Join(", ", sourceColumnNamesQuoted); + var targetColumnsJoined = string.Join(", ", targetColumnNamesQuoted); + var orderBySourceColumnsJoined = string.Join(", ", orderBySourceColumnsQuoted); + + var orderByComponent = !string.IsNullOrWhiteSpace(orderBySourceColumnsJoined) ? $"ORDER BY {orderBySourceColumnsJoined}" : null; + + List sqlComponents = + [ + $"INSERT INTO {targetTableNameQuoted} ({targetColumnsJoined}) SELECT {sourceColumnsJoined} FROM {sourceTableNameQuoted}", + orderByComponent + ]; + + var sql = string.Join(" ", sqlComponents.Where(x => x != null)); + ExecuteNonQuery(sql); + } + + public override bool TableExists(string tableName) + { + // This is not clean! Usually you should use schema as well as this query will find tables in other tables as well! + + using var cmd = CreateCommand(); + using var reader = ExecuteQuery(cmd, $"SELECT OBJECT_ID('{tableName}', 'U')"); + + if (reader.Read()) + { + var result = reader.GetValue(0); + var tableExists = result != DBNull.Value && result != null; + + return tableExists; + } + + return false; + } + + public override bool ViewExists(string viewName) + { + // This is not clean! Usually you should use schema as well as this query will find views in other tables as well! + + using var cmd = CreateCommand(); + cmd.CommandText = $"SELECT OBJECT_ID(@FullViewName, 'V')"; + + var parameter = cmd.CreateParameter(); + parameter.ParameterName = "@FullViewName"; + parameter.Value = viewName; + cmd.Parameters.Add(parameter); + + var result = cmd.ExecuteScalar(); + + var viewExists = result != DBNull.Value && result != null; + + return viewExists; + } + + public override bool ConstraintExists(string table, string name) + { + var retVal = false; + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, string.Format("SELECT TOP 1 * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_NAME ='{0}'", name))) + { + retVal = reader.Read(); + } + + if (!retVal) + { + using var cmd = CreateCommand(); + using var reader = ExecuteQuery(cmd, string.Format("SELECT TOP 1 * FROM sys.default_constraints WHERE parent_object_id = OBJECT_ID('{0}') AND name = '{1}'", table, name)); + return reader.Read(); + } + + return true; + } + + public override void AddColumn(string table, string sqlColumn) + { + table = _dialect.TableNameNeedsQuote ? _dialect.Quote(table) : table; + ExecuteNonQuery(string.Format("ALTER TABLE {0} ADD {1}", table, sqlColumn)); + } + + public override void AddPrimaryKeyNonClustered(string name, string table, params string[] columns) + { + var nonclusteredString = "NONCLUSTERED"; + ExecuteNonQuery( + string.Format("ALTER TABLE {0} ADD CONSTRAINT {1} PRIMARY KEY {2} ({3}) ", table, name, nonclusteredString, + string.Join(",", QuoteColumnNamesIfRequired(columns)))); + } + + public override string AddIndex(string table, Index index) + { + ValidateIndex(tableName: table, index: index); + + var hasIncludedColumns = index.IncludeColumns != null && index.IncludeColumns.Length > 0; + var name = QuoteConstraintNameIfRequired(index.Name); + table = QuoteTableNameIfRequired(table); + var columns = QuoteColumnNamesIfRequired(index.KeyColumns); + + var uniqueString = index.Unique ? "UNIQUE" : null; + var columnsString = $"({string.Join(", ", columns)})"; + var includeString = hasIncludedColumns ? $"INCLUDE ({string.Join(", ", index.IncludeColumns)})" : null; + var filterString = string.Empty; + var clusteredString = index.Clustered ? "CLUSTERED" : "NONCLUSTERED"; + + if (index.FilterItems != null && index.FilterItems.Count > 0) + { + List singleFilterStrings = []; + + foreach (var filterItem in index.FilterItems) + { + var comparisonString = _dialect.GetComparisonStringByFilterType(filterItem.Filter); + + var filterColumnQuoted = QuoteColumnNameIfRequired(filterItem.ColumnName); + string value = null; + + if (filterItem.Value is bool booleanValue) + { + value = booleanValue ? "1" : "0"; + } + else if (filterItem.Value is string stringValue) + { + value = $"'{stringValue}'"; + } + else if (filterItem.Value is byte || filterItem.Value is short || filterItem.Value is int || filterItem.Value is long) + { + value = Convert.ToInt64(filterItem.Value).ToString(); + } + else if (filterItem.Value is sbyte || filterItem.Value is ushort || filterItem.Value is uint || filterItem.Value is ulong) + { + value = Convert.ToUInt64(filterItem.Value).ToString(); + } + else + { + throw new NotImplementedException("Given type is not implemented. Please file an issue."); + } + + var singleFilterString = $"{filterColumnQuoted} {comparisonString} {value}"; + + singleFilterStrings.Add(singleFilterString); + } + + filterString = $"WHERE {string.Join(" AND ", singleFilterStrings)}"; + } + + List list = []; + list.Add("CREATE"); + list.Add(uniqueString); + list.Add(clusteredString); + list.Add("INDEX"); + list.Add(name); + list.Add("ON"); + list.Add(table); + list.Add(columnsString); + list.Add(includeString); + list.Add(filterString); + + list = [.. list.Where(x => !string.IsNullOrWhiteSpace(x))]; + + var sql = string.Join(" ", list); + + ExecuteNonQuery(sql); + + return sql; + } + + public override void ChangeColumn(string table, Column column) + { + if (column.DefaultValue == null || column.DefaultValue == DBNull.Value) + { + base.ChangeColumn(table, column); + } + else + { + var def = column.DefaultValue; + var notNull = column.ColumnProperty.IsSet(ColumnProperty.NotNull); + column.DefaultValue = null; + column.ColumnProperty = column.ColumnProperty.Set(ColumnProperty.Null); + column.ColumnProperty = column.ColumnProperty.Clear(ColumnProperty.NotNull); + + base.ChangeColumn(table, column); + + var mapper = _dialect.GetAndMapColumnPropertiesWithoutDefault(column); + ExecuteNonQuery(string.Format("ALTER TABLE {0} ADD CONSTRAINT {1} {2} FOR {3}", this.QuoteTableNameIfRequired(table), "DF_" + table + "_" + column.Name, _dialect.Default(def), this.QuoteColumnNameIfRequired(column.Name))); + + if (notNull) + { + column.ColumnProperty = column.ColumnProperty.Set(ColumnProperty.NotNull); + column.ColumnProperty = column.ColumnProperty.Clear(ColumnProperty.Null); + base.ChangeColumn(table, column); + } + } + } + + public override bool ColumnExists(string table, string column) + { + string schema; + + if (!TableExists(table)) + { + return false; + } + + var firstIndex = table.IndexOf("."); + + if (firstIndex >= 0) + { + schema = table.Substring(0, firstIndex); + table = table.Substring(firstIndex + 1); + } + else + { + schema = _defaultSchema; + } + + using var cmd = CreateCommand(); + using var reader = base.ExecuteQuery(cmd, string.Format("SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '{0}' AND TABLE_NAME='{1}' AND COLUMN_NAME='{2}'", schema, table, column)); + return reader.Read(); + } + + public override void RemoveColumnDefaultValue(string table, string column) + { + var sql = string.Format("SELECT name FROM sys.default_constraints WHERE parent_object_id = OBJECT_ID('{0}') AND parent_column_id = (SELECT column_id FROM sys.columns WHERE name = '{1}' AND object_id = OBJECT_ID('{0}'))", table, column); + var constraintName = ExecuteScalar(sql); + if (constraintName != null) + { + RemoveConstraint(table, constraintName.ToString()); + } + } + + public override Index[] GetIndexes(string table) + { + // This migrator does not support schemas so we fall back to dbo in SQL Server + var schemaName = "dbo"; + + var indexes = new List(); + + var sql = @$"SELECT + s.name AS SchemaName, + t.name AS TableName, + i.name AS IndexName, + i.type_desc AS IndexType, + i.is_unique AS IsUnique, + i.is_primary_key AS IsPrimaryKey, + i.is_unique_constraint AS IsUniqueConstraint, + ic.index_column_id AS ColumnOrder, + col.name AS ColumnName, + ic.is_descending_key AS IsDescending, + ic.is_included_column AS IsIncludedColumn, + i.has_filter AS IsFilteredIndex, + i.filter_definition AS FilterDefinition + FROM + sys.indexes i + JOIN sys.tables t ON i.object_id = t.object_id + JOIN sys.schemas s ON t.schema_id = s.schema_id + JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id + JOIN sys.columns col ON ic.object_id = col.object_id AND ic.column_id = col.column_id + WHERE + LOWER(t.name) = '{table.ToLowerInvariant()}' AND + LOWER(s.name) = '{schemaName.ToLowerInvariant()}' + ORDER BY + s.name, t.name, i.name, ic.index_column_id"; + + List indexItems = []; + + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, string.Format(sql, table))) + { + var columnNameOrdinal = reader.GetOrdinal("ColumnName"); + var columnOrderOrdinal = reader.GetOrdinal("ColumnOrder"); + var filterDefinitionOrdinal = reader.GetOrdinal("FilterDefinition"); + var indexNameOrdinal = reader.GetOrdinal("IndexName"); + var indexTypeOrdinal = reader.GetOrdinal("IndexType"); + var isDescendingOrdinal = reader.GetOrdinal("IsDescending"); + var isFilteredIndexOrdinal = reader.GetOrdinal("IsFilteredIndex"); + var isIncludedColumnOrdinal = reader.GetOrdinal("IsIncludedColumn"); + var isPrimaryKeyOrdinal = reader.GetOrdinal("IsPrimaryKey"); + var isUniqueConstraintOrdinal = reader.GetOrdinal("IsUniqueConstraint"); + var isUniqueOrdinal = reader.GetOrdinal("IsUnique"); + var schemaNameOrdinal = reader.GetOrdinal("SchemaName"); + var tableNameOrdinal = reader.GetOrdinal("TableName"); + + while (reader.Read()) + { + var indexItem = new IndexItem + { + Clustered = reader.GetString(indexTypeOrdinal) == "CLUSTERED", + ColumnName = reader.GetString(columnNameOrdinal), + ColumnOrder = reader.GetInt32(columnOrderOrdinal), + FilterString = !reader.IsDBNull(filterDefinitionOrdinal) ? reader.GetString(filterDefinitionOrdinal) : null, + IsFilteredIndex = reader.GetBoolean(isFilteredIndexOrdinal), + IsIncludedColumn = reader.GetBoolean(isIncludedColumnOrdinal), + Name = reader.GetString(indexNameOrdinal), + PrimaryKey = reader.GetBoolean(isPrimaryKeyOrdinal), + SchemaName = reader.GetString(schemaNameOrdinal), + TableName = reader.GetString(tableNameOrdinal), + Unique = reader.GetBoolean(isUniqueOrdinal), + UniqueConstraint = reader.GetBoolean(isUniqueConstraintOrdinal), + }; + + indexItems.Add(indexItem); + } + } + + var indexGroups = indexItems.GroupBy(x => new + { + x.Name, + x.SchemaName, + x.TableName, + }); + + foreach (var indexGroup in indexGroups) + { + var first = indexGroup.First(); + + List filterItems = []; + + if (!string.IsNullOrWhiteSpace(first.FilterString)) + { + const string unexpectedPatternString = "Unexpected pattern in filter string detected. Not implemented yet - please file an issue"; + var comparisonStrings = _dialect.GetComparisonStrings(); + var stripOuterBracesRegex = new Regex(@"(?<=^\().+(?=\)$)"); + var stripBracesMatch = stripOuterBracesRegex.Match(first.FilterString.Trim()); + + if (!stripBracesMatch.Success) + { + throw new NotImplementedException(unexpectedPatternString); + } + + var andSplitted = Regex.Split(stripBracesMatch.Value, @" AND (?=\[)") + .Select(x => x.Trim()) + .ToList(); + + var columns = GetColumns(table: table); + + foreach (var andSplittedItem in andSplitted) + { + var filterItem = new FilterItem(); + // We assume nobody uses column names with brackets in it. + var columnRegex = new Regex(@"(?<=^\[)[^\]]+"); + var columnMatch = columnRegex.Match(andSplittedItem); + + if (!columnMatch.Success) + { + throw new NotImplementedException(unexpectedPatternString); + } + + filterItem.ColumnName = columnMatch.Value; + var column = columns.OrderByDescending(x => x.Name).First(x => x.Name.Equals(filterItem.ColumnName, StringComparison.OrdinalIgnoreCase)); + + var remainingString = andSplittedItem.Substring(filterItem.ColumnName.Length + 2); + var comparisonString = comparisonStrings.OrderByDescending(x => x.Length) + .First(x => remainingString.StartsWith(x)); + + filterItem.Filter = _dialect.GetFilterTypeByComparisonString(comparisonString); + remainingString = remainingString.Substring(comparisonString.Length); + + var valueRegex = new Regex(@"(?<=^[\(|']).+(?=[\)|']$)"); + var valueStringMatch = valueRegex.Match(remainingString); + + if (!valueStringMatch.Success) + { + throw new NotImplementedException(unexpectedPatternString); + } + + var valueAsString = valueStringMatch.Value; + + filterItem.Value = column.MigratorDbType switch + { + MigratorDbType.Int16 => short.Parse(valueAsString), + MigratorDbType.Int32 => int.Parse(valueAsString), + MigratorDbType.Int64 => long.Parse(valueAsString), + MigratorDbType.UInt16 => ushort.Parse(valueAsString), + MigratorDbType.UInt32 => uint.Parse(valueAsString), + MigratorDbType.UInt64 => ulong.Parse(valueAsString), + MigratorDbType.Decimal => decimal.Parse(valueAsString), + MigratorDbType.Boolean => valueAsString == "1" || valueAsString.Equals("true", StringComparison.OrdinalIgnoreCase), + MigratorDbType.String => valueAsString, + _ => throw new NotImplementedException("Type not yet supported. Please file an issue."), + }; + + filterItems.Add(filterItem); + } + } + + var index = new Index + { + Clustered = first.Clustered, + FilterItems = filterItems, + IncludeColumns = [.. indexGroup.Where(x => x.IsIncludedColumn) + .OrderBy(x => x.ColumnOrder) + .Select(x => x.ColumnName) + .Distinct()], + KeyColumns = [.. indexGroup.Where(x => !x.IsIncludedColumn) + .OrderBy(x => x.ColumnOrder) + .Select(x => x.ColumnName) + .Distinct()], + Name = first.Name, + PrimaryKey = first.PrimaryKey, + Unique = first.Unique, + UniqueConstraint = first.UniqueConstraint, + }; + + indexes.Add(index); + + } + + return [.. indexes]; + } + + public override int GetColumnContentSize(string table, string columnName) + { + var result = ExecuteScalar("SELECT MAX(LEN(" + this.QuoteColumnNameIfRequired(columnName) + ")) FROM " + this.QuoteTableNameIfRequired(table)); + + if (result == DBNull.Value) + { + return 0; + } + + return Convert.ToInt32(result); + } + + public override Column[] GetColumns(string table) + { + string schema; + + var firstIndex = table.IndexOf("."); + if (firstIndex >= 0) + { + schema = table.Substring(0, firstIndex); + table = table.Substring(firstIndex + 1); + } + else + { + schema = _defaultSchema; + } + + var pkColumns = new List(); + try + { + pkColumns = ExecuteStringQuery("SELECT cu.COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE cu WHERE EXISTS ( SELECT tc.* FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc WHERE tc.TABLE_NAME = '{0}' AND tc.CONSTRAINT_TYPE = 'PRIMARY KEY' AND tc.CONSTRAINT_NAME = cu.CONSTRAINT_NAME )", table); + } + catch (Exception) + { } + + var idtColumns = new List(); + try + { + idtColumns = ExecuteStringQuery("SELECT COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS where TABLE_SCHEMA = '{1}' and TABLE_NAME = '{0}' and COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') = 1", table, schema); + } + catch (Exception) + { } + + var columns = new List(); + using (var cmd = CreateCommand()) + using ( + var reader = + ExecuteQuery(cmd, + string.Format("SELECT COLUMN_NAME, IS_NULLABLE, DATA_TYPE, ISNULL(CHARACTER_MAXIMUM_LENGTH , NUMERIC_PRECISION), COLUMN_DEFAULT, NUMERIC_SCALE, CHARACTER_MAXIMUM_LENGTH from INFORMATION_SCHEMA.COLUMNS where table_name = '{0}'", table))) + { + while (reader.Read()) + { + var column = new Column(reader.GetString(0), DbType.String); + + var defaultValueOrdinal = reader.GetOrdinal("COLUMN_DEFAULT"); + var dataTypeOrdinal = reader.GetOrdinal("DATA_TYPE"); + var characterMaximumLengthOrdinal = reader.GetOrdinal("CHARACTER_MAXIMUM_LENGTH"); + + var defaultValueString = reader.IsDBNull(defaultValueOrdinal) ? null : reader.GetString(defaultValueOrdinal).Trim(); + var characterMaximumLength = reader.IsDBNull(characterMaximumLengthOrdinal) ? (int?)null : reader.GetInt32(characterMaximumLengthOrdinal); + + if (pkColumns.Contains(column.Name)) + { + column.ColumnProperty |= ColumnProperty.PrimaryKey; + } + + if (idtColumns.Contains(column.Name)) + { + column.ColumnProperty |= ColumnProperty.Identity; + } + + var nullableStr = reader.GetString(1); + var isNullable = nullableStr == "YES"; + + var dataTypeString = reader.GetString(dataTypeOrdinal); + + if (dataTypeString == "date") + { + column.MigratorDbType = MigratorDbType.Date; + } + else if (dataTypeString == "int") + { + column.MigratorDbType = MigratorDbType.Int32; + } + else if (dataTypeString == "bigint") + { + column.MigratorDbType = MigratorDbType.Int64; + } + else if (dataTypeString == "smallint") + { + column.MigratorDbType = MigratorDbType.Int16; + } + else if (dataTypeString == "tinyint") + { + column.MigratorDbType = MigratorDbType.Byte; + } + else if (dataTypeString == "bit") + { + column.MigratorDbType = MigratorDbType.Boolean; + } + else if (dataTypeString == "money") + { + column.MigratorDbType = MigratorDbType.Currency; + } + else if (dataTypeString == "float") + { + column.MigratorDbType = MigratorDbType.Double; + } + else if (new[] { "text", "nchar", "ntext", "varchar", "nvarchar" }.Contains(dataTypeString)) + { + // We use string for all string-like data types. + column.MigratorDbType = MigratorDbType.String; + column.Size = characterMaximumLength.Value; + } + else if (dataTypeString == "decimal") + { + column.MigratorDbType = MigratorDbType.Decimal; + } + else if (dataTypeString == "datetime") + { + column.MigratorDbType = MigratorDbType.DateTime; + } + else if (dataTypeString == "datetime2") + { + column.MigratorDbType = MigratorDbType.DateTime2; + } + else if (dataTypeString == "datetimeoffset") + { + column.MigratorDbType = MigratorDbType.DateTimeOffset; + } + else if (dataTypeString == "binary" || dataTypeString == "varbinary") + { + column.MigratorDbType = MigratorDbType.Binary; + } + else if (dataTypeString == "uniqueidentifier") + { + column.MigratorDbType = MigratorDbType.Guid; + } + else if (dataTypeString == "real") + { + column.MigratorDbType = MigratorDbType.Single; + } + else + { + throw new NotImplementedException($"The data type '{dataTypeString}' is not implemented yet. Please file an issue."); + } + + if (!reader.IsDBNull(3)) + { + column.Size = reader.GetInt32(3); + } + + if (defaultValueString != null) + { + var bracesStrippedString = defaultValueString.Replace("(", "").Replace(")", "").Trim(); + var bracesAndSingleQuoteStrippedString = bracesStrippedString.Replace("'", ""); + + if (column.Type == DbType.Int16 || column.Type == DbType.Int32 || column.Type == DbType.Int64) + { + column.DefaultValue = long.Parse(bracesAndSingleQuoteStrippedString, CultureInfo.InvariantCulture); + } + else if (column.Type == DbType.UInt16 || column.Type == DbType.UInt32 || column.Type == DbType.UInt64) + { + column.DefaultValue = ulong.Parse(bracesAndSingleQuoteStrippedString, CultureInfo.InvariantCulture); + } + else if (column.Type == DbType.Double || column.Type == DbType.Single) + { + column.DefaultValue = double.Parse(bracesAndSingleQuoteStrippedString, CultureInfo.InvariantCulture); + } + else if (column.Type == DbType.Boolean) + { + var truthy = new string[] { "'TRUE'", "1" }; + var falsy = new string[] { "'FALSE'", "0" }; + + if (truthy.Contains(bracesStrippedString)) + { + column.DefaultValue = true; + } + else if (falsy.Contains(bracesStrippedString)) + { + column.DefaultValue = false; + } + else if (bracesStrippedString == "NULL") + { + column.DefaultValue = null; + } + else + { + throw new NotImplementedException($"Cannot parse the boolean default value '{defaultValueString}' of column '{column.Name}'"); + } + } + else if (column.Type == DbType.DateTime || column.Type == DbType.DateTime2) + { + // (CONVERT([datetime],'2000-01-02 03:04:05.000',(121))) + // 121 is a pattern: it contains milliseconds + // Search for 121 here: https://learn.microsoft.com/de-de/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver17 + var regexDateTimeConvert121 = new Regex(@"(?<=^\(CONVERT\([\[]+datetime[\]]+,')[^']+(?='\s*,\s*\(121\s*\)\)\)$)"); + var match121 = regexDateTimeConvert121.Match(defaultValueString); + + if (match121.Success) + { + // We convert to UTC since we restrict date time default values to UTC on default value definition. + column.DefaultValue = DateTime.ParseExact(match121.Value, "yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); + } + else if (defaultValueString is string defVal) + { + // Not tested + var dt = defVal; + if (defVal.StartsWith("'")) + { + dt = defVal.Substring(1, defVal.Length - 2); + } + + // We convert to UTC since we restrict date time default values to UTC on default value definition. + column.DefaultValue = DateTime.ParseExact(dt, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); + } + else + { + throw new NotImplementedException($"Cannot interpret {column.DefaultValue} in column '{column.Name}' unexpected pattern."); + } + } + else if (column.Type == DbType.Guid) + { + column.DefaultValue = Guid.Parse(bracesAndSingleQuoteStrippedString); + } + else if (column.MigratorDbType == MigratorDbType.Decimal) + { + // We assume ((1.234)) + column.DefaultValue = decimal.Parse(bracesAndSingleQuoteStrippedString, CultureInfo.InvariantCulture); + } + else if (column.MigratorDbType == MigratorDbType.String) + { + column.DefaultValue = bracesAndSingleQuoteStrippedString; + } + else if (column.MigratorDbType == MigratorDbType.Binary) + { + if (bracesStrippedString.StartsWith("0x")) + { + var hexString = bracesStrippedString.Substring(2); + + // Not available in old .NET version: Convert.FromHexString(hexString); + + column.DefaultValue = Enumerable.Range(0, hexString.Length / 2) + .Select(x => Convert.ToByte(hexString.Substring(x * 2, 2), 16)) + .ToArray(); + } + else + { + throw new NotImplementedException($"Cannot parse the binary default value of '{column.Name}'. The value is '{defaultValueString}'"); + } + } + else if (column.MigratorDbType == MigratorDbType.Byte) + { + column.DefaultValue = byte.Parse(bracesAndSingleQuoteStrippedString); + } + else + { + throw new NotImplementedException($"Cannot parse the default value of {column.Name} type '{column.MigratorDbType}'. It is not yet implemented - file an issue."); + } + } + if (!reader.IsDBNull(5)) + { + if (column.Type == DbType.Decimal) + { + column.Size = reader.GetInt32(5); + } + } + + column.ColumnProperty |= isNullable ? ColumnProperty.Null : ColumnProperty.NotNull; + + columns.Add(column); + } + } + + return columns.ToArray(); + } + + public override List GetDatabases() + { + return ExecuteStringQuery("SELECT name FROM sys.databases"); + } + + public override void KillDatabaseConnections(string databaseName) + { + ExecuteNonQuery(string.Format( + "USE [master]" + System.Environment.NewLine + + "ALTER DATABASE {0} SET SINGLE_USER WITH ROLLBACK IMMEDIATE", databaseName)); + } + + public override void DropDatabases(string databaseName) + { + ExecuteNonQuery(string.Format("USE [master]" + System.Environment.NewLine + "DROP DATABASE {0}", databaseName)); + } + + public override void RemoveColumn(string table, string column) + { + DeleteColumnConstraints(table, column); + DeleteColumnIndexes(table, column); + RemoveColumnDefaultValue(table, column); + base.RemoveColumn(table, column); + } + + public override void RenameColumn(string tableName, string oldColumnName, string newColumnName) + { + if (!TableExists(tableName)) + { + throw new MigrationException($"The table '{tableName}' does not exist"); + } + + if (ColumnExists(tableName, newColumnName)) + { + throw new MigrationException(string.Format("Table '{0}' has column named '{1}' already", tableName, newColumnName)); + } + + if (!ColumnExists(tableName, oldColumnName)) + { + throw new MigrationException(string.Format("The table '{0}' does not have a column named '{1}'", tableName, oldColumnName)); + } + + if (ColumnExists(tableName, oldColumnName)) + { + ExecuteNonQuery(string.Format("EXEC sp_rename '{0}.{1}', '{2}', 'COLUMN'", tableName, oldColumnName, newColumnName)); + } + } + + public override void RenameTable(string oldName, string newName) + { + if (TableExists(newName)) + { + throw new MigrationException(string.Format("Table with name '{0}' already exists", newName)); + } + + if (!TableExists(oldName)) + { + throw new MigrationException(string.Format("Table with name '{0}' does not exist to rename", oldName)); + } + + ExecuteNonQuery(string.Format("EXEC sp_rename '{0}', '{1}'", oldName, newName)); + } + + public override void UpdateTargetFromSource(string tableSourceNotQuoted, string tableTargetNotQuoted, ColumnPair[] fromSourceToTargetColumnPairs, ColumnPair[] conditionColumnPairs) + { + if (!TableExists(tableSourceNotQuoted)) + { + throw new Exception($"Table '{tableSourceNotQuoted}' given in '{nameof(tableSourceNotQuoted)}' does not exist"); + } + + if (!TableExists(tableTargetNotQuoted)) + { + throw new Exception($"Table '{tableTargetNotQuoted}' given in '{nameof(tableTargetNotQuoted)}' does not exist"); + } + + if (fromSourceToTargetColumnPairs.Length == 0) + { + throw new Exception($"{nameof(fromSourceToTargetColumnPairs)} is empty."); + } + + if (fromSourceToTargetColumnPairs.Any(x => string.IsNullOrWhiteSpace(x.ColumnNameSource) || string.IsNullOrWhiteSpace(x.ColumnNameTarget))) + { + throw new Exception($"One of the strings in {nameof(fromSourceToTargetColumnPairs)} is null or empty"); + } + + if (conditionColumnPairs.Length == 0) + { + throw new Exception($"{nameof(conditionColumnPairs)} is empty."); + } + + if (conditionColumnPairs.Any(x => string.IsNullOrWhiteSpace(x.ColumnNameSource) || string.IsNullOrWhiteSpace(x.ColumnNameTarget))) + { + throw new Exception($"One of the strings in {nameof(conditionColumnPairs)} is null or empty"); + } + + var tableNameSource = QuoteTableNameIfRequired(tableSourceNotQuoted); + var tableNameTarget = QuoteTableNameIfRequired(tableTargetNotQuoted); + + var conditionStrings = conditionColumnPairs.Select(x => $"t.{QuoteColumnNameIfRequired(x.ColumnNameTarget)} = s.{QuoteColumnNameIfRequired(x.ColumnNameSource)}"); + + var assignStrings = fromSourceToTargetColumnPairs.Select(x => $"{QuoteColumnNameIfRequired(x.ColumnNameTarget)} = s.{QuoteColumnNameIfRequired(x.ColumnNameSource)}").ToList(); + + var conditionStringsJoined = string.Join(" AND ", conditionStrings); + var assignStringsJoined = string.Join(", ", assignStrings); + + var sql = $"MERGE INTO {tableNameTarget} t USING {tableNameSource} s ON ({conditionStringsJoined}) WHEN MATCHED THEN UPDATE SET {assignStringsJoined};"; + ExecuteNonQuery(sql); + } + + // Deletes all constraints linked to a column. Sql Server + // doesn't seems to do this. + private void DeleteColumnConstraints(string table, string column) + { + var sqlContrainte = FindConstraints(table, column); + var constraints = new List(); + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, sqlContrainte)) + { + while (reader.Read()) + { + constraints.Add(reader.GetString(0)); + } + } + // Can't share the connection so two phase modif + foreach (var constraint in constraints) + { + RemoveForeignKey(table, constraint); + } + } + + private void DeleteColumnIndexes(string table, string column) + { + var sqlIndex = this.FindIndexes(table, column); + var indexes = new List(); + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, sqlIndex)) + { + while (reader.Read()) + { + indexes.Add(reader.GetString(0)); + } + } + // Can't share the connection so two phase modif + foreach (var index in indexes) + { + this.RemoveIndex(table, index); + } + } + + protected virtual string FindIndexes(string table, string column) + { + return string.Format(@" +select + i.name as IndexName +from sys.indexes i +join sys.objects o on i.object_id = o.object_id +join sys.index_columns ic on ic.object_id = i.object_id + and ic.index_id = i.index_id +join sys.columns co on co.object_id = i.object_id + and co.column_id = ic.column_id +where (select count(*) from sys.index_columns ic1 where ic1.object_id = i.object_id and ic1.index_id = i.index_id) = 1 +and o.[Name] = '{0}' +and co.[Name] = '{1}'", + table, column); + } + + // FIXME: We should look into implementing this with INFORMATION_SCHEMA if possible + // so that it would be usable by all the SQL Server implementations + protected virtual string FindConstraints(string table, string column) + { + return string.Format(@"SELECT DISTINCT CU.CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE CU +INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC +ON CU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME +AND CU.TABLE_NAME = '{0}' +AND CU.COLUMN_NAME = '{1}'", + table, column); + } + + public override bool IndexExists(string table, string name) + { + using var cmd = CreateCommand(); + using var reader = + ExecuteQuery(cmd, string.Format("SELECT top 1 * FROM sys.indexes WHERE object_id = OBJECT_ID('{0}') AND name = '{1}'", table, name)); + return reader.Read(); + } + + public override void RemoveIndex(string table, string name) + { + if (TableExists(table) && IndexExists(table, name)) + { + ExecuteNonQuery(string.Format("DROP INDEX {0} ON {1}", QuoteConstraintNameIfRequired(name), QuoteTableNameIfRequired(table))); + } + } + + protected override string GetPrimaryKeyConstraintName(string table) + { + using var cmd = CreateCommand(); + using var reader = + ExecuteQuery(cmd, string.Format("SELECT name FROM sys.indexes WHERE object_id = OBJECT_ID('{0}') AND is_primary_key = 1", table)); + return reader.Read() ? reader.GetString(0) : null; + } + + protected override void ConfigureParameterWithValue(IDbDataParameter parameter, int index, object value) + { + if (value is ushort) + { + parameter.DbType = DbType.Int32; + parameter.Value = value; + } + else if (value is uint) + { + parameter.DbType = DbType.Int64; + parameter.Value = value; + } + else if (value is ulong) + { + parameter.DbType = DbType.Decimal; + parameter.Value = value; + } + else + { + base.ConfigureParameterWithValue(parameter, index, value); + } + } + + public override string Concatenate(params string[] strings) + { + return string.Join(" + ", strings); + } +} diff --git a/src/Migrator/Providers/Impl/Sybase/SybaseDialect.cs b/src/Migrator/Providers/Impl/Sybase/SybaseDialect.cs new file mode 100644 index 00000000..a8bbdf13 --- /dev/null +++ b/src/Migrator/Providers/Impl/Sybase/SybaseDialect.cs @@ -0,0 +1,23 @@ +using System.Data; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.Sybase; + +public class SybaseDialect : Dialect +{ + public SybaseDialect() + { + } + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) + { + return new SybaseTransformationProvider(dialect, connectionString, scope, providerName); + } + + public override ITransformationProvider GetTransformationProvider(Dialect dialect, IDbConnection connection, + string defaultSchema, + string scope, string providerName) + { + return new SybaseTransformationProvider(dialect, connection, scope, providerName); + } +} diff --git a/src/Migrator/Providers/Impl/Sybase/SybaseTransformationProvider.cs b/src/Migrator/Providers/Impl/Sybase/SybaseTransformationProvider.cs new file mode 100644 index 00000000..46464db2 --- /dev/null +++ b/src/Migrator/Providers/Impl/Sybase/SybaseTransformationProvider.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Data; + +namespace DotNetProjects.Migrator.Providers.Impl.Sybase; + +public class SybaseTransformationProvider : TransformationProvider +{ + public SybaseTransformationProvider(Dialect dialect, string connectionString, string scope, string providerName) + : base(dialect, connectionString, null, scope) + { + if (string.IsNullOrEmpty(providerName)) + { + providerName = "Sybase.Data.AseClient"; + } + + var fac = DbProviderFactoriesHelper.GetFactory(providerName, null, null); + _connection = fac.CreateConnection(); + _connection.ConnectionString = _connectionString; + this._connection.Open(); + } + + public SybaseTransformationProvider(Dialect dialect, IDbConnection connection, string scope, string providerName) + : base(dialect, connection, null, scope) + { + } + + public override List GetDatabases() + { + throw new NotImplementedException(); + } + + public override bool ConstraintExists(string table, string name) + { + throw new NotImplementedException(); + } + + public override bool IndexExists(string table, string name) + { + throw new NotImplementedException(); + } +} diff --git a/src/Migrator/Providers/Models/ColumnPair.cs b/src/Migrator/Providers/Models/ColumnPair.cs new file mode 100644 index 00000000..a0fb51a6 --- /dev/null +++ b/src/Migrator/Providers/Models/ColumnPair.cs @@ -0,0 +1,17 @@ +namespace DotNetProjects.Migrator.Framework.Models; + +/// +/// Represents a column pair for usage e.g. in a column comparison. +/// +public class ColumnPair +{ + /// + /// Gets or sets the column name of the source table. Use the unquoted column name. + /// + public string ColumnNameSource { get; set; } + + /// + /// Gets or sets the column name of the target table. Use the unquoted column name. + /// + public string ColumnNameTarget { get; set; } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Models/FilterTypeToString.cs b/src/Migrator/Providers/Models/FilterTypeToString.cs new file mode 100644 index 00000000..8b4adb0e --- /dev/null +++ b/src/Migrator/Providers/Models/FilterTypeToString.cs @@ -0,0 +1,19 @@ +using DotNetProjects.Migrator.Providers.Models.Indexes.Enums; + +namespace DotNetProjects.Migrator.Providers.Models; + +/// +/// Model for filter type => filter string mapping. +/// +public class FilterTypeToString +{ + /// + /// Gets or sets the filter type + /// + public FilterType FilterType { get; set; } + + /// + /// Gets or sets the filter string like >, <, =, >= etc. + /// + public string FilterString { get; set; } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Models/ForeignKeyConstraintItem.cs b/src/Migrator/Providers/Models/ForeignKeyConstraintItem.cs new file mode 100644 index 00000000..03f58e20 --- /dev/null +++ b/src/Migrator/Providers/Models/ForeignKeyConstraintItem.cs @@ -0,0 +1,11 @@ +namespace DotNetProjects.Migrator.Providers.Models; + +public class ForeignKeyConstraintItem +{ + public string SchemaName { get; set; } + public string ForeignKeyName { get; set; } + public string ChildTableName { get; set; } + public string ChildColumnName { get; set; } + public string ParentTableName { get; set; } + public string ParentColumnName { get; set; } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Models/Indexes/Enums/FilterType.cs b/src/Migrator/Providers/Models/Indexes/Enums/FilterType.cs new file mode 100644 index 00000000..44ad2b5a --- /dev/null +++ b/src/Migrator/Providers/Models/Indexes/Enums/FilterType.cs @@ -0,0 +1,36 @@ +namespace DotNetProjects.Migrator.Providers.Models.Indexes.Enums; + +public enum FilterType +{ + None = 0, + + /// + /// Greater than + /// + GreaterThan, + + /// + /// Greater than or equal to + /// + GreaterThanOrEqualTo, + + /// + /// Equal to + /// + EqualTo, + + /// + /// Smaller than + /// + SmallerThan, + + /// + /// Smaller than or equal to + /// + SmallerThanOrEqualTo, + + /// + /// Not equal to + /// + NotEqualTo +} \ No newline at end of file diff --git a/src/Migrator/Providers/Models/Indexes/FilterItem.cs b/src/Migrator/Providers/Models/Indexes/FilterItem.cs new file mode 100644 index 00000000..6d034146 --- /dev/null +++ b/src/Migrator/Providers/Models/Indexes/FilterItem.cs @@ -0,0 +1,21 @@ +using DotNetProjects.Migrator.Providers.Models.Indexes.Enums; + +namespace DotNetProjects.Migrator.Providers.Models.Indexes; + +public class FilterItem +{ + /// + /// Gets or sets the not quoted column name. If the column name is not a reserved word it will be converted to lower cased string in Postgre and to upper cased string in Oracle if you use the default settings. + /// + public string ColumnName { get; set; } + + /// + /// Gets or sets the filter. + /// + public FilterType Filter { get; set; } + + /// + /// Gets or sets the value used in the comparison. It needs to be a static not dynamic value. Currently we support bool, byte, short, int, long + /// + public object Value { get; set; } +} \ No newline at end of file diff --git a/src/Migrator/Providers/Models/Indexes/IndexItem.cs b/src/Migrator/Providers/Models/Indexes/IndexItem.cs new file mode 100644 index 00000000..73f64d5e --- /dev/null +++ b/src/Migrator/Providers/Models/Indexes/IndexItem.cs @@ -0,0 +1,66 @@ +namespace DotNetProjects.Migrator.Providers.Models.Indexes; + +public class IndexItem +{ + /// + /// Indicates whether the index is clustered (false for NONCLUSTERED). + /// + public bool Clustered { get; set; } + + /// + /// Gets or sets the column order. + /// + public int ColumnOrder { get; set; } + + /// + /// Gets or sets the index name. + /// + public string Name { get; set; } + + /// + /// Indicates whether the index is unique. + /// + public bool Unique { get; set; } + + /// + /// Indicates whether it is a primary key constraint. + /// + public bool PrimaryKey { get; internal set; } + + /// + /// Indicates whether it is a unique constraint. + /// + public bool UniqueConstraint { get; internal set; } + + /// + /// Gets or sets the column name. + /// + public string ColumnName { get; set; } + + /// + /// Gets or sets the included columns. Not supported in SQLite and Oracle. + /// + public bool IsIncludedColumn { get; set; } + + /// + /// Indicates whether the index is a filtered index. + /// + public bool IsFilteredIndex { get; set; } + + /// + /// Gets or sets items that represent filter expressions in filtered indexes. Currently string, integer and boolean values are supported. + /// Attention: In SQL Server the column used in the filter must be NOT NULL. + /// + public string FilterString { get; set; } + + /// + /// Gets or sets the schema name. + /// + public string SchemaName { get; set; } + + /// + /// Gets or sets the table name. + /// + public string TableName { get; set; } + +} \ No newline at end of file diff --git a/src/Migrator/Providers/NoOpTransformationProvider.cs b/src/Migrator/Providers/NoOpTransformationProvider.cs new file mode 100644 index 00000000..145918df --- /dev/null +++ b/src/Migrator/Providers/NoOpTransformationProvider.cs @@ -0,0 +1,605 @@ +using System; +using System.Collections.Generic; +using System.Data; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Framework.Models; +using DotNetProjects.Migrator.Framework.SchemaBuilder; +using ForeignKeyConstraint = DotNetProjects.Migrator.Framework.ForeignKeyConstraint; +using Index = DotNetProjects.Migrator.Framework.Index; + +namespace DotNetProjects.Migrator.Providers; + +/// +/// No Op (Null Object Pattern) implementation of the ITransformationProvider +/// +public class NoOpTransformationProvider : ITransformationProvider +{ + public static readonly NoOpTransformationProvider Instance = new NoOpTransformationProvider(); + + private NoOpTransformationProvider() + { + } + + public int? CommandTimeout { get; set; } + + public IDialect Dialect + { + get { return null; } + } + + public bool IsMigrationApplied(long version, string scope) + { + throw new NotImplementedException(); + } + + public string ConnectionString + { + get { return string.Empty; } + } + + public virtual ILogger Logger + { + get { return null; } + set { } + } + + public string[] GetTables() + { + return null; + } + + public ForeignKeyConstraint[] GetForeignKeyConstraints(string table) + { + return null; + } + + public int Insert(string table, string[] columns, object[] values) + { + return 0; + } + + public int InsertIfNotExists(string table, string[] columns, object[] values, string[] whereColumns, object[] whereValues) + { + return 0; + } + + public List ExecuteStringQuery(string sql, params object[] args) + { + return new List(); + } + + public Index[] GetIndexes(string table) + { + return null; + } + + public Column[] GetColumns(string table) + { + return null; + } + + public Column GetColumnByName(string table, string column) + { + return null; + } + + public void RemoveForeignKey(string table, string name) + { + // No Op + } + + public void RemoveConstraint(string table, string name) + { + // No Op + } + + public void RemoveAllConstraints(string table) + { + // No Op + } + + public void RemovePrimaryKey(string table) + { + // No Op + } + + public void AddView(string name, string tableName, params IViewElement[] viewElements) + { + // No Op + } + + public void AddView(string name, string tableName, params IViewField[] fields) + { + throw new NotImplementedException(); + } + + public void AddTable(string name, params IDbField[] columns) + { + // No Op + } + + public void AddTable(string name, string engine, params IDbField[] columns) + { + // No Op + } + + public void RemoveTable(string name) + { + // No Op + } + + public void RenameTable(string oldName, string newName) + { + // No Op + } + + public void RenameColumn(string tableName, string oldColumnName, string newColumnName) + { + // No Op + } + + public void RemoveColumn(string table, string column) + { + // No Op + } + + public void RemoveColumnDefaultValue(string table, string column) + { + // No Op + } + + public bool ColumnExists(string table, string column) + { + return false; + } + + public bool TableExists(string table) + { + return false; + } + + public bool ViewExists(string view) + { + return false; + } + + public void AddColumn(string table, string column, DbType type, int size, ColumnProperty property, object defaultValue) + { + // No Op + } + + public void AddColumn(string table, string column, DbType type) + { + // No Op + } + + public void AddColumn(string table, string column, DbType type, object defaultValue) + { + // No Op + } + + public void AddColumn(string table, string column, DbType type, int size) + { + // No Op + } + + public void AddColumn(string table, string column, DbType type, ColumnProperty property) + { + // No Op + } + + public void AddColumn(string table, string column, DbType type, int size, ColumnProperty property) + { + // No Op + } + + public void AddPrimaryKey(string name, string table, params string[] columns) + { + // No Op + } + public void AddPrimaryKeyNonClustered(string name, string table, params string[] columns) + { + // No Op + } + public void GenerateForeignKey(string primaryTable, string primaryColumn, string refTable, string refColumn) + { + // No Op + } + + public void GenerateForeignKey(string primaryTable, string[] primaryColumns, string refTable, string[] refColumns) + { + // No Op + } + + public void GenerateForeignKey(string primaryTable, string primaryColumn, string refTable, string refColumn, ForeignKeyConstraintType constraint) + { + // No Op + } + + public void GenerateForeignKey(string primaryTable, string[] primaryColumns, string refTable, + string[] refColumns, ForeignKeyConstraintType constraint) + { + // No Op + } + + public void AddForeignKey(string name, string primaryTable, string primaryColumn, string refTable, + string refColumn) + { + // No Op + } + + public void AddForeignKey(string name, string primaryTable, string[] primaryColumns, string refTable, string[] refColumns) + { + // No Op + } + + public void AddForeignKey(string name, string primaryTable, string primaryColumn, string refTable, string refColumn, ForeignKeyConstraintType constraint) + { + // No Op + } + + public void AddForeignKey(string name, string primaryTable, string[] primaryColumns, string refTable, + string[] refColumns, ForeignKeyConstraintType constraint) + { + // No Op + } + + public void AddUniqueConstraint(string name, string table, params string[] columns) + { + // No Op + } + + public void AddCheckConstraint(string name, string table, string checkSql) + { + // No Op + } + + public bool ConstraintExists(string table, string name) + { + return false; + } + + public void ChangeColumn(string table, Column column) + { + // No Op + } + + public bool PrimaryKeyExists(string table, string name) + { + return false; + } + + public int ExecuteNonQuery(string sql) + { + return 0; + } + public int ExecuteNonQuery(string sql, int timeout) + { + return 0; + } + public int ExecuteNonQuery(string sql, int timeout, object[] parameters) + { + return 0; + } + + public IDataReader ExecuteQuery(IDbCommand cmd, string sql) + { + return null; + } + + public IDbCommand CreateCommand() + { + throw new NotImplementedException(); + } + + public object ExecuteScalar(string sql) + { + return null; + } + + public IDataReader Select(IDbCommand cmd, string table, string[] columns, string[] whereColumns, object[] whereValues) + { + return null; + } + + public IDataReader SelectComplex(IDbCommand cmd, string table, string[] columns, string[] whereColumns = null, + object[] whereValues = null, string[] nullWhereColumns = null, string[] notNullWhereColumns = null) + { + return null; + } + + public IDataReader Select(IDbCommand cmd, string what, string from) + { + return null; + } + + public IDataReader Select(IDbCommand cmd, string what, string from, string where) + { + return null; + } + + public object SelectScalar(string what, string from) + { + return null; + } + + public object SelectScalar(string what, string from, string where) + { + return null; + } + + public int Update(string table, string[] columns, object[] values) + { + return 0; + } + + public int Update(string table, string[] columns, object[] values, string where) + { + return 0; + } + + public int Update(string table, string[] columns, object[] values, string[] whereColumns, object[] whereValues) + { + return 0; + } + + public int Delete(string table, string[] columns = null, object[] columnValues = null) + { + return 0; + } + + public int Delete(string table, string column, string value) + { + return 0; + } + + public int TruncateTable(string table) + { + return 0; + } + + public void BeginTransaction() + { + // No Op + } + + public void Rollback() + { + // No Op + } + + public void Commit() + { + // No Op + } + + public ITransformationProvider this[string provider] + { + get { return this; } + } + + public string SchemaInfoTable { get; set; } + + public void MigrationApplied(long version, string scope) + { + //no op + } + + public void MigrationUnApplied(long version, string scope) + { + //no op + } + + public List AppliedMigrations + { + get { return new List(); } + } + + public void AddColumn(string table, Column column) + { + // No Op + } + + public void GenerateForeignKey(string primaryTable, string refTable) + { + // No Op + } + + public void GenerateForeignKey(string primaryTable, string refTable, ForeignKeyConstraintType constraint) + { + // No Op + } + + public IDbCommand GetCommand() + { + return null; + } + + public void ExecuteSchemaBuilder(SchemaBuilder schemaBuilder) + { + // No Op + } + + public void RemoveAllForeignKeys(string tableName, string columnName) + { + + } + + public bool IsThisProvider(string provider) + { + return false; + } + + public string[] QuoteColumnNamesIfRequired(params string[] columnNames) + { + throw new NotImplementedException(); + } + + public string QuoteColumnNameIfRequired(string name) + { + throw new NotImplementedException(); + } + + public string QuoteTableNameIfRequired(string name) + { + throw new NotImplementedException(); + } + + public string Encode(Guid guid) + { + return guid.ToString(); + } + + public void SwitchDatabase(string databaseName) + { + + } + + public List GetDatabases() + { + return new List(); + } + + public bool DatabaseExists(string name) + { + return true; + } + + public void CreateDatabases(string databaseName) + { + + } + + public void KillDatabaseConnections(string databaseName) + { + + } + + public void DropDatabases(string databaseName) + { + + } + + public string AddIndex(string table, Index index) + { + // Don't know what this is for... + + return string.Empty; + } + + public void Dispose() + { + //No Op + } + + public void AddColumn(string table, string sqlColumn) + { + // No Op + } + + public int Insert(string table, string[] columns, string[] columnValues) + { + return 0; + } + + protected void CreateSchemaInfoTable() + { + } + + public void RemoveIndex(string table, string name) + { + // No Op + } + + public string AddIndex(string name, string table, params string[] columns) + { + // No Op + + // Don't know what this is for... + + return string.Empty; + } + + public bool IndexExists(string table, string name) + { + return false; + } + + public string GenerateParameterName(int index) + { + return "@p" + index; + } + + public void RemoveAllIndexes(string table) + { + // No Op + } + + public string Concatenate(params string[] strings) + { + return ""; + } + + public IDbConnection Connection + { + get + { + return null; + } + } + + public IEnumerable GetTables(string schema) + { + throw new NotImplementedException(); + } + + public IEnumerable GetColumns(string schema, string table) + { + throw new NotImplementedException(); + } + + public int GetColumnContentSize(string table, string columnName) + { + throw new NotImplementedException(); + } + + public void AddColumn(string table, string column, MigratorDbType type, int size, ColumnProperty property, object defaultValue) + { + throw new NotImplementedException(); + } + + public void AddColumn(string table, string column, MigratorDbType type) + { + throw new NotImplementedException(); + } + + public void AddColumn(string table, string column, MigratorDbType type, int size) + { + throw new NotImplementedException(); + } + + public void AddColumn(string table, string column, MigratorDbType type, int size, ColumnProperty property) + { + throw new NotImplementedException(); + } + + public void AddColumn(string table, string column, MigratorDbType type, ColumnProperty property) + { + throw new NotImplementedException(); + } + + public void AddColumn(string table, string column, MigratorDbType type, object defaultValue) + { + throw new NotImplementedException(); + } + + public void UpdateTargetFromSource(string tableSourceNotQuoted, string tableTargetNotQuoted, ColumnPair[] fromSourceToTargetColumnPairs, ColumnPair[] conditionColumnPairs) + { + throw new NotImplementedException(); + } + + public virtual void CopyDataFromTableToTable(string sourceTableName, List sourceColumnNames, string targetTableName, List targetColumnNames, List orderBySourceColumns) + { + throw new NotImplementedException(); + } +} diff --git a/src/Migrator/Providers/ProviderTypes.cs b/src/Migrator/Providers/ProviderTypes.cs new file mode 100644 index 00000000..7a7dbcba --- /dev/null +++ b/src/Migrator/Providers/ProviderTypes.cs @@ -0,0 +1,21 @@ +namespace DotNetProjects.Migrator.Providers; + +public enum ProviderTypes +{ + none, + SqlServer2005, + SqlServer, + Mysql, + MariaDB, + SQLite, + MonoSQLite, + PostgreSQL82, + PostgreSQL, + Oracle, + MsOracle, + IBM_DB2, + IBM_Informix, + Firebird, + Ingres, + Sybase, +} diff --git a/src/Migrator/Providers/TransformationProvider.cs b/src/Migrator/Providers/TransformationProvider.cs new file mode 100644 index 00000000..7a945c5f --- /dev/null +++ b/src/Migrator/Providers/TransformationProvider.cs @@ -0,0 +1,2236 @@ +#region License + +//The contents of this file are subject to the Mozilla Public License +//Version 1.1 (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.mozilla.org/MPL/ +//Software distributed under the License is distributed on an "AS IS" +//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +//License for the specific language governing rights and limitations +//under the License. + +#endregion + +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Framework.Loggers; +using DotNetProjects.Migrator.Framework.Models; +using DotNetProjects.Migrator.Framework.SchemaBuilder; +using DotNetProjects.Migrator.Providers.Impl.SQLite; +using DotNetProjects.Migrator.Providers.Models; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.IO; +using System.Linq; +using System.Text; +using ForeignKeyConstraint = DotNetProjects.Migrator.Framework.ForeignKeyConstraint; +using ForeignKeyConstraintType = DotNetProjects.Migrator.Framework.ForeignKeyConstraintType; +using Index = DotNetProjects.Migrator.Framework.Index; + +namespace DotNetProjects.Migrator.Providers; + +/// +/// Base class for every transformation providers. +/// A 'tranformation' is an operation that modifies the database. +/// +public abstract class TransformationProvider : ITransformationProvider +{ + private string _scope; + protected readonly string _connectionString; + protected readonly string _defaultSchema; + private readonly ForeignKeyConstraintMapper constraintMapper = new(); + protected List _appliedMigrations; + protected IDbConnection _connection; + protected bool _outsideConnection = false; + protected Dialect _dialect; + private ILogger _logger; + private IDbTransaction _transaction; + + protected TransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope) + { + _dialect = dialect; + _connectionString = connectionString; + _defaultSchema = defaultSchema; + _logger = new Logger(false); + _scope = scope; + } + + protected TransformationProvider(Dialect dialect, IDbConnection connection, string defaultSchema, string scope) + { + _dialect = dialect; + _connection = connection; + _outsideConnection = true; + _defaultSchema = defaultSchema; + _logger = new Logger(false); + _scope = scope; + } + + public IMigration CurrentMigration { get; set; } + + private string _schemaInfotable = "SchemaInfo"; + public string SchemaInfoTable + { + get + { + return _schemaInfotable; + } + set + { + _schemaInfotable = value; + } + } + + public int? CommandTimeout { get; set; } + + public IDialect Dialect + { + get { return _dialect; } + } + + public string ConnectionString { get { return _connectionString; } } + + /// + /// Returns the event logger + /// + public virtual ILogger Logger + { + get { return _logger; } + set { _logger = value; } + } + + public virtual ITransformationProvider this[string provider] + { + get + { + if (null != provider && IsThisProvider(provider)) + { + return this; + } + + return NoOpTransformationProvider.Instance; + } + } + + public virtual Index[] GetIndexes(string table) + { + throw new NotImplementedException(); + } + + public virtual Column[] GetColumns(string table) + { + var columns = new List(); + using (var cmd = CreateCommand()) + using ( + var reader = + ExecuteQuery( + cmd, string.Format("select COLUMN_NAME, IS_NULLABLE from INFORMATION_SCHEMA.COLUMNS where table_name = '{0}'", table))) + { + while (reader.Read()) + { + var column = new Column(reader.GetString(0), DbType.String); + var nullableStr = reader.GetString(1); + var isNullable = nullableStr == "YES"; + column.ColumnProperty |= isNullable ? ColumnProperty.Null : ColumnProperty.NotNull; + + columns.Add(column); + } + } + + return columns.ToArray(); + } + + /// + /// Basic implementation works for Postgre and probably for MySQL (not tested). For Oracle it should be overridden + /// + /// + /// + /// + public virtual ForeignKeyConstraint[] GetForeignKeyConstraints(string table) + { + var constraints = new List(); + var sb = new StringBuilder(); + sb.AppendLine("SELECT"); + sb.AppendLine(" tc.CONSTRAINT_NAME AS FK_KEY,"); + sb.AppendLine(" tc.TABLE_SCHEMA,"); + sb.AppendLine(" tc.TABLE_NAME AS CHILD_TABLE,"); + sb.AppendLine(" kcu.COLUMN_NAME AS CHILD_COLUMN,"); + sb.AppendLine(" ccu.TABLE_NAME AS PARENT_TABLE,"); + sb.AppendLine(" ccu.COLUMN_NAME AS PARENT_COLUMN"); + sb.AppendLine("FROM "); + sb.AppendLine(" INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc "); + sb.AppendLine("JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE as kcu"); + sb.AppendLine(" ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND tc.TABLE_SCHEMA = kcu.TABLE_SCHEMA"); + sb.AppendLine("JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS as rc"); + sb.AppendLine(" ON tc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME AND tc.TABLE_SCHEMA = rc.CONSTRAINT_SCHEMA"); + sb.AppendLine("JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS ccu"); + sb.AppendLine(" ON rc.UNIQUE_CONSTRAINT_NAME = ccu.CONSTRAINT_NAME AND rc.UNIQUE_CONSTRAINT_SCHEMA = ccu.CONSTRAINT_SCHEMA"); + sb.AppendLine($"WHERE LOWER(tc.TABLE_NAME) = LOWER('{table}') AND tc.CONSTRAINT_TYPE = 'FOREIGN KEY'"); + sb.AppendLine("ORDER BY kcu.ORDINAL_POSITION"); + + var sql = sb.ToString(); + List foreignKeyConstraintItems = []; + + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, sql)) + { + while (reader.Read()) + { + var constraintItem = new ForeignKeyConstraintItem + { + SchemaName = reader.GetString(reader.GetOrdinal("TABLE_SCHEMA")), + ForeignKeyName = reader.GetString(reader.GetOrdinal("FK_KEY")), + ChildTableName = reader.GetString(reader.GetOrdinal("CHILD_TABLE")), + ChildColumnName = reader.GetString(reader.GetOrdinal("CHILD_COLUMN")), + ParentTableName = reader.GetString(reader.GetOrdinal("PARENT_TABLE")), + ParentColumnName = reader.GetString(reader.GetOrdinal("PARENT_COLUMN")) + }; + + foreignKeyConstraintItems.Add(constraintItem); + } + } + + var schemaChildTableGroups = foreignKeyConstraintItems.GroupBy(x => new { x.SchemaName, x.ChildTableName }).Count(); + + if (schemaChildTableGroups > 1) + { + throw new MigrationException($"Duplicates found (grouping by schema name and child table name). Since we do not offer schemas in '{nameof(GetForeignKeyConstraints)}' at this moment in time we cannot filter your target schema. Your database use the same table name in different schemas."); + } + + var groups = foreignKeyConstraintItems.GroupBy(x => x.ForeignKeyName); + + foreach (var group in groups) + { + var first = group.First(); + + var foreignKeyConstraint = new ForeignKeyConstraint + { + Name = first.ForeignKeyName, + ParentTable = first.ParentTableName, + ParentColumns = [.. group.Select(x => x.ParentColumnName).Distinct()], + ChildTable = first.ChildTableName, + ChildColumns = [.. group.Select(x => x.ChildColumnName).Distinct()] + }; + + constraints.Add(foreignKeyConstraint); + } + + return [.. constraints]; + } + + public virtual string[] GetConstraints(string table) + { + var constraints = new List(); + using (var cmd = CreateCommand()) + using ( + var reader = + ExecuteQuery( + cmd, string.Format("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE LOWER(TABLE_NAME) = LOWER('{0}')", table))) + { + while (reader.Read()) + { + constraints.Add(reader.GetString(0)); + } + } + + return constraints.ToArray(); + } + + public virtual Column GetColumnByName(string table, string columnName) + { + var columns = GetColumns(table); + var column = columns.FirstOrDefault(x => x.Name.Equals(columnName, StringComparison.OrdinalIgnoreCase)) ?? + throw new Exception($"Cannot find column '{columnName}' in table '{table}'"); + + return column; + } + + public virtual int GetColumnContentSize(string table, string columnName) + { + var result = this.ExecuteScalar("SELECT MAX(LENGTH(" + this.QuoteColumnNameIfRequired(columnName) + ")) FROM " + this.QuoteTableNameIfRequired(table)); + + if (result == DBNull.Value) + { + return 0; + } + + return Convert.ToInt32(result); + } + + public virtual string[] GetTables() + { + var tables = new List(); + using (var cmd = CreateCommand()) + using (var reader = ExecuteQuery(cmd, "SELECT table_name FROM INFORMATION_SCHEMA.TABLES")) + { + while (reader.Read()) + { + tables.Add((string)reader[0]); + } + } + return tables.ToArray(); + } + + public virtual void RemoveForeignKey(string table, string name) + { + if (!TableExists(table)) + { + throw new MigrationException($"Table '{table}' does not exist."); + } + + RemoveConstraint(table, name); + } + + public virtual void RemoveConstraint(string table, string name) + { + if (!TableExists(table)) + { + throw new MigrationException($"Table '{name}' does not exist"); + } + + if (!ConstraintExists(table, name)) + { + throw new MigrationException($"Constraint '{name}' does not exist"); + } + + ExecuteNonQuery(string.Format("ALTER TABLE {0} DROP CONSTRAINT {1}", QuoteTableNameIfRequired(table), QuoteConstraintNameIfRequired(name))); + } + + public virtual void RemoveAllConstraints(string table) + { + foreach (var constraint in GetConstraints(table)) + { + RemoveConstraint(table, constraint); + } + } + + public virtual void AddView(string name, string tableName, params IViewField[] fields) + { + var lst = + fields.Where(x => string.IsNullOrEmpty(x.TableName) || x.TableName == tableName) + .Select(x => x.ColumnName) + .ToList(); + + var nr = 0; + var joins = ""; + foreach (var joinTable in fields.Where(x => !string.IsNullOrEmpty(x.TableName) && x.TableName != tableName).GroupBy(x => x.TableName)) + { + foreach (var viewField in joinTable) + { + joins += string.Format("JOIN {0} {1} ON {1}.{2} = {3}.{4} ", viewField.TableName, " T" + nr, + viewField.KeyColumnName, viewField.ParentTableName, viewField.ParentKeyColumnName); + lst.Add(" T" + nr + "." + viewField.ColumnName); + } + } + + var select = string.Format("SELECT {0} FROM {1} {2}", string.Join(",", lst), tableName, joins); + + var sql = string.Format("CREATE VIEW {0} AS {1}", name, select); + + ExecuteNonQuery(sql); + } + + public virtual void AddView(string name, string tableName, params IViewElement[] viewElements) + { + var selectedColumns = viewElements.Where(x => x is ViewColumn) + .Select(x => + { + var viewColumn = (ViewColumn)x; + return $"{viewColumn.Prefix}.{viewColumn.ColumnName} {viewColumn.Prefix}{viewColumn.ColumnName}"; + }) + .ToList(); + + var joins = string.Empty; + + foreach (var viewJoin in viewElements.Where(x => x is ViewJoin).Cast()) + { + var joinType = string.Empty; + + switch (viewJoin.JoinType) + { + case JoinType.LeftJoin: + joinType = "LEFT JOIN"; + break; + case JoinType.Join: + joinType = "JOIN"; + break; + } + + var tableAlias = string.IsNullOrEmpty(viewJoin.TableAlias) ? viewJoin.TableName : viewJoin.TableAlias; + + joins += string.Format("{0} {1} {2} ON {2}.{3} = {4}.{5} ", joinType, viewJoin.TableName, tableAlias, + viewJoin.ColumnName, viewJoin.ParentTableName, viewJoin.ParentColumnName); + } + + var select = string.Format("SELECT {0} FROM {1} {1} {2}", string.Join(",", selectedColumns), tableName, joins); + var sql = string.Format("CREATE VIEW {0} AS {1}", name, select); + + + // Works with all DBs. "CREATE OR REPLACE" does not work with SQLite. "DROP IF EXISTS" does not work with oracle. + try + { + ExecuteNonQuery($"DROP VIEW {name}"); + } + catch + { + // Works with all DBs. "CREATE OR REPLACE" does not work with SQLite. "DROP IF EXISTS" does not work with oracle. + } + + ExecuteNonQuery(sql); + } + + /// + /// Add a new table + /// + /// Table name + /// Columns + public virtual void AddTable(string name, params IDbField[] columns) + { + if (this is not SQLiteTransformationProvider && columns.Any(x => x is CheckConstraint)) + { + throw new MigrationException($"{nameof(CheckConstraint)}s are currently only supported in SQLite."); + } + + // Most databases don't have the concept of a storage engine, so default is to not use it. + AddTable(name, null, columns); + } + + /// + /// Adds a new table + /// + /// Table name + /// Columns + /// the database storage engine to use + public virtual void AddTable(string name, string engine, params IDbField[] fields) + { + var columns = fields.Where(x => x is Column).Cast().ToArray(); + + var pks = GetPrimaryKeys(columns); + var compoundPrimaryKey = pks.Count > 1; + + var columnProviders = new List(columns.Count()); + + foreach (var column in columns) + { + // Remove the primary key notation if compound primary key because we'll add it back later + if (compoundPrimaryKey && column.IsPrimaryKey) + { + column.ColumnProperty = column.ColumnProperty ^ ColumnProperty.PrimaryKey; + column.ColumnProperty = column.ColumnProperty | ColumnProperty.NotNull; // PK is always not-null + } + + var mapper = _dialect.GetAndMapColumnProperties(column); + columnProviders.Add(mapper); + } + + var columnsAndIndexes = JoinColumnsAndIndexes(columnProviders); + + AddTable(name, engine, columnsAndIndexes); + + if (compoundPrimaryKey) + { + AddPrimaryKey(GetPrimaryKeyname(name), name, pks.ToArray()); + } + + var indexes = fields.Where(x => x is Index).Cast().ToArray(); + + foreach (var index in indexes) + { + AddIndex(name, index); + } + + var foreignKeys = fields.Where(x => x is ForeignKeyConstraint).Cast().ToArray(); + + foreach (var foreignKey in foreignKeys) + { + AddForeignKey(name, foreignKey); + } + } + + protected virtual string GetPrimaryKeyname(string tableName) + { + return "PK_" + tableName; + } + + public virtual void RemoveTable(string name) + { + if (!TableExists(name)) + { + throw new MigrationException(string.Format("Table with name '{0}' does not exist to rename", name)); + } + + ExecuteNonQuery(string.Format("DROP TABLE {0}", name)); + } + + public virtual void RenameTable(string oldName, string newName) + { + oldName = QuoteTableNameIfRequired(oldName); + newName = QuoteTableNameIfRequired(newName); + + if (TableExists(newName)) + { + throw new MigrationException(string.Format("Table with name '{0}' already exists", newName)); + } + + if (!TableExists(oldName)) + { + throw new MigrationException(string.Format("Table with name '{0}' does not exist to rename", oldName)); + } + + ExecuteNonQuery(string.Format("ALTER TABLE {0} RENAME TO {1}", oldName, newName)); + } + + public virtual void RenameColumn(string tableName, string oldColumnName, string newColumnName) + { + if (ColumnExists(tableName, newColumnName)) + { + throw new MigrationException(string.Format("Table '{0}' has column named '{1}' already", tableName, newColumnName)); + } + + if (!ColumnExists(tableName, oldColumnName)) + { + throw new MigrationException(string.Format("The table '{0}' does not have a column named '{1}'", tableName, oldColumnName)); + } + + var column = GetColumnByName(tableName, oldColumnName); + + var quotedNewColumnName = QuoteColumnNameIfRequired(newColumnName); + + ExecuteNonQuery(string.Format("ALTER TABLE {0} RENAME COLUMN {1} TO {2}", tableName, Dialect.Quote(column.Name), quotedNewColumnName)); + } + + public virtual void RemoveColumn(string tableName, string column) + { + if (!TableExists(tableName)) + { + throw new MigrationException($"The table '{tableName}' does not exist"); + } + + if (!ColumnExists(tableName, column, true)) + { + throw new MigrationException(string.Format("The table '{0}' does not have a column named '{1}'", tableName, column)); + } + + var existingColumn = GetColumnByName(tableName, column); + + ExecuteNonQuery(string.Format("ALTER TABLE {0} DROP COLUMN {1} ", tableName, Dialect.Quote(existingColumn.Name))); + } + + public virtual bool ColumnExists(string table, string column) + { + return ColumnExists(table, column, true); + } + + public virtual bool ColumnExists(string table, string column, bool ignoreCase) + { + if (ignoreCase) + { + return GetColumns(table).Any(x => x.Name.Equals(column, StringComparison.OrdinalIgnoreCase)); + } + + return GetColumns(table).Any(x => x.Name == column); + } + + public virtual void ChangeColumn(string table, Column column) + { + var isUniqueSet = column.ColumnProperty.IsSet(ColumnProperty.Unique); + + column.ColumnProperty = column.ColumnProperty.Clear(ColumnProperty.Unique); + + var mapper = _dialect.GetAndMapColumnProperties(column); + + ChangeColumn(table, mapper.ColumnSql); + + if (isUniqueSet) + { + AddUniqueConstraint(string.Format("UX_{0}_{1}", table, column.Name), table, [column.Name]); + } + } + + public virtual void RemoveColumnDefaultValue(string table, string column) + { + var sql = string.Format("ALTER TABLE {0} ALTER {1} DROP DEFAULT", table, column); + ExecuteNonQuery(sql); + } + + public virtual bool TableExists(string table) + { + throw new NotImplementedException(); + } + + public virtual bool ViewExists(string view) + { + throw new NotImplementedException(); + } + + public virtual void SwitchDatabase(string databaseName) + { + _connection.ChangeDatabase(databaseName); + } + + public abstract List GetDatabases(); + + public bool DatabaseExists(string name) + { + return GetDatabases().Any(c => string.Equals(name, c, StringComparison.OrdinalIgnoreCase)); + } + + public virtual void CreateDatabases(string databaseName) + { + ExecuteNonQuery(string.Format("CREATE DATABASE {0}", databaseName)); + } + + public virtual void KillDatabaseConnections(string databaseName) + { + //todo, implement this for each DB, no default implementation possible!!! + } + + public virtual void DropDatabases(string databaseName) + { + ExecuteNonQuery(string.Format("DROP DATABASE {0}", databaseName)); + } + + /// + /// Add a new column to an existing table. + /// + /// Table to which to add the column + /// Column name + /// Date type of the column + /// Max length of the column + /// Properties of the column, see ColumnProperty, + /// Default value + public void AddColumn(string table, string column, DbType type, int size, ColumnProperty property, + object defaultValue) + { + AddColumn(table, column, (MigratorDbType)type, size, property, defaultValue); + } + + /// + /// Add a new column to an existing table. + /// + /// Table to which to add the column + /// Column name + /// Date type of the column + /// Max length of the column + /// Properties of the column, see ColumnProperty, + /// Default value + public virtual void AddColumn(string table, string column, MigratorDbType type, int size, ColumnProperty property, + object defaultValue) + { + var mapper = + _dialect.GetAndMapColumnProperties(new Column(column, type, size, property, defaultValue)); + + AddColumn(table, mapper.ColumnSql); + } + + /// + /// + /// AddColumn(string, string, Type, int, ColumnProperty, object) + /// + /// + public virtual void AddColumn(string table, string column, DbType type) + { + AddColumn(table, column, type, 0, ColumnProperty.Null, null); + } + + /// + /// + /// AddColumn(string, string, Type, int, ColumnProperty, object) + /// + /// + public virtual void AddColumn(string table, string column, MigratorDbType type) + { + AddColumn(table, column, type, 0, ColumnProperty.Null, null); + } + + /// + /// + /// AddColumn(string, string, Type, int, ColumnProperty, object) + /// + /// + public virtual void AddColumn(string table, string column, DbType type, int size) + { + AddColumn(table, column, type, size, ColumnProperty.Null, null); + } + + /// + /// + /// AddColumn(string, string, Type, int, ColumnProperty, object) + /// + /// + public virtual void AddColumn(string table, string column, MigratorDbType type, int size) + { + AddColumn(table, column, type, size, ColumnProperty.Null, null); + } + + public virtual void AddColumn(string table, string column, DbType type, object defaultValue) + { + AddColumn(table, column, (MigratorDbType)type, defaultValue); + } + + public virtual void AddColumn(string table, string column, MigratorDbType type, object defaultValue) + { + var mapper = + _dialect.GetAndMapColumnProperties(new Column(column, type, defaultValue)); + + AddColumn(table, mapper.ColumnSql); + } + + /// + /// + /// AddColumn(string, string, Type, int, ColumnProperty, object) + /// + /// + public virtual void AddColumn(string table, string column, DbType type, ColumnProperty property) + { + AddColumn(table, column, type, 0, property, null); + } + + /// + /// + /// AddColumn(string, string, Type, int, ColumnProperty, object) + /// + /// + public virtual void AddColumn(string table, string column, MigratorDbType type, ColumnProperty property) + { + AddColumn(table, column, type, 0, property, null); + } + + /// + /// + /// AddColumn(string, string, Type, int, ColumnProperty, object) + /// + /// + public virtual void AddColumn(string table, string column, DbType type, int size, ColumnProperty property) + { + AddColumn(table, column, type, size, property, null); + } + + /// + /// + /// AddColumn(string, string, Type, int, ColumnProperty, object) + /// + /// + public virtual void AddColumn(string table, string column, MigratorDbType type, int size, ColumnProperty property) + { + AddColumn(table, column, type, size, property, null); + } + + /// + /// Append a primary key to a table. + /// + /// Constraint name + /// Table name + /// Primary column names + public virtual void AddPrimaryKey(string name, string table, params string[] columns) + { + table = QuoteTableNameIfRequired(table); + + ExecuteNonQuery( + string.Format("ALTER TABLE {0} ADD CONSTRAINT {1} PRIMARY KEY ({2}) ", table, name, + string.Join(",", QuoteColumnNamesIfRequired(columns)))); + } + public virtual void AddPrimaryKeyNonClustered(string name, string table, params string[] columns) + { + this.AddPrimaryKey(name, table, columns); + } + public virtual void AddUniqueConstraint(string name, string table, params string[] columns) + { + table = QuoteTableNameIfRequired(table); + + ExecuteNonQuery(string.Format("ALTER TABLE {0} ADD CONSTRAINT {1} UNIQUE({2}) ", table, name, + string.Join(", ", QuoteColumnNamesIfRequired(columns)))); + } + + public virtual void AddCheckConstraint(string name, string table, string checkSql) + { + table = QuoteTableNameIfRequired(table); + + ExecuteNonQuery(string.Format("ALTER TABLE {0} ADD CONSTRAINT {1} CHECK ({2}) ", table, name, checkSql)); + } + + /// + /// Guesses the name of the foreign key and adds it + /// + public virtual void GenerateForeignKey(string childTable, string childColumn, string parentTable, string parentColumn) + { + AddForeignKey("FK_" + childTable + "_" + parentTable, childTable, childColumn, parentTable, parentColumn); + } + + /// + /// Guesses the name of the foreign key and adds it + /// + /// + public virtual void GenerateForeignKey( + string childTable, + string[] childColumns, + string parentTable, + string[] parentColumns) + { + AddForeignKey("FK_" + childTable + "_" + parentTable, childTable, childColumns, parentTable, parentColumns); + } + + /// + /// Guesses the name of the foreign key and adds it + /// + public virtual void GenerateForeignKey( + string childTable, + string childColumn, + string parentTable, + string parentColumn, + ForeignKeyConstraintType constraint) + { + AddForeignKey("FK_" + childTable + "_" + parentTable, childTable, childColumn, parentTable, parentColumn, constraint); + } + + /// + /// Guesses the name of the foreign key and add it + /// + /// + public virtual void GenerateForeignKey( + string childTable, + string[] childColumns, + string parentTable, + string[] parentColumns, + ForeignKeyConstraintType constraint) + { + AddForeignKey("FK_" + childTable + "_" + parentTable, childTable, childColumns, parentTable, parentColumns, constraint); + } + + public virtual void AddForeignKey(string table, ForeignKeyConstraint fk) + { + AddForeignKey(fk.Name, table, fk.ParentColumns, fk.ChildTable, fk.ChildColumns); + } + + public virtual void AddForeignKey(string name, string childTable, string childColumn, string parentTable, string parentColumn) + { + try + { + AddForeignKey(name, childTable, [childColumn], parentTable, [parentColumn]); + } + catch (Exception ex) + { + throw new Exception(string.Format("Error occured while adding foreign key: \"{0}\" between table: \"{1}\" and table: \"{2}\" - see inner exception for details", name, parentTable, childTable), ex); + } + } + + public virtual void AddForeignKey(string name, string childTable, string[] childColumns, string parentTable, string[] parentColumns) + { + AddForeignKey(name, childTable, childColumns, parentTable, parentColumns, ForeignKeyConstraintType.NoAction); + } + + public virtual void AddForeignKey(string name, string childTable, string childColumn, string parentTable, string parentColumn, ForeignKeyConstraintType constraint) + { + AddForeignKey(name, childTable, [childColumn], parentTable, [parentColumn], constraint); + } + + public virtual void AddForeignKey( + string name, + string childTable, + string[] childColumns, + string parentTable, + string[] parentColumns, + ForeignKeyConstraintType constraint) + { + childTable = QuoteTableNameIfRequired(childTable); + parentTable = QuoteTableNameIfRequired(parentTable); + QuoteColumnNames(parentColumns); + QuoteColumnNames(childColumns); + + var constraintResolved = constraintMapper.SqlForConstraint(constraint); + + // TODO Issue #52 still unresolved + var childColumnsString = string.Join(", ", childColumns); + var parentColumnsString = string.Join(", ", parentColumns); + + var stringBuilder = new StringBuilder(); + stringBuilder.Append($"ALTER TABLE {childTable} ADD CONSTRAINT {name} FOREIGN KEY ({childColumnsString}) REFERENCES {parentTable} ({parentColumnsString})"); + stringBuilder.Append($"ON UPDATE {constraintResolved} ON DELETE {constraintResolved}"); + + ExecuteNonQuery(stringBuilder.ToString()); + } + + /// + /// Determines if a constraint exists. + /// + /// Constraint name + /// Table owning the constraint + /// true if the constraint exists. + public abstract bool ConstraintExists(string table, string name); + + public virtual bool PrimaryKeyExists(string table, string name) + { + return ConstraintExists(table, name); + } + + public virtual int ExecuteNonQuery(string sql) + { + return ExecuteNonQuery(sql, CommandTimeout ?? 30); + } + + public virtual int ExecuteNonQuery(string sql, int timeout) + { + return ExecuteNonQuery(sql, timeout, null); + } + + public virtual int ExecuteNonQuery(string sql, int timeout, params object[] args) + { + if (args == null) + { + Logger.Trace(sql); + Logger.ApplyingDBChange(sql); + } + else + { + Logger.Trace(string.Format(sql, args)); + Logger.ApplyingDBChange(string.Format(sql, args)); + } + + using var cmd = BuildCommand(sql); + + try + { + cmd.CommandTimeout = timeout; + + if (args != null) + { + var index = 0; + + foreach (var obj in args) + { + var parameter = cmd.CreateParameter(); + ConfigureParameterWithValue(parameter, index, obj); + parameter.ParameterName = GenerateParameterNameParameter(index); + cmd.Parameters.Add(parameter); + ++index; + } + } + + Logger.Trace(cmd.CommandText); + return cmd.ExecuteNonQuery(); + } + catch (Exception ex) + { + Logger.Warn(ex.Message); + throw new MigrationException(string.Format("Error occured executing sql: {0}, see inner exception for details, error: " + ex, sql), ex); + } + } + + public List ExecuteStringQuery(string sql, params object[] args) + { + var values = new List(); + + using (var cmd = CreateCommand()) + { + using var reader = ExecuteQuery(cmd, string.Format(sql, args)); + while (reader.Read()) + { + var value = reader[0]; + + if (value == null || value == DBNull.Value) + { + values.Add(null); + } + else + { + values.Add(value.ToString()); + } + } + } + + return values; + } + + public virtual void ExecuteScript(string fileName) + { + if (CurrentMigration != null) + { +#if NETSTANDARD + var assembly = CurrentMigration.GetType().GetTypeInfo().Assembly; +#else + var assembly = CurrentMigration.GetType().Assembly; +#endif + + string sqlText; + var file = (new System.Uri(assembly.CodeBase)).AbsolutePath; + using (var reader = File.OpenText(file)) + { + sqlText = reader.ReadToEnd(); + } + + ExecuteNonQuery(sqlText); + } + } + + /// + /// Execute an SQL query returning results. + /// + /// The SQL text. + /// The IDbCommand. + /// A data iterator, IDataReader. + public virtual IDataReader ExecuteQuery(IDbCommand cmd, string sql) + { + Logger.Trace(sql); + cmd.CommandText = sql; + try + { + return cmd.ExecuteReader(); + } + catch (Exception ex) + { + Logger.Warn("query failed: {0}", cmd.CommandText); + throw new Exception("Failed to execute sql statement: " + sql, ex); + } + } + + public virtual object ExecuteScalar(string sql) + { + Logger.Trace(sql); + using var cmd = BuildCommand(sql); + try + { + return cmd.ExecuteScalar(); + } + catch + { + Logger.Warn("Query failed: {0}", cmd.CommandText); + throw; + } + } + + public virtual IDataReader Select(IDbCommand cmd, string what, string from) + { + return Select(cmd, what, from, "1=1"); + } + + public virtual IDataReader Select(IDbCommand cmd, string what, string from, string where) + { + return ExecuteQuery(cmd, string.Format("SELECT {0} FROM {1} WHERE {2}", what, from, where)); + } + + public virtual IDataReader Select(IDbCommand cmd, string table, string[] columns, string[] whereColumns = null, object[] whereValues = null) + { + return SelectComplex(cmd, table, columns, whereColumns, whereValues); + } + + public virtual IDataReader SelectComplex(IDbCommand cmd, string table, string[] columns, string[] whereColumns = null, + object[] whereValues = null, string[] nullWhereColumns = null, string[] notNullWhereColumns = null) + { + if (string.IsNullOrEmpty(table)) + { + throw new ArgumentNullException("table"); + } + + if (columns == null) + { + throw new ArgumentNullException("columns"); + } + + table = QuoteTableNameIfRequired(table); + + var builder = new StringBuilder(); + for (var i = 0; i < columns.Length; i++) + { + if (builder.Length > 0) + { + builder.Append(", "); + } + + builder.Append(QuoteColumnNameIfRequired(columns[i])); + } + + + cmd.Transaction = _transaction; + + var query = string.Format("SELECT {0} FROM {1}", builder.ToString(), table); + + if (whereColumns != null || nullWhereColumns != null || notNullWhereColumns != null) + { + query = string.Format("SELECT {0} FROM {1} WHERE ", builder.ToString(), table); + } + + var andNeeded = false; + if (whereColumns != null) + { + query += GetWhereString(whereColumns, whereValues); + andNeeded = true; + } + if (nullWhereColumns != null) + { + if (andNeeded) + { + query += " AND "; + } + + query += GetWhereStringIsNull(nullWhereColumns); + andNeeded = true; + } + if (notNullWhereColumns != null) + { + if (andNeeded) + { + query += " AND "; + } + + query += GetWhereStringIsNotNull(notNullWhereColumns); + andNeeded = true; + } + + cmd.CommandText = query; + cmd.CommandType = CommandType.Text; + + var paramCount = 0; + + if (whereColumns != null) + { + foreach (var value in whereValues) + { + var parameter = cmd.CreateParameter(); + + ConfigureParameterWithValue(parameter, paramCount, value); + + parameter.ParameterName = GenerateParameterNameParameter(paramCount); + + cmd.Parameters.Add(parameter); + + paramCount++; + } + } + + Logger.Trace(cmd.CommandText); + return cmd.ExecuteReader(); + + } + + public object SelectScalar(string what, string from) + { + return SelectScalar(what, from, "1=1"); + } + + public virtual object SelectScalar(string what, string from, string where) + { + return ExecuteScalar(string.Format("SELECT {0} FROM {1} WHERE {2}", what, from, where)); + } + + public virtual object SelectScalar(string what, string from, string[] whereColumns, object[] whereValues) + { + using var command = _connection.CreateCommand(); + if (CommandTimeout.HasValue) + { + command.CommandTimeout = CommandTimeout.Value; + } + + command.Transaction = _transaction; + + var query = string.Format("SELECT {0} FROM {1} WHERE {2}", what, from, GetWhereString(whereColumns, whereValues)); + + command.CommandText = query; + command.CommandType = CommandType.Text; + + var paramCount = 0; + + foreach (var value in whereValues) + { + var parameter = command.CreateParameter(); + + ConfigureParameterWithValue(parameter, paramCount, value); + + parameter.ParameterName = GenerateParameterNameParameter(paramCount); + + command.Parameters.Add(parameter); + + paramCount++; + } + + Logger.Trace(command.CommandText); + return command.ExecuteScalar(); + } + + public virtual int Update(string table, string[] columns, object[] values) + { + return Update(table, columns, values, null); + } + + public virtual int Update(string table, string[] columns, object[] values, string where) + { + if (string.IsNullOrEmpty(table)) + { + throw new ArgumentNullException("table"); + } + + if (columns == null) + { + throw new ArgumentNullException("columns"); + } + + if (values == null) + { + throw new ArgumentNullException("values"); + } + + if (columns.Length != values.Length) + { + throw new Exception(string.Format("The number of columns: {0} does not match the number of supplied values: {1}", columns.Length, values.Length)); + } + + table = QuoteTableNameIfRequired(table); + + var builder = new StringBuilder(); + for (var i = 0; i < values.Length; i++) + { + if (builder.Length > 0) + { + builder.Append(", "); + } + + builder.Append(QuoteColumnNameIfRequired(columns[i])); + builder.Append(" = "); + builder.Append(GenerateParameterName(i)); + } + + using var command = _connection.CreateCommand(); + if (CommandTimeout.HasValue) + { + command.CommandTimeout = CommandTimeout.Value; + } + + command.Transaction = _transaction; + + var query = string.Format("UPDATE {0} SET {1}", table, builder.ToString()); + if (!string.IsNullOrEmpty(where)) + { + query += " WHERE " + where; + } + command.CommandText = query; + command.CommandType = CommandType.Text; + + var paramCount = 0; + + foreach (var value in values) + { + var parameter = command.CreateParameter(); + + ConfigureParameterWithValue(parameter, paramCount, value); + + parameter.ParameterName = GenerateParameterNameParameter(paramCount); + + command.Parameters.Add(parameter); + + paramCount++; + } + + Logger.Trace(command.CommandText); + return command.ExecuteNonQuery(); + } + + public virtual int Update(string table, string[] columns, object[] values, string[] whereColumns, object[] whereValues) + { + if (string.IsNullOrEmpty(table)) + { + throw new ArgumentNullException("table"); + } + + if (columns == null) + { + throw new ArgumentNullException("columns"); + } + + if (values == null) + { + throw new ArgumentNullException("values"); + } + + if (columns.Length != values.Length) + { + throw new Exception(string.Format("The number of columns: {0} does not match the number of supplied values: {1}", columns.Length, values.Length)); + } + + if (whereColumns.Length != whereValues.Length) + { + throw new Exception(string.Format("The number of whereColumns: {0} does not match the number of supplied whereValues: {1}", whereColumns.Length, whereValues.Length)); + } + + table = QuoteTableNameIfRequired(table); + + var builder = new StringBuilder(); + + for (var i = 0; i < values.Length; i++) + { + if (builder.Length > 0) + { + builder.Append(", "); + } + + builder.Append(QuoteColumnNameIfRequired(columns[i])); + builder.Append(" = "); + builder.Append(GenerateParameterName(i)); + } + + using var command = _connection.CreateCommand(); + if (CommandTimeout.HasValue) + { + command.CommandTimeout = CommandTimeout.Value; + } + + command.Transaction = _transaction; + + var query = string.Format("UPDATE {0} SET {1} WHERE {2}", table, builder.ToString(), GetWhereStringWithNullCheck(whereColumns, whereValues, values.Length)); + + command.CommandText = query; + command.CommandType = CommandType.Text; + + var paramCount = 0; + + foreach (var value in values) + { + var parameter = command.CreateParameter(); + + ConfigureParameterWithValue(parameter, paramCount, value); + + parameter.ParameterName = GenerateParameterNameParameter(paramCount); + + command.Parameters.Add(parameter); + + paramCount++; + } + + foreach (var value in whereValues) + { + if (value == null || value == DBNull.Value) + { + continue; + } + + var parameter = command.CreateParameter(); + + ConfigureParameterWithValue(parameter, paramCount, value); + + parameter.ParameterName = GenerateParameterNameParameter(paramCount); + + command.Parameters.Add(parameter); + + paramCount++; + } + + + Logger.Trace(command.CommandText); + return command.ExecuteNonQuery(); + } + + public virtual void UpdateTargetFromSource(string tableSourceNotQuoted, string tableTargetNotQuoted, ColumnPair[] fromSourceToTargetColumnPairs, ColumnPair[] conditionColumnPairs) + { + throw new NotImplementedException(); + } + + public virtual int Insert(string table, string[] columns, object[] values) + { + if (string.IsNullOrEmpty(table)) + { + throw new ArgumentNullException("table"); + } + + if (columns == null) + { + throw new ArgumentNullException("columns"); + } + + if (values == null) + { + throw new ArgumentNullException("values"); + } + + if (columns.Length != values.Length) + { + throw new MigrationException(string.Format("The number of columns: {0} does not match the number of supplied values: {1}", columns.Length, values.Length)); + } + + table = QuoteTableNameIfRequired(table); + + var columnNames = string.Join(", ", columns.Select(col => QuoteColumnNameIfRequired(col)).ToArray()); + + var builder = new StringBuilder(); + + for (var i = 0; i < values.Length; i++) + { + if (builder.Length > 0) + { + builder.Append(", "); + } + + builder.Append(GenerateParameterName(i)); + } + + var parameterNames = builder.ToString(); + + using var command = _connection.CreateCommand(); + if (CommandTimeout.HasValue) + { + command.CommandTimeout = CommandTimeout.Value; + } + + command.Transaction = _transaction; + + command.CommandText = string.Format("INSERT INTO {0} ({1}) VALUES ({2})", table, columnNames, parameterNames); + command.CommandType = CommandType.Text; + + var paramCount = 0; + + foreach (var value in values) + { + var parameter = command.CreateParameter(); + + ConfigureParameterWithValue(parameter, paramCount, value); + + parameter.ParameterName = GenerateParameterNameParameter(paramCount); + + command.Parameters.Add(parameter); + + paramCount++; + } + + return command.ExecuteNonQuery(); + } + + protected virtual string GetWhereStringWithNullCheck(string[] whereColumns, object[] whereValues, int parameterStartIndex = 0) + { + var builder2 = new StringBuilder(); + var parCnt = 0; + for (var i = 0; i < whereColumns.Length; i++) + { + if (builder2.Length > 0) + { + builder2.Append(" AND "); + } + + var val = whereValues[i]; + if (val == null || val == DBNull.Value) + { + builder2.Append(QuoteColumnNameIfRequired(whereColumns[i])); + builder2.Append(" is null "); + } + else + { + builder2.Append(QuoteColumnNameIfRequired(whereColumns[i])); + builder2.Append(" = "); + builder2.Append(GenerateParameterName(parCnt + parameterStartIndex)); + parCnt++; + } + } + + return builder2.ToString(); + } + + protected virtual string GetWhereString(string[] whereColumns, object[] whereValues, int parameterStartIndex = 0) + { + var builder2 = new StringBuilder(); + for (var i = 0; i < whereColumns.Length; i++) + { + if (builder2.Length > 0) + { + builder2.Append(" AND "); + } + + builder2.Append(QuoteColumnNameIfRequired(whereColumns[i])); + builder2.Append(" = "); + builder2.Append(GenerateParameterName(i + parameterStartIndex)); + } + + return builder2.ToString(); + } + + protected virtual string GetWhereStringIsNull(string[] whereColumns) + { + var builder2 = new StringBuilder(); + for (var i = 0; i < whereColumns.Length; i++) + { + if (builder2.Length > 0) + { + builder2.Append(" AND "); + } + + builder2.Append(QuoteColumnNameIfRequired(whereColumns[i])); + builder2.Append(" IS NULL"); + } + + return builder2.ToString(); + } + + protected virtual string GetWhereStringIsNotNull(string[] whereColumns) + { + var builder2 = new StringBuilder(); + for (var i = 0; i < whereColumns.Length; i++) + { + if (builder2.Length > 0) + { + builder2.Append(" AND "); + } + + builder2.Append(QuoteColumnNameIfRequired(whereColumns[i])); + builder2.Append(" IS NOT NULL"); + } + + return builder2.ToString(); + } + + public virtual int InsertIfNotExists(string table, string[] columns, object[] values, string[] whereColumns, object[] whereValues) + { + using var cmd = CreateCommand(); + using var reader = this.Select(cmd, table, [whereColumns[0]], whereColumns, whereValues); + if (!reader.Read()) + { + reader.Close(); + return this.Insert(table, columns, values); + } + else + { + reader.Close(); + return 0; + } + } + + public virtual int Delete(string table, string[] whereColumns = null, object[] whereValues = null) + { + if (string.IsNullOrEmpty(table)) + { + throw new ArgumentNullException("table"); + } + + if (null == whereColumns || null == whereValues) + { + return ExecuteNonQuery(string.Format("DELETE FROM {0}", table)); + } + else + { + table = QuoteTableNameIfRequired(table); + + using var command = _connection.CreateCommand(); + if (CommandTimeout.HasValue) + { + command.CommandTimeout = CommandTimeout.Value; + } + + command.Transaction = _transaction; + + var query = string.Format("DELETE FROM {0} WHERE ({1})", table, + GetWhereString(whereColumns, whereValues)); + + command.CommandText = query; + command.CommandType = CommandType.Text; + + var paramCount = 0; + + foreach (var value in whereValues) + { + var parameter = command.CreateParameter(); + + ConfigureParameterWithValue(parameter, paramCount, value); + + parameter.ParameterName = GenerateParameterNameParameter(paramCount); + + command.Parameters.Add(parameter); + + paramCount++; + } + + Logger.Trace(command.CommandText); + return command.ExecuteNonQuery(); + } + } + + public virtual int Delete(string table, string wherecolumn, string wherevalue) + { + if (string.IsNullOrEmpty(wherecolumn) && string.IsNullOrEmpty(wherevalue)) + { + return Delete(table, (string[])null, null); + } + + return ExecuteNonQuery(string.Format("DELETE FROM {0} WHERE {1} = {2}", table, wherecolumn, QuoteValues(wherevalue))); + } + + public virtual int TruncateTable(string table) + { + return ExecuteNonQuery(string.Format("TRUNCATE TABLE {0} ", table)); + } + + /// + /// Starts a transaction. Called by the migration mediator. + /// + public virtual void BeginTransaction() + { + if (_transaction == null && _connection != null) + { + EnsureHasConnection(); + _transaction = _connection.BeginTransaction(IsolationLevel.ReadCommitted); + } + } + + /// + /// Rollback the current migration. Called by the migration mediator. + /// + public virtual void Rollback() + { + if (_transaction != null && _connection != null && _connection.State == ConnectionState.Open) + { + try + { + _transaction.Rollback(); + } + finally + { + if (!_outsideConnection) + { + _connection.Close(); + } + } + } + _transaction = null; + } + + /// + /// Commit the current transaction. Called by the migrations mediator. + /// + public virtual void Commit() + { + if (_transaction != null && _connection != null && _connection.State == ConnectionState.Open) + { + try + { + _transaction.Commit(); + } + finally + { + if (!_outsideConnection) + { + _connection.Close(); + } + } + } + _transaction = null; + } + + /// + /// The list of Migrations currently applied to the database. + /// + public virtual List AppliedMigrations + { + get + { + if (_appliedMigrations == null) + { + _appliedMigrations = new List(); + CreateSchemaInfoTable(); + + var versionColumn = "Version"; + var scopeColumn = "Scope"; + + versionColumn = QuoteColumnNameIfRequired(versionColumn); + scopeColumn = QuoteColumnNameIfRequired(scopeColumn); + + using var cmd = CreateCommand(); + using var reader = Select(cmd, versionColumn, _schemaInfotable, string.Format("{0} = '{1}'", scopeColumn, _scope)); + while (reader.Read()) + { + if (reader.GetFieldType(0) == typeof(decimal)) + { + _appliedMigrations.Add((long)reader.GetDecimal(0)); + } + else + { + _appliedMigrations.Add(reader.GetInt64(0)); + } + } + } + return _appliedMigrations; + } + } + + public virtual bool IsMigrationApplied(long version, string scope) + { + var value = SelectScalar("Version", _schemaInfotable, ["Scope", "Version"], [scope, version]); + return Convert.ToInt64(value) == version; + } + + /// + /// Marks a Migration version number as having been applied + /// + /// The version number of the migration that was applied + public virtual void MigrationApplied(long version, string scope) + { + CreateSchemaInfoTable(); + Insert(_schemaInfotable, ["Scope", "Version", "TimeStamp"], [scope ?? _scope, version, DateTime.UtcNow]); + _appliedMigrations.Add(version); + } + + /// + /// Marks a Migration version number as having been rolled back from the database + /// + /// The version number of the migration that was removed + public virtual void MigrationUnApplied(long version, string scope) + { + CreateSchemaInfoTable(); + Delete(_schemaInfotable, ["Scope", "Version"], [scope ?? _scope, version.ToString()]); + _appliedMigrations.Remove(version); + } + + public virtual void AddColumn(string table, Column column) + { + AddColumn(table, column.Name, column.Type, column.Size, column.ColumnProperty, column.DefaultValue); + } + + public virtual void GenerateForeignKey(string primaryTable, string refTable) + { + GenerateForeignKey(primaryTable, refTable, ForeignKeyConstraintType.NoAction); + } + + public virtual void GenerateForeignKey(string primaryTable, string refTable, ForeignKeyConstraintType constraint) + { + GenerateForeignKey(primaryTable, refTable + "Id", refTable, "Id", constraint); + } + + public virtual IDbCommand GetCommand() + { + return BuildCommand(null); + } + + public virtual void ExecuteSchemaBuilder(SchemaBuilder builder) + { + foreach (var expr in builder.Expressions) + { + expr.Create(this); + } + } + + public void Dispose() + { + if (_connection != null && _connection.State == ConnectionState.Open) + { + if (!_outsideConnection) + { + _connection.Close(); + } + } + + if (_connection != null) + { + if (!_outsideConnection) + { + _connection.Close(); + } + } + + _connection = null; + } + + public virtual string QuoteColumnNameIfRequired(string name) + { + if (Dialect.ColumnNameNeedsQuote || Dialect.IsReservedWord(name)) + { + return Dialect.Quote(name); + } + + return name; + } + + public virtual string QuoteTableNameIfRequired(string name) + { + if (Dialect.TableNameNeedsQuote || Dialect.IsReservedWord(name)) + { + return Dialect.Quote(name); + } + + return name; + } + + public virtual string Encode(Guid guid) + { + return guid.ToString(); + } + + public virtual string[] QuoteColumnNamesIfRequired(params string[] columnNames) + { + var quotedColumns = new string[columnNames.Length]; + + for (var i = 0; i < columnNames.Length; i++) + { + quotedColumns[i] = QuoteColumnNameIfRequired(columnNames[i]); + } + + return quotedColumns; + } + + public virtual bool IsThisProvider(string provider) + { + // XXX: This might need to be more sophisticated. Currently just a convention + return GetType().Name.ToLower().StartsWith(provider.ToLower()); + } + + public virtual void RemoveAllForeignKeys(string tableName, string columnName) + { } + + public virtual void AddTable(string table, string engine, string columns) + { + table = _dialect.TableNameNeedsQuote ? _dialect.Quote(table) : table; + var sqlCreate = string.Format("CREATE TABLE {0} ({1})", table, columns); + + ExecuteNonQuery(sqlCreate); + } + + public virtual List GetPrimaryKeys(IEnumerable columns) + { + var primaryKeys = new List(); + + foreach (var col in columns) + { + if (col.IsPrimaryKey) + { + primaryKeys.Add(col.Name); + } + } + + return primaryKeys; + } + + public virtual void AddColumnDefaultValue(string table, string column, object defaultValue) + { + if (defaultValue is DateTime defaultValueDateTime) + { + if (defaultValueDateTime.Kind != DateTimeKind.Utc) + { + throw new Exception("Only UTC values are accepted as default DateTime values."); + } + } + + table = QuoteTableNameIfRequired(table); + column = QuoteColumnNameIfRequired(column); + var def = Dialect.Default(defaultValue); + ExecuteNonQuery(string.Format("ALTER TABLE {0} ADD DEFAULT('{1}') FOR {2}", table, def, column)); + } + + public virtual void AddColumn(string table, string sqlColumn) + { + table = QuoteTableNameIfRequired(table); + ExecuteNonQuery(string.Format("ALTER TABLE {0} ADD COLUMN {1}", table, sqlColumn)); + } + + public virtual void ChangeColumn(string table, string sqlColumn) + { + table = QuoteTableNameIfRequired(table); + ExecuteNonQuery(string.Format("ALTER TABLE {0} ALTER COLUMN {1}", table, sqlColumn)); + } + + protected virtual string JoinColumnsAndIndexes(IEnumerable columns) + { + var indexes = JoinIndexes(columns); + var columnsAndIndexes = JoinColumns(columns) + (indexes != null ? "," + indexes : string.Empty); + return columnsAndIndexes; + } + + protected virtual string JoinIndexes(IEnumerable columns) + { + var indexes = new List(); + foreach (var column in columns) + { + var indexSql = column.IndexSql; + + if (indexSql != null) + { + indexes.Add(indexSql); + } + } + + if (indexes.Count == 0) + { + return null; + } + + return string.Join(", ", [.. indexes]); + } + + protected virtual string JoinColumns(IEnumerable columns) + { + var columnStrings = new List(); + + foreach (var column in columns) + { + columnStrings.Add(column.ColumnSql); + } + + return string.Join(", ", columnStrings.ToArray()); + } + + public IDbCommand CreateCommand() + { + EnsureHasConnection(); + var cmd = _connection.CreateCommand(); + + if (CommandTimeout.HasValue) + { + cmd.CommandTimeout = CommandTimeout.Value; + } + + cmd.CommandType = CommandType.Text; + + if (_transaction != null) + { + cmd.Transaction = _transaction; + } + + if (CommandTimeout.HasValue) + { + cmd.CommandTimeout = CommandTimeout.Value; + } + return cmd; + } + + protected IDbCommand BuildCommand(string sql) + { + var cmd = CreateCommand(); + cmd.CommandText = sql; + return cmd; + } + + public virtual int Delete(string table) + { + return Delete(table, null, (string[])null); + } + + protected void EnsureHasConnection() + { + if (_connection.State != ConnectionState.Open) + { + _connection.Open(); + } + } + + protected virtual void CreateSchemaInfoTable() + { + EnsureHasConnection(); + if (!TableExists(_schemaInfotable)) + { + AddTable(_schemaInfotable, + new Column("Version", DbType.Int64, ColumnProperty.NotNull | ColumnProperty.PrimaryKey), + new Column("Scope", DbType.String, 50, ColumnProperty.NotNull | ColumnProperty.PrimaryKey, "default"), + new Column("TimeStamp", DbType.DateTime)); + } + else + { + if (!ColumnExists(_schemaInfotable, "Scope")) + { + AddColumn(_schemaInfotable, "Scope", DbType.String, 50, ColumnProperty.NotNull, "default"); + RemoveAllConstraints(_schemaInfotable); + AddPrimaryKey("PK_SchemaInfo", _schemaInfotable, ["Version", "Scope"]); + } + + if (!ColumnExists(_schemaInfotable, "TimeStamp")) + { + AddColumn(_schemaInfotable, "TimeStamp", DbType.DateTime); + } + } + } + + public virtual string QuoteValues(string values) + { + return QuoteValues([values])[0]; + } + + public virtual string[] QuoteValues(string[] values) + { + return values.Select(val => + { + if (null == val) + { + return "null"; + } + else + { + return string.Format("'{0}'", val.Replace("'", "''")); + } + }).ToArray(); + } + + public virtual string JoinColumnsAndValues(string[] columns, string[] values) + { + return JoinColumnsAndValues(columns, values, ", "); + } + + public virtual string JoinColumnsAndValues(string[] columns, string[] values, string joinSeperator) + { + var quotedValues = QuoteValues(values); + var namesAndValues = new string[columns.Length]; + for (var i = 0; i < columns.Length; i++) + { + namesAndValues[i] = string.Format("{0}={1}", columns[i], quotedValues[i]); + } + + return string.Join(joinSeperator, namesAndValues); + } + + public virtual string GenerateParameterNameParameter(int index) + { + return "@p" + index; + } + + public virtual string GenerateParameterName(int index) + { + return GenerateParameterNameParameter(index); + } + + protected virtual void ConfigureParameterWithValue(IDbDataParameter parameter, int index, object value) + { + if (value == null || value == DBNull.Value) + { + parameter.Value = DBNull.Value; + } + else if (value is Guid || value is Guid?) + { + parameter.DbType = DbType.Guid; + parameter.Value = (Guid)value; + } + else if (value is short) + { + parameter.DbType = DbType.Int16; + parameter.Value = value; + } + else if (value is int) + { + parameter.DbType = DbType.Int32; + parameter.Value = value; + } + else if (value is long) + { + parameter.DbType = DbType.Int64; + parameter.Value = value; + } + else if (value is ushort) + { + parameter.DbType = DbType.UInt16; + parameter.Value = value; + } + else if (value is uint) + { + parameter.DbType = DbType.UInt32; + parameter.Value = value; + } + else if (value is ulong) + { + parameter.DbType = DbType.UInt64; + parameter.Value = value; + } + else if (value is double) + { + parameter.DbType = DbType.Double; + parameter.Value = value; + } + else if (value is decimal) + { + parameter.DbType = DbType.Decimal; + parameter.Value = value; + } + else if (value is string) + { + parameter.DbType = DbType.String; + parameter.Value = value; + } + else if (value is DateTime || value is DateTime?) + { + parameter.DbType = DbType.DateTime; + parameter.Value = value; + } + else if (value is DateTimeOffset dateTimeOffset) + { + parameter.DbType = DbType.DateTimeOffset; + parameter.Value = dateTimeOffset.ToUniversalTime(); + } + else if (value is DateTimeOffset?) + { + parameter.DbType = DbType.DateTimeOffset; + parameter.Value = value == null ? null : ((DateTimeOffset?)value).Value.ToUniversalTime(); + } + else if (value is bool || value is bool?) + { + parameter.DbType = DbType.Boolean; + parameter.Value = value; + } + else + { + throw new NotSupportedException(string.Format("TransformationProvider does not support value: {0} of type: {1}", value, value.GetType())); + } + } + + private string FormatValue(object value) + { + if (value == null) + { + return null; + } + + if (value is DateTime) + { + return ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss:fff"); + } + + return value.ToString(); + } + + private void QuoteColumnNames(string[] primaryColumns) + { + for (var i = 0; i < primaryColumns.Length; i++) + { + primaryColumns[i] = QuoteColumnNameIfRequired(primaryColumns[i]); + } + } + + public virtual void RemoveIndex(string table, string name) + { + if (TableExists(table) && IndexExists(table, name)) + { + name = QuoteConstraintNameIfRequired(name); + ExecuteNonQuery(string.Format("DROP INDEX {0}", name)); + } + } + + public virtual string AddIndex(string table, Index index) + { + throw new NotImplementedException($"{nameof(AddIndex)} is not overridden for the provider."); + } + + public virtual string AddIndex(string name, string table, params string[] columns) + { + var index = new Index { Name = name, KeyColumns = columns }; + + return AddIndex(table, index); + } + + protected string QuoteConstraintNameIfRequired(string name) + { + return _dialect.ConstraintNameNeedsQuote ? _dialect.Quote(name) : name; + } + + public abstract bool IndexExists(string table, string name); + + protected virtual string GetPrimaryKeyConstraintName(string table) + { + return null; + } + + public virtual void RemovePrimaryKey(string table) + { + if (!TableExists(table)) + { + return; + } + + var primaryKeyConstraintName = GetPrimaryKeyConstraintName(table); + + if (primaryKeyConstraintName == null || !ConstraintExists(table, primaryKeyConstraintName)) + { + return; + } + + RemoveConstraint(table, primaryKeyConstraintName); + } + + public virtual void RemoveAllIndexes(string table) + { + if (!TableExists(table)) + { + return; + } + + var indexes = GetIndexes(table); + + foreach (var index in indexes) + { + if (index.Name == null || !IndexExists(table, index.Name)) + { + continue; + } + + if (index.PrimaryKey || index.UniqueConstraint) + { + RemoveConstraint(table, index.Name); + } + else + { + RemoveIndex(table, index.Name); + } + } + } + + public virtual string Concatenate(params string[] strings) + { + return string.Join(" || ", strings); + } + + public IDbConnection Connection + { + get { return _connection; } + } + + public IEnumerable GetTables(string schema) + { + var tableRestrictions = new string[4]; + tableRestrictions[1] = schema; + + var c = _connection as DbConnection; + var tables = c.GetSchema("Tables", tableRestrictions); + return from DataRow row in tables.Rows select (row["TABLE_NAME"] as string); + } + + public IEnumerable GetColumns(string schema, string table) + { + var tableRestrictions = new string[4]; + tableRestrictions[1] = schema; + tableRestrictions[2] = table; + + var c = _connection as DbConnection; + var tables = c.GetSchema("Columns", tableRestrictions); + return from DataRow row in tables.Rows select (row["TABLE_NAME"] as string); + } + + protected void ValidateIndex(string tableName, Index index) + { + var hasFilterItems = index.FilterItems != null && index.FilterItems.Count > 0; + var columns = GetColumns(table: tableName); + + if (!TableExists(tableName)) + { + throw new MigrationException($"Table '{tableName}' does not exist."); + } + + foreach (var keyColumn in index.KeyColumns) + { + if (!index.KeyColumns.All(x => columns.Any(y => y.Name.Equals(x, StringComparison.OrdinalIgnoreCase)))) + { + throw new MigrationException($"Column '{keyColumn}' does not exist."); + } + } + + if (hasFilterItems) + { + if (!index.FilterItems.All(x => index.KeyColumns.Any(y => x.ColumnName.Equals(y, StringComparison.OrdinalIgnoreCase)))) + { + throw new MigrationException($"All columns in the {nameof(index.FilterItems)} should exist in the {nameof(index.KeyColumns)}."); + } + } + + if (IndexExists(tableName, index.Name)) + { + throw new MigrationException($"Index '{index.Name}' in table {tableName} already exists."); + } + + if (index.IncludeColumns != null && index.IncludeColumns.Length > 0) + { + if (index.IncludeColumns.Any(x => index.KeyColumns.Any(y => x.Equals(y, StringComparison.OrdinalIgnoreCase)))) + { + throw new MigrationException($"It is not allowed to use a column in {nameof(index.IncludeColumns)} that exist in {nameof(index.KeyColumns)}."); + } + } + } + + public virtual void CopyDataFromTableToTable(string sourceTableName, List sourceColumnNames, string targetTableName, List targetColumnNames, List orderBySourceColumns = null) + { + throw new NotImplementedException(); + } +} + diff --git a/src/Migrator/Providers/TypeNames.cs b/src/Migrator/Providers/TypeNames.cs new file mode 100644 index 00000000..8f0944d0 --- /dev/null +++ b/src/Migrator/Providers/TypeNames.cs @@ -0,0 +1,217 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers; + +/// +/// This class maps a DbType to names. +/// +/// +/// Associations may be marked with a capacity. Calling the Get() +/// method with a type and actual size n will return the associated +/// name with smallest capacity >= n, if available and an unmarked +/// default type otherwise. +/// Eg, setting +/// +/// Names.Put(DbType, "TEXT" ); +/// Names.Put(DbType, 255, "VARCHAR($l)" ); +/// Names.Put(DbType, 65534, "LONGVARCHAR($l)" ); +/// +/// will give you back the following: +/// +/// Names.Get(DbType) // --> "TEXT" (default) +/// Names.Get(DbType,100) // --> "VARCHAR(100)" (100 is in [0:255]) +/// Names.Get(DbType,1000) // --> "LONGVARCHAR(1000)" (100 is in [256:65534]) +/// Names.Get(DbType,100000) // --> "TEXT" (default) +/// +/// On the other hand, simply putting +/// +/// Names.Put(DbType, "VARCHAR($l)" ); +/// +/// would result in +/// +/// Names.Get(DbType) // --> "VARCHAR($l)" (will cause trouble) +/// Names.Get(DbType,100) // --> "VARCHAR(100)" +/// Names.Get(DbType,1000) // --> "VARCHAR(1000)" +/// Names.Get(DbType,10000) // --> "VARCHAR(10000)" +/// +/// +public class TypeNames +{ + public const string LengthPlaceHolder = "$l"; + public const string PrecisionPlaceHolder = "$p"; + public const string ScalePlaceHolder = "$s"; + + private readonly Dictionary defaults = new Dictionary(); + + private readonly Dictionary parametrized = new Dictionary(); + + private readonly Dictionary aliases = new Dictionary(); + + private readonly Dictionary> weighted = + new Dictionary>(); + + public DbType GetDbType(string type) + { + type = type.Trim().ToLower(); + var retval = defaults.Where(x => x.Value.Trim().ToLower().StartsWith(type)).Select(x => x.Key); + if (retval.Any()) + { + return (DbType)retval.First(); + } + + retval = weighted.Where(x => x.Value.Where(y => y.Value.Trim().ToLower().StartsWith(type)).Any()).Select(x => x.Key); + if (retval.Any()) + { + return (DbType)retval.First(); + } + + var alias = aliases.Where(x => x.Key.Trim().ToLower().StartsWith(type)); + + if (alias.Any()) + { + return (DbType)alias.First().Value; + } + + return DbType.AnsiString; + } + + /// + /// Get default type name for specified type + /// + /// the type key + /// the default type name associated with the specified key + public string Get(DbType typecode) + { + string result; + if (!defaults.TryGetValue((MigratorDbType)typecode, out result)) + { + throw new ArgumentException("Dialect does not support DbType." + typecode, "typecode"); + } + return result; + } + + /// + /// Get default type name for specified type + /// + /// the type key + /// the default type name associated with the specified key + public string GetParametrized(DbType typecode) + { + string result; + if (!parametrized.TryGetValue((MigratorDbType)typecode, out result)) + { + return null; + } + return result; + } + + /// + /// Get the type name specified type and size + /// + /// the type key + /// the SQL length + /// the SQL scale + /// the SQL precision + /// + /// The associated name with smallest capacity >= size if available and the + /// default type name otherwise + /// + public string Get(DbType typecode, int size, int precision, int scale) + { + SortedList map; + weighted.TryGetValue((MigratorDbType)typecode, out map); + if (map != null && map.Count > 0) + { + foreach (var entry in map) + { + if (size <= entry.Key) + { + return Replace(entry.Value, size, precision, scale); + } + } + } + //Could not find a specific type for the size, using the default + return Get(typecode); + } + + private static string Replace(string type, int size, int precision, int scale) + { + type = StringUtils.ReplaceOnce(type, LengthPlaceHolder, size.ToString()); + type = StringUtils.ReplaceOnce(type, ScalePlaceHolder, scale.ToString()); + return StringUtils.ReplaceOnce(type, PrecisionPlaceHolder, precision.ToString()); + } + + /// + /// Set a type name for specified type key and capacity + /// + /// the type key + /// the (maximum) type size/length + /// The associated name + public void Put(DbType typecode, int capacity, string value) + { + SortedList map; + if (!weighted.TryGetValue((MigratorDbType)typecode, out map)) + { + // add new ordered map + weighted[(MigratorDbType)typecode] = map = new SortedList(); + } + map[capacity] = value; + } + + /// + /// Set a type name for specified type key and capacity + /// + /// the type key + /// the (maximum) type size/length + /// The associated name + public void Put(MigratorDbType typecode, int capacity, string value) + { + SortedList map; + if (!weighted.TryGetValue(typecode, out map)) + { + // add new ordered map + weighted[typecode] = map = new SortedList(); + } + map[capacity] = value; + } + + /// + /// + /// + /// + /// + public void Put(DbType typecode, string value) + { + defaults[(MigratorDbType)typecode] = value; + } + + /// + /// + /// + /// + /// + public void Put(MigratorDbType typecode, string value) + { + defaults[typecode] = value; + } + + /// + /// + /// + /// + /// + public void PutParametrized(DbType typecode, string value) + { + parametrized[(MigratorDbType)typecode] = value; + } + + + public void PutAlias(DbType typecode, string value) + { + aliases[value] = (MigratorDbType)typecode; + } +} diff --git a/src/Migrator/Providers/Utility/SqlServerUtility.cs b/src/Migrator/Providers/Utility/SqlServerUtility.cs new file mode 100644 index 00000000..eb477467 --- /dev/null +++ b/src/Migrator/Providers/Utility/SqlServerUtility.cs @@ -0,0 +1,65 @@ +using System.Data; +using DotNetProjects.Migrator.Providers.Impl.SqlServer; + +namespace DotNetProjects.Migrator.Providers.Utility; + +public static class SqlServerUtility +{ + public static void RemoveAllTablesFromDefaultDatabase(string connectionString) + { + var d = new SqlServerDialect(); + using var p = d.NewProviderForDialect(connectionString, null, null, null); + using var connection = p.Connection; + connection.Open(); + RemoveAllForeignKeys(connection); + DropAllTables(connection); + connection.Close(); + } + + private static void DropAllTables(IDbConnection connection) + { + ExecuteForEachTable(connection, "DROP TABLE ?"); + } + + private static void RemoveAllForeignKeys(IDbConnection connection) + { + using var dropConstraintsCommand = connection.CreateCommand(); + dropConstraintsCommand.CommandText = @"DECLARE @Sql NVARCHAR(500) DECLARE @Cursor CURSOR + +SET @Cursor = CURSOR FAST_FORWARD FOR + +SELECT DISTINCT sql = 'ALTER TABLE [' + tc2.TABLE_NAME + '] DROP [' + rc1.CONSTRAINT_NAME + ']' + +FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc1 + +LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc2 ON tc2.CONSTRAINT_NAME =rc1.CONSTRAINT_NAME + +OPEN @Cursor FETCH NEXT FROM @Cursor INTO @Sql + +WHILE (@@FETCH_STATUS = 0) + +BEGIN + +Exec sys.sp_executesql @Sql + +FETCH NEXT FROM @Cursor INTO @Sql + +END + +CLOSE @Cursor DEALLOCATE @Cursor"; + dropConstraintsCommand.CommandType = CommandType.Text; + dropConstraintsCommand.ExecuteNonQuery(); + } + + private static void ExecuteForEachTable(IDbConnection connection, string command) + { + using var forEachCommand = connection.CreateCommand(); + forEachCommand.CommandText = "sp_MSforeachtable"; + forEachCommand.CommandType = CommandType.StoredProcedure; + var par = forEachCommand.CreateParameter(); + par.ParameterName = "@command1"; + par.Value = command; + forEachCommand.Parameters.Add(par); + forEachCommand.ExecuteNonQuery(); + } +} diff --git a/src/Migrator/Tools/SchemaDumper.cs b/src/Migrator/Tools/SchemaDumper.cs deleted file mode 100755 index f4c3b2d9..00000000 --- a/src/Migrator/Tools/SchemaDumper.cs +++ /dev/null @@ -1,150 +0,0 @@ -#region License - -//The contents of this file are subject to the Mozilla Public License -//Version 1.1 (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.mozilla.org/MPL/ -//Software distributed under the License is distributed on an "AS IS" -//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -//License for the specific language governing rights and limitations -//under the License. - -#endregion - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Migrator.Framework; -using Migrator.Providers; - -namespace Migrator.Tools -{ - public class SchemaDumper - { - private readonly ITransformationProvider _provider; - - public SchemaDumper(ProviderTypes provider, string connectionString, string defaultSchema) - { - _provider = ProviderFactory.Create(provider, connectionString, defaultSchema); - } - - public string Dump() - { - var writer = new StringWriter(); - - writer.WriteLine("using Migrator;\n"); - writer.WriteLine("[Migration(1)]"); - writer.WriteLine("public class SchemaDump : Migration"); - writer.WriteLine("{"); - writer.WriteLine("\tpublic override void Up()"); - writer.WriteLine("\t{"); - - foreach (string table in _provider.GetTables()) - { - writer.WriteLine("\t\tDatabase.AddTable(\"{0}\",", table); - var columnLines = new List(); - foreach (Column column in _provider.GetColumns(table)) - { - if (column.Size>0 && column.DefaultValue!=null) - columnLines.Add(string.Format("\t\t\tnew Column(\"{0}\", DbType.{1}, {2}, {3}, \"{4}\")", column.Name, column.Type, column.Size, getColumnPropertyString(column.ColumnProperty), column.DefaultValue)); - else if (column.Size > 0) - columnLines.Add(string.Format("\t\t\tnew Column(\"{0}\", DbType.{1}, {2}, {3})", column.Name, column.Type, column.Size, getColumnPropertyString(column.ColumnProperty))); - else if (column.DefaultValue != null) - columnLines.Add(string.Format("\t\t\tnew Column(\"{0}\", DbType.{1}, {2}, \"{3}\")", column.Name, column.Type, getColumnPropertyString(column.ColumnProperty), column.DefaultValue)); - else - columnLines.Add(string.Format("\t\t\tnew Column(\"{0}\", DbType.{1}, {2})", column.Name, column.Type, getColumnPropertyString(column.ColumnProperty))); - } - foreach (var constraint in _provider.GetForeignKeyConstraints(table)) - { - columnLines.Add(string.Format("\t\t\tnew ForeignKeyConstraint(\"{0}\", \"{1}\", new[] {{\"{2}\"}}, \"{3}\", new[] {{\"{4}\"}})", constraint.Name, constraint.Table, string.Join("\",\"", constraint.Columns), constraint.PkTable, string.Join("\",\"", constraint.PkColumns))); - } - writer.WriteLine(string.Join(string.Format(",{0}", Environment.NewLine), columnLines.ToArray())); - writer.WriteLine("\t\t);"); - - foreach (Index index in _provider.GetIndexes(table).Where( x => !x.PrimaryKey)) - { - if (index.IncludeColumns == null) - { - writer.WriteLine(string.Format("\t\tDatabase.AddIndex(\"{0}\", new Index() {{ Name = \"{1}\", Unique = {2}, Clustered = {3}, KeyColumns = new[] {{\"{4}\"}}, IncludeColumns = null }});", - table, - index.Name, - index.Unique.ToString().ToLower(), - index.Clustered.ToString().ToLower(), - string.Join("\",\"", index.KeyColumns))); - } - else - { - writer.WriteLine(string.Format("\t\tDatabase.AddIndex(\"{0}\", new Index() {{ Name = \"{1}\", Unique = {2}, Clustered = {3}, KeyColumns = new[] {{\"{4}\"}}, IncludeColumns = new[] {{\"{5}\"}} }});", - table, - index.Name, - index.Unique.ToString().ToLower(), - index.Clustered.ToString().ToLower(), - string.Join("\",\"", index.KeyColumns), - string.Join("\",\"", index.IncludeColumns))); - } - } - - writer.WriteLine(""); - } - - writer.WriteLine(""); - writer.WriteLine(""); - - /*foreach (string table in _provider.GetTables()) - { - foreach (var constraint in _provider.GetForeignKeyConstraints(table)) - { - writer.WriteLine("\t\tDatabase.AddForeignKey(\"{0}\", \"{1}\", new[] {{\"{2}\"}}, \"{3}\", new[] {{\"{4}\"}});", constraint.Name, constraint.Table, string.Join("\",\"", constraint.Columns), constraint.PkTable, string.Join("\",\"", constraint.PkColumns)); - writer.WriteLine(""); - } - }*/ - - writer.WriteLine(""); - writer.WriteLine(""); - - writer.WriteLine("\t}\n"); - writer.WriteLine("\tpublic override void Down()"); - writer.WriteLine("\t{"); - - foreach (string table in _provider.GetTables()) - { - writer.WriteLine("\t\tDatabase.RemoveTable(\"{0}\");", table); - writer.WriteLine(""); - } - - writer.WriteLine("\t}"); - writer.WriteLine("}"); - - return writer.ToString(); - } - - private string getColumnPropertyString(ColumnProperty prp) - { - string retVal = ""; - if ((prp & ColumnProperty.ForeignKey) == ColumnProperty.ForeignKey) retVal += "ColumnProperty.ForeignKey | "; - if ((prp & ColumnProperty.Identity) == ColumnProperty.Identity) retVal += "ColumnProperty.Identity | "; - if ((prp & ColumnProperty.Indexed) == ColumnProperty.Indexed) retVal += "ColumnProperty.Indexed | "; - if ((prp & ColumnProperty.NotNull) == ColumnProperty.NotNull) retVal += "ColumnProperty.NotNull | "; - if ((prp & ColumnProperty.Null) == ColumnProperty.Null) retVal += "ColumnProperty.Null | "; - if ((prp & ColumnProperty.PrimaryKey) == ColumnProperty.PrimaryKey) retVal += "ColumnProperty.PrimaryKey | "; - if ((prp & ColumnProperty.PrimaryKeyWithIdentity) == ColumnProperty.PrimaryKeyWithIdentity) retVal += "ColumnProperty.PrimaryKeyWithIdentity | "; - if ((prp & ColumnProperty.Unique) == ColumnProperty.Unique) retVal += "ColumnProperty.Unique | "; - if ((prp & ColumnProperty.Unsigned) == ColumnProperty.Unsigned) retVal += "ColumnProperty.Unsigned | "; - - if (retVal != "") retVal = retVal.Substring(0, retVal.Length - 3); - - if (retVal == "") retVal = "ColumnProperty.None"; - - return retVal; - } - - public void DumpTo(string file) - { - using (var writer = new StreamWriter(file)) - { - writer.Write(Dump()); - } - } - } -} \ No newline at end of file diff --git a/src/MigratorDotNet.snk b/src/MigratorDotNet.snk deleted file mode 100644 index 5032d709..00000000 Binary files a/src/MigratorDotNet.snk and /dev/null differ diff --git a/src/config/GlobalAssemblyInfo.cs b/src/config/GlobalAssemblyInfo.cs deleted file mode 100644 index f193933a..00000000 --- a/src/config/GlobalAssemblyInfo.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyProduct("DotNetProjects.Migrator")] -[assembly: AssemblyCopyright("Copyright © 2015")] - -[assembly: ComVisible(false)] - -[assembly: AssemblyVersion("4.0.2.*")] \ No newline at end of file diff --git a/src/config/app.config b/src/config/app.config deleted file mode 100644 index a0d3412c..00000000 --- a/src/config/app.config +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - -