diff --git a/.hgsubstate b/.hgsubstate index 4669d200b..dcdfe419b 100644 --- a/.hgsubstate +++ b/.hgsubstate @@ -1,5 +1,5 @@ ec573e5476f7e8a5a61593d6393e9985e9484fcc src/Orchard.Web/Modules/Orchard.Forms -d3ffae8d849f6b10eb1c4421f7b18e1d05edc85e src/Orchard.Web/Modules/Orchard.Projections +4e99d7e20caab07ddbdcf9c780525613bcf8a15b src/Orchard.Web/Modules/Orchard.Projections 01b83c05050bb731d9f69256bbe8884d458ea1c9 src/Orchard.Web/Modules/Orchard.Rules 65057c6a5cd71f7994ba9bcbeece50dbb737620e src/Orchard.Web/Modules/Orchard.TaskLease c2e3c396c1fc6c60b131bfc9f83564c6f8d18e58 src/Orchard.Web/Modules/Orchard.Tokens diff --git a/src/Orchard.Tests/ContentManagement/DynamicContentQueryTests.cs b/src/Orchard.Tests/ContentManagement/DynamicContentQueryTests.cs index 51f90a5be..bdf21147a 100644 --- a/src/Orchard.Tests/ContentManagement/DynamicContentQueryTests.cs +++ b/src/Orchard.Tests/ContentManagement/DynamicContentQueryTests.cs @@ -93,7 +93,7 @@ namespace Orchard.Tests.ContentManagement { public void SpecificTypeIsReturnedWhenSpecified() { AddSampleData(); - var alphaBeta = _manager.Query().Where(x => x.WithRecord("ContentType").In("Name", new [] {"alpha", "beta"})).List(); + var alphaBeta = _manager.HqlQuery().ForType("alpha", "beta").List(); Assert.That(alphaBeta.Count(), Is.EqualTo(2)); Assert.That(alphaBeta.Count(x => x.Has()), Is.EqualTo(1)); @@ -101,7 +101,7 @@ namespace Orchard.Tests.ContentManagement { Assert.That(alphaBeta.Count(x => x.Has()), Is.EqualTo(0)); Assert.That(alphaBeta.Count(x => x.Has()), Is.EqualTo(0)); - var gammaDelta = _manager.Query().Where(x => x.WithRecord("ContentType").In("Name", new[] { "gamma", "delta" })).List(); + var gammaDelta = _manager.HqlQuery().ForType("gamma", "delta").List(); Assert.That(gammaDelta.Count(), Is.EqualTo(2)); Assert.That(gammaDelta.Count(x => x.Has()), Is.EqualTo(0)); @@ -119,8 +119,11 @@ namespace Orchard.Tests.ContentManagement { _manager.Create("gamma", init => { init.Record.Frap = "four"; }); _session.Flush(); - var twoOrFour = _manager.Query() - .Where(x => x.WithRecord("GammaRecord").Or(a => a.Eq("Frap", "one"), b => b.Eq("Frap", "four"))) + var twoOrFour = _manager + .HqlQuery() + .Where( + alias => alias.ContentPartRecord(), + x => x.Or(a => a.Eq("Frap", "one"), b => b.Eq("Frap", "four"))) .List(); Assert.That(twoOrFour.Count(), Is.EqualTo(2)); @@ -133,8 +136,8 @@ namespace Orchard.Tests.ContentManagement { [Test] public void EmptyWherePredicateRequiresRecord() { AddSampleData(); - var gammas = _manager.Query().Where(x => x.WithRecord("GammaRecord")).List(); // simulates an inner join - var deltas = _manager.Query().Where(x => x.WithRecord("DeltaRecord")).List(); + var gammas = _manager.HqlQuery().Join(alias => alias.ContentPartRecord()).List(); + var deltas = _manager.HqlQuery().Join(alias => alias.ContentPartRecord()).List(); Assert.That(gammas.Count(), Is.EqualTo(1)); Assert.That(deltas.Count(), Is.EqualTo(1)); @@ -152,9 +155,10 @@ namespace Orchard.Tests.ContentManagement { _session.Flush(); _session.Clear(); - var ascending = _manager.Query("gamma") - .OrderBy(x => x.WithRecord("GammaRecord").Asc("Frap")) - .List().ToList(); + var ascending = _manager.HqlQuery() + .ForType("gamma") + .OrderBy(alias => alias.ContentPartRecord(), x => x.Asc("Frap")) + .List().ToList(); Assert.That(ascending.Count(), Is.EqualTo(5)); Assert.That(ascending.First().Record.Frap, Is.EqualTo("four")); @@ -162,8 +166,9 @@ namespace Orchard.Tests.ContentManagement { _session.Clear(); - var descending = _manager.Query() - .OrderBy(x => x.WithRecord("GammaRecord").Desc("Frap")) + var descending = _manager.HqlQuery() + .ForType("gamma") + .OrderBy(alias => alias.ContentPartRecord(), x => x.Desc("Frap")) .List().ToList(); Assert.That(descending.Count(), Is.EqualTo(5)); @@ -180,12 +185,12 @@ namespace Orchard.Tests.ContentManagement { _manager.Create("gamma", init => { init.Record.Frap = "four"; }); _session.Flush(); - var reverseById = _manager.Query() - .OrderBy(x => x.WithRecord("GammaRecord").Desc("Id")) + var reverseById = _manager.HqlQuery() + .OrderBy(alias => alias.ContentPartRecord(), x => x.Desc("Id")) .List(); - var subset = _manager.Query() - .OrderBy(x => x.WithRecord("GammaRecord").Desc("Id")) + var subset = _manager.HqlQuery() + .OrderBy(alias => alias.ContentPartRecord(), x => x.Desc("Id")) .Slice(2, 3); Assert.That(subset.Count(), Is.EqualTo(3)); @@ -217,10 +222,11 @@ namespace Orchard.Tests.ContentManagement { _session.Flush(); _session.Clear(); - var results = _manager.Query("gamma") - .Where(x => x.WithVersionRecord("EpsilonRecord").Or(a => a.Eq("Quad", "2"), b => b.Eq("Quad", "3"))) - .OrderBy(x => x.WithVersionRecord("EpsilonRecord").Desc("Quad")) - .List(); + var results = _manager.HqlQuery() + .Where(alias => alias.ContentPartRecord(), x => x.Or(a => a.Eq("Quad", "2"), b => b.Eq("Quad", "3"))) + .OrderBy(alias => alias.ContentPartRecord(), x => x.Desc("Quad")) + .ForType("gamma") + .List(); Assert.That(results.Count(), Is.EqualTo(2)); Assert.That(results.First().Record, Has.Property("Quad").EqualTo("3")); diff --git a/src/Orchard/ContentManagement/ContentExtensions.cs b/src/Orchard/ContentManagement/ContentExtensions.cs index a2b86aac6..5d8532a43 100644 --- a/src/Orchard/ContentManagement/ContentExtensions.cs +++ b/src/Orchard/ContentManagement/ContentExtensions.cs @@ -74,12 +74,18 @@ namespace Orchard.ContentManagement { where TPart : ContentPart { return manager.Query().ForPart(); } + public static IContentQuery Query(this IContentManager manager) where TPart : ContentPart where TRecord : ContentPartRecord { return manager.Query().ForPart().Join(); } + public static IHqlQuery HqlQuery(this IContentManager manager) + where TPart : ContentPart { + return manager.HqlQuery().ForPart(); + } + /* Query(VersionOptions options) */ public static IContentQuery Query(this IContentManager manager, VersionOptions options) { diff --git a/src/Orchard/ContentManagement/DefaultContentManager.cs b/src/Orchard/ContentManagement/DefaultContentManager.cs index 7966bc299..d95441c8e 100644 --- a/src/Orchard/ContentManagement/DefaultContentManager.cs +++ b/src/Orchard/ContentManagement/DefaultContentManager.cs @@ -487,6 +487,10 @@ namespace Orchard.ContentManagement { return query.ForPart(); } + public IHqlQuery HqlQuery() { + return new DefaultHqlQuery(this, _sessionLocator.Value.For(typeof(ContentItemVersionRecord))); + } + // Insert or Update imported data into the content manager. // Call content item handlers. public void Import(XElement element, ImportContentSession importContentSession) { diff --git a/src/Orchard/ContentManagement/DefaultHqlQuery.cs b/src/Orchard/ContentManagement/DefaultHqlQuery.cs new file mode 100644 index 000000000..6a8e5e849 --- /dev/null +++ b/src/Orchard/ContentManagement/DefaultHqlQuery.cs @@ -0,0 +1,390 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using NHibernate; +using Orchard.ContentManagement.Records; +using Orchard.Utility.Extensions; + +namespace Orchard.ContentManagement { + + public class DefaultHqlQuery : IHqlQuery { + private readonly ISession _session; + private VersionOptions _versionOptions; + + protected IJoin _from; + protected readonly List> _joins = new List>(); + protected readonly List>> _wheres = new List>>(); + protected readonly List>> _sortings = new List>>(); + + public IContentManager ContentManager { get; private set; } + + public DefaultHqlQuery(IContentManager contentManager, ISession session) { + _session = session; + ContentManager = contentManager; + } + + internal string PathToAlias(string path) { + if (String.IsNullOrWhiteSpace(path)) { + throw new ArgumentException("Path can't be empty"); + } + + return Char.ToLower(path[0], CultureInfo.InvariantCulture) + path.Substring(1); + } + + internal Join BindNamedAlias(string alias) { + var tuple = _joins.FirstOrDefault(x => x.Item2.Name == alias); + return tuple == null ? null : tuple.Item2; + } + + internal IAlias BindCriteriaByPath(IAlias alias, string path) { + return BindCriteriaByAlias(alias, path, PathToAlias(path)); + } + + internal IAlias BindCriteriaByAlias(IAlias alias, string path, string aliasName) { + // is this Join already existing (based on aliasName) + + Join join = BindNamedAlias(aliasName); + + if (join == null) { + join = new Join(path, aliasName); + _joins.Add(new Tuple(alias, join)); + } + + return join; + } + + internal IAlias BindTypeCriteria() { + // ([ContentItemVersionRecord] >join> [ContentItemRecord]) >join> [ContentType] + return BindCriteriaByAlias(BindItemCriteria(), "ContentType", "ct"); + } + + internal IAlias BindItemCriteria() { + // [ContentItemVersionRecord] >join> [ContentItemRecord] + return BindCriteriaByAlias(BindItemVersionCriteria(), typeof(ContentItemRecord).Name, "ci"); + } + + internal IAlias BindItemVersionCriteria() { + return _from ?? (_from = new Join(typeof(ContentItemVersionRecord).FullName, "civ", "")); + } + + internal IAlias BindPartCriteria() where TRecord : ContentPartRecord { + return BindPartCriteria(typeof(TRecord)); + } + + internal IAlias BindPartCriteria(Type contentPartRecordType) { + if (!contentPartRecordType.IsSubclassOf(typeof(ContentPartRecord))) { + throw new ArgumentException("The type must inherit from ContentPartRecord", "contentPartRecordType"); + } + + if (contentPartRecordType.IsSubclassOf(typeof(ContentPartVersionRecord))) { + return BindCriteriaByPath(BindItemVersionCriteria(), contentPartRecordType.Name); + } + return BindCriteriaByPath(BindItemCriteria(), contentPartRecordType.Name); + } + + internal void Where(IAlias alias, Action predicate) { + _wheres.Add(new Tuple>(alias, predicate)); + } + + internal IAlias ApplyHqlVersionOptionsRestrictions(VersionOptions versionOptions) { + var alias = BindItemVersionCriteria(); + + if (versionOptions == null) { + Where(alias, x => x.Eq("Published", true)); + } + else if (versionOptions.IsPublished) { + Where(alias, x => x.Eq("Published", true)); + } + else if (versionOptions.IsLatest) { + Where(alias, x => x.Eq("Latest", true)); + } + else if (versionOptions.IsDraft) { + Where(alias, x => x.And(y => y.Eq("Latest", true), y => y.Eq("Published", false))); + } + else if (versionOptions.IsAllVersions) { + // no-op... all versions will be returned by default + } + else { + throw new ApplicationException("Invalid VersionOptions for content query"); + } + + return alias; + } + + public IHqlQuery Join(Action alias) { + var aliasFactory = new DefaultAliasFactory(this); + alias(aliasFactory); + return this; + } + + public IHqlQuery Where(Action alias, Action predicate) { + var aliasFactory = new DefaultAliasFactory(this); + alias(aliasFactory); + Where(aliasFactory.Current, predicate); + return this; + } + + public IHqlQuery OrderBy(Action alias, Action order) { + var aliasFactory = new DefaultAliasFactory(this); + alias(aliasFactory); + + _sortings.Add(new Tuple>(aliasFactory.Current, order)); + return this; + } + + public IHqlQuery ForType(params string[] contentTypeNames) { + if (contentTypeNames != null && contentTypeNames.Length != 0) { + Where(BindTypeCriteria(), x => x.InG("Name", contentTypeNames)); + } + return this; + } + + public IHqlQuery ForVersion(VersionOptions options) { + _versionOptions = options; + return this; + } + + public IHqlQuery ForPart() where T : IContent { + return new DefaultHqlQuery(this); + } + + public IEnumerable List() { + return Slice(0, 0); + } + + public IEnumerable Slice(int skip, int count) { + ApplyHqlVersionOptionsRestrictions(_versionOptions); + var hql = ToHql(false); + var query = _session.CreateQuery(hql); + + if (skip != 0) { + query.SetFirstResult(skip); + } + if (count != 0) { + query.SetMaxResults(count); + } + + return query.List() + .Select(x => ContentManager.Get(x.Id, VersionOptions.VersionRecord(x.Id))) + .ToReadOnlyCollection(); + } + + public int Count() { + ApplyHqlVersionOptionsRestrictions(_versionOptions); + return _session.CreateQuery(ToHql(true)).UniqueResult(); + } + + public string ToHql(bool count) { + var sb = new StringBuilder(); + + if (count) { + sb.Append("select count(civ) ").AppendLine(); + } + else { + sb.Append("select civ ").AppendLine(); + } + + sb.Append("from ").Append(_from.TableName).Append(" as ").Append(_from.Name).AppendLine(); + + foreach (var join in _joins) { + sb.Append(join.Item2.Type).Append(" ").Append(join.Item1.Name + "." + join.Item2.TableName).Append(" as ").Append(join.Item2.Name).AppendLine(); + } + + #region Where + + bool first = true; + foreach (var where in _wheres) { + if (!first) { + sb.Append("and "); + } + else { + sb.Append("where "); + first = false; + } + + var expressionFactory = new DefaultHqlExpressionFactory(); + where.Item2(expressionFactory); + sb.Append(expressionFactory.Criterion.ToHql(where.Item1)).AppendLine(); + } + + #endregion + + #region Order by + + bool firstSort = true; + foreach (var sort in _sortings) { + if (!firstSort) { + sb.Append(", "); + } + else { + sb.Append("order by "); + firstSort = false; + } + + var sortFactory = new DefaultHqlSortFactory(); + sort.Item2(sortFactory); + + sb.Append(sort.Item1.Name).Append(".").Append(sortFactory.PropertyName); + if (!sortFactory.Ascending) { + sb.Append(" desc"); + } + } + + #endregion + + return sb.ToString(); + } + + } + + public class DefaultHqlQuery : IHqlQuery where TPart : IContent { + private readonly DefaultHqlQuery _query; + + public DefaultHqlQuery(DefaultHqlQuery query) { + _query = query; + } + + public IContentManager ContentManager { + get { return _query.ContentManager; } + } + + public IHqlQuery ForType(params string[] contentTypes) { + _query.ForType(contentTypes); + return new DefaultHqlQuery(_query); + } + + public IHqlQuery ForVersion(VersionOptions options) { + _query.ForVersion(options); + return new DefaultHqlQuery(_query); + } + + IEnumerable IHqlQuery.List() { + return _query.List().AsPart(); + } + + IEnumerable IHqlQuery.Slice(int skip, int count) { + return _query.Slice(skip, count).AsPart(); + } + + int IHqlQuery.Count() { + return _query.Count(); + } + + public IHqlQuery Join(Action alias) { + _query.Join(alias); + return new DefaultHqlQuery(_query); + } + + public IHqlQuery Where(Action alias, Action predicate) { + _query.Where(alias, predicate); + return new DefaultHqlQuery(_query); + } + + public IHqlQuery OrderBy(Action alias, Action order) { + _query.OrderBy(alias, order); + return new DefaultHqlQuery(_query); + } + } + + public class Alias : IAlias { + public Alias(string name) { + if (String.IsNullOrEmpty(name)) { + throw new ArgumentException("Alias can't be empty"); + } + + Name = name; + } + + public DefaultHqlQuery Query { get; set; } + public string Name { get; set; } + } + + public interface IJoin : IAlias { + string TableName { get; set; } + string Type { get; set; } + } + + public class Sort { + + public Sort(IAlias alias, string propertyName, bool ascending) { + Alias = alias; + PropertyName = propertyName; + Ascending = ascending; + } + + public IAlias Alias { get; set; } + public string PropertyName { get; set; } + public bool Ascending { get; set; } + } + + public class Join : Alias, IJoin { + + public Join(string tableName, string alias) + : this(tableName, alias, "join") {} + + public Join(string tableName, string alias, string type) + : base(alias) { + if (String.IsNullOrEmpty(tableName)) { + throw new ArgumentException("Table Name can't be empty"); + } + + TableName = tableName; + Type = type; + } + + public string TableName { get; set; } + public string Type { get; set; } + } + + public class DefaultHqlSortFactory : IHqlSortFactory + { + public bool Ascending { get; set; } + public string PropertyName { get; set; } + + public void Asc(string propertyName) { + PropertyName = propertyName; + Ascending = true; + } + + public void Desc(string propertyName) { + PropertyName = propertyName; + Ascending = false; + } + } + + public class DefaultAliasFactory : IAliasFactory{ + private readonly DefaultHqlQuery _query; + public IAlias Current { get; private set; } + + public DefaultAliasFactory(DefaultHqlQuery query) { + _query = query; + Current = _query.BindItemCriteria(); + } + + public IAliasFactory ContentPartRecord() where TRecord : ContentPartRecord { + Current = _query.BindPartCriteria(); + return this; + } + + public IAliasFactory ContentPartRecord(Type contentPartRecord) { + if(!contentPartRecord.IsSubclassOf(typeof(ContentPartRecord))) { + throw new ArgumentException("Type must inherit from ContentPartRecord", "contentPartRecord"); + } + + Current = _query.BindPartCriteria(contentPartRecord); + return this; + } + + public IAliasFactory Property(string propertyName, string alias) { + Current = _query.BindCriteriaByAlias(Current, propertyName, alias); + return this; + } + + public IAliasFactory Named(string alias) { + Current = _query.BindNamedAlias(alias); + return this; + } + } +} diff --git a/src/Orchard/ContentManagement/IContentManager.cs b/src/Orchard/ContentManagement/IContentManager.cs index 7379a8482..7a039fc39 100644 --- a/src/Orchard/ContentManagement/IContentManager.cs +++ b/src/Orchard/ContentManagement/IContentManager.cs @@ -29,6 +29,7 @@ namespace Orchard.ContentManagement { void Flush(); IContentQuery Query(); + IHqlQuery HqlQuery(); ContentItemMetadata GetItemMetadata(IContent contentItem); IEnumerable GetEditorGroupInfos(IContent contentItem); diff --git a/src/Orchard/ContentManagement/IHqlCriterion.cs b/src/Orchard/ContentManagement/IHqlCriterion.cs new file mode 100644 index 000000000..93e495da8 --- /dev/null +++ b/src/Orchard/ContentManagement/IHqlCriterion.cs @@ -0,0 +1,248 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace Orchard.ContentManagement { + public interface IHqlCriterion { + + string ToHql(IAlias alias); + } + + public abstract class HqlCriterion : IHqlCriterion { + public abstract string ToHql(IAlias alias); + } + + public class BinaryExpression : HqlCriterion { + public BinaryExpression(string op, string propertyName, string value, Func processPropertyName = null) { + if(value == null) { + throw new ArgumentNullException("value"); + } + + Op = op; + PropertyName = propertyName; + Value = value; + ProcessPropertyName = processPropertyName; + } + + public string Op { get; set; } + public string PropertyName { get; set; } + public string Value { get; set; } + public Func ProcessPropertyName { get; set; } + + public override string ToHql(IAlias alias) { + var processed = String.Concat(alias.Name, ".", PropertyName); + if(ProcessPropertyName != null) { + processed = ProcessPropertyName(processed); + } + return String.Concat(processed, " ", Op, " ", Value); + } + } + + public class CompositeHqlCriterion : HqlCriterion { + public string Op { get; set; } + public IList Criterions { get; private set; } + + public CompositeHqlCriterion(string op) { + Op = op; + Criterions = new List(); + } + + public CompositeHqlCriterion Add(IHqlCriterion criterion) { + Criterions.Add(criterion); + return this; + } + + public override string ToHql(IAlias alias) { + var sb = new StringBuilder(); + var first = true; + foreach(var criterion in Criterions) { + if(!first) { + sb.Append(Op).Append(" "); + } + else { + first = false; + } + + sb.Append(criterion.ToHql(alias)).Append(" "); + } + + return sb.ToString(); + } + } + + public static class HqlRestrictions { + + public static IEnumerable FormatValue(IEnumerable values, bool quoteStrings = true) { + return from object value in values select FormatValue(value, quoteStrings); + } + + public static string FormatValue(object value, bool quoteStrings = true) { + var typeCode = Type.GetTypeCode(value.GetType()); + switch (typeCode) { + case TypeCode.String: + if (quoteStrings) { + return String.Concat("'", Convert.ToString(value, CultureInfo.InvariantCulture), "'"); + } + + return Convert.ToString(value, CultureInfo.InvariantCulture); + } + + return Convert.ToString(value, CultureInfo.InvariantCulture); + } + + public static IHqlCriterion AllEq(IDictionary propertyNameValues) { + var conjuction = new CompositeHqlCriterion("and"); + + foreach(string propertyName in propertyNameValues.Keys) { + conjuction.Add(Eq(propertyName, propertyNameValues[propertyName])); + } + + return conjuction; + } + + public static IHqlCriterion And(IHqlCriterion lhs, IHqlCriterion rhs) { + return new CompositeHqlCriterion("and") + .Add(lhs) + .Add(rhs); + } + + public static IHqlCriterion Between(string propertyName, object lo, object hi) { + return null; + } + + public static CompositeHqlCriterion Conjunction() { + return new CompositeHqlCriterion("and"); + } + + public static CompositeHqlCriterion Disjunction() { + return new CompositeHqlCriterion("or"); + } + + public static IHqlCriterion Eq(string propertyName, object value) { + return new BinaryExpression("=", propertyName, FormatValue(value)); + } + + public static IHqlCriterion EqProperty(string propertyName, string otherPropertyName) { + return null; + } + + public static IHqlCriterion Ge(string propertyName, object value) { + return new BinaryExpression(">=", propertyName, FormatValue(value)); + } + + public static IHqlCriterion GeProperty(string propertyName, string otherPropertyName) { + return null; + } + + public static IHqlCriterion Gt(string propertyName, object value) { + return new BinaryExpression(">", propertyName, FormatValue(value)); + } + + public static IHqlCriterion GtProperty(string propertyName, string otherPropertyName) { + return null; + } + + public static IHqlCriterion IdEq(object value) { + return null; + } + + public static IHqlCriterion In(string propertyName, ICollection values) { + if (values.Count == 0) { + throw new ArgumentException("Collection can't be empty", "values"); + } + return new BinaryExpression("in", propertyName, "(" + String.Join(", ", FormatValue(values)) + ")"); + } + + public static IHqlCriterion In(string propertyName, object[] values) { + if (values.Length == 0) { + throw new ArgumentException("Collection can't be empty", "values"); + } + return new BinaryExpression("in", propertyName, "(" + String.Join(", ", FormatValue(values)) + ")"); + } + + public static IHqlCriterion InG(string propertyName, ICollection values) { + if (values.Count == 0) { + throw new ArgumentException("Collection can't be empty", "values"); + } + return new BinaryExpression("in", propertyName, "(" + String.Join(", ", FormatValue(values)) + ")"); + } + + public static IHqlCriterion InsensitiveLike(string propertyName, string value, HqlMatchMode matchMode) { + var expression = Like(propertyName, value, matchMode); + expression.ProcessPropertyName = x => String.Concat("lower(", x, ")"); + + return expression; + } + + public static IHqlCriterion IsEmpty(string propertyName) { + return null; + } + + public static IHqlCriterion IsNotEmpty(string propertyName) { + return null; + } + + public static IHqlCriterion IsNotNull(string propertyName) { + return null; + } + + public static IHqlCriterion IsNull(string propertyName) { + return null; + } + + public static IHqlCriterion Le(string propertyName, object value) { + return new BinaryExpression("<=", propertyName, FormatValue(value)); + } + + public static IHqlCriterion LeProperty(string propertyName, string otherPropertyName) { + return null; + } + + public static BinaryExpression Like(string propertyName, string value, HqlMatchMode matchMode) { + switch (matchMode) { + case HqlMatchMode.Start: + value = "'" + value + "%'"; + break; + case HqlMatchMode.Exact: + break; + case HqlMatchMode.Anywhere: + value = "'%" + value + "%'"; + break; + case HqlMatchMode.End: + value = "'%" + value + "'"; + break; + } + + return new BinaryExpression("like", propertyName, FormatValue((object)value, false)); + } + + public static IHqlCriterion Lt(string propertyName, object value) { + return new BinaryExpression("<", propertyName, FormatValue(value)); + } + + public static IHqlCriterion LtProperty(string propertyName, string otherPropertyName) { + return null; + } + + public static IHqlCriterion NaturalId() { + return null; + } + + public static IHqlCriterion Not(IHqlCriterion expression) { + return null; + } + + public static IHqlCriterion NotEqProperty(string propertyName, string otherPropertyName) { + return null; + } + + public static IHqlCriterion Or(IHqlCriterion lhs, IHqlCriterion rhs) { + return new CompositeHqlCriterion("or") + .Add(lhs) + .Add(rhs); + } + } +} diff --git a/src/Orchard/ContentManagement/IHqlExpression.cs b/src/Orchard/ContentManagement/IHqlExpression.cs new file mode 100644 index 000000000..b726f6f2a --- /dev/null +++ b/src/Orchard/ContentManagement/IHqlExpression.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Orchard.ContentManagement { + + public interface IHqlExpressionFactory { + + void Eq(string propertyName, object value); + void Like(string propertyName, string value, HqlMatchMode matchMode); + void InsensitiveLike(string propertyName, string value, HqlMatchMode matchMode); + void Gt(string propertyName, object value); + void Lt(string propertyName, object value); + void Le(string propertyName, object value); + void Ge(string propertyName, object value); + void Between(string propertyName, object lo, object hi); + void In(string propertyName, object[] values); + void In(string propertyName, ICollection values); + void InG(string propertyName, ICollection values); + void IsNull(string propertyName); + void EqProperty(string propertyName, string otherPropertyName); + void NotEqProperty(string propertyName, string otherPropertyName); + void GtProperty(string propertyName, string otherPropertyName); + void GeProperty(string propertyName, string otherPropertyName); + void LtProperty(string propertyName, string otherPropertyName); + void LeProperty(string propertyName, string otherPropertyName); + void IsNotNull(string propertyName); + void IsNotEmpty(string propertyName); + void IsEmpty(string propertyName); + void And(Action lhs, Action rhs); + void Or(Action lhs, Action rhs); + void Not(Action expression); + void Conjunction(Action expression, params Action[] otherExpressions); + void Disjunction(Action expression, params Action[] otherExpressions); + void AllEq(IDictionary propertyNameValues); + void NaturalId(); + } + + public class DefaultHqlExpressionFactory : IHqlExpressionFactory { + public IHqlCriterion Criterion { get; private set; } + + public void Eq(string propertyName, object value) { + Criterion = HqlRestrictions.Eq(propertyName, value); + } + + public void Like(string propertyName, string value, HqlMatchMode matchMode) { + Criterion = HqlRestrictions.Like(propertyName, value, matchMode); + } + + public void InsensitiveLike(string propertyName, string value, HqlMatchMode matchMode) { + Criterion = HqlRestrictions.InsensitiveLike(propertyName, value, matchMode); + } + + public void Gt(string propertyName, object value) { + Criterion = HqlRestrictions.Gt(propertyName, value); + } + + public void Lt(string propertyName, object value) { + Criterion = HqlRestrictions.Lt(propertyName, value); + } + + public void Le(string propertyName, object value) { + Criterion = HqlRestrictions.Le(propertyName, value); + } + + public void Ge(string propertyName, object value) { + Criterion = HqlRestrictions.Ge(propertyName, value); + } + + public void Between(string propertyName, object lo, object hi) { + Criterion = HqlRestrictions.Between(propertyName, lo, hi); + } + + public void In(string propertyName, object[] values) { + Criterion = HqlRestrictions.In(propertyName, values); + } + + public void In(string propertyName, ICollection values) { + Criterion = HqlRestrictions.In(propertyName, values); + } + + public void InG(string propertyName, ICollection values) { + Criterion = HqlRestrictions.InG(propertyName, values); + } + + public void IsNull(string propertyName) { + Criterion = HqlRestrictions.IsNull(propertyName); + } + + public void EqProperty(string propertyName, string otherPropertyName) { + Criterion = HqlRestrictions.EqProperty(propertyName, otherPropertyName); + } + + public void NotEqProperty(string propertyName, string otherPropertyName) { + Criterion = HqlRestrictions.NotEqProperty(propertyName, otherPropertyName); + } + + public void GtProperty(string propertyName, string otherPropertyName) { + Criterion = HqlRestrictions.GtProperty(propertyName, otherPropertyName); + } + + public void GeProperty(string propertyName, string otherPropertyName) { + Criterion = HqlRestrictions.GeProperty(propertyName, otherPropertyName); + } + + public void LtProperty(string propertyName, string otherPropertyName) { + Criterion = HqlRestrictions.LtProperty(propertyName, otherPropertyName); + } + + public void LeProperty(string propertyName, string otherPropertyName) { + Criterion = HqlRestrictions.LeProperty(propertyName, otherPropertyName); + } + + public void IsNotNull(string propertyName) { + Criterion = HqlRestrictions.IsNotNull(propertyName); + } + + public void IsNotEmpty(string propertyName) { + Criterion = HqlRestrictions.IsNotEmpty(propertyName); + } + + public void IsEmpty(string propertyName) { + Criterion = HqlRestrictions.IsEmpty(propertyName); + } + + public void And(Action lhs, Action rhs) { + lhs(this); + var a = Criterion; + rhs(this); + var b = Criterion; + Criterion = HqlRestrictions.And(a, b); + } + + public void Or(Action lhs, Action rhs) { + lhs(this); + var a = Criterion; + rhs(this); + var b = Criterion; + Criterion = HqlRestrictions.Or(a, b); + } + + public void Not(Action expression) { + expression(this); + var a = Criterion; + Criterion = HqlRestrictions.Not(a); + } + + public void Conjunction(Action expression, params Action[] otherExpressions) { + var junction = HqlRestrictions.Conjunction(); + foreach (var exp in Enumerable.Empty>().Union(new[] { expression }).Union(otherExpressions)) { + exp(this); + junction.Add(Criterion); + } + + Criterion = junction; + } + + public void Disjunction(Action expression, params Action[] otherExpressions) { + var junction = HqlRestrictions.Disjunction(); + foreach (var exp in Enumerable.Empty>().Union(new[] { expression }).Union(otherExpressions)) { + exp(this); + junction.Add(Criterion); + } + + Criterion = junction; + } + + public void AllEq(IDictionary propertyNameValues) { + Criterion = HqlRestrictions.AllEq(propertyNameValues); + } + + public void NaturalId() { + Criterion = HqlRestrictions.NaturalId(); + } + } + + public enum HqlMatchMode { + Exact, + Start, + End, + Anywhere + } + +} diff --git a/src/Orchard/ContentManagement/IHqlQuery.cs b/src/Orchard/ContentManagement/IHqlQuery.cs new file mode 100644 index 000000000..6b3818343 --- /dev/null +++ b/src/Orchard/ContentManagement/IHqlQuery.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using Orchard.ContentManagement.Records; + +namespace Orchard.ContentManagement { + + public interface IHqlQuery { + IContentManager ContentManager { get; } + IHqlQuery ForType(params string[] contentTypes); + IHqlQuery ForVersion(VersionOptions options); + IHqlQuery ForPart() where T : IContent; + + IEnumerable List(); + IEnumerable Slice(int skip, int count); + int Count(); + + IHqlQuery Join(Action alias); + IHqlQuery Where(Action alias, Action predicate); + IHqlQuery OrderBy(Action alias, Action order); + } + + public interface IHqlQuery where TPart : IContent { + IHqlQuery ForType(params string[] contentTypes); + IHqlQuery ForVersion(VersionOptions options); + + IEnumerable List(); + IEnumerable Slice(int skip, int count); + int Count(); + + IHqlQuery Join(Action alias); + IHqlQuery Where(Action alias, Action predicate); + IHqlQuery OrderBy(Action alias, Action order); + } + + + public interface IAlias { + string Name { get; } + } + + public interface IAliasFactory { + IAliasFactory ContentPartRecord() where TRecord : ContentPartRecord; + IAliasFactory ContentPartRecord(Type contentPartRecord); + IAliasFactory Property(string propertyName, string alias); + IAliasFactory Named(string alias); // returns an existing alias + } + + public interface IHqlSortFactory { + void Asc(string propertyName); + void Desc(string propertyName); + } + + /* + * + * query // IHqlQuery + * .Join().As("route") // IAlias(query), IAlias(query), IAlias(query) alias name is implicit, there can't be any conflict + * x => x.Where(route => route. + * ) + * + * query.Join< + * + * query: IAlias + * + * + * IHqlQuery ONLY, because we can have by default, and the Records can't be used in expression though, thus having them + * in the generic type is useless. _manager.HqlQuery() will create a new query then apply ForPart + * + * .Join is only valid for ContentPartRecord because they will resolve the fake civ.{TRecord} property + * .Where is only valid for ContentPartRecord because they will resolve the fake civ.{TRecord} property + * .Where( on => on.Named("foo"), x => x.Eq("Id", 1)) + * + * + * .Join( on => on.ContentPartRecord().Property("IntegerFieldIndexRecords", alias: "integerFields") + * .Where( on => on.Named("foo"), x => x.Eq("Id", 1)) + * .Where( on => on.ContentPartRecord(), x => x.Eq("Id", 1)) + * + * + * Join(Action alias) + * Where(Action alias, Action predicate) + * + * Thus we can create aliases directly from the Where() + * + * IAlias { + * ContentPartRecord() where TRecord : ContentPartRecord + * Property(string propertyName, string alias) + * Named(string alias) // returns an existing alias + * + * } + */ + + +} + diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 0d9e5ff4f..a8ec89816 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -169,7 +169,11 @@ + + + +