Skip to content

Support DateTimeKind connection setting. #479

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

Merged
merged 3 commits into from
Apr 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions docs/content/connection-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,13 @@ These are the other options that MySqlConnector supports. They are set to sensib
<td>false</td>
<td>True to have MySqlDataReader.GetValue() and MySqlDataReader.GetDateTime() return DateTime.MinValue for date or datetime columns that have disallowed values.</td>
</tr>
<tr>
<td>DateTimeKind</td>
<td>Unspecified</td>
<td>The <code>DateTimeKind</code> used when <code>MySqlDataReader</code> returns a <code>DateTime</code>. If set to <code>Utc</code> or <code>Local</code>,
a <code>MySqlException</code> will be thrown if a <code>DateTime</code> command parameter has a <code>Kind</code> of <code>Local</code> or <code>Utc</code>,
respectively.</td>
</tr>
<tr>
<td>Default Command Timeout, Command Timeout, DefaultCommandTimeout</td>
<td>30</td>
Expand Down
10 changes: 6 additions & 4 deletions src/MySqlConnector/Core/ConnectionSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,19 @@ public ConnectionSettings(MySqlConnectionStringBuilder csb)
ConnectionLifeTime = Math.Min(csb.ConnectionLifeTime, uint.MaxValue / 1000) * 1000;
ConnectionReset = csb.ConnectionReset;
ConnectionIdlePingTime = Math.Min(csb.ConnectionIdlePingTime, uint.MaxValue / 1000) * 1000;
ConnectionIdleTimeout = (int)csb.ConnectionIdleTimeout;
ConnectionIdleTimeout = (int) csb.ConnectionIdleTimeout;
if (csb.MinimumPoolSize > csb.MaximumPoolSize)
throw new MySqlException("MaximumPoolSize must be greater than or equal to MinimumPoolSize");
MinimumPoolSize = (int)csb.MinimumPoolSize;
MaximumPoolSize = (int)csb.MaximumPoolSize;
MinimumPoolSize = (int) csb.MinimumPoolSize;
MaximumPoolSize = (int) csb.MaximumPoolSize;

// Other Options
AllowPublicKeyRetrieval = csb.AllowPublicKeyRetrieval;
AllowUserVariables = csb.AllowUserVariables;
AutoEnlist = csb.AutoEnlist;
ConnectionTimeout = (int)csb.ConnectionTimeout;
ConnectionTimeout = (int) csb.ConnectionTimeout;
ConvertZeroDateTime = csb.ConvertZeroDateTime;
DateTimeKind = (DateTimeKind) csb.DateTimeKind;
DefaultCommandTimeout = (int) csb.DefaultCommandTimeout;
ForceSynchronous = csb.ForceSynchronous;
IgnoreCommandTransaction = csb.IgnoreCommandTransaction;
Expand Down Expand Up @@ -105,6 +106,7 @@ public ConnectionSettings(MySqlConnectionStringBuilder csb)
public bool AutoEnlist { get; }
public int ConnectionTimeout { get; }
public bool ConvertZeroDateTime { get; }
public DateTimeKind DateTimeKind { get; }
public int DefaultCommandTimeout { get; }
public bool ForceSynchronous { get; }
public bool IgnoreCommandTransaction { get; }
Expand Down
2 changes: 1 addition & 1 deletion src/MySqlConnector/Core/Row.cs
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ private DateTime ParseDateTime(ArraySegment<byte> value)
return new DateTime(year, month, day, hour, minute, second);

var microseconds = int.Parse(parts[6] + new string('0', 6 - parts[6].Length), CultureInfo.InvariantCulture);
return new DateTime(year, month, day, hour, minute, second, microseconds / 1000).AddTicks(microseconds % 1000 * 10);
return new DateTime(year, month, day, hour, minute, second, microseconds / 1000, Connection.DateTimeKind).AddTicks(microseconds % 1000 * 10);
}

private static TimeSpan ParseTimeSpan(ArraySegment<byte> value)
Expand Down
2 changes: 1 addition & 1 deletion src/MySqlConnector/Core/StatementPreparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ private void DoAppendParameter(int parameterIndex, int textIndex, int textLength
var parameter = m_preparer.m_parameters[parameterIndex];
if (parameter.Direction != ParameterDirection.Input && (m_preparer.m_options & StatementPreparerOptions.AllowOutputParameters) == 0)
throw new MySqlException("Only ParameterDirection.Input is supported when CommandType is Text (parameter name: {0})".FormatInvariant(parameter.ParameterName));
m_preparer.m_parameters[parameterIndex].AppendSqlString(m_writer, m_preparer.m_options);
m_preparer.m_parameters[parameterIndex].AppendSqlString(m_writer, m_preparer.m_options, parameter.ParameterName);
m_lastIndex = textIndex + textLength;
}

