Skip to content

Commit 15f4c55

Browse files
SNOW-1053935: Support for double quote character in delimited identifier. (#926)
### Description Support for double quote character in delimited identifier. ### 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
1 parent 14cf8a5 commit 15f4c55

File tree

3 files changed

+73
-26
lines changed

3 files changed

+73
-26
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,25 @@ The following examples show how you can include different types of special chara
234234

235235
Note that previously you needed to use a double equal sign (==) to escape the character. However, beginning with version 2.0.18, you can use a single equal size.
236236

237+
238+
Snowflake supports using [double quote identifiers](https://docs.snowflake.com/en/sql-reference/identifiers-syntax#double-quoted-identifiers) for object property values (WAREHOUSE, DATABASE, SCHEMA AND ROLES). The value should be delimited with `\"` in the connection string. The value is case-sensitive and allow to use special characters as part of the value.
239+
240+
```cs
241+
string connectionString = String.Format(
242+
"account=testaccount; " +
243+
"database=\"testDB\";"
244+
);
245+
```
246+
- To include a `"` character as part of the value should be escaped using `\"\"`.
247+
248+
```cs
249+
string connectionString = String.Format(
250+
"account=testaccount; " +
251+
"database=\"\"\"test\"\"user\"\"\";" // DATABASE => ""test"db""
252+
);
253+
```
254+
255+
237256
### Other Authentication Methods
238257

239258
If you are using a different method for authentication, see the examples below:

Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,15 @@ public void TestValidateCorrectAccountNames(string accountName, string expectedA
3939
{
4040
// arrange
4141
var connectionString = $"ACCOUNT={accountName};USER=test;PASSWORD=test;";
42-
42+
4343
// act
4444
var properties = SFSessionProperties.ParseConnectionString(connectionString, null);
45-
45+
4646
// assert
4747
Assert.AreEqual(expectedAccountName, properties[SFSessionProperty.ACCOUNT]);
4848
Assert.AreEqual(expectedHost, properties[SFSessionProperty.HOST]);
4949
}
50-
50+
5151
[Test]
5252
[TestCase("ACCOUNT=testaccount;USER=testuser;PASSWORD=testpassword;FILE_TRANSFER_MEMORY_THRESHOLD=0;", "Error: Invalid parameter value 0 for FILE_TRANSFER_MEMORY_THRESHOLD")]
5353
[TestCase("ACCOUNT=testaccount;USER=testuser;PASSWORD=testpassword;FILE_TRANSFER_MEMORY_THRESHOLD=xyz;", "Error: Invalid parameter value xyz for FILE_TRANSFER_MEMORY_THRESHOLD")]
@@ -63,7 +63,7 @@ public void TestThatItFailsForWrongConnectionParameter(string connectionString,
6363
var exception = Assert.Throws<SnowflakeDbException>(
6464
() => SFSessionProperties.ParseConnectionString(connectionString, null)
6565
);
66-
66+
6767
// assert
6868
Assert.AreEqual(SFError.INVALID_CONNECTION_PARAMETER_VALUE.GetAttribute<SFErrorAttr>().errorCode, exception.ErrorCode);
6969
Assert.IsTrue(exception.Message.Contains(expectedErrorMessagePart));
@@ -78,12 +78,10 @@ public void TestThatItFailsIfNoAccountSpecified(string connectionString)
7878
var exception = Assert.Throws<SnowflakeDbException>(
7979
() => SFSessionProperties.ParseConnectionString(connectionString, null)
8080
);
81-
81+
8282
// assert
8383
Assert.AreEqual(SFError.MISSING_CONNECTION_PROPERTY.GetAttribute<SFErrorAttr>().errorCode, exception.ErrorCode);
8484
}
85-
86-
8785

8886
[Test]
8987
[TestCase("DB", SFSessionProperty.DB, "\"testdb\"")]
@@ -94,28 +92,46 @@ public void TestValidateSupportEscapedQuotesValuesForObjectProperties(string pro
9492
{
9593
// arrange
9694
var connectionString = $"ACCOUNT=test;{propertyName}={value};USER=test;PASSWORD=test;";
97-
95+
9896
// act
9997
var properties = SFSessionProperties.ParseConnectionString(connectionString, null);
100-
98+
10199
// assert
102100
Assert.AreEqual(value, properties[sessionProperty]);
103101
}
104-
102+
103+
[Test]
104+
[TestCase("DB", SFSessionProperty.DB, "testdb", "testdb")]
105+
[TestCase("DB", SFSessionProperty.DB, "\"testdb\"", "\"testdb\"")]
106+
[TestCase("DB", SFSessionProperty.DB, "\"\"\"testDB\"\"\"", "\"\"testDB\"\"")]
107+
[TestCase("DB", SFSessionProperty.DB, "\"\"\"test\"\"DB\"\"\"", "\"\"test\"DB\"\"")]
108+
[TestCase("SCHEMA", SFSessionProperty.SCHEMA, "\"quoted\"\"Schema\"", "\"quoted\"Schema\"")]
109+
public void TestValidateSupportEscapedQuotesInsideValuesForObjectProperties(string propertyName, SFSessionProperty sessionProperty, string value, string expectedValue)
110+
{
111+
// arrange
112+
var connectionString = $"ACCOUNT=test;{propertyName}={value};USER=test;PASSWORD=test;";
113+
114+
// act
115+
var properties = SFSessionProperties.ParseConnectionString(connectionString, null);
116+
117+
// assert
118+
Assert.AreEqual(expectedValue, properties[sessionProperty]);
119+
}
120+
105121
[Test]
106122
public void TestProcessEmptyUserAndPasswordInConnectionString()
107123
{
108124
// arrange
109125
var connectionString = $"ACCOUNT=test;USER=;PASSWORD=;";
110-
126+
111127
// act
112128
var properties = SFSessionProperties.ParseConnectionString(connectionString, null);
113-
129+
114130
// assert
115131
Assert.AreEqual(string.Empty, properties[SFSessionProperty.USER]);
116132
Assert.AreEqual(string.Empty, properties[SFSessionProperty.PASSWORD]);
117133
}
118-
134+
119135
public static IEnumerable<TestCase> ConnectionStringTestCases()
120136
{
121137
string defAccount = "testaccount";
@@ -168,7 +184,7 @@ public static IEnumerable<TestCase> ConnectionStringTestCases()
168184
{ SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }
169185
}
170186
};
171-
187+
172188
var testCaseWithBrowserResponseTimeout = new TestCase()
173189
{
174190
ConnectionString = $"ACCOUNT={defAccount};BROWSER_RESPONSE_TIMEOUT=180;authenticator=externalbrowser",
@@ -501,7 +517,7 @@ public static IEnumerable<TestCase> ConnectionStringTestCases()
501517
{ SFSessionProperty.QUERY_TAG, testQueryTag }
502518
}
503519
};
504-
520+
505521
return new TestCase[]
506522
{
507523
simpleTestCase,
@@ -518,7 +534,7 @@ public static IEnumerable<TestCase> ConnectionStringTestCases()
518534
testCaseQueryTag
519535
};
520536
}
521-
537+
522538
internal class TestCase
523539
{
524540
public string ConnectionString { get; set; }

Snowflake.Data/Core/Session/SFSessionProperty.cs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*
22
* Copyright (c) 2012-2021 Snowflake Computing Inc. All rights reserved.
33
*/
44

@@ -167,7 +167,7 @@ internal static SFSessionProperties ParseConnectionString(string connectionStrin
167167
logger.Info("Start parsing connection string.");
168168
var builder = new DbConnectionStringBuilder();
169169
try
170-
{
170+
{
171171
builder.ConnectionString = connectionString;
172172
}
173173
catch (ArgumentException e)
@@ -197,7 +197,7 @@ internal static SFSessionProperties ParseConnectionString(string connectionStrin
197197
logger.Warn($"Property {keys[i]} not found ignored.", e);
198198
}
199199
}
200-
200+
201201
UpdatePropertiesForSpecialCases(properties, connectionString);
202202

203203
var useProxy = false;
@@ -241,7 +241,7 @@ internal static SFSessionProperties ParseConnectionString(string connectionStrin
241241
ValidateAccountDomain(properties);
242242

243243
var allowUnderscoresInHost = ParseAllowUnderscoresInHost(properties);
244-
244+
245245
// compose host value if not specified
246246
if (!properties.ContainsKey(SFSessionProperty.HOST) ||
247247
(0 == properties[SFSessionProperty.HOST].Length))
@@ -260,7 +260,7 @@ internal static SFSessionProperties ParseConnectionString(string connectionStrin
260260
}
261261

262262
// Trim the account name to remove the region and cloud platform if any were provided
263-
// because the login request data does not expect region and cloud information to be
263+
// because the login request data does not expect region and cloud information to be
264264
// passed on for account_name
265265
properties[SFSessionProperty.ACCOUNT] = properties[SFSessionProperty.ACCOUNT].Split('.')[0];
266266

@@ -287,15 +287,15 @@ private static void UpdatePropertiesForSpecialCases(SFSessionProperties properti
287287
{
288288
var sessionProperty = (SFSessionProperty)Enum.Parse(
289289
typeof(SFSessionProperty), propertyName);
290-
properties[sessionProperty]= tokens[1];
290+
properties[sessionProperty]= ProcessObjectEscapedCharacters(tokens[1]);
291291
}
292-
292+
293293
break;
294294
}
295295
case "USER":
296296
case "PASSWORD":
297297
{
298-
298+
299299
var sessionProperty = (SFSessionProperty)Enum.Parse(
300300
typeof(SFSessionProperty), propertyName);
301301
if (!properties.ContainsKey(sessionProperty))
@@ -310,6 +310,18 @@ private static void UpdatePropertiesForSpecialCases(SFSessionProperties properti
310310
}
311311
}
312312

313+
private static string ProcessObjectEscapedCharacters(string objectValue)
314+
{
315+
var match = Regex.Match(objectValue, "^\"(.*)\"$");
316+
if(match.Success)
317+
{
318+
var replaceEscapedQuotes = match.Groups[1].Value.Replace("\"\"", "\"");
319+
return $"\"{replaceEscapedQuotes}\"";
320+
}
321+
322+
return objectValue;
323+
}
324+
313325
private static void ValidateAccountDomain(SFSessionProperties properties)
314326
{
315327
var account = properties[SFSessionProperty.ACCOUNT];
@@ -372,7 +384,7 @@ private static void ValidateFileTransferMaxBytesInMemoryProperty(SFSessionProper
372384
logger.Error($"Value for parameter {propertyName} could not be parsed");
373385
throw new SnowflakeDbException(e, SFError.INVALID_CONNECTION_PARAMETER_VALUE, maxBytesInMemoryString, propertyName);
374386
}
375-
387+
376388
if (maxBytesInMemory <= 0)
377389
{
378390
logger.Error($"Value for parameter {propertyName} should be greater than 0");
@@ -381,7 +393,7 @@ private static void ValidateFileTransferMaxBytesInMemoryProperty(SFSessionProper
381393
SFError.INVALID_CONNECTION_PARAMETER_VALUE, maxBytesInMemoryString, propertyName);
382394
}
383395
}
384-
396+
385397
private static bool IsRequired(SFSessionProperty sessionProperty, SFSessionProperties properties)
386398
{
387399
if (sessionProperty.Equals(SFSessionProperty.PASSWORD))

0 commit comments

Comments
 (0)