Skip to content
This repository has been archived by the owner on Feb 29, 2020. It is now read-only.

Commit

Permalink
[client][managed][offline] use more fine grained types in sqlite store
Browse files Browse the repository at this point in the history
hasankhan committed Nov 6, 2014
1 parent 68e5540 commit de49712
Showing 14 changed files with 426 additions and 137 deletions.
Original file line number Diff line number Diff line change
@@ -43,5 +43,10 @@ public override bool Equals(object obj)
this.JsonType.Equals(other.JsonType) &&
this.StoreType.Equals(other.StoreType);
}

public override string ToString()
{
return String.Format("{0}, {1}, {1}", this.Name, this.JsonType, this.StoreType);
}
}
}
Original file line number Diff line number Diff line change
@@ -75,7 +75,7 @@ public override void DefineTable(string tableName, JObject item)
}

var tableDefinition = (from property in item.Properties()
let storeType = GetStoreType(property)
let storeType = SqlHelpers.GetStoreType(property.Value.Type, allowNull: false)
select new ColumnDefinition(property.Name, property.Value.Type, storeType))
.ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase);

@@ -465,31 +465,6 @@ private IList<JObject> ExecuteQuery(TableDefinition table, string sql, IDictiona
return rows;
}

private JToken DeserializeValue(ColumnDefinition column, object value)
{
if (value == null)
{
return null;
}

string sqlType = column.StoreType;
JTokenType jsonType = column.JsonType;
if (sqlType == SqlColumnType.Integer)
{
return SqlHelpers.ParseInteger(jsonType, value);
}
if (sqlType == SqlColumnType.Real)
{
return SqlHelpers.ParseReal(jsonType, value);
}
if (sqlType == SqlColumnType.Text)
{
return SqlHelpers.ParseText(jsonType, value);
}

return null;
}

private static void ValidateResult(SQLiteResult result)
{
if (result != SQLiteResult.DONE)
@@ -509,7 +484,7 @@ private JObject ReadRow(TableDefinition table, ISQLiteStatement statement)
ColumnDefinition column;
if (table.TryGetValue(name, out column))
{
JToken jVal = this.DeserializeValue(column, value);
JToken jVal = SqlHelpers.DeserializeValue(value, column.StoreType, column.JsonType);
row[name] = jVal;
}
else
@@ -543,11 +518,6 @@ private static MobileServiceSystemProperties GetSystemProperties(JObject item)
return sysProperties;
}

private string GetStoreType(JProperty property)
{
return SqlHelpers.GetColumnType(property.Value.Type, allowNull: false);
}

