diff --git a/.EditorConfig b/.EditorConfig deleted file mode 100644 index 61ab3ee2c..000000000 --- a/.EditorConfig +++ /dev/null @@ -1,61 +0,0 @@ -root = true -# All files -[*.*] -indent_style = space -indent_size = 4 -insert_final_newline = true -trim_trailing_whitespace = true -charset = utf-8 -max_line_length=150 - -# Interfaces should start with I and PascalCase -dotnet_naming_rule.interfaces_begin_with_I.severity = warning -dotnet_naming_rule.interfaces_begin_with_I.symbols = interfaces -dotnet_naming_rule.interfaces_begin_with_I.style = prefix_and_pascal_case -dotnet_naming_rule.interfaces_begin_with_I.required_prefix = I -dotnet_naming_symbols.interfaces.applicable_kinds = interface -dotnet_diagnostic.interfaces_begin_with_I.severity = warning -dotnet_diagnostic.interfaces_begin_with_I.enabled = true - -# Static fields should start with _s -dotnet_naming_rule.static_fields_begin_with_s.severity = warning -dotnet_naming_rule.static_fields_begin_with_s.symbols = static_fields -dotnet_naming_rule.static_fields_begin_with_s.style = custom -dotnet_naming_rule.static_fields_begin_with_s.custom_recommended_prefix = _r -dotnet_naming_rule.static_fields_begin_with_s.required_prefix = _r -dotnet_naming_rule.static_fields_begin_with_s.capitalization = camel_case -dotnet_naming_symbols.static_fields.applicable_kinds = field -dotnet_naming_symbols.static_fields.applicable_accessibilities = public, internal, private, protected, protected_internal -dotnet_naming_symbols.static_fields.required_modifiers = static -dotnet_diagnostic.static_fields_begin_with_s.severity = warning -dotnet_diagnostic.static_fields_begin_with_s.enabled = true - -# Enforce use of Pascal case in enums, classes, const and methods -dotnet_naming_rule.enforce_pascal_case.severity = suggestion -dotnet_naming_rule.enforce_pascal_case.symbols = methods, enums, consts, public_methods, public_classes -dotnet_naming_rule.enforce_pascal_case.style = pascal_case -dotnet_naming_symbols.methods.applicable_kinds = method -dotnet_naming_symbols.enums.applicable_kinds = enum -dotnet_naming_symbols.consts.applicable_kinds = field -dotnet_naming_symbols.consts.applicable_modifiers = const -dotnet_naming_symbols.public_methods.applicable_kinds = method -dotnet_naming_symbols.public_methods.applicable_accessibilities = public -dotnet_naming_symbols.public_classes.applicable_kinds = class -dotnet_naming_symbols.public_classes.applicable_accessibilities = public -dotnet_diagnostic.enforce_pascal_case.severity = suggestion -dotnet_diagnostic.enforce_pascal_case.enabled = true - -# private and internal members should start with underscore -dotnet_naming_rule.private_and_internal_members_start_with_underscore.severity = warning -dotnet_naming_rule.private_and_internal_members_start_with_underscore.symbols = private_fields, internal_fields, private_properties, internal_properties, private_methods, internal_methods -dotnet_naming_rule.private_and_internal_members_start_with_underscore.style = underscore_prefix -dotnet_naming_symbols.private_fields.applicable_kinds = field -dotnet_naming_symbols.internal_fields.applicable_kinds = field -dotnet_naming_symbols.private_properties.applicable_kinds = property -dotnet_naming_symbols.internal_properties.applicable_kinds = property -dotnet_naming_symbols.private_methods.applicable_kinds = method -dotnet_naming_symbols.internal_methods.applicable_kinds = method -dotnet_naming_symbols.private_methods.applicable_accessibilities = private -dotnet_naming_symbols.internal_methods.applicable_accessibilities = internal -dotnet_diagnostic.private_and_internal_members_start_with_underscore.severity = warning -dotnet_diagnostic.private_and_internal_members_start_with_underscore.enabled = true diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..d9d57561d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,73 @@ +root = true +# yml files +[*.yml] +indent_style = space +indent_size = 2 + +# All files +[*.*] +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 +max_line_length = 150 + +## Interfaces should start with I and PascalCase +dotnet_naming_style.prefix_and_pascal_case.required_prefix = I +dotnet_naming_style.prefix_and_pascal_case.capitalization = pascal_case +dotnet_naming_symbols.interfaces.applicable_kinds = interface +dotnet_naming_rule.interfaces_begin_with_I.severity = error +dotnet_naming_rule.interfaces_begin_with_I.symbols = interfaces +dotnet_naming_rule.interfaces_begin_with_I.style = prefix_and_pascal_case +dotnet_diagnostic.interfaces_begin_with_I.enabled = true + +## Static fields should start with _s +dotnet_naming_style.prefix_s.required_prefix = s_ +dotnet_naming_style.prefix_s.capitalization = camel_case +dotnet_naming_rule.static_fields_begin_with_s.style = prefix_s +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.applicable_accessibilities = public, internal, private, protected, protected_internal +dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_rule.static_fields_begin_with_s.severity = error +dotnet_naming_rule.static_fields_begin_with_s.symbols = static_fields +dotnet_diagnostic.static_fields_begin_with_s.enabled = true + +## Internal or private member should prefixed with _ +dotnet_naming_style.internal_prefix_.required_prefix = _ +dotnet_naming_style.internal_prefix_.capitalization = camel_case +dotnet_naming_rule.private_internal_prefix_.style = internal_prefix_ +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = internal, private, protected_internal +dotnet_naming_rule.private_internal_prefix_.severity = error +dotnet_naming_rule.private_internal_prefix_.symbols = private_internal_fields +dotnet_diagnostic.private_internal_prefix_.enabled = true + +# Enforce use of Pascal case in enums, classes, const and methods +dotnet_naming_style.pascal_case_style.capitalization = pascal_case +dotnet_naming_rule.enforce_pascal_case.style = pascal_case_style +dotnet_naming_symbols.methods.applicable_kinds = method +dotnet_naming_symbols.enums.applicable_kinds = enum +dotnet_naming_symbols.consts.applicable_kinds = field +dotnet_naming_symbols.consts.applicable_modifiers = const +dotnet_naming_symbols.public_methods.applicable_kinds = method +dotnet_naming_symbols.public_methods.applicable_accessibilities = public +dotnet_naming_symbols.public_classes.applicable_kinds = class +dotnet_naming_symbols.public_classes.applicable_accessibilities = public +dotnet_naming_symbols.enum_members.applicable_kinds = enum_member +dotnet_naming_symbols.enum_members.applicable_accessibilities = * +dotnet_naming_rule.enforce_pascal_case.severity = error +dotnet_naming_rule.enforce_pascal_case.symbols = methods, enums, consts, public_methods, public_classes, enum_members +dotnet_diagnostic.enforce_pascal_case.enabled = true + + +# Naming styles for different symbol kinds +dotnet_naming_style.camel_case_style.capitalization = camel_case +dotnet_naming_rule.method_parameters_should_be_camel_case.symbols = method_parameters +dotnet_naming_rule.method_parameters_should_be_camel_case.style = camel_case_style +dotnet_naming_symbols.method_parameters.applicable_kinds = parameter +dotnet_naming_symbols.method_parameters.applicable_accessibilities = * +dotnet_naming_symbols.method_parameters.required_modifiers = * +dotnet_naming_rule.method_parameters_should_be_camel_case.severity = error +dotnet_naming_rule.enforce_pascal_case.symbols = method_parameters +dotnet_diagnostic.method_parameters_should_be_camel_case.enabled = true diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 1c00a29aa..b72f02ca9 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -13,6 +13,7 @@ on: tags: description: "Linter" required: false +permissions: { } concurrency: # older builds for the same pull request number or branch should be cancelled @@ -26,14 +27,20 @@ jobs: steps: - name: Check out Git repository uses: actions/checkout@v3 - - name: Set up .NET - uses: actions/setup-dotnet@v1 with: - dotnet-version: '8.0.x' - dotnet-quality: 'ga' - - name: Run linters - uses: wearerequired/lint-action@v2 + depth: 0 + + - name: Set up .NET + uses: actions/setup-dotnet@v4 with: - dotnet_format: true - continue_on_error: true - check_name: ${linter} run + dotnet-version: '6.0.103' + + - name: Install dotnet format + run: dotnet tool install -g dotnet-format + + - name: Add dotnet tools to PATH + run: echo "$HOME/.dotnet/tools" >> $GITHUB_PATH + + - name: Run Linter Bash Script + run: | + bash ci/scripts/linter.sh diff --git a/Snowflake.Data.Tests/UnitTests/SFDbCommandTest.cs b/Snowflake.Data.Tests/UnitTests/SFDbCommandTest.cs index e595002ad..a496dbbbd 100644 --- a/Snowflake.Data.Tests/UnitTests/SFDbCommandTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFDbCommandTest.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. */ @@ -13,12 +13,12 @@ namespace Snowflake.Data.Tests.UnitTests [TestFixture] class SFDbCommandTest { - SnowflakeDbCommand command; + SnowflakeDbCommand _command; [SetUp] public void BeforeTest() { - command = new SnowflakeDbCommand(); + _command = new SnowflakeDbCommand(); } [Test] @@ -29,18 +29,18 @@ public void TestCommandWithConnectionAndCommandText() string commandText = "select 1"; // Act - command = new SnowflakeDbCommand(conn, commandText); + _command = new SnowflakeDbCommand(conn, commandText); // Assert - Assert.AreEqual(conn, command.Connection); - Assert.AreEqual(commandText, command.CommandText); + Assert.AreEqual(conn, _command.Connection); + Assert.AreEqual(commandText, _command.CommandText); } [Test] public void TestCommandExecuteThrowsExceptionWhenCommandTextIsNotSet() { // Act - var thrown = Assert.Throws(() => command.ExecuteScalar()); + var thrown = Assert.Throws(() => _command.ExecuteScalar()); // Assert Assert.AreEqual(thrown.Message, "Unable to execute command due to command text not being set"); @@ -50,7 +50,7 @@ public void TestCommandExecuteThrowsExceptionWhenCommandTextIsNotSet() public void TestCommandExecuteAsyncThrowsExceptionWhenCommandTextIsNotSet() { // Arrange - Task commandTask = command.ExecuteScalarAsync(CancellationToken.None); + Task commandTask = _command.ExecuteScalarAsync(CancellationToken.None); // Act var thrown = Assert.Throws(() => commandTask.Wait()); @@ -62,7 +62,7 @@ public void TestCommandExecuteAsyncThrowsExceptionWhenCommandTextIsNotSet() [Test] public void TestCommandPrepareShouldNotThrowsException() { - Assert.DoesNotThrow(() => command.Prepare()); + Assert.DoesNotThrow(() => _command.Prepare()); } } } diff --git a/Snowflake.Data/Client/SnowflakeDbCommand.cs b/Snowflake.Data/Client/SnowflakeDbCommand.cs index b52d53643..b8e00f737 100755 --- a/Snowflake.Data/Client/SnowflakeDbCommand.cs +++ b/Snowflake.Data/Client/SnowflakeDbCommand.cs @@ -1,474 +1,455 @@ -/* - * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved. - */ - -using System; -using Snowflake.Data.Core; -using System.Data.Common; -using System.Data; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Snowflake.Data.Log; - -namespace Snowflake.Data.Client -{ - [System.ComponentModel.DesignerCategory("Code")] - public class SnowflakeDbCommand : DbCommand - { - private SnowflakeDbConnection connection; - - private SFStatement sfStatement; - - private SnowflakeDbParameterCollection parameterCollection; - - private SFLogger logger = SFLoggerFactory.GetLogger(); - - private readonly QueryResultsAwaiter _queryResultsAwaiter = QueryResultsAwaiter.Instance; - - public SnowflakeDbCommand() - { - logger.Debug("Constructing SnowflakeDbCommand class"); - // by default, no query timeout - this.CommandTimeout = 0; - parameterCollection = new SnowflakeDbParameterCollection(); - } - - public SnowflakeDbCommand(SnowflakeDbConnection connection) : this() - { - this.connection = connection; - } - - public SnowflakeDbCommand(SnowflakeDbConnection connection, string cmdText) : this(connection) - { - this.CommandText = cmdText; - } - - public override string CommandText - { - get; set; - } - - public override int CommandTimeout - { - get; set; - } - - public string QueryTag - { - get; set; - } - - public override CommandType CommandType - { - get - { - return CommandType.Text; - } - - set - { - if (value != CommandType.Text) - { - throw new SnowflakeDbException(SFError.UNSUPPORTED_FEATURE); - } - } - } - - public override bool DesignTimeVisible - { - get - { - return false; - } - - set - { - if (value) - { - throw new SnowflakeDbException(SFError.UNSUPPORTED_FEATURE); - } - } - } - - public override UpdateRowSource UpdatedRowSource - { - get => UpdateRowSource.None; - - set - { - if (value != UpdateRowSource.None) - { - throw new SnowflakeDbException(SFError.UNSUPPORTED_FEATURE); - } - } - } - - protected override DbConnection DbConnection - { - get => connection; - - set - { - if (value == null) - { - if (connection == null) - { - return; - } - - // Unsetting connection not supported. - throw new SnowflakeDbException(SFError.UNSUPPORTED_FEATURE); - } - - if (!(value is SnowflakeDbConnection)) - { - // Must be of type SnowflakeDbConnection. - throw new SnowflakeDbException(SFError.UNSUPPORTED_FEATURE); - } - - var sfc = (SnowflakeDbConnection) value; - if (connection != null && connection != sfc) - { - // Connection already set. - throw new SnowflakeDbException(SFError.UNSUPPORTED_FEATURE); - } - - connection = sfc; - if (sfc.SfSession != null) - { - sfStatement = new SFStatement(sfc.SfSession, QueryTag); - } - } - } - - protected override DbParameterCollection DbParameterCollection - { - get - { - return this.parameterCollection; - } - } - - protected override DbTransaction DbTransaction - { - get; - - set; - } - - public override void Cancel() - { - // doesn't throw exception when sfStatement is null - sfStatement?.Cancel(); - } - - public override int ExecuteNonQuery() - { - logger.Debug($"ExecuteNonQuery"); - SFBaseResultSet resultSet = ExecuteInternal(); - long total = 0; - do - { - if (resultSet.HasResultSet()) continue; - int count = resultSet.CalculateUpdateCount(); - if (count < 0) - { - // exceeded max int, return -1 - return -1; - } - total += count; - if (total > int.MaxValue) - { - return -1; - } - } - while (resultSet.NextResult()); - - return (int)total; - } - - public override async Task ExecuteNonQueryAsync(CancellationToken cancellationToken) - { - logger.Debug($"ExecuteNonQueryAsync"); - cancellationToken.ThrowIfCancellationRequested(); - - var resultSet = await ExecuteInternalAsync(cancellationToken).ConfigureAwait(false); - long total = 0; - do - { - if (resultSet.HasResultSet()) continue; - int count = resultSet.CalculateUpdateCount(); - if (count < 0) - { - // exceeded max int, return -1 - return -1; - } - total += count; - if (total > int.MaxValue) - { - return -1; - } - } - while (await resultSet.NextResultAsync(cancellationToken).ConfigureAwait(false)); - - return (int)total; - } - - public override object ExecuteScalar() - { - logger.Debug($"ExecuteScalar"); - SFBaseResultSet resultSet = ExecuteInternal(); - - if(resultSet.Next()) - return resultSet.GetValue(0); - else - return DBNull.Value; - } - - public override async Task ExecuteScalarAsync(CancellationToken cancellationToken) - { - logger.Debug($"ExecuteScalarAsync"); - cancellationToken.ThrowIfCancellationRequested(); - - var result = await ExecuteInternalAsync(cancellationToken).ConfigureAwait(false); - - if(await result.NextAsync().ConfigureAwait(false)) - return result.GetValue(0); - else - return DBNull.Value; - } - - /// - /// Prepares the command for execution. - /// This method is currently not implemented and acts as a no-operation (Noop). - /// - public override void Prepare() - { - } - - public string GetQueryId() - { - if (sfStatement != null) - { - return sfStatement.GetQueryId(); - } - return null; - } - - protected override DbParameter CreateDbParameter() - { - return new SnowflakeDbParameter(); - } - - protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) - { - logger.Debug($"ExecuteDbDataReader"); - SFBaseResultSet resultSet = ExecuteInternal(); - return new SnowflakeDbDataReader(this, resultSet); - } - - protected override async Task ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) - { - logger.Debug($"ExecuteDbDataReaderAsync"); - try - { - var result = await ExecuteInternalAsync(cancellationToken).ConfigureAwait(false); - return new SnowflakeDbDataReader(this, result); - } - catch (Exception ex) - { - logger.Error("The command failed to execute.", ex); - throw; - } - } - - /// - /// Execute a query in async mode. - /// Async mode means the server will respond immediately with the query ID and execute the query asynchronously - /// - /// The query id. - public string ExecuteInAsyncMode() - { - logger.Debug($"ExecuteInAsyncMode"); - SFBaseResultSet resultSet = ExecuteInternal(asyncExec: true); - return resultSet.queryId; - } - - /// - /// Executes an asynchronous query in async mode. - /// Async mode means the server will respond immediately with the query ID and execute the query asynchronously - /// - /// - /// The query id. - public async Task ExecuteAsyncInAsyncMode(CancellationToken cancellationToken) - { - logger.Debug($"ExecuteAsyncInAsyncMode"); - var resultSet = await ExecuteInternalAsync(cancellationToken, asyncExec: true).ConfigureAwait(false); - return resultSet.queryId; - } - - /// - /// Gets the query status based on query ID. - /// - /// - /// The query status. - public QueryStatus GetQueryStatus(string queryId) - { - logger.Debug($"GetQueryStatus"); - return _queryResultsAwaiter.GetQueryStatus(connection, queryId); - } - - /// - /// Gets the query status based on query ID. - /// - /// - /// - /// The query status. - public async Task GetQueryStatusAsync(string queryId, CancellationToken cancellationToken) - { - logger.Debug($"GetQueryStatusAsync"); - return await _queryResultsAwaiter.GetQueryStatusAsync(connection, queryId, cancellationToken); - } - - /// - /// Gets the query results based on query ID. - /// - /// - /// The query results. - public DbDataReader GetResultsFromQueryId(string queryId) - { - logger.Debug($"GetResultsFromQueryId"); - - Task task = _queryResultsAwaiter.RetryUntilQueryResultIsAvailable(connection, queryId, CancellationToken.None, false); - task.Wait(); - - SFBaseResultSet resultSet = sfStatement.GetResultWithId(queryId); - - return new SnowflakeDbDataReader(this, resultSet); - } - - /// - /// Gets the query results based on query ID. - /// - /// - /// - /// The query results. - public async Task GetResultsFromQueryIdAsync(string queryId, CancellationToken cancellationToken) - { - logger.Debug($"GetResultsFromQueryIdAsync"); - - await _queryResultsAwaiter.RetryUntilQueryResultIsAvailable(connection, queryId, cancellationToken, true); - - SFBaseResultSet resultSet = await sfStatement.GetResultWithIdAsync(queryId, cancellationToken).ConfigureAwait(false); - - return new SnowflakeDbDataReader(this, resultSet); - } - - private static Dictionary convertToBindList(List parameters) - { - if (parameters == null || parameters.Count == 0) - { - return null; - } - else - { - Dictionary binding = new Dictionary(); - foreach(SnowflakeDbParameter parameter in parameters) - { - string bindingType = ""; - object bindingVal; - - if (parameter.Value.GetType().IsArray && - // byte array and char array will not be treated as array binding - parameter.Value.GetType().GetElementType() != typeof(char) && - parameter.Value.GetType().GetElementType() != typeof(byte)) - { - List vals = new List(); - foreach(object val in (Array)parameter.Value) - { - // if the user is using interface, SFDataType will be None and there will - // a conversion from DbType to SFDataType - // if the user is using concrete class, they should specify SFDataType. - if (parameter.SFDataType == SFDataType.None) - { - Tuple typeAndVal = SFDataConverter - .csharpTypeValToSfTypeVal(parameter.DbType, val); - - bindingType = typeAndVal.Item1; - vals.Add(typeAndVal.Item2); - } - else - { - bindingType = parameter.SFDataType.ToString(); - vals.Add(SFDataConverter.csharpValToSfVal(parameter.SFDataType, val)); - } - } - bindingVal = vals; - } - else - { - if (parameter.SFDataType == SFDataType.None) - { - Tuple typeAndVal = SFDataConverter - .csharpTypeValToSfTypeVal(parameter.DbType, parameter.Value); - bindingType = typeAndVal.Item1; - bindingVal = typeAndVal.Item2; - } - else - { - bindingType = parameter.SFDataType.ToString(); - bindingVal = SFDataConverter.csharpValToSfVal(parameter.SFDataType, parameter.Value); - } - } - - binding[parameter.ParameterName] = new BindingDTO(bindingType, bindingVal); - } - return binding; - } - } - - private void SetStatement() - { - if (connection == null) - { - throw new SnowflakeDbException(SFError.EXECUTE_COMMAND_ON_CLOSED_CONNECTION); - } - - var session = (connection as SnowflakeDbConnection).SfSession; - - // SetStatement is called when executing a command. If SfSession is null - // the connection has never been opened. Exception might be a bit vague. - if (session == null) - throw new SnowflakeDbException(SFError.EXECUTE_COMMAND_ON_CLOSED_CONNECTION); - - this.sfStatement = new SFStatement(session, QueryTag); - } - - private SFBaseResultSet ExecuteInternal(bool describeOnly = false, bool asyncExec = false) - { - CheckIfCommandTextIsSet(); - SetStatement(); - return sfStatement.Execute(CommandTimeout, CommandText, convertToBindList(parameterCollection.parameterList), describeOnly, asyncExec); - } - - private Task ExecuteInternalAsync(CancellationToken cancellationToken, bool describeOnly = false, bool asyncExec = false) - { - CheckIfCommandTextIsSet(); - SetStatement(); - return sfStatement.ExecuteAsync(CommandTimeout, CommandText, convertToBindList(parameterCollection.parameterList), describeOnly, asyncExec, cancellationToken); - } - - private void CheckIfCommandTextIsSet() - { - if (string.IsNullOrEmpty(CommandText)) - { - var errorMessage = "Unable to execute command due to command text not being set"; - logger.Error(errorMessage); - throw new Exception(errorMessage); - } - } - - internal string GetBindStage() => sfStatement?.GetBindStage(); - } -} +/* + * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved. + */ + +using System; +using Snowflake.Data.Core; +using System.Data.Common; +using System.Data; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Snowflake.Data.Log; + +namespace Snowflake.Data.Client +{ + [System.ComponentModel.DesignerCategory("Code")] + public class SnowflakeDbCommand : DbCommand + { + private SnowflakeDbConnection _connection; + + private SFStatement _sfStatement; + + private SnowflakeDbParameterCollection _parameterCollection; + + private SFLogger _logger = SFLoggerFactory.GetLogger(); + + private readonly QueryResultsAwaiter _queryResultsAwaiter = QueryResultsAwaiter.Instance; + + public SnowflakeDbCommand() + { + _logger.Debug("Constructing SnowflakeDbCommand class"); + // by default, no query timeout + this.CommandTimeout = 0; + _parameterCollection = new SnowflakeDbParameterCollection(); + } + + public SnowflakeDbCommand(SnowflakeDbConnection connection) : this() + { + this._connection = connection; + } + + public SnowflakeDbCommand(SnowflakeDbConnection connection, string cmdText) : this(connection) + { + this.CommandText = cmdText; + } + + public override string CommandText { get; set; } + + public override int CommandTimeout { get; set; } + + public string QueryTag { get; set; } + + public override CommandType CommandType + { + get { return CommandType.Text; } + + set + { + if (value != CommandType.Text) + { + throw new SnowflakeDbException(SFError.UNSUPPORTED_FEATURE); + } + } + } + + public override bool DesignTimeVisible + { + get { return false; } + + set + { + if (value) + { + throw new SnowflakeDbException(SFError.UNSUPPORTED_FEATURE); + } + } + } + + public override UpdateRowSource UpdatedRowSource + { + get => UpdateRowSource.None; + + set + { + if (value != UpdateRowSource.None) + { + throw new SnowflakeDbException(SFError.UNSUPPORTED_FEATURE); + } + } + } + + protected override DbConnection DbConnection + { + get => _connection; + + set + { + if (value == null) + { + if (_connection == null) + { + return; + } + + // Unsetting connection not supported. + throw new SnowflakeDbException(SFError.UNSUPPORTED_FEATURE); + } + + if (!(value is SnowflakeDbConnection)) + { + // Must be of type SnowflakeDbConnection. + throw new SnowflakeDbException(SFError.UNSUPPORTED_FEATURE); + } + + var sfc = (SnowflakeDbConnection)value; + if (_connection != null && _connection != sfc) + { + // Connection already set. + throw new SnowflakeDbException(SFError.UNSUPPORTED_FEATURE); + } + + _connection = sfc; + if (sfc.SfSession != null) + { + _sfStatement = new SFStatement(sfc.SfSession, QueryTag); + } + } + } + + protected override DbParameterCollection DbParameterCollection + { + get { return this._parameterCollection; } + } + + protected override DbTransaction DbTransaction { get; set; } + + public override void Cancel() + { + // doesn't throw exception when sfStatement is null + _sfStatement?.Cancel(); + } + + public override int ExecuteNonQuery() + { + _logger.Debug($"ExecuteNonQuery"); + SFBaseResultSet resultSet = ExecuteInternal(); + long total = 0; + do + { + if (resultSet.HasResultSet()) continue; + int count = resultSet.CalculateUpdateCount(); + if (count < 0) + { + // exceeded max int, return -1 + return -1; + } + + total += count; + if (total > int.MaxValue) + { + return -1; + } + } while (resultSet.NextResult()); + + return (int)total; + } + + public override async Task ExecuteNonQueryAsync(CancellationToken cancellationToken) + { + _logger.Debug($"ExecuteNonQueryAsync"); + cancellationToken.ThrowIfCancellationRequested(); + + var resultSet = await ExecuteInternalAsync(cancellationToken).ConfigureAwait(false); + long total = 0; + do + { + if (resultSet.HasResultSet()) continue; + int count = resultSet.CalculateUpdateCount(); + if (count < 0) + { + // exceeded max int, return -1 + return -1; + } + + total += count; + if (total > int.MaxValue) + { + return -1; + } + } while (await resultSet.NextResultAsync(cancellationToken).ConfigureAwait(false)); + + return (int)total; + } + + public override object ExecuteScalar() + { + _logger.Debug($"ExecuteScalar"); + SFBaseResultSet resultSet = ExecuteInternal(); + + if (resultSet.Next()) + return resultSet.GetValue(0); + else + return DBNull.Value; + } + + public override async Task ExecuteScalarAsync(CancellationToken cancellationToken) + { + _logger.Debug($"ExecuteScalarAsync"); + cancellationToken.ThrowIfCancellationRequested(); + + var result = await ExecuteInternalAsync(cancellationToken).ConfigureAwait(false); + + if (await result.NextAsync().ConfigureAwait(false)) + return result.GetValue(0); + else + return DBNull.Value; + } + + /// + /// Prepares the command for execution. + /// This method is currently not implemented and acts as a no-operation (Noop). + /// + public override void Prepare() + { + } + + public string GetQueryId() + { + if (_sfStatement != null) + { + return _sfStatement.GetQueryId(); + } + + return null; + } + + protected override DbParameter CreateDbParameter() + { + return new SnowflakeDbParameter(); + } + + protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) + { + _logger.Debug($"ExecuteDbDataReader"); + SFBaseResultSet resultSet = ExecuteInternal(); + return new SnowflakeDbDataReader(this, resultSet); + } + + protected override async Task ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) + { + _logger.Debug($"ExecuteDbDataReaderAsync"); + try + { + var result = await ExecuteInternalAsync(cancellationToken).ConfigureAwait(false); + return new SnowflakeDbDataReader(this, result); + } + catch (Exception ex) + { + _logger.Error("The command failed to execute.", ex); + throw; + } + } + + /// + /// Execute a query in async mode. + /// Async mode means the server will respond immediately with the query ID and execute the query asynchronously + /// + /// The query id. + public string ExecuteInAsyncMode() + { + _logger.Debug($"ExecuteInAsyncMode"); + SFBaseResultSet resultSet = ExecuteInternal(asyncExec: true); + return resultSet.queryId; + } + + /// + /// Executes an asynchronous query in async mode. + /// Async mode means the server will respond immediately with the query ID and execute the query asynchronously + /// + /// + /// The query id. + public async Task ExecuteAsyncInAsyncMode(CancellationToken cancellationToken) + { + _logger.Debug($"ExecuteAsyncInAsyncMode"); + var resultSet = await ExecuteInternalAsync(cancellationToken, asyncExec: true).ConfigureAwait(false); + return resultSet.queryId; + } + + /// + /// Gets the query status based on query ID. + /// + /// + /// The query status. + public QueryStatus GetQueryStatus(string queryId) + { + _logger.Debug($"GetQueryStatus"); + return _queryResultsAwaiter.GetQueryStatus(_connection, queryId); + } + + /// + /// Gets the query status based on query ID. + /// + /// + /// + /// The query status. + public async Task GetQueryStatusAsync(string queryId, CancellationToken cancellationToken) + { + _logger.Debug($"GetQueryStatusAsync"); + return await _queryResultsAwaiter.GetQueryStatusAsync(_connection, queryId, cancellationToken); + } + + /// + /// Gets the query results based on query ID. + /// + /// + /// The query results. + public DbDataReader GetResultsFromQueryId(string queryId) + { + _logger.Debug($"GetResultsFromQueryId"); + + Task task = _queryResultsAwaiter.RetryUntilQueryResultIsAvailable(_connection, queryId, CancellationToken.None, false); + task.Wait(); + + SFBaseResultSet resultSet = _sfStatement.GetResultWithId(queryId); + + return new SnowflakeDbDataReader(this, resultSet); + } + + /// + /// Gets the query results based on query ID. + /// + /// + /// + /// The query results. + public async Task GetResultsFromQueryIdAsync(string queryId, CancellationToken cancellationToken) + { + _logger.Debug($"GetResultsFromQueryIdAsync"); + + await _queryResultsAwaiter.RetryUntilQueryResultIsAvailable(_connection, queryId, cancellationToken, true); + + SFBaseResultSet resultSet = await _sfStatement.GetResultWithIdAsync(queryId, cancellationToken).ConfigureAwait(false); + + return new SnowflakeDbDataReader(this, resultSet); + } + + private static Dictionary _convertToBindList(List parameters) + { + if (parameters == null || parameters.Count == 0) + { + return null; + } + else + { + Dictionary binding = new Dictionary(); + foreach (SnowflakeDbParameter parameter in parameters) + { + string bindingType = ""; + object bindingVal; + + if (parameter.Value.GetType().IsArray && + // byte array and char array will not be treated as array binding + parameter.Value.GetType().GetElementType() != typeof(char) && + parameter.Value.GetType().GetElementType() != typeof(byte)) + { + List vals = new List(); + foreach (object val in (Array)parameter.Value) + { + // if the user is using interface, SFDataType will be None and there will + // a conversion from DbType to SFDataType + // if the user is using concrete class, they should specify SFDataType. + if (parameter.SFDataType == SFDataType.None) + { + Tuple typeAndVal = SFDataConverter + .csharpTypeValToSfTypeVal(parameter.DbType, val); + + bindingType = typeAndVal.Item1; + vals.Add(typeAndVal.Item2); + } + else + { + bindingType = parameter.SFDataType.ToString(); + vals.Add(SFDataConverter.csharpValToSfVal(parameter.SFDataType, val)); + } + } + + bindingVal = vals; + } + else + { + if (parameter.SFDataType == SFDataType.None) + { + Tuple typeAndVal = SFDataConverter + .csharpTypeValToSfTypeVal(parameter.DbType, parameter.Value); + bindingType = typeAndVal.Item1; + bindingVal = typeAndVal.Item2; + } + else + { + bindingType = parameter.SFDataType.ToString(); + bindingVal = SFDataConverter.csharpValToSfVal(parameter.SFDataType, parameter.Value); + } + } + + binding[parameter.ParameterName] = new BindingDTO(bindingType, bindingVal); + } + + return binding; + } + } + + private void SetStatement() + { + if (_connection == null) + { + throw new SnowflakeDbException(SFError.EXECUTE_COMMAND_ON_CLOSED_CONNECTION); + } + + var session = (_connection as SnowflakeDbConnection).SfSession; + + // SetStatement is called when executing a command. If SfSession is null + // the connection has never been opened. Exception might be a bit vague. + if (session == null) + throw new SnowflakeDbException(SFError.EXECUTE_COMMAND_ON_CLOSED_CONNECTION); + + this._sfStatement = new SFStatement(session, QueryTag); + } + + private SFBaseResultSet ExecuteInternal(bool describeOnly = false, bool asyncExec = false) + { + CheckIfCommandTextIsSet(); + SetStatement(); + return _sfStatement.Execute(CommandTimeout, CommandText, _convertToBindList(_parameterCollection.parameterList), describeOnly, asyncExec); + } + + private Task ExecuteInternalAsync(CancellationToken cancellationToken, bool describeOnly = false, bool asyncExec = false) + { + CheckIfCommandTextIsSet(); + SetStatement(); + return _sfStatement.ExecuteAsync(CommandTimeout, CommandText, _convertToBindList(_parameterCollection.parameterList), describeOnly, + asyncExec, cancellationToken); + } + + private void CheckIfCommandTextIsSet() + { + if (string.IsNullOrEmpty(CommandText)) + { + var errorMessage = "Unable to execute command due to command text not being set"; + _logger.Error(errorMessage); + throw new Exception(errorMessage); + } + } + + internal string GetBindStage() => _sfStatement?.GetBindStage(); + } +} diff --git a/Snowflake.Data/Core/Session/ISessionPoolEventHandler.cs b/Snowflake.Data/Core/Session/ISessionPoolEventHandler.cs index 2b16959a2..f8d78262a 100644 --- a/Snowflake.Data/Core/Session/ISessionPoolEventHandler.cs +++ b/Snowflake.Data/Core/Session/ISessionPoolEventHandler.cs @@ -5,7 +5,7 @@ internal interface ISessionPoolEventHandler void OnNewSessionCreated(SessionPool sessionPool); void OnWaitingForSessionStarted(SessionPool sessionPool); - + void OnWaitingForSessionStarted(SessionPool sessionPool, long millisLeft); void OnWaitingForSessionSuccessful(SessionPool sessionPool); diff --git a/Snowflake.Data/Core/Session/SFSessionProperty.cs b/Snowflake.Data/Core/Session/SFSessionProperty.cs index bfbe71a2a..d76f2c865 100644 --- a/Snowflake.Data/Core/Session/SFSessionProperty.cs +++ b/Snowflake.Data/Core/Session/SFSessionProperty.cs @@ -21,96 +21,142 @@ internal enum SFSessionProperty { [SFSessionPropertyAttr(required = true)] ACCOUNT, + [SFSessionPropertyAttr(required = false)] DB, + [SFSessionPropertyAttr(required = false)] HOST, + [SFSessionPropertyAttr(required = true, IsSecret = true)] PASSWORD, + [SFSessionPropertyAttr(required = false, defaultValue = "443")] PORT, + [SFSessionPropertyAttr(required = false)] ROLE, + [SFSessionPropertyAttr(required = false)] SCHEMA, + [SFSessionPropertyAttr(required = false, defaultValue = "https")] SCHEME, + [SFSessionPropertyAttr(required = true, defaultValue = "")] USER, + [SFSessionPropertyAttr(required = false)] WAREHOUSE, + [SFSessionPropertyAttr(required = false, defaultValue = "300")] CONNECTION_TIMEOUT, + [SFSessionPropertyAttr(required = false, defaultValue = "snowflake")] AUTHENTICATOR, + [SFSessionPropertyAttr(required = false, defaultValue = "true")] VALIDATE_DEFAULT_PARAMETERS, + [SFSessionPropertyAttr(required = false)] PRIVATE_KEY_FILE, + [SFSessionPropertyAttr(required = false, IsSecret = true)] PRIVATE_KEY_PWD, + [SFSessionPropertyAttr(required = false, IsSecret = true)] PRIVATE_KEY, + [SFSessionPropertyAttr(required = false, IsSecret = true)] TOKEN, + [SFSessionPropertyAttr(required = false, defaultValue = "false")] INSECUREMODE, + [SFSessionPropertyAttr(required = false, defaultValue = "false")] USEPROXY, + [SFSessionPropertyAttr(required = false)] PROXYHOST, + [SFSessionPropertyAttr(required = false)] PROXYPORT, + [SFSessionPropertyAttr(required = false)] PROXYUSER, + [SFSessionPropertyAttr(required = false, IsSecret = true)] PROXYPASSWORD, + [SFSessionPropertyAttr(required = false)] NONPROXYHOSTS, + [SFSessionPropertyAttr(required = false)] APPLICATION, + [SFSessionPropertyAttr(required = false, defaultValue = "false")] DISABLERETRY, + [SFSessionPropertyAttr(required = false, defaultValue = "false")] FORCERETRYON404, + [SFSessionPropertyAttr(required = false, defaultValue = "false")] CLIENT_SESSION_KEEP_ALIVE, + [SFSessionPropertyAttr(required = false)] GCS_USE_DOWNSCOPED_CREDENTIAL, + [SFSessionPropertyAttr(required = false, defaultValue = "false")] FORCEPARSEERROR, + [SFSessionPropertyAttr(required = false, defaultValue = "120")] BROWSER_RESPONSE_TIMEOUT, + [SFSessionPropertyAttr(required = false, defaultValue = "300")] RETRY_TIMEOUT, + [SFSessionPropertyAttr(required = false, defaultValue = "7")] MAXHTTPRETRIES, + [SFSessionPropertyAttr(required = false)] FILE_TRANSFER_MEMORY_THRESHOLD, + [SFSessionPropertyAttr(required = false, defaultValue = "true")] INCLUDERETRYREASON, + [SFSessionPropertyAttr(required = false, defaultValue = "false")] DISABLEQUERYCONTEXTCACHE, + [SFSessionPropertyAttr(required = false)] CLIENT_CONFIG_FILE, + [SFSessionPropertyAttr(required = false, defaultValue = "true")] DISABLE_CONSOLE_LOGIN, + [SFSessionPropertyAttr(required = false, defaultValue = "false")] ALLOWUNDERSCORESINHOST, + [SFSessionPropertyAttr(required = false)] QUERY_TAG, + [SFSessionPropertyAttr(required = false, defaultValue = "10")] MAXPOOLSIZE, + [SFSessionPropertyAttr(required = false, defaultValue = "2")] MINPOOLSIZE, + [SFSessionPropertyAttr(required = false, defaultValue = "Destroy")] CHANGEDSESSION, + [SFSessionPropertyAttr(required = false, defaultValue = "30s")] WAITINGFORIDLESESSIONTIMEOUT, + [SFSessionPropertyAttr(required = false, defaultValue = "60m")] EXPIRATIONTIMEOUT, + [SFSessionPropertyAttr(required = false, defaultValue = "true")] POOLINGENABLED, + [SFSessionPropertyAttr(required = false, defaultValue = "false")] DISABLE_SAML_URL_CHECK } @@ -126,7 +172,7 @@ class SFSessionPropertyAttr : Attribute class SFSessionProperties : Dictionary { - private static SFLogger logger = SFLoggerFactory.GetLogger(); + private static SFLogger s_logger = SFLoggerFactory.GetLogger(); internal string ConnectionStringWithoutSecrets { get; set; } @@ -158,20 +204,23 @@ public override bool Equals(object obj) { return false; } + if (!this.ContainsKey(sessionProperty)) { continue; } + if (!this[sessionProperty].Equals(prop[sessionProperty])) { return false; } } + return true; } catch (InvalidCastException) { - logger.Warn("Invalid casting to SFSessionProperties"); + s_logger.Warn("Invalid casting to SFSessionProperties"); return false; } } @@ -183,7 +232,7 @@ public override int GetHashCode() internal static SFSessionProperties ParseConnectionString(string connectionString, SecureString password) { - logger.Info("Start parsing connection string."); + s_logger.Info("Start parsing connection string."); var builder = new DbConnectionStringBuilder(); try { @@ -191,31 +240,32 @@ internal static SFSessionProperties ParseConnectionString(string connectionStrin } catch (ArgumentException e) { - logger.Warn("Invalid connectionString", e); + s_logger.Warn("Invalid connectionString", e); throw new SnowflakeDbException(e, - SFError.INVALID_CONNECTION_STRING, - e.Message); + SFError.INVALID_CONNECTION_STRING, + e.Message); } + var properties = new SFSessionProperties(); var keys = new string[builder.Keys.Count]; var values = new string[builder.Values.Count]; builder.Keys.CopyTo(keys, 0); - builder.Values.CopyTo(values,0); + builder.Values.CopyTo(values, 0); properties.ConnectionStringWithoutSecrets = BuildConnectionStringWithoutSecrets(ref keys, ref values); - for(var i=0; i private static void ValidateAuthenticator(SFSessionProperties properties) { - var knownAuthenticators = new[] { + var knownAuthenticators = new[] + { BasicAuthenticator.AUTH_NAME, OktaAuthenticator.AUTH_NAME, OAuthAuthenticator.AUTH_NAME, @@ -309,10 +362,11 @@ private static void ValidateAuthenticator(SFSessionProperties properties) if (properties.TryGetValue(SFSessionProperty.AUTHENTICATOR, out var authenticator)) { authenticator = authenticator.ToLower(); - if (!knownAuthenticators.Contains(authenticator) && !(authenticator.Contains(OktaAuthenticator.AUTH_NAME) && authenticator.StartsWith("https://"))) + if (!knownAuthenticators.Contains(authenticator) && + !(authenticator.Contains(OktaAuthenticator.AUTH_NAME) && authenticator.StartsWith("https://"))) { var error = $"Unknown authenticator: {authenticator}"; - logger.Error(error); + s_logger.Error(error); throw new SnowflakeDbException(SFError.UNKNOWN_AUTHENTICATOR, authenticator); } } @@ -325,7 +379,7 @@ private static string BuildConnectionStringWithoutSecrets(ref string[] keys, ref { var count = keys.Length; var result = new StringBuilder(); - for (var i = 0; i < count; i++ ) + for (var i = 0; i < count; i++) { if (!IsSecretProperty(keys[i])) { @@ -335,6 +389,7 @@ private static string BuildConnectionStringWithoutSecrets(ref string[] keys, ref result.Append(";"); } } + return result.ToString(); } @@ -346,9 +401,9 @@ private static bool IsSecretProperty(string propertyName) private static void UpdatePropertiesForSpecialCases(SFSessionProperties properties, string connectionString) { var propertyEntry = connectionString.Split(';'); - foreach(var keyVal in propertyEntry) + foreach (var keyVal in propertyEntry) { - if(keyVal.Length > 0) + if (keyVal.Length > 0) { var tokens = keyVal.Split(new string[] { "=" }, StringSplitOptions.None); var propertyName = tokens[0].ToUpper(); @@ -358,29 +413,28 @@ private static void UpdatePropertiesForSpecialCases(SFSessionProperties properti case "SCHEMA": case "WAREHOUSE": case "ROLE": - { - if (tokens.Length == 2) { - var sessionProperty = (SFSessionProperty)Enum.Parse( - typeof(SFSessionProperty), propertyName); - properties[sessionProperty]= ProcessObjectEscapedCharacters(tokens[1]); + if (tokens.Length == 2) + { + var sessionProperty = (SFSessionProperty)Enum.Parse( + typeof(SFSessionProperty), propertyName); + properties[sessionProperty] = ProcessObjectEscapedCharacters(tokens[1]); + } + + break; } - - break; - } case "USER": case "PASSWORD": - { - - var sessionProperty = (SFSessionProperty)Enum.Parse( - typeof(SFSessionProperty), propertyName); - if (!properties.ContainsKey(sessionProperty)) { - properties.Add(sessionProperty, ""); - } + var sessionProperty = (SFSessionProperty)Enum.Parse( + typeof(SFSessionProperty), propertyName); + if (!properties.ContainsKey(sessionProperty)) + { + properties.Add(sessionProperty, ""); + } - break; - } + break; + } } } } @@ -389,7 +443,7 @@ private static void UpdatePropertiesForSpecialCases(SFSessionProperties properti private static string ProcessObjectEscapedCharacters(string objectValue) { var match = Regex.Match(objectValue, "^\"(.*)\"$"); - if(match.Success) + if (match.Success) { var replaceEscapedQuotes = match.Groups[1].Value.Replace("\"\"", "\""); return $"\"{replaceEscapedQuotes}\""; @@ -405,7 +459,7 @@ private static void ValidateAccountDomain(SFSessionProperties properties) return; if (IsAccountRegexMatched(account)) return; - logger.Error($"Invalid account {account}"); + s_logger.Error($"Invalid account {account}"); throw new SnowflakeDbException( new Exception("Invalid account"), SFError.INVALID_CONNECTION_PARAMETER_VALUE, @@ -427,14 +481,14 @@ private static void CheckSessionProperties(SFSessionProperties properties) !properties.ContainsKey(sessionProperty)) { SnowflakeDbException e = new SnowflakeDbException(SFError.MISSING_CONNECTION_PROPERTY, sessionProperty); - logger.Error("Missing connection property", e); + s_logger.Error("Missing connection property", e); throw e; } if (IsRequired(sessionProperty, properties) && string.IsNullOrEmpty(properties[sessionProperty])) { SnowflakeDbException e = new SnowflakeDbException(SFError.MISSING_CONNECTION_PROPERTY, sessionProperty); - logger.Error("Empty connection property", e); + s_logger.Error("Empty connection property", e); throw e; } @@ -442,7 +496,7 @@ private static void CheckSessionProperties(SFSessionProperties properties) string defaultVal = sessionProperty.GetAttribute().defaultValue; if (defaultVal != null && !properties.ContainsKey(sessionProperty)) { - logger.Debug($"Session property {sessionProperty} set to default value: {defaultVal}"); + s_logger.Debug($"Session property {sessionProperty} set to default value: {defaultVal}"); properties.Add(sessionProperty, defaultVal); } } @@ -463,13 +517,13 @@ private static void ValidateFileTransferMaxBytesInMemoryProperty(SFSessionProper } catch (Exception e) { - logger.Error($"Value for parameter {propertyName} could not be parsed"); + s_logger.Error($"Value for parameter {propertyName} could not be parsed"); throw new SnowflakeDbException(e, SFError.INVALID_CONNECTION_PARAMETER_VALUE, maxBytesInMemoryString, propertyName); } if (maxBytesInMemory <= 0) { - logger.Error($"Value for parameter {propertyName} should be greater than 0"); + s_logger.Error($"Value for parameter {propertyName} should be greater than 0"); throw new SnowflakeDbException( new Exception($"Value for parameter {propertyName} should be greater than 0"), SFError.INVALID_CONNECTION_PARAMETER_VALUE, maxBytesInMemoryString, propertyName); @@ -496,7 +550,7 @@ private static bool IsRequired(SFSessionProperty sessionProperty, SFSessionPrope else if (sessionProperty.Equals(SFSessionProperty.USER)) { var authenticatorDefined = - properties.TryGetValue(SFSessionProperty.AUTHENTICATOR, out var authenticator); + properties.TryGetValue(SFSessionProperty.AUTHENTICATOR, out var authenticator); var authenticatorsWithoutUsername = new List() { @@ -529,7 +583,7 @@ private static bool ParseAllowUnderscoresInHost(SFSessionProperties properties) } catch (Exception e) { - logger.Warn("Unable to parse property 'allowUnderscoresInHost'", e); + s_logger.Warn("Unable to parse property 'allowUnderscoresInHost'", e); } return allowUnderscoresInHost; diff --git a/ci/scripts/linter.sh b/ci/scripts/linter.sh new file mode 100755 index 000000000..4ce73f1e0 --- /dev/null +++ b/ci/scripts/linter.sh @@ -0,0 +1,29 @@ +#!/bin/bash -e +# +# Apply Linter to changed files in PR +set -e +set -o pipefail + +current_branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} +git fetch --no-tags origin master:master +git fetch --no-tags origin $current_branch:$current_branch + +BASE_SHA=$(git merge-base $GITHUB_BASE_REF $current_branch) + +changed_files=$(git diff --name-only $BASE_SHA HEAD) + +echo "All files changed:" +for file in $changed_files; do + echo "$file" +done + +if [-z "$changed_files" ]; then + echo "no changed files detected" + exit 0 +fi + +echo "Run dotnet restore" +dotnet restore + +echo "Running Dotnet format to changed files" +dotnet format --include $changed_files --verify-no-changes --no-restore