From 8c2feaae1a0a41c938dcd58a5792e4ff6903961d Mon Sep 17 00:00:00 2001 From: Harry Xi <44687000+sfc-gh-ext-simba-hx@users.noreply.github.com> Date: Thu, 12 Oct 2023 09:33:32 -0700 Subject: [PATCH 1/6] SNOW-933246: adding missed fix for HTAP parameter removal (#787) ### Description sdk issue 693 Some cases for HTAP metadata/parameter removal was missed in September release. Fix them and add test case as well. ### Checklist - [x] Code compiles correctly - [x] Code is formatted according to [Coding Conventions](../CodingConventions.md) - [x] Created tests which fail without the change (if possible) - [x] All tests passing (`dotnet test`) - [ ] Extended the README / documentation, if necessary - [x] Provide JIRA issue id (if possible) or GitHub issue id in PR name --- .../IntegrationTests/SFDbDataReaderIT.cs | 32 +++++++++++++++++++ .../UnitTests/SFSessionTest.cs | 6 ++++ Snowflake.Data/Core/ArrowResultSet.cs | 2 +- Snowflake.Data/Core/SFResultSet.cs | 2 +- Snowflake.Data/Core/SFResultSetMetaData.cs | 20 +++++------- Snowflake.Data/Core/Session/SFSession.cs | 19 +++++++++-- .../Core/Session/SFSessionParameter.cs | 2 ++ 7 files changed, 67 insertions(+), 16 deletions(-) diff --git a/Snowflake.Data.Tests/IntegrationTests/SFDbDataReaderIT.cs b/Snowflake.Data.Tests/IntegrationTests/SFDbDataReaderIT.cs index 158abc7f0..f2613acf6 100755 --- a/Snowflake.Data.Tests/IntegrationTests/SFDbDataReaderIT.cs +++ b/Snowflake.Data.Tests/IntegrationTests/SFDbDataReaderIT.cs @@ -174,6 +174,38 @@ public void TestGetDate(string inputTimeStr) testGetDateAndOrTime(inputTimeStr, null, SFDataType.DATE); } + [Test] + public void TestDateOutputFormat() + { + using (IDbConnection conn = new SnowflakeDbConnection()) + { + conn.ConnectionString = ConnectionString; + conn.Open(); + IDbCommand cmd = conn.CreateCommand(); + + try + { + cmd.CommandText = "alter session set DATE_OUTPUT_FORMAT='MM/DD/YYYY'"; + cmd.ExecuteNonQuery(); + + cmd.CommandText = $"select TO_DATE('2013-05-17')"; + IDataReader reader = cmd.ExecuteReader(); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual("05/17/2013", reader.GetString(0)); + + reader.Close(); + } + finally + { + // set format back to default to avoid impact other test cases + cmd.CommandText = "alter session set DATE_OUTPUT_FORMAT='YYYY-MM-DD'"; + cmd.ExecuteNonQuery(); + } + + conn.Close(); + } + } [Test] [TestCase(null, null)] diff --git a/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs b/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs index 51635d662..b9530b83b 100644 --- a/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs @@ -34,6 +34,12 @@ public void TestUpdateDatabaseAndSchema() Assert.AreEqual(databaseName, sfSession.database); Assert.AreEqual(schemaName, sfSession.schema); + + // when database or schema name is missing in the response, + // the cached value should keep unchanged + sfSession.UpdateDatabaseAndSchema(null, null); + Assert.AreEqual(databaseName, sfSession.database); + Assert.AreEqual(schemaName, sfSession.schema); } [Test] diff --git a/Snowflake.Data/Core/ArrowResultSet.cs b/Snowflake.Data/Core/ArrowResultSet.cs index 5c3103320..6bc0112c3 100755 --- a/Snowflake.Data/Core/ArrowResultSet.cs +++ b/Snowflake.Data/Core/ArrowResultSet.cs @@ -45,7 +45,7 @@ public ArrowResultSet(QueryExecResponseData responseData, SFStatement sfStatemen responseData.rowSet = null; - sfResultSetMetaData = new SFResultSetMetaData(responseData); + sfResultSetMetaData = new SFResultSetMetaData(responseData, this.sfStatement.SfSession); isClosed = false; diff --git a/Snowflake.Data/Core/SFResultSet.cs b/Snowflake.Data/Core/SFResultSet.cs index 2e20453ba..1f36a5cd2 100755 --- a/Snowflake.Data/Core/SFResultSet.cs +++ b/Snowflake.Data/Core/SFResultSet.cs @@ -39,7 +39,7 @@ public SFResultSet(QueryExecResponseData responseData, SFStatement sfStatement, _currentChunk = new SFResultChunk(responseData.rowSet); responseData.rowSet = null; - sfResultSetMetaData = new SFResultSetMetaData(responseData); + sfResultSetMetaData = new SFResultSetMetaData(responseData, this.sfStatement.SfSession); isClosed = false; diff --git a/Snowflake.Data/Core/SFResultSetMetaData.cs b/Snowflake.Data/Core/SFResultSetMetaData.cs index 1045109f4..25cf28ef8 100755 --- a/Snowflake.Data/Core/SFResultSetMetaData.cs +++ b/Snowflake.Data/Core/SFResultSetMetaData.cs @@ -37,24 +37,20 @@ class SFResultSetMetaData /// private Dictionary columnNameToIndexCache = new Dictionary(); - internal SFResultSetMetaData(QueryExecResponseData queryExecResponseData) + internal SFResultSetMetaData(QueryExecResponseData queryExecResponseData, SFSession session) { rowTypes = queryExecResponseData.rowType; columnCount = rowTypes.Count; statementType = findStatementTypeById(queryExecResponseData.statementTypeId); columnTypes = InitColumnTypes(); - - foreach (NameValueParameter parameter in queryExecResponseData.parameters) + + if (session.ParameterMap.ContainsKey(SFSessionParameter.DATE_OUTPUT_FORMAT)) { - switch(parameter.name) - { - case "DATE_OUTPUT_FORMAT": - dateOutputFormat = parameter.value; - break; - case "TIME_OUTPUT_FORMAT": - timeOutputFormat = parameter.value; - break; - } + dateOutputFormat = session.ParameterMap[SFSessionParameter.DATE_OUTPUT_FORMAT].ToString(); + } + if (session.ParameterMap.ContainsKey(SFSessionParameter.TIME_OUTPUT_FORMAT)) + { + timeOutputFormat = session.ParameterMap[SFSessionParameter.TIME_OUTPUT_FORMAT].ToString(); } } diff --git a/Snowflake.Data/Core/Session/SFSession.cs b/Snowflake.Data/Core/Session/SFSession.cs index ad9aa07cd..49586c08d 100755 --- a/Snowflake.Data/Core/Session/SFSession.cs +++ b/Snowflake.Data/Core/Session/SFSession.cs @@ -380,6 +380,13 @@ internal SFRestRequest BuildTimeoutRestRequest(Uri uri, Object body) internal void UpdateSessionParameterMap(List parameterList) { logger.Debug("Update parameter map"); + // with HTAP parameter removal parameters might not returned + // query response + if (parameterList is null) + { + return; + } + foreach (NameValueParameter parameter in parameterList) { if (Enum.TryParse(parameter.name, out SFSessionParameter parameterName)) @@ -432,8 +439,16 @@ internal RequestQueryContext GetQueryContextRequest() internal void UpdateDatabaseAndSchema(string databaseName, string schemaName) { - this.database = databaseName; - this.schema = schemaName; + // with HTAP session metadata removal database/schema + // might be not returened in query result + if (!String.IsNullOrEmpty(databaseName)) + { + this.database = databaseName; + } + if (!String.IsNullOrEmpty(schemaName)) + { + this.schema = schemaName; + } } internal void startHeartBeatForThisSession() diff --git a/Snowflake.Data/Core/Session/SFSessionParameter.cs b/Snowflake.Data/Core/Session/SFSessionParameter.cs index 4212f5b35..97fdcec23 100755 --- a/Snowflake.Data/Core/Session/SFSessionParameter.cs +++ b/Snowflake.Data/Core/Session/SFSessionParameter.cs @@ -12,5 +12,7 @@ internal enum SFSessionParameter CLIENT_STAGE_ARRAY_BINDING_THRESHOLD, CLIENT_SESSION_KEEP_ALIVE, QUERY_CONTEXT_CACHE_SIZE, + DATE_OUTPUT_FORMAT, + TIME_OUTPUT_FORMAT, } } From edca3a2e4226650c7757333daea987c830a6cb5f Mon Sep 17 00:00:00 2001 From: Dawid Heyman Date: Fri, 13 Oct 2023 10:16:37 +0200 Subject: [PATCH 2/6] SNOW-859636 Excluded codecod[bot] from Jira comment action (#795) --- .github/workflows/jira_comment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/jira_comment.yml b/.github/workflows/jira_comment.yml index ff647ade0..b021a2925 100644 --- a/.github/workflows/jira_comment.yml +++ b/.github/workflows/jira_comment.yml @@ -23,7 +23,7 @@ jobs: echo ::set-output name=jira::$jira - name: Comment on issue uses: atlassian/gajira-comment@master - if: startsWith(steps.extract.outputs.jira, 'SNOW-') + if: startsWith(steps.extract.outputs.jira, 'SNOW-') && github.event.comment.user.login != 'codecov[bot]' with: issue: "${{ steps.extract.outputs.jira }}" comment: "${{ github.event.comment.user.login }} commented:\n\n${{ github.event.comment.body }}\n\n${{ github.event.comment.html_url }}" From 7ff70e43190c70963aeafa98cd01db4515be4732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Hofman?= Date: Mon, 16 Oct 2023 23:34:33 +0200 Subject: [PATCH 3/6] =?UTF-8?q?pool/SNOW-902611=20#1/4=20removed=20singlet?= =?UTF-8?q?on=20pattern=20from=20SessionPool;=20introduced=20ne=E2=80=A6?= =?UTF-8?q?=20(#791)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description Connection Pool v1 -Removal of singleton pattern from SessionPool -Introduction of interface and its implementation of ConnectionManager -Split integration tests of ConnectionPool (into separate files) + some cleanup in the classes ### Checklist - [x] Code compiles correctly - [x] Code is formatted according to [Coding Conventions](../CodingConventions.md) - [x] Created tests which fail without the change (if possible) - [x] All tests passing (`dotnet test`) - [x] Extended the README / documentation, if necessary - [x] Provide JIRA issue id (if possible) or GitHub issue id in PR name --- .../SFConnectionPoolAsyncIT.cs | 360 ++++++++ .../IntegrationTests/SFConnectionPoolIT.cs | 399 +++++++++ .../IntegrationTests/SFConnectionPoolT.cs | 775 ------------------ Snowflake.Data.Tests/Util/PoolConfig.cs | 29 + .../Client/SnowflakeDbConnectionPool.cs | 47 +- .../Core/Session/ConnectionCacheManager.cs | 23 + .../Core/Session/IConnectionManager.cs | 25 + Snowflake.Data/Core/Session/SessionPool.cs | 61 +- 8 files changed, 890 insertions(+), 829 deletions(-) create mode 100644 Snowflake.Data.Tests/IntegrationTests/SFConnectionPoolAsyncIT.cs create mode 100644 Snowflake.Data.Tests/IntegrationTests/SFConnectionPoolIT.cs delete mode 100644 Snowflake.Data.Tests/IntegrationTests/SFConnectionPoolT.cs create mode 100644 Snowflake.Data.Tests/Util/PoolConfig.cs create mode 100644 Snowflake.Data/Core/Session/ConnectionCacheManager.cs create mode 100644 Snowflake.Data/Core/Session/IConnectionManager.cs diff --git a/Snowflake.Data.Tests/IntegrationTests/SFConnectionPoolAsyncIT.cs b/Snowflake.Data.Tests/IntegrationTests/SFConnectionPoolAsyncIT.cs new file mode 100644 index 000000000..05f7ed17f --- /dev/null +++ b/Snowflake.Data.Tests/IntegrationTests/SFConnectionPoolAsyncIT.cs @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. + */ + +using Snowflake.Data.Tests.Util; +using System; +using System.Data; +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; +using Snowflake.Data.Client; +using Snowflake.Data.Core; +using Snowflake.Data.Log; +using Snowflake.Data.Tests.Mock; +using Moq; +using NUnit.Framework; + +namespace Snowflake.Data.Tests.IntegrationTests +{ + [TestFixture, NonParallelizable] + class SFConnectionPoolITAsync : SFBaseTestAsync + { + private static PoolConfig s_previousPoolConfigRestorer; + + [OneTimeSetUp] + public static void BeforeAllTests() + { + s_previousPoolConfigRestorer = new PoolConfig(); + } + + [SetUp] + public new void BeforeTest() + { + SnowflakeDbConnectionPool.SetPooling(true); + SnowflakeDbConnectionPool.ClearAllPools(); + } + + [TearDown] + public new void AfterTest() + { + s_previousPoolConfigRestorer.Reset(); + } + + [OneTimeTearDown] + public static void AfterAllTests() + { + SnowflakeDbConnectionPool.ClearAllPools(); + } + + [Test] + public void TestConnectionPoolWithAsync() + { + using (var conn = new MockSnowflakeDbConnection()) + { + SnowflakeDbConnectionPool.SetMaxPoolSize(1); + + int timeoutSec = 0; + string infiniteLoginTimeOut = $";connection_timeout={timeoutSec}"; + + conn.ConnectionString = infiniteLoginTimeOut; + + Assert.AreEqual(conn.State, ConnectionState.Closed); + + CancellationTokenSource connectionCancelToken = new CancellationTokenSource(); + try + { + conn.OpenAsync(connectionCancelToken.Token); + } + catch (SnowflakeDbException ex) + { + conn.CloseAsync(connectionCancelToken.Token); + } + + Thread.Sleep(10 * 1000); + Assert.AreEqual(ConnectionState.Closed, conn.State); + Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + } + } + + [Test] + public void TestConnectionPoolWithInvalidOpenAsync() + { + SnowflakeDbConnectionPool.SetMaxPoolSize(10); + // make the connection string unique so it won't pick up connection + // pooled by other test cases. + string connStr = ConnectionString + ";application=conn_pool_test_invalid_openasync"; + using (var connection = new SnowflakeDbConnection()) + { + connection.ConnectionString = connStr; + // call openAsync but do not wait and destroy it direct + // so the session is initialized with empty token + connection.OpenAsync(); + } + + // use the same connection string to make a new connection + // to ensure the invalid connection made previously is not pooled + using (var connection1 = new SnowflakeDbConnection()) + { + connection1.ConnectionString = connStr; + // this will not open a new session but get the invalid connection from pool + connection1.Open(); + // Now run query with connection1 + var command = connection1.CreateCommand(); + command.CommandText = "select 1, 2, 3"; + + try + { + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + for (int i = 0; i < reader.FieldCount; i++) + { + // Process each column as appropriate + reader.GetFieldValue(i); + } + } + } + } + catch (SnowflakeDbException) + { + // fail the test case if anything wrong. + Assert.Fail(); + } + } + } + + [Test] + // test connection pooling with concurrent connection using async calls + public void TestConcurrentConnectionPoolingAsync() + { + // add test case name in connection string to make in unique for each test case + string connStr = ConnectionString + ";application=TestConcurrentConnectionPoolingAsync"; + ConcurrentPoolingAsyncHelper(connStr, true); + } + + [Test] + public void TestRollbackTransactionOnPooledWhenExceptionOccurred() + { + SnowflakeDbConnectionPool.SetMaxPoolSize(1); + + object firstOpenedSessionId; + using (var connection = new SnowflakeDbConnection()) + { + connection.ConnectionString = ConnectionString; + connection.Open(); + firstOpenedSessionId = connection.SfSession.sessionId; + connection.BeginTransaction(); + Assert.AreEqual(true, connection.HasActiveExplicitTransaction()); + Assert.Throws(() => + { + using (var command = connection.CreateCommand()) + { + command.CommandText = "invalid command will throw exception and leave session with an unfinished transaction"; + command.ExecuteNonQuery(); + } + }); + } + + using (var connectionWithSessionReused = new SnowflakeDbConnection()) + { + connectionWithSessionReused.ConnectionString = ConnectionString; + connectionWithSessionReused.Open(); + + Assert.AreEqual(firstOpenedSessionId, connectionWithSessionReused.SfSession.sessionId); + Assert.AreEqual(false, connectionWithSessionReused.HasActiveExplicitTransaction()); + using (var cmd = connectionWithSessionReused.CreateCommand()) + { + cmd.CommandText = "SELECT CURRENT_TRANSACTION()"; + Assert.AreEqual(DBNull.Value, cmd.ExecuteScalar()); + } + } + + Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection should be reused and any pending transaction rolled back before it gets back to the pool"); + } + + [Test] + public void TestTransactionStatusNotTrackedForNonExplicitTransactionCalls() + { + SnowflakeDbConnectionPool.SetMaxPoolSize(1); + using (var connection = new SnowflakeDbConnection()) + { + connection.ConnectionString = ConnectionString; + connection.Open(); + using (var command = connection.CreateCommand()) + { + command.CommandText = "BEGIN"; // in general can be put as a part of a multi statement call and mixed with commit as well + command.ExecuteNonQuery(); + Assert.AreEqual(false, connection.HasActiveExplicitTransaction()); + } + } + } + + [Test] + public void TestRollbackTransactionOnPooledWhenConnectionClose() + { + SnowflakeDbConnectionPool.SetMaxPoolSize(1); + Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection should be returned to the pool"); + + string firstOpenedSessionId; + using (var connection1 = new SnowflakeDbConnection()) + { + connection1.ConnectionString = ConnectionString; + connection1.Open(); + Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection session is added to the pool after close connection"); + connection1.BeginTransaction(); + Assert.AreEqual(true, connection1.HasActiveExplicitTransaction()); + using (var command = connection1.CreateCommand()) + { + firstOpenedSessionId = connection1.SfSession.sessionId; + command.CommandText = "SELECT CURRENT_TRANSACTION()"; + Assert.AreNotEqual(DBNull.Value, command.ExecuteScalar()); + } + } + Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection should be returned to the pool"); + + using (var connection2 = new SnowflakeDbConnection()) + { + connection2.ConnectionString = ConnectionString; + connection2.Open(); + Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection session should be now removed from the pool"); + Assert.AreEqual(false, connection2.HasActiveExplicitTransaction()); + using (var command = connection2.CreateCommand()) + { + Assert.AreEqual(firstOpenedSessionId, connection2.SfSession.sessionId); + command.CommandText = "SELECT CURRENT_TRANSACTION()"; + Assert.AreEqual(DBNull.Value, command.ExecuteScalar()); + } + } + Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection should be returned to the pool"); + } + + [Test] + public void TestFailureOfTransactionRollbackOnConnectionClosePreventsAddingToPool() + { + SnowflakeDbConnectionPool.SetMaxPoolSize(10); + var commandThrowingExceptionOnlyForRollback = new Mock(); + commandThrowingExceptionOnlyForRollback.CallBase = true; + commandThrowingExceptionOnlyForRollback.SetupSet(it => it.CommandText = "ROLLBACK") + .Throws(new SnowflakeDbException(SFError.INTERNAL_ERROR, "Unexpected failure on transaction rollback when connection is returned to the pool with pending transaction")); + var mockDbProviderFactory = new Mock(); + mockDbProviderFactory.Setup(p => p.CreateCommand()).Returns(commandThrowingExceptionOnlyForRollback.Object); + + Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + using (var connection = new TestSnowflakeDbConnection(mockDbProviderFactory.Object)) + { + connection.ConnectionString = ConnectionString; + connection.Open(); + connection.BeginTransaction(); + Assert.AreEqual(true, connection.HasActiveExplicitTransaction()); + // no Rollback or Commit; during internal Rollback while closing a connection a mocked exception will be thrown + } + + Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Should not return connection to the pool"); + } + + [Test] + // test connection pooling with concurrent connection and using async calls no close + // call for connection. Connection is closed when Dispose() is called + // by framework. + public void TestConcurrentConnectionPoolingDisposeAsync() + { + // add test case name in connection string to make in unique for each test case + string connStr = ConnectionString + ";application=TestConcurrentConnectionPoolingDisposeAsync"; + ConcurrentPoolingAsyncHelper(connStr, false); + } + + static void ConcurrentPoolingAsyncHelper(string connectionString, bool closeConnection) + { + // task number a bit larger than pool size so some connections + // would fail on pooling while some connections could success + const int TaskNum = 12; + // set short pooling timeout to cover the case that connection expired + const int PoolTimeout = 3; + + // reset to default settings in case it changed by other test cases + SnowflakeDbConnectionPool.SetMaxPoolSize(10); + SnowflakeDbConnectionPool.SetTimeout(PoolTimeout); + + var tasks = new Task[TaskNum + 1]; + for (int i = 0; i < TaskNum; i++) + { + tasks[i] = QueryExecutionTaskAsync(connectionString, closeConnection); + } + // cover the case of invalid sessions to ensure that won't + // break connection pooling + tasks[TaskNum] = InvalidConnectionTaskAsync(connectionString); + Task.WaitAll(tasks); + + // set pooling timeout back to default to avoid impact on other test cases + SnowflakeDbConnectionPool.SetTimeout(3600); + } + + // task to execute query with new connection in a loop + static async Task QueryExecutionTaskAsync(string connectionString, bool closeConnection) + { + for (int i = 0; i < 100; i++) + { + using (var conn = new SnowflakeDbConnection(connectionString)) + { + await conn.OpenAsync(); + using (DbCommand cmd = conn.CreateCommand()) + { + cmd.CommandText = "select 1, 2, 3"; + try + { + using (DbDataReader reader = await cmd.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + for (int j = 0; j < reader.FieldCount; j++) + { + // Process each column as appropriate + await reader.GetFieldValueAsync(j); + } + } + } + } + catch (Exception e) + { + Assert.Fail("Caught unexpected exception: " + e); + } + } + + if (closeConnection) + { + await conn.CloseAsync(new CancellationTokenSource().Token); + } + } + } + } + + // task to generate invalid(not finish open) connections in a loop + static async Task InvalidConnectionTaskAsync(string connectionString) + { + for (int i = 0; i < 100; i++) + { + using (var conn = new SnowflakeDbConnection(connectionString)) + { + // intentionally not using await so the connection + // will be disposed with invalid underlying session + conn.OpenAsync(); + }; + // wait 100ms each time so the invalid sessions are generated + // roughly at the same speed as connections for query tasks + await Task.Delay(100); + } + } + + private class TestSnowflakeDbConnection : SnowflakeDbConnection + { + public TestSnowflakeDbConnection(DbProviderFactory dbProviderFactory) + { + DbProviderFactory = dbProviderFactory; + } + + protected override DbProviderFactory DbProviderFactory { get; } + } + } +} diff --git a/Snowflake.Data.Tests/IntegrationTests/SFConnectionPoolIT.cs b/Snowflake.Data.Tests/IntegrationTests/SFConnectionPoolIT.cs new file mode 100644 index 000000000..5c4529225 --- /dev/null +++ b/Snowflake.Data.Tests/IntegrationTests/SFConnectionPoolIT.cs @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. + */ + +using Snowflake.Data.Tests.Util; +using System; +using System.Data; +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; +using Snowflake.Data.Core; +using Snowflake.Data.Client; +using Snowflake.Data.Log; +using NUnit.Framework; + +namespace Snowflake.Data.Tests.IntegrationTests +{ + [TestFixture, NonParallelizable] + class SFConnectionPoolIT : SFBaseTest + { + private static PoolConfig s_previousPoolConfig; + + [OneTimeSetUp] + public static void BeforeAllTests() + { + s_previousPoolConfig = new PoolConfig(); + } + + [SetUp] + public new void BeforeTest() + { + SnowflakeDbConnectionPool.SetPooling(true); + SnowflakeDbConnectionPool.ClearAllPools(); + } + + [TearDown] + public new void AfterTest() + { + s_previousPoolConfig.Reset(); + } + + [OneTimeTearDown] + public static void AfterAllTests() + { + SnowflakeDbConnectionPool.ClearAllPools(); + } + + [Test] + // test connection pooling with concurrent connection + public void TestConcurrentConnectionPooling() + { + // add test case name in connection string to make in unique for each test case + string connStr = ConnectionString + ";application=TestConcurrentConnectionPooling"; + ConcurrentPoolingHelper(connStr, true); + } + + [Test] + // test connection pooling with concurrent connection and no close + // call for connection. Connection is closed when Dispose() is called + // by framework. + public void TestConcurrentConnectionPoolingDispose() + { + // add test case name in connection string to make in unique for each test case + string connStr = ConnectionString + ";application=TestConcurrentConnectionPoolingNoClose"; + ConcurrentPoolingHelper(connStr, false); + } + + static void ConcurrentPoolingHelper(string connectionString, bool closeConnection) + { + // thread number a bit larger than pool size so some connections + // would fail on pooling while some connections could success + const int ThreadNum = 12; + // set short pooling timeout to cover the case that connection expired + const int PoolTimeout = 3; + + // reset to default settings in case it changed by other test cases + SnowflakeDbConnectionPool.SetMaxPoolSize(10); + SnowflakeDbConnectionPool.SetTimeout(PoolTimeout); + + var threads = new Task[ThreadNum]; + for (int i = 0; i < ThreadNum; i++) + { + threads[i] = Task.Factory.StartNew(() => + { + QueryExecutionThread(connectionString, closeConnection); + }); + } + Task.WaitAll(threads); + // set pooling timeout back to default to avoid impact on other test cases + SnowflakeDbConnectionPool.SetTimeout(3600); + } + + // thead to execute query with new connection in a loop + static void QueryExecutionThread(string connectionString, bool closeConnection) + { + for (int i = 0; i < 100; i++) + { + using (DbConnection conn = new SnowflakeDbConnection(connectionString)) + { + conn.Open(); + using (DbCommand cmd = conn.CreateCommand()) + { + cmd.CommandText = "select 1, 2, 3"; + try + { + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + for (int j = 0; j < reader.FieldCount; j++) + { + // Process each column as appropriate + reader.GetFieldValue(j); + } + } + } + } + catch (Exception e) + { + Assert.Fail("Caught unexpected exception: " + e); + } + } + + if (closeConnection) + { + conn.Close(); + } + } + } + } + + [Test] + public void TestBasicConnectionPool() + { + SnowflakeDbConnectionPool.SetMaxPoolSize(1); + + var conn1 = new SnowflakeDbConnection(ConnectionString); + conn1.Open(); + Assert.AreEqual(ConnectionState.Open, conn1.State); + conn1.Close(); + + Assert.AreEqual(ConnectionState.Closed, conn1.State); + Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + } + + [Test] + public void TestConnectionPool() + { + var conn1 = new SnowflakeDbConnection(ConnectionString); + conn1.Open(); + Assert.AreEqual(ConnectionState.Open, conn1.State); + conn1.Close(); + Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + + var conn2 = new SnowflakeDbConnection(); + conn2.ConnectionString = ConnectionString; + conn2.Open(); + Assert.AreEqual(ConnectionState.Open, conn2.State); + Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + + conn2.Close(); + Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + Assert.AreEqual(ConnectionState.Closed, conn1.State); + Assert.AreEqual(ConnectionState.Closed, conn2.State); + SnowflakeDbConnectionPool.ClearAllPools(); + } + + [Test] + public void TestConnectionPoolIsFull() + { + SnowflakeDbConnectionPool.SetMaxPoolSize(2); + var conn1 = new SnowflakeDbConnection(); + conn1.ConnectionString = ConnectionString; + conn1.Open(); + Assert.AreEqual(ConnectionState.Open, conn1.State); + + var conn2 = new SnowflakeDbConnection(); + conn2.ConnectionString = ConnectionString + " retryCount=1"; + conn2.Open(); + Assert.AreEqual(ConnectionState.Open, conn2.State); + + var conn3 = new SnowflakeDbConnection(); + conn3.ConnectionString = ConnectionString + " retryCount=2"; + conn3.Open(); + Assert.AreEqual(ConnectionState.Open, conn3.State); + SnowflakeDbConnectionPool.ClearAllPools(); + + conn1.Close(); + Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + conn2.Close(); + Assert.AreEqual(2, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + conn3.Close(); + Assert.AreEqual(2, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + + Assert.AreEqual(ConnectionState.Closed, conn1.State); + Assert.AreEqual(ConnectionState.Closed, conn2.State); + Assert.AreEqual(ConnectionState.Closed, conn3.State); + SnowflakeDbConnectionPool.ClearAllPools(); + } + + [Test] + public void TestConnectionPoolExpirationWorks() + { + SnowflakeDbConnectionPool.SetMaxPoolSize(2); + SnowflakeDbConnectionPool.SetTimeout(10); + + var conn1 = new SnowflakeDbConnection(); + conn1.ConnectionString = ConnectionString; + + conn1.Open(); + conn1.Close(); + SnowflakeDbConnectionPool.SetTimeout(-1); + + var conn2 = new SnowflakeDbConnection(); + conn2.ConnectionString = ConnectionString; + conn2.Open(); + conn2.Close(); + var conn3 = new SnowflakeDbConnection(); + conn3.ConnectionString = ConnectionString; + conn3.Open(); + conn3.Close(); + + // The pooling timeout should apply to all connections being pooled, + // not just the connections created after the new setting, + // so expected result should be 0 + Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + SnowflakeDbConnectionPool.SetPooling(false); + } + + [Test] + public void TestConnectionPoolClean() + { + SnowflakeDbConnectionPool.SetMaxPoolSize(2); + var conn1 = new SnowflakeDbConnection(); + conn1.ConnectionString = ConnectionString; + conn1.Open(); + Assert.AreEqual(ConnectionState.Open, conn1.State); + + var conn2 = new SnowflakeDbConnection(); + conn2.ConnectionString = ConnectionString + " retryCount=1"; + conn2.Open(); + Assert.AreEqual(ConnectionState.Open, conn2.State); + + var conn3 = new SnowflakeDbConnection(); + conn3.ConnectionString = ConnectionString + " retryCount=2"; + conn3.Open(); + Assert.AreEqual(ConnectionState.Open, conn3.State); + + conn1.Close(); + conn2.Close(); + Assert.AreEqual(2, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + SnowflakeDbConnectionPool.ClearAllPools(); + Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + conn3.Close(); + Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + + Assert.AreEqual(ConnectionState.Closed, conn1.State); + Assert.AreEqual(ConnectionState.Closed, conn2.State); + Assert.AreEqual(ConnectionState.Closed, conn3.State); + SnowflakeDbConnectionPool.ClearAllPools(); + } + + [Test] + public void TestConnectionPoolFull() + { + SnowflakeDbConnectionPool.SetMaxPoolSize(2); + + var conn1 = new SnowflakeDbConnection(); + conn1.ConnectionString = ConnectionString; + conn1.Open(); + Assert.AreEqual(ConnectionState.Open, conn1.State); + + var conn2 = new SnowflakeDbConnection(); + conn2.ConnectionString = ConnectionString + " retryCount=1"; + conn2.Open(); + Assert.AreEqual(ConnectionState.Open, conn2.State); + Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + conn1.Close(); + conn2.Close(); + Assert.AreEqual(2, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + var conn3 = new SnowflakeDbConnection(); + conn3.ConnectionString = ConnectionString + " retryCount=2"; + conn3.Open(); + Assert.AreEqual(ConnectionState.Open, conn3.State); + + var conn4 = new SnowflakeDbConnection(); + conn4.ConnectionString = ConnectionString + " retryCount=3"; + conn4.Open(); + Assert.AreEqual(ConnectionState.Open, conn4.State); + + conn3.Close(); + Assert.AreEqual(2, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + conn4.Close(); + Assert.AreEqual(2, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + + Assert.AreEqual(ConnectionState.Closed, conn1.State); + Assert.AreEqual(ConnectionState.Closed, conn2.State); + Assert.AreEqual(ConnectionState.Closed, conn3.State); + Assert.AreEqual(ConnectionState.Closed, conn4.State); + SnowflakeDbConnectionPool.ClearAllPools(); + } + + [Test] + public void TestConnectionPoolMultiThreading() + { + Thread t1 = new Thread(() => ThreadProcess1(ConnectionString)); + Thread t2 = new Thread(() => ThreadProcess2(ConnectionString)); + + t1.Start(); + t2.Start(); + + t1.Join(); + t2.Join(); + } + + void ThreadProcess1(string connstr) + { + var conn1 = new SnowflakeDbConnection(); + conn1.ConnectionString = connstr; + conn1.Open(); + Thread.Sleep(1000); + conn1.Close(); + Thread.Sleep(4000); + Assert.AreEqual(ConnectionState.Closed, conn1.State); + } + + void ThreadProcess2(string connstr) + { + var conn1 = new SnowflakeDbConnection(); + conn1.ConnectionString = connstr; + conn1.Open(); + + Thread.Sleep(5000); + SFStatement statement = new SFStatement(conn1.SfSession); + SFBaseResultSet resultSet = statement.Execute(0, "select 1", null, false); + Assert.AreEqual(true, resultSet.Next()); + Assert.AreEqual("1", resultSet.GetString(0)); + SnowflakeDbConnectionPool.ClearAllPools(); + SnowflakeDbConnectionPool.SetMaxPoolSize(0); + SnowflakeDbConnectionPool.SetPooling(false); + } + + [Test] + public void TestConnectionPoolDisable() + { + SnowflakeDbConnectionPool.SetPooling(false); + + var conn1 = new SnowflakeDbConnection(); + conn1.ConnectionString = ConnectionString; + conn1.Open(); + Assert.AreEqual(ConnectionState.Open, conn1.State); + conn1.Close(); + + Assert.AreEqual(ConnectionState.Closed, conn1.State); + Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + } + + [Test] + public void TestConnectionPoolWithDispose() + { + SnowflakeDbConnectionPool.SetMaxPoolSize(1); + + var conn1 = new SnowflakeDbConnection(); + conn1.ConnectionString = ""; + try + { + conn1.Open(); + } + catch (SnowflakeDbException ex) + { + conn1.Close(); + } + + Assert.AreEqual(ConnectionState.Closed, conn1.State); + Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + } + + [Test] + public void TestConnectionPoolTurnOff() + { + SnowflakeDbConnectionPool.SetPooling(false); + SnowflakeDbConnectionPool.SetPooling(true); + SnowflakeDbConnectionPool.SetMaxPoolSize(1); + SnowflakeDbConnectionPool.ClearAllPools(); + + var conn1 = new SnowflakeDbConnection(); + conn1.ConnectionString = ConnectionString; + conn1.Open(); + Assert.AreEqual(ConnectionState.Open, conn1.State); + conn1.Close(); + + Assert.AreEqual(ConnectionState.Closed, conn1.State); + Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + + SnowflakeDbConnectionPool.SetPooling(false); + //Put a breakpoint at SFSession close function, after connection pool is off, it will send close session request. + } + } +} diff --git a/Snowflake.Data.Tests/IntegrationTests/SFConnectionPoolT.cs b/Snowflake.Data.Tests/IntegrationTests/SFConnectionPoolT.cs deleted file mode 100644 index 7ea58702d..000000000 --- a/Snowflake.Data.Tests/IntegrationTests/SFConnectionPoolT.cs +++ /dev/null @@ -1,775 +0,0 @@ -/* - * Copyright (c) 2012-2021 Snowflake Computing Inc. All rights reserved. - */ - -namespace Snowflake.Data.Tests.IntegrationTests -{ - using NUnit.Framework; - using Snowflake.Data.Client; - using System.Data; - using System; - using Snowflake.Data.Core; - using System.Threading.Tasks; - using System.Threading; - using Snowflake.Data.Log; - using Snowflake.Data.Tests.Mock; - using System.Data.Common; - using Moq; - - class PoolConfig { - private readonly bool _pooling; - private readonly long _timeout; - private readonly int _maxPoolSize; - - public PoolConfig() - { - _maxPoolSize = SnowflakeDbConnectionPool.GetMaxPoolSize(); - _timeout = SnowflakeDbConnectionPool.GetTimeout(); - _pooling = SnowflakeDbConnectionPool.GetPooling(); - } - - public void Reset() - { - SnowflakeDbConnectionPool.SetMaxPoolSize(_maxPoolSize); - SnowflakeDbConnectionPool.SetTimeout(_timeout); - SnowflakeDbConnectionPool.SetPooling(_pooling); - } - } - - [TestFixture, NonParallelizable] - class SFConnectionPoolT : SFBaseTest - { - private static readonly PoolConfig s_previousPoolConfig = new PoolConfig(); - - [SetUp] - public void BeforeTest() - { - s_previousPoolConfig.Reset(); - SnowflakeDbConnectionPool.SetPooling(true); - SnowflakeDbConnectionPool.ClearAllPools(); - } - - [TearDown] - public void AfterTest() - { - s_previousPoolConfig.Reset(); - } - - [OneTimeTearDown] - public static void AfterAllTests() - { - SnowflakeDbConnectionPool.ClearAllPools(); - } - - [Test] - // test connection pooling with concurrent connection - public void TestConcurrentConnectionPooling() - { - // add test case name in connection string to make in unique for each test case - string connStr = ConnectionString + ";application=TestConcurrentConnectionPooling"; - ConcurrentPoolingHelper(connStr, true); - } - - [Test] - // test connection pooling with concurrent connection and no close - // call for connection. Connection is closed when Dispose() is called - // by framework. - public void TestConcurrentConnectionPoolingDispose() - { - // add test case name in connection string to make in unique for each test case - string connStr = ConnectionString + ";application=TestConcurrentConnectionPoolingNoClose"; - ConcurrentPoolingHelper(connStr, false); - } - - static void ConcurrentPoolingHelper(string connectionString, bool closeConnection) - { - // thread number a bit larger than pool size so some connections - // would fail on pooling while some connections could success - const int threadNum = 12; - // set short pooling timeout to cover the case that connection expired - const int poolTimeout = 3; - - // reset to default settings in case it changed by other test cases - SnowflakeDbConnectionPool.SetPooling(true); - SnowflakeDbConnectionPool.SetMaxPoolSize(10); - SnowflakeDbConnectionPool.ClearAllPools(); - SnowflakeDbConnectionPool.SetTimeout(poolTimeout); - - var threads = new Task[threadNum]; - for (int i = 0; i < threadNum; i++) - { - threads[i] = Task.Factory.StartNew(() => - { - QueryExecutionThread(connectionString, closeConnection); - }); - } - Task.WaitAll(threads); - // set pooling timeout back to default to avoid impact on other test cases - SnowflakeDbConnectionPool.SetTimeout(3600); - } - - // thead to execute query with new connection in a loop - static void QueryExecutionThread(string connectionString, bool closeConnection) - { - for (int i = 0; i < 100; i++) - { - using (DbConnection conn = new SnowflakeDbConnection(connectionString)) - { - conn.Open(); - using (DbCommand cmd = conn.CreateCommand()) - { - cmd.CommandText = "select 1, 2, 3"; - try - { - using (var reader = cmd.ExecuteReader()) - { - while (reader.Read()) - { - for (int j = 0; j < reader.FieldCount; j++) - { - // Process each column as appropriate - object obj = reader.GetFieldValue(j); - } - } - } - } - catch (Exception e) - { - Assert.Fail("Caught unexpected exception: " + e); - } - } - - if (closeConnection) - { - conn.Close(); - } - }; - } - } - - [Test] - public void TestBasicConnectionPool() - { - SnowflakeDbConnectionPool.SetPooling(true); - SnowflakeDbConnectionPool.SetMaxPoolSize(1); - SnowflakeDbConnectionPool.ClearAllPools(); - - var conn1 = new SnowflakeDbConnection(ConnectionString); - conn1.Open(); - Assert.AreEqual(ConnectionState.Open, conn1.State); - conn1.Close(); - - Assert.AreEqual(ConnectionState.Closed, conn1.State); - Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - } - - [Test] - public void TestConnectionPool() - { - SnowflakeDbConnectionPool.ClearAllPools(); - var conn1 = new SnowflakeDbConnection(ConnectionString); - conn1.Open(); - Assert.AreEqual(ConnectionState.Open, conn1.State); - conn1.Close(); - Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - - var conn2 = new SnowflakeDbConnection(); - conn2.ConnectionString = ConnectionString; - conn2.Open(); - Assert.AreEqual(ConnectionState.Open, conn2.State); - Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - - conn2.Close(); - Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - Assert.AreEqual(ConnectionState.Closed, conn1.State); - Assert.AreEqual(ConnectionState.Closed, conn2.State); - SnowflakeDbConnectionPool.ClearAllPools(); - } - - [Test] - public void TestConnectionPoolIsFull() - { - SnowflakeDbConnectionPool.SetPooling(true); - SnowflakeDbConnectionPool.ClearAllPools(); - SnowflakeDbConnectionPool.SetMaxPoolSize(2); - var conn1 = new SnowflakeDbConnection(); - conn1.ConnectionString = ConnectionString; - conn1.Open(); - Assert.AreEqual(ConnectionState.Open, conn1.State); - - var conn2 = new SnowflakeDbConnection(); - conn2.ConnectionString = ConnectionString + " retryCount=1"; - conn2.Open(); - Assert.AreEqual(ConnectionState.Open, conn2.State); - - var conn3 = new SnowflakeDbConnection(); - conn3.ConnectionString = ConnectionString + " retryCount=2"; - conn3.Open(); - Assert.AreEqual(ConnectionState.Open, conn3.State); - SnowflakeDbConnectionPool.ClearAllPools(); - - conn1.Close(); - Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - conn2.Close(); - Assert.AreEqual(2, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - conn3.Close(); - Assert.AreEqual(2, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - - Assert.AreEqual(ConnectionState.Closed, conn1.State); - Assert.AreEqual(ConnectionState.Closed, conn2.State); - Assert.AreEqual(ConnectionState.Closed, conn3.State); - SnowflakeDbConnectionPool.ClearAllPools(); - } - - [Test] - public void TestConnectionPoolExpirationWorks() - { - System.Threading.Thread.Sleep(10000); // wait for 10 seconds, in case other test still running. - SnowflakeDbConnectionPool.ClearAllPools(); - SnowflakeDbConnectionPool.SetMaxPoolSize(2); - SnowflakeDbConnectionPool.SetTimeout(10); - SnowflakeDbConnectionPool.SetPooling(true); - - var conn1 = new SnowflakeDbConnection(); - conn1.ConnectionString = ConnectionString; - - conn1.Open(); - conn1.Close(); - SnowflakeDbConnectionPool.SetTimeout(-1); - - var conn2 = new SnowflakeDbConnection(); - conn2.ConnectionString = ConnectionString; - conn2.Open(); - conn2.Close(); - var conn3 = new SnowflakeDbConnection(); - conn3.ConnectionString = ConnectionString; - conn3.Open(); - conn3.Close(); - - // The pooling timeout should apply to all connections being pooled, - // not just the connections created after the new setting, - // so expected result should be 0 - Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - SnowflakeDbConnectionPool.SetPooling(false); - } - - [Test] - public void TestConnectionPoolClean() - { - SnowflakeDbConnectionPool.ClearAllPools(); - SnowflakeDbConnectionPool.SetMaxPoolSize(2); - var conn1 = new SnowflakeDbConnection(); - conn1.ConnectionString = ConnectionString; - conn1.Open(); - Assert.AreEqual(ConnectionState.Open, conn1.State); - - var conn2 = new SnowflakeDbConnection(); - conn2.ConnectionString = ConnectionString + " retryCount=1"; - conn2.Open(); - Assert.AreEqual(ConnectionState.Open, conn2.State); - - var conn3 = new SnowflakeDbConnection(); - conn3.ConnectionString = ConnectionString + " retryCount=2"; - conn3.Open(); - Assert.AreEqual(ConnectionState.Open, conn3.State); - - conn1.Close(); - conn2.Close(); - Assert.AreEqual(2, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - SnowflakeDbConnectionPool.ClearAllPools(); - Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - conn3.Close(); - Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - - Assert.AreEqual(ConnectionState.Closed, conn1.State); - Assert.AreEqual(ConnectionState.Closed, conn2.State); - Assert.AreEqual(ConnectionState.Closed, conn3.State); - SnowflakeDbConnectionPool.ClearAllPools(); - } - - [Test] - public void TestConnectionPoolFull() - { - SnowflakeDbConnectionPool.ClearAllPools(); - SnowflakeDbConnectionPool.SetMaxPoolSize(2); - SnowflakeDbConnectionPool.SetPooling(true); - - var conn1 = new SnowflakeDbConnection(); - conn1.ConnectionString = ConnectionString; - conn1.Open(); - Assert.AreEqual(ConnectionState.Open, conn1.State); - - var conn2 = new SnowflakeDbConnection(); - conn2.ConnectionString = ConnectionString + " retryCount=1"; - conn2.Open(); - Assert.AreEqual(ConnectionState.Open, conn2.State); - Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - conn1.Close(); - conn2.Close(); - Assert.AreEqual(2, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - var conn3 = new SnowflakeDbConnection(); - conn3.ConnectionString = ConnectionString + " retryCount=2"; - conn3.Open(); - Assert.AreEqual(ConnectionState.Open, conn3.State); - - var conn4 = new SnowflakeDbConnection(); - conn4.ConnectionString = ConnectionString + " retryCount=3"; - conn4.Open(); - Assert.AreEqual(ConnectionState.Open, conn4.State); - - conn3.Close(); - Assert.AreEqual(2, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - conn4.Close(); - Assert.AreEqual(2, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - - Assert.AreEqual(ConnectionState.Closed, conn1.State); - Assert.AreEqual(ConnectionState.Closed, conn2.State); - Assert.AreEqual(ConnectionState.Closed, conn3.State); - Assert.AreEqual(ConnectionState.Closed, conn4.State); - SnowflakeDbConnectionPool.ClearAllPools(); - } - - [Test] - public void TestConnectionPoolMultiThreading() - { - Thread t1 = new Thread(() => ThreadProcess1(ConnectionString)); - Thread t2 = new Thread(() => ThreadProcess2(ConnectionString)); - - t1.Start(); - t2.Start(); - - t1.Join(); - t2.Join(); - } - - void ThreadProcess1(string connstr) - { - var conn1 = new SnowflakeDbConnection(); - conn1.ConnectionString = connstr; - conn1.Open(); - Thread.Sleep(1000); - conn1.Close(); - Thread.Sleep(4000); - Assert.AreEqual(ConnectionState.Closed, conn1.State); - } - - void ThreadProcess2(string connstr) - { - var conn1 = new SnowflakeDbConnection(); - conn1.ConnectionString = connstr; - conn1.Open(); - - Thread.Sleep(5000); - SFStatement statement = new SFStatement(conn1.SfSession); - SFBaseResultSet resultSet = statement.Execute(0, "select 1", null, false); - Assert.AreEqual(true, resultSet.Next()); - Assert.AreEqual("1", resultSet.GetString(0)); - SnowflakeDbConnectionPool.ClearAllPools(); - SnowflakeDbConnectionPool.SetMaxPoolSize(0); - SnowflakeDbConnectionPool.SetPooling(false); - } - - [Test] - public void TestConnectionPoolDisable() - { - SnowflakeDbConnectionPool.ClearAllPools(); - SnowflakeDbConnectionPool.SetPooling(false); - - var conn1 = new SnowflakeDbConnection(); - conn1.ConnectionString = ConnectionString; - conn1.Open(); - Assert.AreEqual(ConnectionState.Open, conn1.State); - conn1.Close(); - - Assert.AreEqual(ConnectionState.Closed, conn1.State); - Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - } - - [Test] - public void TestConnectionPoolWithDispose() - { - SnowflakeDbConnectionPool.SetPooling(true); - SnowflakeDbConnectionPool.SetMaxPoolSize(1); - SnowflakeDbConnectionPool.ClearAllPools(); - - var conn1 = new SnowflakeDbConnection(); - conn1.ConnectionString = ""; - try - { - conn1.Open(); - } - catch (SnowflakeDbException ex) - { - Console.WriteLine($"connection failed:" + ex); - conn1.Close(); - } - - Assert.AreEqual(ConnectionState.Closed, conn1.State); - Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - } - - [Test] - public void TestConnectionPoolTurnOff() - { - SnowflakeDbConnectionPool.SetPooling(false); - SnowflakeDbConnectionPool.SetPooling(true); - SnowflakeDbConnectionPool.SetMaxPoolSize(1); - SnowflakeDbConnectionPool.ClearAllPools(); - - var conn1 = new SnowflakeDbConnection(); - conn1.ConnectionString = ConnectionString; - conn1.Open(); - Assert.AreEqual(ConnectionState.Open, conn1.State); - conn1.Close(); - - Assert.AreEqual(ConnectionState.Closed, conn1.State); - Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - - SnowflakeDbConnectionPool.SetPooling(false); - //Put a breakpoint at SFSession close function, after connection pool is off, it will send close session request. - } - } - - [TestFixture, NonParallelizable] - class SFConnectionPoolITAsync : SFBaseTestAsync - { - private static SFLogger logger = SFLoggerFactory.GetLogger(); - private static readonly PoolConfig s_previousPoolConfigRestorer = new PoolConfig(); - - [SetUp] - public void BeforeTest() - { - s_previousPoolConfigRestorer.Reset(); - SnowflakeDbConnectionPool.SetPooling(true); - SnowflakeDbConnectionPool.ClearAllPools(); - } - - [TearDown] - public void AfterTest() - { - s_previousPoolConfigRestorer.Reset(); - } - - [OneTimeTearDown] - public static void AfterAllTests() - { - SnowflakeDbConnectionPool.ClearAllPools(); - } - - [Test] - public void TestConnectionPoolWithAsync() - { - using (var conn = new MockSnowflakeDbConnection()) - { - SnowflakeDbConnectionPool.SetMaxPoolSize(1); - SnowflakeDbConnectionPool.ClearAllPools(); - - int timeoutSec = 0; - string infiniteLoginTimeOut = String.Format("" + ";connection_timeout={0}", - timeoutSec); - - conn.ConnectionString = infiniteLoginTimeOut; - - Assert.AreEqual(conn.State, ConnectionState.Closed); - - CancellationTokenSource connectionCancelToken = new CancellationTokenSource(); - try - { - Task connectTask = conn.OpenAsync(connectionCancelToken.Token); - } - catch (SnowflakeDbException ex) - { - Console.WriteLine($"connection failed:" + ex); - conn.CloseAsync(connectionCancelToken.Token); - } - - Thread.Sleep(10 * 1000); - Assert.AreEqual(ConnectionState.Closed, conn.State); - Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - } - } - - [Test] - public void TestConnectionPoolWithInvalidOpenAsync() - { - SnowflakeDbConnectionPool.SetMaxPoolSize(10); - // make the connection string unique so it won't pick up connection - // pooled by other test cases. - string connStr = ConnectionString + ";application=conn_pool_test_invalid_openasync"; - using (var connection = new SnowflakeDbConnection()) - { - connection.ConnectionString = connStr; - // call openAsync but do not wait and destroy it direct - // so the session is initialized with empty token - connection.OpenAsync(); - } - - // use the same connection string to make a new connection - // to ensure the invalid connection made previously is not pooled - using (var connection1 = new SnowflakeDbConnection()) - { - connection1.ConnectionString = connStr; - // this will not open a new session but get the invalid connection from pool - connection1.Open(); - // Now run query with connection1 - var command = connection1.CreateCommand(); - command.CommandText = "select 1, 2, 3"; - - try - { - using (var reader = command.ExecuteReader()) - { - while (reader.Read()) - { - for (int i = 0; i < reader.FieldCount; i++) - { - // Process each column as appropriate - object obj = reader.GetFieldValue(i); - } - } - } - } - catch (SnowflakeDbException ex) - { - // fail the test case if anything wrong. - Assert.Fail(); - } - } - } - - [Test] - // test connection pooling with concurrent connection using async calls - public void TestConcurrentConnectionPoolingAsync() - { - // add test case name in connection string to make in unique for each test case - string connStr = ConnectionString + ";application=TestConcurrentConnectionPoolingAsync"; - ConcurrentPoolingAsyncHelper(connStr, true); - } - - [Test] - public void TestRollbackTransactionOnPooledWhenExceptionOccurred() - { - SnowflakeDbConnectionPool.SetMaxPoolSize(1); - - object firstOpenedSessionId = null; - using (var connection = new SnowflakeDbConnection()) - { - connection.ConnectionString = ConnectionString; - connection.Open(); - firstOpenedSessionId = connection.SfSession.sessionId; - connection.BeginTransaction(); - Assert.AreEqual(true, connection.HasActiveExplicitTransaction()); - Assert.Throws(() => - { - using (var command = connection.CreateCommand()) - { - command.CommandText = "invalid command will throw exception and leave session with an unfinished transaction"; - command.ExecuteNonQuery(); - } - }); - } - - using (var connectionWithSessionReused = new SnowflakeDbConnection()) - { - connectionWithSessionReused.ConnectionString = ConnectionString; - connectionWithSessionReused.Open(); - - Assert.AreEqual(firstOpenedSessionId, connectionWithSessionReused.SfSession.sessionId); - Assert.AreEqual(false, connectionWithSessionReused.HasActiveExplicitTransaction()); - using (var cmd = connectionWithSessionReused.CreateCommand()) - { - cmd.CommandText = "SELECT CURRENT_TRANSACTION()"; - Assert.AreEqual(DBNull.Value, cmd.ExecuteScalar()); - } - } - - Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection should be reused and any pending transaction rolled back before it gets back to the pool"); - } - - [Test] - public void TestTransactionStatusNotTrackedForNonExplicitTransactionCalls() - { - SnowflakeDbConnectionPool.SetMaxPoolSize(1); - using (var connection = new SnowflakeDbConnection()) - { - connection.ConnectionString = ConnectionString; - connection.Open(); - using (var command = connection.CreateCommand()) - { - command.CommandText = "BEGIN"; // in general can be put as a part of a multi statement call and mixed with commit as well - command.ExecuteNonQuery(); - Assert.AreEqual(false, connection.HasActiveExplicitTransaction()); - } - } - } - - [Test] - public void TestRollbackTransactionOnPooledWhenConnectionClose() - { - SnowflakeDbConnectionPool.SetMaxPoolSize(1); - Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection should be returned to the pool"); - - string firstOpenedSessionId = null; - using (var connection1 = new SnowflakeDbConnection()) - { - connection1.ConnectionString = ConnectionString; - connection1.Open(); - Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection session is added to the pool after close connection"); - connection1.BeginTransaction(); - Assert.AreEqual(true, connection1.HasActiveExplicitTransaction()); - using (var command = connection1.CreateCommand()) - { - firstOpenedSessionId = connection1.SfSession.sessionId; - command.CommandText = "SELECT CURRENT_TRANSACTION()"; - Assert.AreNotEqual(DBNull.Value, command.ExecuteScalar()); - } - } - Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection should be returned to the pool"); - - using (var connection2 = new SnowflakeDbConnection()) - { - connection2.ConnectionString = ConnectionString; - connection2.Open(); - Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection session should be now removed from the pool"); - Assert.AreEqual(false, connection2.HasActiveExplicitTransaction()); - using (var command = connection2.CreateCommand()) - { - Assert.AreEqual(firstOpenedSessionId, connection2.SfSession.sessionId); - command.CommandText = "SELECT CURRENT_TRANSACTION()"; - Assert.AreEqual(DBNull.Value, command.ExecuteScalar()); - } - } - Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection should be returned to the pool"); - } - - [Test] - public void TestFailureOfTransactionRollbackOnConnectionClosePreventsAddingToPool() - { - SnowflakeDbConnectionPool.SetPooling(true); - SnowflakeDbConnectionPool.SetMaxPoolSize(10); - var commandThrowingExceptionOnlyForRollback = new Mock(); - commandThrowingExceptionOnlyForRollback.CallBase = true; - commandThrowingExceptionOnlyForRollback.SetupSet(it => it.CommandText = "ROLLBACK") - .Throws(new SnowflakeDbException(SFError.INTERNAL_ERROR, "Unexpected failure on transaction rollback when connection is returned to the pool with pending transaction")); - var mockDbProviderFactory = new Mock(); - mockDbProviderFactory.Setup(p => p.CreateCommand()).Returns(commandThrowingExceptionOnlyForRollback.Object); - - Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - using (var connection = new TestSnowflakeDbConnection(mockDbProviderFactory.Object)) - { - connection.ConnectionString = ConnectionString; - connection.Open(); - connection.BeginTransaction(); - Assert.AreEqual(true, connection.HasActiveExplicitTransaction()); - // no Rollback or Commit; during internal Rollback while closing a connection a mocked exception will be thrown - } - - Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Should not return connection to the pool"); - } - - [Test] - // test connection pooling with concurrent connection and using async calls no close - // call for connection. Connection is closed when Dispose() is called - // by framework. - public void TestConcurrentConnectionPoolingDisposeAsync() - { - // add test case name in connection string to make in unique for each test case - string connStr = ConnectionString + ";application=TestConcurrentConnectionPoolingDisposeAsync"; - ConcurrentPoolingAsyncHelper(connStr, false); - } - - static void ConcurrentPoolingAsyncHelper(string connectionString, bool closeConnection) - { - // task number a bit larger than pool size so some connections - // would fail on pooling while some connections could success - const int taskNum = 12; - // set short pooling timeout to cover the case that connection expired - const int poolTimeout = 3; - - // reset to default settings in case it changed by other test cases - SnowflakeDbConnectionPool.SetPooling(true); - SnowflakeDbConnectionPool.SetMaxPoolSize(10); - SnowflakeDbConnectionPool.ClearAllPools(); - SnowflakeDbConnectionPool.SetTimeout(poolTimeout); - - var tasks = new Task[taskNum + 1]; - for (int i = 0; i < taskNum; i++) - { - tasks[i] = QueryExecutionTaskAsync(connectionString, closeConnection); - } - // cover the case of invalid sessions to ensure that won't - // break connection pooling - tasks[taskNum] = InvalidConnectionTaskAsync(connectionString); - Task.WaitAll(tasks); - - // set pooling timeout back to default to avoid impact on other test cases - SnowflakeDbConnectionPool.SetTimeout(3600); - } - - // task to execute query with new connection in a loop - static async Task QueryExecutionTaskAsync(string connectionString, bool closeConnection) - { - for (int i = 0; i < 100; i++) - { - using (var conn = new SnowflakeDbConnection(connectionString)) - { - await conn.OpenAsync(); - using (DbCommand cmd = conn.CreateCommand()) - { - cmd.CommandText = "select 1, 2, 3"; - try - { - using (DbDataReader reader = await cmd.ExecuteReaderAsync()) - { - while (await reader.ReadAsync()) - { - for (int j = 0; j < reader.FieldCount; j++) - { - // Process each column as appropriate - object obj = await reader.GetFieldValueAsync(j); - } - } - } - } - catch (Exception e) - { - Assert.Fail("Caught unexpected exception: " + e); - } - } - - if (closeConnection) - { - await conn.CloseAsync(new CancellationTokenSource().Token); - } - }; - } - } - - // task to generate invalid(not finish open) connections in a loop - static async Task InvalidConnectionTaskAsync(string connectionString) - { - for (int i = 0; i < 100; i++) - { - using (var conn = new SnowflakeDbConnection(connectionString)) - { - // intentially not using await so the connection - // will be disposed with invalid underlying session - conn.OpenAsync(); - }; - // wait 100ms each time so the invalid sessions are generated - // roughly at the same speed as connections for query tasks - await Task.Delay(100); - } - } - - private class TestSnowflakeDbConnection : SnowflakeDbConnection - { - public TestSnowflakeDbConnection(DbProviderFactory dbProviderFactory) - { - DbProviderFactory = dbProviderFactory; - } - - protected override DbProviderFactory DbProviderFactory { get; } - } - } -} diff --git a/Snowflake.Data.Tests/Util/PoolConfig.cs b/Snowflake.Data.Tests/Util/PoolConfig.cs new file mode 100644 index 000000000..4856da243 --- /dev/null +++ b/Snowflake.Data.Tests/Util/PoolConfig.cs @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012-2021 Snowflake Computing Inc. All rights reserved. + */ + +using Snowflake.Data.Client; + +namespace Snowflake.Data.Tests.Util +{ + class PoolConfig + { + private readonly bool _pooling; + private readonly long _timeout; + private readonly int _maxPoolSize; + + public PoolConfig() + { + _maxPoolSize = SnowflakeDbConnectionPool.GetMaxPoolSize(); + _timeout = SnowflakeDbConnectionPool.GetTimeout(); + _pooling = SnowflakeDbConnectionPool.GetPooling(); + } + + public void Reset() + { + SnowflakeDbConnectionPool.SetMaxPoolSize(_maxPoolSize); + SnowflakeDbConnectionPool.SetTimeout(_timeout); + SnowflakeDbConnectionPool.SetPooling(_pooling); + } + } +} diff --git a/Snowflake.Data/Client/SnowflakeDbConnectionPool.cs b/Snowflake.Data/Client/SnowflakeDbConnectionPool.cs index 81cb3528a..f643fa5c9 100644 --- a/Snowflake.Data/Client/SnowflakeDbConnectionPool.cs +++ b/Snowflake.Data/Client/SnowflakeDbConnectionPool.cs @@ -1,4 +1,8 @@ -using System.Security; +/* + * Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. + */ + +using System.Security; using System.Threading; using System.Threading.Tasks; using Snowflake.Data.Core; @@ -10,63 +14,72 @@ namespace Snowflake.Data.Client public class SnowflakeDbConnectionPool { private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); - - internal static SFSession GetSession(string connStr, SecureString password) + private static readonly IConnectionManager s_connectionManager = new ConnectionCacheManager(); + + internal static SFSession GetSession(string connectionString, SecureString password) { s_logger.Debug("SnowflakeDbConnectionPool::GetSession"); - return SessionPoolSingleton.Instance.GetSession(connStr, password); + return s_connectionManager.GetSession(connectionString, password); } - internal static Task GetSessionAsync(string connStr, SecureString password, CancellationToken cancellationToken) + internal static Task GetSessionAsync(string connectionString, SecureString password, CancellationToken cancellationToken) { - return SessionPoolSingleton.Instance.GetSessionAsync(connStr, password, cancellationToken); + s_logger.Debug("SnowflakeDbConnectionPool::GetSessionAsync"); + return s_connectionManager.GetSessionAsync(connectionString, password, cancellationToken); } internal static bool AddSession(SFSession session) { s_logger.Debug("SnowflakeDbConnectionPool::AddSession"); - return SessionPoolSingleton.Instance.AddSession(session); + return s_connectionManager.AddSession(session); } public static void ClearAllPools() { s_logger.Debug("SnowflakeDbConnectionPool::ClearAllPools"); - SessionPoolSingleton.Instance.ClearAllPools(); + s_connectionManager.ClearAllPools(); } - public static void SetMaxPoolSize(int size) + public static void SetMaxPoolSize(int maxPoolSize) { - SessionPoolSingleton.Instance.SetMaxPoolSize(size); + s_logger.Debug("SnowflakeDbConnectionPool::SetMaxPoolSize"); + s_connectionManager.SetMaxPoolSize(maxPoolSize); } public static int GetMaxPoolSize() { - return SessionPoolSingleton.Instance.GetMaxPoolSize(); + s_logger.Debug("SnowflakeDbConnectionPool::GetMaxPoolSize"); + return s_connectionManager.GetMaxPoolSize(); } - public static void SetTimeout(long time) + public static void SetTimeout(long connectionTimeout) { - SessionPoolSingleton.Instance.SetTimeout(time); + s_logger.Debug("SnowflakeDbConnectionPool::SetTimeout"); + s_connectionManager.SetTimeout(connectionTimeout); } public static long GetTimeout() { - return SessionPoolSingleton.Instance.GetTimeout(); + s_logger.Debug("SnowflakeDbConnectionPool::GetTimeout"); + return s_connectionManager.GetTimeout(); } public static int GetCurrentPoolSize() { - return SessionPoolSingleton.Instance.GetCurrentPoolSize(); + s_logger.Debug("SnowflakeDbConnectionPool::GetCurrentPoolSize"); + return s_connectionManager.GetCurrentPoolSize(); } public static bool SetPooling(bool isEnable) { - return SessionPoolSingleton.Instance.SetPooling(isEnable); + s_logger.Debug("SnowflakeDbConnectionPool::SetPooling"); + return s_connectionManager.SetPooling(isEnable); } public static bool GetPooling() { - return SessionPoolSingleton.Instance.GetPooling(); + s_logger.Debug("SnowflakeDbConnectionPool::GetPooling"); + return s_connectionManager.GetPooling(); } } } diff --git a/Snowflake.Data/Core/Session/ConnectionCacheManager.cs b/Snowflake.Data/Core/Session/ConnectionCacheManager.cs new file mode 100644 index 000000000..e10a984e3 --- /dev/null +++ b/Snowflake.Data/Core/Session/ConnectionCacheManager.cs @@ -0,0 +1,23 @@ +using System.Security; +using System.Threading; +using System.Threading.Tasks; + +namespace Snowflake.Data.Core.Session +{ + internal sealed class ConnectionCacheManager : IConnectionManager + { + private readonly SessionPool _sessionPool = new SessionPool(); + public SFSession GetSession(string connectionString, SecureString password) => _sessionPool.GetSession(connectionString, password); + public Task GetSessionAsync(string connectionString, SecureString password, CancellationToken cancellationToken) + => _sessionPool.GetSessionAsync(connectionString, password, cancellationToken); + public bool AddSession(SFSession session) => _sessionPool.AddSession(session); + public void ClearAllPools() => _sessionPool.ClearAllPools(); + public void SetMaxPoolSize(int maxPoolSize) => _sessionPool.SetMaxPoolSize(maxPoolSize); + public int GetMaxPoolSize() => _sessionPool.GetMaxPoolSize(); + public void SetTimeout(long connectionTimeout) => _sessionPool.SetTimeout(connectionTimeout); + public long GetTimeout() => _sessionPool.GetTimeout(); + public int GetCurrentPoolSize() => _sessionPool.GetCurrentPoolSize(); + public bool SetPooling(bool poolingEnabled) => _sessionPool.SetPooling(poolingEnabled); + public bool GetPooling() => _sessionPool.GetPooling(); + } +} diff --git a/Snowflake.Data/Core/Session/IConnectionManager.cs b/Snowflake.Data/Core/Session/IConnectionManager.cs new file mode 100644 index 000000000..e72ade2e7 --- /dev/null +++ b/Snowflake.Data/Core/Session/IConnectionManager.cs @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. + */ + +using System.Security; +using System.Threading; +using System.Threading.Tasks; + +namespace Snowflake.Data.Core.Session +{ + internal interface IConnectionManager + { + SFSession GetSession(string connectionString, SecureString password); + Task GetSessionAsync(string connectionString, SecureString password, CancellationToken cancellationToken); + bool AddSession(SFSession session); + void ClearAllPools(); + void SetMaxPoolSize(int maxPoolSize); + int GetMaxPoolSize(); + void SetTimeout(long connectionTimeout); + long GetTimeout(); + int GetCurrentPoolSize(); + bool SetPooling(bool poolingEnabled); + bool GetPooling(); + } +} diff --git a/Snowflake.Data/Core/Session/SessionPool.cs b/Snowflake.Data/Core/Session/SessionPool.cs index 6f9dd58dd..5013da99f 100644 --- a/Snowflake.Data/Core/Session/SessionPool.cs +++ b/Snowflake.Data/Core/Session/SessionPool.cs @@ -1,3 +1,7 @@ +/* + * Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. + */ + using System; using System.Collections.Generic; using System.Linq; @@ -9,29 +13,28 @@ namespace Snowflake.Data.Core.Session { - sealed class SessionPoolSingleton : IDisposable + sealed class SessionPool : IDisposable { - private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); - private static SessionPoolSingleton s_instance = null; + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); private static readonly object s_sessionPoolLock = new object(); - - private readonly List _sessionPool; + private readonly List _sessions; private int _maxPoolSize; private long _timeout; private const int MaxPoolSize = 10; private const long Timeout = 3600; private bool _pooling = true; - SessionPoolSingleton() + internal SessionPool() { lock (s_sessionPoolLock) { - _sessionPool = new List(); + _sessions = new List(); _maxPoolSize = MaxPoolSize; _timeout = Timeout; } } - ~SessionPoolSingleton() + + ~SessionPool() { ClearAllPools(); } @@ -41,21 +44,6 @@ public void Dispose() ClearAllPools(); } - public static SessionPoolSingleton Instance - { - get - { - lock (s_sessionPoolLock) - { - if(s_instance == null) - { - s_instance = new SessionPoolSingleton(); - } - return s_instance; - } - } - } - private void CleanExpiredSessions() { s_logger.Debug("SessionPool::CleanExpiredSessions"); @@ -63,11 +51,11 @@ private void CleanExpiredSessions() { long timeNow = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - foreach (var item in _sessionPool.ToList()) + foreach (var item in _sessions.ToList()) { if (item.IsExpired(_timeout, timeNow)) { - _sessionPool.Remove(item); + _sessions.Remove(item); item.close(); } } @@ -97,12 +85,12 @@ private SFSession GetIdleSession(string connStr) s_logger.Debug("SessionPool::GetIdleSession"); lock (s_sessionPoolLock) { - for (int i = 0; i < _sessionPool.Count; i++) + for (int i = 0; i < _sessions.Count; i++) { - if (_sessionPool[i].connStr.Equals(connStr)) + if (_sessions[i].connStr.Equals(connStr)) { - SFSession session = _sessionPool[i]; - _sessionPool.RemoveAt(i); + SFSession session = _sessions[i]; + _sessions.RemoveAt(i); long timeNow = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); if (session.IsExpired(_timeout, timeNow)) { @@ -174,18 +162,18 @@ internal bool AddSession(SFSession session) lock (s_sessionPoolLock) { - if (_sessionPool.Count >= _maxPoolSize) + if (_sessions.Count >= _maxPoolSize) { CleanExpiredSessions(); } - if (_sessionPool.Count >= _maxPoolSize) + if (_sessions.Count >= _maxPoolSize) { // pool is full return false; } s_logger.Debug($"pool connection with sid {session.sessionId}"); - _sessionPool.Add(session); + _sessions.Add(session); return true; } } @@ -195,11 +183,11 @@ internal void ClearAllPools() s_logger.Debug("SessionPool::ClearAllPools"); lock (s_sessionPoolLock) { - foreach (SFSession session in _sessionPool) + foreach (SFSession session in _sessions) { session.close(); } - _sessionPool.Clear(); + _sessions.Clear(); } } @@ -225,7 +213,7 @@ public long GetTimeout() public int GetCurrentPoolSize() { - return _sessionPool.Count; + return _sessions.Count; } public bool SetPooling(bool isEnable) @@ -246,5 +234,4 @@ public bool GetPooling() return _pooling; } } - -} \ No newline at end of file +} From e954f95ba403dff45caeaa0bbdf5d91a5118c14d Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf <115584722+sfc-gh-ext-simba-lf@users.noreply.github.com> Date: Wed, 18 Oct 2023 09:03:59 -0700 Subject: [PATCH 4/6] SNOW-723810: Add coverage for RestRequest (#784) ### Description Regarding issue 113 The PR adds tests for RestRequest to increase code coverage ### Checklist - [x] Code compiles correctly - [x] Code is formatted according to [Coding Conventions](../CodingConventions.md) - [x] Created tests which fail without the change (if possible) - [x] All tests passing (`dotnet test`) - [x] Extended the README / documentation, if necessary - [x] Provide JIRA issue id (if possible) or GitHub issue id in PR name --- Snowflake.Data.Tests/UnitTests/SFOktaTest.cs | 57 ++++++++++++++++++++ Snowflake.Data/Core/RestRequest.cs | 2 +- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/Snowflake.Data.Tests/UnitTests/SFOktaTest.cs b/Snowflake.Data.Tests/UnitTests/SFOktaTest.cs index 81b958c81..5de0c6c06 100644 --- a/Snowflake.Data.Tests/UnitTests/SFOktaTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFOktaTest.cs @@ -69,5 +69,62 @@ public void TestWrongPostbackUrl() Assert.AreEqual(SFError.IDP_SAML_POSTBACK_INVALID.GetAttribute().errorCode, e.ErrorCode); } } + + [Test] + public void TestLoginRequestToString() + { + // Arrange + string expectedOktaAccount = "mockOktaAccount"; + string expectedOktaUser = "mockOktaUser"; + string expectedOktaUrl = "mockOktaUrl"; + + LoginRequestClientEnv loginRequestClientEnv = new LoginRequestClientEnv(); + + // Act + LoginRequest loginRequest = new LoginRequest() + { + data = new LoginRequestData() + { + loginName = expectedOktaUser, + accountName = expectedOktaAccount, + clientAppVersion = SFEnvironment.DriverVersion, + clientEnv = loginRequestClientEnv, + Authenticator = expectedOktaUrl, + } + }; + + // Assert + Assert.AreEqual($"LoginRequest {{data: LoginRequestData {{ClientAppVersion: {SFEnvironment.DriverVersion},\n " + + $"AccountName: {expectedOktaAccount},\n " + + $"loginName: {expectedOktaUser},\n " + + $"ClientEnv: {{ " + + $"APPLICATION: , " + + $"OS_VERSION: , " + + $"NET_RUNTIME: , " + + $"NET_VERSION: , " + + $"INSECURE_MODE: }},\n " + + $"authenticator: {expectedOktaUrl} }} }}", + loginRequest.ToString()); + } + + [Test] + public void TestAuthenticatorRequestToString() + { + // Arrange + string expectedOktaAccount = "mockOktaAccount"; + + // Act + AuthenticatorRequest authenticatorRequest = new AuthenticatorRequest() + { + Data = new AuthenticatorRequestData() + { + AccountName = expectedOktaAccount, + } + }; + + // Assert + Assert.AreEqual($"AuthenticatorRequest {{data: AuthenticatorRequestData {{ACCOUNT_NAME: {expectedOktaAccount} }} }}", + authenticatorRequest.ToString()); + } } } diff --git a/Snowflake.Data/Core/RestRequest.cs b/Snowflake.Data/Core/RestRequest.cs index 49a53bc04..77a88323b 100644 --- a/Snowflake.Data/Core/RestRequest.cs +++ b/Snowflake.Data/Core/RestRequest.cs @@ -198,7 +198,7 @@ class AuthenticatorRequestData public override string ToString() { - return String.Format("AuthenticatorRequestData {{ACCOUNT_NANM: {0} }}", + return String.Format("AuthenticatorRequestData {{ACCOUNT_NAME: {0} }}", AccountName.ToString()); } } From 459b170cf3b152032f40bfe3124a94a690a4970b Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf <115584722+sfc-gh-ext-simba-lf@users.noreply.github.com> Date: Wed, 18 Oct 2023 10:32:57 -0700 Subject: [PATCH 5/6] SNOW-723810: Add coverage for StorageClient (#785) ### Description Regarding issue 113 The PR removes unused code in the StorageClient ### Checklist - [x] Code compiles correctly - [x] Code is formatted according to [Coding Conventions](../CodingConventions.md) - [x] Created tests which fail without the change (if possible) - [x] All tests passing (`dotnet test`) - [x] Extended the README / documentation, if necessary - [x] Provide JIRA issue id (if possible) or GitHub issue id in PR name --- .../StorageClient/SFStorageClientUtil.cs | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 Snowflake.Data/Core/FileTransfer/StorageClient/SFStorageClientUtil.cs diff --git a/Snowflake.Data/Core/FileTransfer/StorageClient/SFStorageClientUtil.cs b/Snowflake.Data/Core/FileTransfer/StorageClient/SFStorageClientUtil.cs deleted file mode 100644 index c7d474080..000000000 --- a/Snowflake.Data/Core/FileTransfer/StorageClient/SFStorageClientUtil.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Snowflake.Data.Core.FileTransfer.StorageClient -{ - class SFStorageClientUtil - { - /// - /// The bad request error code. - /// - public static readonly string BAD_REQUEST_ERR = "400"; - - /// - /// The unauthorized error code. - /// - public static readonly string UNAUTHORIZED_ERR = "401"; - - /// - /// The forbidden error code. - /// - public static readonly string FORBIDDEN_ERR = "403"; - - /// - /// The not found error code. - /// - public static readonly string NOT_FOUND_ERR = "404"; - - /// - /// The internal server error code. - /// - public static readonly string INTERNAL_SERVER_ERR = "500"; - - /// - /// The server unavailable error code. - /// - public static readonly string SERVER_UNAVAILABLE_ERR = "503"; - } -} From febed63d5baff74dd9c05db4561a30bca3b14c91 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf <115584722+sfc-gh-ext-simba-lf@users.noreply.github.com> Date: Thu, 19 Oct 2023 09:28:53 -0700 Subject: [PATCH 6/6] SNOW-723810: Add coverage for SnowflakeDb classes (#786) ### Description Regarding issue 113 The PR adds tests for SnowflakeDb classes to increase code coverage ### Checklist - [x] Code compiles correctly - [x] Code is formatted according to [Coding Conventions](../CodingConventions.md) - [x] Created tests which fail without the change (if possible) - [x] All tests passing (`dotnet test`) - [x] Extended the README / documentation, if necessary - [x] Provide JIRA issue id (if possible) or GitHub issue id in PR name --- .../IntegrationTests/SFDbDataReaderIT.cs | 116 ++++++++++++++++++ .../IntegrationTests/SFDbTransactionIT.cs | 36 ++++++ .../UnitTests/SFDbCommandTest.cs | 43 +++++++ 3 files changed, 195 insertions(+) create mode 100644 Snowflake.Data.Tests/UnitTests/SFDbCommandTest.cs diff --git a/Snowflake.Data.Tests/IntegrationTests/SFDbDataReaderIT.cs b/Snowflake.Data.Tests/IntegrationTests/SFDbDataReaderIT.cs index f2613acf6..11d08e721 100755 --- a/Snowflake.Data.Tests/IntegrationTests/SFDbDataReaderIT.cs +++ b/Snowflake.Data.Tests/IntegrationTests/SFDbDataReaderIT.cs @@ -594,6 +594,47 @@ public void testGetBoolean() } } + [Test] + public void testGetByte() + { + using (IDbConnection conn = new SnowflakeDbConnection()) + { + // Arrange + conn.ConnectionString = ConnectionString; + conn.Open(); + + CreateOrReplaceTable(conn, TableName, new[] + { + "col1 BINARY", + }); + + byte[] testBytes = Encoding.UTF8.GetBytes("TEST_GET_BINARAY"); + + IDbCommand cmd = conn.CreateCommand(); + + var p1 = cmd.CreateParameter(); + p1.ParameterName = "1"; + p1.DbType = DbType.Binary; + p1.Value = testBytes; + + cmd.Parameters.Add(p1); + cmd.CommandText = $"insert into {TableName} values (?)"; + cmd.ExecuteNonQuery(); + cmd.CommandText = $"select * from {TableName}"; + + // Act + using (IDataReader reader = cmd.ExecuteReader()) + { + int index = 0; + while (reader.Read()) + { + // Assert + Assert.AreEqual(testBytes[index++], reader.GetByte(0)); + } + } + } + } + [Test] public void testGetBinary() { @@ -737,6 +778,37 @@ public void testGetBinary() } } + [Test] + public void testGetChar() + { + using (IDbConnection conn = new SnowflakeDbConnection()) + { + // Arrange + conn.ConnectionString = ConnectionString; + conn.Open(); + + CreateOrReplaceTable(conn, TableName, new[] + { + "col1 VARCHAR(50)", + }); + + char testChar = 'T'; + + IDbCommand cmd = conn.CreateCommand(); + cmd.CommandText = $"insert into {TableName} values ('{testChar}')"; + cmd.ExecuteNonQuery(); + cmd.CommandText = $"select * from {TableName}"; + + // Act + using (IDataReader reader = cmd.ExecuteReader()) + { + // Assert + Assert.IsTrue(reader.Read()); + Assert.AreEqual(testChar, reader.GetChar(0)); + } + } + } + [Test] public void testGetChars() { @@ -882,6 +954,50 @@ public void testGetChars() } } + [Test] + public void testGetDataTypeName() + { + using (IDbConnection conn = new SnowflakeDbConnection()) + { + // Arrange + conn.ConnectionString = ConnectionString; + conn.Open(); + + CreateOrReplaceTable(conn, TableName, new[] + { + "col1 VARCHAR(50)", + "col2 BINARY", + "col3 DOUBLE" + }); + + string testChars = "TEST_GET_CHARS"; + byte[] testBytes = Encoding.UTF8.GetBytes("TEST_GET_BINARY"); + double testDouble = 1.2345678; + + IDbCommand cmd = conn.CreateCommand(); + + var p1 = cmd.CreateParameter(); + p1.ParameterName = "1"; + p1.DbType = DbType.Binary; + p1.Value = testBytes; + + cmd.Parameters.Add(p1); + cmd.CommandText = $"insert into {TableName} values ('{testChars}', ?, {testDouble.ToString()})"; + cmd.ExecuteNonQuery(); + cmd.CommandText = $"select * from {TableName}"; + + // Act + using (DbDataReader reader = (DbDataReader)cmd.ExecuteReader()) + { + // Assert + Assert.IsTrue(reader.Read()); + Assert.AreEqual("TEXT", reader.GetDataTypeName(0)); + Assert.AreEqual("BINARY", reader.GetDataTypeName(1)); + Assert.AreEqual("REAL", reader.GetDataTypeName(2)); + } + } + } + [Test] public void testGetStream() { diff --git a/Snowflake.Data.Tests/IntegrationTests/SFDbTransactionIT.cs b/Snowflake.Data.Tests/IntegrationTests/SFDbTransactionIT.cs index c0b571f98..2786d8226 100644 --- a/Snowflake.Data.Tests/IntegrationTests/SFDbTransactionIT.cs +++ b/Snowflake.Data.Tests/IntegrationTests/SFDbTransactionIT.cs @@ -15,6 +15,42 @@ namespace Snowflake.Data.Tests.IntegrationTests [TestFixture] class SFDbTransactionIT : SFBaseTest { + [Test] + public void TestTransactionDbConnection() + { + using (var conn = new SnowflakeDbConnection()) + { + // Arrange + conn.ConnectionString = ConnectionString; + conn.Open(); + + // Act + using (IDbTransaction t1 = conn.BeginTransaction()) + { + // Assert + Assert.AreEqual(conn, t1.Connection); + } + } + } + + [Test] + public void TestTransactionIsolationLevel() + { + using (var conn = new SnowflakeDbConnection()) + { + // Arrange + conn.ConnectionString = ConnectionString; + conn.Open(); + + // Act + using (IDbTransaction t1 = conn.BeginTransaction()) + { + // Assert + Assert.AreEqual(IsolationLevel.ReadCommitted, t1.IsolationLevel); + } + } + } + [Test] // Test that when a transaction is disposed, rollback would be sent out public void TestTransactionDispose() diff --git a/Snowflake.Data.Tests/UnitTests/SFDbCommandTest.cs b/Snowflake.Data.Tests/UnitTests/SFDbCommandTest.cs new file mode 100644 index 000000000..e4350cd61 --- /dev/null +++ b/Snowflake.Data.Tests/UnitTests/SFDbCommandTest.cs @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. + */ + +namespace Snowflake.Data.Tests.UnitTests +{ + using NUnit.Framework; + using Snowflake.Data.Client; + using System; + + [TestFixture] + class SFDbCommandTest + { + SnowflakeDbCommand command; + + [SetUp] + public void BeforeTest() + { + command = new SnowflakeDbCommand(); + } + + [Test] + public void TestCommandWithConnectionAndCommandText() + { + // Arrange + SnowflakeDbConnection conn = new SnowflakeDbConnection(); + string commandText = "select 1"; + + // Act + command = new SnowflakeDbCommand(conn, commandText); + + // Assert + Assert.AreEqual(conn, command.Connection); + Assert.AreEqual(commandText, command.CommandText); + } + + [Test] + public void TestCommandPrepareThrowsNotImplemented() + { + Assert.Throws(() => command.Prepare()); + } + } +}