protected override void Dispose(bool disposing)
{
if (disposing)
Original file line number Diff line number Diff line change
@@ -2,20 +2,29 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// ----------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.WindowsAzure.MobileServices.SQLiteStore
{
internal static class SqlColumnType
{
// base types
public const string Integer = "INTEGER";
public const string Real = "REAL";
public const string Text = "TEXT";
public const string Blob = "BLOB";
public const string Null = "NULL";
public const string None = "NONE";
public const string Real = "REAL";
public const string Numeric = "NUMERIC";

// type aliases
public const string Boolean = "BOOLEAN"; // NUMERIC
public const string DateTime = "DATETIME"; // NUMERIC
public const string Float = "FLOAT"; // REAL
public const string Blob = "BLOB"; // NONE


// custom types = NONE
public const string Guid = "GUID";
public const string Json = "JSON";
public const string Uri = "URI";
public const string TimeSpan = "TIMESPAN";
}
}
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@

using System;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Microsoft.WindowsAzure.MobileServices.SQLiteStore
@@ -15,33 +14,135 @@ internal class SqlHelpers

public static object SerializeValue(JValue value, bool allowNull)
{
string columnType = SqlHelpers.GetColumnType(value.Type, allowNull);
return SerializeValue(value, columnType, value.Type);
string storeType = SqlHelpers.GetStoreType(value.Type, allowNull);
return SerializeValue(value, storeType, value.Type);
}

public static object SerializeValue(JToken value, string sqlType, JTokenType columnType)
public static object SerializeValue(JToken value, string storeType, JTokenType columnType)
{
if (value == null || value.Type == JTokenType.Null)
{
return null;
}

if (sqlType == SqlColumnType.Text)
if (IsTextType(storeType))
{
return SerializeAsText(value, columnType);
}
if (sqlType == SqlColumnType.Real)
if (IsRealType(storeType))
{
return SerializeAsReal(value, columnType);
}
if (sqlType == SqlColumnType.Integer)
if (IsNumberType(storeType))
{
return SerializeAsInteger(value, columnType);
return SerializeAsNumber(value, columnType);
}

return value.ToString();
}

public static JToken DeserializeValue(object value, string storeType, JTokenType columnType)
{
if (value == null)
{
return null;
}

if (IsTextType(storeType))
{
return SqlHelpers.ParseText(columnType, value);
}
if (IsRealType(storeType))
{
return SqlHelpers.ParseReal(columnType, value);
}
if (IsNumberType(storeType))
{
return SqlHelpers.ParseNumber(columnType, value);
}

return null;
}

// https://www.sqlite.org/datatype3.html (2.2 Affinity Name Examples)
public static string GetStoreCastType(Type type)
{
if (type == typeof(bool) ||
type == typeof(DateTime) ||
type == typeof(decimal))
{
return SqlColumnType.Numeric;
}
else if (type == typeof(int) ||
type == typeof(uint) ||
type == typeof(long) ||
type == typeof(ulong) ||
type == typeof(short) ||
type == typeof(ushort) ||
type == typeof(byte) ||
type == typeof(sbyte))
{
return SqlColumnType.Integer;
}
else if (type == typeof(float) ||
type == typeof(double))
{
return SqlColumnType.Real;
}
else if (type == typeof(string) ||
type == typeof(Guid) ||
type == typeof(byte[]) ||
type == typeof(Uri) ||
type == typeof(TimeSpan))
{
return SqlColumnType.Text;
}

throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, WindowsAzure.MobileServices.SQLiteStore.Properties.Resources.SQLiteStore_ValueTypeNotSupported, type.Name));
}

public static string GetStoreType(JTokenType type, bool allowNull)
{
switch (type)
{
case JTokenType.Boolean:
return SqlColumnType.Boolean;
case JTokenType.Integer:
return SqlColumnType.Integer;
case JTokenType.Date:
return SqlColumnType.DateTime;
case JTokenType.Float:
return SqlColumnType.Float;
case JTokenType.String:
return SqlColumnType.Text;
case JTokenType.Guid:
return SqlColumnType.Guid;
case JTokenType.Array:
case JTokenType.Object:
return SqlColumnType.Json;
case JTokenType.Bytes:
return SqlColumnType.Blob;
case JTokenType.Uri:
return SqlColumnType.Uri;
case JTokenType.TimeSpan:
return SqlColumnType.TimeSpan;
case JTokenType.Null:
if (allowNull)
{
return null;
}
throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, WindowsAzure.MobileServices.SQLiteStore.Properties.Resources.SQLiteStore_JTokenNotSupported, type));
case JTokenType.Comment:
case JTokenType.Constructor:
case JTokenType.None:
case JTokenType.Property:
case JTokenType.Raw:
case JTokenType.Undefined:
default:
throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, WindowsAzure.MobileServices.SQLiteStore.Properties.Resources.SQLiteStore_JTokenNotSupported, type));
}
}

public static string FormatTableName(string tableName)
{
ValidateIdentifier(tableName);
@@ -54,23 +155,41 @@ public static string FormatMember(string memberName)
return string.Format("[{0}]", memberName);
}

private static long SerializeAsInteger(JToken value, JTokenType columnType)
private static bool IsNumberType(string storeType)
{
return value.Value<long>();
return storeType == SqlColumnType.Integer ||
storeType == SqlColumnType.Numeric ||
storeType == SqlColumnType.Boolean ||
storeType == SqlColumnType.DateTime;
}

private static double SerializeAsReal(JToken value, JTokenType columnType)
private static bool IsRealType(string storeType)
{
return storeType == SqlColumnType.Real ||
storeType == SqlColumnType.Float;
}

private static bool IsTextType(string storeType)
{
return storeType == SqlColumnType.Text ||
storeType == SqlColumnType.Blob ||
storeType == SqlColumnType.Guid ||
storeType == SqlColumnType.Json ||
storeType == SqlColumnType.Uri ||
storeType == SqlColumnType.TimeSpan;
}

