diff --git a/src/SQLite.Net/Attributes/DefaultColumnInformationProvider.cs b/src/SQLite.Net/Attributes/DefaultColumnInformationProvider.cs index cb871190..55e14419 100644 --- a/src/SQLite.Net/Attributes/DefaultColumnInformationProvider.cs +++ b/src/SQLite.Net/Attributes/DefaultColumnInformationProvider.cs @@ -12,11 +12,19 @@ public class DefaultColumnInformationProvider : IColumnInformationProvider public string GetColumnName(PropertyInfo p) { - var colAttr = p.GetCustomAttributes(true).FirstOrDefault(); + var colAttr = + p.GetCustomAttributes(true).FirstOrDefault(); return colAttr == null ? p.Name : colAttr.Name; } - public bool IsIgnored(PropertyInfo p) + public string GetTableName(TypeInfo t) + { + var tableAttr = + t.GetCustomAttributes(true).FirstOrDefault(); + return tableAttr == null ? t.Name : tableAttr.Name; + } + + public bool IsIgnored(PropertyInfo p) { return p.IsDefined(typeof (IgnoreAttribute), true); } diff --git a/src/SQLite.Net/Attributes/IColumnInformationProvider.cs b/src/SQLite.Net/Attributes/IColumnInformationProvider.cs index 75cd8f65..3a9abb18 100644 --- a/src/SQLite.Net/Attributes/IColumnInformationProvider.cs +++ b/src/SQLite.Net/Attributes/IColumnInformationProvider.cs @@ -15,6 +15,7 @@ public interface IColumnInformationProvider bool IsMarkedNotNull(MemberInfo p); bool IsIgnored(PropertyInfo p); string GetColumnName(PropertyInfo p); + string GetTableName(TypeInfo t); } } diff --git a/src/SQLite.Net/PreparedSqlLiteInsertCommand.cs b/src/SQLite.Net/PreparedSqlLiteInsertCommand.cs index 3afee874..60e7fb43 100644 --- a/src/SQLite.Net/PreparedSqlLiteInsertCommand.cs +++ b/src/SQLite.Net/PreparedSqlLiteInsertCommand.cs @@ -110,6 +110,12 @@ public int ExecuteNonQuery(object[] source) sqlitePlatform.SQLiteApi.Reset(Statement); throw NotNullConstraintViolationException.New(r, sqlitePlatform.SQLiteApi.Errmsg16(Connection.Handle)); } + + else if (r == Result.Constraint) + { + sqlitePlatform.SQLiteApi.Reset(Statement); + throw SQLiteException.New(r, sqlitePlatform.SQLiteApi.Errmsg16(Connection.Handle)); + } sqlitePlatform.SQLiteApi.Reset(Statement); throw SQLiteException.New(r, r.ToString()); diff --git a/src/SQLite.Net/TableMapping.cs b/src/SQLite.Net/TableMapping.cs index 86b49a03..0c5740f7 100644 --- a/src/SQLite.Net/TableMapping.cs +++ b/src/SQLite.Net/TableMapping.cs @@ -44,10 +44,7 @@ public TableMapping(Type type, IEnumerable properties, CreateFlags } MappedType = type; - - var tableAttr = type.GetTypeInfo().GetCustomAttributes().FirstOrDefault(); - - TableName = tableAttr != null ? tableAttr.Name : MappedType.Name; + TableName = infoProvider.GetTableName(MappedType.GetTypeInfo()); var props = properties; diff --git a/src/SQLite.Net/TableQuery.cs b/src/SQLite.Net/TableQuery.cs index 96c4f989..78d34157 100644 --- a/src/SQLite.Net/TableQuery.cs +++ b/src/SQLite.Net/TableQuery.cs @@ -26,6 +26,7 @@ using System.Globalization; using System.Linq; using System.Linq.Expressions; +using System.Reflection; using System.Text; using JetBrains.Annotations; using SQLite.Net.Interop; @@ -34,19 +35,21 @@ namespace SQLite.Net { public class TableQuery : BaseTableQuery, IEnumerable { - private readonly ISQLitePlatform _sqlitePlatform; - private bool _deferred; - private BaseTableQuery _joinInner; - private Expression _joinInnerKeySelector; - private BaseTableQuery _joinOuter; - private Expression _joinOuterKeySelector; - private Expression _joinSelector; - private int? _limit; - private int? _offset; - private List _orderBys; - private Expression _where; - - private TableQuery(ISQLitePlatform platformImplementation, SQLiteConnection conn, TableMapping table) + protected readonly ISQLitePlatform _sqlitePlatform; + protected bool _deferred; + protected string _select = "*"; + protected BaseTableQuery _joinInner; + protected Expression _joinInnerKeySelector; + protected BaseTableQuery _joinOuter; + protected Expression _joinOuterKeySelector; + protected Expression _joinSelector; + protected int? _limit; + protected int? _offset; + protected List _orderBys; + protected bool _orderByRand; + protected Expression _where; + + protected TableQuery(ISQLitePlatform platformImplementation, SQLiteConnection conn, TableMapping table) { _sqlitePlatform = platformImplementation; Connection = conn; @@ -61,21 +64,49 @@ public TableQuery(ISQLitePlatform platformImplementation, SQLiteConnection conn) Table = Connection.GetMapping(typeof (T)); } + /// + /// Copy constructor + /// + /// Instance to copy + protected TableQuery(TableQuery other) + : this(other._sqlitePlatform, other.Connection, other.Table) + { + _where = other._where; + _select = other._select; + _deferred = other._deferred; + _limit = other._limit; + _offset = other._offset; + _joinInner = other._joinInner; + _joinInnerKeySelector = other._joinInnerKeySelector; + _joinOuter = other._joinOuter; + _joinOuterKeySelector = other._joinOuterKeySelector; + _joinSelector = other._joinSelector; + _orderByRand = other._orderByRand; + _orderBys = other._orderBys == null + ? null + : new List(other._orderBys); + } + [PublicAPI] public SQLiteConnection Connection { get; private set; } [PublicAPI] public TableMapping Table { get; private set; } + + private IEnumerable GetEnumerable() + { + if (!_deferred) + return GenerateCommand(_select) + .ExecuteQuery(); + + return GenerateCommand(_select) + .ExecuteDeferredQuery(); + } [PublicAPI] public IEnumerator GetEnumerator() { - if (!_deferred) - { - return GenerateCommand("*").ExecuteQuery().GetEnumerator(); - } - - return GenerateCommand("*").ExecuteDeferredQuery().GetEnumerator(); + return GetEnumerable().GetEnumerator(); } [PublicAPI] @@ -85,23 +116,47 @@ IEnumerator IEnumerable.GetEnumerator() } [PublicAPI] - public TableQuery Clone() - { - return new TableQuery(_sqlitePlatform, Connection, Table) - { - _where = _where, - _deferred = _deferred, - _limit = _limit, - _offset = _offset, - _joinInner = _joinInner, - _joinInnerKeySelector = _joinInnerKeySelector, - _joinOuter = _joinOuter, - _joinOuterKeySelector = _joinOuterKeySelector, - _joinSelector = _joinSelector, - _orderBys = _orderBys == null ? null : new List(_orderBys) - }; + public IEnumerable MapTo( + bool selectFromAvailableProperties = true) + { + if (selectFromAvailableProperties) + SelectColumns(_sqlitePlatform.ReflectionService + .GetPublicInstanceProperties(typeof(TMapped)) + .Select(prop => prop.Name) + .ToArray()); + + return GetEnumerable(); + } + + [PublicAPI] + public virtual object Clone() + { + return new TableQuery(this); } + // Alex 13/07/16 + // Should this get removed ? + + //[PublicAPI] + //public TableQuery Clone() + //{ + // return new TableQuery(_sqlitePlatform, Connection, Table) + // { + // _where = _where, + // _select = _select, + // _deferred = _deferred, + // _limit = _limit, + // _offset = _offset, + // _joinInner = _joinInner, + // _joinInnerKeySelector = _joinInnerKeySelector, + // _joinOuter = _joinOuter, + // _joinOuterKeySelector = _joinOuterKeySelector, + // _joinSelector = _joinSelector, + // _orderByRand = _orderByRand, + // _orderBys = _orderBys == null ? null : new List(_orderBys) + // }; + //} + [PublicAPI] public TableQuery Where([NotNull] Expression> predExpr) { @@ -115,7 +170,7 @@ public TableQuery Where([NotNull] Expression> predExpr) } var lambda = (LambdaExpression) predExpr; var pred = lambda.Body; - var q = Clone(); + var q = (TableQuery)Clone(); q.AddWhere(pred); return q; } @@ -123,7 +178,7 @@ public TableQuery Where([NotNull] Expression> predExpr) [PublicAPI] public TableQuery Take(int n) { - var q = Clone(); + var q = (TableQuery)Clone(); // If there is already a limit then the limit will be the minimum // of the current limit and n. @@ -171,7 +226,7 @@ public int Delete([NotNull] Expression> predExpr) [PublicAPI] public TableQuery Skip(int n) { - var q = Clone(); + var q = (TableQuery)Clone(); q._offset = n + (q._offset ?? 0); return q; @@ -186,7 +241,7 @@ public T ElementAt(int index) [PublicAPI] public TableQuery Deferred() { - var q = Clone(); + var q = (TableQuery)Clone(); q._deferred = true; return q; } @@ -197,25 +252,63 @@ public TableQuery OrderBy(Expression> orderExpr) return AddOrderBy(orderExpr, true); } + [PublicAPI] + public TableQuery OrderBy(string propertyName) + { + return AddOrderBy(propertyName, true); + } + [PublicAPI] public TableQuery OrderByDescending(Expression> orderExpr) { return AddOrderBy(orderExpr, false); } + [PublicAPI] + public TableQuery OrderByDescending(string propertyName) + { + return AddOrderBy(propertyName, false); + } + [PublicAPI] public TableQuery ThenBy(Expression> orderExpr) { return AddOrderBy(orderExpr, true); } + [PublicAPI] + public TableQuery ThenBy(string propertyName) + { + return AddOrderBy(propertyName, true); + } + [PublicAPI] public TableQuery ThenByDescending(Expression> orderExpr) { return AddOrderBy(orderExpr, false); } - private TableQuery AddOrderBy([NotNull] Expression> orderExpr, bool asc) + [PublicAPI] + public TableQuery ThenByDescending(string propertyName) + { + return AddOrderBy(propertyName, false); + } + + [PublicAPI] + public TableQuery OrderByRand() + { + if (_orderBys != null) + throw new InvalidOperationException( + "Cannot concomitantly order by Random AND column(s)"); + + var q = (TableQuery)Clone(); + q._orderByRand = true; + + return q; + } + + [PublicAPI] + public TableQuery AddOrderBy([NotNull]Expression> orderExpr, bool asc) { if (orderExpr == null) { @@ -225,6 +318,10 @@ private TableQuery AddOrderBy([NotNull] Expression> o { throw new NotSupportedException("Must be a predicate"); } + if (_orderByRand) + throw new InvalidOperationException( + "Cannot concomitantly order by Random AND column(s)"); + var lambda = (LambdaExpression) orderExpr; MemberExpression mem; @@ -243,16 +340,26 @@ private TableQuery AddOrderBy([NotNull] Expression> o { throw new NotSupportedException("Order By does not support: " + orderExpr); } - var q = Clone(); + + return AddOrderBy(mem.Member.Name, asc); + } + + [PublicAPI] + public TableQuery AddOrderBy([NotNull] string propertyName, bool asc) + { + var q = (TableQuery)Clone(); + if (q._orderBys == null) { q._orderBys = new List(); } + q._orderBys.Add(new Ordering { - ColumnName = Table.FindColumnWithPropertyName(mem.Member.Name).Name, + ColumnName = Table.FindColumnWithPropertyName(propertyName).Name, Ascending = asc }); + return q; } @@ -295,6 +402,40 @@ public TableQuery Join( return q; } + public TableQuery SelectColumns(params string[] propertiesName) + { + int i = 0; + string selectSqlStatement = ""; + + for (; i < propertiesName.Length - 1; i++) + selectSqlStatement += "\"{" + i + "}\", "; + + return SelectColumns(selectSqlStatement + "\"{" + i + "}\"", propertiesName); + } + + public TableQuery SelectColumns(string selectSqlStatement, + params string[] propertiesName) + { + var q = (TableQuery)Clone(); + + for (int i = 0; i < propertiesName.Length; i++) + { + TableMapping.Column column = Table.FindColumnWithPropertyName( + propertiesName[i] as string); + + if (column == null) + throw new ArgumentException( + "No such column " + propertiesName[i], + "propertiesName"); + + propertiesName[i] = column.Name; + } + + q._select = String.Format(selectSqlStatement, propertiesName); + + return q; + } + private SQLiteCommand GenerateCommand([NotNull] string selectionList) { if (selectionList == null) @@ -305,19 +446,25 @@ private SQLiteCommand GenerateCommand([NotNull] string selectionList) { throw new NotSupportedException("Joins are not supported."); } + var cmdText = "select " + selectionList + " from \"" + Table.TableName + "\""; + var args = new List(); if (_where != null) { var w = CompileExpr(_where, args); cmdText += " where " + w.CommandText; } + if ((_orderBys != null) && (_orderBys.Count > 0)) { var t = string.Join(", ", _orderBys.Select(o => "\"" + o.ColumnName + "\"" + (o.Ascending ? "" : " desc")).ToArray()); cmdText += " order by " + t; } + else if (_orderByRand) + cmdText += " order by RANDOM() "; + if (_limit.HasValue) { cmdText += " limit " + _limit.Value; @@ -386,6 +533,15 @@ private CompileResult CompileExpr([NotNull] Expression expr, List queryA var args = new CompileResult[call.Arguments.Count]; var obj = call.Object != null ? CompileExpr(call.Object, queryArgs) : null; + if (call.Method.Name == "Select" && args.Length == 2) + { + object val = Expression.Lambda(call) + .Compile() + .DynamicInvoke(); + + return CompileEnumerable(val, queryArgs); + } + for (var i = 0; i < args.Length; i++) { args[i] = CompileExpr(call.Arguments[i], queryArgs); @@ -506,33 +662,38 @@ private CompileResult CompileExpr([NotNull] Expression expr, List queryA // // Work special magic for enumerables // - if (val != null && val is IEnumerable && !(val is string) && !(val is IEnumerable)) + return CompileEnumerable(val, queryArgs); + } + throw new NotSupportedException("Cannot compile: " + expr.NodeType); + } + + private CompileResult CompileEnumerable(object val, List queryArgs) + { + if (val != null && val is IEnumerable && !(val is string) && !(val is IEnumerable)) + { + var sb = new StringBuilder(); + sb.Append("("); + var head = ""; + foreach (var a in (IEnumerable)val) { - var sb = new StringBuilder(); - sb.Append("("); - var head = ""; - foreach (var a in (IEnumerable) val) - { - queryArgs.Add(a); - sb.Append(head); - sb.Append("?"); - head = ","; - } - sb.Append(")"); - return new CompileResult - { - CommandText = sb.ToString(), - Value = val - }; + queryArgs.Add(a); + sb.Append(head); + sb.Append("?"); + head = ","; } - queryArgs.Add(val); + sb.Append(")"); return new CompileResult { - CommandText = "?", + CommandText = sb.ToString(), Value = val }; } - throw new NotSupportedException("Cannot compile: " + expr.NodeType); + queryArgs.Add(val); + return new CompileResult + { + CommandText = "?", + Value = val + }; } [CanBeNull] diff --git a/tests/DefaulAttributeTest.cs b/tests/DefaulAttributeTest.cs index 9d308cf3..73af4f16 100644 --- a/tests/DefaulAttributeTest.cs +++ b/tests/DefaulAttributeTest.cs @@ -77,7 +77,12 @@ public string GetColumnName(PropertyInfo p) return p.Name; } - public bool IsIgnored(PropertyInfo p) + public string GetTableName(TypeInfo t) + { + return t.Name; + } + + public bool IsIgnored(PropertyInfo p) { return false; } diff --git a/tests/ExpressionTests.cs b/tests/ExpressionTests.cs index 81d1b0f4..26e5d252 100644 --- a/tests/ExpressionTests.cs +++ b/tests/ExpressionTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using SQLite.Net.Attributes; @@ -10,7 +11,7 @@ public class ExpressionTests [Table("AGoodTableName")] private class TestTable { - [PrimaryKey] + [PrimaryKey, AutoIncrement] public int Id { get; set; } public string Name { get; set; } @@ -50,5 +51,26 @@ public void ToUpper() Assert.AreEqual(1, x.Count()); } + + [Test] + public void Select() + { + var db = new TestDb(); + + db.CreateTable(); + + List tests = new List(); + + for (int i = 0; i < 10; i++) + tests.Add(new TestTable { Name = "test" + i }); + + db.InsertAll(tests); + + var x = db.Table().Where( + t => + tests.Select(t2 => t2.Name).Contains(t.Name)); + + Assert.AreEqual(tests.Count, x.Count()); + } } } \ No newline at end of file diff --git a/tests/IgnoreTest.cs b/tests/IgnoreTest.cs index 8f1a26c3..ab48bef1 100644 --- a/tests/IgnoreTest.cs +++ b/tests/IgnoreTest.cs @@ -33,7 +33,12 @@ public string GetColumnName(PropertyInfo p) return p.Name; } - public bool IsIgnored(PropertyInfo p) + public string GetTableName(TypeInfo t) + { + return t.Name; + } + + public bool IsIgnored(PropertyInfo p) { return p.IsDefined(typeof (TestIgnoreAttribute), true); } diff --git a/tests/OrderByTests.cs b/tests/OrderByTests.cs new file mode 100644 index 00000000..e8beb533 --- /dev/null +++ b/tests/OrderByTests.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using SQLite.Net.Async; +using SQLite.Net.Attributes; + +namespace SQLite.Net.Tests +{ + + + /// + /// Defines tests that exercise async behaviour. + /// + [TestFixture] + public class OrderByTest + { + public class TestObj + { + [AutoIncrement, PrimaryKey] + public int Id { get; set; } + + public override string ToString() + { + return string.Format("[TestObj: Id={0}]", Id); + } + + public override bool Equals(Object obj) + { + return obj is TestObj && Id == ((TestObj)obj).Id; + } + + protected bool Equals(TestObj other) + { + return Id == other.Id; + } + + public override int GetHashCode() + { + return Id; + } + } + + public class TestDb : SQLiteConnection + { + public TestDb(String path) + : base(new SQLitePlatformTest(), path) + { + CreateTable(); + } + } + + [Test] + public void OrderByWorks() + { + using (var db = new TestDb(TestPath.CreateTemporaryDatabase())) + { + TestObj testObj = new TestObj(); + TestObj[] testObjects = new TestObj[100]; + + for (int i = 0; i < testObjects.Length; i++) + testObjects[i] = testObj; + + db.InsertAll(testObjects); + + try + { + AssertCollectionContent( + db.Table().OrderBy(k => k.Id), + db.Table().OrderBy(k => k.Id)); + AssertCollectionContent( + db.Table().OrderBy(k => k.Id), + db.Table().OrderByDescending(k => k.Id), + true); + AssertCollectionContent( + db.Table().OrderByRand(), + db.Table().OrderByRand(), + true); + } + catch (NotImplementedException) + { + //Allow Not implemented exceptions as the selection may be too complex. + } + } + } + + private void AssertCollectionContent( + IEnumerable col1, IEnumerable col2, bool negate = false) + { + Assert.AreEqual(col1.Count(), col2.Count()); + + var enumerator1 = col1.GetEnumerator(); + var enumerator2 = col2.GetEnumerator(); + + while (enumerator1.MoveNext() && enumerator2.MoveNext()) + { + T item1 = enumerator1.Current; + T item2 = enumerator2.Current; + + if (negate) + { + Assert.AreNotEqual(item1, item2); + + // Only one comparison suffice to assert condition true + break; + } + + Assert.AreEqual(item1, item2); + } + } + } +} \ No newline at end of file diff --git a/tests/SQLite.Net.Tests.XamarinAndroid/Resources/Resource.designer.cs b/tests/SQLite.Net.Tests.XamarinAndroid/Resources/Resource.designer.cs index bc99d2a2..afa804f4 100644 --- a/tests/SQLite.Net.Tests.XamarinAndroid/Resources/Resource.designer.cs +++ b/tests/SQLite.Net.Tests.XamarinAndroid/Resources/Resource.designer.cs @@ -28,6 +28,8 @@ public static void UpdateIdValues() { global::PCLStorage.Resource.String.ApplicationName = global::SQLite.Net.Tests.XamarinAndroid.Resource.String.ApplicationName; global::PCLStorage.Resource.String.Hello = global::SQLite.Net.Tests.XamarinAndroid.Resource.String.Hello; + global::SQLite.Net.Platform.XamarinAndroid.Resource.String.ApplicationName = global::SQLite.Net.Tests.XamarinAndroid.Resource.String.ApplicationName; + global::SQLite.Net.Platform.XamarinAndroid.Resource.String.Hello = global::SQLite.Net.Tests.XamarinAndroid.Resource.String.Hello; global::Xamarin.Android.NUnitLite.Resource.Id.OptionHostName = global::SQLite.Net.Tests.XamarinAndroid.Resource.Id.OptionHostName; global::Xamarin.Android.NUnitLite.Resource.Id.OptionPort = global::SQLite.Net.Tests.XamarinAndroid.Resource.Id.OptionPort; global::Xamarin.Android.NUnitLite.Resource.Id.OptionRemoteServer = global::SQLite.Net.Tests.XamarinAndroid.Resource.Id.OptionRemoteServer; diff --git a/tests/SelectTests.cs b/tests/SelectTests.cs index 073048d5..80e059b8 100644 --- a/tests/SelectTests.cs +++ b/tests/SelectTests.cs @@ -31,6 +31,11 @@ public override string ToString() } } + class TestMappedObj + { + public int Id { get; set; } + } + public class TestDb : SQLiteConnection { public TestDb(String path) @@ -54,8 +59,27 @@ public void SelectWorks() { //Allow Not implemented exceptions as the selection may be too complex. } + + Assert.That( + db.Table() + .SelectColumns("\"{0}\" * 2 as \"Order\"", "Order") + .Select(obj => obj.Order).First(), + Is.EqualTo(10)); } - + } + + [Test] + public void SelectMapping() + { + using (var db = new TestDb(TestPath.CreateTemporaryDatabase())) + { + db.Insert(new TestObj() { Order = 5 }); + + Assert.That( + db.Table().MapTo().First().Id, + Is.GreaterThan(0)); + } + } } } \ No newline at end of file