Skip to content

Commit

Permalink
finish HistoryTable in postgres
Browse files Browse the repository at this point in the history
  • Loading branch information
olmobrutall committed Jan 22, 2020
1 parent e1c686a commit e829c4c
Show file tree
Hide file tree
Showing 12 changed files with 279 additions and 55 deletions.
17 changes: 14 additions & 3 deletions Signum.Engine/Connection/FieldReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -558,9 +558,8 @@ public T[] GetArray<T>(int ordinal)
return (T[])this.reader[ordinal];
}

static MethodInfo miGetRange = ReflectionTools.GetMethodInfo((FieldReader r) => r.GetRange<int>(0)).GetGenericMethodDefinition();

public NpgsqlTypes.NpgsqlRange<T>? GetRange<T>(int ordinal)
static MethodInfo miNullableGetRange = ReflectionTools.GetMethodInfo((FieldReader r) => r.GetNullableRange<int>(0)).GetGenericMethodDefinition();
public NpgsqlTypes.NpgsqlRange<T>? GetNullableRange<T>(int ordinal)
{
LastOrdinal = ordinal;
if (reader.IsDBNull(ordinal))
Expand All @@ -571,6 +570,13 @@ public T[] GetArray<T>(int ordinal)
return (NpgsqlTypes.NpgsqlRange<T>)this.reader[ordinal];
}

static MethodInfo miGetRange = ReflectionTools.GetMethodInfo((FieldReader r) => r.GetRange<int>(0)).GetGenericMethodDefinition();
public NpgsqlTypes.NpgsqlRange<T> GetRange<T>(int ordinal)
{
LastOrdinal = ordinal;
return (NpgsqlTypes.NpgsqlRange<T>)this.reader[ordinal];
}

static Dictionary<Type, MethodInfo> methods =
typeof(FieldReader).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Where(m => m.Name != "GetExpression" && m.Name != "IsNull")
Expand Down Expand Up @@ -601,6 +607,11 @@ public static Expression GetExpression(Expression reader, int ordinal, Type type
return Expression.Call(reader, miGetRange.MakeGenericMethod(type.GetGenericArguments()[0]!), Expression.Constant(ordinal));
}

if (type.IsNullable() && type.UnNullify().IsInstantiationOf(typeof(NpgsqlTypes.NpgsqlRange<>)))
{
return Expression.Call(reader, miGetRange.MakeGenericMethod(type.UnNullify().GetGenericArguments()[0]!), Expression.Constant(ordinal));
}

throw new InvalidOperationException("Type {0} not supported".FormatWith(type));
}

Expand Down
39 changes: 32 additions & 7 deletions Signum.Engine/Linq/DbExpressions.Sql.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Signum.Entities;
using Signum.Entities.DynamicQuery;
using Signum.Utilities;
using Signum.Utilities.DataStructures;
using Signum.Utilities.ExpressionTrees;
using Signum.Utilities.Reflection;
using System;
Expand Down Expand Up @@ -67,6 +68,7 @@ internal enum DbExpressionType
PrimaryKey,
PrimaryKeyString,
ToDayOfWeek,
Interval,
}


