From a25b9c5a066a6ccb041305f6c52a8dc6ec36b3f2 Mon Sep 17 00:00:00 2001 From: Ilkka Kudjoi Date: Wed, 17 Oct 2018 13:57:57 +0300 Subject: [PATCH 1/3] #83 Add DbProviderFactory with inflicted changes --- Snowflake.Data/Client/SnowflakeDbCommand.cs | 45 +++++++++++++++---- .../Client/SnowflakeDbCommandBuilder.cs | 36 +++++++++++++++ .../Client/SnowflakeDbConnection.cs | 16 +++++-- .../SnowflakeDbConnectionStringBuilder.cs | 11 +++++ Snowflake.Data/Client/SnowflakeDbFactory.cs | 37 +++++++++++++++ .../Client/SnowflakeDbTransaction.cs | 2 +- Snowflake.Data/Core/SFSessionProperty.cs | 4 +- 7 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 Snowflake.Data/Client/SnowflakeDbCommandBuilder.cs create mode 100644 Snowflake.Data/Client/SnowflakeDbConnectionStringBuilder.cs create mode 100644 Snowflake.Data/Client/SnowflakeDbFactory.cs diff --git a/Snowflake.Data/Client/SnowflakeDbCommand.cs b/Snowflake.Data/Client/SnowflakeDbCommand.cs index 8b7b53556..fba21d91c 100755 --- a/Snowflake.Data/Client/SnowflakeDbCommand.cs +++ b/Snowflake.Data/Client/SnowflakeDbCommand.cs @@ -25,6 +25,14 @@ public class SnowflakeDbCommand : DbCommand private SFLogger logger = SFLoggerFactory.GetLogger(); + public SnowflakeDbCommand() + { + logger.Debug("Constucting SnowflakeDbCommand class"); + // by default, no query timeout + this.CommandTimeout = 0; + parameterCollection = new SnowflakeDbParameterCollection(); + } + public SnowflakeDbCommand(SnowflakeDbConnection connection) { logger.Debug("Constucting SnowflakeDbCommand class"); @@ -79,10 +87,7 @@ public override bool DesignTimeVisible public override UpdateRowSource UpdatedRowSource { - get - { - return UpdateRowSource.None; - } + get => UpdateRowSource.None; set { @@ -95,14 +100,36 @@ public override UpdateRowSource UpdatedRowSource protected override DbConnection DbConnection { - get - { - return connection; - } + get => connection; set { - throw new SnowflakeDbException(SFError.UNSUPPORTED_FEATURE); + if (value == null) + { + if (connection == null) + { + return; + } + + // Unsetting connection not supported. + throw new SnowflakeDbException(SFError.UNSUPPORTED_FEATURE); + } + + if (!(value is SnowflakeDbConnection)) + { + // Must be of type SnowflakeDbConnection. + throw new SnowflakeDbException(SFError.UNSUPPORTED_FEATURE); + } + + var sfc = (SnowflakeDbConnection) value; + if (connection != null && connection != sfc) + { + // Connection already set. + throw new SnowflakeDbException(SFError.UNSUPPORTED_FEATURE); + } + + connection = sfc; + sfStatement = new SFStatement(sfc.SfSession); } } diff --git a/Snowflake.Data/Client/SnowflakeDbCommandBuilder.cs b/Snowflake.Data/Client/SnowflakeDbCommandBuilder.cs new file mode 100644 index 000000000..de18e8ad0 --- /dev/null +++ b/Snowflake.Data/Client/SnowflakeDbCommandBuilder.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Text; + +namespace Snowflake.Data.Client +{ + public class SnowflakeDbCommandBuilder : DbCommandBuilder + { + protected override void ApplyParameterInfo(DbParameter parameter, DataRow row, StatementType statementType, bool whereClause) + { + throw new NotImplementedException(); + } + + protected override string GetParameterName(int parameterOrdinal) + { + throw new NotImplementedException(); + } + + protected override string GetParameterName(string parameterName) + { + throw new NotImplementedException(); + } + + protected override string GetParameterPlaceholder(int parameterOrdinal) + { + throw new NotImplementedException(); + } + + protected override void SetRowUpdatingHandler(DbDataAdapter adapter) + { + throw new NotImplementedException(); + } + } +} diff --git a/Snowflake.Data/Client/SnowflakeDbConnection.cs b/Snowflake.Data/Client/SnowflakeDbConnection.cs index cf21ac54c..e38edc62f 100755 --- a/Snowflake.Data/Client/SnowflakeDbConnection.cs +++ b/Snowflake.Data/Client/SnowflakeDbConnection.cs @@ -83,17 +83,19 @@ public override void ChangeDatabase(string databaseName) public override void Close() { - logger.Debug($"Close Connection."); + logger.Debug("Close Connection."); if (_connectionState != ConnectionState.Closed && SfSession != null) { SfSession.close(); } + + _connectionState = ConnectionState.Closed; } public override void Open() { - logger.Debug($"Open Connection."); + logger.Debug("Open Connection."); SetSession(); SfSession.Open(); OnSessionEstablished(); @@ -101,7 +103,7 @@ public override void Open() public override Task OpenAsync(CancellationToken cancellationToken) { - logger.Debug($"Open Connection Async."); + logger.Debug("Open Connection Async."); if (cancellationToken.IsCancellationRequested) return Task.FromCanceled(cancellationToken); @@ -123,6 +125,14 @@ private void OnSessionEstablished() protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) { + // Parameterless BeginTransaction() method of the super class calls this method with IsolationLevel.Unspecified, + // but the SnowflakeDbTransaction supports only ReadCommitted for some reason. + // Change the isolation level here as a workaround. + if (isolationLevel == IsolationLevel.Unspecified) + { + isolationLevel = IsolationLevel.ReadCommitted; + } + return new SnowflakeDbTransaction(isolationLevel, this); } diff --git a/Snowflake.Data/Client/SnowflakeDbConnectionStringBuilder.cs b/Snowflake.Data/Client/SnowflakeDbConnectionStringBuilder.cs new file mode 100644 index 000000000..e7da430f5 --- /dev/null +++ b/Snowflake.Data/Client/SnowflakeDbConnectionStringBuilder.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Text; + +namespace Snowflake.Data.Client +{ + public class SnowflakeDbConnectionStringBuilder : DbConnectionStringBuilder + { + } +} diff --git a/Snowflake.Data/Client/SnowflakeDbFactory.cs b/Snowflake.Data/Client/SnowflakeDbFactory.cs new file mode 100644 index 000000000..71780c9ed --- /dev/null +++ b/Snowflake.Data/Client/SnowflakeDbFactory.cs @@ -0,0 +1,37 @@ +using System.Data.Common; + +namespace Snowflake.Data.Client +{ + public sealed class SnowflakeDbFactory : DbProviderFactory + { + /// + /// Returns a strongly typed instance. + /// + public override DbCommand CreateCommand() => new SnowflakeDbCommand(); + + /// + /// Returns a strongly typed instance. + /// + public override DbConnection CreateConnection() => new SnowflakeDbConnection(); + + /// + /// Returns a strongly typed instance. + /// + public override DbParameter CreateParameter() => new SnowflakeDbParameter(); + + /// + /// Returns a strongly typed instance. + /// + public override DbConnectionStringBuilder CreateConnectionStringBuilder() => new SnowflakeDbConnectionStringBuilder(); + + /// + /// Returns a strongly typed instance. + /// + public override DbCommandBuilder CreateCommandBuilder() => new SnowflakeDbCommandBuilder(); + + /// + /// Returns a strongly typed instance. + /// + public override DbDataAdapter CreateDataAdapter() => new SnowflakeDbDataAdapter(); + } +} diff --git a/Snowflake.Data/Client/SnowflakeDbTransaction.cs b/Snowflake.Data/Client/SnowflakeDbTransaction.cs index af142df87..2a98660b7 100755 --- a/Snowflake.Data/Client/SnowflakeDbTransaction.cs +++ b/Snowflake.Data/Client/SnowflakeDbTransaction.cs @@ -26,7 +26,7 @@ public SnowflakeDbTransaction(IsolationLevel isolationLevel, SnowflakeDbConnecti throw new SnowflakeDbException(SFError.UNSUPPORTED_FEATURE); } - this.isolationLevel = IsolationLevel; + this.isolationLevel = isolationLevel; this.connection = connection; using (IDbCommand command = connection.CreateCommand()) diff --git a/Snowflake.Data/Core/SFSessionProperty.cs b/Snowflake.Data/Core/SFSessionProperty.cs index 2cc2b07ac..a68f959e4 100755 --- a/Snowflake.Data/Core/SFSessionProperty.cs +++ b/Snowflake.Data/Core/SFSessionProperty.cs @@ -71,12 +71,12 @@ internal static SFSessionProperties parseConnectionString(String connectionStrin } catch (ArgumentException e) { - logger.Warn($"Property {token[0]} not found ignored."); + logger.Warn($"Property {token[0]} not found ignored.", e); } } else { - string invalidStringDetail = String.Format("Invalid kay value pair {0}", keyVal); + string invalidStringDetail = String.Format("Invalid key value pair {0}", keyVal); SnowflakeDbException e = new SnowflakeDbException(SFError.INVALID_CONNECTION_STRING, new object[] { invalidStringDetail }); logger.Error("Invalid string.", e); From 1a7e485c23084c927578b6b9a56f11ac3a52acc7 Mon Sep 17 00:00:00 2001 From: Ilkka Kudjoi Date: Tue, 23 Oct 2018 15:27:22 +0300 Subject: [PATCH 2/3] #83 Implement SnowflakeDbCommandBuilder --- .../Client/SnowflakeDbCommandBuilder.cs | 98 +++++++++++++++++-- 1 file changed, 88 insertions(+), 10 deletions(-) diff --git a/Snowflake.Data/Client/SnowflakeDbCommandBuilder.cs b/Snowflake.Data/Client/SnowflakeDbCommandBuilder.cs index de18e8ad0..34f3e3990 100644 --- a/Snowflake.Data/Client/SnowflakeDbCommandBuilder.cs +++ b/Snowflake.Data/Client/SnowflakeDbCommandBuilder.cs @@ -1,36 +1,114 @@ -using System; -using System.Collections.Generic; -using System.Data; +using System.Data; using System.Data.Common; -using System.Text; +using System.Globalization; namespace Snowflake.Data.Client { public class SnowflakeDbCommandBuilder : DbCommandBuilder { - protected override void ApplyParameterInfo(DbParameter parameter, DataRow row, StatementType statementType, bool whereClause) + /// + /// Initializes a new instance of the class. + /// + public SnowflakeDbCommandBuilder() + : this(null) { - throw new NotImplementedException(); } + /// + /// Initializes a new instance of the class. + /// + /// The adapter. + public SnowflakeDbCommandBuilder(SnowflakeDbDataAdapter adapter) + { + DataAdapter = adapter; + QuotePrefix = "\""; + QuoteSuffix = "\""; + } + + /// + /// Gets or sets the beginning character or characters to use when specifying database objects (for example, tables or columns) whose names contain characters such as spaces or reserved tokens. + /// + /// + /// The beginning character or characters to use. The default is an empty string. + /// + /// + /// + /// + public sealed override string QuotePrefix + { + get => base.QuotePrefix; + // TODO: Why should it be possible to remove the QuotePrefix? + set => base.QuotePrefix = string.IsNullOrEmpty(value) ? value : "\""; + } + + /// + /// Gets or sets the ending character or characters to use when specifying database objects (for example, tables or columns) whose names contain characters such as spaces or reserved tokens. + /// + /// + /// The ending character or characters to use. The default is an empty string. + /// + /// + /// + /// + public sealed override string QuoteSuffix + { + get => base.QuoteSuffix; + // TODO: Why should it be possible to remove the QuoteSuffix? + set => base.QuoteSuffix = string.IsNullOrEmpty(value) ? value : "\""; + } + + /// + /// Applies the parameter information. + /// + /// The parameter. + /// The row. + /// Type of the statement. + /// if set to true [where clause]. + protected override void ApplyParameterInfo(DbParameter p, DataRow row, StatementType statementType, bool whereClause) + { + var param = (SnowflakeDbParameter)p; + param.DbType = (DbType)row[SchemaTableColumn.ProviderType]; + } + + /// + /// Returns the name of the specified parameter in the format of #. + /// + /// The number to be included as part of the parameter's name.. + /// + /// The name of the parameter with the specified number appended as part of the parameter name. + /// protected override string GetParameterName(int parameterOrdinal) { - throw new NotImplementedException(); + return string.Format(CultureInfo.InvariantCulture, "{0}", parameterOrdinal); } + /// + /// Returns the full parameter name, given the partial parameter name. + /// + /// The partial name of the parameter. + /// + /// The full parameter name corresponding to the partial parameter name requested. + /// protected override string GetParameterName(string parameterName) { - throw new NotImplementedException(); + return string.Format(CultureInfo.InvariantCulture, "{0}", parameterName); } + /// + /// Returns the placeholder for the parameter in the associated SQL statement. + /// + /// The number to be included as part of the parameter's name. + /// + /// The name of the parameter with the specified number appended. + /// protected override string GetParameterPlaceholder(int parameterOrdinal) { - throw new NotImplementedException(); + return GetParameterName(parameterOrdinal); } + /// protected override void SetRowUpdatingHandler(DbDataAdapter adapter) { - throw new NotImplementedException(); } } } From 2ea758286e7904af024912fb9825dba8a2657907 Mon Sep 17 00:00:00 2001 From: howryu Date: Tue, 30 Oct 2018 09:22:30 -0700 Subject: [PATCH 3/3] Add tests --- Snowflake.Data.Tests/App.config | 9 +++++ Snowflake.Data.Tests/SFDbFactoryIT.cs | 39 +++++++++++++++++++ .../Client/SnowflakeDbConnection.cs | 3 +- Snowflake.Data/Client/SnowflakeDbFactory.cs | 2 + 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100755 Snowflake.Data.Tests/SFDbFactoryIT.cs mode change 100644 => 100755 Snowflake.Data/Client/SnowflakeDbFactory.cs diff --git a/Snowflake.Data.Tests/App.config b/Snowflake.Data.Tests/App.config index f64115147..9604de538 100755 --- a/Snowflake.Data.Tests/App.config +++ b/Snowflake.Data.Tests/App.config @@ -26,6 +26,15 @@ Copyright (c) 2012-2017 Snowflake Computing Inc. All rights reserved. + + + + + + +