Expand Down
2 changes: 2 additions & 0 deletions src/MySqlConnector/Core/StatementPreparerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ internal enum StatementPreparerOptions
AllowUserVariables = 1,
OldGuids = 2,
AllowOutputParameters = 4,
DateTimeUtc = 8,
DateTimeLocal = 16,
}
}
4 changes: 4 additions & 0 deletions src/MySqlConnector/Core/TextCommandExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ private PayloadData CreateQueryPayload(string commandText, MySqlParameterCollect
statementPreparerOptions |= StatementPreparerOptions.AllowUserVariables;
if (m_command.Connection.OldGuids)
statementPreparerOptions |= StatementPreparerOptions.OldGuids;
if (m_command.Connection.DateTimeKind == DateTimeKind.Utc)
statementPreparerOptions |= StatementPreparerOptions.DateTimeUtc;
else if (m_command.Connection.DateTimeKind == DateTimeKind.Local)
statementPreparerOptions |= StatementPreparerOptions.DateTimeLocal;
if (m_command.CommandType == CommandType.StoredProcedure)
statementPreparerOptions |= StatementPreparerOptions.AllowOutputParameters;
var preparer = new StatementPreparer(commandText, parameterCollection, statementPreparerOptions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ internal async Task<CachedProcedure> GetCachedProcedure(IOBehavior ioBehavior, s
internal MySqlTransaction CurrentTransaction { get; set; }
internal bool AllowUserVariables => m_connectionSettings.AllowUserVariables;
internal bool ConvertZeroDateTime => m_connectionSettings.ConvertZeroDateTime;
internal DateTimeKind DateTimeKind => m_connectionSettings.DateTimeKind;
internal int DefaultCommandTimeout => GetConnectionSettings().DefaultCommandTimeout;
internal bool IgnoreCommandTransaction => m_connectionSettings.IgnoreCommandTransaction;
internal bool OldGuids => m_connectionSettings.OldGuids;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,12 @@ public bool ConvertZeroDateTime
set => MySqlConnectionStringOption.ConvertZeroDateTime.SetValue(this, value);
}

public MySqlDateTimeKind DateTimeKind
{
get => MySqlConnectionStringOption.DateTimeKind.GetValue(this);
set => MySqlConnectionStringOption.DateTimeKind.SetValue(this, value);
}

public uint DefaultCommandTimeout
{
get => MySqlConnectionStringOption.DefaultCommandTimeout.GetValue(this);
Expand Down Expand Up @@ -294,6 +300,7 @@ internal abstract class MySqlConnectionStringOption
public static readonly MySqlConnectionStringOption<string> CharacterSet;
public static readonly MySqlConnectionStringOption<uint> ConnectionTimeout;
public static readonly MySqlConnectionStringOption<bool> ConvertZeroDateTime;
public static readonly MySqlConnectionStringOption<MySqlDateTimeKind> DateTimeKind;
public static readonly MySqlConnectionStringOption<uint> DefaultCommandTimeout;
public static readonly MySqlConnectionStringOption<bool> ForceSynchronous;
public static readonly MySqlConnectionStringOption<bool> IgnoreCommandTransaction;
Expand Down Expand Up @@ -427,6 +434,10 @@ static MySqlConnectionStringOption()
keys: new[] { "Convert Zero Datetime", "ConvertZeroDateTime" },
defaultValue: false));

AddOption(DateTimeKind = new MySqlConnectionStringOption<MySqlDateTimeKind>(
keys: new[] { "DateTimeKind" },
defaultValue: MySqlDateTimeKind.Unspecified));

AddOption(DefaultCommandTimeout = new MySqlConnectionStringOption<uint>(
keys: new[] { "Default Command Timeout", "DefaultCommandTimeout", "Command Timeout" },
defaultValue: 30u));
Expand Down Expand Up @@ -505,21 +516,11 @@ private static T ChangeType(object objectValue)
return (T) (object) false;
}

if (typeof(T) == typeof(MySqlLoadBalance) && objectValue is string loadBalanceString)
{
foreach (var val in Enum.GetValues(typeof(T)))
{
if (string.Equals(loadBalanceString, val.ToString(), StringComparison.OrdinalIgnoreCase))
return (T) val;
}
throw new InvalidOperationException("Value '{0}' not supported for option '{1}'.".FormatInvariant(objectValue, typeof(T).Name));
}