private static object SerializeAsNumber(JToken value, JTokenType columnType)
{
if (columnType == JTokenType.Date)
{
var date = value.ToObject<DateTime>();
if (date.Kind == DateTimeKind.Unspecified)
{
date = DateTime.SpecifyKind(date, DateTimeKind.Utc);
}
double unixTimestamp = Math.Round((date.ToUniversalTime() - epoch).TotalSeconds, 3);
return unixTimestamp;
return SerializeDateTime(value);
}
return value.Value<long>();
}

private static double SerializeAsReal(JToken value, JTokenType columnType)
{
return value.Value<double>();
}

@@ -84,7 +203,7 @@ private static string SerializeAsText(JToken value, JTokenType columnType)
return value.ToString();
}

public static JToken ParseText(JTokenType type, object value)
private static JToken ParseText(JTokenType type, object value)
{
string strValue = value as string;
if (value == null)
@@ -100,6 +219,14 @@ public static JToken ParseText(JTokenType type, object value)
{
return Convert.FromBase64String(strValue);
}
if (type == JTokenType.TimeSpan)
{
return TimeSpan.Parse(strValue);
}
if (type == JTokenType.Uri)
{
return new Uri(strValue, UriKind.RelativeOrAbsolute);
}
if (type == JTokenType.Array || type == JTokenType.Object)
{
return JToken.Parse(strValue);
@@ -108,19 +235,25 @@ public static JToken ParseText(JTokenType type, object value)
return strValue;
}

public static JToken ParseReal(JTokenType type, object value)
private static JToken ParseReal(JTokenType type, object value)
{
double dblValue = (value as double?).GetValueOrDefault();
if (type == JTokenType.Date)
double dblValue = Convert.ToDouble(value);
if (type == JTokenType.Date) // for compatibility reason i.e. in earlier release datetime was serialized as real type
{
return epoch.AddSeconds(dblValue);
return DeserializeDateTime(dblValue);
}

return dblValue;
}

public static JToken ParseInteger(JTokenType type, object value)
private static JToken ParseNumber(JTokenType type, object value)
{
long longValue = (value as long?).GetValueOrDefault();
if (type == JTokenType.Date)
{
return DeserializeDateTime(Convert.ToDouble(value));
}

long longValue = Convert.ToInt64(value);
if (type == JTokenType.Boolean)
{
bool boolValue = longValue == 1;
@@ -129,62 +262,20 @@ public static JToken ParseInteger(JTokenType type, object value)
return longValue;
}

public static string GetColumnType(Type type)
private static JToken DeserializeDateTime(double value)
{
if (type == typeof(bool) ||
type == typeof(int))
{
return SqlColumnType.Integer;
}
else if (type == typeof(DateTime) ||
type == typeof(float) ||
type == typeof(double))
{
return SqlColumnType.Real;
}
else if (type == typeof(string) ||
type == typeof(Guid) ||
type == typeof(byte[]))
{
return SqlColumnType.Text;
}

throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, WindowsAzure.MobileServices.SQLiteStore.Properties.Resources.SQLiteStore_ValueTypeNotSupported, type.Name));
return epoch.AddSeconds(value);
}

public static string GetColumnType(JTokenType type, bool allowNull)
private static double SerializeDateTime(JToken value)
{
switch (type)
var date = value.ToObject<DateTime>();
if (date.Kind == DateTimeKind.Unspecified)
{
case JTokenType.Boolean:
case JTokenType.Integer:
return SqlColumnType.Integer;
case JTokenType.Date:
case JTokenType.Float:
return SqlColumnType.Real;
case JTokenType.String:
case JTokenType.Guid:
case JTokenType.Array:
case JTokenType.Object:
case JTokenType.Bytes:
return SqlColumnType.Text;
case JTokenType.Null:
if (allowNull)
{
return null;
}
throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, WindowsAzure.MobileServices.SQLiteStore.Properties.Resources.SQLiteStore_JTokenNotSupported, type));
case JTokenType.Comment:
case JTokenType.Constructor:
case JTokenType.None:
case JTokenType.Property:
case JTokenType.Raw:
case JTokenType.TimeSpan:
case JTokenType.Undefined:
case JTokenType.Uri:
default:
throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, WindowsAzure.MobileServices.SQLiteStore.Properties.Resources.SQLiteStore_JTokenNotSupported, type));
date = DateTime.SpecifyKind(date, DateTimeKind.Utc);
}
double unixTimestamp = Math.Round((date.ToUniversalTime() - epoch).TotalSeconds, 3);
return unixTimestamp;
}

