You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by cu...@apache.org on 2024/03/22 18:23:16 UTC

(arrow-adbc) branch main updated: test(csharp/test/Drivers/Interop/Snowflake): test error handling (#1592)

This is an automated email from the ASF dual-hosted git repository.

curth pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git


The following commit(s) were added to refs/heads/main by this push:
     new ff866d9e2 test(csharp/test/Drivers/Interop/Snowflake): test error handling (#1592)
ff866d9e2 is described below

commit ff866d9e2a696accbff3de3157eff70b3509fa79
Author: Bruce Irschick <br...@improving.com>
AuthorDate: Fri Mar 22 11:23:11 2024 -0700

    test(csharp/test/Drivers/Interop/Snowflake): test error handling (#1592)
    
    Add tests to check for error handling of the following invalid input.
    - invalid SQL syntax
    - invalid column or table names
    - invalid connection information (user, password)
    - invalid driver information (location, entry point)
    - invalid database, warehouse
---
 csharp/test/Drivers/Interop/Snowflake/CastTests.cs |  11 +-
 .../Interop/Snowflake/ErrorHandlingTests.cs        | 284 +++++++++++++++++++++
 .../Interop/Snowflake/SnowflakeTestingUtils.cs     |  15 ++
 3 files changed, 300 insertions(+), 10 deletions(-)

diff --git a/csharp/test/Drivers/Interop/Snowflake/CastTests.cs b/csharp/test/Drivers/Interop/Snowflake/CastTests.cs
index aa4d3bada..867db8b7a 100644
--- a/csharp/test/Drivers/Interop/Snowflake/CastTests.cs
+++ b/csharp/test/Drivers/Interop/Snowflake/CastTests.cs
@@ -276,16 +276,7 @@ namespace Apache.Arrow.Adbc.Tests.Drivers.Interop.Snowflake
             string[] expectedExceptionTextContains = null)
         {
             Exception actualException = Assert.Throws(expectedExceptionType, () => PerformQuery(table, castFunction, castExpression));
-            AssertContainsAll(expectedExceptionTextContains, actualException.Message);
-        }
-
-        private static void AssertContainsAll(string[] expectedTexts, string actualText)
-        {
-            if (expectedTexts == null) { return; };
-            foreach (string text in expectedTexts)
-            {
-                Assert.Contains(text, actualText);
-            }
+            SnowflakeTestingUtils.AssertContainsAll(expectedExceptionTextContains, actualException.Message);
         }
 
         private QueryResult PerformQuery(string table, string castFunction, string castExpression)
diff --git a/csharp/test/Drivers/Interop/Snowflake/ErrorHandlingTests.cs b/csharp/test/Drivers/Interop/Snowflake/ErrorHandlingTests.cs
new file mode 100644
index 000000000..9d4a427c5
--- /dev/null
+++ b/csharp/test/Drivers/Interop/Snowflake/ErrorHandlingTests.cs
@@ -0,0 +1,284 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*    http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Apache.Arrow.Adbc.Tests.Drivers.Interop.Snowflake
+{
+    // TODO: When supported, use prepared statements instead of SQL string literals
+    //      Which will better test how the driver handles values sent/received
+
+    public class ErrorHandlingTests : IDisposable
+    {
+        private const string TestTablePrefix = "ADBCERRORSTEST_"; // Make configurable? Also; must be all caps if not double quoted
+        private const string DefaultColumnName = "COLUMN_NAME";
+        private const string DefaultColumnNameLower = "column_name";
+        readonly SnowflakeTestConfiguration _snowflakeTestConfiguration;
+        readonly AdbcConnection _connection;
+        readonly AdbcStatement _statement;
+        readonly string _catalogSchema;
+        private readonly ITestOutputHelper _output;
+        private bool _disposedValue = false;
+
+        /// <summary>
+        /// Validates that specific errors are generated as expected.
+        /// </summary>
+        public ErrorHandlingTests(ITestOutputHelper output)
+        {
+            Skip.IfNot(Utils.CanExecuteTestConfig(SnowflakeTestingUtils.SNOWFLAKE_TEST_CONFIG_VARIABLE));
+            _snowflakeTestConfiguration = SnowflakeTestingUtils.TestConfiguration;
+            Dictionary<string, string> options = new();
+            AdbcDriver snowflakeDriver = SnowflakeTestingUtils.GetSnowflakeAdbcDriver(_snowflakeTestConfiguration, out Dictionary<string, string> parameters);
+            AdbcDatabase adbcDatabase = snowflakeDriver.Open(parameters);
+            _connection = adbcDatabase.Connect(options);
+            _statement = _connection.CreateStatement();
+            _catalogSchema = string.Format("{0}.{1}", _snowflakeTestConfiguration.Metadata.Catalog, _snowflakeTestConfiguration.Metadata.Schema);
+            _output = output;
+        }
+
+        /// <summary>
+        /// Tests for invalid table and column names
+        /// </summary>
+        [SkippableTheory]
+        [InlineData("NUMERIC", "0", "INVALID_TABLE_NAME", null, new[] { "002003", "42S02" })]
+        [InlineData("NUMERIC", "0", null, "INVALID_" + DefaultColumnName, new[] { "000904", "42000" })]
+        [InlineData("NUMERIC", "0", null, "\"" + DefaultColumnNameLower + "\"", new[] { "000904", "42000" })]
+        public void TestInvalidObjectName(string columnSpecification, string sourceValue, string overrideTableName, string overrideColumnName, string[] expectedExceptionMessage = null)
+        {
+            InitializeTest(columnSpecification, sourceValue, out string columnName, out string tableName);
+            SelectAndValidateException(overrideTableName ?? tableName, overrideColumnName ?? columnName, typeof(AdbcException), expectedExceptionMessage);
+        }
+
+        /// <summary>
+        /// Tests for invalid syntax.
+        /// </summary>
+        [SkippableTheory]
+        [InlineData("NUMERIC", "0", null, DefaultColumnName + ",", new[] { "001003", "42000" })]
+        [InlineData("NUMERIC", "0", null, "," + DefaultColumnName, new[] { "001003", "42000" })]
+        [InlineData("NUMERIC", "0", null, "'" + DefaultColumnName, new[] { "001003", "42000" })]
+        [InlineData("NUMERIC", "0", null, DefaultColumnName + "'", new[] { "001003", "42000" })]
+        [InlineData("NUMERIC", "0", null, "\"" + DefaultColumnName, new[] { "001003", "42000" })]
+        [InlineData("NUMERIC", "0", null, DefaultColumnName + "\"", new[] { "001003", "42000" })]
+        public void TestInvalidSyntax(string columnSpecification, string sourceValue, string overrideTableName, string overrideColumnName, string[] expectedExceptionMessage = null)
+        {
+            InitializeTest(columnSpecification, sourceValue, out string columnName, out string tableName);
+            SelectAndValidateException(overrideTableName ?? tableName, overrideColumnName ?? columnName, typeof(AdbcException), expectedExceptionMessage);
+        }
+
+        /// <summary>
+        /// Tests for invalid driver path
+        /// </summary>
+        [SkippableFact]
+        public void TestDriverLoadInvalidPath()
+        {
+            Dictionary<string, string> parameters = new();
+            SnowflakeTestConfiguration testConfiguration = Utils.LoadTestConfiguration<SnowflakeTestConfiguration>(SnowflakeTestingUtils.SNOWFLAKE_TEST_CONFIG_VARIABLE);
+            string driverFilename = Guid.NewGuid().ToString();
+            testConfiguration.DriverPath = Path.Combine(Environment.CurrentDirectory, driverFilename + ".dll");
+            Exception actualException = Assert.Throws<ArgumentException>(() => SnowflakeTestingUtils.GetSnowflakeAdbcDriver(testConfiguration, out parameters));
+            SnowflakeTestingUtils.AssertContainsAll(new[] { "file does not exist (Parameter 'file')" }, actualException.Message);
+        }
+
+        /// <summary>
+        /// Tests for invalid driver entry point
+        /// </summary>
+        [SkippableFact]
+        public void TestDriverLoadInvalidEntryPoint()
+        {
+            Dictionary<string, string> parameters = new();
+            SnowflakeTestConfiguration testConfiguration = Utils.LoadTestConfiguration<SnowflakeTestConfiguration>(SnowflakeTestingUtils.SNOWFLAKE_TEST_CONFIG_VARIABLE);
+            string entryPoint = Guid.NewGuid().ToString();
+            testConfiguration.DriverEntryPoint = entryPoint;
+            Exception actualException = Assert.Throws<EntryPointNotFoundException>(() => SnowflakeTestingUtils.GetSnowflakeAdbcDriver(testConfiguration, out parameters));
+            SnowflakeTestingUtils.AssertContainsAll(new[] { "Unable to find an entry point named", entryPoint }, actualException.Message);
+        }
+
+        /// <summary>
+        /// Tests for various invalid connection properties.
+        /// </summary>
+        /// <param name="test">A test for an invalid configuration checking the exception type and keywords in the error message.</param>
+        [SkippableTheory]
+        [MemberData(nameof(GenerateTestConfigurationData))]
+        public void TestDriverConnectionInvalidConnection(SnowflakeTestConfigurationTest test)
+        {
+            Dictionary<string, string> options = new();
+            AdbcDriver snowflakeDriver = SnowflakeTestingUtils.GetSnowflakeAdbcDriver(
+                test.TestConfiguration as SnowflakeTestConfiguration,
+                out Dictionary<string, string> parameters);
+            AdbcDatabase adbcDatabase = snowflakeDriver.Open(parameters);
+            Exception actualException = Assert.Throws(test.ExceptionType, () => adbcDatabase.Connect(options));
+            SnowflakeTestingUtils.AssertContainsAll(test.ExceptionMessageComponents, actualException.Message);
+        }
+
+        /// <summary>
+        /// Generates test configuration for testing invalid properties in the connection.
+        /// </summary>
+        /// <returns>The list of tests.</returns>
+        public static IEnumerable<object[]> GenerateTestConfigurationData()
+        {
+            string property = Guid.NewGuid().ToString();
+            SnowflakeTestConfiguration testConfiguration;
+
+            // Note: Providing an invalid Host will hang the test.
+            //testConfiguration = Utils.LoadTestConfiguration<SnowflakeTestConfiguration>(SnowflakeTestingUtils.SNOWFLAKE_TEST_CONFIG_VARIABLE);
+            //testConfiguration.Host = property;
+            //yield return new object[] { new SnowflakeTestConfigurationTest(testConfiguration, typeof(AdbcException), ["Unknown"]) };
+
+            testConfiguration = Utils.LoadTestConfiguration<SnowflakeTestConfiguration>(SnowflakeTestingUtils.SNOWFLAKE_TEST_CONFIG_VARIABLE);
+            testConfiguration.Authentication.Default.User = property;
+            yield return new object[] { new SnowflakeTestConfigurationTest(testConfiguration, typeof(AdbcException), new[] { "[Snowflake] 390100 (08004)", "Incorrect username or password was specified" }) };
+
+            testConfiguration = Utils.LoadTestConfiguration<SnowflakeTestConfiguration>(SnowflakeTestingUtils.SNOWFLAKE_TEST_CONFIG_VARIABLE);
+            testConfiguration.Authentication.Default.Password = property;
+            yield return new object[] { new SnowflakeTestConfigurationTest(testConfiguration, typeof(AdbcException), new[] { "[Snowflake] 390100 (08004)", "Incorrect username or password was specified" }) };
+
+            testConfiguration = Utils.LoadTestConfiguration<SnowflakeTestConfiguration>(SnowflakeTestingUtils.SNOWFLAKE_TEST_CONFIG_VARIABLE);
+            testConfiguration.Database = property;
+            yield return new object[] { new SnowflakeTestConfigurationTest(testConfiguration, typeof(AdbcException), new[] { "[Snowflake] 390201 (08004)", "The requested database does not exist or not authorized." }) };
+
+            testConfiguration = Utils.LoadTestConfiguration<SnowflakeTestConfiguration>(SnowflakeTestingUtils.SNOWFLAKE_TEST_CONFIG_VARIABLE);
+            testConfiguration.Warehouse = property;
+            yield return new object[] { new SnowflakeTestConfigurationTest(testConfiguration, typeof(AdbcException), new[] { "[Snowflake] 390201 (08004)", "The requested warehouse does not exist or not authorized." }) };
+        }
+
+        /// <summary>
+        /// A test.
+        /// </summary>
+        public readonly struct SnowflakeTestConfigurationTest
+        {
+            public TestConfiguration TestConfiguration { get; }
+
+            public Type ExceptionType { get; }
+
+            public string[] ExceptionMessageComponents { get; }
+
+            /// <param name="testConfiguration">A test configuration.</param>
+            /// <param name="exceptionType">Expected exception type.</param>
+            /// <param name="exceptionMessageComponents">Expected keywords/phrases to be found in exception message.</param>
+            public SnowflakeTestConfigurationTest(
+                TestConfiguration testConfiguration,
+                Type exceptionType,
+                string[] exceptionMessageComponents)
+            {
+                TestConfiguration = testConfiguration;
+                ExceptionType = exceptionType;
+                ExceptionMessageComponents = exceptionMessageComponents;
+            }
+        }
+
+        private void SelectAndValidateException(
+            string table,
+            string projection,
+            Type expectedExceptionType,
+            string[] expectedExceptionTextContains = null)
+        {
+            Exception actualException = Assert.Throws(expectedExceptionType, () => PerformQuery(table, projection));
+            SnowflakeTestingUtils.AssertContainsAll(expectedExceptionTextContains, actualException.Message);
+        }
+
+        private QueryResult PerformQuery(string table, string projection)
+        {
+            string selectStatement = string.Format(
+                "SELECT {0} AS CASTRESULT FROM {1};",
+                projection,
+                table);
+            _output.WriteLine(selectStatement);
+            _statement.SqlQuery = selectStatement;
+            return _statement.ExecuteQuery();
+        }
+
+        private void InsertSingleValue(string table, string columnName, string value)
+        {
+            string insertNumberStatement = string.Format("INSERT INTO {0} ({1}) VALUES ({2});", table, columnName, value);
+            Console.WriteLine(insertNumberStatement);
+            _statement.SqlQuery = insertNumberStatement;
+            UpdateResult updateResult = _statement.ExecuteUpdate();
+            Assert.Equal(1, updateResult.AffectedRows);
+        }
+
+        private void InitializeTest(
+            string columnSpecification,
+            string sourceValue,
+            out string columnName,
+            out string tableName,
+            bool useSelectSyntax = false)
+        {
+            columnName = DefaultColumnName;
+            tableName = CreateTemporaryTable(
+                _statement,
+                TestTablePrefix,
+                _catalogSchema,
+                string.Format("{0} {1}", columnName, columnSpecification));
+            if (useSelectSyntax)
+            {
+                InsertIntoFromSelect(tableName, columnName, sourceValue);
+            }
+            else
+            {
+                InsertSingleValue(tableName, columnName, sourceValue);
+            }
+        }
+
+        private static string CreateTemporaryTable(
+            AdbcStatement statement,
+            string testTablePrefix,
+            string catalogSchema,
+            string columns)
+        {
+            string tableName = string.Format("{0}.{1}{2}", catalogSchema, testTablePrefix, Guid.NewGuid().ToString().Replace("-", ""));
+            string createTableStatement = string.Format("CREATE TEMPORARY TABLE {0} ({1})", tableName, columns);
+            statement.SqlQuery = createTableStatement;
+            statement.ExecuteUpdate();
+            return tableName;
+        }
+
+        private void InsertIntoFromSelect(string table, string columnName, string selectQuery, long expectedAffectedRows = 1)
+        {
+            string insertStatement = string.Format("INSERT INTO {0} ({1}) {2};", table, columnName, selectQuery);
+            _output.WriteLine(insertStatement);
+            _statement.SqlQuery = insertStatement;
+            UpdateResult updateResult = _statement.ExecuteUpdate();
+            Assert.Equal(expectedAffectedRows, updateResult.AffectedRows);
+        }
+
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+
+        // Protected implementation of Dispose pattern.
+        protected virtual void Dispose(bool disposing)
+        {
+            if (!_disposedValue)
+            {
+                if (disposing)
+                {
+
+                    _connection?.Dispose();
+                    _statement?.Dispose();
+                }
+
+                _disposedValue = true;
+            }
+        }
+    }
+}
diff --git a/csharp/test/Drivers/Interop/Snowflake/SnowflakeTestingUtils.cs b/csharp/test/Drivers/Interop/Snowflake/SnowflakeTestingUtils.cs
index 2f394a89a..3aa57345e 100644
--- a/csharp/test/Drivers/Interop/Snowflake/SnowflakeTestingUtils.cs
+++ b/csharp/test/Drivers/Interop/Snowflake/SnowflakeTestingUtils.cs
@@ -21,6 +21,7 @@ using System.IO;
 using System.Linq;
 using System.Text;
 using Apache.Arrow.Adbc.Drivers.Interop.Snowflake;
+using Xunit;
 
 namespace Apache.Arrow.Adbc.Tests.Drivers.Interop.Snowflake
 {
@@ -165,5 +166,19 @@ namespace Apache.Arrow.Adbc.Tests.Drivers.Interop.Snowflake
 
             return queries;
         }
+
+        /// <summary>
+        /// Assert that all expected texts in collection appear in the value.
+        /// </summary>
+        /// <param name="expectedTexts"></param>
+        /// <param name="value"></param>
+        internal static void AssertContainsAll(string[] expectedTexts, string value)
+        {
+            if (expectedTexts == null) { return; };
+            foreach (string text in expectedTexts)
+            {
+                Assert.Contains(text, value);
+            }
+        }
     }
 }