From 02cd81aab5390337a7b3e6b795251843f1df6340 Mon Sep 17 00:00:00 2001 From: "grzegorz.russek" Date: Tue, 4 Jun 2013 17:44:36 +0000 Subject: [PATCH] New version alpha --- DynamORM.Tests/DynamORM.Tests.csproj | 24 +- DynamORM.Tests/Helpers/AttachToDebugger.cs | 8 +- .../Helpers/Dynamic/DynamicParserTests.cs | 121 ++ DynamORM.Tests/Helpers/Users.cs | 2 +- DynamORM.Tests/Helpers/UsersBareBoneClass.cs | 2 +- .../Modify/DynamicModificationTests.cs | 60 +- DynamORM.Tests/Modify/ParserTests.cs | 72 ++ DynamORM.Tests/Select/DynamicAccessTests.cs | 322 ++++- DynamORM.Tests/Select/LegacyParserTests.cs | 284 +++++ DynamORM.Tests/Select/ParserTests.cs | 731 +++++++++++ DynamORM.Tests/Select/TypedAccessTests.cs | 362 +++++- DynamORM.Tests/TestsBase.cs | 2 +- .../Builders/DynamicInsertQueryBuilder.cs | 192 --- DynamORM/Builders/DynamicQueryBuilder.cs | 403 ------ .../Builders/DynamicSelectQueryBuilder.cs | 272 ---- .../Builders/DynamicUpdateQueryBuilder.cs | 238 ---- .../Builders/IDynamicDeleteQueryBuilder.cs | 78 ++ .../Builders/IDynamicInsertQueryBuilder.cs | 67 + DynamORM/Builders/IDynamicQueryBuilder.cs | 39 +- .../Builders/IDynamicSelectQueryBuilder.cs | 195 +++ .../Builders/IDynamicUpdateQueryBuilder.cs | 132 ++ ...micDeleteQueryBuilder.cs => IParameter.cs} | 40 +- DynamORM/Builders/ITableInfo.cs | 26 + DynamORM/DynamORM.csproj | 25 +- DynamORM/DynamicColumn.cs | 31 +- DynamORM/DynamicConnection.cs | 8 +- DynamORM/DynamicDatabase.cs | 88 +- DynamORM/DynamicExtensions.cs | 75 +- DynamORM/DynamicTable.cs | 145 ++- DynamORM/Helpers/Dynamics/DynamicParser.cs | 1136 +++++++++++++++++ DynamORM/Helpers/IExtendedDisposable.cs | 44 + DynamORM/Helpers/StringExtensions.cs | 332 +++++ DynamORM/Helpers/UnclassifiedExtensions.cs | 75 ++ DynamORM/Mapper/TableAttribute.cs | 3 + DynamORM/Properties/AssemblyInfo.cs | 4 +- 35 files changed, 4363 insertions(+), 1275 deletions(-) create mode 100644 DynamORM.Tests/Helpers/Dynamic/DynamicParserTests.cs create mode 100644 DynamORM.Tests/Modify/ParserTests.cs create mode 100644 DynamORM.Tests/Select/LegacyParserTests.cs create mode 100644 DynamORM.Tests/Select/ParserTests.cs delete mode 100644 DynamORM/Builders/DynamicInsertQueryBuilder.cs delete mode 100644 DynamORM/Builders/DynamicQueryBuilder.cs delete mode 100644 DynamORM/Builders/DynamicSelectQueryBuilder.cs delete mode 100644 DynamORM/Builders/DynamicUpdateQueryBuilder.cs create mode 100644 DynamORM/Builders/IDynamicDeleteQueryBuilder.cs create mode 100644 DynamORM/Builders/IDynamicInsertQueryBuilder.cs create mode 100644 DynamORM/Builders/IDynamicSelectQueryBuilder.cs create mode 100644 DynamORM/Builders/IDynamicUpdateQueryBuilder.cs rename DynamORM/Builders/{DynamicDeleteQueryBuilder.cs => IParameter.cs} (54%) create mode 100644 DynamORM/Builders/ITableInfo.cs create mode 100644 DynamORM/Helpers/Dynamics/DynamicParser.cs create mode 100644 DynamORM/Helpers/IExtendedDisposable.cs create mode 100644 DynamORM/Helpers/StringExtensions.cs create mode 100644 DynamORM/Helpers/UnclassifiedExtensions.cs diff --git a/DynamORM.Tests/DynamORM.Tests.csproj b/DynamORM.Tests/DynamORM.Tests.csproj index cb4aeca..4a77af6 100644 --- a/DynamORM.Tests/DynamORM.Tests.csproj +++ b/DynamORM.Tests/DynamORM.Tests.csproj @@ -13,11 +13,16 @@ v4.0 512 + False + False + False + False + obj\$(Configuration)\ true - full - false + Full + False bin\Debug\ DEBUG;TRACE prompt @@ -31,6 +36,17 @@ prompt 4 + + False + obj\ + + + 4194304 + AnyCPU + False + Auto + False + @@ -45,10 +61,12 @@ + + @@ -61,7 +79,9 @@ True Resources.resx + + diff --git a/DynamORM.Tests/Helpers/AttachToDebugger.cs b/DynamORM.Tests/Helpers/AttachToDebugger.cs index 18ede23..d920611 100644 --- a/DynamORM.Tests/Helpers/AttachToDebugger.cs +++ b/DynamORM.Tests/Helpers/AttachToDebugger.cs @@ -45,7 +45,7 @@ namespace DynamORM.Tests.Helpers Debugger.Launch(); } - /// Test anonymous type compatybility. + /// Test anonymous type compatibility. [Test] public void TestAnonType() { @@ -59,14 +59,14 @@ namespace DynamORM.Tests.Helpers [Test] public void TestAnonTypeValue() { - var a = new { x = 1, y = "bla bla" }; + var a = new { x = 1, y = "bla bla" }; var b = new { x = 1, y = "bla bla" }; Assert.AreEqual(a, b); Assert.IsTrue(a.Equals(b)); - + Dictionary dict = new Dictionary() { { a, 999 } }; - + Assert.IsTrue(dict.ContainsKey(b)); } } diff --git a/DynamORM.Tests/Helpers/Dynamic/DynamicParserTests.cs b/DynamORM.Tests/Helpers/Dynamic/DynamicParserTests.cs new file mode 100644 index 0000000..1c7d1c7 --- /dev/null +++ b/DynamORM.Tests/Helpers/Dynamic/DynamicParserTests.cs @@ -0,0 +1,121 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using DynamORM.Helpers.Dynamics; +using NUnit.Framework; + +namespace DynamORM.Tests.Helpers.Dynamic +{ + /// tests. + [TestFixture] + public class DynamicParserTests + { + /// + /// Tests the get member. + /// + [Test] + public void TestGetMember() + { + Func f = x => x.SomePropery; + + var val = DynamicParser.Parse(f).Result as DynamicParser.Node.GetMember; + + Assert.NotNull(val); + Assert.AreEqual("SomePropery", val.Name); + } + + /// + /// Tests the set member. + /// + [Test] + public void TestSetMember() + { + Func f = x => x.SomePropery = "value"; + + var val = DynamicParser.Parse(f).Result as DynamicParser.Node.SetMember; + + Assert.NotNull(val); + Assert.AreEqual("SomePropery", val.Name); + Assert.AreEqual("value", val.Value); + } + + /// + /// Tests the index of the get. + /// + [Test] + public void TestGetIndex() + { + Func f = x => x.SomePropery[0]; + + var val = DynamicParser.Parse(f).Result as DynamicParser.Node.GetIndex; + + Assert.NotNull(val); + } + + /// + /// Tests the index of the set. + /// + [Test] + public void TestSetIndex() + { + Func f = x => x.SomePropery[0] = "value"; + + var val = DynamicParser.Parse(f).Result as DynamicParser.Node.SetIndex; + + Assert.NotNull(val); + Assert.AreEqual("value", val.Value); + } + + /// + /// Tests something. + /// + [Test] + public void TestSomething() + { + Func f = x => x.SomePropery == "value" || x.OtherProperty == -1; + + var p = DynamicParser.Parse(f); + var val = p.Result as DynamicParser.Node.Binary; + + Assert.NotNull(val); + + var left = val.Host as DynamicParser.Node.Binary; + var right = val.Right as DynamicParser.Node.Binary; + + Assert.NotNull(left); + Assert.NotNull(right); + + Assert.IsInstanceOf(left.Host); + Assert.IsInstanceOf(right.Host); + + Assert.AreEqual("value", left.Right); + Assert.AreEqual(-1, right.Right); + } + } +} \ No newline at end of file diff --git a/DynamORM.Tests/Helpers/Users.cs b/DynamORM.Tests/Helpers/Users.cs index fe47a5c..c29917c 100644 --- a/DynamORM.Tests/Helpers/Users.cs +++ b/DynamORM.Tests/Helpers/Users.cs @@ -46,7 +46,7 @@ namespace DynamORM.Tests.Helpers [Column("login")] public string Login { get; set; } - /// Gets or sets first columnvalue. + /// Gets or sets first column value. [Column("first")] public string First { get; set; } diff --git a/DynamORM.Tests/Helpers/UsersBareBoneClass.cs b/DynamORM.Tests/Helpers/UsersBareBoneClass.cs index 7879bfb..82ee275 100644 --- a/DynamORM.Tests/Helpers/UsersBareBoneClass.cs +++ b/DynamORM.Tests/Helpers/UsersBareBoneClass.cs @@ -45,7 +45,7 @@ namespace DynamORM.Tests.Helpers /// Gets or sets login column value. public string login { get; set; } - /// Gets or sets first columnvalue. + /// Gets or sets first column value. public string first { get; set; } /// Gets or sets last column value. diff --git a/DynamORM.Tests/Modify/DynamicModificationTests.cs b/DynamORM.Tests/Modify/DynamicModificationTests.cs index 9411d95..b5ecbd2 100644 --- a/DynamORM.Tests/Modify/DynamicModificationTests.cs +++ b/DynamORM.Tests/Modify/DynamicModificationTests.cs @@ -65,12 +65,12 @@ namespace DynamORM.Tests.Modify [Test] public void TestInsertByArguments() { - Assert.AreEqual(1, GetTestTable().Insert(code: 201, first: null, last: "Gagarin", email: "juri.gagarin@megacorp.com", quote: "bla, bla, bla")); + Assert.AreEqual(1, GetTestTable().Insert(code: "201", first: null, last: "Gagarin", email: "juri.gagarin@megacorp.com", quote: "bla, bla, bla")); // Verify - var o = GetTestTable().Single(code: 201); + var o = GetTestTable().Single(code: "201"); Assert.Less(200, o.id); - Assert.AreEqual("201", o.code); + Assert.AreEqual("201", o.code.ToString()); Assert.AreEqual(null, o.first); Assert.AreEqual("Gagarin", o.last); Assert.AreEqual("juri.gagarin@megacorp.com", o.email); @@ -82,12 +82,12 @@ namespace DynamORM.Tests.Modify [Test] public void TestInsertByDynamicObjects() { - Assert.AreEqual(1, GetTestTable().Insert(values: new { code = 202, first = DBNull.Value, last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" })); + Assert.AreEqual(1, GetTestTable().Insert(values: new { code = "202", first = DBNull.Value, last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" })); // Verify - var o = GetTestTable().Single(code: 202); + var o = GetTestTable().Single(code: "202"); Assert.Less(200, o.id); - Assert.AreEqual("202", o.code); + Assert.AreEqual("202", o.code.ToString()); Assert.AreEqual(null, o.first); Assert.AreEqual("Gagarin", o.last); Assert.AreEqual("juri.gagarin@megacorp.com", o.email); @@ -112,9 +112,9 @@ namespace DynamORM.Tests.Modify })); // Verify - var o = u.Single(code: 203); + var o = u.Single(code: "203"); Assert.Less(200, o.id); - Assert.AreEqual("203", o.code); + Assert.AreEqual("203", o.code.ToString()); Assert.AreEqual(null, o.first); Assert.AreEqual("Gagarin", o.last); Assert.AreEqual("juri.gagarin@megacorp.com", o.email); @@ -139,9 +139,9 @@ namespace DynamORM.Tests.Modify })); // Verify - var o = u.Single(code: 204); + var o = u.Single(code: "204"); Assert.Less(200, o.id); - Assert.AreEqual("204", o.code); + Assert.AreEqual("204", o.code.ToString()); Assert.AreEqual(null, o.first); Assert.AreEqual("Gagarin", o.last); Assert.AreEqual("juri.gagarin@megacorp.com", o.email); @@ -157,12 +157,12 @@ namespace DynamORM.Tests.Modify [Test] public void TestUpdateByArguments() { - Assert.AreEqual(1, GetTestTable().Update(id: 1, code: 201, first: null, last: "Gagarin", email: "juri.gagarin@megacorp.com", quote: "bla, bla, bla")); + Assert.AreEqual(1, GetTestTable().Update(id: 1, code: "201", first: null, last: "Gagarin", email: "juri.gagarin@megacorp.com", quote: "bla, bla, bla")); // Verify - var o = GetTestTable().Single(code: 201); + var o = GetTestTable().Single(code: "201"); Assert.AreEqual(1, o.id); - Assert.AreEqual("201", o.code); + Assert.AreEqual("201", o.code.ToString()); Assert.AreEqual(null, o.first); Assert.AreEqual("Gagarin", o.last); Assert.AreEqual("juri.gagarin@megacorp.com", o.email); @@ -174,12 +174,12 @@ namespace DynamORM.Tests.Modify [Test] public void TestUpdateByDynamicObject() { - Assert.AreEqual(1, GetTestTable().Update(update: new { id = 2, code = 202, first = DBNull.Value, last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" })); + Assert.AreEqual(1, GetTestTable().Update(update: new { id = 2, code = "202", first = DBNull.Value, last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" })); // Verify - var o = GetTestTable().Single(code: 202); + var o = GetTestTable().Single(code: "202"); Assert.AreEqual(2, o.id); - Assert.AreEqual("202", o.code); + Assert.AreEqual("202", o.code.ToString()); Assert.AreEqual(null, o.first); Assert.AreEqual("Gagarin", o.last); Assert.AreEqual("juri.gagarin@megacorp.com", o.email); @@ -204,9 +204,9 @@ namespace DynamORM.Tests.Modify })); // Verify - var o = u.Single(code: 203); + var o = u.Single(code: "203"); Assert.AreEqual(3, o.id); - Assert.AreEqual("203", o.code); + Assert.AreEqual("203", o.code.ToString()); Assert.AreEqual(null, o.first); Assert.AreEqual("Gagarin", o.last); Assert.AreEqual("juri.gagarin@megacorp.com", o.email); @@ -231,9 +231,9 @@ namespace DynamORM.Tests.Modify })); // Verify - var o = u.Single(code: 204); + var o = u.Single(code: "204"); Assert.AreEqual(4, o.id); - Assert.AreEqual("204", o.code); + Assert.AreEqual("204", o.code.ToString()); Assert.AreEqual(null, o.first); Assert.AreEqual("Gagarin", o.last); Assert.AreEqual("juri.gagarin@megacorp.com", o.email); @@ -245,12 +245,12 @@ namespace DynamORM.Tests.Modify [Test] public void TestUpdateByDynamicObjects() { - Assert.AreEqual(1, GetTestTable().Update(values: new { code = 205, first = DBNull.Value, last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" }, where: new { id = 5 })); + Assert.AreEqual(1, GetTestTable().Update(values: new { code = "205", first = DBNull.Value, last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" }, where: new { id = 5 })); // Verify - var o = GetTestTable().Single(code: 205); + var o = GetTestTable().Single(code: "205"); Assert.AreEqual(5, o.id); - Assert.AreEqual("205", o.code); + Assert.AreEqual("205", o.code.ToString()); Assert.AreEqual(null, o.first); Assert.AreEqual("Gagarin", o.last); Assert.AreEqual("juri.gagarin@megacorp.com", o.email); @@ -275,9 +275,9 @@ namespace DynamORM.Tests.Modify }, id: 6)); // Verify - var o = u.Single(code: 206); + var o = u.Single(code: "206"); Assert.AreEqual(6, o.id); - Assert.AreEqual("206", o.code); + Assert.AreEqual("206", o.code.ToString()); Assert.AreEqual(null, o.first); Assert.AreEqual("Gagarin", o.last); Assert.AreEqual("juri.gagarin@megacorp.com", o.email); @@ -302,9 +302,9 @@ namespace DynamORM.Tests.Modify }, id: 7)); // Verify - var o = u.Single(code: 207); + var o = u.Single(code: "207"); Assert.AreEqual(7, o.id); - Assert.AreEqual("207", o.code); + Assert.AreEqual("207", o.code.ToString()); Assert.AreEqual(null, o.first); Assert.AreEqual("Gagarin", o.last); Assert.AreEqual("juri.gagarin@megacorp.com", o.email); @@ -320,10 +320,10 @@ namespace DynamORM.Tests.Modify [Test] public void TestDeleteByArguments() { - Assert.AreEqual(1, GetTestTable().Delete(code: 10)); + Assert.AreEqual(1, GetTestTable().Delete(code: "10")); // Verify - Assert.AreEqual(0, GetTestTable().Count(code: 10)); + Assert.AreEqual(0, GetTestTable().Count(code: "10")); } /// Test row deleting by dynamic objects (all except ID should be ignored). @@ -380,7 +380,7 @@ namespace DynamORM.Tests.Modify [Test] public void TestDeleteyDynamicObjectWhere() { - Assert.AreEqual(1, GetTestTable().Delete(where: new { id = 14, code = 14 })); + Assert.AreEqual(1, GetTestTable().Delete(where: new { id = 14, code = "14" })); // Verify Assert.AreEqual(0, GetTestTable().Count(id: 14)); diff --git a/DynamORM.Tests/Modify/ParserTests.cs b/DynamORM.Tests/Modify/ParserTests.cs new file mode 100644 index 0000000..5603885 --- /dev/null +++ b/DynamORM.Tests/Modify/ParserTests.cs @@ -0,0 +1,72 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Linq; +using DynamORM.Builders.Implementation; +using NUnit.Framework; + +namespace DynamORM.Tests.Modify +{ + /// New parser tests. + [TestFixture] + public class ParserTests : TestsBase + { + /// Setup test parameters. + [TestFixtureSetUp] + public virtual void SetUp() + { + CreateTestDatabase(); + CreateDynamicDatabase( + DynamicDatabaseOptions.SingleConnection | + DynamicDatabaseOptions.SingleTransaction | + DynamicDatabaseOptions.SupportLimitOffset); + } + + /// Tear down test objects. + [TestFixtureTearDown] + public virtual void TearDown() + { + DestroyDynamicDatabase(); + DestroyTestDatabase(); + } + + /// + /// Tests the basic insert. + /// + [Test] + public void TestBasicInsert() + { + var cmd = new DynamicInsertQueryBuilder(Database, "Users"); + + cmd.Insert(x => x.Users.Code = "001", x => x.Users.Name = "Admin", x => x.Users.IsAdmin = 1); + + Assert.AreEqual(string.Format(@"INSERT INTO ""Users"" (""Code"", ""Name"", ""IsAdmin"") VALUES ({0})", + string.Join(", ", cmd.Parameters.Keys.Select(p => string.Format("[${0}]", p)))), cmd.CommandText()); + } + } +} \ No newline at end of file diff --git a/DynamORM.Tests/Select/DynamicAccessTests.cs b/DynamORM.Tests/Select/DynamicAccessTests.cs index 3f1cecd..223e569 100644 --- a/DynamORM.Tests/Select/DynamicAccessTests.cs +++ b/DynamORM.Tests/Select/DynamicAccessTests.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; using System.Linq; +using DynamORM.Builders; using NUnit.Framework; namespace DynamORM.Tests.Select @@ -60,6 +61,13 @@ namespace DynamORM.Tests.Select return Database.Table("users"); } + /// Create table using specified method. + /// Dynamic table. + public virtual IDynamicSelectQueryBuilder GetTestBuilder() + { + return Database.Table("users").Query() as IDynamicSelectQueryBuilder; + } + #region Select /// Test unknown op. @@ -76,7 +84,14 @@ namespace DynamORM.Tests.Select Assert.AreEqual(200, GetTestTable().Count(columns: "id")); } - /// Test count with in steatement. + /// Test dynamic Count method. + [Test] + public void TestCount2() + { + Assert.AreEqual(200, GetTestBuilder().Select(x => x.Count(x.id)).Scalar()); + } + + /// Test count with in statement. [Test] public void TestSelectInEnumerableCount() { @@ -87,7 +102,17 @@ namespace DynamORM.Tests.Select })); } - /// Test count with in steatement. + /// Test count with in statement. + [Test] + public void TestSelectInEnumerableCount2() + { + Assert.AreEqual(4, GetTestBuilder() + .Where(x => x.last.In(new object[] { "Hendricks", "Goodwin", "Freeman" }.Take(3))) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test count with in statement. [Test] public void TestSelectInArrayCount() { @@ -98,6 +123,16 @@ namespace DynamORM.Tests.Select })); } + /// Test count with in statement. + [Test] + public void TestSelectInArrayCount2() + { + Assert.AreEqual(4, GetTestBuilder() + .Where(x => x.last.In(new object[] { "Hendricks", "Goodwin", "Freeman" })) + .Select(x => x.Count()) + .Scalar()); + } + /// Test dynamic First method. [Test] public void TestFirst() @@ -105,6 +140,16 @@ namespace DynamORM.Tests.Select Assert.AreEqual(1, GetTestTable().First(columns: "id").id); } + /// Test dynamic First method. + [Test] + public void TestFirst2() + { + Assert.AreEqual(1, GetTestBuilder() + .Select(x => x.id) + .Execute() + .First().id); + } + /// Test dynamic Last method. [Test] public void TestLast() @@ -112,6 +157,16 @@ namespace DynamORM.Tests.Select Assert.AreEqual(200, GetTestTable().Last(columns: "id").id); } + /// Test dynamic Last method. + [Test] + public void TestLast2() + { + Assert.AreEqual(200, GetTestBuilder() + .Select(x => x.id) + .Execute() + .Last().id); + } + /// Test dynamic Count method. [Test] public void TestCountSpecificRecord() @@ -119,6 +174,16 @@ namespace DynamORM.Tests.Select Assert.AreEqual(1, GetTestTable().Count(first: "Ori")); } + /// Test dynamic Count method. + [Test] + public void TestCountSpecificRecord2() + { + Assert.AreEqual(1, GetTestBuilder() + .Where(x => x.first == "Ori") + .Select(x => x.Count()) + .Scalar()); + } + /// Test dynamic Min method. [Test] public void TestMin() @@ -126,6 +191,15 @@ namespace DynamORM.Tests.Select Assert.AreEqual(1, GetTestTable().Min(columns: "id")); } + /// Test dynamic Min method. + [Test] + public void TestMin2() + { + Assert.AreEqual(1, GetTestBuilder() + .Select(x => x.Min(x.id)) + .Scalar()); + } + /// Test dynamic Min method. [Test] public void TestMax() @@ -133,6 +207,15 @@ namespace DynamORM.Tests.Select Assert.AreEqual(200, GetTestTable().Max(columns: "id")); } + /// Test dynamic Min method. + [Test] + public void TestMax2() + { + Assert.AreEqual(200, GetTestBuilder() + .Select(x => x.Max(x.id)) + .Scalar()); + } + /// Test dynamic Min method. [Test] public void TesttAvg() @@ -140,6 +223,15 @@ namespace DynamORM.Tests.Select Assert.AreEqual(100.5, GetTestTable().Avg(columns: "id")); } + /// Test dynamic Min method. + [Test] + public void TesttAvg2() + { + Assert.AreEqual(100.5, GetTestBuilder() + .Select(x => x.Avg(x.id)) + .Scalar()); + } + /// Test dynamic Sum method. [Test] public void TestSum() @@ -147,6 +239,15 @@ namespace DynamORM.Tests.Select Assert.AreEqual(20100, GetTestTable().Sum(columns: "id")); } + /// Test dynamic Sum method. + [Test] + public void TestSum2() + { + Assert.AreEqual(20100, GetTestBuilder() + .Select(x => x.Sum(x.id)) + .Scalar()); + } + /// Test dynamic Scalar method for invalid operation exception. [Test] public void TestScalarException() @@ -161,6 +262,16 @@ namespace DynamORM.Tests.Select Assert.AreEqual("Ori", GetTestTable().Scalar(columns: "first", id: 19)); } + /// Test dynamic Scalar method. + [Test] + public void TestScalar2() + { + Assert.AreEqual("Ori", GetTestBuilder() + .Where(x => x.id == 19) + .Select(x => x.first) + .Scalar()); + } + /// Test dynamic Scalar method with SQLite specific aggregate. [Test] public void TestScalarGroupConcat() @@ -171,6 +282,19 @@ namespace DynamORM.Tests.Select GetTestTable().Scalar(columns: "first:first:group_concat", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); } + /// Test dynamic Scalar method with SQLite specific aggregate. + [Test] + public void TestScalarGroupConcat2() + { + // This test should produce something like this: + // select group_concat("first") AS first from "users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestBuilder() + .Where(x => x.id < 20) + .Select(x => x.group_concat(x.first).As(x.first)) + .Scalar()); + } + /// Test dynamic Scalar method with SQLite specific aggregate not using aggregate field. [Test] public void TestScalarGroupConcatNoAggregateField() @@ -181,6 +305,19 @@ namespace DynamORM.Tests.Select GetTestTable().Scalar(columns: "group_concat(first):first", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); } + /// Test dynamic Scalar method with SQLite specific aggregate not using aggregate field. + [Test] + public void TestScalarGroupConcatNoAggregateField2() + { + // This test should produce something like this: + // select group_concat(first) AS first from "users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestBuilder() + .Where(x => x.id < 20) + .Select("group_concat(first):first") + .Scalar()); + } + /// Test something fancy... like: select "first", count("first") occurs from "users" group by "first" order by 2 desc;. [Test] public void TestFancyAggregateQuery() @@ -196,6 +333,26 @@ namespace DynamORM.Tests.Select Assert.AreEqual(1, v.Last().occurs); } + /// Test something fancy... like: select "first", count("first") occurs from "users" group by "first" order by 2 desc;. + [Test] + public void TestFancyAggregateQuery2() + { + var v = GetTestBuilder() + .Select(x => x.first, x => x.Count(x.first).As(x.occurs)) + .GroupBy(x => x.first) + .OrderBy(x => x.Desc(2)) + .Execute() + .ToList(); + + Assert.IsNotNull(v); + Assert.AreEqual(187, v.Count()); + Assert.AreEqual(4, v.First().occurs); + Assert.AreEqual("Logan", v.First().first); + Assert.AreEqual(2, v.Take(10).Last().occurs); + Assert.AreEqual(1, v.Take(11).Last().occurs); + Assert.AreEqual(1, v.Last().occurs); + } + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("login")) len from "users";. [Test] public void TestAggregateInAggregate() @@ -203,6 +360,15 @@ namespace DynamORM.Tests.Select Assert.AreEqual(12.77, GetTestTable().Scalar(columns: @"length(""login""):len:avg")); } + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("login")) len from "users";. + [Test] + public void TestAggregateInAggregate2() + { + Assert.AreEqual(12.77, GetTestBuilder() + .Select(x => x.Avg(x.Length(x.login)).As(x.len)) + .Scalar()); + } + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("email")) len from "users";. [Test] public void TestAggregateInAggregateMark2() @@ -210,6 +376,15 @@ namespace DynamORM.Tests.Select Assert.AreEqual(27.7, GetTestTable().Avg(columns: @"length(""email""):len")); } + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("email")) len from "users";. + [Test] + public void TestAggregateInAggregateMark3() + { + Assert.AreEqual(27.7, GetTestBuilder() + .Select(x => "AVG(LENGTH(email)) AS LEN") + .Scalar()); + } + /// Test emails longer than 27 chars. select count(*) from "users" where length("email") > 27;. public void TestFunctionInWhere() { @@ -224,6 +399,15 @@ namespace DynamORM.Tests.Select })); } + /// Test emails longer than 27 chars. select count(*) from "users" where length("email") > 27;. + public void TestFunctionInWhere2() + { + Assert.AreEqual(97, GetTestBuilder() + .Where(x => x.Length(x.email) > 27) + .Select(x => x.Count()) + .Scalar()); + } + /// Test dynamic Single multi. [Test] public void TestSingleObject() @@ -236,6 +420,22 @@ namespace DynamORM.Tests.Select Assert.AreEqual(exp.last, o.last); } + /// Test dynamic Single multi. + [Test] + public void TestSingleObject2() + { + var exp = new { id = 19, first = "Ori", last = "Ellis" }; + var o = GetTestBuilder() + .Where(x => x.id == 19) + .Select(x => new { id = x.id, first = x.first, last = x.last }) + .Execute() + .First(); + + Assert.AreEqual(exp.id, o.id); + Assert.AreEqual(exp.first, o.first); + Assert.AreEqual(exp.last, o.last); + } + #endregion Select #region Where @@ -247,6 +447,14 @@ namespace DynamORM.Tests.Select Assert.AreEqual("hoyt.tran", GetTestTable().Single(where: new DynamicColumn("id").Eq(100)).login); } + /// Test dynamic where expression equal. + [Test] + public void TestWhereEq2() + { + Assert.AreEqual("hoyt.tran", GetTestBuilder() + .Where(x => x.id == 100).Execute().First().login); + } + /// Test dynamic where expression not equal. [Test] public void TestWhereNot() @@ -254,6 +462,16 @@ namespace DynamORM.Tests.Select Assert.AreEqual(199, GetTestTable().Count(where: new DynamicColumn("id").Not(100))); } + /// Test dynamic where expression not equal. + [Test] + public void TestWhereNot2() + { + Assert.AreEqual(199, GetTestBuilder() + .Where(x => x.id != 100) + .Select(x => x.Count()) + .Scalar()); + } + /// Test dynamic where expression like. [Test] public void TestWhereLike() @@ -261,6 +479,14 @@ namespace DynamORM.Tests.Select Assert.AreEqual(100, GetTestTable().Single(where: new DynamicColumn("login").Like("Hoyt.%")).id); } + /// Test dynamic where expression like. + [Test] + public void TestWhereLike2() + { + Assert.AreEqual(100, GetTestBuilder() + .Where(x => x.login.Like("Hoyt.%")).Execute().First().id); + } + /// Test dynamic where expression not like. [Test] public void TestWhereNotLike() @@ -268,6 +494,26 @@ namespace DynamORM.Tests.Select Assert.AreEqual(199, GetTestTable().Count(where: new DynamicColumn("login").NotLike("Hoyt.%"))); } + /// Test dynamic where expression not like. + [Test] + public void TestWhereNotLike2() + { + Assert.AreEqual(199, GetTestBuilder() + .Where(x => x.login.NotLike("Hoyt.%")) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test dynamic where expression not like. + [Test] + public void TestWhereNotLike3() + { + Assert.AreEqual(199, GetTestBuilder() + .Where(x => !x.login.Like("Hoyt.%")) + .Select(x => x.Count()) + .Scalar()); + } + /// Test dynamic where expression greater. [Test] public void TestWhereGt() @@ -275,6 +521,16 @@ namespace DynamORM.Tests.Select Assert.AreEqual(100, GetTestTable().Count(where: new DynamicColumn("id").Greater(100))); } + /// Test dynamic where expression greater. + [Test] + public void TestWhereGt2() + { + Assert.AreEqual(100, GetTestBuilder() + .Where(x => x.id > 100) + .Select(x => x.Count()) + .Scalar()); + } + /// Test dynamic where expression greater or equal. [Test] public void TestWhereGte() @@ -282,6 +538,16 @@ namespace DynamORM.Tests.Select Assert.AreEqual(101, GetTestTable().Count(where: new DynamicColumn("id").GreaterOrEqual(100))); } + /// Test dynamic where expression greater or equal. + [Test] + public void TestWhereGte2() + { + Assert.AreEqual(101, GetTestBuilder() + .Where(x => x.id >= 100) + .Select(x => x.Count()) + .Scalar()); + } + /// Test dynamic where expression less. [Test] public void TestWhereLt() @@ -289,6 +555,16 @@ namespace DynamORM.Tests.Select Assert.AreEqual(99, GetTestTable().Count(where: new DynamicColumn("id").Less(100))); } + /// Test dynamic where expression less. + [Test] + public void TestWhereLt2() + { + Assert.AreEqual(99, GetTestBuilder() + .Where(x => x.id < 100) + .Select(x => x.Count()) + .Scalar()); + } + /// Test dynamic where expression less or equal. [Test] public void TestWhereLte() @@ -296,6 +572,16 @@ namespace DynamORM.Tests.Select Assert.AreEqual(100, GetTestTable().Count(where: new DynamicColumn("id").LessOrEqual(100))); } + /// Test dynamic where expression less or equal. + [Test] + public void TestWhereLte2() + { + Assert.AreEqual(100, GetTestBuilder() + .Where(x => x.id <= 100) + .Select(x => x.Count()) + .Scalar()); + } + /// Test dynamic where expression between. [Test] public void TestWhereBetween() @@ -303,7 +589,17 @@ namespace DynamORM.Tests.Select Assert.AreEqual(26, GetTestTable().Count(where: new DynamicColumn("id").Between(75, 100))); } - /// Test dynamic where expression in params. + /// Test dynamic where expression between. + [Test] + public void TestWhereBetween2() + { + Assert.AreEqual(26, GetTestBuilder() + .Where(x => x.id.Between(75, 100)) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test dynamic where expression in parameters. [Test] public void TestWhereIn1() { @@ -317,6 +613,26 @@ namespace DynamORM.Tests.Select Assert.AreEqual(3, GetTestTable().Count(where: new DynamicColumn("id").In(new[] { 75, 99, 100 }))); } + /// Test dynamic where expression in parameters. + [Test] + public void TestWhereIn3() + { + Assert.AreEqual(3, GetTestBuilder() + .Where(x => x.id.In(75, 99, 100)) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test dynamic where expression in parameters. + [Test] + public void TestWhereIn4() + { + Assert.AreEqual(3, GetTestBuilder() + .Where(x => x.id.In(new[] { 75, 99, 100 })) + .Select(x => x.Count()) + .Scalar()); + } + #endregion Where } } \ No newline at end of file diff --git a/DynamORM.Tests/Select/LegacyParserTests.cs b/DynamORM.Tests/Select/LegacyParserTests.cs new file mode 100644 index 0000000..70d0242 --- /dev/null +++ b/DynamORM.Tests/Select/LegacyParserTests.cs @@ -0,0 +1,284 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Linq; +using DynamORM.Builders; +using DynamORM.Builders.Implementation; +using NUnit.Framework; + +namespace DynamORM.Tests.Select +{ + /// Tests of legacy parser methods. + public class LegacyParserTests : TestsBase + { + /// Setup test parameters. + [TestFixtureSetUp] + public virtual void SetUp() + { + CreateTestDatabase(); + CreateDynamicDatabase( + DynamicDatabaseOptions.SingleConnection | + DynamicDatabaseOptions.SingleTransaction | + DynamicDatabaseOptions.SupportLimitOffset); + } + + /// Tear down test objects. + [TestFixtureTearDown] + public virtual void TearDown() + { + DestroyDynamicDatabase(); + DestroyTestDatabase(); + } + + /// + /// Tests the where expression equal. + /// + [Test] + public void TestWhereEq() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").Eq(0)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" = [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests the where expression equal with brackets. + /// + [Test] + public void TestWhereBracketsEq() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").Eq(0).SetBeginBlock()) + .Where(new DynamicColumn("u.IsActive").Eq(1).SetEndBlock()); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE ((u.\"Deleted\" = [${0}]) AND (u.\"IsActive\" = [${1}]))", cmd.Parameters.Keys.First(), cmd.Parameters.Keys.Last()), cmd.CommandText()); + } + + /// + /// Tests the where expression equal with brackets and or condition. + /// + [Test] + public void TestWhereBracketsOrEq() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").Eq(0).SetBeginBlock()) + .Where(new DynamicColumn("u.IsActive").Eq(1).SetOr().SetEndBlock()); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE ((u.\"Deleted\" = [${0}]) OR (u.\"IsActive\" = [${1}]))", cmd.Parameters.Keys.First(), cmd.Parameters.Keys.Last()), cmd.CommandText()); + } + + /// + /// Tests the where expression not equal. + /// + [Test] + public void TestWhereNotEq() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").Not(0)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" <> [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests the where expression greater. + /// + [Test] + public void TestWhereGreater() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").Greater(0)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" > [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests the where expression greater or equal. + /// + [Test] + public void TestWhereGreaterOrEqual() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").GreaterOrEqual(0)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" >= [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests the where expression less. + /// + [Test] + public void TestWhereLess() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").Less(1)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" < [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests the where expression less or equal. + /// + [Test] + public void TestWhereLessOrEqual() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").LessOrEqual(1)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" <= [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests the where expression like. + /// + [Test] + public void TestWhereLike() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").Like("%1")); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE u.\"Deleted\" LIKE [${0}]", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests the where expression not like. + /// + [Test] + public void TestWhereNotLike() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").NotLike("%1")); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE u.\"Deleted\" NOT LIKE [${0}]", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests the where expression between. + /// + [Test] + public void TestWhereBetween() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").Between(0, 1)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE u.\"Deleted\" BETWEEN [${0}] AND [${1}]", cmd.Parameters.Keys.First(), cmd.Parameters.Keys.Last()), cmd.CommandText()); + } + + /// + /// Tests the where expression in. + /// + [Test] + public void TestWhereIn() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").In(0, 1)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE u.\"Deleted\" IN([${0}], [${1}])", cmd.Parameters.Keys.First(), cmd.Parameters.Keys.Last()), cmd.CommandText()); + } + + /// + /// Tests the where expression using anonymous types. + /// + [Test] + public void TestWhereAnon() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new { Deleted = 0, IsActive = 1, _table = "u" }); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" = [${0}]) AND (u.\"IsActive\" = [${1}])", cmd.Parameters.Keys.First(), cmd.Parameters.Keys.Last()), cmd.CommandText()); + } + + /// + /// Tests the order by column. + /// + [Test] + public void TestOrderByCol() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .OrderBy(new DynamicColumn("u.Name").Desc()); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u ORDER BY u.\"Name\" DESC"), cmd.CommandText()); + } + + /// + /// Tests the order by column number. + /// + [Test] + public void TestOrderByNum() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .OrderBy(new DynamicColumn("u.Name").SetAlias("1").Desc()); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u ORDER BY 1 DESC"), cmd.CommandText()); + } + + /// + /// Tests the group by column. + /// + [Test] + public void TestGroupByCol() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .GroupBy(new DynamicColumn("u.Name")); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u GROUP BY u.\"Name\""), cmd.CommandText()); + } + } +} \ No newline at end of file diff --git a/DynamORM.Tests/Select/ParserTests.cs b/DynamORM.Tests/Select/ParserTests.cs new file mode 100644 index 0000000..7de7914 --- /dev/null +++ b/DynamORM.Tests/Select/ParserTests.cs @@ -0,0 +1,731 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Linq; +using DynamORM.Builders; +using DynamORM.Builders.Implementation; +using NUnit.Framework; + +namespace DynamORM.Tests.Select +{ + /// + /// New parser tests. + /// + public class ParserTests : TestsBase + { + /// Setup test parameters. + [TestFixtureSetUp] + public virtual void SetUp() + { + CreateTestDatabase(); + CreateDynamicDatabase( + DynamicDatabaseOptions.SingleConnection | + DynamicDatabaseOptions.SingleTransaction | + DynamicDatabaseOptions.SupportLimitOffset); + } + + /// Tear down test objects. + [TestFixtureTearDown] + public virtual void TearDown() + { + DestroyDynamicDatabase(); + DestroyTestDatabase(); + } + + /// + /// Tests from method. + /// + [Test] + public void TestFromGet() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users); + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\"", cmd.CommandText()); + } + + /// + /// Tests from method with multi tables. + /// + [Test] + public void TestFromGetMultiKulti() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users, c => c.Clients); + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\", \"Clients\"", cmd.CommandText()); + } + + /// + /// Tests from method with as expression in text. + /// + [Test] + public void TestFromGetAs1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As("c")); + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests from method with as expression using lambda. + /// + [Test] + public void TestFromGetAs2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)); + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests from method using text. + /// + [Test] + public void TestFromText() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => "dbo.Users"); + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\"", cmd.CommandText()); + } + + /// + /// Tests from method using text with decorators. + /// + [Test] + public void TestFromDecoratedText() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => "\"dbo\".\"Users\""); + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\"", cmd.CommandText()); + } + + /// + /// Tests from method using text with as. + /// + [Test] + public void TestFromTextAs1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => "dbo.Users AS c"); + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests from method using invoke with as. + /// + [Test] + public void TestFromTextAs2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u(u.dbo.Users).As(u.u)); + + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS u", cmd.CommandText()); + } + + /// + /// Tests from method using invoke with sub query. + /// + [Test] + public void TestFromSubQuery1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u(new DynamicSelectQueryBuilder(Database).From(x => x.dbo.Users)).As("u")); + + Assert.AreEqual("SELECT * FROM (SELECT * FROM \"dbo\".\"Users\") AS u", cmd.CommandText()); + } + + /// + /// Tests from method using invoke with sub query. + /// + [Test] + public void TestFromSubQuery2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u(cmd.SubQuery(x => x.dbo.Users)).As("u")); + + Assert.AreEqual("SELECT * FROM (SELECT * FROM \"dbo\".\"Users\") AS u", cmd.CommandText()); + } + + /// + /// Tests where method with alias. + /// + [Test] + public void TestWhereAlias() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Where(u => u.c.UserName == "admin"); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE (c.\"UserName\" = [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests complex where method with alias. + /// + [Test] + public void TestWhereAliasComplex() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Where(u => u.c.UserName == "admin" || u.c.UserName == "root") + .Where(u => u.c.IsActive = true); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE ((c.\"UserName\" = [${0}]) OR (c.\"UserName\" = [${1}])) AND c.\"IsActive\" = ([${2}])", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); + } + + /// + /// Tests where method with alias using in. + /// + [Test] + public void TestWhereAliasIn() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + int[] ids = new int[] { 0, 1, 2, 3, 4, 5 }; + + cmd.From(u => u.dbo.Users.As(u.c)) + .Where(u => u.c.UserName == "admin" || u.c.UserName == "root") + .Where(u => u.c.IsActive == true) + .Where(u => u.c.Id_User.In(ids)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE ((c.\"UserName\" = [${0}]) OR (c.\"UserName\" = [${1}])) AND (c.\"IsActive\" = [${2}]) AND c.\"Id_User\" IN({3})", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2], + string.Join(", ", cmd.Parameters.Keys.Skip(3).Select(p => string.Format("[${0}]", p)))), cmd.CommandText()); + } + + /// + /// Tests where method with alias using between. + /// + [Test] + public void TestWhereAliasBetween1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + int[] ids = new int[] { 0, 5 }; + + cmd.From(u => u.dbo.Users.As(u.c)) + .Where(u => u.c.UserName == "admin" || u.c.UserName == "root") + .Where(u => u.c.IsActive == true) + .Where(u => u.c.Id_User.Between(ids)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE ((c.\"UserName\" = [${0}]) OR (c.\"UserName\" = [${1}])) AND (c.\"IsActive\" = [${2}]) AND c.\"Id_User\" BETWEEN [${3}] AND [${4}]", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2], + cmd.Parameters.Keys.ToArray()[3], cmd.Parameters.Keys.ToArray()[4]), cmd.CommandText()); + } + + /// + /// Tests where method with alias using between. + /// + [Test] + public void TestWhereAliasBetween2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + int[] ids = new int[] { 0, 5 }; + + cmd.From(u => u.dbo.Users.As(u.c)) + .Where(u => u.c.UserName == "admin" || u.c.UserName == "root") + .Where(u => u.c.IsActive == true) + .Where(u => u.c.Id_User.Between(ids[0], ids[1])); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE ((c.\"UserName\" = [${0}]) OR (c.\"UserName\" = [${1}])) AND (c.\"IsActive\" = [${2}]) AND c.\"Id_User\" BETWEEN [${3}] AND [${4}]", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2], + cmd.Parameters.Keys.ToArray()[3], cmd.Parameters.Keys.ToArray()[4]), cmd.CommandText()); + } + + /// + /// Tests where method without alias. + /// + [Test] + public void TestWhereNoAlias() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users) + .Where(u => u.UserName == "admin"); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" WHERE (\"UserName\" = [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests where method with full column name. + /// + [Test] + public void TestWhereNoAliasTableName() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users) + .Where(u => u.dbo.Users.UserName == "admin"); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" WHERE (\"dbo\".\"Users\".\"UserName\" = [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests simple join method. + /// + [Test] + public void TestJoinClassic() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr)) + .Join(u => u.dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); + } + + /// + /// Tests inner join method. + /// + [Test] + public void TestInnerJoin() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr)) + .Join(u => u.Inner().dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr INNER JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); + } + + /// + /// Tests left outer join method. + /// + [Test] + public void TestLeftOuterJoin() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr)) + .Join(u => u.LeftOuter().dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr LEFT OUTER JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); + } + + /// + /// Tests left join method. + /// + [Test] + public void TestLeftJoin() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr)) + .Join(u => u.Left().dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr LEFT JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); + } + + /// + /// Tests right outer join method. + /// + [Test] + public void TestRightOuterJoin() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr)) + .Join(u => u.RightOuter().dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr RIGHT OUTER JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); + } + + /// + /// Tests right join method. + /// + [Test] + public void TestRightJoin() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr)) + .Join(u => u.Right().dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr RIGHT JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); + } + + /// + /// Tests complex join with parameters. + /// + [Test] + public void TestJoinClassicWithParamAndWhere() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr)) + .Join(u => u.dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id && u.uc.Deleted == 0)) + .Where(u => u.usr.Active == true); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr JOIN \"dbo\".\"UserClients\" AS uc ON ((usr.\"Id_User\" = uc.\"User_Id\") AND (uc.\"Deleted\" = [${0}])) WHERE (usr.\"Active\" = [${1}])", + cmd.Parameters.Keys.First(), cmd.Parameters.Keys.Last()), cmd.CommandText()); + } + + /// + /// Tests select all. + /// + [Test] + public void TestSelectAll1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u.c.All()); + + Assert.AreEqual("SELECT c.* FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests select all. + /// + [Test] + public void TestSelectAll2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users) + .Select(u => u.dbo.Users.All()); + + Assert.AreEqual("SELECT \"dbo\".\"Users\".* FROM \"dbo\".\"Users\"", cmd.CommandText()); + } + + /// + /// Tests select field. + /// + [Test] + public void TestSelectField1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u.c.UserName); + + Assert.AreEqual("SELECT c.\"UserName\" FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests select field. + /// + [Test] + public void TestSelectField2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users) + .Select(u => u.dbo.Users.UserName); + + Assert.AreEqual("SELECT \"dbo\".\"Users\".\"UserName\" FROM \"dbo\".\"Users\"", cmd.CommandText()); + } + + /// + /// Tests select field with alias. + /// + [Test] + public void TestSelectFieldAlias1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u.c.UserName.As(u.Name)); + + Assert.AreEqual("SELECT c.\"UserName\" AS \"Name\" FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests select field with alias. + /// + [Test] + public void TestSelectFieldAlias2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users) + .Select(u => u.dbo.Users.UserName.As(u.Name)); + + Assert.AreEqual("SELECT \"dbo\".\"Users\".\"UserName\" AS \"Name\" FROM \"dbo\".\"Users\"", cmd.CommandText()); + } + + /// + /// Tests select aggregate field with alias (Sum). + /// + [Test] + public void TestSelectAggregateField1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u.Sum(u.c.UserName).As(u.Name)); + + Assert.AreEqual("SELECT Sum(c.\"UserName\") AS \"Name\" FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests select aggregate field with alias (Coalesce). + /// + [Test] + public void TestSelectAggregateField2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u.Coalesce(u.c.UserName, u.c.FirstName + " " + u.c.LastName).As(u.Name)); + + Assert.AreEqual(string.Format("SELECT Coalesce(c.\"UserName\", ((c.\"FirstName\" + [${0}]) + c.\"LastName\")) AS \"Name\" FROM \"dbo\".\"Users\" AS c", + cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests select aggregate field with alias (Sum). + /// + [Test] + public void TestSelectAggregateField3() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users) + .Select(u => u.Sum(u.dbo.Users.UserName)); + + Assert.AreEqual("SELECT Sum(\"dbo\".\"Users\".\"UserName\") FROM \"dbo\".\"Users\"", cmd.CommandText()); + } + + /// + /// Tests select from anonymous type. + /// + [Test] + public void TestSelectAnon() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => new + { + Id_User = u.c.Id_User, + Name = u.c.UserName, + }); + + Assert.AreEqual("SELECT c.\"Id_User\" AS \"Id_User\", c.\"UserName\" AS \"Name\" FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests select escaped case. + /// + [Test] + public void TestSelectCaseEscaped1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u("CASE ", u.c.IsActive, " WHEN ", 1, " THEN ", 0, " ELSE ", 1, " END").As(u.Deleted)); + + Assert.AreEqual(string.Format("SELECT CASE c.\"IsActive\" WHEN [${0}] THEN [${1}] ELSE [${2}] END AS \"Deleted\" FROM \"dbo\".\"Users\" AS c", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); + } + + /// + /// Tests select escaped case. + /// + [Test] + public void TestSelectCaseEscaped2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u("CASE WHEN ", u.c.IsActive == 1, " THEN ", 0, " ELSE ", 1, " END").As(u.Deleted)); + + Assert.AreEqual(string.Format("SELECT CASE WHEN (c.\"IsActive\" = [${0}]) THEN [${1}] ELSE [${2}] END AS \"Deleted\" FROM \"dbo\".\"Users\" AS c", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); + } + + /// + /// Tests select escaped case with sub query. + /// + [Test] + public void TestSelectCaseEscapedAndSub() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u("CASE WHEN ", u.c.IsActive == 1, " AND ", u.c.IsAdmin == u(cmd.SubQuery() + .From(x => x.dbo.AccessRights.As(x.a)) + .Where(x => x.a.User_Id == x.c.Id_User) + .Select(x => x.a.IsAdmin)), " THEN ", 0, " ELSE ", 1, " END").As(u.Deleted)); + + Assert.AreEqual(string.Format("SELECT CASE WHEN (c.\"IsActive\" = [${0}]) AND (c.\"IsAdmin\" = (SELECT a.\"IsAdmin\" FROM \"dbo\".\"AccessRights\" AS a WHERE (a.\"User_Id\" = c.\"Id_User\"))) THEN [${1}] ELSE [${2}] END AS \"Deleted\" FROM \"dbo\".\"Users\" AS c", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); + } + + /// + /// Tests group by. + /// + [Test] + public void TestGroupBy() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .GroupBy(u => u.c.UserName); + + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c GROUP BY c.\"UserName\"", cmd.CommandText()); + } + + /// + /// Tests order by. + /// + [Test] + public void TestOrderBy() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .OrderBy(u => u.c.UserName); + + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c ORDER BY c.\"UserName\" ASC", cmd.CommandText()); + } + + /// + /// Tests order by using string with number. + /// + [Test] + public void TestOrderByNumberedColumnStr() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .OrderBy(u => "1 DESC"); + + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c ORDER BY 1 DESC", cmd.CommandText()); + } + + /// + /// Tests order by using member with number. + /// + [Test] + public void TestOrderByNumberedColFn() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .OrderBy(u => u.Desc(1)); + + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c ORDER BY 1 DESC", cmd.CommandText()); + } + + /// + /// Tests order by using member with field. + /// + [Test] + public void TestOrderByAlt() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .OrderBy(u => u.Desc(u.c.UserName)); + + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c ORDER BY c.\"UserName\" DESC", cmd.CommandText()); + } + + /// + /// Tests sub query select. + /// + [Test] + public void TestSubQuerySelect() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u(cmd.SubQuery() + .From(x => x.dbo.AccessRights.As(x.a)) + .Where(x => x.a.User_Id == x.c.Id_User) + .Select(x => x.a.IsAdmin)).As(u.IsAdmin)); + + Assert.AreEqual("SELECT (SELECT a.\"IsAdmin\" FROM \"dbo\".\"AccessRights\" AS a WHERE (a.\"User_Id\" = c.\"Id_User\")) AS \"IsAdmin\" FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests sub query where. + /// + [Test] + public void TestSubQueryWhere() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Where(u => u.c.IsAdmin == u(cmd.SubQuery() + .From(x => x.dbo.AccessRights.As(x.a)) + .Where(x => x.a.User_Id == x.c.Id_User) + .Select(x => x.a.IsAdmin))); + + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c WHERE (c.\"IsAdmin\" = (SELECT a.\"IsAdmin\" FROM \"dbo\".\"AccessRights\" AS a WHERE (a.\"User_Id\" = c.\"Id_User\")))", cmd.CommandText()); + } + + /// + /// Tests sub query in. + /// + [Test] + public void TestSubQueryWhereIn() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Where(u => u.c.Id_User.In(u(cmd.SubQuery() + .From(x => x.dbo.AccessRights.As(x.a)) + .Where(x => x.a.IsAdmin == 1) + .Select(x => x.a.User_Id)))); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE c.\"Id_User\" IN((SELECT a.\"User_Id\" FROM \"dbo\".\"AccessRights\" AS a WHERE (a.\"IsAdmin\" = [${0}])))", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests sub query join. + /// + [Test] + public void TestSubQueryJoin() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Join(u => u.Inner()(cmd.SubQuery() + .From(x => x.dbo.AccessRights.As(x.a)) + .Select(x => x.a.IsAdmin, x => x.a.User_Id)).As(u.ar).On(u.ar.User_Id == u.c.Id_User)); + + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c INNER JOIN (SELECT a.\"IsAdmin\", a.\"User_Id\" FROM \"dbo\".\"AccessRights\" AS a) AS ar ON (ar.\"User_Id\" = c.\"Id_User\")", cmd.CommandText()); + } + } +} \ No newline at end of file diff --git a/DynamORM.Tests/Select/TypedAccessTests.cs b/DynamORM.Tests/Select/TypedAccessTests.cs index 81e13cd..333c74e 100644 --- a/DynamORM.Tests/Select/TypedAccessTests.cs +++ b/DynamORM.Tests/Select/TypedAccessTests.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; using System.Linq; +using DynamORM.Builders; using DynamORM.Tests.Helpers; using NUnit.Framework; @@ -65,6 +66,13 @@ namespace DynamORM.Tests.Select return Database.Table(); } + /// Create table using specified method. + /// Dynamic table. + public virtual IDynamicSelectQueryBuilder GetTestBuilder() + { + return Database.Table().Query() as IDynamicSelectQueryBuilder; + } + #region Select typed /// Test load all rows into mapped list alternate way. @@ -76,6 +84,19 @@ namespace DynamORM.Tests.Select Assert.AreEqual(200, list.Count); } + /// Test load all rows into mapped list alternate way. + [Test] + public virtual void TestTypedGetAll2() + { + var list = GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Execute() + .MapEnumerable() + .ToList(); + + Assert.AreEqual(200, list.Count); + } + /// Test unknown op. [Test] public virtual void TestTypedUnknownOperation() @@ -90,7 +111,17 @@ namespace DynamORM.Tests.Select Assert.AreEqual(200, GetTestTable().Count(type: typeof(T), columns: "id")); } - /// Test count with in steatement. + /// Test typed Count method. + [Test] + public virtual void TestTypedCount2() + { + Assert.AreEqual(200, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => x.Count(x.t.id)) + .Scalar()); + } + + /// Test count with in statement. [Test] public virtual void TestTypedSelectInEnumerableCount() { @@ -101,7 +132,18 @@ namespace DynamORM.Tests.Select })); } - /// Test count with in steatement. + /// Test count with in statement. + [Test] + public virtual void TestTypedSelectInEnumerableCount2() + { + Assert.AreEqual(4, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.last.In(new object[] { "Hendricks", "Goodwin", "Freeman" }.Take(3))) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test count with in statement. [Test] public virtual void TestTypedSelectInArrayCount() { @@ -112,6 +154,17 @@ namespace DynamORM.Tests.Select })); } + /// Test count with in statement. + [Test] + public virtual void TestTypedSelectInArrayCount2() + { + Assert.AreEqual(4, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.last.In(new object[] { "Hendricks", "Goodwin", "Freeman" })) + .Select(x => x.Count()) + .Scalar()); + } + /// Test typed First method. [Test] public virtual void TestTypedFirst() @@ -119,6 +172,17 @@ namespace DynamORM.Tests.Select Assert.AreEqual(1, GetTestTable().First(type: typeof(T), columns: "id").id); } + /// Test typed First method. + [Test] + public virtual void TestTypedFirst2() + { + Assert.AreEqual(1, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => x.t.id) + .Execute() + .First().id); + } + /// Test typed Last method. [Test] public virtual void TestTypedLast() @@ -126,6 +190,17 @@ namespace DynamORM.Tests.Select Assert.AreEqual(200, GetTestTable().Last(type: typeof(T), columns: "id").id); } + /// Test typed Last method. + [Test] + public virtual void TestTypedLast2() + { + Assert.AreEqual(200, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => x.t.id) + .Execute() + .Last().id); + } + /// Test typed Count method. [Test] public virtual void TestTypedCountSpecificRecord() @@ -140,6 +215,16 @@ namespace DynamORM.Tests.Select Assert.AreEqual(1, GetTestTable().Min(type: typeof(T), columns: "id")); } + /// Test typed Min method. + [Test] + public virtual void TestTypedMin2() + { + Assert.AreEqual(1, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => x.Min(x.t.id)) + .Scalar()); + } + /// Test typed Min method. [Test] public virtual void TestTypedMax() @@ -147,6 +232,16 @@ namespace DynamORM.Tests.Select Assert.AreEqual(200, GetTestTable().Max(type: typeof(T), columns: "id")); } + /// Test typed Min method. + [Test] + public virtual void TestTypedMax2() + { + Assert.AreEqual(200, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => x.Max(x.t.id)) + .Scalar()); + } + /// Test typed Min method. [Test] public virtual void TestTypedtAvg() @@ -154,6 +249,16 @@ namespace DynamORM.Tests.Select Assert.AreEqual(100.5, GetTestTable().Avg(type: typeof(T), columns: "id")); } + /// Test typed Min method. + [Test] + public virtual void TestTypedtAvg2() + { + Assert.AreEqual(100.5, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => x.Avg(x.t.id)) + .Scalar()); + } + /// Test typed Sum method. [Test] public virtual void TestTypedSum() @@ -161,6 +266,16 @@ namespace DynamORM.Tests.Select Assert.AreEqual(20100, GetTestTable().Sum(type: typeof(T), columns: "id")); } + /// Test typed Sum method. + [Test] + public virtual void TestTypedSum2() + { + Assert.AreEqual(20100, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => x.Sum(x.t.id)) + .Scalar()); + } + /// Test typed Scalar method for invalid operation exception. [Test] public virtual void TestTypedScalarException() @@ -175,6 +290,17 @@ namespace DynamORM.Tests.Select Assert.AreEqual("Ori", GetTestTable().Scalar(type: typeof(T), columns: "first", id: 19)); } + /// Test typed Scalar method. + [Test] + public void TestTypedScalar2() + { + Assert.AreEqual("Ori", GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id == 19) + .Select(x => x.t.first) + .Scalar()); + } + /// Test typed Scalar method with SQLite specific aggregate. [Test] public virtual void TestTypedScalarGroupConcat() @@ -185,6 +311,20 @@ namespace DynamORM.Tests.Select GetTestTable().Scalar(type: typeof(T), columns: "first:first:group_concat", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); } + /// Test typed Scalar method with SQLite specific aggregate. + [Test] + public virtual void TestTypedScalarGroupConcat2() + { + // This test should produce something like this: + // select group_concat("first") AS first from "users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id < 20) + .Select(x => x.group_concat(x.t.first).As(x.first)) + .Scalar()); + } + /// Test typed Scalar method with SQLite specific aggregate not using aggregate field. [Test] public virtual void TestTypedScalarGroupConcatNoAggregateField() @@ -195,6 +335,20 @@ namespace DynamORM.Tests.Select GetTestTable().Scalar(type: typeof(T), columns: "group_concat(first):first", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); } + /// Test typed Scalar method with SQLite specific aggregate not using aggregate field. + [Test] + public virtual void TestTypedScalarGroupConcatNoAggregateField2() + { + // This test should produce something like this: + // select group_concat("first") AS first from "users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id < 20) + .Select("group_concat(first):first") + .Scalar()); + } + /// Test something fancy... like: select "first", count("first") aggregatefield from "users" group by "first" order by 2 desc;. [Test] public virtual void TestTypedFancyAggregateQuery() @@ -210,6 +364,27 @@ namespace DynamORM.Tests.Select Assert.AreEqual(1, v.Last().aggregatefield); } + /// Test something fancy... like: select "first", count("first") aggregatefield from "users" group by "first" order by 2 desc;. + [Test] + public virtual void TestTypedFancyAggregateQuery2() + { + var v = GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => x.t.first, x => x.Count(x.t.first).As(x.aggregatefield)) + .GroupBy(x => x.t.first) + .OrderBy(x => x.Desc(2)) + .Execute() + .ToList(); + + Assert.IsNotNull(v); + Assert.AreEqual(187, v.Count()); + Assert.AreEqual(4, v.First().aggregatefield); + Assert.AreEqual("Logan", v.First().first); + Assert.AreEqual(2, v.Take(10).Last().aggregatefield); + Assert.AreEqual(1, v.Take(11).Last().aggregatefield); + Assert.AreEqual(1, v.Last().aggregatefield); + } + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("login")) len from "users";. [Test] public virtual void TestTypedAggregateInAggregate() @@ -217,6 +392,16 @@ namespace DynamORM.Tests.Select Assert.AreEqual(12.77, GetTestTable().Scalar(type: typeof(T), columns: @"length(""login""):len:avg")); } + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("login")) len from "users";. + [Test] + public virtual void TestTypedAggregateInAggregate2() + { + Assert.AreEqual(12.77, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => x.Avg(x.Length(x.t.login)).As(x.len)) + .Scalar()); + } + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("email")) len from "users";. [Test] public virtual void TestTypedAggregateInAggregateMark2() @@ -224,6 +409,16 @@ namespace DynamORM.Tests.Select Assert.AreEqual(27.7, GetTestTable().Avg(type: typeof(T), columns: @"length(""email""):len")); } + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("email")) len from "users";. + [Test] + public virtual void TestTypedAggregateInAggregateMark3() + { + Assert.AreEqual(27.7, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => "AVG(LENGTH(t.email)) AS LEN") + .Scalar()); + } + /// Test emails longer than 27 chars. select count(*) from "users" where length("email") > 27;. public virtual void TestTypedFunctionInWhere() { @@ -238,6 +433,16 @@ namespace DynamORM.Tests.Select })); } + /// Test emails longer than 27 chars. select count(*) from "users" where length("email") > 27;. + public virtual void TestTypedFunctionInWhere2() + { + Assert.AreEqual(97, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.Length(x.t.email) > 27) + .Select(x => x.Count(x.t.All())) + .Scalar()); + } + /// Test typed Single multi. [Test] public virtual void TestTypedSingleObject() @@ -250,6 +455,23 @@ namespace DynamORM.Tests.Select Assert.AreEqual(exp.last, o.last); } + /// Test typed Single multi. + [Test] + public virtual void TestTypedSingleObject2() + { + var exp = new { id = 19, first = "Ori", last = "Ellis" }; + var o = GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id == 19) + .Select(x => new { id = x.t.id, first = x.t.first, last = x.t.last }) + .Execute() + .First(); + + Assert.AreEqual(exp.id, o.id); + Assert.AreEqual(exp.first, o.first); + Assert.AreEqual(exp.last, o.last); + } + #endregion Select typed #region Where typed @@ -261,6 +483,15 @@ namespace DynamORM.Tests.Select Assert.AreEqual("hoyt.tran", GetTestTable().Single(type: typeof(T), where: new DynamicColumn("id").Eq(100)).login); } + /// Test typed where expression equal. + [Test] + public virtual void TestTypedWhereEq2() + { + Assert.AreEqual("hoyt.tran", GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id == 100).Execute().First().login); + } + /// Test typed where expression not equal. [Test] public virtual void TestTypedWhereNot() @@ -268,6 +499,17 @@ namespace DynamORM.Tests.Select Assert.AreEqual(199, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").Not(100))); } + /// Test typed where expression not equal. + [Test] + public virtual void TestTypedWhereNot2() + { + Assert.AreEqual(199, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id != 100) + .Select(x => x.Count()) + .Scalar()); + } + /// Test typed where expression like. [Test] public virtual void TestTypedWhereLike() @@ -275,6 +517,15 @@ namespace DynamORM.Tests.Select Assert.AreEqual(100, GetTestTable().Single(type: typeof(T), where: new DynamicColumn("login").Like("Hoyt.%")).id); } + /// Test typed where expression like. + [Test] + public virtual void TestTypedWhereLike2() + { + Assert.AreEqual(100, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.login.Like("Hoyt.%")).Execute().First().id); + } + /// Test typed where expression not like. [Test] public virtual void TestTypedWhereNotLike() @@ -282,6 +533,28 @@ namespace DynamORM.Tests.Select Assert.AreEqual(199, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("login").NotLike("Hoyt.%"))); } + /// Test typed where expression not like. + [Test] + public virtual void TestTypedWhereNotLike2() + { + Assert.AreEqual(199, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.login.NotLike("Hoyt.%")) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test typed where expression not like. + [Test] + public virtual void TestTypedWhereNotLike3() + { + Assert.AreEqual(199, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => !x.t.login.Like("Hoyt.%")) + .Select(x => x.Count()) + .Scalar()); + } + /// Test typed where expression greater. [Test] public virtual void TestTypedWhereGt() @@ -289,6 +562,17 @@ namespace DynamORM.Tests.Select Assert.AreEqual(100, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").Greater(100))); } + /// Test typed where expression greater. + [Test] + public virtual void TestTypedWhereGt2() + { + Assert.AreEqual(100, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id > 100) + .Select(x => x.Count()) + .Scalar()); + } + /// Test typed where expression greater or equal. [Test] public virtual void TestTypedWhereGte() @@ -296,6 +580,17 @@ namespace DynamORM.Tests.Select Assert.AreEqual(101, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").GreaterOrEqual(100))); } + /// Test typed where expression greater or equal. + [Test] + public virtual void TestTypedWhereGte2() + { + Assert.AreEqual(101, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id >= 100) + .Select(x => x.Count()) + .Scalar()); + } + /// Test typed where expression less. [Test] public virtual void TestTypedWhereLt() @@ -303,6 +598,17 @@ namespace DynamORM.Tests.Select Assert.AreEqual(99, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").Less(100))); } + /// Test typed where expression less. + [Test] + public virtual void TestTypedWhereLt2() + { + Assert.AreEqual(99, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id < 100) + .Select(x => x.Count()) + .Scalar()); + } + /// Test typed where expression less or equal. [Test] public virtual void TestTypedWhereLte() @@ -310,6 +616,17 @@ namespace DynamORM.Tests.Select Assert.AreEqual(100, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").LessOrEqual(100))); } + /// Test typed where expression less or equal. + [Test] + public virtual void TestTypedWhereLte2() + { + Assert.AreEqual(100, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id <= 100) + .Select(x => x.Count()) + .Scalar()); + } + /// Test typed where expression between. [Test] public virtual void TestTypedWhereBetween() @@ -317,7 +634,18 @@ namespace DynamORM.Tests.Select Assert.AreEqual(26, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").Between(75, 100))); } - /// Test typed where expression in params. + /// Test typed where expression between. + [Test] + public virtual void TestTypedWhereBetween2() + { + Assert.AreEqual(26, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id.Between(75, 100)) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test typed where expression in parameters. [Test] public virtual void TestTypedWhereIn1() { @@ -331,6 +659,28 @@ namespace DynamORM.Tests.Select Assert.AreEqual(3, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").In(new[] { 75, 99, 100 }))); } + /// Test typed where expression in parameters. + [Test] + public virtual void TestTypedWhereIn3() + { + Assert.AreEqual(3, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id.In(75, 99, 100)) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test typed where expression in array. + [Test] + public virtual void TestTypedWhereIn4() + { + Assert.AreEqual(3, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id.In(new[] { 75, 99, 100 })) + .Select(x => x.Count()) + .Scalar()); + } + #endregion Where typed #region Select generic @@ -358,7 +708,7 @@ namespace DynamORM.Tests.Select Assert.AreEqual(200, GetTestTable().Count(columns: "id")); } - /// Test count with in steatement. + /// Test count with in statement. [Test] public virtual void TestGenericSelectInEnumerableCount() { @@ -369,7 +719,7 @@ namespace DynamORM.Tests.Select })); } - /// Test count with in steatement. + /// Test count with in statement. [Test] public virtual void TestGenericSelectInArrayCount() { @@ -585,7 +935,7 @@ namespace DynamORM.Tests.Select Assert.AreEqual(26, GetTestTable().Count(where: new DynamicColumn("id").Between(75, 100))); } - /// Test generic where expression in params. + /// Test generic where expression in parameters. [Test] public virtual void TestGenericWhereIn1() { diff --git a/DynamORM.Tests/TestsBase.cs b/DynamORM.Tests/TestsBase.cs index 414a581..e08719a 100644 --- a/DynamORM.Tests/TestsBase.cs +++ b/DynamORM.Tests/TestsBase.cs @@ -86,7 +86,7 @@ namespace DynamORM.Tests #region DynamORM Initialization - /// Create with default otions for SQLite. + /// Create with default options for SQLite. public void CreateDynamicDatabase() { CreateDynamicDatabase( diff --git a/DynamORM/Builders/DynamicInsertQueryBuilder.cs b/DynamORM/Builders/DynamicInsertQueryBuilder.cs deleted file mode 100644 index 79e828f..0000000 --- a/DynamORM/Builders/DynamicInsertQueryBuilder.cs +++ /dev/null @@ -1,192 +0,0 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using System; -using System.Collections.Generic; -using System.Data; -using System.Text; -using DynamORM.Mapper; - -namespace DynamORM.Builders -{ - /// Insert query builder. - public class DynamicInsertQueryBuilder : DynamicQueryBuilder - { - /// Gets list of columns that will be selected. - public IDictionary ValueColumns { get; private set; } - - /// Initializes a new instance of the class. - /// Parent dynamic table. - public DynamicInsertQueryBuilder(DynamicTable table) - : base(table) - { - ValueColumns = new Dictionary(); - } - - /// Add insert fields. - /// Insert column and value. - /// Builder instance. - public virtual DynamicInsertQueryBuilder Insert(DynamicColumn column) - { - if (ValueColumns.ContainsKey(column.ColumnName.ToLower())) - ValueColumns[column.ColumnName.ToLower()] = column; - else - ValueColumns.Add(column.ColumnName.ToLower(), column); - - return this; - } - - /// Add insert fields. - /// Insert column. - /// Insert value. - /// Builder instance. - public virtual DynamicInsertQueryBuilder Insert(string column, object value) - { - if (value is DynamicColumn) - { - var v = (DynamicColumn)value; - - if (string.IsNullOrEmpty(v.ColumnName)) - v.ColumnName = column; - - return Insert(v); - } - - if (ValueColumns.ContainsKey(column.ToLower())) - ValueColumns[column.ToLower()].Value = value; - else - ValueColumns.Add(column.ToLower(), new DynamicColumn - { - ColumnName = column, - Value = value - }); - - return this; - } - - /// Add insert fields. - /// Set insert value as properties and values of an object. - /// Builder instance. - public virtual DynamicInsertQueryBuilder Insert(object o) - { - var dict = o.ToDictionary(); - var mapper = DynamicMapperCache.GetMapper(o.GetType()); - - if (mapper != null) - { - foreach (var con in dict) - if (!mapper.Ignored.Contains(con.Key)) - Insert(mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key, con.Value); - } - else - foreach (var con in dict) - Insert(con.Key, con.Value); - - return this; - } - - /// Add where condition. - /// Condition column with operator and value. - /// Builder instance. - public override DynamicInsertQueryBuilder Where(DynamicColumn column) - { - throw new NotSupportedException(); - } - - /// Add where condition. - /// Condition column. - /// Condition operator. - /// Condition value. - /// Builder instance. - public override DynamicInsertQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value) - { - throw new NotSupportedException(); - } - - /// Add where condition. - /// Condition column. - /// Condition value. - /// Builder instance. - public override DynamicInsertQueryBuilder Where(string column, object value) - { - throw new NotSupportedException(); - } - - /// Add where condition. - /// Set conditions as properties and values of an object. - /// If true use schema to determine key columns and ignore those which - /// aren't keys. - /// Builder instance. - public override DynamicInsertQueryBuilder Where(object conditions, bool schema = false) - { - throw new NotSupportedException(); - } - - /// Fill command with query. - /// Command to fill. - /// Filled instance of . - public override IDbCommand FillCommand(IDbCommand command) - { - if (ValueColumns.Count == 0) - throw new InvalidOperationException("Insert query should contain columns to change."); - - StringBuilder builderColumns = new StringBuilder(); - StringBuilder builderValues = new StringBuilder(); - - bool first = true; - var db = DynamicTable.Database; - - foreach (var v in ValueColumns) - { - int pos = command.Parameters.Count; - - if (!first) - { - builderColumns.Append(", "); - builderValues.Append(", "); - } - - db.DecorateName(builderColumns, v.Value.ColumnName); - db.GetParameterName(builderValues, pos); - - command.AddParameter(this, v.Value); - - first = false; - } - - return command.SetCommand("INSERT INTO {0} ({1}) VALUES ({2})", db.DecorateName(TableName), builderColumns, builderValues); - } - - /// Execute this builder. - /// Number of affected rows. - public override dynamic Execute() - { - return DynamicTable.Execute(this); - } - } -} \ No newline at end of file diff --git a/DynamORM/Builders/DynamicQueryBuilder.cs b/DynamORM/Builders/DynamicQueryBuilder.cs deleted file mode 100644 index b701ddf..0000000 --- a/DynamORM/Builders/DynamicQueryBuilder.cs +++ /dev/null @@ -1,403 +0,0 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Text; -using DynamORM.Mapper; - -namespace DynamORM.Builders -{ - /// Base query builder. - /// Return type of methods that should return self. - public abstract class DynamicQueryBuilder : IDynamicQueryBuilder where T : class - { - /// Gets instance. - public DynamicTable DynamicTable { get; private set; } - - /// Gets where conditions. - public List WhereConditions { get; private set; } - - /// Gets table schema. - public Dictionary Schema { get; private set; } - - /// Gets a value indicating whether database supports standard schema. - public bool SupportSchema { get; private set; } - - /// Gets or sets a value indicating whether set parameters for null values. - public bool VirtualMode { get; set; } - - /// Gets table name. - public string TableName { get; private set; } - - /// Initializes a new instance of the DynamicQueryBuilder class. - /// Parent dynamic table. - public DynamicQueryBuilder(DynamicTable table) - { - DynamicTable = table; - TableName = table.TableName; - VirtualMode = false; - - WhereConditions = new List(); - - SupportSchema = (DynamicTable.Database.Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema; - - Schema = DynamicTable.Schema; - } - - /// Set table name. - /// Name of table. - /// Builder instance. - public virtual T Table(string name) - { - if (TableName.ToLower() != name.ToLower()) - { - TableName = name; - - Schema = DynamicTable.Database.GetSchema(TableName); - - if (Schema == null) - throw new InvalidOperationException("Can't assign type as a table for which schema can't be build."); - } - - return this as T; - } - - /// Set table name. - /// Type representing table. - /// Builder instance. - public virtual T Table() - { - return Table(typeof(T)); - } - - /// Set table name. - /// Type representing table. - /// Builder instance. - public virtual T Table(Type type) - { - var mapper = DynamicMapperCache.GetMapper(type); - string name = string.Empty; - - if (mapper == null) - throw new InvalidOperationException("Cant assign unmapable type as a table."); - else - name = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? - mapper.Type.Name : mapper.Table.Name; - - if (TableName.ToLower() != name.ToLower()) - { - TableName = name; - - Schema = DynamicTable.Database.GetSchema(type); - } - - return this as T; - } - - /// Add where condition. - /// Condition column with operator and value. - /// Builder instance. - public virtual T Where(DynamicColumn column) - { - WhereConditions.Add(column); - - return this as T; - } - - /// Add where condition. - /// Condition column. - /// Condition operator. - /// Condition value. - /// Builder instance. - public virtual T Where(string column, DynamicColumn.CompareOperator op, object value) - { - if (value is DynamicColumn) - { - var v = (DynamicColumn)value; - - if (string.IsNullOrEmpty(v.ColumnName)) - v.ColumnName = column; - - return Where(v); - } - else if (value is IEnumerable) - { - foreach (var v in (IEnumerable)value) - Where(v); - - return this as T; - } - - WhereConditions.Add(new DynamicColumn - { - ColumnName = column, - Operator = op, - Value = value - }); - - return this as T; - } - - /// Add where condition. - /// Condition column. - /// Condition value. - /// Builder instance. - public virtual T Where(string column, object value) - { - return Where(column, DynamicColumn.CompareOperator.Eq, value); - } - - /// Add where condition. - /// Set conditions as properties and values of an object. - /// If true use schema to determine key columns and ignore those which - /// aren't keys. - /// Builder instance. - public virtual T Where(object conditions, bool schema = false) - { - if (conditions is DynamicColumn) - return Where((DynamicColumn)conditions); - - var dict = conditions.ToDictionary(); - var mapper = DynamicMapperCache.GetMapper(conditions.GetType()); - - foreach (var con in dict) - { - if (mapper.Ignored.Contains(con.Key)) - continue; - - string colName = mapper != null ? mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key : con.Key; - - DynamicSchemaColumn? col = null; - - if (schema) - { - col = Schema.TryGetNullable(colName.ToLower()); - - if (!col.HasValue) - throw new InvalidOperationException(string.Format("Column '{0}' not found in schema, can't use universal approach.", con.Key)); - - if (!col.Value.IsKey) - continue; - - colName = col.Value.Name; - } - - Where(colName, con.Value); - } - - return this as T; - } - - /// Get string representation of operator. - /// Operator object. - /// String representation of operator. - public string ToOperator(DynamicColumn.CompareOperator op) - { - switch (op) - { - case DynamicColumn.CompareOperator.Eq: return "="; - case DynamicColumn.CompareOperator.Not: return "<>"; - case DynamicColumn.CompareOperator.Like: return "LIKE"; - case DynamicColumn.CompareOperator.NotLike: return "NOT LIKE"; - case DynamicColumn.CompareOperator.Lt: return "<"; - case DynamicColumn.CompareOperator.Lte: return "<="; - case DynamicColumn.CompareOperator.Gt: return ">"; - case DynamicColumn.CompareOperator.Gte: return ">="; - case DynamicColumn.CompareOperator.Between: - case DynamicColumn.CompareOperator.In: - default: - throw new ArgumentException(string.Format("This operator ('{0}') requires more than conversion to string.", op)); - } - } - - /// Fill where part of a query. - /// Command to fill. - /// String builder. - public virtual void FillWhere(IDbCommand command, StringBuilder sb) - { - // Yes, this method qualifies fo refactoring... but it's fast - bool first = true; - var db = DynamicTable.Database; - - foreach (var v in WhereConditions) - { - var col = Schema.TryGetNullable(v.ColumnName.ToLower()); - - string column = col.HasValue ? col.Value.Name : v.ColumnName; - - if ((column.IndexOf(db.LeftDecorator) == -1 || column.IndexOf(db.LeftDecorator) == -1) && - (column.IndexOf('(') == -1 || column.IndexOf(')') == -1)) - column = db.DecorateName(column); - - if ((v.Value == null || v.Value == DBNull.Value) && !VirtualMode && !v.VirtualColumn) - { - #region Null operators - - if (v.Operator == DynamicColumn.CompareOperator.Not || v.Operator == DynamicColumn.CompareOperator.Eq) - sb.AppendFormat(" {0} {1}{2} IS{3} NULL{4}", - first ? "WHERE" : v.Or ? "OR" : "AND", - v.BeginBlock ? "(" : string.Empty, - column, - v.Operator == DynamicColumn.CompareOperator.Not ? " NOT" : string.Empty, - v.EndBlock ? ")" : string.Empty); - else - throw new InvalidOperationException("NULL can only be compared by IS or IS NOT operator."); - - #endregion - } - else if (v.Operator != DynamicColumn.CompareOperator.In && - v.Operator != DynamicColumn.CompareOperator.Between) - { - #region Standard operators - - int pos = command.Parameters.Count; - - sb.AppendFormat(" {0} {1}{2} {3} ", - first ? "WHERE" : v.Or ? "OR" : "AND", - v.BeginBlock ? "(" : string.Empty, - column, - ToOperator(v.Operator)); - - db.GetParameterName(sb, pos); - - if (v.EndBlock) - sb.Append(")"); - - command.AddParameter(this, v); - - #endregion - } - else if (((object)v.Value).GetType().IsCollection() || v.Value is IEnumerable) - { - #region In or Between operator - - if (v.Operator == DynamicColumn.CompareOperator.Between) - { - #region Between operator - - var vals = (v.Value as IEnumerable).Take(2).ToList(); - - if (vals == null && v.Value is Array) - vals = ((Array)v.Value).Cast().ToList(); - - if (vals.Count == 2) - { - sb.AppendFormat(" {0} {1}{2} BETWEEN ", - first ? "WHERE" : v.Or ? "OR" : "AND", - v.BeginBlock ? "(" : string.Empty, - column); - - // From parameter - db.GetParameterName(sb, command.Parameters.Count); - v.Value = vals[0]; - command.AddParameter(this, v); - - sb.Append(" AND "); - - // To parameter - db.GetParameterName(sb, command.Parameters.Count); - v.Value = vals[1]; - command.AddParameter(this, v); - - if (v.EndBlock) - sb.Append(")"); - - // Reset value - v.Value = vals; - } - else - throw new InvalidOperationException("BETWEEN must have two values."); - - #endregion - } - else if (v.Operator == DynamicColumn.CompareOperator.In) - { - #region In operator - - sb.AppendFormat(" {0} {1}{2} IN(", - first ? "WHERE" : v.Or ? "OR" : "AND", - v.BeginBlock ? "(" : string.Empty, - column); - - bool firstParam = true; - - var vals = v.Value as IEnumerable; - - if (vals == null && v.Value is Array) - vals = ((Array)v.Value).Cast() as IEnumerable; - - foreach (var val in vals) - { - int pos = command.Parameters.Count; - - if (!firstParam) - sb.Append(", "); - - db.GetParameterName(sb, pos); - v.Value = val; - - command.AddParameter(this, v); - - firstParam = false; - } - - v.Value = vals; - - sb.Append(")"); - - if (v.EndBlock) - sb.Append(")"); - - #endregion - } - else - throw new Exception("BAZINGA. You have reached unreachable code."); - - #endregion - } - else - throw new InvalidOperationException( - string.Format("Operator was {0}, but value wasn't enumerable. Column: '{1}'", v.Operator.ToString().ToUpper(), col)); - - first = false; - } - } - - /// Fill command with query. - /// Command to fill. - /// Filled instance of . - public abstract IDbCommand FillCommand(IDbCommand command); - - /// Execute this builder. - /// Result of an execution.. - public abstract dynamic Execute(); - } -} \ No newline at end of file diff --git a/DynamORM/Builders/DynamicSelectQueryBuilder.cs b/DynamORM/Builders/DynamicSelectQueryBuilder.cs deleted file mode 100644 index e977d4d..0000000 --- a/DynamORM/Builders/DynamicSelectQueryBuilder.cs +++ /dev/null @@ -1,272 +0,0 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Text; - -namespace DynamORM.Builders -{ - /// Select query builder. - public class DynamicSelectQueryBuilder : DynamicQueryBuilder - { - /// Gets dictionary of columns that will be selected. - public List Columns { get; private set; } - - /// Gets dictionary of columns that will be used to group query. - public List Group { get; private set; } - - /// Gets dictionary of columns that will be used to order query. - public List Order { get; private set; } - - private int? _top = null; - private int? _limit = null; - private int? _offset = null; - private bool _distinct = false; - - /// Initializes a new instance of the class. - /// Parent dynamic table. - public DynamicSelectQueryBuilder(DynamicTable table) - : base(table) - { - Columns = new List(); - Group = new List(); - Order = new List(); - } - - /// Add select columns. - /// Columns to add to object. - /// Builder instance. - public DynamicSelectQueryBuilder Select(params DynamicColumn[] columns) - { - foreach (var col in columns) - Columns.Add(col); - - return this; - } - - /// Add select columns. - /// Columns to add to object. - /// Column format consist of Column Name, Alias and - /// Aggregate function in this order separated by ':'. - /// Builder instance. - public DynamicSelectQueryBuilder Select(params string[] columns) - { - return Select(columns.Select(c => DynamicColumn.ParseSelectColumn(c)).ToArray()); - } - - /// Add select columns. - /// Columns to group by. - /// Builder instance. - public DynamicSelectQueryBuilder GroupBy(params DynamicColumn[] columns) - { - foreach (var col in columns) - Group.Add(col); - - return this; - } - - /// Add select columns. - /// Columns to group by. - /// Column format consist of Column Name and - /// Alias in this order separated by ':'. - /// Builder instance. - public DynamicSelectQueryBuilder GroupBy(params string[] columns) - { - return GroupBy(columns.Select(c => DynamicColumn.ParseSelectColumn(c)).ToArray()); - } - - /// Add select columns. - /// Columns to order by. - /// Builder instance. - public DynamicSelectQueryBuilder OrderBy(params DynamicColumn[] columns) - { - foreach (var col in columns) - Order.Add(col); - - return this; - } - - /// Add select columns. - /// Columns to order by. - /// Column format consist of Column Name and - /// Alias in this order separated by ':'. - /// Builder instance. - public DynamicSelectQueryBuilder OrderBy(params string[] columns) - { - return OrderBy(columns.Select(c => DynamicColumn.ParseOrderByColumn(c)).ToArray()); - } - - /// Set top if database support it. - /// How many objects select. - /// Builder instance. - public DynamicSelectQueryBuilder Top(int? top) - { - if ((DynamicTable.Database.Options & DynamicDatabaseOptions.SupportTop) != DynamicDatabaseOptions.SupportTop) - throw new NotSupportedException("Database doesn't support TOP clause."); - - _top = top; - return this; - } - - /// Set top if database support it. - /// How many objects select. - /// Builder instance. - public DynamicSelectQueryBuilder Limit(int? limit) - { - if ((DynamicTable.Database.Options & DynamicDatabaseOptions.SupportLimitOffset) != DynamicDatabaseOptions.SupportLimitOffset) - throw new NotSupportedException("Database doesn't support LIMIT clause."); - - _limit = limit; - return this; - } - - /// Set top if database support it. - /// How many objects skip selecting. - /// Builder instance. - public DynamicSelectQueryBuilder Offset(int? offset) - { - if ((DynamicTable.Database.Options & DynamicDatabaseOptions.SupportLimitOffset) != DynamicDatabaseOptions.SupportLimitOffset) - throw new NotSupportedException("Database doesn't support OFFSET clause."); - - _offset = offset; - return this; - } - - /// Set distinct mode. - /// Distinct mode. - /// Builder instance. - public DynamicSelectQueryBuilder Distinct(bool distinct = true) - { - _distinct = distinct; - return this; - } - - /// Fill command with query. - /// Command to fill. - /// Filled instance of . - public override IDbCommand FillCommand(IDbCommand command) - { - StringBuilder sb = new StringBuilder(); - - var db = DynamicTable.Database; - - sb.AppendFormat("SELECT{0}{1} ", - _distinct ? " DISTINCT" : string.Empty, - _top.HasValue ? string.Format(" TOP {0}", _top.Value) : string.Empty); - - BuildColumns(sb, db); - - sb.Append(" FROM "); - db.DecorateName(sb, TableName); - - FillWhere(command, sb); - - BuildGroup(sb, db); - BuildOrder(sb, db); - - if (_limit.HasValue) - sb.AppendFormat(" LIMIT {0}", _limit.Value); - - if (_offset.HasValue) - sb.AppendFormat(" OFFSET {0}", _offset.Value); - - return command.SetCommand(sb.ToString()); - } - - private void BuildColumns(StringBuilder sb, DynamicDatabase db) - { - if (Columns.Count > 0) - { - bool first = true; - - // Not pretty but blazing fast - Columns.ForEach(c => - { - if (!first) - sb.Append(", "); - - c.ToSQLSelectColumn(db, sb); - first = false; - }); - } - else - sb.Append("*"); - } - - private void BuildGroup(StringBuilder sb, DynamicDatabase db) - { - if (Group.Count > 0) - { - sb.Append(" GROUP BY "); - bool first = true; - - // Not pretty but blazing fast - Group.ForEach(c => - { - if (!first) - sb.Append(", "); - - c.ToSQLGroupByColumn(db, sb); - first = false; - }); - } - } - - private void BuildOrder(StringBuilder sb, DynamicDatabase db) - { - if (Order.Count > 0) - { - sb.Append(" ORDER BY "); - bool first = true; - - // Not pretty but blazing fast - Order.ForEach(c => - { - if (!first) - sb.Append(", "); - - c.ToSQLOrderByColumn(db, sb); - first = false; - }); - } - } - - /// Execute this builder. - /// Enumerator of objects expanded from query. - public override dynamic Execute() - { - if (Columns.Count <= 1 && ((_top.HasValue && _top.Value == 1) || (_limit.HasValue && _limit.Value == 1))) - return DynamicTable.Scalar(this); - else - return DynamicTable.Query(this); - } - } -} \ No newline at end of file diff --git a/DynamORM/Builders/DynamicUpdateQueryBuilder.cs b/DynamORM/Builders/DynamicUpdateQueryBuilder.cs deleted file mode 100644 index a36fe8a..0000000 --- a/DynamORM/Builders/DynamicUpdateQueryBuilder.cs +++ /dev/null @@ -1,238 +0,0 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using System; -using System.Collections.Generic; -using System.Data; -using System.Text; -using DynamORM.Mapper; - -namespace DynamORM.Builders -{ - /// Update query builder. - public class DynamicUpdateQueryBuilder : DynamicQueryBuilder - { - /// Gets list of columns that will be selected. - public IDictionary ValueColumns { get; private set; } - - /// Initializes a new instance of the class. - /// Parent dynamic table. - public DynamicUpdateQueryBuilder(DynamicTable table) - : base(table) - { - ValueColumns = new Dictionary(); - } - - /// Add update value or where condition using schema. - /// Update or where column name and value. - /// Builder instance. - public virtual DynamicUpdateQueryBuilder Update(DynamicColumn column) - { - var col = Schema.TryGetNullable(column.ColumnName.ToLower()); - - if (!col.HasValue && SupportSchema) - throw new InvalidOperationException(string.Format("Column '{0}' not found in schema, can't use universal approach.", column)); - - if (col.HasValue && col.Value.IsKey) - Where(column); - else - Values(column.ColumnName, column.Value); - - return this; - } - - /// Add update value or where condition using schema. - /// Update or where column name. - /// Column value. - /// Builder instance. - public virtual DynamicUpdateQueryBuilder Update(string column, object value) - { - var col = Schema.TryGetNullable(column.ToLower()); - - if (!col.HasValue && SupportSchema) - throw new InvalidOperationException(string.Format("Column '{0}' not found in schema, can't use universal approach.", column)); - - if (col.HasValue && col.Value.IsKey) - Where(column, value); - else - Values(column, value); - - return this; - } - - /// Add update values and where condition columns using schema. - /// Set values or conditions as properties and values of an object. - /// Builder instance. - public virtual DynamicUpdateQueryBuilder Update(object conditions) - { - if (conditions is DynamicColumn) - return Update((DynamicColumn)conditions); - - var dict = conditions.ToDictionary(); - var mapper = DynamicMapperCache.GetMapper(conditions.GetType()); - - foreach (var con in dict) - { - if (mapper.Ignored.Contains(con.Key)) - continue; - - string colName = mapper != null ? mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key : con.Key; - var col = Schema.TryGetNullable(colName.ToLower()); - - if (!col.HasValue && SupportSchema) - throw new InvalidOperationException(string.Format("Column '{0}' not found in schema, can't use universal approach.", colName)); - - if (col.HasValue) - { - colName = col.Value.Name; - - if (col.Value.IsKey) - { - Where(colName, con.Value); - - continue; - } - } - - Values(colName, con.Value); - } - - return this; - } - - /// Add update fields. - /// Update column and value. - /// Builder instance. - public virtual DynamicUpdateQueryBuilder Values(DynamicColumn column) - { - if (ValueColumns.ContainsKey(column.ColumnName.ToLower())) - ValueColumns[column.ColumnName.ToLower()] = column; - else - ValueColumns.Add(column.ColumnName.ToLower(), column); - - return this; - } - - /// Add update fields. - /// Update column. - /// Update value. - /// Builder instance. - public virtual DynamicUpdateQueryBuilder Values(string column, object value) - { - if (value is DynamicColumn) - { - var v = (DynamicColumn)value; - - if (string.IsNullOrEmpty(v.ColumnName)) - v.ColumnName = column; - - return Values(v); - } - - if (ValueColumns.ContainsKey(column.ToLower())) - ValueColumns[column.ToLower()].Value = value; - else - ValueColumns.Add(column.ToLower(), new DynamicColumn - { - ColumnName = column, - Value = value - }); - - return this; - } - - /// Add update fields. - /// Set update value as properties and values of an object. - /// Builder instance. - public virtual DynamicUpdateQueryBuilder Values(object values) - { - if (values is DynamicColumn) - return Values((DynamicColumn)values); - - var dict = values.ToDictionary(); - var mapper = DynamicMapperCache.GetMapper(values.GetType()); - - if (mapper != null) - { - foreach (var con in dict) - if (!mapper.Ignored.Contains(con.Key)) - Values(mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key, con.Value); - } - else - foreach (var con in dict) - Values(con.Key, con.Value); - - return this; - } - - /// Fill command with query. - /// Command to fill. - /// Filled instance of . - public override IDbCommand FillCommand(IDbCommand command) - { - if (ValueColumns.Count == 0) - throw new InvalidOperationException("Update query should contain columns to change."); - - StringBuilder sb = new StringBuilder(); - var db = DynamicTable.Database; - - sb.Append("UPDATE "); - db.DecorateName(sb, TableName); - sb.Append(" SET "); - - bool first = true; - - foreach (var v in ValueColumns) - { - int pos = command.Parameters.Count; - - if (!first) - sb.Append(", "); - - db.DecorateName(sb, v.Value.ColumnName); - sb.Append(" = "); - db.GetParameterName(sb, pos); - - command.AddParameter(this, v.Value); - - first = false; - } - - FillWhere(command, sb); - - return command.SetCommand(sb.ToString()); - } - - /// Execute this builder. - /// Number of affected rows. - public override dynamic Execute() - { - return DynamicTable.Execute(this); - } - } -} \ No newline at end of file diff --git a/DynamORM/Builders/IDynamicDeleteQueryBuilder.cs b/DynamORM/Builders/IDynamicDeleteQueryBuilder.cs new file mode 100644 index 0000000..f9dcfa0 --- /dev/null +++ b/DynamORM/Builders/IDynamicDeleteQueryBuilder.cs @@ -0,0 +1,78 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; + +namespace DynamORM.Builders +{ + /// Dynamic delete query builder interface. + /// This interface it publically available. Implementation should be hidden. + public interface IDynamicDeleteQueryBuilder : IDynamicQueryBuilder + { + /// Execute this builder. + /// Result of an execution.. + int Execute(); + + /// + /// Adds to the 'Where' clause the contents obtained from parsing the dynamic lambda expression given. The condition + /// is parsed to the appropriate syntax, where the specific customs virtual methods supported by the parser are used + /// as needed. + /// - If several Where() methods are chained their contents are, by default, concatenated with an 'AND' operator. + /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in: + /// 'Where( x => x.Or( condition ) )'. + /// + /// The specification. + /// This instance to permit chaining. + IDynamicDeleteQueryBuilder Where(Func func); + + /// Add where condition. + /// Condition column with operator and value. + /// Builder instance. + IDynamicDeleteQueryBuilder Where(DynamicColumn column); + + /// Add where condition. + /// Condition column. + /// Condition operator. + /// Condition value. + /// Builder instance. + IDynamicDeleteQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value); + + /// Add where condition. + /// Condition column. + /// Condition value. + /// Builder instance. + IDynamicDeleteQueryBuilder Where(string column, object value); + + /// Add where condition. + /// Set conditions as properties and values of an object. + /// If true use schema to determine key columns and ignore those which + /// aren't keys. + /// Builder instance. + IDynamicDeleteQueryBuilder Where(object conditions, bool schema = false); + } +} \ No newline at end of file diff --git a/DynamORM/Builders/IDynamicInsertQueryBuilder.cs b/DynamORM/Builders/IDynamicInsertQueryBuilder.cs new file mode 100644 index 0000000..1416dc9 --- /dev/null +++ b/DynamORM/Builders/IDynamicInsertQueryBuilder.cs @@ -0,0 +1,67 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; + +namespace DynamORM.Builders +{ + /// Dynamic insert query builder interface. + /// This interface it publically available. Implementation should be hidden. + public interface IDynamicInsertQueryBuilder : IDynamicQueryBuilder + { + /// Execute this builder. + /// Result of an execution.. + int Execute(); + + /// + /// Specifies the columns to insert using the dynamic lambda expressions given. Each expression correspond to one + /// column, and can: + /// - Resolve to a string, in this case a '=' must appear in the string. + /// - Resolve to a expression with the form: 'x => x.Column = Value'. + /// + /// The specifications. + /// This instance to permit chaining. + IDynamicInsertQueryBuilder Insert(params Func[] func); + + /// Add insert fields. + /// Insert column and value. + /// Builder instance. + IDynamicInsertQueryBuilder Insert(DynamicColumn column); + + /// Add insert fields. + /// Insert column. + /// Insert value. + /// Builder instance. + IDynamicInsertQueryBuilder Insert(string column, object value); + + /// Add insert fields. + /// Set insert value as properties and values of an object. + /// Builder instance. + IDynamicInsertQueryBuilder Insert(object o); + } +} \ No newline at end of file diff --git a/DynamORM/Builders/IDynamicQueryBuilder.cs b/DynamORM/Builders/IDynamicQueryBuilder.cs index f05af75..19e031b 100644 --- a/DynamORM/Builders/IDynamicQueryBuilder.cs +++ b/DynamORM/Builders/IDynamicQueryBuilder.cs @@ -26,19 +26,24 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ +using System; using System.Collections.Generic; using System.Data; namespace DynamORM.Builders { - /// Base query builder interface. + /// Dynamic query builder base interface. + /// This interface it publically available. Implementation should be hidden. public interface IDynamicQueryBuilder { - /// Gets instance. - DynamicTable DynamicTable { get; } + /// Gets instance. + DynamicDatabase Database { get; } - /// Gets table schema. - Dictionary Schema { get; } + /// Gets tables information. + IList Tables { get; } + + /// Gets the tables used in this builder. + IDictionary Parameters { get; } /// Gets a value indicating whether database supports standard schema. bool SupportSchema { get; } @@ -48,8 +53,26 @@ namespace DynamORM.Builders /// Filled instance of . IDbCommand FillCommand(IDbCommand command); - /// Execute this builder. - /// Result of an execution.. - dynamic Execute(); + /// + /// Generates the text this command will execute against the underlying database. + /// + /// The text to execute against the underlying database. + /// This method must be override by derived classes. + string CommandText(); + + /// Creates sub query. + /// Sub query builder. + IDynamicSelectQueryBuilder SubQuery(); + + /// Adds to the 'From' clause of sub query the contents obtained by + /// parsing the dynamic lambda expressions given. The supported formats are: + /// - Resolve to a string: 'x => "Table AS Alias', where the alias part is optional. + /// - Resolve to an expression: 'x => x.Table.As( x.Alias )', where the alias part is optional. + /// - Generic expression: 'x => x( expression ).As( x.Alias )', where the alias part is mandatory. In this + /// case the alias is not annotated. + /// + /// The specification. + /// This instance to permit chaining. + IDynamicSelectQueryBuilder SubQuery(params Func[] func); } } \ No newline at end of file diff --git a/DynamORM/Builders/IDynamicSelectQueryBuilder.cs b/DynamORM/Builders/IDynamicSelectQueryBuilder.cs new file mode 100644 index 0000000..a4935e8 --- /dev/null +++ b/DynamORM/Builders/IDynamicSelectQueryBuilder.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; + +namespace DynamORM.Builders +{ + /// Dynamic select query builder interface. + /// This interface it publically available. Implementation should be hidden. + public interface IDynamicSelectQueryBuilder : IDynamicQueryBuilder, IEnumerable + { + /// Execute this builder. + /// Enumerator of objects expanded from query. + IEnumerable Execute(); + + /// Returns a single result. + /// Result of a query. + object Scalar(); + + #region From/Join + + /// + /// Adds to the 'From' clause the contents obtained by parsing the dynamic lambda expressions given. The supported + /// formats are: + /// - Resolve to a string: 'x => "Table AS Alias', where the alias part is optional. + /// - Resolve to an expression: 'x => x.Table.As( x.Alias )', where the alias part is optional. + /// - Generic expression: 'x => x( expression ).As( x.Alias )', where the alias part is mandatory. In this + /// case the alias is not annotated. + /// + /// The specification. + /// This instance to permit chaining. + IDynamicSelectQueryBuilder From(params Func[] func); + + /// + /// Adds to the 'Join' clause the contents obtained by parsing the dynamic lambda expressions given. The supported + /// formats are: + /// - Resolve to a string: 'x => "Table AS Alias ON Condition', where the alias part is optional. + /// - Resolve to an expression: 'x => x.Table.As( x.Alias ).On( condition )', where the alias part is optional. + /// - Generic expression: 'x => x( expression ).As( x.Alias ).On( condition )', where the alias part is mandatory. + /// In this case the alias is not annotated. + /// The expression might be prepended by a method that, in this case, is used to specify the specific join type you + /// want to perform, as in: 'x => x.Left()...". Two considerations apply: + /// - If a 'false' argument is used when no 'Join' part appears in its name, then no 'Join' suffix is added + /// with a space in between. + /// - If a 'false' argument is used when a 'Join' part does appear, then no split is performed to separate the + /// 'Join' part. + /// + /// The specification. + /// This instance to permit chaining. + IDynamicSelectQueryBuilder Join(params Func[] func); + + #endregion From/Join + + #region Where + + /// + /// Adds to the 'Where' clause the contents obtained from parsing the dynamic lambda expression given. The condition + /// is parsed to the appropriate syntax, where the specific customs virtual methods supported by the parser are used + /// as needed. + /// - If several Where() methods are chained their contents are, by default, concatenated with an 'AND' operator. + /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in: + /// 'Where( x => x.Or( condition ) )'. + /// + /// The specification. + /// This instance to permit chaining. + IDynamicSelectQueryBuilder Where(Func func); + + /// Add where condition. + /// Condition column with operator and value. + /// Builder instance. + IDynamicSelectQueryBuilder Where(DynamicColumn column); + + /// Add where condition. + /// Condition column. + /// Condition operator. + /// Condition value. + /// Builder instance. + IDynamicSelectQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value); + + /// Add where condition. + /// Condition column. + /// Condition value. + /// Builder instance. + IDynamicSelectQueryBuilder Where(string column, object value); + + /// Add where condition. + /// Set conditions as properties and values of an object. + /// If true use schema to determine key columns and ignore those which + /// aren't keys. + /// Builder instance. + IDynamicSelectQueryBuilder Where(object conditions, bool schema = false); + + #endregion Where + + #region Select + + /// + /// Adds to the 'Select' clause the contents obtained by parsing the dynamic lambda expressions given. The supported + /// formats are: + /// - Resolve to a string: 'x => "Table.Column AS Alias', where the alias part is optional. + /// - Resolve to an expression: 'x => x.Table.Column.As( x.Alias )', where the alias part is optional. + /// - Select all columns from a table: 'x => x.Table.All()'. + /// - Generic expression: 'x => x( expression ).As( x.Alias )', where the alias part is mandatory. In this case + /// the alias is not annotated. + /// + /// The specification. + /// This instance to permit chaining. + IDynamicSelectQueryBuilder Select(params Func[] func); + + /// Add select columns. + /// Columns to add to object. + /// Builder instance. + IDynamicSelectQueryBuilder Select(params DynamicColumn[] columns); + + /// Add select columns. + /// Columns to add to object. + /// Column format consist of Column Name, Alias and + /// Aggregate function in this order separated by ':'. + /// Builder instance. + IDynamicSelectQueryBuilder Select(params string[] columns); + + #endregion Select + + #region GroupBy + + /// + /// Adds to the 'Group By' clause the contents obtained from from parsing the dynamic lambda expression given. + /// + /// The specification. + /// This instance to permit chaining. + IDynamicSelectQueryBuilder GroupBy(params Func[] func); + + /// Add select columns. + /// Columns to group by. + /// Builder instance. + IDynamicSelectQueryBuilder GroupBy(params DynamicColumn[] columns); + + /// Add select columns. + /// Columns to group by. + /// Column format consist of Column Name and + /// Alias in this order separated by ':'. + /// Builder instance. + IDynamicSelectQueryBuilder GroupBy(params string[] columns); + + #endregion GroupBy + + #region OrderBy + + /// + /// Adds to the 'Order By' clause the contents obtained from from parsing the dynamic lambda expression given. It + /// accepts a multipart column specification followed by an optional Ascending() or Descending() virtual methods + /// to specify the direction. If no virtual method is used, the default is ascending order. You can also use the + /// shorter versions Asc() and Desc(). + /// + /// The specification. + /// This instance to permit chaining. + IDynamicSelectQueryBuilder OrderBy(params Func[] func); + + /// Add select columns. + /// Columns to order by. + /// Builder instance. + IDynamicSelectQueryBuilder OrderBy(params DynamicColumn[] columns); + + /// Add select columns. + /// Columns to order by. + /// Column format consist of Column Name and + /// Alias in this order separated by ':'. + /// Builder instance. + IDynamicSelectQueryBuilder OrderBy(params string[] columns); + + #endregion OrderBy + + #region Top/Limit/Offset/Distinct + + /// Set top if database support it. + /// How many objects select. + /// Builder instance. + IDynamicSelectQueryBuilder Top(int? top); + + /// Set top if database support it. + /// How many objects select. + /// Builder instance. + IDynamicSelectQueryBuilder Limit(int? limit); + + /// Set top if database support it. + /// How many objects skip selecting. + /// Builder instance. + IDynamicSelectQueryBuilder Offset(int? offset); + + /// Set distinct mode. + /// Distinct mode. + /// Builder instance. + IDynamicSelectQueryBuilder Distinct(bool distinct = true); + + #endregion Top/Limit/Offset/Distinct + } +} \ No newline at end of file diff --git a/DynamORM/Builders/IDynamicUpdateQueryBuilder.cs b/DynamORM/Builders/IDynamicUpdateQueryBuilder.cs new file mode 100644 index 0000000..9474f66 --- /dev/null +++ b/DynamORM/Builders/IDynamicUpdateQueryBuilder.cs @@ -0,0 +1,132 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; + +namespace DynamORM.Builders +{ + /// Dynamic update query builder interface. + /// This interface it publically available. Implementation should be hidden. + public interface IDynamicUpdateQueryBuilder : IDynamicQueryBuilder + { + /// Execute this builder. + /// Result of an execution.. + int Execute(); + + #region Update + + /// Add update value or where condition using schema. + /// Update or where column name and value. + /// Builder instance. + IDynamicUpdateQueryBuilder Update(DynamicColumn column); + + /// Add update value or where condition using schema. + /// Update or where column name. + /// Column value. + /// Builder instance. + IDynamicUpdateQueryBuilder Update(string column, object value); + + /// Add update values and where condition columns using schema. + /// Set values or conditions as properties and values of an object. + /// Builder instance. + IDynamicUpdateQueryBuilder Update(object conditions); + + #endregion Update + + #region Values + + /// + /// Specifies the columns to update using the dynamic lambda expressions given. Each expression correspond to one + /// column, and can: + /// - Resolve to a string, in this case a '=' must appear in the string. + /// - Resolve to a expression with the form: 'x => x.Column = Value'. + /// + /// The specifications. + /// This instance to permit chaining. + IDynamicUpdateQueryBuilder Values(params Func[] func); + + /// Add insert fields. + /// Insert column and value. + /// Builder instance. + IDynamicUpdateQueryBuilder Values(DynamicColumn column); + + /// Add insert fields. + /// Insert column. + /// Insert value. + /// Builder instance. + IDynamicUpdateQueryBuilder Values(string column, object value); + + /// Add insert fields. + /// Set insert value as properties and values of an object. + /// Builder instance. + IDynamicUpdateQueryBuilder Values(object o); + + #endregion Values + + #region Where + + /// + /// Adds to the 'Where' clause the contents obtained from parsing the dynamic lambda expression given. The condition + /// is parsed to the appropriate syntax, where the specific customs virtual methods supported by the parser are used + /// as needed. + /// - If several Where() methods are chained their contents are, by default, concatenated with an 'AND' operator. + /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in: + /// 'Where( x => x.Or( condition ) )'. + /// + /// The specification. + /// This instance to permit chaining. + IDynamicUpdateQueryBuilder Where(Func func); + + /// Add where condition. + /// Condition column with operator and value. + /// Builder instance. + IDynamicUpdateQueryBuilder Where(DynamicColumn column); + + /// Add where condition. + /// Condition column. + /// Condition operator. + /// Condition value. + /// Builder instance. + IDynamicUpdateQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value); + + /// Add where condition. + /// Condition column. + /// Condition value. + /// Builder instance. + IDynamicUpdateQueryBuilder Where(string column, object value); + + /// Add where condition. + /// Set conditions as properties and values of an object. + /// If true use schema to determine key columns and ignore those which + /// aren't keys. + /// Builder instance. + IDynamicUpdateQueryBuilder Where(object conditions, bool schema = false); + + #endregion Where + } +} \ No newline at end of file diff --git a/DynamORM/Builders/DynamicDeleteQueryBuilder.cs b/DynamORM/Builders/IParameter.cs similarity index 54% rename from DynamORM/Builders/DynamicDeleteQueryBuilder.cs rename to DynamORM/Builders/IParameter.cs index 68b9315..9e4a31a 100644 --- a/DynamORM/Builders/DynamicDeleteQueryBuilder.cs +++ b/DynamORM/Builders/IParameter.cs @@ -26,41 +26,21 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -using System.Data; -using System.Text; - namespace DynamORM.Builders { - /// Delete query builder. - public class DynamicDeleteQueryBuilder : DynamicQueryBuilder + /// Interface describing parameter info. + public interface IParameter { - /// Initializes a new instance of the class. - /// Parent dynamic table. - public DynamicDeleteQueryBuilder(DynamicTable table) - : base(table) - { - } + /// Gets the parameter temporary name. + string Name { get; } - /// Fill command with query. - /// Command to fill. - /// Filled instance of . - public override IDbCommand FillCommand(IDbCommand command) - { - StringBuilder sb = new StringBuilder(); + /// Gets or sets the parameter value. + object Value { get; set; } - sb.Append("DELETE FROM "); - DynamicTable.Database.DecorateName(sb, TableName); + /// Gets or sets a value indicating whether this is virtual. + bool Virtual { get; set; } - FillWhere(command, sb); - - return command.SetCommand(sb.ToString()); - } - - /// Execute this builder. - /// Number of affected rows. - public override dynamic Execute() - { - return DynamicTable.Execute(this); - } + /// Gets the parameter schema information. + DynamicSchemaColumn? Schema { get; } } } \ No newline at end of file diff --git a/DynamORM/Builders/ITableInfo.cs b/DynamORM/Builders/ITableInfo.cs new file mode 100644 index 0000000..03bb867 --- /dev/null +++ b/DynamORM/Builders/ITableInfo.cs @@ -0,0 +1,26 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace DynamORM.Builders +{ + /// Interface describing table information. + public interface ITableInfo + { + /// Gets table owner name. + string Owner { get; } + + /// Gets table name. + string Name { get; } + + /// Gets table alias. + string Alias { get; } + + /// Gets table schema. + Dictionary Schema { get; } + } +} \ No newline at end of file diff --git a/DynamORM/DynamORM.csproj b/DynamORM/DynamORM.csproj index 7bd6eb7..a9a5483 100644 --- a/DynamORM/DynamORM.csproj +++ b/DynamORM/DynamORM.csproj @@ -1,4 +1,4 @@ - + Debug @@ -45,12 +45,21 @@ - - - - - + + + + + + + + + + + + + + @@ -61,7 +70,11 @@ + + + + diff --git a/DynamORM/DynamicColumn.cs b/DynamORM/DynamicColumn.cs index 854122c..885b0a8 100644 --- a/DynamORM/DynamicColumn.cs +++ b/DynamORM/DynamicColumn.cs @@ -86,7 +86,9 @@ namespace DynamORM #region Constructors /// Initializes a new instance of the class. - public DynamicColumn() { } + public DynamicColumn() + { + } /// Initializes a new instance of the class. /// Constructor provided for easier object creation in queries. @@ -341,7 +343,7 @@ namespace DynamORM /// Sets the begin block flag. /// If set to true [begin]. /// Returns self. - public DynamicColumn SetBeginBlock(bool begin) + public DynamicColumn SetBeginBlock(bool begin = true) { BeginBlock = begin; return this; @@ -350,7 +352,7 @@ namespace DynamORM /// Sets the end block flag. /// If set to true [end]. /// Returns self. - public DynamicColumn SetEndBlock(bool end) + public DynamicColumn SetEndBlock(bool end = true) { EndBlock = end; return this; @@ -359,7 +361,7 @@ namespace DynamORM /// Sets the or flag. /// If set to true [or]. /// Returns self. - public DynamicColumn SetOr(bool or) + public DynamicColumn SetOr(bool or = true) { Or = or; return this; @@ -436,6 +438,13 @@ namespace DynamORM #region ToSQL + internal string ToSQLSelectColumn(DynamicDatabase db) + { + StringBuilder sb = new StringBuilder(); + ToSQLSelectColumn(db, sb); + return sb.ToString(); + } + internal void ToSQLSelectColumn(DynamicDatabase db, StringBuilder sb) { string column = ColumnName == "*" ? "*" : ColumnName; @@ -462,11 +471,25 @@ namespace DynamORM sb.AppendFormat(" AS {0}", alias); } + internal string ToSQLGroupByColumn(DynamicDatabase db) + { + StringBuilder sb = new StringBuilder(); + ToSQLGroupByColumn(db, sb); + return sb.ToString(); + } + internal void ToSQLGroupByColumn(DynamicDatabase db, StringBuilder sb) { sb.Append(db.DecorateName(ColumnName)); } + internal string ToSQLOrderByColumn(DynamicDatabase db) + { + StringBuilder sb = new StringBuilder(); + ToSQLOrderByColumn(db, sb); + return sb.ToString(); + } + internal void ToSQLOrderByColumn(DynamicDatabase db, StringBuilder sb) { if (!string.IsNullOrEmpty(Alias)) diff --git a/DynamORM/DynamicConnection.cs b/DynamORM/DynamicConnection.cs index e7aba2e..8285b9b 100644 --- a/DynamORM/DynamicConnection.cs +++ b/DynamORM/DynamicConnection.cs @@ -101,7 +101,9 @@ namespace DynamORM /// Connection object. /// Does nothing. handles /// opening connections. - public void Open() { } + public void Open() + { + } /// Closes the connection to the database. /// Does nothing. handles @@ -109,7 +111,9 @@ namespace DynamORM /// It will close if this is multi connection configuration, otherwise /// it will stay open until is not /// disposed. - public void Close() { } + public void Close() + { + } /// Gets or sets the string used to open a database. /// Changing connection string operation is not supported in DynamORM. diff --git a/DynamORM/DynamicDatabase.cs b/DynamORM/DynamicDatabase.cs index 2651bf7..6ddbbe9 100644 --- a/DynamORM/DynamicDatabase.cs +++ b/DynamORM/DynamicDatabase.cs @@ -32,6 +32,9 @@ using System.Data; using System.Data.Common; using System.Linq; using System.Text; +using DynamORM.Builders; +using DynamORM.Builders.Implementation; +using DynamORM.Helpers; using DynamORM.Mapper; namespace DynamORM @@ -47,6 +50,8 @@ namespace DynamORM private bool _singleTransaction; private string _leftDecorator = "\""; private string _rightDecorator = "\""; + private bool _leftDecoratorIsInInvalidMembersChars = true; + private bool _rightDecoratorIsInInvalidMembersChars = true; private string _parameterFormat = "@{0}"; private int? _commandTimeout = null; private long _poolStamp = 0; @@ -162,9 +167,10 @@ namespace DynamORM /// The action with instance of as parameter. /// Table name. /// Override keys in schema. - public void Table(Action action, string table = "", string[] keys = null) + /// Owner of the table. + public void Table(Action action, string table = "", string[] keys = null, string owner = "") { - using (dynamic t = Table(table, keys)) + using (dynamic t = Table(table, keys, owner)) action(t); } @@ -181,8 +187,9 @@ namespace DynamORM /// Gets dynamic table which is a simple ORM using dynamic objects. /// Table name. /// Override keys in schema. + /// Owner of the table. /// Instance of . - public dynamic Table(string table = "", string[] keys = null) + public dynamic Table(string table = "", string[] keys = null, string owner = "") { string key = string.Concat( table == null ? string.Empty : table, @@ -192,7 +199,7 @@ namespace DynamORM lock (SyncLock) dt = TablesCache.TryGetValue(key) ?? TablesCache.AddAndPassValue(key, - new DynamicTable(this, table, keys)); + new DynamicTable(this, table, owner, keys)); return dt; } @@ -227,18 +234,38 @@ namespace DynamORM #endregion Table + #region From + + /// + /// Adds to the 'From' clause the contents obtained by parsing the dynamic lambda expressions given. The supported + /// formats are: + /// - Resolve to a string: 'x => "Table AS Alias', where the alias part is optional. + /// - Resolve to an expression: 'x => x.Table.As( x.Alias )', where the alias part is optional. + /// - Generic expression: 'x => x( expression ).As( x.Alias )', where the alias part is mandatory. In this + /// case the alias is not annotated. + /// + /// The specification. + /// This instance to permit chaining. + public virtual IDynamicSelectQueryBuilder From(params Func[] func) + { + return new DynamicSelectQueryBuilder(this).From(func); + } + + #endregion From + #region Schema /// Builds table cache if necessary and returns it. /// Name of table for which build schema. + /// Owner of table for which build schema. /// Table schema. - public Dictionary GetSchema(string table) + public Dictionary GetSchema(string table, string owner = null) { Dictionary schema = null; lock (SyncLock) schema = Schema.TryGetValue(table.ToLower()) ?? - BuildAndCacheSchema(table, null); + BuildAndCacheSchema(table, null, owner); return schema; } @@ -279,15 +306,18 @@ namespace DynamORM /// Get schema describing objects from reader. /// Table from which extract column info. + /// Owner of table from which extract column info. /// List of objects . /// If your database doesn't get those values in upper case (like most of the databases) you should override this method. - protected virtual IEnumerable ReadSchema(string table) + protected virtual IEnumerable ReadSchema(string table, string owner) { using (var con = Open()) using (var cmd = con.CreateCommand()) { using (var rdr = cmd - .SetCommand(string.Format("SELECT * FROM {0} WHERE 1 = 0", DecorateName(table))) + .SetCommand(string.Format("SELECT * FROM {0}{1} WHERE 1 = 0", + !string.IsNullOrEmpty(owner) ? string.Format("{0}.", DecorateName(owner)) : string.Empty, + DecorateName(table))) .ExecuteReader(CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo)) foreach (DataRow col in rdr.GetSchemaTable().Rows) { @@ -307,7 +337,7 @@ namespace DynamORM } } - private Dictionary BuildAndCacheSchema(string tableName, DynamicTypeMap mapper) + private Dictionary BuildAndCacheSchema(string tableName, DynamicTypeMap mapper, string owner = null) { Dictionary schema = null; @@ -323,7 +353,7 @@ namespace DynamORM if (databaseSchemaSupport && !Schema.ContainsKey(tableName.ToLower())) { - schema = ReadSchema(tableName) + schema = ReadSchema(tableName, owner) .ToDictionary(k => k.Name.ToLower(), k => k); Schema[tableName.ToLower()] = schema; @@ -406,10 +436,28 @@ namespace DynamORM #region Decorators /// Gets or sets left side decorator for database objects. - public string LeftDecorator { get { return _leftDecorator; } set { _leftDecorator = value; } } + public string LeftDecorator + { + get { return _leftDecorator; } + set + { + _leftDecorator = value; + _leftDecoratorIsInInvalidMembersChars = + _leftDecorator.Length == 1 && StringExtensions.InvalidMemberChars.Contains(_leftDecorator[0]); + } + } /// Gets or sets right side decorator for database objects. - public string RightDecorator { get { return _rightDecorator; } set { _rightDecorator = value; } } + public string RightDecorator + { + get { return _rightDecorator; } + set + { + _rightDecorator = value; + _rightDecoratorIsInInvalidMembersChars = + _rightDecorator.Length == 1 && StringExtensions.InvalidMemberChars.Contains(_rightDecorator[0]); + } + } /// Gets or sets parameter name format. public string ParameterFormat { get { return _parameterFormat; } set { _parameterFormat = value; } } @@ -422,6 +470,22 @@ namespace DynamORM return String.Concat(_leftDecorator, name, _rightDecorator); } + /// Strip string representing name of database object from decorators. + /// Decorated name of database object. + /// Not decorated name of database object. + public string StripName(string name) + { + string res = name.Trim(StringExtensions.InvalidMemberChars); + + if (!_leftDecoratorIsInInvalidMembersChars && res.StartsWith(_leftDecorator)) + res = res.Substring(_leftDecorator.Length); + + if (!_rightDecoratorIsInInvalidMembersChars && res.EndsWith(_rightDecorator)) + res = res.Substring(0, res.Length - _rightDecorator.Length); + + return res; + } + /// Decorate string representing name of database object. /// String builder to which add decorated name. /// Name of database object. diff --git a/DynamORM/DynamicExtensions.cs b/DynamORM/DynamicExtensions.cs index c072fc0..5b61315 100644 --- a/DynamORM/DynamicExtensions.cs +++ b/DynamORM/DynamicExtensions.cs @@ -37,6 +37,8 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using DynamORM.Builders; +using DynamORM.Builders.Implementation; +using DynamORM.Helpers; using DynamORM.Mapper; namespace DynamORM @@ -270,6 +272,52 @@ namespace DynamORM return cmd; } + /// Extension for adding single parameter determining only type of object. + /// Command to handle. + /// Query builder containing schema. + /// Column schema to use. + /// Parameter value. + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand cmd, IDynamicQueryBuilder builder, DynamicSchemaColumn? col, object value) + { + var p = cmd.CreateParameter(); + p.ParameterName = builder.Database.GetParameterName(cmd.Parameters.Count); + + if (col.HasValue) + { + p.DbType = col.Value.Type; + + if ((builder.Database.Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema) + { + p.Size = col.Value.Size; + p.Precision = col.Value.Precision; + p.Scale = col.Value.Scale; + + // Quick fix - review that + // Quick fix 2 - use item.Schema in that case + if (p.Scale > p.Precision) + p.Scale = 4; + } + + p.Value = value == null ? DBNull.Value : value; + } + else if (value == null || value == DBNull.Value) + p.Value = DBNull.Value; + else + { + p.DbType = TypeMap.TryGetNullable(value.GetType()) ?? DbType.String; + + if (p.DbType == DbType.String) + p.Size = value.ToString().Length > 4000 ? -1 : 4000; + + p.Value = value; + } + + cmd.Parameters.Add(p); + + return cmd; + } + /// Extension for adding single parameter determining only type of object. /// Command to handle. /// Query builder containing schema. @@ -278,9 +326,13 @@ namespace DynamORM public static IDbCommand AddParameter(this IDbCommand cmd, IDynamicQueryBuilder builder, DynamicColumn item) { var p = cmd.CreateParameter(); - p.ParameterName = builder.DynamicTable.Database.GetParameterName(cmd.Parameters.Count); + p.ParameterName = builder.Database.GetParameterName(cmd.Parameters.Count); - var col = item.Schema ?? builder.Schema.TryGetNullable(item.ColumnName.ToLower()); + var col = item.Schema ?? (builder as DynamicQueryBuilder) + .NullOr(b => b.GetColumnFromSchema(item.ColumnName), + builder.Tables.FirstOrDefault() + .NullOr(t => t.Schema + .NullOr(s => s.TryGetNullable(item.ColumnName.ToLower()), null), null)); if (col.HasValue) { @@ -910,6 +962,19 @@ namespace DynamORM typeof(IEnumerable<>).IsAssignableFrom(type.GetGenericTypeDefinition()); } + /// Check if type implements IEnumerable<> interface. + /// Type to check. + /// Returns true if it does. + public static bool IsNullableType(this Type type) + { + Type generic = type.IsGenericType ? type.GetGenericTypeDefinition() : null; + + if (generic != null && generic.Equals(typeof(Nullable<>)) && type.IsClass) + return true; + + return false; + } + /// Check if type is collection of any kind. /// Type to check. /// Returns true if it is. @@ -1094,7 +1159,7 @@ namespace DynamORM /// Type to parse to. /// Value to parse. /// Handler of a try parse method. - /// Returns true if conversion was sucessfull. + /// Returns true if conversion was successful. public static T? TryParse(this string value, TryParseHandler handler) where T : struct { if (String.IsNullOrEmpty(value)) @@ -1113,7 +1178,7 @@ namespace DynamORM /// Value to parse. /// Default value of a result. /// Handler of a try parse method. - /// Returns true if conversion was sucessfull. + /// Returns true if conversion was successful. public static T TryParseDefault(this string value, T defaultValue, TryParseHandler handler) { if (String.IsNullOrEmpty(value)) @@ -1131,7 +1196,7 @@ namespace DynamORM /// Type which implements this function. /// Value to parse. /// Resulting value. - /// Returns true if conversion was sucessfull. + /// Returns true if conversion was successful. public delegate bool TryParseHandler(string value, out T result); #endregion TryParse extensions diff --git a/DynamORM/DynamicTable.cs b/DynamORM/DynamicTable.cs index 454cfcb..a11b05b 100644 --- a/DynamORM/DynamicTable.cs +++ b/DynamORM/DynamicTable.cs @@ -32,6 +32,8 @@ using System.Data; using System.Dynamic; using System.Linq; using DynamORM.Builders; +using DynamORM.Builders.Extensions; +using DynamORM.Builders.Implementation; using DynamORM.Helpers; using DynamORM.Mapper; @@ -211,6 +213,20 @@ namespace DynamORM /// Gets name of table. public virtual string TableName { get; private set; } + /// Gets name of owner. + public virtual string OwnerName { get; private set; } + + /// Gets full name of table containing owner and table name. + public virtual string FullName + { + get + { + return string.IsNullOrEmpty(OwnerName) ? + Database.DecorateName(TableName) : + string.Format("{0}.{1}", Database.DecorateName(OwnerName), Database.DecorateName(TableName)); + } + } + /// Gets table schema. /// If database doesn't support schema, only key columns are listed here. public virtual Dictionary Schema { get; private set; } @@ -222,11 +238,13 @@ namespace DynamORM /// Initializes a new instance of the class. /// Database and connection management. /// Table name. + /// Owner of the table. /// Override keys in schema. - public DynamicTable(DynamicDatabase database, string table = "", string[] keys = null) + public DynamicTable(DynamicDatabase database, string table = "", string owner = "", string[] keys = null) { Database = database; - TableName = table; + TableName = Database.StripName(table); + OwnerName = Database.StripName(owner); TableType = null; BuildAndCacheSchema(keys); @@ -350,9 +368,14 @@ namespace DynamORM /// Create new . /// New instance. - public virtual DynamicSelectQueryBuilder Query() + public virtual IDynamicSelectQueryBuilder Query() { - return new DynamicSelectQueryBuilder(this); + var builder = new DynamicSelectQueryBuilder(this.Database); + + if (!string.IsNullOrEmpty(this.TableName)) + builder.From(x => this.TableName); + + return builder; } /// Returns a single result. @@ -469,9 +492,9 @@ namespace DynamORM /// Create new . /// New instance. - public DynamicInsertQueryBuilder Insert() + public IDynamicInsertQueryBuilder Insert() { - return new DynamicInsertQueryBuilder(this); + return new DynamicInsertQueryBuilder(this.Database, this.TableName); } /// Adds a record to the database. You can pass in an Anonymous object, an , @@ -492,9 +515,9 @@ namespace DynamORM /// Create new . /// New instance. - public DynamicUpdateQueryBuilder Update() + public IDynamicUpdateQueryBuilder Update() { - return new DynamicUpdateQueryBuilder(this); + return new DynamicUpdateQueryBuilder(this.Database, this.TableName); } /// Updates a record in the database. You can pass in an Anonymous object, an ExpandoObject, @@ -530,9 +553,9 @@ namespace DynamORM /// Create new . /// New instance. - public DynamicDeleteQueryBuilder Delete() + public IDynamicDeleteQueryBuilder Delete() { - return new DynamicDeleteQueryBuilder(this); + return new DynamicDeleteQueryBuilder(this.Database, this.TableName); } /// Removes a record from the database. You can pass in an Anonymous object, an , @@ -600,11 +623,14 @@ namespace DynamORM private object DynamicInsert(object[] args, CallInfo info, IList types) { - var builder = new DynamicInsertQueryBuilder(this); + var builder = new DynamicInsertQueryBuilder(this.Database); if (types != null && types.Count == 1) HandleTypeArgument(null, info, ref types, builder, 0); + if (!string.IsNullOrEmpty(this.TableName) && builder.Tables.Count == 0) + builder.Table(this.TableName, this.Schema); + // loop the named args - see if we have order, columns and constraints if (info.ArgumentNames.Count > 0) { @@ -643,11 +669,14 @@ namespace DynamORM private object DynamicUpdate(object[] args, CallInfo info, IList types) { - var builder = new DynamicUpdateQueryBuilder(this); + var builder = new DynamicUpdateQueryBuilder(this.Database); if (types != null && types.Count == 1) HandleTypeArgument(null, info, ref types, builder, 0); + if (!string.IsNullOrEmpty(this.TableName) && builder.Tables.Count == 0) + builder.Table(this.TableName, this.Schema); + // loop the named args - see if we have order, columns and constraints if (info.ArgumentNames.Count > 0) { @@ -694,11 +723,14 @@ namespace DynamORM private object DynamicDelete(object[] args, CallInfo info, IList types) { - var builder = new DynamicDeleteQueryBuilder(this); + var builder = new DynamicDeleteQueryBuilder(this.Database); if (types != null && types.Count == 1) HandleTypeArgument(null, info, ref types, builder, 0); + if (!string.IsNullOrEmpty(this.TableName) && builder.Tables.Count == 0) + builder.Table(this.TableName, this.Schema); + // loop the named args - see if we have order, columns and constraints if (info.ArgumentNames.Count > 0) { @@ -742,11 +774,14 @@ namespace DynamORM private object DynamicQuery(object[] args, CallInfo info, string op, IList types) { object result; - var builder = new DynamicSelectQueryBuilder(this); + var builder = new DynamicSelectQueryBuilder(this.Database); if (types != null && types.Count == 1) HandleTypeArgument(null, info, ref types, builder, 0); + if (!string.IsNullOrEmpty(this.TableName) && builder.Tables.Count == 0) + builder.From(x => this.TableName); + // loop the named args - see if we have order, columns and constraints if (info.ArgumentNames.Count > 0) { @@ -782,15 +817,32 @@ namespace DynamORM break; case "columns": - if (args[i] is string) - builder.Select(((string)args[i]).Split(',')); - else if (args[i] is string[]) - builder.Select(args[i] as string); - else if (args[i] is DynamicColumn[]) - builder.Select((DynamicColumn[])args[i]); - else if (args[i] is DynamicColumn) - builder.Select((DynamicColumn)args[i]); - else goto default; + { + var agregate = (op == "Sum" || op == "Max" || op == "Min" || op == "Avg" || op == "Count") ? + op.ToUpper() : null; + + if (args[i] is string || args[i] is string[]) + builder.Select((args[i] as String).NullOr(s => s.Split(','), args[i] as String[]) + .Select(c => + { + var col = DynamicColumn.ParseSelectColumn(c); + if (string.IsNullOrEmpty(col.Aggregate)) + col.Aggregate = agregate; + + return col; + }).ToArray()); + else if (args[i] is DynamicColumn || args[i] is DynamicColumn[]) + builder.Select((args[i] as DynamicColumn).NullOr(c => new DynamicColumn[] { c }, args[i] as DynamicColumn[]) + .Select(c => + { + if (string.IsNullOrEmpty(c.Aggregate)) + c.Aggregate = agregate; + + return c; + }).ToArray()); + else goto default; + } + break; case "where": @@ -799,7 +851,7 @@ namespace DynamORM case "table": if (args[i] is string) - builder.Table(args[i].ToString()); + builder.From(x => args[i].ToString()); else goto default; break; @@ -816,14 +868,9 @@ namespace DynamORM } } - if (op == "Count" && builder.Columns.Count == 0) + if (op == "Count" && !builder.HasSelectColumns) { - result = Scalar(builder.Select(new DynamicColumn - { - ColumnName = "*", - Aggregate = op.ToUpper(), - Alias = "Count" - })); + result = Scalar(builder.Select(x => x.Count())); if (result is long) result = (int)(long)result; @@ -831,25 +878,15 @@ namespace DynamORM else if (op == "Sum" || op == "Max" || op == "Min" || op == "Avg" || op == "Count") { - if (builder.Columns.Count == 0) - throw new InvalidOperationException("You must select at least one column to agregate."); + if (!builder.HasSelectColumns) + throw new InvalidOperationException("You must select one column to agregate."); - foreach (var o in builder.Columns) - o.Aggregate = op.ToUpper(); + result = Scalar(builder); - if (builder.Columns.Count == 1) - { - result = Scalar(builder); - - if (op == "Count" && result is long) - result = (int)(long)result; - else if (result == DBNull.Value) - result = null; - } - else - { - result = Query(builder).FirstOrDefault(); // return lots - } + if (op == "Count" && result is long) + result = (int)(long)result; + else if (result == DBNull.Value) + result = null; } else { @@ -857,15 +894,15 @@ namespace DynamORM var justOne = op == "First" || op == "Last" || op == "Get" || op == "Single"; // Be sure to sort by DESC on selected columns - if (op == "Last") + /*if (op == "Last") { if (builder.Order.Count > 0) foreach (var o in builder.Order) o.Order = o.Order == DynamicColumn.SortOrder.Desc ? DynamicColumn.SortOrder.Asc : DynamicColumn.SortOrder.Desc; - } + }*/ - if (justOne && !(op == "Last" && builder.Order.Count == 0)) + if (justOne && !(op == "Last")) { if ((Database.Options & DynamicDatabaseOptions.SupportLimitOffset) == DynamicDatabaseOptions.SupportLimitOffset) builder.Limit(1); @@ -875,7 +912,7 @@ namespace DynamORM if (op == "Scalar") { - if (builder.Columns.Count != 1) + if (!builder.HasSelectColumns) throw new InvalidOperationException("You must select one column in scalar statement."); result = Scalar(builder); @@ -884,7 +921,7 @@ namespace DynamORM { if (justOne) { - if (op == "Last" && builder.Order.Count == 0) + if (op == "Last") result = Query(builder).LastOrDefault(); // Last record fallback else result = Query(builder).FirstOrDefault(); // return a single record @@ -908,7 +945,7 @@ namespace DynamORM return result; } - private void HandleTypeArgument(object[] args, CallInfo info, ref IList types, DynamicQueryBuilder builder, int i) where T : class + private void HandleTypeArgument(object[] args, CallInfo info, ref IList types, T builder, int i) where T : DynamicQueryBuilder { if (args != null) { diff --git a/DynamORM/Helpers/Dynamics/DynamicParser.cs b/DynamORM/Helpers/Dynamics/DynamicParser.cs new file mode 100644 index 0000000..1121876 --- /dev/null +++ b/DynamORM/Helpers/Dynamics/DynamicParser.cs @@ -0,0 +1,1136 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * This code file is based on Kerosene ORM solution for parsing dynamic + * lambda expressions by Moisés Barba Cebeira + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using System.Text; + +namespace DynamORM.Helpers.Dynamics +{ + /// + /// Class able to parse dynamic lambda expressions. Allows to create dynamic logic. + /// + public class DynamicParser : IExtendedDisposable + { + #region Node + + /// + /// Generic bindable operation where some of its operands is a dynamic argument, or a dynamic member or + /// a method of that argument. + /// + [Serializable] + public class Node : IDynamicMetaObjectProvider, IExtendedDisposable, ISerializable + { + #region MetaNode + + /// + /// Represents the dynamic binding and a binding logic of + /// an object participating in the dynamic binding. + /// + internal class MetaNode : DynamicMetaObject + { + /// + /// Initializes a new instance of the class. + /// + /// The parameter. + /// The restrictions. + /// The value. + public MetaNode(Expression parameter, BindingRestrictions rest, object value) + : base(parameter, rest, value) + { + } + + private DynamicMetaObject GetBinder(Func newNodeFunc) + { + var o = (Node)this.Value; + var node = newNodeFunc(o); + o.Parser.Last = node; + + var p = Expression.Variable(typeof(Node), "ret"); + var exp = Expression.Block(new ParameterExpression[] { p }, Expression.Assign(p, Expression.Constant(node))); + + return new MetaNode(exp, this.Restrictions, node); + } + + /// + /// Performs the binding of the dynamic get member operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindGetMember(GetMemberBinder binder) + { + return GetBinder(x => new GetMember(x, binder.Name) { Parser = x.Parser }); + } + + /// + /// Performs the binding of the dynamic set member operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// The representing the value for the set member operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) + { + return GetBinder(x => new SetMember(x, binder.Name, value.Value) { Parser = x.Parser }); + } + + /// + /// Performs the binding of the dynamic get index operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// An array of instances - indexes for the get index operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) + { + return GetBinder(x => new GetIndex(x, indexes.Select(m => m.Value).ToArray()) { Parser = x.Parser }); + } + + /// + /// Performs the binding of the dynamic set index operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// An array of instances - indexes for the set index operation. + /// The representing the value for the set index operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) + { + return GetBinder(x => new SetIndex(x, indexes.Select(m => m.Value).ToArray(), value.Value) { Parser = x.Parser }); + } + + /// + /// Performs the binding of the dynamic invoke operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// An array of instances - arguments to the invoke operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) + { + return GetBinder(x => new Invoke(x, args.Select(m => m.Value).ToArray()) { Parser = x.Parser }); + } + + /// + /// Performs the binding of the dynamic invoke member operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// An array of instances - arguments to the invoke member operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) + { + return GetBinder(x => new Method(x, binder.Name, args.Select(m => m.Value).ToArray()) { Parser = x.Parser }); + } + + /// + /// Performs the binding of the dynamic binary operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// An instance of the representing the right hand side of the binary operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg) + { + return GetBinder(x => new Binary(x, binder.Operation, arg.Value) { Parser = x.Parser }); + } + + /// + /// Performs the binding of the dynamic unary operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder) + { + var o = (Node)this.Value; + var node = new Unary(o, binder.Operation) { Parser = o.Parser }; + o.Parser.Last = node; + + // If operation is 'IsTrue' or 'IsFalse', we will return false to keep the engine working... + object ret = node; + if (binder.Operation == ExpressionType.IsTrue) ret = (object)false; + if (binder.Operation == ExpressionType.IsFalse) ret = (object)false; + + var p = Expression.Variable(ret.GetType(), "ret"); // the type is now obtained from "ret" + var exp = Expression.Block( + new ParameterExpression[] { p }, + Expression.Assign(p, Expression.Constant(ret))); // the expression is now obtained from "ret" + + return new MetaNode(exp, this.Restrictions, node); + } + + /// + /// Performs the binding of the dynamic conversion operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindConvert(ConvertBinder binder) + { + var o = (Node)this.Value; + var node = new Convert(o, binder.ReturnType) { Parser = o.Parser }; + o.Parser.Last = node; + + // Reducing the object to return if this is an assignment node... + object ret = o; + bool done = false; + + while (!done) + { + if (ret is SetMember) + ret = ((SetMember)o).Value; + else if (ret is SetIndex) + ret = ((SetIndex)o).Value; + else + done = true; + } + + // Creating an instance... + if (binder.ReturnType == typeof(string)) ret = ret.ToString(); + else + { + try + { + if (binder.ReturnType.IsNullableType()) + ret = null; // to avoid cast exceptions + else + ret = Activator.CreateInstance(binder.ReturnType, true); // true to allow non-public ctor as well + } + catch + { + // as the last resort scenario + ret = new object(); + } + } + + var p = Expression.Variable(binder.ReturnType, "ret"); + var exp = Expression.Block( + new ParameterExpression[] { p }, + Expression.Assign(p, Expression.Constant(ret, binder.ReturnType))); // specifying binder.ReturnType + + return new MetaNode(exp, this.Restrictions, node); + } + } + + #endregion MetaNode + + #region Argument + + /// + /// Describe a dynamic argument used in a dynamic lambda expression. + /// + [Serializable] + public class Argument : Node, ISerializable + { + /// + /// Initializes a new instance of the class. + /// + /// The name. + public Argument(string name) + : base(name) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Argument(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::Argument::Disposed}"; + return Name; + } + } + + #endregion Argument + + #region GetMember + + /// + /// Describe a 'get member' operation, as in 'x => x.Member'. + /// + [Serializable] + public class GetMember : Node, ISerializable + { + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The name. + public GetMember(Node host, string name) + : base(host, name) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected GetMember(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::GetMember::Disposed}"; + return string.Format("{0}.{1}", Host.Sketch(), Name.Sketch()); + } + } + + #endregion GetMember + + #region SetMember + + /// + /// Describe a 'set member' operation, as in 'x => x.Member = y'. + /// + [Serializable] + public class SetMember : Node, ISerializable + { + /// + /// Gets the value that has been (virtually) assigned to this member. It might be null if the null value has been + /// assigned to this instance, or if this instance is disposed. + /// + public object Value { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The name. + /// The value. + public SetMember(Node host, string name, object value) + : base(host, name) + { + Value = value; + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected SetMember(SerializationInfo info, StreamingContext context) + : base(info, context) + { + string type = info.GetString("MemberType"); + Value = type == "NULL" ? null : info.GetValue("MemberValue", Type.GetType(type)); + } + + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("MemberType", Value == null ? "NULL" : Value.GetType().AssemblyQualifiedName); + if (Value != null) + info.AddValue("MemberValue", Value); + + base.GetObjectData(info, context); + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::SetMember::Disposed}"; + return string.Format("({0}.{1} = {2})", Host.Sketch(), Name.Sketch(), Value.Sketch()); + } + } + + #endregion SetMember + + #region GetIndex + + /// + /// Describe a 'get indexed' operation, as in 'x => x.Member[...]'. + /// + [Serializable] + public class GetIndex : Node, ISerializable + { + /// Gets the indexes. + public object[] Indexes { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The indexes. + /// Indexes array cannot be null. + /// Indexes array cannot be empty. + public GetIndex(Node host, object[] indexes) + : base(host) + { + if (indexes == null) + throw new ArgumentNullException("indexes", "Indexes array cannot be null."); + if (indexes.Length == 0) + throw new ArgumentException("Indexes array cannot be empty."); + + Indexes = indexes; + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected GetIndex(SerializationInfo info, StreamingContext context) + : base(info, context) + { + int count = (int)info.GetValue("IndexCount", typeof(int)); + + if (count != 0) + { + Indexes = new object[count]; for (int i = 0; i < count; i++) + { + string typeName = info.GetString("IndexType" + i); + object obj = typeName == "NULL" ? null : info.GetValue("IndexValue" + i, Type.GetType(typeName)); + Indexes[i] = obj; + } + } + } + + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + int count = Indexes == null ? 0 : Indexes.Length; info.AddValue("IndexCount", count); + for (int i = 0; i < count; i++) + { + info.AddValue("IndexType" + i, Indexes[i] == null ? "NULL" : Indexes[i].GetType().AssemblyQualifiedName); + if (Indexes[i] != null) info.AddValue("IndexValue" + i, Indexes[i]); + } + + base.GetObjectData(info, context); + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::GetIndex::Disposed}"; + + return string.Format("{0}{1}", Host.Sketch(), Indexes == null ? "[empty]" : Indexes.Sketch()); + } + } + + #endregion GetIndex + + #region SetIndex + + /// + /// Describe a 'set indexed' operation, as in 'x => x.Member[...] = Value'. + /// + [Serializable] + public class SetIndex : GetIndex, ISerializable + { + /// + /// Gets the value that has been (virtually) assigned to this member. It might be null if the null value has been + /// assigned to this instance, or if this instance is disposed. + /// + public object Value { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The indexes. + /// The value. + public SetIndex(Node host, object[] indexes, object value) + : base(host, indexes) + { + Value = value; + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected SetIndex(SerializationInfo info, StreamingContext context) + : base(info, context) + { + string type = info.GetString("MemberType"); + Value = type == "NULL" ? null : info.GetValue("MemberValue", Type.GetType(type)); + } + + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("MemberType", Value == null ? "NULL" : Value.GetType().AssemblyQualifiedName); + if (Value != null) info.AddValue("MemberValue", Value); + + base.GetObjectData(info, context); + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::SetIndex::Disposed}"; + + return string.Format("({0}{1} = {2})", Host.Sketch(), Indexes == null ? "[empty]" : Indexes.Sketch(), Value.Sketch()); + } + } + + #endregion SetIndex + + #region Invoke + + /// + /// Describe a method invocation operation, as in 'x => x.Method(...)". + /// + [Serializable] + public class Invoke : Node, ISerializable + { + /// Gets the arguments. + public object[] Arguments { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The arguments. + public Invoke(Node host, object[] arguments) + : base(host) + { + Arguments = arguments == null || arguments.Length == 0 ? null : arguments; + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Invoke(SerializationInfo info, StreamingContext context) + : base(info, context) + { + int count = (int)info.GetValue("ArgumentCount", typeof(int)); + + if (count != 0) + { + Arguments = new object[count]; for (int i = 0; i < count; i++) + { + string typeName = info.GetString("ArgumentType" + i); + object obj = typeName == "NULL" ? null : info.GetValue("ArgumentValue" + i, Type.GetType(typeName)); + Arguments[i] = obj; + } + } + } + + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + int count = Arguments == null ? 0 : Arguments.Length; info.AddValue("ArgumentCount", count); + for (int i = 0; i < count; i++) + { + info.AddValue("ArgumentType" + i, Arguments[i] == null ? "NULL" : Arguments[i].GetType().AssemblyQualifiedName); + if (Arguments[i] != null) info.AddValue("ArgumentValue" + i, Arguments[i]); + } + + base.GetObjectData(info, context); + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::Invoke::Disposed}"; + + return string.Format("{0}{1}", Host.Sketch(), Arguments == null ? "()" : Arguments.Sketch(brackets: "()".ToCharArray())); + } + } + + #endregion Invoke + + #region Method + + /// + /// Describe a method invocation operation, as in 'x => x.Method(...)". + /// + [Serializable] + public class Method : Node, ISerializable + { + /// Gets the arguments. + public object[] Arguments { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The name. + /// The arguments. + public Method(Node host, string name, object[] arguments) + : base(host, name) + { + Arguments = arguments == null || arguments.Length == 0 ? null : arguments; + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Method(SerializationInfo info, StreamingContext context) + : base(info, context) + { + int count = (int)info.GetValue("ArgumentCount", typeof(int)); + + if (count != 0) + { + Arguments = new object[count]; for (int i = 0; i < count; i++) + { + string typeName = info.GetString("ArgumentType" + i); + object obj = typeName == "NULL" ? null : info.GetValue("ArgumentValue" + i, Type.GetType(typeName)); + Arguments[i] = obj; + } + } + } + + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + int count = Arguments == null ? 0 : Arguments.Length; info.AddValue("ArgumentCount", count); + for (int i = 0; i < count; i++) + { + info.AddValue("ArgumentType" + i, Arguments[i] == null ? "NULL" : Arguments[i].GetType().AssemblyQualifiedName); + if (Arguments[i] != null) info.AddValue("ArgumentValue" + i, Arguments[i]); + } + + base.GetObjectData(info, context); + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::Method::Disposed}"; + + return string.Format("{0}.{1}{2}", Host.Sketch(), Name.Sketch(), Arguments == null ? "()" : Arguments.Sketch(brackets: "()".ToCharArray())); + } + } + + #endregion Method + + #region Binary + + /// + /// Represents a binary operation between a dynamic element and an arbitrary object, including null ones, as in + /// 'x => (x && null)'. The left operand must be an instance of , whereas the right one + /// can be any object, including null values. + /// + [Serializable] + public class Binary : Node, ISerializable + { + /// Gets the operation. + public ExpressionType Operation { get; private set; } + + /// Gets host of the . + public Node Left { get { return Host; } } + + /// Gets the right side value. + public object Right { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The left. + /// The operation. + /// The right. + public Binary(Node left, ExpressionType operation, object right) + : base(left) + { + Operation = operation; + Right = right; + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Binary(SerializationInfo info, StreamingContext context) + : base(info, context) + { + Operation = (ExpressionType)info.GetValue("Operation", typeof(ExpressionType)); + + string type = info.GetString("RightType"); + Right = type == "NULL" ? null : (Node)info.GetValue("RightItem", Type.GetType(type)); + } + + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("Operation", Operation); + + info.AddValue("RightType", Right == null ? "NULL" : Right.GetType().AssemblyQualifiedName); + if (Right != null) + info.AddValue("RightItem", Right); + + base.GetObjectData(info, context); + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::Binary::Disposed}"; + + return string.Format("({0} {1} {2})", Host.Sketch(), Operation, Right.Sketch()); + } + } + + #endregion Binary + + #region Unary + + /// + /// Represents an unary operation, as in 'x => !x'. The target must be a instance. There + /// is no distinction between pre- and post- version of the same operation. + /// + [Serializable] + public class Unary : Node, ISerializable + { + /// Gets the operation. + public ExpressionType Operation { get; private set; } + + /// Gets host of the . + public Node Target { get { return Host; } } + + /// + /// Initializes a new instance of the class. + /// + /// The target. + /// The operation. + public Unary(Node target, ExpressionType operation) + : base(target) + { + Operation = operation; + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Unary(SerializationInfo info, StreamingContext context) + : base(info, context) + { + Operation = (ExpressionType)info.GetValue("Operation", typeof(ExpressionType)); + } + + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("Operation", Operation); + + base.GetObjectData(info, context); + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::Binary::Disposed}"; + + return string.Format("({0} {1})", Operation, Host.Sketch()); + } + } + + #endregion Unary + + #region Convert + + /// + /// Represents a conversion operation, as in 'x => (string)x'. + /// + [Serializable] + public class Convert : Node, ISerializable + { + /// Gets the new type to which value will be converted. + public Type NewType { get; private set; } + + /// Gets host of the . + public Node Target { get { return Host; } } + + /// + /// Initializes a new instance of the class. + /// + /// The target. + /// The new type. + public Convert(Node target, Type newType) + : base(target) + { + NewType = newType; + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Convert(SerializationInfo info, StreamingContext context) + : base(info, context) + { + NewType = (Type)info.GetValue("NewType", typeof(Type)); + } + + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("NewType", NewType); + + base.GetObjectData(info, context); + } + } + + #endregion Convert + + /// + /// Gets the name of the member. It might be null if this instance is disposed. + /// + public string Name { get; internal set; } + + /// Gets host of the . + public Node Host { get; internal set; } + + /// Gets reference to the parser. + public DynamicParser Parser { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + internal Node() + { + IsDisposed = false; + } + + /// + /// Initializes a new instance of the class. + /// + /// The host. + internal Node(Node host) + : this() + { + if (host == null) + throw new ArgumentNullException("host", "Host cannot be null."); + + Host = host; + } + + /// + /// Initializes a new instance of the class. + /// + /// The name. + internal Node(string name) + : this() + { + Name = name.Validated("Name"); + } + + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The name. + /// Host cannot be null. + internal Node(Node host, string name) + : this(host) + { + Name = name.Validated("Name"); + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Node(SerializationInfo info, StreamingContext context) + { + Name = info.GetString("MemberName"); + + string type = info.GetString("HostType"); + Host = type == "NULL" ? null : (Node)info.GetValue("HostItem", Type.GetType(type)); + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::Disposed}"; + + return "{DynamicParser::Node::Empty}"; + } + + #region Implementation of IDynamicMetaObjectProvider + + /// Returns the responsible + /// for binding operations performed on this object. + /// The expression tree representation of the runtime value. + /// The to bind this object. + /// Thrown if this instance is disposed. + public DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression parameter) + { + if (IsDisposed) + throw new ObjectDisposedException("DynamicParser.Node"); + + return new MetaNode( + parameter, + BindingRestrictions.GetInstanceRestriction(parameter, this), + this); + } + + #endregion Implementation of IDynamicMetaObjectProvider + + #region Implementation of IExtendedDisposable + + /// Gets a value indicating whether this instance is disposed. + public bool IsDisposed { get; private set; } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + IsDisposed = true; + } + + #endregion Implementation of IExtendedDisposable + + #region Implementation of ISerializable + + /// + /// Populates a with the data needed to serialize the target object. + /// + /// The to populate with data. + /// The destination (see ) for this serialization. + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (!string.IsNullOrEmpty(Name)) + info.AddValue("MemberName", Name); + + info.AddValue("HostType", Host == null ? "NULL" : Host.GetType().AssemblyQualifiedName); + if (Host != null) + info.AddValue("HostItem", Host); + } + + #endregion Implementation of ISerializable + } + + #endregion Node + + #region Data + + private List _arguments = new List(); + private object _uncertainResult; + + #endregion Data + + #region Properties + + /// Gets the last node (root of the tree). + public Node Last { get; internal set; } + + /// + /// Gets an enumeration containing the dynamic arguments used in the dynamic lambda expression parsed. + /// + public IEnumerable Arguments + { + get + { + List list = new List(); + if (!IsDisposed && _arguments != null) + list.AddRange(_arguments); + + foreach (var arg in list) + yield return arg; + + list.Clear(); + list = null; + } + } + + /// + /// Gets the number of dynamic arguments used in the dynamic lambda expression parsed. + /// + public int Count + { + get { return _arguments == null ? 0 : _arguments.Count; } + } + + /// + /// Gets the result of the parsing of the dynamic lambda expression. This result can be either an arbitrary object, + /// including null, if the expression resolves to it, or an instance of the class that + /// contains the last logic expression evaluated when parsing the dynamic lambda expression. + /// + public object Result + { + get { return _uncertainResult ?? Last; } + } + + #endregion Properties + + private DynamicParser(Delegate f) + { + foreach (var p in f.Method.GetParameters()) + { + if (p.GetCustomAttributes(typeof(DynamicAttribute), true).Any()) + this._arguments.Add(new Node.Argument(p.Name) { Parser = this }); + else + throw new ArgumentException(string.Format("Argument '{0}' must be dynamic.", p.Name)); + } + + _uncertainResult = f.DynamicInvoke(_arguments.ToArray()); + } + + /// + /// Parses the dynamic lambda expression given in the form of a delegate, and returns a new instance of the + /// class that holds the dynamic arguments used in the dynamic lambda expression, and + /// the result of the parsing. + /// + /// The dynamic lambda expression to parse. + /// A new instance of . + public static DynamicParser Parse(Delegate f) + { + return new DynamicParser(f); + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Disposed}"; + + StringBuilder sb = new StringBuilder(); + + sb.Append("("); + bool first = true; + + if (_arguments != null) + { + foreach (var arg in _arguments) + { + if (!first) sb.Append(", "); else first = false; + sb.Append(arg); + } + } + + sb.Append(")"); + + sb.AppendFormat(" => {0}", Result.Sketch()); + + return sb.ToString(); + } + + #region Implementation of IExtendedDisposable + + /// Gets a value indicating whether this instance is disposed. + public bool IsDisposed { get; private set; } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + IsDisposed = true; + } + + #endregion Implementation of IExtendedDisposable + } +} \ No newline at end of file diff --git a/DynamORM/Helpers/IExtendedDisposable.cs b/DynamORM/Helpers/IExtendedDisposable.cs new file mode 100644 index 0000000..562d146 --- /dev/null +++ b/DynamORM/Helpers/IExtendedDisposable.cs @@ -0,0 +1,44 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; + +namespace DynamORM.Helpers +{ + /// Extends interface. + public interface IExtendedDisposable : IDisposable + { + /// + /// Gets a value indicating whether this instance is disposed. + /// + /// + /// true if this instance is disposed; otherwise, false. + /// + bool IsDisposed { get; } + } +} \ No newline at end of file diff --git a/DynamORM/Helpers/StringExtensions.cs b/DynamORM/Helpers/StringExtensions.cs new file mode 100644 index 0000000..ed6edd2 --- /dev/null +++ b/DynamORM/Helpers/StringExtensions.cs @@ -0,0 +1,332 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Collections; +using System.Reflection; +using System.Text; + +namespace DynamORM.Helpers +{ + /// Class containing useful string extensions. + internal static class StringExtensions + { + static StringExtensions() + { + InvalidMultipartMemberChars = _InvalidMultipartMemberChars.ToCharArray(); + InvalidMemberChars = _InvalidMemberChars.ToCharArray(); + } + + private static readonly string _InvalidMultipartMemberChars = " +-*/^%[]{}()!\"\\&=?¿"; + private static readonly string _InvalidMemberChars = "." + _InvalidMultipartMemberChars; + + /// + /// Gets an array with some invalid characters that cannot be used with multipart names for class members. + /// + public static char[] InvalidMultipartMemberChars { get; private set; } + + /// + /// Gets an array with some invalid characters that cannot be used with names for class members. + /// + public static char[] InvalidMemberChars { get; private set; } + + /// + /// Provides with an alternate and generic way to obtain an alternate string representation for this instance, + /// applying the following rules: + /// - Null values are returned as with the value, or a null object. + /// - Enum values are translated into their string representation. + /// - If the type has override the 'ToString' method then it is used. + /// - If it is a dictionary, then a collection of key/value pairs where the value part is also translated. + /// - If it is a collection, then a collection of value items also translated. + /// - If it has public public properties (or if not, if it has public fields), the collection of name/value + /// pairs, with the values translated. + /// - Finally it falls back to the standard 'type.FullName' mechanism. + /// + /// The object to obtain its alternate string representation from. + /// The brackets to use if needed. If not null it must be at least a 2-chars' array containing + /// the opening and closing brackets. + /// Representation of null string.. + /// The alternate string representation of this object. + public static string Sketch(this object obj, char[] brackets = null, string nullString = "(null)") + { + if (obj == null) return nullString; + if (obj is string) return (string)obj; + + Type type = obj.GetType(); + if (type.IsEnum) return obj.ToString(); + + // If the ToString() method has been overriden (by the type itself, or by its parents), let's use it... + MethodInfo method = type.GetMethod("ToString", Type.EmptyTypes); + if (method.DeclaringType != typeof(object)) return obj.ToString(); + + // For alll other cases... + StringBuilder sb = new StringBuilder(); + bool first = true; + + // Dictionaries... + if (obj is IDictionary) + { + if (brackets == null || brackets.Length < 2) + brackets = "[]".ToCharArray(); + + sb.AppendFormat("{0}", brackets[0]); first = true; foreach (DictionaryEntry kvp in (IDictionary)obj) + { + if (!first) sb.Append(", "); else first = false; + sb.AppendFormat("'{0}'='{1}'", kvp.Key.Sketch(), kvp.Value.Sketch()); + } + + sb.AppendFormat("{0}", brackets[1]); + return sb.ToString(); + } + + // IEnumerables... + IEnumerator ator = null; + if (obj is IEnumerable) + ator = ((IEnumerable)obj).GetEnumerator(); + else + { + method = type.GetMethod("GetEnumerator", Type.EmptyTypes); + if (method != null) + ator = (IEnumerator)method.Invoke(obj, null); + } + + if (ator != null) + { + if (brackets == null || brackets.Length < 2) brackets = "[]".ToCharArray(); + sb.AppendFormat("{0}", brackets[0]); first = true; while (ator.MoveNext()) + { + if (!first) sb.Append(", "); else first = false; + sb.AppendFormat("{0}", ator.Current.Sketch()); + } + + sb.AppendFormat("{0}", brackets[1]); + + if (ator is IDisposable) + ((IDisposable)ator).Dispose(); + + return sb.ToString(); + } + + // As a last resort, using the public properties (or fields if needed, or type name)... + BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy; + PropertyInfo[] props = type.GetProperties(flags); + FieldInfo[] infos = type.GetFields(flags); + + if (props.Length == 0 && infos.Length == 0) sb.Append(type.FullName); // Fallback if needed + else + { + if (brackets == null || brackets.Length < 2) brackets = "{}".ToCharArray(); + sb.AppendFormat("{0}", brackets[0]); + first = true; + + if (props.Length != 0) + { + foreach (var prop in props) + { + if (!first) sb.Append(", "); else first = false; + sb.AppendFormat("{0}='{1}'", prop.Name, prop.GetValue(obj, null).Sketch()); + } + } + else + { + if (infos.Length != 0) + { + foreach (var info in infos) + { + if (!first) sb.Append(", "); else first = false; + sb.AppendFormat("{0}='{1}'", info.Name, info.GetValue(obj).Sketch()); + } + } + } + + sb.AppendFormat("{0}", brackets[1]); + } + + // And returning... + return sb.ToString(); + } + + /// + /// Returns true if the target string contains any of the characters given. + /// + /// The target string. It cannot be null. + /// An array containing the characters to test. It cannot be null. If empty false is returned. + /// True if the target string contains any of the characters given, false otherwise. + public static bool ContainsAny(this string source, char[] items) + { + if (source == null) throw new ArgumentNullException("source", "Source string cannot be null."); + if (items == null) throw new ArgumentNullException("items", "Array of characters to test cannot be null."); + + if (items.Length == 0) return false; // No characters to validate + int ix = source.IndexOfAny(items); + return ix >= 0 ? true : false; + } + + /// + /// Returns a new validated string using the rules given. + /// + /// The source string. + /// A description of the source string to build errors and exceptions if needed. + /// True if the returned string can be null. + /// True if the returned string can be empty. + /// True to trim the returned string. + /// True to left-trim the returned string. + /// True to right-trim the returned string. + /// If >= 0, the min valid length for the returned string. + /// If >= 0, the max valid length for the returned string. + /// If not '\0', the character to use to left-pad the returned string if needed. + /// If not '\0', the character to use to right-pad the returned string if needed. + /// If not null, an array containing invalid chars that must not appear in the returned + /// string. + /// If not null, an array containing the only characters that are considered valid for the + /// returned string. + /// A new validated string. + public static string Validated(this string source, string desc = null, + bool canbeNull = false, bool canbeEmpty = false, + bool trim = true, bool trimStart = false, bool trimEnd = false, + int minLen = -1, int maxLen = -1, char padLeft = '\0', char padRight = '\0', + char[] invalidChars = null, char[] validChars = null) + { + // Assuring a valid descriptor... + if (string.IsNullOrWhiteSpace(desc)) desc = "Source"; + + // Validating if null sources are accepted... + if (source == null) + { + if (!canbeNull) throw new ArgumentNullException(desc, string.Format("{0} cannot be null.", desc)); + return null; + } + + // Trimming if needed... + if (trim && !(trimStart || trimEnd)) source = source.Trim(); + else + { + if (trimStart) source = source.TrimStart(' '); + if (trimEnd) source = source.TrimEnd(' '); + } + + // Adjusting lenght... + if (minLen > 0) + { + if (padLeft != '\0') source = source.PadLeft(minLen, padLeft); + if (padRight != '\0') source = source.PadRight(minLen, padRight); + } + + if (maxLen > 0) + { + if (padLeft != '\0') source = source.PadLeft(maxLen, padLeft); + if (padRight != '\0') source = source.PadRight(maxLen, padRight); + } + + // Validating emptyness and lenghts... + if (source.Length == 0) + { + if (!canbeEmpty) throw new ArgumentException(string.Format("{0} cannot be empty.", desc)); + return string.Empty; + } + + if (minLen >= 0 && source.Length < minLen) throw new ArgumentException(string.Format("Lenght of {0} '{1}' is lower than '{2}'.", desc, source, minLen)); + if (maxLen >= 0 && source.Length > maxLen) throw new ArgumentException(string.Format("Lenght of {0} '{1}' is bigger than '{2}'.", desc, source, maxLen)); + + // Checking invalid chars... + if (invalidChars != null) + { + int n = source.IndexOfAny(invalidChars); + if (n >= 0) throw new ArgumentException(string.Format("Invalid character '{0}' found in {1} '{2}'.", source[n], desc, source)); + } + + // Checking valid chars... + if (validChars != null) + { + int n = validChars.ToString().IndexOfAny(source.ToCharArray()); + if (n >= 0) throw new ArgumentException(string.Format("Invalid character '{0}' found in {1} '{2}'.", validChars.ToString()[n], desc, source)); + } + + return source; + } + + /// + /// Splits the given string with the 'something AS alias' format, returning a tuple containing its 'something' and 'alias' parts. + /// If no alias is detected, then its component in the tuple returned is null and all the contents from the source + /// string are considered as the 'something' part. + /// + /// The source string. + /// A tuple containing the 'something' and 'alias' parts. + public static Tuple SplitSomethingAndAlias(this string source) + { + source = source.Validated("[Something AS Alias]"); + + string something = null; + string alias = null; + int n = source.LastIndexOf(" AS ", StringComparison.OrdinalIgnoreCase); + + if (n < 0) + something = source; + else + { + something = source.Substring(0, n); + alias = source.Substring(n + 4); + } + + return new Tuple(something, alias); + } + + /// Allows to replace parameters inside of string. + /// String containing parameters in format [$ParameterName]. + /// Function that should return value that will be placed in string in place of placed parameter. + /// Prefix of the parameter. This value can't be null or empty, default value [$. + /// Suffix of the parameter. This value can't be null or empty, default value ]. + /// Parsed string. + public static string FillStringWithVariables(this string stringToFill, Func getValue, string prefix = "[$", string sufix = "]") + { + int startPos = 0, endPos = 0; + prefix.Validated(); + sufix.Validated(); + + startPos = stringToFill.IndexOf(prefix, startPos); + while (startPos >= 0) + { + endPos = stringToFill.IndexOf(sufix, startPos + prefix.Length); + int nextStartPos = stringToFill.IndexOf(prefix, startPos + prefix.Length); + + if (endPos > startPos + prefix.Length + 1 && (nextStartPos > endPos || nextStartPos == -1)) + { + string paramName = stringToFill.Substring(startPos + prefix.Length, endPos - (startPos + prefix.Length)); + + stringToFill = stringToFill + .Remove(startPos, (endPos - startPos) + sufix.Length) + .Insert(startPos, getValue(paramName)); + } + + startPos = stringToFill.IndexOf(prefix, startPos + prefix.Length); + } + + return stringToFill; + } + } +} \ No newline at end of file diff --git a/DynamORM/Helpers/UnclassifiedExtensions.cs b/DynamORM/Helpers/UnclassifiedExtensions.cs new file mode 100644 index 0000000..a271ff7 --- /dev/null +++ b/DynamORM/Helpers/UnclassifiedExtensions.cs @@ -0,0 +1,75 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; + +namespace DynamORM.Helpers +{ + /// Class contains unclassified extensions. + public static class UnclassifiedExtensions + { + /// Easy way to use conditional value. + /// Includes . + /// Input object type to check. + /// Result type. + /// The object to check. + /// The select function. + /// The else value. + /// Selected value or default value. + /// It lets you do this: + /// var lname = thingy.NullOr(t => t.Name).NullOr(n => n.ToLower()); + /// which is more fluent and (IMO) easier to read than this: + /// var lname = (thingy != null ? thingy.Name : null) != null ? thingy.Name.ToLower() : null; + /// + public static R NullOr(this T obj, Func func, R elseValue = default(R)) where T : class + { + return obj != null && obj != DBNull.Value ? + func(obj) : elseValue; + } + + /// Easy way to use conditional value. + /// Includes . + /// Input object type to check. + /// Result type. + /// The object to check. + /// The select function. + /// The else value function. + /// Selected value or default value. + /// It lets you do this: + /// var lname = thingy.NullOr(t => t.Name).NullOr(n => n.ToLower()); + /// which is more fluent and (IMO) easier to read than this: + /// var lname = (thingy != null ? thingy.Name : null) != null ? thingy.Name.ToLower() : null; + /// + public static R NullOrFn(this T obj, Func func, Func elseFunc = null) where T : class + { + // Old if to avoid recurency. + return obj != null && obj != DBNull.Value ? + func(obj) : elseFunc != null ? elseFunc() : default(R); + } + } +} \ No newline at end of file diff --git a/DynamORM/Mapper/TableAttribute.cs b/DynamORM/Mapper/TableAttribute.cs index 49fe2a0..66f21b5 100644 --- a/DynamORM/Mapper/TableAttribute.cs +++ b/DynamORM/Mapper/TableAttribute.cs @@ -34,6 +34,9 @@ namespace DynamORM.Mapper [AttributeUsage(AttributeTargets.Class)] public class TableAttribute : Attribute { + /// Gets or sets table owner name. + public string Owner { get; set; } + /// Gets or sets name. public string Name { get; set; } diff --git a/DynamORM/Properties/AssemblyInfo.cs b/DynamORM/Properties/AssemblyInfo.cs index 84529c1..9458c1c 100644 --- a/DynamORM/Properties/AssemblyInfo.cs +++ b/DynamORM/Properties/AssemblyInfo.cs @@ -29,6 +29,7 @@ */ using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following @@ -62,4 +63,5 @@ using System.Runtime.InteropServices; // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.1")] -[assembly: AssemblyFileVersion("1.0.0.1")] \ No newline at end of file +[assembly: AssemblyFileVersion("1.0.0.1")] +[assembly: InternalsVisibleTo("DynamORM.Tests")] \ No newline at end of file