Expand Down Expand Up @@ -210,7 +212,7 @@ internal TableExpression(Alias alias, ITable table, SystemTime? systemTime, stri

public override string ToString()
{
var st = SystemTime != null && SystemTime is SystemTime.HistoryTable ? "FOR SYSTEM_TIME " + SystemTime.ToString() : null;
var st = SystemTime != null && !(SystemTime is SystemTime.HistoryTable) ? " FOR SYSTEM_TIME " + SystemTime.ToString() : null;

return $"{Name}{st} as {Alias}";
}
Expand Down Expand Up @@ -574,8 +576,19 @@ internal SetOperatorExpression(SetOperator @operator, SourceWithAliasExpression
: base(DbExpressionType.SetOperator, alias)
{
this.Operator = @operator;
this.Left = left ?? throw new ArgumentNullException(nameof(left));
this.Right = right ?? throw new ArgumentNullException(nameof(right));
this.Left = Validate(left, nameof(left));
this.Right = Validate(right, nameof(right));
}

static SourceWithAliasExpression Validate(SourceWithAliasExpression exp, string name)
{
if (exp == null)
throw new ArgumentNullException(name);

if (exp is TableExpression || exp is SqlTableValuedFunctionExpression)
throw new ArgumentException($"{name} should not be a {exp.GetType().Name}");

return exp;
}

public override string ToString()
Expand Down Expand Up @@ -649,6 +662,16 @@ internal enum PostgresFunction
repeat,
date_trunc,
age,
tstzrange,
}

public static class PostgressOperator
{
public static string Overlap = "&&";
public static string Contains = "@>";

public static string[] All = new[] { Overlap, Contains };

}

internal enum SqlEnums
Expand Down Expand Up @@ -957,29 +980,31 @@ internal class IntervalExpression : DbExpression
public readonly Expression? Min;
public readonly Expression? Max;
public readonly Expression? PostgresRange;
public readonly bool AsUtc;

public IntervalExpression(Type type, Expression? min, Expression? max, Expression? postgresRange)
public IntervalExpression(Type type, Expression? min, Expression? max, Expression? postgresRange, bool asUtc)
:base(DbExpressionType.Interval, type)

{
this.Min = min ?? (postgresRange == null ? throw new ArgumentException(nameof(min)) : (Expression?)null);
this.Max = max ?? (postgresRange == null ? throw new ArgumentException(nameof(max)) : (Expression?)null);
this.PostgresRange = postgresRange ?? ((min == null || max == null) ? throw new ArgumentException(nameof(min)) : (Expression?)null);
this.AsUtc = asUtc;
}

public override string ToString()
{
var type = this.Type.GetGenericArguments()[0].TypeName();

if (PostgresRange != null)
return $"new Interval<{type}({this.PostgresRange})";
return $"new Interval<{type}>({this.PostgresRange})";
else
return $"new Interval<{type}({this.Min}, {this.Max})";
return $"new Interval<{type}>({this.Min}, {this.Max})";
}

protected override Expression Accept(DbExpressionVisitor visitor)
{
return visitor.VisitLike(this);
return visitor.VisitInterval(this);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@ protected internal virtual Expression VisitInterval(IntervalExpression interval)
Expression max = Visit(interval.Max);
Expression postgresRange = Visit(interval.PostgresRange);
if (min != interval.Min || max != interval.Max || postgresRange != interval.PostgresRange)
return new IntervalExpression(interval.Type, min, max, postgresRange);
return new IntervalExpression(interval.Type, min, max, postgresRange, interval.AsUtc);
return interval;
}
}
Expand Down
165 changes: 165 additions & 0 deletions Signum.Engine/Linq/ExpressionVisitor/DuplicateHistory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using Signum.Engine.Maps;
using Signum.Entities;
using Signum.Utilities;

