Repository: hangfire-postgres/Hangfire.PostgreSql Branch: master Commit: 81a9f06a3324 Files: 109 Total size: 651.9 KB Directory structure: gitextract_l7v50vjb/ ├── .devcontainer/ │ ├── devcontainer.json │ ├── init.sh │ └── pg_hba.conf ├── .editorconfig ├── .gitattributes ├── .github/ │ └── workflows/ │ ├── ci.yml │ ├── pack.yml │ └── test-report.yml ├── .gitignore ├── .vscode/ │ └── settings.json ├── COPYING ├── COPYING.LESSER ├── Hangfire.PostgreSql.sln ├── Hangfire.PostgreSql.sln.DotSettings ├── LICENSE.md ├── README.md ├── SECURITY.md ├── src/ │ ├── Common/ │ │ ├── Hangfire.ruleset │ │ └── Hangfire.targets │ └── Hangfire.PostgreSql/ │ ├── CountersAggregator.cs │ ├── EnqueuedAndFetchedCountDto.cs │ ├── Entities/ │ │ ├── JobParameter.cs │ │ ├── Server.cs │ │ ├── ServerData.cs │ │ ├── SqlHash.cs │ │ ├── SqlJob.cs │ │ └── SqlState.cs │ ├── EnvironmentHelpers.cs │ ├── ExpirationManager.cs │ ├── Factories/ │ │ ├── ExistingNpgsqlConnectionFactory.cs │ │ ├── NpgsqlConnectionFactory.cs │ │ └── NpgsqlInstanceConnectionFactoryBase.cs │ ├── Hangfire.PostgreSql.csproj │ ├── IConnectionFactory.cs │ ├── IPersistentJobQueue.cs │ ├── IPersistentJobQueueMonitoringApi.cs │ ├── IPersistentJobQueueProvider.cs │ ├── JsonParameter.cs │ ├── PersistentJobQueueProviderCollection.cs │ ├── PostgreSqlBootstrapperConfigurationExtensions.cs │ ├── PostgreSqlBootstrapperOptions.cs │ ├── PostgreSqlConnection.cs │ ├── PostgreSqlDistributedLock.cs │ ├── PostgreSqlDistributedLockException.cs │ ├── PostgreSqlFetchedJob.cs │ ├── PostgreSqlHeartbeatProcess.cs │ ├── PostgreSqlJobQueue.cs │ ├── PostgreSqlJobQueueMonitoringApi.cs │ ├── PostgreSqlJobQueueProvider.cs │ ├── PostgreSqlMonitoringApi.cs │ ├── PostgreSqlObjectsInstaller.cs │ ├── PostgreSqlStorage.cs │ ├── PostgreSqlStorageOptions.cs │ ├── PostgreSqlWriteOnlyTransaction.cs │ ├── Properties/ │ │ ├── Annotations.cs │ │ └── AssemblyInfo.cs │ ├── Scripts/ │ │ ├── Install.v10.sql │ │ ├── Install.v11.sql │ │ ├── Install.v12.sql │ │ ├── Install.v13.sql │ │ ├── Install.v14.sql │ │ ├── Install.v15.sql │ │ ├── Install.v16.sql │ │ ├── Install.v17.sql │ │ ├── Install.v18.sql │ │ ├── Install.v19.sql │ │ ├── Install.v20.sql │ │ ├── Install.v21.sql │ │ ├── Install.v22.sql │ │ ├── Install.v23.sql │ │ ├── Install.v3.sql │ │ ├── Install.v4.sql │ │ ├── Install.v5.sql │ │ ├── Install.v6.sql │ │ ├── Install.v7.sql │ │ ├── Install.v8.sql │ │ └── Install.v9.sql │ └── Utils/ │ ├── AutoResetEventRegistry.cs │ ├── DbConnectionExtensions.cs │ ├── ExceptionTypeHelper.cs │ ├── TimestampHelper.cs │ ├── TransactionHelpers.cs │ └── TryExecute.cs └── tests/ └── Hangfire.PostgreSql.Tests/ ├── AssemblyAttributes.cs ├── CountersAggregatorFacts.cs ├── Entities/ │ └── TestJob.cs ├── ExpirationManagerFacts.cs ├── FirstClassQueueFeatureSupportTests.cs ├── GlobalSuppressions.cs ├── Hangfire.PostgreSql.Tests.csproj ├── PersistentJobQueueProviderCollectionFacts.cs ├── PostgreSqlConnectionFacts.cs ├── PostgreSqlDistributedLockFacts.cs ├── PostgreSqlFetchedJobFacts.cs ├── PostgreSqlInstallerFacts.cs ├── PostgreSqlJobQueueFacts.cs ├── PostgreSqlMonitoringApiFacts.cs ├── PostgreSqlStorageFacts.cs ├── PostgreSqlStorageOptionsFacts.cs ├── PostgreSqlWriteOnlyTransactionFacts.cs ├── Scripts/ │ └── Clean.sql └── Utils/ ├── CleanDatabaseAttribute.cs ├── ConnectionUtils.cs ├── DefaultConnectionFactory.cs ├── DelegateConnectionFactory.cs ├── Helper.cs ├── PostgreSqlStorageExtensions.cs ├── PostgreSqlStorageFixture.cs └── PostgreSqlTestObjectsInitializer.cs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .devcontainer/devcontainer.json ================================================ { "image": "mcr.microsoft.com/devcontainers/universal:2", "features": { "ghcr.io/devcontainers/features/dotnet:1": {} }, "customizations": { "vscode": { "extensions": [ "ms-dotnettools.csharp", "eamodio.gitlens", "formulahendry.dotnet-test-explorer", "mtxr.sqltools-driver-pg" ] } }, "containerEnv": { "DOTNET_CLI_HOME": "/tmp/DOTNET_CLI_HOME" }, "postCreateCommand": "./.devcontainer/init.sh", "postStartCommand": "sudo service postgresql start" } ================================================ FILE: .devcontainer/init.sh ================================================ sudo apt update sudo apt install postgresql postgresql-contrib -y sudo cp ./.devcontainer/pg_hba.conf /etc/postgresql/12/main/pg_hba.conf sudo chown postgres:postgres /etc/postgresql/12/main/pg_hba.conf sudo service postgresql start sudo psql -U postgres -c "ALTER USER postgres PASSWORD 'password';" dotnet restore ================================================ FILE: .devcontainer/pg_hba.conf ================================================ # PostgreSQL Client Authentication Configuration File # =================================================== # # Refer to the "Client Authentication" section in the PostgreSQL # documentation for a complete description of this file. A short # synopsis follows. # # This file controls: which hosts are allowed to connect, how clients # are authenticated, which PostgreSQL user names they can use, which # databases they can access. Records take one of these forms: # # local DATABASE USER METHOD [OPTIONS] # host DATABASE USER ADDRESS METHOD [OPTIONS] # hostssl DATABASE USER ADDRESS METHOD [OPTIONS] # hostnossl DATABASE USER ADDRESS METHOD [OPTIONS] # hostgssenc DATABASE USER ADDRESS METHOD [OPTIONS] # hostnogssenc DATABASE USER ADDRESS METHOD [OPTIONS] # # (The uppercase items must be replaced by actual values.) # # The first field is the connection type: "local" is a Unix-domain # socket, "host" is either a plain or SSL-encrypted TCP/IP socket, # "hostssl" is an SSL-encrypted TCP/IP socket, and "hostnossl" is a # non-SSL TCP/IP socket. Similarly, "hostgssenc" uses a # GSSAPI-encrypted TCP/IP socket, while "hostnogssenc" uses a # non-GSSAPI socket. # # DATABASE can be "all", "sameuser", "samerole", "replication", a # database name, or a comma-separated list thereof. The "all" # keyword does not match "replication". Access to replication # must be enabled in a separate record (see example below). # # USER can be "all", a user name, a group name prefixed with "+", or a # comma-separated list thereof. In both the DATABASE and USER fields # you can also write a file name prefixed with "@" to include names # from a separate file. # # ADDRESS specifies the set of hosts the record matches. It can be a # host name, or it is made up of an IP address and a CIDR mask that is # an integer (between 0 and 32 (IPv4) or 128 (IPv6) inclusive) that # specifies the number of significant bits in the mask. A host name # that starts with a dot (.) matches a suffix of the actual host name. # Alternatively, you can write an IP address and netmask in separate # columns to specify the set of hosts. Instead of a CIDR-address, you # can write "samehost" to match any of the server's own IP addresses, # or "samenet" to match any address in any subnet that the server is # directly connected to. # # METHOD can be "trust", "reject", "md5", "password", "scram-sha-256", # "gss", "sspi", "ident", "peer", "pam", "ldap", "radius" or "cert". # Note that "password" sends passwords in clear text; "md5" or # "scram-sha-256" are preferred since they send encrypted passwords. # # OPTIONS are a set of options for the authentication in the format # NAME=VALUE. The available options depend on the different # authentication methods -- refer to the "Client Authentication" # section in the documentation for a list of which options are # available for which authentication methods. # # Database and user names containing spaces, commas, quotes and other # special characters must be quoted. Quoting one of the keywords # "all", "sameuser", "samerole" or "replication" makes the name lose # its special character, and just match a database or username with # that name. # # This file is read on server startup and when the server receives a # SIGHUP signal. If you edit the file on a running system, you have to # SIGHUP the server for the changes to take effect, run "pg_ctl reload", # or execute "SELECT pg_reload_conf()". # # Put your actual configuration here # ---------------------------------- # # If you want to allow non-local connections, you need to add more # "host" records. In that case you will also need to make PostgreSQL # listen on a non-local interface via the listen_addresses # configuration parameter, or via the -i or -h command line switches. # DO NOT DISABLE! # If you change this first entry you will need to make sure that the # database superuser can access the database using some other method. # Noninteractive access to all databases is required during automatic # maintenance (custom daily cronjobs, replication, and similar tasks). # # Database administrative login by Unix domain socket local all postgres trust local all postgres md5 # TYPE DATABASE USER ADDRESS METHOD # "local" is for Unix domain socket connections only local all all peer # IPv4 local connections: host all all 127.0.0.1/32 md5 # IPv6 local connections: host all all ::1/128 md5 # Allow replication connections from localhost, by a user with the # replication privilege. local replication all peer host replication all 127.0.0.1/32 md5 host replication all ::1/128 md5 ================================================ FILE: .editorconfig ================================================ [*] charset = utf-8-bom end_of_line = lf trim_trailing_whitespace = false indent_style = space indent_size = 2 # Microsoft .NET properties # GLOBAL ERROR LEVEL dotnet_analyzer_diagnostic.severity = warning # A property with [Parameter] should be public dotnet_diagnostic.BL0004.severity = error # Method does not use this and can be marked static dotnet_diagnostic.CA1822.severity = suggestion # An IDisposable implementation should call GC.SuppressFinalize dotnet_diagnostic.CA1816.severity = none # The behavior of 'string.StartsWith(string)' could vary based on the current user's locale settings. dotnet_diagnostic.CA1310.severity = suggestion # The behavior of 'string.ToLower(string)' could vary based on the current user's locale settings. dotnet_diagnostic.CA1304.severity = suggestion # Identifiers should not have incorrect suffix (Do not use reserved words in type names) dotnet_diagnostic.CA1711.severity = suggestion # Identifiers should not match keywords dotnet_diagnostic.CA1716.severity = suggestion # Identifiers should not contain underscores dotnet_diagnostic.CA1707.severity = suggestion [*] csharp_indent_braces = false csharp_indent_switch_labels = true csharp_new_line_before_catch = true csharp_new_line_before_else = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_object_initializers = false csharp_new_line_before_open_brace = control_blocks,events,indexers,local_functions,methods,properties,types csharp_new_line_between_query_expression_clauses = true csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:warning csharp_preserve_single_line_blocks = true 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_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 csharp_style_unused_value_expression_statement_preference = discard_variable:none csharp_style_var_elsewhere = false:warning csharp_style_var_for_built_in_types = false:warning csharp_style_var_when_type_is_apparent = false:warning csharp_using_directive_placement = outside_namespace:single_indent dotnet_naming_rule.constants_rule.import_to_resharper = as_predefined dotnet_naming_rule.constants_rule.severity = warning dotnet_naming_rule.constants_rule.style = upper_camel_case_style dotnet_naming_rule.constants_rule.symbols = constants_symbols dotnet_naming_rule.event_rule.import_to_resharper = as_predefined dotnet_naming_rule.event_rule.severity = warning dotnet_naming_rule.event_rule.style = upper_camel_case_style dotnet_naming_rule.event_rule.symbols = event_symbols dotnet_naming_rule.interfaces_rule.import_to_resharper = as_predefined dotnet_naming_rule.interfaces_rule.severity = warning dotnet_naming_rule.interfaces_rule.style = i_upper_camel_case_style dotnet_naming_rule.interfaces_rule.symbols = interfaces_symbols dotnet_naming_rule.locals_rule.import_to_resharper = as_predefined dotnet_naming_rule.locals_rule.severity = warning dotnet_naming_rule.locals_rule.style = lower_camel_case_style_1 dotnet_naming_rule.locals_rule.symbols = locals_symbols dotnet_naming_rule.local_constants_rule.import_to_resharper = as_predefined dotnet_naming_rule.local_constants_rule.severity = warning dotnet_naming_rule.local_constants_rule.style = lower_camel_case_style_1 dotnet_naming_rule.local_constants_rule.symbols = local_constants_symbols dotnet_naming_rule.local_constants_rule_1.import_to_resharper = True dotnet_naming_rule.local_constants_rule_1.resharper_description = LocalConstants dotnet_naming_rule.local_constants_rule_1.resharper_guid = 02852745-d182-4e0a-8332-8b22091b5e66 dotnet_naming_rule.local_constants_rule_1.severity = warning dotnet_naming_rule.local_constants_rule_1.style = lower_camel_case_style_1 dotnet_naming_rule.local_constants_rule_1.symbols = local_constants_symbols_1 dotnet_naming_rule.local_functions_rule.import_to_resharper = as_predefined dotnet_naming_rule.local_functions_rule.severity = warning dotnet_naming_rule.local_functions_rule.style = upper_camel_case_style dotnet_naming_rule.local_functions_rule.symbols = local_functions_symbols dotnet_naming_rule.method_rule.import_to_resharper = as_predefined dotnet_naming_rule.method_rule.severity = warning dotnet_naming_rule.method_rule.style = upper_camel_case_style dotnet_naming_rule.method_rule.symbols = method_symbols dotnet_naming_rule.parameters_rule.import_to_resharper = as_predefined dotnet_naming_rule.parameters_rule.severity = warning dotnet_naming_rule.parameters_rule.style = lower_camel_case_style_1 dotnet_naming_rule.parameters_rule.symbols = parameters_symbols dotnet_naming_rule.private_constants_rule.import_to_resharper = as_predefined dotnet_naming_rule.private_constants_rule.severity = warning dotnet_naming_rule.private_constants_rule.style = upper_camel_case_style dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols dotnet_naming_rule.private_instance_fields_rule.import_to_resharper = as_predefined dotnet_naming_rule.private_instance_fields_rule.severity = warning dotnet_naming_rule.private_instance_fields_rule.style = lower_camel_case_style dotnet_naming_rule.private_instance_fields_rule.symbols = private_instance_fields_symbols dotnet_naming_rule.private_static_fields_override_rule.import_to_resharper = False dotnet_naming_rule.private_static_fields_override_rule.severity = warning dotnet_naming_rule.private_static_fields_override_rule.style = upper_camel_case_style dotnet_naming_rule.private_static_fields_override_rule.symbols = private_static_fields_override_symbols dotnet_naming_rule.private_static_fields_rule.import_to_resharper = as_predefined dotnet_naming_rule.private_static_fields_rule.severity = warning dotnet_naming_rule.private_static_fields_rule.style = lower_camel_case_style dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols dotnet_naming_rule.private_static_fields_rule_1.import_to_resharper = True dotnet_naming_rule.private_static_fields_rule_1.resharper_description = PrivateStaticFields dotnet_naming_rule.private_static_fields_rule_1.resharper_guid = ee7c9d0d-12d0-427b-bf48-38a6b1740690 dotnet_naming_rule.private_static_fields_rule_1.severity = warning dotnet_naming_rule.private_static_fields_rule_1.style = lower_camel_case_style dotnet_naming_rule.private_static_fields_rule_1.symbols = private_static_fields_symbols_1 dotnet_naming_rule.private_static_readonly_rule.import_to_resharper = as_predefined dotnet_naming_rule.private_static_readonly_rule.severity = warning dotnet_naming_rule.private_static_readonly_rule.style = lower_camel_case_style dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols dotnet_naming_rule.property_rule.import_to_resharper = as_predefined dotnet_naming_rule.property_rule.severity = warning dotnet_naming_rule.property_rule.style = upper_camel_case_style dotnet_naming_rule.property_rule.symbols = property_symbols dotnet_naming_rule.public_fields_rule.import_to_resharper = as_predefined dotnet_naming_rule.public_fields_rule.severity = warning dotnet_naming_rule.public_fields_rule.style = lower_camel_case_style dotnet_naming_rule.public_fields_rule.symbols = public_fields_symbols dotnet_naming_rule.static_readonly_rule.import_to_resharper = as_predefined dotnet_naming_rule.static_readonly_rule.severity = warning dotnet_naming_rule.static_readonly_rule.style = lower_camel_case_style dotnet_naming_rule.static_readonly_rule.symbols = static_readonly_symbols dotnet_naming_rule.types_and_namespaces_rule.import_to_resharper = as_predefined dotnet_naming_rule.types_and_namespaces_rule.severity = warning dotnet_naming_rule.types_and_namespaces_rule.style = upper_camel_case_style dotnet_naming_rule.types_and_namespaces_rule.symbols = types_and_namespaces_symbols dotnet_naming_rule.type_parameters_rule.import_to_resharper = as_predefined dotnet_naming_rule.type_parameters_rule.resharper_style = T + AaBb, V + AaBb, K + AaBb dotnet_naming_rule.type_parameters_rule.severity = warning dotnet_naming_rule.type_parameters_rule.style = t_upper_camel_case_style dotnet_naming_rule.type_parameters_rule.symbols = type_parameters_symbols dotnet_naming_style.i_upper_camel_case_style.capitalization = pascal_case dotnet_naming_style.i_upper_camel_case_style.required_prefix = I dotnet_naming_style.lower_camel_case_style.capitalization = camel_case dotnet_naming_style.lower_camel_case_style.required_prefix = _ dotnet_naming_style.lower_camel_case_style_1.capitalization = camel_case dotnet_naming_style.t_upper_camel_case_style.capitalization = pascal_case dotnet_naming_style.t_upper_camel_case_style.required_prefix = T dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected dotnet_naming_symbols.constants_symbols.applicable_kinds = field dotnet_naming_symbols.constants_symbols.required_modifiers = const dotnet_naming_symbols.event_symbols.applicable_accessibilities = * dotnet_naming_symbols.event_symbols.applicable_kinds = event dotnet_naming_symbols.interfaces_symbols.applicable_accessibilities = * dotnet_naming_symbols.interfaces_symbols.applicable_kinds = interface dotnet_naming_symbols.locals_symbols.applicable_accessibilities = * dotnet_naming_symbols.locals_symbols.applicable_kinds = local dotnet_naming_symbols.local_constants_symbols.applicable_accessibilities = * dotnet_naming_symbols.local_constants_symbols.applicable_kinds = local dotnet_naming_symbols.local_constants_symbols.required_modifiers = const dotnet_naming_symbols.local_constants_symbols_1.applicable_accessibilities = * dotnet_naming_symbols.local_constants_symbols_1.applicable_kinds = local dotnet_naming_symbols.local_constants_symbols_1.required_modifiers = const dotnet_naming_symbols.local_constants_symbols_1.resharper_applicable_kinds = local_constant dotnet_naming_symbols.local_constants_symbols_1.resharper_required_modifiers = any dotnet_naming_symbols.local_functions_symbols.applicable_accessibilities = * dotnet_naming_symbols.local_functions_symbols.applicable_kinds = local_function dotnet_naming_symbols.method_symbols.applicable_accessibilities = * dotnet_naming_symbols.method_symbols.applicable_kinds = method dotnet_naming_symbols.parameters_symbols.applicable_accessibilities = * dotnet_naming_symbols.parameters_symbols.applicable_kinds = parameter dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field dotnet_naming_symbols.private_constants_symbols.required_modifiers = const dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities = private dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds = field dotnet_naming_symbols.private_static_fields_override_symbols.applicable_accessibilities = local,private dotnet_naming_symbols.private_static_fields_override_symbols.applicable_kinds = field dotnet_naming_symbols.private_static_fields_override_symbols.required_modifiers = const,static dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static dotnet_naming_symbols.private_static_fields_symbols_1.applicable_accessibilities = local,private dotnet_naming_symbols.private_static_fields_symbols_1.applicable_kinds = field dotnet_naming_symbols.private_static_fields_symbols_1.required_modifiers = static dotnet_naming_symbols.private_static_fields_symbols_1.resharper_applicable_kinds = field dotnet_naming_symbols.private_static_fields_symbols_1.resharper_required_modifiers = static dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly dotnet_naming_symbols.property_symbols.applicable_accessibilities = * dotnet_naming_symbols.property_symbols.applicable_kinds = property dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected dotnet_naming_symbols.public_fields_symbols.applicable_kinds = field dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected dotnet_naming_symbols.static_readonly_symbols.applicable_kinds = field dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static,readonly dotnet_naming_symbols.types_and_namespaces_symbols.applicable_accessibilities = * dotnet_naming_symbols.types_and_namespaces_symbols.applicable_kinds = namespace,class,struct,enum,delegate dotnet_naming_symbols.type_parameters_symbols.applicable_accessibilities = * dotnet_naming_symbols.type_parameters_symbols.applicable_kinds = type_parameter dotnet_separate_import_directive_groups = false dotnet_sort_system_directives_first = true dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:none dotnet_style_predefined_type_for_locals_parameters_members = true:warning dotnet_style_predefined_type_for_member_access = true:warning dotnet_style_qualification_for_event = false:suggestion dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_method = false:suggestion dotnet_style_qualification_for_property = false:suggestion dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning file_header_template = # ReSharper properties resharper_accessor_owner_body = expression_body resharper_alignment_tab_fill_style = use_spaces resharper_align_first_arg_by_paren = false resharper_align_linq_query = false resharper_align_multiline_array_and_object_initializer = false resharper_align_multiline_array_initializer = true resharper_align_multiline_ctor_init = true resharper_align_multiline_expression_braces = false resharper_align_multiline_implements_list = true resharper_align_multiline_property_pattern = false resharper_align_multiline_switch_expression = false resharper_align_multiline_type_argument = true resharper_align_multiline_type_parameter = true resharper_align_multline_type_parameter_constrains = false resharper_align_multline_type_parameter_list = false resharper_align_ternary = align_not_nested resharper_align_tuple_components = false resharper_allow_alias = true resharper_allow_comment_after_lbrace = true resharper_allow_far_alignment = false resharper_always_use_end_of_line_brace_style = false resharper_apply_auto_detected_rules = false resharper_apply_on_completion = false resharper_arguments_anonymous_function = positional resharper_arguments_literal = positional resharper_arguments_named = positional resharper_arguments_other = positional resharper_arguments_skip_single = false resharper_arguments_string_literal = positional resharper_attribute_style = do_not_touch resharper_autodetect_indent_settings = false resharper_blank_lines_after_block_statements = 1 resharper_blank_lines_after_case = 0 resharper_blank_lines_after_control_transfer_statements = 0 resharper_blank_lines_after_imports = 1 resharper_blank_lines_after_multiline_statements = 0 resharper_blank_lines_after_options = 1 resharper_blank_lines_after_start_comment = 1 resharper_blank_lines_after_using_list = 1 resharper_blank_lines_around_accessor = 0 resharper_blank_lines_around_auto_property = 1 resharper_blank_lines_around_block_case_section = 0 resharper_blank_lines_around_class_definition = 1 resharper_blank_lines_around_field = 1 resharper_blank_lines_around_function_declaration = 0 resharper_blank_lines_around_function_definition = 1 resharper_blank_lines_around_global_attribute = 0 resharper_blank_lines_around_invocable = 1 resharper_blank_lines_around_local_method = 1 resharper_blank_lines_around_multiline_case_section = 0 resharper_blank_lines_around_namespace = 1 resharper_blank_lines_around_other_declaration = 0 resharper_blank_lines_around_property = 1 resharper_blank_lines_around_razor_functions = 1 resharper_blank_lines_around_razor_helpers = 1 resharper_blank_lines_around_razor_sections = 1 resharper_blank_lines_around_single_line_accessor = 0 resharper_blank_lines_around_single_line_auto_property = 0 resharper_blank_lines_around_single_line_field = 0 resharper_blank_lines_around_single_line_function_definition = 0 resharper_blank_lines_around_single_line_invocable = 0 resharper_blank_lines_around_single_line_local_method = 0 resharper_blank_lines_around_single_line_property = 0 resharper_blank_lines_around_single_line_type = 0 resharper_blank_lines_around_type = 1 resharper_blank_lines_before_block_statements = 0 resharper_blank_lines_before_case = 0 resharper_blank_lines_before_control_transfer_statements = 0 resharper_blank_lines_before_multiline_statements = 0 resharper_blank_lines_before_single_line_comment = 0 resharper_blank_lines_inside_namespace = 0 resharper_blank_lines_inside_region = 1 resharper_blank_lines_inside_type = 0 resharper_blank_line_after_pi = true resharper_braces_for_dowhile = required_for_multiline_statement resharper_braces_for_fixed = required resharper_braces_for_for = required_for_multiline_statement resharper_braces_for_foreach = required_for_multiline_statement resharper_braces_for_ifelse = required_for_multiline resharper_braces_for_lock = required_for_multiline_statement resharper_braces_for_using = required_for_multiline_statement resharper_braces_for_while = required_for_multiline_statement resharper_braces_redundant = false resharper_break_template_declaration = line_break resharper_can_use_global_alias = false resharper_configure_await_analysis_mode = disabled resharper_constructor_or_destructor_body = block_body resharper_continuous_indent_multiplier = 1 resharper_continuous_line_indent = single resharper_cpp_align_multiline_argument = true resharper_cpp_align_multiline_binary_expressions_chain = true resharper_cpp_align_multiline_calls_chain = true resharper_cpp_align_multiline_extends_list = true resharper_cpp_align_multiline_for_stmt = true resharper_cpp_align_multiline_parameter = true resharper_cpp_align_multiple_declaration = true resharper_cpp_anonymous_method_declaration_braces = next_line resharper_cpp_case_block_braces = next_line_shifted_2 resharper_cpp_empty_block_style = multiline resharper_cpp_indent_invocation_pars = inside resharper_cpp_indent_method_decl_pars = inside resharper_cpp_indent_statement_pars = inside resharper_cpp_indent_switch_labels = false resharper_cpp_insert_final_newline = false resharper_cpp_int_align_comments = false resharper_cpp_invocable_declaration_braces = next_line resharper_cpp_max_line_length = 160 resharper_cpp_new_line_before_while = true resharper_cpp_other_braces = next_line resharper_cpp_space_after_cast = false resharper_cpp_space_around_binary_operator = true resharper_cpp_type_declaration_braces = next_line resharper_cpp_use_indent_from_previous_element = true resharper_cpp_wrap_lines = true resharper_cpp_wrap_multiple_type_parameter_constraints_style = chop_if_long resharper_cpp_wrap_parameters_style = wrap_if_long resharper_csharp_align_multiline_argument = false resharper_csharp_align_multiline_binary_expressions_chain = false resharper_csharp_align_multiline_calls_chain = false resharper_csharp_align_multiline_expression = false resharper_csharp_align_multiline_extends_list = false resharper_csharp_align_multiline_for_stmt = false resharper_csharp_align_multiline_parameter = false resharper_csharp_align_multiple_declaration = false resharper_csharp_blank_lines_around_region = 2 resharper_csharp_case_block_braces = end_of_line resharper_csharp_empty_block_style = together_same_line resharper_csharp_indent_invocation_pars = outside_and_inside resharper_csharp_indent_method_decl_pars = outside_and_inside resharper_csharp_indent_statement_pars = outside_and_inside resharper_csharp_insert_final_newline = true resharper_csharp_int_align_comments = true resharper_csharp_max_line_length = 160 resharper_csharp_naming_rule.constants = AaBb resharper_csharp_naming_rule.enum_member = AaBb resharper_csharp_naming_rule.local_constants = aaBb resharper_csharp_naming_rule.method_property_event = AaBb resharper_csharp_naming_rule.other = AaBb resharper_csharp_naming_rule.private_constants = AaBb resharper_csharp_naming_rule.private_static_fields = _ + aaBb resharper_csharp_naming_rule.private_static_readonly = _ + aaBb resharper_csharp_naming_rule.static_readonly = _ + aaBb resharper_csharp_new_line_before_while = false resharper_csharp_prefer_qualified_reference = false resharper_csharp_space_after_unary_operator = false resharper_csharp_stick_comment = false resharper_csharp_use_indent_from_previous_element = false resharper_csharp_wrap_lines = true resharper_csharp_wrap_multiple_type_parameter_constraints_style = wrap_if_long resharper_csharp_wrap_parameters_style = chop_if_long resharper_cxxcli_property_declaration_braces = next_line resharper_default_exception_variable_name = e resharper_default_value_when_type_evident = default_literal resharper_default_value_when_type_not_evident = default_expression resharper_delete_quotes_from_solid_values = false resharper_disable_blank_line_changes = false resharper_disable_formatter = false resharper_disable_indenter = false resharper_disable_int_align = false resharper_disable_line_break_changes = false resharper_disable_line_break_removal = false resharper_disable_space_changes = false resharper_disable_space_changes_before_trailing_comment = false resharper_dont_remove_extra_blank_lines = false resharper_enable_wrapping = false resharper_enforce_line_ending_style = false resharper_event_handler_pattern_long = $object$On$event$ resharper_event_handler_pattern_short = On$event$ resharper_expression_braces = inside resharper_expression_pars = inside resharper_extra_spaces = remove_all resharper_force_attribute_style = separate resharper_force_chop_compound_do_expression = false resharper_force_chop_compound_if_expression = false resharper_force_chop_compound_while_expression = false resharper_format_leading_spaces_decl = false resharper_free_block_braces = next_line resharper_function_declaration_return_type_style = do_not_change resharper_function_definition_return_type_style = do_not_change resharper_generator_mode = false resharper_html_attribute_indent = align_by_first_attribute resharper_html_insert_final_newline = true resharper_html_linebreak_before_elements = body,div,p,form,h1,h2,h3 resharper_html_max_blank_lines_between_tags = 2 resharper_html_max_line_length = 160 resharper_html_pi_attributes_indent = align_by_first_attribute resharper_html_pi_attribute_style = on_single_line resharper_html_space_before_self_closing = false resharper_html_use_indent_from_previous_element = true resharper_html_wrap_lines = true resharper_ignore_space_preservation = false resharper_include_prefix_comment_in_indent = false resharper_indent_access_specifiers_from_class = false resharper_indent_aligned_ternary = true resharper_indent_anonymous_method_block = false resharper_indent_case_from_select = true resharper_indent_child_elements = OneIndent resharper_indent_class_members_from_access_specifiers = false resharper_indent_comment = true resharper_indent_inside_namespace = true resharper_indent_nested_fixed_stmt = false resharper_indent_nested_foreach_stmt = true resharper_indent_nested_for_stmt = true resharper_indent_nested_lock_stmt = true resharper_indent_nested_usings_stmt = false resharper_indent_nested_while_stmt = true resharper_indent_pars = outside_and_inside resharper_indent_preprocessor_directives = none resharper_indent_preprocessor_if = no_indent resharper_indent_preprocessor_other = no_indent resharper_indent_preprocessor_region = usual_indent resharper_indent_text = OneIndent resharper_indent_typearg_angles = outside_and_inside resharper_indent_typeparam_angles = outside_and_inside resharper_indent_type_constraints = true resharper_indent_wrapped_function_names = false resharper_instance_members_qualify_declared_in = resharper_int_align_assignments = false resharper_int_align_binary_expressions = false resharper_int_align_declaration_names = false resharper_int_align_eq = false resharper_int_align_fields = false resharper_int_align_fix_in_adjacent = true resharper_int_align_invocations = false resharper_int_align_methods = false resharper_int_align_nested_ternary = false resharper_int_align_parameters = false resharper_int_align_properties = false resharper_int_align_property_patterns = false resharper_int_align_switch_expressions = false resharper_int_align_switch_sections = false resharper_int_align_variables = false resharper_keep_blank_lines_in_code = 2 resharper_keep_blank_lines_in_declarations = 2 resharper_keep_existing_attribute_arrangement = false resharper_keep_existing_declaration_block_arrangement = false resharper_keep_existing_declaration_parens_arrangement = true resharper_keep_existing_embedded_arrangement = true resharper_keep_existing_embedded_block_arrangement = false resharper_keep_existing_enum_arrangement = false resharper_keep_existing_expr_member_arrangement = true resharper_keep_existing_invocation_parens_arrangement = false resharper_keep_existing_property_patterns_arrangement = true resharper_keep_existing_switch_expression_arrangement = false resharper_keep_nontrivial_alias = true resharper_keep_user_linebreaks = true resharper_keep_user_wrapping = true resharper_linebreaks_around_razor_statements = true resharper_linebreaks_inside_tags_for_elements_longer_than = 2147483647 resharper_linebreaks_inside_tags_for_elements_with_child_elements = true resharper_linebreaks_inside_tags_for_multiline_elements = true resharper_linebreak_before_all_elements = false resharper_linebreak_before_multiline_elements = true resharper_linebreak_before_singleline_elements = false resharper_line_break_after_colon_in_member_initializer_lists = do_not_change resharper_line_break_after_comma_in_member_initializer_lists = false resharper_line_break_before_comma_in_member_initializer_lists = false resharper_line_break_before_requires_clause = do_not_change resharper_linkage_specification_braces = end_of_line resharper_linkage_specification_indentation = none resharper_local_function_body = block_body resharper_macro_block_begin = resharper_macro_block_end = resharper_max_array_initializer_elements_on_line = 10000 resharper_max_attribute_length_for_same_line = 38 resharper_max_enum_members_on_line = 1 resharper_max_formal_parameters_on_line = 10000 resharper_max_initializer_elements_on_line = 4 resharper_max_invocation_arguments_on_line = 10000 resharper_member_initializer_list_style = do_not_change resharper_method_or_operator_body = block_body resharper_namespace_declaration_braces = next_line resharper_namespace_indentation = all resharper_nested_ternary_style = autodetect resharper_new_line_before_catch = true resharper_new_line_before_else = true resharper_new_line_before_enumerators = true resharper_normalize_tag_names = false resharper_no_indent_inside_elements = html,body,thead,tbody,tfoot resharper_no_indent_inside_if_element_longer_than = 200 resharper_object_creation_when_type_evident = target_typed resharper_object_creation_when_type_not_evident = explicitly_typed resharper_old_engine = false resharper_outdent_binary_ops = false resharper_outdent_commas = false resharper_outdent_dots = false resharper_outdent_namespace_member = false resharper_outdent_statement_labels = false resharper_outdent_ternary_ops = false resharper_parentheses_non_obvious_operations = none, bitwise, bitwise_inclusive_or, bitwise_exclusive_or, shift, bitwise_and resharper_parentheses_redundancy_style = remove_if_not_clarifies_precedence resharper_parentheses_same_type_operations = true resharper_place_accessorholder_attribute_on_same_line = if_owner_is_single_line resharper_place_accessor_attribute_on_same_line = true resharper_place_comments_at_first_column = false resharper_place_constructor_initializer_on_same_line = true resharper_place_event_attribute_on_same_line = false resharper_place_expr_accessor_on_single_line = true resharper_place_expr_method_on_single_line = true resharper_place_expr_property_on_single_line = true resharper_place_field_attribute_on_same_line = if_owner_is_single_line resharper_place_linq_into_on_new_line = false resharper_place_method_attribute_on_same_line = false resharper_place_namespace_definitions_on_same_line = false resharper_place_property_attribute_on_same_line = false resharper_place_simple_case_statement_on_same_line = if_owner_is_single_line resharper_place_simple_embedded_statement_on_same_line = if_owner_is_single_line resharper_place_simple_initializer_on_single_line = true resharper_place_simple_property_pattern_on_single_line = true resharper_place_simple_switch_expression_on_single_line = false resharper_place_type_attribute_on_same_line = false resharper_place_type_constraints_on_same_line = true resharper_prefer_explicit_discard_declaration = true resharper_prefer_separate_deconstructed_variables_declaration = false resharper_preserve_spaces_inside_tags = pre,textarea resharper_qualified_using_at_nested_scope = false resharper_quote_style = doublequoted resharper_razor_prefer_qualified_reference = true resharper_remove_blank_lines_near_braces = false resharper_remove_blank_lines_near_braces_in_code = true resharper_remove_blank_lines_near_braces_in_declarations = true resharper_remove_this_qualifier = true resharper_requires_expression_braces = next_line resharper_resx_attribute_indent = single_indent resharper_resx_insert_final_newline = false resharper_resx_linebreak_before_elements = resharper_resx_max_blank_lines_between_tags = 0 resharper_resx_max_line_length = 2147483647 resharper_resx_pi_attributes_indent = align_by_first_attribute resharper_resx_pi_attribute_style = do_not_touch resharper_resx_space_before_self_closing = false resharper_resx_use_indent_from_previous_element = true resharper_resx_wrap_lines = false resharper_resx_wrap_tags_and_pi = false resharper_resx_wrap_text = false resharper_show_autodetect_configure_formatting_tip = false resharper_simple_block_style = do_not_change resharper_simple_case_statement_style = do_not_change resharper_simple_embedded_statement_style = do_not_change resharper_sort_attributes = false resharper_sort_class_selectors = false resharper_sort_usings = true resharper_sort_usings_lowercase_first = false resharper_spaces_around_eq_in_attribute = false resharper_spaces_around_eq_in_pi_attribute = false resharper_spaces_inside_tags = false resharper_space_after_attributes = true resharper_space_after_attribute_target_colon = true resharper_space_after_cast = false resharper_space_after_colon = true resharper_space_after_colon_in_case = true resharper_space_after_colon_in_inheritance_clause = true resharper_space_after_comma = true resharper_space_after_for_colon = true resharper_space_after_keywords_in_control_flow_statements = true resharper_space_after_last_attribute = false resharper_space_after_last_pi_attribute = false resharper_space_after_operator_keyword = true resharper_space_after_ptr_in_data_member = true resharper_space_after_ptr_in_data_members = false resharper_space_after_ptr_in_method = true resharper_space_after_ref_in_data_member = true resharper_space_after_ref_in_data_members = false resharper_space_after_ref_in_method = true resharper_space_after_semicolon_in_for_statement = true resharper_space_after_ternary_colon = true resharper_space_after_ternary_quest = true resharper_space_after_triple_slash = true resharper_space_after_type_parameter_constraint_colon = true resharper_space_around_additive_op = true resharper_space_around_alias_eq = true resharper_space_around_assignment_op = true resharper_space_around_assignment_operator = true resharper_space_around_deref_in_trailing_return_type = true resharper_space_around_lambda_arrow = true resharper_space_around_member_access_operator = false resharper_space_around_relational_op = true resharper_space_around_shift_op = true resharper_space_around_stmt_colon = true resharper_space_around_ternary_operator = true resharper_space_before_array_rank_parentheses = false resharper_space_before_attribute_target_colon = false resharper_space_before_checked_parentheses = false resharper_space_before_colon = false resharper_space_before_colon_in_case = false resharper_space_before_colon_in_inheritance_clause = true resharper_space_before_comma = false resharper_space_before_default_parentheses = false resharper_space_before_empty_invocation_parentheses = false resharper_space_before_empty_method_parentheses = false resharper_space_before_for_colon = true resharper_space_before_initializer_braces = false resharper_space_before_invocation_parentheses = false resharper_space_before_label_colon = false resharper_space_before_lambda_parentheses = false resharper_space_before_method_parentheses = false resharper_space_before_nameof_parentheses = false resharper_space_before_new_parentheses = false resharper_space_before_nullable_mark = false resharper_space_before_open_square_brackets = false resharper_space_before_pointer_asterik_declaration = false resharper_space_before_ptr_in_abstract_decl = false resharper_space_before_ptr_in_data_member = false resharper_space_before_ptr_in_data_members = true resharper_space_before_ptr_in_method = false resharper_space_before_ref_in_abstract_decl = false resharper_space_before_ref_in_data_member = false resharper_space_before_ref_in_data_members = true resharper_space_before_ref_in_method = false resharper_space_before_semicolon = false resharper_space_before_semicolon_in_for_statement = false resharper_space_before_singleline_accessorholder = true resharper_space_before_sizeof_parentheses = false resharper_space_before_template_args = false resharper_space_before_template_params = true resharper_space_before_ternary_colon = true resharper_space_before_ternary_quest = true resharper_space_before_trailing_comment = true resharper_space_before_typeof_parentheses = false resharper_space_before_type_argument_angle = false resharper_space_before_type_parameter_angle = false resharper_space_before_type_parameter_constraint_colon = true resharper_space_before_type_parameter_parentheses = true resharper_space_between_accessors_in_singleline_property = true resharper_space_between_attribute_sections = true resharper_space_between_closing_angle_brackets_in_template_args = false resharper_space_between_keyword_and_expression = true resharper_space_between_keyword_and_type = true resharper_space_between_method_call_empty_parameter_list_parentheses = false resharper_space_between_method_call_name_and_opening_parenthesis = false resharper_space_between_method_call_parameter_list_parentheses = false resharper_space_between_method_declaration_empty_parameter_list_parentheses = false resharper_space_between_method_declaration_name_and_open_parenthesis = false resharper_space_between_method_declaration_parameter_list_parentheses = false resharper_space_between_parentheses_of_control_flow_statements = false resharper_space_between_square_brackets = false resharper_space_between_typecast_parentheses = false resharper_space_in_singleline_accessorholder = true resharper_space_in_singleline_anonymous_method = true resharper_space_in_singleline_method = true resharper_space_near_postfix_and_prefix_op = false resharper_space_within_array_initialization_braces = false resharper_space_within_array_rank_empty_parentheses = false resharper_space_within_array_rank_parentheses = false resharper_space_within_attribute_angles = false resharper_space_within_checked_parentheses = false resharper_space_within_default_parentheses = false resharper_space_within_empty_braces = true resharper_space_within_empty_initializer_braces = false resharper_space_within_empty_invocation_parentheses = false resharper_space_within_empty_method_parentheses = false resharper_space_within_empty_template_params = false resharper_space_within_expression_parentheses = false resharper_space_within_initializer_braces = false resharper_space_within_invocation_parentheses = false resharper_space_within_method_parentheses = false resharper_space_within_nameof_parentheses = false resharper_space_within_new_parentheses = false resharper_space_within_parentheses = false resharper_space_within_single_line_array_initializer_braces = true resharper_space_within_sizeof_parentheses = false resharper_space_within_template_args = false resharper_space_within_template_params = false resharper_space_within_tuple_parentheses = false resharper_space_within_typeof_parentheses = false resharper_space_within_type_argument_angles = false resharper_space_within_type_parameter_angles = false resharper_space_within_type_parameter_parentheses = false resharper_special_else_if_treatment = true resharper_static_members_qualify_members = none resharper_static_members_qualify_with = declared_type resharper_support_vs_event_naming_pattern = true resharper_toplevel_function_declaration_return_type_style = do_not_change resharper_toplevel_function_definition_return_type_style = do_not_change resharper_trailing_comma_in_multiline_lists = true resharper_trailing_comma_in_singleline_lists = false resharper_use_continuous_indent_inside_initializer_braces = true resharper_use_continuous_indent_inside_parens = true resharper_use_continuous_line_indent_in_expression_braces = false resharper_use_continuous_line_indent_in_method_pars = false resharper_use_heuristics_for_body_style = true resharper_use_indents_from_main_language_in_file = true resharper_use_indent_from_vs = false resharper_use_roslyn_logic_for_evident_types = false resharper_vb_align_multiline_argument = true resharper_vb_align_multiline_expression = true resharper_vb_align_multiline_parameter = true resharper_vb_align_multiple_declaration = true resharper_vb_blank_lines_around_region = 1 resharper_vb_insert_final_newline = false resharper_vb_max_line_length = 160 resharper_vb_place_field_attribute_on_same_line = true resharper_vb_place_method_attribute_on_same_line = false resharper_vb_place_type_attribute_on_same_line = false resharper_vb_prefer_qualified_reference = false resharper_vb_space_after_unary_operator = true resharper_vb_space_around_multiplicative_op = false resharper_vb_stick_comment = true resharper_vb_use_indent_from_previous_element = true resharper_vb_wrap_lines = true resharper_vb_wrap_parameters_style = wrap_if_long resharper_wrap_after_binary_opsign = true resharper_wrap_after_declaration_lpar = false resharper_wrap_after_dot = false resharper_wrap_after_dot_in_method_calls = false resharper_wrap_after_expression_lbrace = true resharper_wrap_after_invocation_lpar = false resharper_wrap_arguments_style = wrap_if_long resharper_wrap_around_elements = true resharper_wrap_array_initializer_style = chop_if_long resharper_wrap_base_clause_style = wrap_if_long resharper_wrap_before_arrow_with_expressions = false resharper_wrap_before_binary_opsign = true resharper_wrap_before_colon = false resharper_wrap_before_comma = false resharper_wrap_before_comma_in_base_clause = false resharper_wrap_before_declaration_lpar = false resharper_wrap_before_declaration_rpar = false resharper_wrap_before_eq = false resharper_wrap_before_expression_rbrace = true resharper_wrap_before_extends_colon = false resharper_wrap_before_first_type_parameter_constraint = false resharper_wrap_before_invocation_lpar = false resharper_wrap_before_invocation_rpar = false resharper_wrap_before_linq_expression = false resharper_wrap_before_ternary_opsigns = true resharper_wrap_before_type_parameter_langle = false resharper_wrap_braced_init_list_style = wrap_if_long resharper_wrap_chained_binary_expressions = chop_if_long resharper_wrap_chained_method_calls = wrap_if_long resharper_wrap_ctor_initializer_style = wrap_if_long resharper_wrap_enumeration_style = chop_if_long resharper_wrap_enum_declaration = chop_always resharper_wrap_extends_list_style = wrap_if_long resharper_wrap_for_stmt_header_style = wrap_if_long resharper_wrap_multiple_declaration_style = chop_if_long resharper_wrap_object_and_collection_initializer_style = chop_if_long resharper_wrap_property_pattern = chop_if_long resharper_wrap_switch_expression = chop_always resharper_wrap_ternary_expr_style = chop_if_long resharper_wrap_verbatim_interpolated_strings = no_wrap resharper_xmldoc_attribute_indent = double_indent resharper_xmldoc_insert_final_newline = false resharper_xmldoc_linebreak_before_elements = summary,remarks,example,returns,param,typeparam,value,para resharper_xmldoc_max_blank_lines_between_tags = 0 resharper_xmldoc_max_line_length = 160 resharper_xmldoc_pi_attributes_indent = double_indent resharper_xmldoc_pi_attribute_style = do_not_touch resharper_xmldoc_space_before_self_closing = true resharper_xmldoc_use_indent_from_previous_element = true resharper_xmldoc_wrap_lines = true resharper_xmldoc_wrap_tags_and_pi = false resharper_xmldoc_wrap_text = true resharper_xml_attribute_indent = align_by_first_attribute resharper_xml_insert_final_newline = false resharper_xml_linebreak_before_elements = resharper_xml_max_blank_lines_between_tags = 2 resharper_xml_max_line_length = 160 resharper_xml_pi_attributes_indent = align_by_first_attribute resharper_xml_pi_attribute_style = do_not_touch resharper_xml_space_before_self_closing = true resharper_xml_use_indent_from_previous_element = true resharper_xml_wrap_lines = true resharper_xml_wrap_tags_and_pi = true resharper_xml_wrap_text = false # ReSharper inspection severities resharper_access_rights_in_text_highlighting = warning resharper_access_to_disposed_closure_highlighting = warning resharper_access_to_for_each_variable_in_closure_highlighting = warning resharper_access_to_modified_closure_highlighting = warning resharper_access_to_static_member_via_derived_type_highlighting = warning resharper_address_of_marshal_by_ref_object_highlighting = warning resharper_angular_html_banana_highlighting = warning resharper_annotate_can_be_null_parameter_highlighting = none resharper_annotate_can_be_null_type_member_highlighting = none resharper_annotate_not_null_parameter_highlighting = none resharper_annotate_not_null_type_member_highlighting = none resharper_annotation_conflict_in_hierarchy_highlighting = warning resharper_annotation_redundancy_at_value_type_highlighting = warning resharper_annotation_redundancy_in_hierarchy_highlighting = warning resharper_arguments_style_anonymous_function_highlighting = hint resharper_arguments_style_literal_highlighting = hint resharper_arguments_style_named_expression_highlighting = hint resharper_arguments_style_other_highlighting = hint resharper_arguments_style_string_literal_highlighting = hint resharper_arrange_accessor_owner_body_highlighting = suggestion resharper_arrange_attributes_highlighting = warning resharper_arrange_constructor_or_destructor_body_highlighting = none resharper_arrange_default_value_when_type_evident_highlighting = suggestion resharper_arrange_default_value_when_type_not_evident_highlighting = warning resharper_arrange_local_function_body_highlighting = none resharper_arrange_method_or_operator_body_highlighting = none resharper_arrange_missing_parentheses_highlighting = warning resharper_arrange_object_creation_when_type_evident_highlighting = suggestion resharper_arrange_object_creation_when_type_not_evident_highlighting = warning resharper_arrange_redundant_parentheses_highlighting = none resharper_arrange_static_member_qualifier_highlighting = hint resharper_arrange_this_qualifier_highlighting = hint resharper_arrange_trailing_comma_in_multiline_lists_highlighting = warning resharper_arrange_trailing_comma_in_singleline_lists_highlighting = warning resharper_arrange_var_keywords_in_deconstructing_declaration_highlighting = suggestion resharper_asp_content_placeholder_not_resolved_highlighting = error resharper_asp_custom_page_parser_filter_type_highlighting = warning resharper_asp_dead_code_highlighting = warning resharper_asp_entity_highlighting = warning resharper_asp_image_highlighting = warning resharper_asp_invalid_control_type_highlighting = error resharper_asp_not_resolved_highlighting = error resharper_asp_ods_method_reference_resolve_error_highlighting = error resharper_asp_resolve_warning_highlighting = warning resharper_asp_skin_not_resolved_highlighting = error resharper_asp_tag_attribute_with_optional_value_highlighting = warning resharper_asp_theme_not_resolved_highlighting = error resharper_asp_unused_register_directive_highlighting_highlighting = warning resharper_asp_warning_highlighting = warning resharper_assignment_in_conditional_expression_highlighting = warning resharper_assignment_is_fully_discarded_highlighting = warning resharper_assign_null_to_not_null_attribute_highlighting = hint resharper_asxx_path_error_highlighting = warning resharper_async_iterator_invocation_without_await_foreach_highlighting = warning resharper_async_void_lambda_highlighting = warning resharper_async_void_method_highlighting = none resharper_auto_property_can_be_made_get_only_global_highlighting = suggestion resharper_auto_property_can_be_made_get_only_local_highlighting = suggestion resharper_bad_attribute_brackets_spaces_highlighting = none resharper_bad_braces_spaces_highlighting = none resharper_bad_child_statement_indent_highlighting = warning resharper_bad_colon_spaces_highlighting = none resharper_bad_comma_spaces_highlighting = none resharper_bad_control_braces_indent_highlighting = suggestion resharper_bad_control_braces_line_breaks_highlighting = none resharper_bad_declaration_braces_indent_highlighting = none resharper_bad_declaration_braces_line_breaks_highlighting = none resharper_bad_empty_braces_line_breaks_highlighting = none resharper_bad_expression_braces_indent_highlighting = none resharper_bad_expression_braces_line_breaks_highlighting = none resharper_bad_generic_brackets_spaces_highlighting = none resharper_bad_indent_highlighting = none resharper_bad_linq_line_breaks_highlighting = none resharper_bad_list_line_breaks_highlighting = none resharper_bad_member_access_spaces_highlighting = none resharper_bad_namespace_braces_indent_highlighting = none resharper_bad_parens_line_breaks_highlighting = none resharper_bad_parens_spaces_highlighting = none resharper_bad_preprocessor_indent_highlighting = none resharper_bad_semicolon_spaces_highlighting = none resharper_bad_spaces_after_keyword_highlighting = none resharper_bad_square_brackets_spaces_highlighting = none resharper_bad_switch_braces_indent_highlighting = none resharper_bad_symbol_spaces_highlighting = none resharper_base_member_has_params_highlighting = warning resharper_base_method_call_with_default_parameter_highlighting = warning resharper_base_object_equals_is_object_equals_highlighting = warning resharper_base_object_get_hash_code_call_in_get_hash_code_highlighting = warning resharper_bitwise_operator_on_enum_without_flags_highlighting = warning resharper_by_ref_argument_is_volatile_field_highlighting = warning resharper_cannot_apply_equality_operator_to_type_highlighting = warning resharper_center_tag_is_obsolete_highlighting = warning resharper_check_for_reference_equality_instead_1_highlighting = suggestion resharper_check_for_reference_equality_instead_2_highlighting = suggestion resharper_check_for_reference_equality_instead_3_highlighting = suggestion resharper_check_for_reference_equality_instead_4_highlighting = suggestion resharper_check_namespace_highlighting = warning resharper_class_cannot_be_instantiated_highlighting = warning resharper_class_can_be_sealed_global_highlighting = none resharper_class_can_be_sealed_local_highlighting = none resharper_class_never_instantiated_global_highlighting = suggestion resharper_class_never_instantiated_local_highlighting = suggestion resharper_class_with_virtual_members_never_inherited_global_highlighting = suggestion resharper_class_with_virtual_members_never_inherited_local_highlighting = suggestion resharper_clear_attribute_is_obsolete_all_highlighting = warning resharper_clear_attribute_is_obsolete_highlighting = warning resharper_collection_never_queried_global_highlighting = warning resharper_collection_never_queried_local_highlighting = warning resharper_collection_never_updated_global_highlighting = warning resharper_collection_never_updated_local_highlighting = warning resharper_comment_typo_highlighting = hint resharper_compare_non_constrained_generic_with_null_highlighting = none resharper_compare_of_floats_by_equality_operator_highlighting = warning resharper_conditional_ternary_equal_branch_highlighting = warning resharper_condition_is_always_true_or_false_highlighting = warning resharper_confusing_char_as_integer_in_constructor_highlighting = warning resharper_constant_conditional_access_qualifier_highlighting = warning resharper_constant_null_coalescing_condition_highlighting = warning resharper_constructor_initializer_loop_highlighting = warning resharper_container_annotation_redundancy_highlighting = warning resharper_context_value_is_provided_highlighting = none resharper_contract_annotation_not_parsed_highlighting = warning resharper_convert_closure_to_method_group_highlighting = suggestion resharper_convert_conditional_ternary_expression_to_switch_expression_highlighting = hint resharper_convert_if_do_to_while_highlighting = suggestion resharper_convert_if_statement_to_conditional_ternary_expression_highlighting = suggestion resharper_convert_if_statement_to_null_coalescing_assignment_highlighting = suggestion resharper_convert_if_statement_to_null_coalescing_expression_highlighting = suggestion resharper_convert_if_statement_to_return_statement_highlighting = hint resharper_convert_if_statement_to_switch_expression_highlighting = hint resharper_convert_if_statement_to_switch_statement_highlighting = hint resharper_convert_if_to_or_expression_highlighting = suggestion resharper_convert_nullable_to_short_form_highlighting = suggestion resharper_convert_switch_statement_to_switch_expression_highlighting = hint resharper_convert_to_auto_property_highlighting = suggestion resharper_convert_to_auto_property_when_possible_highlighting = hint resharper_convert_to_auto_property_with_private_setter_highlighting = hint resharper_convert_to_compound_assignment_highlighting = hint resharper_convert_to_constant_global_highlighting = hint resharper_convert_to_constant_local_highlighting = hint resharper_convert_to_lambda_expression_highlighting = suggestion resharper_convert_to_lambda_expression_when_possible_highlighting = none resharper_convert_to_local_function_highlighting = suggestion resharper_convert_to_null_coalescing_compound_assignment_highlighting = suggestion resharper_convert_to_static_class_highlighting = suggestion resharper_convert_to_using_declaration_highlighting = suggestion resharper_convert_to_vb_auto_property_highlighting = suggestion resharper_convert_to_vb_auto_property_when_possible_highlighting = hint resharper_convert_to_vb_auto_property_with_private_setter_highlighting = hint resharper_co_variant_array_conversion_highlighting = warning resharper_cpp_abstract_class_without_specifier_highlighting = warning resharper_cpp_abstract_virtual_function_call_in_ctor_highlighting = error resharper_cpp_access_specifier_with_no_declarations_highlighting = suggestion resharper_cpp_assigned_value_is_never_used_highlighting = warning resharper_cpp_awaiter_type_is_not_class_highlighting = warning resharper_cpp_bad_angle_brackets_spaces_highlighting = none resharper_cpp_bad_braces_spaces_highlighting = none resharper_cpp_bad_child_statement_indent_highlighting = none resharper_cpp_bad_colon_spaces_highlighting = none resharper_cpp_bad_comma_spaces_highlighting = none resharper_cpp_bad_control_braces_indent_highlighting = none resharper_cpp_bad_control_braces_line_breaks_highlighting = none resharper_cpp_bad_declaration_braces_indent_highlighting = none resharper_cpp_bad_declaration_braces_line_breaks_highlighting = none resharper_cpp_bad_empty_braces_line_breaks_highlighting = none resharper_cpp_bad_expression_braces_indent_highlighting = none resharper_cpp_bad_expression_braces_line_breaks_highlighting = none resharper_cpp_bad_indent_highlighting = none resharper_cpp_bad_list_line_breaks_highlighting = none resharper_cpp_bad_member_access_spaces_highlighting = none resharper_cpp_bad_namespace_braces_indent_highlighting = none resharper_cpp_bad_parens_line_breaks_highlighting = none resharper_cpp_bad_parens_spaces_highlighting = none resharper_cpp_bad_semicolon_spaces_highlighting = none resharper_cpp_bad_spaces_after_keyword_highlighting = none resharper_cpp_bad_square_brackets_spaces_highlighting = none resharper_cpp_bad_switch_braces_indent_highlighting = none resharper_cpp_bad_symbol_spaces_highlighting = none resharper_cpp_boolean_increment_expression_highlighting = warning resharper_cpp_boost_format_bad_code_highlighting = warning resharper_cpp_boost_format_legacy_code_highlighting = suggestion resharper_cpp_boost_format_mixed_args_highlighting = error resharper_cpp_boost_format_too_few_args_highlighting = error resharper_cpp_boost_format_too_many_args_highlighting = warning resharper_cpp_clang_tidy_abseil_duration_addition_highlighting = none resharper_cpp_clang_tidy_abseil_duration_comparison_highlighting = none resharper_cpp_clang_tidy_abseil_duration_conversion_cast_highlighting = none resharper_cpp_clang_tidy_abseil_duration_division_highlighting = none resharper_cpp_clang_tidy_abseil_duration_factory_float_highlighting = none resharper_cpp_clang_tidy_abseil_duration_factory_scale_highlighting = none resharper_cpp_clang_tidy_abseil_duration_subtraction_highlighting = none resharper_cpp_clang_tidy_abseil_duration_unnecessary_conversion_highlighting = none resharper_cpp_clang_tidy_abseil_faster_strsplit_delimiter_highlighting = none resharper_cpp_clang_tidy_abseil_no_internal_dependencies_highlighting = none resharper_cpp_clang_tidy_abseil_no_namespace_highlighting = none resharper_cpp_clang_tidy_abseil_redundant_strcat_calls_highlighting = none resharper_cpp_clang_tidy_abseil_string_find_startswith_highlighting = none resharper_cpp_clang_tidy_abseil_string_find_str_contains_highlighting = none resharper_cpp_clang_tidy_abseil_str_cat_append_highlighting = none resharper_cpp_clang_tidy_abseil_time_comparison_highlighting = none resharper_cpp_clang_tidy_abseil_time_subtraction_highlighting = none resharper_cpp_clang_tidy_abseil_upgrade_duration_conversions_highlighting = none resharper_cpp_clang_tidy_altera_kernel_name_restriction_highlighting = none resharper_cpp_clang_tidy_altera_single_work_item_barrier_highlighting = none resharper_cpp_clang_tidy_altera_struct_pack_align_highlighting = none resharper_cpp_clang_tidy_android_cloexec_accept4_highlighting = none resharper_cpp_clang_tidy_android_cloexec_accept_highlighting = none resharper_cpp_clang_tidy_android_cloexec_creat_highlighting = none resharper_cpp_clang_tidy_android_cloexec_dup_highlighting = none resharper_cpp_clang_tidy_android_cloexec_epoll_create1_highlighting = none resharper_cpp_clang_tidy_android_cloexec_epoll_create_highlighting = none resharper_cpp_clang_tidy_android_cloexec_fopen_highlighting = none resharper_cpp_clang_tidy_android_cloexec_inotify_init1_highlighting = none resharper_cpp_clang_tidy_android_cloexec_inotify_init_highlighting = none resharper_cpp_clang_tidy_android_cloexec_memfd_create_highlighting = none resharper_cpp_clang_tidy_android_cloexec_open_highlighting = none resharper_cpp_clang_tidy_android_cloexec_pipe2_highlighting = none resharper_cpp_clang_tidy_android_cloexec_pipe_highlighting = none resharper_cpp_clang_tidy_android_cloexec_socket_highlighting = none resharper_cpp_clang_tidy_android_comparison_in_temp_failure_retry_highlighting = none resharper_cpp_clang_tidy_boost_use_to_string_highlighting = suggestion resharper_cpp_clang_tidy_bugprone_argument_comment_highlighting = suggestion resharper_cpp_clang_tidy_bugprone_assert_side_effect_highlighting = warning resharper_cpp_clang_tidy_bugprone_bad_signal_to_kill_thread_highlighting = warning resharper_cpp_clang_tidy_bugprone_bool_pointer_implicit_conversion_highlighting = none resharper_cpp_clang_tidy_bugprone_branch_clone_highlighting = warning resharper_cpp_clang_tidy_bugprone_copy_constructor_init_highlighting = warning resharper_cpp_clang_tidy_bugprone_dangling_handle_highlighting = warning resharper_cpp_clang_tidy_bugprone_dynamic_static_initializers_highlighting = warning resharper_cpp_clang_tidy_bugprone_exception_escape_highlighting = warning resharper_cpp_clang_tidy_bugprone_fold_init_type_highlighting = warning resharper_cpp_clang_tidy_bugprone_forwarding_reference_overload_highlighting = warning resharper_cpp_clang_tidy_bugprone_forward_declaration_namespace_highlighting = warning resharper_cpp_clang_tidy_bugprone_inaccurate_erase_highlighting = warning resharper_cpp_clang_tidy_bugprone_incorrect_roundings_highlighting = warning resharper_cpp_clang_tidy_bugprone_infinite_loop_highlighting = warning resharper_cpp_clang_tidy_bugprone_integer_division_highlighting = warning resharper_cpp_clang_tidy_bugprone_lambda_function_name_highlighting = warning resharper_cpp_clang_tidy_bugprone_macro_parentheses_highlighting = warning resharper_cpp_clang_tidy_bugprone_macro_repeated_side_effects_highlighting = warning resharper_cpp_clang_tidy_bugprone_misplaced_operator_in_strlen_in_alloc_highlighting = warning resharper_cpp_clang_tidy_bugprone_misplaced_pointer_arithmetic_in_alloc_highlighting = warning resharper_cpp_clang_tidy_bugprone_misplaced_widening_cast_highlighting = warning resharper_cpp_clang_tidy_bugprone_move_forwarding_reference_highlighting = warning resharper_cpp_clang_tidy_bugprone_multiple_statement_macro_highlighting = warning resharper_cpp_clang_tidy_bugprone_narrowing_conversions_highlighting = warning resharper_cpp_clang_tidy_bugprone_not_null_terminated_result_highlighting = warning resharper_cpp_clang_tidy_bugprone_no_escape_highlighting = warning resharper_cpp_clang_tidy_bugprone_parent_virtual_call_highlighting = warning resharper_cpp_clang_tidy_bugprone_posix_return_highlighting = warning resharper_cpp_clang_tidy_bugprone_redundant_branch_condition_highlighting = warning resharper_cpp_clang_tidy_bugprone_reserved_identifier_highlighting = warning resharper_cpp_clang_tidy_bugprone_signal_handler_highlighting = warning resharper_cpp_clang_tidy_bugprone_signed_char_misuse_highlighting = warning resharper_cpp_clang_tidy_bugprone_sizeof_container_highlighting = warning resharper_cpp_clang_tidy_bugprone_sizeof_expression_highlighting = warning resharper_cpp_clang_tidy_bugprone_spuriously_wake_up_functions_highlighting = warning resharper_cpp_clang_tidy_bugprone_string_constructor_highlighting = warning resharper_cpp_clang_tidy_bugprone_string_integer_assignment_highlighting = warning resharper_cpp_clang_tidy_bugprone_string_literal_with_embedded_nul_highlighting = warning resharper_cpp_clang_tidy_bugprone_suspicious_enum_usage_highlighting = warning resharper_cpp_clang_tidy_bugprone_suspicious_include_highlighting = warning resharper_cpp_clang_tidy_bugprone_suspicious_memset_usage_highlighting = warning resharper_cpp_clang_tidy_bugprone_suspicious_missing_comma_highlighting = warning resharper_cpp_clang_tidy_bugprone_suspicious_semicolon_highlighting = warning resharper_cpp_clang_tidy_bugprone_suspicious_string_compare_highlighting = warning resharper_cpp_clang_tidy_bugprone_swapped_arguments_highlighting = warning resharper_cpp_clang_tidy_bugprone_terminating_continue_highlighting = warning resharper_cpp_clang_tidy_bugprone_throw_keyword_missing_highlighting = warning resharper_cpp_clang_tidy_bugprone_too_small_loop_variable_highlighting = warning resharper_cpp_clang_tidy_bugprone_undefined_memory_manipulation_highlighting = warning resharper_cpp_clang_tidy_bugprone_undelegated_constructor_highlighting = warning resharper_cpp_clang_tidy_bugprone_unhandled_self_assignment_highlighting = warning resharper_cpp_clang_tidy_bugprone_unused_raii_highlighting = warning resharper_cpp_clang_tidy_bugprone_unused_return_value_highlighting = warning resharper_cpp_clang_tidy_bugprone_use_after_move_highlighting = warning resharper_cpp_clang_tidy_bugprone_virtual_near_miss_highlighting = suggestion resharper_cpp_clang_tidy_cert_con36_c_highlighting = none resharper_cpp_clang_tidy_cert_con54_cpp_highlighting = none resharper_cpp_clang_tidy_cert_dcl03_c_highlighting = none resharper_cpp_clang_tidy_cert_dcl16_c_highlighting = none resharper_cpp_clang_tidy_cert_dcl21_cpp_highlighting = none resharper_cpp_clang_tidy_cert_dcl37_c_highlighting = none resharper_cpp_clang_tidy_cert_dcl50_cpp_highlighting = none resharper_cpp_clang_tidy_cert_dcl51_cpp_highlighting = none resharper_cpp_clang_tidy_cert_dcl54_cpp_highlighting = none resharper_cpp_clang_tidy_cert_dcl58_cpp_highlighting = warning resharper_cpp_clang_tidy_cert_dcl59_cpp_highlighting = none resharper_cpp_clang_tidy_cert_env33_c_highlighting = none resharper_cpp_clang_tidy_cert_err09_cpp_highlighting = none resharper_cpp_clang_tidy_cert_err34_c_highlighting = suggestion resharper_cpp_clang_tidy_cert_err52_cpp_highlighting = none resharper_cpp_clang_tidy_cert_err58_cpp_highlighting = none resharper_cpp_clang_tidy_cert_err60_cpp_highlighting = warning resharper_cpp_clang_tidy_cert_err61_cpp_highlighting = none resharper_cpp_clang_tidy_cert_fio38_c_highlighting = none resharper_cpp_clang_tidy_cert_flp30_c_highlighting = warning resharper_cpp_clang_tidy_cert_mem57_cpp_highlighting = warning resharper_cpp_clang_tidy_cert_msc30_c_highlighting = none resharper_cpp_clang_tidy_cert_msc32_c_highlighting = none resharper_cpp_clang_tidy_cert_msc50_cpp_highlighting = none resharper_cpp_clang_tidy_cert_msc51_cpp_highlighting = warning resharper_cpp_clang_tidy_cert_oop11_cpp_highlighting = none resharper_cpp_clang_tidy_cert_oop54_cpp_highlighting = none resharper_cpp_clang_tidy_cert_oop57_cpp_highlighting = warning resharper_cpp_clang_tidy_cert_oop58_cpp_highlighting = warning resharper_cpp_clang_tidy_cert_pos44_c_highlighting = none resharper_cpp_clang_tidy_cert_sig30_c_highlighting = none resharper_cpp_clang_tidy_cert_str34_c_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_api_modeling_google_g_test_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_api_modeling_llvm_cast_value_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_api_modeling_llvm_return_value_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_api_modeling_std_c_library_functions_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_api_modeling_trust_nonnull_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_core_builtin_builtin_functions_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_core_builtin_no_return_functions_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_core_call_and_message_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_core_call_and_message_modeling_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_core_divide_zero_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_core_dynamic_type_propagation_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_core_nonnil_string_constants_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_core_non_null_param_checker_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_core_null_dereference_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_core_stack_address_escape_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_core_stack_addr_escape_base_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_core_undefined_binary_operator_result_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_core_uninitialized_array_subscript_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_core_uninitialized_assign_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_core_uninitialized_branch_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_core_uninitialized_captured_block_variable_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_core_uninitialized_undef_return_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_core_vla_size_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_cplusplus_inner_pointer_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_cplusplus_move_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_cplusplus_new_delete_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_cplusplus_new_delete_leaks_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_cplusplus_placement_new_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_cplusplus_pure_virtual_call_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_cplusplus_self_assignment_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_cplusplus_smart_ptr_modeling_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_cplusplus_virtual_call_modeling_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_deadcode_dead_stores_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_fuchsia_handle_checker_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_nullability_nullability_base_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_nullability_nullable_dereferenced_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_nullability_nullable_passed_to_nonnull_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_nullability_nullable_returned_from_nonnull_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_nullability_null_passed_to_nonnull_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_nullability_null_returned_from_nonnull_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_optin_cplusplus_uninitialized_object_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_optin_cplusplus_virtual_call_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_optin_mpi_mpi_checker_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_optin_osx_cocoa_localizability_empty_localization_context_checker_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_optin_osx_cocoa_localizability_non_localized_string_checker_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_optin_osx_os_object_c_style_cast_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_optin_performance_gcd_antipattern_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_optin_performance_padding_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_optin_portability_unix_api_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_api_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_cocoa_at_sync_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_cocoa_autorelease_write_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_cocoa_class_release_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_cocoa_dealloc_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_cocoa_incompatible_method_types_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_cocoa_loops_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_cocoa_missing_super_call_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_cocoa_nil_arg_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_cocoa_non_nil_return_value_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_cocoa_ns_autorelease_pool_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_cocoa_ns_error_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_cocoa_obj_c_generics_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_cocoa_retain_count_base_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_cocoa_retain_count_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_cocoa_run_loop_autorelease_leak_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_cocoa_self_init_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_cocoa_super_dealloc_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_cocoa_unused_ivars_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_cocoa_variadic_method_types_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_core_foundation_cf_error_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_core_foundation_cf_number_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_core_foundation_cf_retain_release_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_core_foundation_containers_out_of_bounds_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_core_foundation_containers_pointer_sized_values_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_mig_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_ns_or_cf_error_deref_checker_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_number_object_conversion_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_obj_c_property_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_os_object_retain_count_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_osx_sec_keychain_api_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_security_float_loop_counter_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_security_insecure_api_bcmp_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_security_insecure_api_bcopy_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_security_insecure_api_bzero_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_security_insecure_api_decode_value_of_obj_c_type_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_security_insecure_api_deprecated_or_unsafe_buffer_handling_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_security_insecure_api_getpw_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_security_insecure_api_gets_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_security_insecure_api_mkstemp_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_security_insecure_api_mktemp_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_security_insecure_api_rand_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_security_insecure_api_security_syntax_checker_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_security_insecure_api_strcpy_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_security_insecure_api_unchecked_return_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_security_insecure_api_vfork_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_unix_api_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_unix_cstring_bad_size_arg_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_unix_cstring_c_string_modeling_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_unix_cstring_null_arg_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_unix_dynamic_memory_modeling_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_unix_malloc_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_unix_malloc_sizeof_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_unix_mismatched_deallocator_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_unix_vfork_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_valist_copy_to_self_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_valist_uninitialized_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_valist_unterminated_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_valist_valist_base_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_webkit_no_uncounted_member_checker_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_webkit_ref_cntbl_base_virtual_dtor_highlighting = none resharper_cpp_clang_tidy_clang_analyzer_webkit_uncounted_lambda_captures_checker_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_absolute_value_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_abstract_final_class_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_abstract_vbase_init_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_address_of_packed_member_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_address_of_temporary_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_alloca_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_alloca_with_align_alignof_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_ambiguous_delete_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_ambiguous_ellipsis_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_ambiguous_macro_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_ambiguous_member_template_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_ambiguous_reversed_operator_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_analyzer_incompatible_plugin_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_anonymous_pack_parens_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_anon_enum_enum_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_arc_bridge_casts_disallowed_in_nonarc_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_arc_maybe_repeated_use_of_weak_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_arc_non_pod_memaccess_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_arc_perform_selector_leaks_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_arc_repeated_use_of_weak_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_arc_retain_cycles_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_arc_unsafe_retained_assign_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_argument_outside_range_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_array_bounds_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_array_bounds_pointer_arithmetic_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_asm_operand_widths_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_assign_enum_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_assume_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_atimport_in_framework_header_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_atomic_alignment_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_atomic_implicit_seq_cst_highlighting = suggestion resharper_cpp_clang_tidy_clang_diagnostic_atomic_memory_ordering_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_atomic_property_with_user_defined_accessor_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_attribute_packed_for_bitfield_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_at_protocol_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_auto_disable_vptr_sanitizer_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_auto_import_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_auto_storage_class_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_auto_var_id_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_availability_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_avr_rtlib_linking_quirks_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_backslash_newline_escape_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_bad_function_cast_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_binding_in_condition_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_bind_to_temporary_copy_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_bitfield_constant_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_bitfield_enum_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_bitfield_width_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_bitwise_conditional_parentheses_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_bitwise_op_parentheses_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_block_capture_autoreleasing_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_bool_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_bool_operation_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_braced_scalar_init_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_bridge_cast_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_builtin_assume_aligned_alignment_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_builtin_macro_redefined_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_builtin_memcpy_chk_size_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_builtin_requires_header_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_c11_extensions_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_c2x_extensions_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_c99_compat_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_c99_designator_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_c99_extensions_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_called_once_parameter_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_call_to_pure_virtual_from_ctor_dtor_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_cast_align_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_cast_calling_convention_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_cast_of_sel_type_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_cast_qual_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_cast_qual_unrelated_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_cf_string_literal_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_char_subscripts_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_clang_cl_pch_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_class_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_class_varargs_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_cmse_union_leak_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_comma_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_comment_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_compare_distinct_pointer_types_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_completion_handler_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_complex_component_init_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_compound_token_split_by_macro_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_compound_token_split_by_space_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_compound_token_split_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_concepts_ts_compat_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_conditional_type_mismatch_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_conditional_uninitialized_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_config_macros_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_constant_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_constant_evaluated_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_constant_logical_operand_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_constexpr_not_const_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_consumed_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_coroutine_missing_unhandled_exception_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_covered_switch_default_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_cpp11_compat_deprecated_writable_strings_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp11_compat_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp11_compat_reserved_user_defined_literal_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp11_extensions_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_cpp11_extra_semi_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_cpp11_inline_namespace_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_cpp11_long_long_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_cpp11_narrowing_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_cpp14_binary_literal_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_cpp14_compat_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp14_extensions_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_cpp17_compat_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp17_compat_mangling_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp17_extensions_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_cpp20_compat_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp20_designator_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_cpp20_extensions_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_cpp2a_extensions_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_cpp98_compat_bind_to_temporary_copy_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp98_compat_extra_semi_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp98_compat_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp98_compat_local_type_template_args_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp98_compat_pedantic_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp98_compat_unnamed_type_template_args_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp98_cpp11_compat_binary_literal_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp98_cpp11_compat_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp98_cpp11_compat_pedantic_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp98_cpp11_cpp14_compat_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp98_cpp11_cpp14_compat_pedantic_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp98_cpp11_cpp14_cpp17_compat_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp98_cpp11_cpp14_cpp17_compat_pedantic_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cpp_compat_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cstring_format_directive_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_ctad_maybe_unsupported_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_ctu_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_cuda_compat_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_custom_atomic_properties_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_dangling_else_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_dangling_field_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_dangling_gsl_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_dangling_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_dangling_initializer_list_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_darwin_sdk_settings_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_date_time_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_dealloc_in_category_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_debug_compression_unavailable_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_declaration_after_statement_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_defaulted_function_deleted_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_delegating_ctor_cycles_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_delete_abstract_non_virtual_dtor_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_delete_incomplete_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_delete_non_abstract_non_virtual_dtor_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_delete_non_virtual_dtor_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_anon_enum_enum_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_array_compare_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_attributes_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_comma_subscript_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_copy_dtor_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_copy_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_declarations_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_dynamic_exception_spec_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_enum_compare_conditional_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_enum_compare_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_enum_enum_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_enum_float_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_implementations_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_increment_bool_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_objc_isa_usage_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_objc_pointer_introspection_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_objc_pointer_introspection_perform_selector_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_register_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_this_capture_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_deprecated_volatile_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_direct_ivar_access_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_disabled_macro_expansion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_distributed_object_modifiers_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_division_by_zero_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_dllexport_explicit_instantiation_decl_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_dllimport_static_field_def_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_dll_attribute_on_redeclaration_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_documentation_deprecated_sync_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_documentation_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_documentation_html_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_documentation_pedantic_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_documentation_unknown_command_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_dollar_in_identifier_extension_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_double_promotion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_dtor_name_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_dtor_typedef_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_duplicate_decl_specifier_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_duplicate_enum_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_duplicate_method_arg_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_duplicate_method_match_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_duplicate_protocol_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_dynamic_class_memaccess_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_dynamic_exception_spec_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_embedded_directive_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_empty_body_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_empty_decomposition_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_empty_init_stmt_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_empty_translation_unit_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_encode_type_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_enum_compare_conditional_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_enum_compare_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_enum_compare_switch_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_enum_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_enum_enum_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_enum_float_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_enum_too_large_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_error_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_exceptions_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_excess_initializers_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_exit_time_destructors_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_expansion_to_defined_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_explicit_initialize_call_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_explicit_ownership_type_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_export_unnamed_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_export_using_directive_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_extern_c_compat_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_extern_initializer_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_extra_qualification_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_extra_semi_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_extra_semi_stmt_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_extra_tokens_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_fallback_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_final_dtor_non_final_class_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_fixed_enum_extension_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_fixed_point_overflow_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_flag_enum_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_flexible_array_extensions_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_float_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_float_equal_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_float_overflow_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_float_zero_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_format_extra_args_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_format_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_format_insufficient_args_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_format_invalid_specifier_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_format_nonliteral_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_format_non_iso_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_format_pedantic_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_format_security_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_format_type_confusion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_format_zero_length_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_fortify_source_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_for_loop_analysis_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_four_char_constants_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_framework_include_private_from_public_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_frame_address_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_frame_larger_than_eq_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_free_nonheap_object_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_function_def_in_objc_container_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_function_multiversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gcc_compat_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_global_constructors_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_global_isel_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_alignof_expression_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_anonymous_struct_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_gnu_array_member_paren_init_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_auto_type_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_binary_literal_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_case_range_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_complex_integer_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_compound_literal_initializer_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_conditional_omitted_operand_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_designator_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_empty_initializer_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_empty_struct_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_flexible_array_initializer_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_flexible_array_union_member_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_folding_constant_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_imaginary_constant_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_include_next_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_inline_cpp_without_extern_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_label_as_value_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_redeclared_enum_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_statement_expression_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_static_float_init_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_string_literal_operator_template_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_union_cast_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_variable_sized_type_not_at_end_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_gnu_zero_variadic_macro_arguments_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_header_guard_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_header_hygiene_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_hip_only_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_idiomatic_parentheses_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_ignored_attributes_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_ignored_optimization_argument_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_ignored_pragmas_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_ignored_pragma_intrinsic_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_ignored_pragma_optimize_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_ignored_qualifiers_highlighting = suggestion resharper_cpp_clang_tidy_clang_diagnostic_implicitly_unsigned_literal_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_implicit_atomic_properties_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_implicit_const_int_float_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_implicit_conversion_floating_point_to_bool_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_implicit_exception_spec_mismatch_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_implicit_fallthrough_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_implicit_fallthrough_per_function_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_implicit_fixed_point_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_implicit_float_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_implicit_function_declaration_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_implicit_int_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_implicit_int_float_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_implicit_int_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_implicit_retain_self_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_import_preprocessor_directive_pedantic_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_inaccessible_base_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_include_next_absolute_path_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_include_next_outside_header_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_incompatible_exception_spec_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_incompatible_function_pointer_types_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_incompatible_library_redeclaration_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_incompatible_ms_struct_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_incompatible_pointer_types_discards_qualifiers_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_incompatible_pointer_types_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_incompatible_property_type_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_incompatible_sysroot_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_incomplete_framework_module_declaration_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_incomplete_implementation_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_incomplete_module_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_incomplete_setjmp_declaration_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_incomplete_umbrella_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_inconsistent_dllimport_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_inconsistent_missing_destructor_override_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_inconsistent_missing_override_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_increment_bool_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_independent_class_attribute_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_infinite_recursion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_initializer_overrides_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_injected_class_name_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_inline_asm_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_inline_namespace_reopened_noninline_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_inline_new_delete_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_instantiation_after_specialization_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_integer_overflow_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_int_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_int_in_bool_context_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_int_to_pointer_cast_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_int_to_void_pointer_cast_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_invalid_constexpr_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_invalid_iboutlet_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_invalid_initializer_from_system_header_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_invalid_ios_deployment_target_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_invalid_noreturn_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_invalid_no_builtin_names_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_invalid_offsetof_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_invalid_or_nonexistent_directory_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_invalid_partial_specialization_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_invalid_pp_token_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_invalid_source_encoding_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_invalid_token_paste_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_jump_seh_finally_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_keyword_compat_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_keyword_macro_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_knr_promoted_parameter_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_language_extension_token_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_large_by_value_copy_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_literal_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_literal_range_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_local_type_template_args_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_logical_not_parentheses_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_logical_op_parentheses_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_long_long_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_macro_redefined_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_main_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_main_return_type_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_malformed_warning_check_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_many_braces_around_scalar_init_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_max_tokens_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_max_unsigned_zero_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_memset_transposed_args_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_memsize_comparison_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_method_signatures_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_anon_tag_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_cast_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_charize_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_comment_paste_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_const_init_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_cpp_macro_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_default_arg_redefinition_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_drectve_section_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_end_of_file_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_enum_forward_reference_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_enum_value_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_exception_spec_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_exists_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_explicit_constructor_call_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_extra_qualification_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_fixed_enum_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_flexible_array_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_goto_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_inaccessible_base_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_include_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_mutable_reference_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_pure_definition_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_redeclare_static_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_sealed_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_template_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_template_shadow_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_union_member_reference_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_unqualified_friend_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_using_decl_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_microsoft_void_pseudo_dtor_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_misleading_indentation_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_mismatched_new_delete_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_mismatched_parameter_types_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_mismatched_return_types_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_mismatched_tags_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_missing_braces_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_missing_constinit_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_missing_declarations_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_missing_exception_spec_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_missing_field_initializers_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_missing_method_return_type_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_missing_noescape_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_missing_noreturn_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_missing_prototypes_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_missing_prototype_for_cc_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_missing_selector_name_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_missing_sysroot_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_missing_variable_declarations_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_misspelled_assumption_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_modules_ambiguous_internal_linkage_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_modules_import_nested_redundant_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_module_conflict_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_module_file_config_mismatch_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_module_file_extension_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_module_import_in_extern_c_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_msvc_not_found_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_multichar_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_multiple_move_vbase_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_nested_anon_types_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_newline_eof_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_new_returns_null_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_noderef_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_nonnull_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_nonportable_include_path_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_nonportable_system_include_path_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_nonportable_vector_initialization_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_nontrivial_memaccess_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_non_c_typedef_for_linkage_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_non_literal_null_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_non_modular_include_in_framework_module_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_non_modular_include_in_module_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_non_pod_varargs_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_non_power_of_two_alignment_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_non_virtual_dtor_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_nsconsumed_mismatch_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_nsreturns_mismatch_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_ns_object_attribute_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_nullability_completeness_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_nullability_completeness_on_arrays_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_nullability_declspec_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_nullability_extension_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_nullability_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_nullability_inferred_on_nested_type_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_nullable_to_nonnull_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_null_arithmetic_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_null_character_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_null_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_null_dereference_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_null_pointer_arithmetic_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_odr_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_old_style_cast_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_opencl_unsupported_rgba_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_openmp_clauses_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_openmp_loop_form_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_openmp_mapping_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_openmp_target_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_option_ignored_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_ordered_compare_function_pointers_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_out_of_line_declaration_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_out_of_scope_function_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_overlength_strings_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_overloaded_shift_op_parentheses_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_overloaded_virtual_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_override_init_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_override_module_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_overriding_method_mismatch_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_overriding_t_option_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_over_aligned_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_packed_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_padded_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_parentheses_equality_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_parentheses_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pass_failed_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pch_date_time_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pedantic_core_features_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pedantic_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pessimizing_move_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pointer_arith_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pointer_bool_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pointer_compare_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pointer_integer_compare_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pointer_sign_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pointer_to_enum_cast_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pointer_to_int_cast_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pointer_type_mismatch_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_poison_system_directories_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_potentially_evaluated_expression_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pragmas_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pragma_clang_attribute_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pragma_messages_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pragma_once_outside_header_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pragma_pack_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pragma_pack_suspicious_include_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_pragma_system_header_outside_header_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_predefined_identifier_outside_function_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_private_extern_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_private_header_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_private_module_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_profile_instr_missing_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_profile_instr_out_of_date_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_profile_instr_unprofiled_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_property_access_dot_syntax_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_property_attribute_mismatch_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_protocol_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_protocol_property_synthesis_ambiguity_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_psabi_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_qualified_void_return_type_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_quoted_include_in_framework_header_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_range_loop_analysis_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_range_loop_bind_reference_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_range_loop_construct_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_readonly_iboutlet_property_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_receiver_expr_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_receiver_forward_class_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_redeclared_class_member_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_redundant_move_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_redundant_parens_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_register_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_reinterpret_base_class_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_reorder_ctor_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_reorder_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_reorder_init_list_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_requires_expression_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_requires_super_attribute_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_reserved_id_macro_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_reserved_user_defined_literal_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_retained_language_linkage_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_return_stack_address_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_return_std_move_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_return_std_move_in_cpp11_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_return_type_c_linkage_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_return_type_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_rewrite_not_bool_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_section_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_selector_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_selector_type_mismatch_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_self_assign_field_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_self_assign_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_self_assign_overloaded_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_self_move_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_semicolon_before_method_body_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_sentinel_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_serialized_diagnostics_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_shadow_field_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_shadow_field_in_constructor_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_shadow_field_in_constructor_modified_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_shadow_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_shadow_ivar_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_shadow_uncaptured_local_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_shift_count_negative_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_shift_count_overflow_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_shift_negative_value_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_shift_op_parentheses_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_shift_overflow_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_shift_sign_overflow_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_shorten64_to32_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_signed_enum_bitfield_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_signed_unsigned_wchar_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_sign_compare_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_sign_conversion_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_sizeof_array_argument_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_sizeof_array_decay_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_sizeof_array_div_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_sizeof_pointer_div_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_sizeof_pointer_memaccess_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_slash_u_filename_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_slh_asm_goto_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_sometimes_uninitialized_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_source_uses_openmp_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_spir_compat_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_static_float_init_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_static_inline_explicit_instantiation_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_static_in_inline_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_static_local_in_inline_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_static_self_init_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_stdlibcxx_not_found_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_strict_prototypes_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_strict_selector_match_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_string_compare_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_string_concatenation_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_string_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_string_plus_char_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_string_plus_int_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_strlcpy_strlcat_size_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_strncat_size_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_suggest_destructor_override_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_suggest_override_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_super_class_method_mismatch_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_suspicious_bzero_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_switch_bool_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_switch_enum_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_switch_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_sync_fetch_and_nand_semantics_changed_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_tautological_bitwise_compare_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_tautological_compare_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_tautological_constant_compare_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_tautological_constant_in_range_compare_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_tautological_constant_out_of_range_compare_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_tautological_objc_bool_compare_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_tautological_overlap_compare_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_tautological_pointer_compare_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_tautological_type_limit_compare_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_tautological_undefined_compare_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_tautological_unsigned_enum_zero_compare_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_tautological_unsigned_zero_compare_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_tautological_value_range_compare_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_tentative_definition_incomplete_type_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_thread_safety_analysis_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_thread_safety_attributes_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_thread_safety_beta_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_thread_safety_negative_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_thread_safety_precise_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_thread_safety_reference_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_thread_safety_verbose_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_trigraphs_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_typedef_redefinition_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_typename_missing_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_type_safety_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unable_to_open_stats_file_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unavailable_declarations_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_undeclared_selector_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_undefined_bool_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_undefined_func_template_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_undefined_inline_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_undefined_internal_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_undefined_internal_type_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_undefined_reinterpret_cast_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_undefined_var_template_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_undef_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_undef_prefix_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_underaligned_exception_object_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unevaluated_expression_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unguarded_availability_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unguarded_availability_new_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unicode_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unicode_homoglyph_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unicode_whitespace_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unicode_zero_width_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_uninitialized_const_reference_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_uninitialized_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unknown_argument_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unknown_attributes_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_unknown_cuda_version_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_unknown_escape_sequence_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unknown_pragmas_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_unknown_sanitizers_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unknown_warning_option_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unnamed_type_template_args_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unneeded_internal_declaration_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unneeded_member_function_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unreachable_code_break_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unreachable_code_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unreachable_code_loop_increment_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unreachable_code_return_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unsequenced_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unsupported_abs_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unsupported_availability_guard_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unsupported_cb_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unsupported_dll_base_class_template_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unsupported_friend_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unsupported_gpopt_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unsupported_nan_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unsupported_target_opt_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_unsupported_visibility_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unusable_partial_specialization_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unused_comparison_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unused_const_variable_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unused_exception_parameter_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unused_function_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unused_getter_return_value_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unused_label_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unused_lambda_capture_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_unused_local_typedef_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unused_macros_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unused_member_function_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unused_parameter_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_unused_private_field_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unused_property_ivar_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unused_result_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unused_template_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unused_value_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_unused_variable_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_unused_volatile_lvalue_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_used_but_marked_unused_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_user_defined_literals_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_user_defined_warnings_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_varargs_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_variadic_macros_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_vector_conversion_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_vec_elem_size_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_vexing_parse_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_visibility_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_vla_extension_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_vla_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_void_pointer_to_enum_cast_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_void_pointer_to_int_cast_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_void_ptr_dereference_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_warnings_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_wasm_exception_spec_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_weak_template_vtables_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_weak_vtables_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_writable_strings_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_xor_used_as_pow_highlighting = warning resharper_cpp_clang_tidy_clang_diagnostic_zero_as_null_pointer_constant_highlighting = none resharper_cpp_clang_tidy_clang_diagnostic_zero_length_array_highlighting = warning resharper_cpp_clang_tidy_concurrency_mt_unsafe_highlighting = warning resharper_cpp_clang_tidy_cppcoreguidelines_avoid_c_arrays_highlighting = none resharper_cpp_clang_tidy_cppcoreguidelines_avoid_goto_highlighting = warning resharper_cpp_clang_tidy_cppcoreguidelines_avoid_magic_numbers_highlighting = none resharper_cpp_clang_tidy_cppcoreguidelines_avoid_non_const_global_variables_highlighting = none resharper_cpp_clang_tidy_cppcoreguidelines_c_copy_assignment_signature_highlighting = none resharper_cpp_clang_tidy_cppcoreguidelines_explicit_virtual_functions_highlighting = none resharper_cpp_clang_tidy_cppcoreguidelines_init_variables_highlighting = none resharper_cpp_clang_tidy_cppcoreguidelines_interfaces_global_init_highlighting = warning resharper_cpp_clang_tidy_cppcoreguidelines_macro_usage_highlighting = warning resharper_cpp_clang_tidy_cppcoreguidelines_narrowing_conversions_highlighting = warning resharper_cpp_clang_tidy_cppcoreguidelines_non_private_member_variables_in_classes_highlighting = none resharper_cpp_clang_tidy_cppcoreguidelines_no_malloc_highlighting = none resharper_cpp_clang_tidy_cppcoreguidelines_owning_memory_highlighting = none resharper_cpp_clang_tidy_cppcoreguidelines_pro_bounds_array_to_pointer_decay_highlighting = none resharper_cpp_clang_tidy_cppcoreguidelines_pro_bounds_constant_array_index_highlighting = none resharper_cpp_clang_tidy_cppcoreguidelines_pro_bounds_pointer_arithmetic_highlighting = none resharper_cpp_clang_tidy_cppcoreguidelines_pro_type_const_cast_highlighting = none resharper_cpp_clang_tidy_cppcoreguidelines_pro_type_cstyle_cast_highlighting = none resharper_cpp_clang_tidy_cppcoreguidelines_pro_type_member_init_highlighting = warning resharper_cpp_clang_tidy_cppcoreguidelines_pro_type_reinterpret_cast_highlighting = none resharper_cpp_clang_tidy_cppcoreguidelines_pro_type_static_cast_downcast_highlighting = suggestion resharper_cpp_clang_tidy_cppcoreguidelines_pro_type_union_access_highlighting = none resharper_cpp_clang_tidy_cppcoreguidelines_pro_type_vararg_highlighting = none resharper_cpp_clang_tidy_cppcoreguidelines_slicing_highlighting = none resharper_cpp_clang_tidy_cppcoreguidelines_special_member_functions_highlighting = suggestion resharper_cpp_clang_tidy_darwin_avoid_spinlock_highlighting = none resharper_cpp_clang_tidy_darwin_dispatch_once_nonstatic_highlighting = none resharper_cpp_clang_tidy_fuchsia_default_arguments_calls_highlighting = none resharper_cpp_clang_tidy_fuchsia_default_arguments_declarations_highlighting = none resharper_cpp_clang_tidy_fuchsia_header_anon_namespaces_highlighting = none resharper_cpp_clang_tidy_fuchsia_multiple_inheritance_highlighting = none resharper_cpp_clang_tidy_fuchsia_overloaded_operator_highlighting = none resharper_cpp_clang_tidy_fuchsia_statically_constructed_objects_highlighting = none resharper_cpp_clang_tidy_fuchsia_trailing_return_highlighting = none resharper_cpp_clang_tidy_fuchsia_virtual_inheritance_highlighting = none resharper_cpp_clang_tidy_google_build_explicit_make_pair_highlighting = none resharper_cpp_clang_tidy_google_build_namespaces_highlighting = none resharper_cpp_clang_tidy_google_build_using_namespace_highlighting = none resharper_cpp_clang_tidy_google_default_arguments_highlighting = none resharper_cpp_clang_tidy_google_explicit_constructor_highlighting = none resharper_cpp_clang_tidy_google_global_names_in_headers_highlighting = none resharper_cpp_clang_tidy_google_objc_avoid_nsobject_new_highlighting = none resharper_cpp_clang_tidy_google_objc_avoid_throwing_exception_highlighting = none resharper_cpp_clang_tidy_google_objc_function_naming_highlighting = none resharper_cpp_clang_tidy_google_objc_global_variable_declaration_highlighting = none resharper_cpp_clang_tidy_google_readability_avoid_underscore_in_googletest_name_highlighting = none resharper_cpp_clang_tidy_google_readability_braces_around_statements_highlighting = none resharper_cpp_clang_tidy_google_readability_casting_highlighting = none resharper_cpp_clang_tidy_google_readability_function_size_highlighting = none resharper_cpp_clang_tidy_google_readability_namespace_comments_highlighting = none resharper_cpp_clang_tidy_google_readability_todo_highlighting = none resharper_cpp_clang_tidy_google_runtime_int_highlighting = none resharper_cpp_clang_tidy_google_runtime_operator_highlighting = warning resharper_cpp_clang_tidy_google_upgrade_googletest_case_highlighting = suggestion resharper_cpp_clang_tidy_hicpp_avoid_c_arrays_highlighting = none resharper_cpp_clang_tidy_hicpp_avoid_goto_highlighting = warning resharper_cpp_clang_tidy_hicpp_braces_around_statements_highlighting = none resharper_cpp_clang_tidy_hicpp_deprecated_headers_highlighting = none resharper_cpp_clang_tidy_hicpp_exception_baseclass_highlighting = suggestion resharper_cpp_clang_tidy_hicpp_explicit_conversions_highlighting = none resharper_cpp_clang_tidy_hicpp_function_size_highlighting = none resharper_cpp_clang_tidy_hicpp_invalid_access_moved_highlighting = none resharper_cpp_clang_tidy_hicpp_member_init_highlighting = none resharper_cpp_clang_tidy_hicpp_move_const_arg_highlighting = none resharper_cpp_clang_tidy_hicpp_multiway_paths_covered_highlighting = warning resharper_cpp_clang_tidy_hicpp_named_parameter_highlighting = none resharper_cpp_clang_tidy_hicpp_new_delete_operators_highlighting = none resharper_cpp_clang_tidy_hicpp_noexcept_move_highlighting = none resharper_cpp_clang_tidy_hicpp_no_array_decay_highlighting = none resharper_cpp_clang_tidy_hicpp_no_assembler_highlighting = none resharper_cpp_clang_tidy_hicpp_no_malloc_highlighting = none resharper_cpp_clang_tidy_hicpp_signed_bitwise_highlighting = none resharper_cpp_clang_tidy_hicpp_special_member_functions_highlighting = none resharper_cpp_clang_tidy_hicpp_static_assert_highlighting = none resharper_cpp_clang_tidy_hicpp_undelegated_constructor_highlighting = none resharper_cpp_clang_tidy_hicpp_uppercase_literal_suffix_highlighting = none resharper_cpp_clang_tidy_hicpp_use_auto_highlighting = none resharper_cpp_clang_tidy_hicpp_use_emplace_highlighting = none resharper_cpp_clang_tidy_hicpp_use_equals_default_highlighting = none resharper_cpp_clang_tidy_hicpp_use_equals_delete_highlighting = none resharper_cpp_clang_tidy_hicpp_use_noexcept_highlighting = none resharper_cpp_clang_tidy_hicpp_use_nullptr_highlighting = none resharper_cpp_clang_tidy_hicpp_use_override_highlighting = none resharper_cpp_clang_tidy_hicpp_vararg_highlighting = none resharper_cpp_clang_tidy_highlighting_highlighting = suggestion resharper_cpp_clang_tidy_linuxkernel_must_check_errs_highlighting = warning resharper_cpp_clang_tidy_llvmlibc_callee_namespace_highlighting = none resharper_cpp_clang_tidy_llvmlibc_implementation_in_namespace_highlighting = none resharper_cpp_clang_tidy_llvmlibc_restrict_system_libc_headers_highlighting = none resharper_cpp_clang_tidy_llvm_else_after_return_highlighting = none resharper_cpp_clang_tidy_llvm_header_guard_highlighting = none resharper_cpp_clang_tidy_llvm_include_order_highlighting = none resharper_cpp_clang_tidy_llvm_namespace_comment_highlighting = none resharper_cpp_clang_tidy_llvm_prefer_isa_or_dyn_cast_in_conditionals_highlighting = none resharper_cpp_clang_tidy_llvm_prefer_register_over_unsigned_highlighting = suggestion resharper_cpp_clang_tidy_llvm_qualified_auto_highlighting = none resharper_cpp_clang_tidy_llvm_twine_local_highlighting = none resharper_cpp_clang_tidy_misc_definitions_in_headers_highlighting = none resharper_cpp_clang_tidy_misc_misplaced_const_highlighting = warning resharper_cpp_clang_tidy_misc_new_delete_overloads_highlighting = warning resharper_cpp_clang_tidy_misc_non_copyable_objects_highlighting = warning resharper_cpp_clang_tidy_misc_non_private_member_variables_in_classes_highlighting = none resharper_cpp_clang_tidy_misc_no_recursion_highlighting = none resharper_cpp_clang_tidy_misc_redundant_expression_highlighting = warning resharper_cpp_clang_tidy_misc_static_assert_highlighting = suggestion resharper_cpp_clang_tidy_misc_throw_by_value_catch_by_reference_highlighting = warning resharper_cpp_clang_tidy_misc_unconventional_assign_operator_highlighting = warning resharper_cpp_clang_tidy_misc_uniqueptr_reset_release_highlighting = suggestion resharper_cpp_clang_tidy_misc_unused_alias_decls_highlighting = suggestion resharper_cpp_clang_tidy_misc_unused_parameters_highlighting = none resharper_cpp_clang_tidy_misc_unused_using_decls_highlighting = suggestion resharper_cpp_clang_tidy_modernize_avoid_bind_highlighting = suggestion resharper_cpp_clang_tidy_modernize_avoid_c_arrays_highlighting = none resharper_cpp_clang_tidy_modernize_concat_nested_namespaces_highlighting = none resharper_cpp_clang_tidy_modernize_deprecated_headers_highlighting = suggestion resharper_cpp_clang_tidy_modernize_deprecated_ios_base_aliases_highlighting = warning resharper_cpp_clang_tidy_modernize_loop_convert_highlighting = suggestion resharper_cpp_clang_tidy_modernize_make_shared_highlighting = none resharper_cpp_clang_tidy_modernize_make_unique_highlighting = none resharper_cpp_clang_tidy_modernize_pass_by_value_highlighting = suggestion resharper_cpp_clang_tidy_modernize_raw_string_literal_highlighting = suggestion resharper_cpp_clang_tidy_modernize_redundant_void_arg_highlighting = none resharper_cpp_clang_tidy_modernize_replace_auto_ptr_highlighting = suggestion resharper_cpp_clang_tidy_modernize_replace_disallow_copy_and_assign_macro_highlighting = suggestion resharper_cpp_clang_tidy_modernize_replace_random_shuffle_highlighting = suggestion resharper_cpp_clang_tidy_modernize_return_braced_init_list_highlighting = suggestion resharper_cpp_clang_tidy_modernize_shrink_to_fit_highlighting = suggestion resharper_cpp_clang_tidy_modernize_unary_static_assert_highlighting = suggestion resharper_cpp_clang_tidy_modernize_use_auto_highlighting = suggestion resharper_cpp_clang_tidy_modernize_use_bool_literals_highlighting = suggestion resharper_cpp_clang_tidy_modernize_use_default_member_init_highlighting = none resharper_cpp_clang_tidy_modernize_use_emplace_highlighting = suggestion resharper_cpp_clang_tidy_modernize_use_equals_default_highlighting = suggestion resharper_cpp_clang_tidy_modernize_use_equals_delete_highlighting = suggestion resharper_cpp_clang_tidy_modernize_use_nodiscard_highlighting = suggestion resharper_cpp_clang_tidy_modernize_use_noexcept_highlighting = suggestion resharper_cpp_clang_tidy_modernize_use_nullptr_highlighting = none resharper_cpp_clang_tidy_modernize_use_override_highlighting = none resharper_cpp_clang_tidy_modernize_use_trailing_return_type_highlighting = none resharper_cpp_clang_tidy_modernize_use_transparent_functors_highlighting = suggestion resharper_cpp_clang_tidy_modernize_use_uncaught_exceptions_highlighting = warning resharper_cpp_clang_tidy_modernize_use_using_highlighting = none resharper_cpp_clang_tidy_mpi_buffer_deref_highlighting = warning resharper_cpp_clang_tidy_mpi_type_mismatch_highlighting = warning resharper_cpp_clang_tidy_objc_avoid_nserror_init_highlighting = warning resharper_cpp_clang_tidy_objc_dealloc_in_category_highlighting = warning resharper_cpp_clang_tidy_objc_forbidden_subclassing_highlighting = warning resharper_cpp_clang_tidy_objc_missing_hash_highlighting = warning resharper_cpp_clang_tidy_objc_nsinvocation_argument_lifetime_highlighting = warning resharper_cpp_clang_tidy_objc_property_declaration_highlighting = warning resharper_cpp_clang_tidy_objc_super_self_highlighting = warning resharper_cpp_clang_tidy_openmp_exception_escape_highlighting = warning resharper_cpp_clang_tidy_openmp_use_default_none_highlighting = warning resharper_cpp_clang_tidy_performance_faster_string_find_highlighting = suggestion resharper_cpp_clang_tidy_performance_for_range_copy_highlighting = suggestion resharper_cpp_clang_tidy_performance_implicit_conversion_in_loop_highlighting = suggestion resharper_cpp_clang_tidy_performance_inefficient_algorithm_highlighting = suggestion resharper_cpp_clang_tidy_performance_inefficient_string_concatenation_highlighting = suggestion resharper_cpp_clang_tidy_performance_inefficient_vector_operation_highlighting = suggestion resharper_cpp_clang_tidy_performance_move_constructor_init_highlighting = warning resharper_cpp_clang_tidy_performance_move_const_arg_highlighting = suggestion resharper_cpp_clang_tidy_performance_noexcept_move_constructor_highlighting = none resharper_cpp_clang_tidy_performance_no_automatic_move_highlighting = warning resharper_cpp_clang_tidy_performance_no_int_to_ptr_highlighting = warning resharper_cpp_clang_tidy_performance_trivially_destructible_highlighting = suggestion resharper_cpp_clang_tidy_performance_type_promotion_in_math_fn_highlighting = suggestion resharper_cpp_clang_tidy_performance_unnecessary_copy_initialization_highlighting = suggestion resharper_cpp_clang_tidy_performance_unnecessary_value_param_highlighting = suggestion resharper_cpp_clang_tidy_portability_restrict_system_includes_highlighting = none resharper_cpp_clang_tidy_portability_simd_intrinsics_highlighting = none resharper_cpp_clang_tidy_readability_avoid_const_params_in_decls_highlighting = none resharper_cpp_clang_tidy_readability_braces_around_statements_highlighting = none resharper_cpp_clang_tidy_readability_const_return_type_highlighting = none resharper_cpp_clang_tidy_readability_container_size_empty_highlighting = suggestion resharper_cpp_clang_tidy_readability_convert_member_functions_to_static_highlighting = none resharper_cpp_clang_tidy_readability_deleted_default_highlighting = none resharper_cpp_clang_tidy_readability_delete_null_pointer_highlighting = suggestion resharper_cpp_clang_tidy_readability_else_after_return_highlighting = none resharper_cpp_clang_tidy_readability_function_cognitive_complexity_highlighting = none resharper_cpp_clang_tidy_readability_function_size_highlighting = none resharper_cpp_clang_tidy_readability_identifier_naming_highlighting = none resharper_cpp_clang_tidy_readability_implicit_bool_conversion_highlighting = none resharper_cpp_clang_tidy_readability_inconsistent_declaration_parameter_name_highlighting = suggestion resharper_cpp_clang_tidy_readability_isolate_declaration_highlighting = none resharper_cpp_clang_tidy_readability_magic_numbers_highlighting = none resharper_cpp_clang_tidy_readability_make_member_function_const_highlighting = none resharper_cpp_clang_tidy_readability_misleading_indentation_highlighting = none resharper_cpp_clang_tidy_readability_misplaced_array_index_highlighting = suggestion resharper_cpp_clang_tidy_readability_named_parameter_highlighting = none resharper_cpp_clang_tidy_readability_non_const_parameter_highlighting = none resharper_cpp_clang_tidy_readability_qualified_auto_highlighting = none resharper_cpp_clang_tidy_readability_redundant_access_specifiers_highlighting = none resharper_cpp_clang_tidy_readability_redundant_control_flow_highlighting = none resharper_cpp_clang_tidy_readability_redundant_declaration_highlighting = suggestion resharper_cpp_clang_tidy_readability_redundant_function_ptr_dereference_highlighting = suggestion resharper_cpp_clang_tidy_readability_redundant_member_init_highlighting = none resharper_cpp_clang_tidy_readability_redundant_preprocessor_highlighting = warning resharper_cpp_clang_tidy_readability_redundant_smartptr_get_highlighting = suggestion resharper_cpp_clang_tidy_readability_redundant_string_cstr_highlighting = suggestion resharper_cpp_clang_tidy_readability_redundant_string_init_highlighting = suggestion resharper_cpp_clang_tidy_readability_simplify_boolean_expr_highlighting = none resharper_cpp_clang_tidy_readability_simplify_subscript_expr_highlighting = warning resharper_cpp_clang_tidy_readability_static_accessed_through_instance_highlighting = suggestion resharper_cpp_clang_tidy_readability_static_definition_in_anonymous_namespace_highlighting = none resharper_cpp_clang_tidy_readability_string_compare_highlighting = warning resharper_cpp_clang_tidy_readability_uniqueptr_delete_release_highlighting = suggestion resharper_cpp_clang_tidy_readability_uppercase_literal_suffix_highlighting = none resharper_cpp_clang_tidy_readability_use_anyofallof_highlighting = suggestion resharper_cpp_clang_tidy_zircon_temporary_objects_highlighting = none resharper_cpp_class_can_be_final_highlighting = hint resharper_cpp_class_disallow_lazy_merging_highlighting = warning resharper_cpp_class_is_incomplete_highlighting = warning resharper_cpp_class_needs_constructor_because_of_uninitialized_member_highlighting = warning resharper_cpp_class_never_used_highlighting = warning resharper_cpp_compile_time_constant_can_be_replaced_with_boolean_constant_highlighting = suggestion resharper_cpp_const_parameter_in_declaration_highlighting = suggestion resharper_cpp_const_value_function_return_type_highlighting = suggestion resharper_cpp_coroutine_call_resolve_error_highlighting = warning resharper_cpp_cv_qualifier_can_not_be_applied_to_reference_highlighting = warning resharper_cpp_c_style_cast_highlighting = suggestion resharper_cpp_declaration_hides_local_highlighting = warning resharper_cpp_declaration_hides_uncaptured_local_highlighting = hint resharper_cpp_declaration_specifier_without_declarators_highlighting = warning resharper_cpp_declarator_disambiguated_as_function_highlighting = warning resharper_cpp_declarator_never_used_highlighting = warning resharper_cpp_declarator_used_before_initialization_highlighting = error resharper_cpp_defaulted_special_member_function_is_implicitly_deleted_highlighting = warning resharper_cpp_default_case_not_handled_in_switch_statement_highlighting = warning resharper_cpp_default_initialization_with_no_user_constructor_highlighting = warning resharper_cpp_default_is_used_as_identifier_highlighting = warning resharper_cpp_deleting_void_pointer_highlighting = warning resharper_cpp_dependent_template_without_template_keyword_highlighting = warning resharper_cpp_dependent_type_without_typename_keyword_highlighting = warning resharper_cpp_deprecated_entity_highlighting = warning resharper_cpp_deprecated_register_storage_class_specifier_highlighting = warning resharper_cpp_dereference_operator_limit_exceeded_highlighting = warning resharper_cpp_discarded_postfix_operator_result_highlighting = suggestion resharper_cpp_doxygen_syntax_error_highlighting = warning resharper_cpp_doxygen_undocumented_parameter_highlighting = suggestion resharper_cpp_doxygen_unresolved_reference_highlighting = warning resharper_cpp_empty_declaration_highlighting = warning resharper_cpp_enforce_cv_qualifiers_order_highlighting = none resharper_cpp_enforce_cv_qualifiers_placement_highlighting = none resharper_cpp_enforce_do_statement_braces_highlighting = none resharper_cpp_enforce_for_statement_braces_highlighting = none resharper_cpp_enforce_function_declaration_style_highlighting = none resharper_cpp_enforce_if_statement_braces_highlighting = none resharper_cpp_enforce_nested_namespaces_style_highlighting = hint resharper_cpp_enforce_overriding_destructor_style_highlighting = suggestion resharper_cpp_enforce_overriding_function_style_highlighting = suggestion resharper_cpp_enforce_type_alias_code_style_highlighting = none resharper_cpp_enforce_while_statement_braces_highlighting = none resharper_cpp_entity_assigned_but_no_read_highlighting = warning resharper_cpp_entity_used_only_in_unevaluated_context_highlighting = warning resharper_cpp_enumerator_never_used_highlighting = warning resharper_cpp_equal_operands_in_binary_expression_highlighting = warning resharper_cpp_explicit_specialization_in_non_namespace_scope_highlighting = warning resharper_cpp_expression_without_side_effects_highlighting = warning resharper_cpp_final_function_in_final_class_highlighting = suggestion resharper_cpp_functional_style_cast_highlighting = suggestion resharper_cpp_function_doesnt_return_value_highlighting = warning resharper_cpp_function_is_not_implemented_highlighting = warning resharper_cpp_header_has_been_already_included_highlighting = hint resharper_cpp_hidden_function_highlighting = warning resharper_cpp_hiding_function_highlighting = warning resharper_cpp_identical_operands_in_binary_expression_highlighting = warning resharper_cpp_if_can_be_replaced_by_constexpr_if_highlighting = suggestion resharper_cpp_implicit_default_constructor_not_available_highlighting = warning resharper_cpp_incompatible_pointer_conversion_highlighting = warning resharper_cpp_incomplete_switch_statement_highlighting = warning resharper_cpp_inconsistent_naming_highlighting = hint resharper_cpp_incorrect_blank_lines_near_braces_highlighting = none resharper_cpp_initialized_value_is_always_rewritten_highlighting = warning resharper_cpp_integral_to_pointer_conversion_highlighting = warning resharper_cpp_invalid_line_continuation_highlighting = warning resharper_cpp_join_declaration_and_assignment_highlighting = suggestion resharper_cpp_lambda_capture_never_used_highlighting = warning resharper_cpp_local_variable_may_be_const_highlighting = suggestion resharper_cpp_local_variable_might_not_be_initialized_highlighting = warning resharper_cpp_local_variable_with_non_trivial_dtor_is_never_used_highlighting = none resharper_cpp_long_float_highlighting = warning resharper_cpp_member_function_may_be_const_highlighting = suggestion resharper_cpp_member_function_may_be_static_highlighting = suggestion resharper_cpp_member_initializers_order_highlighting = suggestion resharper_cpp_mismatched_class_tags_highlighting = warning resharper_cpp_missing_blank_lines_highlighting = none resharper_cpp_missing_include_guard_highlighting = warning resharper_cpp_missing_indent_highlighting = none resharper_cpp_missing_linebreak_highlighting = none resharper_cpp_missing_space_highlighting = none resharper_cpp_ms_ext_address_of_class_r_value_highlighting = warning resharper_cpp_ms_ext_binding_r_value_to_lvalue_reference_highlighting = warning resharper_cpp_ms_ext_copy_elision_in_copy_init_declarator_highlighting = warning resharper_cpp_ms_ext_double_user_conversion_in_copy_init_highlighting = warning resharper_cpp_ms_ext_not_initialized_static_const_local_var_highlighting = warning resharper_cpp_ms_ext_reinterpret_cast_from_nullptr_highlighting = warning resharper_cpp_multiple_spaces_highlighting = none resharper_cpp_must_be_public_virtual_to_implement_interface_highlighting = warning resharper_cpp_mutable_specifier_on_reference_member_highlighting = warning resharper_cpp_non_exception_safe_resource_acquisition_highlighting = hint resharper_cpp_non_explicit_conversion_operator_highlighting = hint resharper_cpp_non_explicit_converting_constructor_highlighting = hint resharper_cpp_non_inline_function_definition_in_header_file_highlighting = warning resharper_cpp_non_inline_variable_definition_in_header_file_highlighting = warning resharper_cpp_not_all_paths_return_value_highlighting = warning resharper_cpp_no_discard_expression_highlighting = warning resharper_cpp_object_member_might_not_be_initialized_highlighting = warning resharper_cpp_outdent_is_off_prev_level_highlighting = none resharper_cpp_out_parameter_must_be_written_highlighting = warning resharper_cpp_parameter_may_be_const_highlighting = hint resharper_cpp_parameter_may_be_const_ptr_or_ref_highlighting = suggestion resharper_cpp_parameter_names_mismatch_highlighting = hint resharper_cpp_parameter_never_used_highlighting = hint resharper_cpp_parameter_value_is_reassigned_highlighting = warning resharper_cpp_pointer_conversion_drops_qualifiers_highlighting = warning resharper_cpp_pointer_to_integral_conversion_highlighting = warning resharper_cpp_polymorphic_class_with_non_virtual_public_destructor_highlighting = warning resharper_cpp_possibly_erroneous_empty_statements_highlighting = warning resharper_cpp_possibly_uninitialized_member_highlighting = warning resharper_cpp_possibly_unintended_object_slicing_highlighting = warning resharper_cpp_precompiled_header_is_not_included_highlighting = error resharper_cpp_precompiled_header_not_found_highlighting = error resharper_cpp_printf_bad_format_highlighting = warning resharper_cpp_printf_extra_arg_highlighting = warning resharper_cpp_printf_missed_arg_highlighting = error resharper_cpp_printf_risky_format_highlighting = warning resharper_cpp_private_special_member_function_is_not_implemented_highlighting = warning resharper_cpp_range_based_for_incompatible_reference_highlighting = warning resharper_cpp_redefinition_of_default_argument_in_override_function_highlighting = warning resharper_cpp_redundant_access_specifier_highlighting = hint resharper_cpp_redundant_base_class_access_specifier_highlighting = hint resharper_cpp_redundant_blank_lines_highlighting = none resharper_cpp_redundant_boolean_expression_argument_highlighting = warning resharper_cpp_redundant_cast_expression_highlighting = hint resharper_cpp_redundant_const_specifier_highlighting = hint resharper_cpp_redundant_control_flow_jump_highlighting = hint resharper_cpp_redundant_else_keyword_highlighting = hint resharper_cpp_redundant_else_keyword_inside_compound_statement_highlighting = hint resharper_cpp_redundant_empty_declaration_highlighting = hint resharper_cpp_redundant_empty_statement_highlighting = hint resharper_cpp_redundant_explicit_template_arguments_highlighting = hint resharper_cpp_redundant_inline_specifier_highlighting = hint resharper_cpp_redundant_linebreak_highlighting = none resharper_cpp_redundant_member_initializer_highlighting = suggestion resharper_cpp_redundant_parentheses_highlighting = hint resharper_cpp_redundant_qualifier_highlighting = hint resharper_cpp_redundant_space_highlighting = none resharper_cpp_redundant_static_specifier_on_member_allocation_function_highlighting = hint resharper_cpp_redundant_template_keyword_highlighting = warning resharper_cpp_redundant_typename_keyword_highlighting = warning resharper_cpp_redundant_void_argument_list_highlighting = suggestion resharper_cpp_reinterpret_cast_from_void_ptr_highlighting = suggestion resharper_cpp_remove_redundant_braces_highlighting = none resharper_cpp_replace_memset_with_zero_initialization_highlighting = suggestion resharper_cpp_replace_tie_with_structured_binding_highlighting = suggestion resharper_cpp_return_no_value_in_non_void_function_highlighting = warning resharper_cpp_smart_pointer_vs_make_function_highlighting = suggestion resharper_cpp_some_object_members_might_not_be_initialized_highlighting = warning resharper_cpp_special_function_without_noexcept_specification_highlighting = warning resharper_cpp_static_assert_failure_highlighting = error resharper_cpp_static_data_member_in_unnamed_struct_highlighting = warning resharper_cpp_static_specifier_on_anonymous_namespace_member_highlighting = suggestion resharper_cpp_syntax_warning_highlighting = warning resharper_cpp_tabs_and_spaces_mismatch_highlighting = none resharper_cpp_tabs_are_disallowed_highlighting = none resharper_cpp_tabs_outside_indent_highlighting = none resharper_cpp_template_parameter_shadowing_highlighting = warning resharper_cpp_this_arg_member_func_delegate_ctor_is_unsuported_by_dot_net_core_highlighting = none resharper_cpp_throw_expression_can_be_replaced_with_rethrow_highlighting = warning resharper_cpp_too_wide_scope_highlighting = suggestion resharper_cpp_too_wide_scope_init_statement_highlighting = hint resharper_cpp_type_alias_never_used_highlighting = warning resharper_cpp_ue4_blueprint_callable_function_may_be_const_highlighting = hint resharper_cpp_ue4_blueprint_callable_function_may_be_static_highlighting = hint resharper_cpp_ue4_coding_standard_naming_violation_warning_highlighting = hint resharper_cpp_ue4_coding_standard_u_class_naming_violation_error_highlighting = error resharper_cpp_ue4_probable_memory_issues_with_u_objects_in_container_highlighting = warning resharper_cpp_ue4_probable_memory_issues_with_u_object_highlighting = warning resharper_cpp_ue_incorrect_engine_directory_highlighting = error resharper_cpp_ue_non_existent_input_action_highlighting = warning resharper_cpp_ue_non_existent_input_axis_highlighting = warning resharper_cpp_ue_source_file_without_predefined_macros_highlighting = warning resharper_cpp_ue_source_file_without_standard_library_highlighting = error resharper_cpp_ue_version_file_doesnt_exist_highlighting = error resharper_cpp_uninitialized_dependent_base_class_highlighting = warning resharper_cpp_uninitialized_non_static_data_member_highlighting = warning resharper_cpp_union_member_of_reference_type_highlighting = warning resharper_cpp_unnamed_namespace_in_header_file_highlighting = warning resharper_cpp_unnecessary_whitespace_highlighting = none resharper_cpp_unreachable_code_highlighting = warning resharper_cpp_unsigned_zero_comparison_highlighting = warning resharper_cpp_unused_include_directive_highlighting = warning resharper_cpp_user_defined_literal_suffix_does_not_start_with_underscore_highlighting = warning resharper_cpp_use_algorithm_with_count_highlighting = suggestion resharper_cpp_use_auto_for_numeric_highlighting = hint resharper_cpp_use_auto_highlighting = hint resharper_cpp_use_elements_view_highlighting = suggestion resharper_cpp_use_familiar_template_syntax_for_generic_lambdas_highlighting = suggestion resharper_cpp_use_range_algorithm_highlighting = suggestion resharper_cpp_use_std_size_highlighting = suggestion resharper_cpp_use_structured_binding_highlighting = hint resharper_cpp_use_type_trait_alias_highlighting = suggestion resharper_cpp_using_result_of_assignment_as_condition_highlighting = warning resharper_cpp_u_function_macro_call_has_no_effect_highlighting = warning resharper_cpp_u_property_macro_call_has_no_effect_highlighting = warning resharper_cpp_variable_can_be_made_constexpr_highlighting = suggestion resharper_cpp_virtual_function_call_inside_ctor_highlighting = warning resharper_cpp_virtual_function_in_final_class_highlighting = warning resharper_cpp_volatile_parameter_in_declaration_highlighting = suggestion resharper_cpp_wrong_includes_order_highlighting = hint resharper_cpp_wrong_indent_size_highlighting = none resharper_cpp_wrong_slashes_in_include_directive_highlighting = hint resharper_cpp_zero_constant_can_be_replaced_with_nullptr_highlighting = suggestion resharper_cpp_zero_valued_expression_used_as_null_pointer_highlighting = warning resharper_c_declaration_with_implicit_int_type_highlighting = warning resharper_default_value_attribute_for_optional_parameter_highlighting = warning resharper_dl_tag_contains_non_dt_or_dd_elements_highlighting = hint resharper_double_negation_in_pattern_highlighting = suggestion resharper_double_negation_operator_highlighting = suggestion resharper_duplicate_resource_highlighting = warning resharper_dynamic_shift_right_op_is_not_int_highlighting = warning resharper_empty_constructor_highlighting = warning resharper_empty_destructor_highlighting = warning resharper_empty_embedded_statement_highlighting = warning resharper_empty_for_statement_highlighting = warning resharper_empty_general_catch_clause_highlighting = warning resharper_empty_namespace_highlighting = warning resharper_empty_statement_highlighting = warning resharper_empty_title_tag_highlighting = hint resharper_enforce_do_while_statement_braces_highlighting = warning resharper_enforce_fixed_statement_braces_highlighting = warning resharper_enforce_foreach_statement_braces_highlighting = warning resharper_enforce_for_statement_braces_highlighting = warning resharper_enforce_if_statement_braces_highlighting = warning resharper_enforce_lock_statement_braces_highlighting = warning resharper_enforce_using_statement_braces_highlighting = warning resharper_enforce_while_statement_braces_highlighting = warning resharper_entity_name_captured_only_global_highlighting = warning resharper_entity_name_captured_only_local_highlighting = warning resharper_enumerable_sum_in_explicit_unchecked_context_highlighting = warning resharper_enum_underlying_type_is_int_highlighting = warning resharper_equal_expression_comparison_highlighting = warning resharper_escaped_keyword_highlighting = warning resharper_event_never_invoked_global_highlighting = suggestion resharper_event_never_subscribed_to_global_highlighting = suggestion resharper_event_never_subscribed_to_local_highlighting = suggestion resharper_event_unsubscription_via_anonymous_delegate_highlighting = warning resharper_explicit_caller_info_argument_highlighting = warning resharper_expression_is_always_null_highlighting = warning resharper_field_can_be_made_read_only_global_highlighting = suggestion resharper_field_can_be_made_read_only_local_highlighting = suggestion resharper_field_hides_interface_property_with_default_implementation_highlighting = warning resharper_foreach_can_be_converted_to_query_using_another_get_enumerator_highlighting = hint resharper_foreach_can_be_partly_converted_to_query_using_another_get_enumerator_highlighting = hint resharper_format_string_placeholders_mismatch_highlighting = warning resharper_format_string_problem_highlighting = warning resharper_for_can_be_converted_to_foreach_highlighting = suggestion resharper_for_statement_condition_is_true_highlighting = warning resharper_function_complexity_overflow_highlighting = none resharper_function_never_returns_highlighting = warning resharper_function_recursive_on_all_paths_highlighting = warning resharper_gc_suppress_finalize_for_type_without_destructor_highlighting = warning resharper_generic_enumerator_not_disposed_highlighting = warning resharper_heuristic_unreachable_code_highlighting = warning resharper_html_attributes_quotes_highlighting = hint resharper_html_attribute_not_resolved_highlighting = warning resharper_html_attribute_value_not_resolved_highlighting = warning resharper_html_dead_code_highlighting = warning resharper_html_event_not_resolved_highlighting = warning resharper_html_id_duplication_highlighting = warning resharper_html_id_not_resolved_highlighting = warning resharper_html_obsolete_highlighting = warning resharper_html_path_error_highlighting = warning resharper_html_tag_not_closed_highlighting = error resharper_html_tag_not_resolved_highlighting = warning resharper_html_tag_should_be_self_closed_highlighting = warning resharper_html_tag_should_not_be_self_closed_highlighting = warning resharper_html_warning_highlighting = warning resharper_identifier_typo_highlighting = hint resharper_ignored_directive_highlighting = warning resharper_inactive_preprocessor_branch_highlighting = warning resharper_inconsistently_synchronized_field_highlighting = warning resharper_inconsistent_naming_highlighting = warning resharper_incorrect_blank_lines_near_braces_highlighting = none resharper_indexing_by_invalid_range_highlighting = warning resharper_inheritdoc_consider_usage_highlighting = none resharper_inheritdoc_invalid_usage_highlighting = warning resharper_inline_out_variable_declaration_highlighting = suggestion resharper_inline_temporary_variable_highlighting = hint resharper_internal_or_private_member_not_documented_highlighting = none resharper_interpolated_string_expression_is_not_i_formattable_highlighting = warning resharper_introduce_optional_parameters_global_highlighting = suggestion resharper_introduce_optional_parameters_local_highlighting = suggestion resharper_int_division_by_zero_highlighting = warning resharper_int_variable_overflow_highlighting = warning resharper_int_variable_overflow_in_checked_context_highlighting = warning resharper_int_variable_overflow_in_unchecked_context_highlighting = warning resharper_invalid_value_type_highlighting = warning resharper_invalid_xml_doc_comment_highlighting = warning resharper_invert_condition_1_highlighting = hint resharper_invert_if_highlighting = hint resharper_invocation_is_skipped_highlighting = hint resharper_invoke_as_extension_method_highlighting = suggestion resharper_is_expression_always_false_highlighting = warning resharper_is_expression_always_of_type_highlighting = warning resharper_is_expression_always_true_highlighting = warning resharper_iterator_method_result_is_ignored_highlighting = warning resharper_iterator_never_returns_highlighting = warning resharper_join_declaration_and_initializer_highlighting = suggestion resharper_join_null_check_with_usage_highlighting = suggestion resharper_join_null_check_with_usage_when_possible_highlighting = none resharper_lambda_expression_can_be_made_static_highlighting = none resharper_lambda_expression_must_be_static_highlighting = suggestion resharper_lambda_should_not_capture_context_highlighting = suggestion resharper_localizable_element_highlighting = warning resharper_local_function_can_be_made_static_highlighting = none resharper_local_function_hides_method_highlighting = warning resharper_local_variable_hides_member_highlighting = warning resharper_long_literal_ending_lower_l_highlighting = warning resharper_loop_can_be_converted_to_query_highlighting = hint resharper_loop_can_be_partly_converted_to_query_highlighting = none resharper_loop_variable_is_never_changed_inside_loop_highlighting = warning resharper_markup_attribute_typo_highlighting = hint resharper_markup_text_typo_highlighting = hint resharper_math_abs_method_is_redundant_highlighting = warning resharper_math_clamp_min_greater_than_max_highlighting = warning resharper_meaningless_default_parameter_value_highlighting = warning resharper_member_can_be_internal_highlighting = none resharper_member_can_be_made_static_global_highlighting = none resharper_member_can_be_made_static_local_highlighting = none resharper_member_can_be_private_global_highlighting = suggestion resharper_member_can_be_private_local_highlighting = suggestion resharper_member_can_be_protected_global_highlighting = suggestion resharper_member_can_be_protected_local_highlighting = suggestion resharper_member_hides_interface_member_with_default_implementation_highlighting = warning resharper_member_hides_static_from_outer_class_highlighting = warning resharper_member_initializer_value_ignored_highlighting = warning resharper_merge_and_pattern_highlighting = suggestion resharper_merge_cast_with_type_check_highlighting = suggestion resharper_merge_conditional_expression_highlighting = suggestion resharper_merge_conditional_expression_when_possible_highlighting = none resharper_merge_into_logical_pattern_highlighting = hint resharper_merge_into_negated_pattern_highlighting = hint resharper_merge_into_pattern_highlighting = suggestion resharper_merge_sequential_checks_highlighting = hint resharper_merge_sequential_checks_when_possible_highlighting = none resharper_method_has_async_overload_highlighting = suggestion resharper_method_has_async_overload_with_cancellation_highlighting = suggestion resharper_method_overload_with_optional_parameter_highlighting = warning resharper_method_supports_cancellation_highlighting = suggestion resharper_missing_alt_attribute_in_img_tag_highlighting = hint resharper_missing_blank_lines_highlighting = none resharper_missing_body_tag_highlighting = warning resharper_missing_head_and_body_tags_highlighting = warning resharper_missing_head_tag_highlighting = warning resharper_missing_indent_highlighting = none resharper_missing_linebreak_highlighting = none resharper_missing_space_highlighting = none resharper_missing_title_tag_highlighting = hint resharper_more_specific_foreach_variable_type_available_highlighting = suggestion resharper_move_to_existing_positional_deconstruction_pattern_highlighting = hint resharper_multiple_nullable_attributes_usage_highlighting = warning resharper_multiple_order_by_highlighting = warning resharper_multiple_resolve_candidates_in_text_highlighting = warning resharper_multiple_spaces_highlighting = none resharper_multiple_statements_on_one_line_highlighting = none resharper_multiple_type_members_on_one_line_highlighting = none resharper_must_use_return_value_highlighting = warning resharper_mvc_action_not_resolved_highlighting = error resharper_mvc_area_not_resolved_highlighting = error resharper_mvc_controller_not_resolved_highlighting = error resharper_mvc_invalid_model_type_highlighting = error resharper_mvc_masterpage_not_resolved_highlighting = error resharper_mvc_partial_view_not_resolved_highlighting = error resharper_mvc_template_not_resolved_highlighting = error resharper_mvc_view_component_not_resolved_highlighting = error resharper_mvc_view_component_view_not_resolved_highlighting = error resharper_mvc_view_not_resolved_highlighting = error resharper_negation_of_relational_pattern_highlighting = suggestion resharper_negative_equality_expression_highlighting = suggestion resharper_negative_index_highlighting = warning resharper_nested_string_interpolation_highlighting = suggestion resharper_non_atomic_compound_operator_highlighting = warning resharper_non_constant_equality_expression_has_constant_result_highlighting = warning resharper_non_parsable_element_highlighting = warning resharper_non_readonly_member_in_get_hash_code_highlighting = warning resharper_non_volatile_field_in_double_check_locking_highlighting = warning resharper_not_accessed_field_global_highlighting = suggestion resharper_not_accessed_field_local_highlighting = warning resharper_not_accessed_variable_highlighting = warning resharper_not_assigned_out_parameter_highlighting = warning resharper_not_declared_in_parent_culture_highlighting = warning resharper_not_null_member_is_not_initialized_highlighting = warning resharper_not_observable_annotation_redundancy_highlighting = warning resharper_not_overridden_in_specific_culture_highlighting = warning resharper_not_resolved_in_text_highlighting = warning resharper_no_support_for_vb_highlighting = warning resharper_nullable_warning_suppression_is_used_highlighting = none resharper_n_unit_async_method_must_be_task_highlighting = warning resharper_n_unit_attribute_produces_too_many_tests_highlighting = none resharper_n_unit_auto_fixture_incorrect_argument_type_highlighting = warning resharper_n_unit_auto_fixture_missed_test_attribute_highlighting = warning resharper_n_unit_auto_fixture_missed_test_or_test_fixture_attribute_highlighting = warning resharper_n_unit_auto_fixture_redundant_argument_in_inline_auto_data_attribute_highlighting = warning resharper_n_unit_duplicate_values_highlighting = warning resharper_n_unit_ignored_parameter_attribute_highlighting = warning resharper_n_unit_implicit_unspecified_null_values_highlighting = warning resharper_n_unit_incorrect_argument_type_highlighting = warning resharper_n_unit_incorrect_expected_result_type_highlighting = warning resharper_n_unit_incorrect_range_bounds_highlighting = warning resharper_n_unit_method_with_parameters_and_test_attribute_highlighting = warning resharper_n_unit_missing_arguments_in_test_case_attribute_highlighting = warning resharper_n_unit_non_public_method_with_test_attribute_highlighting = warning resharper_n_unit_no_values_provided_highlighting = warning resharper_n_unit_parameter_type_is_not_compatible_with_attribute_highlighting = warning resharper_n_unit_range_attribute_bounds_are_out_of_range_highlighting = warning resharper_n_unit_range_step_sign_mismatch_highlighting = warning resharper_n_unit_range_step_value_must_not_be_zero_highlighting = warning resharper_n_unit_range_to_value_is_not_reachable_highlighting = warning resharper_n_unit_redundant_argument_instead_of_expected_result_highlighting = warning resharper_n_unit_redundant_argument_in_test_case_attribute_highlighting = warning resharper_n_unit_redundant_expected_result_in_test_case_attribute_highlighting = warning resharper_n_unit_test_case_attribute_requires_expected_result_highlighting = warning resharper_n_unit_test_case_result_property_duplicates_expected_result_highlighting = warning resharper_n_unit_test_case_result_property_is_obsolete_highlighting = warning resharper_n_unit_test_case_source_cannot_be_resolved_highlighting = warning resharper_n_unit_test_case_source_must_be_field_property_method_highlighting = warning resharper_n_unit_test_case_source_must_be_static_highlighting = warning resharper_n_unit_test_case_source_should_implement_i_enumerable_highlighting = warning resharper_object_creation_as_statement_highlighting = none resharper_obsolete_element_error_highlighting = error resharper_obsolete_element_highlighting = warning resharper_ol_tag_contains_non_li_elements_highlighting = hint resharper_one_way_operation_contract_with_return_type_highlighting = warning resharper_operation_contract_without_service_contract_highlighting = warning resharper_operator_is_can_be_used_highlighting = warning resharper_optional_parameter_hierarchy_mismatch_highlighting = warning resharper_optional_parameter_ref_out_highlighting = warning resharper_other_tags_inside_script1_highlighting = error resharper_other_tags_inside_script2_highlighting = error resharper_other_tags_inside_unclosed_script_highlighting = error resharper_outdent_is_off_prev_level_highlighting = none resharper_overridden_with_empty_value_highlighting = warning resharper_overridden_with_same_value_highlighting = suggestion resharper_parameter_hides_member_highlighting = warning resharper_parameter_only_used_for_precondition_check_global_highlighting = suggestion resharper_parameter_only_used_for_precondition_check_local_highlighting = warning resharper_parameter_type_can_be_enumerable_global_highlighting = hint resharper_parameter_type_can_be_enumerable_local_highlighting = hint resharper_partial_method_parameter_name_mismatch_highlighting = warning resharper_partial_method_with_single_part_highlighting = warning resharper_partial_type_with_single_part_highlighting = warning resharper_pattern_always_matches_highlighting = warning resharper_pattern_always_of_type_highlighting = warning resharper_pattern_is_always_true_or_false_highlighting = warning resharper_pattern_never_matches_highlighting = warning resharper_polymorphic_field_like_event_invocation_highlighting = warning resharper_possible_infinite_inheritance_highlighting = warning resharper_possible_intended_rethrow_highlighting = warning resharper_possible_interface_member_ambiguity_highlighting = warning resharper_possible_invalid_cast_exception_highlighting = warning resharper_possible_invalid_cast_exception_in_foreach_loop_highlighting = warning resharper_possible_invalid_operation_exception_highlighting = hint resharper_possible_loss_of_fraction_highlighting = warning resharper_possible_mistaken_argument_highlighting = warning resharper_possible_mistaken_call_to_get_type_1_highlighting = warning resharper_possible_mistaken_call_to_get_type_2_highlighting = warning resharper_possible_multiple_enumeration_highlighting = suggestion resharper_possible_multiple_write_access_in_double_check_locking_highlighting = warning resharper_possible_null_reference_exception_highlighting = hint resharper_possible_struct_member_modification_of_non_variable_struct_highlighting = warning resharper_possible_unintended_linear_search_in_set_highlighting = warning resharper_possible_unintended_queryable_as_enumerable_highlighting = suggestion resharper_possible_unintended_reference_comparison_highlighting = warning resharper_possible_write_to_me_highlighting = warning resharper_possibly_impure_method_call_on_readonly_variable_highlighting = warning resharper_possibly_missing_indexer_initializer_comma_highlighting = warning resharper_possibly_mistaken_use_of_interpolated_string_insert_highlighting = warning resharper_possibly_mistaken_use_of_params_method_highlighting = hint resharper_private_field_can_be_converted_to_local_variable_highlighting = warning resharper_property_can_be_made_init_only_global_highlighting = suggestion resharper_property_can_be_made_init_only_local_highlighting = suggestion resharper_property_not_resolved_highlighting = error resharper_public_constructor_in_abstract_class_highlighting = suggestion resharper_pure_attribute_on_void_method_highlighting = warning resharper_razor_layout_not_resolved_highlighting = error resharper_razor_section_not_resolved_highlighting = error resharper_read_access_in_double_check_locking_highlighting = warning resharper_redundant_abstract_modifier_highlighting = warning resharper_redundant_always_match_subpattern_highlighting = suggestion resharper_redundant_anonymous_type_property_name_highlighting = warning resharper_redundant_argument_default_value_highlighting = none resharper_redundant_array_creation_expression_highlighting = hint resharper_redundant_array_lower_bound_specification_highlighting = warning resharper_redundant_assignment_highlighting = warning resharper_redundant_attribute_parentheses_highlighting = hint resharper_redundant_attribute_usage_property_highlighting = suggestion resharper_redundant_base_constructor_call_highlighting = warning resharper_redundant_base_qualifier_highlighting = warning resharper_redundant_blank_lines_highlighting = none resharper_redundant_bool_compare_highlighting = warning resharper_redundant_case_label_highlighting = warning resharper_redundant_cast_highlighting = warning resharper_redundant_catch_clause_highlighting = warning resharper_redundant_check_before_assignment_highlighting = warning resharper_redundant_collection_initializer_element_braces_highlighting = hint resharper_redundant_configure_await_highlighting = suggestion resharper_redundant_declaration_semicolon_highlighting = hint resharper_redundant_default_member_initializer_highlighting = warning resharper_redundant_delegate_creation_highlighting = warning resharper_redundant_disable_warning_comment_highlighting = warning resharper_redundant_discard_designation_highlighting = suggestion resharper_redundant_empty_case_else_highlighting = warning resharper_redundant_empty_finally_block_highlighting = warning resharper_redundant_empty_object_creation_argument_list_highlighting = none resharper_redundant_empty_object_or_collection_initializer_highlighting = warning resharper_redundant_empty_switch_section_highlighting = warning resharper_redundant_enumerable_cast_call_highlighting = warning resharper_redundant_explicit_array_creation_highlighting = warning resharper_redundant_explicit_array_size_highlighting = warning resharper_redundant_explicit_nullable_creation_highlighting = warning resharper_redundant_explicit_params_array_creation_highlighting = suggestion resharper_redundant_explicit_positional_property_declaration_highlighting = warning resharper_redundant_explicit_tuple_component_name_highlighting = warning resharper_redundant_extends_list_entry_highlighting = warning resharper_redundant_fixed_pointer_declaration_highlighting = suggestion resharper_redundant_if_else_block_highlighting = none resharper_redundant_if_statement_then_keyword_highlighting = none resharper_redundant_immediate_delegate_invocation_highlighting = suggestion resharper_redundant_include_highlighting = warning resharper_redundant_is_before_relational_pattern_highlighting = suggestion resharper_redundant_iterator_keyword_highlighting = warning resharper_redundant_jump_statement_highlighting = warning resharper_redundant_lambda_parameter_type_highlighting = warning resharper_redundant_lambda_signature_parentheses_highlighting = hint resharper_redundant_linebreak_highlighting = none resharper_redundant_logical_conditional_expression_operand_highlighting = warning resharper_redundant_me_qualifier_highlighting = warning resharper_redundant_my_base_qualifier_highlighting = warning resharper_redundant_my_class_qualifier_highlighting = warning resharper_redundant_name_qualifier_highlighting = warning resharper_redundant_not_null_constraint_highlighting = warning resharper_redundant_nullable_annotation_on_reference_type_constraint_highlighting = warning resharper_redundant_nullable_annotation_on_type_constraint_has_non_nullable_base_type_highlighting = warning resharper_redundant_nullable_annotation_on_type_constraint_has_non_nullable_type_kind_highlighting = warning resharper_redundant_nullable_flow_attribute_highlighting = warning resharper_redundant_nullable_type_mark_highlighting = warning resharper_redundant_nullness_attribute_with_nullable_reference_types_highlighting = warning resharper_redundant_overflow_checking_context_highlighting = warning resharper_redundant_overload_global_highlighting = suggestion resharper_redundant_overload_local_highlighting = suggestion resharper_redundant_overridden_member_highlighting = warning resharper_redundant_params_highlighting = warning resharper_redundant_parentheses_highlighting = none resharper_redundant_pattern_parentheses_highlighting = hint resharper_redundant_property_parentheses_highlighting = hint resharper_redundant_property_pattern_clause_highlighting = suggestion resharper_redundant_qualifier_highlighting = warning resharper_redundant_query_order_by_ascending_keyword_highlighting = hint resharper_redundant_range_bound_highlighting = suggestion resharper_redundant_readonly_modifier_highlighting = suggestion resharper_redundant_record_body_highlighting = warning resharper_redundant_record_class_keyword_highlighting = warning resharper_redundant_setter_value_parameter_declaration_highlighting = hint resharper_redundant_space_highlighting = none resharper_redundant_string_format_call_highlighting = warning resharper_redundant_string_interpolation_highlighting = suggestion resharper_redundant_string_to_char_array_call_highlighting = warning resharper_redundant_string_type_highlighting = suggestion resharper_redundant_suppress_nullable_warning_expression_highlighting = warning resharper_redundant_ternary_expression_highlighting = warning resharper_redundant_to_string_call_for_value_type_highlighting = hint resharper_redundant_to_string_call_highlighting = warning resharper_redundant_type_arguments_of_method_highlighting = warning resharper_redundant_unsafe_context_highlighting = warning resharper_redundant_using_directive_global_highlighting = warning resharper_redundant_using_directive_highlighting = warning resharper_redundant_verbatim_prefix_highlighting = suggestion resharper_redundant_verbatim_string_prefix_highlighting = suggestion resharper_redundant_with_expression_highlighting = suggestion resharper_reference_equals_with_value_type_highlighting = warning resharper_reg_exp_inspections_highlighting = warning resharper_remove_constructor_invocation_highlighting = none resharper_remove_redundant_braces_highlighting = hint resharper_remove_redundant_or_statement_false_highlighting = suggestion resharper_remove_redundant_or_statement_true_highlighting = suggestion resharper_remove_to_list_1_highlighting = suggestion resharper_remove_to_list_2_highlighting = suggestion resharper_replace_auto_property_with_computed_property_highlighting = hint resharper_replace_object_pattern_with_var_pattern_highlighting = suggestion resharper_replace_slice_with_range_indexer_highlighting = hint resharper_replace_substring_with_range_indexer_highlighting = hint resharper_replace_with_first_or_default_1_highlighting = suggestion resharper_replace_with_first_or_default_2_highlighting = suggestion resharper_replace_with_first_or_default_3_highlighting = suggestion resharper_replace_with_first_or_default_4_highlighting = suggestion resharper_replace_with_last_or_default_1_highlighting = suggestion resharper_replace_with_last_or_default_2_highlighting = suggestion resharper_replace_with_last_or_default_3_highlighting = suggestion resharper_replace_with_last_or_default_4_highlighting = suggestion resharper_replace_with_of_type_1_highlighting = suggestion resharper_replace_with_of_type_2_highlighting = suggestion resharper_replace_with_of_type_3_highlighting = suggestion resharper_replace_with_of_type_any_1_highlighting = suggestion resharper_replace_with_of_type_any_2_highlighting = suggestion resharper_replace_with_of_type_count_1_highlighting = suggestion resharper_replace_with_of_type_count_2_highlighting = suggestion resharper_replace_with_of_type_first_1_highlighting = suggestion resharper_replace_with_of_type_first_2_highlighting = suggestion resharper_replace_with_of_type_first_or_default_1_highlighting = suggestion resharper_replace_with_of_type_first_or_default_2_highlighting = suggestion resharper_replace_with_of_type_last_1_highlighting = suggestion resharper_replace_with_of_type_last_2_highlighting = suggestion resharper_replace_with_of_type_last_or_default_1_highlighting = suggestion resharper_replace_with_of_type_last_or_default_2_highlighting = suggestion resharper_replace_with_of_type_long_count_highlighting = suggestion resharper_replace_with_of_type_single_1_highlighting = suggestion resharper_replace_with_of_type_single_2_highlighting = suggestion resharper_replace_with_of_type_single_or_default_1_highlighting = suggestion resharper_replace_with_of_type_single_or_default_2_highlighting = suggestion resharper_replace_with_of_type_where_highlighting = suggestion resharper_replace_with_simple_assignment_false_highlighting = suggestion resharper_replace_with_simple_assignment_true_highlighting = suggestion resharper_replace_with_single_assignment_false_highlighting = suggestion resharper_replace_with_single_assignment_true_highlighting = suggestion resharper_replace_with_single_call_to_any_highlighting = suggestion resharper_replace_with_single_call_to_count_highlighting = suggestion resharper_replace_with_single_call_to_first_highlighting = suggestion resharper_replace_with_single_call_to_first_or_default_highlighting = suggestion resharper_replace_with_single_call_to_last_highlighting = suggestion resharper_replace_with_single_call_to_last_or_default_highlighting = suggestion resharper_replace_with_single_call_to_single_highlighting = suggestion resharper_replace_with_single_call_to_single_or_default_highlighting = suggestion resharper_replace_with_single_or_default_1_highlighting = suggestion resharper_replace_with_single_or_default_2_highlighting = suggestion resharper_replace_with_single_or_default_3_highlighting = suggestion resharper_replace_with_single_or_default_4_highlighting = suggestion resharper_replace_with_string_is_null_or_empty_highlighting = suggestion resharper_required_base_types_conflict_highlighting = warning resharper_required_base_types_direct_conflict_highlighting = warning resharper_required_base_types_is_not_inherited_highlighting = warning resharper_resource_item_not_resolved_highlighting = error resharper_resource_not_resolved_highlighting = error resharper_resx_not_resolved_highlighting = warning resharper_return_type_can_be_enumerable_global_highlighting = hint resharper_return_type_can_be_enumerable_local_highlighting = hint resharper_return_type_can_be_not_nullable_highlighting = warning resharper_return_value_of_pure_method_is_not_used_highlighting = warning resharper_route_templates_action_route_prefix_can_be_extracted_to_controller_route_highlighting = hint resharper_route_templates_ambiguous_matching_constraint_constructor_highlighting = warning resharper_route_templates_ambiguous_route_match_highlighting = warning resharper_route_templates_constraint_argument_cannot_be_converted_highlighting = warning resharper_route_templates_controller_route_parameter_is_not_passed_to_methods_highlighting = hint resharper_route_templates_duplicated_parameter_highlighting = warning resharper_route_templates_matching_constraint_constructor_not_resolved_highlighting = warning resharper_route_templates_method_missing_route_parameters_highlighting = hint resharper_route_templates_optional_parameter_can_be_preceded_only_by_single_period_highlighting = warning resharper_route_templates_optional_parameter_must_be_at_the_end_of_segment_highlighting = warning resharper_route_templates_parameter_constraint_can_be_specified_highlighting = hint resharper_route_templates_parameter_type_and_constraints_mismatch_highlighting = warning resharper_route_templates_parameter_type_can_be_made_stricter_highlighting = suggestion resharper_route_templates_route_parameter_constraint_not_resolved_highlighting = warning resharper_route_templates_route_parameter_is_not_passed_to_method_highlighting = hint resharper_route_templates_route_token_not_resolved_highlighting = warning resharper_route_templates_symbol_not_resolved_highlighting = warning resharper_route_templates_syntax_error_highlighting = warning resharper_safe_cast_is_used_as_type_check_highlighting = suggestion resharper_script_tag_has_both_src_and_content_attributes_highlighting = error resharper_script_tag_with_content_before_includes_highlighting = hint resharper_sealed_member_in_sealed_class_highlighting = warning resharper_separate_control_transfer_statement_highlighting = none resharper_service_contract_without_operations_highlighting = warning resharper_shift_expression_real_shift_count_is_zero_highlighting = warning resharper_shift_expression_result_equals_zero_highlighting = warning resharper_shift_expression_right_operand_not_equal_real_count_highlighting = warning resharper_shift_expression_zero_left_operand_highlighting = warning resharper_similar_anonymous_type_nearby_highlighting = hint resharper_simplify_conditional_operator_highlighting = suggestion resharper_simplify_conditional_ternary_expression_highlighting = suggestion resharper_simplify_i_if_highlighting = suggestion resharper_simplify_linq_expression_use_all_highlighting = suggestion resharper_simplify_linq_expression_use_any_highlighting = suggestion resharper_specify_a_culture_in_string_conversion_explicitly_highlighting = hint resharper_specify_string_comparison_highlighting = hint resharper_spin_lock_in_readonly_field_highlighting = warning resharper_stack_alloc_inside_loop_highlighting = warning resharper_static_member_initializer_referes_to_member_below_highlighting = warning resharper_static_member_in_generic_type_highlighting = warning resharper_static_problem_in_text_highlighting = warning resharper_string_compare_is_culture_specific_1_highlighting = warning resharper_string_compare_is_culture_specific_2_highlighting = warning resharper_string_compare_is_culture_specific_3_highlighting = warning resharper_string_compare_is_culture_specific_4_highlighting = warning resharper_string_compare_is_culture_specific_5_highlighting = warning resharper_string_compare_is_culture_specific_6_highlighting = warning resharper_string_compare_to_is_culture_specific_highlighting = warning resharper_string_ends_with_is_culture_specific_highlighting = none resharper_string_index_of_is_culture_specific_1_highlighting = warning resharper_string_index_of_is_culture_specific_2_highlighting = warning resharper_string_index_of_is_culture_specific_3_highlighting = warning resharper_string_last_index_of_is_culture_specific_1_highlighting = warning resharper_string_last_index_of_is_culture_specific_2_highlighting = warning resharper_string_last_index_of_is_culture_specific_3_highlighting = warning resharper_string_literal_as_interpolation_argument_highlighting = suggestion resharper_string_literal_typo_highlighting = hint resharper_string_starts_with_is_culture_specific_highlighting = none resharper_structured_message_template_problem_highlighting = warning resharper_struct_can_be_made_read_only_highlighting = suggestion resharper_struct_member_can_be_made_read_only_highlighting = none resharper_suggest_base_type_for_parameter_highlighting = hint resharper_suggest_base_type_for_parameter_in_constructor_highlighting = hint resharper_suggest_discard_declaration_var_style_highlighting = suggestion resharper_suggest_var_or_type_deconstruction_declarations_highlighting = hint resharper_suppress_nullable_warning_expression_as_inverted_is_expression_highlighting = warning resharper_suspicious_math_sign_method_highlighting = warning resharper_suspicious_parameter_name_in_argument_null_exception_highlighting = warning resharper_suspicious_type_conversion_global_highlighting = warning resharper_swap_via_deconstruction_highlighting = suggestion resharper_switch_expression_handles_some_known_enum_values_with_exception_in_default_highlighting = hint resharper_switch_statement_for_enum_misses_default_section_highlighting = hint resharper_switch_statement_handles_some_known_enum_values_with_default_highlighting = hint resharper_switch_statement_missing_some_enum_cases_no_default_highlighting = hint resharper_symbol_from_not_copied_locally_reference_used_warning_highlighting = warning resharper_tabs_and_spaces_mismatch_highlighting = none resharper_tabs_are_disallowed_highlighting = none resharper_tabs_outside_indent_highlighting = none resharper_tail_recursive_call_highlighting = hint resharper_thread_static_at_instance_field_highlighting = warning resharper_thread_static_field_has_initializer_highlighting = warning resharper_too_wide_local_variable_scope_highlighting = suggestion resharper_try_cast_always_succeeds_highlighting = suggestion resharper_try_statements_can_be_merged_highlighting = hint resharper_type_parameter_can_be_variant_highlighting = suggestion resharper_ul_tag_contains_non_li_elements_highlighting = hint resharper_unassigned_field_global_highlighting = suggestion resharper_unassigned_field_local_highlighting = warning resharper_unassigned_get_only_auto_property_highlighting = warning resharper_unassigned_readonly_field_highlighting = warning resharper_unclosed_script_highlighting = error resharper_unexpected_attribute_highlighting = warning resharper_unexpected_directive_highlighting = warning resharper_unnecessary_whitespace_highlighting = none resharper_unreachable_code_highlighting = warning resharper_unreachable_switch_arm_due_to_integer_analysis_highlighting = warning resharper_unreachable_switch_case_due_to_integer_analysis_highlighting = warning resharper_unreal_header_tool_error_highlighting = error resharper_unreal_header_tool_parser_error_highlighting = error resharper_unreal_header_tool_warning_highlighting = warning resharper_unsupported_required_base_type_highlighting = warning resharper_unused_anonymous_method_signature_highlighting = warning resharper_unused_auto_property_accessor_global_highlighting = suggestion resharper_unused_auto_property_accessor_local_highlighting = warning resharper_unused_import_clause_highlighting = warning resharper_unused_local_function_highlighting = warning resharper_unused_local_function_parameter_highlighting = warning resharper_unused_local_function_return_value_highlighting = warning resharper_unused_member_global_highlighting = suggestion resharper_unused_member_hierarchy_global_highlighting = suggestion resharper_unused_member_hierarchy_local_highlighting = warning resharper_unused_member_in_super_global_highlighting = suggestion resharper_unused_member_in_super_local_highlighting = warning resharper_unused_member_local_highlighting = warning resharper_unused_method_return_value_global_highlighting = suggestion resharper_unused_method_return_value_local_highlighting = warning resharper_unused_parameter_global_highlighting = suggestion resharper_unused_parameter_in_partial_method_highlighting = warning resharper_unused_parameter_local_highlighting = warning resharper_unused_tuple_component_in_return_value_highlighting = warning resharper_unused_type_global_highlighting = suggestion resharper_unused_type_local_highlighting = warning resharper_unused_type_parameter_highlighting = warning resharper_unused_variable_highlighting = warning resharper_useless_binary_operation_highlighting = warning resharper_useless_comparison_to_integral_constant_highlighting = warning resharper_use_array_creation_expression_1_highlighting = suggestion resharper_use_array_creation_expression_2_highlighting = suggestion resharper_use_array_empty_method_highlighting = suggestion resharper_use_await_using_highlighting = suggestion resharper_use_cancellation_token_for_i_async_enumerable_highlighting = suggestion resharper_use_collection_count_property_highlighting = warning resharper_use_configure_await_false_for_async_disposable_highlighting = none resharper_use_configure_await_false_highlighting = suggestion resharper_use_deconstruction_highlighting = hint resharper_use_deconstruction_on_parameter_highlighting = hint resharper_use_empty_types_field_highlighting = suggestion resharper_use_event_args_empty_field_highlighting = suggestion resharper_use_format_specifier_in_format_string_highlighting = suggestion resharper_use_format_specifier_in_interpolation_highlighting = suggestion resharper_use_implicitly_typed_variable_evident_highlighting = hint resharper_use_implicitly_typed_variable_highlighting = none resharper_use_implicit_by_val_modifier_highlighting = hint resharper_use_indexed_property_highlighting = suggestion resharper_use_index_from_end_expression_highlighting = suggestion resharper_use_is_operator_1_highlighting = suggestion resharper_use_is_operator_2_highlighting = suggestion resharper_use_method_any_0_highlighting = suggestion resharper_use_method_any_1_highlighting = suggestion resharper_use_method_any_2_highlighting = suggestion resharper_use_method_any_3_highlighting = suggestion resharper_use_method_any_4_highlighting = suggestion resharper_use_method_is_instance_of_type_highlighting = suggestion resharper_use_nameof_expression_for_part_of_the_string_highlighting = none resharper_use_nameof_expression_highlighting = suggestion resharper_use_name_of_instead_of_type_of_highlighting = suggestion resharper_use_negated_pattern_in_is_expression_highlighting = hint resharper_use_negated_pattern_matching_highlighting = hint resharper_use_nullable_annotation_instead_of_attribute_highlighting = suggestion resharper_use_nullable_attributes_supported_by_compiler_highlighting = suggestion resharper_use_nullable_reference_types_annotation_syntax_highlighting = warning resharper_use_null_propagation_highlighting = hint resharper_use_null_propagation_when_possible_highlighting = none resharper_use_object_or_collection_initializer_highlighting = suggestion resharper_use_pattern_matching_highlighting = suggestion resharper_use_positional_deconstruction_pattern_highlighting = none resharper_use_string_interpolation_highlighting = suggestion resharper_use_switch_case_pattern_variable_highlighting = suggestion resharper_use_verbatim_string_highlighting = hint resharper_value_parameter_not_used_highlighting = warning resharper_value_range_attribute_violation_highlighting = warning resharper_variable_can_be_not_nullable_highlighting = warning resharper_variable_hides_outer_variable_highlighting = warning resharper_vb_check_for_reference_equality_instead_1_highlighting = suggestion resharper_vb_check_for_reference_equality_instead_2_highlighting = suggestion resharper_vb_possible_mistaken_argument_highlighting = warning resharper_vb_possible_mistaken_call_to_get_type_1_highlighting = warning resharper_vb_possible_mistaken_call_to_get_type_2_highlighting = warning resharper_vb_remove_to_list_1_highlighting = suggestion resharper_vb_remove_to_list_2_highlighting = suggestion resharper_vb_replace_with_first_or_default_highlighting = suggestion resharper_vb_replace_with_last_or_default_highlighting = suggestion resharper_vb_replace_with_of_type_1_highlighting = suggestion resharper_vb_replace_with_of_type_2_highlighting = suggestion resharper_vb_replace_with_of_type_any_1_highlighting = suggestion resharper_vb_replace_with_of_type_any_2_highlighting = suggestion resharper_vb_replace_with_of_type_count_1_highlighting = suggestion resharper_vb_replace_with_of_type_count_2_highlighting = suggestion resharper_vb_replace_with_of_type_first_1_highlighting = suggestion resharper_vb_replace_with_of_type_first_2_highlighting = suggestion resharper_vb_replace_with_of_type_first_or_default_1_highlighting = suggestion resharper_vb_replace_with_of_type_first_or_default_2_highlighting = suggestion resharper_vb_replace_with_of_type_last_1_highlighting = suggestion resharper_vb_replace_with_of_type_last_2_highlighting = suggestion resharper_vb_replace_with_of_type_last_or_default_1_highlighting = suggestion resharper_vb_replace_with_of_type_last_or_default_2_highlighting = suggestion resharper_vb_replace_with_of_type_single_1_highlighting = suggestion resharper_vb_replace_with_of_type_single_2_highlighting = suggestion resharper_vb_replace_with_of_type_single_or_default_1_highlighting = suggestion resharper_vb_replace_with_of_type_single_or_default_2_highlighting = suggestion resharper_vb_replace_with_of_type_where_highlighting = suggestion resharper_vb_replace_with_single_assignment_1_highlighting = suggestion resharper_vb_replace_with_single_assignment_2_highlighting = suggestion resharper_vb_replace_with_single_call_to_any_highlighting = suggestion resharper_vb_replace_with_single_call_to_count_highlighting = suggestion resharper_vb_replace_with_single_call_to_first_highlighting = suggestion resharper_vb_replace_with_single_call_to_first_or_default_highlighting = suggestion resharper_vb_replace_with_single_call_to_last_highlighting = suggestion resharper_vb_replace_with_single_call_to_last_or_default_highlighting = suggestion resharper_vb_replace_with_single_call_to_single_highlighting = suggestion resharper_vb_replace_with_single_call_to_single_or_default_highlighting = suggestion resharper_vb_replace_with_single_or_default_highlighting = suggestion resharper_vb_simplify_linq_expression_10_highlighting = hint resharper_vb_simplify_linq_expression_1_highlighting = suggestion resharper_vb_simplify_linq_expression_2_highlighting = suggestion resharper_vb_simplify_linq_expression_3_highlighting = suggestion resharper_vb_simplify_linq_expression_4_highlighting = suggestion resharper_vb_simplify_linq_expression_5_highlighting = suggestion resharper_vb_simplify_linq_expression_6_highlighting = suggestion resharper_vb_simplify_linq_expression_7_highlighting = hint resharper_vb_simplify_linq_expression_8_highlighting = hint resharper_vb_simplify_linq_expression_9_highlighting = hint resharper_vb_string_compare_is_culture_specific_1_highlighting = warning resharper_vb_string_compare_is_culture_specific_2_highlighting = warning resharper_vb_string_compare_is_culture_specific_3_highlighting = warning resharper_vb_string_compare_is_culture_specific_4_highlighting = warning resharper_vb_string_compare_is_culture_specific_5_highlighting = warning resharper_vb_string_compare_is_culture_specific_6_highlighting = warning resharper_vb_string_compare_to_is_culture_specific_highlighting = warning resharper_vb_string_ends_with_is_culture_specific_highlighting = none resharper_vb_string_index_of_is_culture_specific_1_highlighting = warning resharper_vb_string_index_of_is_culture_specific_2_highlighting = warning resharper_vb_string_index_of_is_culture_specific_3_highlighting = warning resharper_vb_string_last_index_of_is_culture_specific_1_highlighting = warning resharper_vb_string_last_index_of_is_culture_specific_2_highlighting = warning resharper_vb_string_last_index_of_is_culture_specific_3_highlighting = warning resharper_vb_string_starts_with_is_culture_specific_highlighting = none resharper_vb_unreachable_code_highlighting = warning resharper_vb_use_array_creation_expression_1_highlighting = suggestion resharper_vb_use_array_creation_expression_2_highlighting = suggestion resharper_vb_use_first_instead_highlighting = warning resharper_vb_use_method_any_1_highlighting = suggestion resharper_vb_use_method_any_2_highlighting = suggestion resharper_vb_use_method_any_3_highlighting = suggestion resharper_vb_use_method_any_4_highlighting = suggestion resharper_vb_use_method_any_5_highlighting = suggestion resharper_vb_use_method_is_instance_of_type_highlighting = suggestion resharper_vb_use_type_of_is_operator_1_highlighting = suggestion resharper_vb_use_type_of_is_operator_2_highlighting = suggestion resharper_virtual_member_call_in_constructor_highlighting = warning resharper_virtual_member_never_overridden_global_highlighting = suggestion resharper_virtual_member_never_overridden_local_highlighting = suggestion resharper_void_method_with_must_use_return_value_attribute_highlighting = warning resharper_web_config_module_not_resolved_highlighting = warning resharper_web_config_module_qualification_resolve_highlighting = warning resharper_web_config_redundant_add_namespace_tag_highlighting = warning resharper_web_config_redundant_location_tag_highlighting = warning resharper_web_config_tag_prefix_redundand_highlighting = warning resharper_web_config_type_not_resolved_highlighting = warning resharper_web_config_unused_add_tag_highlighting = warning resharper_web_config_unused_element_due_to_config_source_attribute_highlighting = warning resharper_web_config_unused_remove_or_clear_tag_highlighting = warning resharper_web_config_web_config_path_warning_highlighting = warning resharper_web_config_wrong_module_highlighting = warning resharper_web_ignored_path_highlighting = none resharper_web_mapped_path_highlighting = hint resharper_with_expression_instead_of_initializer_highlighting = suggestion resharper_wrong_indent_size_highlighting = none resharper_xaml_assign_null_to_not_null_attribute_highlighting = warning resharper_xaml_avalonia_wrong_binding_mode_for_stream_binding_operator_highlighting = warning resharper_xaml_binding_without_context_not_resolved_highlighting = hint resharper_xaml_binding_with_context_not_resolved_highlighting = warning resharper_xaml_compiled_binding_missing_data_type_error_highlighting_highlighting = error resharper_xaml_constructor_warning_highlighting = warning resharper_xaml_decimal_parsing_is_culture_dependent_highlighting = warning resharper_xaml_dependency_property_resolve_error_highlighting = warning resharper_xaml_duplicate_style_setter_highlighting = warning resharper_xaml_dynamic_resource_error_highlighting = error resharper_xaml_element_name_reference_not_resolved_highlighting = error resharper_xaml_empty_grid_length_definition_highlighting = error resharper_xaml_grid_definitions_can_be_converted_to_attribute_highlighting = hint resharper_xaml_ignored_path_highlighting_highlighting = none resharper_xaml_index_out_of_grid_definition_highlighting = warning resharper_xaml_invalid_member_type_highlighting = error resharper_xaml_invalid_resource_target_type_highlighting = error resharper_xaml_invalid_resource_type_highlighting = error resharper_xaml_invalid_type_highlighting = error resharper_xaml_language_level_highlighting = error resharper_xaml_mapped_path_highlighting_highlighting = hint resharper_xaml_method_arguments_will_be_ignored_highlighting = warning resharper_xaml_missing_grid_index_highlighting = warning resharper_xaml_overloads_collision_highlighting = warning resharper_xaml_parent_is_out_of_current_component_tree_highlighting = warning resharper_xaml_path_error_highlighting = warning resharper_xaml_possible_null_reference_exception_highlighting = suggestion resharper_xaml_redundant_attached_property_highlighting = warning resharper_xaml_redundant_binding_mode_attribute_highlighting = warning resharper_xaml_redundant_collection_property_highlighting = warning resharper_xaml_redundant_freeze_attribute_highlighting = warning resharper_xaml_redundant_grid_definitions_highlighting = warning resharper_xaml_redundant_grid_span_highlighting = warning resharper_xaml_redundant_modifiers_attribute_highlighting = warning resharper_xaml_redundant_namespace_alias_highlighting = warning resharper_xaml_redundant_name_attribute_highlighting = warning resharper_xaml_redundant_property_type_qualifier_highlighting = warning resharper_xaml_redundant_resource_highlighting = warning resharper_xaml_redundant_styled_value_highlighting = warning resharper_xaml_redundant_update_source_trigger_attribute_highlighting = warning resharper_xaml_redundant_xamarin_forms_class_declaration_highlighting = warning resharper_xaml_resource_file_path_case_mismatch_highlighting = warning resharper_xaml_routed_event_resolve_error_highlighting = warning resharper_xaml_static_resource_not_resolved_highlighting = warning resharper_xaml_style_class_not_found_highlighting = warning resharper_xaml_style_invalid_target_type_highlighting = error resharper_xaml_unexpected_text_token_highlighting = error resharper_xaml_xaml_duplicate_device_family_type_view_highlighting_highlighting = error resharper_xaml_xaml_mismatched_device_family_view_clr_name_highlighting_highlighting = warning resharper_xaml_xaml_relative_source_default_mode_warning_highlighting_highlighting = warning resharper_xaml_xaml_unknown_device_family_type_highlighting_highlighting = warning resharper_xaml_xaml_xamarin_forms_data_type_and_binding_context_type_mismatched_highlighting_highlighting = warning resharper_xaml_x_key_attribute_disallowed_highlighting = error resharper_xunit_xunit_test_with_console_output_highlighting = warning [{*.cql,*.ddl,*.sql}] indent_style = space indent_size = 4 [.editorconfig] indent_style = space indent_size = 4 [*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,hlsl,hlsli,hlslinc,master,nuspec,paml,razor,resw,resx,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}] indent_style = space indent_size = 2 tab_width = 2 ================================================ FILE: .gitattributes ================================================ ############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto ############################################################################### # Set default behavior for command prompt diff. # # This is need for earlier builds of msysgit that does not have it on by # default for csharp files. # Note: This is only used by command line ############################################################################### #*.cs diff=csharp ############################################################################### # Set the merge driver for project and solution files # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS # the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below ############################################################################### #*.sln merge=binary #*.csproj merge=binary #*.vbproj merge=binary #*.vcxproj merge=binary #*.vcproj merge=binary #*.dbproj merge=binary #*.fsproj merge=binary #*.lsproj merge=binary #*.wixproj merge=binary #*.modelproj merge=binary #*.sqlproj merge=binary #*.wwaproj merge=binary ############################################################################### # behavior for image files # # image files are treated as binary by default. ############################################################################### #*.jpg binary #*.png binary #*.gif binary ############################################################################### # diff behavior for common document formats # # Convert binary document formats to text before diffing them. This feature # is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain #*.DOC diff=astextplain #*.docx diff=astextplain #*.DOCX diff=astextplain #*.dot diff=astextplain #*.DOT diff=astextplain #*.pdf diff=astextplain #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain ================================================ FILE: .github/workflows/ci.yml ================================================ name: Verification build on: workflow_dispatch: pull_request: branches: ["master"] paths: - "src/**" - "test/**" permissions: write-all jobs: build-and-test: runs-on: ubuntu-latest services: postgres: image: postgres env: POSTGRES_PASSWORD: postgres POSTGRES_HOST_AUTH_METHOD: trust TZ: UTC+13 PGTZ: UTC+13 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432:5432 steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: 9.0.x - name: Restore dependencies run: dotnet restore - name: Build run: dotnet build --no-restore - name: Test run: dotnet test --no-build --verbosity normal --logger "trx;LogFileName=TestResults.trx" - name: Test Report uses: actions/upload-artifact@v4 if: success() || failure() with: name: test-results path: "**/TestResults.trx" ================================================ FILE: .github/workflows/pack.yml ================================================ name: Build and tests on: workflow_dispatch: push: branches: ["master"] paths: ["src/**"] jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: 9.0.x - name: Restore dependencies run: dotnet restore - name: Build run: dotnet build --no-restore -c Release - name: Pack run: dotnet pack -c Release --no-build --output publish - name: Upload a Build Artifact uses: actions/upload-artifact@v4 with: name: Package path: publish/**.nupkg test: permissions: write-all runs-on: ubuntu-latest services: postgres: image: postgres env: POSTGRES_PASSWORD: postgres POSTGRES_HOST_AUTH_METHOD: trust TZ: UTC+13 PGTZ: UTC+13 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432:5432 steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: 9.0.x - name: Restore dependencies run: dotnet restore - name: Build run: dotnet build -c Release --no-restore - name: Test run: dotnet test -c Release --no-build --verbosity normal --logger "trx;LogFileName=TestResults.trx" || true - name: Test Report uses: dorny/test-reporter@v1 if: always() with: name: Test results path: "**/TestResults.trx" reporter: dotnet-trx fail-on-error: true ================================================ FILE: .github/workflows/test-report.yml ================================================ name: Test report on: workflow_run: workflows: ['Verification build', 'Build and tests'] types: - completed permissions: id-token: write contents: read checks: write jobs: report: runs-on: ubuntu-latest steps: - uses: dorny/test-reporter@v1 with: artifact: test-results name: Test results path: "**/TestResults.trx" reporter: dotnet-trx fail-on-error: false ================================================ FILE: .gitignore ================================================ ################# ## Eclipse ################# *.pydevproject .project .metadata bin/ tmp/ *.tmp *.bak *.swp *~.nib local.properties .classpath .settings/ .loadpath # External tool builders .externalToolBuilders/ # Locally stored "Eclipse launch configurations" *.launch # CDT-specific .cproject # PDT-specific .buildpath ################# ## Visual Studio ################# ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.sln.docstates packages/ # Build results [Bb]uild/ [Dd]ebug/ [Rr]elease/ x64/ [Bb]in/ [Oo]bj/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* *_i.c *_p.c *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.log *.scc # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch *.ncrunch* .*crunch*.local.xml # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.Publish.xml *.pubxml # NuGet Packages Directory ## TODO: If you have NuGet Package Restore enabled, uncomment the next line #packages/ # Windows Azure Build Output csx *.build.csdef # Windows Store app package directory AppPackages/ # Others sql/ *.Cache ClientBin/ [Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.[Pp]ublish.xml *.pfx *.publishsettings # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file to a newer # Visual Studio version. Backup files are not needed, because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files App_Data/*.mdf App_Data/*.ldf ############# ## Windows detritus ############# # Windows image file caches Thumbs.db ehthumbs.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Mac crap .DS_Store ############# ## Python ############# *.py[co] # Packages *.egg *.egg-info dist/ eggs/ parts/ var/ sdist/ develop-eggs/ .installed.cfg # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox #Translations *.mo #Mr Developer .mr.developer.cfg #Sphinx Built Docs docs/_build #Jekyll site builds _site *.userprefs src/Hangfire.PostgreSql/.vs/restore.dg src/Hangfire.PostgreSql/project.lock.json */**/project.lock.json tools .vs .idea ================================================ FILE: .vscode/settings.json ================================================ { "sqltools.connections": [ { "previewLimit": 50, "server": "localhost", "port": 5432, "driver": "PostgreSQL", "name": "localhost", "database": "postgres", "username": "postgres", "password": "password" } ] } ================================================ FILE: COPYING ================================================ GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ================================================ FILE: COPYING.LESSER ================================================ GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ================================================ FILE: Hangfire.PostgreSql.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.33424.131 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{766BE831-F758-46BC-AFD3-BBEEFE0F686F}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5CA38188-92EE-453C-A04E-A506DF15A787}" ProjectSection(SolutionItems) = preProject README.md = README.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0D30A51B-814F-474E-93B8-44E9C155255C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hangfire.PostgreSql", "src\Hangfire.PostgreSql\Hangfire.PostgreSql.csproj", "{3E4DBC41-F38E-4D1C-A6A7-206A132A29D6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hangfire.PostgreSql.Tests", "tests\Hangfire.PostgreSql.Tests\Hangfire.PostgreSql.Tests.csproj", "{6044A48D-730B-4D1F-B03A-EB2B458DAF53}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{AAA78654-9846-4870-A13C-D9DBAF0792C4}" ProjectSection(SolutionItems) = preProject .github\workflows\ci.yml = .github\workflows\ci.yml .github\workflows\pack.yml = .github\workflows\pack.yml EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {3E4DBC41-F38E-4D1C-A6A7-206A132A29D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3E4DBC41-F38E-4D1C-A6A7-206A132A29D6}.Debug|Any CPU.Build.0 = Debug|Any CPU {3E4DBC41-F38E-4D1C-A6A7-206A132A29D6}.Release|Any CPU.ActiveCfg = Release|Any CPU {3E4DBC41-F38E-4D1C-A6A7-206A132A29D6}.Release|Any CPU.Build.0 = Release|Any CPU {6044A48D-730B-4D1F-B03A-EB2B458DAF53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6044A48D-730B-4D1F-B03A-EB2B458DAF53}.Debug|Any CPU.Build.0 = Debug|Any CPU {6044A48D-730B-4D1F-B03A-EB2B458DAF53}.Release|Any CPU.ActiveCfg = Release|Any CPU {6044A48D-730B-4D1F-B03A-EB2B458DAF53}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {3E4DBC41-F38E-4D1C-A6A7-206A132A29D6} = {0D30A51B-814F-474E-93B8-44E9C155255C} {6044A48D-730B-4D1F-B03A-EB2B458DAF53} = {766BE831-F758-46BC-AFD3-BBEEFE0F686F} {AAA78654-9846-4870-A13C-D9DBAF0792C4} = {5CA38188-92EE-453C-A04E-A506DF15A787} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F7E32105-7F61-4127-8517-5E4275B9CABE} EndGlobalSection EndGlobal ================================================ FILE: Hangfire.PostgreSql.sln.DotSettings ================================================  True True True True True True <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Hangfire.Core.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Hangfire.SqlServer.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> <data /> ================================================ FILE: LICENSE.md ================================================ License ======== Copyright © 2014-2022 Frank Hommers https://github.com/frankhommers/Hangfire.PostgreSql and others (Burhan Irmikci (barhun), Zachary Sims( zsims), kgamecarter, Stafford Williams (staff0rd), briangweber, Viktor Svyatokha (ahydrax), Christopher Dresel (Dresel), Vytautas Kasparavičius (vytautask), Vincent Vrijburg, David Roth (davidroth). Hangfire.PostgreSql is an Open Source project licensed under the terms of the LGPLv3 license. Please see http://www.gnu.org/licenses/lgpl-3.0.html for license text or COPYING.LESSER file distributed with the source code. This work is based on the work of Sergey Odinokov, author of Hangfire. ================================================ FILE: README.md ================================================ # Hangfire.PostgreSql [![Build status](https://github.com/hangfire-postgres/Hangfire.PostgreSql/actions/workflows/pack.yml/badge.svg)](https://github.com/hangfire-postgres/Hangfire.PostgreSql/actions/workflows/pack.yml) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/hangfire-postgres/Hangfire.PostgreSql?label=Release)](https://github.com/hangfire-postgres/Hangfire.PostgreSql/releases/latest) [![Nuget](https://img.shields.io/nuget/v/Hangfire.PostgreSql?label=NuGet)](https://www.nuget.org/packages/Hangfire.PostgreSql) This is an plugin to the Hangfire to enable PostgreSQL as a storage system. Read about hangfire here: https://github.com/HangfireIO/Hangfire#overview and here: http://hangfire.io/ ## Instructions ### For .NET Install Hangfire, see https://github.com/HangfireIO/Hangfire#installation Download all files from this repository, add the Hangfire.PostgreSql.csproj to your solution. Reference it in your project, and you are ready to go by using: ```csharp app.UseHangfireServer(new BackgroundJobServerOptions(), new PostgreSqlStorage("")); app.UseHangfireDashboard(); ``` ### For ASP.NET Core First, NuGet package needs installation. - Hangfire.AspNetCore - Hangfire.PostgreSql (Uses Npgsql 6) - Hangfire.PostgreSql.Npgsql5 (Uses Npgsql 5) Historically both packages were functionally the same up until the package version 1.9.11, the only difference was the underlying Npgsql dependency version. Afterwards, the support for Npgsql v5 has been dropped and now minimum required version is 6.0.0. In `Startup.cs` _ConfigureServices(IServiceCollection services)_ method add the following line: ```csharp services.AddHangfire(config => config.UsePostgreSqlStorage(c => c.UseNpgsqlConnection(Configuration.GetConnectionString("HangfireConnection")))); ``` In Configure method, add these two lines: ```csharp app.UseHangfireServer(); app.UseHangfireDashboard(); ``` And... That's it. You are ready to go. If you encounter any issues/bugs or have idea of a feature regarding Hangfire.Postgresql, [create us an issue](https://github.com/hangfire-postgres/Hangfire.PostgreSql/issues/new). Thanks! ### Enabling SSL support SSL support can be enabled for Hangfire.PostgreSql library using the following mechanism: ```csharp config.UsePostgreSqlStorage(c => c.UseNpgsqlConnection( Configuration.GetConnectionString("HangfireConnection"), // connection string, connection => // connection setup - gets called after instantiating the connection and before any calls to DB are made { connection.ProvideClientCertificatesCallback += clientCerts => { clientCerts.Add(X509Certificate.CreateFromCertFile("[CERT_FILENAME]")); }; } ) ); ``` ### Queue processing Similar to `Hangfire.SqlServer`, queues are processed in alphabetical order. Given the following example ```csharp var options = new BackgroundJobServerOptions { Queues = new[] { "general-queue", "very-fast-queue", "a-long-running-queue" } }; app.UseHangfireServer(options); ``` this provider would first process jobs in `a-long-running-queue`, then `general-queue` and lastly `very-fast-queue`. ### Startup resilience and transient database outages Starting from version 1.20.13 (where `PostgreSqlStorageOptions` gained startup resilience options), the storage tries to be more tolerant to *transient* PostgreSQL outages during application startup. #### Default behavior By default, when you use the new-style configuration: ```csharp services.AddHangfire((provider, config) => { config.UsePostgreSqlStorage(opts => opts.UseNpgsqlConnection(Configuration.GetConnectionString("HangfireConnection"))); }); app.UseHangfireServer(); app.UseHangfireDashboard(); ``` `PostgreSqlStorageOptions` uses the following defaults for startup resilience: - `PrepareSchemaIfNecessary = true` - `StartupConnectionMaxRetries = 5` - `StartupConnectionBaseDelay = 1 second` - `StartupConnectionMaxDelay = 1 minute` - `AllowDegradedModeWithoutStorage = true` With these defaults: 1. On application startup, when schema preparation is required, the storage will try to open a connection and install/upgrade the schema. 2. If the database is temporarily unavailable, it will retry the operation up to 6 times (1 initial attempt + 5 retries) with exponential backoff between attempts, capped at 1 minute. 3. If all attempts fail **during startup**, the storage enters a *degraded* state instead of crashing the whole process. Your ASP.NET Core application can still start and serve other endpoints that do not depend on Hangfire. 4. On the *first actual use* of the storage (e.g. dashboard, background job server), Hangfire will try to initialize again. If the database is available by then, initialization succeeds and everything works as usual. If it is still unavailable, an `InvalidOperationException` with the original database exception as `InnerException` is thrown at that call site. This behavior is designed to make applications more robust in scenarios where the database may briefly lag behind the application during deployments or orchestrated restarts. #### Opting out of resilient startup (fail fast) If you prefer to fail the whole process immediately if the database is not reachable during startup – you can disable retries by setting `StartupConnectionMaxRetries` to `0`: ```csharp var storageOptions = new PostgreSqlStorageOptions { PrepareSchemaIfNecessary = true, StartupConnectionMaxRetries = 0, // disables resilient startup AllowDegradedModeWithoutStorage = false, // fail fast if DB is down at startup }; services.AddHangfire((provider, config) => { config.UsePostgreSqlStorage(opts => opts.UseNpgsqlConnection(Configuration.GetConnectionString("HangfireConnection")), storageOptions); }); ``` With this configuration: - A single attempt is made to open a connection and prepare the schema. - If that attempt fails, the storage constructor throws and application startup fails. #### Controlling degraded mode Degraded mode is controlled via `AllowDegradedModeWithoutStorage`: - `true` (default): if all startup attempts fail, both startup and lazy initialization will keep the storage in an uninitialized state on failure and retry on subsequent uses, until initialization eventually succeeds. - `false`: if all startup attempts fail, the storage constructor will throw an `InvalidOperationException("Failed to initialize Hangfire PostgreSQL storage.", innerException)`. For example, to keep retries but still fail startup if the DB never becomes available: ```csharp var storageOptions = new PostgreSqlStorageOptions { PrepareSchemaIfNecessary = true, StartupConnectionMaxRetries = 10, // more aggressive retry policy StartupConnectionBaseDelay = TimeSpan.FromSeconds(2), StartupConnectionMaxDelay = TimeSpan.FromMinutes(2), AllowDegradedModeWithoutStorage = false, // do not start the app without storage }; ``` #### Turning off schema preparation entirely If you manage the Hangfire schema yourself (for example via migrations or a dedicated deployment step) and do not want the storage to touch the database during startup or on first use, set `PrepareSchemaIfNecessary = false`: ```csharp var storageOptions = new PostgreSqlStorageOptions { PrepareSchemaIfNecessary = false, // no schema installation/upgrade }; ``` In this case: - No schema initialization is performed by `PostgreSqlStorage`. - The first query that actually needs the database will fail if the schema is missing or mismatched, so you must ensure it is created/updated out of band. > Note: startup resilience settings (`StartupConnectionMaxRetries`, `AllowDegradedModeWithoutStorage`, etc.) only apply when `PrepareSchemaIfNecessary` is `true`. ### License Copyright © 2014-2024 Frank Hommers https://github.com/hangfire-postgres/Hangfire.PostgreSql. Collaborators: Frank Hommers (frankhommers), Vytautas Kasparavičius (vytautask), Žygimantas Arūna (azygis) Contributors: Andrew Armstrong (Plasma), Burhan Irmikci (barhun), Zachary Sims(zsims), kgamecarter, Stafford Williams (staff0rd), briangweber, Viktor Svyatokha (ahydrax), Christopher Dresel (Dresel), Vincent Vrijburg, David Roth (davidroth) and Ivan Tiniakov (Tinyakov). Hangfire.PostgreSql is an Open Source project licensed under the terms of the LGPLv3 license. Please see http://www.gnu.org/licenses/lgpl-3.0.html for license text or COPYING.LESSER file distributed with the source code. This work is based on the work of Sergey Odinokov, author of Hangfire. ### Related Projects - [Hangfire.Core](https://github.com/HangfireIO/Hangfire) ================================================ FILE: SECURITY.md ================================================ # Security Policy ## Supported Versions Use this section to tell people about which versions of your project are currently being supported with security updates. | Version | Supported | | ------- | ------------------ | | 1.6.3 | :white_check_mark: | | < 1.6.3 | :x: | ## Reporting a Vulnerability Do not create a new issue ticket with the detailed information of the vulnerability under any circumstances! All vulnerabilities can be reported directly to the main project maintainers (currently @frankfommers and @vytautask) directly via email (frank-hfpg[at]hommers[dot]nl or vytautaskasp[at]gmail[dot]com respectively). We should get back to you in 24 hours. Please include as much information as possible (if you have a POC - even better). All security issues have our highest attention and will be solved ASAP. You would help us out if you also include a fix in a pull request. That way we can quickly release a fix. Thank you for your understanding. ================================================ FILE: src/Common/Hangfire.ruleset ================================================  ================================================ FILE: src/Common/Hangfire.targets ================================================ $(ArtifactsDir)\$(MSBuildProjectName) $(ArtifactsDir)\$(MSBuildProjectName)\bin $(MSBuildThisFileDirectory)Hangfire.ruleset false 1591 false $(DefineConstants);CODE_ANALYSIS 11.0 $(DefineConstants);MONO $(DefineConstants);SIGNED true true $(KeyFile) GlobalSuppressions.cs ================================================ FILE: src/Hangfire.PostgreSql/CountersAggregator.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using System.Threading; using Dapper; using Hangfire.Common; using Hangfire.Logging; using Hangfire.Server; namespace Hangfire.PostgreSql { #pragma warning disable 618 internal class CountersAggregator : IServerComponent #pragma warning restore 618 { // This number should be high enough to aggregate counters efficiently, // but low enough to not to cause large amount of row locks to be taken. // Lock escalation to page locks may pause the background processing. private const int NumberOfRecordsInSinglePass = 1000; private static readonly TimeSpan _delayBetweenPasses = TimeSpan.FromMilliseconds(500); private readonly ILog _logger = LogProvider.For(); private readonly TimeSpan _interval; private readonly PostgreSqlStorage _storage; public CountersAggregator(PostgreSqlStorage storage, TimeSpan interval) { _storage = storage ?? throw new ArgumentNullException(nameof(storage)); _interval = interval; } public void Execute(CancellationToken cancellationToken) { _logger.Debug("Aggregating records in 'Counter' table..."); int removedCount = 0; do { _storage.UseConnection(null, connection => { removedCount = connection.Execute(GetAggregationQuery(), new { now = DateTime.UtcNow, count = NumberOfRecordsInSinglePass }, commandTimeout: 0); }); if (removedCount < NumberOfRecordsInSinglePass) { continue; } cancellationToken.Wait(_delayBetweenPasses); cancellationToken.ThrowIfCancellationRequested(); // ReSharper disable once LoopVariableIsNeverChangedInsideLoop } while (removedCount >= NumberOfRecordsInSinglePass); _logger.Trace("Records from the 'Counter' table aggregated."); cancellationToken.Wait(_interval); } private string GetAggregationQuery() { string schemaName = _storage.Options.SchemaName; return $""" BEGIN; INSERT INTO "{schemaName}"."aggregatedcounter" ("key", "value", "expireat") SELECT "key", SUM("value"), MAX("expireat") FROM "{schemaName}"."counter" GROUP BY "key" ON CONFLICT("key") DO UPDATE SET "value" = "aggregatedcounter"."value" + EXCLUDED."value", "expireat" = EXCLUDED."expireat"; DELETE FROM "{schemaName}"."counter" WHERE "key" IN ( SELECT "key" FROM "{schemaName}"."aggregatedcounter" ); COMMIT; """; } } } ================================================ FILE: src/Hangfire.PostgreSql/EnqueuedAndFetchedCountDto.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. namespace Hangfire.PostgreSql { public class EnqueuedAndFetchedCountDto { public long EnqueuedCount { get; set; } public long FetchedCount { get; set; } } } ================================================ FILE: src/Hangfire.PostgreSql/Entities/JobParameter.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using Hangfire.PostgreSql.Properties; namespace Hangfire.PostgreSql.Entities { [UsedImplicitly] internal class JobParameter { public long JobId { get; set; } public string Name { get; set; } public string Value { get; set; } } } ================================================ FILE: src/Hangfire.PostgreSql/Entities/Server.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using Hangfire.PostgreSql.Properties; namespace Hangfire.PostgreSql.Entities { [UsedImplicitly] internal class Server { public string Id { get; set; } public string Data { get; set; } public DateTime LastHeartbeat { get; set; } } } ================================================ FILE: src/Hangfire.PostgreSql/Entities/ServerData.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; namespace Hangfire.PostgreSql.Entities { internal class ServerData { public int WorkerCount { get; set; } public string[] Queues { get; set; } public DateTime? StartedAt { get; set; } } } ================================================ FILE: src/Hangfire.PostgreSql/Entities/SqlHash.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using Hangfire.PostgreSql.Properties; namespace Hangfire.PostgreSql.Entities { [UsedImplicitly] internal class SqlHash { public long Id { get; set; } public string Key { get; set; } public string Field { get; set; } public string Value { get; set; } public DateTime? ExpireAt { get; set; } } } ================================================ FILE: src/Hangfire.PostgreSql/Entities/SqlJob.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright � 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using Hangfire.PostgreSql.Properties; namespace Hangfire.PostgreSql.Entities { [UsedImplicitly] internal class SqlJob { public long Id { get; set; } public string InvocationData { get; set; } public string Arguments { get; set; } public DateTime CreatedAt { get; set; } public DateTime? ExpireAt { get; set; } public DateTime? FetchedAt { get; set; } public string StateName { get; set; } public string StateReason { get; set; } public string StateData { get; set; } } } ================================================ FILE: src/Hangfire.PostgreSql/Entities/SqlState.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using Hangfire.PostgreSql.Properties; namespace Hangfire.PostgreSql.Entities { [UsedImplicitly] internal class SqlState { public long JobId { get; set; } public string Name { get; set; } public string Reason { get; set; } public DateTime CreatedAt { get; set; } public string Data { get; set; } } } ================================================ FILE: src/Hangfire.PostgreSql/EnvironmentHelpers.cs ================================================ using System; namespace Hangfire.PostgreSql { internal class EnvironmentHelpers { private static bool? _isMono; public static bool IsMono() { return _isMono ??= Type.GetType("Mono.Runtime") != null; } } } ================================================ FILE: src/Hangfire.PostgreSql/ExpirationManager.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using System.Data; using System.Globalization; using System.Threading; using Dapper; using Hangfire.Logging; using Hangfire.Server; using Hangfire.Storage; namespace Hangfire.PostgreSql { #pragma warning disable CS0618 internal class ExpirationManager : IBackgroundProcess, IServerComponent #pragma warning restore CS0618 { private const string DistributedLockKey = "locks:expirationmanager"; private static readonly TimeSpan _defaultLockTimeout = TimeSpan.FromMinutes(5); private static readonly TimeSpan _delayBetweenPasses = TimeSpan.FromSeconds(1); private static readonly ILog _logger = LogProvider.GetLogger(typeof(ExpirationManager)); private static readonly string[] _processedCounters = { "stats:succeeded", "stats:deleted", }; private static readonly string[] _processedTables = { "aggregatedcounter", "counter", "job", "list", "set", "hash", }; private readonly TimeSpan _checkInterval; private readonly PostgreSqlStorage _storage; public ExpirationManager(PostgreSqlStorage storage) : this(storage ?? throw new ArgumentNullException(nameof(storage)), storage.Options.JobExpirationCheckInterval) { } public ExpirationManager(PostgreSqlStorage storage, TimeSpan checkInterval) { _storage = storage ?? throw new ArgumentNullException(nameof(storage)); _checkInterval = checkInterval; } public void Execute(BackgroundProcessContext context) { Execute(context.StoppingToken); } public void Execute(CancellationToken cancellationToken) { foreach (string table in _processedTables) { _logger.DebugFormat("Removing outdated records from table '{0}'...", table); UseConnectionDistributedLock(_storage, connection => { int removedCount; do { using IDbTransaction transaction = connection.BeginTransaction(); removedCount = connection.Execute($@" DELETE FROM ""{_storage.Options.SchemaName}"".""{table}"" WHERE ""id"" IN ( SELECT ""id"" FROM ""{_storage.Options.SchemaName}"".""{table}"" WHERE ""expireat"" < NOW() LIMIT {_storage.Options.DeleteExpiredBatchSize.ToString(CultureInfo.InvariantCulture)} )", transaction: transaction); if (removedCount <= 0) { continue; } transaction.Commit(); _logger.InfoFormat("Removed {0} outdated record(s) from '{1}' table.", removedCount, table); cancellationToken.WaitHandle.WaitOne(_delayBetweenPasses); cancellationToken.ThrowIfCancellationRequested(); } while (removedCount != 0); }); } AggregateCounters(cancellationToken); cancellationToken.WaitHandle.WaitOne(_checkInterval); } public override string ToString() { return "SQL Records Expiration Manager"; } private void AggregateCounters(CancellationToken cancellationToken) { foreach (string processedCounter in _processedCounters) { AggregateCounter(processedCounter); cancellationToken.ThrowIfCancellationRequested(); } } private void AggregateCounter(string counterName) { UseConnectionDistributedLock(_storage, connection => { using IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); string aggregateQuery = $@" WITH ""counters"" AS ( DELETE FROM ""{_storage.Options.SchemaName}"".""counter"" WHERE ""key"" = @Key AND ""expireat"" IS NULL RETURNING * ) SELECT SUM(""value"") FROM ""counters""; "; long aggregatedValue = connection.ExecuteScalar(aggregateQuery, new { Key = counterName }, transaction); transaction.Commit(); if (aggregatedValue > 0) { string insertQuery = $@"INSERT INTO ""{_storage.Options.SchemaName}"".""counter""(""key"", ""value"") VALUES (@Key, @Value);"; connection.Execute(insertQuery, new { Key = counterName, Value = aggregatedValue }); } }); } private void UseConnectionDistributedLock(PostgreSqlStorage storage, Action action) { try { storage.UseConnection(null, connection => { PostgreSqlDistributedLock.Acquire(connection, DistributedLockKey, _defaultLockTimeout, _storage.Options); try { action(connection); } finally { PostgreSqlDistributedLock.Release(connection, DistributedLockKey, _storage.Options); } }); } catch (DistributedLockTimeoutException e) when (e.Resource == DistributedLockKey) { // DistributedLockTimeoutException here doesn't mean that outdated records weren't removed. // It just means another Hangfire server did this work. _logger.Log(LogLevel.Debug, () => $@"An exception was thrown during acquiring distributed lock on the {DistributedLockKey} resource within {_defaultLockTimeout.TotalSeconds} seconds. Outdated records were not removed. It will be retried in {_checkInterval.TotalSeconds} seconds.", e); } } } } ================================================ FILE: src/Hangfire.PostgreSql/Factories/ExistingNpgsqlConnectionFactory.cs ================================================ using System; using Npgsql; namespace Hangfire.PostgreSql.Factories; /// /// Connection factory that utilizes an already-existing . /// public sealed class ExistingNpgsqlConnectionFactory : NpgsqlInstanceConnectionFactoryBase { private readonly NpgsqlConnection _connection; /// /// Instantiates the factory using specified . /// /// to use. /// used for connection string verification. /// public ExistingNpgsqlConnectionFactory(NpgsqlConnection connection, PostgreSqlStorageOptions options) : base(options) { _connection = connection ?? throw new ArgumentNullException(nameof(connection)); // To ensure valid connection string - throws internally SetupConnectionStringBuilder(_connection.ConnectionString); } /// public override NpgsqlConnection GetOrCreateConnection() { return _connection; } } ================================================ FILE: src/Hangfire.PostgreSql/Factories/NpgsqlConnectionFactory.cs ================================================ using System; using Hangfire.Annotations; using Npgsql; namespace Hangfire.PostgreSql.Factories; /// /// Connection factory that creates a new based on the connection string. /// public sealed class NpgsqlConnectionFactory : NpgsqlInstanceConnectionFactoryBase { private readonly string _connectionString; [CanBeNull] private readonly Action _connectionSetup; [CanBeNull] private readonly Func _getConnectionString; /// /// Instantiates the factory using specified . /// /// Connection string. /// used for connection string verification. /// Optional additional connection setup action to be performed on the created . /// Throws if is null. public NpgsqlConnectionFactory(string connectionString, PostgreSqlStorageOptions options, [CanBeNull] Action connectionSetup = null) : base(options) { _connectionString = SetupConnectionStringBuilder(connectionString ?? throw new ArgumentNullException(nameof(connectionString))).ConnectionString; _connectionSetup = connectionSetup; } public NpgsqlConnectionFactory(Func getConnectionString, PostgreSqlStorageOptions options, [CanBeNull] Action connectionSetup = null) : this(getConnectionString.Invoke(), options, connectionSetup) { _getConnectionString = getConnectionString; } /// public override NpgsqlConnection GetOrCreateConnection() { var connectionString = _connectionString; if (_getConnectionString != null) { connectionString = SetupConnectionStringBuilder(_getConnectionString.Invoke()).ConnectionString; } NpgsqlConnection connection = new(connectionString); _connectionSetup?.Invoke(connection); return connection; } } ================================================ FILE: src/Hangfire.PostgreSql/Factories/NpgsqlInstanceConnectionFactoryBase.cs ================================================ using System; using Hangfire.Annotations; using Npgsql; namespace Hangfire.PostgreSql.Factories; public abstract class NpgsqlInstanceConnectionFactoryBase : IConnectionFactory { private readonly PostgreSqlStorageOptions _options; [CanBeNull] private NpgsqlConnectionStringBuilder _connectionStringBuilder; [CanBeNull] private string _connectionString; protected NpgsqlInstanceConnectionFactoryBase(PostgreSqlStorageOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); } /// /// Gets the connection string builder associated with the current instance. /// /// Throws if connection string builder has not been initialized. public NpgsqlConnectionStringBuilder ConnectionString => _connectionStringBuilder ?? throw new InvalidOperationException("Connection string builder has not been initialized"); protected NpgsqlConnectionStringBuilder SetupConnectionStringBuilder(string connectionString) { if (_connectionStringBuilder != null && string.Equals(_connectionString, connectionString, StringComparison.OrdinalIgnoreCase)) { return _connectionStringBuilder; } try { _connectionString = connectionString; NpgsqlConnectionStringBuilder builder = new(connectionString); // The connection string must not be modified when transaction enlistment is enabled, otherwise it will cause // prepared transactions and probably fail when other statements (outside of hangfire) ran within the same // transaction. Also see #248. if (!_options.EnableTransactionScopeEnlistment && builder.Enlist) { throw new ArgumentException($"TransactionScope enlistment must be enabled by setting {nameof(PostgreSqlStorageOptions)}.{nameof(PostgreSqlStorageOptions.EnableTransactionScopeEnlistment)} to `true`."); } return _connectionStringBuilder = builder; } catch (ArgumentException ex) { throw new ArgumentException($"Connection string is not valid", nameof(connectionString), ex); } } /// public abstract NpgsqlConnection GetOrCreateConnection(); } ================================================ FILE: src/Hangfire.PostgreSql/Hangfire.PostgreSql.csproj ================================================  PostgreSql storage implementation for Hangfire (background job system for ASP.NET and aspnet core applications). Copyright © 2014-2024 Frank Hommers and others Hangfire PostgreSql Storage 1.9.4 Frank Hommers, Vytautas Kasparavičius, Žygimantas Arūna netstandard2.0 Hangfire.PostgreSql Library Hangfire;PostgreSql;Postgres Hangfire.PostgreSql https://github.com/frankhommers/Hangfire.PostgreSql/releases http://hmm.rs/Hangfire.PostgreSql 1.9.4.0 1.9.4.0 1.9.4.0 True LICENSE.md https://github.com/frankhommers/Hangfire.PostgreSql git https://github.com/hangfire-postgres default true $(NoWarn);1591 $(InterceptorsNamespaces);Dapper.AOT all runtime; build; native; contentfiles; analyzers; buildtransitive True ================================================ FILE: src/Hangfire.PostgreSql/IConnectionFactory.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using Npgsql; namespace Hangfire.PostgreSql { /// /// Connection factory for creating at runtime. /// public interface IConnectionFactory { /// /// Get or create . /// NpgsqlConnection GetOrCreateConnection(); } } ================================================ FILE: src/Hangfire.PostgreSql/IPersistentJobQueue.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System.Data; using System.Threading; using Hangfire.Storage; namespace Hangfire.PostgreSql { public interface IPersistentJobQueue { IFetchedJob Dequeue(string[] queues, CancellationToken cancellationToken); void Enqueue(IDbConnection connection, string queue, string jobId); } } ================================================ FILE: src/Hangfire.PostgreSql/IPersistentJobQueueMonitoringApi.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System.Collections.Generic; namespace Hangfire.PostgreSql { public interface IPersistentJobQueueMonitoringApi { IEnumerable GetQueues(); IEnumerable GetEnqueuedJobIds(string queue, int from, int perPage); IEnumerable GetFetchedJobIds(string queue, int from, int perPage); EnqueuedAndFetchedCountDto GetEnqueuedAndFetchedCount(string queue); } } ================================================ FILE: src/Hangfire.PostgreSql/IPersistentJobQueueProvider.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. namespace Hangfire.PostgreSql { public interface IPersistentJobQueueProvider { IPersistentJobQueue GetJobQueue(); IPersistentJobQueueMonitoringApi GetJobQueueMonitoringApi(); } } ================================================ FILE: src/Hangfire.PostgreSql/JsonParameter.cs ================================================ using System; using System.Text.Json; using Hangfire.Annotations; namespace Hangfire.PostgreSql; internal static class JsonParameter { public static string GetParameterValue([CanBeNull] object value) { return GetParameterValue(value, ValueType.Object); } public static string GetParameterValue([CanBeNull] object value, ValueType type) { return value switch { string { Length: > 0 } stringValue => stringValue, string { Length: 0 } or null => GetDefaultValue(type), var _ => JsonSerializer.Serialize(value), }; } private static string GetDefaultValue(ValueType type) { return type switch { ValueType.Object => "{}", ValueType.Array => "[]", var _ => throw new ArgumentOutOfRangeException(), }; } public enum ValueType { Object, Array, } } ================================================ FILE: src/Hangfire.PostgreSql/PersistentJobQueueProviderCollection.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using System.Collections; using System.Collections.Generic; namespace Hangfire.PostgreSql { public class PersistentJobQueueProviderCollection : IEnumerable { private readonly IPersistentJobQueueProvider _defaultProvider; private readonly List _providers = new(); private readonly Dictionary _providersByQueue = new(StringComparer.OrdinalIgnoreCase); public PersistentJobQueueProviderCollection(IPersistentJobQueueProvider defaultProvider) { _defaultProvider = defaultProvider ?? throw new ArgumentNullException(nameof(defaultProvider)); _providers.Add(_defaultProvider); } public IEnumerator GetEnumerator() { return _providers.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public void Add(IPersistentJobQueueProvider provider, IEnumerable queues) { if (provider == null) { throw new ArgumentNullException(nameof(provider)); } if (queues == null) { throw new ArgumentNullException(nameof(queues)); } _providers.Add(provider); foreach (string queue in queues) { _providersByQueue.Add(queue, provider); } } public IPersistentJobQueueProvider GetProvider(string queue) { return _providersByQueue.TryGetValue(queue, out IPersistentJobQueueProvider provider) ? provider : _defaultProvider; } public void Remove(string queue) { if (!_providersByQueue.ContainsKey(queue)) { return; } IPersistentJobQueueProvider provider = _providersByQueue[queue]; _providersByQueue.Remove(queue); _providers.Remove(provider); } } } ================================================ FILE: src/Hangfire.PostgreSql/PostgreSqlBootstrapperConfigurationExtensions.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using Npgsql; namespace Hangfire.PostgreSql { public static class PostgreSqlBootstrapperConfigurationExtensions { /// /// Tells the bootstrapper to use PostgreSQL as a job storage, /// that can be accessed using the given connection string or /// its name. /// /// Configuration /// Connection string [Obsolete("Will be removed in 2.0. Please use UsePostgreSqlStorage(Action) overload.")] public static IGlobalConfiguration UsePostgreSqlStorage( this IGlobalConfiguration configuration, string connectionString) { return configuration.UsePostgreSqlStorage(connectionString, null, new PostgreSqlStorageOptions()); } /// /// Tells the bootstrapper to use PostgreSQL as a job storage /// with the given options, that can be accessed using the specified /// connection string. /// /// Configuration /// Connection string /// Advanced options [Obsolete("Will be removed in 2.0. Please use UsePostgreSqlStorage(Action, PostgreSqlStorageOptions) overload.")] public static IGlobalConfiguration UsePostgreSqlStorage( this IGlobalConfiguration configuration, string connectionString, PostgreSqlStorageOptions options) { return configuration.UsePostgreSqlStorage(connectionString, null, options); } /// /// Tells the bootstrapper to use PostgreSQL as a job storage /// with the given options, that can be accessed using the specified /// connection string. /// /// Configuration /// Connection string /// Optional setup action to apply to created connections /// Advanced options [Obsolete("Will be removed in 2.0. Please use UsePostgreSqlStorage(Action, PostgreSqlStorageOptions) overload.")] public static IGlobalConfiguration UsePostgreSqlStorage( this IGlobalConfiguration configuration, string connectionString, Action connectionSetup, PostgreSqlStorageOptions options) { return configuration.UsePostgreSqlStorage(configure => configure.UseNpgsqlConnection(connectionString, connectionSetup), options); } /// /// Tells the bootstrapper to use PostgreSQL as a job storage /// with the given options, that can be accessed using the specified /// connection factory. /// /// Configuration /// Connection factory /// Advanced options [Obsolete("Will be removed in 2.0. Please use UsePostgreSqlStorage(Action, PostgreSqlStorageOptions) overload.")] public static IGlobalConfiguration UsePostgreSqlStorage( this IGlobalConfiguration configuration, IConnectionFactory connectionFactory, PostgreSqlStorageOptions options) { return configuration.UsePostgreSqlStorage(configure => configure.UseConnectionFactory(connectionFactory), options); } /// /// Tells the bootstrapper to use PostgreSQL as a job storage /// with the given options, that can be accessed using the specified /// connection factory. /// /// Configuration /// Connection factory [Obsolete("Will be removed in 2.0. Please use UsePostgreSqlStorage(Action) overload.")] public static IGlobalConfiguration UsePostgreSqlStorage( this IGlobalConfiguration configuration, IConnectionFactory connectionFactory) { return configuration.UsePostgreSqlStorage(connectionFactory, new PostgreSqlStorageOptions()); } /// /// Tells the bootstrapper to use PostgreSQL as the job storage with the default storage options. /// /// Configuration instance. /// Bootstrapper configuration action. /// instance whose generic type argument is . public static IGlobalConfiguration UsePostgreSqlStorage(this IGlobalConfiguration configuration, Action configure) { return configuration.UsePostgreSqlStorage(configure, new PostgreSqlStorageOptions()); } /// /// Tells the bootstrapper to use PostgreSQL as the job storage with the specified storage options. /// /// Configuration instance. /// Bootstrapper configuration action. /// Storage options. /// instance whose generic type argument is . /// Throws if is not set up in the action. public static IGlobalConfiguration UsePostgreSqlStorage(this IGlobalConfiguration configuration, Action configure, PostgreSqlStorageOptions options) { if (options == null) { throw new ArgumentNullException(nameof(options)); } PostgreSqlBootstrapperOptions bootstrapperOptions = new(options); configure(bootstrapperOptions); IConnectionFactory connectionFactory = bootstrapperOptions.ConnectionFactory; if (connectionFactory == null) { throw new InvalidOperationException("Connection factory is not specified"); } PostgreSqlStorage storage = new(connectionFactory, options); return configuration.UseStorage(storage); } } } ================================================ FILE: src/Hangfire.PostgreSql/PostgreSqlBootstrapperOptions.cs ================================================ using System; using Hangfire.Annotations; using Hangfire.PostgreSql.Factories; using Npgsql; namespace Hangfire.PostgreSql; /// /// Bootstrapper options. /// public class PostgreSqlBootstrapperOptions { private readonly PostgreSqlStorageOptions _options; internal PostgreSqlBootstrapperOptions(PostgreSqlStorageOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); } [CanBeNull] internal IConnectionFactory ConnectionFactory { get; private set; } /// /// Configures the bootstrapper to use a custom to use for each database action. /// /// Instance of . /// This instance. /// Throws if is null. public PostgreSqlBootstrapperOptions UseConnectionFactory(IConnectionFactory connectionFactory) { ConnectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); return this; } /// /// Configures the bootstrapper to create a new for each database action. /// /// Connection string. /// Optional additional connection setup action to be performed on the created . /// This instance. public PostgreSqlBootstrapperOptions UseNpgsqlConnection(string connectionString, [CanBeNull] Action connectionSetup = null) { return UseConnectionFactory(new NpgsqlConnectionFactory(connectionString, _options, connectionSetup)); } public PostgreSqlBootstrapperOptions UseNpgsqlConnection(Func getConnectionString, [CanBeNull] Action connectionSetup = null) { return UseConnectionFactory(new NpgsqlConnectionFactory(getConnectionString, _options, connectionSetup)); } /// /// Configures the bootstrapper to use the existing for each database action. /// /// to use. /// This instance. public PostgreSqlBootstrapperOptions UseExistingNpgsqlConnection(NpgsqlConnection connection) { return UseConnectionFactory(new ExistingNpgsqlConnectionFactory(connection, _options)); } } ================================================ FILE: src/Hangfire.PostgreSql/PostgreSqlConnection.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Threading; using Dapper; using Hangfire.Common; using Hangfire.PostgreSql.Entities; using Hangfire.Server; using Hangfire.Storage; using Npgsql; using IsolationLevel = System.Transactions.IsolationLevel; namespace Hangfire.PostgreSql { [DapperAot] public class PostgreSqlConnection : JobStorageConnection { private readonly Dictionary> _lockedResources; private readonly PostgreSqlStorageOptions _options; private readonly PostgreSqlStorage _storage; private DbConnection _dedicatedConnection; public PostgreSqlConnection(PostgreSqlStorage storage) { _storage = storage ?? throw new ArgumentNullException(nameof(storage)); _options = storage.Options ?? throw new ArgumentNullException(nameof(storage.Options)); _lockedResources = new Dictionary>(); } public override void Dispose() { if (_dedicatedConnection == null) { return; } _dedicatedConnection.Dispose(); _dedicatedConnection = null; } public override IWriteOnlyTransaction CreateWriteTransaction() { return new PostgreSqlWriteOnlyTransaction(_storage, () => _dedicatedConnection); } public override IDisposable AcquireDistributedLock(string resource, TimeSpan timeout) { return string.IsNullOrEmpty(resource) ? throw new ArgumentNullException(nameof(resource)) : AcquireLock($"{_options.SchemaName}:{resource}", timeout); } public override IFetchedJob FetchNextJob(string[] queues, CancellationToken cancellationToken) { if (queues == null || queues.Length == 0) { throw new ArgumentNullException(nameof(queues)); } IPersistentJobQueueProvider[] providers = queues .Select(_storage.QueueProviders.GetProvider) .Distinct() .ToArray(); if (providers.Length != 1) { throw new InvalidOperationException( $"Multiple provider instances registered for queues: {string.Join(", ", queues)}. You should choose only one type of persistent queues per server instance."); } IPersistentJobQueue persistentQueue = providers[0].GetJobQueue(); return persistentQueue.Dequeue(queues, cancellationToken); } public override string CreateExpiredJob( Job job, IDictionary parameters, DateTime createdAt, TimeSpan expireIn) { if (job == null) { throw new ArgumentNullException(nameof(job)); } if (parameters == null) { throw new ArgumentNullException(nameof(parameters)); } string createJobSql = $@" INSERT INTO ""{_options.SchemaName}"".""job"" (""invocationdata"", ""arguments"", ""createdat"", ""expireat"") VALUES (@InvocationData::jsonb, @Arguments::jsonb, @CreatedAt, @ExpireAt) RETURNING ""id""; "; InvocationData invocationData = InvocationData.SerializeJob(job); return _storage.UseTransaction(_dedicatedConnection, (connection, transaction) => { string jobId = connection.QuerySingle(createJobSql, new { InvocationData = JsonParameter.GetParameterValue(SerializationHelper.Serialize(invocationData)), Arguments = JsonParameter.GetParameterValue(invocationData.Arguments, JsonParameter.ValueType.Array), CreatedAt = createdAt, ExpireAt = createdAt.Add(expireIn), }).ToString(CultureInfo.InvariantCulture); if (parameters.Count > 0) { var parameterArray = parameters .Select(parameter => new { JobId = Convert.ToInt64(jobId, CultureInfo.InvariantCulture), Name = parameter.Key, parameter.Value, } ) .ToArray(); string insertParameterSql = $@" INSERT INTO ""{_options.SchemaName}"".""jobparameter"" (""jobid"", ""name"", ""value"") VALUES (@JobId, @Name, @Value); "; connection.Execute(insertParameterSql, parameterArray, transaction); } return jobId; }); } public override JobData GetJobData(string id) { if (id == null) { throw new ArgumentNullException(nameof(id)); } string sql = $@" SELECT ""invocationdata"" ""invocationData"", ""statename"" ""stateName"", ""arguments"", ""createdat"" ""createdAt"" FROM ""{_options.SchemaName}"".""job"" WHERE ""id"" = @Id; "; SqlJob jobData = _storage.UseConnection(_dedicatedConnection, connection => connection .QuerySingleOrDefault(sql, new { Id = Convert.ToInt64(id, CultureInfo.InvariantCulture) })); if (jobData == null) { return null; } // TODO: conversion exception could be thrown. InvocationData invocationData = SerializationHelper.Deserialize(jobData.InvocationData); invocationData.Arguments = jobData.Arguments; Job job = null; JobLoadException loadException = null; try { job = invocationData.DeserializeJob(); } catch (JobLoadException ex) { loadException = ex; } return new JobData { Job = job, State = jobData.StateName, CreatedAt = jobData.CreatedAt, LoadException = loadException, }; } public override StateData GetStateData(string jobId) { if (jobId == null) { throw new ArgumentNullException(nameof(jobId)); } string sql = $@" SELECT s.""name"" ""Name"", s.""reason"" ""Reason"", s.""data"" ""Data"" FROM ""{_options.SchemaName}"".""state"" s INNER JOIN ""{_options.SchemaName}"".""job"" j on j.""stateid"" = s.""id"" WHERE j.""id"" = @JobId; "; SqlState sqlState = _storage.UseConnection(_dedicatedConnection, connection => connection .QuerySingleOrDefault(sql, new { JobId = Convert.ToInt64(jobId, CultureInfo.InvariantCulture) })); return sqlState == null ? null : new StateData { Name = sqlState.Name, Reason = sqlState.Reason, Data = SerializationHelper.Deserialize>(sqlState.Data), }; } public override void SetJobParameter(string id, string name, string value) { if (id == null) { throw new ArgumentNullException(nameof(id)); } if (name == null) { throw new ArgumentNullException(nameof(name)); } string sql = $@" WITH ""inputvalues"" AS ( SELECT @JobId ""jobid"", @Name ""name"", @Value ""value"" ), ""updatedrows"" AS ( UPDATE ""{_options.SchemaName}"".""jobparameter"" ""updatetarget"" SET ""value"" = ""inputvalues"".""value"" FROM ""inputvalues"" WHERE ""updatetarget"".""jobid"" = ""inputvalues"".""jobid"" AND ""updatetarget"".""name"" = ""inputvalues"".""name"" RETURNING ""updatetarget"".""jobid"", ""updatetarget"".""name"" ) INSERT INTO ""{_options.SchemaName}"".""jobparameter""(""jobid"", ""name"", ""value"") SELECT ""jobid"", ""name"", ""value"" FROM ""inputvalues"" ""insertvalues"" WHERE NOT EXISTS ( SELECT 1 FROM ""updatedrows"" WHERE ""updatedrows"".""jobid"" = ""insertvalues"".""jobid"" AND ""updatedrows"".""name"" = ""insertvalues"".""name"" ); "; _storage.UseConnection(_dedicatedConnection, connection => connection .Execute(sql, new { JobId = Convert.ToInt64(id, CultureInfo.InvariantCulture), Name = name, Value = value })); } public override string GetJobParameter(string id, string name) { if (id == null) { throw new ArgumentNullException(nameof(id)); } if (name == null) { throw new ArgumentNullException(nameof(name)); } string query = $@"SELECT ""value"" FROM ""{_options.SchemaName}"".""jobparameter"" WHERE ""jobid"" = @Id AND ""name"" = @Name;"; return _storage.UseConnection(_dedicatedConnection, connection => connection .QuerySingleOrDefault(query, new { Id = Convert.ToInt64(id, CultureInfo.InvariantCulture), Name = name })); } public override HashSet GetAllItemsFromSet(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } string query = $@"SELECT ""value"" FROM ""{_options.SchemaName}"".""set"" WHERE ""key"" = @Key;"; return _storage.UseConnection(_dedicatedConnection, connection => { IEnumerable result = connection.Query(query, new { Key = key }); return new HashSet(result); }); } public override string GetFirstByLowestScoreFromSet(string key, double fromScore, double toScore) { if (key == null) { throw new ArgumentNullException(nameof(key)); } if (toScore < fromScore) { throw new ArgumentException($"The '{nameof(toScore)}' value must be higher or equal to the '{nameof(fromScore)}' value."); } return _storage.UseConnection(_dedicatedConnection, connection => connection .QuerySingleOrDefault($@" SELECT ""value"" FROM ""{_options.SchemaName}"".""set"" WHERE ""key"" = @Key AND ""score"" BETWEEN @FromScore AND @ToScore ORDER BY ""score"" LIMIT 1; ", new { Key = key, FromScore = fromScore, ToScore = toScore })); } public override List GetFirstByLowestScoreFromSet(string key, double fromScore, double toScore, int count) { if (key == null) { throw new ArgumentNullException(nameof(key)); } if (toScore < fromScore) { throw new ArgumentException($"The '{nameof(toScore)}' value must be higher or equal to the '{nameof(fromScore)}' value."); } if (count < 1) { throw new ArgumentException($"The '{nameof(count)}' value must be greater than zero (0)."); } return _storage.UseConnection(_dedicatedConnection, connection => connection .Query($@" SELECT ""value"" FROM ""{_options.SchemaName}"".""set"" WHERE ""key"" = @Key AND ""score"" BETWEEN @FromScore AND @ToScore ORDER BY ""score"" LIMIT @Limit; ", new { Key = key, FromScore = fromScore, ToScore = toScore, Limit = count })) .AsList(); } public override void SetRangeInHash(string key, IEnumerable> keyValuePairs) { if (key == null) { throw new ArgumentNullException(nameof(key)); } if (keyValuePairs == null) { throw new ArgumentNullException(nameof(keyValuePairs)); } string sql = $@" WITH ""inputvalues"" AS ( SELECT @Key ""key"", @Field ""field"", @Value ""value"" ), ""updatedrows"" AS ( UPDATE ""{_options.SchemaName}"".""hash"" ""updatetarget"" SET ""value"" = ""inputvalues"".""value"" FROM ""inputvalues"" WHERE ""updatetarget"".""key"" = ""inputvalues"".""key"" AND ""updatetarget"".""field"" = ""inputvalues"".""field"" RETURNING ""updatetarget"".""key"", ""updatetarget"".""field"" ) INSERT INTO ""{_options.SchemaName}"".""hash""(""key"", ""field"", ""value"") SELECT ""key"", ""field"", ""value"" FROM ""inputvalues"" ""insertvalues"" WHERE NOT EXISTS ( SELECT 1 FROM ""updatedrows"" WHERE ""updatedrows"".""key"" = ""insertvalues"".""key"" AND ""updatedrows"".""field"" = ""insertvalues"".""field"" ); "; Stopwatch executionTimer = Stopwatch.StartNew(); while (true) { try { _storage.UseTransaction(_dedicatedConnection, (connection, transaction) => { foreach (KeyValuePair keyValuePair in keyValuePairs) { connection.Execute(sql, new { Key = key, Field = keyValuePair.Key, keyValuePair.Value }, transaction); } }, IsolationLevel.Serializable); return; } catch (PostgresException exception) { if (!exception.SqlState.Equals(PostgresErrorCodes.SerializationFailure)) // 40001 { throw; } } if (executionTimer.Elapsed > _options.TransactionSynchronisationTimeout) { throw new TimeoutException("SetRangeInHash experienced timeout while trying to execute transaction"); } } } public override Dictionary GetAllEntriesFromHash(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } Dictionary result = _storage.UseConnection(_dedicatedConnection, connection => connection .Query($@" SELECT ""field"" ""Field"", ""value"" ""Value"" FROM ""{_options.SchemaName}"".""hash"" WHERE ""key"" = @Key;", new { Key = key }) .ToDictionary(x => x.Field, x => x.Value)); return result.Count != 0 ? result : null; } public override void AnnounceServer(string serverId, ServerContext context) { if (serverId == null) { throw new ArgumentNullException(nameof(serverId)); } if (context == null) { throw new ArgumentNullException(nameof(context)); } ServerData data = new() { WorkerCount = context.WorkerCount, Queues = context.Queues, StartedAt = DateTime.UtcNow, }; string sql = $@" WITH ""inputvalues"" AS ( SELECT @Id ""id"", @Data::jsonb ""data"", NOW() ""lastheartbeat"" ), ""updatedrows"" AS ( UPDATE ""{_options.SchemaName}"".""server"" ""updatetarget"" SET ""data"" = ""inputvalues"".""data"", ""lastheartbeat"" = ""inputvalues"".""lastheartbeat"" FROM ""inputvalues"" WHERE ""updatetarget"".""id"" = ""inputvalues"".""id"" RETURNING ""updatetarget"".""id"" ) INSERT INTO ""{_options.SchemaName}"".""server""(""id"", ""data"", ""lastheartbeat"") SELECT ""id"", ""data"", ""lastheartbeat"" FROM ""inputvalues"" ""insertvalues"" WHERE NOT EXISTS ( SELECT 1 FROM ""updatedrows"" WHERE ""updatedrows"".""id"" = ""insertvalues"".""id"" ); "; _storage.UseConnection(_dedicatedConnection, connection => connection .Execute(sql, new { Id = serverId, Data = JsonParameter.GetParameterValue(SerializationHelper.Serialize(data)) })); } public override void RemoveServer(string serverId) { if (serverId == null) { throw new ArgumentNullException(nameof(serverId)); } _storage.UseConnection(_dedicatedConnection, connection => connection .Execute($@"DELETE FROM ""{_options.SchemaName}"".""server"" WHERE ""id"" = @Id;", new { Id = serverId })); } public override void Heartbeat(string serverId) { if (serverId == null) { throw new ArgumentNullException(nameof(serverId)); } string query = $@" UPDATE ""{_options.SchemaName}"".""server"" SET ""lastheartbeat"" = NOW() WHERE ""id"" = @Id; "; int affectedRows = _storage.UseConnection(_dedicatedConnection, connection => connection .Execute(query, new { Id = serverId })); if (affectedRows == 0) { throw new BackgroundServerGoneException(); } } public override int RemoveTimedOutServers(TimeSpan timeOut) { if (timeOut.Duration() != timeOut) { throw new ArgumentException("The 'timeOut' value must be positive.", nameof(timeOut)); } string query = $@" DELETE FROM ""{_options.SchemaName}"".""server"" WHERE ""lastheartbeat"" < (NOW() - INTERVAL '{((long)timeOut.TotalMilliseconds).ToString(CultureInfo.InvariantCulture)} MILLISECONDS');"; return _storage.UseConnection(_dedicatedConnection, connection => connection.Execute(query)); } public override long GetSetCount(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } string query = $@"SELECT COUNT(""key"") FROM ""{_options.SchemaName}"".""set"" WHERE ""key"" = @Key"; return _storage.UseConnection(_dedicatedConnection, connection => connection .QuerySingleOrDefault(query, new { Key = key })); } public override List GetAllItemsFromList(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } string query = $@"SELECT ""value"" FROM ""{_options.SchemaName}"".""list"" WHERE ""key"" = @Key ORDER BY ""id"" DESC"; return _storage.UseConnection(_dedicatedConnection, connection => connection .Query(query, new { Key = key }) .AsList()); } public override long GetCounter(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } string query = $@"SELECT SUM(""value"") FROM ( SELECT SUM(""value"") ""value"" FROM ""{_options.SchemaName}"".""counter"" WHERE ""key"" = @Key UNION ALL SELECT SUM(""value"") ""value"" FROM ""{_options.SchemaName}"".""aggregatedcounter"" WHERE ""key"" = @Key) c"; return _storage.UseConnection(_dedicatedConnection, connection => connection .QuerySingleOrDefault(query, new { Key = key }) ?? 0); } public override long GetListCount(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } string query = $@"SELECT COUNT(""id"") FROM ""{_options.SchemaName}"".""list"" WHERE ""key"" = @Key"; return _storage.UseConnection(_dedicatedConnection, connection => connection .QuerySingleOrDefault(query, new { Key = key })); } public override TimeSpan GetListTtl(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } string query = $@"SELECT min(""expireat"") FROM ""{_options.SchemaName}"".""list"" WHERE ""key"" = @Key"; DateTime? result = _storage.UseConnection(_dedicatedConnection, connection => connection .QuerySingleOrDefault(query, new { Key = key })); return !result.HasValue ? TimeSpan.FromSeconds(-1) : result.Value - DateTime.UtcNow; } public override List GetRangeFromList(string key, int startingFrom, int endingAt) { if (key == null) { throw new ArgumentNullException(nameof(key)); } string query = $@" SELECT ""value"" FROM ""{_options.SchemaName}"".""list"" WHERE ""key"" = @Key ORDER BY ""id"" DESC LIMIT @Limit OFFSET @Offset "; return _storage.UseConnection(_dedicatedConnection, connection => connection .Query(query, new { Key = key, Limit = endingAt - startingFrom + 1, Offset = startingFrom }) .AsList()); } public override long GetHashCount(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } string query = $@"SELECT COUNT(""id"") FROM ""{_options.SchemaName}"".""hash"" WHERE ""key"" = @Key"; return _storage.UseConnection(_dedicatedConnection, connection => connection .QuerySingle(query, new { Key = key })); } public override TimeSpan GetHashTtl(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } string query = $@"SELECT MIN(""expireat"") FROM ""{_options.SchemaName}"".""hash"" WHERE ""key"" = @Key"; DateTime? result = _storage.UseConnection(_dedicatedConnection, connection => connection .QuerySingleOrDefault(query, new { Key = key })); return !result.HasValue ? TimeSpan.FromSeconds(-1) : result.Value - DateTime.UtcNow; } public override List GetRangeFromSet(string key, int startingFrom, int endingAt) { if (key == null) { throw new ArgumentNullException(nameof(key)); } string query = $@" SELECT ""value"" FROM ""{_options.SchemaName}"".""set"" WHERE ""key"" = @Key ORDER BY ""id"" LIMIT @Limit OFFSET @Offset "; return _storage.UseConnection(_dedicatedConnection, connection => connection .Query(query, new { Key = key, Limit = endingAt - startingFrom + 1, Offset = startingFrom }) .AsList()); } public override TimeSpan GetSetTtl(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } string query = $@"SELECT min(""expireat"") FROM ""{_options.SchemaName}"".""set"" WHERE ""key"" = @Key"; DateTime? result = _storage.UseConnection(_dedicatedConnection, connection => connection .QuerySingleOrDefault(query, new { Key = key })); return !result.HasValue ? TimeSpan.FromSeconds(-1) : result.Value - DateTime.UtcNow; } public override string GetValueFromHash(string key, string name) { if (key == null) { throw new ArgumentNullException(nameof(key)); } if (name == null) { throw new ArgumentNullException(nameof(name)); } string query = $@"SELECT ""value"" FROM ""{_options.SchemaName}"".""hash"" WHERE ""key"" = @Key AND ""field"" = @Field"; return _storage.UseConnection(_dedicatedConnection, connection => connection .QuerySingleOrDefault(query, new { Key = key, Field = name })); } private IDisposable AcquireLock(string resource, TimeSpan timeout) { _dedicatedConnection ??= _storage.CreateAndOpenConnection(); Guid lockId = Guid.NewGuid(); if (!_lockedResources.ContainsKey(resource)) { try { PostgreSqlDistributedLock.Acquire(_dedicatedConnection, resource, timeout, _options); } catch (Exception) { ReleaseLock(resource, lockId, true); throw; } _lockedResources.Add(resource, new HashSet()); } _lockedResources[resource].Add(lockId); return new DisposableLock(this, resource, lockId); } private void ReleaseLock(string resource, Guid lockId, bool onDisposing) { try { if (!_lockedResources.TryGetValue(resource, out HashSet resourceLocks)) { return; } if (!resourceLocks.Contains(lockId)) { return; } if (resourceLocks.Remove(lockId) && resourceLocks.Count == 0 && _lockedResources.Remove(resource) && _dedicatedConnection.State == ConnectionState.Open) { PostgreSqlDistributedLock.Release(_dedicatedConnection, resource, _options); } } catch (Exception) { if (!onDisposing) { throw; } } finally { if (_lockedResources.Count == 0) { _storage.ReleaseConnection(_dedicatedConnection); _dedicatedConnection = null; } } } private class DisposableLock : IDisposable { private readonly PostgreSqlConnection _connection; private readonly Guid _lockId; private readonly string _resource; public DisposableLock(PostgreSqlConnection connection, string resource, Guid lockId) { _connection = connection; _resource = resource; _lockId = lockId; } public void Dispose() { _connection.ReleaseLock(_resource, _lockId, true); } } } } ================================================ FILE: src/Hangfire.PostgreSql/PostgreSqlDistributedLock.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using System.Data; using System.Diagnostics; using System.Threading; using System.Transactions; using Dapper; using Hangfire.Annotations; using Hangfire.Logging; using Npgsql; using IsolationLevel = System.Data.IsolationLevel; namespace Hangfire.PostgreSql { public sealed class PostgreSqlDistributedLock { private static readonly ILog _logger = LogProvider.GetCurrentClassLogger(); private static void Log(string resource, string message, Exception ex) { bool isConcurrencyError = ex is PostgresException { SqlState: PostgresErrorCodes.SerializationFailure }; _logger.Log(isConcurrencyError ? LogLevel.Trace : LogLevel.Warn, () => $"{resource}: {message}", ex); } internal static void Acquire(IDbConnection connection, string resource, TimeSpan timeout, PostgreSqlStorageOptions options) { if (connection == null) { throw new ArgumentNullException(nameof(connection)); } if (string.IsNullOrEmpty(resource)) { throw new ArgumentNullException(nameof(resource)); } if (options == null) { throw new ArgumentNullException(nameof(options)); } if (connection.State != ConnectionState.Open) { // When we are passing a closed connection to Dapper's Execute method, // it kindly opens it for us, but after command execution, it will be closed // automatically, and our just-acquired application lock will immediately // be released. This is not behavior we want to achieve, so let's throw an // exception instead. throw new InvalidOperationException("Connection must be open before acquiring a distributed lock."); } LockHandler.Lock(resource, timeout, connection, options); } internal static void Release(IDbConnection connection, string resource, PostgreSqlStorageOptions options) { if (connection == null) { throw new ArgumentNullException(nameof(connection)); } if (resource == null) { throw new ArgumentNullException(nameof(resource)); } if (options == null) { throw new ArgumentNullException(nameof(options)); } if (!LockHandler.TryRemoveLock(resource, connection, options, false)) { throw new PostgreSqlDistributedLockException(resource); } } private static class LockHandler { public static void Lock(string resource, TimeSpan timeout, IDbConnection connection, PostgreSqlStorageOptions options) { Stopwatch lockAcquiringTime = Stopwatch.StartNew(); bool tryAcquireLock = true; Func tryLock = options.UseNativeDatabaseTransactions ? TransactionLockHandler.TryLock : UpdateCountLockHandler.TryLock; while (tryAcquireLock) { if (connection.State != ConnectionState.Open) { connection.Open(); } TryRemoveLock(resource, connection, options, true); try { if (tryLock(connection, options.SchemaName, resource)) { return; } } catch (Exception ex) { Log(resource, "Failed to acquire lock", ex); } if (lockAcquiringTime.ElapsedMilliseconds > timeout.TotalMilliseconds) { tryAcquireLock = false; } else { int sleepDuration = (int)(timeout.TotalMilliseconds - lockAcquiringTime.ElapsedMilliseconds); if (sleepDuration > 1000) { sleepDuration = 1000; } if (sleepDuration > 0) { Thread.Sleep(sleepDuration); } else { tryAcquireLock = false; } } } throw new PostgreSqlDistributedLockException(resource); } public static bool TryRemoveLock(string resource, IDbConnection connection, PostgreSqlStorageOptions options, bool onlyExpired) { IDbTransaction trx = null; try { // Non-expired locks are removed only when releasing them. Transaction is not needed in that case. if (onlyExpired && options.UseNativeDatabaseTransactions) { trx = TransactionLockHandler.BeginTransactionIfNotPresent(connection); } DateTime timeout = onlyExpired ? DateTime.UtcNow - options.DistributedLockTimeout : DateTime.MaxValue; int rowsAffected = connection.Execute($@"DELETE FROM ""{options.SchemaName}"".""lock"" WHERE ""resource"" = @Resource AND ""acquired"" < @Timeout", new { Resource = resource, Timeout = timeout, }, trx); trx?.Commit(); return rowsAffected >= 0; } catch (Exception ex) { Log(resource, "Failed to remove lock", ex); return false; } finally { trx?.Dispose(); } } } private static class TransactionLockHandler { public static bool TryLock(IDbConnection connection, string schemaName, string resource) { IDbTransaction trx = null; try { trx = BeginTransactionIfNotPresent(connection); int rowsAffected = connection.Execute($@" INSERT INTO ""{schemaName}"".""lock""(""resource"", ""acquired"") SELECT @Resource, @Acquired WHERE NOT EXISTS ( SELECT 1 FROM ""{schemaName}"".""lock"" WHERE ""resource"" = @Resource ) ON CONFLICT DO NOTHING; ", new { Resource = resource, Acquired = DateTime.UtcNow, }, trx); trx?.Commit(); return rowsAffected > 0; } finally { trx?.Dispose(); } } [CanBeNull] public static IDbTransaction BeginTransactionIfNotPresent(IDbConnection connection) { // If transaction scope was created outside of hangfire, the newly-opened connection is automatically enlisted into the transaction. // Starting a new transaction throws "A transaction is already in progress; nested/concurrent transactions aren't supported." in that case. return Transaction.Current == null ? connection.BeginTransaction(IsolationLevel.ReadCommitted) : null; } } private static class UpdateCountLockHandler { public static bool TryLock(IDbConnection connection, string schemaName, string resource) { connection.Execute($@" INSERT INTO ""{schemaName}"".""lock""(""resource"", ""updatecount"", ""acquired"") SELECT @Resource, 0, @Acquired WHERE NOT EXISTS ( SELECT 1 FROM ""{schemaName}"".""lock"" WHERE ""resource"" = @Resource ) ON CONFLICT DO NOTHING; ", new { Resource = resource, Acquired = DateTime.UtcNow, }); int rowsAffected = connection.Execute( $@"UPDATE ""{schemaName}"".""lock"" SET ""updatecount"" = 1 WHERE ""updatecount"" = 0 AND ""resource"" = @Resource", new { Resource = resource }); return rowsAffected > 0; } } } } ================================================ FILE: src/Hangfire.PostgreSql/PostgreSqlDistributedLockException.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using Hangfire.Storage; namespace Hangfire.PostgreSql { [Serializable] public class PostgreSqlDistributedLockException : DistributedLockTimeoutException { public PostgreSqlDistributedLockException(string resource) : base(resource) { } } } ================================================ FILE: src/Hangfire.PostgreSql/PostgreSqlFetchedJob.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using System.Threading; using Dapper; using Hangfire.Logging; using Hangfire.PostgreSql.Utils; using Hangfire.Storage; namespace Hangfire.PostgreSql { public class PostgreSqlFetchedJob : IFetchedJob { private readonly ILog _logger = LogProvider.GetLogger(typeof(PostgreSqlFetchedJob)); private readonly PostgreSqlStorage _storage; private bool _disposed; private bool _removedFromQueue; private bool _requeued; private readonly object _syncRoot = new object(); private long _lastHeartbeat; private readonly TimeSpan _interval; public PostgreSqlFetchedJob( PostgreSqlStorage storage, long id, string jobId, string queue, DateTime? fetchedAt) { _storage = storage ?? throw new ArgumentNullException(nameof(storage)); Id = id; JobId = jobId ?? throw new ArgumentNullException(nameof(jobId)); Queue = queue ?? throw new ArgumentNullException(nameof(queue)); FetchedAt = fetchedAt ?? throw new ArgumentNullException(nameof(fetchedAt)); if (storage.Options.UseSlidingInvisibilityTimeout) { _lastHeartbeat = TimestampHelper.GetTimestamp(); _interval = TimeSpan.FromSeconds(storage.Options.InvisibilityTimeout.TotalSeconds / 5); storage.HeartbeatProcess.Track(this); } } public long Id { get; } public string Queue { get; } public string JobId { get; } internal DateTime? FetchedAt { get; private set; } public void RemoveFromQueue() { lock (_syncRoot) { if (!FetchedAt.HasValue) { return; } _storage.UseConnection(null, connection => connection.Execute($@" DELETE FROM ""{_storage.Options.SchemaName}"".""jobqueue"" WHERE ""id"" = @Id AND ""fetchedat"" = @FetchedAt; ", new { Id, FetchedAt })); _removedFromQueue = true; } } public void Requeue() { lock (_syncRoot) { if (!FetchedAt.HasValue) { return; } _storage.UseConnection(null, connection => connection.Execute($@" UPDATE ""{_storage.Options.SchemaName}"".""jobqueue"" SET ""fetchedat"" = NULL WHERE ""id"" = @Id AND ""fetchedat"" = @FetchedAt; ", new { Id, FetchedAt })); FetchedAt = null; _requeued = true; } } public void Dispose() { if (_disposed) { return; } _disposed = true; DisposeTimer(); lock (_syncRoot) { if (!_removedFromQueue && !_requeued) { Requeue(); } } } internal void DisposeTimer() { if (_storage.Options.UseSlidingInvisibilityTimeout) { _storage.HeartbeatProcess.Untrack(this); } } internal void ExecuteKeepAliveQueryIfRequired() { var now = TimestampHelper.GetTimestamp(); if (TimestampHelper.Elapsed(now, Interlocked.Read(ref _lastHeartbeat)) < _interval) { return; } lock (_syncRoot) { if (!FetchedAt.HasValue) { return; } if (_requeued || _removedFromQueue) { return; } string updateFetchAtSql = $@" UPDATE ""{_storage.Options.SchemaName}"".""jobqueue"" SET ""fetchedat"" = NOW() WHERE ""id"" = @id AND ""fetchedat"" = @fetchedAt RETURNING ""fetchedat"" AS ""FetchedAt""; "; try { _storage.UseConnection(null, connection => { FetchedAt = connection.ExecuteScalar(updateFetchAtSql, new { queue = Queue, id = Id, fetchedAt = FetchedAt }); }); if (!FetchedAt.HasValue) { _logger.Warn( $"Background job identifier '{JobId}' was fetched by another worker, will not execute keep alive."); } _logger.Trace($"Keep-alive query for message {Id} sent"); Interlocked.Exchange(ref _lastHeartbeat, now); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { _logger.DebugException($"Unable to execute keep-alive query for message {Id}", ex); } } } } } ================================================ FILE: src/Hangfire.PostgreSql/PostgreSqlHeartbeatProcess.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using System.Collections.Concurrent; using System.Threading; using Hangfire.Common; using Hangfire.Server; namespace Hangfire.PostgreSql { #pragma warning disable CS0618 internal sealed class PostgreSqlHeartbeatProcess : IServerComponent, IBackgroundProcess #pragma warning restore CS0618 { private readonly ConcurrentDictionary _items = new(); public void Track(PostgreSqlFetchedJob item) { _items.TryAdd(item, null); } public void Untrack(PostgreSqlFetchedJob item) { _items.TryRemove(item, out var _); } public void Execute(CancellationToken cancellationToken) { foreach (var item in _items) { item.Key.ExecuteKeepAliveQueryIfRequired(); } cancellationToken.Wait(TimeSpan.FromSeconds(1)); } public void Execute(BackgroundProcessContext context) { Execute(context.StoppingToken); } } } ================================================ FILE: src/Hangfire.PostgreSql/PostgreSqlJobQueue.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using System.Data; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; using Dapper; using Hangfire.PostgreSql.Properties; using Hangfire.PostgreSql.Utils; using Hangfire.Storage; using Npgsql; namespace Hangfire.PostgreSql { [DapperAot] public class PostgreSqlJobQueue : IPersistentJobQueue { private const string JobNotificationChannel = "new_job"; internal static readonly AutoResetEventRegistry _queueEventRegistry = new(); private readonly PostgreSqlStorage _storage; public PostgreSqlJobQueue(PostgreSqlStorage storage) { _storage = storage ?? throw new ArgumentNullException(nameof(storage)); SignalDequeue = new AutoResetEvent(false); JobQueueNotification = new AutoResetEvent(false); } private AutoResetEvent SignalDequeue { get; } private AutoResetEvent JobQueueNotification { get; } [NotNull] public IFetchedJob Dequeue(string[] queues, CancellationToken cancellationToken) { Task listenTask = null; CancellationTokenSource cancelListenSource = null; if (_storage.Options.EnableLongPolling) { cancelListenSource = new CancellationTokenSource(); listenTask = ListenForNotificationsAsync(cancelListenSource.Token); } try { return _storage.Options.UseNativeDatabaseTransactions ? Dequeue_Transaction(queues, cancellationToken) : Dequeue_UpdateCount(queues, cancellationToken); } finally { try { cancelListenSource?.Cancel(); listenTask?.Wait(); } catch (AggregateException) { // Swallow all exceptions from listen task cleanup. // The main dequeue operation already succeeded or failed independently. } finally { cancelListenSource?.Dispose(); } } } public void Enqueue(IDbConnection connection, string queue, string jobId) { string enqueueJobSql = $@" INSERT INTO ""{_storage.Options.SchemaName}"".""jobqueue"" (""jobid"", ""queue"") VALUES (@JobId, @Queue); "; connection.Execute(enqueueJobSql, new { JobId = Convert.ToInt64(jobId, CultureInfo.InvariantCulture), Queue = queue }); if (_storage.Options.EnableLongPolling) { connection.Execute($"NOTIFY {JobNotificationChannel}"); } } /// /// Signal the waiting Thread to lookup a new Job /// public void FetchNextJob() { SignalDequeue.Set(); } [NotNull] internal IFetchedJob Dequeue_Transaction(string[] queues, CancellationToken cancellationToken) { if (queues == null) { throw new ArgumentNullException(nameof(queues)); } if (queues.Length == 0) { throw new ArgumentException("Queue array must be non-empty.", nameof(queues)); } long timeoutSeconds = (long)_storage.Options.InvisibilityTimeout.Negate().TotalSeconds; FetchedJob fetchedJob; string fetchJobSql = $@" UPDATE ""{_storage.Options.SchemaName}"".""jobqueue"" SET ""fetchedat"" = NOW() WHERE ""id"" = ( SELECT ""id"" FROM ""{_storage.Options.SchemaName}"".""jobqueue"" WHERE ""queue"" = ANY (@Queues) AND (""fetchedat"" IS NULL OR ""fetchedat"" < NOW() + INTERVAL '{timeoutSeconds.ToString(CultureInfo.InvariantCulture)} SECONDS') ORDER BY ""fetchedat"" NULLS FIRST, ""queue"", ""jobid"" FOR UPDATE SKIP LOCKED LIMIT 1 ) RETURNING ""id"" AS ""Id"", ""jobid"" AS ""JobId"", ""queue"" AS ""Queue"", ""fetchedat"" AS ""FetchedAt""; "; WaitHandle[] nextFetchIterationWaitHandles = new[] { cancellationToken.WaitHandle, SignalDequeue, JobQueueNotification, }.Concat(_queueEventRegistry.GetWaitHandles(queues)).ToArray(); do { cancellationToken.ThrowIfCancellationRequested(); Utils.Utils.TryExecute(() => { NpgsqlConnection connection = _storage.CreateAndOpenConnection(); try { using NpgsqlTransaction trx = connection.BeginTransaction(IsolationLevel.ReadCommitted); FetchedJob jobToFetch = connection.QuerySingleOrDefault(fetchJobSql, new { Queues = queues.ToList() }, trx); trx.Commit(); return jobToFetch; } catch (InvalidOperationException) { // thrown by .QuerySingleOrDefault(): stop the exception propagation if the fetched job was concurrently fetched by another worker } finally { _storage.ReleaseConnection(connection); } return null; }, out fetchedJob, ex => ex is PostgresException { SqlState: PostgresErrorCodes.SerializationFailure }); if (fetchedJob == null) { WaitHandle.WaitAny(nextFetchIterationWaitHandles, _storage.Options.QueuePollInterval); } } while (fetchedJob == null); return new PostgreSqlFetchedJob(_storage, fetchedJob.Id, fetchedJob.JobId.ToString(CultureInfo.InvariantCulture), fetchedJob.Queue, fetchedJob.FetchedAt); } [NotNull] internal IFetchedJob Dequeue_UpdateCount(string[] queues, CancellationToken cancellationToken) { if (queues == null) { throw new ArgumentNullException(nameof(queues)); } if (queues.Length == 0) { throw new ArgumentException("Queue array must be non-empty.", nameof(queues)); } long timeoutSeconds = (long)_storage.Options.InvisibilityTimeout.Negate().TotalSeconds; FetchedJob markJobAsFetched = null; string jobToFetchSql = $@" SELECT ""id"" AS ""Id"", ""jobid"" AS ""JobId"", ""queue"" AS ""Queue"", ""fetchedat"" AS ""FetchedAt"", ""updatecount"" AS ""UpdateCount"" FROM ""{_storage.Options.SchemaName}"".""jobqueue"" WHERE ""queue"" = ANY (@Queues) AND (""fetchedat"" IS NULL OR ""fetchedat"" < NOW() + INTERVAL '{timeoutSeconds.ToString(CultureInfo.InvariantCulture)} SECONDS') ORDER BY ""fetchedat"" NULLS FIRST, ""queue"", ""jobid"" LIMIT 1; "; string markJobAsFetchedSql = $@" UPDATE ""{_storage.Options.SchemaName}"".""jobqueue"" SET ""fetchedat"" = NOW(), ""updatecount"" = (""updatecount"" + 1) % 2000000000 WHERE ""id"" = @Id AND ""updatecount"" = @UpdateCount RETURNING ""id"" AS ""Id"", ""jobid"" AS ""JobId"", ""queue"" AS ""Queue"", ""fetchedat"" AS ""FetchedAt""; "; do { cancellationToken.ThrowIfCancellationRequested(); FetchedJob jobToFetch = _storage.UseConnection(null, connection => connection.QuerySingleOrDefault(jobToFetchSql, new { Queues = queues.ToList() }) ); if (jobToFetch == null) { WaitHandle.WaitAny(new[] { cancellationToken.WaitHandle, SignalDequeue, JobQueueNotification, }, _storage.Options.QueuePollInterval); cancellationToken.ThrowIfCancellationRequested(); } else { markJobAsFetched = _storage.UseConnection(null, connection => connection.QuerySingleOrDefault(markJobAsFetchedSql, jobToFetch) ); } } while (markJobAsFetched == null); return new PostgreSqlFetchedJob(_storage, markJobAsFetched.Id, markJobAsFetched.JobId.ToString(CultureInfo.InvariantCulture), markJobAsFetched.Queue, markJobAsFetched.FetchedAt); } private Task ListenForNotificationsAsync(CancellationToken cancellationToken) { NpgsqlConnection connection = _storage.CreateAndOpenConnection(); try { if (!connection.SupportsNotifications()) { return Task.CompletedTask; } // CreateAnOpenConnection can return the same connection over and over if an existing connection // is passed in the constructor of PostgreSqlStorage. We must use a separate dedicated // connection to listen for notifications. string connectionString = connection.ConnectionString; NpgsqlConnection clonedConnection = connection.CloneWith(connectionString); return Task.Run(async () => { NpgsqlConnection currentConnection = clonedConnection; try { while (!cancellationToken.IsCancellationRequested) { try { if (currentConnection.State != ConnectionState.Open) { await currentConnection.OpenAsync(cancellationToken); } await currentConnection.ExecuteAsync($"LISTEN {JobNotificationChannel}"); await currentConnection.WaitAsync(cancellationToken); JobQueueNotification.Set(); } catch (OperationCanceledException) { throw; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { currentConnection?.Dispose(); await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); currentConnection = new NpgsqlConnection(connectionString); } } } catch (OperationCanceledException) { // Do nothing, cancellation requested so just end. } finally { currentConnection?.Dispose(); } }, cancellationToken); } finally { _storage.ReleaseConnection(connection); } } [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] internal class FetchedJob { public long Id { get; set; } public long JobId { get; set; } public string Queue { get; set; } public DateTime? FetchedAt { get; set; } public int UpdateCount { get; set; } } } } ================================================ FILE: src/Hangfire.PostgreSql/PostgreSqlJobQueueMonitoringApi.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using System.Collections.Generic; using System.Linq; using Dapper; namespace Hangfire.PostgreSql { [DapperAot] internal class PostgreSqlJobQueueMonitoringApi : IPersistentJobQueueMonitoringApi { private readonly PostgreSqlStorage _storage; public PostgreSqlJobQueueMonitoringApi(PostgreSqlStorage storage) { _storage = storage ?? throw new ArgumentNullException(nameof(storage)); } public IEnumerable GetQueues() { string sqlQuery = $@"SELECT DISTINCT ""queue"" FROM ""{_storage.Options.SchemaName}"".""jobqueue"""; return _storage.UseConnection(null, connection => connection.Query(sqlQuery).AsList()); } public IEnumerable GetEnqueuedJobIds(string queue, int from, int perPage) { return GetQueuedOrFetchedJobIds(queue, false, from, perPage); } public IEnumerable GetFetchedJobIds(string queue, int from, int perPage) { return GetQueuedOrFetchedJobIds(queue, true, from, perPage); } public EnqueuedAndFetchedCountDto GetEnqueuedAndFetchedCount(string queue) { string sqlQuery = $@" SELECT ( SELECT COUNT(*) FROM ""{_storage.Options.SchemaName}"".""jobqueue"" WHERE ""fetchedat"" IS NULL AND ""queue"" = @Queue ) ""EnqueuedCount"", ( SELECT COUNT(*) FROM ""{_storage.Options.SchemaName}"".""jobqueue"" WHERE ""fetchedat"" IS NOT NULL AND ""queue"" = @Queue ) ""FetchedCount""; "; (long enqueuedCount, long fetchedCount) = _storage.UseConnection(null, connection => connection.Query(sqlQuery, new { Queue = queue })).Single(); return new EnqueuedAndFetchedCountDto { EnqueuedCount = enqueuedCount, FetchedCount = fetchedCount, }; } private IEnumerable GetQueuedOrFetchedJobIds(string queue, bool fetched, int from, int perPage) { string sqlQuery = $@" SELECT DISTINCT j.""id"" FROM ""{_storage.Options.SchemaName}"".""jobqueue"" jq LEFT JOIN ""{_storage.Options.SchemaName}"".""job"" j ON jq.""jobid"" = j.""id"" WHERE jq.""queue"" = @Queue AND jq.""fetchedat"" {(fetched ? "IS NOT NULL" : "IS NULL")} AND j.""id"" IS NOT NULL ORDER BY j.""id"" LIMIT @Limit OFFSET @Offset; "; return _storage.UseConnection(null, connection => connection.Query(sqlQuery, new { Queue = queue, Offset = from, Limit = perPage }) .AsList()); } internal record struct EnqueuedAndFetchedCount(long EnqueuedCount, long FetchedCount); } } ================================================ FILE: src/Hangfire.PostgreSql/PostgreSqlJobQueueProvider.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; namespace Hangfire.PostgreSql { public class PostgreSqlJobQueueProvider : IPersistentJobQueueProvider { public PostgreSqlJobQueueProvider(PostgreSqlStorage storage, PostgreSqlStorageOptions options) { Storage = storage ?? throw new ArgumentNullException(nameof(storage)); Options = options ?? throw new ArgumentNullException(nameof(options)); } public PostgreSqlStorageOptions Options { get; } public PostgreSqlStorage Storage { get; } public IPersistentJobQueue GetJobQueue() { return new PostgreSqlJobQueue(Storage); } public IPersistentJobQueueMonitoringApi GetJobQueueMonitoringApi() { return new PostgreSqlJobQueueMonitoringApi(Storage); } } } ================================================ FILE: src/Hangfire.PostgreSql/PostgreSqlMonitoringApi.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using System.Collections.Generic; using System.Data; using System.Globalization; using System.Linq; using Dapper; using Hangfire.Common; using Hangfire.PostgreSql.Entities; using Hangfire.States; using Hangfire.Storage; using Hangfire.Storage.Monitoring; namespace Hangfire.PostgreSql { [DapperAot] public class PostgreSqlMonitoringApi : IMonitoringApi { private readonly PersistentJobQueueProviderCollection _queueProviders; private readonly PostgreSqlStorage _storage; public PostgreSqlMonitoringApi( PostgreSqlStorage storage, PersistentJobQueueProviderCollection queueProviders) { _storage = storage ?? throw new ArgumentNullException(nameof(storage)); _queueProviders = queueProviders ?? throw new ArgumentNullException(nameof(queueProviders)); } public long ScheduledCount() { return GetNumberOfJobsByStateName(ScheduledState.StateName); } public long EnqueuedCount(string queue) { IPersistentJobQueueMonitoringApi queueApi = GetQueueApi(queue); EnqueuedAndFetchedCountDto counters = queueApi.GetEnqueuedAndFetchedCount(queue); return counters.EnqueuedCount; } public long FetchedCount(string queue) { IPersistentJobQueueMonitoringApi queueApi = GetQueueApi(queue); EnqueuedAndFetchedCountDto counters = queueApi.GetEnqueuedAndFetchedCount(queue); return counters.FetchedCount; } public long FailedCount() { return GetNumberOfJobsByStateName(FailedState.StateName); } public long ProcessingCount() { return GetNumberOfJobsByStateName(ProcessingState.StateName); } public JobList ProcessingJobs(int from, int count) { return GetJobs(from, count, ProcessingState.StateName, (_, job, stateData) => new ProcessingJobDto { Job = job, ServerId = stateData.TryGetValue("ServerId", out string serverId) ? serverId : stateData["ServerName"], StartedAt = JobHelper.DeserializeDateTime(stateData["StartedAt"]), }); } public JobList ScheduledJobs(int from, int count) { return GetJobs(from, count, ScheduledState.StateName, (_, job, stateData) => new ScheduledJobDto { Job = job, EnqueueAt = JobHelper.DeserializeDateTime(stateData["EnqueueAt"]), ScheduledAt = JobHelper.DeserializeDateTime(stateData["ScheduledAt"]), }); } public IDictionary SucceededByDatesCount() { return GetTimelineStats("succeeded"); } public IDictionary FailedByDatesCount() { return GetTimelineStats("failed"); } public IList Servers() { return UseConnection(connection => { List<(Entities.Server Server, ServerData Data)> servers = connection.Query($@"SELECT * FROM ""{_storage.Options.SchemaName}"".""server""") .AsEnumerable() .Select(server => (server, SerializationHelper.Deserialize(server.Data))) .ToList(); List result = servers.Select(item => new ServerDto { Name = item.Server.Id, Heartbeat = item.Server.LastHeartbeat, Queues = item.Data.Queues, StartedAt = item.Data.StartedAt ?? DateTime.MinValue, WorkersCount = item.Data.WorkerCount, }).ToList(); return result; }); } public JobList FailedJobs(int from, int count) { return GetJobs(from, count, FailedState.StateName, (sqlJob, job, stateData) => new FailedJobDto { Job = job, Reason = sqlJob.StateReason, ExceptionDetails = stateData["ExceptionDetails"], ExceptionMessage = stateData["ExceptionMessage"], ExceptionType = stateData["ExceptionType"], FailedAt = JobHelper.DeserializeNullableDateTime(stateData["FailedAt"]), }); } public JobList SucceededJobs(int from, int count) { return GetJobs(from, count, SucceededState.StateName, (_, job, stateData) => new SucceededJobDto { Job = job, Result = stateData.TryGetValue("Result", out string result) ? result : null, TotalDuration = stateData.ContainsKey("PerformanceDuration") && stateData.TryGetValue("Latency", out string latency) ? long.Parse(stateData["PerformanceDuration"]) + (long?)long.Parse(latency) : null, SucceededAt = JobHelper.DeserializeNullableDateTime(stateData["SucceededAt"]), }); } public JobList DeletedJobs(int from, int count) { return GetJobs(from, count, DeletedState.StateName, (_, job, stateData) => new DeletedJobDto { Job = job, DeletedAt = JobHelper.DeserializeNullableDateTime(stateData["DeletedAt"]), }); } public IList Queues() { var tuples = _queueProviders .Select(x => x.GetJobQueueMonitoringApi()) .SelectMany(x => x.GetQueues(), (monitoring, queue) => new { Monitoring = monitoring, Queue = queue }) .OrderBy(x => x.Queue) .ToArray(); List result = new(tuples.Length); foreach (var tuple in tuples) { IEnumerable enqueuedJobIds = tuple.Monitoring.GetEnqueuedJobIds(tuple.Queue, 0, 5); EnqueuedAndFetchedCountDto counters = tuple.Monitoring.GetEnqueuedAndFetchedCount(tuple.Queue); result.Add(new QueueWithTopEnqueuedJobsDto { Name = tuple.Queue, Length = counters.EnqueuedCount, Fetched = counters.FetchedCount, FirstJobs = EnqueuedJobs(enqueuedJobIds), }); } return result; } public JobList EnqueuedJobs(string queue, int from, int perPage) { IPersistentJobQueueMonitoringApi queueApi = GetQueueApi(queue); IEnumerable enqueuedJobIds = queueApi.GetEnqueuedJobIds(queue, from, perPage); return EnqueuedJobs(enqueuedJobIds); } public JobList FetchedJobs(string queue, int from, int perPage) { IPersistentJobQueueMonitoringApi queueApi = GetQueueApi(queue); IEnumerable fetchedJobIds = queueApi.GetFetchedJobIds(queue, from, perPage); return FetchedJobs(fetchedJobIds); } public IDictionary HourlySucceededJobs() { return GetHourlyTimelineStats("succeeded"); } public IDictionary HourlyFailedJobs() { return GetHourlyTimelineStats("failed"); } public JobDetailsDto JobDetails(string jobId) { return UseConnection(connection => { string sql = $@" SELECT ""id"" ""Id"", ""invocationdata"" ""InvocationData"", ""arguments"" ""Arguments"", ""createdat"" ""CreatedAt"", ""expireat"" ""ExpireAt"" FROM ""{_storage.Options.SchemaName}"".""job"" WHERE ""id"" = @Id; SELECT ""jobid"" ""JobId"", ""name"" ""Name"", ""value"" ""Value"" FROM ""{_storage.Options.SchemaName}"".""jobparameter"" WHERE ""jobid"" = @Id; SELECT ""jobid"" ""JobId"", ""name"" ""Name"", ""reason"" ""Reason"", ""createdat"" ""CreatedAt"", ""data"" ""Data"" FROM ""{_storage.Options.SchemaName}"".""state"" WHERE ""jobid"" = @Id ORDER BY ""id"" DESC; "; using SqlMapper.GridReader multi = connection.QueryMultiple(sql, new { Id = Convert.ToInt64(jobId, CultureInfo.InvariantCulture) }); SqlJob job = multi.Read().SingleOrDefault(); if (job == null) { return null; } Dictionary parameters = multi.Read().ToDictionary(x => x.Name, x => x.Value); List history = multi.Read() .ToList() .Select(x => new StateHistoryDto { StateName = x.Name, CreatedAt = x.CreatedAt, Reason = x.Reason, Data = new SafeDictionary(SerializationHelper.Deserialize>(x.Data), StringComparer.OrdinalIgnoreCase), }) .ToList(); return new JobDetailsDto { CreatedAt = job.CreatedAt, Job = DeserializeJob(job.InvocationData, job.Arguments), History = history, Properties = parameters, ExpireAt = job.ExpireAt, }; }); } public long SucceededListCount() { return GetNumberOfJobsByStateName(SucceededState.StateName); } public long DeletedListCount() { return GetNumberOfJobsByStateName(DeletedState.StateName); } public StatisticsDto GetStatistics() { return UseConnection(connection => { string sql = $@" SELECT ""statename"" ""State"", COUNT(""id"") ""Count"" FROM ""{_storage.Options.SchemaName}"".""job"" WHERE ""statename"" IS NOT NULL GROUP BY ""statename""; SELECT COUNT(*) FROM ""{_storage.Options.SchemaName}"".""server""; SELECT SUM(""value"") FROM (SELECT SUM(""value"") AS value FROM ""{_storage.Options.SchemaName}"".""counter"" WHERE ""key"" = 'stats:succeeded' UNION ALL SELECT SUM(""value"") AS value FROM ""{_storage.Options.SchemaName}"".""aggregatedcounter"" WHERE ""key"" = 'stats:succeeded') c; SELECT SUM(""value"") FROM (SELECT SUM(""value"") AS value FROM ""{_storage.Options.SchemaName}"".""counter"" WHERE ""key"" = 'stats:deleted' UNION ALL SELECT SUM(""value"") AS value FROM ""{_storage.Options.SchemaName}"".""aggregatedcounter"" WHERE ""key"" = 'stats:deleted') c; SELECT COUNT(*) FROM ""{_storage.Options.SchemaName}"".""set"" WHERE ""key"" = 'recurring-jobs'; "; StatisticsDto stats = new(); using (SqlMapper.GridReader multi = connection.QueryMultiple(sql)) { Dictionary countByStates = multi.Read<(string StateName, long Count)>() .ToDictionary(x => x.StateName, x => x.Count); long GetCountIfExists(string name) { return countByStates.TryGetValue(name, out long stateCount) ? stateCount : 0; } stats.Enqueued = GetCountIfExists(EnqueuedState.StateName); stats.Failed = GetCountIfExists(FailedState.StateName); stats.Processing = GetCountIfExists(ProcessingState.StateName); stats.Scheduled = GetCountIfExists(ScheduledState.StateName); stats.Servers = multi.ReadSingle(); stats.Succeeded = multi.ReadSingleOrDefault() ?? 0; stats.Deleted = multi.ReadSingleOrDefault() ?? 0; stats.Recurring = multi.ReadSingle(); } stats.Queues = _queueProviders .SelectMany(x => x.GetJobQueueMonitoringApi().GetQueues()) .Count(); return stats; }); } private Dictionary GetHourlyTimelineStats(string type) { DateTime endDate = DateTime.UtcNow; List dates = new(); for (int i = 0; i < 24; i++) { dates.Add(endDate); endDate = endDate.AddHours(-1); } Dictionary keyMaps = dates.ToDictionary(x => $"stats:{type}:{x:yyyy-MM-dd-HH}", x => x); return GetTimelineStats(keyMaps); } private Dictionary GetTimelineStats(string type) { DateTime endDate = DateTime.UtcNow.Date; List dates = new(); for (int i = 0; i < 7; i++) { dates.Add(endDate); endDate = endDate.AddDays(-1); } Dictionary keyMaps = dates.ToDictionary(x => $"stats:{type}:{x:yyyy-MM-dd}", x => x); return GetTimelineStats(keyMaps); } private Dictionary GetTimelineStats(IDictionary keyMaps) { string query = $""" WITH "aggregated_counters" AS ( SELECT "key", "value" FROM "{_storage.Options.SchemaName}"."aggregatedcounter" WHERE "key" = ANY(@Keys) ), "regular_counters" AS ( SELECT "key", "value" FROM "{_storage.Options.SchemaName}"."counter" WHERE "key" = ANY(@Keys) ), "all_counters" AS ( SELECT * FROM "aggregated_counters" UNION ALL SELECT * FROM "regular_counters" ) SELECT "key", COALESCE(SUM("value"), 0) AS "count" FROM "all_counters" GROUP BY "key" """; Dictionary valuesMap = UseConnection(connection => connection.Query(query, new { Keys = keyMaps.Keys.ToList() }) .AsList() .ToDictionary(x => x.Key, x => x.Count)); foreach (string key in keyMaps.Keys) { if (!valuesMap.ContainsKey(key)) { valuesMap.Add(key, 0); } } Dictionary result = new(); foreach (KeyValuePair keyMap in keyMaps) { long value = valuesMap[keyMap.Key]; result.Add(keyMap.Value, value); } return result; } internal record struct KeyCount(string Key, long Count); private IPersistentJobQueueMonitoringApi GetQueueApi(string queueName) { IPersistentJobQueueProvider provider = _queueProviders.GetProvider(queueName); IPersistentJobQueueMonitoringApi monitoringApi = provider.GetJobQueueMonitoringApi(); return monitoringApi; } private JobList EnqueuedJobs(IEnumerable jobIds) { string enqueuedJobsSql = $@" SELECT DISTINCT ON (""j"".""id"") ""j"".""id"" ""Id"", ""j"".""invocationdata"" ""InvocationData"", ""j"".""arguments"" ""Arguments"", ""j"".""createdat"" ""CreatedAt"", ""j"".""expireat"" ""ExpireAt"", ""s"".""name"" ""StateName"", ""s"".""reason"" ""StateReason"", ""s"".""data"" ""StateData"" FROM ""{_storage.Options.SchemaName}"".""job"" ""j"" LEFT JOIN ""{_storage.Options.SchemaName}"".""state"" ""s"" ON ""s"".""id"" = ""j"".""stateid"" LEFT JOIN ""{_storage.Options.SchemaName}"".""jobqueue"" ""jq"" ON ""jq"".""jobid"" = ""j"".""id"" WHERE ""j"".""id"" = ANY (@JobIds) AND ""jq"".""fetchedat"" IS NULL ORDER BY ""j"".""id""; "; List jobs = UseConnection(connection => connection.Query(enqueuedJobsSql, new { JobIds = jobIds.ToList() }) .AsList()); return DeserializeJobs(jobs, (sqlJob, job, stateData) => new EnqueuedJobDto { Job = job, State = sqlJob.StateName, EnqueuedAt = sqlJob.StateName == EnqueuedState.StateName ? JobHelper.DeserializeNullableDateTime(stateData["EnqueuedAt"]) : null, }); } private long GetNumberOfJobsByStateName(string stateName) { string sqlQuery = $@"SELECT COUNT(""id"") FROM ""{_storage.Options.SchemaName}"".""job"" WHERE ""statename"" = @StateName;"; return UseConnection(connection => connection.QuerySingle(sqlQuery, new { StateName = stateName })); } private static Job DeserializeJob(string invocationData, string arguments) { InvocationData data = SerializationHelper.Deserialize(invocationData); data.Arguments = arguments; try { return data.DeserializeJob(); } catch (JobLoadException) { return null; } } private JobList GetJobs(int from, int count, string stateName, Func, TDto> selector) { string jobsSql = $@" SELECT ""j"".""id"" ""Id"", ""j"".""invocationdata"" ""InvocationData"", ""j"".""arguments"" ""Arguments"", ""j"".""createdat"" ""CreatedAt"", ""j"".""expireat"" ""ExpireAt"", NULL ""FetchedAt"", ""j"".""statename"" ""StateName"", ""s"".""reason"" ""StateReason"", ""s"".""data"" ""StateData"" FROM ""{_storage.Options.SchemaName}"".""job"" ""j"" LEFT JOIN ""{_storage.Options.SchemaName}"".""state"" ""s"" ON ""j"".""stateid"" = ""s"".""id"" WHERE ""j"".""statename"" = @StateName ORDER BY ""j"".""id"" DESC LIMIT @Limit OFFSET @Offset; "; List jobs = UseConnection(connection => connection.Query(jobsSql, new { StateName = stateName, Limit = count, Offset = from }) .AsList()); return DeserializeJobs(jobs, selector); } private static JobList DeserializeJobs( ICollection jobs, Func, TDto> selector) { List> result = new(jobs.Count); foreach (SqlJob job in jobs) { TDto dto = default; if (job.InvocationData != null) { Dictionary deserializedData = SerializationHelper.Deserialize>(job.StateData); SafeDictionary stateData = deserializedData != null ? new SafeDictionary(deserializedData, StringComparer.OrdinalIgnoreCase) : null; dto = selector(job, DeserializeJob(job.InvocationData, job.Arguments), stateData); } result.Add(new KeyValuePair(job.Id.ToString(), dto)); } return new JobList(result); } private JobList FetchedJobs( IEnumerable jobIds) { string fetchedJobsSql = $@" SELECT DISTINCT ON (""j"".""id"") ""j"".""id"" ""Id"", ""j"".""invocationdata"" ""InvocationData"", ""j"".""arguments"" ""Arguments"", ""j"".""createdat"" ""CreatedAt"", ""j"".""expireat"" ""ExpireAt"", ""jq"".""fetchedat"" ""FetchedAt"", ""j"".""statename"" ""StateName"", ""s"".""reason"" ""StateReason"", ""s"".""data"" ""StateData"" FROM ""{_storage.Options.SchemaName}"".""job"" ""j"" LEFT JOIN ""{_storage.Options.SchemaName}"".""state"" ""s"" ON ""j"".""stateid"" = ""s"".""id"" LEFT JOIN ""{_storage.Options.SchemaName}"".""jobqueue"" ""jq"" ON ""jq"".""jobid"" = ""j"".""id"" WHERE ""j"".""id"" = ANY (@JobIds) AND ""jq"".""fetchedat"" IS NOT NULL ORDER BY ""j"".""id"", ""jq"".""fetchedat"" DESC; "; List jobs = UseConnection(connection => connection.Query(fetchedJobsSql, new { JobIds = jobIds.ToList() }) .AsList()); Dictionary result = jobs.ToDictionary(job => job.Id.ToString(), job => new FetchedJobDto { Job = DeserializeJob(job.InvocationData, job.Arguments), State = job.StateName, FetchedAt = job.FetchedAt, }); return new JobList(result); } private T UseConnection(Func func) { return _storage.UseConnection(null, func); } /// /// Overloaded dictionary that doesn't throw if given an invalid key /// Fixes issues such as https://github.com/frankhommers/Hangfire.PostgreSql/issues/79 /// private class SafeDictionary : Dictionary { public SafeDictionary(IDictionary dictionary, IEqualityComparer comparer) : base(dictionary, comparer) { } public new TValue this[TKey i] { // ReSharper disable once ArrangeDefaultValueWhenTypeNotEvident get => ContainsKey(i) ? base[i] : default; // ReSharper disable once UnusedMember.Local set => base[i] = value; } } } } ================================================ FILE: src/Hangfire.PostgreSql/PostgreSqlObjectsInstaller.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using System.Data; using System.Globalization; using System.IO; using System.Reflection; using System.Resources; using Hangfire.Logging; using Npgsql; namespace Hangfire.PostgreSql { public static class PostgreSqlObjectsInstaller { private static readonly ILog _logger = LogProvider.GetLogger(typeof(PostgreSqlStorage)); public static void Install(NpgsqlConnection connection, string schemaName = "hangfire") { if (connection == null) { throw new ArgumentNullException(nameof(connection)); } _logger.Info("Start installing Hangfire SQL objects..."); // starts with version 3 to keep in check with Hangfire SqlServer, but I couldn't keep up with that idea after all; int version = 3; int previousVersion = 1; do { try { string script; try { script = GetStringResource(typeof(PostgreSqlObjectsInstaller).GetTypeInfo().Assembly, $"Hangfire.PostgreSql.Scripts.Install.v{version.ToString(CultureInfo.InvariantCulture)}.sql"); } catch (MissingManifestResourceException) { break; } if (schemaName != "hangfire") { script = script.Replace("'hangfire'", $"'{schemaName}'").Replace(@"""hangfire""", $@"""{schemaName}"""); } if (!VersionAlreadyApplied(connection, schemaName, version)) { string commandText = $@"{script}; UPDATE ""{schemaName}"".""schema"" SET ""version"" = @Version WHERE ""version"" = @PreviousVersion"; using NpgsqlTransaction transaction = connection.BeginTransaction(IsolationLevel.Serializable); using NpgsqlCommand command = new(commandText, connection, transaction); command.CommandTimeout = 120; command.Parameters.Add(new NpgsqlParameter("Version", version)); command.Parameters.Add(new NpgsqlParameter("PreviousVersion", previousVersion)); try { command.ExecuteNonQuery(); transaction.Commit(); } catch (PostgresException ex) { if ((ex.MessageText ?? "") != "version-already-applied") { throw; } } } } catch (Exception ex) { if (ex.Source.Equals("Npgsql")) { _logger.ErrorException("Error while executing install/upgrade", ex); } throw; } previousVersion = version; version++; } while (true); _logger.Info("Hangfire SQL objects installed."); } private static bool VersionAlreadyApplied(NpgsqlConnection connection, string schemaName, int version) { try { using NpgsqlCommand command = new($@"SELECT true ""VersionAlreadyApplied"" FROM ""{schemaName}"".""schema"" WHERE ""version"" >= $1", connection); command.Parameters.Add(new NpgsqlParameter { Value = version }); object result = command.ExecuteScalar(); if (true.Equals(result)) { return true; } } catch (PostgresException ex) { if (ex.SqlState.Equals(PostgresErrorCodes.UndefinedTable)) //42P01: Relation (table) does not exist. So no schema table yet. { return false; } throw; } return false; } private static string GetStringResource(Assembly assembly, string resourceName) { using Stream stream = assembly.GetManifestResourceStream(resourceName); if (stream == null) { throw new MissingManifestResourceException($"Requested resource `{resourceName}` was not found in the assembly `{assembly}`."); } using StreamReader reader = new(stream); return reader.ReadToEnd(); } } } ================================================ FILE: src/Hangfire.PostgreSql/PostgreSqlStorage.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Text; using System.Threading; using System.Transactions; using Hangfire.Annotations; using Hangfire.Logging; using Hangfire.PostgreSql.Factories; using Hangfire.PostgreSql.Utils; using Hangfire.Server; using Hangfire.Storage; using Npgsql; using IsolationLevel = System.Transactions.IsolationLevel; namespace Hangfire.PostgreSql { public class PostgreSqlStorage : JobStorage { private readonly IConnectionFactory _connectionFactory; private readonly object _initializationLock = new(); private bool _initialized; private Exception _lastInitializationException; private readonly Dictionary _features = new(StringComparer.OrdinalIgnoreCase) { { JobStorageFeatures.JobQueueProperty, true }, { JobStorageFeatures.Connection.BatchedGetFirstByLowest, true } }; [Obsolete("Will be removed in 2.0, please use the overload with IConnectionFactory argument")] public PostgreSqlStorage(string connectionString) : this(connectionString, new PostgreSqlStorageOptions()) { } [Obsolete("Will be removed in 2.0, please use the overload with IConnectionFactory argument")] public PostgreSqlStorage(string connectionString, PostgreSqlStorageOptions options) : this(connectionString, null, options) { } /// /// Initializes PostgreSqlStorage from the provided PostgreSqlStorageOptions and either the provided connection string. /// /// PostgreSQL connection string /// Optional setup action to apply to created connections /// Storage options /// argument is null. /// argument is null. /// argument not a valid PostgreSQL connection string config file. [Obsolete("Will be removed in 2.0, please use the overload with IConnectionFactory argument")] public PostgreSqlStorage(string connectionString, Action connectionSetup, PostgreSqlStorageOptions options) : this(new NpgsqlConnectionFactory(connectionString, options, connectionSetup), options) { } [Obsolete("Will be removed in 2.0, please use the overload with IConnectionFactory argument")] public PostgreSqlStorage(NpgsqlConnection existingConnection) : this(existingConnection, new PostgreSqlStorageOptions()) { } /// /// Initializes a new instance of the class with /// explicit instance of the class that will be used /// to query the data. /// /// Existing connection /// PostgreSqlStorageOptions [Obsolete("Will be removed in 2.0, please use the overload with IConnectionFactory argument")] public PostgreSqlStorage(NpgsqlConnection existingConnection, PostgreSqlStorageOptions options) : this(new ExistingNpgsqlConnectionFactory(existingConnection, options), options) { } public PostgreSqlStorage(IConnectionFactory connectionFactory) : this(connectionFactory, new PostgreSqlStorageOptions()) { } public PostgreSqlStorage(IConnectionFactory connectionFactory, PostgreSqlStorageOptions options) { _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); Options = options ?? throw new ArgumentNullException(nameof(options)); InitializeQueueProviders(); if (Options.UseSlidingInvisibilityTimeout) { HeartbeatProcess = new PostgreSqlHeartbeatProcess(); } // Perform eager initialization if schema preparation is requested. This can be made resilient // via the options exposed on PostgreSqlStorageOptions. if (Options.PrepareSchemaIfNecessary) { TryInitializeStorage(isStartup: true); } } public PersistentJobQueueProviderCollection QueueProviders { get; internal set; } internal PostgreSqlStorageOptions Options { get; } internal PostgreSqlHeartbeatProcess HeartbeatProcess { get; } public override IMonitoringApi GetMonitoringApi() { EnsureInitialized(); return new PostgreSqlMonitoringApi(this, QueueProviders); } public override IStorageConnection GetConnection() { EnsureInitialized(); return new PostgreSqlConnection(this); } #pragma warning disable CS0618 public override IEnumerable GetComponents() #pragma warning restore CS0618 { yield return new ExpirationManager(this); yield return new CountersAggregator(this, Options.CountersAggregateInterval); if (Options.UseSlidingInvisibilityTimeout) { // This is only used to update the sliding invisibility timeouts, so if not enabled then do not use it yield return HeartbeatProcess; } } public override void WriteOptionsToLog(ILog logger) { logger.Info("Using the following options for PostgreSQL job storage:"); logger.InfoFormat(" Queue poll interval: {0}.", Options.QueuePollInterval); logger.InfoFormat(" Invisibility timeout: {0}.", Options.InvisibilityTimeout); logger.InfoFormat(" Use sliding invisibility timeout: {0}.", Options.UseSlidingInvisibilityTimeout); } public override string ToString() { const string canNotParseMessage = ""; try { StringBuilder builder = new(); if (_connectionFactory is NpgsqlInstanceConnectionFactoryBase connectionFactory) { NpgsqlConnectionStringBuilder connectionStringBuilder = connectionFactory.ConnectionString; builder.Append("Host: "); builder.Append(connectionStringBuilder.Host); builder.Append(", DB: "); builder.Append(connectionStringBuilder.Database); builder.Append(", "); } builder.Append("Schema: "); builder.Append(Options.SchemaName); return builder.Length != 0 ? $"PostgreSQL Server: {builder}" : canNotParseMessage; } catch (Exception) { return canNotParseMessage; } } internal NpgsqlConnection CreateAndOpenConnection() { NpgsqlConnection connection = _connectionFactory.GetOrCreateConnection(); try { if (connection.State == ConnectionState.Closed) { connection.Open(); } if (Options.EnableLongPolling && !connection.SupportsNotifications()) { throw new InvalidOperationException("Long polling is supported only with PostgreSQL version 11 or higher."); } return connection; } catch { ReleaseConnection(connection); throw; } } /// /// Ensures storage is initialized. When resilient startup and degraded mode are enabled, /// this will attempt a lazy initialization on first use. /// private void EnsureInitialized() { if (_initialized || !Options.PrepareSchemaIfNecessary) { return; } lock (_initializationLock) { if (_initialized) { return; } TryInitializeStorage(isStartup: false); if (!_initialized && !Options.AllowDegradedModeWithoutStorage) { // Initialization failed and degraded mode is not enabled - rethrow with the last error // to give a clear signal to the caller. throw new InvalidOperationException( "Hangfire PostgreSQL storage is not initialized. See inner exception for details.", _lastInitializationException); } } } private void TryInitializeStorage(bool isStartup) { // Fast-path: no resilient startup configured - keep the current behavior of a single attempt. if (!Options.EnableResilientStartup) { PerformSingleInitializationAttempt(); _initialized = true; _lastInitializationException = null; return; } int attempts = 0; int maxAttempts = 1 + Options.StartupConnectionMaxRetries; // initial + retries Exception lastException = null; while (attempts < maxAttempts) { try { PerformSingleInitializationAttempt(); _initialized = true; _lastInitializationException = null; return; } catch (Exception ex) { lastException = ex; attempts++; if (attempts >= maxAttempts) { break; } // Apply exponential backoff with capping. TimeSpan delay = ComputeBackoffDelay(attempts, Options.StartupConnectionBaseDelay, Options.StartupConnectionMaxDelay); try { Thread.Sleep(delay); } catch (ThreadInterruptedException) { // Preserve original exception and abort initialization. break; } } } _initialized = false; _lastInitializationException = lastException; if (!Options.AllowDegradedModeWithoutStorage && isStartup) { // During startup without degraded mode, fail fast to avoid starting the app in // a partially configured state. throw new InvalidOperationException( "Failed to initialize Hangfire PostgreSQL storage.", lastException); } // When degraded mode is allowed, we swallow the exception here and leave storage // uninitialized. Subsequent calls will attempt to initialize lazily via EnsureInitialized. } private void PerformSingleInitializationAttempt() { NpgsqlConnection connection = CreateAndOpenConnection(); try { PostgreSqlObjectsInstaller.Install(connection, Options.SchemaName); } finally { if (_connectionFactory is not ExistingNpgsqlConnectionFactory) { connection.Dispose(); } } } private static TimeSpan ComputeBackoffDelay(int attempt, TimeSpan baseDelay, TimeSpan maxDelay) { if (attempt <= 0) { return baseDelay; } double factor = Math.Pow(2, attempt - 1); double millis = baseDelay.TotalMilliseconds * factor; if (millis < 0 || millis > maxDelay.TotalMilliseconds || double.IsInfinity(millis) || double.IsNaN(millis)) { millis = maxDelay.TotalMilliseconds; } return TimeSpan.FromMilliseconds(millis); } internal void UseTransaction(DbConnection dedicatedConnection, [InstantHandle] Action action, IsolationLevel? isolationLevel = null) { UseTransaction(dedicatedConnection, (connection, transaction) => { action(connection, transaction); return true; }, isolationLevel); } internal T UseTransaction(DbConnection dedicatedConnection, [InstantHandle] Func func, IsolationLevel? isolationLevel = null) { // Use isolation level of an already opened transaction in order to avoid isolation level conflict isolationLevel ??= Transaction.Current?.IsolationLevel ?? IsolationLevel.ReadCommitted; if (!EnvironmentHelpers.IsMono()) { T result = UseConnection(dedicatedConnection, connection => { using TransactionScope transaction = CreateTransactionScope(isolationLevel); connection.EnlistTransaction(Transaction.Current); T result = func(connection, null); transaction.Complete(); return result; }); return result; } return UseConnection(dedicatedConnection, connection => { System.Data.IsolationLevel transactionIsolationLevel = ConvertIsolationLevel(isolationLevel) ?? System.Data.IsolationLevel.ReadCommitted; using DbTransaction transaction = connection.BeginTransaction(transactionIsolationLevel); T result; try { result = func(connection, transaction); transaction.Commit(); } catch { if (transaction.Connection != null) { // Don't rely on implicit rollback when calling the Dispose // method, because some implementations may throw the // NullReferenceException, although it's prohibited to throw // any exception from a Dispose method, according to the // .NET Framework Design Guidelines: // https://github.com/dotnet/efcore/issues/12864 // https://github.com/HangfireIO/Hangfire/issues/1494 transaction.Rollback(); } throw; } return result; }); } internal void UseTransaction(DbConnection dedicatedConnection, Action action, Func transactionScopeFactory) { UseTransaction(dedicatedConnection, (connection, transaction) => { action(connection, transaction); return true; }, transactionScopeFactory); } internal T UseTransaction(DbConnection dedicatedConnection, Func func, Func transactionScopeFactory) { return UseConnection(dedicatedConnection, connection => { using TransactionScope transaction = transactionScopeFactory(); connection.EnlistTransaction(Transaction.Current); T result = func(connection, null); transaction.Complete(); return result; }); } internal TransactionScope CreateTransactionScope(IsolationLevel? isolationLevel, TimeSpan? timeout = null) { return TransactionHelpers.CreateTransactionScope(isolationLevel, Options.EnableTransactionScopeEnlistment, timeout); } private static System.Data.IsolationLevel? ConvertIsolationLevel(IsolationLevel? isolationLevel) { return isolationLevel switch { IsolationLevel.Chaos => System.Data.IsolationLevel.Chaos, IsolationLevel.ReadCommitted => System.Data.IsolationLevel.ReadCommitted, IsolationLevel.ReadUncommitted => System.Data.IsolationLevel.ReadUncommitted, IsolationLevel.RepeatableRead => System.Data.IsolationLevel.RepeatableRead, IsolationLevel.Serializable => System.Data.IsolationLevel.Serializable, IsolationLevel.Snapshot => System.Data.IsolationLevel.Snapshot, IsolationLevel.Unspecified => System.Data.IsolationLevel.Unspecified, null => null, var _ => throw new ArgumentOutOfRangeException(nameof(isolationLevel), isolationLevel, null), }; } internal void UseConnection(DbConnection dedicatedConnection, [InstantHandle] Action action) { UseConnection(dedicatedConnection, connection => { action(connection); return true; }); } internal T UseConnection(DbConnection dedicatedConnection, Func func) { DbConnection connection = null; try { connection = dedicatedConnection ?? CreateAndOpenConnection(); return func(connection); } finally { if (dedicatedConnection == null) { ReleaseConnection(connection); } } } internal void ReleaseConnection(DbConnection connection) { if (connection != null && !IsExistingConnection(connection)) { connection.Dispose(); } } private bool IsExistingConnection(IDbConnection connection) { return connection != null && _connectionFactory is ExistingNpgsqlConnectionFactory && ReferenceEquals(connection, _connectionFactory.GetOrCreateConnection()); } private void InitializeQueueProviders() { PostgreSqlJobQueueProvider defaultQueueProvider = new(this, Options); QueueProviders = new PersistentJobQueueProviderCollection(defaultQueueProvider); } public override bool HasFeature(string featureId) { if (featureId == null) { throw new ArgumentNullException(nameof(featureId)); } return _features.TryGetValue(featureId, out bool isSupported) ? isSupported : base.HasFeature(featureId); } } } ================================================ FILE: src/Hangfire.PostgreSql/PostgreSqlStorageOptions.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; namespace Hangfire.PostgreSql { public class PostgreSqlStorageOptions { private static readonly TimeSpan _minimumQueuePollInterval = TimeSpan.FromMilliseconds(50); private int _deleteExpiredBatchSize; private TimeSpan _distributedLockTimeout; private TimeSpan _invisibilityTimeout; private TimeSpan _jobExpirationCheckInterval; private TimeSpan _queuePollInterval; private TimeSpan _transactionSerializationTimeout; private TimeSpan _countersAggregateInterval; public PostgreSqlStorageOptions() { QueuePollInterval = TimeSpan.FromSeconds(15); InvisibilityTimeout = TimeSpan.FromMinutes(30); DistributedLockTimeout = TimeSpan.FromMinutes(10); TransactionSynchronisationTimeout = TimeSpan.FromMilliseconds(500); JobExpirationCheckInterval = TimeSpan.FromHours(1); CountersAggregateInterval = TimeSpan.FromMinutes(5); SchemaName = "hangfire"; AllowUnsafeValues = false; UseNativeDatabaseTransactions = true; PrepareSchemaIfNecessary = true; EnableTransactionScopeEnlistment = true; DeleteExpiredBatchSize = 1000; UseSlidingInvisibilityTimeout = false; StartupConnectionMaxRetries = 5; StartupConnectionBaseDelay = TimeSpan.FromSeconds(1); StartupConnectionMaxDelay = TimeSpan.FromMinutes(1); AllowDegradedModeWithoutStorage = true; } public TimeSpan QueuePollInterval { get => _queuePollInterval; set { ThrowIfValueIsLowerThan(_minimumQueuePollInterval, value, nameof(QueuePollInterval)); _queuePollInterval = value; } } public TimeSpan InvisibilityTimeout { get => _invisibilityTimeout; set { ThrowIfValueIsNotPositive(value, nameof(InvisibilityTimeout)); _invisibilityTimeout = value; } } public TimeSpan DistributedLockTimeout { get => _distributedLockTimeout; set { ThrowIfValueIsNotPositive(value, nameof(DistributedLockTimeout)); _distributedLockTimeout = value; } } // ReSharper disable once IdentifierTypo public TimeSpan TransactionSynchronisationTimeout { get => _transactionSerializationTimeout; set { ThrowIfValueIsNotPositive(value, nameof(TransactionSynchronisationTimeout)); _transactionSerializationTimeout = value; } } public TimeSpan JobExpirationCheckInterval { get => _jobExpirationCheckInterval; set { ThrowIfValueIsNotPositive(value, nameof(JobExpirationCheckInterval)); _jobExpirationCheckInterval = value; } } public TimeSpan CountersAggregateInterval { get => _countersAggregateInterval; set { ThrowIfValueIsNotPositive(value, nameof(CountersAggregateInterval)); _countersAggregateInterval = value; } } /// /// Gets or sets the number of records deleted in a single batch in expiration manager /// public int DeleteExpiredBatchSize { get => _deleteExpiredBatchSize; set { ThrowIfValueIsNotPositive(value, nameof(DeleteExpiredBatchSize)); _deleteExpiredBatchSize = value; } } public bool AllowUnsafeValues { get; set; } public bool UseNativeDatabaseTransactions { get; set; } public bool PrepareSchemaIfNecessary { get; set; } public string SchemaName { get; set; } public bool EnableTransactionScopeEnlistment { get; set; } public bool EnableLongPolling { get; set; } /// /// Apply a sliding invisibility timeout where the last fetched time is continually updated in the background. /// This allows a lower invisibility timeout to be used with longer running jobs /// IMPORTANT: If option is used, then sliding invisiblity timeouts will not work /// since the background storage processes are not run (which is used to update the invisibility timeouts) /// public bool UseSlidingInvisibilityTimeout { get; set; } /// /// Gets if additional resilience during storage initialization is enabled. When /// is greater than zero and is true, Hangfire will retry opening a /// connection and installing schema instead of failing immediately. /// This property is computed from . /// public bool EnableResilientStartup => StartupConnectionMaxRetries > 0; /// /// Maximum number of additional attempts (after the initial one) to obtain a connection and /// prepare the schema during startup when is true. /// Value of 0 keeps current behavior (no retries). /// public int StartupConnectionMaxRetries { get; set; } /// /// Base delay used to compute exponential backoff between startup connection attempts when is true. /// public TimeSpan StartupConnectionBaseDelay { get; set; } /// /// Maximum delay between startup connection attempts when is true. /// public TimeSpan StartupConnectionMaxDelay { get; set; } /// /// When true and is enabled, storage initialization will /// not throw even if all startup connection attempts fail. Instead, the storage starts in a /// degraded mode and will attempt to initialize lazily on first use. /// public bool AllowDegradedModeWithoutStorage { get; set; } private static void ThrowIfValueIsNotPositive(TimeSpan value, string fieldName) { string message = $"The {fieldName} property value should be positive. Given: {value}."; if (value == TimeSpan.Zero) { throw new ArgumentException(message, nameof(value)); } if (value != value.Duration()) { throw new ArgumentException(message, nameof(value)); } } private void ThrowIfValueIsLowerThan(TimeSpan minValue, TimeSpan value, string fieldName) { if (!AllowUnsafeValues) { string message = $"The {fieldName} property value seems to be too low ({value}, lower than suggested minimum of {minValue}). Consider increasing it. If you really need to have such a low value, please set {nameof(PostgreSqlStorageOptions)}.{nameof(AllowUnsafeValues)} to true."; if (value < minValue) { throw new ArgumentException(message, nameof(value)); } } ThrowIfValueIsNotPositive(value, fieldName); } private static void ThrowIfValueIsNotPositive(int value, string fieldName) { if (value <= 0) { throw new ArgumentException($"The {fieldName} property value should be positive. Given: {value}."); } } } } ================================================ FILE: src/Hangfire.PostgreSql/PostgreSqlWriteOnlyTransaction.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Globalization; using System.Linq; using System.Transactions; using Dapper; using Hangfire.Common; using Hangfire.States; using Hangfire.Storage; namespace Hangfire.PostgreSql { public class PostgreSqlWriteOnlyTransaction : JobStorageTransaction { private readonly Queue> _commandQueue = new(); private readonly Func _dedicatedConnectionFunc; private readonly List _queuesWithAddedJobs = new(); private readonly PostgreSqlStorage _storage; public PostgreSqlWriteOnlyTransaction( PostgreSqlStorage storage, Func dedicatedConnectionFunc) { _storage = storage ?? throw new ArgumentNullException(nameof(storage)); _dedicatedConnectionFunc = dedicatedConnectionFunc ?? throw new ArgumentNullException(nameof(dedicatedConnectionFunc)); } public override void Commit() { _storage.UseTransaction(_dedicatedConnectionFunc(), (connection, _) => { RegisterNewJobsEventWithTransactionCompletedEvent(); foreach (Action command in _commandQueue) { command(connection); } }, CreateTransactionScope); } private void RegisterNewJobsEventWithTransactionCompletedEvent() { // TransactionCompleted event is required here, because if this TransactionScope is enlisted // within an ambient TransactionScope, the ambient TransactionScope controls when the TransactionScope completes. Transaction.Current.TransactionCompleted += (_, args) => { if (args.Transaction.TransactionInformation.Status == TransactionStatus.Committed) { // Triggers signals for all queues to which jobs have been added in this transaction _queuesWithAddedJobs.ForEach(PostgreSqlJobQueue._queueEventRegistry.Set); _queuesWithAddedJobs.Clear(); } }; } private TransactionScope CreateTransactionScope() { return _storage.CreateTransactionScope(null, TransactionManager.MaximumTimeout); } public override void ExpireJob(string jobId, TimeSpan expireIn) { string sql = $@" UPDATE ""{_storage.Options.SchemaName}"".""job"" SET ""expireat"" = NOW() + INTERVAL '{(long)expireIn.TotalSeconds} SECONDS' WHERE ""id"" = @Id; "; QueueCommand(con => con.Execute(sql, new { Id = Convert.ToInt64(jobId, CultureInfo.InvariantCulture) })); } public override void PersistJob(string jobId) { string sql = $@" UPDATE ""{_storage.Options.SchemaName}"".""job"" SET ""expireat"" = NULL WHERE ""id"" = @Id; "; QueueCommand(con => con.Execute(sql, new { Id = Convert.ToInt64(jobId, CultureInfo.InvariantCulture) })); } public override void SetJobState(string jobId, IState state) { string addAndSetStateSql = $@" WITH ""s"" AS ( INSERT INTO ""{_storage.Options.SchemaName}"".""state"" (""jobid"", ""name"", ""reason"", ""createdat"", ""data"") VALUES (@JobId, @Name, @Reason, @CreatedAt, @Data::jsonb) RETURNING ""id"" ) UPDATE ""{_storage.Options.SchemaName}"".""job"" ""j"" SET ""stateid"" = s.""id"", ""statename"" = @Name FROM ""s"" WHERE ""j"".""id"" = @Id; "; QueueCommand(con => con.Execute(addAndSetStateSql, new { JobId = Convert.ToInt64(jobId, CultureInfo.InvariantCulture), state.Name, state.Reason, CreatedAt = DateTime.UtcNow, Data = JsonParameter.GetParameterValue(SerializationHelper.Serialize(state.SerializeData())), Id = Convert.ToInt64(jobId, CultureInfo.InvariantCulture), })); } public override void AddJobState(string jobId, IState state) { string addStateSql = $@" INSERT INTO ""{_storage.Options.SchemaName}"".""state"" (""jobid"", ""name"", ""reason"", ""createdat"", ""data"") VALUES (@JobId, @Name, @Reason, @CreatedAt, @Data::jsonb); "; QueueCommand(con => con.Execute(addStateSql, new { JobId = Convert.ToInt64(jobId, CultureInfo.InvariantCulture), state.Name, state.Reason, CreatedAt = DateTime.UtcNow, Data = JsonParameter.GetParameterValue(SerializationHelper.Serialize(state.SerializeData())), })); } public override void AddToQueue(string queue, string jobId) { IPersistentJobQueueProvider provider = _storage.QueueProviders.GetProvider(queue); IPersistentJobQueue persistentQueue = provider.GetJobQueue(); QueueCommand(con => persistentQueue.Enqueue(con, queue, jobId)); _queuesWithAddedJobs.Add(queue); } public override void IncrementCounter(string key) { string sql = $@" INSERT INTO ""{_storage.Options.SchemaName}"".""counter"" (""key"", ""value"") VALUES (@Key, @Value); "; QueueCommand(con => con.Execute(sql, new { Key = key, Value = +1 })); } public override void IncrementCounter(string key, TimeSpan expireIn) { string sql = $@" INSERT INTO ""{_storage.Options.SchemaName}"".""counter""(""key"", ""value"", ""expireat"") VALUES (@Key, @Value, NOW() + INTERVAL '{(long)expireIn.TotalSeconds} SECONDS'); "; QueueCommand(con => con.Execute(sql, new { Key = key, Value = +1 })); } public override void DecrementCounter(string key) { string sql = $@" INSERT INTO ""{_storage.Options.SchemaName}"".""counter"" (""key"", ""value"") VALUES (@Key, @Value); "; QueueCommand(con => con.Execute(sql, new { Key = key, Value = -1 })); } public override void DecrementCounter(string key, TimeSpan expireIn) { string sql = $@" INSERT INTO ""{_storage.Options.SchemaName}"".""counter""(""key"", ""value"", ""expireat"") VALUES (@Key, @Value, NOW() + INTERVAL '{((long)expireIn.TotalSeconds).ToString(CultureInfo.InvariantCulture)} SECONDS'); "; QueueCommand(con => con.Execute(sql, new { Key = key, Value = -1 })); } public override void AddToSet(string key, string value) { AddToSet(key, value, 0.0); } public override void AddToSet(string key, string value, double score) { string addSql = $@" INSERT INTO ""{_storage.Options.SchemaName}"".""set""(""key"", ""value"", ""score"") VALUES(@Key, @Value, @Score) ON CONFLICT (""key"", ""value"") DO UPDATE SET ""score"" = EXCLUDED.""score"" "; QueueCommand(con => con.Execute(addSql, new { Key = key, Value = value, Score = score })); } public override void RemoveFromSet(string key, string value) { QueueCommand(con => con.Execute($@" DELETE FROM ""{_storage.Options.SchemaName}"".""set"" WHERE ""key"" = @Key AND ""value"" = @Value; ", new { Key = key, Value = value })); } public override void InsertToList(string key, string value) { QueueCommand(con => con.Execute($@" INSERT INTO ""{_storage.Options.SchemaName}"".""list"" (""key"", ""value"") VALUES (@Key, @Value); ", new { Key = key, Value = value })); } public override void RemoveFromList(string key, string value) { QueueCommand(con => con.Execute($@" DELETE FROM ""{_storage.Options.SchemaName}"".""list"" WHERE ""key"" = @Key AND ""value"" = @Value; ", new { Key = key, Value = value })); } public override void TrimList(string key, int keepStartingFrom, int keepEndingAt) { string trimSql = $@" DELETE FROM ""{_storage.Options.SchemaName}"".""list"" AS source WHERE ""key"" = @Key AND ""id"" NOT IN ( SELECT ""id"" FROM ""{_storage.Options.SchemaName}"".""list"" AS keep WHERE keep.""key"" = source.""key"" ORDER BY ""id"" OFFSET @Offset LIMIT @Limit ); "; QueueCommand(con => con.Execute(trimSql, new { Key = key, Offset = keepStartingFrom, Limit = keepEndingAt - keepStartingFrom + 1 })); } public override void SetRangeInHash(string key, IEnumerable> keyValuePairs) { if (key == null) { throw new ArgumentNullException(nameof(key)); } if (keyValuePairs == null) { throw new ArgumentNullException(nameof(keyValuePairs)); } string sql = $@" WITH ""inputvalues"" AS ( SELECT @Key ""key"", @Field ""field"", @Value ""value"" ), ""updatedrows"" AS ( UPDATE ""{_storage.Options.SchemaName}"".""hash"" ""updatetarget"" SET ""value"" = ""inputvalues"".""value"" FROM ""inputvalues"" WHERE ""updatetarget"".""key"" = ""inputvalues"".""key"" AND ""updatetarget"".""field"" = ""inputvalues"".""field"" RETURNING ""updatetarget"".""key"", ""updatetarget"".""field"" ) INSERT INTO ""{_storage.Options.SchemaName}"".""hash""(""key"", ""field"", ""value"") SELECT ""key"", ""field"", ""value"" FROM ""inputvalues"" ""insertvalues"" WHERE NOT EXISTS ( SELECT 1 FROM ""updatedrows"" WHERE ""updatedrows"".""key"" = ""insertvalues"".""key"" AND ""updatedrows"".""field"" = ""insertvalues"".""field"" ); "; foreach (KeyValuePair keyValuePair in keyValuePairs) { KeyValuePair pair = keyValuePair; QueueCommand(con => con.Execute(sql, new { Key = key, Field = pair.Key, pair.Value })); } } public override void RemoveHash(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } string sql = $@"DELETE FROM ""{_storage.Options.SchemaName}"".""hash"" WHERE ""key"" = @Key"; QueueCommand(con => con.Execute(sql, new { Key = key })); } public override void ExpireSet(string key, TimeSpan expireIn) { if (key == null) { throw new ArgumentNullException(nameof(key)); } string sql = $@"UPDATE ""{_storage.Options.SchemaName}"".""set"" SET ""expireat"" = @ExpireAt WHERE ""key"" = @Key"; QueueCommand(connection => connection.Execute(sql, new { Key = key, ExpireAt = DateTime.UtcNow.Add(expireIn) })); } public override void ExpireList(string key, TimeSpan expireIn) { if (key == null) { throw new ArgumentNullException(nameof(key)); } string sql = $@"UPDATE ""{_storage.Options.SchemaName}"".""list"" SET ""expireat"" = @ExpireAt WHERE ""key"" = @Key"; QueueCommand(connection => connection.Execute(sql, new { Key = key, ExpireAt = DateTime.UtcNow.Add(expireIn) })); } public override void ExpireHash(string key, TimeSpan expireIn) { if (key == null) { throw new ArgumentNullException(nameof(key)); } string sql = $@"UPDATE ""{_storage.Options.SchemaName}"".""hash"" SET expireat = @ExpireAt WHERE ""key"" = @Key"; QueueCommand(connection => connection.Execute(sql, new { Key = key, ExpireAt = DateTime.UtcNow.Add(expireIn) })); } public override void PersistSet(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } string sql = $@"UPDATE ""{_storage.Options.SchemaName}"".""set"" SET expireat = null WHERE ""key"" = @Key"; QueueCommand(connection => connection.Execute(sql, new { Key = key })); } public override void PersistList(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } string sql = $@"UPDATE ""{_storage.Options.SchemaName}"".""list"" SET expireat = null WHERE ""key"" = @Key"; QueueCommand(connection => connection.Execute(sql, new { Key = key })); } public override void PersistHash(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } string sql = $@"UPDATE ""{_storage.Options.SchemaName}"".""hash"" SET expireat = null WHERE ""key"" = @Key"; QueueCommand(connection => connection.Execute(sql, new { Key = key })); } public override void AddRangeToSet(string key, IList items) { if (key == null) { throw new ArgumentNullException(nameof(key)); } if (items == null) { throw new ArgumentNullException(nameof(items)); } string sql = $@"INSERT INTO ""{_storage.Options.SchemaName}"".""set"" (""key"", ""value"", ""score"") VALUES (@Key, @Value, 0.0)"; QueueCommand(connection => connection.Execute(sql, items.Select(value => new { Key = key, Value = value }).ToList())); } public override void RemoveSet(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } string sql = $@"DELETE FROM ""{_storage.Options.SchemaName}"".""set"" WHERE ""key"" = @Key"; QueueCommand(connection => connection.Execute(sql, new { Key = key })); } internal void QueueCommand(Action action) { _commandQueue.Enqueue(action); } } } ================================================ FILE: src/Hangfire.PostgreSql/Properties/Annotations.cs ================================================ using System; #pragma warning disable 1591 // ReSharper disable UnusedMember.Global // ReSharper disable UnusedParameter.Local // ReSharper disable MemberCanBePrivate.Global // ReSharper disable UnusedAutoPropertyAccessor.Global // ReSharper disable IntroduceOptionalParameters.Global // ReSharper disable MemberCanBeProtected.Global // ReSharper disable InconsistentNaming namespace Hangfire.PostgreSql.Properties { /// /// Indicates that the value of the marked element could be null sometimes, /// so the check for null is necessary before its usage /// /// /// /// [CanBeNull] public object Test() { return null; } /// public void UseTest() { /// var p = Test(); /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' /// } /// /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field)] public sealed class CanBeNullAttribute : Attribute { } /// /// Indicates that the value of the marked element could never be null /// /// /// /// [NotNull] public object Foo() { /// return null; // Warning: Possible 'null' assignment /// } /// /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field)] public sealed class NotNullAttribute : Attribute { } /// /// Indicates that the marked method builds string by format pattern and (optional) arguments. /// Parameter, which contains format string, should be given in constructor. The format string /// should be in -like form /// /// /// /// [StringFormatMethod("message")] /// public void ShowError(string message, params object[] args) { /* do something */ } /// public void Foo() { /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string /// } /// /// [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method)] public sealed class StringFormatMethodAttribute : Attribute { /// /// Specifies which parameter of an annotated method should be treated as format-string /// public StringFormatMethodAttribute(string formatParameterName) { FormatParameterName = formatParameterName; } public string FormatParameterName { get; } } /// /// Indicates that the function argument should be string literal and match one /// of the parameters of the caller function. For example, ReSharper annotates /// the parameter of /// /// /// /// public void Foo(string param) { /// if (param == null) /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol /// } /// /// [AttributeUsage(AttributeTargets.Parameter)] public sealed class InvokerParameterNameAttribute : Attribute { } /// /// Indicates that the method is contained in a type that implements /// interface /// and this method is used to notify that some property value changed /// /// /// The method should be non-static and conform to one of the supported signatures: /// /// /// NotifyChanged(string) /// /// /// NotifyChanged(params string[]) /// /// /// NotifyChanged{T}(Expression{Func{T}}) /// /// /// NotifyChanged{T,U}(Expression{Func{T,U}}) /// /// /// SetProperty{T}(ref T, T, string) /// /// /// /// /// /// public class Foo : INotifyPropertyChanged { /// public event PropertyChangedEventHandler PropertyChanged; /// [NotifyPropertyChangedInvocator] /// protected virtual void NotifyChanged(string propertyName) { ... } /// /// private string _name; /// public string Name { /// get { return _name; } /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } /// } /// } /// /// Examples of generated notifications: /// /// /// NotifyChanged("Property") /// /// /// NotifyChanged(() => Property) /// /// /// NotifyChanged((VM x) => x.Property) /// /// /// SetProperty(ref myField, value, "Property") /// /// /// [AttributeUsage(AttributeTargets.Method)] public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute { public NotifyPropertyChangedInvocatorAttribute() { } public NotifyPropertyChangedInvocatorAttribute(string parameterName) { ParameterName = parameterName; } public string ParameterName { get; } } /// /// Describes dependency between method input and output /// /// ///

Function Definition Table syntax:

/// /// FDT ::= FDTRow [;FDTRow]* /// FDTRow ::= Input => Output | Output <= Input /// Input ::= ParameterName: Value [, Input]* /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} /// Value ::= true | false | null | notnull | canbenull /// /// If method has single input parameter, it's name could be omitted.
/// Using halt (or void/nothing, which is the same) /// for method output means that the methos doesn't return normally.
/// canbenull annotation is only applicable for output parameters.
/// You can use multiple [ContractAnnotation] for each FDT row, /// or use single attribute with rows separated by semicolon.
///
/// /// /// /// /// [ContractAnnotation("=> halt")] /// public void TerminationMethod() /// /// /// /// /// [ContractAnnotation("halt <= condition: false")] /// public void Assert(bool condition, string text) // regular assertion method /// /// /// /// /// [ContractAnnotation("s:null => true")] /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() /// /// /// /// /// // A method that returns null if the parameter is null, and not null if the parameter is not null /// [ContractAnnotation("null => null; notnull => notnull")] /// public object Transform(object data) /// /// /// /// /// [ContractAnnotation("s:null=>false; =>true,result:notnull; =>false, result:null")] /// public bool TryParse(string s, out Person result) /// /// /// /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public sealed class ContractAnnotationAttribute : Attribute { public ContractAnnotationAttribute([NotNull] string contract) : this(contract, false) { } public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) { Contract = contract; ForceFullStates = forceFullStates; } public string Contract { get; } public bool ForceFullStates { get; } } /// /// Indicates that marked element should be localized or not /// /// /// /// [LocalizationRequiredAttribute(true)] /// public class Foo { /// private string str = "my string"; // Warning: Localizable string /// } /// /// [AttributeUsage(AttributeTargets.All)] public sealed class LocalizationRequiredAttribute : Attribute { public LocalizationRequiredAttribute() : this(true) { } public LocalizationRequiredAttribute(bool required) { Required = required; } public bool Required { get; } } /// /// Indicates that the value of the marked type (or its derivatives) /// cannot be compared using '==' or '!=' operators and Equals() /// should be used instead. However, using '==' or '!=' for comparison /// with null is always permitted. /// /// /// /// [CannotApplyEqualityOperator] /// class NoEquality { } /// class UsesNoEquality { /// public void Test() { /// var ca1 = new NoEquality(); /// var ca2 = new NoEquality(); /// if (ca1 != null) { // OK /// bool condition = ca1 == ca2; // Warning /// } /// } /// } /// /// [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] public sealed class CannotApplyEqualityOperatorAttribute : Attribute { } /// /// When applied to a target attribute, specifies a requirement for any type marked /// with the target attribute to implement or inherit specific type or types. /// /// /// /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement /// public class ComponentAttribute : Attribute { } /// [Component] // ComponentAttribute requires implementing IComponent interface /// public class MyComponent : IComponent { } /// /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] [BaseTypeRequired(typeof(Attribute))] public sealed class BaseTypeRequiredAttribute : Attribute { public BaseTypeRequiredAttribute([NotNull] Type baseType) { BaseType = baseType; } [NotNull] public Type BaseType { get; } } /// /// Indicates that the marked symbol is used implicitly /// (e.g. via reflection, in external library), so this symbol /// will not be marked as unused (as well as by other usage inspections) /// [AttributeUsage(AttributeTargets.All)] [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] public sealed class UsedImplicitlyAttribute : Attribute { public UsedImplicitlyAttribute() : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) : this(useKindFlags, ImplicitUseTargetFlags.Default) { } public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) : this(ImplicitUseKindFlags.Default, targetFlags) { } public UsedImplicitlyAttribute( ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) { UseKindFlags = useKindFlags; TargetFlags = targetFlags; } public ImplicitUseKindFlags UseKindFlags { get; } public ImplicitUseTargetFlags TargetFlags { get; } } /// /// Should be used on attributes and causes ReSharper /// to not mark symbols marked with such attributes as unused /// (as well as by other usage inspections) /// [AttributeUsage(AttributeTargets.Class)] public sealed class MeansImplicitUseAttribute : Attribute { public MeansImplicitUseAttribute() : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) : this(useKindFlags, ImplicitUseTargetFlags.Default) { } public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) : this(ImplicitUseKindFlags.Default, targetFlags) { } public MeansImplicitUseAttribute( ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) { UseKindFlags = useKindFlags; TargetFlags = targetFlags; } [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; } [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; } } [Flags] public enum ImplicitUseKindFlags { Default = Access | Assign | InstantiatedWithFixedConstructorSignature, /// Only entity marked with attribute considered used Access = 1, /// Indicates implicit assignment to a member Assign = 2, /// /// Indicates implicit instantiation of a type with fixed constructor signature. /// That means any unused constructor parameters won't be reported as such. /// InstantiatedWithFixedConstructorSignature = 4, /// Indicates implicit instantiation of a type InstantiatedNoFixedConstructorSignature = 8, } /// /// Specify what is considered used implicitly /// when marked with /// or /// [Flags] public enum ImplicitUseTargetFlags { Default = Itself, Itself = 1, /// Members of entity marked with attribute are considered used Members = 2, /// Entity marked with attribute and all its members considered used WithMembers = Itself | Members, } /// /// This attribute is intended to mark publicly available API /// which should not be removed and so is treated as used /// [MeansImplicitUse] public sealed class PublicAPIAttribute : Attribute { public PublicAPIAttribute() { } public PublicAPIAttribute([NotNull] string comment) { Comment = comment; } [NotNull] public string Comment { get; } } /// /// Tells code analysis engine if the parameter is completely handled /// when the invoked method is on stack. If the parameter is a delegate, /// indicates that delegate is executed while the method is executed. /// If the parameter is an enumerable, indicates that it is enumerated /// while the method is executed /// [AttributeUsage(AttributeTargets.Parameter)] public sealed class InstantHandleAttribute : Attribute { } /// /// Indicates that a method does not make any observable state changes. /// The same as System.Diagnostics.Contracts.PureAttribute /// /// /// /// [Pure] private int Multiply(int x, int y) { return x * y; } /// public void Foo() { /// const int a = 2, b = 2; /// Multiply(a, b); // Waring: Return value of pure method is not used /// } /// /// [AttributeUsage(AttributeTargets.Method)] public sealed class PureAttribute : Attribute { } /// /// Indicates that a parameter is a path to a file or a folder /// within a web project. Path can be relative or absolute, /// starting from web root (~) /// [AttributeUsage(AttributeTargets.Parameter)] public class PathReferenceAttribute : Attribute { public PathReferenceAttribute() { } public PathReferenceAttribute([PathReference] string basePath) { BasePath = basePath; } [NotNull] public string BasePath { get; } } // ASP.NET MVC attributes [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute { public AspMvcAreaMasterLocationFormatAttribute(string format) { } } [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute { public AspMvcAreaPartialViewLocationFormatAttribute(string format) { } } [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute { public AspMvcAreaViewLocationFormatAttribute(string format) { } } [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public sealed class AspMvcMasterLocationFormatAttribute : Attribute { public AspMvcMasterLocationFormatAttribute(string format) { } } [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute { public AspMvcPartialViewLocationFormatAttribute(string format) { } } [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public sealed class AspMvcViewLocationFormatAttribute : Attribute { public AspMvcViewLocationFormatAttribute(string format) { } } /// /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter /// is an MVC action. If applied to a method, the MVC action name is calculated /// implicitly from the context. Use this attribute for custom wrappers similar to /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String) /// [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] public sealed class AspMvcActionAttribute : Attribute { public AspMvcActionAttribute() { } public AspMvcActionAttribute([NotNull] string anonymousProperty) { AnonymousProperty = anonymousProperty; } [NotNull] public string AnonymousProperty { get; } } /// /// ASP.NET MVC attribute. Indicates that a parameter is an MVC area. /// Use this attribute for custom wrappers similar to /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String) /// [AttributeUsage(AttributeTargets.Parameter)] public sealed class AspMvcAreaAttribute : PathReferenceAttribute { public AspMvcAreaAttribute() { } public AspMvcAreaAttribute([NotNull] string anonymousProperty) { AnonymousProperty = anonymousProperty; } [NotNull] public string AnonymousProperty { get; } } /// /// ASP.NET MVC attribute. If applied to a parameter, indicates that /// the parameter is an MVC controller. If applied to a method, /// the MVC controller name is calculated implicitly from the context. /// Use this attribute for custom wrappers similar to /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String) /// [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] public sealed class AspMvcControllerAttribute : Attribute { public AspMvcControllerAttribute() { } public AspMvcControllerAttribute([NotNull] string anonymousProperty) { AnonymousProperty = anonymousProperty; } [NotNull] public string AnonymousProperty { get; } } /// /// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. /// Use this attribute for custom wrappers similar to /// System.Web.Mvc.Controller.View(String, String) /// [AttributeUsage(AttributeTargets.Parameter)] public sealed class AspMvcMasterAttribute : Attribute { } /// /// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. /// Use this attribute for custom wrappers similar to /// System.Web.Mvc.Controller.View(String, Object) /// [AttributeUsage(AttributeTargets.Parameter)] public sealed class AspMvcModelTypeAttribute : Attribute { } /// /// ASP.NET MVC attribute. If applied to a parameter, indicates that /// the parameter is an MVC partial view. If applied to a method, /// the MVC partial view name is calculated implicitly from the context. /// Use this attribute for custom wrappers similar to /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String) /// [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] public sealed class AspMvcPartialViewAttribute : PathReferenceAttribute { } /// /// ASP.NET MVC attribute. Allows disabling all inspections /// for MVC views within a class or a method. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public sealed class AspMvcSupressViewErrorAttribute : Attribute { } /// /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. /// Use this attribute for custom wrappers similar to /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String) /// [AttributeUsage(AttributeTargets.Parameter)] public sealed class AspMvcDisplayTemplateAttribute : Attribute { } /// /// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template. /// Use this attribute for custom wrappers similar to /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String) /// [AttributeUsage(AttributeTargets.Parameter)] public sealed class AspMvcEditorTemplateAttribute : Attribute { } /// /// ASP.NET MVC attribute. Indicates that a parameter is an MVC template. /// Use this attribute for custom wrappers similar to /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String) /// [AttributeUsage(AttributeTargets.Parameter)] public sealed class AspMvcTemplateAttribute : Attribute { } /// /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter /// is an MVC view. If applied to a method, the MVC view name is calculated implicitly /// from the context. Use this attribute for custom wrappers similar to /// System.Web.Mvc.Controller.View(Object) /// [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] public sealed class AspMvcViewAttribute : PathReferenceAttribute { } /// /// ASP.NET MVC attribute. When applied to a parameter of an attribute, /// indicates that this parameter is an MVC action name /// /// /// /// [ActionName("Foo")] /// public ActionResult Login(string returnUrl) { /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK /// return RedirectToAction("Bar"); // Error: Cannot resolve action /// } /// /// [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] public sealed class AspMvcActionSelectorAttribute : Attribute { } [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] public sealed class HtmlElementAttributesAttribute : Attribute { public HtmlElementAttributesAttribute() { } public HtmlElementAttributesAttribute([NotNull] string name) { Name = name; } [NotNull] public string Name { get; } } [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] public sealed class HtmlAttributeValueAttribute : Attribute { public HtmlAttributeValueAttribute([NotNull] string name) { Name = name; } [NotNull] public string Name { get; } } // Razor attributes /// /// Razor attribute. Indicates that a parameter or a method is a Razor section. /// Use this attribute for custom wrappers similar to /// System.Web.WebPages.WebPageBase.RenderSection(String) /// [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] public sealed class RazorSectionAttribute : Attribute { } } ================================================ FILE: src/Hangfire.PostgreSql/Properties/AssemblyInfo.cs ================================================ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Hangfire.PostgreSql.Tests")] // Allow the generation of mocks for internal types [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v10.sql ================================================ SET search_path = 'hangfire'; -- -- Table structure for table `Schema` -- DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 10) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; ALTER TABLE "jobqueue" ALTER COLUMN "queue" TYPE TEXT; RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v11.sql ================================================ SET search_path = 'hangfire'; DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 11) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; ALTER TABLE "counter" ALTER COLUMN id TYPE BIGINT; ALTER TABLE "hash" ALTER COLUMN id TYPE BIGINT; ALTER TABLE "job" ALTER COLUMN id TYPE BIGINT; ALTER TABLE "job" ALTER COLUMN stateid TYPE BIGINT; ALTER TABLE "state" ALTER COLUMN id TYPE BIGINT; ALTER TABLE "state" ALTER COLUMN jobid TYPE BIGINT; ALTER TABLE "jobparameter" ALTER COLUMN id TYPE BIGINT; ALTER TABLE "jobparameter" ALTER COLUMN jobid TYPE BIGINT; ALTER TABLE "jobqueue" ALTER COLUMN id TYPE BIGINT; ALTER TABLE "jobqueue" ALTER COLUMN jobid TYPE BIGINT; ALTER TABLE "list" ALTER COLUMN id TYPE BIGINT; ALTER TABLE "set" ALTER COLUMN id TYPE BIGINT; RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v12.sql ================================================ SET search_path = 'hangfire'; DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 12) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; ALTER TABLE "counter" ALTER COLUMN "key" TYPE TEXT; ALTER TABLE "hash" ALTER COLUMN "key" TYPE TEXT; ALTER TABLE "hash" ALTER COLUMN field TYPE TEXT; ALTER TABLE "job" ALTER COLUMN statename TYPE TEXT; ALTER TABLE "list" ALTER COLUMN "key" TYPE TEXT; ALTER TABLE "server" ALTER COLUMN id TYPE TEXT; ALTER TABLE "set" ALTER COLUMN "key" TYPE TEXT; ALTER TABLE "jobparameter" ALTER COLUMN "name" TYPE TEXT; ALTER TABLE "state" ALTER COLUMN "name" TYPE TEXT; ALTER TABLE "state" ALTER COLUMN reason TYPE TEXT; RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v13.sql ================================================ SET search_path = 'hangfire'; DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 13) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; CREATE INDEX IF NOT EXISTS jobqueue_queue_fetchat_jobId ON jobqueue USING btree (queue asc, fetchedat asc nulls last, jobid asc); RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v14.sql ================================================ SET search_path = 'hangfire'; DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 14) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; do $$ DECLARE BEGIN EXECUTE ('ALTER SEQUENCE "' || 'hangfire' || '".job_id_seq AS bigint MAXVALUE 9223372036854775807'); EXCEPTION WHEN syntax_error THEN EXECUTE ('ALTER SEQUENCE "' || 'hangfire' || '".job_id_seq MAXVALUE 9223372036854775807'); END; $$; RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v15.sql ================================================ SET search_path = 'hangfire'; DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 15) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; CREATE INDEX ix_hangfire_job_expireat ON "job" (expireat); CREATE INDEX ix_hangfire_list_expireat ON "list" (expireat); CREATE INDEX ix_hangfire_set_expireat ON "set" (expireat); CREATE INDEX ix_hangfire_hash_expireat ON "hash" (expireat); RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v16.sql ================================================ SET search_path = 'hangfire'; DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 16) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; -- Note: job_id_seq is already bigint as per migration script v14 DO $$ DECLARE BEGIN EXECUTE ('ALTER SEQUENCE "' || 'hangfire' || '".counter_id_seq AS bigint MAXVALUE 9223372036854775807'); EXECUTE ('ALTER SEQUENCE "' || 'hangfire' || '".hash_id_seq AS bigint MAXVALUE 9223372036854775807'); EXECUTE ('ALTER SEQUENCE "' || 'hangfire' || '".jobparameter_id_seq AS bigint MAXVALUE 9223372036854775807'); EXECUTE ('ALTER SEQUENCE "' || 'hangfire' || '".jobqueue_id_seq AS bigint MAXVALUE 9223372036854775807'); EXECUTE ('ALTER SEQUENCE "' || 'hangfire' || '".list_id_seq AS bigint MAXVALUE 9223372036854775807'); EXECUTE ('ALTER SEQUENCE "' || 'hangfire' || '".set_id_seq AS bigint MAXVALUE 9223372036854775807'); EXECUTE ('ALTER SEQUENCE "' || 'hangfire' || '".state_id_seq AS bigint MAXVALUE 9223372036854775807'); EXCEPTION WHEN syntax_error THEN EXECUTE ('ALTER SEQUENCE "' || 'hangfire' || '".counter_id_seq MAXVALUE 9223372036854775807'); EXECUTE ('ALTER SEQUENCE "' || 'hangfire' || '".hash_id_seq MAXVALUE 9223372036854775807'); EXECUTE ('ALTER SEQUENCE "' || 'hangfire' || '".jobparameter_id_seq MAXVALUE 9223372036854775807'); EXECUTE ('ALTER SEQUENCE "' || 'hangfire' || '".jobqueue_id_seq MAXVALUE 9223372036854775807'); EXECUTE ('ALTER SEQUENCE "' || 'hangfire' || '".list_id_seq MAXVALUE 9223372036854775807'); EXECUTE ('ALTER SEQUENCE "' || 'hangfire' || '".set_id_seq MAXVALUE 9223372036854775807'); EXECUTE ('ALTER SEQUENCE "' || 'hangfire' || '".state_id_seq MAXVALUE 9223372036854775807'); END $$; RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v17.sql ================================================ SET search_path = 'hangfire'; DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 17) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; CREATE INDEX IF NOT EXISTS ix_hangfire_set_key_score ON "set" (key, score); RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v18.sql ================================================ SET search_path = 'hangfire'; DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 18) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; CREATE TABLE aggregatedcounter ( "id" bigserial PRIMARY KEY NOT NULL, "key" text NOT NULL UNIQUE, "value" int8 NOT NULL, "expireat" timestamp ); RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v19.sql ================================================ SET search_path = 'hangfire'; DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 19) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; ALTER TABLE "aggregatedcounter" ALTER COLUMN "expireat" TYPE timestamp with time zone; ALTER TABLE "counter" ALTER COLUMN "expireat" TYPE timestamp with time zone; ALTER TABLE "hash" ALTER COLUMN "expireat" TYPE timestamp with time zone; ALTER TABLE "job" ALTER COLUMN "createdat" TYPE timestamp with time zone; ALTER TABLE "job" ALTER COLUMN "expireat" TYPE timestamp with time zone; ALTER TABLE "jobqueue" ALTER COLUMN "fetchedat" TYPE timestamp with time zone; ALTER TABLE "list" ALTER COLUMN "expireat" TYPE timestamp with time zone; ALTER TABLE "lock" ALTER COLUMN "acquired" TYPE timestamp with time zone; ALTER TABLE "server" ALTER COLUMN "lastheartbeat" TYPE timestamp with time zone; ALTER TABLE "set" ALTER COLUMN "expireat" TYPE timestamp with time zone; ALTER TABLE "state" ALTER COLUMN "createdat" TYPE timestamp with time zone; RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v20.sql ================================================ SET search_path = 'hangfire'; DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 20) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; -- Update existing jobs, if any have empty values first UPDATE "job" SET "invocationdata" = '{}' WHERE "invocationdata" = ''; UPDATE "job" SET "arguments" = '[]' WHERE "arguments" = ''; -- Change the type ALTER TABLE "job" ALTER COLUMN "invocationdata" TYPE jsonb USING "invocationdata"::jsonb; ALTER TABLE "job" ALTER COLUMN "arguments" TYPE jsonb USING "arguments"::jsonb; ALTER TABLE "server" ALTER COLUMN "data" TYPE jsonb USING "data"::jsonb; ALTER TABLE "state" ALTER COLUMN "data" TYPE jsonb USING "data"::jsonb; RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v21.sql ================================================ SET search_path = 'hangfire'; DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 21) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; -- Set REPLICA IDENTITY to allow replication ALTER TABLE "lock" REPLICA IDENTITY USING INDEX "lock_resource_key"; RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v22.sql ================================================ SET search_path = 'hangfire'; DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 22) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; DROP INDEX IF EXISTS jobqueue_queue_fetchat_jobId; CREATE INDEX IF NOT EXISTS ix_hangfire_jobqueue_fetchedat_queue_jobid ON jobqueue USING btree (fetchedat nulls first, queue, jobid); RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v23.sql ================================================ SET search_path = 'hangfire'; DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 23) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; DROP INDEX IF EXISTS ix_hangfire_job_statename_is_not_null; DO $$ BEGIN IF current_setting('server_version_num')::int >= 110000 THEN EXECUTE 'CREATE INDEX ix_hangfire_job_statename_is_not_null ON job USING btree(statename) INCLUDE (id) WHERE statename IS NOT NULL'; ELSE CREATE INDEX ix_hangfire_job_statename_is_not_null ON job USING btree(statename) WHERE statename IS NOT NULL; END IF; END $$; RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v3.sql ================================================ DO $$ BEGIN IF NOT EXISTS( SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'hangfire' ) THEN EXECUTE 'CREATE SCHEMA "hangfire";'; END IF; END $$; SET search_path = 'hangfire'; -- -- Table structure for table `Schema` -- CREATE TABLE IF NOT EXISTS "schema" ( "version" INT NOT NULL, PRIMARY KEY ("version") ); DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 3) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; INSERT INTO "schema"("version") VALUES ('1'); -- -- Table structure for table `Counter` -- CREATE TABLE IF NOT EXISTS "counter" ( "id" SERIAL NOT NULL, "key" VARCHAR(100) NOT NULL, "value" SMALLINT NOT NULL, "expireat" TIMESTAMP NULL, PRIMARY KEY ("id") ); DO $$ BEGIN BEGIN CREATE INDEX "ix_hangfire_counter_key" ON "counter" ("key"); EXCEPTION WHEN duplicate_table THEN RAISE NOTICE 'INDEX ix_hangfire_counter_key already exists.'; END; END; $$; -- -- Table structure for table `Hash` -- CREATE TABLE IF NOT EXISTS "hash" ( "id" SERIAL NOT NULL, "key" VARCHAR(100) NOT NULL, "field" VARCHAR(100) NOT NULL, "value" TEXT NULL, "expireat" TIMESTAMP NULL, PRIMARY KEY ("id"), UNIQUE ("key", "field") ); -- -- Table structure for table `Job` -- CREATE TABLE IF NOT EXISTS "job" ( "id" SERIAL NOT NULL, "stateid" INT NULL, "statename" VARCHAR(20) NULL, "invocationdata" TEXT NOT NULL, "arguments" TEXT NOT NULL, "createdat" TIMESTAMP NOT NULL, "expireat" TIMESTAMP NULL, PRIMARY KEY ("id") ); DO $$ BEGIN BEGIN CREATE INDEX "ix_hangfire_job_statename" ON "job" ("statename"); EXCEPTION WHEN duplicate_table THEN RAISE NOTICE 'INDEX "ix_hangfire_job_statename" already exists.'; END; END; $$; -- -- Table structure for table `State` -- CREATE TABLE IF NOT EXISTS "state" ( "id" SERIAL NOT NULL, "jobid" INT NOT NULL, "name" VARCHAR(20) NOT NULL, "reason" VARCHAR(100) NULL, "createdat" TIMESTAMP NOT NULL, "data" TEXT NULL, PRIMARY KEY ("id"), FOREIGN KEY ("jobid") REFERENCES "job" ("id") ON UPDATE CASCADE ON DELETE CASCADE ); DO $$ BEGIN BEGIN CREATE INDEX "ix_hangfire_state_jobid" ON "state" ("jobid"); EXCEPTION WHEN duplicate_table THEN RAISE NOTICE 'INDEX "ix_hangfire_state_jobid" already exists.'; END; END; $$; -- -- Table structure for table `JobQueue` -- CREATE TABLE IF NOT EXISTS "jobqueue" ( "id" SERIAL NOT NULL, "jobid" INT NOT NULL, "queue" VARCHAR(20) NOT NULL, "fetchedat" TIMESTAMP NULL, PRIMARY KEY ("id") ); DO $$ BEGIN BEGIN CREATE INDEX "ix_hangfire_jobqueue_queueandfetchedat" ON "jobqueue" ("queue", "fetchedat"); EXCEPTION WHEN duplicate_table THEN RAISE NOTICE 'INDEX "ix_hangfire_jobqueue_queueandfetchedat" already exists.'; END; END; $$; -- -- Table structure for table `List` -- CREATE TABLE IF NOT EXISTS "list" ( "id" SERIAL NOT NULL, "key" VARCHAR(100) NOT NULL, "value" TEXT NULL, "expireat" TIMESTAMP NULL, PRIMARY KEY ("id") ); -- -- Table structure for table `Server` -- CREATE TABLE IF NOT EXISTS "server" ( "id" VARCHAR(50) NOT NULL, "data" TEXT NULL, "lastheartbeat" TIMESTAMP NOT NULL, PRIMARY KEY ("id") ); -- -- Table structure for table `Set` -- CREATE TABLE IF NOT EXISTS "set" ( "id" SERIAL NOT NULL, "key" VARCHAR(100) NOT NULL, "score" FLOAT8 NOT NULL, "value" TEXT NOT NULL, "expireat" TIMESTAMP NULL, PRIMARY KEY ("id"), UNIQUE ("key", "value") ); -- -- Table structure for table `JobParameter` -- CREATE TABLE IF NOT EXISTS "jobparameter" ( "id" SERIAL NOT NULL, "jobid" INT NOT NULL, "name" VARCHAR(40) NOT NULL, "value" TEXT NULL, PRIMARY KEY ("id"), FOREIGN KEY ("jobid") REFERENCES "job" ("id") ON UPDATE CASCADE ON DELETE CASCADE ); DO $$ BEGIN BEGIN CREATE INDEX "ix_hangfire_jobparameter_jobidandname" ON "jobparameter" ("jobid", "name"); EXCEPTION WHEN duplicate_table THEN RAISE NOTICE 'INDEX "ix_hangfire_jobparameter_jobidandname" already exists.'; END; END; $$; CREATE TABLE IF NOT EXISTS "lock" ( "resource" VARCHAR(100) NOT NULL, UNIQUE ("resource") ); RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v4.sql ================================================ SET search_path = 'hangfire'; -- -- Table structure for table `Schema` -- DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 4) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; ALTER TABLE "counter" ADD COLUMN "updatecount" integer NOT NULL DEFAULT 0; ALTER TABLE "lock" ADD COLUMN "updatecount" integer NOT NULL DEFAULT 0; ALTER TABLE "hash" ADD COLUMN "updatecount" integer NOT NULL DEFAULT 0; ALTER TABLE "job" ADD COLUMN "updatecount" integer NOT NULL DEFAULT 0; ALTER TABLE "jobparameter" ADD COLUMN "updatecount" integer NOT NULL DEFAULT 0; ALTER TABLE "jobqueue" ADD COLUMN "updatecount" integer NOT NULL DEFAULT 0; ALTER TABLE "list" ADD COLUMN "updatecount" integer NOT NULL DEFAULT 0; ALTER TABLE "server" ADD COLUMN "updatecount" integer NOT NULL DEFAULT 0; ALTER TABLE "set" ADD COLUMN "updatecount" integer NOT NULL DEFAULT 0; ALTER TABLE "state" ADD COLUMN "updatecount" integer NOT NULL DEFAULT 0; RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v5.sql ================================================ SET search_path = 'hangfire'; -- -- Table structure for table `Schema` -- DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 5) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; ALTER TABLE "server" ALTER COLUMN "id" TYPE VARCHAR(100); RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v6.sql ================================================ SET search_path = 'hangfire'; -- -- Adds indices, greatly speeds-up deleting old jobs. -- DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 6) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; DO $$ BEGIN BEGIN CREATE INDEX "ix_hangfire_counter_expireat" ON "counter" ("expireat"); EXCEPTION WHEN duplicate_table THEN RAISE NOTICE 'INDEX ix_hangfire_counter_expireat already exists.'; END; END; $$; DO $$ BEGIN BEGIN CREATE INDEX "ix_hangfire_jobqueue_jobidandqueue" ON "jobqueue" ("jobid", "queue"); EXCEPTION WHEN duplicate_table THEN RAISE NOTICE 'INDEX "ix_hangfire_jobqueue_jobidandqueue" already exists.'; END; END; $$; RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v7.sql ================================================ SET search_path = 'hangfire'; -- -- Table structure for table `Schema` -- DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 7) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; ALTER TABLE "lock" ADD COLUMN acquired timestamp without time zone; RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v8.sql ================================================ SET search_path = 'hangfire'; -- -- Table structure for table `Schema` -- DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 8) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; ALTER TABLE "counter" ALTER COLUMN value TYPE bigint; ALTER TABLE "counter" DROP COLUMN updatecount RESTRICT; RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Scripts/Install.v9.sql ================================================ SET search_path = 'hangfire'; -- -- Table structure for table `Schema` -- DO $$ BEGIN IF EXISTS(SELECT 1 FROM "schema" WHERE "version"::integer >= 9) THEN RAISE EXCEPTION 'version-already-applied'; END IF; END $$; ALTER TABLE "lock" ALTER COLUMN "resource" TYPE TEXT; RESET search_path; ================================================ FILE: src/Hangfire.PostgreSql/Utils/AutoResetEventRegistry.cs ================================================ using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; namespace Hangfire.PostgreSql.Utils { /// /// Represents a registry for managing AutoResetEvent instances using event keys. /// public class AutoResetEventRegistry { private readonly ConcurrentDictionary _events = new(); /// /// Retrieves the wait handles associated with the specified event keys. /// /// The event keys. /// An enumerable of wait handles. public IEnumerable GetWaitHandles(IEnumerable eventKeys) { foreach (string eventKey in eventKeys) { AutoResetEvent newHandle = _events.GetOrAdd(eventKey, _ => new AutoResetEvent(false)); yield return newHandle; } } /// /// Sets the specified event. /// /// The event key. public void Set(string eventKey) { if (_events.TryGetValue(eventKey, out AutoResetEvent handle)) { handle.Set(); } } } } ================================================ FILE: src/Hangfire.PostgreSql/Utils/DbConnectionExtensions.cs ================================================ using System.Data; using Npgsql; namespace Hangfire.PostgreSql.Utils; internal static class DbConnectionExtensions { private static bool? _supportsNotifications; internal static bool SupportsNotifications(this IDbConnection connection) { if (_supportsNotifications.HasValue) { return _supportsNotifications.Value; } if (connection is not NpgsqlConnection npgsqlConnection) { _supportsNotifications = false; return false; } if (npgsqlConnection.State != ConnectionState.Open) { npgsqlConnection.Open(); } _supportsNotifications = npgsqlConnection.PostgreSqlVersion.Major >= 11; return _supportsNotifications.Value; } } ================================================ FILE: src/Hangfire.PostgreSql/Utils/ExceptionTypeHelper.cs ================================================ // This file is part of Hangfire. Copyright © 2022 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . // Borrowed from Hangfire using System; namespace Hangfire.PostgreSql.Utils { internal static class ExceptionTypeHelper { #if !NETSTANDARD1_3 private static readonly Type StackOverflowType = typeof(StackOverflowException); #endif private static readonly Type OutOfMemoryType = typeof(OutOfMemoryException); internal static bool IsCatchableExceptionType(this Exception e) { var type = e.GetType(); return #if !NETSTANDARD1_3 type != StackOverflowType && #endif type != OutOfMemoryType; } } } ================================================ FILE: src/Hangfire.PostgreSql/Utils/TimestampHelper.cs ================================================ // This file is part of Hangfire. Copyright © 2022 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . // Borrowed from Hangfire using System; namespace Hangfire.PostgreSql.Utils { internal static class TimestampHelper { public static long GetTimestamp() { #if NETCOREAPP3_0 return Environment.TickCount64; #else return Environment.TickCount; #endif } public static TimeSpan Elapsed(long timestamp) { long now = GetTimestamp(); return Elapsed(now, timestamp); } public static TimeSpan Elapsed(long now, long timestamp) { #if NETCOREAPP3_0 return TimeSpan.FromMilliseconds(now - timestamp); #else return TimeSpan.FromMilliseconds(unchecked((int)now - (int)timestamp)); #endif } } } ================================================ FILE: src/Hangfire.PostgreSql/Utils/TransactionHelpers.cs ================================================ using System; using System.Transactions; namespace Hangfire.PostgreSql.Utils { public static class TransactionHelpers { internal static TransactionScope CreateTransactionScope(IsolationLevel? isolationLevel = IsolationLevel.ReadCommitted, bool enlist = true, TimeSpan? timeout = null) { TransactionScopeOption scopeOption = TransactionScopeOption.RequiresNew; if (enlist) { Transaction currentTransaction = Transaction.Current; if (currentTransaction != null) { isolationLevel = currentTransaction.IsolationLevel; scopeOption = TransactionScopeOption.Required; } } return new TransactionScope( scopeOption, new TransactionOptions { IsolationLevel = isolationLevel.GetValueOrDefault(IsolationLevel.ReadCommitted), Timeout = timeout.GetValueOrDefault(TransactionManager.DefaultTimeout), }); } } } ================================================ FILE: src/Hangfire.PostgreSql/Utils/TryExecute.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; // ReSharper disable ArrangeDefaultValueWhenTypeNotEvident namespace Hangfire.PostgreSql.Utils { public static class Utils { public static bool TryExecute( Func func, out T result, Func swallowException = default, int? tryCount = default) { while (tryCount == default || tryCount-- > 0) { try { result = func(); return true; } catch (Exception ex) { if (swallowException != null && !swallowException(ex)) { throw; } } } result = default; return false; } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/AssemblyAttributes.cs ================================================ using Xunit; // Running tests in parallel actually takes more time when we are cleaning the database for majority of tests. It's quicker to just let them run sequentially. [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true)] ================================================ FILE: tests/Hangfire.PostgreSql.Tests/CountersAggregatorFacts.cs ================================================ using System; using System.Threading; using Dapper; using Hangfire.PostgreSql.Tests.Utils; using Npgsql; using Xunit; namespace Hangfire.PostgreSql.Tests; public class CountersAggregatorFacts : IClassFixture { private static readonly string _schemaName = ConnectionUtils.GetSchemaName(); private readonly CancellationToken _token; private readonly PostgreSqlStorageFixture _fixture; public CountersAggregatorFacts(PostgreSqlStorageFixture fixture) { CancellationTokenSource cts = new(); _token = cts.Token; _fixture = fixture; _fixture.SetupOptions(o => o.CountersAggregateInterval = TimeSpan.FromMinutes(5)); } [Fact] [CleanDatabase] public void Execute_AggregatesCounters() { UseConnection((connection, manager) => { CreateEntry(1); CreateEntry(5); CreateEntry(15); CreateEntry(5, "key2"); CreateEntry(10, "key2"); manager.Execute(_token); Assert.Equal(21, GetAggregatedCounters(connection)); Assert.Equal(15, GetAggregatedCounters(connection, "key2")); Assert.Null(GetRegularCounters(connection)); Assert.Null(GetRegularCounters(connection, "key2")); return; void CreateEntry(long value, string key = "key") { CreateCounterEntry(connection, value, key); } }); } private void UseConnection(Action action) { PostgreSqlStorage storage = _fixture.SafeInit(); CountersAggregator aggregator = new(storage, TimeSpan.Zero); action(storage.CreateAndOpenConnection(), aggregator); } private static void CreateCounterEntry(NpgsqlConnection connection, long? value, string key = "key") { value ??= 1; string insertSql = $""" INSERT INTO "{_schemaName}"."counter"("key", "value", "expireat") VALUES (@Key, @Value, null) """; connection.Execute(insertSql, new { Key = key, Value = value }); } private static long GetAggregatedCounters(NpgsqlConnection connection, string key = "key") { return connection.QuerySingle( $""" SELECT "value" FROM {_schemaName}."aggregatedcounter" WHERE "key" = @Key """, new { Key = key }); } private static long? GetRegularCounters(NpgsqlConnection connection, string key = "key") { return connection.QuerySingle( $""" SELECT SUM("value") FROM {_schemaName}."counter" WHERE "key" = @Key """, new { Key = key }); } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/Entities/TestJob.cs ================================================ using System; namespace Hangfire.PostgreSql.Tests.Entities { public record TestJob(long Id, string InvocationData, string Arguments, DateTime? ExpireAt, string StateName, long? StateId, DateTime CreatedAt); public class TestJobs { public void Run(string logMessage) { Console.WriteLine("Running test job: {0}", logMessage); } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/ExpirationManagerFacts.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; using Dapper; using Hangfire.PostgreSql.Tests.Utils; using Npgsql; using Xunit; namespace Hangfire.PostgreSql.Tests { public class ExpirationManagerFacts : IClassFixture { private readonly PostgreSqlStorageFixture _fixture; private readonly CancellationToken _token; public ExpirationManagerFacts(PostgreSqlStorageFixture fixture) { CancellationTokenSource cts = new(); _token = cts.Token; _fixture = fixture; _fixture.SetupOptions(o => o.DeleteExpiredBatchSize = 2); } [Fact] public void Ctor_ThrowsAnException_WhenStorageIsNull() { Assert.Throws(() => new ExpirationManager(null)); } [Fact] [CleanDatabase] public void Execute_RemovesOutdatedRecords() { UseConnection((connection, manager) => { long CreateEntry(string key) { return CreateExpirationEntry(connection, DateTime.UtcNow.AddMonths(-1), key); } List entryIds = Enumerable.Range(1, 3).Select(i => CreateEntry($"key{i}")).ToList(); manager.Execute(_token); entryIds.ForEach(entryId => Assert.True(IsEntryExpired(connection, entryId))); }); } [Fact] [CleanDatabase] public void Execute_DoesNotRemoveEntries_WithNoExpirationTimeSet() { UseConnection((connection, manager) => { long entryId = CreateExpirationEntry(connection, null); manager.Execute(_token); Assert.False(IsEntryExpired(connection, entryId)); }); } [Fact] [CleanDatabase] public void Execute_DoesNotRemoveEntries_WithFreshExpirationTime() { UseConnection((connection, manager) => { long entryId = CreateExpirationEntry(connection, DateTime.Now.AddMonths(1)); manager.Execute(_token); Assert.False(IsEntryExpired(connection, entryId)); }); } [Fact] [CleanDatabase] public void Execute_Processes_CounterTable() { UseConnection((connection, manager) => { // Arrange string createSql = $@" INSERT INTO ""{GetSchemaName()}"".""counter"" (""key"", ""value"", ""expireat"") VALUES ('key', 1, @ExpireAt) "; connection.Execute(createSql, new { ExpireAt = DateTime.UtcNow.AddMonths(-1) }); // Act manager.Execute(_token); // Assert Assert.Equal(0, connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""counter""")); }); } [Fact] [CleanDatabase] public void Execute_Aggregates_CounterTable() { UseConnection((connection, manager) => { // Arrange string createSql = $@" INSERT INTO ""{GetSchemaName()}"".""counter"" (""key"", ""value"") VALUES ('stats:succeeded', 1) "; for (int i = 0; i < 5; i++) { connection.Execute(createSql); } // Act manager.Execute(_token); // Assert Assert.Equal(1, connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""counter""")); Assert.Equal(5, connection.QuerySingle($@"SELECT SUM(""value"") FROM ""{GetSchemaName()}"".""counter""")); }); } [Fact] [CleanDatabase] public void Execute_Processes_JobTable() { UseConnection((connection, manager) => { // Arrange string createSql = $@" INSERT INTO ""{GetSchemaName()}"".""job"" (""invocationdata"", ""arguments"", ""createdat"", ""expireat"") VALUES ('{{}}', '[]', NOW(), @ExpireAt) "; connection.Execute(createSql, new { ExpireAt = DateTime.UtcNow.AddMonths(-1) }); // Act manager.Execute(_token); // Assert Assert.Equal(0, connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""job""")); }); } [Fact] [CleanDatabase] public void Execute_Processes_ListTable() { UseConnection((connection, manager) => { // Arrange string createSql = $@"INSERT INTO ""{GetSchemaName()}"".""list"" (""key"", ""expireat"") VALUES ('key', @ExpireAt)"; connection.Execute(createSql, new { ExpireAt = DateTime.UtcNow.AddMonths(-1) }); // Act manager.Execute(_token); // Assert Assert.Equal(0, connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""list""")); }); } [Fact] [CleanDatabase] public void Execute_Processes_SetTable() { UseConnection((connection, manager) => { // Arrange string createSql = $@"INSERT INTO ""{GetSchemaName()}"".""set"" (""key"", ""score"", ""value"", ""expireat"") VALUES ('key', 0, '', @ExpireAt)"; connection.Execute(createSql, new { ExpireAt = DateTime.UtcNow.AddMonths(-1) }); // Act manager.Execute(_token); // Assert Assert.Equal(0, connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""set""")); }); } [Fact] [CleanDatabase] public void Execute_Processes_HashTable() { UseConnection((connection, manager) => { // Arrange string createSql = $@" INSERT INTO ""{GetSchemaName()}"".""hash"" (""key"", ""field"", ""value"", ""expireat"") VALUES ('key', 'field', '', @ExpireAt)"; connection.Execute(createSql, new { ExpireAt = DateTime.UtcNow.AddMonths(-1) }); // Act manager.Execute(_token); // Assert Assert.Equal(0, connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""hash""")); }); } private static long CreateExpirationEntry(NpgsqlConnection connection, DateTime? expireAt, string key = "key") { string insertSqlNull = $@" INSERT INTO ""{GetSchemaName()}"".""counter""(""key"", ""value"", ""expireat"") VALUES (@Key, 1, null) RETURNING ""id"" "; string insertSqlValue = $@" INSERT INTO ""{GetSchemaName()}"".""counter""(""key"", ""value"", ""expireat"") VALUES (@Key, 1, NOW() - interval '{{0}} seconds') RETURNING ""id"" "; string insertSql = expireAt == null ? insertSqlNull : string.Format(CultureInfo.InvariantCulture, insertSqlValue, ((long)(DateTime.UtcNow - expireAt.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture)); return connection.QuerySingle(insertSql, new { Key = key }); } private static bool IsEntryExpired(NpgsqlConnection connection, long entryId) { return connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""counter"" WHERE ""id"" = @Id", new { Id = entryId }) == 0; } private void UseConnection(Action action) { PostgreSqlStorage storage = _fixture.SafeInit(); ExpirationManager manager = new ExpirationManager(storage, TimeSpan.Zero); action(storage.CreateAndOpenConnection(), manager); } private static string GetSchemaName() { return ConnectionUtils.GetSchemaName(); } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/FirstClassQueueFeatureSupportTests.cs ================================================ using System.Threading; using Hangfire.PostgreSql.Tests.Entities; using Hangfire.PostgreSql.Tests.Utils; using Hangfire.Storage; using Hangfire.Storage.Monitoring; using Xunit; namespace Hangfire.PostgreSql.Tests; public class FirstClassQueueFeatureSupportTests { public FirstClassQueueFeatureSupportTests() { JobStorage.Current = new PostgreSqlStorage(ConnectionUtils.GetDefaultConnectionFactory()); } [Fact] public void HasFlag_ShouldReturnTrue_ForJobQueueProperty() { bool supportJobQueueProperty = JobStorage.Current.HasFeature(JobStorageFeatures.JobQueueProperty); Assert.True(supportJobQueueProperty); } [Fact] [CleanDatabase] public void EnqueueJobWithSpecificQueue_ShouldEnqueueCorrectlyAndJobMustBeProcessedInThatQueue() { BackgroundJob.Enqueue("critical", job => job.Run("critical")); BackgroundJob.Enqueue("offline", job => job.Run("offline")); BackgroundJobServer unused = new(new BackgroundJobServerOptions() { Queues = new[] { "critical" }, }); Thread.Sleep(200); IMonitoringApi monitoringApi = JobStorage.Current.GetMonitoringApi(); JobList jobsInCriticalQueue = monitoringApi.EnqueuedJobs("critical", 0, 10); JobList jobsInOfflineQueue = monitoringApi.EnqueuedJobs("offline", 0, 10); Assert.Empty(jobsInCriticalQueue); //Job from 'critical' queue must be processed by the server Assert.NotEmpty(jobsInOfflineQueue); //Job from 'offline' queue must be left untouched because no server is processing it } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/GlobalSuppressions.cs ================================================ // This file is used by Code Analysis to maintain SuppressMessage // attributes that are applied to this project. // Project-level suppressions either have no target or are given // a specific target and scoped to a namespace, type, member, etc. using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "This is a test project, underscores in test names are allowed", Scope = "module")] ================================================ FILE: tests/Hangfire.PostgreSql.Tests/Hangfire.PostgreSql.Tests.csproj ================================================  Hangfire.PostgreSql.Tests net9.0 Hangfire.PostgreSql.Tests Hangfire.PostgreSql.Tests true default all runtime; build; native; contentfiles; analyzers; buildtransitive ================================================ FILE: tests/Hangfire.PostgreSql.Tests/PersistentJobQueueProviderCollectionFacts.cs ================================================ using System; using System.Linq; using Moq; using Xunit; namespace Hangfire.PostgreSql.Tests { public class PersistentJobQueueProviderCollectionFacts { private static readonly string[] _queues = { "default", "critical" }; private readonly Mock _defaultProvider; private readonly Mock _provider; public PersistentJobQueueProviderCollectionFacts() { _defaultProvider = new Mock(); _provider = new Mock(); } [Fact] public void Ctor_ThrowsAnException_WhenDefaultProviderIsNull() { Assert.Throws(() => new PersistentJobQueueProviderCollection(null)); } [Fact] public void Enumeration_IncludesTheDefaultProvider() { PersistentJobQueueProviderCollection collection = CreateCollection(); IPersistentJobQueueProvider[] result = collection.ToArray(); Assert.Single(result); Assert.Same(_defaultProvider.Object, result[0]); } [Fact] public void GetProvider_ReturnsTheDefaultProvider_WhenProviderCanNotBeResolvedByQueue() { PersistentJobQueueProviderCollection collection = CreateCollection(); IPersistentJobQueueProvider provider = collection.GetProvider("queue"); Assert.Same(_defaultProvider.Object, provider); } [Fact] public void Add_ThrowsAnException_WhenProviderIsNull() { PersistentJobQueueProviderCollection collection = CreateCollection(); ArgumentNullException exception = Assert.Throws(() => collection.Add(null, _queues)); Assert.Equal("provider", exception.ParamName); } [Fact] public void Add_ThrowsAnException_WhenQueuesCollectionIsNull() { PersistentJobQueueProviderCollection collection = CreateCollection(); ArgumentNullException exception = Assert.Throws(() => collection.Add(_provider.Object, null)); Assert.Equal("queues", exception.ParamName); } [Fact] public void Enumeration_ContainsAddedProvider() { PersistentJobQueueProviderCollection collection = CreateCollection(); collection.Add(_provider.Object, _queues); Assert.Contains(_provider.Object, collection); } [Fact] public void GetProvider_CanBeResolved_ByAnyQueue() { PersistentJobQueueProviderCollection collection = CreateCollection(); collection.Add(_provider.Object, _queues); IPersistentJobQueueProvider provider1 = collection.GetProvider("default"); IPersistentJobQueueProvider provider2 = collection.GetProvider("critical"); Assert.NotSame(_defaultProvider.Object, provider1); Assert.Same(provider1, provider2); } private PersistentJobQueueProviderCollection CreateCollection() { return new PersistentJobQueueProviderCollection(_defaultProvider.Object); } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/PostgreSqlConnectionFacts.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using System.Transactions; using Dapper; using Hangfire.Common; using Hangfire.PostgreSql.Factories; using Hangfire.PostgreSql.Tests.Entities; using Hangfire.PostgreSql.Tests.Utils; using Hangfire.Server; using Hangfire.Storage; using Moq; using Npgsql; using Xunit; namespace Hangfire.PostgreSql.Tests { public class PostgreSqlConnectionFacts : IClassFixture { private readonly PostgreSqlStorageFixture _fixture; public PostgreSqlConnectionFacts(PostgreSqlStorageFixture fixture) { _fixture = fixture; _fixture.SetupOptions(o => o.TransactionSynchronisationTimeout = TimeSpan.FromSeconds(2)); } [Fact] public void Ctor_ThrowsAnException_WhenStorageIsNull() { ArgumentNullException exception = Assert.Throws(() => new PostgreSqlConnection(null)); Assert.Equal("storage", exception.ParamName); } [Fact] [CleanDatabase] public void Ctor_ThrowsAnException_WhenOptionsIsNull() { ArgumentNullException exception = Assert.Throws( () => new PostgreSqlConnection(new PostgreSqlStorage(ConnectionUtils.GetDefaultConnectionFactory(), null))); Assert.Equal("options", exception.ParamName); } [Fact] [CleanDatabase] public void FetchNextJob_DelegatesItsExecution_ToTheQueue() { UseConnection(connection => { CancellationToken token = new(); string[] queues = { "default" }; connection.FetchNextJob(queues, token); _fixture.PersistentJobQueueMock.Verify(x => x.Dequeue(queues, token)); }); } [Fact] [CleanDatabase] public void FetchNextJob_Throws_IfMultipleProvidersResolved() { UseConnection(connection => { CancellationToken token = new CancellationToken(); Mock anotherProvider = new Mock(); _fixture.PersistentJobQueueProviderCollection.Add(anotherProvider.Object, new[] { "critical" }); try { Assert.Throws(() => connection.FetchNextJob(new[] { "critical", "default" }, token)); } finally { _fixture.PersistentJobQueueProviderCollection.Remove("critical"); } }); } [Fact] [CleanDatabase] public void CreateWriteTransaction_ReturnsNonNullInstance() { UseConnection(connection => { IWriteOnlyTransaction transaction = connection.CreateWriteTransaction(); Assert.NotNull(transaction); }); } [Fact] [CleanDatabase] public void AcquireLock_ReturnsNonNullInstance() { UseConnection(connection => { IDisposable distributedLock = connection.AcquireDistributedLock("1", TimeSpan.FromSeconds(1)); Assert.NotNull(distributedLock); }); } [Fact] [CleanDatabase] public void CreateExpiredJob_ThrowsAnException_WhenJobIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => connection.CreateExpiredJob(null, new Dictionary(), DateTime.UtcNow, TimeSpan.Zero)); Assert.Equal("job", exception.ParamName); }); } [Fact] [CleanDatabase] public void CreateExpiredJob_ThrowsAnException_WhenParametersCollectionIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => connection.CreateExpiredJob( Job.FromExpression(() => SampleMethod("hello")), null, DateTime.UtcNow, TimeSpan.Zero)); Assert.Equal("parameters", exception.ParamName); }); } [Fact] [CleanDatabase] public void CreateExpiredJob_CreatesAJobInTheStorage_AndSetsItsParameters() { UseConnections((connection, jobStorageConnection) => { DateTime createdAt = new DateTime(2012, 12, 12, 0, 0, 0, DateTimeKind.Utc); string jobId = jobStorageConnection.CreateExpiredJob(Job.FromExpression(() => SampleMethod("Hello")), new Dictionary { { "Key1", "Value1" }, { "Key2", "Value2" } }, createdAt, TimeSpan.FromDays(1)); Assert.NotNull(jobId); Assert.NotEmpty(jobId); TestJob testJob = Helper.GetTestJob(connection, GetSchemaName(), "-1"); Assert.Equal(jobId, testJob.Id.ToString(CultureInfo.InvariantCulture)); Assert.Equal(createdAt, testJob.CreatedAt); Assert.Null(testJob.StateId); Assert.Null(testJob.StateName); InvocationData invocationData = SerializationHelper.Deserialize(testJob.InvocationData); invocationData.Arguments = testJob.Arguments; Job job = invocationData.DeserializeJob(); Assert.Equal(typeof(PostgreSqlConnectionFacts), job.Type); Assert.Equal("SampleMethod", job.Method.Name); Assert.Equal("Hello", job.Args[0]); Assert.True(createdAt.AddDays(1).AddMinutes(-1) < testJob.ExpireAt); Assert.True(testJob.ExpireAt < createdAt.AddDays(1).AddMinutes(1)); Dictionary parameters = connection.Query($@"SELECT * FROM ""{GetSchemaName()}"".""jobparameter"" WHERE ""jobid"" = @Id", new { Id = Convert.ToInt64(jobId, CultureInfo.InvariantCulture) }) .ToDictionary(x => (string)x.name, x => (string)x.value); Assert.Equal("Value1", parameters["Key1"]); Assert.Equal("Value2", parameters["Key2"]); }); } [Fact] [CleanDatabase] public void GetJobData_ThrowsAnException_WhenJobIdIsNull() { UseConnection(connection => Assert.Throws(() => connection.GetJobData(null))); } [Fact] [CleanDatabase] public void GetJobData_ReturnsNull_WhenThereIsNoSuchJob() { UseConnection(connection => { JobData result = connection.GetJobData("1"); Assert.Null(result); }); } [Fact] [CleanDatabase] public void GetJobData_ReturnsResult_WhenJobExists() { string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""job"" (""invocationdata"", ""arguments"", ""statename"", ""createdat"") VALUES (@InvocationData::jsonb, @Arguments::jsonb, @StateName, NOW()) RETURNING ""id"" "; UseConnections((connection, jobStorageConnection) => { Job job = Job.FromExpression(() => SampleMethod("wrong")); long jobId = connection.QuerySingle(arrangeSql, new { InvocationData = JsonParameter.GetParameterValue(SerializationHelper.Serialize(InvocationData.SerializeJob(job))), StateName = "Succeeded", Arguments = JsonParameter.GetParameterValue("[\"\\\"Arguments\\\"\"]", JsonParameter.ValueType.Array), }); JobData result = jobStorageConnection.GetJobData(jobId.ToString(CultureInfo.InvariantCulture)); Assert.NotNull(result); Assert.NotNull(result.Job); Assert.Equal("Succeeded", result.State); Assert.Equal("Arguments", result.Job.Args[0]); Assert.Null(result.LoadException); Assert.True(DateTime.UtcNow.AddMinutes(-1) < result.CreatedAt); Assert.True(result.CreatedAt < DateTime.UtcNow.AddMinutes(1)); }); } [Fact] [CleanDatabase] public void GetStateData_ThrowsAnException_WhenJobIdIsNull() { UseConnection(connection => Assert.Throws(() => connection.GetStateData(null))); } [Fact] [CleanDatabase] public void GetStateData_ReturnsNull_IfThereIsNoSuchState() { UseConnection(connection => { StateData result = connection.GetStateData("1"); Assert.Null(result); }); } [Fact] [CleanDatabase] public void GetStateData_ReturnsCorrectData() { string createJobSql = $@" INSERT INTO ""{GetSchemaName()}"".""job"" (""invocationdata"", ""arguments"", ""statename"", ""createdat"") VALUES ('{{}}', '[]', '', NOW()) RETURNING ""id""; "; string createStateSql = $@" INSERT INTO ""{GetSchemaName()}"".""state"" (""jobid"", ""name"", ""createdat"") VALUES(@JobId, 'old-state', NOW()); INSERT INTO ""{GetSchemaName()}"".""state"" (""jobid"", ""name"", ""reason"", ""data"", ""createdat"") VALUES(@JobId, @Name, @Reason, @Data::jsonb, NOW()) RETURNING ""id""; "; string updateJobStateSql = $@" UPDATE ""{GetSchemaName()}"".""job"" SET ""stateid"" = @StateId WHERE ""id"" = @JobId; "; UseConnections((connection, jobStorageConnection) => { Dictionary data = new() { { "Key", "Value" }, }; long jobId = connection.QuerySingle(createJobSql); long stateId = connection.QuerySingle(createStateSql, new { JobId = jobId, Name = "Name", Reason = "Reason", Data = JsonParameter.GetParameterValue(SerializationHelper.Serialize(data)) }); connection.Execute(updateJobStateSql, new { JobId = jobId, StateId = stateId }); StateData result = jobStorageConnection.GetStateData(jobId.ToString(CultureInfo.InvariantCulture)); Assert.NotNull(result); Assert.Equal("Name", result.Name); Assert.Equal("Reason", result.Reason); Assert.Equal("Value", result.Data["Key"]); }); } [Fact] [CleanDatabase] public void SetParameter_ThrowsAnException_WhenJobIdIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => connection.SetJobParameter(null, "name", "value")); Assert.Equal("id", exception.ParamName); }); } [Fact] [CleanDatabase] public void SetParameter_ThrowsAnException_WhenNameIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => connection.SetJobParameter("1", null, "value")); Assert.Equal("name", exception.ParamName); }); } [Fact] [CleanDatabase] public void SetParameters_CreatesNewParameter_WhenParameterWithTheGivenNameDoesNotExists() { string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""job"" (""invocationdata"", ""arguments"", ""createdat"") VALUES ('{{}}', '[]', NOW()) RETURNING ""id"" "; UseConnections((connection, jobStorageConnection) => { string jobId = connection.QuerySingle(arrangeSql).ToString(CultureInfo.InvariantCulture); jobStorageConnection.SetJobParameter(jobId, "Name", "Value"); string parameterValue = connection.QuerySingle($@"SELECT ""value"" FROM ""{GetSchemaName()}"".""jobparameter"" WHERE ""jobid"" = @Id AND ""name"" = @Name", new { Id = Convert.ToInt64(jobId, CultureInfo.InvariantCulture), Name = "Name" }); Assert.Equal("Value", parameterValue); }); } [Fact] [CleanDatabase] public void SetParameter_UpdatesValue_WhenParameterWithTheGivenName_AlreadyExists() { string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""job"" (""invocationdata"", ""arguments"", ""createdat"") VALUES ('{{}}', '[]', NOW()) RETURNING ""id"" "; UseConnections((connection, jobStorageConnection) => { string jobId = connection.QuerySingle(arrangeSql).ToString(CultureInfo.InvariantCulture); jobStorageConnection.SetJobParameter(jobId, "Name", "Value"); jobStorageConnection.SetJobParameter(jobId, "Name", "AnotherValue"); string parameterValue = connection.QuerySingle($@"SELECT ""value"" FROM ""{GetSchemaName()}"".""jobparameter"" WHERE ""jobid"" = @Id AND ""name"" = @Name", new { Id = Convert.ToInt64(jobId, CultureInfo.InvariantCulture), Name = "Name" }); Assert.Equal("AnotherValue", parameterValue); }); } [Fact] [CleanDatabase] public void SetParameter_CanAcceptNulls_AsValues() { string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""job"" (""invocationdata"", ""arguments"", ""createdat"") VALUES ('{{}}', '[]', NOW()) RETURNING ""id"" "; UseConnections((connection, jobStorageConnection) => { string jobId = connection.QuerySingle(arrangeSql).ToString(CultureInfo.InvariantCulture); jobStorageConnection.SetJobParameter(jobId, "Name", null); string parameterValue = connection.QuerySingle($@"SELECT ""value"" FROM ""{GetSchemaName()}"".""jobparameter"" WHERE ""jobid"" = @Id AND ""name"" = @Name", new { Id = Convert.ToInt64(jobId, CultureInfo.InvariantCulture), Name = "Name" }); Assert.Null(parameterValue); }); } [Fact] [CleanDatabase] public void GetParameter_ThrowsAnException_WhenJobIdIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => connection.GetJobParameter(null, "hello")); Assert.Equal("id", exception.ParamName); }); } [Fact] [CleanDatabase] public void GetParameter_ThrowsAnException_WhenNameIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => connection.GetJobParameter("1", null)); Assert.Equal("name", exception.ParamName); }); } [Fact] [CleanDatabase] public void GetParameter_ReturnsNull_WhenParameterDoesNotExists() { UseConnection(connection => { string value = connection.GetJobParameter("1", "hello"); Assert.Null(value); }); } [Fact] [CleanDatabase] public void GetParameter_ReturnsParameterValue_WhenJobExists() { string arrangeSql = $@" WITH ""insertedjob"" AS ( INSERT INTO ""{GetSchemaName()}"".""job"" (""invocationdata"", ""arguments"", ""createdat"") VALUES ('{{}}', '[]', NOW()) RETURNING ""id"" ) INSERT INTO ""{GetSchemaName()}"".""jobparameter"" (""jobid"", ""name"", ""value"") SELECT ""insertedjob"".""id"", @Name, @Value FROM ""insertedjob"" RETURNING ""jobid""; "; UseConnections((connection, jobStorageConnection) => { long id = connection.QuerySingle(arrangeSql, new { Name = "name", Value = "value" }); string value = jobStorageConnection.GetJobParameter(Convert.ToString(id, CultureInfo.InvariantCulture), "name"); Assert.Equal("value", value); }); } [Fact] [CleanDatabase] public void GetFirstByLowestScoreFromSet_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => connection.GetFirstByLowestScoreFromSet(null, 0, 1)); Assert.Equal("key", exception.ParamName); }); } [Fact] [CleanDatabase] public void GetFirstByLowestScoreFromSet_ThrowsAnException_ToScoreIsLowerThanFromScore() { UseConnection(connection => Assert.Throws(() => connection.GetFirstByLowestScoreFromSet("key", 0, -1))); } [Fact] [CleanDatabase] public void GetFirstByLowestScoreFromSet_ReturnsNull_WhenTheKeyDoesNotExist() { UseConnection(connection => { string result = connection.GetFirstByLowestScoreFromSet("key", 0, 1); Assert.Null(result); }); } [Fact] [CleanDatabase] public void GetFirstByLowestScoreFromSet_ReturnsTheValueWithTheLowestScore() { string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""set"" (""key"", ""score"", ""value"") VALUES ('key', 1.0, '1.0'), ('key', -1.0, '-1.0'), ('key', -5.0, '-5.0'), ('another-key', -2.0, '-2.0') "; UseConnections((connection, jobStorageConnection) => { connection.Execute(arrangeSql); string result = jobStorageConnection.GetFirstByLowestScoreFromSet("key", -1.0, 3.0); Assert.Equal("-1.0", result); }); } [Fact] [CleanDatabase] public void GetFirstByLowestScoreFromSet_List_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => connection.GetFirstByLowestScoreFromSet(null, 0, 1, 1)); Assert.Equal("key", exception.ParamName); }); } [Fact] [CleanDatabase] public void GetFirstByLowestScoreFromSet_List_ThrowsAnException_WhenToScoreIsLowerThanFromScore() { UseConnection(connection => { ArgumentException exception = Assert.Throws(() => connection.GetFirstByLowestScoreFromSet("key", 0, -1, 1)); Assert.Contains("The 'toScore' value must be higher or equal to the 'fromScore' value.", exception.Message); }); } [Theory] [CleanDatabase] [InlineData(-1)] [InlineData(0)] public void GetFirstByLowestScoreFromSet_List_ThrowsAnException_WhenCountIsLessThanOne(int count) { UseConnection(connection => { ArgumentException exception = Assert.Throws(() => connection.GetFirstByLowestScoreFromSet("key", 0, 1, count)); Assert.Contains("The 'count' value must be greater than zero (0).", exception.Message); }); } [Fact] [CleanDatabase] public void GetFirstByLowestScoreFromSet_List_ReturnsEmpty_WhenTheKeyDoesNotExist() { UseConnection(connection => { List result = connection.GetFirstByLowestScoreFromSet("key", 0, 1, 1); Assert.NotNull(result); Assert.Empty(result); }); } [Fact] [CleanDatabase] public void GetFirstByLowestScoreFromSet_List_ReturnsEmpty_WhenNoValuesExistForKey() { string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""set"" (""key"", ""score"", ""value"") VALUES ('another-key', -2.0, '-2.0') "; UseConnections((connection, jobStorageConnection) => { connection.Execute(arrangeSql); List result = jobStorageConnection.GetFirstByLowestScoreFromSet("key", 0, 1, 1); Assert.NotNull(result); Assert.Empty(result); }); } [Fact] [CleanDatabase] public void GetFirstByLowestScoreFromSet_List_ReturnsAllLowestValuesMatchingInputs() { string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""set"" (""key"", ""score"", ""value"") VALUES ('key', 1.0, '1.0'), ('key', -1.0, '-1.0'), ('key', -5.0, '-5.0'), ('another-key', -2.0, '-2.0') "; UseConnections((connection, jobStorageConnection) => { connection.Execute(arrangeSql); List result = jobStorageConnection.GetFirstByLowestScoreFromSet("key", -1.0, 3.0, 10); Assert.Equal(2, result.Count); Assert.Equal("-1.0", result[0]); Assert.Equal("1.0", result[1]); }); } [Fact] [CleanDatabase] public void GetFirstByLowestScoreFromSet_List_ReturnsSubsetOfLowestValuesMatchingInputs() { string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""set"" (""key"", ""score"", ""value"") VALUES ('key', 1.0, '1.0'), ('key', 1.5, '1.5'), ('key', 2.0, '2.0'), ('key', 2.5, '2.5'), ('key', -1.0, '-1.0'), ('key', -5.0, '-5.0'), ('another-key', -2.0, '-2.0') "; int count = 3; UseConnections((connection, jobStorageConnection) => { connection.Execute(arrangeSql); List result = jobStorageConnection.GetFirstByLowestScoreFromSet("key", -1.0, 3.0, count); Assert.Equal(count, result.Count); Assert.Equal("-1.0", result[0]); Assert.Equal("1.0", result[1]); Assert.Equal("1.5", result[2]); }); } [Fact] [CleanDatabase] public void AnnounceServer_ThrowsAnException_WhenServerIdIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => connection.AnnounceServer(null, new ServerContext())); Assert.Equal("serverId", exception.ParamName); }); } [Fact] [CleanDatabase] public void AnnounceServer_ThrowsAnException_WhenContextIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => connection.AnnounceServer("server", null)); Assert.Equal("context", exception.ParamName); }); } [Fact] [CleanDatabase] public void AnnounceServer_CreatesOrUpdatesARecord() { UseConnections((connection, jobStorageConnection) => { ServerContext context1 = new ServerContext { WorkerCount = 4, Queues = new[] { "critical", "default" }, }; jobStorageConnection.AnnounceServer("server", context1); dynamic server = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""server"""); Assert.Equal("server", server.id); ServerContext serverData = JsonSerializer.Deserialize(server.data); Assert.Equal(4, serverData.WorkerCount); Assert.Equal(context1.Queues, serverData.Queues); Assert.NotNull(server.lastheartbeat); ServerContext context2 = new ServerContext { Queues = new[] { "default" }, WorkerCount = 1000, }; jobStorageConnection.AnnounceServer("server", context2); dynamic sameServer = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""server"""); Assert.Equal("server", sameServer.id); Assert.Contains("1000", sameServer.data); }); } [Fact] [CleanDatabase] public void RemoveServer_ThrowsAnException_WhenServerIdIsNull() { UseConnection(connection => Assert.Throws(() => connection.RemoveServer(null))); } [Fact] [CleanDatabase] public void RemoveServer_RemovesAServerRecord() { string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""server"" (""id"", ""data"", ""lastheartbeat"") VALUES ('Server1', '{{}}', NOW()), ('Server2', '{{}}', NOW()) "; UseConnections((connection, jobStorageConnection) => { connection.Execute(arrangeSql); jobStorageConnection.RemoveServer("Server1"); dynamic server = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""server"""); Assert.NotEqual("Server1", server.Id, StringComparer.OrdinalIgnoreCase); }); } [Fact] [CleanDatabase] public void Heartbeat_ThrowsAnException_WhenServerIdIsNull() { UseConnection(connection => Assert.Throws(() => connection.Heartbeat(null))); } [Fact] [CleanDatabase] public void Heartbeat_ThrowsBackgroundServerGoneException_WhenServerDisappeared() { string disappearedServerId = Guid.NewGuid().ToString(); UseConnection(connection => Assert.Throws(() => connection.Heartbeat(disappearedServerId))); } [Fact] [CleanDatabase] public void Heartbeat_UpdatesLastHeartbeat_OfTheServerWithGivenId() { string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""server"" (""id"", ""data"", ""lastheartbeat"") VALUES ('server1', '{{}}', '2012-12-12 12:12:12'), ('server2', '{{}}', '2012-12-12 12:12:12') "; UseConnections((connection, jobStorageConnection) => { connection.Execute(arrangeSql); jobStorageConnection.Heartbeat("server1"); Dictionary servers = connection.Query($@"SELECT * FROM ""{GetSchemaName()}"".""server""") .ToDictionary(x => (string)x.id, x => (DateTime)x.lastheartbeat); Assert.NotEqual(2012, servers["server1"].Year); Assert.Equal(2012, servers["server2"].Year); }); } [Fact] [CleanDatabase] public void RemoveTimedOutServers_ThrowsAnException_WhenTimeOutIsNegative() { UseConnection(connection => Assert.Throws(() => connection.RemoveTimedOutServers(TimeSpan.FromMinutes(-5)))); } [Fact] [CleanDatabase] public void RemoveTimedOutServers_DoItsWorkPerfectly() { string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""server"" (""id"", ""data"", ""lastheartbeat"") VALUES (@Id, '{{}}', @Heartbeat) "; UseConnections((connection, jobStorageConnection) => { connection.Execute(arrangeSql, new[] { new { Id = "server1", Heartbeat = DateTime.UtcNow.AddDays(-1) }, new { Id = "server2", Heartbeat = DateTime.UtcNow.AddHours(-12) }, }); jobStorageConnection.RemoveTimedOutServers(TimeSpan.FromHours(15)); dynamic liveServer = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""server"""); Assert.Equal("server2", liveServer.id); }); } [Fact] [CleanDatabase] public void GetAllItemsFromSet_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => Assert.Throws(() => connection.GetAllItemsFromSet(null))); } [Fact] [CleanDatabase] public void GetAllItemsFromSet_ReturnsEmptyCollection_WhenKeyDoesNotExist() { UseConnection(connection => { HashSet result = connection.GetAllItemsFromSet("some-set"); Assert.NotNull(result); Assert.Empty(result); }); } [Fact] [CleanDatabase] public void GetAllItemsFromSet_ReturnsAllItems() { string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""set"" (""key"", ""score"", ""value"") VALUES (@Key, 0.0, @Value) "; UseConnections((connection, jobStorageConnection) => { // Arrange connection.Execute(arrangeSql, new[] { new { Key = "some-set", Value = "1" }, new { Key = "some-set", Value = "2" }, new { Key = "another-set", Value = "3" }, }); // Act HashSet result = jobStorageConnection.GetAllItemsFromSet("some-set"); // Assert Assert.Equal(2, result.Count); Assert.Contains("1", result); Assert.Contains("2", result); }); } [Fact] [CleanDatabase] public void SetRangeInHash_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => connection.SetRangeInHash(null, new Dictionary())); Assert.Equal("key", exception.ParamName); }); } [Fact] [CleanDatabase] public void SetRangeInHash_ThrowsAnException_WhenKeyValuePairsArgumentIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => connection.SetRangeInHash("some-hash", null)); Assert.Equal("keyValuePairs", exception.ParamName); }); } [Fact] [CleanDatabase] public void SetRangeInHash_MergesAllRecords() { UseConnections((connection, jobStorageConnection) => { jobStorageConnection.SetRangeInHash("some-hash", new Dictionary { { "Key1", "Value1" }, { "Key2", "Value2" }, }); Dictionary result = connection.Query($@"SELECT * FROM ""{GetSchemaName()}"".""hash"" WHERE ""key"" = @Key", new { Key = "some-hash" }) .ToDictionary(x => (string)x.field, x => (string)x.value); Assert.Equal("Value1", result["Key1"]); Assert.Equal("Value2", result["Key2"]); }); } [Fact] [CleanDatabase] public void SetRangeInHash_DoesNotThrowSerializationException() { Parallel.For(1, 100, _ => { UseDisposableConnection(connection => { connection.SetRangeInHash("some-hash", new Dictionary { { "Key1", "Value1" }, { "Key2", "Value2" }, }); }); }); } [Fact] [CleanDatabase] public void GetAllEntriesFromHash_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => Assert.Throws(() => connection.GetAllEntriesFromHash(null))); } [Fact] [CleanDatabase] public void GetAllEntriesFromHash_ReturnsNull_IfHashDoesNotExist() { UseConnection(connection => { Dictionary result = connection.GetAllEntriesFromHash("some-hash"); Assert.Null(result); }); } [Fact] [CleanDatabase] public void GetAllEntriesFromHash_ReturnsAllKeysAndTheirValues() { string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""hash"" (""key"", ""field"", ""value"") VALUES (@Key, @Field, @Value) "; UseConnections((connection, jobStorageConnection) => { // Arrange connection.Execute(arrangeSql, new[] { new { Key = "some-hash", Field = "Key1", Value = "Value1" }, new { Key = "some-hash", Field = "Key2", Value = "Value2" }, new { Key = "another-hash", Field = "Key3", Value = "Value3" }, }); // Act Dictionary result = jobStorageConnection.GetAllEntriesFromHash("some-hash"); // Assert Assert.NotNull(result); Assert.Equal(2, result.Count); Assert.Equal("Value1", result["Key1"]); Assert.Equal("Value2", result["Key2"]); }); } [Fact] [CleanDatabase] public void GetSetCount_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { Assert.Throws(() => connection.GetSetCount(null)); }); } [Fact] [CleanDatabase] public void GetSetCount_ReturnsZero_WhenSetDoesNotExist() { UseConnection(connection => { long result = connection.GetSetCount("my-set"); Assert.Equal(0, result); }); } [Fact] [CleanDatabase] public void GetSetCount_ReturnsNumberOfElements_InASet() { string arrangeSql = $@"INSERT INTO ""{GetSchemaName()}"".set (key, value, score) VALUES (@Key, @Value, 0.0)"; UseConnections((connection, jobStorageConnection) => { connection.Execute(arrangeSql, new[] { new { Key = "set-1", Value = "value-1" }, new { Key = "set-2", Value = "value-1" }, new { Key = "set-1", Value = "value-2" }, }); long result = jobStorageConnection.GetSetCount("set-1"); Assert.Equal(2, result); }); } [Fact] [CleanDatabase] public void GetAllItemsFromList_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { Assert.Throws(() => connection.GetAllItemsFromList(null)); }); } [Fact] [CleanDatabase] public void GetAllItemsFromList_ReturnsAnEmptyList_WhenListDoesNotExist() { UseConnection(connection => { List result = connection.GetAllItemsFromList("my-list"); Assert.Empty(result); }); } [Fact] [CleanDatabase] public void GetAllItemsFromList_ReturnsAllItems_FromAGivenList() { string arrangeSql = $@"INSERT INTO ""{GetSchemaName()}"".list (key, value) VALUES (@Key, @Value)"; UseConnections((connection, jobStorageConnection) => { // Arrange connection.Execute(arrangeSql, new[] { new { Key = "list-1", Value = "1" }, new { Key = "list-2", Value = "2" }, new { Key = "list-1", Value = "3" }, }); // Act List result = jobStorageConnection.GetAllItemsFromList("list-1"); // Assert Assert.Equal(new[] { "3", "1" }, result); }); } [Fact] [CleanDatabase] public void GetCounter_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { Assert.Throws(() => connection.GetCounter(null)); }); } [Fact] [CleanDatabase] public void GetCounter_ReturnsZero_WhenKeyDoesNotExist() { UseConnection(connection => { long result = connection.GetCounter("my-counter"); Assert.Equal(0, result); }); } [Fact] [CleanDatabase] public void GetCounter_ReturnsSumOfValues_InCounterTable() { string arrangeSql = $@"INSERT INTO ""{GetSchemaName()}"".counter (key, value) VALUES (@Key, @Value)"; UseConnections((connection, jobStorageConnection) => { // Arrange connection.Execute(arrangeSql, new[] { new { Key = "counter-1", Value = 1 }, new { Key = "counter-2", Value = 1 }, new { Key = "counter-1", Value = 1 }, }); // Act long result = jobStorageConnection.GetCounter("counter-1"); // Assert Assert.Equal(2, result); }); } [Fact] [CleanDatabase] public void GetListCount_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { Assert.Throws(() => connection.GetListCount(null)); }); } [Fact] [CleanDatabase] public void GetListCount_ReturnsZero_WhenListDoesNotExist() { UseConnection(connection => { long result = connection.GetListCount("my-list"); Assert.Equal(0, result); }); } [Fact] [CleanDatabase] public void GetListCount_ReturnsTheNumberOfListElements() { string arrangeSql = $@"INSERT INTO ""{GetSchemaName()}"".""list""(""key"") VALUES (@Key)"; UseConnections((connection, jobStorageConnection) => { // Arrange connection.Execute(arrangeSql, new[] { new { Key = "list-1" }, new { Key = "list-1" }, new { Key = "list-2" }, }); // Act long result = jobStorageConnection.GetListCount("list-1"); // Assert Assert.Equal(2, result); }); } [Fact] [CleanDatabase] public void GetListTtl_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { Assert.Throws(() => connection.GetListTtl(null)); }); } [Fact] [CleanDatabase] public void GetListTtl_ReturnsNegativeValue_WhenListDoesNotExist() { UseConnection(connection => { TimeSpan result = connection.GetListTtl("my-list"); Assert.True(result < TimeSpan.Zero); }); } [Fact] [CleanDatabase] public void GetListTtl_ReturnsExpirationTimeForList() { string arrangeSql = $@"INSERT INTO ""{GetSchemaName()}"".list (key, expireat) VALUES (@Key, @ExpireAt)"; UseConnections((connection, jobStorageConnection) => { // Arrange connection.Execute(arrangeSql, new[] { new { Key = "list-1", ExpireAt = (DateTime?)DateTime.UtcNow.AddHours(1) }, new { Key = "list-2", ExpireAt = (DateTime?)null }, }); // Act TimeSpan result = jobStorageConnection.GetListTtl("list-1"); // Assert Assert.True(TimeSpan.FromMinutes(59) < result); Assert.True(result < TimeSpan.FromMinutes(61)); }); } [Fact] [CleanDatabase] public void GetRangeFromList_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => connection.GetRangeFromList(null, 0, 1)); Assert.Equal("key", exception.ParamName); }); } [Fact] [CleanDatabase] public void GetRangeFromList_ReturnsAnEmptyList_WhenListDoesNotExist() { UseConnection(connection => { List result = connection.GetRangeFromList("my-list", 0, 1); Assert.Empty(result); }); } [Fact] [CleanDatabase] public void GetRangeFromList_ReturnsAllEntries_WithinGivenBounds() { string arrangeSql = $@"INSERT INTO ""{GetSchemaName()}"".list (key, value) VALUES (@Key, @Value)"; UseConnections((connection, jobStorageConnection) => { // Arrange connection.Execute(arrangeSql, new[] { new { Key = "list-1", Value = "1" }, new { Key = "list-2", Value = "2" }, new { Key = "list-1", Value = "3" }, new { Key = "list-1", Value = "4" }, new { Key = "list-1", Value = "5" }, }); // Act List result = jobStorageConnection.GetRangeFromList("list-1", 1, 2); // Assert Assert.Equal(new[] { "4", "3" }, result); }); } [Fact] [CleanDatabase] public void GetHashCount_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { Assert.Throws(() => connection.GetHashCount(null)); }); } [Fact] [CleanDatabase] public void GetHashCount_ReturnsZero_WhenKeyDoesNotExist() { UseConnection(connection => { long result = connection.GetHashCount("my-hash"); Assert.Equal(0, result); }); } [Fact] [CleanDatabase] public void GetHashCount_ReturnsNumber_OfHashFields() { string arrangeSql = $@"INSERT INTO ""{GetSchemaName()}"".hash (key, field) VALUES (@Key, @Field)"; UseConnections((connection, jobStorageConnection) => { // Arrange connection.Execute(arrangeSql, new[] { new { Key = "hash-1", Field = "field-1" }, new { Key = "hash-1", Field = "field-2" }, new { Key = "hash-2", Field = "field-1" }, }); // Act long result = jobStorageConnection.GetHashCount("hash-1"); // Assert Assert.Equal(2, result); }); } [Fact] [CleanDatabase] public void GetHashTtl_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { Assert.Throws(() => connection.GetHashTtl(null)); }); } [Fact] [CleanDatabase] public void GetHashTtl_ReturnsNegativeValue_WhenHashDoesNotExist() { UseConnection(connection => { TimeSpan result = connection.GetHashTtl("my-hash"); Assert.True(result < TimeSpan.Zero); }); } [Fact] [CleanDatabase] public void GetHashTtl_ReturnsExpirationTimeForHash() { string arrangeSql = $@"INSERT INTO ""{GetSchemaName()}"".hash (key, field, expireat) VALUES (@Key, @Field, @ExpireAt)"; UseConnections((connection, jobStorageConnection) => { // Arrange connection.Execute(arrangeSql, new[] { new { Key = "hash-1", Field = "field", ExpireAt = (DateTime?)DateTime.UtcNow.AddHours(1) }, new { Key = "hash-2", Field = "field", ExpireAt = (DateTime?)null }, }); // Act TimeSpan result = jobStorageConnection.GetHashTtl("hash-1"); // Assert Assert.True(TimeSpan.FromMinutes(59) < result); Assert.True(result < TimeSpan.FromMinutes(61)); }); } [Fact] [CleanDatabase] public void GetRangeFromSet_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { Assert.Throws(() => connection.GetRangeFromSet(null, 0, 1)); }); } [Fact] [CleanDatabase] public void GetRangeFromSet_ReturnsPagedElements() { string arrangeSql = $@"INSERT INTO ""{GetSchemaName()}"".set (key, value, score) VALUES (@Key, @Value, 0.0)"; UseConnections((connection, jobStorageConnection) => { connection.Execute(arrangeSql, new[] { new { Key = "set-1", Value = "1" }, new { Key = "set-1", Value = "2" }, new { Key = "set-1", Value = "3" }, new { Key = "set-1", Value = "4" }, new { Key = "set-2", Value = "4" }, new { Key = "set-1", Value = "5" }, }); List result = jobStorageConnection.GetRangeFromSet("set-1", 2, 3); Assert.Equal(new[] { "3", "4" }, result); }); } [Fact] [CleanDatabase] public void GetSetTtl_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { Assert.Throws(() => connection.GetSetTtl(null)); }); } [Fact] [CleanDatabase] public void GetSetTtl_ReturnsNegativeValue_WhenSetDoesNotExist() { UseConnection(connection => { TimeSpan result = connection.GetSetTtl("my-set"); Assert.True(result < TimeSpan.Zero); }); } [Fact] [CleanDatabase] public void GetSetTtl_ReturnsExpirationTime_OfAGivenSet() { string arrangeSql = $@"INSERT INTO ""{GetSchemaName()}"".set (key, value, expireat, score) VALUES (@Key, @Value, @ExpireAt, 0.0)"; UseConnections((connection, jobStorageConnection) => { // Arrange connection.Execute(arrangeSql, new[] { new { Key = "set-1", Value = "1", ExpireAt = (DateTime?)DateTime.UtcNow.AddMinutes(60) }, new { Key = "set-2", Value = "2", ExpireAt = (DateTime?)null }, }); // Act TimeSpan result = jobStorageConnection.GetSetTtl("set-1"); // Assert Assert.True(TimeSpan.FromMinutes(59) < result); Assert.True(result < TimeSpan.FromMinutes(61)); }); } [Fact] [CleanDatabase] public void GetValueFromHash_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => connection.GetValueFromHash(null, "name")); Assert.Equal("key", exception.ParamName); }); } [Fact] [CleanDatabase] public void GetValueFromHash_ThrowsAnException_WhenNameIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => connection.GetValueFromHash("key", null)); Assert.Equal("name", exception.ParamName); }); } [Fact] [CleanDatabase] public void GetValueFromHash_ReturnsNull_WhenHashDoesNotExist() { UseConnection(connection => { string result = connection.GetValueFromHash("my-hash", "name"); Assert.Null(result); }); } [Fact] [CleanDatabase] public void GetValueFromHash_ReturnsValue_OfAGivenField() { string arrangeSql = $@"INSERT INTO ""{GetSchemaName()}"".hash (key, field, value) VALUES (@Key, @Field, @Value)"; UseConnections((connection, jobStorageConnection) => { // Arrange connection.Execute(arrangeSql, new[] { new { Key = "hash-1", Field = "field-1", Value = "1" }, new { Key = "hash-1", Field = "field-2", Value = "2" }, new { Key = "hash-2", Field = "field-1", Value = "3" }, }); // Act string result = jobStorageConnection.GetValueFromHash("hash-1", "field-1"); // Assert Assert.Equal("1", result); }); } [Theory] [CleanDatabase] [InlineData(false)] [InlineData(true)] public void CreateExpiredJob_EnlistsInTransaction(bool completeTransactionScope) { TransactionScope CreateTransactionScope() { TransactionOptions transactionOptions = new TransactionOptions() { IsolationLevel = IsolationLevel.ReadCommitted, Timeout = TransactionManager.MaximumTimeout, }; return new TransactionScope(TransactionScopeOption.Required, transactionOptions); } string jobId = null; DateTime createdAt = new DateTime(2012, 12, 12, 0, 0, 0, DateTimeKind.Utc); using (TransactionScope scope = CreateTransactionScope()) { UseConnections((_, connection) => { jobId = connection.CreateExpiredJob(Job.FromExpression(() => SampleMethod("Hello")), new Dictionary { { "Key1", "Value1" }, { "Key2", "Value2" } }, createdAt, TimeSpan.FromDays(1)); Assert.NotNull(jobId); Assert.NotEmpty(jobId); }); if (completeTransactionScope) { scope.Complete(); } } UseConnections((connection, _) => { if (completeTransactionScope) { dynamic sqlJob = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""job"""); Assert.Equal(jobId, sqlJob.id.ToString()); Assert.Equal(createdAt, sqlJob.createdat); Assert.Null((long?)sqlJob.stateid); Assert.Null((string)sqlJob.statename); } else { TestJob job = connection.QuerySingleOrDefault($@"SELECT * FROM ""{GetSchemaName()}"".""job"""); Assert.Null(job); } }); } private void UseConnections(Action action) { PostgreSqlStorage storage = _fixture.SafeInit(); action(storage.CreateAndOpenConnection(), storage.GetStorageConnection()); } private void UseConnection(Action action) { PostgreSqlStorage storage = _fixture.SafeInit(); action(storage.GetStorageConnection()); } private static void UseDisposableConnection(Action action) { using (NpgsqlConnection sqlConnection = ConnectionUtils.CreateConnection()) { PostgreSqlStorageOptions options = new() { EnableTransactionScopeEnlistment = true, SchemaName = GetSchemaName(), TransactionSynchronisationTimeout = TimeSpan.FromSeconds(1), }; PostgreSqlStorage storage = new(new ExistingNpgsqlConnectionFactory(sqlConnection, options), options); using (PostgreSqlConnection connection = storage.GetStorageConnection()) { action(connection); } } } private static string GetSchemaName() { return ConnectionUtils.GetSchemaName(); } #pragma warning disable xUnit1013 // Public method should be marked as test public static void SampleMethod(string arg) #pragma warning restore xUnit1013 // Public method should be marked as test { } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/PostgreSqlDistributedLockFacts.cs ================================================ using System; using System.Data; using System.Threading; using Dapper; using Hangfire.PostgreSql.Tests.Utils; using Moq; using Npgsql; using Xunit; namespace Hangfire.PostgreSql.Tests { public class PostgreSqlDistributedLockFacts : IDisposable { private readonly TimeSpan _timeout = TimeSpan.FromSeconds(5); private NpgsqlConnection _connection; public void Dispose() { _connection?.Dispose(); } [Fact] public void Acquire_ThrowsAnException_WhenResourceIsNullOrEmpty() { PostgreSqlStorageOptions options = new(); ArgumentNullException exception = Assert.Throws( () => PostgreSqlDistributedLock.Acquire(new Mock().Object, "", _timeout, options)); Assert.Equal("resource", exception.ParamName); } [Fact] public void Acquire_ThrowsAnException_WhenConnectionIsNull() { PostgreSqlStorageOptions options = new(); ArgumentNullException exception = Assert.Throws(() => PostgreSqlDistributedLock.Acquire(null, "hello", _timeout, options)); Assert.Equal("connection", exception.ParamName); } [Fact] public void Acquire_ThrowsAnException_WhenOptionsIsNull() { Mock connection = new Mock(); connection.SetupGet(c => c.State).Returns(ConnectionState.Open); ArgumentNullException exception = Assert.Throws( () => PostgreSqlDistributedLock.Acquire(new Mock().Object, "hi", _timeout, null)); Assert.Equal("options", exception.ParamName); } [Fact] [CleanDatabase] public void Acquire_AcquiresExclusiveApplicationLock_WithUseNativeDatabaseTransactions_OnSession() { PostgreSqlStorageOptions options = new() { SchemaName = GetSchemaName(), UseNativeDatabaseTransactions = true, }; UseConnection(connection => { // ReSharper disable once UnusedVariable PostgreSqlDistributedLock.Acquire(connection, "hello", _timeout, options); long lockCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""lock"" WHERE ""resource"" = @Resource", new { Resource = "hello" }); Assert.Equal(1, lockCount); //Assert.Equal("Exclusive", lockMode); }); } [Fact] [CleanDatabase] public void Acquire_AcquiresExclusiveApplicationLock_WithUseNativeDatabaseTransactions_OnSession_WhenDeadlockOccurs() { PostgreSqlStorageOptions options = new() { SchemaName = GetSchemaName(), UseNativeDatabaseTransactions = true, DistributedLockTimeout = TimeSpan.FromSeconds(10), }; UseConnection(connection => { // Arrange TimeSpan timeout = TimeSpan.FromSeconds(15); string resourceName = "hello"; connection.Execute($@"INSERT INTO ""{GetSchemaName()}"".""lock"" VALUES (@ResourceName, 0, @Now)", new { ResourceName = resourceName, Now = DateTime.UtcNow }); // Act && Assert (not throwing means it worked) PostgreSqlDistributedLock.Acquire(connection, resourceName, timeout, options); }); } [Fact] [CleanDatabase] public void Acquire_AcquiresExclusiveApplicationLock_WithoutUseNativeDatabaseTransactions_OnSession() { PostgreSqlStorageOptions options = new() { SchemaName = GetSchemaName(), UseNativeDatabaseTransactions = false, }; UseConnection(connection => { // Acquire locks on two different resources to make sure they don't conflict. PostgreSqlDistributedLock.Acquire(connection, "hello", _timeout, options); PostgreSqlDistributedLock.Acquire(connection, "hello2", _timeout, options); long lockCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""lock"" WHERE ""resource"" = @Resource", new { Resource = "hello" }); Assert.Equal(1, lockCount); }); } [Fact] [CleanDatabase] public void Acquire_ThrowsAnException_IfLockCanNotBeGranted_WithUseNativeDatabaseTransactions() { PostgreSqlStorageOptions options = new() { SchemaName = GetSchemaName(), UseNativeDatabaseTransactions = true, }; ManualResetEventSlim releaseLock = new(false); ManualResetEventSlim lockAcquired = new(false); Thread thread = new(() => UseConnection(connection1 => { PostgreSqlDistributedLock.Acquire(connection1, "exclusive", _timeout, options); lockAcquired.Set(); releaseLock.Wait(); PostgreSqlDistributedLock.Release(connection1, "exclusive", options); })); thread.Start(); lockAcquired.Wait(); UseConnection(connection2 => Assert.Throws(() => PostgreSqlDistributedLock.Acquire(connection2, "exclusive", _timeout, options))); releaseLock.Set(); thread.Join(); } [Fact] [CleanDatabase] public void Acquire_ThrowsAnException_IfLockCanNotBeGranted_WithoutUseNativeDatabaseTransactions() { PostgreSqlStorageOptions options = new() { SchemaName = GetSchemaName(), UseNativeDatabaseTransactions = false, }; ManualResetEventSlim releaseLock = new(false); ManualResetEventSlim lockAcquired = new(false); Thread thread = new(() => UseConnection(connection1 => { PostgreSqlDistributedLock.Acquire(connection1, "exclusive", _timeout, options); lockAcquired.Set(); releaseLock.Wait(); PostgreSqlDistributedLock.Release(connection1, "exclusive", options); })); thread.Start(); lockAcquired.Wait(); UseConnection(connection2 => Assert.Throws(() => PostgreSqlDistributedLock.Acquire(connection2, "exclusive", _timeout, options))); releaseLock.Set(); thread.Join(); } [Theory] [InlineData(true)] [InlineData(false)] [CleanDatabase] public void Acquire_ExpiredLockExists_LocksAnyway(bool useNativeDatabaseTransactions) { const string resource = "hello"; PostgreSqlStorageOptions options = new() { SchemaName = GetSchemaName(), UseNativeDatabaseTransactions = useNativeDatabaseTransactions, }; UseConnection(connection => { DateTime acquired = DateTime.UtcNow - options.DistributedLockTimeout - TimeSpan.FromMinutes(1); connection.Execute($@"INSERT INTO ""{GetSchemaName()}"".""lock"" (""resource"", ""acquired"") VALUES (@Resource, @Acquired)", new { Resource = resource, Acquired = acquired }); PostgreSqlDistributedLock.Acquire(connection, resource, _timeout, options); long lockCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""lock"" WHERE ""resource"" = @Resource", new { Resource = resource }); Assert.Equal(1, lockCount); }); } [Fact] [CleanDatabase] public void Dispose_ReleasesExclusiveApplicationLock_WithUseNativeDatabaseTransactions() { PostgreSqlStorageOptions options = new() { SchemaName = GetSchemaName(), UseNativeDatabaseTransactions = true, }; UseConnection(connection => { PostgreSqlDistributedLock.Acquire(connection, "hello", _timeout, options); PostgreSqlDistributedLock.Release(connection, "hello", options); long lockCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""lock"" WHERE ""resource"" = @Resource", new { Resource = "hello" }); Assert.Equal(0, lockCount); }); } [Fact] [CleanDatabase] public void Dispose_ReleasesExclusiveApplicationLock_WithoutUseNativeDatabaseTransactions() { PostgreSqlStorageOptions options = new() { SchemaName = GetSchemaName(), UseNativeDatabaseTransactions = false, }; UseConnection(connection => { PostgreSqlDistributedLock.Acquire(connection, "hello", _timeout, options); PostgreSqlDistributedLock.Release(connection, "hello", options); long lockCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""lock"" WHERE ""resource"" = @Resource", new { Resource = "hello" }); Assert.Equal(0, lockCount); }); } private void UseConnection(Action action) { _connection ??= ConnectionUtils.CreateConnection(); action(_connection); } private static string GetSchemaName() { return ConnectionUtils.GetSchemaName(); } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/PostgreSqlFetchedJobFacts.cs ================================================ using System; using System.Globalization; using System.Linq; using System.Threading; using Dapper; using Hangfire.PostgreSql.Tests.Utils; using Xunit; namespace Hangfire.PostgreSql.Tests { public class PostgreSqlFetchedJobFacts { private const string JobId = "id"; private const string Queue = "queue"; private DateTime _fetchedAt = DateTime.UtcNow; private readonly PostgreSqlStorage _storage; public PostgreSqlFetchedJobFacts() { _storage = new PostgreSqlStorage(ConnectionUtils.GetDefaultConnectionFactory()); } [Fact] public void Ctor_ThrowsAnException_WhenStorageIsNull() { ArgumentNullException exception = Assert.Throws(() => new PostgreSqlFetchedJob(null, 1, JobId, Queue, _fetchedAt)); Assert.Equal("storage", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenJobIdIsNull() { ArgumentNullException exception = Assert.Throws(() => new PostgreSqlFetchedJob(_storage, 1, null, Queue, _fetchedAt)); Assert.Equal("jobId", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenQueueIsNull() { ArgumentNullException exception = Assert.Throws(() => new PostgreSqlFetchedJob(_storage, 1, JobId, null, _fetchedAt)); Assert.Equal("queue", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenFetchedAtIsNull() { ArgumentNullException exception = Assert.Throws(() => new PostgreSqlFetchedJob(_storage, 1, JobId, Queue, null)); Assert.Equal("fetchedAt", exception.ParamName); } [Fact] public void Ctor_CorrectlySets_AllInstanceProperties() { PostgreSqlFetchedJob fetchedJob = new(_storage, 1, JobId, Queue, _fetchedAt); Assert.Equal(1, fetchedJob.Id); Assert.Equal(JobId, fetchedJob.JobId); Assert.Equal(Queue, fetchedJob.Queue); Assert.Equal(_fetchedAt, fetchedJob.FetchedAt); } [Fact] [CleanDatabase] public void RemoveFromQueue_ReallyDeletesTheJobFromTheQueue() { // Arrange long id = CreateJobQueueRecord(_storage, "1", "default", _fetchedAt); PostgreSqlFetchedJob processingJob = new(_storage, id, "1", "default", _fetchedAt); // Act processingJob.RemoveFromQueue(); // Assert long count = _storage.UseConnection(null, connection => connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""jobqueue""")); Assert.Equal(0, count); } [Fact] [CleanDatabase] public void RemoveFromQueue_DoesNotDelete_UnrelatedJobs() { // Arrange CreateJobQueueRecord(_storage, "1", "default", _fetchedAt); CreateJobQueueRecord(_storage, "1", "critical", _fetchedAt); CreateJobQueueRecord(_storage, "2", "default", _fetchedAt); PostgreSqlFetchedJob fetchedJob = new PostgreSqlFetchedJob(_storage, 999, "1", "default", _fetchedAt); // Act fetchedJob.RemoveFromQueue(); // Assert long count = _storage.UseConnection(null, connection => connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""jobqueue""")); Assert.Equal(3, count); } [Fact] [CleanDatabase] public void Requeue_SetsFetchedAtValueToNull() { // Arrange long id = CreateJobQueueRecord(_storage, "1", "default", _fetchedAt); PostgreSqlFetchedJob processingJob = new(_storage, id, "1", "default", _fetchedAt); // Act processingJob.Requeue(); // Assert dynamic record = _storage.UseConnection(null, connection => connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""jobqueue""")); Assert.Null(record.fetchedat); } [Fact] [CleanDatabase] public void Timer_UpdatesFetchedAtColumn() { _storage.UseConnection(null, connection => { // Arrange var fetchedAt = DateTime.UtcNow.AddMinutes(-5); long id = CreateJobQueueRecord(_storage, "1", "default", fetchedAt); using (var processingJob = new PostgreSqlFetchedJob(_storage, id, "1", "default", fetchedAt)) { processingJob.DisposeTimer(); Thread.Sleep(TimeSpan.FromSeconds(10)); processingJob.ExecuteKeepAliveQueryIfRequired(); dynamic record = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""jobqueue"""); Assert.NotNull(processingJob.FetchedAt); Assert.Equal(processingJob.FetchedAt, record.fetchedat); DateTime now = DateTime.UtcNow; Assert.True(now.AddSeconds(-5) < record.fetchedat, (now - record.fetchedat).ToString()); } }); } [Fact] [CleanDatabase] public void RemoveFromQueue_AfterTimer_RemovesJobFromTheQueue() { _storage.UseConnection(null, connection => { // Arrange long id = CreateJobQueueRecord(_storage, "1", "default", _fetchedAt); using (PostgreSqlFetchedJob processingJob = new PostgreSqlFetchedJob(_storage, id, "1", "default", _fetchedAt)) { Thread.Sleep(TimeSpan.FromSeconds(10)); processingJob.DisposeTimer(); // Act processingJob.RemoveFromQueue(); // Assert int count = connection.QuerySingle($@"SELECT count(*) FROM ""{GetSchemaName()}"".""jobqueue"""); Assert.Equal(0, count); } }); } [Fact] [CleanDatabase] public void RequeueQueue_AfterTimer_SetsFetchedAtValueToNull() { _storage.UseConnection(null, connection => { // Arrange long id = CreateJobQueueRecord(_storage, "1", "default", _fetchedAt); using (var processingJob = new PostgreSqlFetchedJob(_storage, id, "1", "default", _fetchedAt)) { Thread.Sleep(TimeSpan.FromSeconds(10)); processingJob.DisposeTimer(); // Act processingJob.Requeue(); // Assert dynamic record = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""jobqueue"""); Assert.Null(record.fetchedat); } }); } [Fact] [CleanDatabase] public void Dispose_SetsFetchedAtValueToNull_IfThereWereNoCallsToComplete() { // Arrange long id = CreateJobQueueRecord(_storage, "1", "default", _fetchedAt); PostgreSqlFetchedJob processingJob = new(_storage, id, "1", "default", _fetchedAt); // Act processingJob.Dispose(); // Assert dynamic record = _storage.UseConnection(null, connection => connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""jobqueue""")); Assert.Null(record.fetchedat); } private static long CreateJobQueueRecord(PostgreSqlStorage storage, string jobId, string queue, DateTime? fetchedAt) { string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""jobqueue"" (""jobid"", ""queue"", ""fetchedat"") VALUES (@Id, @Queue, @FetchedAt) RETURNING ""id"" "; return storage.UseConnection(null, connection => connection.QuerySingle(arrangeSql, new { Id = Convert.ToInt64(jobId, CultureInfo.InvariantCulture), Queue = queue, FetchedAt = fetchedAt })); } private static string GetSchemaName() { return ConnectionUtils.GetSchemaName(); } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/PostgreSqlInstallerFacts.cs ================================================ using System; using System.Globalization; using Dapper; using Hangfire.PostgreSql.Tests.Utils; using Npgsql; using Xunit; namespace Hangfire.PostgreSql.Tests { public class PostgreSqlInstallerFacts { [Fact] public void InstallingSchemaUpdatesVersionAndShouldNotThrowAnException() { Exception ex = Record.Exception(() => { UseConnection(connection => { string schemaName = "hangfire_tests_" + Guid.NewGuid().ToString().Replace("-", "_").ToLower(CultureInfo.InvariantCulture); PostgreSqlObjectsInstaller.Install(connection, schemaName); int lastVersion = connection.QuerySingle($@"SELECT version FROM ""{schemaName}"".""schema"""); Assert.Equal(23, lastVersion); connection.Execute($@"DROP SCHEMA ""{schemaName}"" CASCADE;"); }); }); Assert.Null(ex); } [Fact] public void InstallingSchemaWithCapitalsUpdatesVersionAndShouldNotThrowAnException() { Exception ex = Record.Exception(() => { UseConnection(connection => { string schemaName = "Hangfire_Tests_" + Guid.NewGuid().ToString().Replace("-", "_").ToLower(CultureInfo.InvariantCulture); PostgreSqlObjectsInstaller.Install(connection, schemaName); int lastVersion = connection.QuerySingle($@"SELECT version FROM ""{schemaName}"".""schema"""); Assert.Equal(23, lastVersion); connection.Execute($@"DROP SCHEMA ""{schemaName}"" CASCADE;"); }); }); Assert.Null(ex); } private static void UseConnection(Action action) { using NpgsqlConnection connection = ConnectionUtils.CreateConnection(); action(connection); } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/PostgreSqlJobQueueFacts.cs ================================================ using System; using System.Data; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; using Dapper; using Hangfire.PostgreSql.Tests.Utils; using Hangfire.PostgreSql.Utils; using Hangfire.Storage; using Npgsql; using Xunit; namespace Hangfire.PostgreSql.Tests { public class PostgreSqlJobQueueFacts : IClassFixture { private static readonly string[] _defaultQueues = { "default" }; private readonly PostgreSqlStorageFixture _fixture; public PostgreSqlJobQueueFacts(PostgreSqlStorageFixture fixture) { _fixture = fixture; _fixture.SetupOptions(o => o.UseSlidingInvisibilityTimeout = true); } [Fact] public void Ctor_ThrowsAnException_WhenStorageIsNull() { ArgumentNullException exception = Assert.Throws(() => new PostgreSqlJobQueue(null)); Assert.Equal("storage", exception.ParamName); } [Fact] [CleanDatabase] public void Dequeue_ShouldThrowAnException_WhenQueuesCollectionIsNull() { UseConnection((_, storage) => { PostgreSqlJobQueue queue = CreateJobQueue(storage, false); ArgumentNullException exception = Assert.Throws(() => queue.Dequeue(null, CreateTimingOutCancellationToken())); Assert.Equal("queues", exception.ParamName); }); } [Fact] [CleanDatabase] public void Dequeue_ShouldFetchAJob_FromQueueWithHigherPriority() { UseConnection((connection, storage) => { PostgreSqlJobQueue queue = CreateJobQueue(storage, false); CancellationToken token = CreateTimingOutCancellationToken(); queue.Enqueue(connection, "1", "1"); queue.Enqueue(connection, "2", "2"); queue.Enqueue(connection, "3", "3"); Assert.Equal("1", queue.Dequeue(new[] { "1", "2", "3" }, token).JobId); Assert.Equal("2", queue.Dequeue(new[] { "2", "3", "1" }, token).JobId); Assert.Equal("3", queue.Dequeue(new[] { "3", "1", "2" }, token).JobId); }); } [Fact] [CleanDatabase] private void Dequeue_ShouldThrowAnException_WhenQueuesCollectionIsEmpty_WithUseNativeDatabaseTransactions() { Dequeue_ShouldThrowAnException_WhenQueuesCollectionIsEmpty(true); } [Fact] [CleanDatabase] private void Dequeue_ShouldThrowAnException_WhenQueuesCollectionIsEmpty_WithoutUseNativeDatabaseTransactions() { Dequeue_ShouldThrowAnException_WhenQueuesCollectionIsEmpty(false); } private void Dequeue_ShouldThrowAnException_WhenQueuesCollectionIsEmpty(bool useNativeDatabaseTransactions) { UseConnection((_, storage) => { PostgreSqlJobQueue queue = CreateJobQueue(storage, useNativeDatabaseTransactions); ArgumentException exception = Assert.Throws(() => queue.Dequeue(Array.Empty(), CreateTimingOutCancellationToken())); Assert.Equal("queues", exception.ParamName); }); } [Fact] private void Dequeue_ThrowsOperationCanceled_WhenCancellationTokenIsSetAtTheBeginning_WithUseNativeDatabaseTransactions() { Dequeue_ThrowsOperationCanceled_WhenCancellationTokenIsSetAtTheBeginning(true); } [Fact] private void Dequeue_ThrowsOperationCanceled_WhenCancellationTokenIsSetAtTheBeginning_WithoutUseNativeDatabaseTransactions() { Dequeue_ThrowsOperationCanceled_WhenCancellationTokenIsSetAtTheBeginning(false); } private void Dequeue_ThrowsOperationCanceled_WhenCancellationTokenIsSetAtTheBeginning( bool useNativeDatabaseTransactions) { UseConnection((_, storage) => { CancellationTokenSource cts = new CancellationTokenSource(); cts.Cancel(); PostgreSqlJobQueue queue = CreateJobQueue(storage, useNativeDatabaseTransactions); Assert.Throws(() => queue.Dequeue(_defaultQueues, cts.Token)); }); } [Fact] [CleanDatabase] public void Dequeue_ShouldWaitIndefinitely_WhenThereAreNoJobs_WithUseNativeDatabaseTransactions() { Dequeue_ShouldWaitIndefinitely_WhenThereAreNoJobs(true); } [Fact] [CleanDatabase] public void Dequeue_ShouldWaitIndefinitely_WhenThereAreNoJobs_WithoutUseNativeDatabaseTransactions() { Dequeue_ShouldWaitIndefinitely_WhenThereAreNoJobs(false); } private void Dequeue_ShouldWaitIndefinitely_WhenThereAreNoJobs(bool useNativeDatabaseTransactions) { UseConnection((_, storage) => { CancellationTokenSource cts = new CancellationTokenSource(200); PostgreSqlJobQueue queue = CreateJobQueue(storage, useNativeDatabaseTransactions); Assert.Throws(() => queue.Dequeue(_defaultQueues, cts.Token)); }); } [Fact] [CleanDatabase] public void Dequeue_ShouldFetchAJob_FromTheSpecifiedQueue_WithUseNativeDatabaseTransactions() { Dequeue_ShouldFetchAJob_FromTheSpecifiedQueue(true); } [Fact] [CleanDatabase] public void Dequeue_ShouldFetchAJob_FromTheSpecifiedQueue_WithoutUseNativeDatabaseTransactions() { Dequeue_ShouldFetchAJob_FromTheSpecifiedQueue(false); } private void Dequeue_ShouldFetchAJob_FromTheSpecifiedQueue(bool useNativeDatabaseTransactions) { string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""jobqueue"" (""jobid"", ""queue"") VALUES (@JobId, @Queue) RETURNING ""id"" "; // Arrange UseConnection((connection, storage) => { long id = connection.QuerySingle(arrangeSql, new { JobId = 1, Queue = "default" }); PostgreSqlJobQueue queue = CreateJobQueue(storage, useNativeDatabaseTransactions); // Act PostgreSqlFetchedJob payload = (PostgreSqlFetchedJob)queue.Dequeue(_defaultQueues, CreateTimingOutCancellationToken()); // Assert Assert.Equal(id, payload.Id); Assert.Equal("1", payload.JobId); Assert.Equal("default", payload.Queue); }); } [Fact] [CleanDatabase] public void Dequeue_ShouldLeaveJobInTheQueue_ButSetItsFetchedAtValue_WithUseNativeDatabaseTransactions() { Dequeue_ShouldLeaveJobInTheQueue_ButSetItsFetchedAtValue(true); } [Fact] [CleanDatabase] public void Dequeue_ShouldLeaveJobInTheQueue_ButSetItsFetchedAtValue_WithoutUseNativeDatabaseTransactions() { Dequeue_ShouldLeaveJobInTheQueue_ButSetItsFetchedAtValue(false); } private void Dequeue_ShouldLeaveJobInTheQueue_ButSetItsFetchedAtValue(bool useNativeDatabaseTransactions) { string arrangeSql = $@" WITH i AS ( INSERT INTO ""{GetSchemaName()}"".""job"" (""invocationdata"", ""arguments"", ""createdat"") VALUES (@InvocationData::jsonb, @Arguments::jsonb, NOW()) RETURNING ""id"" ) INSERT INTO ""{GetSchemaName()}"".""jobqueue"" (""jobid"", ""queue"") SELECT i.""id"", @Queue FROM i; "; // Arrange UseConnection((connection, storage) => { connection.Execute(arrangeSql, new { InvocationData = JsonParameter.GetParameterValue(""), Arguments = JsonParameter.GetParameterValue("", JsonParameter.ValueType.Array), Queue = "default" }); PostgreSqlJobQueue queue = CreateJobQueue(storage, useNativeDatabaseTransactions); // Act IFetchedJob payload = queue.Dequeue(_defaultQueues, CreateTimingOutCancellationToken()); // Assert Assert.NotNull(payload); DateTime? fetchedAt = connection.QuerySingle($@"SELECT ""fetchedat"" FROM ""{GetSchemaName()}"".""jobqueue"" WHERE ""jobid"" = @Id", new { Id = Convert.ToInt64(payload.JobId, CultureInfo.InvariantCulture) }); Assert.NotNull(fetchedAt); Assert.True(fetchedAt > DateTime.UtcNow.AddMinutes(-1)); }); } [Fact] [CleanDatabase] public void Dequeue_ShouldFetchATimedOutJobs_FromTheSpecifiedQueue_WithUseNativeDatabaseTransactions() { Dequeue_ShouldFetchATimedOutJobs_FromTheSpecifiedQueue(true, false); } [Fact] [CleanDatabase] public void Dequeue_ShouldFetchATimedOutJobs_FromTheSpecifiedQueue_WithoutUseNativeDatabaseTransactions() { Dequeue_ShouldFetchATimedOutJobs_FromTheSpecifiedQueue(false, false); } [Fact] [CleanDatabase] public void Dequeue_ShouldFetchATimedOutJobs_FromTheSpecifiedQueue_WithUseNativeDatabaseTransactionsAndSlidingInvisbility() { Dequeue_ShouldFetchATimedOutJobs_FromTheSpecifiedQueue(true, true); } [Fact] [CleanDatabase] public void Dequeue_ShouldFetchATimedOutJobs_FromTheSpecifiedQueue_WithoutUseNativeDatabaseTransactionsAndSlidingInvisbility() { Dequeue_ShouldFetchATimedOutJobs_FromTheSpecifiedQueue(false, true); } private void Dequeue_ShouldFetchATimedOutJobs_FromTheSpecifiedQueue(bool useNativeDatabaseTransactions, bool useSlidingInvisibilityTimeout) { string arrangeSql = $@" WITH i AS ( INSERT INTO ""{GetSchemaName()}"".""job"" (""invocationdata"", ""arguments"", ""createdat"") VALUES (@InvocationData::jsonb, @Arguments::jsonb, NOW()) RETURNING ""id"" ) INSERT INTO ""{GetSchemaName()}"".""jobqueue"" (""jobid"", ""queue"", ""fetchedat"") SELECT i.""id"", @Queue, @FetchedAt FROM i; "; // Arrange UseConnection((connection, storage) => { connection.Execute(arrangeSql, new { Queue = "default", FetchedAt = DateTime.UtcNow.AddDays(-1), InvocationData = JsonParameter.GetParameterValue(""), Arguments = JsonParameter.GetParameterValue("", JsonParameter.ValueType.Array), }); PostgreSqlJobQueue queue = CreateJobQueue(storage, useNativeDatabaseTransactions, useSlidingInvisibilityTimeout: useSlidingInvisibilityTimeout); // Act IFetchedJob payload = queue.Dequeue(_defaultQueues, CreateTimingOutCancellationToken()); // Assert Assert.NotEmpty(payload.JobId); }); } [Fact] [CleanDatabase] public void Dequeue_ShouldSetFetchedAt_OnlyForTheFetchedJob_WithUseNativeDatabaseTransactions() { Dequeue_ShouldSetFetchedAt_OnlyForTheFetchedJob(true); } [Fact] [CleanDatabase] public void Dequeue_ShouldSetFetchedAt_OnlyForTheFetchedJob_WithoutUseNativeDatabaseTransactions() { Dequeue_ShouldSetFetchedAt_OnlyForTheFetchedJob(false); } private void Dequeue_ShouldSetFetchedAt_OnlyForTheFetchedJob(bool useNativeDatabaseTransactions) { string arrangeSql = $@" WITH i AS ( INSERT INTO ""{GetSchemaName()}"".""job"" (""invocationdata"", ""arguments"", ""createdat"") VALUES (@InvocationData::jsonb, @Arguments::jsonb, NOW()) RETURNING ""id"" ) INSERT INTO ""{GetSchemaName()}"".""jobqueue"" (""jobid"", ""queue"") SELECT i.""id"", @Queue FROM i; "; UseConnection((connection, storage) => { connection.Execute(arrangeSql, new[] { new { Queue = "default", InvocationData = JsonParameter.GetParameterValue(""), Arguments = JsonParameter.GetParameterValue("", JsonParameter.ValueType.Array) }, new { Queue = "default", InvocationData = JsonParameter.GetParameterValue(""), Arguments = JsonParameter.GetParameterValue("", JsonParameter.ValueType.Array) }, }); PostgreSqlJobQueue queue = CreateJobQueue(storage, useNativeDatabaseTransactions); // Act IFetchedJob payload = queue.Dequeue(_defaultQueues, CreateTimingOutCancellationToken()); // Assert DateTime? otherJobFetchedAt = connection.QuerySingle($@"SELECT ""fetchedat"" FROM ""{GetSchemaName()}"".""jobqueue"" WHERE ""jobid"" <> @Id", new { Id = Convert.ToInt64(payload.JobId, CultureInfo.InvariantCulture) }); Assert.Null(otherJobFetchedAt); }); } [Fact] [CleanDatabase] public void Dequeue_ShouldFetchJobs_OnlyFromSpecifiedQueues_WithUseNativeDatabaseTransactions() { Dequeue_ShouldFetchJobs_OnlyFromSpecifiedQueues(true); } [Fact] [CleanDatabase] public void Dequeue_ShouldFetchJobs_OnlyFromSpecifiedQueues_WithoutUseNativeDatabaseTransactions() { Dequeue_ShouldFetchJobs_OnlyFromSpecifiedQueues(false); } private void Dequeue_ShouldFetchJobs_OnlyFromSpecifiedQueues(bool useNativeDatabaseTransactions) { string arrangeSql = $@" WITH i AS ( INSERT INTO ""{GetSchemaName()}"".""job"" (""invocationdata"", ""arguments"", ""createdat"") VALUES (@InvocationData::jsonb, @Arguments::jsonb, NOW()) RETURNING ""id"" ) INSERT INTO ""{GetSchemaName()}"".""jobqueue"" (""jobid"", ""queue"") SELECT i.""id"", @Queue FROM i; "; UseConnection((connection, storage) => { PostgreSqlJobQueue queue = CreateJobQueue(storage, useNativeDatabaseTransactions); connection.Execute(arrangeSql, new { Queue = "critical", InvocationData = JsonParameter.GetParameterValue(""), Arguments = JsonParameter.GetParameterValue("", JsonParameter.ValueType.Array) }); Assert.Throws(() => queue.Dequeue(_defaultQueues, CreateTimingOutCancellationToken())); }); } [Fact] [CleanDatabase] private void Dequeue_ShouldFetchJobs_FromMultipleQueues_WithUseNativeDatabaseTransactions() { Dequeue_ShouldFetchJobs_FromMultipleQueues(true); } [Fact] [CleanDatabase] private void Dequeue_ShouldFetchJobs_FromMultipleQueues_WithoutUseNativeDatabaseTransactions() { Dequeue_ShouldFetchJobs_FromMultipleQueues(false); } private void Dequeue_ShouldFetchJobs_FromMultipleQueues(bool useNativeDatabaseTransactions) { string arrangeSql = $@" WITH i AS ( INSERT INTO ""{GetSchemaName()}"".""job"" (""invocationdata"", ""arguments"", ""createdat"") VALUES (@InvocationData::jsonb, @Arguments::jsonb, NOW()) RETURNING ""id"" ) INSERT INTO ""{GetSchemaName()}"".""jobqueue"" (""jobid"", ""queue"") SELECT i.""id"", @Queue FROM i; "; string[] queueNames = { "default", "critical" }; UseConnection((connection, storage) => { connection.Execute(arrangeSql, new[] { new { Queue = queueNames.First(), InvocationData = JsonParameter.GetParameterValue("") , Arguments = JsonParameter.GetParameterValue("", JsonParameter.ValueType.Array) }, new { Queue = queueNames.Last(), InvocationData = JsonParameter.GetParameterValue(""), Arguments = JsonParameter.GetParameterValue("", JsonParameter.ValueType.Array) }, }); PostgreSqlJobQueue queue = CreateJobQueue(storage, useNativeDatabaseTransactions); PostgreSqlFetchedJob queueFirst = (PostgreSqlFetchedJob)queue.Dequeue(queueNames, CreateTimingOutCancellationToken()); Assert.NotNull(queueFirst.JobId); Assert.Contains(queueFirst.Queue, queueNames); PostgreSqlFetchedJob queueLast = (PostgreSqlFetchedJob)queue.Dequeue(queueNames, CreateTimingOutCancellationToken()); Assert.NotNull(queueLast.JobId); Assert.Contains(queueLast.Queue, queueNames); }); } [Fact] [CleanDatabase] public void Enqueue_AddsAJobToTheQueue_WithUseNativeDatabaseTransactions() { Enqueue_AddsAJobToTheQueue(true); } [Fact] [CleanDatabase] public void Enqueue_AddsAJobToTheQueue_WithoutUseNativeDatabaseTransactions() { Enqueue_AddsAJobToTheQueue(false); } [Fact] [CleanDatabase] public void Queues_Should_Support_Long_Queue_Names() { UseConnection((connection, storage) => { PostgreSqlJobQueue queue = CreateJobQueue(storage, false); string name = "very_long_name_that_is_over_20_characters_long_or_something"; Assert.True(name.Length > 21); queue.Enqueue(connection, name, "1"); string retrievedName = connection.QuerySingle($@"SELECT ""queue"" FROM ""{GetSchemaName()}"".""jobqueue"""); Assert.Equal(name, retrievedName); }); } [Fact] [CleanDatabase] public void Queues_Can_Dequeue_On_Signal() { UseConnection((connection, storage) => { PostgreSqlJobQueue queue = CreateJobQueue(storage, false); IFetchedJob job = null; //as UseConnection does not support async-await we have to work with Thread.Sleep Task.Run(() => { //dequeue the job asynchronously job = queue.Dequeue(new[] { "default" }, CreateTimingOutCancellationToken()); }); //all sleeps are possibly way to high but this ensures that any race condition is unlikely //to ensure that the task would run Thread.Sleep(1000); Assert.Null(job); //enqueue a job that does not trigger the existing queue to reevaluate its state queue.Enqueue(connection, "default", "1"); Thread.Sleep(1000); //the job should still be unset Assert.Null(job); //trigger a reevaluation queue.FetchNextJob(); //wait for the Dequeue to execute and return the next job Thread.Sleep(1000); Assert.NotNull(job); }); } [Fact] [CleanDatabase] public void Queues_Can_Dequeue_On_Notification() { UseConnection((connection, storage) => { TimeSpan timeout = TimeSpan.FromSeconds(30); // Only for Postgres 11+ should we have a polling time greater than the timeout. if (connection.SupportsNotifications()) { storage.Options.QueuePollInterval = TimeSpan.FromMinutes(2); } PostgreSqlJobQueue queue = CreateJobQueue(storage, false, true); IFetchedJob job = null; //as UseConnection does not support async-await we have to work with Thread.Sleep Task task = Task.Run(() => { //dequeue the job asynchronously CancellationTokenSource cancellationTokenSource = new(timeout); try { job = queue.Dequeue(new[] { "default" }, cancellationTokenSource.Token); } catch (OperationCanceledException) { // Do nothing, task was intentionally cancelled. } finally { cancellationTokenSource.Dispose(); } }); Thread.Sleep(2000); // Give thread time to startup. queue.Enqueue(connection, "default", "1"); task.Wait(timeout); Assert.NotNull(job); }); } private void Enqueue_AddsAJobToTheQueue(bool useNativeDatabaseTransactions) { UseConnection((connection, storage) => { PostgreSqlJobQueue queue = CreateJobQueue(storage, useNativeDatabaseTransactions); queue.Enqueue(connection, "default", "1"); dynamic record = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""jobqueue"""); Assert.Equal("1", record.jobid.ToString()); Assert.Equal("default", record.queue); Assert.Null(record.FetchedAt); }); } [Fact] [CleanDatabase] public void Dequeue_ShouldSelfHeal_WhenListenConnectionFails() { UseConnection((_, storage) => { storage.Options.QueuePollInterval = TimeSpan.FromMilliseconds(500); PostgreSqlJobQueue queue = CreateJobQueue(storage, false, true); Exception thrownException = null; IFetchedJob job = null; CancellationTokenSource cts = new(TimeSpan.FromSeconds(10)); Task dequeueTask = Task.Run(() => { try { job = queue.Dequeue(new[] { "default" }, cts.Token); } catch (Exception ex) when (ex is not OperationCanceledException) { thrownException = ex; } }); Thread.Sleep(1000); using (NpgsqlConnection adminConnection = ConnectionUtils.CreateMasterConnection()) { adminConnection.Execute(@" SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE query LIKE '%LISTEN%' AND pid <> pg_backend_pid()"); } Thread.Sleep(500); using (NpgsqlConnection enqueueConnection = ConnectionUtils.CreateConnection()) { queue.Enqueue(enqueueConnection, "default", "1"); } dequeueTask.Wait(TimeSpan.FromSeconds(5)); Assert.Null(thrownException); Assert.NotNull(job); }); } private static CancellationToken CreateTimingOutCancellationToken() { CancellationTokenSource source = new CancellationTokenSource(TimeSpan.FromSeconds(10)); return source.Token; } #pragma warning disable xUnit1013 // Public method should be marked as test public static void Sample(string arg1, string arg2) #pragma warning restore xUnit1013 // Public method should be marked as test { } private static PostgreSqlJobQueue CreateJobQueue(PostgreSqlStorage storage, bool useNativeDatabaseTransactions, bool enableLongPolling = false, bool useSlidingInvisibilityTimeout = false) { storage.Options.SchemaName = GetSchemaName(); storage.Options.UseNativeDatabaseTransactions = useNativeDatabaseTransactions; storage.Options.EnableLongPolling = enableLongPolling; storage.Options.UseSlidingInvisibilityTimeout = useSlidingInvisibilityTimeout; return new PostgreSqlJobQueue(storage); } private void UseConnection(Action action) { PostgreSqlStorage storage = _fixture.SafeInit(); storage.UseConnection(null, connection => { action(connection, storage); return true; }); } private static string GetSchemaName() { return ConnectionUtils.GetSchemaName(); } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/PostgreSqlMonitoringApiFacts.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Dapper; using Hangfire.Common; using Hangfire.PostgreSql.Tests.Utils; using Hangfire.States; using Hangfire.Storage; using Hangfire.Storage.Monitoring; using Moq; using Npgsql; using Xunit; namespace Hangfire.PostgreSql.Tests { public class PostgreSqlMonitoringApiFacts : IClassFixture { private readonly PostgreSqlStorageFixture _fixture; public PostgreSqlMonitoringApiFacts(PostgreSqlStorageFixture fixture) { _fixture = fixture; } [Fact] [CleanDatabase] public void GetJobs_MixedCasing_ReturnsJob() { string arrangeSql = $@" INSERT INTO ""{ConnectionUtils.GetSchemaName()}"".""job""(""invocationdata"", ""arguments"", ""createdat"") VALUES (@InvocationData::jsonb, @Arguments::jsonb, NOW()) RETURNING ""id"""; Job job = Job.FromExpression(() => SampleMethod("Hello")); InvocationData invocationData = InvocationData.SerializeJob(job); UseConnection(connection => { long jobId = connection.QuerySingle(arrangeSql, new { InvocationData = JsonParameter.GetParameterValue(SerializationHelper.Serialize(invocationData)), Arguments = JsonParameter.GetParameterValue(invocationData.Arguments, JsonParameter.ValueType.Array), }); Mock state = new(); state.Setup(x => x.Name).Returns(SucceededState.StateName); state.Setup(x => x.SerializeData()) .Returns(new Dictionary { { "SUCCEEDEDAT", "2018-05-03T13:28:18.3939693Z" }, { "PerformanceDuration", "53" }, { "latency", "6730" }, }); Commit(connection, x => x.SetJobState(jobId.ToString(CultureInfo.InvariantCulture), state.Object)); IMonitoringApi monitoringApi = _fixture.Storage.GetMonitoringApi(); JobList jobs = monitoringApi.SucceededJobs(0, 10); Assert.NotNull(jobs); }); } [Fact] [CleanDatabase] public void HourlySucceededJobs_ReturnsAggregatedStats() { DateTime now = DateTime.UtcNow; string schemaName = ConnectionUtils.GetSchemaName(); string key = $"stats:succeeded:{now.ToString("yyyy-MM-dd-HH", CultureInfo.InvariantCulture)}"; string arrangeSql = $""" BEGIN; INSERT INTO "{schemaName}"."counter"("key", "value") VALUES (@Key, 5); INSERT INTO "{schemaName}"."aggregatedcounter"("key", "value") VALUES (@Key, 7); COMMIT; """; UseConnection(connection => { connection.Execute(arrangeSql, new { Key = key }); IMonitoringApi monitoringApi = _fixture.Storage.GetMonitoringApi(); IDictionary stats = monitoringApi.HourlySucceededJobs(); Assert.Equal(24, stats.Count); long actualCounter = Assert.Single(stats.Where(x => x.Key.Hour == now.Hour).Select(x => x.Value)); Assert.Equal(12, actualCounter); }); } private void UseConnection(Action action) { PostgreSqlStorage storage = _fixture.SafeInit(); action(storage.CreateAndOpenConnection()); } private void Commit( NpgsqlConnection connection, Action action) { PostgreSqlStorage storage = _fixture.SafeInit(); using PostgreSqlWriteOnlyTransaction transaction = new(storage, () => connection); action(transaction); transaction.Commit(); } [Fact] [CleanDatabase] public void FetchedJobs_WithDuplicateJobQueueEntries_DoesNotThrow() { string schemaName = ConnectionUtils.GetSchemaName(); string createJobSql = $@" INSERT INTO ""{schemaName}"".""job"" (""invocationdata"", ""arguments"", ""createdat"") VALUES (@InvocationData::jsonb, @Arguments::jsonb, NOW()) RETURNING ""id"""; string createJobQueueSql = $@" INSERT INTO ""{schemaName}"".""jobqueue"" (""jobid"", ""queue"", ""fetchedat"") VALUES (@JobId, @Queue, @FetchedAt)"; UseConnection(connection => { Job job = Job.FromExpression(() => SampleMethod("test")); InvocationData invocationData = InvocationData.SerializeJob(job); long jobId = connection.QuerySingle(createJobSql, new { InvocationData = JsonParameter.GetParameterValue(SerializationHelper.Serialize(invocationData)), Arguments = JsonParameter.GetParameterValue(invocationData.Arguments, JsonParameter.ValueType.Array), }); DateTime fetchedAt = DateTime.UtcNow; connection.Execute(createJobQueueSql, new { JobId = jobId, Queue = "default", FetchedAt = fetchedAt }); connection.Execute(createJobQueueSql, new { JobId = jobId, Queue = "default", FetchedAt = fetchedAt.AddSeconds(1) }); PostgreSqlStorage storage = _fixture.SafeInit(); PostgreSqlStorageOptions options = new() { SchemaName = schemaName }; PostgreSqlJobQueueProvider provider = new(storage, options); PersistentJobQueueProviderCollection providers = new(provider); storage.QueueProviders = providers; IMonitoringApi monitoringApi = storage.GetMonitoringApi(); JobList fetchedJobs = monitoringApi.FetchedJobs("default", 0, 10); Assert.NotNull(fetchedJobs); Assert.Single(fetchedJobs); }); } #pragma warning disable xUnit1013 // Public method should be marked as test public static void SampleMethod(string arg) #pragma warning restore xUnit1013 // Public method should be marked as test { } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/PostgreSqlStorageFacts.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Transactions; using Hangfire.PostgreSql.Factories; using Hangfire.PostgreSql.Tests.Utils; using Hangfire.Server; using Hangfire.Storage; using Npgsql; using Xunit; namespace Hangfire.PostgreSql.Tests { public class PostgreSqlStorageFacts { private readonly PostgreSqlStorageOptions _options; public PostgreSqlStorageFacts() { _options = new PostgreSqlStorageOptions { PrepareSchemaIfNecessary = false, EnableTransactionScopeEnlistment = true }; } [Fact] [CleanDatabase] public void Ctor_CanCreateSqlServerStorage_WithExistingConnection() { NpgsqlConnection connection = ConnectionUtils.CreateConnection(); PostgreSqlStorage storage = new(new ExistingNpgsqlConnectionFactory(connection, _options), _options); Assert.NotNull(storage); } [Fact] [CleanDatabase] public void Ctor_InitializesDefaultJobQueueProvider_AndPassesCorrectOptions() { PostgreSqlStorage storage = CreateStorage(); PersistentJobQueueProviderCollection providers = storage.QueueProviders; PostgreSqlJobQueueProvider provider = (PostgreSqlJobQueueProvider)providers.GetProvider("default"); Assert.Same(_options, provider.Options); } [Fact] [CleanDatabase] public void GetMonitoringApi_ReturnsNonNullInstance() { PostgreSqlStorage storage = CreateStorage(); IMonitoringApi api = storage.GetMonitoringApi(); Assert.NotNull(api); } [Fact] [CleanDatabase] public void GetComponents_ReturnsAllNeededComponents() { PostgreSqlStorage storage = CreateStorage(); #pragma warning disable CS0618 // Type or member is obsolete IEnumerable components = storage.GetComponents(); #pragma warning restore CS0618 // Type or member is obsolete Type[] componentTypes = components.Select(x => x.GetType()).ToArray(); Assert.Contains(typeof(ExpirationManager), componentTypes); } [Fact] public void Ctor_ThrowsAnException_WhenConnectionFactoryIsNull() { ArgumentNullException exception = Assert.Throws(() => new PostgreSqlStorage(connectionFactory: null, new PostgreSqlStorageOptions())); Assert.Equal("connectionFactory", exception.ParamName); } [Fact] [CleanDatabase] public void Ctor_CanCreateSqlServerStorage_WithExistingConnectionFactory() { PostgreSqlStorage storage = new(new DefaultConnectionFactory(), _options); Assert.NotNull(storage); } [Fact] [CleanDatabase] public void CanCreateAndOpenConnection_WithExistingConnectionFactory() { PostgreSqlStorage storage = new(new DefaultConnectionFactory(), _options); NpgsqlConnection connection = storage.CreateAndOpenConnection(); Assert.NotNull(connection); } [Fact] public void CreateAndOpenConnection_ThrowsAnException_WithExistingConnectionFactoryAndInvalidOptions() { PostgreSqlStorageOptions option = new() { EnableTransactionScopeEnlistment = false, PrepareSchemaIfNecessary = false, }; Assert.Throws(() => new PostgreSqlStorage(ConnectionUtils.GetDefaultConnectionFactory(option), option)); } [Fact] public void CanUseTransaction_WithDifferentTransactionIsolationLevel() { using TransactionScope scope = new(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = IsolationLevel.Serializable }); PostgreSqlStorage storage = new(new DefaultConnectionFactory(), _options); NpgsqlConnection connection = storage.CreateAndOpenConnection(); bool success = storage.UseTransaction(connection, (_, _) => true); Assert.True(success); } [Fact] public void HasFeature_ThrowsAnException_WhenFeatureIsNull() { ArgumentNullException aex = Assert.Throws(() => new PostgreSqlStorage(new DefaultConnectionFactory(), _options).HasFeature(null)); Assert.Equal("featureId", aex.ParamName); } [Theory] [InlineData("Job.Queue", true)] // JobStorageFeatures.JobQueueProperty [InlineData("Connection.BatchedGetFirstByLowestScoreFromSet", true)] // JobStorageFeatures.Connection.BatchedGetFirstByLowest [InlineData("", false)] [InlineData("Unsupported", false)] public void HasFeature_ReturnsCorrectValues(string featureName, bool expected) { PostgreSqlStorage storage = new(new DefaultConnectionFactory(), _options); bool actual = storage.HasFeature(featureName); Assert.Equal(expected, actual); } [Fact] public void Ctor_RetriesInitialization_WhenResilientStartupIsEnabled_AndConnectionFails() { // Arrange PostgreSqlStorageOptions options = new() { PrepareSchemaIfNecessary = true, StartupConnectionMaxRetries = 2, StartupConnectionBaseDelay = TimeSpan.FromMilliseconds(1), StartupConnectionMaxDelay = TimeSpan.FromMilliseconds(2), AllowDegradedModeWithoutStorage = false, }; int callCount = 0; IConnectionFactory failingFactory = new DelegateConnectionFactory(() => { callCount++; throw new NpgsqlException("Simulated connection failure"); }); // Act & assert InvalidOperationException ex = Assert.Throws(() => new PostgreSqlStorage(failingFactory, options)); Assert.Contains("Failed to initialize Hangfire PostgreSQL storage.", ex.Message); Assert.Equal(1 + options.StartupConnectionMaxRetries, callCount); } [Fact] public void Ctor_AllowsDegradedMode_WhenResilientStartupIsEnabled_AndDegradedModeAllowed() { // Arrange PostgreSqlStorageOptions options = new() { PrepareSchemaIfNecessary = true, StartupConnectionMaxRetries = 1, StartupConnectionBaseDelay = TimeSpan.FromMilliseconds(1), StartupConnectionMaxDelay = TimeSpan.FromMilliseconds(2), AllowDegradedModeWithoutStorage = true, }; int callCount = 0; IConnectionFactory failingFactory = new DelegateConnectionFactory(() => { callCount++; throw new NpgsqlException("Simulated connection failure"); }); // Act: constructor should not throw due to degraded mode PostgreSqlStorage storage = new(failingFactory, options); Assert.NotNull(storage); // Lazy initialization should also not throw when degraded mode is enabled, // even if the storage still cannot be initialized. storage.GetConnection(); // Ensure that initialization was attempted more than once (startup + lazy init) Assert.True(callCount >= 1 + options.StartupConnectionMaxRetries); } private PostgreSqlStorage CreateStorage() { return new PostgreSqlStorage(ConnectionUtils.GetDefaultConnectionFactory(), _options); } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/PostgreSqlStorageOptionsFacts.cs ================================================ using System; using Xunit; namespace Hangfire.PostgreSql.Tests { public class PostgreSqlStorageOptionsFacts { [Fact] public void Ctor_SetsTheDefaultOptions() { PostgreSqlStorageOptions options = new(); Assert.True(options.QueuePollInterval > TimeSpan.Zero); Assert.True(options.InvisibilityTimeout > TimeSpan.Zero); Assert.True(options.DistributedLockTimeout > TimeSpan.Zero); Assert.True(options.PrepareSchemaIfNecessary); // Resilient startup defaults Assert.Equal(5, options.StartupConnectionMaxRetries); Assert.True(options.EnableResilientStartup); Assert.Equal(TimeSpan.FromSeconds(1), options.StartupConnectionBaseDelay); Assert.Equal(TimeSpan.FromMinutes(1), options.StartupConnectionMaxDelay); Assert.True(options.AllowDegradedModeWithoutStorage); } [Fact] public void EnableResilientStartup_IsFalse_WhenStartupConnectionMaxRetriesIsZero() { PostgreSqlStorageOptions options = new() { StartupConnectionMaxRetries = 0 }; Assert.False(options.EnableResilientStartup); } [Fact] public void EnableResilientStartup_IsTrue_WhenStartupConnectionMaxRetriesIsPositive() { PostgreSqlStorageOptions options = new() { StartupConnectionMaxRetries = 3 }; Assert.True(options.EnableResilientStartup); } [Fact] public void Set_QueuePollInterval_ShouldThrowAnException_WhenGivenIntervalIsTooLow() { PostgreSqlStorageOptions options = new(); Assert.Throws(() => options.QueuePollInterval = TimeSpan.FromMilliseconds(10)); } [Fact] public void Set_QueuePollInterval_SetsTheValue_WhenGivenIntervalIsTooLow_ButIgnored() { PostgreSqlStorageOptions options = new() { AllowUnsafeValues = true, QueuePollInterval = TimeSpan.FromMilliseconds(10), }; Assert.Equal(TimeSpan.FromMilliseconds(10), options.QueuePollInterval); } [Fact] public void Set_QueuePollInterval_ShouldThrowAnException_WhenGivenIntervalIsEqualToZero_EvenIfIgnored() { PostgreSqlStorageOptions options = new() { AllowUnsafeValues = true }; Assert.Throws(() => options.QueuePollInterval = TimeSpan.Zero); } [Fact] public void Set_QueuePollInterval_SetsTheValue() { PostgreSqlStorageOptions options = new(); options.QueuePollInterval = TimeSpan.FromSeconds(1); Assert.Equal(TimeSpan.FromSeconds(1), options.QueuePollInterval); } [Fact] public void Set_InvisibilityTimeout_ShouldThrowAnException_WhenGivenIntervalIsEqualToZero() { PostgreSqlStorageOptions options = new(); Assert.Throws(() => options.InvisibilityTimeout = TimeSpan.Zero); } [Fact] public void Set_InvisibilityTimeout_ShouldThrowAnException_WhenGivenIntervalIsNegative() { PostgreSqlStorageOptions options = new(); Assert.Throws(() => options.InvisibilityTimeout = TimeSpan.FromSeconds(-1)); } [Fact] public void Set_InvisibilityTimeout_SetsTheValue() { PostgreSqlStorageOptions options = new(); options.InvisibilityTimeout = TimeSpan.FromSeconds(1); Assert.Equal(TimeSpan.FromSeconds(1), options.InvisibilityTimeout); } [Fact] public void Set_DistributedLockTimeout_ShouldThrowAnException_WhenGivenIntervalIsEqualToZero() { PostgreSqlStorageOptions options = new(); Assert.Throws(() => options.DistributedLockTimeout = TimeSpan.Zero); } [Fact] public void Set_DistributedLockTimeout_ShouldThrowAnException_WhenGivenIntervalIsNegative() { PostgreSqlStorageOptions options = new(); Assert.Throws(() => options.DistributedLockTimeout = TimeSpan.FromSeconds(-1)); } [Fact] public void Set_DistributedLockTimeout_SetsTheValue() { PostgreSqlStorageOptions options = new(); options.DistributedLockTimeout = TimeSpan.FromSeconds(1); Assert.Equal(TimeSpan.FromSeconds(1), options.DistributedLockTimeout); } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/PostgreSqlWriteOnlyTransactionFacts.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text.Json; using System.Threading.Tasks; using System.Transactions; using Dapper; using Hangfire.Common; using Hangfire.PostgreSql.Factories; using Hangfire.PostgreSql.Tests.Entities; using Hangfire.PostgreSql.Tests.Utils; using Hangfire.States; using Hangfire.Storage; using Moq; using Npgsql; using Xunit; using IsolationLevel = System.Transactions.IsolationLevel; namespace Hangfire.PostgreSql.Tests { public class PostgreSqlWriteOnlyTransactionFacts : IClassFixture { private readonly PostgreSqlStorageFixture _fixture; public PostgreSqlWriteOnlyTransactionFacts(PostgreSqlStorageFixture fixture) { _fixture = fixture; } [Fact] public void Ctor_ThrowsAnException_IfStorageIsNull() { ArgumentNullException exception = Assert.Throws(() => new PostgreSqlWriteOnlyTransaction(null, () => null)); Assert.Equal("storage", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_IfDedicatedConnectionFuncIsNull() { PostgreSqlStorageOptions options = new() { EnableTransactionScopeEnlistment = true }; ArgumentNullException exception = Assert.Throws(() => new PostgreSqlWriteOnlyTransaction(new PostgreSqlStorage(new ExistingNpgsqlConnectionFactory(ConnectionUtils.CreateConnection(), options), options), null)); Assert.Equal("dedicatedConnectionFunc", exception.ParamName); } [Fact] [CleanDatabase] public void ExpireJob_SetsJobExpirationData() { string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""job""(""invocationdata"", ""arguments"", ""createdat"") VALUES ('{{}}', '[]', @When) RETURNING ""id"" "; UseConnection(connection => { DateTime utcNow = DateTime.UtcNow; string jobId = connection.QuerySingle(arrangeSql, new { When = utcNow }).ToString(CultureInfo.InvariantCulture); string anotherJobId = connection.QuerySingle(arrangeSql, new { When = utcNow }).ToString(CultureInfo.InvariantCulture); Commit(connection, x => x.ExpireJob(jobId, TimeSpan.FromDays(1))); TestJob job = Helper.GetTestJob(connection, GetSchemaName(), jobId); Assert.True(utcNow.AddMinutes(-1) < job.ExpireAt && job.ExpireAt <= utcNow.AddDays(1).AddSeconds(5)); TestJob anotherJob = Helper.GetTestJob(connection, GetSchemaName(), anotherJobId); Assert.Null(anotherJob.ExpireAt); }); } [Fact] [CleanDatabase] public void PersistJob_ClearsTheJobExpirationData() { string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""job"" (""invocationdata"", ""arguments"", ""createdat"", ""expireat"") VALUES ('{{}}', '[]', NOW(), NOW()) RETURNING ""id"" "; UseConnection(connection => { string jobId = connection.QuerySingle(arrangeSql).ToString(CultureInfo.InvariantCulture); string anotherJobId = connection.QuerySingle(arrangeSql).ToString(CultureInfo.InvariantCulture); Commit(connection, x => x.PersistJob(jobId)); TestJob job = Helper.GetTestJob(connection, GetSchemaName(), jobId); Assert.Null(job.ExpireAt); TestJob anotherJob = Helper.GetTestJob(connection, GetSchemaName(), anotherJobId); Assert.NotNull(anotherJob.ExpireAt); }); } [Fact] [CleanDatabase] public void SetJobState_AppendsAStateAndSetItToTheJob() { string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""job"" (""invocationdata"", ""arguments"", ""createdat"") VALUES ('{{}}', '[]', NOW()) RETURNING ""id"""; UseConnection(connection => { dynamic jobId = connection.QuerySingle(arrangeSql).id.ToString(); dynamic anotherJobId = connection.QuerySingle(arrangeSql).id.ToString(); Mock state = new(); state.Setup(x => x.Name).Returns("State"); state.Setup(x => x.Reason).Returns("Reason"); state.Setup(x => x.SerializeData()) .Returns(new Dictionary { { "Name", "Value" } }); Commit(connection, x => x.SetJobState(jobId, state.Object)); TestJob job = Helper.GetTestJob(connection, GetSchemaName(), jobId); Assert.Equal("State", job.StateName); Assert.NotNull(job.StateId); TestJob anotherJob = Helper.GetTestJob(connection, GetSchemaName(), anotherJobId); Assert.Null(anotherJob.StateName); Assert.Null(anotherJob.StateId); dynamic jobState = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""state"""); Assert.Equal((string)jobId, jobState.jobid.ToString()); Assert.Equal("State", jobState.name); Assert.Equal("Reason", jobState.reason); Assert.NotNull(jobState.createdat); Dictionary data = JsonSerializer.Deserialize>(jobState.data); KeyValuePair value = Assert.Single(data); Assert.Equal("Name", value.Key); Assert.Equal("Value", value.Value); }); } [Theory] [CleanDatabase] [InlineData(false)] [InlineData(true)] public void SetJobState_EnlistsInAmbientTransaction(bool completeTransactionScope) { TransactionScope CreateTransactionScope(IsolationLevel isolationLevel = IsolationLevel.RepeatableRead) { TransactionOptions transactionOptions = new() { IsolationLevel = isolationLevel, Timeout = TransactionManager.MaximumTimeout, }; return new TransactionScope(TransactionScopeOption.Required, transactionOptions); } string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""job"" (""invocationdata"", ""arguments"", ""createdat"") VALUES ('{{}}', '[]', NOW()) RETURNING ""id"""; string jobId = null; string anotherJobId = null; UseConnection(connection => { jobId = connection.QuerySingle(arrangeSql).id.ToString(); anotherJobId = connection.QuerySingle(arrangeSql).id.ToString(); }); using (TransactionScope scope = CreateTransactionScope()) { UseConnection(connection => { Mock state = new(); state.Setup(x => x.Name).Returns("State"); state.Setup(x => x.Reason).Returns("Reason"); state.Setup(x => x.SerializeData()) .Returns(new Dictionary { { "Name", "Value" } }); Commit(connection, x => x.SetJobState(jobId, state.Object)); }); if (completeTransactionScope) { scope.Complete(); } } UseConnection(connection => { TestJob job = Helper.GetTestJob(connection, GetSchemaName(), jobId); if (completeTransactionScope) { Assert.Equal("State", job.StateName); Assert.NotNull(job.StateId); dynamic jobState = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""state"""); Assert.Equal(jobId, jobState.jobid.ToString()); Assert.Equal("State", jobState.name); Assert.Equal("Reason", jobState.reason); Assert.NotNull(jobState.createdat); Dictionary data = JsonSerializer.Deserialize>(jobState.data); KeyValuePair value = Assert.Single(data); Assert.Equal("Name", value.Key); Assert.Equal("Value", value.Value); } else { Assert.Null(job.StateName); Assert.Null(job.StateId); Assert.Null(connection.QuerySingleOrDefault($@"SELECT * FROM ""{GetSchemaName()}"".""state""")); } TestJob anotherJob = Helper.GetTestJob(connection, GetSchemaName(), anotherJobId); Assert.Null(anotherJob.StateName); Assert.Null(anotherJob.StateId); }); } [Fact] [CleanDatabase] public void AddJobState_JustAddsANewRecordInATable() { string arrangeSql = $@" INSERT INTO ""{GetSchemaName()}"".""job"" (""invocationdata"", ""arguments"", ""createdat"") VALUES ('{{}}', '[]', NOW()) RETURNING ""id"" "; Dictionary expectedData = new() { { "Name", "Value" } }; UseConnection(connection => { dynamic jobId = connection.QuerySingle(arrangeSql).id.ToString(CultureInfo.InvariantCulture); Mock state = new(); state.Setup(x => x.Name).Returns("State"); state.Setup(x => x.Reason).Returns("Reason"); state.Setup(x => x.SerializeData()).Returns(expectedData); Commit(connection, x => x.AddJobState(jobId, state.Object)); TestJob job = Helper.GetTestJob(connection, GetSchemaName(), jobId); Assert.Null(job.StateName); Assert.Null(job.StateId); dynamic jobState = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""state"""); Assert.Equal((string)jobId, jobState.jobid.ToString(CultureInfo.InvariantCulture)); Assert.Equal("State", jobState.name); Assert.Equal("Reason", jobState.reason); Assert.NotNull(jobState.createdat); Dictionary data = JsonSerializer.Deserialize>(jobState.data); KeyValuePair value = Assert.Single(data); Assert.Equal("Name", value.Key); Assert.Equal("Value", value.Value); }); } [Fact] [CleanDatabase] public void AddToQueue_CallsEnqueue_OnTargetPersistentQueue() { UseConnection(connection => { Mock correctJobQueue = new(); Mock correctProvider = new(); correctProvider.Setup(x => x.GetJobQueue()) .Returns(correctJobQueue.Object); _fixture.PersistentJobQueueProviderCollection.Add(correctProvider.Object, new[] { "default" }); try { Commit(connection, x => x.AddToQueue("default", "1")); correctJobQueue.Verify(x => x.Enqueue(connection, "default", "1")); } finally { _fixture.PersistentJobQueueProviderCollection.Remove("default"); } }); } [Fact] [CleanDatabase] public void IncrementCounter_AddsRecordToCounterTable_WithPositiveValue() { UseConnection(connection => { Commit(connection, x => x.IncrementCounter("my-key")); dynamic record = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""counter"""); Assert.Equal("my-key", record.key); Assert.Equal(1, record.value); Assert.Equal((DateTime?)null, record.expireat); }); } [Fact] [CleanDatabase] public void IncrementCounter_WithExpiry_AddsARecord_WithExpirationTimeSet() { UseConnection(connection => { Commit(connection, x => x.IncrementCounter("my-key", TimeSpan.FromDays(1))); dynamic record = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""counter"""); Assert.Equal("my-key", record.key); Assert.Equal(1, record.value); Assert.NotNull(record.expireat); DateTime expireAt = (DateTime)record.expireat; Assert.True(DateTime.UtcNow.AddHours(23) < expireAt); Assert.True(expireAt < DateTime.UtcNow.AddHours(25)); }); } [Fact] [CleanDatabase] public void IncrementCounter_WithExistingKey_AddsAnotherRecord() { UseConnection(connection => { Commit(connection, x => { x.IncrementCounter("my-key"); x.IncrementCounter("my-key"); }); long recordCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""counter"""); Assert.Equal(2, recordCount); }); } [Fact] [CleanDatabase] public void DecrementCounter_AddsRecordToCounterTable_WithNegativeValue() { UseConnection(connection => { Commit(connection, x => x.DecrementCounter("my-key")); dynamic record = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""counter"""); Assert.Equal("my-key", record.key); Assert.Equal(-1, record.value); Assert.Equal((DateTime?)null, record.expireat); }); } [Fact] [CleanDatabase] public void DecrementCounter_WithExpiry_AddsARecord_WithExpirationTimeSet() { UseConnection(connection => { Commit(connection, x => x.DecrementCounter("my-key", TimeSpan.FromDays(1))); dynamic record = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""counter"""); Assert.Equal("my-key", record.key); Assert.Equal(-1, record.value); Assert.NotNull(record.expireat); DateTime expireAt = (DateTime)record.expireat; Assert.True(DateTime.UtcNow.AddHours(23) < expireAt); Assert.True(expireAt < DateTime.UtcNow.AddHours(25)); }); } [Fact] [CleanDatabase] public void DecrementCounter_WithExistingKey_AddsAnotherRecord() { UseConnection(connection => { Commit(connection, x => { x.DecrementCounter("my-key"); x.DecrementCounter("my-key"); }); long recordCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""counter"""); Assert.Equal(2, recordCount); }); } [Fact] [CleanDatabase] public void AddToSet_AddsARecord_IfThereIsNo_SuchKeyAndValue() { UseConnection(connection => { Commit(connection, x => x.AddToSet("my-key", "my-value")); dynamic record = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""set"""); Assert.Equal("my-key", record.key); Assert.Equal("my-value", record.value); Assert.Equal(0.0, record.score, 2); }); } [Fact] [CleanDatabase] public void AddToSet_AddsARecord_WhenKeyIsExists_ButValuesAreDifferent() { UseConnection(connection => { Commit(connection, x => { x.AddToSet("my-key", "my-value"); x.AddToSet("my-key", "another-value"); }); long recordCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""set"""); Assert.Equal(2, recordCount); }); } [Fact] [CleanDatabase] public void AddToSet_DoesNotAddARecord_WhenBothKeyAndValueAreExist() { UseConnection(connection => { Commit(connection, x => { x.AddToSet("my-key", "my-value"); x.AddToSet("my-key", "my-value"); }); long recordCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""set"""); Assert.Equal(1, recordCount); }); } [Fact] [CleanDatabase] public void AddToSet_WithScore_AddsARecordWithScore_WhenBothKeyAndValueAreNotExist() { UseConnection(connection => { Commit(connection, x => x.AddToSet("my-key", "my-value", 3.2)); dynamic record = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""set"""); Assert.Equal("my-key", record.key); Assert.Equal("my-value", record.value); Assert.Equal(3.2, record.score, 3); }); } [Fact] [CleanDatabase] public void AddToSet_WithScore_UpdatesAScore_WhenBothKeyAndValueAreExist() { UseConnection(connection => { Commit(connection, x => { x.AddToSet("my-key", "my-value"); x.AddToSet("my-key", "my-value", 3.2); }); dynamic record = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""set"""); Assert.Equal(3.2, record.score, 3); }); } [SkippableFact] [CleanDatabase] public void AddToSet_DoesNotFailWithConcurrencyError_WhenRunningMultipleThreads() { if (Environment.ProcessorCount < 2) { throw new SkipException("You need to have more than 1 CPU to run the test"); } void CommitTags(PostgreSqlWriteOnlyTransaction transaction, IEnumerable tags, string jobId) { //Imitating concurrency issue scenario from Hangfire.Tags library. //Details: https://github.com/frankhommers/Hangfire.PostgreSql/issues/191 foreach (string tag in tags) { long score = DateTime.Now.Ticks; transaction.AddToSet("tags", tag, score); transaction.AddToSet($"tags:{jobId}", tag, score); transaction.AddToSet($"tags:{tag}", jobId, score); } } const int loopIterations = 1_000; const int jobGroups = 10; const int totalTagsCount = 2; Parallel.For(1, 1 + loopIterations, i => { UseDisposableConnection(sql => { CommitDisposable(sql, x => { int jobTypeIndex = i % jobGroups; CommitTags(x, new[] { "my-shared-tag", $"job-type-{jobTypeIndex}" }, i.ToString(CultureInfo.InvariantCulture)); }); }); }); UseConnection(connection => { int jobsCountUnderMySharedTag = connection.QuerySingle($@" SELECT COUNT(*) FROM ""{GetSchemaName()}"".set WHERE key LIKE 'tags:my-shared-tag'"); Assert.Equal(loopIterations, jobsCountUnderMySharedTag); int[] jobsCountsUnderJobTypeTags = connection.Query($@" SELECT COUNT(*) FROM ""{GetSchemaName()}"".set where key like 'tags:job-type-%' group by key;").ToArray(); Assert.All(jobsCountsUnderJobTypeTags, count => Assert.Equal(loopIterations / jobGroups, count)); int jobLinkTagsCount = connection.QuerySingle($@" SELECT COUNT(*) FROM ""{GetSchemaName()}"".set where value ~ '^\d+$' "); Assert.Equal(loopIterations * totalTagsCount, jobLinkTagsCount); }); } [Fact] [CleanDatabase] public void RemoveFromSet_RemovesARecord_WithGivenKeyAndValue() { UseConnection(connection => { Commit(connection, x => { x.AddToSet("my-key", "my-value"); x.RemoveFromSet("my-key", "my-value"); }); long recordCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""set"""); Assert.Equal(0, recordCount); }); } [Fact] [CleanDatabase] public void RemoveFromSet_DoesNotRemoveRecord_WithSameKey_AndDifferentValue() { UseConnection(connection => { Commit(connection, x => { x.AddToSet("my-key", "my-value"); x.RemoveFromSet("my-key", "different-value"); }); long recordCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""set"""); Assert.Equal(1, recordCount); }); } [Fact] [CleanDatabase] public void RemoveFromSet_DoesNotRemoveRecord_WithSameValue_AndDifferentKey() { UseConnection(connection => { Commit(connection, x => { x.AddToSet("my-key", "my-value"); x.RemoveFromSet("different-key", "my-value"); }); long recordCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""set"""); Assert.Equal(1, recordCount); }); } [Fact] [CleanDatabase] public void InsertToList_AddsARecord_WithGivenValues() { UseConnection(connection => { Commit(connection, x => x.InsertToList("my-key", "my-value")); dynamic record = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""list"""); Assert.Equal("my-key", record.key); Assert.Equal("my-value", record.value); }); } [Fact] [CleanDatabase] public void InsertToList_AddsAnotherRecord_WhenBothKeyAndValueAreExist() { UseConnection(connection => { Commit(connection, x => { x.InsertToList("my-key", "my-value"); x.InsertToList("my-key", "my-value"); }); long recordCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""list"""); Assert.Equal(2, recordCount); }); } [Fact] [CleanDatabase] public void RemoveFromList_RemovesAllRecords_WithGivenKeyAndValue() { UseConnection(connection => { Commit(connection, x => { x.InsertToList("my-key", "my-value"); x.InsertToList("my-key", "my-value"); x.RemoveFromList("my-key", "my-value"); }); long recordCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""list"""); Assert.Equal(0, recordCount); }); } [Fact] [CleanDatabase] public void RemoveFromList_DoesNotRemoveRecords_WithSameKey_ButDifferentValue() { UseConnection(connection => { Commit(connection, x => { x.InsertToList("my-key", "my-value"); x.RemoveFromList("my-key", "different-value"); }); long recordCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""list"""); Assert.Equal(1, recordCount); }); } [Fact] [CleanDatabase] public void RemoveFromList_DoesNotRemoveRecords_WithSameValue_ButDifferentKey() { UseConnection(connection => { Commit(connection, x => { x.InsertToList("my-key", "my-value"); x.RemoveFromList("different-key", "my-value"); }); long recordCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""list"""); Assert.Equal(1, recordCount); }); } [Fact] [CleanDatabase] public void TrimList_TrimsAList_ToASpecifiedRange() { UseConnection(connection => { Commit(connection, x => { x.InsertToList("my-key", "0"); x.InsertToList("my-key", "1"); x.InsertToList("my-key", "2"); x.InsertToList("my-key", "3"); x.TrimList("my-key", 1, 2); }); dynamic[] records = connection.Query($@"SELECT * FROM ""{GetSchemaName()}"".""list""").ToArray(); Assert.Equal(2, records.Length); Assert.Equal("1", records[0].value); Assert.Equal("2", records[1].value); }); } [Fact] [CleanDatabase] public void TrimList_RemovesRecordsToEnd_IfKeepAndingAt_GreaterThanMaxElementIndex() { UseConnection(connection => { Commit(connection, x => { x.InsertToList("my-key", "0"); x.InsertToList("my-key", "1"); x.InsertToList("my-key", "2"); x.TrimList("my-key", 1, 100); }); long recordCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""list"""); Assert.Equal(2, recordCount); }); } [Fact] [CleanDatabase] public void TrimList_RemovesAllRecords_WhenStartingFromValue_GreaterThanMaxElementIndex() { UseConnection(connection => { Commit(connection, x => { x.InsertToList("my-key", "0"); x.TrimList("my-key", 1, 100); }); long recordCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""list"""); Assert.Equal(0, recordCount); }); } [Fact] [CleanDatabase] public void TrimList_RemovesAllRecords_IfStartFromGreaterThanEndingAt() { UseConnection(connection => { Commit(connection, x => { x.InsertToList("my-key", "0"); x.TrimList("my-key", 1, 0); }); long recordCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""list"""); Assert.Equal(0, recordCount); }); } [Fact] [CleanDatabase] public void TrimList_RemovesRecords_OnlyOfAGivenKey() { UseConnection(connection => { Commit(connection, x => { x.InsertToList("my-key", "0"); x.TrimList("another-key", 1, 0); }); long recordCount = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""list"""); Assert.Equal(1, recordCount); }); } [Fact] [CleanDatabase] public void SetRangeInHash_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws( () => Commit(connection, x => x.SetRangeInHash(null, new Dictionary()))); Assert.Equal("key", exception.ParamName); }); } [Fact] [CleanDatabase] public void SetRangeInHash_ThrowsAnException_WhenKeyValuePairsArgumentIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => Commit(connection, x => x.SetRangeInHash("some-hash", null))); Assert.Equal("keyValuePairs", exception.ParamName); }); } [Fact] [CleanDatabase] public void SetRangeInHash_MergesAllRecords() { UseConnection(connection => { Commit(connection, x => x.SetRangeInHash("some-hash", new Dictionary { { "Key1", "Value1" }, { "Key2", "Value2" }, })); Dictionary result = connection.Query($@"SELECT * FROM ""{GetSchemaName()}"".""hash"" WHERE ""key"" = @Key", new { Key = "some-hash" }) .ToDictionary(x => (string)x.field, x => (string)x.value); Assert.Equal("Value1", result["Key1"]); Assert.Equal("Value2", result["Key2"]); }); } [Fact] [CleanDatabase] public void RemoveHash_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { Assert.Throws(() => Commit(connection, x => x.RemoveHash(null))); }); } [Fact] [CleanDatabase] public void RemoveHash_RemovesAllHashRecords() { UseConnection(connection => { // Arrange Commit(connection, x => x.SetRangeInHash("some-hash", new Dictionary { { "Key1", "Value1" }, { "Key2", "Value2" }, })); // Act Commit(connection, x => x.RemoveHash("some-hash")); // Assert long count = connection.QuerySingle($@"SELECT COUNT(*) FROM ""{GetSchemaName()}"".""hash"""); Assert.Equal(0, count); }); } [Fact] [CleanDatabase] public void AddRangeToSet_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => Commit(connection, x => x.AddRangeToSet(null, new List()))); Assert.Equal("key", exception.ParamName); }); } [Fact] [CleanDatabase] public void AddRangeToSet_ThrowsAnException_WhenItemsValueIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => Commit(connection, x => x.AddRangeToSet("my-set", null))); Assert.Equal("items", exception.ParamName); }); } [Fact] [CleanDatabase] public void AddRangeToSet_AddsAllItems_ToAGivenSet() { UseConnection(connection => { List items = new() { "1", "2", "3" }; Commit(connection, x => x.AddRangeToSet("my-set", items)); IEnumerable records = connection.Query($@"SELECT ""value"" FROM ""{GetSchemaName()}"".""set"" WHERE ""key"" = 'my-set'"); Assert.Equal(items, records); }); } [Fact] [CleanDatabase] public void RemoveSet_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { Assert.Throws(() => Commit(connection, x => x.RemoveSet(null))); }); } [Fact] [CleanDatabase] public void RemoveSet_RemovesASet_WithAGivenKey() { string arrangeSql = $@"INSERT INTO ""{GetSchemaName()}"".""set"" (""key"", ""value"", ""score"") VALUES (@Key, @Value, 0.0)"; UseConnection(connection => { connection.Execute(arrangeSql, new[] { new { Key = "set-1", Value = "1" }, new { Key = "set-2", Value = "1" }, }); Commit(connection, x => x.RemoveSet("set-1")); dynamic record = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""set"""); Assert.Equal("set-2", record.key); }); } [Fact] [CleanDatabase] public void ExpireHash_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => Commit(connection, x => x.ExpireHash(null, TimeSpan.FromMinutes(5))) ); Assert.Equal("key", exception.ParamName); }); } [Fact] [CleanDatabase] public void ExpireHash_SetsExpirationTimeOnAHash_WithGivenKey() { string arrangeSql = $@"INSERT INTO ""{GetSchemaName()}"".hash (""key"", ""field"") VALUES (@Key, @Field)"; UseConnection(connection => { // Arrange connection.Execute(arrangeSql, new[] { new { Key = "hash-1", Field = "field" }, new { Key = "hash-2", Field = "field" }, }); // Act Commit(connection, x => x.ExpireHash("hash-1", TimeSpan.FromMinutes(60))); // Assert Dictionary records = connection.Query($@"SELECT * FROM ""{GetSchemaName()}"".hash") .ToDictionary(x => (string)x.key, x => (DateTime?)x.expireat); Assert.True(DateTime.UtcNow.AddMinutes(59) < records["hash-1"]); Assert.True(records["hash-1"] < DateTime.UtcNow.AddMinutes(61)); Assert.Null(records["hash-2"]); }); } [Fact] [CleanDatabase] public void ExpireSet_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => Commit(connection, x => x.ExpireSet(null, TimeSpan.FromSeconds(45))) ); Assert.Equal("key", exception.ParamName); }); } [Fact] [CleanDatabase] public void ExpireSet_SetsExpirationTime_OnASet_WithGivenKey() { string arrangeSql = $@"INSERT INTO ""{GetSchemaName()}"".""set"" (""key"", ""value"", ""score"") VALUES (@Key, @Value, 0.0)"; UseConnection(connection => { // Arrange connection.Execute(arrangeSql, new[] { new { Key = "set-1", Value = "1" }, new { Key = "set-2", Value = "1" }, }); // Act Commit(connection, x => x.ExpireSet("set-1", TimeSpan.FromMinutes(60))); // Assert Dictionary records = connection.Query($@"SELECT * FROM ""{GetSchemaName()}"".""set""") .ToDictionary(x => (string)x.key, x => (DateTime?)x.expireat); Assert.True(DateTime.UtcNow.AddMinutes(59) < records["set-1"]); Assert.True(records["set-1"] < DateTime.UtcNow.AddMinutes(61)); Assert.Null(records["set-2"]); }); } [Fact] [CleanDatabase] public void ExpireList_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => Commit(connection, x => x.ExpireList(null, TimeSpan.FromSeconds(45))) ); Assert.Equal("key", exception.ParamName); }); } [Fact] [CleanDatabase] public void ExpireList_SetsExpirationTime_OnAList_WithGivenKey() { string arrangeSql = $@"INSERT INTO ""{GetSchemaName()}"".""list"" (""key"") VALUES (@Key)"; UseConnection(connection => { // Arrange connection.Execute(arrangeSql, new[] { new { Key = "list-1" }, new { Key = "list-2" }, }); // Act Commit(connection, x => x.ExpireList("list-1", TimeSpan.FromMinutes(60))); // Assert Dictionary records = connection.Query($@"SELECT * FROM ""{GetSchemaName()}"".""list""") .ToDictionary(x => (string)x.key, x => (DateTime?)x.expireat); Assert.True(DateTime.UtcNow.AddMinutes(59) < records["list-1"]); Assert.True(records["list-1"] < DateTime.UtcNow.AddMinutes(61)); Assert.Null(records["list-2"]); }); } [Fact] [CleanDatabase] public void PersistHash_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => Commit(connection, x => x.PersistHash(null))); Assert.Equal("key", exception.ParamName); }); } [Fact] [CleanDatabase] public void PersistHash_ClearsExpirationTime_OnAGivenHash() { string arrangeSql = $@"INSERT INTO ""{GetSchemaName()}"".hash (""key"", ""field"", ""expireat"") VALUES (@Key, @Field, @ExpireAt)"; UseConnection(connection => { // Arrange connection.Execute(arrangeSql, new[] { new { Key = "hash-1", Field = "field", ExpireAt = DateTime.UtcNow.AddDays(1) }, new { Key = "hash-2", Field = "field", ExpireAt = DateTime.UtcNow.AddDays(1) }, }); // Act Commit(connection, x => x.PersistHash("hash-1")); // Assert Dictionary records = connection.Query($@"SELECT * FROM ""{GetSchemaName()}"".hash") .ToDictionary(x => (string)x.key, x => (DateTime?)x.expireat); Assert.Null(records["hash-1"]); Assert.NotNull(records["hash-2"]); }); } [Fact] [CleanDatabase] public void PersistSet_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => Commit(connection, x => x.PersistSet(null))); Assert.Equal("key", exception.ParamName); }); } [Fact] [CleanDatabase] public void PersistSet_ClearsExpirationTime_OnAGivenHash() { string arrangeSql = $@"INSERT INTO ""{GetSchemaName()}"".""set"" (""key"", ""value"", ""expireat"", ""score"") VALUES (@Key, @Value, @ExpireAt, 0.0)"; UseConnection(connection => { // Arrange connection.Execute(arrangeSql, new[] { new { Key = "set-1", Value = "1", ExpireAt = DateTime.UtcNow.AddDays(1) }, new { Key = "set-2", Value = "1", ExpireAt = DateTime.UtcNow.AddDays(1) }, }); // Act Commit(connection, x => x.PersistSet("set-1")); // Assert Dictionary records = connection.Query($@"SELECT * FROM ""{GetSchemaName()}"".""set""") .ToDictionary(x => (string)x.key, x => (DateTime?)x.expireat); Assert.Null(records["set-1"]); Assert.NotNull(records["set-2"]); }); } [Fact] [CleanDatabase] public void PersistList_ThrowsAnException_WhenKeyIsNull() { UseConnection(connection => { ArgumentNullException exception = Assert.Throws(() => Commit(connection, x => x.PersistList(null))); Assert.Equal("key", exception.ParamName); }); } [Fact] [CleanDatabase] public void PersistList_ClearsExpirationTime_OnAGivenHash() { string arrangeSql = $@"INSERT INTO ""{GetSchemaName()}"".""list"" (""key"", ""expireat"") VALUES (@Key, @ExpireAt)"; UseConnection(connection => { // Arrange connection.Execute(arrangeSql, new[] { new { Key = "list-1", ExpireAt = DateTime.UtcNow.AddDays(1) }, new { Key = "list-2", ExpireAt = DateTime.UtcNow.AddDays(1) }, }); // Act Commit(connection, x => x.PersistList("list-1")); // Assert Dictionary records = connection.Query($@"SELECT * FROM ""{GetSchemaName()}"".""list""") .ToDictionary(x => (string)x.key, x => (DateTime?)x.expireat); Assert.Null(records["list-1"]); Assert.NotNull(records["list-2"]); }); } [Fact] [CleanDatabase] public void AddToQueue_AddsAJobToTheQueue_UsingStorageConnection_WithTransactionScopeEnlistment() { string jobId; using (TransactionScope transactionScope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }, TransactionScopeAsyncFlowOption.Enabled)) { // Need to run a query within that transaction. If PostgreSqlStorage modifies the connection string, TransactionAbortedExceptions appear // because of prepared transactions. string connectionString = ConnectionUtils.GetConnectionString(); using (NpgsqlConnection connection = new NpgsqlConnection(connectionString)) { dynamic _ = connection.QueryFirstOrDefault($@"SELECT * FROM ""{GetSchemaName()}"".""jobqueue"""); } PostgreSqlStorageOptions options = new() { EnableTransactionScopeEnlistment = true }; PostgreSqlStorage storage = new(new NpgsqlConnectionFactory(connectionString, options), options); using (IStorageConnection storageConnection = storage.GetConnection()) { using (IWriteOnlyTransaction writeTransaction = storageConnection.CreateWriteTransaction()) { // Explicitly call multiple write commands here, as AddToQueue previously opened an own connection. // This triggered a prepared transaction which should be avoided. jobId = storageConnection.CreateExpiredJob(Job.FromExpression(() => Console.Write("Hi")), new Dictionary(), DateTime.UtcNow, TimeSpan.FromMinutes(1)); writeTransaction.SetJobState(jobId, new ScheduledState(DateTime.UtcNow)); writeTransaction.AddToQueue("default", jobId); writeTransaction.PersistJob(jobId); writeTransaction.Commit(); } } transactionScope.Complete(); } UseConnection(connection => { dynamic record = connection.QuerySingle($@"SELECT * FROM ""{GetSchemaName()}"".""jobqueue"""); Assert.Equal(jobId, record.jobid.ToString()); Assert.Equal("default", record.queue); Assert.Null(record.FetchedAt); }); } private void UseConnection(Action action) { PostgreSqlStorage storage = _fixture.SafeInit(); action(storage.CreateAndOpenConnection()); } private static void UseDisposableConnection(Action action) { using (NpgsqlConnection sqlConnection = ConnectionUtils.CreateConnection()) { action(sqlConnection); } } private void Commit(NpgsqlConnection connection, Action action) { PostgreSqlStorage storage = _fixture.ForceInit(connection); using (IWriteOnlyTransaction transaction = storage.GetConnection().CreateWriteTransaction()) { action(transaction as PostgreSqlWriteOnlyTransaction); transaction.Commit(); } } private void CommitDisposable(NpgsqlConnection connection, Action action) { PostgreSqlStorageOptions options = new() { EnableTransactionScopeEnlistment = true, SchemaName = GetSchemaName(), }; PostgreSqlStorage storage = new(new ExistingNpgsqlConnectionFactory(connection, options), options); using (IWriteOnlyTransaction transaction = storage.GetConnection().CreateWriteTransaction()) { action(transaction as PostgreSqlWriteOnlyTransaction); transaction.Commit(); } } private static string GetSchemaName() { return ConnectionUtils.GetSchemaName(); } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/Scripts/Clean.sql ================================================ SET search_path = 'hangfire'; DELETE FROM hangfire."aggregatedcounter"; DELETE FROM hangfire."counter"; DELETE FROM hangfire."hash"; DELETE FROM hangfire."job"; DELETE FROM hangfire."jobparameter"; DELETE FROM hangfire."jobqueue"; DELETE FROM hangfire."list"; DELETE FROM hangfire."lock"; DELETE FROM hangfire."server"; DELETE FROM hangfire."set"; DELETE FROM hangfire."state"; ================================================ FILE: tests/Hangfire.PostgreSql.Tests/Utils/CleanDatabaseAttribute.cs ================================================ using System.Data; using System.Reflection; using System.Threading; using Dapper; using Npgsql; using Xunit.Sdk; namespace Hangfire.PostgreSql.Tests.Utils { public class CleanDatabaseAttribute : BeforeAfterTestAttribute { private static readonly object _globalLock = new(); private static bool _sqlObjectInstalled; public override void Before(MethodInfo methodUnderTest) { Monitor.Enter(_globalLock); if (!_sqlObjectInstalled) { RecreateSchemaAndInstallObjects(); _sqlObjectInstalled = true; } CleanTables(); } public override void After(MethodInfo methodUnderTest) { try { } finally { Monitor.Exit(_globalLock); } } private static void RecreateSchemaAndInstallObjects() { using NpgsqlConnection masterConnection = ConnectionUtils.CreateMasterConnection(); bool databaseExists = masterConnection.QuerySingleOrDefault($@"SELECT true :: boolean FROM pg_database WHERE datname = @DatabaseName;", new { DatabaseName = ConnectionUtils.GetDatabaseName(), }) ?? false; if (!databaseExists) { masterConnection.Execute($@"CREATE DATABASE ""{ConnectionUtils.GetDatabaseName()}"""); } using NpgsqlConnection connection = ConnectionUtils.CreateConnection(); if (connection.State == ConnectionState.Closed) { connection.Open(); } PostgreSqlObjectsInstaller.Install(connection); PostgreSqlTestObjectsInitializer.CleanTables(connection); } private static void CleanTables() { using NpgsqlConnection connection = ConnectionUtils.CreateConnection(); PostgreSqlTestObjectsInitializer.CleanTables(connection); } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/Utils/ConnectionUtils.cs ================================================ using System; using System.Globalization; using Hangfire.Annotations; using Hangfire.PostgreSql.Factories; using Npgsql; namespace Hangfire.PostgreSql.Tests.Utils { public static class ConnectionUtils { private const string DatabaseVariable = "Hangfire_PostgreSql_DatabaseName"; private const string SchemaVariable = "Hangfire_PostgreSql_SchemaName"; private const string ConnectionStringTemplateVariable = "Hangfire_PostgreSql_ConnectionStringTemplate"; private const string MasterDatabaseName = "postgres"; private const string DefaultDatabaseName = @"hangfire_tests"; private const string DefaultSchemaName = @"hangfire"; private const string DefaultConnectionStringTemplate = @"Server=127.0.0.1;Port=5432;Database=postgres;User Id=postgres;Password=password;Include Error Detail=true"; public static string GetDatabaseName() { return Environment.GetEnvironmentVariable(DatabaseVariable) ?? DefaultDatabaseName; } public static string GetSchemaName() { return Environment.GetEnvironmentVariable(SchemaVariable) ?? DefaultSchemaName; } public static string GetMasterConnectionString() { return string.Format(CultureInfo.InvariantCulture, GetConnectionStringTemplate(), MasterDatabaseName); } public static string GetConnectionString() { return string.Format(CultureInfo.InvariantCulture, GetConnectionStringTemplate(), GetDatabaseName()); } public static NpgsqlConnectionFactory GetDefaultConnectionFactory([CanBeNull] PostgreSqlStorageOptions options = null) { return new NpgsqlConnectionFactory(GetConnectionString(), options ?? new PostgreSqlStorageOptions()); } private static string GetConnectionStringTemplate() { return Environment.GetEnvironmentVariable(ConnectionStringTemplateVariable) ?? DefaultConnectionStringTemplate; } public static NpgsqlConnection CreateConnection() { NpgsqlConnectionStringBuilder csb = new(GetConnectionString()); NpgsqlConnection connection = new() { ConnectionString = csb.ToString(), }; connection.Open(); return connection; } public static NpgsqlConnection CreateMasterConnection() { NpgsqlConnectionStringBuilder csb = new(GetMasterConnectionString()); NpgsqlConnection connection = new() { ConnectionString = csb.ToString(), }; connection.Open(); return connection; } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/Utils/DefaultConnectionFactory.cs ================================================ using Npgsql; namespace Hangfire.PostgreSql.Tests.Utils { public class DefaultConnectionFactory : IConnectionFactory { /// /// Get or create NpgsqlConnection /// public NpgsqlConnection GetOrCreateConnection() { return ConnectionUtils.CreateConnection(); } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/Utils/DelegateConnectionFactory.cs ================================================ using System; using Npgsql; namespace Hangfire.PostgreSql.Tests.Utils { /// /// Simple test-only connection factory that delegates connection creation to the provided function. /// internal sealed class DelegateConnectionFactory : IConnectionFactory { private readonly Func _factory; public DelegateConnectionFactory(Func factory) { _factory = factory ?? throw new ArgumentNullException(nameof(factory)); } public NpgsqlConnection GetOrCreateConnection() { return _factory(); } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/Utils/Helper.cs ================================================ using System; using System.Data; using System.Globalization; using Dapper; using Hangfire.PostgreSql.Tests.Entities; namespace Hangfire.PostgreSql.Tests.Utils { public static class Helper { public static TestJob GetTestJob(IDbConnection connection, string schemaName, string jobId) { return connection .QuerySingle($@"SELECT ""id"" ""Id"", ""invocationdata"" ""InvocationData"", ""arguments"" ""Arguments"", ""expireat"" ""ExpireAt"", ""statename"" ""StateName"", ""stateid"" ""StateId"", ""createdat"" ""CreatedAt"" FROM ""{schemaName}"".""job"" WHERE ""id"" = @Id OR @Id = -1", new { Id = Convert.ToInt64(jobId, CultureInfo.InvariantCulture) }); } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/Utils/PostgreSqlStorageExtensions.cs ================================================ namespace Hangfire.PostgreSql.Tests.Utils { internal static class PostgreSqlStorageExtensions { public static PostgreSqlConnection GetStorageConnection(this PostgreSqlStorage storage) { return storage.GetConnection() as PostgreSqlConnection; } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/Utils/PostgreSqlStorageFixture.cs ================================================ using System; using Hangfire.PostgreSql.Factories; using Moq; using Npgsql; namespace Hangfire.PostgreSql.Tests.Utils { public class PostgreSqlStorageFixture : IDisposable { private readonly PostgreSqlStorageOptions _storageOptions; private bool _initialized; private NpgsqlConnection _mainConnection; public PostgreSqlStorageFixture() { PersistentJobQueueMock = new Mock(); Mock provider = new(); provider.Setup(x => x.GetJobQueue()) .Returns(PersistentJobQueueMock.Object); PersistentJobQueueProviderCollection = new PersistentJobQueueProviderCollection(provider.Object); _storageOptions = new PostgreSqlStorageOptions { SchemaName = ConnectionUtils.GetSchemaName(), EnableTransactionScopeEnlistment = true, }; } public Mock PersistentJobQueueMock { get; } public PersistentJobQueueProviderCollection PersistentJobQueueProviderCollection { get; } public PostgreSqlStorage Storage { get; private set; } public NpgsqlConnection MainConnection => _mainConnection ?? (_mainConnection = ConnectionUtils.CreateConnection()); public void Dispose() { _mainConnection?.Dispose(); _mainConnection = null; } public void SetupOptions(Action storageOptionsConfigure) { storageOptionsConfigure(_storageOptions); } public PostgreSqlStorage SafeInit(NpgsqlConnection connection = null) { return _initialized ? Storage : ForceInit(connection); } public PostgreSqlStorage ForceInit(NpgsqlConnection connection = null) { Storage = new PostgreSqlStorage(new ExistingNpgsqlConnectionFactory(connection ?? MainConnection, _storageOptions), _storageOptions) { QueueProviders = PersistentJobQueueProviderCollection, }; _initialized = true; return Storage; } public void SafeInit(PostgreSqlStorageOptions options, PersistentJobQueueProviderCollection jobQueueProviderCollection = null, NpgsqlConnection connection = null) { if (!_initialized) { ForceInit(options, jobQueueProviderCollection, connection); return; } Storage.QueueProviders = jobQueueProviderCollection; } public void ForceInit(PostgreSqlStorageOptions options, PersistentJobQueueProviderCollection jobQueueProviderCollection = null, NpgsqlConnection connection = null) { Storage = new PostgreSqlStorage(new ExistingNpgsqlConnectionFactory(connection ?? MainConnection, options), options) { QueueProviders = jobQueueProviderCollection, }; _initialized = true; } } } ================================================ FILE: tests/Hangfire.PostgreSql.Tests/Utils/PostgreSqlTestObjectsInitializer.cs ================================================ // This file is part of Hangfire.PostgreSql. // Copyright © 2014 Frank Hommers . // // Hangfire.PostgreSql is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire.PostgreSql is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire.PostgreSql. If not, see . // // This work is based on the work of Sergey Odinokov, author of // Hangfire. // // Special thanks goes to him. using System; using System.Data; using System.IO; using System.Reflection; using Npgsql; namespace Hangfire.PostgreSql.Tests.Utils { internal static class PostgreSqlTestObjectsInitializer { public static void CleanTables(NpgsqlConnection connection) { if (connection == null) throw new ArgumentNullException(nameof(connection)); string script = GetStringResource(typeof(PostgreSqlTestObjectsInitializer).GetTypeInfo().Assembly, "Hangfire.PostgreSql.Tests.Scripts.Clean.sql").Replace("'hangfire'", $"'{ConnectionUtils.GetSchemaName()}'"); using NpgsqlTransaction transaction = connection.BeginTransaction(IsolationLevel.Serializable); using NpgsqlCommand command = new(script, connection, transaction); command.CommandTimeout = 120; command.ExecuteNonQuery(); transaction.Commit(); } private static string GetStringResource(Assembly assembly, string resourceName) { using Stream stream = assembly.GetManifestResourceStream(resourceName); if (stream == null) { throw new InvalidOperationException($"Requested resource '{resourceName}' was not found in the assembly '{assembly}'."); } using StreamReader reader = new(stream); return reader.ReadToEnd(); } } }