Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pool/SNOW-937188 changed session behavior #937

Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e727d1a
SNOW-937188 pool mode where a session gets destroyed on pooling when …
sfc-gh-mhofman Apr 26, 2024
afe21b9
SNOW-1353952 GetPool interface driven with user/password
sfc-gh-mhofman Apr 29, 2024
e84b33c
SNOW-937188 review fixes
sfc-gh-mhofman Apr 29, 2024
0429a48
Merge branch 'pool/SNOW-1353952-getpool_interface' into pool/SNOW-937…
sfc-gh-mhofman Apr 29, 2024
5e4ef69
SNOW-937188 introduction of full API for new pool
sfc-gh-mhofman Apr 29, 2024
7a9d811
SNOW-937188 changed default of ChangedSession flag, changes to public…
sfc-gh-mhofman Apr 30, 2024
a77e6e8
SNOW-937188 adoption of new pool interfaces among tests
sfc-gh-mhofman Apr 30, 2024
98dd1f9
SNOW-937188 adoption of new pool interfaces among tests
sfc-gh-mhofman Apr 30, 2024
fdc027f
SNOW-937188 review suggestions
sfc-gh-mhofman May 6, 2024
3ac0204
Merge branch 'pool/SNOW-860872-connection-pool' into pool/SNOW-937188…
sfc-gh-mhofman May 7, 2024
59013fa
make build work again
sfc-gh-knozderko May 7, 2024
a73b38d
fix review comments
sfc-gh-knozderko May 7, 2024
2849369
SNOW-937188 fixes to the unquoting and comparison of final session re…
sfc-gh-mhofman May 7, 2024
d705c21
SNOW-937188 fixes to the unquoting
sfc-gh-mhofman May 7, 2024
3cad13b
SNOW-937188 fixes to the unquoting
sfc-gh-mhofman May 7, 2024
6d6cc81
throw error when getting pool for invalid connection string
sfc-gh-knozderko May 7, 2024
6468c82
remove test for pool identification for invalid connection string
sfc-gh-knozderko May 8, 2024
459ead6
fix issues
sfc-gh-knozderko May 8, 2024
ca5efb5
SNOW-937188 fixes to returning a pool for connection string (and alte…
sfc-gh-mhofman May 8, 2024
04633e2
SNOW-938188 validation of session properties for protecting unauthori…
sfc-gh-mhofman May 9, 2024
caf1eed
SNOW-937188 fixes for KeyPairAuth params validation
sfc-gh-mhofman May 9, 2024
1963f28
SNOW-937188 fixes to Authenticator validation (moved validation from …
sfc-gh-mhofman May 9, 2024
191bacc
SNOW-937188 connection string validation fixes
sfc-gh-mhofman May 9, 2024
b8e3634
SNOW-937188 fixes to testing failures after moving validation to sess…
sfc-gh-mhofman May 9, 2024
bcce8ef
SNOW-937188 added test to cover pool retrieval not working for invali…
sfc-gh-mhofman May 9, 2024
280e00b
SNOW-937188 fixed tests with default timeouts + review fix about none…
sfc-gh-mhofman May 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions Snowflake.Data.Tests/IntegrationTests/ConnectionChangedSessionIT.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
using NUnit.Framework;
using Snowflake.Data.Client;
using Snowflake.Data.Core;
using Snowflake.Data.Core.Session;
using Snowflake.Data.Tests.Util;

namespace Snowflake.Data.Tests.IntegrationTests
{
[TestFixture]
[NonParallelizable]
public class ConnectionChangedSessionIT : SFBaseTest
sfc-gh-mhofman marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly QueryExecResponseData _queryExecResponseChangedRole = new()
{
finalDatabaseName = TestEnvironment.TestConfig.database,
finalSchemaName = TestEnvironment.TestConfig.schema,
finalRoleName = "role change",
finalWarehouseName = TestEnvironment.TestConfig.warehouse
};

private readonly QueryExecResponseData _queryExecResponseChangedDatabase = new()
{
finalDatabaseName = "database changed",
finalSchemaName = TestEnvironment.TestConfig.schema,
finalRoleName = TestEnvironment.TestConfig.role,
finalWarehouseName = TestEnvironment.TestConfig.warehouse
};

private readonly QueryExecResponseData _queryExecResponseChangedSchema = new()
{
finalDatabaseName = TestEnvironment.TestConfig.database,
finalSchemaName = "schema changed",
finalRoleName = TestEnvironment.TestConfig.role,
finalWarehouseName = TestEnvironment.TestConfig.warehouse
};

private readonly QueryExecResponseData _queryExecResponseChangedWarehouse = new()
{
finalDatabaseName = TestEnvironment.TestConfig.database,
finalSchemaName = TestEnvironment.TestConfig.schema,
finalRoleName = TestEnvironment.TestConfig.role,
finalWarehouseName = "warehouse changed"
};

private static PoolConfig s_previousPoolConfigRestorer;

[OneTimeSetUp]
public static void BeforeAllTests()
{
s_previousPoolConfigRestorer = new PoolConfig();
SnowflakeDbConnectionPool.SetConnectionPoolVersion(ConnectionPoolType.MultipleConnectionPool);
}

[SetUp]
public new void BeforeTest()
{
SnowflakeDbConnectionPool.ClearAllPools();
}

[TearDown]
public new void AfterTest()
{
SnowflakeDbConnectionPool.ClearAllPools();
}

[OneTimeTearDown]
public static void AfterAllTests()
{
s_previousPoolConfigRestorer.Reset();
}

[Test]
public void TestPoolDestroysConnectionWhenChangedSessionProperties()
{
var connectionString = ConnectionString + "application=Destroy;ChangedSession=Destroy;minPoolSize=0;maxPoolSize=3";
var pool = SnowflakeDbConnectionPool.GetPool(connectionString);

var connection = new SnowflakeDbConnection(connectionString);
connection.Open();
connection.SfSession.UpdateSessionProperties(_queryExecResponseChangedDatabase);
connection.Close();

Assert.AreEqual(0, pool.GetCurrentPoolSize());
}

[Test]
public void TestPoolingWhenSessionPropertiesUnchanged()
{
var connectionString = ConnectionString + "application=NoSessionChanges;ChangedSession=Destroy;minPoolSize=0;maxPoolSize=3";
var pool = SnowflakeDbConnectionPool.GetPool(connectionString);

var connection = new SnowflakeDbConnection(connectionString);
connection.Open();
connection.Close();

Assert.AreEqual(1, pool.GetCurrentPoolSize());
}

[Test]
public void TestPoolingWhenConnectionPropertiesChangedForOriginalPoolMode()
{
var connectionString = ConnectionString + "application=OriginalPoolMode;ChangedSession=OriginalPool;minPoolSize=0;maxPoolSize=3";
var pool = SnowflakeDbConnectionPool.GetPool(connectionString);

var connection = new SnowflakeDbConnection(connectionString);
connection.Open();
connection.SfSession.UpdateSessionProperties(_queryExecResponseChangedWarehouse);
var sessionId = connection.SfSession.sessionId;
connection.Close();

Assert.AreEqual(1, pool.GetCurrentPoolSize());
connection.Close();

var connection2 = new SnowflakeDbConnection(connectionString);
connection2.Open();
Assert.AreEqual(sessionId, connection2.SfSession.sessionId);
connection2.Close();
}

[Test]
public void TestPoolingWhenConnectionPropertiesChangedForDefaultPoolMode()
{
var connectionString = ConnectionString + "application=DefaultPoolMode;minPoolSize=0;maxPoolSize=3";
var pool = SnowflakeDbConnectionPool.GetPool(connectionString);

var connection = new SnowflakeDbConnection(connectionString);
connection.Open();
connection.SfSession.UpdateSessionProperties(_queryExecResponseChangedRole);
var sessionId = connection.SfSession.sessionId;
connection.Close();

Assert.AreEqual(0, pool.GetCurrentPoolSize());

var connection2 = new SnowflakeDbConnection(connectionString);
connection2.Open();
Assert.AreNotEqual(sessionId, connection2.SfSession.sessionId);
connection2.Close();
}

[Test]
public void TestPoolDestroysAndRecreatesConnection()
{
var connectionString = ConnectionString + "application=DestroyRecreateSession;ChangedSession=Destroy;minPoolSize=1;maxPoolSize=3";

var connection = new SnowflakeDbConnection(connectionString);
connection.Open();
var sessionId = connection.SfSession.sessionId;
connection.SfSession.UpdateSessionProperties(_queryExecResponseChangedSchema);
connection.Close();

var pool = SnowflakeDbConnectionPool.GetPool(connectionString);
Assert.AreEqual(1, pool.GetCurrentPoolSize());

var connection2 = new SnowflakeDbConnection(connectionString);
connection2.Open();
Assert.AreNotEqual(sessionId, connection2.SfSession.sessionId);
connection2.Close();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public void TestWaitInAQueueForAnIdleSession()
{
// arrange
var connectionString = ConnectionString + "application=TestWaitForMaxSize3;waitingForIdleSessionTimeout=3s;maxPoolSize=2;minPoolSize=0";
var pool = SnowflakeDbConnectionPool.GetPool(connectionString);
var pool = SnowflakeDbConnectionPool.GetPoolInternal(connectionString);
Assert.AreEqual(0, pool.GetCurrentPoolSize(), "the pool is expected to be empty");
const long ADelay = 0;
const long BDelay = 400;
Expand Down Expand Up @@ -262,8 +262,7 @@ public void TestConnectionPoolNotPossibleToDisableForAllPools()
public void TestConnectionPoolDisable()
{
// arrange
var pool = SnowflakeDbConnectionPool.GetPool(ConnectionString);
pool.SetPooling(false);
var pool = SnowflakeDbConnectionPool.GetPool(ConnectionString + ";poolingEnabled=false");
var conn1 = new SnowflakeDbConnection();
conn1.ConnectionString = ConnectionString;

Expand Down Expand Up @@ -322,7 +321,7 @@ public void TestConnectionPoolExpirationWorks()
// arrange
const int ExpirationTimeoutInSeconds = 10;
var connectionString = ConnectionString + $"expirationTimeout={ExpirationTimeoutInSeconds};maxPoolSize=4;minPoolSize=2";
var pool = SnowflakeDbConnectionPool.GetPool(connectionString);
var pool = SnowflakeDbConnectionPool.GetPoolInternal(connectionString);
Assert.AreEqual(0, pool.GetCurrentPoolSize());

// act
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ public void TestConnectionPoolDisable()
{
// arrange
var pool = SnowflakeDbConnectionPool.GetPool(ConnectionString);
pool.SetPooling(false);
SnowflakeDbConnectionPool.SetPooling(false);
var conn1 = new SnowflakeDbConnection();
conn1.ConnectionString = ConnectionString;

Expand Down
58 changes: 45 additions & 13 deletions Snowflake.Data.Tests/UnitTests/SFSessionTest.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
/*
* Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved.
* Copyright (c) 2012-2024 Snowflake Computing Inc. All rights reserved.
*/

using Snowflake.Data.Configuration;
using Snowflake.Data.Log;
using Snowflake.Data.Core;
using NUnit.Framework;

namespace Snowflake.Data.Tests.UnitTests
{
using Snowflake.Data.Core;
using NUnit.Framework;

[TestFixture]
class SFSessionTest
{
Expand All @@ -20,26 +17,61 @@ public void TestSessionGoneWhenClose()
Mock.MockCloseSessionGone restRequester = new Mock.MockCloseSessionGone();
SFSession sfSession = new SFSession("account=test;user=test;password=test", null, restRequester);
sfSession.Open();
sfSession.close(); // no exception is raised.
Assert.DoesNotThrow(() => sfSession.close());
}

[Test]
public void TestUpdateDatabaseAndSchema()
public void TestUpdateSessionProperties()
{
// arrange
string databaseName = "DB_TEST";
string schemaName = "SC_TEST";

string warehouseName = "WH_TEST";
string roleName = "ROLE_TEST";
QueryExecResponseData queryExecResponseData = new QueryExecResponseData
{
finalSchemaName = schemaName,
finalDatabaseName = databaseName,
finalRoleName = roleName,
finalWarehouseName = warehouseName
};

// act
SFSession sfSession = new SFSession("account=test;user=test;password=test", null);
sfSession.UpdateDatabaseAndSchema(databaseName, schemaName);
sfSession.UpdateSessionProperties(queryExecResponseData);

// assert
Assert.AreEqual(databaseName, sfSession.database);
Assert.AreEqual(schemaName, sfSession.schema);
Assert.AreEqual(warehouseName, sfSession.warehouse);
Assert.AreEqual(roleName, sfSession.role);
}

[Test]
public void TestSkipUpdateSessionPropertiesWhenPropertiesMissing()
{
// arrange
string databaseName = "DB_TEST";
string schemaName = "SC_TEST";
string warehouseName = "WH_TEST";
string roleName = "ROLE_TEST";
SFSession sfSession = new SFSession("account=test;user=test;password=test", null);
sfSession.database = databaseName;
sfSession.warehouse = warehouseName;
sfSession.role = roleName;
sfSession.schema = schemaName;

// act
QueryExecResponseData queryExecResponseWithoutData = new QueryExecResponseData();
sfSession.UpdateSessionProperties(queryExecResponseWithoutData);

// assert
// 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);
Assert.AreEqual(warehouseName, sfSession.warehouse);
Assert.AreEqual(roleName, sfSession.role);
}

[Test]
Expand All @@ -54,10 +86,10 @@ public void TestThatConfiguresEasyLogging(string configPath)
var connectionString = configPath == null
? simpleConnectionString
: $"{simpleConnectionString}client_config_file={configPath};";

// act
new SFSession(connectionString, null, easyLoggingStarter.Object);

// assert
easyLoggingStarter.Verify(starter => starter.Init(configPath));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ public void TestRevertPoolToPreviousVersion()
{
// act
SnowflakeDbConnectionPool.SetOldConnectionPoolVersion();

// assert
var sessionPool1 = SnowflakeDbConnectionPool.GetPool(_connectionString1);
var sessionPool2 = SnowflakeDbConnectionPool.GetPool(_connectionString2);
var sessionPool1 = SnowflakeDbConnectionPool.GetPoolInternal(_connectionString1);
var sessionPool2 = SnowflakeDbConnectionPool.GetPoolInternal(_connectionString2);
Assert.AreEqual(ConnectionPoolType.SingleConnectionCache, SnowflakeDbConnectionPool.GetConnectionPoolVersion());
Assert.AreEqual(sessionPool1, sessionPool2);
}
Expand Down
30 changes: 15 additions & 15 deletions Snowflake.Data/Client/SnowflakeDbConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ public class SnowflakeDbConnection : DbConnection
{
private SFLogger logger = SFLoggerFactory.GetLogger<SnowflakeDbConnection>();

internal SFSession SfSession { get; set; }
internal SFSession SfSession { get; set; }
sfc-gh-mhofman marked this conversation as resolved.
Show resolved Hide resolved

internal ConnectionState _connectionState;

protected override DbProviderFactory DbProviderFactory => new SnowflakeDbFactory();

internal int _connectionTimeout;

private bool _disposed = false;
Expand All @@ -47,7 +47,7 @@ protected enum TransactionRollbackStatus
public SnowflakeDbConnection()
{
_connectionState = ConnectionState.Closed;
_connectionTimeout =
_connectionTimeout =
int.Parse(SFSessionProperty.CONNECTION_TIMEOUT.GetAttribute<SFSessionPropertyAttr>().
defaultValue);
_isArrayBindStageCreated = false;
Expand Down Expand Up @@ -84,12 +84,12 @@ private bool IsNonClosedWithSession()
public override int ConnectionTimeout => this._connectionTimeout;

/// <summary>
/// If the connection to the database is closed, the DataSource returns whatever is contained
/// in the ConnectionString for the DataSource keyword. If the connection is open and the
/// ConnectionString data source keyword's value starts with "|datadirectory|", the property
/// returns whatever is contained in the ConnectionString for the DataSource keyword only. If
/// the connection to the database is open, the property returns what the native provider
/// returns for the DBPROP_INIT_DATASOURCE, and if that is empty, the native provider's
/// If the connection to the database is closed, the DataSource returns whatever is contained
/// in the ConnectionString for the DataSource keyword. If the connection is open and the
/// ConnectionString data source keyword's value starts with "|datadirectory|", the property
/// returns whatever is contained in the ConnectionString for the DataSource keyword only. If
/// the connection to the database is open, the property returns what the native provider
/// returns for the DBPROP_INIT_DATASOURCE, and if that is empty, the native provider's
/// DBPROP_DATASOURCENAME is returned.
/// Note: not yet implemented
/// </summary>
Expand All @@ -115,7 +115,7 @@ public void PreventPooling()
SfSession.SetPooling(false);
logger.Debug($"Session {SfSession.sessionId} marked not to be pooled any more");
}

internal bool HasActiveExplicitTransaction() => ExplicitTransaction != null && ExplicitTransaction.IsActive;

private bool TryToReturnSessionToPool()
Expand Down Expand Up @@ -150,12 +150,12 @@ private TransactionRollbackStatus TerminateTransactionForDirtyConnectionReturnin
// error to indicate a problem within application code that a connection was closed while still having a pending transaction
logger.Error("Closing dirty connection: rollback transaction in session " + SfSession.sessionId + " succeeded.");
ExplicitTransaction = null;
return TransactionRollbackStatus.Success;
return TransactionRollbackStatus.Success;
}
}
catch (Exception exception)
{
// error to indicate a problem with rollback of an active transaction and inability to return dirty connection to the pool
// error to indicate a problem with rollback of an active transaction and inability to return dirty connection to the pool
logger.Error("Closing dirty connection: rollback transaction in session: " + SfSession.sessionId + " failed, exception: " + exception.Message);
return TransactionRollbackStatus.Failure; // connection won't be pooled
}
Expand Down Expand Up @@ -254,10 +254,10 @@ await SfSession.CloseAsync(cancellationToken).ContinueWith(

protected virtual bool CanReuseSession(TransactionRollbackStatus transactionRollbackStatus)
{
return SnowflakeDbConnectionPool.GetPooling() &&
return SnowflakeDbConnectionPool.GetPooling() &&
transactionRollbackStatus == TransactionRollbackStatus.Success;
}

public override void Open()
{
logger.Debug("Open Connection.");
Expand Down Expand Up @@ -401,7 +401,7 @@ protected override void Dispose(bool disposing)
SfSession = null;
_connectionState = ConnectionState.Closed;
}

_disposed = true;
}

Expand Down
Loading
Loading