if (typeof(T) == typeof(MySqlSslMode) && objectValue is string sslModeString)
if ((typeof(T) == typeof(MySqlLoadBalance) || typeof(T) == typeof(MySqlSslMode) || typeof(T) == typeof(MySqlDateTimeKind)) && objectValue is string enumString)
{
foreach (var val in Enum.GetValues(typeof(T)))
{
if (string.Equals(sslModeString, val.ToString(), StringComparison.OrdinalIgnoreCase))
if (string.Equals(enumString, val.ToString(), StringComparison.OrdinalIgnoreCase))
return (T) val;
}
throw new InvalidOperationException("Value '{0}' not supported for option '{1}'.".FormatInvariant(objectValue, typeof(T).Name));
Expand Down
25 changes: 25 additions & 0 deletions src/MySqlConnector/MySql.Data.MySqlClient/MySqlDateTimeKind.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace MySql.Data.MySqlClient
{
/// <summary>
/// The <see cref="DateTimeKind" /> used when reading <see cref="DateTime" /> from the databsae.
/// </summary>
public enum MySqlDateTimeKind
{
/// <summary>
/// Use <see cref="DateTimeKind.Unspecified" /> when reading; allow any <see cref="DateTimeKind" /> in command parameters.
/// </summary>
Unspecified = DateTimeKind.Unspecified,

/// <summary>
/// Use <see cref="DateTimeKind.Utc" /> when reading; reject <see cref="DateTimeKind.Local" /> in command parameters.
/// </summary>
Utc = DateTimeKind.Utc,

/// <summary>
/// Use <see cref="DateTimeKind.Local" /> when reading; reject <see cref="DateTimeKind.Utc" /> in command parameters.
/// </summary>
Local = DateTimeKind.Local,
}
}
11 changes: 8 additions & 3 deletions src/MySqlConnector/MySql.Data.MySqlClient/MySqlParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ private MySqlParameter(MySqlParameter other, string parameterName)

internal string NormalizedParameterName { get; private set; }

internal void AppendSqlString(BinaryWriter writer, StatementPreparerOptions options)
internal void AppendSqlString(BinaryWriter writer, StatementPreparerOptions options, string parameterName)
{
if (Value == null || Value == DBNull.Value)
{
Expand Down Expand Up @@ -236,9 +236,14 @@ internal void AppendSqlString(BinaryWriter writer, StatementPreparerOptions opti
{
writer.WriteUtf8("{0:R}".FormatInvariant(Value));
}
else if (Value is DateTime)
else if (Value is DateTime dateTimeValue)
{
writer.WriteUtf8("timestamp('{0:yyyy'-'MM'-'dd' 'HH':'mm':'ss'.'ffffff}')".FormatInvariant(Value));
if ((options & StatementPreparerOptions.DateTimeUtc) != 0 && dateTimeValue.Kind == DateTimeKind.Local)
throw new MySqlException("DateTime.Kind must not be Local when DateTimeKind setting is Utc (parameter name: {0})".FormatInvariant(parameterName));
else if ((options & StatementPreparerOptions.DateTimeLocal) != 0 && dateTimeValue.Kind == DateTimeKind.Utc)
throw new MySqlException("DateTime.Kind must not be Utc when DateTimeKind setting is Local (parameter name: {0})".FormatInvariant(parameterName));

writer.WriteUtf8("timestamp('{0:yyyy'-'MM'-'dd' 'HH':'mm':'ss'.'ffffff}')".FormatInvariant(dateTimeValue));
}
else if (Value is DateTimeOffset dateTimeOffsetValue)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public void Defaults()
#endif
Assert.Equal(15u, csb.ConnectionTimeout);
Assert.False(csb.ConvertZeroDateTime);
#if !BASELINE
Assert.Equal(MySqlDateTimeKind.Unspecified, csb.DateTimeKind);
#endif
Assert.Equal("", csb.Database);
Assert.Equal(30u, csb.DefaultCommandTimeout);
#if !BASELINE
Expand Down Expand Up @@ -76,6 +79,9 @@ public void ParseConnectionString()
"connection lifetime=15;" +
"ConnectionReset=false;" +
"Convert Zero Datetime=true;" +
#if !BASELINE
"datetimekind=utc;" +
#endif
"default command timeout=123;" +
#if !BASELINE
"connection idle ping time=60;" +
Expand Down Expand Up @@ -109,6 +115,9 @@ public void ParseConnectionString()
Assert.False(csb.ConnectionReset);
Assert.Equal(30u, csb.ConnectionTimeout);
Assert.True(csb.ConvertZeroDateTime);
#if !BASELINE
Assert.Equal(MySqlDateTimeKind.Utc, csb.DateTimeKind);
#endif
Assert.Equal("schema_name", csb.Database);
Assert.Equal(123u, csb.DefaultCommandTimeout);
#if !BASELINE
Expand Down
Loading