From 14cf8a5daaf9611b237eb4b51ccdd61cb44761d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joanna=20Siemi=C5=84ska?= Date: Fri, 19 Apr 2024 08:13:05 +0200 Subject: [PATCH] SNOW-834812 Adding connection parameter QUERY_TAG. (#916) ### Description Adding connection parameter QUERY_TAG. ### Checklist - [x] Code compiles correctly - [x] Code is formatted according to [Coding Conventions](../blob/master/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 --- README.md | 5 +-- .../IntegrationTests/SFConnectionIT.cs | 18 ++++++++++ .../UnitTests/SFSessionPropertyTest.cs | 35 ++++++++++++++++++- Snowflake.Data/Core/SFStatement.cs | 18 ++++++++-- Snowflake.Data/Core/Session/SFSession.cs | 3 ++ .../Core/Session/SFSessionProperty.cs | 4 ++- 6 files changed, 77 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7c7e65356..263de2c0f 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ The following table lists all valid connection properties:
| Connection Property | Required | Comment | -| ------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +|--------------------------------| -------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ACCOUNT | Yes | Your full account name might include additional segments that identify the region and cloud platform where your account is hosted | | APPLICATION | No | **_Snowflake partner use only_**: Specifies the name of a partner application to connect through .NET. The name must match the following pattern: ^\[A-Za-z](\[A-Za-z0-9.-]){1,50}$ (one letter followed by 1 to 50 letter, digit, .,- or, \_ characters). | | DB | No | | @@ -163,10 +163,11 @@ The following table lists all valid connection properties: | PROXYPORT | Depends | The port number of the proxy server.

If USEPROXY is set to `true`, you must set this parameter.

This parameter was introduced in v2.0.4. | | PROXYUSER | No | The username for authenticating to the proxy server.

This parameter was introduced in v2.0.4. | | PROXYPASSWORD | Depends | The password for authenticating to the proxy server.

If USEPROXY is `true` and PROXYUSER is set, you must set this parameter.

This parameter was introduced in v2.0.4. | -| NONPROXYHOSTS | No | The list of hosts that the driver should connect to directly, bypassing the proxy server. Separate the hostnames with a pipe symbol (\|). You can also use an asterisk (`*`) as a wildcard.
The host target value should fully match with any item from the proxy host list to bypass the proxy server.

This parameter was introduced in v2.0.4. | +| NONPROXYHOSTS | No | The list of hosts that the driver should connect to directly, bypassing the proxy server. Separate the hostnames with a pipe symbol (\|). You can also use an asterisk (`*`) as a wildcard.
The host target value should fully match with any item from the proxy host list to bypass the proxy server.

This parameter was introduced in v2.0.4. | | FILE_TRANSFER_MEMORY_THRESHOLD | No | The maximum number of bytes to store in memory used in order to provide a file encryption. If encrypting/decrypting file size exceeds provided value a temporary file will be created and the work will be continued in the temporary file instead of memory.
If no value provided 1MB will be used as a default value (that is 1048576 bytes).
It is possible to configure any integer value bigger than zero representing maximal number of bytes to reside in memory. | | CLIENT_CONFIG_FILE | No | The location of the client configuration json file. In this file you can configure easy logging feature. | | ALLOWUNDERSCORESINHOST | No | Specifies whether to allow underscores in account names. This impacts PrivateLink customers whose account names contain underscores. In this situation, you must override the default value by setting allowUnderscoresInHost to true. | +| QUERY_TAG | No | Optional string that can be used to tag queries and other SQL statements executed within a connection. The tags are displayed in the output of the QUERY_HISTORY , QUERY_HISTORY_BY_* functions. |
diff --git a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs index c248ef575..cc4fea738 100644 --- a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs +++ b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs @@ -2224,6 +2224,24 @@ public void TestNativeOktaSuccess() } } + [Test] + public void TestConnectStringWithQueryTag() + { + using (var conn = new SnowflakeDbConnection()) + { + string expectedQueryTag = "Test QUERY_TAG 12345"; + conn.ConnectionString = ConnectionString + $";query_tag={expectedQueryTag}"; + + conn.Open(); + var command = conn.CreateCommand(); + // This query itself will be part of the history and will have the query tag + command.CommandText = "SELECT QUERY_TAG FROM table(information_schema.query_history_by_session())"; + var queryTag = command.ExecuteScalar(); + + Assert.AreEqual(expectedQueryTag, queryTag); + } + } + } } diff --git a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs index 4b2e3ec8f..309570ca6 100644 --- a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs @@ -470,6 +470,38 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.ALLOWUNDERSCORESINHOST, "true" } } }; + var testQueryTag = "Test QUERY_TAG 12345"; + var testCaseQueryTag = new TestCase() + { + ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};QUERY_TAG={testQueryTag}", + ExpectedProperties = new SFSessionProperties() + { + { SFSessionProperty.ACCOUNT, $"{defAccount}" }, + { SFSessionProperty.USER, defUser }, + { SFSessionProperty.HOST, $"{defAccount}.snowflakecomputing.com" }, + { SFSessionProperty.AUTHENTICATOR, defAuthenticator }, + { SFSessionProperty.SCHEME, defScheme }, + { SFSessionProperty.CONNECTION_TIMEOUT, defConnectionTimeout }, + { SFSessionProperty.PASSWORD, defPassword }, + { SFSessionProperty.PORT, defPort }, + { SFSessionProperty.VALIDATE_DEFAULT_PARAMETERS, "true" }, + { SFSessionProperty.USEPROXY, "false" }, + { SFSessionProperty.INSECUREMODE, "false" }, + { SFSessionProperty.DISABLERETRY, "false" }, + { SFSessionProperty.FORCERETRYON404, "false" }, + { SFSessionProperty.CLIENT_SESSION_KEEP_ALIVE, "false" }, + { SFSessionProperty.FORCEPARSEERROR, "false" }, + { SFSessionProperty.BROWSER_RESPONSE_TIMEOUT, defBrowserResponseTime }, + { SFSessionProperty.RETRY_TIMEOUT, defRetryTimeout }, + { SFSessionProperty.MAXHTTPRETRIES, defMaxHttpRetries }, + { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, + { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, + { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, + { SFSessionProperty.ALLOWUNDERSCORESINHOST, "false" }, + { SFSessionProperty.QUERY_TAG, testQueryTag } + } + }; + return new TestCase[] { simpleTestCase, @@ -482,7 +514,8 @@ public static IEnumerable ConnectionStringTestCases() testCaseWithDisableConsoleLogin, testCaseComplicatedAccountName, testCaseUnderscoredAccountName, - testCaseUnderscoredAccountNameWithEnabledAllowUnderscores + testCaseUnderscoredAccountNameWithEnabledAllowUnderscores, + testCaseQueryTag }; } diff --git a/Snowflake.Data/Core/SFStatement.cs b/Snowflake.Data/Core/SFStatement.cs index 05e905263..ae5ecbf4e 100644 --- a/Snowflake.Data/Core/SFStatement.cs +++ b/Snowflake.Data/Core/SFStatement.cs @@ -110,7 +110,9 @@ class SFStatement private const string SF_QUERY_RESULT_PATH = "/queries/{0}/result"; private const string SF_PARAM_MULTI_STATEMENT_COUNT = "MULTI_STATEMENT_COUNT"; - + + private const string SF_PARAM_QUERY_TAG = "QUERY_TAG"; + private const int SF_QUERY_IN_PROGRESS = 333333; private const int SF_QUERY_IN_PROGRESS_ASYNC = 333334; @@ -141,10 +143,13 @@ class SFStatement // the query id of the last query string _lastQueryId = null; + private string _queryTag = null; + internal SFStatement(SFSession session) { SfSession = session; _restRequester = session.restRequester; + _queryTag = session._queryTag; } internal string GetBindStage() => _bindStage; @@ -195,7 +200,16 @@ private SFRestRequest BuildQueryRequest(string sql, Dictionary(); + } + bodyParameters[SF_PARAM_QUERY_TAG] = _queryTag; + } QueryRequest postBody = new QueryRequest(); postBody.sqlText = sql; diff --git a/Snowflake.Data/Core/Session/SFSession.cs b/Snowflake.Data/Core/Session/SFSession.cs index 8f56fdda4..3b0c80f8d 100755 --- a/Snowflake.Data/Core/Session/SFSession.cs +++ b/Snowflake.Data/Core/Session/SFSession.cs @@ -83,6 +83,8 @@ public class SFSession internal int _maxRetryTimeout; + internal String _queryTag; + internal void ProcessLoginResponse(LoginResponse authnResponse) { if (authnResponse.success) @@ -168,6 +170,7 @@ internal SFSession( connectionTimeout = extractedProperties.TimeoutDuration(); properties.TryGetValue(SFSessionProperty.CLIENT_CONFIG_FILE, out var easyLoggingConfigFile); _easyLoggingStarter.Init(easyLoggingConfigFile); + properties.TryGetValue(SFSessionProperty.QUERY_TAG, out _queryTag); _maxRetryCount = extractedProperties.maxHttpRetries; _maxRetryTimeout = extractedProperties.retryTimeout; } diff --git a/Snowflake.Data/Core/Session/SFSessionProperty.cs b/Snowflake.Data/Core/Session/SFSessionProperty.cs index 6ed45be81..7ce6b4731 100644 --- a/Snowflake.Data/Core/Session/SFSessionProperty.cs +++ b/Snowflake.Data/Core/Session/SFSessionProperty.cs @@ -94,7 +94,9 @@ internal enum SFSessionProperty [SFSessionPropertyAttr(required = false, defaultValue = "true")] DISABLE_CONSOLE_LOGIN, [SFSessionPropertyAttr(required = false, defaultValue = "false")] - ALLOWUNDERSCORESINHOST + ALLOWUNDERSCORESINHOST, + [SFSessionPropertyAttr(required = false)] + QUERY_TAG } class SFSessionPropertyAttr : Attribute