namespace Signum.Engine.Linq
{
/// <summary>
/// Rewrite aggregate expressions, moving them into same select expression that has the group-by clause
/// </summary>
internal class DuplicateHistory : DbExpressionVisitor
{
private AliasGenerator aliasGenerator;

public DuplicateHistory(AliasGenerator generator)
{
this.aliasGenerator = generator;
}

public static Expression Rewrite(Expression expr, AliasGenerator generator)
{
if (!Schema.Current.Settings.IsPostgres)
return expr;

return new DuplicateHistory(generator).Visit(expr);
}

public Dictionary<Alias, Dictionary<ColumnExpression, ColumnExpression?>> columnReplacements = new Dictionary<Alias, Dictionary<ColumnExpression, ColumnExpression?>>();

protected internal override Expression VisitTable(TableExpression table)
{
if (table.SystemTime != null)
{
if (table.SystemTime is SystemTime.HistoryTable)
return table;

var requests = columnReplacements.TryGetC(table.Alias);

SelectExpression CreateSelect(string tableNameForAlias, SystemTime? systemTime)
{
var tableExp = new TableExpression(aliasGenerator.NextTableAlias(tableNameForAlias), table.Table, systemTime, null);

ColumnExpression GetTablePeriod() => new ColumnExpression(typeof(NpgsqlTypes.NpgsqlRange<DateTime>), tableExp.Alias, table.Table.SystemVersioned!.PostgreeSysPeriodColumnName);
SqlFunctionExpression tstzrange(DateTime start, DateTime end) => new SqlFunctionExpression(typeof(NpgsqlTypes.NpgsqlRange<DateTime>), null, PostgresFunction.tstzrange.ToString(),
new[] { Expression.Constant(new DateTimeOffset(start)), Expression.Constant(new DateTimeOffset(end)) });

var where = table.SystemTime is SystemTime.All ? null :
table.SystemTime is SystemTime.AsOf asOf ? new SqlFunctionExpression(typeof(bool), null, PostgressOperator.Contains, new Expression[] { GetTablePeriod(), Expression.Constant(new DateTimeOffset(asOf.DateTime)) }) :
table.SystemTime is SystemTime.Between b ? new SqlFunctionExpression(typeof(bool), null, PostgressOperator.Overlap, new Expression[] { tstzrange(b.StartDateTime, b.EndtDateTime), GetTablePeriod() }) :
table.SystemTime is SystemTime.ContainedIn ci ? new SqlFunctionExpression(typeof(bool), null, PostgressOperator.Contains, new Expression[] { tstzrange(ci.StartDateTime, ci.EndtDateTime), GetTablePeriod() }) :
throw new UnexpectedValueException(table.SystemTime);

var newSelect = new SelectExpression(aliasGenerator.NextTableAlias(tableNameForAlias), false, null,
columns: requests?.Select(kvp => new ColumnDeclaration(kvp.Key.Name!, new ColumnExpression(kvp.Key.Type, tableExp.Alias, kvp.Key.Name))),
tableExp, where, null, null, 0);

return newSelect;
}

var current = CreateSelect(table.Table.Name.Name, null);
var history = CreateSelect(table.Table.SystemVersioned!.TableName.Name, new SystemTime.HistoryTable());

var unionAlias = aliasGenerator.NextTableAlias(table.Table.Name.Name);
if (requests != null)
{
foreach (var col in requests.Keys.ToList())
{
requests[col] = new ColumnExpression(col.Type, unionAlias, col.Name);
}
}

return new SetOperatorExpression(SetOperator.UnionAll, current, history, unionAlias);
}

return base.VisitTable(table);
}



protected internal override Expression VisitJoin(JoinExpression join)
{
this.Visit(join.Condition);
if (join.JoinType == JoinType.CrossApply || join.JoinType == JoinType.OuterApply)
this.VisitSource(join.Right);

SourceExpression left = this.VisitSource(join.Left);
SourceExpression right = this.VisitSource(join.Right);
Expression condition = this.Visit(join.Condition);
if (left != join.Left || right != join.Right || condition != join.Condition)
{
return new JoinExpression(join.JoinType, left, right, condition);
}
return join;
}

protected internal override Expression VisitSelect(SelectExpression select)
{
//if (select.SelectRoles == SelectRoles.Where && select.From is TableExpression table && table.SystemTime != null && !(table.SystemTime is SystemTime.HistoryTable))
//{
// var current = (SelectExpression)AliasReplacer.Replace(select, this.aliasGenerator);
// var history = (SelectExpression)AliasReplacer.Replace(select, this.aliasGenerator);

// var newAlias = aliasGenerator.NextSelectAlias();

// if (columnReplacements.ContainsKey(select.Alias))
// throw new InvalidOperationException("Requests to trivial select (only where) not expected");

// var requests = columnReplacements.TryGetC(table.Alias).EmptyIfNull().Select(ce => new ColumnDeclaration(ce.Key, ));

// return new SetOperatorExpression(SetOperator.UnionAll, current, history, table.Alias);
//}
//else
//{
this.Visit(select.Top);
this.Visit(select.Where);
Visit(select.Columns, VisitColumnDeclaration);
Visit(select.OrderBy, VisitOrderBy);
Visit(select.GroupBy, Visit);
SourceExpression from = this.VisitSource(select.From!);
Expression top = this.Visit(select.Top);
Expression where = this.Visit(select.Where);
ReadOnlyCollection<ColumnDeclaration> columns = Visit(select.Columns, VisitColumnDeclaration);
ReadOnlyCollection<OrderExpression> orderBy = Visit(select.OrderBy, VisitOrderBy);
ReadOnlyCollection<Expression> groupBy = Visit(select.GroupBy, Visit);

if (top != select.Top || from != select.From || where != select.Where || columns != select.Columns || orderBy != select.OrderBy || groupBy != select.GroupBy)
return new SelectExpression(select.Alias, select.IsDistinct, top, columns, from, where, orderBy, groupBy, select.SelectOptions);

return select;
//}
}

protected internal override Expression VisitProjection(ProjectionExpression proj)
{
this.Visit(proj.Projector);
SelectExpression source = (SelectExpression)this.Visit(proj.Select);
Expression projector = this.Visit(proj.Projector);

if (source != proj.Select || projector != proj.Projector)
return new ProjectionExpression(source, projector, proj.UniqueFunction, proj.Type);

return proj;
}

protected internal override Expression VisitColumn(ColumnExpression column)
{
if (column.Name == null)
return column;

if (this.columnReplacements.TryGetValue(column.Alias, out var dic) && dic.TryGetValue(column, out var repColumn))
return repColumn ?? column;

this.columnReplacements.GetOrCreate(column.Alias).Add(column, null);

return column;
}



}
}
35 changes: 19 additions & 16 deletions Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -575,30 +575,41 @@ protected internal override Expression VisitAggregate(AggregateExpression aggreg

protected internal override Expression VisitSqlFunction(SqlFunctionExpression sqlFunction)
{
if (sqlFunction.Object != null)
{
Visit(sqlFunction.Object);
sb.Append(".");
}
sb.Append(sqlFunction.SqlFunction);
sb.Append("(");
if (isPostgres && sqlFunction.SqlFunction == PostgresFunction.EXTRACT.ToString())
{
sb.Append(sqlFunction.SqlFunction);
sb.Append("(");
this.Visit(sqlFunction.Arguments[0]);
sb.Append(" from ");
this.Visit(sqlFunction.Arguments[1]);
sb.Append(")");
}
else if(isPostgres && PostgressOperator.All.Contains(sqlFunction.SqlFunction))
{
sb.Append("(");
this.Visit(sqlFunction.Arguments[0]);
sb.Append(" " + sqlFunction.SqlFunction + " ");
this.Visit(sqlFunction.Arguments[1]);
sb.Append(")");
}
else
{
if (sqlFunction.Object != null)
{
Visit(sqlFunction.Object);
sb.Append(".");
}
sb.Append(sqlFunction.SqlFunction);
sb.Append("(");
for (int i = 0, n = sqlFunction.Arguments.Count; i < n; i++)
{
Expression exp = sqlFunction.Arguments[i];
if (i > 0)
sb.Append(", ");
this.Visit(exp);
}
sb.Append(")");
}
sb.Append(")");

return sqlFunction;
}
Expand Down Expand Up @@ -656,14 +667,6 @@ private void WriteSystemTime(SystemTime st)
sb.Append("AS OF ");
this.VisitSystemTimeConstant(asOf.DateTime);
}
else if (st is SystemTime.FromTo fromTo)
{
sb.Append("FROM ");
this.VisitSystemTimeConstant(fromTo.StartDateTime);

sb.Append(" TO ");
this.VisitSystemTimeConstant(fromTo.EndtDateTime);
}
else if (st is SystemTime.Between between)
{
sb.Append("BETWEEN ");
Expand Down
Loading

0 comments on commit e829c4c

Please sign in to comment.