private static void ValidateIdentifier(string identifier)
Original file line number Diff line number Diff line change
@@ -484,7 +484,7 @@ public override QueryNode Visit(ConvertNode nodeIn)

this.sql.Append(" AS ");

string sqlType = SqlHelpers.GetColumnType(nodeIn.TargetType);
string sqlType = SqlHelpers.GetStoreCastType(nodeIn.TargetType);
this.sql.Append(sqlType);

this.sql.Append(")");
Original file line number Diff line number Diff line change
@@ -179,14 +179,17 @@ public async Task PullAsync(string tableName, MobileServiceTableKind tableKind,
{
await this.EnsureInitializedAsync();

if (parameters.Keys.Any(k => k.Equals(MobileServiceTable.IncludeDeletedParameterName, StringComparison.OrdinalIgnoreCase)))
if (parameters != null)
{
throw new ArgumentException(Resources.Pull_Cannot_Use_Reserved_Key.FormatInvariant(MobileServiceTable.IncludeDeletedParameterName));
}
if (parameters.Keys.Any(k => k.Equals(MobileServiceTable.IncludeDeletedParameterName, StringComparison.OrdinalIgnoreCase)))
{
throw new ArgumentException(Resources.Pull_Cannot_Use_Reserved_Key.FormatInvariant(MobileServiceTable.IncludeDeletedParameterName));
}

if (parameters.Keys.Any(k => k.Equals(MobileServiceTable.SystemPropertiesQueryParameterName, StringComparison.OrdinalIgnoreCase)))
{
throw new ArgumentException(Resources.Pull_Cannot_Use_Reserved_Key.FormatInvariant(MobileServiceTable.SystemPropertiesQueryParameterName));
if (parameters.Keys.Any(k => k.Equals(MobileServiceTable.SystemPropertiesQueryParameterName, StringComparison.OrdinalIgnoreCase)))
{
throw new ArgumentException(Resources.Pull_Cannot_Use_Reserved_Key.FormatInvariant(MobileServiceTable.SystemPropertiesQueryParameterName));
}
}

var table = await this.GetTable(tableName);
Original file line number Diff line number Diff line change
@@ -16,6 +16,68 @@ namespace Microsoft.WindowsAzure.MobileServices.SQLiteStore.Test.Unit
[TestClass]
public class MobileServiceSQLiteStoreExtensionTests
{
[TestMethod]
public async Task DefineTable_Succeeds_WithAllTypes_Generic()
{
var columns = new[]
{
new ColumnDefinition("id", JTokenType.String, SqlColumnType.Text),
new ColumnDefinition("__createdAt", JTokenType.Date, SqlColumnType.DateTime),
new ColumnDefinition("__updatedAt", JTokenType.Date, SqlColumnType.DateTime),
new ColumnDefinition("__version", JTokenType.String, SqlColumnType.Text),
new ColumnDefinition("Bool", JTokenType.Boolean, SqlColumnType.Boolean),
new ColumnDefinition("Byte", JTokenType.Integer, SqlColumnType.Integer),
new ColumnDefinition("SByte", JTokenType.Integer, SqlColumnType.Integer),
new ColumnDefinition("UShort", JTokenType.Integer, SqlColumnType.Integer),
new ColumnDefinition("Short", JTokenType.Integer, SqlColumnType.Integer),
new ColumnDefinition("UInt", JTokenType.Integer, SqlColumnType.Integer),
new ColumnDefinition("Int", JTokenType.Integer, SqlColumnType.Integer),
new ColumnDefinition("ULong", JTokenType.Integer, SqlColumnType.Integer),
new ColumnDefinition("Long", JTokenType.Integer, SqlColumnType.Integer),
new ColumnDefinition("Float", JTokenType.Float, SqlColumnType.Float),
new ColumnDefinition("Double", JTokenType.Float, SqlColumnType.Float),
new ColumnDefinition("Decimal", JTokenType.Float, SqlColumnType.Float),
new ColumnDefinition("String", JTokenType.String, SqlColumnType.Text),
new ColumnDefinition("Char", JTokenType.String, SqlColumnType.Text),
new ColumnDefinition("DateTime", JTokenType.Date, SqlColumnType.DateTime),
new ColumnDefinition("DateTimeOffset", JTokenType.Date, SqlColumnType.DateTime),
new ColumnDefinition("Nullable", JTokenType.Float, SqlColumnType.Float),
new ColumnDefinition("NullableDateTime", JTokenType.Date, SqlColumnType.DateTime),
new ColumnDefinition("TimeSpan", JTokenType.String, SqlColumnType.Text),
new ColumnDefinition("Uri", JTokenType.String, SqlColumnType.Text),
new ColumnDefinition("Enum1", JTokenType.String, SqlColumnType.Text),
new ColumnDefinition("Enum2", JTokenType.String, SqlColumnType.Text),
new ColumnDefinition("Enum3", JTokenType.String, SqlColumnType.Text),
new ColumnDefinition("Enum4", JTokenType.String, SqlColumnType.Text),
new ColumnDefinition("Enum5", JTokenType.String, SqlColumnType.Text),
new ColumnDefinition("Enum6", JTokenType.String, SqlColumnType.Text)
};
await TestDefineTable<AllBaseTypesWithAllSystemPropertiesType>("AllBaseTypesWithAllSystemPropertiesType", columns);
}

[TestMethod]
public async Task DefineTable_Succeeds_WithAllTypes()
{
var item = JObjectTypes.GetObjectWithAllTypes();

var columns = new[]
{
new ColumnDefinition("id", JTokenType.String, SqlColumnType.Text),
new ColumnDefinition("Object", JTokenType.Object, SqlColumnType.Json),
new ColumnDefinition("Array", JTokenType.Array, SqlColumnType.Json),
new ColumnDefinition("Integer", JTokenType.Integer, SqlColumnType.Integer),
new ColumnDefinition("Float", JTokenType.Float, SqlColumnType.Float),
new ColumnDefinition("String", JTokenType.String, SqlColumnType.Text),
new ColumnDefinition("Boolean", JTokenType.Boolean, SqlColumnType.Boolean),
new ColumnDefinition("Date", JTokenType.Date, SqlColumnType.DateTime),
new ColumnDefinition("Bytes", JTokenType.Bytes, SqlColumnType.Blob),
new ColumnDefinition("Guid", JTokenType.Guid, SqlColumnType.Guid),
new ColumnDefinition("TimeSpan", JTokenType.TimeSpan, SqlColumnType.TimeSpan)
};

await TestDefineTable(item, "AllTypes", columns);
}

[TestMethod]
public async Task DefineTable_Succeeds_WithReadonlyProperty()
{
@@ -35,10 +97,10 @@ public async Task DefineTable_Succeeds_WithSystemProperties()
{
new ColumnDefinition("id", JTokenType.String, SqlColumnType.Text),
new ColumnDefinition("String", JTokenType.String, SqlColumnType.Text),
new ColumnDefinition("__createdAt", JTokenType.Date, SqlColumnType.Real),
new ColumnDefinition("__updatedAt", JTokenType.Date, SqlColumnType.Real),
new ColumnDefinition("__createdAt", JTokenType.Date, SqlColumnType.DateTime),
new ColumnDefinition("__updatedAt", JTokenType.Date, SqlColumnType.DateTime),
new ColumnDefinition("__version", JTokenType.String, SqlColumnType.Text),
new ColumnDefinition("__deleted", JTokenType.Boolean, SqlColumnType.Integer)
new ColumnDefinition("__deleted", JTokenType.Boolean, SqlColumnType.Boolean)
};

await TestDefineTable<ToDoWithSystemPropertiesType>("stringId_test_table", columns);
@@ -59,7 +121,7 @@ public async Task DefineTable_Succeeds_WithComplexType()
{
new ColumnDefinition("id", JTokenType.Integer, SqlColumnType.Integer),
new ColumnDefinition("Name", JTokenType.String, SqlColumnType.Text),
new ColumnDefinition("Child", JTokenType.Object, SqlColumnType.Text)
new ColumnDefinition("Child", JTokenType.Object, SqlColumnType.Json)
};

await TestDefineTable<ComplexType>("ComplexType", columns);
@@ -81,6 +143,16 @@ public async Task DefineTable_Succeeds_WithDerivedType()
}

private static async Task TestDefineTable<T>(string testTableName, ColumnDefinition[] columns)
{
await TestDefineTable(testTableName, columns, store => store.DefineTable<T>());
}

private static async Task TestDefineTable(JObject item, string testTableName, ColumnDefinition[] columns)
{
await TestDefineTable(testTableName, columns, store => store.DefineTable(testTableName, item));
}

private static async Task TestDefineTable(string testTableName, ColumnDefinition[] columns, Action<MobileServiceSQLiteStore> defineAction)
{
bool defined = false;

@@ -99,7 +171,7 @@ private static async Task TestDefineTable<T>(string testTableName, ColumnDefinit

storeMock.Setup(store => store.SaveSetting(It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(0));

storeMock.Object.DefineTable<T>();
defineAction(storeMock.Object);
await storeMock.Object.InitializeAsync();

Assert.IsTrue(defined);
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
// ----------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.WindowsAzure.MobileServices.Test;
using Newtonsoft.Json.Linq;
@@ -13,29 +14,96 @@ namespace Microsoft.WindowsAzure.MobileServices.SQLiteStore.Test.Unit
public class SqlHelperTests
{
[TestMethod]
public void GetColumnType_Throws_WhenTypeIsNotSupported()
public void GetStoreCastType_Throws_WhenTypeIsNotSupported()
{
var types = new []{typeof(uint), typeof(long), typeof(short), typeof(ushort), typeof(DateTimeOffset)};
var types = new[] { typeof(SqlHelperTests), typeof(DateTimeOffset) };

foreach (Type type in types)
{
var ex = AssertEx.Throws<NotSupportedException>(() => SqlHelpers.GetColumnType(type));
var ex = AssertEx.Throws<NotSupportedException>(() => SqlHelpers.GetStoreCastType(type));
Assert.AreEqual("Value of type '" + type.Name + "' is not supported.", ex.Message);
}
}

[TestMethod]
public void GetStoreCastType_ReturnsCorrectType()
{
var data = new Dictionary<Type, string>()
{
{ typeof(bool), SqlColumnType.Numeric },
{ typeof(DateTime), SqlColumnType.Numeric },
{ typeof(decimal), SqlColumnType.Numeric },
{ typeof(int), SqlColumnType.Integer },
{ typeof(uint), SqlColumnType.Integer },
{ typeof(long), SqlColumnType.Integer },
{ typeof(ulong), SqlColumnType.Integer },
{ typeof(short), SqlColumnType.Integer },
{ typeof(ushort), SqlColumnType.Integer },
{ typeof(byte), SqlColumnType.Integer },
{ typeof(sbyte), SqlColumnType.Integer },
{ typeof(float), SqlColumnType.Real },
{ typeof(double), SqlColumnType.Real },
{ typeof(string), SqlColumnType.Text },
{ typeof(Guid), SqlColumnType.Text },
{ typeof(byte[]), SqlColumnType.Text },
{ typeof(Uri), SqlColumnType.Text },
{ typeof(TimeSpan), SqlColumnType.Text }
};

foreach (var item in data)
{
Assert.AreEqual(SqlHelpers.GetStoreCastType(item.Key), item.Value);
}
}

[TestMethod]
public void GetStoreType_ReturnsCorrectType()
{
var data = new Dictionary<JTokenType, string>()
{
{ JTokenType.Boolean, SqlColumnType.Boolean },
{ JTokenType.Integer, SqlColumnType.Integer },
{ JTokenType.Date, SqlColumnType.DateTime },
{ JTokenType.Float, SqlColumnType.Float },
{ JTokenType.String, SqlColumnType.Text },
{ JTokenType.Guid, SqlColumnType.Guid },
{ JTokenType.Array, SqlColumnType.Json },
{ JTokenType.Object, SqlColumnType.Json },
{ JTokenType.Bytes, SqlColumnType.Blob },
{ JTokenType.Uri, SqlColumnType.Uri },
{ JTokenType.TimeSpan, SqlColumnType.TimeSpan },
};

foreach (var item in data)
{
Assert.AreEqual(SqlHelpers.GetStoreType(item.Key, allowNull: false), item.Value);
}
}

[TestMethod]
public void GetStoreType_Throws_OnUnsupportedTypes()
{
var items = new[] { JTokenType.Comment, JTokenType.Constructor, JTokenType.None, JTokenType.Property, JTokenType.Raw, JTokenType.Undefined, JTokenType.Null };

foreach (var type in items)
{
var ex = AssertEx.Throws<NotSupportedException>(() => SqlHelpers.GetStoreType(type, allowNull: false));
Assert.AreEqual(ex.Message, String.Format("Property of type '{0}' is not supported.", type));
}
}

[TestMethod]
public void SerializeValue_LosesPrecision_WhenValueIsDate()
{
var original = new DateTime(635338107839470268);
var serialized = (double)SqlHelpers.SerializeValue(new JValue(original), SqlColumnType.Real, JTokenType.Date);
var serialized = (double)SqlHelpers.SerializeValue(new JValue(original), SqlColumnType.DateTime, JTokenType.Date);
Assert.AreEqual(1398213983.9470000, serialized);
}

[TestMethod]
[TestMethod]
public void ParseReal_LosesPrecision_WhenValueIsDate()
{
var date = (DateTime)SqlHelpers.ParseReal(JTokenType.Date, 1398213983.9470267);
var date = (DateTime)SqlHelpers.DeserializeValue(1398213983.9470267, SqlColumnType.Real, JTokenType.Date);
Assert.AreEqual(635338107839470000, date.Ticks);
}
}
Original file line number Diff line number Diff line change
@@ -799,7 +799,8 @@ public async Task Insert_AllTypes_ThenRead_ThenPush_ThenLookup()
DateTime = new DateTime(2010, 10, 10, 10, 10, 10, DateTimeKind.Utc),
DateTimeOffset = new DateTimeOffset(2011, 11, 11, 11, 11, 11, 11, TimeSpan.Zero),
Nullable = 12.13,
NullableDateTime = new DateTime(2014, 12, 14, 14, 14, 14, DateTimeKind.Utc),
NullableDateTime = new DateTime(2010, 10, 10, 10, 10, 10, DateTimeKind.Utc),
TimeSpan = new TimeSpan(0, 12, 12, 15, 95),
Uri = new Uri("http://example.com"),
Enum1 = Enum1.Enum1Value2,
Enum2 = Enum2.Enum2Value2,
@@ -836,6 +837,7 @@ public async Task Insert_AllTypes_ThenRead_ThenPush_ThenLookup()
""dateTime"":""2010-10-10T10:10:10.000Z"",
""dateTimeOffset"":""2011-11-11T11:11:11.011Z"",
""nullableDateTime"":""2010-10-10T10:10:10.000Z"",
""timeSpan"":""12:12:15.095"",
""nullable"":12.13,
""uri"":""http://example.com/"",
""enum1"":""Enum1Value2"",
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
using Microsoft.WindowsAzure.MobileServices.Query;
using Microsoft.WindowsAzure.MobileServices.Test;
using Microsoft.WindowsAzure.MobileServices.TestFramework;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Microsoft.WindowsAzure.MobileServices.SQLiteStore.Test.UnitTests
@@ -88,6 +89,44 @@ public void ReadAsync_Throws_WhenStoreIsNotInitialized()
TestStoreThrowOnUninitialized(store => store.ReadAsync(MobileServiceTableQueryDescription.Parse("abc", "")));
}

[AsyncTestMethod]
public async Task UpsertAsync_ThenReadAsync_AllTypes()
{
TestUtilities.DropTestTable(TestDbName, TestTable);

// first create a table called todo
using (MobileServiceSQLiteStore store = new MobileServiceSQLiteStore(TestDbName))
{
store.DefineTable(TestTable, JObjectTypes.GetObjectWithAllTypes());

await store.InitializeAsync();

var upserted = new JObject()
{
{ "id", "xyz" },
{ "Object", new JObject() { {"id", "abc"} }},
{ "Array", new JArray() { new JObject(){{"id", 3}} } },
{ "Integer", 123L },
{ "Float", 12.5m },
{ "String", "def" },
{ "Boolean", true },
{ "Date", new DateTime(2003, 5, 6, 4, 5, 1, DateTimeKind.Utc) },
{ "Bytes", new byte[] { 1, 2, 3} },
{ "Guid", new Guid("AB3EB1AB-53CD-4780-928B-A7E1CB7A927C") },
{ "TimeSpan", new TimeSpan(1234) }
};
await store.UpsertAsync(TestTable, new[] { upserted }, false);

var query = new MobileServiceTableQueryDescription(TestTable);
var items = await store.ReadAsync(query) as JArray;
Assert.IsNotNull(items);
Assert.AreEqual(items.Count, 1);

var lookedup = items.First as JObject;
Assert.AreEqual(upserted.ToString(Formatting.None), lookedup.ToString(Formatting.None));
}
}

[AsyncTestMethod]
public async Task ReadAsync_ReadsItems()
{
Original file line number Diff line number Diff line change
@@ -42,6 +42,7 @@
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
<None Include="packages.config" />
<Compile Include="SerializationTypes\JObjectTypes.cs" />
<Compile Include="SerializationTypes\TypeWithConstructor.cs" />
<Compile Include="UnitTests\Http\HttpUtilityTests.cs" />
<Compile Include="UnitTests\OData\ODataExpressionParser.Test.cs" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

using System;
using Newtonsoft.Json.Linq;

namespace Microsoft.WindowsAzure.MobileServices.Test
{
public class JObjectTypes
{
public static JObject GetObjectWithAllTypes()
{
return new JObject()
{
{ "Object", new JObject() },
{ "Array", new JArray() },
{ "Integer", 0L },
{ "Float", 0f },
{ "String", String.Empty },
{ "Boolean", false },
{ "Date", DateTime.MinValue },
{ "Bytes", new byte[0] },
{ "Guid", Guid.Empty },
{ "TimeSpan", TimeSpan.Zero }
};
}
}
}
Original file line number Diff line number Diff line change
@@ -59,7 +59,7 @@ public class NotSystemPropertyVersionType
public string version { get; set; }
}

public class AllBaseTypesWithAllSystemPropertiesType: IEquatable<AllBaseTypesWithAllSystemPropertiesType>
public class AllBaseTypesWithAllSystemPropertiesType : IEquatable<AllBaseTypesWithAllSystemPropertiesType>
{
public string Id { get; set; }

@@ -90,6 +90,7 @@ public class AllBaseTypesWithAllSystemPropertiesType: IEquatable<AllBaseTypesWit
public DateTimeOffset DateTimeOffset { get; set; }
public double? Nullable { get; set; }
public DateTime? NullableDateTime { get; set; }
public TimeSpan TimeSpan { get; set; }
public Uri Uri { get; set; }
public Enum1 Enum1 { get; set; }
public Enum2 Enum2 { get; set; }
@@ -131,6 +132,8 @@ public bool Equals(AllBaseTypesWithAllSystemPropertiesType other)
this.DateTime == other.DateTime &&
this.DateTimeOffset == other.DateTimeOffset &&
this.Nullable == other.Nullable &&
this.NullableDateTime == other.NullableDateTime &&
this.TimeSpan == other.TimeSpan &&
this.Uri == other.Uri &&
this.Enum1 == other.Enum1 &&
this.Enum2 == other.Enum2 &&
@@ -214,7 +217,7 @@ public class DoubleJsonPropertyNamedSystemPropertiesType
[CreatedAt]
public DateTime CreatedAt { get; set; }

[JsonProperty(PropertyName= "__createdAt")]
[JsonProperty(PropertyName = "__createdAt")]
public DateTime AlsoCreatedAt { get; set; }
}

Original file line number Diff line number Diff line change
@@ -799,7 +799,7 @@ await this.TestPullQueryOverrideThrows(new Dictionary<string, string>()
{ "__systemProperties", "createdAt" },
{ "param1", "val1" }
},
"The key '__systemProperties' is reserved and cannot be specified as a query parameter.");
"The key '__systemproperties' is reserved and cannot be specified as a query parameter.");
}

[AsyncTestMethod]

0 comments on commit de49712

Please sign in to comment.