From 46d91d249f085f74072ac395ac8f75b1d1a3d3cf Mon Sep 17 00:00:00 2001 From: Olmo del Corral Date: Thu, 12 Dec 2019 00:05:50 +0100 Subject: [PATCH 01/13] initial Postgres support (AbstractDbType, Create Schema and initial saving) --- Signum.Engine/Administrator.cs | 69 +- Signum.Engine/Basics/TypeLogic.cs | 51 +- Signum.Engine/BulkInserter.cs | 4 +- .../CodeGeneration/EntityCodeGenerator.cs | 22 +- .../CodeGeneration/LogicCodeGenerator.cs | 2 +- Signum.Engine/Connection/Connector.cs | 26 +- Signum.Engine/Connection/Executor.cs | 11 - .../Connection/PostgreSqlConnector.cs | 527 ++++++++++++++ Signum.Engine/Connection/SqlConnector.cs | 92 +-- Signum.Engine/Database.cs | 5 +- Signum.Engine/Engine/SchemaGenerator.cs | 58 +- Signum.Engine/Engine/SchemaSynchronizer.cs | 271 ++++---- .../Engine/Scripts/versioning_function.sql | 186 +++++ .../Scripts/versioning_function_nochecks.sql | 83 +++ Signum.Engine/Engine/SqlBuilder.cs | 316 ++++----- Signum.Engine/Engine/SqlPreCommand.cs | 3 +- Signum.Engine/Engine/SqlUtils.cs | 656 +++++++++++++++++- Signum.Engine/Engine/Synchronizer.cs | 4 +- Signum.Engine/Linq/AliasGenerator.cs | 19 +- Signum.Engine/Linq/DbExpressions.Sql.cs | 8 +- .../ExpressionVisitor/ConditionsRewriter.cs | 2 +- .../ExpressionVisitor/DbExpressionComparer.cs | 2 +- .../DbExpressionNominator.cs | 6 +- .../ExpressionVisitor/DbExpressionVisitor.cs | 2 +- .../Linq/ExpressionVisitor/QueryFormatter.cs | 24 +- Signum.Engine/Schema/ObjectName.cs | 101 ++- Signum.Engine/Schema/Schema.Basics.cs | 285 +++++++- Signum.Engine/Schema/Schema.Delete.cs | 8 +- Signum.Engine/Schema/Schema.Expressions.cs | 8 +- Signum.Engine/Schema/Schema.Save.cs | 212 +++--- Signum.Engine/Schema/Schema.cs | 10 +- Signum.Engine/Schema/SchemaAssets.cs | 15 +- .../Schema/SchemaBuilder/SchemaBuilder.cs | 69 +- ...maBuilderSettings.cs => SchemaSettings.cs} | 173 +++-- Signum.Engine/Schema/UniqueTableIndex.cs | 51 +- Signum.Engine/Signum.Engine.csproj | 10 +- Signum.Entities/Basics/Exception.cs | 36 +- Signum.Entities/FieldAttributes.cs | 33 +- Signum.Entities/MList.cs | 2 +- Signum.Entities/Signum.Entities.csproj | 3 +- Signum.React/Signum.React.csproj | 9 +- Signum.Test/Environment/Entities.cs | 29 +- Signum.Test/Environment/MusicStarter.cs | 10 +- Signum.Test/ObjectNameTest.cs | 14 +- Signum.Test/appsettings.json | 3 +- Signum.Utilities/Date.cs | 318 +++++++++ .../Extensions/DateTimeExtensions.cs | 5 + 47 files changed, 3013 insertions(+), 840 deletions(-) create mode 100644 Signum.Engine/Connection/PostgreSqlConnector.cs create mode 100644 Signum.Engine/Engine/Scripts/versioning_function.sql create mode 100644 Signum.Engine/Engine/Scripts/versioning_function_nochecks.sql rename Signum.Engine/Schema/SchemaBuilder/{SchemaBuilderSettings.cs => SchemaSettings.cs} (70%) create mode 100644 Signum.Utilities/Date.cs diff --git a/Signum.Engine/Administrator.cs b/Signum.Engine/Administrator.cs index d4ee775c62..c07b4b0a9a 100644 --- a/Signum.Engine/Administrator.cs +++ b/Signum.Engine/Administrator.cs @@ -31,7 +31,7 @@ public static void TotalGeneration() public static string GenerateViewCodes(params string[] tableNames) => tableNames.ToString(tn => GenerateViewCode(tn), "\r\n\r\n"); - public static string GenerateViewCode(string tableName) => GenerateViewCode(ObjectName.Parse(tableName)); + public static string GenerateViewCode(string tableName) => GenerateViewCode(ObjectName.Parse(tableName, Schema.Current.Settings.IsPostgres)); public static string GenerateViewCode(ObjectName tableName) { @@ -42,7 +42,7 @@ from c in t.Columns() select new DiffColumn { Name = c.name, - SqlDbType = SchemaSynchronizer.ToSqlDbType(c.Type()!.name), + DbType = new AbstractDbType(SchemaSynchronizer.ToSqlDbType(c.Type()!.name)), UserTypeName = null, PrimaryKey = t.Indices().Any(i => i.is_primary_key && i.IndexColumns().Any(ic => ic.column_id == c.column_id)), Nullable = c.is_nullable, @@ -102,7 +102,7 @@ public static void CreateTemporaryTable() if (!view.Name.IsTemporal) throw new InvalidOperationException($"Temporary tables should start with # (i.e. #myTable). Consider using {nameof(TableNameAttribute)}"); - SqlBuilder.CreateTableSql(view).ExecuteNonQuery(); + Connector.Current.SqlBuilder.CreateTableSql(view).ExecuteLeaves(); } public static IDisposable TemporaryTable() where T : IView @@ -123,7 +123,7 @@ public static void DropTemporaryTable() if (!view.Name.IsTemporal) throw new InvalidOperationException($"Temporary tables should start with # (i.e. #myTable). Consider using {nameof(TableNameAttribute)}"); - SqlBuilder.DropTable(view.Name).ExecuteNonQuery(); + Connector.Current.SqlBuilder.DropTable(view.Name).ExecuteNonQuery(); } public static void CreateTemporaryIndex(Expression> fields, bool unique = false) @@ -137,7 +137,7 @@ public static void CreateTemporaryIndex(Expression> fields, b new UniqueTableIndex(view, columns) : new TableIndex(view, columns); - SqlBuilder.CreateIndex(index, checkUnique: null).ExecuteLeaves(); + Connector.Current.SqlBuilder.CreateIndex(index, checkUnique: null).ExecuteLeaves(); } internal static readonly ThreadVariable?> registeredViewNameReplacer = Statics.ThreadVariable?>("overrideDatabase"); @@ -208,7 +208,7 @@ public static List TryRetrieveAll(Type type, Replacements replacements) { Table table = Schema.Current.Table(type); - using (Synchronizer.RenameTable(table, replacements)) + using (Synchronizer.UseOldTableName(table, replacements)) using (ExecutionMode.DisableCache()) { if (ExistsTable(table)) @@ -238,24 +238,24 @@ public static IDisposable DisableIdentity(Table table) table.IdentityBehaviour = false; if (table.PrimaryKey.Default == null) - SqlBuilder.SetIdentityInsert(table.Name, true).ExecuteNonQuery(); + Connector.Current.SqlBuilder.SetIdentityInsert(table.Name, true).ExecuteNonQuery(); return new Disposable(() => { table.IdentityBehaviour = true; if (table.PrimaryKey.Default == null) - SqlBuilder.SetIdentityInsert(table.Name, false).ExecuteNonQuery(); + Connector.Current.SqlBuilder.SetIdentityInsert(table.Name, false).ExecuteNonQuery(); }); } public static IDisposable DisableIdentity(ObjectName tableName) { - SqlBuilder.SetIdentityInsert(tableName, true).ExecuteNonQuery(); + Connector.Current.SqlBuilder.SetIdentityInsert(tableName, true).ExecuteNonQuery(); return new Disposable(() => { - SqlBuilder.SetIdentityInsert(tableName, false).ExecuteNonQuery(); + Connector.Current.SqlBuilder.SetIdentityInsert(tableName, false).ExecuteNonQuery(); }); } @@ -409,6 +409,7 @@ static IDisposable PrepareForBathLoadScope(this Table table, bool disableForeign public static IDisposable PrepareTableForBatchLoadScope(ITable table, bool disableForeignKeys, bool disableMultipleIndexes, bool disableUniqueIndexes) { + var sqlBuilder = Connector.Current.SqlBuilder; SafeConsole.WriteColor(ConsoleColor.Magenta, table.Name + ":"); Action onDispose = () => SafeConsole.WriteColor(ConsoleColor.Magenta, table.Name + ":"); @@ -431,13 +432,13 @@ public static IDisposable PrepareTableForBatchLoadScope(ITable table, bool disab if (multiIndexes.Any()) { SafeConsole.WriteColor(ConsoleColor.DarkMagenta, " DISABLE Multiple Indexes"); - multiIndexes.Select(i => SqlBuilder.DisableIndex(table.Name, i)).Combine(Spacing.Simple)!.ExecuteLeaves(); + multiIndexes.Select(i => sqlBuilder.DisableIndex(table.Name, i)).Combine(Spacing.Simple)!.ExecuteLeaves(); Executor.ExecuteNonQuery(multiIndexes.ToString(i => "ALTER INDEX [{0}] ON {1} DISABLE".FormatWith(i, table.Name), "\r\n")); onDispose += () => { SafeConsole.WriteColor(ConsoleColor.DarkMagenta, " REBUILD Multiple Indexes"); - multiIndexes.Select(i => SqlBuilder.RebuildIndex(table.Name, i)).Combine(Spacing.Simple)!.ExecuteLeaves(); + multiIndexes.Select(i => sqlBuilder.RebuildIndex(table.Name, i)).Combine(Spacing.Simple)!.ExecuteLeaves(); }; } } @@ -449,11 +450,11 @@ public static IDisposable PrepareTableForBatchLoadScope(ITable table, bool disab if (uniqueIndexes.Any()) { SafeConsole.WriteColor(ConsoleColor.DarkMagenta, " DISABLE Unique Indexes"); - uniqueIndexes.Select(i => SqlBuilder.DisableIndex(table.Name, i)).Combine(Spacing.Simple)!.ExecuteLeaves(); + uniqueIndexes.Select(i => sqlBuilder.DisableIndex(table.Name, i)).Combine(Spacing.Simple)!.ExecuteLeaves(); onDispose += () => { SafeConsole.WriteColor(ConsoleColor.DarkMagenta, " REBUILD Unique Indexes"); - uniqueIndexes.Select(i => SqlBuilder.RebuildIndex(table.Name, i)).Combine(Spacing.Simple)!.ExecuteLeaves(); + uniqueIndexes.Select(i => sqlBuilder.RebuildIndex(table.Name, i)).Combine(Spacing.Simple)!.ExecuteLeaves(); }; } } @@ -485,19 +486,24 @@ public static void TruncateTable(Type type) public static void TruncateTableSystemVersioning(ITable table) { + var sqlBuilder = Connector.Current.SqlBuilder; + if(table.SystemVersioned == null) - SqlBuilder.TruncateTable(table.Name).ExecuteLeaves(); + sqlBuilder.TruncateTable(table.Name).ExecuteLeaves(); else { - SqlBuilder.AlterTableDisableSystemVersioning(table.Name).ExecuteLeaves(); - SqlBuilder.TruncateTable(table.Name).ExecuteLeaves(); - SqlBuilder.TruncateTable(table.SystemVersioned.TableName).ExecuteLeaves(); - SqlBuilder.AlterTableEnableSystemVersioning(table).ExecuteLeaves(); + sqlBuilder.AlterTableDisableSystemVersioning(table.Name).ExecuteLeaves(); + sqlBuilder.TruncateTable(table.Name).ExecuteLeaves(); + sqlBuilder.TruncateTable(table.SystemVersioned.TableName).ExecuteLeaves(); + sqlBuilder.AlterTableEnableSystemVersioning(table).ExecuteLeaves(); } } public static IDisposable DropAndCreateIncommingForeignKeys(Table table) { + var sqlBuilder = Connector.Current.SqlBuilder; + var isPostgres = Schema.Current.Settings.IsPostgres; + var foreignKeys = Administrator.OverrideDatabaseInSysViews(table.Name.Schema.Database).Using(_ => (from targetTable in Database.View() where targetTable.name == table.Name.Name && targetTable.Schema().name == table.Name.Schema.Name @@ -506,26 +512,27 @@ from ifk in targetTable.IncommingForeignKeys() select new { Name = ifk.name, - ParentTable = new ObjectName(new SchemaName(table.Name.Schema.Database, parentTable.Schema().name), parentTable.name), + ParentTable = new ObjectName(new SchemaName(table.Name.Schema.Database, parentTable.Schema().name, isPostgres), parentTable.name, isPostgres), ParentColumn = parentTable.Columns().SingleEx(c => c.column_id == ifk.ForeignKeyColumns().SingleEx().parent_column_id).name, }).ToList()); - foreignKeys.ForEach(fk => SqlBuilder.AlterTableDropConstraint(fk.ParentTable!, fk.Name! /*CSBUG*/).ExecuteLeaves()); + foreignKeys.ForEach(fk => sqlBuilder.AlterTableDropConstraint(fk.ParentTable!, fk.Name! /*CSBUG*/).ExecuteLeaves()); return new Disposable(() => { - foreignKeys.ToList().ForEach(fk => SqlBuilder.AlterTableAddConstraintForeignKey(fk.ParentTable!, fk.ParentColumn!, table.Name, table.PrimaryKey.Name)!.ExecuteLeaves()); + foreignKeys.ToList().ForEach(fk => sqlBuilder.AlterTableAddConstraintForeignKey(fk.ParentTable!, fk.ParentColumn!, table.Name, table.PrimaryKey.Name)!.ExecuteLeaves()); }); } public static IDisposable DisableUniqueIndex(UniqueTableIndex index) { + var sqlBuilder = Connector.Current.SqlBuilder; SafeConsole.WriteLineColor(ConsoleColor.DarkMagenta, " DISABLE Unique Index " + index.IndexName); - SqlBuilder.DisableIndex(index.Table.Name, index.IndexName).ExecuteLeaves(); + sqlBuilder.DisableIndex(index.Table.Name, index.IndexName).ExecuteLeaves(); return new Disposable(() => { SafeConsole.WriteLineColor(ConsoleColor.DarkMagenta, " REBUILD Unique Index " + index.IndexName); - SqlBuilder.RebuildIndex(index.Table.Name, index.IndexName).ExecuteLeaves(); + sqlBuilder.RebuildIndex(index.Table.Name, index.IndexName).ExecuteLeaves(); }); } @@ -545,11 +552,12 @@ from i in t.Indices() public static void DropUniqueIndexes() where T : Entity { + var sqlBuilder = Connector.Current.SqlBuilder; var table = Schema.Current.Table(); var indexesNames = Administrator.GetIndixesNames(table, unique: true); if (indexesNames.HasItems()) - indexesNames.Select(n => SqlBuilder.DropIndex(table.Name, n)).Combine(Spacing.Simple)!.ExecuteLeaves(); + indexesNames.Select(n => sqlBuilder.DropIndex(table.Name, n)).Combine(Spacing.Simple)!.ExecuteLeaves(); } @@ -608,12 +616,13 @@ static List MoveAllForeignKeysPrivate(Lite fromEntity, if (shouldMove != null) columns = columns.Where(p => shouldMove!(p.Table, p.Column)).ToList(); + var isPostgres = Schema.Current.Settings.IsPostgres; var pb = Connector.Current.ParameterBuilder; - return columns.Select(ct => new ColumnTableScript(ct, new SqlPreCommandSimple("UPDATE {0}\r\nSET {1} = @toEntity\r\nWHERE {1} = @fromEntity".FormatWith(ct.Table.Name, ct.Column.Name.SqlEscape()), new List - { - pb.CreateReferenceParameter("@fromEntity", fromEntity.Id, ct.Column), - pb.CreateReferenceParameter("@toEntity", toEntity.Id, ct.Column), - }))).ToList(); + return columns.Select(ct => new ColumnTableScript(ct, new SqlPreCommandSimple("UPDATE {0}\r\nSET {1} = @toEntity\r\nWHERE {1} = @fromEntity".FormatWith(ct.Table.Name, ct.Column.Name.SqlEscape(isPostgres)), new List + { + pb.CreateReferenceParameter("@fromEntity", fromEntity.Id, ct.Column), + pb.CreateReferenceParameter("@toEntity", toEntity.Id, ct.Column), + }))).ToList(); } class ColumnTable diff --git a/Signum.Engine/Basics/TypeLogic.cs b/Signum.Engine/Basics/TypeLogic.cs index 20d0101424..21b4ea56b9 100644 --- a/Signum.Engine/Basics/TypeLogic.cs +++ b/Signum.Engine/Basics/TypeLogic.cs @@ -44,11 +44,22 @@ public static void Start(SchemaBuilder sb) { if (sb.NotDefined(MethodInfo.GetCurrentMethod())) { - Schema current = Schema.Current; + Schema schema = Schema.Current; - current.SchemaCompleted += () => + sb.Include() + .WithQuery(() => t => new + { + Entity = t, + t.Id, + t.TableName, + t.CleanName, + t.ClassName, + t.Namespace, + }); + + schema.SchemaCompleted += () => { - var attributes = current.Tables.Keys.Select(t => KeyValuePair.Create(t, t.GetCustomAttribute(true))).ToList(); + var attributes = schema.Tables.Keys.Select(t => KeyValuePair.Create(t, t.GetCustomAttribute(true))).ToList(); var errors = attributes.Where(a => a.Value == null).ToString(a => "Type {0} does not have an EntityTypeAttribute".FormatWith(a.Key.Name), "\r\n"); @@ -56,25 +67,12 @@ public static void Start(SchemaBuilder sb) throw new InvalidOperationException(errors); }; - current.Initializing += () => + schema.Initializing += () => { - current.typeCachesLazy.Load(); + schema.typeCachesLazy.Load(); }; - current.typeCachesLazy = sb.GlobalLazy(() => new TypeCaches(current), - new InvalidateWith(typeof(TypeEntity)), - Schema.Current.InvalidateMetadata); - - sb.Include() - .WithQuery(() => t => new - { - Entity = t, - t.Id, - t.TableName, - t.CleanName, - t.ClassName, - t.Namespace, - }); + schema.typeCachesLazy = sb.GlobalLazy(() => new TypeCaches(schema), new InvalidateWith(typeof(TypeEntity)), Schema.Current.InvalidateMetadata); TypeEntity.SetTypeEntityCallbacks( t => TypeToEntity.GetOrThrow(t), @@ -92,6 +90,7 @@ join t in Schema.Current.Tables.Keys on dn.FullClassName equals (EnumEntity.Extr public static SqlPreCommand? Schema_Synchronizing(Replacements replacements) { var schema = Schema.Current; + var isPostgres = schema.Settings.IsPostgres; Dictionary should = GenerateSchemaTypes().ToDictionaryEx(s => s.TableName, "tableName in memory"); @@ -99,8 +98,8 @@ join t in Schema.Current.Tables.Keys on dn.FullClassName equals (EnumEntity.Extr { //External entities are nt asked in SchemaSynchronizer replacements.AskForReplacements( - currentList.Where(t => schema.IsExternalDatabase(ObjectName.Parse(t.TableName).Schema.Database)).Select(a => a.TableName).ToHashSet(), - should.Values.Where(t => schema.IsExternalDatabase(ObjectName.Parse(t.TableName).Schema.Database)).Select(a => a.TableName).ToHashSet(), + currentList.Where(t => schema.IsExternalDatabase(ObjectName.Parse(t.TableName, isPostgres).Schema.Database)).Select(a => a.TableName).ToHashSet(), + should.Values.Where(t => schema.IsExternalDatabase(ObjectName.Parse(t.TableName, isPostgres).Schema.Database)).Select(a => a.TableName).ToHashSet(), Replacements.KeyTables); } @@ -109,12 +108,12 @@ join t in Schema.Current.Tables.Keys on dn.FullClassName equals (EnumEntity.Extr { //Temporal solution until applications are updated var repeated = - should.Keys.Select(k => ObjectName.Parse(k)).GroupBy(a => a.Name).Where(a => a.Count() > 1).Select(a => a.Key).Concat( - current.Keys.Select(k => ObjectName.Parse(k)).GroupBy(a => a.Name).Where(a => a.Count() > 1).Select(a => a.Key)).ToList(); + should.Keys.Select(k => ObjectName.Parse(k, isPostgres)).GroupBy(a => a.Name).Where(a => a.Count() > 1).Select(a => a.Key).Concat( + current.Keys.Select(k => ObjectName.Parse(k, isPostgres)).GroupBy(a => a.Name).Where(a => a.Count() > 1).Select(a => a.Key)).ToList(); Func simplify = tn => { - ObjectName name = ObjectName.Parse(tn); + ObjectName name = ObjectName.Parse(tn, isPostgres); return repeated.Contains(name.Name) ? name.ToString() : name.Name; }; @@ -138,8 +137,8 @@ join t in Schema.Current.Tables.Keys on dn.FullClassName equals (EnumEntity.Extr if (c.TableName != s.TableName) { - var pc = ObjectName.Parse(c.TableName); - var ps = ObjectName.Parse(s.TableName); + var pc = ObjectName.Parse(c.TableName, isPostgres); + var ps = ObjectName.Parse(s.TableName, isPostgres); if (!EqualsIgnoringDatabasePrefix(pc, ps)) { diff --git a/Signum.Engine/BulkInserter.cs b/Signum.Engine/BulkInserter.cs index baffbb6b6b..ef5c1e50b5 100644 --- a/Signum.Engine/BulkInserter.cs +++ b/Signum.Engine/BulkInserter.cs @@ -158,7 +158,7 @@ public static int BulkInsertTable(IEnumerable entities, bool oldIdentityBehaviour = t.IdentityBehaviour; DataTable dt = new DataTable(); - foreach (var c in t.Columns.Values.Where(c => !(c is SystemVersionedInfo.Column) && (disableIdentityBehaviour || !c.IdentityBehaviour))) + foreach (var c in t.Columns.Values.Where(c => !(c is SystemVersionedInfo.SqlServerPeriodColumn) && (disableIdentityBehaviour || !c.IdentityBehaviour))) dt.Columns.Add(new DataColumn(c.Name, c.Type.UnNullify())); if (disableIdentityBehaviour) t.IdentityBehaviour = false; @@ -275,7 +275,7 @@ public static int BulkInsertMListTable( DataTable dt = new DataTable(); - foreach (var c in mlistTable.Columns.Values.Where(c => !(c is SystemVersionedInfo.Column) && !c.IdentityBehaviour)) + foreach (var c in mlistTable.Columns.Values.Where(c => !(c is SystemVersionedInfo.SqlServerPeriodColumn) && !c.IdentityBehaviour)) dt.Columns.Add(new DataColumn(c.Name, c.Type.UnNullify())); var list = mlistElements.ToList(); diff --git a/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs b/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs index 674f748af6..a4851fadfe 100644 --- a/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs +++ b/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs @@ -26,7 +26,7 @@ public class EntityCodeGenerator public virtual void GenerateEntitiesFromDatabaseTables() { - CurrentSchema = Schema.Current; + CurrentSchema = Schema.Current; var tables = GetTables(); @@ -538,7 +538,7 @@ protected virtual string GetTicksColumnAttribute(DiffTable table) { StringBuilder sb = new StringBuilder(); sb.Append("TableName(\"" + objectName.Name + "\""); - if (objectName.Schema != SchemaName.Default) + if (objectName.Schema != SchemaName.Default(CurrentSchema.Settings.IsPostgres)) sb.Append(", SchemaName = \"" + objectName.Schema.Name + "\""); if (objectName.Schema.Database != null) @@ -636,7 +636,7 @@ protected virtual IEnumerable GetPropertyAttributes(DiffTable table, Dif parts.Add("Min = " + min); if (col.Length != -1) - parts.Add("Max = " + col.Length / DiffColumn.BytesPerChar(col.SqlDbType)); + parts.Add("Max = " + col.Length / DiffColumn.BytesPerChar(col.DbType.SqlServer)); return "StringLengthValidator(" + parts.ToString(", ") + ")"; } @@ -743,19 +743,19 @@ protected virtual List GetSqlDbTypeParts(DiffColumn col, Type type) { List parts = new List(); var pair = CurrentSchema.Settings.GetSqlDbTypePair(type); - if (pair.SqlDbType != col.SqlDbType) - parts.Add("SqlDbType = SqlDbType." + col.SqlDbType); + if (pair.DbType.SqlServer != col.DbType.SqlServer) + parts.Add("SqlDbType = SqlDbType." + col.DbType.SqlServer); - var defaultSize = CurrentSchema.Settings.GetSqlSize(null, null, pair.SqlDbType); + var defaultSize = CurrentSchema.Settings.GetSqlSize(null, null, pair.DbType); if (defaultSize != null) { - if (!(defaultSize == col.Precision || defaultSize == col.Length / DiffColumn.BytesPerChar(col.SqlDbType) || defaultSize == int.MaxValue && col.Length == -1)) + if (!(defaultSize == col.Precision || defaultSize == col.Length / DiffColumn.BytesPerChar(col.DbType.SqlServer) || defaultSize == int.MaxValue && col.Length == -1)) parts.Add("Size = " + (col.Length == -1 ? "int.MaxValue" : - col.Length != 0 ? (col.Length / DiffColumn.BytesPerChar(col.SqlDbType)).ToString() : + col.Length != 0 ? (col.Length / DiffColumn.BytesPerChar(col.DbType.SqlServer)).ToString() : col.Precision != 0 ? col.Precision.ToString() : "0")); } - var defaultScale = CurrentSchema.Settings.GetSqlScale(null, null, col.SqlDbType); + var defaultScale = CurrentSchema.Settings.GetSqlScale(null, null, col.DbType); if (defaultScale != null) { if (!(col.Scale == defaultScale)) @@ -801,7 +801,7 @@ protected virtual bool IsLite(DiffTable table, DiffColumn col) protected internal virtual Type GetValueType(DiffColumn col) { - switch (col.SqlDbType) + switch (col.DbType.SqlServer) { case SqlDbType.BigInt: return typeof(long); case SqlDbType.Binary: return typeof(byte[]); @@ -834,7 +834,7 @@ protected internal virtual Type GetValueType(DiffColumn col) case SqlDbType.Udt: return Schema.Current.Settings.UdtSqlName .SingleOrDefaultEx(kvp => StringComparer.InvariantCultureIgnoreCase.Equals(kvp.Value, col.UserTypeName)) .Key; - default: throw new NotImplementedException("Unknown translation for " + col.SqlDbType); + default: throw new NotImplementedException("Unknown translation for " + col.DbType.SqlServer); } } diff --git a/Signum.Engine/CodeGeneration/LogicCodeGenerator.cs b/Signum.Engine/CodeGeneration/LogicCodeGenerator.cs index 3fe83ab66a..fca622642d 100644 --- a/Signum.Engine/CodeGeneration/LogicCodeGenerator.cs +++ b/Signum.Engine/CodeGeneration/LogicCodeGenerator.cs @@ -426,7 +426,7 @@ protected virtual bool IsSimpleValueType(Type type) { var t = CurrentSchema.Settings.TryGetSqlDbTypePair(type.UnNullify()); - return t != null && t.UserDefinedTypeName == null && t.SqlDbType != SqlDbType.Image && t.SqlDbType != SqlDbType.VarBinary; + return t != null && t.UserDefinedTypeName == null && (t.DbType.IsNumber() || t.DbType.IsString() || t.DbType.IsDate()); } protected virtual string WriteOperations(Type type) diff --git a/Signum.Engine/Connection/Connector.cs b/Signum.Engine/Connection/Connector.cs index ff2908b2f7..4a610e3d0e 100644 --- a/Signum.Engine/Connection/Connector.cs +++ b/Signum.Engine/Connection/Connector.cs @@ -19,6 +19,8 @@ public abstract class Connector { static readonly Variable currentConnector = Statics.ThreadVariable("connection"); + public SqlBuilder SqlBuilder; + public static IDisposable Override(Connector connector) { Connector oldConnection = currentConnector.Value; @@ -48,6 +50,7 @@ public Connector(Schema schema) { this.Schema = schema; this.IsolationLevel = IsolationLevel.Unspecified; + this.SqlBuilder = new SqlBuilder(this); } public Schema Schema { get; private set; } @@ -75,14 +78,13 @@ protected static void Log(SqlPreCommandSimple pcs) } } - public abstract SqlDbType GetSqlDbType(DbParameter p); + public abstract string GetSqlDbType(DbParameter p); protected internal abstract object? ExecuteScalar(SqlPreCommandSimple preCommand, CommandType commandType); protected internal abstract int ExecuteNonQuery(SqlPreCommandSimple preCommand, CommandType commandType); - protected internal abstract DataTable ExecuteDataTable(SqlPreCommandSimple command, CommandType commandType); - protected internal abstract DbDataReaderWithCommand UnsafeExecuteDataReader(SqlPreCommandSimple sqlPreCommandSimple, CommandType commandType); + protected internal abstract DataTable ExecuteDataTable(SqlPreCommandSimple preCommand, CommandType commandType); + protected internal abstract DbDataReaderWithCommand UnsafeExecuteDataReader(SqlPreCommandSimple preCommand, CommandType commandType); protected internal abstract Task UnsafeExecuteDataReaderAsync(SqlPreCommandSimple preCommand, CommandType commandType, CancellationToken token); - protected internal abstract DataSet ExecuteDataSet(SqlPreCommandSimple sqlPreCommandSimple, CommandType commandType); protected internal abstract void BulkCopy(DataTable dt, ObjectName destinationTable, SqlBulkCopyOptions options, int? timeout); public abstract string DatabaseName(); @@ -141,8 +143,6 @@ public static string ExtractCatalogPostfix(ref string connectionString, string c public abstract bool AllowsIndexWithWhere(string where); - public abstract SqlPreCommand ShrinkDatabase(string databaseName); - public abstract bool AllowsConvertToDate { get; } public abstract bool AllowsConvertToTime { get; } @@ -165,24 +165,18 @@ public static string GetParameterName(string name) public DbParameter CreateReferenceParameter(string parameterName, PrimaryKey? id, IColumn column) { - return CreateParameter(parameterName, column.SqlDbType, null, column.Nullable.ToBool(), id == null ? null : id.Value.Object); + return CreateParameter(parameterName, column.DbType, null, column.Nullable.ToBool(), id == null ? null : id.Value.Object); } public DbParameter CreateParameter(string parameterName, object? value, Type type) { var pair = Schema.Current.Settings.GetSqlDbTypePair(type.UnNullify()); - return CreateParameter(parameterName, pair.SqlDbType, pair.UserDefinedTypeName, type == null || type.IsByRef || type.IsNullable(), value); - } - - public abstract DbParameter CreateParameter(string parameterName, SqlDbType type, string? udtTypeName, bool nullable, object? value); - public abstract MemberInitExpression ParameterFactory(Expression parameterName, SqlDbType type, string? udtTypeName, bool nullable, Expression value); - - protected static bool IsDate(SqlDbType type) - { - return type == SqlDbType.Date || type == SqlDbType.DateTime || type == SqlDbType.DateTime2 || type == SqlDbType.SmallDateTime; + return CreateParameter(parameterName, pair.DbType, pair.UserDefinedTypeName, type == null || type.IsByRef || type.IsNullable(), value); } + public abstract DbParameter CreateParameter(string parameterName, AbstractDbType dbType, string? udtTypeName, bool nullable, object? value); + public abstract MemberInitExpression ParameterFactory(Expression parameterName, AbstractDbType dbType, string? udtTypeName, bool nullable, Expression value); protected static MethodInfo miAsserDateTime = ReflectionTools.GetMethodInfo(() => AssertDateTime(null)); diff --git a/Signum.Engine/Connection/Executor.cs b/Signum.Engine/Connection/Executor.cs index 79d19dc694..c4b70a6c7f 100644 --- a/Signum.Engine/Connection/Executor.cs +++ b/Signum.Engine/Connection/Executor.cs @@ -61,17 +61,6 @@ public static DataTable ExecuteDataTable(this SqlPreCommandSimple preCommand, Co return Connector.Current.ExecuteDataTable(preCommand, commandType); } - - public static DataSet ExecuteDataSet(string sql, List? parameters = null, CommandType commandType = CommandType.Text) - { - return Connector.Current.ExecuteDataSet(new SqlPreCommandSimple(sql, parameters), commandType); - } - - public static DataSet ExecuteDataSet(this SqlPreCommandSimple preCommand, CommandType commandType = CommandType.Text) - { - return Connector.Current.ExecuteDataSet(preCommand, commandType); - } - public static void ExecuteLeaves(this SqlPreCommand preCommand, CommandType commandType = CommandType.Text) { foreach (var simple in preCommand.Leaves()) diff --git a/Signum.Engine/Connection/PostgreSqlConnector.cs b/Signum.Engine/Connection/PostgreSqlConnector.cs new file mode 100644 index 0000000000..c0dceded05 --- /dev/null +++ b/Signum.Engine/Connection/PostgreSqlConnector.cs @@ -0,0 +1,527 @@ +using Npgsql; +using Signum.Engine.Connection; +using Signum.Engine.Maps; +using Signum.Utilities; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Data.SqlClient; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Signum.Engine +{ + + public static class PostgresVersionDetector + { + public static Version Detect(string connectionString) + { + return SqlServerRetry.Retry(() => + { + using (NpgsqlConnection con = new NpgsqlConnection(connectionString)) + { + var sql = @"SHOW server_version;"; + + using (NpgsqlCommand cmd = new NpgsqlCommand(sql, con)) + { + NpgsqlDataAdapter da = new NpgsqlDataAdapter(cmd); + + DataTable result = new DataTable(); + da.Fill(result); + + var version = (string)result.Rows[0]["server_version"]!; + + return new Version(version); + } + } + }); + } + } + + public class PostgreSqlConnector : Connector + { + public override ParameterBuilder ParameterBuilder { get; protected set; } + + public Version PostgresVersion { get; set; } + + public PostgreSqlConnector(string connectionString, Schema schema, Version postgresVersion) : base(schema.Do(s => s.Settings.IsPostgres = true)) + { + this.ConnectionString = connectionString; + this.ParameterBuilder = new PostgreSqlParameterBuilder(); + this.PostgresVersion = postgresVersion; + } + + public int? CommandTimeout { get; set; } = null; + public string ConnectionString { get; set; } + + public override bool AllowsMultipleQueries => true; + + public override bool SupportsScalarSubquery => true; + + public override bool SupportsScalarSubqueryInAggregates => true; + + public override bool AllowsSetSnapshotIsolation => false; + + public override bool AllowsConvertToDate => true; + + public override bool AllowsConvertToTime => true; + + public override bool SupportsSqlDependency => true; + + public override bool SupportsFormat => true; + + public override bool SupportsTemporalTables => true; + + public override bool RequiresRetry => false; + + public override bool AllowsIndexWithWhere(string where) => true; + + public override void CleanDatabase(DatabaseName? database) + { + PostgreSqlConnectorScripts.RemoveAllScript(database).ExecuteNonQuery(); + } + + public override DbParameter CloneParameter(DbParameter p) + { + NpgsqlParameter sp = (NpgsqlParameter)p; + return new NpgsqlParameter(sp.ParameterName, sp.Value) { IsNullable = sp.IsNullable, NpgsqlDbType = sp.NpgsqlDbType }; + } + + public override DbConnection CreateConnection() + { + return new NpgsqlConnection(ConnectionString); + } + + public override string DatabaseName() + { + return new NpgsqlConnection(ConnectionString).Database!; + } + + public override string DataSourceName() + { + return new NpgsqlConnection(ConnectionString).DataSource; + } + + public override string GetSqlDbType(DbParameter p) + { + return ((NpgsqlParameter)p).NpgsqlDbType.ToString().ToUpperInvariant(); + } + + public override void RollbackTransactionPoint(DbTransaction transaction, string savePointName) + { + ((NpgsqlTransaction)transaction).Rollback(savePointName); + } + + public override void SaveTransactionPoint(DbTransaction transaction, string savePointName) + { + ((NpgsqlTransaction)transaction).Save(savePointName); + } + + T EnsureConnectionRetry(Func action) + { + if (Transaction.HasTransaction) + return action(null); + + using (NpgsqlConnection con = new NpgsqlConnection(this.ConnectionString)) + { + con.Open(); + + return action(con); + } + } + + NpgsqlCommand NewCommand(SqlPreCommandSimple preCommand, NpgsqlConnection? overridenConnection, CommandType commandType) + { + NpgsqlCommand cmd = new NpgsqlCommand { CommandType = commandType }; + + int? timeout = Connector.ScopeTimeout ?? CommandTimeout; + if (timeout.HasValue) + cmd.CommandTimeout = timeout.Value; + + if (overridenConnection != null) + cmd.Connection = overridenConnection; + else + { + cmd.Connection = (NpgsqlConnection)Transaction.CurrentConnection!; + cmd.Transaction = (NpgsqlTransaction)Transaction.CurrentTransaccion!; + } + + cmd.CommandText = preCommand.Sql; + + if (preCommand.Parameters != null) + { + foreach (NpgsqlParameter param in preCommand.Parameters) + { + cmd.Parameters.Add(param); + } + } + + Log(preCommand); + + return cmd; + } + + protected internal override void BulkCopy(DataTable dt, ObjectName destinationTable, SqlBulkCopyOptions options, int? timeout) + { + EnsureConnectionRetry(con => + { + con = con ?? (NpgsqlConnection)Transaction.CurrentConnection!; + + using (var writer = con.BeginBinaryImport($"COPY {destinationTable} ({dt.Columns.Cast().ToString(a => a.ColumnName, ", ")}) FROM STDIN (FORMAT BINARY)")) + { + for (int i = 0; i < dt.Rows.Count; i++) + { + var row = dt.Rows[i]; + + for (int j = 0; j < dt.Columns.Count; j++) + { + var col = dt.Columns[j]; + writer.Write(row[col]); + } + } + + writer.Complete(); + return 0; + } + }); + } + + protected internal override DataTable ExecuteDataTable(SqlPreCommandSimple preCommand, CommandType commandType) + { + return EnsureConnectionRetry(con => + { + using (NpgsqlCommand cmd = NewCommand(preCommand, con, commandType)) + using (HeavyProfiler.Log("SQL", () => preCommand.sp_executesql())) + { + try + { + NpgsqlDataAdapter da = new NpgsqlDataAdapter(cmd); + + DataTable result = new DataTable(); + da.Fill(result); + return result; + } + catch (Exception ex) + { + var nex = HandleException(ex, preCommand); + if (nex == ex) + throw; + + throw nex; + } + } + }); + } + + protected internal override int ExecuteNonQuery(SqlPreCommandSimple preCommand, CommandType commandType) + { + return EnsureConnectionRetry(con => + { + using (NpgsqlCommand cmd = NewCommand(preCommand, con, commandType)) + using (HeavyProfiler.Log("SQL", () => preCommand.sp_executesql())) + { + try + { + int result = cmd.ExecuteNonQuery(); + return result; + } + catch (Exception ex) + { + var nex = HandleException(ex, preCommand); + if (nex == ex) + throw; + + throw nex; + } + } + }); + } + + protected internal override object? ExecuteScalar(SqlPreCommandSimple preCommand, CommandType commandType) + { + return EnsureConnectionRetry(con => + { + using (NpgsqlCommand cmd = NewCommand(preCommand, con, commandType)) + using (HeavyProfiler.Log("SQL", () => preCommand.sp_executesql())) + { + try + { + object result = cmd.ExecuteScalar(); + + if (result == null || result == DBNull.Value) + return null; + + return result; + } + catch (Exception ex) + { + var nex = HandleException(ex, preCommand); + if (nex == ex) + throw; + + throw nex; + } + } + }); + } + + protected internal override DbDataReaderWithCommand UnsafeExecuteDataReader(SqlPreCommandSimple preCommand, CommandType commandType) + { + try + { + var cmd = NewCommand(preCommand, null, commandType); + + var reader = cmd.ExecuteReader(); + + return new DbDataReaderWithCommand(cmd, reader); + } + catch (Exception ex) + { + var nex = HandleException(ex, preCommand); + if (nex == ex) + throw; + + throw nex; + } + } + + protected internal override async Task UnsafeExecuteDataReaderAsync(SqlPreCommandSimple preCommand, CommandType commandType, CancellationToken token) + { + try + { + var cmd = NewCommand(preCommand, null, commandType); + + var reader = await cmd.ExecuteReaderAsync(token); + + return new DbDataReaderWithCommand(cmd, reader); + } + catch (Exception ex) + { + var nex = HandleException(ex, preCommand); + if (nex == ex) + throw; + + throw nex; + } + } + + public Exception HandleException(Exception ex, SqlPreCommandSimple command) + { + var nex = ReplaceException(ex, command); + nex.Data["Sql"] = command.sp_executesql(); + return nex; + } + + Exception ReplaceException(Exception ex, SqlPreCommandSimple command) + { + //if (ex is Npgsql.PostgresException se) + //{ + // switch (se.Number) + // { + // case -2: return new TimeoutException(ex.Message, ex); + // case 2601: return new UniqueKeyException(ex); + // case 547: return new ForeignKeyException(ex); + // default: return ex; + // } + //} + + //if (ex is SqlTypeException ste && ex.Message.Contains("DateTime")) + //{ + // var mins = command.Parameters.Where(a => DateTime.MinValue.Equals(a.Value)); + + // if (mins.Any()) + // { + // return new ArgumentOutOfRangeException("{0} {1} not initialized and equal to DateTime.MinValue".FormatWith( + // mins.CommaAnd(a => a.ParameterName), + // mins.Count() == 1 ? "is" : "are"), ex); + // } + //} + + return ex; + } + } + + public static class PostgreSqlConnectorScripts + { + public static SqlPreCommandSimple RemoveAllScript(DatabaseName? databaseName) + { + if (databaseName != null) + throw new NotSupportedException(); + + return new SqlPreCommandSimple(@"-- Copyright © 2019 +-- mirabilos +-- +-- Provided that these terms and disclaimer and all copyright notices +-- are retained or reproduced in an accompanying document, permission +-- is granted to deal in this work without restriction, including un‐ +-- limited rights to use, publicly perform, distribute, sell, modify, +-- merge, give away, or sublicence. +-- +-- This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to +-- the utmost extent permitted by applicable law, neither express nor +-- implied; without malicious intent or gross negligence. In no event +-- may a licensor, author or contributor be held liable for indirect, +-- direct, other damage, loss, or other issues arising in any way out +-- of dealing in the work, even if advised of the possibility of such +-- damage or existence of a defect, except proven that it results out +-- of said person’s immediate fault when using the work as intended. +-- - +-- Drop everything from the PostgreSQL database. + +DO $$ +DECLARE + r RECORD; +BEGIN + -- triggers + FOR r IN (SELECT pns.nspname, pc.relname, pt.tgname + FROM pg_trigger pt, pg_class pc, pg_namespace pns + WHERE pns.oid=pc.relnamespace AND pc.oid=pt.tgrelid + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') + AND pt.tgisinternal=false + ) LOOP + EXECUTE format('DROP TRIGGER %I ON %I.%I;', + r.tgname, r.nspname, r.relname); + END LOOP; + -- constraints #1: foreign key + FOR r IN (SELECT pns.nspname, pc.relname, pcon.conname + FROM pg_constraint pcon, pg_class pc, pg_namespace pns + WHERE pns.oid=pc.relnamespace AND pc.oid=pcon.conrelid + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') + AND pcon.contype='f' + ) LOOP + EXECUTE format('ALTER TABLE ONLY %I.%I DROP CONSTRAINT %I;', + r.nspname, r.relname, r.conname); + END LOOP; + -- constraints #2: the rest + FOR r IN (SELECT pns.nspname, pc.relname, pcon.conname + FROM pg_constraint pcon, pg_class pc, pg_namespace pns + WHERE pns.oid=pc.relnamespace AND pc.oid=pcon.conrelid + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') + AND pcon.contype<>'f' + ) LOOP + EXECUTE format('ALTER TABLE ONLY %I.%I DROP CONSTRAINT %I;', + r.nspname, r.relname, r.conname); + END LOOP; + -- indicēs + FOR r IN (SELECT pns.nspname, pc.relname + FROM pg_class pc, pg_namespace pns + WHERE pns.oid=pc.relnamespace + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') + AND pc.relkind='i' + ) LOOP + EXECUTE format('DROP INDEX %I.%I;', + r.nspname, r.relname); + END LOOP; + -- normal and materialised views + FOR r IN (SELECT pns.nspname, pc.relname + FROM pg_class pc, pg_namespace pns + WHERE pns.oid=pc.relnamespace + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') + AND pc.relkind IN ('v', 'm') + ) LOOP + EXECUTE format('DROP VIEW %I.%I;', + r.nspname, r.relname); + END LOOP; + -- tables + FOR r IN (SELECT pns.nspname, pc.relname + FROM pg_class pc, pg_namespace pns + WHERE pns.oid=pc.relnamespace + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') + AND pc.relkind='r' + ) LOOP + EXECUTE format('DROP TABLE %I.%I;', + r.nspname, r.relname); + END LOOP; + -- sequences + FOR r IN (SELECT pns.nspname, pc.relname + FROM pg_class pc, pg_namespace pns + WHERE pns.oid=pc.relnamespace + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') + AND pc.relkind='S' + ) LOOP + EXECUTE format('DROP SEQUENCE %I.%I;', + r.nspname, r.relname); + END LOOP; + -- extensions (see below), only if necessary + FOR r IN (SELECT pns.nspname, pe.extname + FROM pg_extension pe, pg_namespace pns + WHERE pns.oid=pe.extnamespace + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') + ) LOOP + EXECUTE format('DROP EXTENSION %I;', r.extname); + END LOOP; + -- functions / procedures + FOR r IN (SELECT pns.nspname, pp.proname, pp.oid + FROM pg_proc pp, pg_namespace pns + WHERE pns.oid=pp.pronamespace + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') + ) LOOP + EXECUTE format('DROP FUNCTION %I.%I(%s);', + r.nspname, r.proname, + pg_get_function_identity_arguments(r.oid)); + END LOOP; + -- nōn-default schemata we own; assume to be run by a not-superuser + FOR r IN (SELECT pns.nspname + FROM pg_namespace pns, pg_roles pr + WHERE pr.oid=pns.nspowner + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast', 'public') + AND pr.rolname=current_user + ) LOOP + EXECUTE format('DROP SCHEMA %I;', r.nspname); + END LOOP; + -- voilà + RAISE NOTICE 'Database cleared!'; +END; $$;"); + } + } + + public class PostgreSqlParameterBuilder : ParameterBuilder + { + public override DbParameter CreateParameter(string parameterName, AbstractDbType dbType, string? udtTypeName, bool nullable, object? value) + { + if (dbType.IsDate()) + AssertDateTime((DateTime?)value); + + var result = new Npgsql.NpgsqlParameter(parameterName, value ?? DBNull.Value) + { + IsNullable = nullable + }; + + result.NpgsqlDbType = dbType.PostgreSql; + if (udtTypeName != null) + result.DataTypeName = udtTypeName; + + + return result; + } + + public override MemberInitExpression ParameterFactory(Expression parameterName, AbstractDbType dbType, string? udtTypeName, bool nullable, Expression value) + { + Expression valueExpr = Expression.Convert(dbType.IsDate() ? Expression.Call(miAsserDateTime, Expression.Convert(value, typeof(DateTime?))) : value, typeof(object)); + + if (nullable) + valueExpr = Expression.Condition(Expression.Equal(value, Expression.Constant(null, value.Type)), + Expression.Constant(DBNull.Value, typeof(object)), + valueExpr); + + NewExpression newExpr = Expression.New(typeof(NpgsqlParameter).GetConstructor(new[] { typeof(string), typeof(object) }), parameterName, valueExpr); + + + List mb = new List() + { + Expression.Bind(typeof(NpgsqlParameter).GetProperty(nameof(NpgsqlParameter.IsNullable)), Expression.Constant(nullable)), + Expression.Bind(typeof(NpgsqlParameter).GetProperty(nameof(NpgsqlParameter.NpgsqlDbType)), Expression.Constant(dbType.PostgreSql)), + }; + + if (udtTypeName != null) + mb.Add(Expression.Bind(typeof(NpgsqlParameter).GetProperty(nameof(NpgsqlParameter.DataTypeName)), Expression.Constant(udtTypeName))); + + return Expression.MemberInit(newExpr, mb); + } + } +} diff --git a/Signum.Engine/Connection/SqlConnector.cs b/Signum.Engine/Connection/SqlConnector.cs index 3dd2245785..8ecf9964cf 100644 --- a/Signum.Engine/Connection/SqlConnector.cs +++ b/Signum.Engine/Connection/SqlConnector.cs @@ -81,14 +81,11 @@ public enum EngineEdition public class SqlConnector : Connector { - int? commandTimeout = null; - string connectionString; - public SqlServerVersion Version { get; set; } public SqlConnector(string connectionString, Schema schema, SqlServerVersion version) : base(schema) { - this.connectionString = connectionString; + this.ConnectionString = connectionString; this.ParameterBuilder = new SqlParameterBuilder(); this.Version = version; @@ -97,21 +94,13 @@ public SqlConnector(string connectionString, Schema schema, SqlServerVersion ver var s = schema.Settings; if (!s.TypeValues.ContainsKey(typeof(TimeSpan))) - schema.Settings.TypeValues.Add(typeof(TimeSpan), SqlDbType.Time); + schema.Settings.TypeValues.Add(typeof(TimeSpan), new AbstractDbType(SqlDbType.Time)); } } - public int? CommandTimeout - { - get { return commandTimeout; } - set { commandTimeout = value; } - } + public int? CommandTimeout { get; set; } = null; - public string ConnectionString - { - get { return connectionString; } - set { connectionString = value; } - } + public string ConnectionString { get; set; } public override bool SupportsScalarSubquery { get { return true; } } public override bool SupportsScalarSubqueryInAggregates { get { return false; } } @@ -351,32 +340,6 @@ protected internal override DataTable ExecuteDataTable(SqlPreCommandSimple preCo }); } - protected internal override DataSet ExecuteDataSet(SqlPreCommandSimple preCommand, CommandType commandType) - { - return EnsureConnectionRetry(con => - { - using (SqlCommand cmd = NewCommand(preCommand, con, commandType)) - using (HeavyProfiler.Log("SQL", () => preCommand.sp_executesql())) - { - try - { - SqlDataAdapter da = new SqlDataAdapter(cmd); - DataSet result = new DataSet(); - da.Fill(result); - return result; - } - catch (Exception ex) - { - var nex = HandleException(ex, preCommand); - if (nex == ex) - throw; - - throw nex; - } - } - }); - } - public Exception HandleException(Exception ex, SqlPreCommandSimple command) { var nex = ReplaceException(ex, command); @@ -436,12 +399,12 @@ protected internal override void BulkCopy(DataTable dt, ObjectName destinationTa public override string DatabaseName() { - return new SqlConnection(connectionString).Database; + return new SqlConnection(ConnectionString).Database; } public override string DataSourceName() { - return new SqlConnection(connectionString).DataSource; + return new SqlConnection(ConnectionString).DataSource; } public override void SaveTransactionPoint(DbTransaction transaction, string savePointName) @@ -454,9 +417,9 @@ public override void RollbackTransactionPoint(DbTransaction transaction, string ((SqlTransaction)transaction).Rollback(savePointName); } - public override SqlDbType GetSqlDbType(DbParameter p) + public override string GetSqlDbType(DbParameter p) { - return ((SqlParameter)p).SqlDbType; + return ((SqlParameter)p).SqlDbType.ToString().ToUpperInvariant(); } public override DbParameter CloneParameter(DbParameter p) @@ -472,10 +435,10 @@ public override DbConnection CreateConnection() public override ParameterBuilder ParameterBuilder { get; protected set; } - public override void CleanDatabase(DatabaseName? databaseName) + public override void CleanDatabase(DatabaseName? database) { - SqlConnectorScripts.RemoveAllScript(databaseName).ExecuteLeaves(); - SqlConnectorScripts.ShrinkDatabase(DatabaseName()); + SqlConnectorScripts.RemoveAllScript(database).ExecuteLeaves(); + ShrinkDatabase(database?.ToString() ?? DatabaseName()); } public override bool AllowsMultipleQueries @@ -488,7 +451,7 @@ public SqlConnector ForDatabase(Maps.DatabaseName? database) if (database == null) return this; - return new SqlConnector(Replace(connectionString, database), this.Schema, this.Version); + return new SqlConnector(Replace(ConnectionString, database), this.Schema, this.Version); } private static string Replace(string connectionString, DatabaseName item) @@ -511,7 +474,7 @@ public override bool AllowsIndexWithWhere(string Where) public static List ComplexWhereKeywords = new List { "OR" }; - public override SqlPreCommand ShrinkDatabase(string databaseName) + public SqlPreCommand ShrinkDatabase(string databaseName) { return new[] { @@ -562,9 +525,9 @@ public override bool SupportsTemporalTables public class SqlParameterBuilder : ParameterBuilder { - public override DbParameter CreateParameter(string parameterName, SqlDbType sqlType, string? udtTypeName, bool nullable, object? value) + public override DbParameter CreateParameter(string parameterName, AbstractDbType dbType, string? udtTypeName, bool nullable, object? value) { - if (IsDate(sqlType)) + if (dbType.IsDate()) AssertDateTime((DateTime?)value); var result = new SqlParameter(parameterName, value ?? DBNull.Value) @@ -572,18 +535,16 @@ public override DbParameter CreateParameter(string parameterName, SqlDbType sqlT IsNullable = nullable }; - result.SqlDbType = sqlType; - - if (sqlType == SqlDbType.Udt) + result.SqlDbType = dbType.SqlServer; + if (udtTypeName != null) result.UdtTypeName = udtTypeName; - return result; } - public override MemberInitExpression ParameterFactory(Expression parameterName, SqlDbType sqlType, string? udtTypeName, bool nullable, Expression value) + public override MemberInitExpression ParameterFactory(Expression parameterName, AbstractDbType dbType, string? udtTypeName, bool nullable, Expression value) { - Expression valueExpr = Expression.Convert(IsDate(sqlType) ? Expression.Call(miAsserDateTime, Expression.Convert(value, typeof(DateTime?))) : value, typeof(object)); + Expression valueExpr = Expression.Convert(dbType.IsDate() ? Expression.Call(miAsserDateTime, Expression.Convert(value, typeof(DateTime?))) : value, typeof(object)); if (nullable) valueExpr = Expression.Condition(Expression.Equal(value, Expression.Constant(null, value.Type)), @@ -596,10 +557,10 @@ public override MemberInitExpression ParameterFactory(Expression parameterName, List mb = new List() { Expression.Bind(typeof(SqlParameter).GetProperty("IsNullable"), Expression.Constant(nullable)), - Expression.Bind(typeof(SqlParameter).GetProperty("SqlDbType"), Expression.Constant(sqlType)), + Expression.Bind(typeof(SqlParameter).GetProperty("SqlDbType"), Expression.Constant(dbType.SqlServer)), }; - if (sqlType == SqlDbType.Udt) + if (udtTypeName != null) mb.Add(Expression.Bind(typeof(SqlParameter).GetProperty("UdtTypeName"), Expression.Constant(udtTypeName))); return Expression.MemberInit(newExpr, mb); @@ -722,8 +683,9 @@ close cur public static SqlPreCommand RemoveAllScript(DatabaseName? databaseName) { - var systemSchemas = SqlBuilder.SystemSchemas.ToString(a => "'" + a + "'", ", "); - var systemSchemasExeptDbo = SqlBuilder.SystemSchemas.Where(s => s != "dbo").ToString(a => "'" + a + "'", ", "); + var sqlBuilder = Connector.Current.SqlBuilder; + var systemSchemas = sqlBuilder.SystemSchemas.ToString(a => "'" + a + "'", ", "); + var systemSchemasExeptDbo = sqlBuilder.SystemSchemas.Where(s => s != "dbo").ToString(a => "'" + a + "'", ", "); return SqlPreCommand.Combine(Spacing.Double, new SqlPreCommandSimple(Use(databaseName, RemoveAllProceduresScript)), @@ -742,11 +704,5 @@ static string Use(DatabaseName? databaseName, string script) return "use " + databaseName + "\r\n" + script; } - - internal static SqlPreCommand ShrinkDatabase(string databaseName) - { - return Connector.Current.ShrinkDatabase(databaseName); - - } } } diff --git a/Signum.Engine/Database.cs b/Signum.Engine/Database.cs index a0a0c72dec..9074e172b8 100644 --- a/Signum.Engine/Database.cs +++ b/Signum.Engine/Database.cs @@ -74,10 +74,11 @@ public static T Save(this T entity) public static int InsertView(this T viewObject) where T : IView { - var view = Schema.Current.View(); + var schema = Schema.Current; + var view = schema.View(); var parameters = view.GetInsertParameters(viewObject); - var sql = $@"INSERT {view.Name} ({view.Columns.ToString(p => p.Key.SqlEscape(), ", ")}) + var sql = $@"INSERT {view.Name} ({view.Columns.ToString(p => p.Key.SqlEscape(schema.Settings.IsPostgres), ", ")}) VALUES ({parameters.ToString(p => p.ParameterName, ", ")})"; return Executor.ExecuteNonQuery(sql, parameters); diff --git a/Signum.Engine/Engine/SchemaGenerator.cs b/Signum.Engine/Engine/SchemaGenerator.cs index 718e62f552..a754608e32 100644 --- a/Signum.Engine/Engine/SchemaGenerator.cs +++ b/Signum.Engine/Engine/SchemaGenerator.cs @@ -4,6 +4,7 @@ using Signum.Utilities; using Signum.Entities; using Signum.Engine.SchemaInfoTables; +using System.IO; namespace Signum.Engine { @@ -12,32 +13,35 @@ public static class SchemaGenerator public static SqlPreCommand? CreateSchemasScript() { Schema s = Schema.Current; + var sqlBuilder = Connector.Current.SqlBuilder; + var defaultSchema = SchemaName.Default(s.Settings.IsPostgres); return s.GetDatabaseTables() .Select(a => a.Name.Schema) - .Where(sn => sn.Name != "dbo" && !s.IsExternalDatabase(sn.Database)) + .Where(sn => sn != defaultSchema && !s.IsExternalDatabase(sn.Database)) .Distinct() - .Select(SqlBuilder.CreateSchema) + .Select(sqlBuilder.CreateSchema) .Combine(Spacing.Simple); } public static SqlPreCommand? CreateTablesScript() { + var sqlBuilder = Connector.Current.SqlBuilder; Schema s = Schema.Current; List tables = s.GetDatabaseTables().Where(t => !s.IsExternalDatabase(t.Name.Schema.Database)).ToList(); - SqlPreCommand? createTables = tables.Select(SqlBuilder.CreateTableSql).Combine(Spacing.Double)?.PlainSqlCommand(); + SqlPreCommand? createTables = tables.Select(sqlBuilder.CreateTableSql).Combine(Spacing.Double)?.PlainSqlCommand(); - SqlPreCommand? foreignKeys = tables.Select(SqlBuilder.AlterTableForeignKeys).Combine(Spacing.Double)?.PlainSqlCommand(); + SqlPreCommand? foreignKeys = tables.Select(sqlBuilder.AlterTableForeignKeys).Combine(Spacing.Double)?.PlainSqlCommand(); SqlPreCommand? indices = tables.Select(t => { var allIndexes = t.GeneratAllIndexes().Where(a => !(a is PrimaryClusteredIndex)); ; - var mainIndices = allIndexes.Select(ix => SqlBuilder.CreateIndex(ix, checkUnique: null)).Combine(Spacing.Simple); + var mainIndices = allIndexes.Select(ix => sqlBuilder.CreateIndex(ix, checkUnique: null)).Combine(Spacing.Simple); var historyIndices = t.SystemVersioned == null ? null : - allIndexes.Where(a => a.GetType() == typeof(TableIndex)).Select(mix => SqlBuilder.CreateIndexBasic(mix, forHistoryTable: true)).Combine(Spacing.Simple); + allIndexes.Where(a => a.GetType() == typeof(TableIndex)).Select(mix => sqlBuilder.CreateIndexBasic(mix, forHistoryTable: true)).Combine(Spacing.Simple); return SqlPreCommand.Combine(Spacing.Double, mainIndices, historyIndices); @@ -56,28 +60,56 @@ select EnumEntity.GetEntities(enumType).Select((e, i) => t.InsertSqlSync(e, suff ).Combine(Spacing.Double)?.PlainSqlCommand(); } + public static SqlPreCommand? PostgreeExtensions() + { + if (!Schema.Current.Settings.IsPostgres) + return null; + + return Schema.Current.PostgreeExtensions.Select(p => Connector.Current.SqlBuilder.CreateExtensionIfNotExist(p)).Combine(Spacing.Simple); + } + + public static SqlPreCommand? PostgreeTemporalTableScript() + { + if (!Schema.Current.Settings.IsPostgres) + return null; + + if (!Schema.Current.Tables.Any(t => t.Value.SystemVersioned != null)) + return null; + + var file = Schema.Current.Settings.PostresVersioningFunctionNoChecks ? + "versioning_function_nochecks.sql" : + "versioning_function.sql"; + + var text = new StreamReader(typeof(Schema).Assembly.GetManifestResourceStream($"Signum.Engine.Engine.Scripts.{file}")!).Using(a => a.ReadToEnd()); + + return new SqlPreCommandSimple(text); + } public static SqlPreCommand? SnapshotIsolation() { - if (!Connector.Current.AllowsSetSnapshotIsolation) + var connector = Connector.Current; + + if (!connector.AllowsSetSnapshotIsolation) return null; - var list = Schema.Current.DatabaseNames().Select(a => a?.ToString()).ToList(); + var list = connector.Schema.DatabaseNames().Select(a => a?.ToString()).ToList(); if (list.Contains(null)) { list.Remove(null); - list.Add(Connector.Current.DatabaseName()); + list.Add(connector.DatabaseName()); } + var sqlBuilder = connector.SqlBuilder; + var cmd = list.NotNull() .Where(db => !SnapshotIsolationEnabled(db)) .Select(db => SqlPreCommand.Combine(Spacing.Simple, - SqlBuilder.SetSingleUser(db), - SqlBuilder.SetSnapshotIsolation(db, true), - SqlBuilder.MakeSnapshotIsolationDefault(db, true), - SqlBuilder.SetMultiUser(db)) + sqlBuilder.SetSingleUser(db), + sqlBuilder.SetSnapshotIsolation(db, true), + sqlBuilder.MakeSnapshotIsolationDefault(db, true), + sqlBuilder.SetMultiUser(db)) ).Combine(Spacing.Double); return cmd; diff --git a/Signum.Engine/Engine/SchemaSynchronizer.cs b/Signum.Engine/Engine/SchemaSynchronizer.cs index 26bc0fe75b..6699d54a0e 100644 --- a/Signum.Engine/Engine/SchemaSynchronizer.cs +++ b/Signum.Engine/Engine/SchemaSynchronizer.cs @@ -1,3 +1,4 @@ +using NpgsqlTypes; using Signum.Engine.Linq; using Signum.Engine.Maps; using Signum.Engine.SchemaInfoTables; @@ -22,9 +23,11 @@ public static class SchemaSynchronizer { Schema s = Schema.Current; + var sqlBuilder = Connector.Current.SqlBuilder; + Dictionary modelTables = s.GetDatabaseTables().Where(t => !s.IsExternalDatabase(t.Name.Schema.Database)).ToDictionaryEx(a => a.Name.ToString(), "schema tables"); var modelTablesHistory = modelTables.Values.Where(a => a.SystemVersioned != null).ToDictionaryEx(a => a.SystemVersioned!.TableName.ToString(), "history schema tables"); - HashSet modelSchemas = modelTables.Values.Select(a => a.Name.Schema).Where(a => !SqlBuilder.SystemSchemas.Contains(a.Name)).ToHashSet(); + HashSet modelSchemas = modelTables.Values.Select(a => a.Name.Schema).Where(a => !sqlBuilder.SystemSchemas.Contains(a.Name)).ToHashSet(); Dictionary databaseTables = DefaultGetDatabaseDescription(s.DatabaseNames()); var databaseTablesHistory = databaseTables.Extract((key, val) => val.TemporalType == SysTableTemporalType.HistoryTable); @@ -103,7 +106,7 @@ public static class SchemaSynchronizer using (replacements.WithReplacedDatabaseName()) { SqlPreCommand? preRenameColumns = preRenameColumnsList - .Select(kvp => kvp.Value.Select(kvp2 => SqlBuilder.RenameColumn(kvp.Key, kvp2.Key, kvp2.Value)).Combine(Spacing.Simple)) + .Select(kvp => kvp.Value.Select(kvp2 => sqlBuilder.RenameColumn(kvp.Key, kvp2.Key, kvp2.Value)).Combine(Spacing.Simple)) .Combine(Spacing.Double); if (preRenameColumns != null) @@ -112,28 +115,28 @@ public static class SchemaSynchronizer SqlPreCommand? createSchemas = Synchronizer.SynchronizeScriptReplacing(replacements, "Schemas", Spacing.Double, modelSchemas.ToDictionary(a => a.ToString()), databaseSchemas.ToDictionary(a => a.ToString()), - createNew: (_, newSN) => SqlBuilder.CreateSchema(newSN), + createNew: (_, newSN) => sqlBuilder.CreateSchema(newSN), removeOld: null, - mergeBoth: (_, newSN, oldSN) => newSN.Equals(oldSN) ? null : SqlBuilder.CreateSchema(newSN) + mergeBoth: (_, newSN, oldSN) => newSN.Equals(oldSN) ? null : sqlBuilder.CreateSchema(newSN) ); //use database without replacements to just remove indexes SqlPreCommand? dropStatistics = Synchronizer.SynchronizeScript(Spacing.Double, modelTables, databaseTables, createNew: null, - removeOld: (tn, dif) => SqlBuilder.DropStatistics(tn, dif.Stats), + removeOld: (tn, dif) => sqlBuilder.DropStatistics(tn, dif.Stats), mergeBoth: (tn, tab, dif) => { var removedColums = dif.Columns.Keys.Except(tab.Columns.Keys).ToHashSet(); - return SqlBuilder.DropStatistics(tn, dif.Stats.Where(a => a.Columns.Any(removedColums.Contains)).ToList()); + return sqlBuilder.DropStatistics(tn, dif.Stats.Where(a => a.Columns.Any(removedColums.Contains)).ToList()); }); SqlPreCommand? dropIndices = Synchronizer.SynchronizeScript(Spacing.Double, modelTables, databaseTables, createNew: null, - removeOld: (tn, dif) => dif.Indices.Values.Where(ix => !ix.IsPrimary).Select(ix => SqlBuilder.DropIndex(dif.Name, ix)).Combine(Spacing.Simple), + removeOld: (tn, dif) => dif.Indices.Values.Where(ix => !ix.IsPrimary).Select(ix => sqlBuilder.DropIndex(dif.Name, ix)).Combine(Spacing.Simple), mergeBoth: (tn, tab, dif) => { Dictionary modelIxs = modelIndices[tab]; @@ -144,8 +147,8 @@ public static class SchemaSynchronizer modelIxs.Where(kvp => !(kvp.Value is PrimaryClusteredIndex)).ToDictionary(), dif.Indices.Where(kvp =>!kvp.Value.IsPrimary).ToDictionary(), createNew: null, - removeOld: (i, dix) => dix.Columns.Any(c => removedColums.Contains(c.ColumnName)) || dix.IsControlledIndex ? SqlBuilder.DropIndex(dif.Name, dix) : null, - mergeBoth: (i, mix, dix) => !dix.IndexEquals(dif, mix) ? SqlBuilder.DropIndex(dif.Name, dix) : null + removeOld: (i, dix) => dix.Columns.Any(c => removedColums.Contains(c.ColumnName)) || dix.IsControlledIndex ? sqlBuilder.DropIndex(dif.Name, dix) : null, + mergeBoth: (i, mix, dix) => !dix.IndexEquals(dif, mix) ? sqlBuilder.DropIndex(dif.Name, dix) : null ); return changes; @@ -154,7 +157,7 @@ public static class SchemaSynchronizer SqlPreCommand? dropIndicesHistory = Synchronizer.SynchronizeScript(Spacing.Double, modelTablesHistory, databaseTablesHistory, createNew: null, - removeOld: (tn, dif) => dif.Indices.Values.Where(ix => ix.Type != DiffIndexType.Clustered).Select(ix => SqlBuilder.DropIndex(dif.Name, ix)).Combine(Spacing.Simple), + removeOld: (tn, dif) => dif.Indices.Values.Where(ix => ix.Type != DiffIndexType.Clustered).Select(ix => sqlBuilder.DropIndex(dif.Name, ix)).Combine(Spacing.Simple), mergeBoth: (tn, tab, dif) => { Dictionary modelIxs = modelIndices[tab]; @@ -165,8 +168,8 @@ public static class SchemaSynchronizer modelIxs.Where(kvp => kvp.Value.GetType() == typeof(TableIndex)).ToDictionary(), dif.Indices.Where(kvp => kvp.Value.Type != DiffIndexType.Clustered).ToDictionary(), createNew: null, - removeOld: (i, dix) => dix.Columns.Any(c => removedColums.Contains(c.ColumnName)) || dix.IsControlledIndex ? SqlBuilder.DropIndex(dif.Name, dix) : null, - mergeBoth: (i, mix, dix) => !dix.IndexEquals(dif, mix) ? SqlBuilder.DropIndex(dif.Name, dix) : null + removeOld: (i, dix) => dix.Columns.Any(c => removedColums.Contains(c.ColumnName)) || dix.IsControlledIndex ? sqlBuilder.DropIndex(dif.Name, dix) : null, + mergeBoth: (i, mix, dix) => !dix.IndexEquals(dif, mix) ? sqlBuilder.DropIndex(dif.Name, dix) : null ); return changes; @@ -177,20 +180,20 @@ public static class SchemaSynchronizer modelTables, databaseTables, createNew: null, - removeOld: (tn, dif) => dif.Columns.Values.Select(c => c.ForeignKey != null ? SqlBuilder.AlterTableDropConstraint(dif.Name, c.ForeignKey.Name) : null) - .Concat(dif.MultiForeignKeys.Select(fk => SqlBuilder.AlterTableDropConstraint(dif.Name, fk.Name))).Combine(Spacing.Simple), + removeOld: (tn, dif) => dif.Columns.Values.Select(c => c.ForeignKey != null ? sqlBuilder.AlterTableDropConstraint(dif.Name, c.ForeignKey.Name) : null) + .Concat(dif.MultiForeignKeys.Select(fk => sqlBuilder.AlterTableDropConstraint(dif.Name, fk.Name))).Combine(Spacing.Simple), mergeBoth: (tn, tab, dif) => SqlPreCommand.Combine(Spacing.Simple, Synchronizer.SynchronizeScript( Spacing.Simple, tab.Columns, dif.Columns, createNew: null, - removeOld: (cn, colDb) => colDb.ForeignKey != null ? SqlBuilder.AlterTableDropConstraint(dif.Name, colDb.ForeignKey.Name) : null, + removeOld: (cn, colDb) => colDb.ForeignKey != null ? sqlBuilder.AlterTableDropConstraint(dif.Name, colDb.ForeignKey.Name) : null, mergeBoth: (cn, colModel, colDb) => colDb.ForeignKey == null ? null : - colModel.ReferenceTable == null || colModel.AvoidForeignKey || !colModel.ReferenceTable.Name.Equals(ChangeName(colDb.ForeignKey.TargetTable)) || DifferentDatabase(tab.Name, colModel.ReferenceTable.Name) || colDb.SqlDbType != colModel.SqlDbType ? - SqlBuilder.AlterTableDropConstraint(dif.Name, colDb.ForeignKey.Name) : + colModel.ReferenceTable == null || colModel.AvoidForeignKey || !colModel.ReferenceTable.Name.Equals(ChangeName(colDb.ForeignKey.TargetTable)) || DifferentDatabase(tab.Name, colModel.ReferenceTable.Name) || colDb.DbType.SqlServer != colModel.DbType.SqlServer ? + sqlBuilder.AlterTableDropConstraint(dif.Name, colDb.ForeignKey.Name) : null), - dif.MultiForeignKeys.Select(fk => SqlBuilder.AlterTableDropConstraint(dif.Name, fk.Name)).Combine(Spacing.Simple)) + dif.MultiForeignKeys.Select(fk => sqlBuilder.AlterTableDropConstraint(dif.Name, fk.Name)).Combine(Spacing.Simple)) ); HashSet hasValueFalse = new HashSet(); @@ -205,12 +208,12 @@ public static class SchemaSynchronizer modelTables, databaseTables, createNew: (tn, tab) => SqlPreCommand.Combine(Spacing.Double, - SqlBuilder.CreateTableSql(tab) + sqlBuilder.CreateTableSql(tab) ), - removeOld: (tn, dif) => SqlBuilder.DropTable(dif), + removeOld: (tn, dif) => sqlBuilder.DropTable(dif), mergeBoth: (tn, tab, dif) => { - var rename = !object.Equals(dif.Name, tab.Name) ? SqlBuilder.RenameOrMove(dif, tab) : null; + var rename = !object.Equals(dif.Name, tab.Name) ? sqlBuilder.RenameOrMove(dif, tab) : null; bool disableEnableSystemVersioning = false; @@ -218,17 +221,17 @@ public static class SchemaSynchronizer (tab.SystemVersioned == null || !object.Equals(replacements.Apply(Replacements.KeyTables, dif.TemporalTableName!.ToString()), tab.SystemVersioned.TableName.ToString()) || (disableEnableSystemVersioning = StrongColumnChanges(tab, dif)))) ? - SqlBuilder.AlterTableDisableSystemVersioning(tab.Name).Do(a => a.GoAfter = true) : + sqlBuilder.AlterTableDisableSystemVersioning(tab.Name).Do(a => a.GoAfter = true) : null; var dropPeriod = (dif.Period != null && (tab.SystemVersioned == null || !dif.Period.PeriodEquals(tab.SystemVersioned)) ? - SqlBuilder.AlterTableDropPeriod(tab) : null); + sqlBuilder.AlterTableDropPeriod(tab) : null); var modelPK = modelIndices[tab].Values.OfType().SingleOrDefaultEx(); var diffPK = dif.Indices.Values.SingleOrDefaultEx(a => a.Type == DiffIndexType.Clustered); - var dropPrimaryKey = diffPK != null && (modelPK == null || !diffPK.IndexEquals(dif, modelPK)) ? SqlBuilder.DropIndex(tab.Name, diffPK) : null; + var dropPrimaryKey = diffPK != null && (modelPK == null || !diffPK.IndexEquals(dif, modelPK)) ? sqlBuilder.DropIndex(tab.Name, diffPK) : null; var columns = Synchronizer.SynchronizeScript( Spacing.Simple, @@ -236,14 +239,14 @@ public static class SchemaSynchronizer dif.Columns, createNew: (cn, tabCol) => SqlPreCommand.Combine(Spacing.Simple, - tabCol.PrimaryKey && dif.PrimaryKeyName != null ? SqlBuilder.DropPrimaryKeyConstraint(tab.Name) : null, - AlterTableAddColumnDefault(tab, tabCol, replacements, + tabCol.PrimaryKey && dif.PrimaryKeyName != null ? sqlBuilder.DropPrimaryKeyConstraint(tab.Name) : null, + AlterTableAddColumnDefault(sqlBuilder, tab, tabCol, replacements, forceDefaultValue: cn.EndsWith("_HasValue") && dif.Columns.Values.Any(c => c.Name.StartsWith(cn.Before("HasValue")) && c.Nullable == false) ? "1" : null, hasValueFalse: hasValueFalse)), removeOld: (cn, difCol) => SqlPreCommand.Combine(Spacing.Simple, - difCol.DefaultConstraint != null ? SqlBuilder.AlterTableDropConstraint(tab.Name, difCol.DefaultConstraint.Name) : null, - SqlBuilder.AlterTableDropColumn(tab, cn)), + difCol.DefaultConstraint != null ? sqlBuilder.AlterTableDropConstraint(tab.Name, difCol.DefaultConstraint.Name) : null, + sqlBuilder.AlterTableDropColumn(tab, cn)), mergeBoth: (cn, tabCol, difCol) => { @@ -251,30 +254,30 @@ public static class SchemaSynchronizer { return SqlPreCommand.Combine(Spacing.Simple, - difCol.Name == tabCol.Name ? null : SqlBuilder.RenameColumn(tab.Name, difCol.Name, tabCol.Name), + difCol.Name == tabCol.Name ? null : sqlBuilder.RenameColumn(tab.Name, difCol.Name, tabCol.Name), difCol.ColumnEquals(tabCol, ignorePrimaryKey: true, ignoreIdentity: false, ignoreGenerateAlways: false) ? null : SqlPreCommand.Combine(Spacing.Simple, - tabCol.PrimaryKey && !difCol.PrimaryKey && dif.PrimaryKeyName != null ? SqlBuilder.DropPrimaryKeyConstraint(tab.Name) : null, - UpdateCompatible(replacements, tab, dif, tabCol, difCol), - tabCol.SqlDbType == SqlDbType.NVarChar && difCol.SqlDbType == SqlDbType.NChar ? SqlBuilder.UpdateTrim(tab, tabCol) : null), + tabCol.PrimaryKey && !difCol.PrimaryKey && dif.PrimaryKeyName != null ? sqlBuilder.DropPrimaryKeyConstraint(tab.Name) : null, + UpdateCompatible(sqlBuilder, replacements, tab, dif, tabCol, difCol), + tabCol.DbType.SqlServer == SqlDbType.NVarChar && difCol.DbType.SqlServer == SqlDbType.NChar ? sqlBuilder.UpdateTrim(tab, tabCol) : null), UpdateByFkChange(tn, difCol, tabCol, ChangeName), difCol.DefaultEquals(tabCol) ? null : SqlPreCommand.Combine(Spacing.Simple, - difCol.DefaultConstraint != null ? SqlBuilder.AlterTableDropConstraint(tab.Name, difCol.DefaultConstraint.Name) : null, - tabCol.Default != null ? SqlBuilder.AlterTableAddDefaultConstraint(tab.Name, SqlBuilder.GetDefaultConstaint(tab, tabCol)!) : null) + difCol.DefaultConstraint != null ? sqlBuilder.AlterTableDropConstraint(tab.Name, difCol.DefaultConstraint.Name) : null, + tabCol.Default != null ? sqlBuilder.AlterTableAddDefaultConstraint(tab.Name, sqlBuilder.GetDefaultConstaint(tab, tabCol)!) : null) ); } else { - var update = difCol.PrimaryKey ? null : UpdateForeignKeyTypeChanged(tab, dif, tabCol, difCol, ChangeName, preRenameColumnsList) ?? UpdateCustom(tab, tabCol, difCol); - var drop = SqlBuilder.AlterTableDropColumn(tab, difCol.Name); + var update = difCol.PrimaryKey ? null : UpdateForeignKeyTypeChanged(sqlBuilder, tab, dif, tabCol, difCol, ChangeName, preRenameColumnsList) ?? UpdateCustom(tab, tabCol, difCol); + var drop = sqlBuilder.AlterTableDropColumn(tab, difCol.Name); delayedUpdates.Add(update); delayedDrops.Add(SqlPreCommand.Combine(Spacing.Simple, - difCol.DefaultConstraint != null ? SqlBuilder.AlterTableDropConstraint(tab.Name, difCol.DefaultConstraint.Name) : null, + difCol.DefaultConstraint != null ? sqlBuilder.AlterTableDropConstraint(tab.Name, difCol.DefaultConstraint.Name) : null, drop )); @@ -285,26 +288,26 @@ public static class SchemaSynchronizer } return SqlPreCommand.Combine(Spacing.Simple, - AlterTableAddColumnDefaultZero(tab, tabCol) + AlterTableAddColumnDefaultZero(sqlBuilder, tab, tabCol) ); } } ); - var createPrimaryKey = modelPK != null && (diffPK == null || !diffPK.IndexEquals(dif, modelPK)) ? SqlBuilder.CreateIndex(modelPK, checkUnique: null) : null; + var createPrimaryKey = modelPK != null && (diffPK == null || !diffPK.IndexEquals(dif, modelPK)) ? sqlBuilder.CreateIndex(modelPK, checkUnique: null) : null; var columnsHistory = columns != null && disableEnableSystemVersioning ? ForHistoryTable(columns, tab).Replace(new Regex(" IDENTITY "), m => " ") : null;/*HACK*/ var addPeriod = ((tab.SystemVersioned != null && (dif.Period == null || !dif.Period.PeriodEquals(tab.SystemVersioned))) ? - (SqlPreCommandSimple)SqlBuilder.AlterTableAddPeriod(tab) : null); + (SqlPreCommandSimple)sqlBuilder.AlterTableAddPeriod(tab) : null); var addSystemVersioning = (tab.SystemVersioned != null && (dif.Period == null || dif.TemporalTableName == null || !object.Equals(replacements.Apply(Replacements.KeyTables, dif.TemporalTableName.ToString()), tab.SystemVersioned.TableName.ToString()) || disableEnableSystemVersioning) ? - SqlBuilder.AlterTableEnableSystemVersioning(tab).Do(a => a.GoBefore = true) : null); + sqlBuilder.AlterTableEnableSystemVersioning(tab).Do(a => a.GoBefore = true) : null); SqlPreCommand? combinedAddPeriod = null; @@ -341,8 +344,8 @@ public static class SchemaSynchronizer SqlPreCommand? historyTables = Synchronizer.SynchronizeScript(Spacing.Double, modelTablesHistory, databaseTablesHistory, createNew: null, - removeOld: (tn, dif) => SqlBuilder.DropTable(dif.Name), - mergeBoth: (tn, tab, dif) => !object.Equals(dif.Name, tab.SystemVersioned!.TableName) ? SqlBuilder.RenameOrChangeSchema(dif.Name, tab.SystemVersioned!.TableName) : null); + removeOld: (tn, dif) => sqlBuilder.DropTable(dif.Name), + mergeBoth: (tn, tab, dif) => !object.Equals(dif.Name, tab.SystemVersioned!.TableName) ? sqlBuilder.RenameOrChangeSchema(dif.Name, tab.SystemVersioned!.TableName) : null); SqlPreCommand? syncEnums = SynchronizeEnumsScript(replacements); @@ -352,7 +355,7 @@ public static class SchemaSynchronizer Spacing.Double, modelTables, databaseTables, - createNew: (tn, tab) => SqlBuilder.AlterTableForeignKeys(tab), + createNew: (tn, tab) => sqlBuilder.AlterTableForeignKeys(tab), removeOld: null, mergeBoth: (tn, tab, dif) => Synchronizer.SynchronizeScript( Spacing.Simple, @@ -360,7 +363,7 @@ public static class SchemaSynchronizer dif.Columns, createNew: (cn, colModel) => colModel.ReferenceTable == null || colModel.AvoidForeignKey || DifferentDatabase(tab.Name, colModel.ReferenceTable.Name) ? null : - SqlBuilder.AlterTableAddConstraintForeignKey(tab, colModel.Name, colModel.ReferenceTable), + sqlBuilder.AlterTableAddConstraintForeignKey(tab, colModel.Name, colModel.ReferenceTable), removeOld: null, @@ -369,20 +372,20 @@ public static class SchemaSynchronizer if (tabCol.ReferenceTable == null || tabCol.AvoidForeignKey || DifferentDatabase(tab.Name, tabCol.ReferenceTable.Name)) return null; - if (difCol.ForeignKey == null || !tabCol.ReferenceTable.Name.Equals(ChangeName(difCol.ForeignKey.TargetTable)) || difCol.SqlDbType != tabCol.SqlDbType) - return SqlBuilder.AlterTableAddConstraintForeignKey(tab, tabCol.Name, tabCol.ReferenceTable); + if (difCol.ForeignKey == null || !tabCol.ReferenceTable.Name.Equals(ChangeName(difCol.ForeignKey.TargetTable)) || difCol.DbType.SqlServer != tabCol.DbType.SqlServer) + return sqlBuilder.AlterTableAddConstraintForeignKey(tab, tabCol.Name, tabCol.ReferenceTable); - var name = SqlBuilder.ForeignKeyName(tab.Name.Name, tabCol.Name); + var name = sqlBuilder.ForeignKeyName(tab.Name.Name, tabCol.Name); return SqlPreCommand.Combine(Spacing.Simple, - name != difCol.ForeignKey.Name.Name ? SqlBuilder.RenameForeignKey(difCol.ForeignKey.Name.OnSchema(tab.Name.Schema), name) : null, - (difCol.ForeignKey.IsDisabled || difCol.ForeignKey.IsNotTrusted) && !replacements.SchemaOnly ? SqlBuilder.EnableForeignKey(tab.Name, name) : null); + name != difCol.ForeignKey.Name.Name ? sqlBuilder.RenameForeignKey(difCol.ForeignKey.Name.OnSchema(tab.Name.Schema), name) : null, + (difCol.ForeignKey.IsDisabled || difCol.ForeignKey.IsNotTrusted) && !replacements.SchemaOnly ? sqlBuilder.EnableForeignKey(tab.Name, name) : null); }) ); SqlPreCommand? addIndices = Synchronizer.SynchronizeScript(Spacing.Double, modelTables, databaseTables, - createNew: (tn, tab) => modelIndices[tab].Values.Where(a => !(a is PrimaryClusteredIndex)).Select(index => SqlBuilder.CreateIndex(index, null)).Combine(Spacing.Simple), + createNew: (tn, tab) => modelIndices[tab].Values.Where(a => !(a is PrimaryClusteredIndex)).Select(index => sqlBuilder.CreateIndex(index, null)).Combine(Spacing.Simple), removeOld: null, mergeBoth: (tn, tab, dif) => { @@ -395,17 +398,17 @@ public static class SchemaSynchronizer var controlledIndexes = Synchronizer.SynchronizeScript(Spacing.Simple, modelIxs.Where(kvp => !(kvp.Value is PrimaryClusteredIndex)).ToDictionary(), dif.Indices.Where(kvp => !kvp.Value.IsPrimary).ToDictionary(), - createNew: (i, mix) => mix is UniqueTableIndex || mix.Columns.Any(isNew) || (replacements.Interactive ? SafeConsole.Ask(ref createMissingFreeIndexes, "Create missing non-unique index {0} in {1}?".FormatWith(mix.IndexName, tab.Name)) : true) ? SqlBuilder.CreateIndex(mix, checkUnique: replacements) : null, + createNew: (i, mix) => mix is UniqueTableIndex || mix.Columns.Any(isNew) || (replacements.Interactive ? SafeConsole.Ask(ref createMissingFreeIndexes, "Create missing non-unique index {0} in {1}?".FormatWith(mix.IndexName, tab.Name)) : true) ? sqlBuilder.CreateIndex(mix, checkUnique: replacements) : null, removeOld: null, - mergeBoth: (i, mix, dix) => !dix.IndexEquals(dif, mix) ? SqlBuilder.CreateIndex(mix, checkUnique: replacements) : - mix.IndexName != dix.IndexName ? SqlBuilder.RenameIndex(tab.Name, dix.IndexName, mix.IndexName) : null); + mergeBoth: (i, mix, dix) => !dix.IndexEquals(dif, mix) ? sqlBuilder.CreateIndex(mix, checkUnique: replacements) : + mix.IndexName != dix.IndexName ? sqlBuilder.RenameIndex(tab.Name, dix.IndexName, mix.IndexName) : null); return SqlPreCommand.Combine(Spacing.Simple, controlledIndexes); }); SqlPreCommand? addIndicesHistory = Synchronizer.SynchronizeScript(Spacing.Double, modelTablesHistory, databaseTablesHistory, - createNew: (tn, tab) => modelIndices[tab].Values.Where(a => a.GetType() == typeof(TableIndex)).Select(mix => SqlBuilder.CreateIndexBasic(mix, forHistoryTable: true)).Combine(Spacing.Simple), + createNew: (tn, tab) => modelIndices[tab].Values.Where(a => a.GetType() == typeof(TableIndex)).Select(mix => sqlBuilder.CreateIndexBasic(mix, forHistoryTable: true)).Combine(Spacing.Simple), removeOld: null, mergeBoth: (tn, tab, dif) => { @@ -418,10 +421,10 @@ public static class SchemaSynchronizer var controlledIndexes = Synchronizer.SynchronizeScript(Spacing.Simple, modelIxs.Where(kvp => kvp.Value.GetType() == typeof(TableIndex)).ToDictionary(), dif.Indices.Where(kvp => kvp.Value.Type != DiffIndexType.Clustered).ToDictionary(), - createNew: (i, mix) => mix is UniqueTableIndex || mix.Columns.Any(isNew) || (replacements.Interactive ? SafeConsole.Ask(ref createMissingFreeIndexes, "Create missing non-unique index {0} in {1}?".FormatWith(mix.IndexName, tab.Name)) : true) ? SqlBuilder.CreateIndexBasic(mix, forHistoryTable: true) : null, + createNew: (i, mix) => mix is UniqueTableIndex || mix.Columns.Any(isNew) || (replacements.Interactive ? SafeConsole.Ask(ref createMissingFreeIndexes, "Create missing non-unique index {0} in {1}?".FormatWith(mix.IndexName, tab.Name)) : true) ? sqlBuilder.CreateIndexBasic(mix, forHistoryTable: true) : null, removeOld: null, - mergeBoth: (i, mix, dix) => !dix.IndexEquals(dif, mix) ? SqlBuilder.CreateIndexBasic(mix, forHistoryTable: true) : - mix.IndexName != dix.IndexName ? SqlBuilder.RenameIndex(tab.SystemVersioned!.TableName, dix.IndexName, mix.IndexName) : null); + mergeBoth: (i, mix, dix) => !dix.IndexEquals(dif, mix) ? sqlBuilder.CreateIndexBasic(mix, forHistoryTable: true) : + mix.IndexName != dix.IndexName ? sqlBuilder.RenameIndex(tab.SystemVersioned!.TableName, dix.IndexName, mix.IndexName) : null); return SqlPreCommand.Combine(Spacing.Simple, controlledIndexes); }); @@ -432,8 +435,8 @@ public static class SchemaSynchronizer modelSchemas.ToDictionary(a => a.ToString()), databaseSchemas.ToDictionary(a => a.ToString()), createNew: null, - removeOld: (_, oldSN) => DropSchema(oldSN) ? SqlBuilder.DropSchema(oldSN) : null, - mergeBoth: (_, newSN, oldSN) => newSN.Equals(oldSN) ? null : SqlBuilder.DropSchema(oldSN) + removeOld: (_, oldSN) => DropSchema(oldSN) ? sqlBuilder.DropSchema(oldSN) : null, + mergeBoth: (_, newSN, oldSN) => newSN.Equals(oldSN) ? null : sqlBuilder.DropSchema(oldSN) ); return SqlPreCommand.Combine(Spacing.Triple, @@ -459,7 +462,7 @@ private static SqlPreCommand ForHistoryTable(SqlPreCommand sqlCommand, ITable ta return sqlCommand.Replace(new Regex(@$"\b{Regex.Escape(tab.Name.Name)}\b"), m => tab.SystemVersioned!.TableName.Name); } - private static SqlPreCommand? UpdateForeignKeyTypeChanged(ITable tab, DiffTable dif, IColumn tabCol, DiffColumn difCol, Func changeName, Dictionary> preRenameColumnsList) + private static SqlPreCommand? UpdateForeignKeyTypeChanged(SqlBuilder sqlBuilder, ITable tab, DiffTable dif, IColumn tabCol, DiffColumn difCol, Func changeName, Dictionary> preRenameColumnsList) { if(difCol.ForeignKey != null && tabCol.ReferenceTable != null) { @@ -478,7 +481,7 @@ private static SqlPreCommand ForHistoryTable(SqlPreCommand sqlCommand, ITable ta return new SqlPreCommandSimple( @$"UPDATE {tabAlias} -SET {tabCol.Name} = {fkAlias}.{tabCol.ReferenceTable.PrimaryKey.Name.SqlEscape()} +SET {tabCol.Name} = {fkAlias}.{tabCol.ReferenceTable.PrimaryKey.Name.SqlEscape(sqlBuilder.IsPostgres)} FROM {tab.Name} {tabAlias} JOIN {tabCol.ReferenceTable.Name} {fkAlias} ON {tabAlias}.{difCol.Name} = {fkAlias}.{oldId} "); @@ -495,10 +498,10 @@ private static SqlPreCommand UpdateCustom(ITable tab, IColumn tabCol, DiffColumn private static string GetZero(IColumn column) { - return (SqlBuilder.IsNumber(column.SqlDbType) ? "0" : - SqlBuilder.IsString(column.SqlDbType) ? "''" : - //SqlBuilder.IsDate(column.SqlDbType) ? "GetDate()" : - column.SqlDbType == SqlDbType.UniqueIdentifier ? Guid.Empty.ToString() : + return (column.DbType.IsNumber() ? "0" : + column.DbType.IsString() ? "''" : + column.DbType.IsDate() ? "GetDate()" : + column.DbType.IsGuid() ? Guid.Empty.ToString() : "?"); } @@ -509,21 +512,21 @@ private static bool StrongColumnChanges(ITable tab, DiffTable dif) .Any(t => (!t.tabCol.Nullable.ToBool() && t.difCol.Nullable) || !t.difCol.CompatibleTypes(t.tabCol)); } - private static SqlPreCommand UpdateCompatible(Replacements replacements, ITable tab, DiffTable dif, IColumn tabCol, DiffColumn difCol) + private static SqlPreCommand UpdateCompatible(SqlBuilder sqlBuilder, Replacements replacements, ITable tab, DiffTable dif, IColumn tabCol, DiffColumn difCol) { if (!(difCol.Nullable && !tabCol.Nullable.ToBool())) - return SqlBuilder.AlterTableAlterColumn(tab, tabCol, difCol.DefaultConstraint?.Name); + return sqlBuilder.AlterTableAlterColumn(tab, tabCol, difCol.DefaultConstraint?.Name); var defaultValue = GetDefaultValue(tab, tabCol, replacements, forNewColumn: false); if (defaultValue == "force") - return SqlBuilder.AlterTableAlterColumn(tab, tabCol, difCol.DefaultConstraint?.Name); + return sqlBuilder.AlterTableAlterColumn(tab, tabCol, difCol.DefaultConstraint?.Name); bool goBefore = difCol.Name != tabCol.Name; return SqlPreCommand.Combine(Spacing.Simple, NotNullUpdate(tab.Name, tabCol, defaultValue, goBefore), - SqlBuilder.AlterTableAlterColumn(tab, tabCol, difCol.DefaultConstraint?.Name) + sqlBuilder.AlterTableAlterColumn(tab, tabCol, difCol.DefaultConstraint?.Name) )!; } @@ -541,48 +544,50 @@ private static bool DifferentDatabase(ObjectName name, ObjectName name2) private static HashSet DefaultGetSchemas(List list) { + var sqlBuilder = Connector.Current.SqlBuilder; + var isPostgres = false; HashSet result = new HashSet(); foreach (var db in list) { using (Administrator.OverrideDatabaseInSysViews(db)) { - var schemaNames = Database.View().Select(s => s.name).ToList().Except(SqlBuilder.SystemSchemas); + var schemaNames = Database.View().Select(s => s.name).ToList().Except(sqlBuilder.SystemSchemas); - result.AddRange(schemaNames.Select(sn => new SchemaName(db, sn)).Where(a => !IgnoreSchema(a))); + result.AddRange(schemaNames.Select(sn => new SchemaName(db, sn, isPostgres)).Where(a => !IgnoreSchema(a))); } } return result; } - private static SqlPreCommand AlterTableAddColumnDefault(ITable table, IColumn column, Replacements rep, string? forceDefaultValue, HashSet hasValueFalse) + private static SqlPreCommand AlterTableAddColumnDefault(SqlBuilder sqlBuilder, ITable table, IColumn column, Replacements rep, string? forceDefaultValue, HashSet hasValueFalse) { if (column.Nullable == IsNullable.Yes || column.Identity || column.Default != null || column is ImplementationColumn) - return SqlBuilder.AlterTableAddColumn(table, column); + return sqlBuilder.AlterTableAddColumn(table, column); if (column.Nullable == IsNullable.Forced) { var hasValueColumn = table.GetHasValueColumn(column); if (hasValueColumn != null && hasValueFalse.Contains(hasValueColumn)) - return SqlBuilder.AlterTableAddColumn(table, column); + return sqlBuilder.AlterTableAddColumn(table, column); var defaultValue = GetDefaultValue(table, column, rep, forNewColumn: true, forceDefaultValue: forceDefaultValue); if (defaultValue == "force") - return SqlBuilder.AlterTableAddColumn(table, column); + return sqlBuilder.AlterTableAddColumn(table, column); var where = hasValueColumn != null ? $"{hasValueColumn.Name} = 1" : "??"; return SqlPreCommand.Combine(Spacing.Simple, - SqlBuilder.AlterTableAddColumn(table, column).Do(a => a.GoAfter = true), + sqlBuilder.AlterTableAddColumn(table, column).Do(a => a.GoAfter = true), new SqlPreCommandSimple($@"UPDATE {table.Name} SET - {column.Name} = {SqlBuilder.Quote(column.SqlDbType, defaultValue)} + {column.Name} = {sqlBuilder.Quote(column.DbType, defaultValue)} WHERE {where}"))!; } else { var defaultValue = GetDefaultValue(table, column, rep, forNewColumn: true, forceDefaultValue: forceDefaultValue); if (defaultValue == "force") - return SqlBuilder.AlterTableAddColumn(table, column); + return sqlBuilder.AlterTableAddColumn(table, column); if (column is FieldEmbedded.EmbeddedHasValueColumn hv && defaultValue == "0") hasValueFalse.Add(hv); @@ -590,40 +595,41 @@ private static SqlPreCommand AlterTableAddColumnDefault(ITable table, IColumn co var tempDefault = new SqlBuilder.DefaultConstraint( columnName: column.Name, name: "DF_TEMP_" + column.Name, - quotedDefinition: SqlBuilder.Quote(column.SqlDbType, defaultValue) + quotedDefinition: sqlBuilder.Quote(column.DbType, defaultValue) ); return SqlPreCommand.Combine(Spacing.Simple, - SqlBuilder.AlterTableAddColumn(table, column, tempDefault), - SqlBuilder.AlterTableDropConstraint(table.Name, tempDefault.Name))!; + sqlBuilder.AlterTableAddColumn(table, column, tempDefault), + sqlBuilder.AlterTableDropConstraint(table.Name, tempDefault.Name))!; } } - private static SqlPreCommand AlterTableAddColumnDefaultZero(ITable table, IColumn column) + private static SqlPreCommand AlterTableAddColumnDefaultZero(SqlBuilder sqlBuilder, ITable table, IColumn column) { if (column.Nullable == IsNullable.Yes || column.Identity || column.Default != null || column is ImplementationColumn) - return SqlBuilder.AlterTableAddColumn(table, column); + return sqlBuilder.AlterTableAddColumn(table, column); - var defaultValue = (SqlBuilder.IsNumber(column.SqlDbType) ? "0" : - SqlBuilder.IsString(column.SqlDbType) ? "''" : - SqlBuilder.IsDate(column.SqlDbType) ? "GetDate()" : - column.SqlDbType == SqlDbType.UniqueIdentifier ? "'00000000-0000-0000-0000-000000000000'" : - "?"); + var defaultValue = + column.DbType.IsNumber()? "0" : + column.DbType.IsString()? "''" : + column.DbType.IsDate() ? "GetDate()" : + column.DbType.IsGuid() ? "'00000000-0000-0000-0000-000000000000'" : + "?"; var tempDefault = new SqlBuilder.DefaultConstraint( columnName: column.Name, name: "DF_TEMP_COPY_" + column.Name, - quotedDefinition: SqlBuilder.Quote(column.SqlDbType, defaultValue) + quotedDefinition: sqlBuilder.Quote(column.DbType, defaultValue) ); return SqlPreCommand.Combine(Spacing.Simple, - SqlBuilder.AlterTableAddColumn(table, column, tempDefault), - SqlBuilder.AlterTableDropConstraint(table.Name, tempDefault.Name))!; + sqlBuilder.AlterTableAddColumn(table, column, tempDefault), + sqlBuilder.AlterTableDropConstraint(table.Name, tempDefault.Name))!; } public static string GetDefaultValue(ITable table, IColumn column, Replacements rep, bool forNewColumn, string? forceDefaultValue = null) { - if (column is SystemVersionedInfo.Column svc) + if (column is SystemVersionedInfo.SqlServerPeriodColumn svc) { var date = svc.SystemVersionColumnType == SystemVersionedInfo.ColumnType.Start ? DateTime.MinValue : DateTime.MaxValue; @@ -631,17 +637,17 @@ public static string GetDefaultValue(ITable table, IColumn column, Replacements } string typeDefault = forceDefaultValue ?? - (SqlBuilder.IsNumber(column.SqlDbType) ? "0" : - SqlBuilder.IsString(column.SqlDbType) ? "''" : - SqlBuilder.IsDate(column.SqlDbType) ? "GetDate()" : - column.SqlDbType == SqlDbType.UniqueIdentifier ? "NEWID()" : + (column.DbType.IsNumber() ? "0" : + column.DbType.IsString() ? "''" : + column.DbType.IsDate() ? "GetDate()" : + column.DbType.IsGuid() ? "NEWID()" : "?"); string defaultValue = rep.Interactive ? SafeConsole.AskString($"Default value for '{table.Name.Name}.{column.Name}'? ([Enter] for {typeDefault} or 'force' if there are no {(forNewColumn ? "rows" : "nulls")}) ", stringValidator: str => null) : ""; if (defaultValue == "force") return defaultValue; - if (defaultValue.HasText() && SqlBuilder.IsString(column.SqlDbType) && !defaultValue.Contains("'")) + if (defaultValue.HasText() && column.DbType.IsString() && !defaultValue.Contains("'")) defaultValue = "'" + defaultValue + "'"; if (string.IsNullOrEmpty(defaultValue)) @@ -735,6 +741,8 @@ public static Dictionary DefaultGetDatabaseDescription(List allTables = new List(); + var isPostgres = false; + foreach (var db in databases) { SafeConsole.WriteColor(ConsoleColor.Cyan, '.'); @@ -752,7 +760,7 @@ public static Dictionary DefaultGetDatabaseDescription(List !t.ExtendedProperties().Any(a => a.name == "microsoft_database_tools_support")) //IntelliSense bug select new DiffTable { - Name = new ObjectName(new SchemaName(db, s.name), t.name), + Name = new ObjectName(new SchemaName(db, s.name, isPostgres), t.name, isPostgres), TemporalType = !con.SupportsTemporalTables ? SysTableTemporalType.None: t.temporal_type, @@ -771,12 +779,12 @@ join ec in t.Columns() on p.end_column_id equals ec.column_id TemporalTableName = !con.SupportsTemporalTables || t.history_table_id == null ? null : Database.View() .Where(ht => ht.object_id == t.history_table_id) - .Select(ht => new ObjectName(new SchemaName(db, ht.Schema().name), ht.name)) + .Select(ht => new ObjectName(new SchemaName(db, ht.Schema().name, isPostgres), ht.name, isPostgres)) .SingleOrDefault(), PrimaryKeyName = (from k in t.KeyConstraints() where k.type == "PK" - select k.name == null ? null : new ObjectName(new SchemaName(db, k.Schema().name), k.name)) + select k.name == null ? null : new ObjectName(new SchemaName(db, k.Schema().name, isPostgres), k.name, isPostgres)) .SingleOrDefaultEx(), Columns = (from c in t.Columns() @@ -786,7 +794,7 @@ join ctr in Database.View().DefaultIfEmpty() on c.default select new DiffColumn { Name = c.name, - SqlDbType = sysType == null ? SqlDbType.Udt : ToSqlDbType(sysType.name), + DbType = new AbstractDbType(sysType == null ? SqlDbType.Udt : ToSqlDbType(sysType.name)), UserTypeName = sysType == null ? userType.name : null, Nullable = c.is_nullable, Collation = c.collation_name == sysDb.collation_name ? null : c.collation_name, @@ -807,9 +815,9 @@ join ctr in Database.View().DefaultIfEmpty() on c.default join rt in Database.View() on fk.referenced_object_id equals rt.object_id select new DiffForeignKey { - Name = new ObjectName(new SchemaName(db, fk.Schema().name), fk.name), + Name = new ObjectName(new SchemaName(db, fk.Schema().name, isPostgres), fk.name, isPostgres), IsDisabled = fk.is_disabled, - TargetTable = new ObjectName(new SchemaName(db, rt.Schema().name), rt.name), + TargetTable = new ObjectName(new SchemaName(db, rt.Schema().name, isPostgres), rt.name, isPostgres), Columns = fk.ForeignKeyColumns().Select(fkc => new DiffForeignKeyColumn { Parent = t.Columns().Single(c => c.column_id == fkc.parent_column_id).name, @@ -1002,6 +1010,7 @@ private static Entity Clone(Entity current) return null; var list = Schema.Current.DatabaseNames().Select(a => a?.ToString()).ToList(); + var sqlBuilder = Connector.Current.SqlBuilder; if (list.Contains(null)) { @@ -1016,8 +1025,8 @@ private static Entity Clone(Entity current) var cmd = replacements.WithReplacedDatabaseName().Using(_ => results.Select((a, i) => SqlPreCommand.Combine(Spacing.Simple, !a.snapshot_isolation_state || !a.is_read_committed_snapshot_on ? DisconnectUsers(a.name!/*CSBUG*/, "SPID" + i) : null, - !a.snapshot_isolation_state ? SqlBuilder.SetSnapshotIsolation(a.name!/*CSBUG*/, true) : null, - !a.is_read_committed_snapshot_on ? SqlBuilder.MakeSnapshotIsolationDefault(a.name!/*CSBUG*/, true) : null)).Combine(Spacing.Double)); + !a.snapshot_isolation_state ? sqlBuilder.SetSnapshotIsolation(a.name!/*CSBUG*/, true) : null, + !a.is_read_committed_snapshot_on ? sqlBuilder.MakeSnapshotIsolationDefault(a.name!/*CSBUG*/, true) : null)).Combine(Spacing.Double)); if (cmd == null) return null; @@ -1210,7 +1219,7 @@ public class DiffDefaultConstraint public class DiffColumn { public string Name; - public SqlDbType SqlDbType; + public AbstractDbType DbType; public string? UserTypeName; public bool Nullable; public string? Collation; @@ -1228,12 +1237,11 @@ public class DiffColumn public bool ColumnEquals(IColumn other, bool ignorePrimaryKey, bool ignoreIdentity, bool ignoreGenerateAlways) { - var result = - SqlDbType == other.SqlDbType + var result = DbType.Equals(other.DbType) && Collation == other.Collation && StringComparer.InvariantCultureIgnoreCase.Equals(UserTypeName, other.UserDefinedTypeName) && Nullable == (other.Nullable.ToBool()) - && (other.Size == null || other.Size.Value == Precision || other.Size.Value == Length / BytesPerChar(other.SqlDbType) || other.Size.Value == int.MaxValue && Length == -1) + && (other.Size == null || other.Size.Value == Precision || other.Size.Value == Length / BytesPerChar(other.DbType.SqlServer) || other.Size.Value == int.MaxValue && Length == -1) && (other.Scale == null || other.Scale.Value == Scale) && (ignoreIdentity || Identity == other.Identity) && (ignorePrimaryKey || PrimaryKey == other.PrimaryKey) @@ -1286,7 +1294,7 @@ public DiffColumn Clone() Nullable = Nullable, Precision = Precision, Scale = Scale, - SqlDbType = SqlDbType, + DbType = DbType, UserTypeName = UserTypeName, }; } @@ -1297,14 +1305,27 @@ public override string ToString() } internal bool CompatibleTypes(IColumn tabCol) + { + if (Schema.Current.Settings.IsPostgres) + return CompatibleTypes_Postgres(this.DbType.PostgreSql, tabCol.DbType.PostgreSql); + else + return CompatibleTypes_SqlServer(this.DbType.SqlServer, tabCol.DbType.SqlServer); + } + + private bool CompatibleTypes_Postgres(NpgsqlDbType fromType, NpgsqlDbType toType) + { + throw new NotImplementedException(); + } + + private bool CompatibleTypes_SqlServer(SqlDbType fromType, SqlDbType toType) { //https://docs.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql - switch (this.SqlDbType) + switch (fromType) { //BLACKLIST!! case SqlDbType.Binary: case SqlDbType.VarBinary: - switch (tabCol.SqlDbType) + switch (fromType) { case SqlDbType.Float: case SqlDbType.Real: @@ -1321,11 +1342,11 @@ internal bool CompatibleTypes(IColumn tabCol) case SqlDbType.NChar: case SqlDbType.NVarChar: - return tabCol.SqlDbType != SqlDbType.Image; + return fromType != SqlDbType.Image; case SqlDbType.DateTime: case SqlDbType.SmallDateTime: - switch (tabCol.SqlDbType) + switch (fromType) { case SqlDbType.UniqueIdentifier: case SqlDbType.Image: @@ -1339,18 +1360,18 @@ internal bool CompatibleTypes(IColumn tabCol) } case SqlDbType.Date: - if (tabCol.SqlDbType == SqlDbType.Time) + if (fromType == SqlDbType.Time) return false; goto case SqlDbType.DateTime2; case SqlDbType.Time: - if (tabCol.SqlDbType == SqlDbType.Date) + if (fromType == SqlDbType.Date) return false; goto case SqlDbType.DateTime2; case SqlDbType.DateTimeOffset: case SqlDbType.DateTime2: - switch (tabCol.SqlDbType) + switch (fromType) { case SqlDbType.Decimal: case SqlDbType.Float: @@ -1383,7 +1404,7 @@ internal bool CompatibleTypes(IColumn tabCol) case SqlDbType.Money: case SqlDbType.SmallMoney: case SqlDbType.Bit: - switch (tabCol.SqlDbType) + switch (fromType) { case SqlDbType.Date: case SqlDbType.Time: @@ -1401,7 +1422,7 @@ internal bool CompatibleTypes(IColumn tabCol) } case SqlDbType.Timestamp: - switch (tabCol.SqlDbType) + switch (fromType) { case SqlDbType.NChar: case SqlDbType.NVarChar: @@ -1420,7 +1441,7 @@ internal bool CompatibleTypes(IColumn tabCol) return true; } case SqlDbType.Variant: - switch (tabCol.SqlDbType) + switch (fromType) { case SqlDbType.Timestamp: case SqlDbType.Image: @@ -1435,7 +1456,7 @@ internal bool CompatibleTypes(IColumn tabCol) //WHITELIST!! case SqlDbType.UniqueIdentifier: - switch (tabCol.SqlDbType) + switch (fromType) { case SqlDbType.Binary: case SqlDbType.VarBinary: @@ -1450,7 +1471,7 @@ internal bool CompatibleTypes(IColumn tabCol) return false; } case SqlDbType.Image: - switch (tabCol.SqlDbType) + switch (fromType) { case SqlDbType.Binary: case SqlDbType.Image: @@ -1462,7 +1483,7 @@ internal bool CompatibleTypes(IColumn tabCol) } case SqlDbType.NText: case SqlDbType.Text: - switch (tabCol.SqlDbType) + switch (fromType) { case SqlDbType.Char: case SqlDbType.VarChar: @@ -1477,7 +1498,7 @@ internal bool CompatibleTypes(IColumn tabCol) } case SqlDbType.Xml: case SqlDbType.Udt: - switch (tabCol.SqlDbType) + switch (fromType) { case SqlDbType.Binary: case SqlDbType.VarBinary: diff --git a/Signum.Engine/Engine/Scripts/versioning_function.sql b/Signum.Engine/Engine/Scripts/versioning_function.sql new file mode 100644 index 0000000000..b9cdda91db --- /dev/null +++ b/Signum.Engine/Engine/Scripts/versioning_function.sql @@ -0,0 +1,186 @@ +CREATE OR REPLACE FUNCTION versioning() +RETURNS TRIGGER AS $$ +DECLARE + sys_period text; + history_table text; + manipulate jsonb; + commonColumns text[]; + time_stamp_to_use timestamptz := current_timestamp; + range_lower timestamptz; + transaction_info txid_snapshot; + existing_range tstzrange; + holder record; + holder2 record; + pg_version integer; +BEGIN + -- version 0.2.0 + + IF TG_WHEN != 'BEFORE' OR TG_LEVEL != 'ROW' THEN + RAISE TRIGGER_PROTOCOL_VIOLATED USING + MESSAGE = 'function "versioning" must be fired BEFORE ROW'; + END IF; + + IF TG_OP != 'INSERT' AND TG_OP != 'UPDATE' AND TG_OP != 'DELETE' THEN + RAISE TRIGGER_PROTOCOL_VIOLATED USING + MESSAGE = 'function "versioning" must be fired for INSERT or UPDATE or DELETE'; + END IF; + + IF TG_NARGS != 3 THEN + RAISE INVALID_PARAMETER_VALUE USING + MESSAGE = 'wrong number of parameters for function "versioning"', + HINT = 'expected 3 parameters but got ' || TG_NARGS; + END IF; + + sys_period := TG_ARGV[0]; + history_table := TG_ARGV[1]; + + -- check if sys_period exists on original table + SELECT atttypid, attndims INTO holder FROM pg_attribute WHERE attrelid = TG_RELID AND attname = sys_period AND NOT attisdropped; + IF NOT FOUND THEN + RAISE 'column "%" of relation "%" does not exist', sys_period, TG_TABLE_NAME USING + ERRCODE = 'undefined_column'; + END IF; + IF holder.atttypid != to_regtype('tstzrange') THEN + IF holder.attndims > 0 THEN + RAISE 'system period column "%" of relation "%" is not a range but an array', sys_period, TG_TABLE_NAME USING + ERRCODE = 'datatype_mismatch'; + END IF; + + SELECT rngsubtype INTO holder2 FROM pg_range WHERE rngtypid = holder.atttypid; + IF FOUND THEN + RAISE 'system period column "%" of relation "%" is not a range of timestamp with timezone but of type %', sys_period, TG_TABLE_NAME, format_type(holder2.rngsubtype, null) USING + ERRCODE = 'datatype_mismatch'; + END IF; + + RAISE 'system period column "%" of relation "%" is not a range but type %', sys_period, TG_TABLE_NAME, format_type(holder.atttypid, null) USING + ERRCODE = 'datatype_mismatch'; + END IF; + + IF TG_OP = 'UPDATE' OR TG_OP = 'DELETE' THEN + -- Ignore rows already modified in this transaction + transaction_info := txid_current_snapshot(); + IF OLD.xmin::text >= (txid_snapshot_xmin(transaction_info) % (2^32)::bigint)::text + AND OLD.xmin::text <= (txid_snapshot_xmax(transaction_info) % (2^32)::bigint)::text THEN + IF TG_OP = 'DELETE' THEN + RETURN OLD; + END IF; + + RETURN NEW; + END IF; + + SELECT current_setting('server_version_num')::integer + INTO pg_version; + + -- to support postgres < 9.6 + IF pg_version < 90600 THEN + -- check if history table exits + IF to_regclass(history_table::cstring) IS NULL THEN + RAISE 'relation "%" does not exist', history_table; + END IF; + ELSE + IF to_regclass(history_table) IS NULL THEN + RAISE 'relation "%" does not exist', history_table; + END IF; + END IF; + + -- check if history table has sys_period + IF NOT EXISTS(SELECT * FROM pg_attribute WHERE attrelid = history_table::regclass AND attname = sys_period AND NOT attisdropped) THEN + RAISE 'history relation "%" does not contain system period column "%"', history_table, sys_period USING + HINT = 'history relation must contain system period column with the same name and data type as the versioned one'; + END IF; + + EXECUTE format('SELECT $1.%I', sys_period) USING OLD INTO existing_range; + + IF existing_range IS NULL THEN + RAISE 'system period column "%" of relation "%" must not be null', sys_period, TG_TABLE_NAME USING + ERRCODE = 'null_value_not_allowed'; + END IF; + + IF isempty(existing_range) OR NOT upper_inf(existing_range) THEN + RAISE 'system period column "%" of relation "%" contains invalid value', sys_period, TG_TABLE_NAME USING + ERRCODE = 'data_exception', + DETAIL = 'valid ranges must be non-empty and unbounded on the high side'; + END IF; + + IF TG_ARGV[2] = 'true' THEN + -- mitigate update conflicts + range_lower := lower(existing_range); + IF range_lower >= time_stamp_to_use THEN + time_stamp_to_use := range_lower + interval '1 microseconds'; + END IF; + END IF; + + WITH history AS + (SELECT attname, atttypid + FROM pg_attribute + WHERE attrelid = history_table::regclass + AND attnum > 0 + AND NOT attisdropped), + main AS + (SELECT attname, atttypid + FROM pg_attribute + WHERE attrelid = TG_RELID + AND attnum > 0 + AND NOT attisdropped) + SELECT + history.attname AS history_name, + main.attname AS main_name, + history.atttypid AS history_type, + main.atttypid AS main_type + INTO holder + FROM history + INNER JOIN main + ON history.attname = main.attname + WHERE + history.atttypid != main.atttypid; + + IF FOUND THEN + RAISE 'column "%" of relation "%" is of type % but column "%" of history relation "%" is of type %', + holder.main_name, TG_TABLE_NAME, format_type(holder.main_type, null), holder.history_name, history_table, format_type(holder.history_type, null) + USING ERRCODE = 'datatype_mismatch'; + END IF; + + WITH history AS + (SELECT attname + FROM pg_attribute + WHERE attrelid = history_table::regclass + AND attnum > 0 + AND NOT attisdropped), + main AS + (SELECT attname + FROM pg_attribute + WHERE attrelid = TG_RELID + AND attnum > 0 + AND NOT attisdropped) + SELECT array_agg(quote_ident(history.attname)) INTO commonColumns + FROM history + INNER JOIN main + ON history.attname = main.attname + AND history.attname != sys_period; + + EXECUTE ('INSERT INTO ' || + CASE split_part(history_table, '.', 2) + WHEN '' THEN + quote_ident(history_table) + ELSE + quote_ident(split_part(history_table, '.', 1)) || '.' || quote_ident(split_part(history_table, '.', 2)) + END || + '(' || + array_to_string(commonColumns , ',') || + ',' || + quote_ident(sys_period) || + ') VALUES ($1.' || + array_to_string(commonColumns, ',$1.') || + ',tstzrange($2, $3, ''[)''))') + USING OLD, range_lower, time_stamp_to_use; + END IF; + + IF TG_OP = 'UPDATE' OR TG_OP = 'INSERT' THEN + manipulate := jsonb_set('{}'::jsonb, ('{' || sys_period || '}')::text[], to_jsonb(tstzrange(time_stamp_to_use, null, '[)'))); + + RETURN jsonb_populate_record(NEW, manipulate); + END IF; + + RETURN OLD; +END; +$$ LANGUAGE plpgsql; diff --git a/Signum.Engine/Engine/Scripts/versioning_function_nochecks.sql b/Signum.Engine/Engine/Scripts/versioning_function_nochecks.sql new file mode 100644 index 0000000000..b9dd1d1d9e --- /dev/null +++ b/Signum.Engine/Engine/Scripts/versioning_function_nochecks.sql @@ -0,0 +1,83 @@ +CREATE OR REPLACE FUNCTION versioning() +RETURNS TRIGGER AS $$ +DECLARE + sys_period text; + history_table text; + manipulate jsonb; + commonColumns text[]; + time_stamp_to_use timestamptz := current_timestamp; + range_lower timestamptz; + transaction_info txid_snapshot; + existing_range tstzrange; +BEGIN + -- version 0.0.1 + + sys_period := TG_ARGV[0]; + history_table := TG_ARGV[1]; + + IF TG_OP = 'UPDATE' OR TG_OP = 'DELETE' THEN + -- Ignore rows already modified in this transaction + transaction_info := txid_current_snapshot(); + IF OLD.xmin::text >= (txid_snapshot_xmin(transaction_info) % (2^32)::bigint)::text + AND OLD.xmin::text <= (txid_snapshot_xmax(transaction_info) % (2^32)::bigint)::text THEN + IF TG_OP = 'DELETE' THEN + RETURN OLD; + END IF; + + RETURN NEW; + END IF; + + EXECUTE format('SELECT $1.%I', sys_period) USING OLD INTO existing_range; + + IF TG_ARGV[2] = 'true' THEN + -- mitigate update conflicts + range_lower := lower(existing_range); + IF range_lower >= time_stamp_to_use THEN + time_stamp_to_use := range_lower + interval '1 microseconds'; + END IF; + END IF; + + WITH history AS + (SELECT attname + FROM pg_attribute + WHERE attrelid = history_table::regclass + AND attnum > 0 + AND NOT attisdropped), + main AS + (SELECT attname + FROM pg_attribute + WHERE attrelid = TG_RELID + AND attnum > 0 + AND NOT attisdropped) + SELECT array_agg(quote_ident(history.attname)) INTO commonColumns + FROM history + INNER JOIN main + ON history.attname = main.attname + AND history.attname != sys_period; + + EXECUTE ('INSERT INTO ' || + CASE split_part(history_table, '.', 2) + WHEN '' THEN + quote_ident(history_table) + ELSE + quote_ident(split_part(history_table, '.', 1)) || '.' || quote_ident(split_part(history_table, '.', 2)) + END || + '(' || + array_to_string(commonColumns , ',') || + ',' || + quote_ident(sys_period) || + ') VALUES ($1.' || + array_to_string(commonColumns, ',$1.') || + ',tstzrange($2, $3, ''[)''))') + USING OLD, range_lower, time_stamp_to_use; + END IF; + + IF TG_OP = 'UPDATE' OR TG_OP = 'INSERT' THEN + manipulate := jsonb_set('{}'::jsonb, ('{' || sys_period || '}')::text[], to_jsonb(tstzrange(time_stamp_to_use, null, '[)'))); + + RETURN jsonb_populate_record(NEW, manipulate); + END IF; + + RETURN OLD; +END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/Signum.Engine/Engine/SqlBuilder.cs b/Signum.Engine/Engine/SqlBuilder.cs index ee81727a35..8078b62c50 100644 --- a/Signum.Engine/Engine/SqlBuilder.cs +++ b/Signum.Engine/Engine/SqlBuilder.cs @@ -7,9 +7,20 @@ namespace Signum.Engine { - public static class SqlBuilder + public class SqlBuilder { - public static List SystemSchemas = new List() + Connector connector; + bool isPostgres; + + public bool IsPostgres => isPostgres; + + internal SqlBuilder(Connector connector) + { + this.connector = connector; + this.isPostgres = connector.Schema.Settings.IsPostgres; + } + + public List SystemSchemas = new List() { "dbo", "guest", @@ -27,25 +38,43 @@ public static class SqlBuilder }; #region Create Tables - public static SqlPreCommandSimple CreateTableSql(ITable t) + public SqlPreCommand CreateTableSql(ITable t) { - var primaryKeyConstraint = t.PrimaryKey == null ? null : "CONSTRAINT {0} PRIMARY KEY CLUSTERED ({1} ASC)".FormatWith(PrimaryClusteredIndex.GetPrimaryKeyName(t.Name), t.PrimaryKey.Name.SqlEscape()); + var primaryKeyConstraint = t.PrimaryKey == null ? null : + isPostgres ? + "CONSTRAINT {0} PRIMARY KEY ({1})".FormatWith(PrimaryClusteredIndex.GetPrimaryKeyName(t.Name), t.PrimaryKey.Name.SqlEscape(isPostgres)) : + "CONSTRAINT {0} PRIMARY KEY CLUSTERED ({1} ASC)".FormatWith(PrimaryClusteredIndex.GetPrimaryKeyName(t.Name), t.PrimaryKey.Name.SqlEscape(isPostgres)); - var systemPeriod = t.SystemVersioned == null ? null : Period(t.SystemVersioned); + var systemPeriod = t.SystemVersioned == null || IsPostgres ? null : Period(t.SystemVersioned); - var columns = t.Columns.Values.Select(c => SqlBuilder.CreateColumn(c, GetDefaultConstaint(t, c), isChange: false)) + var columns = t.Columns.Values.Select(c => this.CreateColumn(c, GetDefaultConstaint(t, c), isChange: false)) .And(primaryKeyConstraint) .And(systemPeriod) .NotNull() .ToString(",\r\n"); - var systemVersioning = t.SystemVersioned == null ? null : + var systemVersioning = t.SystemVersioned == null || IsPostgres ? null : $"\r\nWITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = {t.SystemVersioned.TableName}))"; - return new SqlPreCommandSimple($"CREATE TABLE {t.Name}(\r\n{columns}\r\n)" + systemVersioning); + var result = new SqlPreCommandSimple($"CREATE TABLE {t.Name}(\r\n{columns}\r\n)" + systemVersioning + ";"); + + if (!(IsPostgres && t.SystemVersioned != null)) + return result; + + return new[] + { + result, + new SqlPreCommandSimple($"CREATE TABLE {t.SystemVersioned.TableName}(LIKE {t.Name});"), + new SqlPreCommandSimple(@$"CREATE TRIGGER versioning_trigger +BEFORE INSERT OR UPDATE OR DELETE ON {t.Name} +FOR EACH ROW EXECUTE PROCEDURE versioning( + 'sys_period', '{t.Name.Name}', true +);") + }.Combine(Spacing.Simple)!; + } - public static SqlPreCommand DropTable(DiffTable diffTable) + public SqlPreCommand DropTable(DiffTable diffTable) { if (diffTable.TemporalTableName == null) return DropTable(diffTable.Name); @@ -57,17 +86,22 @@ public static SqlPreCommand DropTable(DiffTable diffTable) )!; } - public static SqlPreCommandSimple DropTable(ObjectName tableName) + public SqlPreCommandSimple DropTable(ObjectName tableName) + { + return new SqlPreCommandSimple("DROP TABLE {0};".FormatWith(tableName)); + } + + public SqlPreCommandSimple DropView(ObjectName viewName) { - return new SqlPreCommandSimple("DROP TABLE {0}".FormatWith(tableName)); + return new SqlPreCommandSimple("DROP VIEW {0};".FormatWith(viewName)); } - public static SqlPreCommandSimple DropView(ObjectName viewName) + public SqlPreCommandSimple CreateExtensionIfNotExist(string extensionName) { - return new SqlPreCommandSimple("DROP VIEW {0}".FormatWith(viewName)); + return new SqlPreCommandSimple($"CREATE EXTENSION IF NOT EXISTS \"{ extensionName }\";"); } - static SqlPreCommand DropViewIndex(ObjectName viewName, string index) + SqlPreCommand DropViewIndex(ObjectName viewName, string index) { return new[]{ DropIndex(viewName, index), @@ -75,97 +109,50 @@ static SqlPreCommand DropViewIndex(ObjectName viewName, string index) }.Combine(Spacing.Simple)!; } - public static SqlPreCommand AlterTableAddPeriod(ITable table) + public SqlPreCommand AlterTableAddPeriod(ITable table) { return new SqlPreCommandSimple($"ALTER TABLE {table.Name} ADD {Period(table.SystemVersioned!)}"); } - static string Period(SystemVersionedInfo sv) { + string? Period(SystemVersionedInfo sv) { if (!Connector.Current.SupportsTemporalTables) throw new InvalidOperationException($"The current connector '{Connector.Current}' does not support Temporal Tables"); - return $"PERIOD FOR SYSTEM_TIME ({sv.StartColumnName.SqlEscape()}, {sv.EndColumnName.SqlEscape()})"; + return $"PERIOD FOR SYSTEM_TIME ({sv.StartColumnName!.SqlEscape(isPostgres)}, {sv.EndColumnName!.SqlEscape(isPostgres)})"; } - public static SqlPreCommand AlterTableDropPeriod(ITable table) + public SqlPreCommand AlterTableDropPeriod(ITable table) { return new SqlPreCommandSimple($"ALTER TABLE {table.Name} DROP PERIOD FOR SYSTEM_TIME"); } - public static SqlPreCommand AlterTableEnableSystemVersioning(ITable table) + public SqlPreCommand AlterTableEnableSystemVersioning(ITable table) { return new SqlPreCommandSimple($"ALTER TABLE {table.Name} SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = {table.SystemVersioned!.TableName}))"); } - public static SqlPreCommandSimple AlterTableDisableSystemVersioning(ObjectName tableName) + public SqlPreCommandSimple AlterTableDisableSystemVersioning(ObjectName tableName) { return new SqlPreCommandSimple($"ALTER TABLE {tableName} SET (SYSTEM_VERSIONING = OFF)"); } - public static SqlPreCommand AlterTableDropColumn(ITable table, string columnName) + public SqlPreCommand AlterTableDropColumn(ITable table, string columnName) { - return new SqlPreCommandSimple("ALTER TABLE {0} DROP COLUMN {1}".FormatWith(table.Name, columnName.SqlEscape())); + return new SqlPreCommandSimple("ALTER TABLE {0} DROP COLUMN {1}".FormatWith(table.Name, columnName.SqlEscape(isPostgres))); } - public static SqlPreCommand AlterTableAddColumn(ITable table, IColumn column, SqlBuilder.DefaultConstraint? tempDefault = null) + public SqlPreCommand AlterTableAddColumn(ITable table, IColumn column, SqlBuilder.DefaultConstraint? tempDefault = null) { return new SqlPreCommandSimple("ALTER TABLE {0} ADD {1}".FormatWith(table.Name, CreateColumn(column, tempDefault ?? GetDefaultConstaint(table, column), isChange: false))); } - public static SqlPreCommand AlterTableAddOldColumn(ITable table, DiffColumn column) + public SqlPreCommand AlterTableAddOldColumn(ITable table, DiffColumn column) { return new SqlPreCommandSimple("ALTER TABLE {0} ADD {1}".FormatWith(table.Name, CreateOldColumn(column))); } - public static bool IsNumber(SqlDbType sqlDbType) - { - switch (sqlDbType) - { - case SqlDbType.BigInt: - case SqlDbType.Float: - case SqlDbType.Decimal: - case SqlDbType.Int: - case SqlDbType.Bit: - case SqlDbType.Money: - case SqlDbType.Real: - case SqlDbType.TinyInt: - case SqlDbType.SmallInt: - case SqlDbType.SmallMoney: - return true; - } - - return false; - } - - public static bool IsString(SqlDbType sqlDbType) - { - switch (sqlDbType) - { - case SqlDbType.NText: - case SqlDbType.NVarChar: - case SqlDbType.Text: - case SqlDbType.VarChar: - return true; - } - - return false; - } - - public static bool IsDate(SqlDbType sqlDbType) - { - switch (sqlDbType) - { - case SqlDbType.DateTime: - case SqlDbType.DateTime2: - case SqlDbType.DateTimeOffset: - return true; - } - - return false; - } - - public static SqlPreCommand AlterTableAlterColumn(ITable table, IColumn column, string? defaultConstraintName = null, ObjectName? forceTableName = null) + public SqlPreCommand AlterTableAlterColumn(ITable table, IColumn column, string? defaultConstraintName = null, ObjectName? forceTableName = null) { var alterColumn = new SqlPreCommandSimple("ALTER TABLE {0} ALTER COLUMN {1}".FormatWith(forceTableName ?? table.Name, CreateColumn(column, null, isChange: true))); @@ -181,12 +168,12 @@ public static SqlPreCommand AlterTableAlterColumn(ITable table, IColumn column, )!; } - public static DefaultConstraint? GetDefaultConstaint(ITable t, IColumn c) + public DefaultConstraint? GetDefaultConstaint(ITable t, IColumn c) { if (c.Default == null) return null; - return new DefaultConstraint(c.Name, $"DF_{t.Name.Name}_{c.Name}", Quote(c.SqlDbType, c.Default)); + return new DefaultConstraint(c.Name, $"DF_{t.Name.Name}_{c.Name}", Quote(c.DbType, c.Default)); } public class DefaultConstraint @@ -203,7 +190,7 @@ public DefaultConstraint(string columnName, string name, string quotedDefinition } } - public static string CreateOldColumn(DiffColumn c) + public string CreateOldColumn(DiffColumn c) { string fullType = GetColumnType(c); @@ -214,7 +201,7 @@ public static string CreateOldColumn(DiffColumn c) var defaultConstraint = c.DefaultConstraint!= null ? $"CONSTRAINT {c.DefaultConstraint.Name} DEFAULT " + c.DefaultConstraint.Definition : null; return $" ".Combine( - c.Name.SqlEscape(), + c.Name.SqlEscape(isPostgres), fullType, c.Identity ? "IDENTITY " : null, generatedAlways, @@ -224,20 +211,20 @@ public static string CreateOldColumn(DiffColumn c) ); } - public static string CreateColumn(IColumn c, DefaultConstraint? constraint, bool isChange) + public string CreateColumn(IColumn c, DefaultConstraint? constraint, bool isChange) { string fullType = GetColumnType(c); - var generatedAlways = c is SystemVersionedInfo.Column svc ? + var generatedAlways = c is SystemVersionedInfo.SqlServerPeriodColumn svc ? $"GENERATED ALWAYS AS ROW {(svc.SystemVersionColumnType == SystemVersionedInfo.ColumnType.Start ? "START" : "END")} HIDDEN" : null; var defaultConstraint = constraint != null ? $"CONSTRAINT {constraint.Name} DEFAULT " + constraint.QuotedDefinition : null; return $" ".Combine( - c.Name.SqlEscape(), + c.Name.SqlEscape(isPostgres), fullType, - c.Identity && !isChange ? "IDENTITY " : null, + c.Identity && !isChange ? (isPostgres? "GENERATED ALWAYS AS IDENTITY": "IDENTITY") : null, generatedAlways, c.Collation != null ? ("COLLATE " + c.Collation) : null, c.Nullable.ToBool() ? "NULL" : "NOT NULL", @@ -245,31 +232,34 @@ public static string CreateColumn(IColumn c, DefaultConstraint? constraint, bool ); } - public static string GetColumnType(IColumn c) + public string GetColumnType(IColumn c) { - return (c.SqlDbType == SqlDbType.Udt ? c.UserDefinedTypeName : c.SqlDbType.ToString().ToUpper()) + GetSizeScale(c.Size, c.Scale); + return c.UserDefinedTypeName ?? (c.DbType.ToString(IsPostgres) + GetSizeScale(c.Size, c.Scale)); } - public static string GetColumnType(DiffColumn c) + public string GetColumnType(DiffColumn c) { - return (c.SqlDbType == SqlDbType.Udt ? c.UserTypeName! : c.SqlDbType.ToString().ToUpper()) /*+ GetSizeScale(Math.Max(c.Length, c.Precision), c.Scale)*/; + return c.UserTypeName ?? c.DbType.ToString(IsPostgres) /*+ GetSizeScale(Math.Max(c.Length, c.Precision), c.Scale)*/; } - public static string Quote(SqlDbType type, string @default) + public string Quote(AbstractDbType type, string @default) { - if (IsString(type) && !(@default.StartsWith("'") && @default.StartsWith("'"))) + if (type.IsString() && !(@default.StartsWith("'") && @default.StartsWith("'"))) return "'" + @default + "'"; + if (@default == "NEWID()" && IsPostgres) //hacky + return "uuid_generate_v1()"; + return @default; } - public static string GetSizeScale(int? size, int? scale) + public string GetSizeScale(int? size, int? scale) { if (size == null) return ""; if (size == int.MaxValue) - return "(MAX)"; + return IsPostgres ? "" : "(MAX)"; if (scale == null) return "({0})".FormatWith(size); @@ -277,56 +267,56 @@ public static string GetSizeScale(int? size, int? scale) return "({0},{1})".FormatWith(size, scale); } - public static SqlPreCommand? AlterTableForeignKeys(ITable t) + public SqlPreCommand? AlterTableForeignKeys(ITable t) { return t.Columns.Values.Select(c => - (c.ReferenceTable == null || c.AvoidForeignKey) ? null : SqlBuilder.AlterTableAddConstraintForeignKey(t, c.Name, c.ReferenceTable)) + (c.ReferenceTable == null || c.AvoidForeignKey) ? null : this.AlterTableAddConstraintForeignKey(t, c.Name, c.ReferenceTable)) .Combine(Spacing.Simple); } - public static SqlPreCommand DropIndex(ObjectName tableName, DiffIndex index) + public SqlPreCommand DropIndex(ObjectName tableName, DiffIndex index) { if (index.IsPrimary) - return AlterTableDropConstraint(tableName, new ObjectName(tableName.Schema, index.IndexName)); + return AlterTableDropConstraint(tableName, new ObjectName(tableName.Schema, index.IndexName, isPostgres)); if (index.ViewName == null) return DropIndex(tableName, index.IndexName); else - return DropViewIndex(new ObjectName(tableName.Schema, index.ViewName), index.IndexName); + return DropViewIndex(new ObjectName(tableName.Schema, index.ViewName, isPostgres), index.IndexName); } - public static SqlPreCommand DropIndex(ObjectName objectName, string indexName) + public SqlPreCommand DropIndex(ObjectName objectName, string indexName) { if (objectName.Schema.Database == null) - return new SqlPreCommandSimple("DROP INDEX {0} ON {1}".FormatWith(indexName.SqlEscape(), objectName)); + return new SqlPreCommandSimple("DROP INDEX {0} ON {1};".FormatWith(indexName.SqlEscape(isPostgres), objectName)); else - return new SqlPreCommandSimple("EXEC {0}.dbo.sp_executesql N'DROP INDEX {1} ON {2}'" - .FormatWith(objectName.Schema.Database.ToString().SqlEscape(), indexName.SqlEscape(), objectName.OnDatabase(null).ToString())); + return new SqlPreCommandSimple("EXEC {0}.dbo.sp_executesql N'DROP INDEX {1} ON {2}';" + .FormatWith(objectName.Schema.Database.ToString().SqlEscape(isPostgres), indexName.SqlEscape(isPostgres), objectName.OnDatabase(null).ToString())); } - public static SqlPreCommand CreateIndex(TableIndex index, Replacements? checkUnique) + public SqlPreCommand CreateIndex(TableIndex index, Replacements? checkUnique) { if (index is PrimaryClusteredIndex) { - var columns = index.Columns.ToString(c => c.Name.SqlEscape(), ", "); + var columns = index.Columns.ToString(c => c.Name.SqlEscape(isPostgres), ", "); - return new SqlPreCommandSimple($"ALTER TABLE {index.Table.Name} ADD CONSTRAINT {index.IndexName} PRIMARY KEY CLUSTERED({columns})"); + return new SqlPreCommandSimple($"ALTER TABLE {index.Table.Name} ADD CONSTRAINT {index.IndexName.SqlEscape(isPostgres)} PRIMARY KEY CLUSTERED({columns});"); } if (index is UniqueTableIndex uIndex) { if (uIndex.ViewName != null) { - ObjectName viewName = new ObjectName(uIndex.Table.Name.Schema, uIndex.ViewName); + ObjectName viewName = new ObjectName(uIndex.Table.Name.Schema, uIndex.ViewName, isPostgres); - var columns = index.Columns.ToString(c => c.Name.SqlEscape(), ", "); + var columns = index.Columns.ToString(c => c.Name.SqlEscape(isPostgres), ", "); - SqlPreCommandSimple viewSql = new SqlPreCommandSimple($"CREATE VIEW {viewName} WITH SCHEMABINDING AS SELECT {columns} FROM {uIndex.Table.Name.ToString()} WHERE {uIndex.Where}") + SqlPreCommandSimple viewSql = new SqlPreCommandSimple($"CREATE VIEW {viewName} WITH SCHEMABINDING AS SELECT {columns} FROM {uIndex.Table.Name} WHERE {uIndex.Where};") { GoBefore = true, GoAfter = true }; - SqlPreCommandSimple indexSql = new SqlPreCommandSimple($"CREATE UNIQUE CLUSTERED INDEX {uIndex.IndexName} ON {viewName}({columns})"); + SqlPreCommandSimple indexSql = new SqlPreCommandSimple($"CREATE UNIQUE CLUSTERED INDEX {uIndex.IndexName.SqlEscape(isPostgres)} ON {viewName}({columns});"); return SqlPreCommand.Combine(Spacing.Simple, checkUnique!=null ? RemoveDuplicatesIfNecessary(uIndex, checkUnique) : null, @@ -337,7 +327,7 @@ public static SqlPreCommand CreateIndex(TableIndex index, Replacements? checkUni { return SqlPreCommand.Combine(Spacing.Double, checkUnique != null ? RemoveDuplicatesIfNecessary(uIndex, checkUnique) : null, - CreateIndexBasic(index, false))!; + CreateIndexBasic(index, forHistoryTable: false))!; } } else @@ -346,7 +336,7 @@ public static SqlPreCommand CreateIndex(TableIndex index, Replacements? checkUni } } - public static int DuplicateCount(UniqueTableIndex uniqueIndex, Replacements rep) + public int DuplicateCount(UniqueTableIndex uniqueIndex, Replacements rep) { var primaryKey = uniqueIndex.Table.Columns.Values.Where(a => a.PrimaryKey).Only(); @@ -357,7 +347,7 @@ public static int DuplicateCount(UniqueTableIndex uniqueIndex, Replacements rep) var columnReplacement = rep.TryGetC(Replacements.KeyColumnsForTable(uniqueIndex.Table.Name.ToString()))?.Inverse() ?? new Dictionary(); - var oldColumns = uniqueIndex.Columns.ToString(c => (columnReplacement.TryGetC(c.Name) ?? c.Name).SqlEscape(), ", "); + var oldColumns = uniqueIndex.Columns.ToString(c => (columnReplacement.TryGetC(c.Name) ?? c.Name).SqlEscape(isPostgres), ", "); var oldPrimaryKey = columnReplacement.TryGetC(primaryKey.Name) ?? primaryKey.Name; @@ -372,7 +362,7 @@ GROUP BY {oldColumns} ){(!uniqueIndex.Where.HasText() ? "" : "AND " + uniqueIndex.Where.Replace(columnReplacement))}")!; } - public static SqlPreCommand? RemoveDuplicatesIfNecessary(UniqueTableIndex uniqueIndex, Replacements rep) + public SqlPreCommand? RemoveDuplicatesIfNecessary(UniqueTableIndex uniqueIndex, Replacements rep) { try { @@ -387,7 +377,7 @@ GROUP BY {oldColumns} if (count == 0) return null; - var columns = uniqueIndex.Columns.ToString(c => c.Name.SqlEscape(), ", "); + var columns = uniqueIndex.Columns.ToString(c => c.Name.SqlEscape(isPostgres), ", "); if (rep.Interactive) { @@ -403,12 +393,12 @@ GROUP BY {oldColumns} } catch (Exception) { - return new SqlPreCommandSimple($"-- Impossible to determine duplicates in new index {uniqueIndex.IndexName}"); + return new SqlPreCommandSimple($"-- Impossible to determine duplicates in new index {uniqueIndex.IndexName.SqlEscape(isPostgres)}"); } } - private static SqlPreCommand RemoveDuplicates(UniqueTableIndex uniqueIndex, IColumn primaryKey, string columns, bool commentedOut) + private SqlPreCommand RemoveDuplicates(UniqueTableIndex uniqueIndex, IColumn primaryKey, string columns, bool commentedOut) { return new SqlPreCommandSimple($@"DELETE {uniqueIndex.Table.Name} WHERE {primaryKey.Name} NOT IN @@ -417,80 +407,80 @@ SELECT MIN({primaryKey.Name}) FROM {uniqueIndex.Table.Name} {(string.IsNullOrWhiteSpace(uniqueIndex.Where) ? "" : "WHERE " + uniqueIndex.Where)} GROUP BY {columns} -){(string.IsNullOrWhiteSpace(uniqueIndex.Where) ? "" : " AND " + uniqueIndex.Where)}".Let(txt => commentedOut ? txt.Indent(2, '-') : txt)); +){(string.IsNullOrWhiteSpace(uniqueIndex.Where) ? "" : " AND " + uniqueIndex.Where)};".Let(txt => commentedOut ? txt.Indent(2, '-') : txt)); } - public static SqlPreCommand CreateIndexBasic(Maps.TableIndex index, bool forHistoryTable) + public SqlPreCommand CreateIndexBasic(Maps.TableIndex index, bool forHistoryTable) { var indexType = index is UniqueTableIndex ? "UNIQUE INDEX" : "INDEX"; - var columns = index.Columns.ToString(c => c.Name.SqlEscape(), ", "); - var include = index.IncludeColumns.HasItems() ? $" INCLUDE ({index.IncludeColumns.ToString(c => c.Name.SqlEscape(), ", ")})" : null; + var columns = index.Columns.ToString(c => c.Name.SqlEscape(isPostgres), ", "); + var include = index.IncludeColumns.HasItems() ? $" INCLUDE ({index.IncludeColumns.ToString(c => c.Name.SqlEscape(isPostgres), ", ")})" : null; var where = index.Where.HasText() ? $" WHERE {index.Where}" : ""; var tableName = forHistoryTable ? index.Table.SystemVersioned!.TableName : index.Table.Name; - return new SqlPreCommandSimple($"CREATE {indexType} {index.IndexName} ON {tableName}({columns}){include}{where}"); + return new SqlPreCommandSimple($"CREATE {indexType} {index.GetIndexName(tableName).SqlEscape(isPostgres)} ON {tableName}({columns}){include}{where};"); } - internal static SqlPreCommand UpdateTrim(ITable tab, IColumn tabCol) + internal SqlPreCommand UpdateTrim(ITable tab, IColumn tabCol) { - return new SqlPreCommandSimple("UPDATE {0} SET {1} = RTRIM({1})".FormatWith(tab.Name, tabCol.Name));; + return new SqlPreCommandSimple("UPDATE {0} SET {1} = RTRIM({1});".FormatWith(tab.Name, tabCol.Name));; } - public static SqlPreCommand AlterTableDropConstraint(ObjectName tableName, ObjectName constraintName) => + public SqlPreCommand AlterTableDropConstraint(ObjectName tableName, ObjectName constraintName) => AlterTableDropConstraint(tableName, constraintName.Name); - public static SqlPreCommand AlterTableDropConstraint(ObjectName tableName, string constraintName) + public SqlPreCommand AlterTableDropConstraint(ObjectName tableName, string constraintName) { - return new SqlPreCommandSimple("ALTER TABLE {0} DROP CONSTRAINT {1}".FormatWith( + return new SqlPreCommandSimple("ALTER TABLE {0} DROP CONSTRAINT {1};".FormatWith( tableName, - constraintName.SqlEscape())); + constraintName.SqlEscape(isPostgres))); } - public static SqlPreCommandSimple AlterTableAddDefaultConstraint(ObjectName tableName, DefaultConstraint constraint) + public SqlPreCommandSimple AlterTableAddDefaultConstraint(ObjectName tableName, DefaultConstraint constraint) { - return new SqlPreCommandSimple($"ALTER TABLE {tableName} ADD CONSTRAINT {constraint.Name} DEFAULT {constraint.QuotedDefinition} FOR {constraint.ColumnName}"); + return new SqlPreCommandSimple($"ALTER TABLE {tableName} ADD CONSTRAINT {constraint.Name} DEFAULT {constraint.QuotedDefinition} FOR {constraint.ColumnName};"); } - public static SqlPreCommand? AlterTableAddConstraintForeignKey(ITable table, string fieldName, ITable foreignTable) + public SqlPreCommand? AlterTableAddConstraintForeignKey(ITable table, string fieldName, ITable foreignTable) { return AlterTableAddConstraintForeignKey(table.Name, fieldName, foreignTable.Name, foreignTable.PrimaryKey.Name); } - public static SqlPreCommand? AlterTableAddConstraintForeignKey(ObjectName parentTable, string parentColumn, ObjectName targetTable, string targetPrimaryKey) + public SqlPreCommand? AlterTableAddConstraintForeignKey(ObjectName parentTable, string parentColumn, ObjectName targetTable, string targetPrimaryKey) { if (!object.Equals(parentTable.Schema.Database, targetTable.Schema.Database)) return null; - return new SqlPreCommandSimple("ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3}({4})".FormatWith( + return new SqlPreCommandSimple("ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3}({4});".FormatWith( parentTable, ForeignKeyName(parentTable.Name, parentColumn), - parentColumn.SqlEscape(), + parentColumn.SqlEscape(isPostgres), targetTable, - targetPrimaryKey.SqlEscape())); + targetPrimaryKey.SqlEscape(isPostgres))); } - public static string ForeignKeyName(string table, string fieldName) + public string ForeignKeyName(string table, string fieldName) { - return "FK_{0}_{1}".FormatWith(table, fieldName).SqlEscape(); + return "FK_{0}_{1}".FormatWith(table, fieldName).SqlEscape(isPostgres); } - public static SqlPreCommand RenameForeignKey(ObjectName foreignKeyName, string newName) + public SqlPreCommand RenameForeignKey(ObjectName foreignKeyName, string newName) { return SP_RENAME(foreignKeyName.Schema.Database, foreignKeyName.OnDatabase(null).ToString(), newName, "OBJECT"); } - public static SqlPreCommandSimple SP_RENAME(DatabaseName? database, string oldName, string newName, string? objectType) + public SqlPreCommandSimple SP_RENAME(DatabaseName? database, string oldName, string newName, string? objectType) { - return new SqlPreCommandSimple("EXEC {0}SP_RENAME '{1}' , '{2}'{3}".FormatWith( - database == null ? null: (new SchemaName(database, "dbo").ToString() + "."), + return new SqlPreCommandSimple("EXEC {0}SP_RENAME '{1}' , '{2}'{3};".FormatWith( + database == null ? null: (SchemaName.Default(isPostgres).ToString() + "."), oldName, newName, objectType == null ? null : ", '{0}'".FormatWith(objectType) )); } - public static SqlPreCommand RenameOrChangeSchema(ObjectName oldTableName, ObjectName newTableName) + public SqlPreCommand RenameOrChangeSchema(ObjectName oldTableName, ObjectName newTableName) { if (!object.Equals(oldTableName.Schema.Database, newTableName.Schema.Database)) throw new InvalidOperationException("Different database"); @@ -505,7 +495,7 @@ public static SqlPreCommand RenameOrChangeSchema(ObjectName oldTableName, Object oldNewSchema.Equals(newTableName) ? null : RenameTable(oldNewSchema, newTableName.Name))!; } - public static SqlPreCommand RenameOrMove(DiffTable oldTable, ITable newTable) + public SqlPreCommand RenameOrMove(DiffTable oldTable, ITable newTable) { if (object.Equals(oldTable.Name.Schema.Database, newTable.Name.Schema.Database)) return RenameOrChangeSchema(oldTable.Name, newTable.Name); @@ -516,7 +506,7 @@ public static SqlPreCommand RenameOrMove(DiffTable oldTable, ITable newTable) DropTable(oldTable))!; } - public static SqlPreCommand MoveRows(ObjectName oldTable, ObjectName newTable, IEnumerable columnNames) + public SqlPreCommand MoveRows(ObjectName oldTable, ObjectName newTable, IEnumerable columnNames) { SqlPreCommandSimple command = new SqlPreCommandSimple( @"INSERT INTO {0} ({2}) @@ -524,8 +514,8 @@ public static SqlPreCommand MoveRows(ObjectName oldTable, ObjectName newTable, I FROM {1} as [table]".FormatWith( newTable, oldTable, - columnNames.ToString(a => a.SqlEscape(), ", "), - columnNames.ToString(a => "[table]." + a.SqlEscape(), ", "))); + columnNames.ToString(a => a.SqlEscape(isPostgres), ", "), + columnNames.ToString(a => "[table]." + a.SqlEscape(isPostgres), ", "))); return SqlPreCommand.Combine(Spacing.Simple, new SqlPreCommandSimple("SET IDENTITY_INSERT {0} ON".FormatWith(newTable)) { GoBefore = true }, @@ -533,92 +523,92 @@ public static SqlPreCommand MoveRows(ObjectName oldTable, ObjectName newTable, I new SqlPreCommandSimple("SET IDENTITY_INSERT {0} OFF".FormatWith(newTable)) { GoAfter = true })!; } - public static SqlPreCommand RenameTable(ObjectName oldName, string newName) + public SqlPreCommand RenameTable(ObjectName oldName, string newName) { return SP_RENAME(oldName.Schema.Database, oldName.OnDatabase(null).ToString(), newName, null); } - public static SqlPreCommandSimple AlterSchema(ObjectName oldName, SchemaName schemaName) + public SqlPreCommandSimple AlterSchema(ObjectName oldName, SchemaName schemaName) { - return new SqlPreCommandSimple("ALTER SCHEMA {0} TRANSFER {1};".FormatWith(schemaName.Name.SqlEscape(), oldName)); + return new SqlPreCommandSimple("ALTER SCHEMA {0} TRANSFER {1};".FormatWith(schemaName.Name.SqlEscape(isPostgres), oldName)); } - public static SqlPreCommand RenameColumn(ObjectName tableName, string oldName, string newName) + public SqlPreCommand RenameColumn(ObjectName tableName, string oldName, string newName) { return SP_RENAME(tableName.Schema.Database, tableName.OnDatabase(null) + "." + oldName, newName, "COLUMN"); } - public static SqlPreCommand RenameIndex(ObjectName tableName, string oldName, string newName) + public SqlPreCommand RenameIndex(ObjectName tableName, string oldName, string newName) { return SP_RENAME(tableName.Schema.Database, tableName.OnDatabase(null) + "." + oldName, newName, "INDEX"); } #endregion - public static SqlPreCommandSimple SetIdentityInsert(ObjectName tableName, bool value) + public SqlPreCommandSimple SetIdentityInsert(ObjectName tableName, bool value) { return new SqlPreCommandSimple("SET IDENTITY_INSERT {0} {1}".FormatWith( tableName, value ? "ON" : "OFF")); } - public static SqlPreCommandSimple SetSingleUser(string databaseName) + public SqlPreCommandSimple SetSingleUser(string databaseName) { return new SqlPreCommandSimple("ALTER DATABASE {0} SET SINGLE_USER WITH ROLLBACK IMMEDIATE;".FormatWith(databaseName)); } - public static SqlPreCommandSimple SetMultiUser(string databaseName) + public SqlPreCommandSimple SetMultiUser(string databaseName) { return new SqlPreCommandSimple("ALTER DATABASE {0} SET MULTI_USER;".FormatWith(databaseName)); } - public static SqlPreCommandSimple SetSnapshotIsolation(string databaseName, bool value) + public SqlPreCommandSimple SetSnapshotIsolation(string databaseName, bool value) { return new SqlPreCommandSimple("ALTER DATABASE {0} SET ALLOW_SNAPSHOT_ISOLATION {1}".FormatWith(databaseName, value ? "ON" : "OFF")); } - public static SqlPreCommandSimple MakeSnapshotIsolationDefault(string databaseName, bool value) + public SqlPreCommandSimple MakeSnapshotIsolationDefault(string databaseName, bool value) { return new SqlPreCommandSimple("ALTER DATABASE {0} SET READ_COMMITTED_SNAPSHOT {1}".FormatWith(databaseName, value ? "ON" : "OFF")); } - public static SqlPreCommandSimple SelectRowCount() + public SqlPreCommandSimple SelectRowCount() { return new SqlPreCommandSimple("select @@rowcount;"); } - public static SqlPreCommand CreateSchema(SchemaName schemaName) + public SqlPreCommand CreateSchema(SchemaName schemaName) { if (schemaName.Database == null) - return new SqlPreCommandSimple("CREATE SCHEMA {0}".FormatWith(schemaName)) { GoAfter = true, GoBefore = true }; + return new SqlPreCommandSimple("CREATE SCHEMA {0};".FormatWith(schemaName)) { GoAfter = true, GoBefore = true }; else - return new SqlPreCommandSimple($"EXEC('use {schemaName.Database}; EXEC sp_executesql N''CREATE SCHEMA {schemaName.Name}'' ')"); + return new SqlPreCommandSimple($"EXEC('use {schemaName.Database}; EXEC sp_executesql N''CREATE SCHEMA {schemaName.Name}'' ');"); } - public static SqlPreCommand DropSchema(SchemaName schemaName) + public SqlPreCommand DropSchema(SchemaName schemaName) { return new SqlPreCommandSimple("DROP SCHEMA {0}".FormatWith(schemaName)) { GoAfter = true, GoBefore = true }; } - public static SqlPreCommandSimple DisableForeignKey(ObjectName tableName, string foreignKey) + public SqlPreCommandSimple DisableForeignKey(ObjectName tableName, string foreignKey) { return new SqlPreCommandSimple("ALTER TABLE {0} NOCHECK CONSTRAINT {1}".FormatWith(tableName, foreignKey)); } - public static SqlPreCommandSimple EnableForeignKey(ObjectName tableName, string foreignKey) + public SqlPreCommandSimple EnableForeignKey(ObjectName tableName, string foreignKey) { return new SqlPreCommandSimple("ALTER TABLE {0} WITH CHECK CHECK CONSTRAINT {1}".FormatWith(tableName, foreignKey)); } - public static SqlPreCommandSimple DisableIndex(ObjectName tableName, string indexName) + public SqlPreCommandSimple DisableIndex(ObjectName tableName, string indexName) { return new SqlPreCommandSimple("ALTER INDEX [{0}] ON {1} DISABLE".FormatWith(indexName, tableName)); } - public static SqlPreCommandSimple RebuildIndex(ObjectName tableName, string indexName) + public SqlPreCommandSimple RebuildIndex(ObjectName tableName, string indexName) { return new SqlPreCommandSimple("ALTER INDEX [{0}] ON {1} REBUILD".FormatWith(indexName, tableName)); } - public static SqlPreCommandSimple DropPrimaryKeyConstraint(ObjectName tableName) + public SqlPreCommandSimple DropPrimaryKeyConstraint(ObjectName tableName) { DatabaseName? db = tableName.Schema.Database; @@ -641,15 +631,15 @@ EXEC DB.dbo.sp_executesql @sql" } - internal static SqlPreCommand? DropStatistics(string tn, List list) + internal SqlPreCommand? DropStatistics(string tn, List list) { if (list.IsEmpty()) return null; - return new SqlPreCommandSimple("DROP STATISTICS " + list.ToString(s => tn.SqlEscape() + "." + s.StatsName.SqlEscape(), ",\r\n")); + return new SqlPreCommandSimple("DROP STATISTICS " + list.ToString(s => tn.SqlEscape(isPostgres) + "." + s.StatsName.SqlEscape(isPostgres), ",\r\n")); } - public static SqlPreCommand TruncateTable(ObjectName tableName) => new SqlPreCommandSimple($"TRUNCATE TABLE {tableName}"); + public SqlPreCommand TruncateTable(ObjectName tableName) => new SqlPreCommandSimple($"TRUNCATE TABLE {tableName}"); } } diff --git a/Signum.Engine/Engine/SqlPreCommand.cs b/Signum.Engine/Engine/SqlPreCommand.cs index 1e9514d904..c1505a92d2 100644 --- a/Signum.Engine/Engine/SqlPreCommand.cs +++ b/Signum.Engine/Engine/SqlPreCommand.cs @@ -206,8 +206,9 @@ protected internal override void PlainSql(StringBuilder sb) public string sp_executesql() { var pars = this.Parameters.EmptyIfNull(); + var sqlBuilder = Connector.Current.SqlBuilder; - var parameterVars = pars.ToString(p => $"{p.ParameterName} {((SqlParameter)p).SqlDbType.ToString()}{SqlBuilder.GetSizeScale(p.Size.DefaultToNull(), p.Scale.DefaultToNull())}", ", "); + var parameterVars = pars.ToString(p => $"{p.ParameterName} {((SqlParameter)p).SqlDbType.ToString()}{sqlBuilder.GetSizeScale(p.Size.DefaultToNull(), p.Scale.DefaultToNull())}", ", "); var parameterValues = pars.ToString(p => Encode(p.Value), ","); return $"EXEC sp_executesql N'{this.Sql}', N'{parameterVars}', {parameterValues}"; diff --git a/Signum.Engine/Engine/SqlUtils.cs b/Signum.Engine/Engine/SqlUtils.cs index 337e8d1781..f964cf2cbe 100644 --- a/Signum.Engine/Engine/SqlUtils.cs +++ b/Signum.Engine/Engine/SqlUtils.cs @@ -9,7 +9,7 @@ namespace Signum.Engine { public static class SqlUtils { - static HashSet Keywords = + static HashSet KeywordsSqlServer = @"ADD ALL ALTER @@ -210,16 +210,658 @@ public static class SqlUtils WORK WRITETEXT".Lines().Select(a => a.Trim().ToUpperInvariant()).ToHashSet(); - public static string SqlEscape(this string ident) + static HashSet KeywordsPostgres = +@"A +ABORT +ABS +ABSOLUTE +ACCESS +ACTION +ADA +ADD +ADMIN +AFTER +AGGREGATE +ALIAS +ALL +ALLOCATE +ALSO +ALTER +ALWAYS +ANALYSE +ANALYZE +AND +ANY +ARE +ARRAY +AS +ASC +ASENSITIVE +ASSERTION +ASSIGNMENT +ASYMMETRIC +AT +ATOMIC +ATTRIBUTE +ATTRIBUTES +AUTHORIZATION +AVG +BACKWARD +BEFORE +BEGIN +BERNOULLI +BETWEEN +BIGINT +BINARY +BIT +BITVAR +BIT_LENGTH +BLOB +BOOLEAN +BOTH +BREADTH +BY +C +CACHE +CALL +CALLED +CARDINALITY +CASCADE +CASCADED +CASE +CAST +CATALOG +CATALOG_NAME +CEIL +CEILING +CHAIN +CHAR +CHARACTER +CHARACTERISTICS +CHARACTERS +CHARACTER_LENGTH +CHARACTER_SET_CATALOG +CHARACTER_SET_NAME +CHARACTER_SET_SCHEMA +CHAR_LENGTH +CHECK +CHECKED +CHECKPOINT +CLASS +CLASS_ORIGIN +CLOB +CLOSE +CLUSTER +COALESCE +COBOL +COLLATE +COLLATION +COLLATION_CATALOG +COLLATION_NAME +COLLATION_SCHEMA +COLLECT +COLUMN +COLUMN_NAME +COMMAND_FUNCTION +COMMAND_FUNCTION_CODE +COMMENT +COMMIT +COMMITTED +COMPLETION +CONDITION +CONDITION_NUMBER +CONNECT +CONNECTION +CONNECTION_NAME +CONSTRAINT +CONSTRAINTS +CONSTRAINT_CATALOG +CONSTRAINT_NAME +CONSTRAINT_SCHEMA +CONSTRUCTOR +CONTAINS +CONTINUE +CONVERSION +CONVERT +COPY +CORR +CORRESPONDING +COUNT +COVAR_POP +COVAR_SAMP +CREATE +CREATEDB +CREATEROLE +CREATEUSER +CROSS +CSV +CUBE +CUME_DIST +CURRENT +CURRENT_DATE +CURRENT_DEFAULT_TRANSFORM_GROUP +CURRENT_PATH +CURRENT_ROLE +CURRENT_TIME +CURRENT_TIMESTAMP +CURRENT_TRANSFORM_GROUP_FOR_TYPE +CURRENT_USER +CURSOR +CURSOR_NAME +CYCLE +DATA +DATABASE +DATE +DATETIME_INTERVAL_CODE +DATETIME_INTERVAL_PRECISION +DAY +DEALLOCATE +DEC +DECIMAL +DECLARE +DEFAULT +DEFAULTS +DEFERRABLE +DEFERRED +DEFINED +DEFINER +DEGREE +DELETE +DELIMITER +DELIMITERS +DENSE_RANK +DEPTH +DEREF +DERIVED +DESC +DESCRIBE +DESCRIPTOR +DESTROY +DESTRUCTOR +DETERMINISTIC +DIAGNOSTICS +DICTIONARY +DISABLE +DISCONNECT +DISPATCH +DISTINCT +DO +DOMAIN +DOUBLE +DROP +DYNAMIC +DYNAMIC_FUNCTION +DYNAMIC_FUNCTION_CODE +EACH +ELEMENT +ELSE +ENABLE +ENCODING +ENCRYPTED +END +END-EXEC +EQUALS +ESCAPE +EVERY +EXCEPT +EXCEPTION +EXCLUDE +EXCLUDING +EXCLUSIVE +EXEC +EXECUTE +EXISTING +EXISTS +EXP +EXPLAIN +EXTERNAL +EXTRACT +FALSE +FETCH +FILTER +FINAL +FIRST +FLOAT +FLOOR +FOLLOWING +FOR +FORCE +FOREIGN +FORTRAN +FORWARD +FOUND +FREE +FREEZE +FROM +FULL +FUNCTION +FUSION +G +GENERAL +GENERATED +GET +GLOBAL +GO +GOTO +GRANT +GRANTED +GREATEST +GROUP +GROUPING +HANDLER +HAVING +HEADER +HIERARCHY +HOLD +HOST +HOUR +IDENTITY +IGNORE +ILIKE +IMMEDIATE +IMMUTABLE +IMPLEMENTATION +IMPLICIT +IN +INCLUDING +INCREMENT +INDEX +INDICATOR +INFIX +INHERIT +INHERITS +INITIALIZE +INITIALLY +INNER +INOUT +INPUT +INSENSITIVE +INSERT +INSTANCE +INSTANTIABLE +INSTEAD +INT +INTEGER +INTERSECT +INTERSECTION +INTERVAL +INTO +INVOKER +IS +ISNULL +ISOLATION +ITERATE +JOIN +K +KEY +KEY_MEMBER +KEY_TYPE +LANCOMPILER +LANGUAGE +LARGE +LAST +LATERAL +LEADING +LEAST +LEFT +LENGTH +LESS +LEVEL +LIKE +LIMIT +LISTEN +LN +LOAD +LOCAL +LOCALTIME +LOCALTIMESTAMP +LOCATION +LOCATOR +LOCK +LOGIN +LOWER +M +MAP +MATCH +MATCHED +MAX +MAXVALUE +MEMBER +MERGE +MESSAGE_LENGTH +MESSAGE_OCTET_LENGTH +MESSAGE_TEXT +METHOD +MIN +MINUTE +MINVALUE +MOD +MODE +MODIFIES +MODIFY +MODULE +MONTH +MORE +MOVE +MULTISET +MUMPS +NAME +NAMES +NATIONAL +NATURAL +NCHAR +NCLOB +NESTING +NEW +NEXT +NO +NOCREATEDB +NOCREATEROLE +NOCREATEUSER +NOINHERIT +NOLOGIN +NONE +NORMALIZE +NORMALIZED +NOSUPERUSER +NOT +NOTHING +NOTIFY +NOTNULL +NOWAIT +NULL +NULLABLE +NULLIF +NULLS +NUMBER +NUMERIC +OBJECT +OCTETS +OCTET_LENGTH +OF +OFF +OFFSET +OIDS +OLD +ON +ONLY +OPEN +OPERATION +OPERATOR +OPTION +OPTIONS +OR +ORDER +ORDERING +ORDINALITY +OTHERS +OUT +OUTER +OUTPUT +OVER +OVERLAPS +OVERLAY +OVERRIDING +OWNER +PAD +PARAMETER +PARAMETERS +PARAMETER_MODE +PARAMETER_NAME +PARAMETER_ORDINAL_POSITION +PARAMETER_SPECIFIC_CATALOG +PARAMETER_SPECIFIC_NAME +PARAMETER_SPECIFIC_SCHEMA +PARTIAL +PARTITION +PASCAL +PASSWORD +PATH +PERCENTILE_CONT +PERCENTILE_DISC +PERCENT_RANK +PLACING +PLI +POSITION +POSTFIX +POWER +PRECEDING +PRECISION +PREFIX +PREORDER +PREPARE +PREPARED +PRESERVE +PRIMARY +PRIOR +PRIVILEGES +PROCEDURAL +PROCEDURE +PUBLIC +QUOTE +RANGE +RANK +READ +READS +REAL +RECHECK +RECURSIVE +REF +REFERENCES +REFERENCING +REGR_AVGX +REGR_AVGY +REGR_COUNT +REGR_INTERCEPT +REGR_R2 +REGR_SLOPE +REGR_SXX +REGR_SXY +REGR_SYY +REINDEX +RELATIVE +RELEASE +RENAME +REPEATABLE +REPLACE +RESET +RESTART +RESTRICT +RESULT +RETURN +RETURNED_CARDINALITY +RETURNED_LENGTH +RETURNED_OCTET_LENGTH +RETURNED_SQLSTATE +RETURNS +REVOKE +RIGHT +ROLE +ROLLBACK +ROLLUP +ROUTINE +ROUTINE_CATALOG +ROUTINE_NAME +ROUTINE_SCHEMA +ROW +ROWS +ROW_COUNT +ROW_NUMBER +RULE +SAVEPOINT +SCALE +SCHEMA +SCHEMA_NAME +SCOPE +SCOPE_CATALOG +SCOPE_NAME +SCOPE_SCHEMA +SCROLL +SEARCH +SECOND +SECTION +SECURITY +SELECT +SELF +SENSITIVE +SEQUENCE +SERIALIZABLE +SERVER_NAME +SESSION +SESSION_USER +SET +SETOF +SETS +SHARE +SHOW +SIMILAR +SIMPLE +SIZE +SMALLINT +SOME +SOURCE +SPACE +SPECIFIC +SPECIFICTYPE +SPECIFIC_NAME +SQL +SQLCODE +SQLERROR +SQLEXCEPTION +SQLSTATE +SQLWARNING +SQRT +STABLE +START +STATE +STATEMENT +STATIC +STATISTICS +STDDEV_POP +STDDEV_SAMP +STDIN +STDOUT +STORAGE +STRICT +STRUCTURE +STYLE +SUBCLASS_ORIGIN +SUBLIST +SUBMULTISET +SUBSTRING +SUM +SUPERUSER +SYMMETRIC +SYSID +SYSTEM +SYSTEM_USER +TABLE +TABLESAMPLE +TABLESPACE +TABLE_NAME +TEMP +TEMPLATE +TEMPORARY +TERMINATE +THAN +THEN +TIES +TIME +TIMESTAMP +TIMEZONE_HOUR +TIMEZONE_MINUTE +TO +TOAST +TOP_LEVEL_COUNT +TRAILING +TRANSACTION +TRANSACTIONS_COMMITTED +TRANSACTIONS_ROLLED_BACK +TRANSACTION_ACTIVE +TRANSFORM +TRANSFORMS +TRANSLATE +TRANSLATION +TREAT +TRIGGER +TRIGGER_CATALOG +TRIGGER_NAME +TRIGGER_SCHEMA +TRIM +TRUE +TRUNCATE +TRUSTED +TYPE +UESCAPE +UNBOUNDED +UNCOMMITTED +UNDER +UNENCRYPTED +UNION +UNIQUE +UNKNOWN +UNLISTEN +UNNAMED +UNNEST +UNTIL +UPDATE +UPPER +USAGE +USER +USER_DEFINED_TYPE_CATALOG +USER_DEFINED_TYPE_CODE +USER_DEFINED_TYPE_NAME +USER_DEFINED_TYPE_SCHEMA +USING +VACUUM +VALID +VALIDATOR +VALUE +VALUES +VARCHAR +VARIABLE +VARYING +VAR_POP +VAR_SAMP +VERBOSE +VIEW +VOLATILE +WHEN +WHENEVER +WHERE +WIDTH_BUCKET +WINDOW +WITH +WITHIN +WITHOUT +WORK +WRITE +YEAR +ZONE".Lines().Select(a => a.Trim().ToUpperInvariant()).ToHashSet(); + + + public static string SqlEscape(this string ident, bool isPostgres) { - if (Keywords.Contains(ident.ToUpperInvariant()) || Regex.IsMatch(ident, @"-\d|[áéíóúàèìòùÁÉÍÓÚÀÈÌÒÙ/\\. -]")) - return "[" + ident + "]"; + if (isPostgres) + { + if (ident.ToLowerInvariant() != ident || KeywordsSqlServer.Contains(ident.ToUpperInvariant()) || Regex.IsMatch(ident, @"-\d|[áéíóúàèìòùÁÉÍÓÚÀÈÌÒÙ/\\. -]")) + return "\"" + ident + "\""; + + return ident; + } + else + { + if (KeywordsSqlServer.Contains(ident.ToUpperInvariant()) || Regex.IsMatch(ident, @"-\d|[áéíóúàèìòùÁÉÍÓÚÀÈÌÒÙ/\\. -]")) + return "[" + ident + "]"; - return ident; + return ident; + } } public static SqlPreCommand? RemoveDuplicatedIndices() { + var isPostgres = Schema.Current.Settings.IsPostgres; + var sqlBuilder = Connector.Current.SqlBuilder; var plainData = (from s in Database.View() from t in s.Tables() from ix in t.Indices() @@ -228,7 +870,7 @@ from c in t.Columns() where ic.column_id == c.column_id select new { - table = new ObjectName(new SchemaName(null, s.name), t.name), + table = new ObjectName(new SchemaName(null, s.name, isPostgres), t.name, isPostgres), index = ix.name, ix.is_unique, column = c.name, @@ -251,7 +893,7 @@ from c in t.Columns() var best = gr.OrderByDescending(a => a.is_unique).ThenByDescending(a => a.index!/*CSBUG*/.StartsWith("IX")).ThenByDescending(a => a.index).First(); return gr.Where(g => g != best) - .Select(g => SqlBuilder.DropIndex(t.Key!, g.index!)) + .Select(g => sqlBuilder.DropIndex(t.Key!, g.index!)) .PreAnd(new SqlPreCommandSimple("-- DUPLICATIONS OF {0}".FormatWith(best.index))).Combine(Spacing.Simple); }) ).Combine(Spacing.Double); diff --git a/Signum.Engine/Engine/Synchronizer.cs b/Signum.Engine/Engine/Synchronizer.cs index 11612ff50a..86009821ec 100644 --- a/Signum.Engine/Engine/Synchronizer.cs +++ b/Signum.Engine/Engine/Synchronizer.cs @@ -157,7 +157,7 @@ public static void SynchronizeReplacing( return SynchronizeScript(spacing, newDictionary, repOldDictionary, createNew, removeOld, mergeBoth); } - public static IDisposable? RenameTable(Table table, Replacements replacements) + public static IDisposable? UseOldTableName(Table table, Replacements replacements) { string? fullName = replacements.TryGetC(Replacements.KeyTablesInverse)?.TryGetC(table.Name.ToString()); if (fullName == null) @@ -165,7 +165,7 @@ public static void SynchronizeReplacing( ObjectName realName = table.Name; - table.Name = ObjectName.Parse(fullName); + table.Name = ObjectName.Parse(fullName, Schema.Current.Settings.IsPostgres); return new Disposable(() => table.Name = realName); } diff --git a/Signum.Engine/Linq/AliasGenerator.cs b/Signum.Engine/Linq/AliasGenerator.cs index 16dfe4b07b..43ac1918c5 100644 --- a/Signum.Engine/Linq/AliasGenerator.cs +++ b/Signum.Engine/Linq/AliasGenerator.cs @@ -9,6 +9,7 @@ namespace Signum.Engine.Linq public class AliasGenerator { readonly HashSet usedAliases = new HashSet(); + public bool isPostgres = Schema.Current.Settings.IsPostgres; int selectAliasCount = 0; public Alias NextSelectAlias() @@ -19,26 +20,26 @@ public Alias NextSelectAlias() public Alias GetUniqueAlias(string baseAlias) { if (usedAliases.Add(baseAlias)) - return new Alias(baseAlias); + return new Alias(baseAlias, isPostgres); for (int i = 1; ; i++) { string alias = baseAlias + i; if (usedAliases.Add(alias)) - return new Alias(alias); + return new Alias(alias, isPostgres); } } - public Alias Table(ObjectName name) + public Alias Table(ObjectName objectName) { - return new Alias(name); + return new Alias(objectName); } public Alias Raw(string name) { - return new Alias(name); + return new Alias(name, isPostgres); } public Alias NextTableAlias(string tableName) @@ -66,14 +67,14 @@ public Alias CloneAlias(Alias alias) public class Alias: IEquatable { - public static readonly Alias Unknown = new Alias("Unknown"); - + public readonly bool isPostgres; public readonly string? Name; //Mutually exclusive public readonly ObjectName? ObjectName; //Mutually exclusive - internal Alias(string name) + internal Alias(string name, bool isPostgres) { this.Name = name; + this.isPostgres = isPostgres; } internal Alias(ObjectName objectName) @@ -98,7 +99,7 @@ public override int GetHashCode() public override string ToString() { - return Name?.SqlEscape() ?? ObjectName!.ToString(); + return Name?.SqlEscape(isPostgres) ?? ObjectName!.ToString(); } } } diff --git a/Signum.Engine/Linq/DbExpressions.Sql.cs b/Signum.Engine/Linq/DbExpressions.Sql.cs index f9fd68a0e6..f9d532d8d6 100644 --- a/Signum.Engine/Linq/DbExpressions.Sql.cs +++ b/Signum.Engine/Linq/DbExpressions.Sql.cs @@ -684,7 +684,7 @@ public static int ToSqlWeekDay(DayOfWeek dayOfWeek, byte dateFirst /*keep parame internal class SqlCastExpression : DbExpression { - public readonly SqlDbType SqlDbType; + public readonly AbstractDbType DbType; public readonly Expression Expression; public SqlCastExpression(Type type, Expression expression) @@ -692,16 +692,16 @@ public SqlCastExpression(Type type, Expression expression) { } - public SqlCastExpression(Type type, Expression expression, SqlDbType sqlDbType) + public SqlCastExpression(Type type, Expression expression, AbstractDbType dbType) : base(DbExpressionType.SqlCast, type) { this.Expression = expression; - this.SqlDbType = sqlDbType; + this.DbType = dbType; } public override string ToString() { - return "Cast({0} as {1})".FormatWith(Expression.ToString(), SqlDbType.ToString().ToUpper()); + return "Cast({0} as {1})".FormatWith(Expression.ToString(), DbType.ToString(Schema.Current.Settings.IsPostgres)); } protected override Expression Accept(DbExpressionVisitor visitor) diff --git a/Signum.Engine/Linq/ExpressionVisitor/ConditionsRewriter.cs b/Signum.Engine/Linq/ExpressionVisitor/ConditionsRewriter.cs index f0c30b34fc..88c1e7b30a 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/ConditionsRewriter.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/ConditionsRewriter.cs @@ -160,7 +160,7 @@ protected internal override Expression VisitSqlCast(SqlCastExpression castExpr) { var expression = MakeSqlValue(Visit(castExpr.Expression)); if (expression != castExpr.Expression) - return new SqlCastExpression(castExpr.Type, expression, castExpr.SqlDbType); + return new SqlCastExpression(castExpr.Type, expression, castExpr.DbType); return castExpr; } diff --git a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionComparer.cs b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionComparer.cs index 4e2d8390d3..767f774697 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionComparer.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionComparer.cs @@ -304,7 +304,7 @@ protected virtual bool CompareAggregateSubquery(AggregateRequestsExpression a, A protected virtual bool CompareSqlCast(SqlCastExpression a, SqlCastExpression b) { - return a.SqlDbType == b.SqlDbType + return a.DbType.Equals(b.DbType) && Compare(a.Expression, b.Expression); } diff --git a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs index ea93b0df88..c0724d0db4 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs @@ -299,7 +299,7 @@ protected internal override Expression VisitSqlCast(SqlCastExpression castExpr) { var expression = Visit(castExpr.Expression); if (expression != castExpr.Expression) - castExpr = new SqlCastExpression(castExpr.Type, expression!, castExpr.SqlDbType); + castExpr = new SqlCastExpression(castExpr.Type, expression!, castExpr.DbType); return Add(castExpr); } @@ -534,8 +534,8 @@ protected Expression GetFormatToString(MethodCallExpression m, string? defaultFo if (innerProjection || !Has(exprDate) || !Has(exprTime)) return null; - var castDate = new SqlCastExpression(typeof(DateTime), exprDate, SqlDbType.DateTime); //Just in case is a Date - var castTime = new SqlCastExpression(typeof(TimeSpan), exprTime, SqlDbType.DateTime); //Just in case is a Date + var castDate = new SqlCastExpression(typeof(DateTime), exprDate, new AbstractDbType(SqlDbType.DateTime)); //Just in case is a Date + var castTime = new SqlCastExpression(typeof(TimeSpan), exprTime, new AbstractDbType(SqlDbType.DateTime)); //Just in case is a Date var result = add ? Expression.Add(castDate, castTime) : Expression.Subtract(castDate, castTime); diff --git a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs index 9a395c191e..872b87aa1b 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs @@ -191,7 +191,7 @@ protected internal virtual Expression VisitSqlCast(SqlCastExpression castExpr) { var expression = Visit(castExpr.Expression); if (expression != castExpr.Expression) - return new SqlCastExpression(castExpr.Type, expression,castExpr.SqlDbType); + return new SqlCastExpression(castExpr.Type, expression,castExpr.DbType); return castExpr; } diff --git a/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs b/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs index 8cc7ffb875..36cd8d885b 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs @@ -19,6 +19,9 @@ namespace Signum.Engine.Linq /// internal class QueryFormatter : DbExpressionVisitor { + Schema schema = Schema.Current; + bool isPostgres = Schema.Current.Settings.IsPostgres; + StringBuilder sb = new StringBuilder(); int indent = 2; int depth; @@ -48,8 +51,6 @@ public string GetNextParamAlias() return "@p" + (parameter++); } - MethodInfo miCreateParameter = ReflectionTools.GetMethodInfo((ParameterBuilder s) => s.CreateParameter(null!, SqlDbType.BigInt, null, false, null)); - DbParameterPair CreateParameter(ConstantExpression value) { string name = GetNextParamAlias(); @@ -63,13 +64,12 @@ DbParameterPair CreateParameter(ConstantExpression value) var pb = Connector.Current.ParameterBuilder; - var param = pb.CreateParameter(name, typePair.SqlDbType, typePair.UserDefinedTypeName, nullable, value.Value ?? DBNull.Value); + var param = pb.CreateParameter(name, typePair.DbType, typePair.UserDefinedTypeName, nullable, value.Value ?? DBNull.Value); return new DbParameterPair(param, name); } ObjectNameOptions objectNameOptions; - private QueryFormatter() { objectNameOptions = ObjectName.CurrentOptions; @@ -344,8 +344,8 @@ protected internal override Expression VisitSqlCast(SqlCastExpression castExpr) sb.Append("CAST("); Visit(castExpr.Expression); sb.Append(" as "); - sb.Append(castExpr.SqlDbType.ToString().ToUpperInvariant()); - if (castExpr.SqlDbType == SqlDbType.NVarChar || castExpr.SqlDbType == SqlDbType.VarChar) + sb.Append(castExpr.DbType.ToString(schema.Settings.IsPostgres)); + if (!schema.Settings.IsPostgres && (castExpr.DbType.SqlServer == SqlDbType.NVarChar || castExpr.DbType.SqlServer == SqlDbType.VarChar)) sb.Append("(MAX)"); sb.Append(")"); return castExpr; @@ -357,7 +357,7 @@ protected override Expression VisitConstant(ConstantExpression c) sb.Append("NULL"); else { - if (!Schema.Current.Settings.IsDbType(c.Value.GetType().UnNullify())) + if (!schema.Settings.IsDbType(c.Value.GetType().UnNullify())) throw new NotSupportedException(string.Format("The constant for {0} is not supported", c.Value)); var pi = parameterExpressions.GetOrCreate(c, () => this.CreateParameter(c)); @@ -373,7 +373,7 @@ protected internal override Expression VisitSqlConstant(SqlConstantExpression c) sb.Append("NULL"); else { - if (!Schema.Current.Settings.IsDbType(c.Value.GetType().UnNullify())) + if (!schema.Settings.IsDbType(c.Value.GetType().UnNullify())) throw new NotSupportedException(string.Format("The constant for {0} is not supported", c.Value)); if (c.Value.Equals(true)) @@ -400,7 +400,7 @@ protected internal override Expression VisitColumn(ColumnExpression column) { sb.Append(column.Alias.ToString()); sb.Append("."); - sb.Append(column.Name.SqlEscape()); + sb.Append(column.Name.SqlEscape(isPostgres)); return column; } @@ -577,7 +577,7 @@ private void AppendColumn(ColumnDeclaration column) if (column.Name.HasText() && (c == null || c.Name != column.Name)) { - sb.Append(column.Name.SqlEscape()); + sb.Append(column.Name.SqlEscape(isPostgres)); sb.Append(" = "); this.Visit(column.Expression); } @@ -793,7 +793,7 @@ protected internal override Expression VisitUpdate(UpdateExpression update) sb.Append(","); this.AppendNewLine(Indentation.Same); } - sb.Append(assignment.Column.SqlEscape()); + sb.Append(assignment.Column.SqlEscape(isPostgres)); sb.Append(" = "); this.Visit(assignment.Expression); } @@ -824,7 +824,7 @@ protected internal override Expression VisitInsertSelect(InsertSelectExpression if (i % 4 == 0) this.AppendNewLine(Indentation.Same); } - sb.Append(assignment.Column.SqlEscape()); + sb.Append(assignment.Column.SqlEscape(isPostgres)); } sb.Append(")"); this.AppendNewLine(Indentation.Same); diff --git a/Signum.Engine/Schema/ObjectName.cs b/Signum.Engine/Schema/ObjectName.cs index 0a2e178292..5736f30886 100644 --- a/Signum.Engine/Schema/ObjectName.cs +++ b/Signum.Engine/Schema/ObjectName.cs @@ -5,8 +5,11 @@ namespace Signum.Engine.Maps { public static class TableExtensions { - internal static string UnScapeSql(this string name) + internal static string UnScapeSql(this string name, bool isPostgres) { + if (isPostgres) + name.Trim('\"'); + return name.Trim('[', ']'); } } @@ -14,22 +17,24 @@ internal static string UnScapeSql(this string name) public class ServerName : IEquatable { public string Name { get; private set; } + public bool IsPostgres { get; private set; } /// /// Linked Servers: http://msdn.microsoft.com/en-us/library/ms188279.aspx /// /// - public ServerName(string name) + public ServerName(string name, bool isPostgres) { if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); this.Name = name; + this.IsPostgres = isPostgres; } public override string ToString() { - return Name.SqlEscape(); + return Name.SqlEscape(IsPostgres); } public override bool Equals(object? obj) => obj is ServerName sn && Equals(sn); @@ -43,35 +48,37 @@ public override int GetHashCode() return Name.GetHashCode(); } - public static ServerName? Parse(string? name) + public static ServerName? Parse(string? name, bool isPostgres) { if (!name.HasText()) return null; - return new ServerName(name.UnScapeSql()); + return new ServerName(name.UnScapeSql(isPostgres), isPostgres); } } public class DatabaseName : IEquatable { public string Name { get; private set; } + public bool IsPostgres { get; private set; } public ServerName? Server { get; private set; } - public DatabaseName(ServerName? server, string name) + public DatabaseName(ServerName? server, string name, bool isPostgres) { if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); this.Name = name; this.Server = server; + this.IsPostgres = isPostgres; } public override string ToString() { var options = ObjectName.CurrentOptions; - var name = !options.DatabaseNameReplacement.HasText() ? Name.SqlEscape(): Name.Replace(Connector.Current.DatabaseName(), options.DatabaseNameReplacement).SqlEscape(); + var name = !options.DatabaseNameReplacement.HasText() ? Name.SqlEscape(IsPostgres): Name.Replace(Connector.Current.DatabaseName(), options.DatabaseNameReplacement).SqlEscape(IsPostgres); if (Server == null) return name; @@ -91,20 +98,21 @@ public override int GetHashCode() return Name.GetHashCode() ^ (Server == null ? 0 : Server.GetHashCode()); } - public static DatabaseName? Parse(string? name) + public static DatabaseName? Parse(string? name, bool isPostgres) { if (!name.HasText()) return null; - var tuple = ObjectName.SplitLast(name); + var tuple = ObjectName.SplitLast(name, isPostgres); - return new DatabaseName(ServerName.Parse(tuple.prefix), tuple.name); + return new DatabaseName(ServerName.Parse(tuple.prefix, isPostgres), tuple.name, isPostgres); } } public class SchemaName : IEquatable { public string Name { get; private set; } + public bool IsPostgres { get; private set; } readonly DatabaseName? database; @@ -119,25 +127,29 @@ public DatabaseName? Database } } - public static readonly SchemaName Default = new SchemaName(null, "dbo"); + static readonly SchemaName defaultSqlServer = new SchemaName(null, "dbo", isPostgres: false); + static readonly SchemaName defaultPostgreeSql = new SchemaName(null, "public", isPostgres: true); + + public static SchemaName Default(bool isPostgres) => isPostgres ? defaultPostgreeSql : defaultSqlServer; public bool IsDefault() { - return Name == "dbo" && Database == null; + return Database == null && (IsPostgres ? defaultPostgreeSql : defaultSqlServer).Name == Name; } - public SchemaName(DatabaseName? database, string name) + public SchemaName(DatabaseName? database, string name, bool isPostgres) { if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); this.Name = name; this.database = database; + this.IsPostgres = isPostgres; } public override string ToString() { - var result = Name.SqlEscape(); + var result = Name.SqlEscape(IsPostgres); if (Database == null) return result; @@ -157,14 +169,14 @@ public override int GetHashCode() return Name.GetHashCode() ^ (Database == null ? 0 : Database.GetHashCode()); } - public static SchemaName Parse(string? name) + public static SchemaName Parse(string? name, bool isPostgres) { if (!name.HasText()) - return SchemaName.Default; + return SchemaName.Default(isPostgres); - var tuple = ObjectName.SplitLast(name); + var tuple = ObjectName.SplitLast(name, isPostgres); - return new SchemaName(DatabaseName.Parse(tuple.prefix), (tuple.name)); + return new SchemaName(DatabaseName.Parse(tuple.prefix, isPostgres), tuple.name, isPostgres); } } @@ -172,18 +184,20 @@ public static SchemaName Parse(string? name) public class ObjectName : IEquatable { public string Name { get; private set; } + public bool IsPostgres { get; private set; } public SchemaName Schema { get; private set; } - public ObjectName(SchemaName schema, string name) + public ObjectName(SchemaName schema, string name, bool isPostgres) { this.Name = name.HasText() ? name : throw new ArgumentNullException(nameof(name)); this.Schema = schema ?? throw new ArgumentNullException(nameof(schema)); + this.IsPostgres = isPostgres; } public override string ToString() { - return Schema.ToString() + "." + Name.SqlEscape(); + return Schema.ToString() + "." + Name.SqlEscape(IsPostgres); } public override bool Equals(object? obj) => obj is ObjectName on && Equals(on); @@ -198,43 +212,60 @@ public override int GetHashCode() return Name.GetHashCode() ^ Schema.GetHashCode(); } - public static ObjectName Parse(string? name) + public static ObjectName Parse(string? name, bool isPostgres) { if (!name.HasText()) throw new ArgumentNullException(nameof(name)); - var tuple = SplitLast(name); + var tuple = SplitLast(name, isPostgres); - return new ObjectName(SchemaName.Parse(tuple.prefix), tuple.name); + return new ObjectName(SchemaName.Parse(tuple.prefix, isPostgres), tuple.name, isPostgres); } //FROM "[a.b.c].[d.e.f].[a.b.c].[c.d.f]" //TO ("[a.b.c].[d.e.f].[a.b.c]", "c.d.f") - internal static (string? prefix, string name) SplitLast(string str) + internal static (string? prefix, string name) SplitLast(string str, bool isPostgres) { - if (!str.EndsWith("]")) + if (isPostgres) { return ( - prefix: str.TryBeforeLast('.'), - name: str.TryAfterLast('.') ?? str - ); + prefix: str.TryBeforeLast("."), + name: (str.TryAfterLast(".") ?? str).UnScapeSql(isPostgres) + ); } + else + { + + if (!str.EndsWith("]")) + { + return ( + prefix: str.TryBeforeLast('.'), + name: str.TryAfterLast('.') ?? str + ); + } - var index = str.LastIndexOf('['); - return ( - prefix: index == 0 ? null : str.Substring(0, index - 1), - name: str.Substring(index).UnScapeSql() - ); + var index = str.LastIndexOf('['); + return ( + prefix: index == 0 ? null : str.Substring(0, index - 1), + name: str.Substring(index).UnScapeSql(isPostgres) + ); + } } public ObjectName OnDatabase(DatabaseName? databaseName) { - return new ObjectName(new SchemaName(databaseName, Schema.Name), Name); + if (databaseName != null && databaseName.IsPostgres != this.IsPostgres) + throw new Exception("Inconsitent IsPostgres"); + + return new ObjectName(new SchemaName(databaseName, Schema.Name, IsPostgres), Name, IsPostgres); } public ObjectName OnSchema(SchemaName schemaName) { - return new ObjectName(schemaName, Name); + if (schemaName.IsPostgres != this.IsPostgres) + throw new Exception("Inconsitent IsPostgres"); + + return new ObjectName(schemaName, Name, IsPostgres); } static readonly ThreadVariable optionsVariable = Statics.ThreadVariable("objectNameOptions"); diff --git a/Signum.Engine/Schema/Schema.Basics.cs b/Signum.Engine/Schema/Schema.Basics.cs index 8ee88e95ef..2528c9aab5 100644 --- a/Signum.Engine/Schema/Schema.Basics.cs +++ b/Signum.Engine/Schema/Schema.Basics.cs @@ -11,6 +11,7 @@ using System.Collections; using Signum.Engine.Linq; using System.Globalization; +using NpgsqlTypes; namespace Signum.Engine.Maps { @@ -42,8 +43,9 @@ public interface ITable public class SystemVersionedInfo { public ObjectName TableName; - public string StartColumnName; - public string EndColumnName; + public string? StartColumnName; + public string? EndColumnName; + public string? PostgreeSysPeriodColumnName; public SystemVersionedInfo(ObjectName tableName, string startColumnName, string endColumnName) { @@ -52,13 +54,25 @@ public SystemVersionedInfo(ObjectName tableName, string startColumnName, string EndColumnName = endColumnName; } + public SystemVersionedInfo(ObjectName tableName, string postgreeSysPeriodColumnName) + { + TableName = tableName; + PostgreeSysPeriodColumnName = postgreeSysPeriodColumnName; + } + internal IEnumerable Columns() { - return new[] - { - new Column(this.StartColumnName, ColumnType.Start), - new Column(this.EndColumnName, ColumnType.End) - }; + if (PostgreeSysPeriodColumnName != null) + return new[] + { + new PostgreePeriodColumn(this.PostgreeSysPeriodColumnName!), + }; + else + return new[] + { + new SqlServerPeriodColumn(this.StartColumnName!, ColumnType.Start), + new SqlServerPeriodColumn(this.EndColumnName!, ColumnType.End) + }; } public enum ColumnType @@ -67,9 +81,9 @@ public enum ColumnType End, } - public class Column : IColumn + public class SqlServerPeriodColumn : IColumn { - public Column(string name, ColumnType systemVersionColumnType) + public SqlServerPeriodColumn(string name, ColumnType systemVersionColumnType) { this.Name = name; this.SystemVersionColumnType = systemVersionColumnType; @@ -79,7 +93,31 @@ public Column(string name, ColumnType systemVersionColumnType) public ColumnType SystemVersionColumnType { get; private set; } public IsNullable Nullable => IsNullable.No; - public SqlDbType SqlDbType => SqlDbType.DateTime2; + public AbstractDbType DbType => new AbstractDbType(SqlDbType.DateTime2); + public Type Type => typeof(DateTime); + public string? UserDefinedTypeName => null; + public bool PrimaryKey => false; + public bool IdentityBehaviour => false; + public bool Identity => false; + public string? Default { get; set; } + public int? Size => null; + public int? Scale => null; + public string? Collation => null; + public Table? ReferenceTable => null; + public bool AvoidForeignKey => false; + } + + public class PostgreePeriodColumn : IColumn + { + public PostgreePeriodColumn(string name) + { + this.Name = name; + } + + public string Name { get; private set; } + + public IsNullable Nullable => IsNullable.No; + public AbstractDbType DbType => new AbstractDbType(NpgsqlDbType.Range | NpgsqlDbType.TimestampTz); public Type Type => typeof(DateTime); public string? UserDefinedTypeName => null; public bool PrimaryKey => false; @@ -103,6 +141,7 @@ interface ITablePrivate public partial class Table : IFieldFinder, ITable, ITablePrivate { public Type Type { get; private set; } + public Schema Schema { get; private set; } public ObjectName Name { get; set; } @@ -349,7 +388,7 @@ public virtual IEnumerable GenerateIndexes(ITable table) }; if(attribute.AllowMultipleNulls) - result.Where = IndexWhereExpressionVisitor.IsNull(this, false); + result.Where = IndexWhereExpressionVisitor.IsNull(this, false, Schema.Current.Settings.IsPostgres); return result; } @@ -391,7 +430,7 @@ public partial interface IColumn { string Name { get; } IsNullable Nullable { get; } - SqlDbType SqlDbType { get; } + AbstractDbType DbType { get; } Type Type { get; } string? UserDefinedTypeName { get; } bool PrimaryKey { get; } @@ -420,14 +459,14 @@ public static bool ToBool(this IsNullable isNullable) return isNullable != IsNullable.No; } - public static string GetSqlDbTypeString(this IColumn column) - { - return column.SqlDbType.ToString().ToUpper(CultureInfo.InvariantCulture) + SqlBuilder.GetSizeScale(column.Size, column.Scale); - } + //public static string GetSqlDbTypeString(this IColumn column) + //{ + // return column.SqlDbType.ToString().ToUpper(CultureInfo.InvariantCulture) + SqlBuilder.GetSizeScale(column.Size, column.Scale); + //} public static GeneratedAlwaysType GetGeneratedAlwaysType(this IColumn column) { - if (column is SystemVersionedInfo.Column svc) + if (column is SystemVersionedInfo.SqlServerPeriodColumn svc) return svc.SystemVersionColumnType == SystemVersionedInfo.ColumnType.Start ? GeneratedAlwaysType.AsRowStart : GeneratedAlwaysType.AsRowEnd; return GeneratedAlwaysType.None; @@ -446,7 +485,7 @@ public partial class FieldPrimaryKey : Field, IColumn { public string Name { get; set; } IsNullable IColumn.Nullable { get { return IsNullable.No; } } - public SqlDbType SqlDbType { get; set; } + public AbstractDbType DbType { get; set; } public string? UserDefinedTypeName { get; set; } bool IColumn.PrimaryKey { get { return true; } } public bool Identity { get; set; } @@ -501,7 +540,7 @@ public partial class FieldValue : Field, IColumn { public string Name { get; set; } public IsNullable Nullable { get; set; } - public SqlDbType SqlDbType { get; set; } + public AbstractDbType DbType { get; set; } public string? UserDefinedTypeName { get; set; } public bool PrimaryKey { get; set; } bool IColumn.Identity { get { return false; } } @@ -523,7 +562,7 @@ public override string ToString() { return "{0} {1} ({2},{3},{4})".FormatWith( Name, - SqlDbType, + DbType, Nullable.ToBool() ? "Nullable" : "", Size, Scale); @@ -567,7 +606,7 @@ public partial class EmbeddedHasValueColumn : IColumn { public string Name { get; set; } public IsNullable Nullable { get { return IsNullable.No; } } //even on neasted embeddeds - public SqlDbType SqlDbType { get { return SqlDbType.Bit; } } + public AbstractDbType DbType => new AbstractDbType(SqlDbType.Bit, NpgsqlDbType.Boolean); string? IColumn.UserDefinedTypeName { get { return null; } } bool IColumn.PrimaryKey { get { return false; } } bool IColumn.Identity { get { return false; } } @@ -763,7 +802,7 @@ public partial class FieldReference : Field, IColumn, IFieldReference int? IColumn.Scale { get { return null; } } public Table ReferenceTable { get; set; } Table? IColumn.ReferenceTable => ReferenceTable; - public SqlDbType SqlDbType { get { return ReferenceTable.PrimaryKey.SqlDbType; } } + public AbstractDbType DbType { get { return ReferenceTable.PrimaryKey.DbType; } } public string? Collation { get { return ReferenceTable.PrimaryKey.Collation; } } public string? UserDefinedTypeName { get { return ReferenceTable.PrimaryKey.UserDefinedTypeName; } } public virtual Type Type { get { return this.Nullable.ToBool() ? ReferenceTable.PrimaryKey.Type.Nullify() : ReferenceTable.PrimaryKey.Type; } } @@ -1007,7 +1046,7 @@ public partial class ImplementationColumn : IColumn int? IColumn.Scale { get { return null; } } public Table ReferenceTable { get; private set; } Table? IColumn.ReferenceTable => ReferenceTable; - public SqlDbType SqlDbType { get { return ReferenceTable.PrimaryKey.SqlDbType; } } + public AbstractDbType DbType { get { return ReferenceTable.PrimaryKey.DbType; } } public string? Collation { get { return ReferenceTable.PrimaryKey.Collation; } } public string? UserDefinedTypeName { get { return ReferenceTable.PrimaryKey.UserDefinedTypeName; } } public Type Type { get { return this.Nullable.ToBool() ? ReferenceTable.PrimaryKey.Type.Nullify() : ReferenceTable.PrimaryKey.Type; } } @@ -1033,7 +1072,7 @@ public partial class ImplementationStringColumn : IColumn int? IColumn.Scale { get { return null; } } public string? Collation { get; set; } public Table? ReferenceTable { get { return null; } } - public SqlDbType SqlDbType { get { return SqlDbType.NVarChar; } } + public AbstractDbType DbType => new AbstractDbType(SqlDbType.NVarChar, NpgsqlDbType.Varchar); public Type Type { get { return typeof(string); } } public bool AvoidForeignKey { get { return false; } } public string? Default { get; set; } @@ -1108,7 +1147,7 @@ public class PrimaryKeyColumn : IColumn { public string Name { get; set; } IsNullable IColumn.Nullable { get { return IsNullable.No; } } - public SqlDbType SqlDbType { get; set; } + public AbstractDbType DbType { get; set; } public string? Collation { get; set; } public string? UserDefinedTypeName { get; set; } bool IColumn.PrimaryKey { get { return true; } } @@ -1248,4 +1287,200 @@ public IEnumerable> GetTables() return null; } } + + public struct AbstractDbType + { + SqlDbType? sqlServer; + public SqlDbType SqlServer => sqlServer ?? throw new InvalidOperationException("No SqlDbType type defined"); + + NpgsqlDbType? posrtgreSql; + public NpgsqlDbType PostgreSql => posrtgreSql ?? throw new InvalidOperationException("No PostgresSql type defined"); + + public AbstractDbType(SqlDbType sqlDbType) + { + this.sqlServer = sqlDbType; + this.posrtgreSql = null; + } + + public AbstractDbType(NpgsqlDbType npgsqlDbType) + { + this.sqlServer = null; + this.posrtgreSql = npgsqlDbType; + } + + public AbstractDbType(SqlDbType sqlDbType, NpgsqlDbType npgsqlDbType) + { + this.sqlServer = sqlDbType; + this.posrtgreSql = npgsqlDbType; + } + + public bool IsDate() + { + if (sqlServer is SqlDbType s) + switch (s) + { + case SqlDbType.Date: + case SqlDbType.DateTime: + case SqlDbType.DateTime2: + case SqlDbType.SmallDateTime: + return true; + default: + return false; + } + + if (posrtgreSql is NpgsqlDbType p) + switch (p) + { + case NpgsqlDbType.Date: + case NpgsqlDbType.Timestamp: + return true; + default: + return false; + } + + throw new NotImplementedException(); + } + + public bool IsNumber() + { + if(sqlServer is SqlDbType s) + switch (s) + { + case SqlDbType.BigInt: + case SqlDbType.Float: + case SqlDbType.Decimal: + case SqlDbType.Int: + case SqlDbType.Bit: + case SqlDbType.Money: + case SqlDbType.Real: + case SqlDbType.TinyInt: + case SqlDbType.SmallInt: + case SqlDbType.SmallMoney: + return true; + default: + return false; + } + + if (posrtgreSql is NpgsqlDbType p) + switch (p) + { + case NpgsqlDbType.Smallint: + case NpgsqlDbType.Integer: + case NpgsqlDbType.Bigint: + case NpgsqlDbType.Numeric: + case NpgsqlDbType.Money: + case NpgsqlDbType.Real: + case NpgsqlDbType.Double: + return true; + default: + return false; + } + + throw new NotImplementedException(); + } + + public bool IsString() + { + if (sqlServer is SqlDbType s) + switch (s) + { + case SqlDbType.NText: + case SqlDbType.NVarChar: + case SqlDbType.Text: + case SqlDbType.VarChar: + return true; + default: + return false; + } + + + if (posrtgreSql is NpgsqlDbType p) + switch (p) + { + case NpgsqlDbType.Char: + case NpgsqlDbType.Varchar: + case NpgsqlDbType.Text: + return true; + default: + return false; + } + + throw new NotImplementedException(); + } + + + public override string? ToString() => throw new InvalidOperationException("use ToString(isPostgress)"); + public string ToString(bool isPostgres) + { + if (!isPostgres) + sqlServer.ToString()!.ToUpperInvariant(); + + var pg = posrtgreSql!.Value; + if ((pg & NpgsqlDbType.Array) != 0) + return (pg & ~NpgsqlDbType.Range).ToString() + "[]"; + + if ((pg & NpgsqlDbType.Range) != 0) + switch (pg & ~NpgsqlDbType.Range) + { + case NpgsqlDbType.Integer: return "int4range"; + case NpgsqlDbType.Bigint : return "int8range"; + case NpgsqlDbType.Numeric: return "numrange"; + case NpgsqlDbType.TimestampTz: return "tstzrange"; + case NpgsqlDbType.Date: return "daterange"; + throw new InvalidOperationException(""); + } + + return pg.ToString()!; + } + + public bool IsGuid() + { + if (sqlServer is SqlDbType s) + switch (s) + { + case SqlDbType.UniqueIdentifier: + return true; + default: + return false; + } + + + if (posrtgreSql is NpgsqlDbType p) + switch (p) + { + case NpgsqlDbType.Uuid: + return true; + default: + return false; + } + + throw new NotImplementedException(); + } + + internal bool IsDecimal() + { + if (sqlServer is SqlDbType s) + switch (s) + { + case SqlDbType.Decimal: + return true; + default: + return false; + } + + if (posrtgreSql is NpgsqlDbType p) + switch (p) + { + case NpgsqlDbType.Numeric: + return true; + default: + return false; + } + + throw new NotImplementedException(); + } + } } + + + diff --git a/Signum.Engine/Schema/Schema.Delete.cs b/Signum.Engine/Schema/Schema.Delete.cs index 8064bfd690..384b682066 100644 --- a/Signum.Engine/Schema/Schema.Delete.cs +++ b/Signum.Engine/Schema/Schema.Delete.cs @@ -18,14 +18,14 @@ public SqlPreCommand DeleteSqlSync(T entity, Expression>? where var declaration = where != null ? DeclarePrimaryKeyVariable(entity, where) : null; var variableOrId = entity.Id.VariableName ?? entity.Id.Object; - + var isPostgres = Schema.Current.Settings.IsPostgres; var pre = OnPreDeleteSqlSync(entity); var collections = (from tml in this.TablesMList() select new SqlPreCommandSimple("DELETE {0} WHERE {1} = {2} --{3}" - .FormatWith(tml.Name, tml.BackReference.Name.SqlEscape(), variableOrId, comment ?? entity.ToString()))).Combine(Spacing.Simple); + .FormatWith(tml.Name, tml.BackReference.Name.SqlEscape(isPostgres), variableOrId, comment ?? entity.ToString()))).Combine(Spacing.Simple); var main = new SqlPreCommandSimple("DELETE {0} WHERE {1} = {2} --{3}" - .FormatWith(Name, this.PrimaryKey.Name.SqlEscape(), variableOrId, comment ?? entity.ToString())); + .FormatWith(Name, this.PrimaryKey.Name.SqlEscape(isPostgres), variableOrId, comment ?? entity.ToString())); return SqlPreCommand.Combine(Spacing.Simple, declaration, pre, collections, main)!; } @@ -40,7 +40,7 @@ private SqlPreCommand DeclarePrimaryKeyVariable(T entity, Expression? graph) public partial class Table { + internal static string Var(bool isPostgres, string varName) + { + if (isPostgres) + return varName; + else + return "@" + varName; + } + + internal static string DeclareTempTable(bool isPostgres, string variableName, string primaryKeyType) + { + if (isPostgres) + return $"CREATE TEMP TABLE {variableName} (Id {primaryKeyType});"; + else + return $"DECLARE {variableName} TABLE(Id {primaryKeyType});"; + } + ResetLazy inserterIdentity; ResetLazy inserterDisableIdentity; @@ -91,22 +107,25 @@ internal object[] BulkInsertDataRow(object/*Entity or IView*/ entity) internal List GetInsertParameters(object entity) { - return IdentityBehaviour ? - inserterIdentity.Value.InsertParameters((Entity)entity, new Forbidden(), "") : - inserterDisableIdentity.Value.InsertParameters(entity, new Forbidden(), ""); + List parameters = new List(); + if (IdentityBehaviour) + inserterIdentity.Value.InsertParameters((Entity)entity, new Forbidden(), "", parameters); + else + inserterDisableIdentity.Value.InsertParameters(entity, new Forbidden(), "", parameters); + return parameters; } class InsertCacheDisableIdentity { internal Table table; - public Func SqlInsertPattern; - public Func> InsertParameters; + public Func SqlInsertPattern; + public Action> InsertParameters; ConcurrentDictionary, DirectedGraph?>> insertDisableIdentityCache = new ConcurrentDictionary, DirectedGraph?>>(); - public InsertCacheDisableIdentity(Table table, Func sqlInsertPattern, Func> insertParameters) + public InsertCacheDisableIdentity(Table table, Func sqlInsertPattern, Action> insertParameters) { this.table = table; SqlInsertPattern = sqlInsertPattern; @@ -118,10 +137,9 @@ public InsertCacheDisableIdentity(Table table, Func sqlInsertPat return insertDisableIdentityCache.GetOrAdd(numElements, (int num) => num == 1 ? GetInsertDisableIdentity() : GetInsertMultiDisableIdentity(num)); } - Action, DirectedGraph?> GetInsertDisableIdentity() { - string sqlSingle = SqlInsertPattern(""); + string sqlSingle = SqlInsertPattern(new[] { "" }); return (list, graph) => { @@ -135,7 +153,9 @@ public InsertCacheDisableIdentity(Table table, Func sqlInsertPat var forbidden = new Forbidden(graph, entity); - new SqlPreCommandSimple(sqlSingle, InsertParameters(entity, forbidden, "")).ExecuteNonQuery(); + var parameters = new List(); + InsertParameters(entity, forbidden, "", parameters); + new SqlPreCommandSimple(sqlSingle, parameters).ExecuteNonQuery(); entity.IsNew = false; if (table.saveCollections.Value != null) @@ -143,11 +163,9 @@ public InsertCacheDisableIdentity(Table table, Func sqlInsertPat }; } - - Action, DirectedGraph?> GetInsertMultiDisableIdentity(int num) { - string sqlMulti = Enumerable.Range(0, num).ToString(i => SqlInsertPattern(i.ToString()), ";\r\n"); + string sqlMulti = SqlInsertPattern(Enumerable.Range(0, num).Select(i => i.ToString()).ToArray()); return (entities, graph) => { @@ -163,7 +181,7 @@ public InsertCacheDisableIdentity(Table table, Func sqlInsertPat List result = new List(); for (int i = 0; i < entities.Count; i++) - result.AddRange(InsertParameters(entities[i], new Forbidden(graph, entities[i]), i.ToString())); + InsertParameters(entities[i], new Forbidden(graph, entities[i]), i.ToString(), result); new SqlPreCommandSimple(sqlMulti, result).ExecuteNonQuery(); for (int i = 0; i < num; i++) @@ -187,6 +205,7 @@ internal static InsertCacheDisableIdentity InitializeInsertDisableIdentity(Table var paramIdent = Expression.Parameter(typeof(object) /*Entity*/, "ident"); var paramForbidden = Expression.Parameter(typeof(Forbidden), "forbidden"); var paramSuffix = Expression.Parameter(typeof(string), "suffix"); + var paramList = Expression.Parameter(typeof(List), "dbParams"); var cast = Expression.Parameter(table.Type, "casted"); assigments.Add(Expression.Assign(cast, Expression.Convert(paramIdent, table.Type))); @@ -198,13 +217,15 @@ internal static InsertCacheDisableIdentity InitializeInsertDisableIdentity(Table foreach (var item in table.Mixins.Values) item.CreateParameter(trios, assigments, cast, paramForbidden, paramSuffix); - Func insertPattern = (suffix) => - "INSERT {0} ({1})\r\n VALUES ({2})".FormatWith(table.Name, - trios.ToString(p => p.SourceColumn.SqlEscape(), ", "), - trios.ToString(p => p.ParameterName + suffix, ", ")); + var isPostgres = Schema.Current.Settings.IsPostgres; + + Func insertPattern = (suffixes) => + "INSERT INTO {0} ({1}) VALUES \r\n{2};".FormatWith(table.Name, + trios.ToString(p => p.SourceColumn.SqlEscape(isPostgres), ", "), + suffixes.ToString(s => " (" + trios.ToString(p => p.ParameterName + s, ", ") + ")", "\r\n")); - var expr = Expression.Lambda>>( - CreateBlock(trios.Select(a => a.ParameterBuilder), assigments), paramIdent, paramForbidden, paramSuffix); + var expr = Expression.Lambda>>( + CreateBlock(trios.Select(a => a.ParameterBuilder), assigments, paramList), paramIdent, paramForbidden, paramSuffix, paramList); return new InsertCacheDisableIdentity(table, insertPattern, expr.Compile()); @@ -216,13 +237,13 @@ class InsertCacheIdentity { internal Table table; - public Func SqlInsertPattern; - public Func> InsertParameters; + public Func SqlInsertPattern; + public Action> InsertParameters; ConcurrentDictionary, DirectedGraph?>> insertIdentityCache = new ConcurrentDictionary, DirectedGraph?>>(); - public InsertCacheIdentity(Table table, Func sqlInsertPattern, Func> insertParameters) + public InsertCacheIdentity(Table table, Func sqlInsertPattern, Action> insertParameters) { this.table = table; SqlInsertPattern = sqlInsertPattern; @@ -236,10 +257,9 @@ public InsertCacheIdentity(Table table, Func sqlInsertPatt Action, DirectedGraph?> GetInsertMultiIdentity(int num) { - string sqlMulti = new StringBuilder() - .AppendLine("DECLARE @MyTable TABLE (Id " + this.table.PrimaryKey.SqlDbType.ToString().ToUpperInvariant() + ");") - .AppendLines(Enumerable.Range(0, num).Select(i => SqlInsertPattern(i.ToString(), true))) - .AppendLine("SELECT Id from @MyTable").ToString(); + var isPostgres = Schema.Current.Settings.IsPostgres; + var newIds = Var(isPostgres, "newIDs"); + string sqlMulti = SqlInsertPattern(Enumerable.Range(0, num).Select(i => i.ToString()).ToArray(), true); return (entities, graph) => { @@ -255,7 +275,7 @@ public InsertCacheIdentity(Table table, Func sqlInsertPatt List result = new List(); for (int i = 0; i < entities.Count; i++) - result.AddRange(InsertParameters(entities[i], new Forbidden(graph, entities[i]), i.ToString())); + InsertParameters(entities[i], new Forbidden(graph, entities[i]), i.ToString(), result); DataTable dt = new SqlPreCommandSimple(sqlMulti, result).ExecuteDataTable(); @@ -282,6 +302,7 @@ internal static InsertCacheIdentity InitializeInsertIdentity(Table table) var paramIdent = Expression.Parameter(typeof(Entity), "ident"); var paramForbidden = Expression.Parameter(typeof(Forbidden), "forbidden"); var paramSuffix = Expression.Parameter(typeof(string), "suffix"); + var paramList = Expression.Parameter(typeof(List), "dbParams"); var cast = Expression.Parameter(table.Type, "casted"); assigments.Add(Expression.Assign(cast, Expression.Convert(paramIdent, table.Type))); @@ -293,16 +314,19 @@ internal static InsertCacheIdentity InitializeInsertIdentity(Table table) foreach (var item in table.Mixins.Values) item.CreateParameter(trios, assigments, cast, paramForbidden, paramSuffix); - Func sqlInsertPattern = (suffix, output) => - "INSERT {0} ({1})\r\n{2} VALUES ({3})".FormatWith( + var isPostgres = Schema.Current.Settings.IsPostgres; + var newIds = Var(isPostgres, "newIDs"); + Func sqlInsertPattern = (suffixes, output) => + "INSERT INTO {0} ({1})\r\n{2}VALUES \r\n{3};".FormatWith( table.Name, - trios.ToString(p => p.SourceColumn.SqlEscape(), ", "), - output ? "OUTPUT INSERTED.Id into @MyTable \r\n" : null, - trios.ToString(p => p.ParameterName + suffix, ", ")); + trios.ToString(p => p.SourceColumn.SqlEscape(isPostgres), ", "), + output && !isPostgres ? $"OUTPUT INSERTED.Id\r\n" : null, + suffixes.ToString(s => " (" + trios.ToString(p => p.ParameterName + s, ", ") + ")", "\r\n"), + output && isPostgres ? $"\r\nRETURNING Id" : null); - var expr = Expression.Lambda>>( - CreateBlock(trios.Select(a => a.ParameterBuilder), assigments), paramIdent, paramForbidden, paramSuffix); + var expr = Expression.Lambda>>( + CreateBlock(trios.Select(a => a.ParameterBuilder), assigments, paramList), paramIdent, paramForbidden, paramSuffix, paramList); return new InsertCacheIdentity(table, sqlInsertPattern, expr.Compile()); } @@ -435,10 +459,12 @@ public UpdateCache(Table table, Func sqlUpdatePattern, Fun Action, DirectedGraph?> GetUpdateMultiple(int num) { + var isPostgres = Schema.Current.Settings.IsPostgres; + var notFound = Table.Var(isPostgres, "notFound"); string sqlMulti = new StringBuilder() - .AppendLine("DECLARE @NotFound TABLE (Id " + this.table.PrimaryKey.SqlDbType.ToString().ToUpperInvariant() + ");") + .AppendLine(Table.DeclareTempTable(isPostgres, notFound, this.table.PrimaryKey.DbType.ToString(isPostgres))) .AppendLines(Enumerable.Range(0, num).Select(i => SqlUpdatePattern(i.ToString(), true))) - .AppendLine("SELECT Id from @NotFound").ToString(); + .AppendLine($"SELECT Id from {notFound}").ToString(); if (table.Ticks != null) { @@ -501,6 +527,7 @@ internal static UpdateCache InitializeUpdate(Table table) var paramForbidden = Expression.Parameter(typeof(Forbidden), "forbidden"); var paramOldTicks = Expression.Parameter(typeof(long), "oldTicks"); var paramSuffix = Expression.Parameter(typeof(string), "suffix"); + var paramList = Expression.Parameter(typeof(List), "paramList"); var cast = Expression.Parameter(table.Type); assigments.Add(Expression.Assign(cast, Expression.Convert(paramIdent, table.Type))); @@ -518,39 +545,40 @@ internal static UpdateCache InitializeUpdate(Table table) string oldTicksParamName = ParameterBuilder.GetParameterName("old_ticks"); + var isPostgres = Schema.Current.Settings.IsPostgres; + var notFound = Table.Var(isPostgres, "notFound"); + Func sqlUpdatePattern = (suffix, output) => { - string update = "UPDATE {0} SET \r\n{1}\r\n WHERE {2} = {3}".FormatWith( + string update = "UPDATE {0} SET \r\n{1}\r\n WHERE {2} = {3}{4};".FormatWith( table.Name, - trios.ToString(p => "{0} = {1}".FormatWith(p.SourceColumn.SqlEscape(), p.ParameterName + suffix).Indent(2), ",\r\n"), - table.PrimaryKey.Name.SqlEscape(), - idParamName + suffix); - - - if (table.Ticks != null) - update += " AND {0} = {1}".FormatWith(table.Ticks.Name.SqlEscape(), oldTicksParamName + suffix); + trios.ToString(p => "{0} = {1}".FormatWith(p.SourceColumn.SqlEscape(isPostgres), p.ParameterName + suffix).Indent(2), ",\r\n"), + table.PrimaryKey.Name.SqlEscape(isPostgres), + idParamName + suffix, + table.Ticks != null ? " AND {0} = {1}".FormatWith(table.Ticks.Name.SqlEscape(isPostgres), oldTicksParamName + suffix) : null + ); if (!output) return update; else - return update + "\r\nIF @@ROWCOUNT = 0 INSERT INTO @NotFound (id) VALUES ({0})".FormatWith(idParamName + suffix); + return update + $"\r\nIF @@ROWCOUNT = 0 INSERT INTO {notFound} (id) VALUES ({idParamName + suffix});"; }; List parameters = new List { - pb.ParameterFactory(Trio.Concat(idParamName, paramSuffix), table.PrimaryKey.SqlDbType, null, false, + pb.ParameterFactory(Trio.Concat(idParamName, paramSuffix), table.PrimaryKey.DbType, null, false, Expression.Field(Expression.Property(Expression.Field(paramIdent, fiId), "Value"), "Object")) }; if (table.Ticks != null) { - parameters.Add(pb.ParameterFactory(Trio.Concat(oldTicksParamName, paramSuffix), table.Ticks.SqlDbType, null, false, table.Ticks.ConvertTicks(paramOldTicks))); + parameters.Add(pb.ParameterFactory(Trio.Concat(oldTicksParamName, paramSuffix), table.Ticks.DbType, null, false, table.Ticks.ConvertTicks(paramOldTicks))); } parameters.AddRange(trios.Select(a => (Expression)a.ParameterBuilder)); var expr = Expression.Lambda>>( - CreateBlock(parameters, assigments), paramIdent, paramOldTicks, paramForbidden, paramSuffix); + CreateBlock(parameters, assigments, paramList), paramIdent, paramOldTicks, paramForbidden, paramSuffix, paramList); return new UpdateCache(table, sqlUpdatePattern, expr.Compile()); @@ -629,36 +657,42 @@ public SqlPreCommand InsertSqlSync(Entity ident, bool includeCollections = true, PrepareEntitySync(ident); SetToStrField(ident); - bool isGuid = this.PrimaryKey.SqlDbType == SqlDbType.UniqueIdentifier; + bool isGuid = this.PrimaryKey.DbType.IsGuid(); SqlPreCommand? collections = GetInsertCollectionSync(ident, includeCollections, suffix); SqlPreCommandSimple insert = IdentityBehaviour ? new SqlPreCommandSimple( - inserterIdentity.Value.SqlInsertPattern(suffix, isGuid && collections != null), - inserterIdentity.Value.InsertParameters(ident, new Forbidden(), suffix)).AddComment(comment) : + inserterIdentity.Value.SqlInsertPattern(new[] { suffix }, isGuid && collections != null), + new List().Do(dbParams => inserterIdentity.Value.InsertParameters(ident, new Forbidden(), suffix, dbParams))).AddComment(comment) : new SqlPreCommandSimple( - inserterDisableIdentity.Value.SqlInsertPattern(suffix), - inserterDisableIdentity.Value.InsertParameters(ident, new Forbidden(), suffix)).AddComment(comment); + inserterDisableIdentity.Value.SqlInsertPattern(new[] { suffix }), + new List().Do(dbParams => inserterDisableIdentity.Value.InsertParameters(ident, new Forbidden(), suffix, dbParams))).AddComment(comment); if (collections == null) return insert; + bool isPostgres = Schema.Current.Settings.IsPostgres; + var pkType = this.PrimaryKey.DbType.ToString(isPostgres); + var newIds = Table.Var(isPostgres, "newIDs"); + var parentId = Table.Var(isPostgres, "parentId"); + if (isGuid) { - SqlPreCommand idTable = new SqlPreCommandSimple("DECLARE @MyTable table (Id UNIQUEIDENTIFIER)") { GoBefore = true }; - - SqlPreCommand declareParent = new SqlPreCommandSimple("DECLARE @parentId UNIQUEIDENTIFIER"); - SqlPreCommand setParent = new SqlPreCommandSimple("SELECT @parentId= ID FROM @MyTable"); - - return SqlPreCommand.Combine(Spacing.Simple, idTable, insert, declareParent, setParent, collections)!; + return SqlPreCommand.Combine(Spacing.Simple, + insert, + new SqlPreCommandSimple($"DECLARE {parentId} {pkType};"), + new SqlPreCommandSimple($"SELECT {parentId} = ID FROM {newIds};"), + collections)!; } else { - SqlPreCommand declareParent = new SqlPreCommandSimple("DECLARE @parentId INT") { GoBefore = true }; - SqlPreCommand setParent = new SqlPreCommandSimple("SET @parentId = @@Identity"); - return SqlPreCommand.Combine(Spacing.Simple, declareParent, insert, setParent, collections)!; + return SqlPreCommand.Combine(Spacing.Simple, + new SqlPreCommandSimple($"DECLARE {parentId} {pkType};") { GoBefore = true }, + insert, + new SqlPreCommandSimple($"SET {parentId} = @@Identity;"), + collections)!; } } @@ -727,7 +761,7 @@ public Trio(IColumn column, Expression value, Expression suffix) { this.SourceColumn = column.Name; this.ParameterName = Engine.ParameterBuilder.GetParameterName(column.Name); - this.ParameterBuilder = Connector.Current.ParameterBuilder.ParameterFactory(Concat(this.ParameterName, suffix), column.SqlDbType, column.UserDefinedTypeName, column.Nullable.ToBool(), value); + this.ParameterBuilder = Connector.Current.ParameterBuilder.ParameterFactory(Concat(this.ParameterName, suffix), column.DbType, column.UserDefinedTypeName, column.Nullable.ToBool(), value); } public string SourceColumn; @@ -747,14 +781,13 @@ internal static Expression Concat(string baseName, Expression suffix) } } - static ConstructorInfo ciNewList = ReflectionTools.GetConstuctorInfo(() => new List(1)); + static MethodInfo miAdd = ReflectionTools.GetMethodInfo(() => new List(1).Add(null!)); - public static Expression CreateBlock(IEnumerable parameters, IEnumerable assigments) + public static Expression CreateBlock(IEnumerable parameters, IEnumerable assigments, Expression parameterList) { - return Expression.Block(assigments.OfType().Select(a => (ParameterExpression)a.Left), - assigments.And( - Expression.ListInit(Expression.New(ciNewList, Expression.Constant(parameters.Count())), - parameters))); + return Expression.Block( + assigments.OfType().Select(a => (ParameterExpression)a.Left), + assigments.Concat(parameters.Select(p => Expression.Call(parameterList, miAdd, p)))); } } @@ -879,10 +912,13 @@ Action> GetInsert(int numElements) { return insertCache.GetOrAdd(numElements, num => { + bool isPostgres = Schema.Current.Settings.IsPostgres; + var newIds = Table.Var(isPostgres, "newIDs"); + string sqlMulti = new StringBuilder() - .AppendLine("DECLARE @MyTable TABLE (Id " + this.table.PrimaryKey.SqlDbType.ToString().ToUpperInvariant() + ");") + .AppendLine(Table.DeclareTempTable(isPostgres, newIds, this.table.PrimaryKey.DbType.ToString(isPostgres))) .AppendLines(Enumerable.Range(0, num).Select(i => sqlInsert(i.ToString(), true))) - .AppendLine("SELECT Id from @MyTable").ToString(); + .AppendLine($"SELECT Id from {newIds}").ToString(); return (List list) => { @@ -1067,22 +1103,23 @@ public void RelationalUpdates(List idents) TableMListCache CreateCache() { var pb = Connector.Current.ParameterBuilder; + var isPostgres = Schema.Current.Settings.IsPostgres; TableMListCache result = new TableMListCache { table = this, Getter = entity => (MList)Getter(entity), - sqlDelete = suffix => "DELETE {0} WHERE {1} = {2}".FormatWith(Name, BackReference.Name.SqlEscape(), ParameterBuilder.GetParameterName(BackReference.Name + suffix)), + sqlDelete = suffix => "DELETE {0} WHERE {1} = {2}".FormatWith(Name, BackReference.Name.SqlEscape(isPostgres), ParameterBuilder.GetParameterName(BackReference.Name + suffix)), DeleteParameter = (ident, suffix) => pb.CreateReferenceParameter(ParameterBuilder.GetParameterName(BackReference.Name + suffix), ident.Id, this.BackReference.ReferenceTable.PrimaryKey), sqlDeleteExcept = num => { var sql = "DELETE {0} WHERE {1} = {2}" - .FormatWith(Name, BackReference.Name.SqlEscape(), ParameterBuilder.GetParameterName(BackReference.Name)); + .FormatWith(Name, BackReference.Name.SqlEscape(isPostgres), ParameterBuilder.GetParameterName(BackReference.Name)); sql += " AND {0} NOT IN ({1})" - .FormatWith(PrimaryKey.Name.SqlEscape(), 0.To(num).Select(i => ParameterBuilder.GetParameterName("e" + i)).ToString(", ")); + .FormatWith(PrimaryKey.Name.SqlEscape(isPostgres), 0.To(num).Select(i => ParameterBuilder.GetParameterName("e" + i)).ToString(", ")); return sql; }, @@ -1104,8 +1141,7 @@ TableMListCache CreateCache() var paramOrder = Expression.Parameter(typeof(int), "order"); var paramForbidden = Expression.Parameter(typeof(Forbidden), "forbidden"); var paramSuffix = Expression.Parameter(typeof(string), "suffix"); - - + var paramList = Expression.Parameter(typeof(List), "paramList"); { var trios = new List(); var assigments = new List(); @@ -1115,13 +1151,15 @@ TableMListCache CreateCache() Order.CreateParameter(trios, assigments, paramOrder, paramForbidden, paramSuffix); Field.CreateParameter(trios, assigments, paramItem, paramForbidden, paramSuffix); - result.sqlInsert = (suffix, output) => "INSERT {0} ({1})\r\n{2} VALUES ({3})".FormatWith(Name, - trios.ToString(p => p.SourceColumn.SqlEscape(), ", "), - output ? "OUTPUT INSERTED.Id into @MyTable \r\n" : null, + var newIds = Table.Var(isPostgres, "newIDs"); + + result.sqlInsert = (suffix, output) => $"INSERT INTO {Name} ({1})\r\n{2} VALUES ({3});".FormatWith(Name, + trios.ToString(p => p.SourceColumn.SqlEscape(isPostgres), ", "), + output ? $"OUTPUT INSERTED.Id into {newIds} \r\n" : null, trios.ToString(p => p.ParameterName + suffix, ", ")); var expr = Expression.Lambda>>( - Table.CreateBlock(trios.Select(a => a.ParameterBuilder), assigments), paramIdent, paramItem, paramOrder, paramForbidden, paramSuffix); + Table.CreateBlock(trios.Select(a => a.ParameterBuilder), assigments, paramList), paramIdent, paramItem, paramOrder, paramForbidden, paramSuffix, paramList); result.InsertParameters = expr.Compile(); } @@ -1144,20 +1182,20 @@ TableMListCache CreateCache() Order.CreateParameter(trios, assigments, paramOrder, paramForbidden, paramSuffix); Field.CreateParameter(trios, assigments, paramItem, paramForbidden, paramSuffix); - result.sqlUpdate = suffix => "UPDATE {0} SET \r\n{1}\r\n WHERE {2} = {3} AND {4} = {5}".FormatWith(Name, - trios.ToString(p => "{0} = {1}".FormatWith(p.SourceColumn.SqlEscape(), p.ParameterName + suffix).Indent(2), ",\r\n"), - this.BackReference.Name.SqlEscape(), ParameterBuilder.GetParameterName(parentId + suffix), - this.PrimaryKey.Name.SqlEscape(), ParameterBuilder.GetParameterName(rowId + suffix)); + result.sqlUpdate = suffix => "UPDATE {0} SET \r\n{1}\r\n WHERE {2} = {3} AND {4} = {5};".FormatWith(Name, + trios.ToString(p => "{0} = {1}".FormatWith(p.SourceColumn.SqlEscape(isPostgres), p.ParameterName + suffix).Indent(2), ",\r\n"), + this.BackReference.Name.SqlEscape(isPostgres), ParameterBuilder.GetParameterName(parentId + suffix), + this.PrimaryKey.Name.SqlEscape(isPostgres), ParameterBuilder.GetParameterName(rowId + suffix)); var parameters = trios.Select(a => a.ParameterBuilder).ToList(); - parameters.Add(pb.ParameterFactory(Table.Trio.Concat(parentId, paramSuffix), this.BackReference.SqlDbType, null, false, + parameters.Add(pb.ParameterFactory(Table.Trio.Concat(parentId, paramSuffix), this.BackReference.DbType, null, false, Expression.Field(Expression.Property(Expression.Field(paramIdent, Table.fiId), "Value"), "Object"))); - parameters.Add(pb.ParameterFactory(Table.Trio.Concat(rowId, paramSuffix), this.PrimaryKey.SqlDbType, null, false, + parameters.Add(pb.ParameterFactory(Table.Trio.Concat(rowId, paramSuffix), this.PrimaryKey.DbType, null, false, Expression.Field(paramRowId, "Object"))); var expr = Expression.Lambda>>( - Table.CreateBlock(parameters, assigments), paramIdent, paramRowId, paramItem, paramOrder, paramForbidden, paramSuffix); + Table.CreateBlock(parameters, assigments, paramList), paramIdent, paramRowId, paramItem, paramOrder, paramForbidden, paramSuffix, paramList); result.UpdateParameters = expr.Compile(); } diff --git a/Signum.Engine/Schema/Schema.cs b/Signum.Engine/Schema/Schema.cs index 594354ffed..5caecf0873 100644 --- a/Signum.Engine/Schema/Schema.cs +++ b/Signum.Engine/Schema/Schema.cs @@ -61,8 +61,10 @@ public Dictionary Tables get { return tables; } } - const string errorType = "TypeEntity table not cached. Remember to call Schema.Current.Initialize"; - + public List PostgreeExtensions = new List() + { + "uuid-ossp" + }; #region Events @@ -541,6 +543,8 @@ static Schema() ModifiableEntity.SetIsRetrievingFunc(() => EntityCache.HasRetriever); } + + internal Schema(SchemaSettings settings) { this.typeCachesLazy = null!; @@ -549,6 +553,8 @@ internal Schema(SchemaSettings settings) this.ViewBuilder = new Maps.ViewBuilder(this); Generating += SchemaGenerator.SnapshotIsolation; + Generating += SchemaGenerator.PostgreeExtensions; + Generating += SchemaGenerator.PostgreeTemporalTableScript; Generating += SchemaGenerator.CreateSchemasScript; Generating += SchemaGenerator.CreateTablesScript; Generating += SchemaGenerator.InsertEnumValuesScript; diff --git a/Signum.Engine/Schema/SchemaAssets.cs b/Signum.Engine/Schema/SchemaAssets.cs index 722a024d55..607c30c497 100644 --- a/Signum.Engine/Schema/SchemaAssets.cs +++ b/Signum.Engine/Schema/SchemaAssets.cs @@ -53,7 +53,8 @@ public SqlPreCommandSimple AlterView() public View IncludeView(string viewName, string viewDefinition) { - return IncludeView(new ObjectName(SchemaName.Default, viewName), viewDefinition); + var isPostgres = Schema.Current.Settings.IsPostgres; + return IncludeView(new ObjectName(SchemaName.Default(isPostgres), viewName, isPostgres), viewDefinition); } public Dictionary Views = new Dictionary(); @@ -69,6 +70,7 @@ public View IncludeView(ObjectName viewName, string viewDefinition) SqlPreCommand? SyncViews(Replacements replacements) { + var isPostgres = Schema.Current.Settings.IsPostgres; var oldView = Schema.Current.DatabaseNames().SelectMany(db => { using (Administrator.OverrideDatabaseInSysViews(db)) @@ -76,7 +78,7 @@ public View IncludeView(ObjectName viewName, string viewDefinition) return (from v in Database.View() join s in Database.View() on v.schema_id equals s.schema_id join m in Database.View() on v.object_id equals m.object_id - select KeyValuePair.Create(new ObjectName(new SchemaName(db, s.name), v.name), m.definition)).ToList(); + select KeyValuePair.Create(new ObjectName(new SchemaName(db, s.name, isPostgres), v.name, isPostgres), m.definition)).ToList(); } }).ToDictionary(); @@ -95,7 +97,8 @@ join m in Database.View() on v.object_id equals m.object_id public Dictionary StoreProcedures = new Dictionary(); public Procedure IncludeStoreProcedure(string procedureName, string procedureCodeAndArguments) { - return IncludeStoreProcedure(new ObjectName(SchemaName.Default, procedureName), procedureCodeAndArguments); + var isPostgres = Schema.Current.Settings.IsPostgres; + return IncludeStoreProcedure(new ObjectName(SchemaName.Default(isPostgres), procedureName, isPostgres), procedureCodeAndArguments); } public Procedure IncludeStoreProcedure(ObjectName procedureName, string procedureCodeAndArguments) @@ -105,7 +108,8 @@ public Procedure IncludeStoreProcedure(ObjectName procedureName, string procedur public Procedure IncludeUserDefinedFunction(string functionName, string functionCodeAndArguments) { - return IncludeUserDefinedFunction(new ObjectName(SchemaName.Default, functionName), functionCodeAndArguments); + var isPostgres = Schema.Current.Settings.IsPostgres; + return IncludeUserDefinedFunction(new ObjectName(SchemaName.Default(isPostgres), functionName, isPostgres), functionCodeAndArguments); } public Procedure IncludeUserDefinedFunction(ObjectName functionName, string functionCodeAndArguments) @@ -120,6 +124,7 @@ public Procedure IncludeUserDefinedFunction(ObjectName functionName, string func SqlPreCommand? SyncProcedures(Replacements replacements) { + var isPostgres = Schema.Current.Settings.IsPostgres; var oldProcedures = Schema.Current.DatabaseNames().SelectMany(db => { using (Administrator.OverrideDatabaseInSysViews(db)) @@ -128,7 +133,7 @@ public Procedure IncludeUserDefinedFunction(ObjectName functionName, string func join s in Database.View() on p.schema_id equals s.schema_id where p.type == "P" || p.type == "IF" || p.type == "FN" join m in Database.View() on p.object_id equals m.object_id - select KeyValuePair.Create(new ObjectName(new SchemaName(db, s.name), p.name), m.definition)).ToList(); + select KeyValuePair.Create(new ObjectName(new SchemaName(db, s.name, isPostgres), p.name, isPostgres), m.definition)).ToList(); } }).ToDictionary(); diff --git a/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs b/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs index 301785f54d..b8f9e71e28 100644 --- a/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs +++ b/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs @@ -38,8 +38,6 @@ public SchemaBuilder(bool isDefault) cleanName => schema.NameToType.TryGetC(cleanName)); FromEnumMethodExpander.miQuery = ReflectionTools.GetMethodInfo(() => Database.Query()).GetGenericMethodDefinition(); - Include() - .WithUniqueIndex(t => new { t.Namespace, t.ClassName }); } Settings.AssertNotIncluded = MixinDeclarations.AssertNotIncluded = t => @@ -304,8 +302,13 @@ void Complete(Table table) if (att == null) return null; - var tn = att.TemporalTableName != null ? ObjectName.Parse(att.TemporalTableName) : - new ObjectName(tableName.Schema, tableName.Name + "_History"); + var isPostgres = this.schema.Settings.IsPostgres; + + var tn = att.TemporalTableName != null ? ObjectName.Parse(att.TemporalTableName, isPostgres) : + new ObjectName(tableName.Schema, tableName.Name + "_History", isPostgres); + + if (isPostgres) + return new SystemVersionedInfo(tn, att.PostgreeSysPeriodColumname); return new SystemVersionedInfo(tn, att.StartDateColumnName, att.EndDateColumnName); } @@ -503,7 +506,7 @@ public enum KindOfField if (route.FieldInfo != null && ReflectionTools.FieldEquals(route.FieldInfo, fiTicks)) return KindOfField.Ticks; - if (Settings.TryGetSqlDbType(Settings.FieldAttribute(route), route.Type) != null) + if (Settings.TryGetSqlDbType(Settings.FieldAttribute(route), route.Type) != null) return KindOfField.Value; if (route.Type.UnNullify().IsEnum) @@ -527,11 +530,11 @@ protected virtual Field GenerateFieldPrimaryKey(Table table, PropertyRoute route PrimaryKey.PrimaryKeyType.SetDefinition(table.Type, attr.Type); - SqlDbTypePair pair = Settings.GetSqlDbType(attr, attr.Type); + DbTypePair pair = Settings.GetSqlDbType(attr, attr.Type); return table.PrimaryKey = new FieldPrimaryKey(route, table, attr.Name, attr.Type) { - SqlDbType = pair.SqlDbType, + DbType = pair.DbType, Collation = Settings.GetCollate(attr), UserDefinedTypeName = pair.UserDefinedTypeName, Default = attr.Default, @@ -561,43 +564,43 @@ protected virtual FieldValue GenerateFieldTicks(Table table, PropertyRoute route Type type = ticksAttr?.Type ?? route.Type; - SqlDbTypePair pair = Settings.GetSqlDbType(ticksAttr, type); + DbTypePair pair = Settings.GetSqlDbType(ticksAttr, type); string ticksName = ticksAttr?.Name ?? name.ToString(); return table.Ticks = new FieldTicks(route, type, ticksName) { - SqlDbType = pair.SqlDbType, + DbType = pair.DbType, Collation = Settings.GetCollate(ticksAttr), UserDefinedTypeName = pair.UserDefinedTypeName, Nullable = IsNullable.No, - Size = Settings.GetSqlSize(ticksAttr, null, pair.SqlDbType), - Scale = Settings.GetSqlScale(ticksAttr, null, pair.SqlDbType), + Size = Settings.GetSqlSize(ticksAttr, null, pair.DbType), + Scale = Settings.GetSqlScale(ticksAttr, null, pair.DbType), Default = ticksAttr?.Default, }; } protected virtual FieldValue GenerateFieldValue(ITable table, PropertyRoute route, NameSequence name, bool forceNull) { - var att = Settings.FieldAttribute(route); + var att = Settings.FieldAttribute(route); - SqlDbTypePair pair = Settings.GetSqlDbType(att, route.Type); + DbTypePair pair = Settings.GetSqlDbType(att, route.Type); return new FieldValue(route, null, name.ToString()) { - SqlDbType = pair.SqlDbType, + DbType = pair.DbType, Collation = Settings.GetCollate(att), UserDefinedTypeName = pair.UserDefinedTypeName, Nullable = Settings.GetIsNullable(route, forceNull), - Size = Settings.GetSqlSize(att, route, pair.SqlDbType), - Scale = Settings.GetSqlScale(att, route, pair.SqlDbType), + Size = Settings.GetSqlSize(att, route, pair.DbType), + Scale = Settings.GetSqlScale(att, route, pair.DbType), Default = att?.Default, }.Do(f => f.UniqueIndex = f.GenerateUniqueIndex(table, Settings.FieldAttribute(route))); } protected virtual FieldEnum GenerateFieldEnum(ITable table, PropertyRoute route, NameSequence name, bool forceNull) { - var att = Settings.FieldAttribute(route); + var att = Settings.FieldAttribute(route); Type cleanEnum = route.Type.UnNullify(); @@ -624,7 +627,7 @@ protected virtual FieldReference GenerateFieldReference(ITable table, PropertyRo IsLite = route.Type.IsLite(), AvoidForeignKey = Settings.FieldAttribute(route) != null, AvoidExpandOnRetrieving = Settings.FieldAttribute(route) != null, - Default = Settings.FieldAttribute(route)?.Default + Default = Settings.FieldAttribute(route)?.Default }.Do(f => f.UniqueIndex = f.GenerateUniqueIndex(table, Settings.FieldAttribute(route))); } @@ -703,12 +706,12 @@ protected virtual FieldMList GenerateFieldMList(Table table, PropertyRoute route order = new FieldValue(route: null!, fieldType: typeof(int), orderAttr.Name ?? "Order") { - SqlDbType = pair.SqlDbType, + DbType = pair.DbType, Collation = Settings.GetCollate(orderAttr), UserDefinedTypeName = pair.UserDefinedTypeName, Nullable = IsNullable.No, - Size = Settings.GetSqlSize(orderAttr, null, pair.SqlDbType), - Scale = Settings.GetSqlScale(orderAttr, null, pair.SqlDbType), + Size = Settings.GetSqlSize(orderAttr, null, pair.DbType), + Scale = Settings.GetSqlScale(orderAttr, null, pair.DbType), }; } @@ -719,7 +722,7 @@ protected virtual FieldMList GenerateFieldMList(Table table, PropertyRoute route primaryKey = new TableMList.PrimaryKeyColumn(keyAttr.Type, keyAttr.Name) { - SqlDbType = pair.SqlDbType, + DbType = pair.DbType, Collation = Settings.GetCollate(orderAttr), UserDefinedTypeName = pair.UserDefinedTypeName, Default = keyAttr.Default, @@ -796,26 +799,32 @@ protected static Type CleanType(Type type) public virtual ObjectName GenerateTableName(Type type, TableNameAttribute? tn) { - SchemaName sn = tn != null ? GetSchemaName(tn) : SchemaName.Default; + var isPostgres = Schema.Settings.IsPostgres; + + SchemaName sn = tn != null ? GetSchemaName(tn) : SchemaName.Default(isPostgres); string name = tn?.Name ?? EnumEntity.Extract(type)?.Name ?? Reflector.CleanTypeName(type); - return new ObjectName(sn, name); + return new ObjectName(sn, name, isPostgres); } private SchemaName GetSchemaName(TableNameAttribute tn) { - ServerName? server = tn.ServerName == null ? null : new ServerName(tn.ServerName); - DatabaseName? dataBase = tn.DatabaseName == null && server == null ? null : new DatabaseName(server, tn.DatabaseName!); - SchemaName schema = tn.SchemaName == null && dataBase == null ? SchemaName.Default : new SchemaName(dataBase, tn.SchemaName!); + var isPostgres = Schema.Settings.IsPostgres; + + ServerName? server = tn.ServerName == null ? null : new ServerName(tn.ServerName, isPostgres); + DatabaseName? dataBase = tn.DatabaseName == null && server == null ? null : new DatabaseName(server, tn.DatabaseName!, isPostgres); + SchemaName schema = tn.SchemaName == null && dataBase == null ? SchemaName.Default(isPostgres) : new SchemaName(dataBase, tn.SchemaName!, isPostgres); return schema; } public virtual ObjectName GenerateTableNameCollection(Table table, NameSequence name, TableNameAttribute? tn) { - SchemaName sn = tn != null ? GetSchemaName(tn) : SchemaName.Default; + var isPostgres = Schema.Settings.IsPostgres; + + SchemaName sn = tn != null ? GetSchemaName(tn) : SchemaName.Default(isPostgres); - return new ObjectName(sn, tn?.Name ?? (table.Name.Name + name.ToString())); + return new ObjectName(sn, tn?.Name ?? (table.Name.Name + name.ToString()), isPostgres); } public virtual string GenerateMListFieldName(PropertyRoute route, KindOfField kindOfField) @@ -1024,7 +1033,7 @@ protected override FieldValue GenerateFieldValue(ITable table, PropertyRoute rou protected override FieldEnum GenerateFieldEnum(ITable table, PropertyRoute route, NameSequence name, bool forceNull) { - var att = Settings.FieldAttribute(route); + var att = Settings.FieldAttribute(route); Type cleanEnum = route.Type.UnNullify(); diff --git a/Signum.Engine/Schema/SchemaBuilder/SchemaBuilderSettings.cs b/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs similarity index 70% rename from Signum.Engine/Schema/SchemaBuilder/SchemaBuilderSettings.cs rename to Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs index 7c3c78ac17..609cfa5359 100644 --- a/Signum.Engine/Schema/SchemaBuilder/SchemaBuilderSettings.cs +++ b/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs @@ -14,6 +14,7 @@ using Signum.Utilities.ExpressionTrees; using System.Runtime.CompilerServices; using Signum.Utilities.Reflection; +using NpgsqlTypes; namespace Signum.Engine.Maps { @@ -21,9 +22,11 @@ public class SchemaSettings { public SchemaSettings() { - } + public bool IsPostgres { get; set; } + public bool PostresVersioningFunctionNoChecks { get; set; } + public PrimaryKeyAttribute DefaultPrimaryKeyAttribute = new PrimaryKeyAttribute(typeof(int), "ID"); public int DefaultImplementedBySize = 40; @@ -36,6 +39,15 @@ public SchemaSettings() public ConcurrentDictionary TypeAttributesCache = new ConcurrentDictionary(); public Dictionary CustomOrder = new Dictionary(); + + internal Dictionary? desambiguatedNames; + public void Desambiguate(Type type, string cleanName) + { + if (desambiguatedNames == null) + desambiguatedNames = new Dictionary(); + + desambiguatedNames[type] = cleanName; + } public Dictionary UdtSqlName = new Dictionary() { @@ -44,31 +56,31 @@ public SchemaSettings() //{ typeof(SqlGeometry), "Geometry"}, }; - public Dictionary TypeValues = new Dictionary - { - {typeof(bool), SqlDbType.Bit}, - - {typeof(byte), SqlDbType.TinyInt}, - {typeof(short), SqlDbType.SmallInt}, - {typeof(int), SqlDbType.Int}, - {typeof(long), SqlDbType.BigInt}, - - {typeof(float), SqlDbType.Real}, - {typeof(double), SqlDbType.Float}, - {typeof(decimal), SqlDbType.Decimal}, - - {typeof(char), SqlDbType.NChar}, - {typeof(string), SqlDbType.NVarChar}, - {typeof(DateTime), SqlDbType.DateTime2}, - {typeof(DateTimeOffset), SqlDbType.DateTimeOffset}, - - {typeof(byte[]), SqlDbType.VarBinary}, - - {typeof(Guid), SqlDbType.UniqueIdentifier}, + public Dictionary TypeValues = new Dictionary + { + {typeof(bool), new AbstractDbType(SqlDbType.Bit, NpgsqlDbType.Boolean)}, + + {typeof(byte), new AbstractDbType(SqlDbType.TinyInt, NpgsqlDbType.Smallint)}, + {typeof(short), new AbstractDbType(SqlDbType.SmallInt, NpgsqlDbType.Smallint)}, + {typeof(int), new AbstractDbType(SqlDbType.Int, NpgsqlDbType.Integer)}, + {typeof(long), new AbstractDbType(SqlDbType.BigInt, NpgsqlDbType.Bigint)}, + + {typeof(float), new AbstractDbType(SqlDbType.Real, NpgsqlDbType.Real)}, + {typeof(double), new AbstractDbType(SqlDbType.Float, NpgsqlDbType.Double)}, + {typeof(decimal), new AbstractDbType(SqlDbType.Decimal, NpgsqlDbType.Numeric)}, + + {typeof(char), new AbstractDbType(SqlDbType.NChar, NpgsqlDbType.Char)}, + {typeof(string), new AbstractDbType(SqlDbType.NVarChar, NpgsqlDbType.Varchar)}, + {typeof(Date), new AbstractDbType(SqlDbType.Date, NpgsqlDbType.Date)}, + {typeof(DateTime), new AbstractDbType(SqlDbType.DateTime2, NpgsqlDbType.Timestamp)}, + {typeof(DateTimeOffset), new AbstractDbType(SqlDbType.DateTimeOffset, NpgsqlDbType.TimestampTz)}, + + {typeof(byte[]), new AbstractDbType(SqlDbType.VarBinary, NpgsqlDbType.Bytea)}, + + {typeof(Guid), new AbstractDbType(SqlDbType.UniqueIdentifier, NpgsqlDbType.Uuid)}, }; - internal Dictionary? desambiguatedNames; - readonly Dictionary defaultSize = new Dictionary() + readonly Dictionary defaultSizeSqlServer = new Dictionary() { {SqlDbType.NVarChar, 200}, {SqlDbType.VarChar, 200}, @@ -79,11 +91,25 @@ public SchemaSettings() {SqlDbType.Decimal, 18}, }; - readonly Dictionary defaultScale = new Dictionary() + readonly Dictionary defaultSizePostgreSql = new Dictionary() + { + {NpgsqlDbType.Bytea, int.MaxValue}, + {NpgsqlDbType.Varbit, 200}, + {NpgsqlDbType.Varchar, 200}, + {NpgsqlDbType.Char, 1}, + {NpgsqlDbType.Numeric, 18}, + }; + + readonly Dictionary defaultScaleSqlServer = new Dictionary() { {SqlDbType.Decimal, 2}, }; + readonly Dictionary defaultScalePostgreSql = new Dictionary() + { + {NpgsqlDbType.Numeric, 2}, + }; + public AttributeCollection FieldAttributes(Expression> propertyRoute) where T : IRootEntity { @@ -268,58 +294,95 @@ public Implementations GetImplementations(PropertyRoute propertyRoute) FieldAttribute(propertyRoute)); } - internal SqlDbTypePair GetSqlDbType(SqlDbTypeAttribute? att, Type type) + public AbstractDbType ToAbstractDbType(DbTypeAttribute att) { - if (att != null && att.HasSqlDbType) - return new SqlDbTypePair(att.SqlDbType, att.UserDefinedTypeName); + if (att.HasNpgsqlDbType && att.HasSqlDbType) + return new AbstractDbType(att.SqlDbType, att.NpgsqlDbType); + + if (att.HasNpgsqlDbType) + return new AbstractDbType(att.NpgsqlDbType); + + if (att.HasNpgsqlDbType && att.HasSqlDbType) + return new AbstractDbType(att.SqlDbType, att.NpgsqlDbType); + + throw new InvalidOperationException("Not type found in DbTypeAttribute"); + } + + internal DbTypePair GetSqlDbType(DbTypeAttribute? att, Type type) + { + if (att != null && (att.HasSqlDbType || att.HasNpgsqlDbType)) + return new DbTypePair(ToAbstractDbType(att), att.UserDefinedTypeName); return GetSqlDbTypePair(type.UnNullify()); } - internal SqlDbTypePair? TryGetSqlDbType(SqlDbTypeAttribute? att, Type type) + internal DbTypePair? TryGetSqlDbType(DbTypeAttribute? att, Type type) { - if (att != null && att.HasSqlDbType) - return new SqlDbTypePair(att.SqlDbType, att.UserDefinedTypeName); + if (att != null && (att.HasSqlDbType || att.HasNpgsqlDbType)) + return new DbTypePair(ToAbstractDbType(att), att.UserDefinedTypeName); return TryGetSqlDbTypePair(type.UnNullify()); } - internal int? GetSqlSize(SqlDbTypeAttribute? att, PropertyRoute? route, SqlDbType sqlDbType) + internal int? GetSqlSize(DbTypeAttribute? att, PropertyRoute? route, AbstractDbType dbType) { if (att != null && att.HasSize) return att.Size; - if(route != null && route.Type == typeof(string)) + if (route != null && route.Type == typeof(string)) { var sla = ValidatorAttribute(route); if (sla != null) return sla.Max == -1 ? int.MaxValue : sla.Max; } - return defaultSize.TryGetS(sqlDbType); + if (this.IsPostgres) + return defaultSizeSqlServer.TryGetS(dbType.SqlServer); + else + return defaultSizePostgreSql.TryGetS(dbType.PostgreSql); } - internal int? GetSqlScale(SqlDbTypeAttribute? att, PropertyRoute? route, SqlDbType sqlDbType) + internal int? GetSqlScale(DbTypeAttribute? att, PropertyRoute? route, AbstractDbType dbType) + { + bool isDecimal = dbType.IsDecimal(); + if (att != null && att.HasScale) + { + if (!isDecimal) + throw new InvalidOperationException($"{dbType} can not have Scale"); + } + + if(isDecimal && route != null) + { + var dv = ValidatorAttribute(route); + if (dv != null) + return dv.DecimalPlaces; + } + + if (this.IsPostgres) + return defaultScaleSqlServer.TryGetS(dbType.SqlServer); + else + return defaultScalePostgreSql.TryGetS(dbType.PostgreSql); + } + + internal int? GetSqlScale(DbTypeAttribute? att, PropertyRoute? route, NpgsqlDbType npgsqlDbType) { if (att != null && att.HasScale) { - if(sqlDbType != SqlDbType.Decimal) - throw new InvalidOperationException($"{sqlDbType} can not have Scale"); return att.Scale; } - if(sqlDbType == SqlDbType.Decimal && route != null) + if (npgsqlDbType == NpgsqlDbType.Numeric && route != null) { var dv = ValidatorAttribute(route); if (dv != null) return dv.DecimalPlaces; } - return defaultScale.TryGetS(sqlDbType); + return defaultScalePostgreSql.TryGetS(npgsqlDbType); } - internal string? GetCollate(SqlDbTypeAttribute? att) + internal string? GetCollate(DbTypeAttribute? att) { if (att != null && att.Collation != null) return att.Collation; @@ -327,20 +390,12 @@ internal SqlDbTypePair GetSqlDbType(SqlDbTypeAttribute? att, Type type) return null; } - internal SqlDbType DefaultSqlType(Type type) + internal AbstractDbType DefaultSqlType(Type type) { return this.TypeValues.GetOrThrow(type, "Type {0} not registered"); } - public void Desambiguate(Type type, string cleanName) - { - if (desambiguatedNames == null) - desambiguatedNames = new Dictionary(); - - desambiguatedNames[type] = cleanName; - } - - public SqlDbTypePair GetSqlDbTypePair(Type type) + public DbTypePair GetSqlDbTypePair(Type type) { var result = TryGetSqlDbTypePair(type); if (result == null) @@ -349,14 +404,14 @@ public SqlDbTypePair GetSqlDbTypePair(Type type) return result; } - public SqlDbTypePair? TryGetSqlDbTypePair(Type type) + public DbTypePair? TryGetSqlDbTypePair(Type type) { - if (TypeValues.TryGetValue(type, out SqlDbType result)) - return new SqlDbTypePair(result, null); + if (TypeValues.TryGetValue(type, out AbstractDbType result)) + return new DbTypePair(result, null); string? udtTypeName = GetUdtName(type); if (udtTypeName != null) - return new SqlDbTypePair(SqlDbType.Udt, udtTypeName); + return new DbTypePair(new AbstractDbType(SqlDbType.Udt), udtTypeName); return null; } @@ -382,14 +437,16 @@ public void RegisterCustomOrder(Expression> customOrder) wher } } - public class SqlDbTypePair + + + public class DbTypePair { - public SqlDbType SqlDbType { get; private set; } + public AbstractDbType DbType { get; private set; } public string? UserDefinedTypeName { get; private set; } - public SqlDbTypePair(SqlDbType type, string? udtTypeName) + public DbTypePair(AbstractDbType type, string? udtTypeName) { - this.SqlDbType = type; + this.DbType = type; this.UserDefinedTypeName = udtTypeName; } } diff --git a/Signum.Engine/Schema/UniqueTableIndex.cs b/Signum.Engine/Schema/UniqueTableIndex.cs index 57de2580d1..8bf3a0fdd4 100644 --- a/Signum.Engine/Schema/UniqueTableIndex.cs +++ b/Signum.Engine/Schema/UniqueTableIndex.cs @@ -43,11 +43,13 @@ public TableIndex(ITable table, params IColumn[] columns) this.Columns = columns; } - public virtual string IndexName + public virtual string GetIndexName(ObjectName tableName) { - get { return "IX_{0}".FormatWith(ColumnSignature()).TryStart(Connector.Current.MaxNameLength); } + return "IX_{0}_{1}".FormatWith(tableName.Name, ColumnSignature()).TryStart(Connector.Current.MaxNameLength); } + public string IndexName => GetIndexName(Table.Name); + protected string ColumnSignature() { string columns = Columns.ToString(c => c.Name, "_"); @@ -77,7 +79,7 @@ public PrimaryClusteredIndex(ITable table) : base(table, new[] { table.PrimaryKe } - public override string IndexName => GetPrimaryKeyName(this.Table.Name); + public override string GetIndexName(ObjectName tableName) => GetPrimaryKeyName(tableName); public static string GetPrimaryKeyName(ObjectName tableName) { @@ -87,12 +89,15 @@ public static string GetPrimaryKeyName(ObjectName tableName) public class UniqueTableIndex : TableIndex { - public UniqueTableIndex(ITable table, IColumn[] columns) : base(table, columns) { } + public UniqueTableIndex(ITable table, IColumn[] columns) + : base(table, columns) + { + } - public override string IndexName + public override string GetIndexName(ObjectName tableName) { - get { return "UIX_{0}".FormatWith(ColumnSignature()).TryStart(Connector.Current.MaxNameLength); } + return "UIX_{0}_{1}".FormatWith(tableName.Name, ColumnSignature()).TryStart(Connector.Current.MaxNameLength); } public string? ViewName @@ -184,10 +189,12 @@ public class IndexWhereExpressionVisitor : ExpressionVisitor StringBuilder sb = new StringBuilder(); IFieldFinder RootFinder; + bool isPostgres; public IndexWhereExpressionVisitor(IFieldFinder rootFinder) { RootFinder = rootFinder; + this.isPostgres = Schema.Current.Settings.IsPostgres; } public static string GetIndexWhere(LambdaExpression lambda, IFieldFinder rootFiender) @@ -240,7 +247,7 @@ protected override Expression VisitTypeBinary(TypeBinaryExpression b) if (f is FieldReference fr) { if (b.TypeOperand.IsAssignableFrom(fr.FieldType)) - sb.Append(fr.Name.SqlEscape() + " IS NOT NULL"); + sb.Append(fr.Name.SqlEscape(isPostgres) + " IS NOT NULL"); else throw new InvalidOperationException("A {0} will never be {1}".FormatWith(fr.FieldType.TypeName(), b.TypeOperand.TypeName())); @@ -254,7 +261,7 @@ protected override Expression VisitTypeBinary(TypeBinaryExpression b) var imp = fib.ImplementationColumns.Where(kvp => typeOperant.IsAssignableFrom(kvp.Key)); if (imp.Any()) - sb.Append(imp.ToString(kvp => kvp.Value.Name.SqlEscape() + " IS NOT NULL", " OR ")); + sb.Append(imp.ToString(kvp => kvp.Value.Name.SqlEscape(isPostgres) + " IS NOT NULL", " OR ")); else throw new InvalidOperationException("No implementation ({0}) will never be {1}".FormatWith(fib.ImplementationColumns.Keys.ToString(t => t.TypeName(), ", "), b.TypeOperand.TypeName())); @@ -276,7 +283,7 @@ protected override Expression VisitMember(MemberExpression m) { var field = GetField(m); - sb.Append(Equals(field, true, true)); + sb.Append(Equals(field, value: true, equals: true, isPostgres)); return m; } @@ -308,52 +315,52 @@ protected override Expression VisitUnary(UnaryExpression u) } - public static string IsNull(Field field, bool equals) + public static string IsNull(Field field, bool equals, bool isPostgres) { string isNull = equals ? "{0} IS NULL" : "{0} IS NOT NULL"; if (field is IColumn col) { - string result = isNull.FormatWith(col.Name.SqlEscape()); + string result = isNull.FormatWith(col.Name.SqlEscape(isPostgres)); - if (!SqlBuilder.IsString(col.SqlDbType)) + if (!col.DbType.IsString()) return result; - return result + (equals ? " OR " : " AND ") + (col.Name.SqlEscape() + (equals ? " = " : " <> ") + "''"); + return result + (equals ? " OR " : " AND ") + (col.Name.SqlEscape(isPostgres) + (equals ? " = " : " <> ") + "''"); } else if (field is FieldImplementedBy ib) { - return ib.ImplementationColumns.Values.Select(ic => isNull.FormatWith(ic.Name.SqlEscape())).ToString(equals ? " AND " : " OR "); + return ib.ImplementationColumns.Values.Select(ic => isNull.FormatWith(ic.Name.SqlEscape(isPostgres))).ToString(equals ? " AND " : " OR "); } else if (field is FieldImplementedByAll iba) { - return isNull.FormatWith(iba.Column.Name.SqlEscape()) + + return isNull.FormatWith(iba.Column.Name.SqlEscape(isPostgres)) + (equals ? " AND " : " OR ") + - isNull.FormatWith(iba.ColumnType.Name.SqlEscape()); + isNull.FormatWith(iba.ColumnType.Name.SqlEscape(isPostgres)); } else if (field is FieldEmbedded fe) { if (fe.HasValue == null) throw new NotSupportedException("{0} is not nullable".FormatWith(field)); - return fe.HasValue.Name.SqlEscape() + " = 1"; + return fe.HasValue.Name.SqlEscape(isPostgres) + " = 1"; } throw new NotSupportedException(isNull.FormatWith(field.GetType())); } - static string Equals(Field field, object value, bool equals) + static string Equals(Field field, object value, bool equals, bool isPostgres) { if (value == null) { - return IsNull(field, equals); + return IsNull(field, equals, isPostgres); } else { if (field is IColumn) { - return ((IColumn)field).Name.SqlEscape() + + return ((IColumn)field).Name.SqlEscape(isPostgres) + (equals ? " = " : " <> ") + SqlPreCommandSimple.Encode(value); } @@ -380,13 +387,13 @@ protected override Expression VisitBinary(BinaryExpression b) Field field = GetField(b.Right); - sb.Append(Equals(field, ((ConstantExpression)b.Left).Value, b.NodeType == ExpressionType.Equal)); + sb.Append(Equals(field, ((ConstantExpression)b.Left).Value, b.NodeType == ExpressionType.Equal, isPostgres)); } else if (b.Right is ConstantExpression) { Field field = GetField(b.Left); - sb.Append(Equals(field, ((ConstantExpression)b.Right).Value, b.NodeType == ExpressionType.Equal)); + sb.Append(Equals(field, ((ConstantExpression)b.Right).Value, b.NodeType == ExpressionType.Equal, isPostgres)); } else throw new NotSupportedException("Impossible to translate {0}".FormatWith(b.ToString())); diff --git a/Signum.Engine/Signum.Engine.csproj b/Signum.Engine/Signum.Engine.csproj index 7a7c2c69a4..5ed17ef24f 100644 --- a/Signum.Engine/Signum.Engine.csproj +++ b/Signum.Engine/Signum.Engine.csproj @@ -11,11 +11,17 @@ - - + + + + + + + + diff --git a/Signum.Entities/Basics/Exception.cs b/Signum.Entities/Basics/Exception.cs index 04b0f1c538..9581a1a60e 100644 --- a/Signum.Entities/Basics/Exception.cs +++ b/Signum.Entities/Basics/Exception.cs @@ -28,10 +28,10 @@ public ExceptionEntity(Exception ex) public DateTime CreationDate { get; private set; } = TimeZoneManager.Now; - [ForceNotNullable, SqlDbType(Size = 100)] + [ForceNotNullable, DbType(Size = 100)] public string? ExceptionType { get; set; } - [SqlDbType(Size = int.MaxValue)] + [DbType(Size = int.MaxValue)] string exceptionMessage; public string ExceptionMessage { @@ -45,7 +45,7 @@ public string ExceptionMessage public int ExceptionMessageHash { get; private set; } - [SqlDbType(Size = int.MaxValue)] + [DbType(Size = int.MaxValue)] string stackTrace; public string StackTrace { @@ -63,49 +63,49 @@ public string StackTrace public Lite? User { get; set; } - [SqlDbType(Size = 100)] + [DbType(Size = 100)] public string? Environment { get; set; } - [SqlDbType(Size = 100)] + [DbType(Size = 100)] public string? Version { get; set; } - [SqlDbType(Size = 300)] + [DbType(Size = 300)] public string? UserAgent { get; set; } - [SqlDbType(Size = int.MaxValue)] + [DbType(Size = int.MaxValue)] public string? RequestUrl { get; set; } - [SqlDbType(Size = 100)] + [DbType(Size = 100)] public string? ControllerName { get; set; } - [SqlDbType(Size = 100)] + [DbType(Size = 100)] public string? ActionName { get; set; } - [SqlDbType(Size = int.MaxValue)] + [DbType(Size = int.MaxValue)] public string? UrlReferer { get; set; } - [SqlDbType(Size = 100)] + [DbType(Size = 100)] public string? MachineName { get; set; } - [SqlDbType(Size = 100)] + [DbType(Size = 100)] public string? ApplicationName { get; set; } - [SqlDbType(Size = 100)] + [DbType(Size = 100)] public string? UserHostAddress { get; set; } - [SqlDbType(Size = 100)] + [DbType(Size = 100)] public string? UserHostName { get; set; } - [SqlDbType(Size = int.MaxValue)] + [DbType(Size = int.MaxValue)] public string? Form { get; set; } - [SqlDbType(Size = int.MaxValue)] + [DbType(Size = int.MaxValue)] public string? QueryString { get; set; } - [SqlDbType(Size = int.MaxValue)] + [DbType(Size = int.MaxValue)] public string? Session { get; set; } - [SqlDbType(Size = int.MaxValue)] + [DbType(Size = int.MaxValue)] public string? Data { get; set; } public int HResult { get; internal set; } diff --git a/Signum.Entities/FieldAttributes.cs b/Signum.Entities/FieldAttributes.cs index 63051ce840..0a9e160065 100644 --- a/Signum.Entities/FieldAttributes.cs +++ b/Signum.Entities/FieldAttributes.cs @@ -7,6 +7,7 @@ using Signum.Entities.Reflection; using Signum.Utilities.ExpressionTrees; using Signum.Entities.Basics; +using NpgsqlTypes; namespace Signum.Entities { @@ -242,44 +243,41 @@ public sealed class ForceNullableAttribute: Attribute [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] - public class SqlDbTypeAttribute : Attribute - { + public class DbTypeAttribute : Attribute + { SqlDbType? sqlDbType; - int? size; - int? scale; - + public bool HasSqlDbType => sqlDbType.HasValue; public SqlDbType SqlDbType { get { return sqlDbType!.Value; } set { sqlDbType = value; } } - public bool HasSqlDbType + NpgsqlDbType? npgsqlDbType; + public bool HasNpgsqlDbType => npgsqlDbType.HasValue; + public NpgsqlDbType NpgsqlDbType { - get { return sqlDbType.HasValue; } + get { return npgsqlDbType!.Value; } + set { npgsqlDbType = value; } } + int? size; + public bool HasSize => size.HasValue; public int Size { get { return size!.Value; } set { size = value; } } - public bool HasSize - { - get { return size.HasValue; } - } + int? scale; + public bool HasScale => scale.HasValue; public int Scale { get { return scale!.Value; } set { scale = value; } } - public bool HasScale - { - get { return scale.HasValue; } - } public string? UserDefinedTypeName { get; set; } @@ -292,7 +290,7 @@ public bool HasScale } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Field | AttributeTargets.Property /*MList fields*/, Inherited = true, AllowMultiple = false)] - public sealed class PrimaryKeyAttribute : SqlDbTypeAttribute + public sealed class PrimaryKeyAttribute : DbTypeAttribute { public Type Type { get; set; } @@ -371,7 +369,7 @@ public TableNameAttribute(string fullName) } [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] - public sealed class TicksColumnAttribute : SqlDbTypeAttribute + public sealed class TicksColumnAttribute : DbTypeAttribute { public bool HasTicks { get; private set; } @@ -394,6 +392,7 @@ public sealed class SystemVersionedAttribute : Attribute public string? TemporalTableName { get; set; } public string StartDateColumnName { get; set; } = "SysStartDate"; public string EndDateColumnName { get; set; } = "SysEndDate"; + public string PostgreeSysPeriodColumname { get; set; } = "sys_period"; } [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] diff --git a/Signum.Entities/MList.cs b/Signum.Entities/MList.cs index d00f202464..404462ff43 100644 --- a/Signum.Entities/MList.cs +++ b/Signum.Entities/MList.cs @@ -858,7 +858,7 @@ public static MList ToMList(this IEnumerable collection) } [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)] - public sealed class PreserveOrderAttribute : SqlDbTypeAttribute + public sealed class PreserveOrderAttribute : DbTypeAttribute { public string? Name { get; set; } diff --git a/Signum.Entities/Signum.Entities.csproj b/Signum.Entities/Signum.Entities.csproj index fe5c9675b1..9e6920db58 100644 --- a/Signum.Entities/Signum.Entities.csproj +++ b/Signum.Entities/Signum.Entities.csproj @@ -10,9 +10,8 @@ NU1605 - - + diff --git a/Signum.React/Signum.React.csproj b/Signum.React/Signum.React.csproj index 09bbc9b04c..3cab5dd0d2 100644 --- a/Signum.React/Signum.React.csproj +++ b/Signum.React/Signum.React.csproj @@ -12,6 +12,7 @@ x64;x86;win86;win64;AnyCPU;linux64 NU1605 + 7b3bfbd4-af24-4a37-a213-f5f281fa39f5 @@ -41,16 +42,15 @@ + - - - - + + @@ -67,6 +67,7 @@ + diff --git a/Signum.Test/Environment/Entities.cs b/Signum.Test/Environment/Entities.cs index 342672d101..d7b006f83a 100644 --- a/Signum.Test/Environment/Entities.cs +++ b/Signum.Test/Environment/Entities.cs @@ -376,19 +376,42 @@ public static IQueryable MinimumTableValued(int? a, int? b) internal static void IncludeFunction(SchemaAssets assets) { - assets.IncludeUserDefinedFunction("MinimumTableValued", @"(@Param1 Integer, @Param2 Integer) + if (Schema.Current.Settings.IsPostgres) + { + assets.IncludeUserDefinedFunction("MinimumTableValued", @"(p1 integer, p2 integer) +RETURNS TABLE(""MinValue"" integer) +AS $$ +BEGIN + RETURN QUERY + SELECT Case When p1 < p2 Then p1 + Else COALESCE(p2, p1) End as MinValue; + END +$$ LANGUAGE plpgsql;"); + + assets.IncludeUserDefinedFunction("MinimumScalar", @"(p1 integer, p2 integer) +RETURNS integer +AS $$ +BEGIN + RETURN (Case When p1 < p2 Then p1 + Else COALESCE(p2, p1) End); +END +$$ LANGUAGE plpgsql;"); + } + else + { + assets.IncludeUserDefinedFunction("MinimumTableValued", @"(@Param1 Integer, @Param2 Integer) RETURNS Table As RETURN (SELECT Case When @Param1 < @Param2 Then @Param1 Else COALESCE(@Param2, @Param1) End MinValue)"); - - assets.IncludeUserDefinedFunction("MinimumScalar", @"(@Param1 Integer, @Param2 Integer) + assets.IncludeUserDefinedFunction("MinimumScalar", @"(@Param1 Integer, @Param2 Integer) RETURNS Integer AS BEGIN RETURN (Case When @Param1 < @Param2 Then @Param1 Else COALESCE(@Param2, @Param1) End); END"); + } } } diff --git a/Signum.Test/Environment/MusicStarter.cs b/Signum.Test/Environment/MusicStarter.cs index a525fe95c2..88b5fc5584 100644 --- a/Signum.Test/Environment/MusicStarter.cs +++ b/Signum.Test/Environment/MusicStarter.cs @@ -47,11 +47,11 @@ public static void Start(string connectionString) { SchemaBuilder sb = new SchemaBuilder(true); - //Connector.Default = new SqlCeConnector(@"Data Source=C:\BaseDatos.sdf", sb.Schema); + var postgreeVersion = PostgresVersionDetector.Detect(connectionString); + Connector.Default = new PostgreSqlConnector(connectionString, sb.Schema, postgreeVersion); - var sqlVersion = SqlServerVersionDetector.Detect(connectionString); - - Connector.Default = new SqlConnector(connectionString, sb.Schema, sqlVersion ?? SqlServerVersion.SqlServer2017); + //var sqlVersion = SqlServerVersionDetector.Detect(connectionString); + //Connector.Default = new SqlConnector(connectionString, sb.Schema, sqlVersion ?? SqlServerVersion.SqlServer2017); sb.Schema.Version = typeof(MusicStarter).Assembly.GetName().Version!; @@ -69,7 +69,7 @@ public static void Start(string connectionString) sb.Settings.FieldAttributes((AlbumEntity a) => a.BonusTrack!.Duration).Add(new Signum.Entities.IgnoreAttribute()); } - if(sqlVersion > SqlServerVersion.SqlServer2008) + if(Connector.Default is SqlConnector c && c.Version > SqlServerVersion.SqlServer2008) { sb.Settings.UdtSqlName.Add(typeof(SqlHierarchyId), "HierarchyId"); } diff --git a/Signum.Test/ObjectNameTest.cs b/Signum.Test/ObjectNameTest.cs index c102e71c29..f872cf2b50 100644 --- a/Signum.Test/ObjectNameTest.cs +++ b/Signum.Test/ObjectNameTest.cs @@ -6,10 +6,12 @@ namespace Signum.Test { public class ObjectNameTest { + bool isPostgree = Signum.Engine.Connector.Current.Schema.Settings.IsPostgres; + [Fact] public void ParseDbo() { - var simple = ObjectName.Parse("MyTable"); + var simple = ObjectName.Parse("MyTable", isPostgree); Assert.Equal("MyTable", simple.Name); Assert.Equal("dbo", simple.Schema.ToString()); } @@ -17,7 +19,7 @@ public void ParseDbo() [Fact] public void ParseSchema() { - var simple = ObjectName.Parse("MySchema.MyTable"); + var simple = ObjectName.Parse("MySchema.MyTable", isPostgree); Assert.Equal("MyTable", simple.Name); Assert.Equal("MySchema", simple.Schema.ToString()); } @@ -25,7 +27,7 @@ public void ParseSchema() [Fact] public void ParseNameEscaped() { - var simple = ObjectName.Parse("MySchema.[Select]"); + var simple = ObjectName.Parse("MySchema.[Select]", isPostgree); Assert.Equal("Select", simple.Name); Assert.Equal("MySchema", simple.Schema.ToString()); Assert.Equal("MySchema.[Select]", simple.ToString()); @@ -34,7 +36,7 @@ public void ParseNameEscaped() [Fact] public void ParseSchemaNameEscaped() { - var simple = ObjectName.Parse("[Select].MyTable"); + var simple = ObjectName.Parse("[Select].MyTable", isPostgree); Assert.Equal("MyTable", simple.Name); Assert.Equal("Select", simple.Schema.Name); Assert.Equal("[Select].MyTable", simple.ToString()); @@ -43,7 +45,7 @@ public void ParseSchemaNameEscaped() [Fact] public void ParseServerName() { - var simple = ObjectName.Parse("[FROM].[SELECT].[WHERE].[TOP]"); + var simple = ObjectName.Parse("[FROM].[SELECT].[WHERE].[TOP]", isPostgree); Assert.Equal("TOP", simple.Name); Assert.Equal("WHERE", simple.Schema.Name); Assert.Equal("SELECT", simple.Schema.Database!.Name); @@ -54,7 +56,7 @@ public void ParseServerName() [Fact] public void ParseServerNameSuperComplex() { - var simple = ObjectName.Parse("[FROM].[SELECT].[WHERE].[TOP.DISTINCT]"); + var simple = ObjectName.Parse("[FROM].[SELECT].[WHERE].[TOP.DISTINCT]", isPostgree); Assert.Equal("TOP.DISTINCT", simple.Name); Assert.Equal("WHERE", simple.Schema.Name); Assert.Equal("SELECT", simple.Schema.Database!.Name); diff --git a/Signum.Test/appsettings.json b/Signum.Test/appsettings.json index c3634e2719..854ba1931c 100644 --- a/Signum.Test/appsettings.json +++ b/Signum.Test/appsettings.json @@ -1,5 +1,6 @@ {//This file is not copy to the bin directory. Use UserSecrets instead. "ConnectionStrings": { - "SignumTest": "Data Source=.\\SQLEXPRESS;Initial Catalog=SignumTest;Integrated Security=true" + //"SignumTest": "Data Source=.\\SQLEXPRESS;Initial Catalog=SignumTest;Integrated Security=true", + "SignumTest": "host=localhost;Username=postgres;Password=bnwfox7g;Database=SignumTest" } } diff --git a/Signum.Utilities/Date.cs b/Signum.Utilities/Date.cs new file mode 100644 index 0000000000..5d8d3275a4 --- /dev/null +++ b/Signum.Utilities/Date.cs @@ -0,0 +1,318 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Runtime.Serialization; +using System.Text; + +//Thanks to supersonicclay +//From https://github.com/supersonicclay/csharp-date/blob/master/CSharpDate/Date.cs +namespace Signum.Utilities +{ + + [Serializable] + public struct Date : IComparable, IFormattable, ISerializable, IComparable, IEquatable + { + private DateTime _dt; + + public static readonly Date MaxValue = new Date(DateTime.MaxValue); + public static readonly Date MinValue = new Date(DateTime.MinValue); + + public Date(int year, int month, int day) + { + this._dt = new DateTime(year, month, day); + } + + public Date(DateTime dateTime) + { + this._dt = dateTime.AddTicks(-dateTime.Ticks % TimeSpan.TicksPerDay); + } + + private Date(SerializationInfo info, StreamingContext context) + { + this._dt = DateTime.FromFileTime(info.GetInt64("ticks")); + } + + public static TimeSpan operator -(Date d1, Date d2) + { + return d1._dt - d2._dt; + } + + public static Date operator -(Date d, TimeSpan t) + { + return new Date(d._dt - t); + } + + public static bool operator !=(Date d1, Date d2) + { + return d1._dt != d2._dt; + } + + public static Date operator +(Date d, TimeSpan t) + { + return new Date(d._dt + t); + } + + public static bool operator <(Date d1, Date d2) + { + return d1._dt < d2._dt; + } + + public static bool operator <=(Date d1, Date d2) + { + return d1._dt <= d2._dt; + } + + public static bool operator ==(Date d1, Date d2) + { + return d1._dt == d2._dt; + } + + public static bool operator >(Date d1, Date d2) + { + return d1._dt > d2._dt; + } + + public static bool operator >=(Date d1, Date d2) + { + return d1._dt >= d2._dt; + } + + public static implicit operator DateTime(Date d) + { + return d._dt; + } + + public static explicit operator Date(DateTime d) + { + return new Date(d); + } + + public int Day + { + get + { + return this._dt.Day; + } + } + + public DayOfWeek DayOfWeek + { + get + { + return this._dt.DayOfWeek; + } + } + + public int DayOfYear + { + get + { + return this._dt.DayOfYear; + } + } + + public int Month + { + get + { + return this._dt.Month; + } + } + + public static Date Today + { + get + { + return new Date(DateTime.Today); + } + } + + public int Year + { + get + { + return this._dt.Year; + } + } + + public long Ticks + { + get + { + return this._dt.Ticks; + } + } + + public Date AddDays(int value) + { + return new Date(this._dt.AddDays(value)); + } + + public Date AddMonths(int value) + { + return new Date(this._dt.AddMonths(value)); + } + + public Date AddYears(int value) + { + return new Date(this._dt.AddYears(value)); + } + + public static int Compare(Date d1, Date d2) + { + return d1.CompareTo(d2); + } + + public int CompareTo(Date value) + { + return this._dt.CompareTo(value._dt); + } + + public int CompareTo(object? value) + { + return this._dt.CompareTo(value); + } + + public static int DaysInMonth(int year, int month) + { + return DateTime.DaysInMonth(year, month); + } + + public bool Equals(Date value) + { + return this._dt.Equals(value._dt); + } + + public override bool Equals(object? value) + { + return value is Date && this._dt.Equals(((Date)value)._dt); + } + + public override int GetHashCode() + { + return this._dt.GetHashCode(); + } + + public static bool Equals(Date d1, Date d2) + { + return d1._dt.Equals(d2._dt); + } + + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("ticks", this._dt.Ticks); + } + + public static bool IsLeapYear(int year) + { + return DateTime.IsLeapYear(year); + } + + public static Date Parse(string s) + { + return new Date(DateTime.Parse(s)); + } + + public static Date Parse(string s, IFormatProvider provider) + { + return new Date(DateTime.Parse(s, provider)); + } + + public static Date Parse(string s, IFormatProvider provider, DateTimeStyles style) + { + return new Date(DateTime.Parse(s, provider, style)); + } + + public static Date ParseExact(string s, string format, IFormatProvider provider) + { + return new Date(DateTime.ParseExact(s, format, provider)); + } + + public static Date ParseExact(string s, string format, IFormatProvider provider, DateTimeStyles style) + { + return new Date(DateTime.ParseExact(s, format, provider, style)); + } + + public static Date ParseExact(string s, string[] formats, IFormatProvider provider, DateTimeStyles style) + { + return new Date(DateTime.ParseExact(s, formats, provider, style)); + } + + public TimeSpan Subtract(Date value) + { + return this - value; + } + + public Date Subtract(TimeSpan value) + { + return this - value; + } + + public string ToLongString() + { + return this._dt.ToLongDateString(); + } + + public string ToShortString() + { + return this._dt.ToShortDateString(); + } + + public override string ToString() + { + return this.ToShortString(); + } + + public string ToString(IFormatProvider provider) + { + return this._dt.ToString(provider); + } + + public string ToString(string format) + { + if (format == "O" || format == "o" || format == "s") + { + return this.ToString("yyyy-MM-dd"); + } + + return this._dt.ToString(format); + } + + public string ToString(string? format, IFormatProvider? provider) + { + return this._dt.ToString(format, provider); + } + + public static bool TryParse(string s, out Date result) + { + DateTime d; + bool success = DateTime.TryParse(s, out d); + result = new Date(d); + return success; + } + + public static bool TryParse(string s, IFormatProvider provider, DateTimeStyles style, out Date result) + { + DateTime d; + bool success = DateTime.TryParse(s, provider, style, out d); + result = new Date(d); + return success; + } + + public static bool TryParseExact(string s, string format, IFormatProvider provider, DateTimeStyles style, out Date result) + { + DateTime d; + bool success = DateTime.TryParseExact(s, format, provider, style, out d); + result = new Date(d); + return success; + } + + public static bool TryParseExact(string s, string[] formats, IFormatProvider provider, DateTimeStyles style, out Date result) + { + DateTime d; + bool success = DateTime.TryParseExact(s, formats, provider, style, out d); + result = new Date(d); + return success; + } + } +} diff --git a/Signum.Utilities/Extensions/DateTimeExtensions.cs b/Signum.Utilities/Extensions/DateTimeExtensions.cs index e1ffd212c5..6b7ca22699 100644 --- a/Signum.Utilities/Extensions/DateTimeExtensions.cs +++ b/Signum.Utilities/Extensions/DateTimeExtensions.cs @@ -457,6 +457,11 @@ public static long ToUnixTimeMilliseconds(this DateTime dateTime) { return new DateTimeOffset(dateTime).ToUnixTimeMilliseconds(); } + + public static Date ToDate(this DateTime dt) + { + return new Date(dt); + } } [DescriptionOptions(DescriptionOptions.Members)] From 875ef4ceb45b90672156ed02200846a578912b4e Mon Sep 17 00:00:00 2001 From: Olmo del Corral Date: Sat, 14 Dec 2019 13:48:48 +0100 Subject: [PATCH 02/13] more on Postgre --- Signum.Engine/Engine/SqlPreCommand.cs | 11 ++- Signum.Engine/Schema/Schema.Save.cs | 113 ++++++++++++++------------ 2 files changed, 72 insertions(+), 52 deletions(-) diff --git a/Signum.Engine/Engine/SqlPreCommand.cs b/Signum.Engine/Engine/SqlPreCommand.cs index c1505a92d2..519b1d1419 100644 --- a/Signum.Engine/Engine/SqlPreCommand.cs +++ b/Signum.Engine/Engine/SqlPreCommand.cs @@ -159,8 +159,10 @@ protected internal override int NumParameters static readonly Regex regex = new Regex(@"@[_\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nl}][_\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nl}\p{Nd}]*"); - internal static string Encode(object value) + internal static string Encode(DbParameter param) { + var value = param.Value; + if (value == null || value == DBNull.Value) return "NULL"; @@ -177,7 +179,12 @@ internal static string Encode(object value) return "convert(time, '{0:g}')".FormatWith(ts.ToString("g", CultureInfo.InvariantCulture)); if (value is bool b) + { + if (param is Npgsql.NpgsqlParameter p && p.NpgsqlDbType == NpgsqlTypes.NpgsqlDbType.Boolean) + return b.ToString(); + return (b ? 1 : 0).ToString(); + } if (Schema.Current.Settings.UdtSqlName.TryGetValue(value.GetType(), out var name)) return "CAST('{0}' AS {1})".FormatWith(value, name); @@ -197,7 +204,7 @@ protected internal override void PlainSql(StringBuilder sb) sb.Append(Sql); else { - var dic = Parameters.ToDictionary(a => a.ParameterName, a => Encode(a.Value)); + var dic = Parameters.ToDictionary(a => a.ParameterName, a => Encode(a)); sb.Append(regex.Replace(Sql, m => dic.TryGetC(m.Value) ?? m.Value)); } diff --git a/Signum.Engine/Schema/Schema.Save.cs b/Signum.Engine/Schema/Schema.Save.cs index d7753cd972..f93ff434e0 100644 --- a/Signum.Engine/Schema/Schema.Save.cs +++ b/Signum.Engine/Schema/Schema.Save.cs @@ -220,7 +220,7 @@ internal static InsertCacheDisableIdentity InitializeInsertDisableIdentity(Table var isPostgres = Schema.Current.Settings.IsPostgres; Func insertPattern = (suffixes) => - "INSERT INTO {0} ({1}) VALUES \r\n{2};".FormatWith(table.Name, + "INSERT INTO {0}\r\n ({1})\r\nVALUES\r\n{2};".FormatWith(table.Name, trios.ToString(p => p.SourceColumn.SqlEscape(isPostgres), ", "), suffixes.ToString(s => " (" + trios.ToString(p => p.ParameterName + s, ", ") + ")", "\r\n")); @@ -317,12 +317,12 @@ internal static InsertCacheIdentity InitializeInsertIdentity(Table table) var isPostgres = Schema.Current.Settings.IsPostgres; var newIds = Var(isPostgres, "newIDs"); Func sqlInsertPattern = (suffixes, output) => - "INSERT INTO {0} ({1})\r\n{2}VALUES \r\n{3};".FormatWith( + "INSERT INTO {0}\r\n ({1})\r\n{2}VALUES\r\n{3}{4};".FormatWith( table.Name, trios.ToString(p => p.SourceColumn.SqlEscape(isPostgres), ", "), - output && !isPostgres ? $"OUTPUT INSERTED.Id\r\n" : null, - suffixes.ToString(s => " (" + trios.ToString(p => p.ParameterName + s, ", ") + ")", "\r\n"), - output && isPostgres ? $"\r\nRETURNING Id" : null); + output && !isPostgres ? $"OUTPUT INSERTED.{table.PrimaryKey.Name.SqlEscape(isPostgres)}\r\n" : null, + suffixes.ToString(s => " (" + trios.ToString(p => p.ParameterName + s, ", ") + ")", ",\r\n"), + output && isPostgres ? $"\r\nRETURNING {table.PrimaryKey.Name.SqlEscape(isPostgres)}" : null); var expr = Expression.Lambda>>( @@ -397,12 +397,12 @@ class UpdateCache internal Table table; public Func SqlUpdatePattern; - public Func> UpdateParameters; + public Action> UpdateParameters; ConcurrentDictionary, DirectedGraph?>> updateCache = new ConcurrentDictionary, DirectedGraph?>>(); - public UpdateCache(Table table, Func sqlUpdatePattern, Func> updateParameters) + public UpdateCache(Table table, Func sqlUpdatePattern, Action> updateParameters) { this.table = table; SqlUpdatePattern = sqlUpdatePattern; @@ -432,7 +432,7 @@ public UpdateCache(Table table, Func sqlUpdatePattern, Fun var forbidden = new Forbidden(graph, ident); - int num = (int)new SqlPreCommandSimple(sqlUpdate, UpdateParameters(ident, oldTicks, forbidden, "")).ExecuteNonQuery(); + int num = (int)new SqlPreCommandSimple(sqlUpdate, new List().Do(ps => UpdateParameters(ident, oldTicks, forbidden, "", ps))).ExecuteNonQuery(); if (num != 1) throw new ConcurrencyException(ident.GetType(), ident.Id); @@ -450,7 +450,7 @@ public UpdateCache(Table table, Func sqlUpdatePattern, Fun var forbidden = new Forbidden(graph, ident); - int num = (int)new SqlPreCommandSimple(sqlUpdate, UpdateParameters(ident, -1, forbidden, "")).ExecuteNonQuery(); + int num = (int)new SqlPreCommandSimple(sqlUpdate, new List().Do(ps => UpdateParameters(ident, -1, forbidden, "", ps))).ExecuteNonQuery(); if (num != 1) throw new EntityNotFoundException(ident.GetType(), ident.Id); }; @@ -460,11 +460,13 @@ public UpdateCache(Table table, Func sqlUpdatePattern, Fun Action, DirectedGraph?> GetUpdateMultiple(int num) { var isPostgres = Schema.Current.Settings.IsPostgres; - var notFound = Table.Var(isPostgres, "notFound"); - string sqlMulti = new StringBuilder() - .AppendLine(Table.DeclareTempTable(isPostgres, notFound, this.table.PrimaryKey.DbType.ToString(isPostgres))) + var updated = Table.Var(isPostgres, "updated"); + var id = this.table.PrimaryKey.Name.SqlEscape(isPostgres); + string sqlMulti = num == 1 ? SqlUpdatePattern("", true): + new StringBuilder() + .AppendLine(Table.DeclareTempTable(isPostgres, updated, this.table.PrimaryKey.DbType.ToString(isPostgres))) .AppendLines(Enumerable.Range(0, num).Select(i => SqlUpdatePattern(i.ToString(), true))) - .AppendLine($"SELECT Id from {notFound}").ToString(); + .AppendLine($"SELECT Id from {updated}").ToString(); if (table.Ticks != null) { @@ -478,13 +480,17 @@ public UpdateCache(Table table, Func sqlUpdatePattern, Fun long oldTicks = entity.Ticks; entity.Ticks = TimeZoneManager.Now.Ticks; - parameters.AddRange(UpdateParameters(entity, oldTicks, new Forbidden(graph, entity), i.ToString())); + UpdateParameters(entity, oldTicks, new Forbidden(graph, entity), i.ToString(), parameters); } DataTable dt = new SqlPreCommandSimple(sqlMulti, parameters).ExecuteDataTable(); - if (dt.Rows.Count > 0) - throw new ConcurrencyException(table.Type, dt.Rows.Cast().Select(r => new PrimaryKey((IComparable)r[0])).ToArray()); + if (dt.Rows.Count == idents.Count) + { + var updated = dt.Rows.Cast().Select(r => new PrimaryKey((IComparable)r[0])).ToList(); + + throw new ConcurrencyException(table.Type, idents.Select(a => a.Id).Except(updated).ToArray()); + } if (table.saveCollections.Value != null) table.saveCollections.Value.UpdateCollections(idents.Select(e => new EntityForbidden(e, new Forbidden(graph, e))).ToList()); @@ -498,7 +504,7 @@ public UpdateCache(Table table, Func sqlUpdatePattern, Fun for (int i = 0; i < num; i++) { var ident = idents[i]; - parameters.AddRange(UpdateParameters(ident, -1, new Forbidden(graph, ident), i.ToString())); + UpdateParameters(ident, -1, new Forbidden(graph, ident), i.ToString(), parameters); } DataTable dt = new SqlPreCommandSimple(sqlMulti, parameters).ExecuteDataTable(); @@ -546,22 +552,26 @@ internal static UpdateCache InitializeUpdate(Table table) string oldTicksParamName = ParameterBuilder.GetParameterName("old_ticks"); var isPostgres = Schema.Current.Settings.IsPostgres; - var notFound = Table.Var(isPostgres, "notFound"); + + var id = table.PrimaryKey.Name.SqlEscape(isPostgres); + var updated = Table.Var(isPostgres, "updated"); Func sqlUpdatePattern = (suffix, output) => { - string update = "UPDATE {0} SET \r\n{1}\r\n WHERE {2} = {3}{4};".FormatWith( - table.Name, - trios.ToString(p => "{0} = {1}".FormatWith(p.SourceColumn.SqlEscape(isPostgres), p.ParameterName + suffix).Indent(2), ",\r\n"), - table.PrimaryKey.Name.SqlEscape(isPostgres), - idParamName + suffix, - table.Ticks != null ? " AND {0} = {1}".FormatWith(table.Ticks.Name.SqlEscape(isPostgres), oldTicksParamName + suffix) : null - ); - - if (!output) - return update; - else - return update + $"\r\nIF @@ROWCOUNT = 0 INSERT INTO {notFound} (id) VALUES ({idParamName + suffix});"; + var result = $"UPDATE {table.Name} SET\r\n" + +trios.ToString(p => "{0} = {1}".FormatWith(p.SourceColumn.SqlEscape(isPostgres), p.ParameterName + suffix).Indent(2), ",\r\n") + "\r\n" + +(output && !isPostgres ? $"OUTPUT INSERTED.{id} INTO {updated}\r\n" : null) + +$"WHERE {id} = {idParamName + suffix}" + (table.Ticks != null ? $" AND {table.Ticks.Name.SqlEscape(isPostgres)} = {oldTicksParamName + suffix}" : null) + "\r\n" + +(output && isPostgres ? $"RETURNING ({id})" : null); + + if (!(isPostgres && output)) + return result; + + return $@"WITH rows AS ( +{result.Indent(4)} +) +INSERT INTO {updated}({id}) +SELECT {id} FROM rows"; }; List parameters = new List @@ -577,7 +587,7 @@ internal static UpdateCache InitializeUpdate(Table table) parameters.AddRange(trios.Select(a => (Expression)a.ParameterBuilder)); - var expr = Expression.Lambda>>( + var expr = Expression.Lambda>>( CreateBlock(parameters, assigments, paramList), paramIdent, paramOldTicks, paramForbidden, paramSuffix, paramList); @@ -712,7 +722,7 @@ public SqlPreCommand InsertSqlSync(Entity ident, bool includeCollections = true, var uc = updater.Value; var sql = uc.SqlUpdatePattern(suffix, false); - var parameters = uc.UpdateParameters(entity, (entity as Entity)?.Ticks ?? -1, new Forbidden(), suffix); + var parameters = new List().Do(ps => uc.UpdateParameters(entity, (entity as Entity)?.Ticks ?? -1, new Forbidden(), suffix, ps)); SqlPreCommand? update; if (where != null) @@ -903,8 +913,8 @@ public MListUpdate(EntityForbidden ef, MList mlist, int index) } } - internal Func sqlInsert = null!; - public Func> InsertParameters = null!; + internal Func sqlInsert = null!; + public Action> InsertParameters = null!; public ConcurrentDictionary>> insertCache = new ConcurrentDictionary>>(); @@ -915,10 +925,7 @@ Action> GetInsert(int numElements) bool isPostgres = Schema.Current.Settings.IsPostgres; var newIds = Table.Var(isPostgres, "newIDs"); - string sqlMulti = new StringBuilder() - .AppendLine(Table.DeclareTempTable(isPostgres, newIds, this.table.PrimaryKey.DbType.ToString(isPostgres))) - .AppendLines(Enumerable.Range(0, num).Select(i => sqlInsert(i.ToString(), true))) - .AppendLine($"SELECT Id from {newIds}").ToString(); + string sqlMulti = sqlInsert(Enumerable.Range(0, num).Select(i => i.ToString()).ToArray(), true); return (List list) => { @@ -926,7 +933,7 @@ Action> GetInsert(int numElements) for (int i = 0; i < num; i++) { var pair = list[i]; - result.AddRange(InsertParameters(pair.Entity, pair.MList.InnerList[pair.Index].Element, pair.Index, pair.Forbidden, i.ToString())); + InsertParameters(pair.Entity, pair.MList.InnerList[pair.Index].Element, pair.Index, pair.Forbidden, i.ToString(), result); } DataTable dt = new SqlPreCommandSimple(sqlMulti, result).ExecuteDataTable(); @@ -962,7 +969,9 @@ public MListInsert(EntityForbidden ef, MList mlist, int index) public object[] BulkInsertDataRow(Entity entity, object value, int order) { - return InsertParameters(entity, (T)value, order, new Forbidden(null), "").Select(a => a.Value).ToArray(); + List paramList = new List(); + InsertParameters(entity, (T)value, order, new Forbidden(null), "", paramList); + return paramList.Select(a => a.Value).ToArray(); } public Func> Getter = null!; @@ -1073,13 +1082,16 @@ public void RelationalUpdates(List idents) if (parent.IsNew) { + var isPostgres = Schema.Current.Settings.IsPostgres; + var parentIdVar = Table.Var(isPostgres, "parentId"); return collection.Select((e, i) => { - var parameters = InsertParameters(parent, e, i, new Forbidden(new HashSet { parent }), suffix + "_" + i); + var parameters = new List(); + InsertParameters(parent, e, i, new Forbidden(new HashSet { parent }), suffix + "_" + i, parameters); var parentId = parameters.First(); // wont be replaced, generating @parentId parameters.RemoveAt(0); - string script = sqlInsert(suffix + "_" + i, false); - script = script.Replace(parentId.ParameterName, "@parentId"); + string script = sqlInsert(new[] { suffix + "_" + i }, false); + script = script.Replace(parentId.ParameterName, parentIdVar); return new SqlPreCommandSimple(script, parameters).AddComment(e?.ToString()); }).Combine(Spacing.Simple); } @@ -1087,7 +1099,7 @@ public void RelationalUpdates(List idents) { return SqlPreCommand.Combine(Spacing.Simple, new SqlPreCommandSimple(sqlDelete(suffix), new List { DeleteParameter(parent, suffix) }).ReplaceFirstParameter(replaceParameter ? parent.Id.VariableName : null), - collection.Select((e, i) => new SqlPreCommandSimple(sqlInsert(suffix + "_" + i, false), InsertParameters(parent, e, i, new Forbidden(), suffix + "_" + i)) + collection.Select((e, i) => new SqlPreCommandSimple(sqlInsert(new[] { suffix + "_" + i }, false), new List().Do(ps => InsertParameters(parent, e, i, new Forbidden(), suffix + "_" + i, ps))) .AddComment(e?.ToString()) .ReplaceFirstParameter(replaceParameter ? parent.Id.VariableName : null) ).Combine(Spacing.Simple)); @@ -1110,12 +1122,12 @@ TableMListCache CreateCache() table = this, Getter = entity => (MList)Getter(entity), - sqlDelete = suffix => "DELETE {0} WHERE {1} = {2}".FormatWith(Name, BackReference.Name.SqlEscape(isPostgres), ParameterBuilder.GetParameterName(BackReference.Name + suffix)), + sqlDelete = suffix => "DELETE FROM {0} WHERE {1} = {2}".FormatWith(Name, BackReference.Name.SqlEscape(isPostgres), ParameterBuilder.GetParameterName(BackReference.Name + suffix)), DeleteParameter = (ident, suffix) => pb.CreateReferenceParameter(ParameterBuilder.GetParameterName(BackReference.Name + suffix), ident.Id, this.BackReference.ReferenceTable.PrimaryKey), sqlDeleteExcept = num => { - var sql = "DELETE {0} WHERE {1} = {2}" + var sql = "DELETE FROM {0} WHERE {1} = {2}" .FormatWith(Name, BackReference.Name.SqlEscape(isPostgres), ParameterBuilder.GetParameterName(BackReference.Name)); sql += " AND {0} NOT IN ({1})" @@ -1153,12 +1165,13 @@ TableMListCache CreateCache() var newIds = Table.Var(isPostgres, "newIDs"); - result.sqlInsert = (suffix, output) => $"INSERT INTO {Name} ({1})\r\n{2} VALUES ({3});".FormatWith(Name, + result.sqlInsert = (suffixes, output) => "INSERT INTO {0} ({1})\r\n{2}VALUES\r\n{3}{4};".FormatWith(Name, trios.ToString(p => p.SourceColumn.SqlEscape(isPostgres), ", "), - output ? $"OUTPUT INSERTED.Id into {newIds} \r\n" : null, - trios.ToString(p => p.ParameterName + suffix, ", ")); + output && !isPostgres ? $"OUTPUT INSERTED.{PrimaryKey.Name.SqlEscape(isPostgres)}\r\n" : null, + suffixes.ToString(s => " (" + trios.ToString(p => p.ParameterName + s, ", ") + ")", ",\r\n"), + output && isPostgres ? $"\r\nRETURNING {PrimaryKey.Name.SqlEscape(isPostgres)}" : null); - var expr = Expression.Lambda>>( + var expr = Expression.Lambda>>( Table.CreateBlock(trios.Select(a => a.ParameterBuilder), assigments, paramList), paramIdent, paramItem, paramOrder, paramForbidden, paramSuffix, paramList); result.InsertParameters = expr.Compile(); From a88c1f50b99ba76101b773902051a1d1c72ecf66 Mon Sep 17 00:00:00 2001 From: Olmo del Corral Date: Tue, 17 Dec 2019 00:11:16 +0100 Subject: [PATCH 03/13] more on Postgres support (Signum.Test environment and initial LINQ provider) --- .../Engine/Scripts/versioning_function.sql | 9 +- .../Scripts/versioning_function_nochecks.sql | 11 +- Signum.Engine/Engine/SqlBuilder.cs | 2 +- Signum.Engine/Engine/SqlPreCommand.cs | 11 +- Signum.Engine/Linq/DbExpressions.Sql.cs | 53 +++-- Signum.Engine/Linq/DbQueryProvider.cs | 4 +- .../ExpressionVisitor/ConditionsRewriter.cs | 2 +- .../ExpressionVisitor/DbExpressionComparer.cs | 16 +- .../DbExpressionNominator.cs | 92 +++++---- .../ExpressionVisitor/DbExpressionVisitor.cs | 11 +- .../Linq/ExpressionVisitor/QueryBinder.cs | 20 +- .../Linq/ExpressionVisitor/QueryFormatter.cs | 181 +++++++++++------- .../Linq/ExpressionVisitor/QueryRebinder.cs | 6 +- .../Linq/ExpressionVisitor/SmartEqualizer.cs | 7 +- .../ExpressionVisitor/UnusedColumnRemover.cs | 6 +- .../UpdateDeleteSimplifier.cs | 42 +++- Signum.Engine/Schema/Schema.Save.cs | 31 +-- 17 files changed, 289 insertions(+), 215 deletions(-) diff --git a/Signum.Engine/Engine/Scripts/versioning_function.sql b/Signum.Engine/Engine/Scripts/versioning_function.sql index b9cdda91db..be2f4c5b00 100644 --- a/Signum.Engine/Engine/Scripts/versioning_function.sql +++ b/Signum.Engine/Engine/Scripts/versioning_function.sql @@ -158,14 +158,7 @@ BEGIN ON history.attname = main.attname AND history.attname != sys_period; - EXECUTE ('INSERT INTO ' || - CASE split_part(history_table, '.', 2) - WHEN '' THEN - quote_ident(history_table) - ELSE - quote_ident(split_part(history_table, '.', 1)) || '.' || quote_ident(split_part(history_table, '.', 2)) - END || - '(' || + EXECUTE ('INSERT INTO ' || history_table || '(' || array_to_string(commonColumns , ',') || ',' || quote_ident(sys_period) || diff --git a/Signum.Engine/Engine/Scripts/versioning_function_nochecks.sql b/Signum.Engine/Engine/Scripts/versioning_function_nochecks.sql index b9dd1d1d9e..6c3d473ec8 100644 --- a/Signum.Engine/Engine/Scripts/versioning_function_nochecks.sql +++ b/Signum.Engine/Engine/Scripts/versioning_function_nochecks.sql @@ -55,14 +55,7 @@ BEGIN ON history.attname = main.attname AND history.attname != sys_period; - EXECUTE ('INSERT INTO ' || - CASE split_part(history_table, '.', 2) - WHEN '' THEN - quote_ident(history_table) - ELSE - quote_ident(split_part(history_table, '.', 1)) || '.' || quote_ident(split_part(history_table, '.', 2)) - END || - '(' || + EXECUTE ('INSERT INTO ' || history_table || '(' || array_to_string(commonColumns , ',') || ',' || quote_ident(sys_period) || @@ -80,4 +73,4 @@ BEGIN RETURN OLD; END; -$$ LANGUAGE plpgsql; \ No newline at end of file +$$ LANGUAGE plpgsql; diff --git a/Signum.Engine/Engine/SqlBuilder.cs b/Signum.Engine/Engine/SqlBuilder.cs index 8078b62c50..946334c422 100644 --- a/Signum.Engine/Engine/SqlBuilder.cs +++ b/Signum.Engine/Engine/SqlBuilder.cs @@ -68,7 +68,7 @@ public SqlPreCommand CreateTableSql(ITable t) new SqlPreCommandSimple(@$"CREATE TRIGGER versioning_trigger BEFORE INSERT OR UPDATE OR DELETE ON {t.Name} FOR EACH ROW EXECUTE PROCEDURE versioning( - 'sys_period', '{t.Name.Name}', true + 'sys_period', '{t.SystemVersioned.TableName}', true );") }.Combine(Spacing.Simple)!; diff --git a/Signum.Engine/Engine/SqlPreCommand.cs b/Signum.Engine/Engine/SqlPreCommand.cs index 519b1d1419..4e9b8b379b 100644 --- a/Signum.Engine/Engine/SqlPreCommand.cs +++ b/Signum.Engine/Engine/SqlPreCommand.cs @@ -11,6 +11,7 @@ using System.Data.Common; using System.Globalization; using Signum.Engine.Maps; +using Npgsql; namespace Signum.Engine { @@ -159,10 +160,8 @@ protected internal override int NumParameters static readonly Regex regex = new Regex(@"@[_\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nl}][_\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nl}\p{Nd}]*"); - internal static string Encode(DbParameter param) + internal static string Encode(object? value) { - var value = param.Value; - if (value == null || value == DBNull.Value) return "NULL"; @@ -180,7 +179,7 @@ internal static string Encode(DbParameter param) if (value is bool b) { - if (param is Npgsql.NpgsqlParameter p && p.NpgsqlDbType == NpgsqlTypes.NpgsqlDbType.Boolean) + if (Schema.Current.Settings.IsPostgres) return b.ToString(); return (b ? 1 : 0).ToString(); @@ -204,7 +203,7 @@ protected internal override void PlainSql(StringBuilder sb) sb.Append(Sql); else { - var dic = Parameters.ToDictionary(a => a.ParameterName, a => Encode(a)); + var dic = Parameters.ToDictionary(a => a.ParameterName, a => Encode(a.Value)); sb.Append(regex.Replace(Sql, m => dic.TryGetC(m.Value) ?? m.Value)); } @@ -215,7 +214,7 @@ public string sp_executesql() var pars = this.Parameters.EmptyIfNull(); var sqlBuilder = Connector.Current.SqlBuilder; - var parameterVars = pars.ToString(p => $"{p.ParameterName} {((SqlParameter)p).SqlDbType.ToString()}{sqlBuilder.GetSizeScale(p.Size.DefaultToNull(), p.Scale.DefaultToNull())}", ", "); + var parameterVars = pars.ToString(p => $"{p.ParameterName} {(p is SqlParameter sp ? sp.SqlDbType.ToString() : ((NpgsqlParameter)p).NpgsqlDbType.ToString())}{sqlBuilder.GetSizeScale(p.Size.DefaultToNull(), p.Scale.DefaultToNull())}", ", "); var parameterValues = pars.ToString(p => Encode(p.Value), ","); return $"EXEC sp_executesql N'{this.Sql}', N'{parameterVars}', {parameterValues}"; diff --git a/Signum.Engine/Linq/DbExpressions.Sql.cs b/Signum.Engine/Linq/DbExpressions.Sql.cs index f9d532d8d6..794c762366 100644 --- a/Signum.Engine/Linq/DbExpressions.Sql.cs +++ b/Signum.Engine/Linq/DbExpressions.Sql.cs @@ -50,7 +50,6 @@ internal enum DbExpressionType Delete, InsertSelect, CommandAggregate, - SelectRowCount, Entity = 2000, EmbeddedInit, MixinInit, @@ -606,6 +605,14 @@ internal enum SqlFunction STUFF, } + internal enum PostgresFunction + { + strpos, + starts_with, + length, + EXTRACT, + } + internal enum SqlEnums { year, @@ -613,13 +620,14 @@ internal enum SqlEnums quarter, day, week, - weekday, + weekday, //Sql Server + dow, //Postgres hour, minute, second, millisecond, - dayofyear, - iso_week + dayofyear, //SQL Server + doy //Postgres } @@ -665,7 +673,7 @@ protected override Expression Accept(DbExpressionVisitor visitor) return visitor.VisitToDayOfWeek(this); } - public static ResetLazy> DateFirst = new ResetLazy>(() => Tuple.Create((byte)Executor.ExecuteScalar("SELECT @@DATEFIRST")!)); + public static ResetLazy> DateFirst = new ResetLazy>(() => Tuple.Create(Schema.Current.Settings.IsPostgres ? (byte)1 /*no idea :D*/ : (byte)Executor.ExecuteScalar("SELECT @@DATEFIRST")!)); internal static MethodInfo miToDayOfWeek = ReflectionTools.GetMethodInfo(() => ToDayOfWeek(1, 1)); public static DayOfWeek? ToDayOfWeek(int? sqlServerWeekDay, byte dateFirst) @@ -1192,22 +1200,25 @@ internal class DeleteExpression : CommandExpression public readonly SourceWithAliasExpression Source; public readonly Expression? Where; + public readonly bool ReturnRowCount; - public DeleteExpression(ITable table, bool useHistoryTable, SourceWithAliasExpression source, Expression? where) + public DeleteExpression(ITable table, bool useHistoryTable, SourceWithAliasExpression source, Expression? where, bool returnRowCount) : base(DbExpressionType.Delete) { this.Table = table; this.UseHistoryTable = useHistoryTable; this.Source = source; this.Where = where; + this.ReturnRowCount = returnRowCount; } public override string ToString() { - return "DELETE {0}\r\nFROM {1}\r\n{2}".FormatWith( + return "DELETE FROM {0}\r\nFROM {1}\r\n{2}".FormatWith( Table.Name, Source.ToString(), - Where?.Let(w => "WHERE " + w.ToString())); + Where?.Let(w => "WHERE " + w.ToString())) + + (ReturnRowCount ? "\r\nSELECT @@rowcount" : ""); } protected override Expression Accept(DbExpressionVisitor visitor) @@ -1225,8 +1236,9 @@ internal class UpdateExpression : CommandExpression public readonly ReadOnlyCollection Assigments; public readonly SourceWithAliasExpression Source; public readonly Expression Where; + public readonly bool ReturnRowCount; - public UpdateExpression(ITable table, bool useHistoryTable, SourceWithAliasExpression source, Expression where, IEnumerable assigments) + public UpdateExpression(ITable table, bool useHistoryTable, SourceWithAliasExpression source, Expression where, IEnumerable assigments, bool returnRowCount) : base(DbExpressionType.Update) { this.Table = table; @@ -1234,6 +1246,7 @@ public UpdateExpression(ITable table, bool useHistoryTable, SourceWithAliasExpre this.Assigments = assigments.ToReadOnly(); this.Source = source; this.Where = where; + this.ReturnRowCount = returnRowCount; } public override string ToString() @@ -1258,14 +1271,16 @@ internal class InsertSelectExpression : CommandExpression public ObjectName Name { get { return UseHistoryTable ? Table.SystemVersioned!.TableName : Table.Name; } } public readonly ReadOnlyCollection Assigments; public readonly SourceWithAliasExpression Source; + public readonly bool ReturnRowCount; - public InsertSelectExpression(ITable table, bool useHistoryTable, SourceWithAliasExpression source, IEnumerable assigments) + public InsertSelectExpression(ITable table, bool useHistoryTable, SourceWithAliasExpression source, IEnumerable assigments, bool returnRowCount) : base(DbExpressionType.InsertSelect) { this.Table = table; this.UseHistoryTable = useHistoryTable; this.Assigments = assigments.ToReadOnly(); this.Source = source; + this.ReturnRowCount = returnRowCount; } public override string ToString() @@ -1320,22 +1335,4 @@ protected override Expression Accept(DbExpressionVisitor visitor) return visitor.VisitCommandAggregate(this); } } - - internal class SelectRowCountExpression : CommandExpression - { - public SelectRowCountExpression() - : base(DbExpressionType.SelectRowCount) - { - } - - public override string ToString() - { - return "SELECT @@rowcount"; - } - - protected override Expression Accept(DbExpressionVisitor visitor) - { - return visitor.VisitSelectRowCount(this); - } - } } diff --git a/Signum.Engine/Linq/DbQueryProvider.cs b/Signum.Engine/Linq/DbQueryProvider.cs index 5f50200e3a..03fe66dbdf 100644 --- a/Signum.Engine/Linq/DbQueryProvider.cs +++ b/Signum.Engine/Linq/DbQueryProvider.cs @@ -83,6 +83,8 @@ internal R Translate(Expression expression, Func continu internal static Expression Optimize(Expression binded, QueryBinder binder, AliasGenerator aliasGenerator, HeavyProfiler.Tracer? log) { + var isPostgres = Schema.Current.Settings.IsPostgres; + log.Switch("Aggregate"); Expression rewrited = AggregateRewriter.Rewrite(binded); log.Switch("EntityCompleter"); @@ -98,7 +100,7 @@ internal static Expression Optimize(Expression binded, QueryBinder binder, Alias log.Switch("Redundant"); Expression subqueryCleaned = RedundantSubqueryRemover.Remove(columnCleaned); log.Switch("Condition"); - Expression rewriteConditions = ConditionsRewriter.Rewrite(subqueryCleaned); + Expression rewriteConditions = isPostgres ? subqueryCleaned : ConditionsRewriter.Rewrite(subqueryCleaned); log.Switch("Scalar"); Expression scalar = ScalarSubqueryRewriter.Rewrite(rewriteConditions); return scalar; diff --git a/Signum.Engine/Linq/ExpressionVisitor/ConditionsRewriter.cs b/Signum.Engine/Linq/ExpressionVisitor/ConditionsRewriter.cs index 88c1e7b30a..d017d1a9df 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/ConditionsRewriter.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/ConditionsRewriter.cs @@ -375,7 +375,7 @@ protected internal override Expression VisitUpdate(UpdateExpression update) return c; }); if (source != update.Source || where != update.Where || assigments != update.Assigments) - return new UpdateExpression(update.Table, update.UseHistoryTable, (SelectExpression)source, where, assigments); + return new UpdateExpression(update.Table, update.UseHistoryTable, (SelectExpression)source, where, assigments, update.ReturnRowCount); return update; } diff --git a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionComparer.cs b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionComparer.cs index 767f774697..0ab872de3a 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionComparer.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionComparer.cs @@ -113,8 +113,6 @@ private bool ComparePrivate(Expression? a, Expression? b) return CompareInsertSelect((InsertSelectExpression)a, (InsertSelectExpression)b); case DbExpressionType.CommandAggregate: return CompareCommandAggregate((CommandAggregateExpression)a, (CommandAggregateExpression)b); - case DbExpressionType.SelectRowCount: - return CompareSelectRowCount((SelectRowCountExpression)a, (SelectRowCountExpression)b); case DbExpressionType.Entity: return CompareEntityInit((EntityExpression)a, (EntityExpression)b); case DbExpressionType.EmbeddedInit: @@ -411,7 +409,8 @@ protected virtual bool CompareDelete(DeleteExpression a, DeleteExpression b) return a.Table == b.Table && a.UseHistoryTable == b.UseHistoryTable && Compare(a.Source, b.Source) - && Compare(a.Where, b.Where); + && Compare(a.Where, b.Where) + && a.ReturnRowCount == b.ReturnRowCount; } protected virtual bool CompareUpdate(UpdateExpression a, UpdateExpression b) @@ -420,7 +419,8 @@ protected virtual bool CompareUpdate(UpdateExpression a, UpdateExpression b) && a.UseHistoryTable == b.UseHistoryTable && CompareList(a.Assigments, b.Assigments, CompareAssigment) && Compare(a.Source, b.Source) - && Compare(a.Where, b.Where); + && Compare(a.Where, b.Where) + && a.ReturnRowCount == b.ReturnRowCount; } protected virtual bool CompareInsertSelect(InsertSelectExpression a, InsertSelectExpression b) @@ -428,7 +428,8 @@ protected virtual bool CompareInsertSelect(InsertSelectExpression a, InsertSelec return a.Table == b.Table && a.UseHistoryTable == b.UseHistoryTable && CompareList(a.Assigments, b.Assigments, CompareAssigment) - && Compare(a.Source, b.Source); + && Compare(a.Source, b.Source) + && a.ReturnRowCount == b.ReturnRowCount; } protected virtual bool CompareAssigment(ColumnAssignment a, ColumnAssignment b) @@ -441,11 +442,6 @@ protected virtual bool CompareCommandAggregate(CommandAggregateExpression a, Com return CompareList(a.Commands, b.Commands, Compare); } - protected virtual bool CompareSelectRowCount(SelectRowCountExpression a, SelectRowCountExpression b) - { - return true; - } - protected virtual bool CompareEntityInit(EntityExpression a, EntityExpression b) { return a.Table == b.Table diff --git a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs index c0724d0db4..8126c50427 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs @@ -1,4 +1,5 @@ using Microsoft.SqlServer.Server; +using NpgsqlTypes; using Signum.Engine.Maps; using Signum.Entities; using Signum.Utilities; @@ -53,7 +54,12 @@ bool Has(Expression expression) return this.candidates.Contains(expression); } - private DbExpressionNominator() { } + + bool isPostgres; + private DbExpressionNominator() + { + this.isPostgres = Schema.Current.Settings.IsPostgres; + } static internal HashSet Nominate(Expression? expression, out Expression newExpression, bool isGroupKey = false) { @@ -105,7 +111,6 @@ private bool IsExcluded(Expression exp) case DbExpressionType.Update: case DbExpressionType.Delete: case DbExpressionType.CommandAggregate: - case DbExpressionType.SelectRowCount: return true; } return false; @@ -385,9 +390,14 @@ protected Expression GetFormatToString(MethodCallExpression m, string? defaultFo new SqlConstantExpression(culture.Name) })); } + protected Expression? TrySqlFunction(Expression? obj, PostgresFunction postgresFunction, Type type, params Expression[] expression) + { + return TrySqlFunction(obj, postgresFunction.ToString(), type, expression); + } + protected Expression? TrySqlFunction(Expression? obj, SqlFunction sqlFunction, Type type, params Expression[] expression) { - return TrySqlFunction(obj, sqlFunction.ToString(), type, expression); + return TrySqlFunction(obj, isPostgres? sqlFunction.ToString().ToLower() : sqlFunction.ToString(), type, expression); } protected Expression? TrySqlFunction(Expression? obj, string sqlFunction, Type type, params Expression[] expression) @@ -413,7 +423,7 @@ protected Expression GetFormatToString(MethodCallExpression m, string? defaultFo return null; } - return Add(new SqlFunctionExpression(type, newObj, sqlFunction.ToString(), newExpressions)); + return Add(new SqlFunctionExpression(type, newObj, sqlFunction, newExpressions)); } private SqlFunctionExpression? TrySqlDifference(SqlEnums sqlEnums, Type type, Expression expression) @@ -457,7 +467,7 @@ protected Expression GetFormatToString(MethodCallExpression m, string? defaultFo if (Connector.Current.AllowsConvertToDate) return Add(new SqlFunctionExpression(typeof(DateTime), null, SqlFunction.CONVERT.ToString(), new[] { - new SqlConstantExpression(SqlDbType.Date), + new SqlConstantExpression(isPostgres ? NpgsqlDbType.Date.ToString() : SqlDbType.Date.ToString()), expr, new SqlConstantExpression(101) })); @@ -478,7 +488,7 @@ protected Expression GetFormatToString(MethodCallExpression m, string? defaultFo if (Connector.Current.AllowsConvertToTime) return Add(new SqlFunctionExpression(typeof(TimeSpan), null, SqlFunction.CONVERT.ToString(), new[] { - new SqlConstantExpression(SqlDbType.Time), + new SqlConstantExpression(isPostgres ? NpgsqlDbType.Time.ToString() : SqlDbType.Time.ToString()), expr, })); @@ -491,7 +501,7 @@ protected Expression GetFormatToString(MethodCallExpression m, string? defaultFo if (innerProjection || !Has(expr)) return null; - var number = TrySqlFunction(null, SqlFunction.DATEPART, typeof(int?), new SqlEnumExpression(SqlEnums.weekday), expr)!; + var number = TrySqlFunction(null, getDatePart(), typeof(int?), new SqlEnumExpression(isPostgres ? SqlEnums.dow : SqlEnums.weekday), expr)!; Add(number); @@ -535,7 +545,7 @@ protected Expression GetFormatToString(MethodCallExpression m, string? defaultFo return null; var castDate = new SqlCastExpression(typeof(DateTime), exprDate, new AbstractDbType(SqlDbType.DateTime)); //Just in case is a Date - var castTime = new SqlCastExpression(typeof(TimeSpan), exprTime, new AbstractDbType(SqlDbType.DateTime)); //Just in case is a Date + var castTime = new SqlCastExpression(typeof(TimeSpan), exprTime, new AbstractDbType(SqlDbType.Time)); //Just in case is a Date var result = add ? Expression.Add(castDate, castTime) : Expression.Subtract(castDate, castTime); @@ -1124,7 +1134,9 @@ protected internal override Expression VisitLike(LikeExpression like) if (Has(newSubExpression) && Has(newExpression)) { - SqlFunctionExpression result = new SqlFunctionExpression(typeof(int), null, SqlFunction.CHARINDEX.ToString(), new[] { newExpression, newSubExpression }); + SqlFunctionExpression result = isPostgres ? + new SqlFunctionExpression(typeof(int), null, PostgresFunction.strpos.ToString(), new[] { newExpression, newSubExpression }): + new SqlFunctionExpression(typeof(int), null, SqlFunction.CHARINDEX.ToString(), new[] { newExpression, newSubExpression, }); Add(result); @@ -1158,20 +1170,27 @@ protected override Expression VisitMember(MemberExpression m) return base.VisitMember(m); } + string getDatePart() + { + return isPostgres ? PostgresFunction.EXTRACT.ToString() : SqlFunction.DATEPART.ToString(); + } + public Expression? HardCodedMembers(MemberExpression m) { + + switch (m.Member.DeclaringType!.TypeName() + "." + m.Member.Name) { - case "string.Length": return TrySqlFunction(null, SqlFunction.LEN, m.Type, m.Expression); + case "string.Length": return TrySqlFunction(null, isPostgres ? PostgresFunction.length.ToString() : SqlFunction.LEN.ToString(), m.Type, m.Expression); case "Math.PI": return TrySqlFunction(null, SqlFunction.PI, m.Type); - case "DateTime.Year": return TrySqlFunction(null, SqlFunction.YEAR, m.Type, m.Expression); - case "DateTime.Month": return TrySqlFunction(null, SqlFunction.MONTH, m.Type, m.Expression); - case "DateTime.Day": return TrySqlFunction(null, SqlFunction.DAY, m.Type, m.Expression); - case "DateTime.DayOfYear": return TrySqlFunction(null, SqlFunction.DATEPART, m.Type, new SqlEnumExpression(SqlEnums.dayofyear), m.Expression); - case "DateTime.Hour": return TrySqlFunction(null, SqlFunction.DATEPART, m.Type, new SqlEnumExpression(SqlEnums.hour), m.Expression); - case "DateTime.Minute": return TrySqlFunction(null, SqlFunction.DATEPART, m.Type, new SqlEnumExpression(SqlEnums.minute), m.Expression); - case "DateTime.Second": return TrySqlFunction(null, SqlFunction.DATEPART, m.Type, new SqlEnumExpression(SqlEnums.second), m.Expression); - case "DateTime.Millisecond": return TrySqlFunction(null, SqlFunction.DATEPART, m.Type, new SqlEnumExpression(SqlEnums.millisecond), m.Expression); + case "DateTime.Year": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.year), m.Expression); + case "DateTime.Month": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.month), m.Expression); + case "DateTime.Day": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.day), m.Expression); + case "DateTime.DayOfYear": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(isPostgres? SqlEnums.dayofyear : SqlEnums.doy), m.Expression); + case "DateTime.Hour": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.hour), m.Expression); + case "DateTime.Minute": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.minute), m.Expression); + case "DateTime.Second": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.second), m.Expression); + case "DateTime.Millisecond": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.millisecond), m.Expression); case "DateTime.Date": return TrySqlDate(m.Expression); case "DateTime.TimeOfDay": return TrySqlTime(m.Expression); case "DateTime.DayOfWeek": return TrySqlDayOftheWeek(m.Expression); @@ -1184,10 +1203,10 @@ protected override Expression VisitMember(MemberExpression m) return Add(new SqlCastExpression(typeof(int?), TrySqlFunction(null, SqlFunction.FLOOR, typeof(double?), diff)!)); } - case "TimeSpan.Hours": return TrySqlFunction(null, SqlFunction.DATEPART, m.Type, new SqlEnumExpression(SqlEnums.hour), m.Expression); - case "TimeSpan.Minutes": return TrySqlFunction(null, SqlFunction.DATEPART, m.Type, new SqlEnumExpression(SqlEnums.minute), m.Expression); - case "TimeSpan.Seconds": return TrySqlFunction(null, SqlFunction.DATEPART, m.Type, new SqlEnumExpression(SqlEnums.second), m.Expression); - case "TimeSpan.Milliseconds": return TrySqlFunction(null, SqlFunction.DATEPART, m.Type, new SqlEnumExpression(SqlEnums.millisecond), m.Expression); + case "TimeSpan.Hours": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.hour), m.Expression); + case "TimeSpan.Minutes": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.minute), m.Expression); + case "TimeSpan.Seconds": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.second), m.Expression); + case "TimeSpan.Milliseconds": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.millisecond), m.Expression); case "TimeSpan.TotalDays": return TrySqlDifference(SqlEnums.day, m.Type, m.Expression); case "TimeSpan.TotalHours": return TrySqlDifference(SqlEnums.hour, m.Type, m.Expression); @@ -1236,6 +1255,11 @@ protected override Expression VisitMember(MemberExpression m) return Connector.Current.SupportsFormat ? GetFormatToString(m, defaultFormat) : TrySqlToString(m); } + private Expression? TryDateAdd(Type returnType, Expression date, Expression value, SqlEnums unit) + { + return TrySqlFunction(null, SqlFunction.DATEADD, returnType, new SqlEnumExpression(unit), value, date); + } + private Expression? HardCodedMethods(MethodCallExpression m) { if (m.Method.Name == "ToString" && m.Method.DeclaringType != typeof(EnumerableExtensions)) @@ -1293,13 +1317,13 @@ protected override Expression VisitMember(MemberExpression m) case "string.Substring": return TrySqlFunction(null, SqlFunction.SUBSTRING, m.Type, m.Object, Expression.Add(m.GetArgument("startIndex"), new SqlConstantExpression(1)), m.TryGetArgument("length") ?? new SqlConstantExpression(int.MaxValue)); case "string.Contains": - return TryCharIndex(m.GetArgument("value"), m.Object, index => Expression.GreaterThanOrEqual(index, new SqlConstantExpression(1))); + return TryCharIndex(m.Object, m.GetArgument("value"), index => Expression.GreaterThanOrEqual(index, new SqlConstantExpression(1))); case "string.StartsWith": - return TryCharIndex(m.GetArgument("value"), m.Object, index => Expression.Equal(index, new SqlConstantExpression(1))); + return TryCharIndex(m.Object, m.GetArgument("value"), index => Expression.Equal(index, new SqlConstantExpression(1))); case "string.EndsWith": return TryCharIndex( - TrySqlFunction(null, SqlFunction.REVERSE, m.Type, m.GetArgument("value"))!, TrySqlFunction(null, SqlFunction.REVERSE, m.Type, m.Object)!, + TrySqlFunction(null, SqlFunction.REVERSE, m.Type, m.GetArgument("value"))!, index => Expression.Equal(index, new SqlConstantExpression(1))); case "string.Format": case "StringExtensions.FormatWith": @@ -1326,13 +1350,13 @@ protected override Expression VisitMember(MemberExpression m) return TryAddSubtractDateTimeTimeSpan(m.Object, val, m.Method.Name == "Add"); } - case "DateTime.AddDays": return TrySqlFunction(null, SqlFunction.DATEADD, m.Type, new SqlEnumExpression(SqlEnums.day), m.GetArgument("value"), m.Object); - case "DateTime.AddHours": return TrySqlFunction(null, SqlFunction.DATEADD, m.Type, new SqlEnumExpression(SqlEnums.hour), m.GetArgument("value"), m.Object); - case "DateTime.AddMilliseconds": return TrySqlFunction(null, SqlFunction.DATEADD, m.Type, new SqlEnumExpression(SqlEnums.millisecond), m.GetArgument("value"), m.Object); - case "DateTime.AddMinutes": return TrySqlFunction(null, SqlFunction.DATEADD, m.Type, new SqlEnumExpression(SqlEnums.minute), m.GetArgument("value"), m.Object); - case "DateTime.AddMonths": return TrySqlFunction(null, SqlFunction.DATEADD, m.Type, new SqlEnumExpression(SqlEnums.month), m.GetArgument("months"), m.Object); - case "DateTime.AddSeconds": return TrySqlFunction(null, SqlFunction.DATEADD, m.Type, new SqlEnumExpression(SqlEnums.second), m.GetArgument("value"), m.Object); - case "DateTime.AddYears": return TrySqlFunction(null, SqlFunction.DATEADD, m.Type, new SqlEnumExpression(SqlEnums.year), m.GetArgument("value"), m.Object); + case "DateTime.AddYears": return TryDateAdd(m.Type, m.Object, m.GetArgument("value"), SqlEnums.year); + case "DateTime.AddMonths": return TryDateAdd(m.Type, m.Object, m.GetArgument("months"), SqlEnums.month); + case "DateTime.AddDays": return TryDateAdd(m.Type, m.Object, m.GetArgument("value"), SqlEnums.day); + case "DateTime.AddHours": return TryDateAdd(m.Type, m.Object, m.GetArgument("value"), SqlEnums.hour); + case "DateTime.AddMinutes": return TryDateAdd(m.Type, m.Object, m.GetArgument("value"), SqlEnums.minute); + case "DateTime.AddSeconds": return TryDateAdd(m.Type, m.Object, m.GetArgument("value"), SqlEnums.second); + case "DateTime.AddMilliseconds": return TryDateAdd(m.Type, m.Object, m.GetArgument("value"), SqlEnums.millisecond); case "DateTime.ToShortDateString": return GetDateTimeToStringSqlFunction(m, "d"); case "DateTime.ToShortTimeString": return GetDateTimeToStringSqlFunction(m, "t"); case "DateTime.ToLongDateString": return GetDateTimeToStringSqlFunction(m, "D"); @@ -1348,8 +1372,8 @@ protected override Expression VisitMember(MemberExpression m) case "DateTimeExtensions.YearsTo": return TryDatePartTo(new SqlEnumExpression(SqlEnums.year), m.GetArgument("start"), m.GetArgument("end")); case "DateTimeExtensions.MonthsTo": return TryDatePartTo(new SqlEnumExpression(SqlEnums.month), m.GetArgument("start"), m.GetArgument("end")); - case "DateTimeExtensions.Quarter": return TrySqlFunction(null, SqlFunction.DATEPART, m.Type, new SqlEnumExpression(SqlEnums.quarter), m.Arguments.Single()); - case "DateTimeExtensions.WeekNumber": return TrySqlFunction(null, SqlFunction.DATEPART, m.Type, new SqlEnumExpression(SqlEnums.week), m.Arguments.Single()); + case "DateTimeExtensions.Quarter": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.quarter), m.Arguments.Single()); + case "DateTimeExtensions.WeekNumber": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.week), m.Arguments.Single()); case "Math.Sign": return TrySqlFunction(null, SqlFunction.SIGN, m.Type, m.GetArgument("value")); case "Math.Abs": return TrySqlFunction(null, SqlFunction.ABS, m.Type, m.GetArgument("value")); diff --git a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs index 872b87aa1b..cdbaa09ab8 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs @@ -32,7 +32,7 @@ protected internal virtual Expression VisitDelete(DeleteExpression delete) var source = VisitSource(delete.Source); var where = Visit(delete.Where); if (source != delete.Source || where != delete.Where) - return new DeleteExpression(delete.Table, delete.UseHistoryTable, (SourceWithAliasExpression)source, where); + return new DeleteExpression(delete.Table, delete.UseHistoryTable, (SourceWithAliasExpression)source, where, delete.ReturnRowCount); return delete; } @@ -42,7 +42,7 @@ protected internal virtual Expression VisitUpdate(UpdateExpression update) var where = Visit(update.Where); var assigments = Visit(update.Assigments, VisitColumnAssigment); if(source != update.Source || where != update.Where || assigments != update.Assigments) - return new UpdateExpression(update.Table, update.UseHistoryTable, (SourceWithAliasExpression)source, where, assigments); + return new UpdateExpression(update.Table, update.UseHistoryTable, (SourceWithAliasExpression)source, where, assigments, update.ReturnRowCount); return update; } @@ -51,7 +51,7 @@ protected internal virtual Expression VisitInsertSelect(InsertSelectExpression i var source = VisitSource(insertSelect.Source); var assigments = Visit(insertSelect.Assigments, VisitColumnAssigment); if (source != insertSelect.Source || assigments != insertSelect.Assigments) - return new InsertSelectExpression(insertSelect.Table, insertSelect.UseHistoryTable, (SourceWithAliasExpression)source, assigments); + return new InsertSelectExpression(insertSelect.Table, insertSelect.UseHistoryTable, (SourceWithAliasExpression)source, assigments, insertSelect.ReturnRowCount); return insertSelect; } @@ -63,11 +63,6 @@ protected internal virtual ColumnAssignment VisitColumnAssigment(ColumnAssignmen return c; } - protected internal virtual Expression VisitSelectRowCount(SelectRowCountExpression src) - { - return src; - } - protected internal virtual Expression VisitLiteReference(LiteReferenceExpression lite) { var newRef = Visit(lite.Reference); diff --git a/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs b/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs index 75f5065c31..5d83944c46 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs @@ -2223,16 +2223,16 @@ internal CommandExpression BindDelete(Expression source) commands.AddRange(ee.Table.TablesMList().Select(t => { Expression backId = t.BackColumnExpression(aliasGenerator.Table(t.GetName(isHistory))); - return new DeleteExpression(t, isHistory && t.SystemVersioned != null, pr.Select, SmartEqualizer.EqualNullable(backId, ee.ExternalId)); + return new DeleteExpression(t, isHistory && t.SystemVersioned != null, pr.Select, SmartEqualizer.EqualNullable(backId, ee.ExternalId), returnRowCount: false); })); - commands.Add(new DeleteExpression(ee.Table, isHistory && ee.Table.SystemVersioned != null, pr.Select, SmartEqualizer.EqualNullable(id, ee.ExternalId))); + commands.Add(new DeleteExpression(ee.Table, isHistory && ee.Table.SystemVersioned != null, pr.Select, SmartEqualizer.EqualNullable(id, ee.ExternalId), returnRowCount: true)); } else if (pr.Projector is MListElementExpression mlee) { Expression id = mlee.Table.RowIdExpression(aliasGenerator.Table(mlee.Table.GetName(isHistory))); - commands.Add(new DeleteExpression(mlee.Table, isHistory && mlee.Table.SystemVersioned != null, pr.Select, SmartEqualizer.EqualNullable(id, mlee.RowId))); + commands.Add(new DeleteExpression(mlee.Table, isHistory && mlee.Table.SystemVersioned != null, pr.Select, SmartEqualizer.EqualNullable(id, mlee.RowId), returnRowCount: true)); } else if (pr.Projector is EmbeddedEntityExpression eee) { @@ -2240,13 +2240,11 @@ internal CommandExpression BindDelete(Expression source) Expression id = vn.GetIdExpression(aliasGenerator.Table(vn.Name)).ThrowIfNull(() => $"{vn.Name} has no primary name"); - commands.Add(new DeleteExpression(vn, false, pr.Select, SmartEqualizer.EqualNullable(id, eee.GetViewId()))); + commands.Add(new DeleteExpression(vn, false, pr.Select, SmartEqualizer.EqualNullable(id, eee.GetViewId()), returnRowCount: true)); } else throw new InvalidOperationException("Delete not supported for {0}".FormatWith(pr.Projector.GetType().TypeName())); - commands.Add(new SelectRowCountExpression()); - return new CommandAggregateExpression(commands); } @@ -2334,8 +2332,7 @@ internal CommandExpression BindUpdate(Expression source, LambdaExpression? partS var result = new CommandAggregateExpression(new CommandExpression[] { - new UpdateExpression(table, isHistory && table.SystemVersioned != null, pr.Select, condition, assignments), - new SelectRowCountExpression() + new UpdateExpression(table, isHistory && table.SystemVersioned != null, pr.Select, condition, assignments, returnRowCount: true), }); return (CommandAggregateExpression)QueryJoinExpander.ExpandJoins(result, this, cleanRequests: true); @@ -2379,8 +2376,7 @@ internal CommandExpression BindInsert(Expression source, LambdaExpression constr var result = new CommandAggregateExpression(new CommandExpression[] { - new InsertSelectExpression(table, isHistory && table.SystemVersioned != null, pr.Select, assignments), - new SelectRowCountExpression() + new InsertSelectExpression(table, isHistory && table.SystemVersioned != null, pr.Select, assignments, returnRowCount: true), }); return (CommandAggregateExpression)QueryJoinExpander.ExpandJoins(result, this, cleanRequests: true); @@ -3276,7 +3272,7 @@ protected internal override Expression VisitUpdate(UpdateExpression update) if (source != update.Source || where != update.Where || assigments != update.Assigments) { var select = (source as SourceWithAliasExpression) ?? WrapSelect(source); - return new UpdateExpression(update.Table, update.UseHistoryTable, select, where, assigments); + return new UpdateExpression(update.Table, update.UseHistoryTable, select, where, assigments, update.ReturnRowCount); } return update; } @@ -3288,7 +3284,7 @@ protected internal override Expression VisitInsertSelect(InsertSelectExpression if (source != insertSelect.Source || assigments != insertSelect.Assigments) { var select = (source as SourceWithAliasExpression) ?? WrapSelect(source); - return new InsertSelectExpression(insertSelect.Table, insertSelect.UseHistoryTable, select, assigments); + return new InsertSelectExpression(insertSelect.Table, insertSelect.UseHistoryTable, select, assigments, insertSelect.ReturnRowCount); } return insertSelect; } diff --git a/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs b/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs index 36cd8d885b..58587b212a 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs @@ -56,15 +56,19 @@ DbParameterPair CreateParameter(ConstantExpression value) string name = GetNextParamAlias(); bool nullable = value.Type.IsClass || value.Type.IsNullable(); + object? val = value.Value; Type clrType = value.Type.UnNullify(); if (clrType.IsEnum) + { clrType = typeof(int); + val = val == null ? (int?)null : Convert.ToInt32(val); + } var typePair = Schema.Current.Settings.GetSqlDbTypePair(clrType); var pb = Connector.Current.ParameterBuilder; - var param = pb.CreateParameter(name, typePair.DbType, typePair.UserDefinedTypeName, nullable, value.Value ?? DBNull.Value); + var param = pb.CreateParameter(name, typePair.DbType, typePair.UserDefinedTypeName, nullable, val ?? DBNull.Value); return new DbParameterPair(param, name); } @@ -201,6 +205,8 @@ protected override Expression VisitBinary(BinaryExpression b) case ExpressionType.Add: case ExpressionType.AddChecked: + if(this.isPostgres && (b.Left.Type == typeof(string) || b.Right.Type == typeof(string))) + sb.Append(" || "); sb.Append(" + "); break; case ExpressionType.Subtract: @@ -576,10 +582,9 @@ private void AppendColumn(ColumnDeclaration column) if (column.Name.HasText() && (c == null || c.Name != column.Name)) { - - sb.Append(column.Name.SqlEscape(isPostgres)); - sb.Append(" = "); this.Visit(column.Expression); + sb.Append(" as "); + sb.Append(column.Name.SqlEscape(isPostgres)); } else { @@ -700,10 +705,10 @@ protected internal override Expression VisitJoin(JoinExpression join) sb.Append("FULL OUTER JOIN "); break; case JoinType.CrossApply: - sb.Append("CROSS APPLY "); + sb.Append(isPostgres ? "JOIN LATERAL " : "CROSS APPLY "); break; case JoinType.OuterApply: - sb.Append("OUTER APPLY "); + sb.Append(isPostgres ? "LEFT JOIN LATERAL " : "OUTER APPLY "); break; } @@ -724,6 +729,12 @@ protected internal override Expression VisitJoin(JoinExpression join) this.Visit(join.Condition); this.Indent(Indentation.Outer); } + else if (isPostgres) + { + this.AppendNewLine(Indentation.Inner); + sb.Append("ON true"); + this.Indent(Indentation.Outer); + } return join; } @@ -764,92 +775,128 @@ void VisitSetPart(SourceWithAliasExpression source) protected internal override Expression VisitDelete(DeleteExpression delete) { - sb.Append("DELETE "); - sb.Append(delete.Name.ToString()); - this.AppendNewLine(Indentation.Same); - sb.Append("FROM "); - VisitSource(delete.Source); - if (delete.Where != null) + using (this.PrintSelectRowCount(delete.ReturnRowCount)) { + sb.Append("DELETE FROM "); + sb.Append(delete.Name.ToString()); this.AppendNewLine(Indentation.Same); - sb.Append("WHERE "); - Visit(delete.Where); + + if (isPostgres) + sb.Append("USING "); + else + sb.Append("FROM "); + + VisitSource(delete.Source); + if (delete.Where != null) + { + this.AppendNewLine(Indentation.Same); + sb.Append("WHERE "); + Visit(delete.Where); + } + return delete; } - return delete; } protected internal override Expression VisitUpdate(UpdateExpression update) { - sb.Append("UPDATE "); - sb.Append(update.Name.ToString()); - sb.Append(" SET"); - this.AppendNewLine(Indentation.Inner); - - for (int i = 0, n = update.Assigments.Count; i < n; i++) + using (this.PrintSelectRowCount(update.ReturnRowCount)) { - ColumnAssignment assignment = update.Assigments[i]; - if (i > 0) + sb.Append("UPDATE "); + sb.Append(update.Name.ToString()); + sb.Append(" SET"); + this.AppendNewLine(Indentation.Inner); + + for (int i = 0, n = update.Assigments.Count; i < n; i++) + { + ColumnAssignment assignment = update.Assigments[i]; + if (i > 0) + { + sb.Append(","); + this.AppendNewLine(Indentation.Same); + } + sb.Append(assignment.Column.SqlEscape(isPostgres)); + sb.Append(" = "); + this.Visit(assignment.Expression); + } + this.AppendNewLine(Indentation.Outer); + sb.Append("FROM "); + VisitSource(update.Source); + if (update.Where != null) { - sb.Append(","); this.AppendNewLine(Indentation.Same); + sb.Append("WHERE "); + Visit(update.Where); } - sb.Append(assignment.Column.SqlEscape(isPostgres)); - sb.Append(" = "); - this.Visit(assignment.Expression); + return update; } - this.AppendNewLine(Indentation.Outer); - sb.Append("FROM "); - VisitSource(update.Source); - if (update.Where != null) - { - this.AppendNewLine(Indentation.Same); - sb.Append("WHERE "); - Visit(update.Where); - } - return update; - } protected internal override Expression VisitInsertSelect(InsertSelectExpression insertSelect) { - sb.Append("INSERT INTO "); - sb.Append(insertSelect.Name.ToString()); - sb.Append("("); - for (int i = 0, n = insertSelect.Assigments.Count; i < n; i++) + using (this.PrintSelectRowCount(insertSelect.ReturnRowCount)) { - ColumnAssignment assignment = insertSelect.Assigments[i]; - if (i > 0) + sb.Append("INSERT INTO "); + sb.Append(insertSelect.Name.ToString()); + sb.Append("("); + for (int i = 0, n = insertSelect.Assigments.Count; i < n; i++) { - sb.Append(", "); - if (i % 4 == 0) - this.AppendNewLine(Indentation.Same); + ColumnAssignment assignment = insertSelect.Assigments[i]; + if (i > 0) + { + sb.Append(", "); + if (i % 4 == 0) + this.AppendNewLine(Indentation.Same); + } + sb.Append(assignment.Column.SqlEscape(isPostgres)); } - sb.Append(assignment.Column.SqlEscape(isPostgres)); - } - sb.Append(")"); - this.AppendNewLine(Indentation.Same); - sb.Append("SELECT "); - for (int i = 0, n = insertSelect.Assigments.Count; i < n; i++) - { - ColumnAssignment assignment = insertSelect.Assigments[i]; - if (i > 0) + sb.Append(")"); + this.AppendNewLine(Indentation.Same); + sb.Append("SELECT "); + for (int i = 0, n = insertSelect.Assigments.Count; i < n; i++) { - sb.Append(", "); - if (i % 4 == 0) - this.AppendNewLine(Indentation.Same); + ColumnAssignment assignment = insertSelect.Assigments[i]; + if (i > 0) + { + sb.Append(", "); + if (i % 4 == 0) + this.AppendNewLine(Indentation.Same); + } + this.Visit(assignment.Expression); } - this.Visit(assignment.Expression); - } - sb.Append(" FROM "); - VisitSource(insertSelect.Source); - return insertSelect; + sb.Append(" FROM "); + VisitSource(insertSelect.Source); + sb.Append(";"); + return insertSelect; + } } - protected internal override Expression VisitSelectRowCount(SelectRowCountExpression src) + protected internal IDisposable? PrintSelectRowCount(bool returnRowCount) { - sb.Append("SELECT @@rowcount"); - return src; + if (returnRowCount == false) + return null; + + if (!this.isPostgres) + { + return new Disposable(() => + { + sb.Append("SELECT @@rowcount"); + }); + } + else + { + sb.Append("WITH rows AS ("); + this.AppendNewLine(Indentation.Inner); + + return new Disposable(() => + { + sb.Append("RETURNING 1"); + this.AppendNewLine(Indentation.Outer); + sb.Append(")"); + this.AppendNewLine(Indentation.Same); + sb.Append("SELECT CAST(COUNT(*) AS INTEGER) FROM rows"); + }); + } } protected internal override Expression VisitCommandAggregate(CommandAggregateExpression cea) diff --git a/Signum.Engine/Linq/ExpressionVisitor/QueryRebinder.cs b/Signum.Engine/Linq/ExpressionVisitor/QueryRebinder.cs index 15909da986..7ba4d1f73d 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/QueryRebinder.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/QueryRebinder.cs @@ -148,7 +148,7 @@ protected internal override Expression VisitDelete(DeleteExpression delete) var where = Visit(delete.Where); if (source != delete.Source || where != delete.Where) - return new DeleteExpression(delete.Table, delete.UseHistoryTable, (SourceWithAliasExpression)source, where); + return new DeleteExpression(delete.Table, delete.UseHistoryTable, (SourceWithAliasExpression)source, where, delete.ReturnRowCount); return delete; } @@ -164,7 +164,7 @@ protected internal override Expression VisitUpdate(UpdateExpression update) var where = Visit(update.Where); var assigments = Visit(update.Assigments, VisitColumnAssigment); if (source != update.Source || where != update.Where || assigments != update.Assigments) - return new UpdateExpression(update.Table, update.UseHistoryTable, (SourceWithAliasExpression)source, where, assigments); + return new UpdateExpression(update.Table, update.UseHistoryTable, (SourceWithAliasExpression)source, where, assigments, update.ReturnRowCount); return update; } @@ -178,7 +178,7 @@ protected internal override Expression VisitInsertSelect(InsertSelectExpression var source = Visit(insertSelect.Source); var assigments = Visit(insertSelect.Assigments, VisitColumnAssigment); if (source != insertSelect.Source || assigments != insertSelect.Assigments) - return new InsertSelectExpression(insertSelect.Table, insertSelect.UseHistoryTable, (SourceWithAliasExpression)source, assigments); + return new InsertSelectExpression(insertSelect.Table, insertSelect.UseHistoryTable, (SourceWithAliasExpression)source, assigments, insertSelect.ReturnRowCount); return insertSelect; } diff --git a/Signum.Engine/Linq/ExpressionVisitor/SmartEqualizer.cs b/Signum.Engine/Linq/ExpressionVisitor/SmartEqualizer.cs index 1ecebea1ad..e20c807ba2 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/SmartEqualizer.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/SmartEqualizer.cs @@ -597,7 +597,12 @@ public static Expression InPrimaryKey(Expression element, PrimaryKey[] values) if (cleanElement == NewId) return False; - return InExpression.FromValues(DbExpressionNominator.FullNominate(cleanElement)!, cleanValues); + cleanElement = DbExpressionNominator.FullNominate(cleanElement)!; + + if (cleanElement.Type == typeof(string)) + return InExpression.FromValues(cleanElement, cleanValues.Select(a => (object)a.ToString()!).ToArray()); + else + return InExpression.FromValues(cleanElement, cleanValues); } private static Expression DispachConditionalTypesIn(ConditionalExpression ce, IEnumerable collection) diff --git a/Signum.Engine/Linq/ExpressionVisitor/UnusedColumnRemover.cs b/Signum.Engine/Linq/ExpressionVisitor/UnusedColumnRemover.cs index 570aa6d42c..eb8c59bea0 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/UnusedColumnRemover.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/UnusedColumnRemover.cs @@ -140,7 +140,7 @@ protected internal override Expression VisitDelete(DeleteExpression delete) var where = Visit(delete.Where); var source = Visit(delete.Source); if (source != delete.Source || where != delete.Where) - return new DeleteExpression(delete.Table, delete.UseHistoryTable, (SourceWithAliasExpression)source, where); + return new DeleteExpression(delete.Table, delete.UseHistoryTable, (SourceWithAliasExpression)source, where, delete.ReturnRowCount); return delete; } @@ -150,7 +150,7 @@ protected internal override Expression VisitUpdate(UpdateExpression update) var assigments = Visit(update.Assigments, VisitColumnAssigment); var source = Visit(update.Source); if (source != update.Source || where != update.Where || assigments != update.Assigments) - return new UpdateExpression(update.Table, update.UseHistoryTable, (SourceWithAliasExpression)source, where, assigments); + return new UpdateExpression(update.Table, update.UseHistoryTable, (SourceWithAliasExpression)source, where, assigments, update.ReturnRowCount); return update; } @@ -159,7 +159,7 @@ protected internal override Expression VisitInsertSelect(InsertSelectExpression var assigments = Visit(insertSelect.Assigments, VisitColumnAssigment); var source = Visit(insertSelect.Source); if (source != insertSelect.Source || assigments != insertSelect.Assigments) - return new InsertSelectExpression(insertSelect.Table, insertSelect.UseHistoryTable, (SourceWithAliasExpression)source, assigments); + return new InsertSelectExpression(insertSelect.Table, insertSelect.UseHistoryTable, (SourceWithAliasExpression)source, assigments, insertSelect.ReturnRowCount); return insertSelect; } diff --git a/Signum.Engine/Linq/ExpressionVisitor/UpdateDeleteSimplifier.cs b/Signum.Engine/Linq/ExpressionVisitor/UpdateDeleteSimplifier.cs index 277e9ade24..1c2b43fa47 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/UpdateDeleteSimplifier.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/UpdateDeleteSimplifier.cs @@ -3,7 +3,7 @@ namespace Signum.Engine.Linq { - class CommandSimplifier: DbExpressionVisitor + class CommandSimplifier : DbExpressionVisitor { bool removeSelectRowCount; AliasGenerator aliasGenerator; @@ -15,16 +15,11 @@ public CommandSimplifier(bool removeSelectRowCount, AliasGenerator aliasGenerato } public static CommandExpression Simplify(CommandExpression ce, bool removeSelectRowCount, AliasGenerator aliasGenerator) - { - return (CommandExpression)new CommandSimplifier(removeSelectRowCount, aliasGenerator).Visit(ce); - } - - protected internal override Expression VisitSelectRowCount(SelectRowCountExpression src) { if (removeSelectRowCount) - return null!; + ce = (CommandExpression)new SelectRowRemover().Visit(ce); - return base.VisitSelectRowCount(src); + return (CommandExpression)new CommandSimplifier(removeSelectRowCount, aliasGenerator).Visit(ce); } protected internal override Expression VisitDelete(DeleteExpression delete) @@ -39,7 +34,7 @@ protected internal override Expression VisitDelete(DeleteExpression delete) if (!TrivialWhere(delete, select)) return delete; - return new DeleteExpression(delete.Table, delete.UseHistoryTable, table, select.Where); + return new DeleteExpression(delete.Table, delete.UseHistoryTable, table, select.Where, delete.ReturnRowCount); } private bool TrivialWhere(DeleteExpression delete, SelectExpression select) @@ -86,7 +81,7 @@ private ColumnExpression ResolveColumn(ColumnExpression ce, SelectExpression sel var result = cd.Expression as ColumnExpression; - if(result == null) + if (result == null) return ce; TableExpression table = (TableExpression)select.From!; @@ -102,4 +97,31 @@ private ColumnExpression ResolveColumn(ColumnExpression ce, SelectExpression sel return ce; } } + + class SelectRowRemover : DbExpressionVisitor + { + protected internal override Expression VisitUpdate(UpdateExpression update) + { + if (update.ReturnRowCount == false) + return update; + + return new UpdateExpression(update.Table, update.UseHistoryTable, update.Source, update.Where, update.Assigments, returnRowCount: false); + } + + protected internal override Expression VisitInsertSelect(InsertSelectExpression insertSelect) + { + if (insertSelect.ReturnRowCount == false) + return insertSelect; + + return new InsertSelectExpression(insertSelect.Table, insertSelect.UseHistoryTable, insertSelect.Source, insertSelect.Assigments, returnRowCount: false); + } + + protected internal override Expression VisitDelete(DeleteExpression delete) + { + if (delete.ReturnRowCount == false) + return delete; + + return new DeleteExpression(delete.Table, delete.UseHistoryTable, delete.Source, delete.Where, returnRowCount: false); + } + } } diff --git a/Signum.Engine/Schema/Schema.Save.cs b/Signum.Engine/Schema/Schema.Save.cs index f93ff434e0..97610beb67 100644 --- a/Signum.Engine/Schema/Schema.Save.cs +++ b/Signum.Engine/Schema/Schema.Save.cs @@ -70,12 +70,12 @@ internal static string Var(bool isPostgres, string varName) return "@" + varName; } - internal static string DeclareTempTable(bool isPostgres, string variableName, string primaryKeyType) + internal static string DeclareTempTable(string variableName, FieldPrimaryKey id, bool isPostgres) { if (isPostgres) - return $"CREATE TEMP TABLE {variableName} (Id {primaryKeyType});"; + return $"CREATE TEMP TABLE {variableName} ({id.Name.SqlEscape(isPostgres)} {id.DbType.ToString(isPostgres)});"; else - return $"DECLARE {variableName} TABLE(Id {primaryKeyType});"; + return $"DECLARE {variableName} TABLE({id.Name.SqlEscape(isPostgres)} {id.DbType.ToString(isPostgres)});"; } ResetLazy inserterIdentity; @@ -462,11 +462,14 @@ public UpdateCache(Table table, Func sqlUpdatePattern, Act var isPostgres = Schema.Current.Settings.IsPostgres; var updated = Table.Var(isPostgres, "updated"); var id = this.table.PrimaryKey.Name.SqlEscape(isPostgres); - string sqlMulti = num == 1 ? SqlUpdatePattern("", true): + string sqlMulti = num == 1 ? SqlUpdatePattern("", true) : new StringBuilder() - .AppendLine(Table.DeclareTempTable(isPostgres, updated, this.table.PrimaryKey.DbType.ToString(isPostgres))) - .AppendLines(Enumerable.Range(0, num).Select(i => SqlUpdatePattern(i.ToString(), true))) - .AppendLine($"SELECT Id from {updated}").ToString(); + .AppendLine(Table.DeclareTempTable(updated, this.table.PrimaryKey, isPostgres)) + .AppendLine() + .AppendLines(Enumerable.Range(0, num).Select(i => SqlUpdatePattern(i.ToString(), true) + "\r\n")) + .AppendLine() + .AppendLine($"SELECT {id} from {updated}") + .ToString(); if (table.Ticks != null) { @@ -485,11 +488,13 @@ public UpdateCache(Table table, Func sqlUpdatePattern, Act DataTable dt = new SqlPreCommandSimple(sqlMulti, parameters).ExecuteDataTable(); - if (dt.Rows.Count == idents.Count) + if (dt.Rows.Count != idents.Count) { var updated = dt.Rows.Cast().Select(r => new PrimaryKey((IComparable)r[0])).ToList(); - throw new ConcurrencyException(table.Type, idents.Select(a => a.Id).Except(updated).ToArray()); + var missing = idents.Select(a => a.Id).Except(updated).ToArray(); + + throw new ConcurrencyException(table.Type, missing); } if (table.saveCollections.Value != null) @@ -571,7 +576,7 @@ internal static UpdateCache InitializeUpdate(Table table) {result.Indent(4)} ) INSERT INTO {updated}({id}) -SELECT {id} FROM rows"; +SELECT {id} FROM rows;"; }; List parameters = new List @@ -871,7 +876,7 @@ public MListDelete(Entity ident, PrimaryKey[] exceptRowIds) internal bool hasOrder = false; internal bool isEmbeddedEntity = false; internal Func sqlUpdate = null!; - public Func> UpdateParameters = null!; + public Action> UpdateParameters = null!; public ConcurrentDictionary>> updateCache = new ConcurrentDictionary>>(); @@ -890,7 +895,7 @@ Action> GetUpdate(int numElements) var row = pair.MList.InnerList[pair.Index]; - parameters.AddRange(UpdateParameters(pair.Entity, row.RowId!.Value, row.Element, pair.Index, pair.Forbidden, i.ToString())); + UpdateParameters(pair.Entity, row.RowId!.Value, row.Element, pair.Index, pair.Forbidden, i.ToString(), parameters); } new SqlPreCommandSimple(sql, parameters).ExecuteNonQuery(); }; @@ -1207,7 +1212,7 @@ TableMListCache CreateCache() parameters.Add(pb.ParameterFactory(Table.Trio.Concat(rowId, paramSuffix), this.PrimaryKey.DbType, null, false, Expression.Field(paramRowId, "Object"))); - var expr = Expression.Lambda>>( + var expr = Expression.Lambda>>( Table.CreateBlock(parameters, assigments, paramList), paramIdent, paramRowId, paramItem, paramOrder, paramForbidden, paramSuffix, paramList); result.UpdateParameters = expr.Compile(); } From 5008b990e8ca653ea595bb4e5b6e2e802c0667fd Mon Sep 17 00:00:00 2001 From: Olmo del Corral Date: Fri, 10 Jan 2020 08:17:11 +0100 Subject: [PATCH 04/13] initial postgre sync --- Signum.Engine/Administrator.cs | 52 +++-- Signum.Engine/BulkInserter.cs | 33 +-- .../CodeGeneration/EntityCodeGenerator.cs | 5 +- Signum.Engine/Connection/Connector.cs | 7 +- Signum.Engine/Connection/Executor.cs | 4 +- Signum.Engine/Connection/FieldReader.cs | 32 ++- .../Connection/PostgreSqlConnector.cs | 24 +- Signum.Engine/Connection/SqlConnector.cs | 14 +- Signum.Engine/Database.cs | 4 +- Signum.Engine/Engine/PostgresCatalog.cs | 206 ++++++++++++++++ Signum.Engine/Engine/PostgresCatalogSchema.cs | 219 ++++++++++++++++++ Signum.Engine/Engine/PostgresFunctions.cs | 36 +++ Signum.Engine/Engine/ProgressExtensions.cs | 28 +-- Signum.Engine/Engine/SchemaGenerator.cs | 12 +- Signum.Engine/Engine/SchemaSynchronizer.cs | 214 +++-------------- Signum.Engine/Engine/SqlBuilder.cs | 32 ++- Signum.Engine/Engine/SqlPreCommand.cs | 6 + .../Engine/{Sys.Tables.cs => SysTables.cs} | 7 - Signum.Engine/Engine/SysTablesSchema.cs | 182 +++++++++++++++ Signum.Engine/Linq/AliasGenerator.cs | 2 +- Signum.Engine/Linq/DbExpressions.Sql.cs | 108 ++++++--- Signum.Engine/Linq/DbQueryProvider.cs | 2 +- .../Linq/ExpressionVisitor/AggregateFinder.cs | 27 ++- .../Linq/ExpressionVisitor/ColumnProjector.cs | 4 +- .../ExpressionVisitor/ConditionsRewriter.cs | 9 +- .../ConditionsRewriterPostgres.cs | 30 +++ .../ExpressionVisitor/DbExpressionComparer.cs | 4 +- .../DbExpressionNominator.cs | 188 ++++++++++----- .../ExpressionVisitor/DbExpressionVisitor.cs | 10 +- .../Linq/ExpressionVisitor/OrderByRewriter.cs | 16 +- .../Linq/ExpressionVisitor/QueryBinder.cs | 151 ++++++++---- .../Linq/ExpressionVisitor/QueryFormatter.cs | 109 ++++++--- .../Linq/ExpressionVisitor/QueryRebinder.cs | 2 +- .../RedundantSubqueryRemover.cs | 63 ++++- .../Linq/ExpressionVisitor/SubqueryRemover.cs | 2 +- .../ExpressionVisitor/TranslatorBuilder.cs | 10 +- .../ExpressionVisitor/UnusedColumnRemover.cs | 6 +- Signum.Engine/Schema/ObjectName.cs | 25 +- Signum.Engine/Schema/Schema.Basics.cs | 41 ++-- Signum.Engine/Schema/Schema.Save.cs | 13 +- Signum.Engine/Schema/Schema.cs | 4 +- .../Schema/SchemaBuilder/SchemaBuilder.cs | 20 +- .../Schema/SchemaBuilder/SchemaSettings.cs | 13 +- Signum.Engine/Schema/UniqueTableIndex.cs | 29 ++- Signum.Entities/Entity.cs | 2 +- .../Reflection/StringHashEncoder.cs | 11 +- Signum.Test/Environment/Entities.cs | 4 +- Signum.Test/Environment/MusicStarter.cs | 21 +- Signum.Test/LinqProvider/SelectTest.cs | 7 +- Signum.Test/LinqProvider/SingleFirstTest.cs | 3 +- Signum.Test/LinqProvider/TakeSkipTest.cs | 12 +- Signum.Test/LinqProvider/ToStringTest.cs | 14 +- Signum.Test/ObjectNameTest.cs | 76 ++++-- .../ExpressionTrees/ExpressionCleaner.cs | 42 ++++ 54 files changed, 1617 insertions(+), 580 deletions(-) create mode 100644 Signum.Engine/Engine/PostgresCatalog.cs create mode 100644 Signum.Engine/Engine/PostgresCatalogSchema.cs create mode 100644 Signum.Engine/Engine/PostgresFunctions.cs rename Signum.Engine/Engine/{Sys.Tables.cs => SysTables.cs} (95%) create mode 100644 Signum.Engine/Engine/SysTablesSchema.cs create mode 100644 Signum.Engine/Linq/ExpressionVisitor/ConditionsRewriterPostgres.cs diff --git a/Signum.Engine/Administrator.cs b/Signum.Engine/Administrator.cs index c07b4b0a9a..52f7b71e1f 100644 --- a/Signum.Engine/Administrator.cs +++ b/Signum.Engine/Administrator.cs @@ -1,6 +1,8 @@ using Signum.Engine; +using Signum.Engine.Engine; using Signum.Engine.Linq; using Signum.Engine.Maps; +using Signum.Engine.PostgresCatalog; using Signum.Engine.SchemaInfoTables; using Signum.Entities; using Signum.Utilities; @@ -8,6 +10,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.Data.Common; using System.Linq; using System.Linq.Expressions; @@ -42,7 +45,7 @@ from c in t.Columns() select new DiffColumn { Name = c.name, - DbType = new AbstractDbType(SchemaSynchronizer.ToSqlDbType(c.Type()!.name)), + DbType = new AbstractDbType(SysTablesSchema.ToSqlDbType(c.Type()!.name)), UserTypeName = null, PrimaryKey = t.Indices().Any(i => i.is_primary_key && i.IndexColumns().Any(ic => ic.column_id == c.column_id)), Nullable = c.is_nullable, @@ -180,6 +183,13 @@ public static bool ExistsTable(Type type) public static bool ExistsTable(ITable table) { SchemaName schema = table.Name.Schema; + if (Schema.Current.Settings.IsPostgres) + { + return (from t in Database.View() + join ns in Database.View() on t.relnamespace equals ns.oid + where t.relname == table.Name.Name && ns.nspname == schema.Name + select t).Any(); + } if (schema.Database != null && schema.Database.Server != null && !Database.View().Any(ss => ss.name == schema.Database!.Server!.Name)) return false; @@ -217,6 +227,8 @@ public static List TryRetrieveAll(Type type, Replacements replacements) } } + + public static IDisposable DisableIdentity() where T : Entity { @@ -224,38 +236,37 @@ public static IDisposable DisableIdentity() return DisableIdentity(table); } - public static IDisposable DisableIdentity(Expression>> mListField) + public static IDisposable? DisableIdentity(Expression>> mListField) where T : Entity { TableMList table = ((FieldMList)Schema.Current.Field(mListField)).TableMList; - return DisableIdentity(table.Name); + return DisableIdentity(table); } - public static IDisposable DisableIdentity(Table table) + public static bool IsIdentityBehaviourDisabled(ITable table) + { + return identityBehaviourDisabled.Value?.Contains(table) == true; + } + + static ThreadVariable?> identityBehaviourDisabled = Statics.ThreadVariable?>("identityBehaviourOverride"); + public static IDisposable DisableIdentity(ITable table, bool behaviourOnly = false) { if (!table.IdentityBehaviour) throw new InvalidOperationException("Identity is false already"); - table.IdentityBehaviour = false; - if (table.PrimaryKey.Default == null) - Connector.Current.SqlBuilder.SetIdentityInsert(table.Name, true).ExecuteNonQuery(); - - return new Disposable(() => - { - table.IdentityBehaviour = true; - - if (table.PrimaryKey.Default == null) - Connector.Current.SqlBuilder.SetIdentityInsert(table.Name, false).ExecuteNonQuery(); - }); - } + var sqlBuilder = Connector.Current.SqlBuilder; + var oldValue = identityBehaviourDisabled.Value ?? ImmutableStack.Empty; - public static IDisposable DisableIdentity(ObjectName tableName) - { - Connector.Current.SqlBuilder.SetIdentityInsert(tableName, true).ExecuteNonQuery(); + identityBehaviourDisabled.Value = oldValue.Push(table); + if (table.PrimaryKey.Default == null && !sqlBuilder.IsPostgres && !behaviourOnly) + sqlBuilder.SetIdentityInsert(table.Name, true).ExecuteNonQuery(); return new Disposable(() => { - Connector.Current.SqlBuilder.SetIdentityInsert(tableName, false).ExecuteNonQuery(); + identityBehaviourDisabled.Value = oldValue.IsEmpty ? null : oldValue; + + if (table.PrimaryKey.Default == null && !sqlBuilder.IsPostgres && !behaviourOnly) + sqlBuilder.SetIdentityInsert(table.Name, false).ExecuteNonQuery(); }); } @@ -270,7 +281,6 @@ public static void SaveDisableIdentity(T entities) } } - public static void SaveListDisableIdentity(IEnumerable entities) where T : Entity { diff --git a/Signum.Engine/BulkInserter.cs b/Signum.Engine/BulkInserter.cs index ef5c1e50b5..bb4271ec61 100644 --- a/Signum.Engine/BulkInserter.cs +++ b/Signum.Engine/BulkInserter.cs @@ -28,7 +28,6 @@ public static int BulkInsert(this IEnumerable entities, using (HeavyProfiler.Log(nameof(BulkInsert), () => typeof(T).TypeName())) using (Transaction tr = new Transaction()) { - var table = Schema.Current.Table(typeof(T)); if (!disableIdentity && table.IdentityBehaviour && table.TablesMList().Any()) @@ -155,27 +154,28 @@ public static int BulkInsertTable(IEnumerable entities, var t = Schema.Current.Table(); bool disableIdentityBehaviour = copyOptions.HasFlag(SqlBulkCopyOptions.KeepIdentity); - bool oldIdentityBehaviour = t.IdentityBehaviour; DataTable dt = new DataTable(); - foreach (var c in t.Columns.Values.Where(c => !(c is SystemVersionedInfo.SqlServerPeriodColumn) && (disableIdentityBehaviour || !c.IdentityBehaviour))) + var columns = t.Columns.Values.Where(c => !(c is SystemVersionedInfo.SqlServerPeriodColumn) && (disableIdentityBehaviour || !c.IdentityBehaviour)).ToList(); + foreach (var c in columns) dt.Columns.Add(new DataColumn(c.Name, c.Type.UnNullify())); - if (disableIdentityBehaviour) t.IdentityBehaviour = false; - foreach (var e in list) + using (disableIdentityBehaviour ? Administrator.DisableIdentity(t, behaviourOnly: true) : null) { - if (!e.IsNew) - throw new InvalidOperationException("Entites should be new"); - t.SetToStrField(e); - dt.Rows.Add(t.BulkInsertDataRow(e)); + foreach (var e in list) + { + if (!e.IsNew) + throw new InvalidOperationException("Entites should be new"); + t.SetToStrField(e); + dt.Rows.Add(t.BulkInsertDataRow(e)); + } } - if (disableIdentityBehaviour) t.IdentityBehaviour = oldIdentityBehaviour; using (Transaction tr = new Transaction()) { Schema.Current.OnPreBulkInsert(typeof(T), inMListTable: false); - Executor.BulkCopy(dt, t.Name, copyOptions, timeout); + Executor.BulkCopy(dt, columns, t.Name, copyOptions, timeout); foreach (var item in list) item.SetNotModified(); @@ -274,8 +274,8 @@ public static int BulkInsertMListTable( var maxRowId = updateParentTicks.Value ? Database.MListQuery(mListProperty).Max(a => (PrimaryKey?)a.RowId) : null; DataTable dt = new DataTable(); - - foreach (var c in mlistTable.Columns.Values.Where(c => !(c is SystemVersionedInfo.SqlServerPeriodColumn) && !c.IdentityBehaviour)) + var columns = mlistTable.Columns.Values.Where(c => !(c is SystemVersionedInfo.SqlServerPeriodColumn) && !c.IdentityBehaviour).ToList(); + foreach (var c in columns) dt.Columns.Add(new DataColumn(c.Name, c.Type.UnNullify())); var list = mlistElements.ToList(); @@ -289,7 +289,7 @@ public static int BulkInsertMListTable( { Schema.Current.OnPreBulkInsert(typeof(E), inMListTable: true); - Executor.BulkCopy(dt, mlistTable.Name, copyOptions, timeout); + Executor.BulkCopy(dt, columns, mlistTable.Name, copyOptions, timeout); var result = list.Count; @@ -329,8 +329,9 @@ public static int BulkInsertView(this IEnumerable entities, bool disableIdentityBehaviour = copyOptions.HasFlag(SqlBulkCopyOptions.KeepIdentity); + var columns = t.Columns.Values.ToList(); DataTable dt = new DataTable(); - foreach (var c in t.Columns.Values) + foreach (var c in columns) dt.Columns.Add(new DataColumn(c.Name, c.Type.UnNullify())); foreach (var e in entities) @@ -342,7 +343,7 @@ public static int BulkInsertView(this IEnumerable entities, { Schema.Current.OnPreBulkInsert(typeof(T), inMListTable: false); - Executor.BulkCopy(dt, t.Name, copyOptions, timeout); + Executor.BulkCopy(dt, columns, t.Name, copyOptions, timeout); return tr.Commit(list.Count); } diff --git a/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs b/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs index a4851fadfe..a305e93a5e 100644 --- a/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs +++ b/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; +using Signum.Engine.Engine; using Signum.Engine.Maps; using Signum.Entities; using Signum.Entities.Reflection; @@ -70,7 +71,9 @@ protected virtual string GetProjectFolder() protected virtual List GetTables() { - return SchemaSynchronizer.DefaultGetDatabaseDescription(Schema.Current.DatabaseNames()).Values.ToList(); + return Schema.Current.Settings.IsPostgres ? + PostgresCatalogSchema.GetDatabaseDescription(Schema.Current.DatabaseNames()).Values.ToList() : + SysTablesSchema.GetDatabaseDescription(Schema.Current.DatabaseNames()).Values.ToList(); } protected virtual void GetSolutionInfo(out string solutionFolder, out string solutionName) diff --git a/Signum.Engine/Connection/Connector.cs b/Signum.Engine/Connection/Connector.cs index 4a610e3d0e..760cf7c8c3 100644 --- a/Signum.Engine/Connection/Connector.cs +++ b/Signum.Engine/Connection/Connector.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using System.Threading; using System.Data.SqlClient; +using System.Collections.Generic; namespace Signum.Engine { @@ -85,13 +86,15 @@ protected static void Log(SqlPreCommandSimple pcs) protected internal abstract DataTable ExecuteDataTable(SqlPreCommandSimple preCommand, CommandType commandType); protected internal abstract DbDataReaderWithCommand UnsafeExecuteDataReader(SqlPreCommandSimple preCommand, CommandType commandType); protected internal abstract Task UnsafeExecuteDataReaderAsync(SqlPreCommandSimple preCommand, CommandType commandType, CancellationToken token); - protected internal abstract void BulkCopy(DataTable dt, ObjectName destinationTable, SqlBulkCopyOptions options, int? timeout); + protected internal abstract void BulkCopy(DataTable dt, List columns, ObjectName destinationTable, SqlBulkCopyOptions options, int? timeout); + + public abstract Connector ForDatabase(Maps.DatabaseName? database); public abstract string DatabaseName(); public abstract string DataSourceName(); - public virtual int MaxNameLength { get { return 128; } } + public abstract int MaxNameLength { get; } public abstract void SaveTransactionPoint(DbTransaction transaction, string savePointName); diff --git a/Signum.Engine/Connection/Executor.cs b/Signum.Engine/Connection/Executor.cs index c4b70a6c7f..fd16b19204 100644 --- a/Signum.Engine/Connection/Executor.cs +++ b/Signum.Engine/Connection/Executor.cs @@ -69,9 +69,9 @@ public static void ExecuteLeaves(this SqlPreCommand preCommand, CommandType comm } } - public static void BulkCopy(DataTable dt, ObjectName destinationTable, SqlBulkCopyOptions options, int? timeout) + public static void BulkCopy(DataTable dt, List column, ObjectName destinationTable, SqlBulkCopyOptions options, int? timeout) { - Connector.Current.BulkCopy(dt, destinationTable, options, timeout); + Connector.Current.BulkCopy(dt, column, destinationTable, options, timeout); } } } diff --git a/Signum.Engine/Connection/FieldReader.cs b/Signum.Engine/Connection/FieldReader.cs index 096f890390..7686e1a599 100644 --- a/Signum.Engine/Connection/FieldReader.cs +++ b/Signum.Engine/Connection/FieldReader.cs @@ -14,6 +14,7 @@ using System.Data.SqlClient; using Microsoft.SqlServer.Server; using System.IO; +using Npgsql; namespace Signum.Engine { @@ -46,8 +47,11 @@ TypeCode GetTypeCode(int ordinal) return tc; } + bool isPostgres; + public FieldReader(DbDataReader reader) { + this.isPostgres = Schema.Current.Settings.IsPostgres; this.reader = reader; this.typeCodes = new TypeCode[reader.FieldCount]; @@ -457,6 +461,9 @@ public DateTimeOffset GetDateTimeOffset(int ordinal) switch (typeCodes[ordinal]) { case tcDateTimeOffset: + if (isPostgres) + throw new InvalidOperationException("DateTimeOffset not supported in Postgres"); + return ((SqlDataReader)reader).GetDateTimeOffset(ordinal); default: return ReflectionTools.ChangeType(reader.GetValue(ordinal)); @@ -480,7 +487,10 @@ public TimeSpan GetTimeSpan(int ordinal) switch (typeCodes[ordinal]) { case tcTimeSpan: - return ((SqlDataReader)reader).GetTimeSpan(ordinal); + if (isPostgres) + return ((NpgsqlDataReader)reader).GetTimeSpan(ordinal); + else + return ((SqlDataReader)reader).GetTimeSpan(ordinal); default: return ReflectionTools.ChangeType(reader.GetValue(ordinal)); } @@ -535,6 +545,19 @@ public T GetUdt(int ordinal) return udt; } + static MethodInfo miGetArray = ReflectionTools.GetMethodInfo((FieldReader r) => r.GetArray(0)).GetGenericMethodDefinition(); + + public T[] GetArray(int ordinal) + { + LastOrdinal = ordinal; + if (reader.IsDBNull(ordinal)) + { + return (T[])(object)null!; + } + + return (T[])this.reader[ordinal]; + } + static Dictionary methods = typeof(FieldReader).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) .Where(m => m.Name != "GetExpression" && m.Name != "IsNull") @@ -555,6 +578,11 @@ public static Expression GetExpression(Expression reader, int ordinal, Type type return Expression.Call(reader, miGetUdt.MakeGenericMethod(type.UnNullify()), Expression.Constant(ordinal)); } + if (type.IsArray) + { + return Expression.Call(reader, miGetArray.MakeGenericMethod(type.ElementType()!), Expression.Constant(ordinal)); + } + throw new InvalidOperationException("Type {0} not supported".FormatWith(type)); } @@ -576,7 +604,7 @@ internal FieldReaderException CreateFieldReaderException(Exception ex) } [Serializable] - public class FieldReaderException : SqlTypeException + public class FieldReaderException : DbException { public FieldReaderException(Exception inner, int ordinal, string columnName, Type columnType) : base(null, inner) { diff --git a/Signum.Engine/Connection/PostgreSqlConnector.cs b/Signum.Engine/Connection/PostgreSqlConnector.cs index c0dceded05..659b432c95 100644 --- a/Signum.Engine/Connection/PostgreSqlConnector.cs +++ b/Signum.Engine/Connection/PostgreSqlConnector.cs @@ -1,4 +1,5 @@ using Npgsql; +using NpgsqlTypes; using Signum.Engine.Connection; using Signum.Engine.Maps; using Signum.Utilities; @@ -56,6 +57,8 @@ public PostgreSqlConnector(string connectionString, Schema schema, Version postg this.PostgresVersion = postgresVersion; } + public override int MaxNameLength => 63; + public int? CommandTimeout { get; set; } = null; public string ConnectionString { get; set; } @@ -71,7 +74,7 @@ public PostgreSqlConnector(string connectionString, Schema schema, Version postg public override bool AllowsConvertToTime => true; - public override bool SupportsSqlDependency => true; + public override bool SupportsSqlDependency => false; public override bool SupportsFormat => true; @@ -81,6 +84,14 @@ public PostgreSqlConnector(string connectionString, Schema schema, Version postg public override bool AllowsIndexWithWhere(string where) => true; + public override Connector ForDatabase(Maps.DatabaseName? database) + { + if (database == null) + return this; + + throw new NotImplementedException("ForDatabase " + database); + } + public override void CleanDatabase(DatabaseName? database) { PostgreSqlConnectorScripts.RemoveAllScript(database).ExecuteNonQuery(); @@ -166,22 +177,25 @@ NpgsqlCommand NewCommand(SqlPreCommandSimple preCommand, NpgsqlConnection? overr return cmd; } - protected internal override void BulkCopy(DataTable dt, ObjectName destinationTable, SqlBulkCopyOptions options, int? timeout) + protected internal override void BulkCopy(DataTable dt, List columns, ObjectName destinationTable, SqlBulkCopyOptions options, int? timeout) { EnsureConnectionRetry(con => { con = con ?? (NpgsqlConnection)Transaction.CurrentConnection!; - using (var writer = con.BeginBinaryImport($"COPY {destinationTable} ({dt.Columns.Cast().ToString(a => a.ColumnName, ", ")}) FROM STDIN (FORMAT BINARY)")) + bool isPostgres = true; + + var columnsSql = dt.Columns.Cast().ToString(a => a.ColumnName.SqlEscape(isPostgres), ", "); + using (var writer = con.BeginBinaryImport($"COPY {destinationTable} ({columnsSql}) FROM STDIN (FORMAT BINARY)")) { for (int i = 0; i < dt.Rows.Count; i++) { var row = dt.Rows[i]; - + writer.StartRow(); for (int j = 0; j < dt.Columns.Count; j++) { var col = dt.Columns[j]; - writer.Write(row[col]); + writer.Write(row[col], columns[j].DbType.PostgreSql); } } diff --git a/Signum.Engine/Connection/SqlConnector.cs b/Signum.Engine/Connection/SqlConnector.cs index 8ecf9964cf..06486db20a 100644 --- a/Signum.Engine/Connection/SqlConnector.cs +++ b/Signum.Engine/Connection/SqlConnector.cs @@ -89,13 +89,6 @@ public SqlConnector(string connectionString, Schema schema, SqlServerVersion ver this.ParameterBuilder = new SqlParameterBuilder(); this.Version = version; - if (version >= SqlServerVersion.SqlServer2008 && schema != null) - { - var s = schema.Settings; - - if (!s.TypeValues.ContainsKey(typeof(TimeSpan))) - schema.Settings.TypeValues.Add(typeof(TimeSpan), new AbstractDbType(SqlDbType.Time)); - } } public int? CommandTimeout { get; set; } = null; @@ -375,7 +368,7 @@ Exception ReplaceException(Exception ex, SqlPreCommandSimple command) return ex; } - protected internal override void BulkCopy(DataTable dt, ObjectName destinationTable, SqlBulkCopyOptions options, int? timeout) + protected internal override void BulkCopy(DataTable dt, List columns, ObjectName destinationTable, SqlBulkCopyOptions options, int? timeout) { EnsureConnectionRetry(con => { @@ -446,7 +439,7 @@ public override bool AllowsMultipleQueries get { return true; } } - public SqlConnector ForDatabase(Maps.DatabaseName? database) + public override Connector ForDatabase(Maps.DatabaseName? database) { if (database == null) return this; @@ -519,7 +512,8 @@ public override bool SupportsTemporalTables get { return Version >= SqlServerVersion.SqlServer2016; } } - + public override int MaxNameLength => 128; + public override string ToString() => $"SqlConnector({Version})"; } diff --git a/Signum.Engine/Database.cs b/Signum.Engine/Database.cs index 9074e172b8..7c7aeed191 100644 --- a/Signum.Engine/Database.cs +++ b/Signum.Engine/Database.cs @@ -1467,7 +1467,7 @@ public static int UnsafeInsertDisableIdentity(this IQueryable query, strin using (Transaction tr = new Transaction()) { int result; - using (Administrator.DisableIdentity(Schema.Current.Table(typeof(E)).Name)) + using (Administrator.DisableIdentity(Schema.Current.Table(typeof(E)))) result = query.UnsafeInsert(a => a, message); return tr.Commit(result); } @@ -1479,7 +1479,7 @@ public static int UnsafeInsertDisableIdentity(this IQueryable query, Ex using (Transaction tr = new Transaction()) { int result; - using (Administrator.DisableIdentity(Schema.Current.Table(typeof(E)).Name)) + using (Administrator.DisableIdentity(Schema.Current.Table(typeof(E)))) result = query.UnsafeInsert(constructor, message); return tr.Commit(result); } diff --git a/Signum.Engine/Engine/PostgresCatalog.cs b/Signum.Engine/Engine/PostgresCatalog.cs new file mode 100644 index 0000000000..f232773753 --- /dev/null +++ b/Signum.Engine/Engine/PostgresCatalog.cs @@ -0,0 +1,206 @@ +using Signum.Entities; +using Signum.Utilities; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. +namespace Signum.Engine.PostgresCatalog +{ + [TableName("pg_catalog.pg_namespace")] + public class PgNamespace : IView + { + [ViewPrimaryKey] + public int oid; + + public string nspname; + + [AutoExpressionField] + public IQueryable Tables() => + As.Expression(() => Database.View().Where(t => t.relnamespace == this.oid && t.relkind == RelKind.Table)); + } + + [TableName("pg_catalog.pg_class")] + public class PgClass : IView + { + [ViewPrimaryKey] + public int oid; + + public string relname; + public int relnamespace; + public char relkind; + + + [AutoExpressionField] + public IQueryable Triggers() => + As.Expression(() => Database.View().Where(t => t.tgfoid == this.oid)); + + [AutoExpressionField] + public IQueryable Indices() => + As.Expression(() => Database.View().Where(t => t.indrelid == this.oid)); + + [AutoExpressionField] + public IQueryable Attributes() => + As.Expression(() => Database.View().Where(t => t.attrelid == this.oid)); + + [AutoExpressionField] + public IQueryable Constraints() => + As.Expression(() => Database.View().Where(t => t.conrelid == this.oid)); + + + [AutoExpressionField] + public PgNamespace Namespace() => + As.Expression(() => Database.View().SingleOrDefault(t => t.oid == this.relnamespace)); + } + + static class RelKind + { + public const char Table = 'r'; + public const char Index = 'i'; + public const char Sequence = 's'; + public const char Toast = 't'; + public const char View = 'v'; + public const char MaterializedView = 'n'; + public const char CompositeType = 'c'; + public const char ForeignKey = 'f'; + public const char PartitionTable = 'p'; + public const char PartitionIndex = 'I'; + } + + [TableName("pg_catalog.pg_attribute")] + public class PgAttribute : IView + { + [ViewPrimaryKey] + public int attrelid; + + [ViewPrimaryKey] + public string attname; + + public int atttypid; + public int atttypmod; + + public short attlen; + public short attnum; + public bool attnotnull; + public char attidentity; + + [AutoExpressionField] + public PgType Type() => As.Expression(() => Database.View().SingleOrDefault(t => t.oid == this.atttypid)); + + [AutoExpressionField] + public PgAttributeDef? Default() => As.Expression(() => Database.View().SingleOrDefault(t => t.oid == this.attrelid && t.adnum == this.attnum)); + } + + [TableName("pg_catalog.pg_attrdef")] + public class PgAttributeDef : IView + { + [ViewPrimaryKey] + public int oid; + + public int adrelid; + + public short adnum; + + public string /*not really*/ adbin; + } + + [TableName("pg_catalog.pg_type")] + public class PgType : IView + { + [ViewPrimaryKey] + public int oid; + + public string typname; + + public int typnamespace; + public short typlen; + public bool typbyval; + } + + [TableName("pg_catalog.pg_trigger")] + public class PgTrigger : IView + { + [ViewPrimaryKey] + public int oid; + + public int tgrelid; + public string tgname; + public int tgfoid; + public byte[] tgargs; + + [AutoExpressionField] + public PgProc Proc() => + As.Expression(() => Database.View().SingleOrDefault(p => p.oid == this.tgfoid)); + } + + [TableName("pg_catalog.pg_proc")] + public class PgProc : IView + { + [ViewPrimaryKey] + public int oid; + + public string proname; + } + + [TableName("pg_catalog.pg_index")] + public class PgIndex : IView + { + [ViewPrimaryKey] + public int indexrelid; + + public int indrelid; + + public bool indisunique; + + public bool indisprimary; + + public short[] indkey; + + public string? indexprs; + public string? indpred; + + [AutoExpressionField] + public PgClass Class() => + As.Expression(() => Database.View().Single(t => t.oid == this.indexrelid)); + } + + + [TableName("pg_catalog.pg_constraint")] + public class PgConstraint : IView + { + [ViewPrimaryKey] + public int oid; + + public string conname; + + public int connamespace; + + public char contype; + + public int conrelid; + + public short[] conkey; + + public int confrelid; + public short[] confkey; + + [AutoExpressionField] + public PgClass TargetTable() => + As.Expression(() => Database.View().Single(t => t.oid == this.confrelid)); + + [AutoExpressionField] + public PgNamespace Namespace() => + As.Expression(() => Database.View().SingleOrDefault(t => t.oid == this.connamespace)); + } + + public static class ConstraintType + { + public const char Check = 'c'; + public const char ForeignKey = 'f'; + public const char PrimaryKey = 'p'; + public const char Unique = 'u'; + public const char Trigger= 't'; + public const char Exclusion = 'x'; + } +} +#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. diff --git a/Signum.Engine/Engine/PostgresCatalogSchema.cs b/Signum.Engine/Engine/PostgresCatalogSchema.cs new file mode 100644 index 0000000000..ceb7a2467b --- /dev/null +++ b/Signum.Engine/Engine/PostgresCatalogSchema.cs @@ -0,0 +1,219 @@ +using Signum.Engine.Maps; +using Signum.Engine.PostgresCatalog; +using Signum.Utilities; +using System; +using System.Linq; +using System.Collections.Generic; +using System.Text; +using NpgsqlTypes; +using static Signum.Engine.PostgresCatalog.PostgresFunctions; + +namespace Signum.Engine.Engine +{ + public static class PostgresCatalogSchema + { + static string[] systemSchemas = new[] { "pg_catalog", "pg_toast", "information_schema" }; + + public static Dictionary GetDatabaseDescription(List databases) + { + List allTables = new List(); + + var isPostgres = Schema.Current.Settings.IsPostgres; + + foreach (var db in databases) + { + SafeConsole.WriteColor(ConsoleColor.Cyan, '.'); + + using (Administrator.OverrideDatabaseInSysViews(db)) + { + var databaseName = db == null ? Connector.Current.DatabaseName() : db.Name; + + //var sysDb = Database.View().Single(a => a.name == databaseName); + + var con = Connector.Current; + + var tables = + (from s in Database.View() + where !systemSchemas.Contains(s.nspname) + from t in s.Tables() + select new DiffTable + { + Name = new ObjectName(new SchemaName(db, s.nspname, isPostgres), t.relname, isPostgres), + + TemporalType = !con.SupportsTemporalTables ? Signum.Engine.SysTableTemporalType.None : t.Triggers().Any(t => t.Proc().proname.StartsWith("versioning_function")) ? Signum.Engine.SysTableTemporalType.SystemVersionTemporalTable : SysTableTemporalType.None, + + //Period = !con.SupportsTemporalTables ? null : + + //(from p in t.Periods() + // join sc in t.Columns() on p.start_column_id equals sc.column_id + // join ec in t.Columns() on p.end_column_id equals ec.column_id + // select new DiffPeriod + // { + // StartColumnName = sc.name, + // EndColumnName = ec.name, + // }).SingleOrDefaultEx(), + + //TemporalTableName = !con.SupportsTemporalTables || t.history_table_id == null ? null : + // Database.View() + // .Where(ht => ht.object_id == t.history_table_id) + // .Select(ht => new ObjectName(new SchemaName(db, ht.Schema().name, isPostgres), ht.name, isPostgres)) + // .SingleOrDefault(), + + PrimaryKeyName = (from ind in t.Indices() + where ind.indisprimary == true +#pragma warning disable CS0472 + select ((int?)ind.indexrelid) == null ? null : new ObjectName(new SchemaName(db, ind.Class().Namespace().nspname, isPostgres), ind.Class().relname, isPostgres)) +#pragma warning restore CS0472 + .SingleOrDefaultEx(), + + Columns = (from c in t.Attributes() + //join userType in Database.View().DefaultIfEmpty() on c.user_type_id equals userType.user_type_id + //join sysType in Database.View().DefaultIfEmpty() on c.system_type_id equals sysType.user_type_id + //join ctr in Database.View().DefaultIfEmpty() on c.default_object_id equals ctr.object_id + select new DiffColumn + { + Name = c.attname, + DbType = new AbstractDbType(ToNpgsqlDbType(c.Type().typname)), + UserTypeName = null, + Nullable = !c.attnotnull, + Collation = null, + Length = c.attlen, + Precision = c.atttypid == 1700 /*numeric*/ ? ((c.atttypmod - 4) >> 16) & 65535 : 0, + Scale = c.atttypid == 1700 /*numeric*/ ? (c.atttypmod - 4) & 65535 : 0, + Identity = c.attidentity == 'a', + GeneratedAlwaysType = GeneratedAlwaysType.None, + DefaultConstraint = c.Default() == null ? null : new DiffDefaultConstraint + { + Definition = pg_get_expr(c.Default()!.adbin, c.Default()!.adrelid), + }, + PrimaryKey = t.Indices().Any(i => i.indisprimary && i.indkey.Contains(c.attnum)), + }).ToDictionaryEx(a => a.Name, "columns"), + + MultiForeignKeys = (from fk in t.Constraints() + where fk.contype == ConstraintType.ForeignKey + select new DiffForeignKey + { + Name = new ObjectName(new SchemaName(db, fk.Namespace().nspname, isPostgres), fk.conname, isPostgres), + IsDisabled = false, + TargetTable = new ObjectName(new SchemaName(db, fk.TargetTable().Namespace().nspname, isPostgres), fk.TargetTable().relname, isPostgres), + Columns = PostgresFunctions.generate_subscripts(fk.conkey, 0).Select(i => new DiffForeignKeyColumn + { + Parent = t.Attributes().Single(c => c.attnum == fk.conkey[i]).attname, + Referenced = fk.TargetTable().Attributes().Single(c => c.attnum == fk.confkey[i]).attname, + }).ToList(), + }).ToList(), + + SimpleIndices = (from i in t.Indices() + where !i.indisprimary + select new DiffIndex + { + IsUnique = i.indisunique, + IsPrimary = i.indisprimary, + IndexName = i.Class().relname, + FilterDefinition = PostgresFunctions.pg_get_expr(i.indpred!, i.indrelid), + Type = DiffIndexType.NonClustered, + Columns = (from at in i.Class().Attributes() + orderby at.attnum + select new DiffIndexColumn { ColumnName = at.attname, IsIncluded = !i.indkey.Contains(at.attnum) }).ToList() + }).ToList(), + + ViewIndices = new List(), + + Stats = new List(), + + }).ToList(); + + + if (SchemaSynchronizer.IgnoreTable != null) + tables.RemoveAll(SchemaSynchronizer.IgnoreTable); + + tables.ForEach(t => t.Columns.RemoveAll(c => c.Value.DbType.PostgreSql == (NpgsqlDbType)(-1))); + + tables.ForEach(t => t.ForeignKeysToColumns()); + + allTables.AddRange(tables); + } + } + + var database = allTables.ToDictionary(t => t.Name.ToString()); + + return database; + } + + + public static NpgsqlDbType ToNpgsqlDbType(string str) + { + switch (str) + { + case "bool": return NpgsqlDbType.Boolean; + case "bytea": return NpgsqlDbType.Bytea; + case "char": return NpgsqlDbType.Char; + case "int8": return NpgsqlDbType.Bigint; + case "int2": return NpgsqlDbType.Smallint; + case "float4": return NpgsqlDbType.Real; + case "float8": return NpgsqlDbType.Double; + case "int2vector": return NpgsqlDbType.Smallint | NpgsqlDbType.Array; + case "int4": return NpgsqlDbType.Integer; + case "text": return NpgsqlDbType.Text; + case "json": return NpgsqlDbType.Json; + case "xml": return NpgsqlDbType.Xml; + case "point": return NpgsqlDbType.Point; + case "lseg": return NpgsqlDbType.LSeg; + case "path": return NpgsqlDbType.Path; + case "box": return NpgsqlDbType.Box; + case "polygon": return NpgsqlDbType.Polygon; + case "line": return NpgsqlDbType.Line; + case "circle": return NpgsqlDbType.Circle; + case "money": return NpgsqlDbType.Money; + case "macaddr": return NpgsqlDbType.MacAddr; + case "macaddr8": return NpgsqlDbType.MacAddr8; + case "inet": return NpgsqlDbType.Inet; + case "varchar": return NpgsqlDbType.Varchar; + case "date": return NpgsqlDbType.Date; + case "time": return NpgsqlDbType.Time; + case "timestamp": return NpgsqlDbType.Timestamp; + case "timestamptz": return NpgsqlDbType.TimestampTz; + case "interval": return NpgsqlDbType.Interval; + case "timetz": return NpgsqlDbType.TimestampTz; + case "bit": return NpgsqlDbType.Bit; + case "varbit": return NpgsqlDbType.Varbit; + case "numeric": return NpgsqlDbType.Numeric; + case "uuid": return NpgsqlDbType.Uuid; + case "tsvector": return NpgsqlDbType.TsVector; + case "tsquery": return NpgsqlDbType.TsQuery; + case "jsonb": return NpgsqlDbType.Jsonb; + case "int4range": return NpgsqlDbType.Range | NpgsqlDbType.Integer; + case "numrange": return NpgsqlDbType.Range | NpgsqlDbType.Numeric; + case "tsrange": return NpgsqlDbType.Range | NpgsqlDbType.Timestamp; + case "tstzrange": return NpgsqlDbType.Range | NpgsqlDbType.TimestampTz; + case "daterange": return NpgsqlDbType.Range | NpgsqlDbType.Date; + case "int8range": return NpgsqlDbType.Range | NpgsqlDbType.Bigint; + case "oid": + case "cid": + case "xid": + case "tid": + return (NpgsqlDbType)(-1); + default: + return (NpgsqlDbType)(-1); + } + + } + + public static HashSet GetSchemaNames(List list) + { + var sqlBuilder = Connector.Current.SqlBuilder; + var isPostgres = sqlBuilder.IsPostgres; + HashSet result = new HashSet(); + foreach (var db in list) + { + using (Administrator.OverrideDatabaseInSysViews(db)) + { + var schemaNames = Database.View().Select(s => s.nspname).ToList(); + + result.AddRange(schemaNames.Select(sn => new SchemaName(db, sn, isPostgres)).Where(a => !SchemaSynchronizer.IgnoreSchema(a))); + } + } + return result; + } + } +} diff --git a/Signum.Engine/Engine/PostgresFunctions.cs b/Signum.Engine/Engine/PostgresFunctions.cs new file mode 100644 index 0000000000..4c15b73b6d --- /dev/null +++ b/Signum.Engine/Engine/PostgresFunctions.cs @@ -0,0 +1,36 @@ +using Microsoft.SqlServer.Server; +using System; +using System.Linq; + +#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. +namespace Signum.Engine.PostgresCatalog +{ + public class PostgresFunctions + { + [SqlMethod(Name = "pg_catalog.string_to_array")] + public static string[] string_to_array(string str, string separator) => throw new NotImplementedException(); + + [SqlMethod(Name = "pg_catalog.encode")] + public static string encode(byte[] bytea, string format) => throw new NotImplementedException(); + + [SqlMethod(Name = "pg_catalog.pg_get_expr")] + public static string pg_get_expr(string adbin, int adrelid) => throw new NotImplementedException(); + + [SqlMethod(Name = "pg_catalog.unnest")] + public static IQueryable unnest(T[] array) => throw new NotImplementedException(); + + [SqlMethod(Name = "pg_catalog.generate_series")] + public static IQueryable generate_series(int start, int stopIncluded) => throw new NotImplementedException(); + + [SqlMethod(Name = "pg_catalog.generate_series")] + public static IQueryable generate_series(int start, int stopIncluded, int step) => throw new NotImplementedException(); + + [SqlMethod(Name = "pg_catalog.generate_subscripts")] + public static IQueryable generate_subscripts(Array array, int dimension) => throw new NotImplementedException(); + + [SqlMethod(Name = "pg_catalog.generate_subscripts")] + public static IQueryable generate_subscripts(Array array, int dimension, bool reverse) => throw new NotImplementedException(); + } + +} +#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. diff --git a/Signum.Engine/Engine/ProgressExtensions.cs b/Signum.Engine/Engine/ProgressExtensions.cs index 4deb7c1d57..2acc15e443 100644 --- a/Signum.Engine/Engine/ProgressExtensions.cs +++ b/Signum.Engine/Engine/ProgressExtensions.cs @@ -77,28 +77,15 @@ public static void ProgressForeach(this IEnumerable collection, Table table = Schema.Current.Table(disableIdentityFor); - if (!table.IdentityBehaviour) - throw new InvalidOperationException("Identity is false already"); - - table.IdentityBehaviour = false; - try + collection.ProgressForeachInternal(elementID, writer, parallelOptions, transactional, showProgress, action: item => { - collection.ProgressForeachInternal(elementID, writer, parallelOptions, transactional, showProgress, action: item => + using (Transaction tr = Transaction.ForceNew()) { - using (Transaction tr = Transaction.ForceNew()) - { - using (table.PrimaryKey.Default != null - ? null - : Administrator.DisableIdentity(table.Name)) - action!(item); - tr.Commit(); - } - }); - } - finally - { - table.IdentityBehaviour = true; - } + using (table.PrimaryKey.Default != null ? null : Administrator.DisableIdentity(table)) + action!(item); + tr.Commit(); + } + }); } } @@ -292,6 +279,5 @@ public static LogWriter GetConsoleWriter() public delegate void LogWriter(ConsoleColor color, string text, params object[] parameters); - } } diff --git a/Signum.Engine/Engine/SchemaGenerator.cs b/Signum.Engine/Engine/SchemaGenerator.cs index a754608e32..88c0fcec64 100644 --- a/Signum.Engine/Engine/SchemaGenerator.cs +++ b/Signum.Engine/Engine/SchemaGenerator.cs @@ -16,10 +16,12 @@ public static class SchemaGenerator var sqlBuilder = Connector.Current.SqlBuilder; var defaultSchema = SchemaName.Default(s.Settings.IsPostgres); - return s.GetDatabaseTables() + var schemas = s.GetDatabaseTables() .Select(a => a.Name.Schema) - .Where(sn => sn != defaultSchema && !s.IsExternalDatabase(sn.Database)) - .Distinct() + .Where(sn => !sn.Equals(defaultSchema) && !s.IsExternalDatabase(sn.Database)) + .Distinct(); + + return schemas .Select(sqlBuilder.CreateSchema) .Combine(Spacing.Simple); } @@ -60,12 +62,12 @@ select EnumEntity.GetEntities(enumType).Select((e, i) => t.InsertSqlSync(e, suff ).Combine(Spacing.Double)?.PlainSqlCommand(); } - public static SqlPreCommand? PostgreeExtensions() + public static SqlPreCommand? PostgresExtensions() { if (!Schema.Current.Settings.IsPostgres) return null; - return Schema.Current.PostgreeExtensions.Select(p => Connector.Current.SqlBuilder.CreateExtensionIfNotExist(p)).Combine(Spacing.Simple); + return Schema.Current.PostgresExtensions.Select(p => Connector.Current.SqlBuilder.CreateExtensionIfNotExist(p)).Combine(Spacing.Simple); } public static SqlPreCommand? PostgreeTemporalTableScript() diff --git a/Signum.Engine/Engine/SchemaSynchronizer.cs b/Signum.Engine/Engine/SchemaSynchronizer.cs index 6699d54a0e..44af5fb8e0 100644 --- a/Signum.Engine/Engine/SchemaSynchronizer.cs +++ b/Signum.Engine/Engine/SchemaSynchronizer.cs @@ -1,4 +1,5 @@ using NpgsqlTypes; +using Signum.Engine.Engine; using Signum.Engine.Linq; using Signum.Engine.Maps; using Signum.Engine.SchemaInfoTables; @@ -29,9 +30,14 @@ public static class SchemaSynchronizer var modelTablesHistory = modelTables.Values.Where(a => a.SystemVersioned != null).ToDictionaryEx(a => a.SystemVersioned!.TableName.ToString(), "history schema tables"); HashSet modelSchemas = modelTables.Values.Select(a => a.Name.Schema).Where(a => !sqlBuilder.SystemSchemas.Contains(a.Name)).ToHashSet(); - Dictionary databaseTables = DefaultGetDatabaseDescription(s.DatabaseNames()); + Dictionary databaseTables = Schema.Current.Settings.IsPostgres ? + PostgresCatalogSchema.GetDatabaseDescription(Schema.Current.DatabaseNames()) : + SysTablesSchema.GetDatabaseDescription(s.DatabaseNames()); + var databaseTablesHistory = databaseTables.Extract((key, val) => val.TemporalType == SysTableTemporalType.HistoryTable); - HashSet databaseSchemas = DefaultGetSchemas(s.DatabaseNames()); + HashSet databaseSchemas = Schema.Current.Settings.IsPostgres ? + PostgresCatalogSchema.GetSchemaNames(s.DatabaseNames()): + SysTablesSchema.GetSchemaNames(s.DatabaseNames()); SimplifyDiffTables?.Invoke(databaseTables); @@ -190,7 +196,7 @@ public static class SchemaSynchronizer createNew: null, removeOld: (cn, colDb) => colDb.ForeignKey != null ? sqlBuilder.AlterTableDropConstraint(dif.Name, colDb.ForeignKey.Name) : null, mergeBoth: (cn, colModel, colDb) => colDb.ForeignKey == null ? null : - colModel.ReferenceTable == null || colModel.AvoidForeignKey || !colModel.ReferenceTable.Name.Equals(ChangeName(colDb.ForeignKey.TargetTable)) || DifferentDatabase(tab.Name, colModel.ReferenceTable.Name) || colDb.DbType.SqlServer != colModel.DbType.SqlServer ? + colModel.ReferenceTable == null || colModel.AvoidForeignKey || !colModel.ReferenceTable.Name.Equals(ChangeName(colDb.ForeignKey.TargetTable)) || DifferentDatabase(tab.Name, colModel.ReferenceTable.Name) || !colDb.DbType.Equals(colModel.DbType) ? sqlBuilder.AlterTableDropConstraint(dif.Name, colDb.ForeignKey.Name) : null), dif.MultiForeignKeys.Select(fk => sqlBuilder.AlterTableDropConstraint(dif.Name, fk.Name)).Combine(Spacing.Simple)) @@ -224,7 +230,7 @@ public static class SchemaSynchronizer sqlBuilder.AlterTableDisableSystemVersioning(tab.Name).Do(a => a.GoAfter = true) : null; - var dropPeriod = (dif.Period != null && + var dropPeriod = (!sqlBuilder.IsPostgres && dif.Period != null && (tab.SystemVersioned == null || !dif.Period.PeriodEquals(tab.SystemVersioned)) ? sqlBuilder.AlterTableDropPeriod(tab) : null); @@ -245,7 +251,7 @@ public static class SchemaSynchronizer hasValueFalse: hasValueFalse)), removeOld: (cn, difCol) => SqlPreCommand.Combine(Spacing.Simple, - difCol.DefaultConstraint != null ? sqlBuilder.AlterTableDropConstraint(tab.Name, difCol.DefaultConstraint.Name) : null, + difCol.DefaultConstraint != null && difCol.DefaultConstraint.Name != null ? sqlBuilder.AlterTableDropConstraint(tab.Name, difCol.DefaultConstraint!.Name) : null, sqlBuilder.AlterTableDropColumn(tab, cn)), mergeBoth: (cn, tabCol, difCol) => @@ -261,12 +267,14 @@ public static class SchemaSynchronizer SqlPreCommand.Combine(Spacing.Simple, tabCol.PrimaryKey && !difCol.PrimaryKey && dif.PrimaryKeyName != null ? sqlBuilder.DropPrimaryKeyConstraint(tab.Name) : null, UpdateCompatible(sqlBuilder, replacements, tab, dif, tabCol, difCol), - tabCol.DbType.SqlServer == SqlDbType.NVarChar && difCol.DbType.SqlServer == SqlDbType.NChar ? sqlBuilder.UpdateTrim(tab, tabCol) : null), + (sqlBuilder.IsPostgres ? + tabCol.DbType.PostgreSql == NpgsqlDbType.Varchar && difCol.DbType.PostgreSql == NpgsqlDbType.Char : + tabCol.DbType.SqlServer == SqlDbType.NVarChar && difCol.DbType.SqlServer == SqlDbType.NChar)? sqlBuilder.UpdateTrim(tab, tabCol) : null), UpdateByFkChange(tn, difCol, tabCol, ChangeName), difCol.DefaultEquals(tabCol) ? null : SqlPreCommand.Combine(Spacing.Simple, - difCol.DefaultConstraint != null ? sqlBuilder.AlterTableDropConstraint(tab.Name, difCol.DefaultConstraint.Name) : null, + difCol.DefaultConstraint != null ? sqlBuilder.AlterTableDropDefaultConstaint(tab.Name, difCol) : null, tabCol.Default != null ? sqlBuilder.AlterTableAddDefaultConstraint(tab.Name, sqlBuilder.GetDefaultConstaint(tab, tabCol)!) : null) ); } @@ -277,7 +285,7 @@ public static class SchemaSynchronizer delayedUpdates.Add(update); delayedDrops.Add(SqlPreCommand.Combine(Spacing.Simple, - difCol.DefaultConstraint != null ? sqlBuilder.AlterTableDropConstraint(tab.Name, difCol.DefaultConstraint.Name) : null, + difCol.DefaultConstraint != null ? sqlBuilder.AlterTableDropDefaultConstaint(tab.Name, difCol) : null, drop )); @@ -299,7 +307,7 @@ public static class SchemaSynchronizer var columnsHistory = columns != null && disableEnableSystemVersioning ? ForHistoryTable(columns, tab).Replace(new Regex(" IDENTITY "), m => " ") : null;/*HACK*/ - var addPeriod = ((tab.SystemVersioned != null && + var addPeriod = ((!sqlBuilder.IsPostgres && tab.SystemVersioned != null && (dif.Period == null || !dif.Period.PeriodEquals(tab.SystemVersioned))) ? (SqlPreCommandSimple)sqlBuilder.AlterTableAddPeriod(tab) : null); @@ -372,7 +380,7 @@ public static class SchemaSynchronizer if (tabCol.ReferenceTable == null || tabCol.AvoidForeignKey || DifferentDatabase(tab.Name, tabCol.ReferenceTable.Name)) return null; - if (difCol.ForeignKey == null || !tabCol.ReferenceTable.Name.Equals(ChangeName(difCol.ForeignKey.TargetTable)) || difCol.DbType.SqlServer != tabCol.DbType.SqlServer) + if (difCol.ForeignKey == null || !tabCol.ReferenceTable.Name.Equals(ChangeName(difCol.ForeignKey.TargetTable)) || !difCol.DbType.Equals(tabCol.DbType)) return sqlBuilder.AlterTableAddConstraintForeignKey(tab, tabCol.Name, tabCol.ReferenceTable); var name = sqlBuilder.ForeignKeyName(tab.Name.Name, tabCol.Name); @@ -542,23 +550,6 @@ private static bool DifferentDatabase(ObjectName name, ObjectName name2) public static Func IgnoreSchema = s => s.Name.Contains("\\"); - private static HashSet DefaultGetSchemas(List list) - { - var sqlBuilder = Connector.Current.SqlBuilder; - var isPostgres = false; - HashSet result = new HashSet(); - foreach (var db in list) - { - using (Administrator.OverrideDatabaseInSysViews(db)) - { - var schemaNames = Database.View().Select(s => s.name).ToList().Except(sqlBuilder.SystemSchemas); - - result.AddRange(schemaNames.Select(sn => new SchemaName(db, sn, isPostgres)).Where(a => !IgnoreSchema(a))); - } - } - return result; - } - private static SqlPreCommand AlterTableAddColumnDefault(SqlBuilder sqlBuilder, ITable table, IColumn column, Replacements rep, string? forceDefaultValue, HashSet hasValueFalse) { if (column.Nullable == IsNullable.Yes || column.Identity || column.Default != null || column is ImplementationColumn) @@ -600,6 +591,8 @@ private static SqlPreCommand AlterTableAddColumnDefault(SqlBuilder sqlBuilder, I return SqlPreCommand.Combine(Spacing.Simple, sqlBuilder.AlterTableAddColumn(table, column, tempDefault), + sqlBuilder.IsPostgres ? + sqlBuilder.AlterTableAlterColumnDropDefault(table.Name, column.Name): sqlBuilder.AlterTableDropConstraint(table.Name, tempDefault.Name))!; } } @@ -737,158 +730,9 @@ private static Dictionary ApplyIndexAutoReplacements(DiffTabl public static Func? IgnoreTable = null; - public static Dictionary DefaultGetDatabaseDescription(List databases) - { - List allTables = new List(); - - var isPostgres = false; - - foreach (var db in databases) - { - SafeConsole.WriteColor(ConsoleColor.Cyan, '.'); + - using (Administrator.OverrideDatabaseInSysViews(db)) - { - var databaseName = db == null ? Connector.Current.DatabaseName() : db.Name; - var sysDb = Database.View().Single(a => a.name == databaseName); - - var con = Connector.Current; - - var tables = - (from s in Database.View() - from t in s.Tables().Where(t => !t.ExtendedProperties().Any(a => a.name == "microsoft_database_tools_support")) //IntelliSense bug - select new DiffTable - { - Name = new ObjectName(new SchemaName(db, s.name, isPostgres), t.name, isPostgres), - - TemporalType = !con.SupportsTemporalTables ? SysTableTemporalType.None: t.temporal_type, - - Period = !con.SupportsTemporalTables ? null : - (from p in t.Periods() - join sc in t.Columns() on p.start_column_id equals sc.column_id - join ec in t.Columns() on p.end_column_id equals ec.column_id -#pragma warning disable CS0472 - select (int?)p.object_id == null ? null : new DiffPeriod -#pragma warning restore CS0472 - { - StartColumnName = sc.name, - EndColumnName = ec.name, - }).SingleOrDefaultEx(), - - TemporalTableName = !con.SupportsTemporalTables || t.history_table_id == null ? null : - Database.View() - .Where(ht => ht.object_id == t.history_table_id) - .Select(ht => new ObjectName(new SchemaName(db, ht.Schema().name, isPostgres), ht.name, isPostgres)) - .SingleOrDefault(), - - PrimaryKeyName = (from k in t.KeyConstraints() - where k.type == "PK" - select k.name == null ? null : new ObjectName(new SchemaName(db, k.Schema().name, isPostgres), k.name, isPostgres)) - .SingleOrDefaultEx(), - - Columns = (from c in t.Columns() - join userType in Database.View().DefaultIfEmpty() on c.user_type_id equals userType.user_type_id - join sysType in Database.View().DefaultIfEmpty() on c.system_type_id equals sysType.user_type_id - join ctr in Database.View().DefaultIfEmpty() on c.default_object_id equals ctr.object_id - select new DiffColumn - { - Name = c.name, - DbType = new AbstractDbType(sysType == null ? SqlDbType.Udt : ToSqlDbType(sysType.name)), - UserTypeName = sysType == null ? userType.name : null, - Nullable = c.is_nullable, - Collation = c.collation_name == sysDb.collation_name ? null : c.collation_name, - Length = c.max_length, - Precision = c.precision, - Scale = c.scale, - Identity = c.is_identity, - GeneratedAlwaysType = con.SupportsTemporalTables ? c.generated_always_type : GeneratedAlwaysType.None, - DefaultConstraint = ctr.name == null ? null : new DiffDefaultConstraint - { - Name = ctr.name, - Definition = ctr.definition - }, - PrimaryKey = t.Indices().Any(i => i.is_primary_key && i.IndexColumns().Any(ic => ic.column_id == c.column_id)), - }).ToDictionaryEx(a => a.Name, "columns"), - - MultiForeignKeys = (from fk in t.ForeignKeys() - join rt in Database.View() on fk.referenced_object_id equals rt.object_id - select new DiffForeignKey - { - Name = new ObjectName(new SchemaName(db, fk.Schema().name, isPostgres), fk.name, isPostgres), - IsDisabled = fk.is_disabled, - TargetTable = new ObjectName(new SchemaName(db, rt.Schema().name, isPostgres), rt.name, isPostgres), - Columns = fk.ForeignKeyColumns().Select(fkc => new DiffForeignKeyColumn - { - Parent = t.Columns().Single(c => c.column_id == fkc.parent_column_id).name, - Referenced = rt.Columns().Single(c => c.column_id == fkc.referenced_column_id).name - }).ToList(), - }).ToList(), - - SimpleIndices = (from i in t.Indices() - where /*!i.is_primary_key && */i.type != 0 /*heap indexes*/ - select new DiffIndex - { - IsUnique = i.is_unique, - IsPrimary = i.is_primary_key, - IndexName = i.name, - FilterDefinition = i.filter_definition, - Type = (DiffIndexType)i.type, - Columns = (from ic in i.IndexColumns() - join c in t.Columns() on ic.column_id equals c.column_id - orderby ic.index_column_id - select new DiffIndexColumn { ColumnName = c.name, IsIncluded = ic.is_included_column }).ToList() - }).ToList(), - - ViewIndices = (from v in Database.View() - where v.name.StartsWith("VIX_" + t.name + "_") - from i in v.Indices() - select new DiffIndex - { - IsUnique = i.is_unique, - ViewName = v.name, - IndexName = i.name, - Columns = (from ic in i.IndexColumns() - join c in v.Columns() on ic.column_id equals c.column_id - orderby ic.index_column_id - select new DiffIndexColumn { ColumnName = c.name, IsIncluded = ic.is_included_column }).ToList() - - }).ToList(), - - Stats = (from st in t.Stats() - where st.user_created - select new DiffStats - { - StatsName = st.name, - Columns = (from ic in st.StatsColumns() - join c in t.Columns() on ic.column_id equals c.column_id - select c.name).ToList() - }).ToList(), - - }).ToList(); - - if (IgnoreTable != null) - tables.RemoveAll(IgnoreTable); - - tables.ForEach(t => t.ForeignKeysToColumns()); - - allTables.AddRange(tables); - } - } - - var database = allTables.ToDictionary(t => t.Name.ToString()); - - return database; - } - - - public static SqlDbType ToSqlDbType(string str) - { - if (str == "numeric") - return SqlDbType.Decimal; - - return str.ToEnum(true); - } static SqlPreCommand? SynchronizeEnumsScript(Replacements replacements) @@ -1006,7 +850,7 @@ private static Entity Clone(Entity current) public static SqlPreCommand? SnapshotIsolation(Replacements replacements) { - if (replacements.SchemaOnly) + if (replacements.SchemaOnly || Schema.Current.Settings.IsPostgres) return null; var list = Schema.Current.DatabaseNames().Select(a => a?.ToString()).ToList(); @@ -1103,6 +947,14 @@ public override string ToString() } } + + public enum SysTableTemporalType + { + None = 0, + HistoryTable = 1, + SystemVersionTemporalTable = 2 + } + public class DiffStats { public string StatsName; @@ -1122,7 +974,7 @@ public class DiffIndex public bool IsPrimary; public string IndexName; public string ViewName; - public string FilterDefinition; + public string? FilterDefinition; public DiffIndexType? Type; public List Columns; @@ -1212,7 +1064,7 @@ public enum GeneratedAlwaysType public class DiffDefaultConstraint { - public string Name; + public string? Name; public string Definition; } @@ -1314,7 +1166,7 @@ internal bool CompatibleTypes(IColumn tabCol) private bool CompatibleTypes_Postgres(NpgsqlDbType fromType, NpgsqlDbType toType) { - throw new NotImplementedException(); + return true; } private bool CompatibleTypes_SqlServer(SqlDbType fromType, SqlDbType toType) diff --git a/Signum.Engine/Engine/SqlBuilder.cs b/Signum.Engine/Engine/SqlBuilder.cs index 946334c422..c229d62b2d 100644 --- a/Signum.Engine/Engine/SqlBuilder.cs +++ b/Signum.Engine/Engine/SqlBuilder.cs @@ -4,6 +4,7 @@ using System.Data; using Signum.Utilities; using Signum.Engine.Maps; +using Signum.Entities.Reflection; namespace Signum.Engine { @@ -42,8 +43,8 @@ public SqlPreCommand CreateTableSql(ITable t) { var primaryKeyConstraint = t.PrimaryKey == null ? null : isPostgres ? - "CONSTRAINT {0} PRIMARY KEY ({1})".FormatWith(PrimaryClusteredIndex.GetPrimaryKeyName(t.Name), t.PrimaryKey.Name.SqlEscape(isPostgres)) : - "CONSTRAINT {0} PRIMARY KEY CLUSTERED ({1} ASC)".FormatWith(PrimaryClusteredIndex.GetPrimaryKeyName(t.Name), t.PrimaryKey.Name.SqlEscape(isPostgres)); + "CONSTRAINT {0} PRIMARY KEY ({1})".FormatWith(PrimaryClusteredIndex.GetPrimaryKeyName(t.Name).SqlEscape(isPostgres), t.PrimaryKey.Name.SqlEscape(isPostgres)) : + "CONSTRAINT {0} PRIMARY KEY CLUSTERED ({1} ASC)".FormatWith(PrimaryClusteredIndex.GetPrimaryKeyName(t.Name).SqlEscape(isPostgres), t.PrimaryKey.Name.SqlEscape(isPostgres)); var systemPeriod = t.SystemVersioned == null || IsPostgres ? null : Period(t.SystemVersioned); @@ -353,9 +354,9 @@ public int DuplicateCount(UniqueTableIndex uniqueIndex, Replacements rep) return (int)Executor.ExecuteScalar( $@"SELECT Count(*) FROM {oldTableName} -WHERE {oldPrimaryKey} NOT IN +WHERE {oldPrimaryKey.SqlEscape(IsPostgres)} NOT IN ( - SELECT MIN({oldPrimaryKey}) + SELECT MIN({oldPrimaryKey.SqlEscape(IsPostgres)}) FROM {oldTableName} {(!uniqueIndex.Where.HasText() ? "" : "WHERE " + uniqueIndex.Where.Replace(columnReplacement))} GROUP BY {oldColumns} @@ -427,8 +428,8 @@ internal SqlPreCommand UpdateTrim(ITable tab, IColumn tabCol) return new SqlPreCommandSimple("UPDATE {0} SET {1} = RTRIM({1});".FormatWith(tab.Name, tabCol.Name));; } - public SqlPreCommand AlterTableDropConstraint(ObjectName tableName, ObjectName constraintName) => - AlterTableDropConstraint(tableName, constraintName.Name); + public SqlPreCommand AlterTableDropConstraint(ObjectName tableName, ObjectName foreignKeyName) => + AlterTableDropConstraint(tableName, foreignKeyName.Name); public SqlPreCommand AlterTableDropConstraint(ObjectName tableName, string constraintName) { @@ -437,6 +438,21 @@ public SqlPreCommand AlterTableDropConstraint(ObjectName tableName, string const constraintName.SqlEscape(isPostgres))); } + public SqlPreCommand AlterTableDropDefaultConstaint(ObjectName tableName, DiffColumn column) + { + if (isPostgres) + return AlterTableAlterColumnDropDefault(tableName, column.Name); + else + return AlterTableDropConstraint(tableName, column.DefaultConstraint!.Name!); + } + + public SqlPreCommand AlterTableAlterColumnDropDefault(ObjectName tableName, string columnName) + { + return new SqlPreCommandSimple("ALTER TABLE {0} ALTER COLUMN {1} DROP DEFAULT;".FormatWith( + tableName, + columnName.SqlEscape(isPostgres))); + } + public SqlPreCommandSimple AlterTableAddDefaultConstraint(ObjectName tableName, DefaultConstraint constraint) { return new SqlPreCommandSimple($"ALTER TABLE {tableName} ADD CONSTRAINT {constraint.Name} DEFAULT {constraint.QuotedDefinition} FOR {constraint.ColumnName};"); @@ -462,7 +478,9 @@ public SqlPreCommandSimple AlterTableAddDefaultConstraint(ObjectName tableName, public string ForeignKeyName(string table, string fieldName) { - return "FK_{0}_{1}".FormatWith(table, fieldName).SqlEscape(isPostgres); + var result = "FK_{0}_{1}".FormatWith(table, fieldName); + + return StringHashEncoder.ChopHash(result, this.connector.MaxNameLength).SqlEscape(isPostgres); } public SqlPreCommand RenameForeignKey(ObjectName foreignKeyName, string newName) diff --git a/Signum.Engine/Engine/SqlPreCommand.cs b/Signum.Engine/Engine/SqlPreCommand.cs index 4e9b8b379b..e0455c4985 100644 --- a/Signum.Engine/Engine/SqlPreCommand.cs +++ b/Signum.Engine/Engine/SqlPreCommand.cs @@ -168,6 +168,9 @@ internal static string Encode(object? value) if (value is string s) return "\'" + s.Replace("'", "''") + "'"; + if (value is char c) + return "\'" + c.ToString().Replace("'", "''") + "'"; + if (value is Guid g) return "\'" + g.ToString() + "'"; @@ -194,6 +197,9 @@ internal static string Encode(object? value) if (value is byte[] bytes) return "0x" + BitConverter.ToString(bytes).Replace("-", ""); + if (value is IFormattable f) + return f.ToString(null, CultureInfo.InvariantCulture); + return value.ToString()!; } diff --git a/Signum.Engine/Engine/Sys.Tables.cs b/Signum.Engine/Engine/SysTables.cs similarity index 95% rename from Signum.Engine/Engine/Sys.Tables.cs rename to Signum.Engine/Engine/SysTables.cs index d1166e5b23..436f573c0e 100644 --- a/Signum.Engine/Engine/Sys.Tables.cs +++ b/Signum.Engine/Engine/SysTables.cs @@ -75,13 +75,6 @@ public IQueryable Tables() => As.Expression(() => Database.View().Where(t => t.schema_id == this.schema_id)); } - public enum SysTableTemporalType - { - None = 0, - HistoryTable = 1, - SystemVersionTemporalTable = 2 - } - [TableName("sys.periods")] public class SysPeriods : IView { diff --git a/Signum.Engine/Engine/SysTablesSchema.cs b/Signum.Engine/Engine/SysTablesSchema.cs new file mode 100644 index 0000000000..da5a20fed3 --- /dev/null +++ b/Signum.Engine/Engine/SysTablesSchema.cs @@ -0,0 +1,182 @@ +using Signum.Engine.Maps; +using Signum.Engine.SchemaInfoTables; +using Signum.Utilities; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; + +namespace Signum.Engine.Engine +{ + public static class SysTablesSchema + { + public static Dictionary GetDatabaseDescription(List databases) + { + List allTables = new List(); + + var isPostgres = false; + + foreach (var db in databases) + { + SafeConsole.WriteColor(ConsoleColor.Cyan, '.'); + + using (Administrator.OverrideDatabaseInSysViews(db)) + { + var databaseName = db == null ? Connector.Current.DatabaseName() : db.Name; + + var sysDb = Database.View().Single(a => a.name == databaseName); + + var con = Connector.Current; + + var tables = + (from s in Database.View() + from t in s.Tables().Where(t => !t.ExtendedProperties().Any(a => a.name == "microsoft_database_tools_support")) //IntelliSense bug + select new DiffTable + { + Name = new ObjectName(new SchemaName(db, s.name, isPostgres), t.name, isPostgres), + + TemporalType = !con.SupportsTemporalTables ? SysTableTemporalType.None : t.temporal_type, + + Period = !con.SupportsTemporalTables ? null : + (from p in t.Periods() + join sc in t.Columns() on p.start_column_id equals sc.column_id + join ec in t.Columns() on p.end_column_id equals ec.column_id + select new DiffPeriod + { + StartColumnName = sc.name, + EndColumnName = ec.name, + }).SingleOrDefaultEx(), + + TemporalTableName = !con.SupportsTemporalTables || t.history_table_id == null ? null : + Database.View() + .Where(ht => ht.object_id == t.history_table_id) + .Select(ht => new ObjectName(new SchemaName(db, ht.Schema().name, isPostgres), ht.name, isPostgres)) + .SingleOrDefault(), + + PrimaryKeyName = (from k in t.KeyConstraints() + where k.type == "PK" + select k.name == null ? null : new ObjectName(new SchemaName(db, k.Schema().name, isPostgres), k.name, isPostgres)) + .SingleOrDefaultEx(), + + Columns = (from c in t.Columns() + join userType in Database.View().DefaultIfEmpty() on c.user_type_id equals userType.user_type_id + join sysType in Database.View().DefaultIfEmpty() on c.system_type_id equals sysType.user_type_id + join ctr in Database.View().DefaultIfEmpty() on c.default_object_id equals ctr.object_id + select new DiffColumn + { + Name = c.name, + DbType = new AbstractDbType(sysType == null ? SqlDbType.Udt : ToSqlDbType(sysType.name)), + UserTypeName = sysType == null ? userType.name : null, + Nullable = c.is_nullable, + Collation = c.collation_name == sysDb.collation_name ? null : c.collation_name, + Length = c.max_length, + Precision = c.precision, + Scale = c.scale, + Identity = c.is_identity, + GeneratedAlwaysType = con.SupportsTemporalTables ? c.generated_always_type : GeneratedAlwaysType.None, + DefaultConstraint = ctr.name == null ? null : new DiffDefaultConstraint + { + Name = ctr.name, + Definition = ctr.definition + }, + PrimaryKey = t.Indices().Any(i => i.is_primary_key && i.IndexColumns().Any(ic => ic.column_id == c.column_id)), + }).ToDictionaryEx(a => a.Name, "columns"), + + MultiForeignKeys = (from fk in t.ForeignKeys() + join rt in Database.View() on fk.referenced_object_id equals rt.object_id + select new DiffForeignKey + { + Name = new ObjectName(new SchemaName(db, fk.Schema().name, isPostgres), fk.name, isPostgres), + IsDisabled = fk.is_disabled, + TargetTable = new ObjectName(new SchemaName(db, rt.Schema().name, isPostgres), rt.name, isPostgres), + Columns = fk.ForeignKeyColumns().Select(fkc => new DiffForeignKeyColumn + { + Parent = t.Columns().Single(c => c.column_id == fkc.parent_column_id).name, + Referenced = rt.Columns().Single(c => c.column_id == fkc.referenced_column_id).name + }).ToList(), + }).ToList(), + + SimpleIndices = (from i in t.Indices() + where /*!i.is_primary_key && */i.type != 0 /*heap indexes*/ + select new DiffIndex + { + IsUnique = i.is_unique, + IsPrimary = i.is_primary_key, + IndexName = i.name, + FilterDefinition = i.filter_definition, + Type = (DiffIndexType)i.type, + Columns = (from ic in i.IndexColumns() + join c in t.Columns() on ic.column_id equals c.column_id + orderby ic.index_column_id + select new DiffIndexColumn { ColumnName = c.name, IsIncluded = ic.is_included_column }).ToList() + }).ToList(), + + ViewIndices = (from v in Database.View() + where v.name.StartsWith("VIX_" + t.name + "_") + from i in v.Indices() + select new DiffIndex + { + IsUnique = i.is_unique, + ViewName = v.name, + IndexName = i.name, + Columns = (from ic in i.IndexColumns() + join c in v.Columns() on ic.column_id equals c.column_id + orderby ic.index_column_id + select new DiffIndexColumn { ColumnName = c.name, IsIncluded = ic.is_included_column }).ToList() + + }).ToList(), + + Stats = (from st in t.Stats() + where st.user_created + select new DiffStats + { + StatsName = st.name, + Columns = (from ic in st.StatsColumns() + join c in t.Columns() on ic.column_id equals c.column_id + select c.name).ToList() + }).ToList(), + + }).ToList(); + + if (SchemaSynchronizer.IgnoreTable != null) + tables.RemoveAll(SchemaSynchronizer.IgnoreTable); + + tables.ForEach(t => t.ForeignKeysToColumns()); + + allTables.AddRange(tables); + } + } + + var database = allTables.ToDictionary(t => t.Name.ToString()); + + return database; + } + + public static SqlDbType ToSqlDbType(string str) + { + if (str == "numeric") + return SqlDbType.Decimal; + + return str.ToEnum(true); + } + + + public static HashSet GetSchemaNames(List list) + { + var sqlBuilder = Connector.Current.SqlBuilder; + var isPostgres = false; + HashSet result = new HashSet(); + foreach (var db in list) + { + using (Administrator.OverrideDatabaseInSysViews(db)) + { + var schemaNames = Database.View().Select(s => s.name).ToList().Except(sqlBuilder.SystemSchemas); + + result.AddRange(schemaNames.Select(sn => new SchemaName(db, sn, isPostgres)).Where(a => !SchemaSynchronizer.IgnoreSchema(a))); + } + } + return result; + } + } +} diff --git a/Signum.Engine/Linq/AliasGenerator.cs b/Signum.Engine/Linq/AliasGenerator.cs index 43ac1918c5..f3d52d3852 100644 --- a/Signum.Engine/Linq/AliasGenerator.cs +++ b/Signum.Engine/Linq/AliasGenerator.cs @@ -48,7 +48,7 @@ public Alias NextTableAlias(string tableName) tableName.Any(a => a == '_') ? new string(tableName.SplitNoEmpty('_' ).Select(s => s[0]).ToArray()) : null; if (!abv.HasText()) - abv = tableName.TryStart(3)!; + abv = tableName.TryStart(3).ToLower(); else abv = abv.ToLower(); diff --git a/Signum.Engine/Linq/DbExpressions.Sql.cs b/Signum.Engine/Linq/DbExpressions.Sql.cs index 794c762366..bbf9c114c4 100644 --- a/Signum.Engine/Linq/DbExpressions.Sql.cs +++ b/Signum.Engine/Linq/DbExpressions.Sql.cs @@ -36,7 +36,7 @@ internal enum DbExpressionType SqlTableValuedFunction, SqlConstant, SqlVariable, - SqlEnum, + SqlLiteral, SqlCast, Case, RowNumber, @@ -132,20 +132,25 @@ public SourceWithAliasExpression(DbExpressionType nodeType, Alias alias) internal class SqlTableValuedFunctionExpression : SourceWithAliasExpression { - public readonly Table Table; + public readonly Table? ViewTable; + public readonly Type? SingleColumnType; public readonly ReadOnlyCollection Arguments; - public readonly string SqlFunction; + public readonly ObjectName SqlFunction; public override Alias[] KnownAliases { get { return new[] { Alias }; } } - public SqlTableValuedFunctionExpression(string sqlFunction, Table table, Alias alias, IEnumerable arguments) + public SqlTableValuedFunctionExpression(ObjectName sqlFunction, Table? viewTable, Type? singleColumnType, Alias alias, IEnumerable arguments) : base(DbExpressionType.SqlTableValuedFunction, alias) { + if ((viewTable == null) == (singleColumnType == null)) + throw new ArgumentException("Either viewTable or singleColumn should be set"); + this.SqlFunction = sqlFunction; - this.Table = table; + this.ViewTable = viewTable; + this.SingleColumnType = singleColumnType; this.Arguments = arguments.ToReadOnly(); } @@ -158,12 +163,20 @@ public override string ToString() internal ColumnExpression GetIdExpression() { - var expression = ((ITablePrivate)Table).GetPrimaryOrder(Alias); + if (ViewTable != null) + { - if (expression == null) - throw new InvalidOperationException("Impossible to determine Primary Key for {0}".FormatWith(Table.Name)); + var expression = ((ITablePrivate)ViewTable).GetPrimaryOrder(Alias); - return expression; + if (expression == null) + throw new InvalidOperationException("Impossible to determine Primary Key for {0}".FormatWith(ViewTable.Name)); + + return expression; + } + else + { + return new ColumnExpression(this.SingleColumnType!, Alias, null); + } } protected override Expression Accept(DbExpressionVisitor visitor) @@ -221,16 +234,16 @@ protected override Expression Accept(DbExpressionVisitor visitor) internal class ColumnExpression : DbExpression, IEquatable { public readonly Alias Alias; - public readonly string Name; + public readonly string? Name; - internal ColumnExpression(Type type, Alias alias, string name) + internal ColumnExpression(Type type, Alias alias, string? name) : base(DbExpressionType.Column, type) { if (type.UnNullify() == typeof(PrimaryKey)) throw new ArgumentException("type should not be PrimaryKey"); this.Alias = alias ?? throw new ArgumentNullException(nameof(alias)); - this.Name = name ?? throw new ArgumentNullException(nameof(name)); + this.Name = name ?? (Schema.Current.Settings.IsPostgres ? (string?)null : throw new ArgumentNullException(nameof(name))); } public override string ToString() @@ -246,7 +259,7 @@ public bool Equals(ColumnExpression other) public override int GetHashCode() { - return Alias.GetHashCode() ^ Name.GetHashCode(); + return Alias.GetHashCode() ^ (Name?.GetHashCode() ?? -1); } protected override Expression Accept(DbExpressionVisitor visitor) @@ -285,33 +298,54 @@ internal enum AggregateSqlFunction StdDev, StdDevP, Count, + CountDistinct, Min, Max, Sum, + + string_agg, + } + + static class AggregateSqlFunctionExtensions + { + public static bool OrderMatters(this AggregateSqlFunction aggregateFunction) + { + switch (aggregateFunction) + { + case AggregateSqlFunction.Average: + case AggregateSqlFunction.StdDev: + case AggregateSqlFunction.StdDevP: + case AggregateSqlFunction.Count: + case AggregateSqlFunction.CountDistinct: + case AggregateSqlFunction.Min: + case AggregateSqlFunction.Max: + case AggregateSqlFunction.Sum: + return false; + case AggregateSqlFunction.string_agg: + return true; + default: + throw new UnexpectedValueException(aggregateFunction); + } + } } internal class AggregateExpression : DbExpression { - public readonly Expression Expression; - public readonly bool Distinct; public readonly AggregateSqlFunction AggregateFunction; - public AggregateExpression(Type type, Expression expression, AggregateSqlFunction aggregateFunction, bool distinct) + public readonly ReadOnlyCollection Arguments; + public AggregateExpression(Type type, AggregateSqlFunction aggregateFunction, IEnumerable arguments) : base(DbExpressionType.Aggregate, type) { - if (aggregateFunction != AggregateSqlFunction.Count && expression == null ) - throw new ArgumentNullException(nameof(expression)); - - if (distinct && (aggregateFunction != AggregateSqlFunction.Count || expression == null)) - throw new ArgumentException("Distinct only allowed for Count with expression"); - - this.Distinct = distinct; - this.Expression = expression; + if (arguments == null) + throw new ArgumentNullException(nameof(arguments)); + this.AggregateFunction = aggregateFunction; + this.Arguments = arguments.ToReadOnly(); } public override string ToString() { - return $"{AggregateFunction}({(Distinct ? "Distinct " : "")}{Expression?.ToString() ?? "*"})"; + return $"{AggregateFunction}({(AggregateFunction == AggregateSqlFunction.CountDistinct ? "Distinct " : "")}{Arguments?.ToString(", ") ?? "*"})"; } protected override Expression Accept(DbExpressionVisitor visitor) @@ -420,7 +454,7 @@ internal SelectRoles SelectRoles if (GroupBy.Count > 0) roles |= SelectRoles.GroupBy; - else if (Columns.Any(cd => AggregateFinder.HasAggregates(cd.Expression))) + else if (AggregateFinder.GetAggregates(Columns) != null) roles |= SelectRoles.Aggregate; if (OrderBy.Count > 0) @@ -439,7 +473,7 @@ internal SelectRoles SelectRoles } } - public bool IsAllAggregates => Columns.Any() && Columns.All(a => a.Expression is AggregateExpression); + public bool IsAllAggregates => Columns.Any() && Columns.All(a => a.Expression is AggregateExpression ag && !ag.AggregateFunction.OrderMatters()); public override string ToString() { @@ -601,7 +635,6 @@ internal enum SqlFunction COALESCE, CONVERT, - ISNULL, STUFF, } @@ -611,6 +644,9 @@ internal enum PostgresFunction starts_with, length, EXTRACT, + trunc, + substr, + repeat, } internal enum SqlEnums @@ -627,16 +663,16 @@ internal enum SqlEnums second, millisecond, dayofyear, //SQL Server - doy //Postgres + doy, //Postgres + epoch } - - - internal class SqlEnumExpression : DbExpression + internal class SqlLiteralExpression : DbExpression { - public readonly SqlEnums Value; - public SqlEnumExpression(SqlEnums value) - : base(DbExpressionType.SqlEnum, typeof(object)) + public readonly string Value; + public SqlLiteralExpression(SqlEnums value) : this(typeof(object), value.ToString()) { } + public SqlLiteralExpression(Type type, string value) + : base(DbExpressionType.SqlLiteral, type) { this.Value = value; } @@ -648,7 +684,7 @@ public override string ToString() protected override Expression Accept(DbExpressionVisitor visitor) { - return visitor.VisitSqlEnum(this); + return visitor.VisitSqlLiteral(this); } } diff --git a/Signum.Engine/Linq/DbQueryProvider.cs b/Signum.Engine/Linq/DbQueryProvider.cs index 03fe66dbdf..d0eb322475 100644 --- a/Signum.Engine/Linq/DbQueryProvider.cs +++ b/Signum.Engine/Linq/DbQueryProvider.cs @@ -100,7 +100,7 @@ internal static Expression Optimize(Expression binded, QueryBinder binder, Alias log.Switch("Redundant"); Expression subqueryCleaned = RedundantSubqueryRemover.Remove(columnCleaned); log.Switch("Condition"); - Expression rewriteConditions = isPostgres ? subqueryCleaned : ConditionsRewriter.Rewrite(subqueryCleaned); + Expression rewriteConditions = isPostgres ? ConditionsRewriterPostgres.Rewrite(subqueryCleaned) : ConditionsRewriter.Rewrite(subqueryCleaned); log.Switch("Scalar"); Expression scalar = ScalarSubqueryRewriter.Rewrite(rewriteConditions); return scalar; diff --git a/Signum.Engine/Linq/ExpressionVisitor/AggregateFinder.cs b/Signum.Engine/Linq/ExpressionVisitor/AggregateFinder.cs index c07411711d..d63658dea8 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/AggregateFinder.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/AggregateFinder.cs @@ -1,4 +1,7 @@ -using System.Linq.Expressions; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq.Expressions; namespace Signum.Engine.Linq { @@ -7,21 +10,29 @@ namespace Signum.Engine.Linq /// internal class AggregateFinder : DbExpressionVisitor { - bool hasAggregates = false; + List? aggregates; private AggregateFinder() { } - public static bool HasAggregates(Expression source) + protected internal override Expression VisitAggregate(AggregateExpression aggregate) + { + if (aggregates == null) + aggregates = new List(); + + aggregates.Add(aggregate); + return base.VisitAggregate(aggregate); + } + + public static List? GetAggregates(ReadOnlyCollection columns) { AggregateFinder ap = new AggregateFinder(); - ap.Visit(source); - return ap.hasAggregates; + Visit(columns, ap.VisitColumnDeclaration); + return ap.aggregates; } - protected internal override Expression VisitAggregate(AggregateExpression aggregate) + protected internal override Expression VisitScalar(ScalarExpression scalar) { - hasAggregates = true; - return base.VisitAggregate(aggregate); + return scalar; } } } diff --git a/Signum.Engine/Linq/ExpressionVisitor/ColumnProjector.cs b/Signum.Engine/Linq/ExpressionVisitor/ColumnProjector.cs index 6e5ba0f576..e2a8b494a1 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/ColumnProjector.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/ColumnProjector.cs @@ -129,7 +129,7 @@ public override Expression Visit(Expression expression) return mapped; } - mapped = request.AddIndependentColumn(column.Type, column.Name, implementation, column); + mapped = request.AddIndependentColumn(column.Type, column.Name!, implementation, column); this.map[column] = mapped; return mapped; } @@ -188,7 +188,7 @@ public string GetNextColumnName() public ColumnDeclaration MapColumn(ColumnExpression ce) { - string columnName = GetUniqueColumnName(ce.Name); + string columnName = GetUniqueColumnName(ce.Name!); var result = new ColumnDeclaration(columnName, ce); columns.Add(result.Name, result); return result; diff --git a/Signum.Engine/Linq/ExpressionVisitor/ConditionsRewriter.cs b/Signum.Engine/Linq/ExpressionVisitor/ConditionsRewriter.cs index d017d1a9df..e1e4a2d104 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/ConditionsRewriter.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/ConditionsRewriter.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using Signum.Utilities.ExpressionTrees; using System.Data.SqlTypes; +using System.Linq; namespace Signum.Engine.Linq { @@ -283,7 +284,7 @@ protected internal override Expression VisitSqlTableValuedFunction(SqlTableValue { ReadOnlyCollection args = Visit(sqlFunction.Arguments, a => MakeSqlValue(Visit(a))); if (args != sqlFunction.Arguments) - return new SqlTableValuedFunctionExpression(sqlFunction.SqlFunction, sqlFunction.Table, sqlFunction.Alias, args); + return new SqlTableValuedFunctionExpression(sqlFunction.SqlFunction, sqlFunction.ViewTable, sqlFunction.SingleColumnType, sqlFunction.Alias, args); return sqlFunction; } @@ -308,9 +309,9 @@ protected internal override When VisitWhen(When when) protected internal override Expression VisitAggregate(AggregateExpression aggregate) { - Expression source = MakeSqlValue(Visit(aggregate.Expression)); - if (source != aggregate.Expression) - return new AggregateExpression(aggregate.Type, source, aggregate.AggregateFunction, aggregate.Distinct); + var arguments = Visit(aggregate.Arguments).Select(a => MakeSqlValue(a)).ToReadOnly(); + if (arguments != aggregate.Arguments) + return new AggregateExpression(aggregate.Type, aggregate.AggregateFunction, arguments); return aggregate; } diff --git a/Signum.Engine/Linq/ExpressionVisitor/ConditionsRewriterPostgres.cs b/Signum.Engine/Linq/ExpressionVisitor/ConditionsRewriterPostgres.cs new file mode 100644 index 0000000000..1c9ad09a0b --- /dev/null +++ b/Signum.Engine/Linq/ExpressionVisitor/ConditionsRewriterPostgres.cs @@ -0,0 +1,30 @@ +using System; +using System.Linq.Expressions; +using Signum.Utilities; +using System.Collections.ObjectModel; +using Signum.Utilities.ExpressionTrees; +using System.Data.SqlTypes; + +namespace Signum.Engine.Linq +{ + internal class ConditionsRewriterPostgres: DbExpressionVisitor + { + public static Expression Rewrite(Expression expression) + { + return new ConditionsRewriterPostgres().Visit(expression); + } + + protected internal override Expression VisitSqlCast(SqlCastExpression castExpr) + { + var expression = Visit(castExpr.Expression); + + if(expression.Type.UnNullify() == typeof(bool) && castExpr.Type.UnNullify() != typeof(int)) + return new SqlCastExpression(castExpr.Type, new SqlCastExpression(typeof(int), expression), castExpr.DbType); + + if (expression != castExpr.Expression) + return new SqlCastExpression(castExpr.Type, expression, castExpr.DbType); + + return castExpr; + } + } +} diff --git a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionComparer.cs b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionComparer.cs index 0ab872de3a..0f9935a7f3 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionComparer.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionComparer.cs @@ -291,7 +291,7 @@ private bool CompareChildProjection(ChildProjectionExpression a, ChildProjection protected virtual bool CompareAggregate(AggregateExpression a, AggregateExpression b) { - return a.AggregateFunction == b.AggregateFunction && Compare(a.Expression, b.Expression); + return a.AggregateFunction == b.AggregateFunction && CompareList(a.Arguments, b.Arguments, Compare); } protected virtual bool CompareAggregateSubquery(AggregateRequestsExpression a, AggregateRequestsExpression b) @@ -316,7 +316,7 @@ protected virtual bool CompareSqlFunction(SqlFunctionExpression a, SqlFunctionEx private bool CompareTableValuedSqlFunction(SqlTableValuedFunctionExpression a, SqlTableValuedFunctionExpression b) { - return a.Table == b.Table + return a.ViewTable == b.ViewTable && CompareAlias(a.Alias, b.Alias) && CompareList(a.Arguments, b.Arguments, Compare); } diff --git a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs index 8126c50427..a80f08d6d5 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs @@ -292,7 +292,7 @@ protected internal override Expression VisitSqlTableValuedFunction(SqlTableValue { ReadOnlyCollection args = Visit(sqlFunction.Arguments, a => Visit(a)!); if (args != sqlFunction.Arguments) - sqlFunction = new SqlTableValuedFunctionExpression(sqlFunction.SqlFunction, sqlFunction.Table, sqlFunction.Alias, args); ; + sqlFunction = new SqlTableValuedFunctionExpression(sqlFunction.SqlFunction, sqlFunction.ViewTable, sqlFunction.SingleColumnType, sqlFunction.Alias, args); ; if (args.All(Has)) return Add(sqlFunction); @@ -426,7 +426,7 @@ protected Expression GetFormatToString(MethodCallExpression m, string? defaultFo return Add(new SqlFunctionExpression(type, newObj, sqlFunction, newExpressions)); } - private SqlFunctionExpression? TrySqlDifference(SqlEnums sqlEnums, Type type, Expression expression) + private Expression? TrySqlDifference(SqlEnums sqlEnums, Type type, Expression expression) { if (innerProjection) return null; @@ -442,7 +442,7 @@ protected Expression GetFormatToString(MethodCallExpression m, string? defaultFo return null; } - private SqlFunctionExpression? TrySqlDifference(SqlEnums sqlEnums, Type type, Expression leftSide, Expression rightSide) + private Expression? TrySqlDifference(SqlEnums sqlEnums, Type type, Expression leftSide, Expression rightSide) { Expression left = Visit(leftSide); if (!Has(left.RemoveNullify())) @@ -452,10 +452,40 @@ protected Expression GetFormatToString(MethodCallExpression m, string? defaultFo if (!Has(right.RemoveNullify())) return null; - SqlFunctionExpression result = new SqlFunctionExpression(type, null, SqlFunction.DATEDIFF.ToString(), new Expression[]{ - new SqlEnumExpression(sqlEnums), right, left}); + if (isPostgres) + { + var secondsDouble = new SqlFunctionExpression(typeof(double), null, PostgresFunction.EXTRACT.ToString(), new Expression[] + { + new SqlLiteralExpression(SqlEnums.epoch), + Expression.Subtract(left, right), + }); - return Add(result); + if (sqlEnums == SqlEnums.second) + return Add(secondsDouble); + + + if (sqlEnums == SqlEnums.millisecond) + return Add(Expression.Multiply(secondsDouble, new SqlConstantExpression(1000.0))); + + double scale = sqlEnums switch + { + SqlEnums.minute => 60, + SqlEnums.hour => 60 * 60, + SqlEnums.day => 60 * 60 * 24, + _ => throw new UnexpectedValueException(sqlEnums), + }; + + return Add(Expression.Divide(secondsDouble, new SqlConstantExpression(scale))); + } + else + { + return Add(new SqlFunctionExpression(type, null, SqlFunction.DATEDIFF.ToString(), new Expression[] + { + new SqlLiteralExpression(sqlEnums), + right, + left + })); + } } private Expression? TrySqlDate(Expression expression) @@ -464,18 +494,27 @@ protected Expression GetFormatToString(MethodCallExpression m, string? defaultFo if (innerProjection || !Has(expr)) return null; - if (Connector.Current.AllowsConvertToDate) - return Add(new SqlFunctionExpression(typeof(DateTime), null, SqlFunction.CONVERT.ToString(), new[] + if (isPostgres) + { + return Add(new SqlCastExpression(typeof(DateTime), expr, new AbstractDbType(NpgsqlDbType.Date))); + } + else + { + if (Connector.Current.AllowsConvertToDate) { - new SqlConstantExpression(isPostgres ? NpgsqlDbType.Date.ToString() : SqlDbType.Date.ToString()), - expr, - new SqlConstantExpression(101) - })); + return Add(new SqlFunctionExpression(typeof(DateTime), null, SqlFunction.CONVERT.ToString(), new[] + { + new SqlConstantExpression(SqlDbType.Date.ToString()), + expr, + new SqlConstantExpression(101) + })); + } - return Add(new SqlCastExpression(typeof(DateTime), - new SqlFunctionExpression(typeof(double), null, SqlFunction.FLOOR.ToString(), - new[] { new SqlCastExpression(typeof(double), expr) } - ))); + return Add(new SqlCastExpression(typeof(DateTime), + new SqlFunctionExpression(typeof(double), null, SqlFunction.FLOOR.ToString(), + new[] { new SqlCastExpression(typeof(double), expr) } + ))); + } } @@ -485,6 +524,9 @@ protected Expression GetFormatToString(MethodCallExpression m, string? defaultFo if (innerProjection || !Has(expr)) return null; + if (isPostgres) + return Add(new SqlCastExpression(typeof(TimeSpan), expression)); + if (Connector.Current.AllowsConvertToTime) return Add(new SqlFunctionExpression(typeof(TimeSpan), null, SqlFunction.CONVERT.ToString(), new[] { @@ -501,7 +543,7 @@ protected Expression GetFormatToString(MethodCallExpression m, string? defaultFo if (innerProjection || !Has(expr)) return null; - var number = TrySqlFunction(null, getDatePart(), typeof(int?), new SqlEnumExpression(isPostgres ? SqlEnums.dow : SqlEnums.weekday), expr)!; + var number = TrySqlFunction(null, getDatePart(), typeof(int?), new SqlLiteralExpression(isPostgres ? SqlEnums.dow : SqlEnums.weekday), expr)!; Add(number); @@ -515,8 +557,8 @@ protected Expression GetFormatToString(MethodCallExpression m, string? defaultFo return null; Expression result = - TrySqlFunction(null, SqlFunction.DATEADD, expr.Type, new SqlEnumExpression(part), - TrySqlFunction(null, SqlFunction.DATEDIFF, typeof(int), new SqlEnumExpression(part), new SqlConstantExpression(0), expr)!, + TrySqlFunction(null, SqlFunction.DATEADD, expr.Type, new SqlLiteralExpression(part), + TrySqlFunction(null, SqlFunction.DATEDIFF, typeof(int), new SqlLiteralExpression(part), new SqlConstantExpression(0), expr)!, new SqlConstantExpression(0))!; return Add(result); @@ -530,8 +572,8 @@ protected Expression GetFormatToString(MethodCallExpression m, string? defaultFo Expression result = - TrySqlFunction(null, SqlFunction.DATEADD, expr.Type, new SqlEnumExpression(SqlEnums.millisecond), - Expression.Negate(TrySqlFunction(null, SqlFunction.DATEPART, typeof(int), new SqlEnumExpression(SqlEnums.millisecond), expr)), expr)!; + TrySqlFunction(null, SqlFunction.DATEADD, expr.Type, new SqlLiteralExpression(SqlEnums.millisecond), + Expression.Negate(TrySqlFunction(null, SqlFunction.DATEPART, typeof(int), new SqlLiteralExpression(SqlEnums.millisecond), expr)), expr)!; return Add(result); } @@ -553,7 +595,7 @@ protected Expression GetFormatToString(MethodCallExpression m, string? defaultFo return Add(result); } - private Expression? TryDatePartTo(SqlEnumExpression datePart, Expression start, Expression end) + private Expression? TryDatePartTo(SqlLiteralExpression datePart, Expression start, Expression end) { Expression exprStart = Visit(start); Expression exprEnd = Visit(end); @@ -793,7 +835,7 @@ private Expression NullToStringEmpty(Expression exp) return exp; } - return new SqlFunctionExpression(typeof(string), null, SqlFunction.ISNULL.ToString(), new[] { exp, new SqlConstantExpression("") }); + return new SqlFunctionExpression(typeof(string), null, SqlFunction.COALESCE.ToString(), new[] { exp, new SqlConstantExpression("") }); } private static bool AlwaysHasValue(Expression exp) @@ -1085,7 +1127,7 @@ protected internal override Expression VisitIsNull(IsNullExpression isNull) return isNull; } - protected internal override Expression VisitSqlEnum(SqlEnumExpression sqlEnum) + protected internal override Expression VisitSqlLiteral(SqlLiteralExpression sqlEnum) { if (!innerProjection) return Add(sqlEnum); @@ -1136,7 +1178,7 @@ protected internal override Expression VisitLike(LikeExpression like) { SqlFunctionExpression result = isPostgres ? new SqlFunctionExpression(typeof(int), null, PostgresFunction.strpos.ToString(), new[] { newExpression, newSubExpression }): - new SqlFunctionExpression(typeof(int), null, SqlFunction.CHARINDEX.ToString(), new[] { newExpression, newSubExpression, }); + new SqlFunctionExpression(typeof(int), null, SqlFunction.CHARINDEX.ToString(), new[] { newSubExpression, newExpression }); Add(result); @@ -1183,14 +1225,14 @@ string getDatePart() { case "string.Length": return TrySqlFunction(null, isPostgres ? PostgresFunction.length.ToString() : SqlFunction.LEN.ToString(), m.Type, m.Expression); case "Math.PI": return TrySqlFunction(null, SqlFunction.PI, m.Type); - case "DateTime.Year": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.year), m.Expression); - case "DateTime.Month": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.month), m.Expression); - case "DateTime.Day": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.day), m.Expression); - case "DateTime.DayOfYear": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(isPostgres? SqlEnums.dayofyear : SqlEnums.doy), m.Expression); - case "DateTime.Hour": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.hour), m.Expression); - case "DateTime.Minute": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.minute), m.Expression); - case "DateTime.Second": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.second), m.Expression); - case "DateTime.Millisecond": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.millisecond), m.Expression); + case "DateTime.Year": return TrySqlFunction(null, getDatePart(), m.Type, new SqlLiteralExpression(SqlEnums.year), m.Expression); + case "DateTime.Month": return TrySqlFunction(null, getDatePart(), m.Type, new SqlLiteralExpression(SqlEnums.month), m.Expression); + case "DateTime.Day": return TrySqlFunction(null, getDatePart(), m.Type, new SqlLiteralExpression(SqlEnums.day), m.Expression); + case "DateTime.DayOfYear": return TrySqlFunction(null, getDatePart(), m.Type, new SqlLiteralExpression(isPostgres? SqlEnums.doy: SqlEnums.dayofyear), m.Expression); + case "DateTime.Hour": return TrySqlFunction(null, getDatePart(), m.Type, new SqlLiteralExpression(SqlEnums.hour), m.Expression); + case "DateTime.Minute": return TrySqlFunction(null, getDatePart(), m.Type, new SqlLiteralExpression(SqlEnums.minute), m.Expression); + case "DateTime.Second": return TrySqlFunction(null, getDatePart(), m.Type, new SqlLiteralExpression(SqlEnums.second), m.Expression); + case "DateTime.Millisecond": return TrySqlFunction(null, getDatePart(), m.Type, new SqlLiteralExpression(SqlEnums.millisecond), m.Expression); case "DateTime.Date": return TrySqlDate(m.Expression); case "DateTime.TimeOfDay": return TrySqlTime(m.Expression); case "DateTime.DayOfWeek": return TrySqlDayOftheWeek(m.Expression); @@ -1203,11 +1245,11 @@ string getDatePart() return Add(new SqlCastExpression(typeof(int?), TrySqlFunction(null, SqlFunction.FLOOR, typeof(double?), diff)!)); } - case "TimeSpan.Hours": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.hour), m.Expression); - case "TimeSpan.Minutes": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.minute), m.Expression); - case "TimeSpan.Seconds": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.second), m.Expression); - case "TimeSpan.Milliseconds": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.millisecond), m.Expression); - + case "TimeSpan.Hours": return TrySqlFunction(null, getDatePart(), m.Type, new SqlLiteralExpression(SqlEnums.hour), m.Expression); + case "TimeSpan.Minutes": return TrySqlFunction(null, getDatePart(), m.Type, new SqlLiteralExpression(SqlEnums.minute), m.Expression); + case "TimeSpan.Seconds": return TrySqlFunction(null, getDatePart(), m.Type, new SqlLiteralExpression(SqlEnums.second), m.Expression); + case "TimeSpan.Milliseconds": return TrySqlFunction(null, getDatePart(), m.Type, new SqlLiteralExpression(SqlEnums.millisecond), m.Expression); + case "TimeSpan.TotalDays": return TrySqlDifference(SqlEnums.day, m.Type, m.Expression); case "TimeSpan.TotalHours": return TrySqlDifference(SqlEnums.hour, m.Type, m.Expression); case "TimeSpan.TotalMilliseconds": return TrySqlDifference(SqlEnums.millisecond, m.Type, m.Expression); @@ -1243,8 +1285,12 @@ string getDatePart() using (ForceFullNominate()) return TrySqlFunction(m.Arguments[0], m.Method.Name, m.Type, m.Arguments.Skip(1).ToArray()); - using (ForceFullNominate()) - return TrySqlFunction(m.Object, sma.Name ?? m.Method.Name, m.Type, m.Arguments.ToArray()); + if (m.Object != null) + using (ForceFullNominate()) + return TrySqlFunction(m.Object, sma.Name ?? m.Method.Name, m.Type, m.Arguments.ToArray()); + + return TrySqlFunction(m.Object, ObjectName.Parse(sma.Name ?? m.Method.Name, isPostgres).ToString(), m.Type, m.Arguments.ToArray()); + } return base.VisitMethodCall(m); @@ -1257,7 +1303,21 @@ string getDatePart() private Expression? TryDateAdd(Type returnType, Expression date, Expression value, SqlEnums unit) { - return TrySqlFunction(null, SqlFunction.DATEADD, returnType, new SqlEnumExpression(unit), value, date); + if (this.isPostgres) + { + Expression d = Visit(date); + if (!Has(d)) + return null; + + Expression v = Visit(value); + if (!Has(v)) + return null; + + return Add(Expression.Add(date, Expression.Multiply(value, new SqlLiteralExpression(typeof(TimeSpan), $"INTERVAL '1 {unit}'")))); + } + + + return TrySqlFunction(null, SqlFunction.DATEADD, returnType, new SqlLiteralExpression(unit), value, date); } private Expression? HardCodedMethods(MethodCallExpression m) @@ -1291,9 +1351,15 @@ string getDatePart() { Expression? startIndex = m.TryGetArgument("startIndex")?.Let(e => Expression.Add(e, new SqlConstantExpression(1))); - Expression? charIndex = startIndex != null ? + Expression? charIndex = isPostgres ? + (startIndex != null ? + throw new NotImplementedException() : + TrySqlFunction(null, PostgresFunction.strpos, m.Type, m.Object, m.GetArgument("value"))) + : + (startIndex != null ? TrySqlFunction(null, SqlFunction.CHARINDEX, m.Type, m.GetArgument("value"), m.Object, startIndex) : - TrySqlFunction(null, SqlFunction.CHARINDEX, m.Type, m.GetArgument("value"), m.Object); + TrySqlFunction(null, SqlFunction.CHARINDEX, m.Type, m.GetArgument("value"), m.Object)); + if (charIndex == null) return null; Expression result = Expression.Subtract(charIndex, new SqlConstantExpression(1)); @@ -1315,7 +1381,14 @@ string getDatePart() case "string.Replace": return TrySqlFunction(null, SqlFunction.REPLACE, m.Type, m.Object, m.GetArgument("oldValue"), m.GetArgument("newValue")); case "string.Substring": - return TrySqlFunction(null, SqlFunction.SUBSTRING, m.Type, m.Object, Expression.Add(m.GetArgument("startIndex"), new SqlConstantExpression(1)), m.TryGetArgument("length") ?? new SqlConstantExpression(int.MaxValue)); + var start = Expression.Add(m.GetArgument("startIndex"), new SqlConstantExpression(1)); + var length = m.TryGetArgument("length"); + if(isPostgres) + return length == null ? + TrySqlFunction(null, PostgresFunction.substr, m.Type, m.Object, start) : + TrySqlFunction(null, PostgresFunction.substr, m.Type, m.Object, start, length); + else + return TrySqlFunction(null, SqlFunction.SUBSTRING, m.Type, m.Object, start, length ?? new SqlConstantExpression(int.MaxValue)); case "string.Contains": return TryCharIndex(m.Object, m.GetArgument("value"), index => Expression.GreaterThanOrEqual(index, new SqlConstantExpression(1))); case "string.StartsWith": @@ -1333,7 +1406,7 @@ string getDatePart() case "StringExtensions.End": return TrySqlFunction(null, SqlFunction.RIGHT, m.Type, m.GetArgument("str"), m.GetArgument("numChars")); case "StringExtensions.Replicate": - return TrySqlFunction(null, SqlFunction.REPLICATE, m.Type, m.GetArgument("str"), m.GetArgument("times")); + return TrySqlFunction(null, isPostgres ? PostgresFunction.repeat.ToString() : SqlFunction.REPLICATE.ToString(), m.Type, m.GetArgument("str"), m.GetArgument("times")); ; case "StringExtensions.Reverse": return TrySqlFunction(null, SqlFunction.REVERSE, m.Type, m.GetArgument("str")); case "StringExtensions.Like": @@ -1369,11 +1442,11 @@ string getDatePart() case "DateTimeExtensions.HourStart": return TrySqlStartOf(m.GetArgument("dateTime"), SqlEnums.hour); case "DateTimeExtensions.MinuteStart": return TrySqlStartOf(m.GetArgument("dateTime"), SqlEnums.minute); case "DateTimeExtensions.SecondStart": return TrySqlSecondsStart(m.GetArgument("dateTime")); - case "DateTimeExtensions.YearsTo": return TryDatePartTo(new SqlEnumExpression(SqlEnums.year), m.GetArgument("start"), m.GetArgument("end")); - case "DateTimeExtensions.MonthsTo": return TryDatePartTo(new SqlEnumExpression(SqlEnums.month), m.GetArgument("start"), m.GetArgument("end")); + case "DateTimeExtensions.YearsTo": return TryDatePartTo(new SqlLiteralExpression(SqlEnums.year), m.GetArgument("start"), m.GetArgument("end")); + case "DateTimeExtensions.MonthsTo": return TryDatePartTo(new SqlLiteralExpression(SqlEnums.month), m.GetArgument("start"), m.GetArgument("end")); - case "DateTimeExtensions.Quarter": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.quarter), m.Arguments.Single()); - case "DateTimeExtensions.WeekNumber": return TrySqlFunction(null, getDatePart(), m.Type, new SqlEnumExpression(SqlEnums.week), m.Arguments.Single()); + case "DateTimeExtensions.Quarter": return TrySqlFunction(null, getDatePart(), m.Type, new SqlLiteralExpression(SqlEnums.quarter), m.Arguments.Single()); + case "DateTimeExtensions.WeekNumber": return TrySqlFunction(null, getDatePart(), m.Type, new SqlLiteralExpression(SqlEnums.week), m.Arguments.Single()); case "Math.Sign": return TrySqlFunction(null, SqlFunction.SIGN, m.Type, m.GetArgument("value")); case "Math.Abs": return TrySqlFunction(null, SqlFunction.ABS, m.Type, m.GetArgument("value")); @@ -1392,10 +1465,19 @@ string getDatePart() case "Math.Log": return m.Arguments.Count != 1 ? null : TrySqlFunction(null, SqlFunction.LOG, m.Type, m.GetArgument("d")); case "Math.Ceiling": return TrySqlFunction(null, SqlFunction.CEILING, m.Type, m.TryGetArgument("d") ?? m.GetArgument("a")); case "Math.Round": - return TrySqlFunction(null, SqlFunction.ROUND, m.Type, - m.TryGetArgument("a") ?? m.TryGetArgument("d") ?? m.GetArgument("value"), - m.TryGetArgument("decimals") ?? m.TryGetArgument("digits") ?? new SqlConstantExpression(0)); - case "Math.Truncate": return TrySqlFunction(null, SqlFunction.ROUND, m.Type, m.GetArgument("d"), new SqlConstantExpression(0), new SqlConstantExpression(1)); + + var value = m.TryGetArgument("a") ?? m.TryGetArgument("d") ?? m.GetArgument("value"); + var digits = m.TryGetArgument("decimals") ?? m.TryGetArgument("digits"); + if (digits == null) + return TrySqlFunction(null, SqlFunction.ROUND, m.Type, value); + else + return TrySqlFunction(null, SqlFunction.ROUND, m.Type, value, digits); + + case "Math.Truncate": + if(isPostgres) + return TrySqlFunction(null, PostgresFunction.trunc, m.Type, m.GetArgument("d")); + + return TrySqlFunction(null, SqlFunction.ROUND, m.Type, m.GetArgument("d"), new SqlConstantExpression(0), new SqlConstantExpression(1)); case "Math.Max": case "Math.Min": return null; /* could be translates to something like 'case when a > b then a * when a < b then b diff --git a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs index cdbaa09ab8..e7e2aa77c6 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs @@ -177,7 +177,7 @@ protected internal virtual Expression VisitAdditionalField(AdditionalFieldExpres return ml; } - protected internal virtual Expression VisitSqlEnum(SqlEnumExpression sqlEnum) + protected internal virtual Expression VisitSqlLiteral(SqlLiteralExpression sqlEnum) { return sqlEnum; } @@ -334,9 +334,9 @@ protected internal virtual Expression VisitRowNumber(RowNumberExpression rowNumb protected internal virtual Expression VisitAggregate(AggregateExpression aggregate) { - Expression source = Visit(aggregate.Expression); - if (source != aggregate.Expression) - return new AggregateExpression(aggregate.Type, source, aggregate.AggregateFunction, aggregate.Distinct); + var expressions = Visit(aggregate.Arguments); + if (expressions != aggregate.Arguments) + return new AggregateExpression(aggregate.Type, aggregate.AggregateFunction, expressions); return aggregate; } @@ -428,7 +428,7 @@ protected internal virtual Expression VisitSqlTableValuedFunction(SqlTableValued { ReadOnlyCollection args = Visit(sqlFunction.Arguments); if (args != sqlFunction.Arguments) - return new SqlTableValuedFunctionExpression(sqlFunction.SqlFunction, sqlFunction.Table, sqlFunction.Alias, args); + return new SqlTableValuedFunctionExpression(sqlFunction.SqlFunction, sqlFunction.ViewTable, sqlFunction.SingleColumnType, sqlFunction.Alias, args); return sqlFunction; } diff --git a/Signum.Engine/Linq/ExpressionVisitor/OrderByRewriter.cs b/Signum.Engine/Linq/ExpressionVisitor/OrderByRewriter.cs index b8cf9ac3fd..20c4e6a1cd 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/OrderByRewriter.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/OrderByRewriter.cs @@ -77,7 +77,20 @@ protected internal override Expression VisitSelect(SelectExpression select) if (gatheredKeys != null && (select.IsDistinct || select.GroupBy.HasItems() || select.IsAllAggregates)) savedKeys = gatheredKeys.ToList(); - select = (SelectExpression)base.VisitSelect(select); + if ((AggregateFinder.GetAggregates(select.Columns)?.Any(a => a.AggregateFunction.OrderMatters()) ?? false) && select.From is SelectExpression from) + { + var oldOuterMostSelect = outerMostSelect; + outerMostSelect = from; + + select = (SelectExpression)base.VisitSelect(select); + + outerMostSelect = oldOuterMostSelect; + } + else + { + select = (SelectExpression)base.VisitSelect(select); + } + if (savedKeys != null) gatheredKeys = savedKeys; @@ -235,6 +248,7 @@ private bool IsCountSumOrAvg(SelectExpression select) return false; return aggExp.AggregateFunction == AggregateSqlFunction.Count || + aggExp.AggregateFunction == AggregateSqlFunction.CountDistinct || aggExp.AggregateFunction == AggregateSqlFunction.Sum || aggExp.AggregateFunction == AggregateSqlFunction.Average || aggExp.AggregateFunction == AggregateSqlFunction.StdDev || diff --git a/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs b/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs index 5d83944c46..2b0d263887 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs @@ -1,6 +1,7 @@ using Microsoft.SqlServer.Server; using Signum.Engine.Basics; using Signum.Engine.Maps; +using Signum.Engine.PostgresCatalog; using Signum.Entities; using Signum.Entities.Basics; using Signum.Entities.DynamicQuery; @@ -33,11 +34,12 @@ internal class QueryBinder : ExpressionVisitor internal SystemTime? systemTime; - internal Schema schema; - + internal Schema schema; + bool isPostgres; public QueryBinder(AliasGenerator aliasGenerator) { this.schema = Schema.Current; + this.isPostgres = this.schema.Settings.IsPostgres; this.systemTime = SystemTime.Current; this.aliasGenerator = aliasGenerator; this.root = null!; @@ -413,16 +415,20 @@ private Expression MapVisitExpandWithIndex(LambdaExpression lambda, ref Projecti private ProjectionExpression VisitCastProjection(Expression source) { - if (source is MethodCallExpression && IsTableValuedFunction((MethodCallExpression)source)) + if (isPostgres && source is MemberExpression m && m.Type.IsArray) { - var oldInTVF = inTableValuedFunction; - inTableValuedFunction = true; + var miUnnest = ReflectionTools.GetMethodInfo(() => PostgresFunctions.unnest(null!)).GetGenericMethodDefinition(); - var visit = Visit(source); + var eType = m.Type.ElementType()!; + + var newSource = Expression.Call(null, miUnnest.MakeGenericMethod(eType), m); - inTableValuedFunction = oldInTVF; + return BindTableValueFunction(newSource); + } - return GetTableValuedFunctionProjection((MethodCallExpression)visit); + if (source is MethodCallExpression mc && IsTableValuedFunction(mc)) + { + return BindTableValueFunction(mc); } else { @@ -432,15 +438,27 @@ private ProjectionExpression VisitCastProjection(Expression source) } } + private ProjectionExpression BindTableValueFunction(MethodCallExpression mc) + { + var oldInTVF = inTableValuedFunction; + inTableValuedFunction = true; + + var visit = (MethodCallExpression)Visit(mc); + + inTableValuedFunction = oldInTVF; + + return GetTableValuedFunctionProjection(visit); + } + private ProjectionExpression AsProjection(Expression expression) { - if (expression is ProjectionExpression) - return (ProjectionExpression)expression; + if (expression is ProjectionExpression pe) + return pe; expression = RemoveProjectionConvert(expression); - if (expression is ProjectionExpression) - return (ProjectionExpression)expression; + if (expression is ProjectionExpression pe2) + return pe2; if (expression.NodeType == ExpressionType.New && expression.Type.IsInstantiationOf(typeof(Grouping<,>))) { @@ -568,19 +586,34 @@ private Expression BindToString(Expression source, Expression separator, MethodI string value = (string)((ConstantExpression)separator).Value; - ColumnDeclaration cd = new ColumnDeclaration(null!, Expression.Add(new SqlConstantExpression(value, typeof(string)), nominated, miStringConcat)); + + if (isPostgres) + { + ColumnDeclaration cd = new ColumnDeclaration(null!, new AggregateExpression(typeof(string), AggregateSqlFunction.string_agg, + new[] { nominated, new SqlConstantExpression(value, typeof(string)) })); - Alias alias = NextSelectAlias(); + Alias alias = NextSelectAlias(); - SelectExpression select = new SelectExpression(alias, false, null, new[] { cd }, projection.Select, null, null, null, SelectOptions.ForXmlPathEmpty); + var select = new SelectExpression(alias, false, null, new[] { cd }, projection.Select, null, null, null, 0); - return new SqlFunctionExpression(typeof(string), null, SqlFunction.STUFF.ToString(), new Expression[] + return new ScalarExpression(typeof(string), select); + } + else { - new ScalarExpression(typeof(string), select), - new SqlConstantExpression(1), - new SqlConstantExpression(value.Length), - new SqlConstantExpression("") - }); + ColumnDeclaration cd = new ColumnDeclaration(null!, Expression.Add(new SqlConstantExpression(value, typeof(string)), nominated, miStringConcat)); + + Alias alias = NextSelectAlias(); + + SelectExpression select = new SelectExpression(alias, false, null, new[] { cd }, projection.Select, null, null, null, SelectOptions.ForXmlPathEmpty); + + return new SqlFunctionExpression(typeof(string), null, SqlFunction.STUFF.ToString(), new Expression[] + { + new ScalarExpression(typeof(string), select), + new SqlConstantExpression(1), + new SqlConstantExpression(value.Length), + new SqlConstantExpression("") + }); + } } static MethodInfo miStringConcat = ReflectionTools.GetMethodInfo(() => string.Concat("", "")); @@ -769,10 +802,11 @@ bool ExtractDistinct(Expression? source, out Expression? innerSource) DbExpressionNominator.FullNominate(exp); var result = new AggregateRequestsExpression(info.GroupAlias, - new AggregateExpression(aggregateFunction == AggregateSqlFunction.Count ? typeof(int) : GetBasicType(nominated), - nominated!, - aggregateFunction, - distinct)); + new AggregateExpression( + aggregateFunction == AggregateSqlFunction.Count ? typeof(int) : GetBasicType(nominated), + aggregateFunction, + new Expression[] { nominated! }) + ); return RestoreWrappedType(result, resultType); } @@ -791,19 +825,19 @@ bool ExtractDistinct(Expression? source, out Expression? innerSource) var nominated = DbExpressionNominator.FullNominate(exp)!.Nullify(); aggregate = (Expression)Expression.Coalesce( - new AggregateExpression(GetBasicType(nominated), nominated, aggregateFunction, distinct), + new AggregateExpression(GetBasicType(nominated), aggregateFunction, new[] { nominated }), new SqlConstantExpression(Activator.CreateInstance(nominated.Type.UnNullify())!)); } else { - var nominated = aggregateFunction == AggregateSqlFunction.Count ? - DbExpressionNominator.FullNominateNotNullable(exp): + var nominated = aggregateFunction == AggregateSqlFunction.Count ? + (exp != null ? DbExpressionNominator.FullNominateNotNullable(exp) : new SqlLiteralExpression(typeof(object), "*")) : DbExpressionNominator.FullNominate(exp); - aggregate = new AggregateExpression(aggregateFunction == AggregateSqlFunction.Count ? typeof(int) : GetBasicType(nominated), - nominated!, - aggregateFunction, - distinct); + aggregate = new AggregateExpression( + aggregateFunction == AggregateSqlFunction.Count ? typeof(int) : GetBasicType(nominated), + distinct ? AggregateSqlFunction.CountDistinct : aggregateFunction, + new[] { nominated!}); } Alias alias = NextSelectAlias(); @@ -1341,27 +1375,52 @@ private ProjectionExpression GetTableValuedFunctionProjection(MethodCallExpressi Type returnType = mce.Method.ReturnType; var type = returnType.GetGenericArguments()[0]; - Table table = schema.ViewBuilder.NewView(type); + var functionName = ObjectName.Parse(mce.Method.GetCustomAttribute()?.Name ?? mce.Method.Name, Schema.Current.Settings.IsPostgres); - Alias tableAlias = NextTableAlias(table.Name); + var arguments = mce.Arguments.Select(a => DbExpressionNominator.FullNominate(a)!).ToList(); - Expression exp = table.GetProjectorExpression(tableAlias, this); - var functionName = mce.Method.GetCustomAttribute()?.Name ?? mce.Method.Name; + if (typeof(IView).IsAssignableFrom(type)) + { + Table table = schema.ViewBuilder.NewView(type); - var argumens = mce.Arguments.Select(a => DbExpressionNominator.FullNominate(a)!).ToList(); + Alias tableAlias = NextTableAlias(table.Name); - SqlTableValuedFunctionExpression tableExpression = new SqlTableValuedFunctionExpression(functionName, table, tableAlias, argumens); + Expression exp = table.GetProjectorExpression(tableAlias, this); - Alias selectAlias = NextSelectAlias(); + SqlTableValuedFunctionExpression tableExpression = new SqlTableValuedFunctionExpression(functionName, table, null, tableAlias, arguments); - ProjectedColumns pc = ColumnProjector.ProjectColumns(exp, selectAlias); + Alias selectAlias = NextSelectAlias(); - ProjectionExpression projection = new ProjectionExpression( - new SelectExpression(selectAlias, false, null, pc.Columns, tableExpression, null, null, null, 0), - pc.Projector, null, returnType); + ProjectedColumns pc = ColumnProjector.ProjectColumns(exp, selectAlias); - return projection; + ProjectionExpression projection = new ProjectionExpression( + new SelectExpression(selectAlias, false, null, pc.Columns, tableExpression, null, null, null, 0), + pc.Projector, null, returnType); + + return projection; + } + else + { + if (!isPostgres) + throw new InvalidOperationException("TableValuedFunctions should return an IQueryable"); + + Alias tableAlias = NextTableAlias(functionName); + + SqlTableValuedFunctionExpression tableExpression = new SqlTableValuedFunctionExpression(functionName, null, type, tableAlias, arguments); + + var columnExpression = new ColumnExpression(type, tableAlias, null); + + Alias selectAlias = NextSelectAlias(); + + var cd = new ColumnDeclaration("val", columnExpression); + + return new ProjectionExpression( + new SelectExpression(selectAlias, false, null, new[] { cd }, tableExpression, null, null, null, 0), + new ColumnExpression(type, selectAlias, "val"), + null, + returnType); + } } internal Expression VisitConstant(object value, Type type) @@ -2543,7 +2602,7 @@ ColumnAssignment AssignColumn(Expression column, Expression expression) if (col == null) throw new InvalidOperationException("{0} does not represent a column".FormatWith(column.ToString())); - return new ColumnAssignment(col.Name, DbExpressionNominator.FullNominate(expression)!); + return new ColumnAssignment(col.Name!, DbExpressionNominator.FullNominate(expression)!); } #region BinderTools @@ -3100,7 +3159,7 @@ public Expression CombineValues(Dictionary implementations, Ty static string GetDefaultName(Expression expression) { if (expression is ColumnExpression) - return ((ColumnExpression)expression).Name; + return ((ColumnExpression)expression).Name ?? "val"; if (expression is UnaryExpression) return GetDefaultName(((UnaryExpression)expression).Operand); diff --git a/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs b/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs index 58587b212a..bee53a9c36 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs @@ -157,7 +157,7 @@ protected override Expression VisitBinary(BinaryExpression b) { if (b.NodeType == ExpressionType.Coalesce) { - sb.Append("IsNull("); + sb.Append("COALESCE("); Visit(b.Left); sb.Append(","); Visit(b.Right); @@ -166,13 +166,18 @@ protected override Expression VisitBinary(BinaryExpression b) else if (b.NodeType == ExpressionType.Equal || b.NodeType == ExpressionType.NotEqual) { sb.Append("("); - Visit(b.Left); sb.Append(b.NodeType == ExpressionType.Equal ? " = " : " <> "); Visit(b.Right); - sb.Append(")"); } + else if (b.NodeType == ExpressionType.ArrayIndex) + { + Visit(b.Left); + sb.Append("["); + Visit(b.Right); + sb.Append("]"); + } else { sb.Append("("); @@ -205,9 +210,10 @@ protected override Expression VisitBinary(BinaryExpression b) case ExpressionType.Add: case ExpressionType.AddChecked: - if(this.isPostgres && (b.Left.Type == typeof(string) || b.Right.Type == typeof(string))) + if (this.isPostgres && (b.Left.Type == typeof(string) || b.Right.Type == typeof(string))) sb.Append(" || "); - sb.Append(" + "); + else + sb.Append(" + "); break; case ExpressionType.Subtract: case ExpressionType.SubtractChecked: @@ -339,7 +345,7 @@ protected internal override Expression VisitIn(InExpression inExpression) return inExpression; } - protected internal override Expression VisitSqlEnum(SqlEnumExpression sqlEnum) + protected internal override Expression VisitSqlLiteral(SqlLiteralExpression sqlEnum) { sb.Append(sqlEnum.Value); return sqlEnum; @@ -351,8 +357,10 @@ protected internal override Expression VisitSqlCast(SqlCastExpression castExpr) Visit(castExpr.Expression); sb.Append(" as "); sb.Append(castExpr.DbType.ToString(schema.Settings.IsPostgres)); + if (!schema.Settings.IsPostgres && (castExpr.DbType.SqlServer == SqlDbType.NVarChar || castExpr.DbType.SqlServer == SqlDbType.VarChar)) sb.Append("(MAX)"); + sb.Append(")"); return castExpr; } @@ -382,9 +390,9 @@ protected internal override Expression VisitSqlConstant(SqlConstantExpression c) if (!schema.Settings.IsDbType(c.Value.GetType().UnNullify())) throw new NotSupportedException(string.Format("The constant for {0} is not supported", c.Value)); - if (c.Value.Equals(true)) + if (!isPostgres && c.Value.Equals(true)) sb.Append("1"); - else if (c.Value.Equals(false)) + else if (!isPostgres && c.Value.Equals(false)) sb.Append("0"); else if (c.Value is string s) sb.Append(s == "" ? "''" : ("'" + s + "'")); @@ -405,9 +413,11 @@ protected internal override Expression VisitSqlVariable(SqlVariableExpression sv protected internal override Expression VisitColumn(ColumnExpression column) { sb.Append(column.Alias.ToString()); - sb.Append("."); - sb.Append(column.Name.SqlEscape(isPostgres)); - + if (column.Name != null) //Is null for PostgressFunctions.unnest and friends (IQueryable table-valued function) + { + sb.Append("."); + sb.Append(column.Name.SqlEscape(isPostgres)); + } return column; } @@ -424,7 +434,7 @@ protected internal override Expression VisitSelect(SelectExpression select) if (select.IsDistinct) sb.Append("DISTINCT "); - if (select.Top != null) + if (select.Top != null && !this.isPostgres) { sb.Append("TOP ("); Visit(select.Top); @@ -497,6 +507,13 @@ protected internal override Expression VisitSelect(SelectExpression select) } } + if (select.Top != null && this.isPostgres) + { + this.AppendNewLine(Indentation.Same); + sb.Append("LIMIT "); + Visit(select.Top); + } + if (select.IsForXmlPathEmpty) { this.AppendNewLine(Indentation.Same); @@ -512,28 +529,38 @@ protected internal override Expression VisitSelect(SelectExpression select) return select; } - Dictionary dic = new Dictionary + + string GetAggregateFunction(AggregateSqlFunction agg) { - {AggregateSqlFunction.Average, "AVG"}, - {AggregateSqlFunction.StdDev, "STDEV"}, - {AggregateSqlFunction.StdDevP, "STDEVP"}, - {AggregateSqlFunction.Count, "COUNT"}, - {AggregateSqlFunction.Max, "MAX"}, - {AggregateSqlFunction.Min, "MIN"}, - {AggregateSqlFunction.Sum, "SUM"} - }; + return agg switch + { + AggregateSqlFunction.Average => "AVG", + AggregateSqlFunction.StdDev => !isPostgres ? "STDEV" : "stddev_samp", + AggregateSqlFunction.StdDevP => !isPostgres? "STDEVP" : "stddev_pop", + AggregateSqlFunction.Count => "COUNT", + AggregateSqlFunction.CountDistinct => "COUNT", + AggregateSqlFunction.Max => "MAX", + AggregateSqlFunction.Min => "MIN", + AggregateSqlFunction.Sum => "SUM", + AggregateSqlFunction.string_agg => "string_agg", + _ => throw new UnexpectedValueException(agg) + }; + } protected internal override Expression VisitAggregate(AggregateExpression aggregate) { - sb.Append(dic[aggregate.AggregateFunction]); + sb.Append(GetAggregateFunction(aggregate.AggregateFunction)); sb.Append("("); - if (aggregate.Distinct) + if (aggregate.AggregateFunction == AggregateSqlFunction.CountDistinct) sb.Append("DISTINCT "); - if (aggregate.Expression == null) - sb.Append("*"); - else - Visit(aggregate.Expression); + for (int i = 0, n = aggregate.Arguments.Count; i < n; i++) + { + Expression exp = aggregate.Arguments[i]; + if (i > 0) + sb.Append(", "); + this.Visit(exp); + } sb.Append(")"); return aggregate; @@ -548,12 +575,21 @@ protected internal override Expression VisitSqlFunction(SqlFunctionExpression sq } sb.Append(sqlFunction.SqlFunction); sb.Append("("); - for (int i = 0, n = sqlFunction.Arguments.Count; i < n; i++) + if (isPostgres && sqlFunction.SqlFunction == PostgresFunction.EXTRACT.ToString()) { - Expression exp = sqlFunction.Arguments[i]; - if (i > 0) - sb.Append(", "); - this.Visit(exp); + this.Visit(sqlFunction.Arguments[0]); + sb.Append(" from "); + this.Visit(sqlFunction.Arguments[1]); + } + else + { + 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(")"); @@ -562,7 +598,7 @@ protected internal override Expression VisitSqlFunction(SqlFunctionExpression sq protected internal override Expression VisitSqlTableValuedFunction(SqlTableValuedFunctionExpression sqlFunction) { - sb.Append(sqlFunction.SqlFunction); + sb.Append(sqlFunction.SqlFunction.ToString()); sb.Append("("); for (int i = 0, n = sqlFunction.Arguments.Count; i < n; i++) { @@ -851,6 +887,11 @@ protected internal override Expression VisitInsertSelect(InsertSelectExpression } sb.Append(")"); this.AppendNewLine(Indentation.Same); + if(this.isPostgres && Administrator.IsIdentityBehaviourDisabled(insertSelect.Table)) + { + sb.Append("OVERRIDING SYSTEM VALUE"); + this.AppendNewLine(Indentation.Same); + } sb.Append("SELECT "); for (int i = 0, n = insertSelect.Assigments.Count; i < n; i++) { @@ -866,7 +907,6 @@ protected internal override Expression VisitInsertSelect(InsertSelectExpression sb.Append(" FROM "); VisitSource(insertSelect.Source); - sb.Append(";"); return insertSelect; } } @@ -890,6 +930,7 @@ protected internal override Expression VisitInsertSelect(InsertSelectExpression return new Disposable(() => { + this.AppendNewLine(Indentation.Same); sb.Append("RETURNING 1"); this.AppendNewLine(Indentation.Outer); sb.Append(")"); diff --git a/Signum.Engine/Linq/ExpressionVisitor/QueryRebinder.cs b/Signum.Engine/Linq/ExpressionVisitor/QueryRebinder.cs index 7ba4d1f73d..d391467826 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/QueryRebinder.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/QueryRebinder.cs @@ -95,7 +95,7 @@ protected internal override Expression VisitSqlTableValuedFunction(SqlTableValue ReadOnlyCollection args = Visit(sqlFunction.Arguments); if (args != sqlFunction.Arguments) - return new SqlTableValuedFunctionExpression(sqlFunction.SqlFunction, sqlFunction.Table, sqlFunction.Alias, args); + return new SqlTableValuedFunctionExpression(sqlFunction.SqlFunction, sqlFunction.ViewTable, sqlFunction.SingleColumnType, sqlFunction.Alias, args); return sqlFunction; } diff --git a/Signum.Engine/Linq/ExpressionVisitor/RedundantSubqueryRemover.cs b/Signum.Engine/Linq/ExpressionVisitor/RedundantSubqueryRemover.cs index 2089253b0b..e3a5d52e03 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/RedundantSubqueryRemover.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/RedundantSubqueryRemover.cs @@ -3,6 +3,7 @@ using System.Collections.ObjectModel; using Signum.Utilities.ExpressionTrees; using Signum.Utilities; +using Signum.Engine.Maps; namespace Signum.Engine.Linq { @@ -14,9 +15,10 @@ private RedundantSubqueryRemover() public static Expression Remove(Expression expression) { - expression = new RedundantSubqueryRemover().Visit(expression); - expression = SubqueryMerger.Merge(expression); - return expression; + var removed = new RedundantSubqueryRemover().Visit(expression); + var merged = SubqueryMerger.Merge(removed); + var simplified = JoinSimplifier.Simplify(merged); + return simplified; } protected internal override Expression VisitSelect(SelectExpression select) @@ -78,11 +80,6 @@ internal static bool IsNameMapProjection(SelectExpression select) return true; } - internal static bool IsInitialProjection(SelectExpression select) - { - return select.From is TableExpression; - } - class RedundantSubqueryGatherer : DbExpressionVisitor { List? redundant; @@ -134,8 +131,31 @@ protected internal override Expression VisitExists(ExistsExpression exists) { return exists; } + + protected internal override Expression VisitJoin(JoinExpression join) + { + var result = (JoinExpression)base.VisitJoin(join); + if (result.JoinType == JoinType.CrossApply || + result.JoinType == JoinType.OuterApply) + { + if (Schema.Current.Settings.IsPostgres && this.redundant != null && result.Right is SelectExpression s && this.redundant.Contains(s)) + { + if (HasJoins(s)) + this.redundant.Remove(s); + } + } + + return result; + } + + static bool HasJoins(SelectExpression s) + { + return s.From is JoinExpression || s.From is SelectExpression s2 && HasJoins(s2); + } } + + class SubqueryMerger : DbExpressionVisitor { private SubqueryMerger() @@ -324,4 +344,31 @@ protected internal override Expression VisitScalar(ScalarExpression scalar) } } } + + class JoinSimplifier : DbExpressionVisitor + { + internal static Expression Simplify(Expression expression) + { + return new JoinSimplifier().Visit(expression); + } + + protected internal override Expression VisitJoin(JoinExpression join) + { + SourceExpression left = this.VisitSource(join.Left); + SourceExpression right = this.VisitSource(join.Right); + Expression condition = this.Visit(join.Condition); + + if(join.JoinType == JoinType.CrossApply || join.JoinType == JoinType.OuterApply) + { + if (right is TableExpression) + return new JoinExpression(join.JoinType == JoinType.OuterApply ? JoinType.LeftOuterJoin : JoinType.InnerJoin, left, right, new SqlConstantExpression(true)); + } + + if (left != join.Left || right != join.Right || condition != join.Condition) + { + return new JoinExpression(join.JoinType, left, right, condition); + } + return join; + } + } } diff --git a/Signum.Engine/Linq/ExpressionVisitor/SubqueryRemover.cs b/Signum.Engine/Linq/ExpressionVisitor/SubqueryRemover.cs index 1e29164e9d..05ce7bcd5b 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/SubqueryRemover.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/SubqueryRemover.cs @@ -32,7 +32,7 @@ protected internal override Expression VisitSelect(SelectExpression select) protected internal override Expression VisitColumn(ColumnExpression column) { return map.TryGetC(column.Alias) - ?.Let(d => d.GetOrThrow(column.Name, "Reference to undefined column {0}")) ?? column; + ?.Let(d => d.GetOrThrow(column.Name!, "Reference to undefined column {0}")) ?? column; } } } diff --git a/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs b/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs index 0462541a29..fa2cd895f0 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs @@ -203,7 +203,7 @@ protected override Expression VisitUnary(UnaryExpression u) if (u.NodeType == ExpressionType.Convert && u.Operand is ColumnExpression && DiffersInNullability(u.Type, u.Operand.Type)) { ColumnExpression column = (ColumnExpression)u.Operand; - return scope.GetColumnExpression(row, column.Alias, column.Name, u.Type); + return scope.GetColumnExpression(row, column.Alias, column.Name!, u.Type); } return base.VisitUnary(u); @@ -219,7 +219,7 @@ bool DiffersInNullability(Type a, Type b) protected internal override Expression VisitColumn(ColumnExpression column) { - return scope.GetColumnExpression(row, column.Alias, column.Name, column.Type); + return scope.GetColumnExpression(row, column.Alias, column.Name!, column.Type); } protected internal override Expression VisitChildProjection(ChildProjectionExpression child) @@ -551,7 +551,7 @@ protected internal override Expression VisitToDayOfWeek(ToDayOfWeekExpression to protected override Expression VisitNew(NewExpression node) { - var expressions = this.Visit(node.Arguments); + var expressions = this.Visit(node.Arguments); if (node.Members != null) { @@ -561,7 +561,7 @@ protected override Expression VisitNew(NewExpression node) var e = expressions[i]; if (m is PropertyInfo pi && !pi.PropertyType.IsAssignableFrom(e.Type)) { - throw new InvalidOperationException( + throw new InvalidOperationException( $"Impossible to assign a '{e.Type.TypeName()}' to the member '{m.Name}' of type '{pi.PropertyType.TypeName()}'." + (e.Type.IsInstantiationOf(typeof(IEnumerable<>)) ? "\nConsider adding '.ToList()' at the end of your sub-query" : null) ); @@ -569,7 +569,7 @@ protected override Expression VisitNew(NewExpression node) } } - return (Expression) node.Update(expressions); + return (Expression)node.Update(expressions); } } } diff --git a/Signum.Engine/Linq/ExpressionVisitor/UnusedColumnRemover.cs b/Signum.Engine/Linq/ExpressionVisitor/UnusedColumnRemover.cs index eb8c59bea0..99dfcdd186 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/UnusedColumnRemover.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/UnusedColumnRemover.cs @@ -9,7 +9,7 @@ namespace Signum.Engine.Linq { internal class UnusedColumnRemover : DbExpressionVisitor { - Dictionary> allColumnsUsed = new Dictionary>(); + Dictionary> allColumnsUsed = new Dictionary>(); private UnusedColumnRemover() { } @@ -32,7 +32,7 @@ bool IsConstant(Expression exp) protected internal override Expression VisitSelect(SelectExpression select) { // visit column projection first - HashSet columnsUsed = allColumnsUsed.GetOrCreate(select.Alias); // a veces no se usa + HashSet columnsUsed = allColumnsUsed.GetOrCreate(select.Alias); // a veces no se usa ReadOnlyCollection columns = select.Columns.Select(c => { @@ -81,7 +81,7 @@ private void AddSingleColumn(SubqueryExpression subQuery) protected internal override Expression VisitSetOperator(SetOperatorExpression set) { - HashSet columnsUsed = allColumnsUsed.GetOrCreate(set.Alias); // a veces no se usa + HashSet columnsUsed = allColumnsUsed.GetOrCreate(set.Alias); // a veces no se usa allColumnsUsed.GetOrCreate(set.Left.Alias).AddRange(columnsUsed); allColumnsUsed.GetOrCreate(set.Right.Alias).AddRange(columnsUsed); diff --git a/Signum.Engine/Schema/ObjectName.cs b/Signum.Engine/Schema/ObjectName.cs index 5736f30886..7fe5b0c945 100644 --- a/Signum.Engine/Schema/ObjectName.cs +++ b/Signum.Engine/Schema/ObjectName.cs @@ -8,7 +8,12 @@ public static class TableExtensions internal static string UnScapeSql(this string name, bool isPostgres) { if (isPostgres) - name.Trim('\"'); + { + if (name.StartsWith('\"')) + return name.Trim('\"'); + + return name.ToLower(); + } return name.Trim('[', ']'); } @@ -183,6 +188,8 @@ public static SchemaName Parse(string? name, bool isPostgres) public class ObjectName : IEquatable { + public static int MaxPostgreeSize = 63; + public string Name { get; private set; } public bool IsPostgres { get; private set; } @@ -191,6 +198,9 @@ public class ObjectName : IEquatable public ObjectName(SchemaName schema, string name, bool isPostgres) { this.Name = name.HasText() ? name : throw new ArgumentNullException(nameof(name)); + if (isPostgres && this.Name.Length > MaxPostgreeSize) + throw new InvalidOperationException($"The name '{name}' is too long, consider using TableNameAttribute/ColumnNameAttribute"); + this.Schema = schema ?? throw new ArgumentNullException(nameof(schema)); this.IsPostgres = isPostgres; } @@ -228,9 +238,18 @@ internal static (string? prefix, string name) SplitLast(string str, bool isPostg { if (isPostgres) { + if (!str.EndsWith('\"')) + { + return ( + prefix: str.TryBeforeLast('.'), + name: str.TryAfterLast('.') ?? str + ); + } + + var index = str.LastIndexOf('\"', str.Length - 2); return ( - prefix: str.TryBeforeLast("."), - name: (str.TryAfterLast(".") ?? str).UnScapeSql(isPostgres) + prefix: index == 0 ? null : str.Substring(0, index - 1), + name: str.Substring(index).UnScapeSql(isPostgres) ); } else diff --git a/Signum.Engine/Schema/Schema.Basics.cs b/Signum.Engine/Schema/Schema.Basics.cs index 2528c9aab5..f2b85b9338 100644 --- a/Signum.Engine/Schema/Schema.Basics.cs +++ b/Signum.Engine/Schema/Schema.Basics.cs @@ -37,6 +37,8 @@ public interface ITable SystemVersionedInfo? SystemVersioned { get; } + bool IdentityBehaviour { get; } + FieldEmbedded.EmbeddedHasValueColumn? GetHasValueColumn(IColumn column); } @@ -145,7 +147,7 @@ public partial class Table : IFieldFinder, ITable, ITablePrivate public ObjectName Name { get; set; } - public bool IdentityBehaviour { get; set; } + public bool IdentityBehaviour { get; internal set; } public bool IsView { get; internal set; } public string CleanTypeName { get; set; } @@ -1269,6 +1271,8 @@ IColumn ITable.PrimaryKey get { return PrimaryKey; } } + public bool IdentityBehaviour => true; //For now + internal object[] BulkInsertDataRow(Entity entity, object value, int order) { return this.cache.Value.BulkInsertDataRow(entity, value, order); @@ -1288,32 +1292,36 @@ public IEnumerable> GetTables() } } - public struct AbstractDbType + public struct AbstractDbType : IEquatable { SqlDbType? sqlServer; public SqlDbType SqlServer => sqlServer ?? throw new InvalidOperationException("No SqlDbType type defined"); - NpgsqlDbType? posrtgreSql; - public NpgsqlDbType PostgreSql => posrtgreSql ?? throw new InvalidOperationException("No PostgresSql type defined"); + NpgsqlDbType? postgreSql; + public NpgsqlDbType PostgreSql => postgreSql ?? throw new InvalidOperationException("No PostgresSql type defined"); public AbstractDbType(SqlDbType sqlDbType) { this.sqlServer = sqlDbType; - this.posrtgreSql = null; + this.postgreSql = null; } public AbstractDbType(NpgsqlDbType npgsqlDbType) { this.sqlServer = null; - this.posrtgreSql = npgsqlDbType; + this.postgreSql = npgsqlDbType; } public AbstractDbType(SqlDbType sqlDbType, NpgsqlDbType npgsqlDbType) { this.sqlServer = sqlDbType; - this.posrtgreSql = npgsqlDbType; + this.postgreSql = npgsqlDbType; } + public override bool Equals(object? obj) => obj is AbstractDbType adt && Equals(adt); + public bool Equals(AbstractDbType adt) => this.postgreSql == adt.postgreSql && this.sqlServer == adt.sqlServer; + public override int GetHashCode() => this.postgreSql.GetHashCode() ^ this.sqlServer.GetHashCode(); + public bool IsDate() { if (sqlServer is SqlDbType s) @@ -1328,7 +1336,7 @@ public bool IsDate() return false; } - if (posrtgreSql is NpgsqlDbType p) + if (postgreSql is NpgsqlDbType p) switch (p) { case NpgsqlDbType.Date: @@ -1361,7 +1369,7 @@ public bool IsNumber() return false; } - if (posrtgreSql is NpgsqlDbType p) + if (postgreSql is NpgsqlDbType p) switch (p) { case NpgsqlDbType.Smallint: @@ -1394,7 +1402,7 @@ public bool IsString() } - if (posrtgreSql is NpgsqlDbType p) + if (postgreSql is NpgsqlDbType p) switch (p) { case NpgsqlDbType.Char: @@ -1409,13 +1417,13 @@ public bool IsString() } - public override string? ToString() => throw new InvalidOperationException("use ToString(isPostgress)"); + public override string? ToString() => ToString(Schema.Current.Settings.IsPostgres); public string ToString(bool isPostgres) { if (!isPostgres) - sqlServer.ToString()!.ToUpperInvariant(); + return sqlServer.ToString()!.ToUpperInvariant(); - var pg = posrtgreSql!.Value; + var pg = postgreSql!.Value; if ((pg & NpgsqlDbType.Array) != 0) return (pg & ~NpgsqlDbType.Range).ToString() + "[]"; @@ -1430,6 +1438,9 @@ public string ToString(bool isPostgres) throw new InvalidOperationException(""); } + if (pg == NpgsqlDbType.Double) + return "double precision"; + return pg.ToString()!; } @@ -1445,7 +1456,7 @@ public bool IsGuid() } - if (posrtgreSql is NpgsqlDbType p) + if (postgreSql is NpgsqlDbType p) switch (p) { case NpgsqlDbType.Uuid: @@ -1468,7 +1479,7 @@ internal bool IsDecimal() return false; } - if (posrtgreSql is NpgsqlDbType p) + if (postgreSql is NpgsqlDbType p) switch (p) { case NpgsqlDbType.Numeric: diff --git a/Signum.Engine/Schema/Schema.Save.cs b/Signum.Engine/Schema/Schema.Save.cs index 97610beb67..9eb3058f4b 100644 --- a/Signum.Engine/Schema/Schema.Save.cs +++ b/Signum.Engine/Schema/Schema.Save.cs @@ -85,7 +85,7 @@ internal void InsertMany(List list, DirectedGraph? backEdges) { using (HeavyProfiler.LogNoStackTrace("InsertMany", () => this.Type.TypeName())) { - if (IdentityBehaviour) + if (IdentityBehaviour && !Administrator.IsIdentityBehaviourDisabled(this)) { InsertCacheIdentity ic = inserterIdentity.Value; list.SplitStatements(this.Columns.Count, ls => ic.GetInserter(ls.Count)(ls, backEdges)); @@ -108,7 +108,7 @@ internal object[] BulkInsertDataRow(object/*Entity or IView*/ entity) internal List GetInsertParameters(object entity) { List parameters = new List(); - if (IdentityBehaviour) + if (IdentityBehaviour && !Administrator.IsIdentityBehaviourDisabled(this)) inserterIdentity.Value.InsertParameters((Entity)entity, new Forbidden(), "", parameters); else inserterDisableIdentity.Value.InsertParameters(entity, new Forbidden(), "", parameters); @@ -497,6 +497,11 @@ public UpdateCache(Table table, Func sqlUpdatePattern, Act throw new ConcurrencyException(table.Type, missing); } + if (isPostgres && num > 1) + { + new SqlPreCommandSimple($"DROP TABLE {updated}").ExecuteNonQuery(); + } + if (table.saveCollections.Value != null) table.saveCollections.Value.UpdateCollections(idents.Select(e => new EntityForbidden(e, new Forbidden(graph, e))).ToList()); }; @@ -676,7 +681,7 @@ public SqlPreCommand InsertSqlSync(Entity ident, bool includeCollections = true, SqlPreCommand? collections = GetInsertCollectionSync(ident, includeCollections, suffix); - SqlPreCommandSimple insert = IdentityBehaviour ? + SqlPreCommandSimple insert = IdentityBehaviour && !Administrator.IsIdentityBehaviourDisabled(this) ? new SqlPreCommandSimple( inserterIdentity.Value.SqlInsertPattern(new[] { suffix }, isGuid && collections != null), new List().Do(dbParams => inserterIdentity.Value.InsertParameters(ident, new Forbidden(), suffix, dbParams))).AddComment(comment) : @@ -775,7 +780,7 @@ public class Trio public Trio(IColumn column, Expression value, Expression suffix) { this.SourceColumn = column.Name; - this.ParameterName = Engine.ParameterBuilder.GetParameterName(column.Name); + this.ParameterName = Signum.Engine.ParameterBuilder.GetParameterName(column.Name); this.ParameterBuilder = Connector.Current.ParameterBuilder.ParameterFactory(Concat(this.ParameterName, suffix), column.DbType, column.UserDefinedTypeName, column.Nullable.ToBool(), value); } diff --git a/Signum.Engine/Schema/Schema.cs b/Signum.Engine/Schema/Schema.cs index 5caecf0873..ab9cfd713c 100644 --- a/Signum.Engine/Schema/Schema.cs +++ b/Signum.Engine/Schema/Schema.cs @@ -61,7 +61,7 @@ public Dictionary Tables get { return tables; } } - public List PostgreeExtensions = new List() + public List PostgresExtensions = new List() { "uuid-ossp" }; @@ -553,7 +553,7 @@ internal Schema(SchemaSettings settings) this.ViewBuilder = new Maps.ViewBuilder(this); Generating += SchemaGenerator.SnapshotIsolation; - Generating += SchemaGenerator.PostgreeExtensions; + Generating += SchemaGenerator.PostgresExtensions; Generating += SchemaGenerator.PostgreeTemporalTableScript; Generating += SchemaGenerator.CreateSchemasScript; Generating += SchemaGenerator.CreateTablesScript; diff --git a/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs b/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs index b8f9e71e28..15dd94f2f9 100644 --- a/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs +++ b/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs @@ -440,7 +440,7 @@ protected virtual Field GenerateField(ITable table, PropertyRoute route, NameSeq { using (HeavyProfiler.LogNoStackTrace("GenerateField", () => route.ToString())) { - KindOfField kof = GetKindOfField(route).ThrowIfNull(() => "Field {0} of type {1} has no database representation".FormatWith(route, route.Type.Name)); + KindOfField kof = GetKindOfField(route); if (kof == KindOfField.MList && inMList) throw new InvalidOperationException("Field {0} of type {1} can not be nested in another MList".FormatWith(route, route.Type.TypeName(), kof)); @@ -498,7 +498,7 @@ public enum KindOfField MList, } - protected virtual KindOfField? GetKindOfField(PropertyRoute route) + protected virtual KindOfField GetKindOfField(PropertyRoute route) { if (route.FieldInfo != null && ReflectionTools.FieldEquals(route.FieldInfo, fiId)) return KindOfField.PrimaryKey; @@ -521,7 +521,13 @@ public enum KindOfField if (Reflector.IsMList(route.Type)) return KindOfField.MList; - return null; + if (Settings.IsPostgres && route.Type.IsArray) + { + if (Settings.TryGetSqlDbType(Settings.FieldAttribute(route), route.Type.ElementType()!) != null) + return KindOfField.Value; + } + + throw new InvalidOperationException($"Field {route} of type {route.Type.Name} has no database representation"); } protected virtual Field GenerateFieldPrimaryKey(Table table, PropertyRoute route, NameSequence name) @@ -584,7 +590,9 @@ protected virtual FieldValue GenerateFieldValue(ITable table, PropertyRoute rout { var att = Settings.FieldAttribute(route); - DbTypePair pair = Settings.GetSqlDbType(att, route.Type); + DbTypePair pair = Settings.IsPostgres && route.Type.IsArray ? + Settings.GetSqlDbType(att, route.Type.ElementType()!) : + Settings.GetSqlDbType(att, route.Type); return new FieldValue(route, null, name.ToString()) { @@ -835,7 +843,7 @@ public virtual string GenerateMListFieldName(PropertyRoute route, KindOfField ki { case KindOfField.Value: case KindOfField.Embedded: - return type.Name.FirstUpper(); + return type.Name; case KindOfField.Enum: case KindOfField.Reference: return (EnumEntity.Extract(type)?.Name ?? Reflector.CleanTypeName(type)) + "ID"; @@ -847,7 +855,7 @@ public virtual string GenerateMListFieldName(PropertyRoute route, KindOfField ki public virtual string GenerateFieldName(PropertyRoute route, KindOfField kindOfField) { string name = route.PropertyInfo != null ? (route.PropertyInfo.Name.TryAfterLast('.') ?? route.PropertyInfo.Name) - : route.FieldInfo!.Name.FirstUpper(); + : route.FieldInfo!.Name; switch (kindOfField) { diff --git a/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs b/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs index 609cfa5359..6d86835af1 100644 --- a/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs +++ b/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs @@ -73,8 +73,9 @@ public void Desambiguate(Type type, string cleanName) {typeof(string), new AbstractDbType(SqlDbType.NVarChar, NpgsqlDbType.Varchar)}, {typeof(Date), new AbstractDbType(SqlDbType.Date, NpgsqlDbType.Date)}, {typeof(DateTime), new AbstractDbType(SqlDbType.DateTime2, NpgsqlDbType.Timestamp)}, - {typeof(DateTimeOffset), new AbstractDbType(SqlDbType.DateTimeOffset, NpgsqlDbType.TimestampTz)}, - + {typeof(DateTimeOffset), new AbstractDbType(SqlDbType.DateTimeOffset/*, NpgsqlDbType.TimestampTz*/)}, + {typeof(TimeSpan), new AbstractDbType(SqlDbType.Time, NpgsqlDbType.Time)}, + {typeof(byte[]), new AbstractDbType(SqlDbType.VarBinary, NpgsqlDbType.Bytea)}, {typeof(Guid), new AbstractDbType(SqlDbType.UniqueIdentifier, NpgsqlDbType.Uuid)}, @@ -93,7 +94,6 @@ public void Desambiguate(Type type, string cleanName) readonly Dictionary defaultSizePostgreSql = new Dictionary() { - {NpgsqlDbType.Bytea, int.MaxValue}, {NpgsqlDbType.Varbit, 200}, {NpgsqlDbType.Varchar, 200}, {NpgsqlDbType.Char, 1}, @@ -326,6 +326,9 @@ internal DbTypePair GetSqlDbType(DbTypeAttribute? att, Type type) internal int? GetSqlSize(DbTypeAttribute? att, PropertyRoute? route, AbstractDbType dbType) { + if (this.IsPostgres && dbType.PostgreSql == NpgsqlDbType.Bytea) + return null; + if (att != null && att.HasSize) return att.Size; @@ -336,7 +339,7 @@ internal DbTypePair GetSqlDbType(DbTypeAttribute? att, Type type) return sla.Max == -1 ? int.MaxValue : sla.Max; } - if (this.IsPostgres) + if (!this.IsPostgres) return defaultSizeSqlServer.TryGetS(dbType.SqlServer); else return defaultSizePostgreSql.TryGetS(dbType.PostgreSql); @@ -358,7 +361,7 @@ internal DbTypePair GetSqlDbType(DbTypeAttribute? att, Type type) return dv.DecimalPlaces; } - if (this.IsPostgres) + if (!this.IsPostgres) return defaultScaleSqlServer.TryGetS(dbType.SqlServer); else return defaultScalePostgreSql.TryGetS(dbType.PostgreSql); diff --git a/Signum.Engine/Schema/UniqueTableIndex.cs b/Signum.Engine/Schema/UniqueTableIndex.cs index 8bf3a0fdd4..4f9fb00d8f 100644 --- a/Signum.Engine/Schema/UniqueTableIndex.cs +++ b/Signum.Engine/Schema/UniqueTableIndex.cs @@ -45,20 +45,31 @@ public TableIndex(ITable table, params IColumn[] columns) public virtual string GetIndexName(ObjectName tableName) { - return "IX_{0}_{1}".FormatWith(tableName.Name, ColumnSignature()).TryStart(Connector.Current.MaxNameLength); + int maxLength = MaxNameLength(); + + return StringHashEncoder.ChopHash("IX_{0}_{1}".FormatWith(tableName.Name, ColumnSignature()), maxLength) + WhereSignature(); + } + + protected static int MaxNameLength() + { + return Connector.Current.MaxNameLength - StringHashEncoder.HashSize - 1; } public string IndexName => GetIndexName(Table.Name); protected string ColumnSignature() { - string columns = Columns.ToString(c => c.Name, "_"); + return Columns.ToString(c => c.Name, "_"); + } + + protected string? WhereSignature() + { var includeColumns = IncludeColumns.HasItems() ? IncludeColumns.ToString(c => c.Name, "_") : null; - if (string.IsNullOrEmpty(Where) && includeColumns == null) - return columns; + if (string.IsNullOrEmpty(Where) && includeColumns == null) + return null; - return columns + "__" + StringHashEncoder.Codify(Where + includeColumns); + return "__" + StringHashEncoder.Codify(Where + includeColumns); } public override string ToString() @@ -97,7 +108,9 @@ public UniqueTableIndex(ITable table, IColumn[] columns) public override string GetIndexName(ObjectName tableName) { - return "UIX_{0}_{1}".FormatWith(tableName.Name, ColumnSignature()).TryStart(Connector.Current.MaxNameLength); + var maxSize = MaxNameLength(); + + return StringHashEncoder.ChopHash("UIX_{0}_{1}".FormatWith(tableName.Name, ColumnSignature()), maxSize) + WhereSignature(); } public string? ViewName @@ -110,7 +123,9 @@ public string? ViewName if (Connector.Current.AllowsIndexWithWhere(Where)) return null; - return "VIX_{0}_{1}".FormatWith(Table.Name.Name, ColumnSignature()).TryStart(Connector.Current.MaxNameLength); + var maxSize = MaxNameLength(); + + return StringHashEncoder.ChopHash("VIX_{0}_{1}".FormatWith(Table.Name.Name, ColumnSignature()), maxSize) + WhereSignature(); } } diff --git a/Signum.Entities/Entity.cs b/Signum.Entities/Entity.cs index df21169d94..8f719c40c5 100644 --- a/Signum.Entities/Entity.cs +++ b/Signum.Entities/Entity.cs @@ -21,7 +21,7 @@ public abstract class Entity : ModifiableEntity, IEntity internal PrimaryKey? id; - [Ignore, DebuggerBrowsable(DebuggerBrowsableState.Never)] + [Ignore, DebuggerBrowsable(DebuggerBrowsableState.Never), ColumnName("ToStr")] protected internal string? toStr; //for queries and lites on entities with non-expression ToString [HiddenProperty, Description("Id")] diff --git a/Signum.Entities/Reflection/StringHashEncoder.cs b/Signum.Entities/Reflection/StringHashEncoder.cs index f6660cfc0e..67a83abedd 100644 --- a/Signum.Entities/Reflection/StringHashEncoder.cs +++ b/Signum.Entities/Reflection/StringHashEncoder.cs @@ -1,9 +1,18 @@ -using System.Text; +using System.Text; namespace Signum.Entities.Reflection { public static class StringHashEncoder { + public const int HashSize = 7; + public static string ChopHash(string str, int maxLength) + { + if (str.Length > maxLength) + return str.Substring(0, maxLength - HashSize) + Codify(str.Substring(maxLength - HashSize)); + + return str; + } + static readonly string letters = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; public static string Codify(string str) diff --git a/Signum.Test/Environment/Entities.cs b/Signum.Test/Environment/Entities.cs index d7b006f83a..38640f592d 100644 --- a/Signum.Test/Environment/Entities.cs +++ b/Signum.Test/Environment/Entities.cs @@ -361,14 +361,14 @@ public class EmbeddedConfigEmbedded : EmbeddedEntity public static class MinimumExtensions { - [SqlMethod(Name = "dbo.MinimumTableValued")] + [SqlMethod(Name = "MinimumTableValued")] public static IQueryable MinimumTableValued(int? a, int? b) { throw new InvalidOperationException("sql only"); } - [SqlMethod(Name = "dbo.MinimumScalar")] + [SqlMethod(Name = "MinimumScalar")] public static int? MinimumScalar(int? a, int? b) { throw new InvalidOperationException("sql only"); diff --git a/Signum.Test/Environment/MusicStarter.cs b/Signum.Test/Environment/MusicStarter.cs index 88b5fc5584..1844dd97de 100644 --- a/Signum.Test/Environment/MusicStarter.cs +++ b/Signum.Test/Environment/MusicStarter.cs @@ -47,11 +47,16 @@ public static void Start(string connectionString) { SchemaBuilder sb = new SchemaBuilder(true); - var postgreeVersion = PostgresVersionDetector.Detect(connectionString); - Connector.Default = new PostgreSqlConnector(connectionString, sb.Schema, postgreeVersion); - - //var sqlVersion = SqlServerVersionDetector.Detect(connectionString); - //Connector.Default = new SqlConnector(connectionString, sb.Schema, sqlVersion ?? SqlServerVersion.SqlServer2017); + if (connectionString.Contains("Data Source")) + { + var sqlVersion = SqlServerVersionDetector.Detect(connectionString); + Connector.Default = new SqlConnector(connectionString, sb.Schema, sqlVersion ?? SqlServerVersion.SqlServer2017); + } + else + { + var postgreeVersion = PostgresVersionDetector.Detect(connectionString); + Connector.Default = new PostgreSqlConnector(connectionString, sb.Schema, postgreeVersion); + } sb.Schema.Version = typeof(MusicStarter).Assembly.GetName().Version!; @@ -63,12 +68,6 @@ public static void Start(string connectionString) sb.Schema.Settings.TypeAttributes().Add(new SystemVersionedAttribute()); } - if (!Schema.Current.Settings.TypeValues.ContainsKey(typeof(TimeSpan))) - { - sb.Settings.FieldAttributes((AlbumEntity a) => a.Songs[0].Duration).Add(new Signum.Entities.IgnoreAttribute()); - sb.Settings.FieldAttributes((AlbumEntity a) => a.BonusTrack!.Duration).Add(new Signum.Entities.IgnoreAttribute()); - } - if(Connector.Default is SqlConnector c && c.Version > SqlServerVersion.SqlServer2008) { sb.Settings.UdtSqlName.Add(typeof(SqlHierarchyId), "HierarchyId"); diff --git a/Signum.Test/LinqProvider/SelectTest.cs b/Signum.Test/LinqProvider/SelectTest.cs index 5c6f42b9cc..18915180d6 100644 --- a/Signum.Test/LinqProvider/SelectTest.cs +++ b/Signum.Test/LinqProvider/SelectTest.cs @@ -8,7 +8,7 @@ using System.Linq; using System.Linq.Expressions; using Signum.Utilities.DataStructures; - +using Signum.Engine.Maps; namespace Signum.Test.LinqProvider { @@ -649,7 +649,10 @@ public void SelectRetrieve() [Fact] public void SelectWithHint() { - var list = Database.Query().WithHint("INDEX(IX_LabelID)").Select(a => a.Label.Name).ToList(); + if (!Schema.Current.Settings.IsPostgres) + { + var list = Database.Query().WithHint("INDEX(IX_LabelID)").Select(a => a.Label.Name).ToList(); + } } [Fact] diff --git a/Signum.Test/LinqProvider/SingleFirstTest.cs b/Signum.Test/LinqProvider/SingleFirstTest.cs index aba93dd365..1007147644 100644 --- a/Signum.Test/LinqProvider/SingleFirstTest.cs +++ b/Signum.Test/LinqProvider/SingleFirstTest.cs @@ -5,6 +5,7 @@ using Signum.Entities; using Signum.Utilities.ExpressionTrees; using Signum.Test.Environment; +using Signum.Engine.Maps; namespace Signum.Test.LinqProvider { @@ -70,7 +71,7 @@ public void SelectDoubleSingle() query.ToList(); - Assert.Equal(1, query.QueryText().CountRepetitions("APPLY")); + Assert.Equal(1, query.QueryText().CountRepetitions(Schema.Current.Settings.IsPostgres ? "LATERAL" : "APPLY")); } [Fact] diff --git a/Signum.Test/LinqProvider/TakeSkipTest.cs b/Signum.Test/LinqProvider/TakeSkipTest.cs index 6230482fb7..97f51330bc 100644 --- a/Signum.Test/LinqProvider/TakeSkipTest.cs +++ b/Signum.Test/LinqProvider/TakeSkipTest.cs @@ -97,7 +97,11 @@ public void SkipTakeOrder() [Fact] public void InnerTake() { - var result = Database.Query().Where(dr => dr.Songs.OrderByDescending(a => a.Seconds).Take(1).Where(a => a.Name.Contains("1976")).Any()).Select(a => a.ToLite()).ToList(); + var result = Database.Query() + .Where(dr => dr.Songs.OrderByDescending(a => a.Seconds).Take(1).Where(a => a.Name.Contains("Tonight")).Any()) + .Select(a => a.ToLite()) + .ToList(); + Assert.Empty(result); } @@ -112,7 +116,7 @@ public void OrderBySelectPaginate() { TestPaginate(Database.Query().OrderBy(a => a.Name).Select(a => a.Name)); } - + [Fact] public void OrderByDescendingSelectPaginate() { @@ -139,14 +143,14 @@ public void SelectOrderByDescendingPaginate() private void TestPaginate(IQueryable query) { - var list = query.ToList(); + var list = query.OrderAlsoByKeys().ToList(); int pageSize = 2; var list2 = 0.To(((list.Count / pageSize) + 1)).SelectMany(page => query.OrderAlsoByKeys().Skip(pageSize * page).Take(pageSize).ToList()).ToList(); - Assert.True(list.SequenceEqual(list2)); + Assert.Equal(list, list2); } } } diff --git a/Signum.Test/LinqProvider/ToStringTest.cs b/Signum.Test/LinqProvider/ToStringTest.cs index ba5af86d42..4592c201b2 100644 --- a/Signum.Test/LinqProvider/ToStringTest.cs +++ b/Signum.Test/LinqProvider/ToStringTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Xunit; @@ -65,7 +65,7 @@ orderby b.Name select new { b.Name, - MembersToString = Database.Query().Where(a => a.Author == b).OrderBy(a => a.Name).ToString(a => a.Name, " | "), + AlbumnsToString = Database.Query().Where(a => a.Author == b).OrderBy(a => a.Name).ToString(a => a.Name, " | "), }).ToList(); var result2 = (from b in Database.Query() @@ -73,10 +73,10 @@ orderby b.Name select new { b.Name, - MembersToString = Database.Query().Where(a => a.Author == b).OrderBy(a => a.Name).Select(a => a.Name).ToList().ToString(" | "), + AlbumnsToString = Database.Query().Where(a => a.Author == b).OrderBy(a => a.Name).Select(a => a.Name).ToList().ToString(" | "), }).ToList(); - Assert.True(Enumerable.SequenceEqual(result1, result2)); + Assert.Equal(result1, result2); } @@ -88,7 +88,7 @@ orderby b.Name select new { b.Name, - MembersToString = Database.Query().Where(a => a.Author == b).OrderBy(a => a.Name).ToString(a => a.Id.ToString(), " | "), + AlbumnsToString = Database.Query().Where(a => a.Author == b).OrderBy(a => a.Name).ToString(a => a.Id.ToString(), " | "), }).ToList(); var result2 = (from b in Database.Query() @@ -96,7 +96,7 @@ orderby b.Name select new { b.Name, - MembersToString = Database.Query().Where(a => a.Author == b).OrderBy(a => a.Name).Select(a => a.Id).ToString(" | "), + AlbumnsToString = Database.Query().Where(a => a.Author == b).OrderBy(a => a.Name).Select(a => a.Id).ToString(" | "), }).ToList(); Func, string> toString = list => list.ToString(" | "); @@ -106,7 +106,7 @@ orderby b.Name select new { b.Name, - MembersToString = toString(Database.Query().Where(a => a.Author == b).OrderBy(a => a.Name).Select(a => a.Id).ToList()), + AlbumnsToString = toString(Database.Query().Where(a => a.Author == b).OrderBy(a => a.Name).Select(a => a.Id).ToList()), }).ToList(); diff --git a/Signum.Test/ObjectNameTest.cs b/Signum.Test/ObjectNameTest.cs index f872cf2b50..35c018a633 100644 --- a/Signum.Test/ObjectNameTest.cs +++ b/Signum.Test/ObjectNameTest.cs @@ -1,51 +1,92 @@ using Xunit; using Signum.Engine.Maps; using System.Globalization; +using Signum.Test.Environment; +using Signum.Engine; +using Signum.Utilities; namespace Signum.Test { public class ObjectNameTest { - bool isPostgree = Signum.Engine.Connector.Current.Schema.Settings.IsPostgres; + bool isPostgres; + public ObjectNameTest() + { + MusicStarter.StartAndLoad(); + Connector.CurrentLogger = new DebugTextWriter(); + this.isPostgres = Signum.Engine.Connector.Current.Schema.Settings.IsPostgres; + } [Fact] public void ParseDbo() { - var simple = ObjectName.Parse("MyTable", isPostgree); + var simple = ObjectName.Parse("MyTable", isPostgres); Assert.Equal("MyTable", simple.Name); - Assert.Equal("dbo", simple.Schema.ToString()); + Assert.Equal(isPostgres? "\"public\"" : "dbo", simple.Schema.ToString()); } [Fact] public void ParseSchema() { - var simple = ObjectName.Parse("MySchema.MyTable", isPostgree); - Assert.Equal("MyTable", simple.Name); - Assert.Equal("MySchema", simple.Schema.ToString()); + if (isPostgres) + { + var simple = ObjectName.Parse("my_schema.my_table", isPostgres); + Assert.Equal("my_table", simple.Name); + Assert.Equal("my_schema", simple.Schema.ToString()); + } + else + { + var simple = ObjectName.Parse("MySchema.MyTable", isPostgres); + Assert.Equal("MyTable", simple.Name); + Assert.Equal("MySchema", simple.Schema.ToString()); + } } [Fact] public void ParseNameEscaped() { - var simple = ObjectName.Parse("MySchema.[Select]", isPostgree); - Assert.Equal("Select", simple.Name); - Assert.Equal("MySchema", simple.Schema.ToString()); - Assert.Equal("MySchema.[Select]", simple.ToString()); + if (isPostgres) + { + var simple = ObjectName.Parse("\"MySchema\".\"Select\"", isPostgres); + Assert.Equal("Select", simple.Name); + Assert.Equal("\"MySchema\"", simple.Schema.ToString()); + Assert.Equal("\"MySchema\".\"Select\"", simple.ToString()); + } + else + { + var simple = ObjectName.Parse("MySchema.[Select]", isPostgres); + Assert.Equal("Select", simple.Name); + Assert.Equal("MySchema", simple.Schema.ToString()); + Assert.Equal("MySchema.[Select]", simple.ToString()); + } } [Fact] public void ParseSchemaNameEscaped() { - var simple = ObjectName.Parse("[Select].MyTable", isPostgree); - Assert.Equal("MyTable", simple.Name); - Assert.Equal("Select", simple.Schema.Name); - Assert.Equal("[Select].MyTable", simple.ToString()); + if (isPostgres) + { + var simple = ObjectName.Parse("\"Select\".MyTable", isPostgres); + Assert.Equal("mytable", simple.Name); + Assert.Equal("Select", simple.Schema.Name); + Assert.Equal("\"Select\".mytable", simple.ToString()); + } + else + { + var simple = ObjectName.Parse("[Select].MyTable", isPostgres); + Assert.Equal("MyTable", simple.Name); + Assert.Equal("Select", simple.Schema.Name); + Assert.Equal("[Select].MyTable", simple.ToString()); + } } [Fact] public void ParseServerName() { - var simple = ObjectName.Parse("[FROM].[SELECT].[WHERE].[TOP]", isPostgree); + var simple = ObjectName.Parse(isPostgres ? + "\"FROM\".\"SELECT\".\"WHERE\".\"TOP\"" : + "[FROM].[SELECT].[WHERE].[TOP]", + isPostgres); Assert.Equal("TOP", simple.Name); Assert.Equal("WHERE", simple.Schema.Name); Assert.Equal("SELECT", simple.Schema.Database!.Name); @@ -56,7 +97,10 @@ public void ParseServerName() [Fact] public void ParseServerNameSuperComplex() { - var simple = ObjectName.Parse("[FROM].[SELECT].[WHERE].[TOP.DISTINCT]", isPostgree); + var simple = ObjectName.Parse(isPostgres ? + "\"FROM\".\"SELECT\".\"WHERE\".\"TOP.DISTINCT\"" : + "[FROM].[SELECT].[WHERE].[TOP.DISTINCT]", + isPostgres); Assert.Equal("TOP.DISTINCT", simple.Name); Assert.Equal("WHERE", simple.Schema.Name); Assert.Equal("SELECT", simple.Schema.Database!.Name); diff --git a/Signum.Utilities/ExpressionTrees/ExpressionCleaner.cs b/Signum.Utilities/ExpressionTrees/ExpressionCleaner.cs index 1ed4208912..6dfec3619d 100644 --- a/Signum.Utilities/ExpressionTrees/ExpressionCleaner.cs +++ b/Signum.Utilities/ExpressionTrees/ExpressionCleaner.cs @@ -313,6 +313,23 @@ protected override Expression VisitBinary(BinaryExpression b) return Expression.MakeBinary(b.NodeType, left, right, b.IsLiftedToNull, b.Method); } + // rel == 'a' is compiled as (int)rel == 123 + if(b.NodeType == ExpressionType.Equal || + b.NodeType == ExpressionType.NotEqual) + { + { + if (IsConvertCharToInt(b.Left) is Expression l && + IsConvertCharToIntOrConstant(b.Right) is Expression r) + return Expression.MakeBinary(b.NodeType, Visit(l), Visit(r)); + } + { + if (IsConvertCharToIntOrConstant(b.Left) is Expression l && + IsConvertCharToInt(b.Right) is Expression r) + return Expression.MakeBinary(b.NodeType, Visit(l), Visit(r)); + } + + } + if (b.Left.Type != typeof(bool)) return base.VisitBinary(b); @@ -344,6 +361,31 @@ protected override Expression VisitBinary(BinaryExpression b) return base.VisitBinary(b); } + static Expression? IsConvertCharToInt(Expression exp) + { + if (exp is UnaryExpression ue && ue.NodeType == ExpressionType.Convert && ue.Operand.Type == typeof(char)) + { + return ue.Operand; + } + + return null; + } + + static Expression? IsConvertCharToIntOrConstant(Expression exp) + { + var result = IsConvertCharToInt(exp); + if (result != null) + return result; + + if (exp is ConstantExpression ceInt && ceInt.Type == typeof(int)) + return Expression.Constant((char)(int)ceInt.Value, typeof(char)); + + if (exp is ConstantExpression ceChar && ceChar.Type == typeof(char)) + return ceChar; + + return null; + } + protected override Expression VisitConditional(ConditionalExpression c) { if (!shortCircuit) From 780037711977885faa09625abbe633b11ab6001f Mon Sep 17 00:00:00 2001 From: Olmo del Corral Date: Mon, 13 Jan 2020 23:27:55 +0100 Subject: [PATCH 05/13] clean sync in Postgres Southwind --- .../CodeGeneration/EntityCodeGenerator.cs | 8 +-- Signum.Engine/Engine/PostgresCatalog.cs | 16 ++++- Signum.Engine/Engine/PostgresCatalogSchema.cs | 54 ++++++++++----- Signum.Engine/Engine/PostgresFunctions.cs | 11 ++++ Signum.Engine/Engine/SchemaGenerator.cs | 2 +- Signum.Engine/Engine/SchemaSynchronizer.cs | 65 ++++++++++--------- Signum.Engine/Engine/SqlBuilder.cs | 17 ++--- Signum.Engine/Engine/Synchronizer.cs | 11 +++- Signum.Engine/Engine/SysTablesSchema.cs | 1 + .../ChildProjectionFlattener.cs | 36 +++++++++- .../DbExpressionNominator.cs | 18 ++--- Signum.Engine/Schema/Schema.Basics.cs | 11 +++- Signum.Engine/Schema/SchemaAssets.cs | 54 +++++++++++---- .../Schema/SchemaBuilder/SchemaBuilder.cs | 16 ++--- Signum.Engine/Schema/UniqueTableIndex.cs | 4 +- Signum.Entities/FieldAttributes.cs | 18 +++-- 16 files changed, 237 insertions(+), 105 deletions(-) diff --git a/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs b/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs index a305e93a5e..2ce50162f4 100644 --- a/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs +++ b/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs @@ -639,7 +639,7 @@ protected virtual IEnumerable GetPropertyAttributes(DiffTable table, Dif parts.Add("Min = " + min); if (col.Length != -1) - parts.Add("Max = " + col.Length / DiffColumn.BytesPerChar(col.DbType.SqlServer)); + parts.Add("Max = " + col.Length); return "StringLengthValidator(" + parts.ToString(", ") + ")"; } @@ -709,7 +709,7 @@ protected virtual bool HasUniqueIndex(DiffTable table, DiffColumn col) ix.FilterDefinition == null && ix.Columns.Only()?.Let(ic => ic.ColumnName == col.Name && ic.IsIncluded == false) == true && ix.IsUnique && - ix.Type == DiffIndexType.NonClustered); + ix.IsPrimary); } protected virtual string DefaultColumnName(DiffTable table, DiffColumn col) @@ -752,9 +752,9 @@ protected virtual List GetSqlDbTypeParts(DiffColumn col, Type type) var defaultSize = CurrentSchema.Settings.GetSqlSize(null, null, pair.DbType); if (defaultSize != null) { - if (!(defaultSize == col.Precision || defaultSize == col.Length / DiffColumn.BytesPerChar(col.DbType.SqlServer) || defaultSize == int.MaxValue && col.Length == -1)) + if (!(defaultSize == col.Precision || defaultSize == col.Length || defaultSize == int.MaxValue && col.Length == -1)) parts.Add("Size = " + (col.Length == -1 ? "int.MaxValue" : - col.Length != 0 ? (col.Length / DiffColumn.BytesPerChar(col.DbType.SqlServer)).ToString() : + col.Length != 0 ? col.Length.ToString() : col.Precision != 0 ? col.Precision.ToString() : "0")); } diff --git a/Signum.Engine/Engine/PostgresCatalog.cs b/Signum.Engine/Engine/PostgresCatalog.cs index f232773753..4cd6fbafb1 100644 --- a/Signum.Engine/Engine/PostgresCatalog.cs +++ b/Signum.Engine/Engine/PostgresCatalog.cs @@ -33,7 +33,7 @@ public class PgClass : IView [AutoExpressionField] public IQueryable Triggers() => - As.Expression(() => Database.View().Where(t => t.tgfoid == this.oid)); + As.Expression(() => Database.View().Where(t => t.tgrelid == this.oid)); [AutoExpressionField] public IQueryable Indices() => @@ -88,7 +88,7 @@ public class PgAttribute : IView public PgType Type() => As.Expression(() => Database.View().SingleOrDefault(t => t.oid == this.atttypid)); [AutoExpressionField] - public PgAttributeDef? Default() => As.Expression(() => Database.View().SingleOrDefault(t => t.oid == this.attrelid && t.adnum == this.attnum)); + public PgAttributeDef? Default() => As.Expression(() => Database.View().SingleOrDefault(d => d.adrelid == this.attrelid && d.adnum == this.attnum)); } [TableName("pg_catalog.pg_attrdef")] @@ -139,6 +139,12 @@ public class PgProc : IView [ViewPrimaryKey] public int oid; + public int pronamespace; + + [AutoExpressionField] + public PgNamespace Namespace() => + As.Expression(() => Database.View().SingleOrDefault(t => t.oid == this.pronamespace)); + public string proname; } @@ -150,6 +156,10 @@ public class PgIndex : IView public int indrelid; + public short indnatts; + + public short indnkeyatts; + public bool indisunique; public bool indisprimary; @@ -186,7 +196,7 @@ public class PgConstraint : IView [AutoExpressionField] public PgClass TargetTable() => - As.Expression(() => Database.View().Single(t => t.oid == this.confrelid)); + As.Expression(() => Database.View().Single(t => t.oid == this.confrelid)); [AutoExpressionField] public PgNamespace Namespace() => diff --git a/Signum.Engine/Engine/PostgresCatalogSchema.cs b/Signum.Engine/Engine/PostgresCatalogSchema.cs index ceb7a2467b..08ee2df499 100644 --- a/Signum.Engine/Engine/PostgresCatalogSchema.cs +++ b/Signum.Engine/Engine/PostgresCatalogSchema.cs @@ -12,7 +12,7 @@ namespace Signum.Engine.Engine { public static class PostgresCatalogSchema { - static string[] systemSchemas = new[] { "pg_catalog", "pg_toast", "information_schema" }; + public static string[] systemSchemas = new[] { "pg_catalog", "pg_toast", "information_schema" }; public static Dictionary GetDatabaseDescription(List databases) { @@ -40,7 +40,7 @@ from t in s.Tables() { Name = new ObjectName(new SchemaName(db, s.nspname, isPostgres), t.relname, isPostgres), - TemporalType = !con.SupportsTemporalTables ? Signum.Engine.SysTableTemporalType.None : t.Triggers().Any(t => t.Proc().proname.StartsWith("versioning_function")) ? Signum.Engine.SysTableTemporalType.SystemVersionTemporalTable : SysTableTemporalType.None, + TemporalType = t.Triggers().Any(t => t.Proc().proname == "versioning") ? Signum.Engine.SysTableTemporalType.SystemVersionTemporalTable : SysTableTemporalType.None, //Period = !con.SupportsTemporalTables ? null : @@ -53,23 +53,24 @@ from t in s.Tables() // EndColumnName = ec.name, // }).SingleOrDefaultEx(), + TemporalTableName = t.Triggers() + .Where(t => t.Proc().proname == "versioning") + .Select(t => ParseVersionFunctionParam(t.tgargs)) + .SingleOrDefaultEx(), + //TemporalTableName = !con.SupportsTemporalTables || t.history_table_id == null ? null : // Database.View() // .Where(ht => ht.object_id == t.history_table_id) // .Select(ht => new ObjectName(new SchemaName(db, ht.Schema().name, isPostgres), ht.name, isPostgres)) // .SingleOrDefault(), - PrimaryKeyName = (from ind in t.Indices() - where ind.indisprimary == true -#pragma warning disable CS0472 - select ((int?)ind.indexrelid) == null ? null : new ObjectName(new SchemaName(db, ind.Class().Namespace().nspname, isPostgres), ind.Class().relname, isPostgres)) -#pragma warning restore CS0472 + PrimaryKeyName = (from c in t.Constraints() + where c.contype == ConstraintType.PrimaryKey + select c.conname == null ? null : new ObjectName(new SchemaName(db, c.Namespace().nspname, isPostgres), c.conname, isPostgres)) .SingleOrDefaultEx(), Columns = (from c in t.Attributes() - //join userType in Database.View().DefaultIfEmpty() on c.user_type_id equals userType.user_type_id - //join sysType in Database.View().DefaultIfEmpty() on c.system_type_id equals sysType.user_type_id - //join ctr in Database.View().DefaultIfEmpty() on c.default_object_id equals ctr.object_id + let def = c.Default() select new DiffColumn { Name = c.attname, @@ -77,14 +78,14 @@ from t in s.Tables() UserTypeName = null, Nullable = !c.attnotnull, Collation = null, - Length = c.attlen, + Length = PostgresFunctions._pg_char_max_length(c.atttypid, c.atttypmod) ?? -1, Precision = c.atttypid == 1700 /*numeric*/ ? ((c.atttypmod - 4) >> 16) & 65535 : 0, Scale = c.atttypid == 1700 /*numeric*/ ? (c.atttypmod - 4) & 65535 : 0, Identity = c.attidentity == 'a', GeneratedAlwaysType = GeneratedAlwaysType.None, - DefaultConstraint = c.Default() == null ? null : new DiffDefaultConstraint + DefaultConstraint = def == null ? null : new DiffDefaultConstraint { - Definition = pg_get_expr(c.Default()!.adbin, c.Default()!.adrelid), + Definition = pg_get_expr(def.adbin, def.adrelid), }, PrimaryKey = t.Indices().Any(i => i.indisprimary && i.indkey.Contains(c.attnum)), }).ToDictionaryEx(a => a.Name, "columns"), @@ -96,7 +97,7 @@ from t in s.Tables() Name = new ObjectName(new SchemaName(db, fk.Namespace().nspname, isPostgres), fk.conname, isPostgres), IsDisabled = false, TargetTable = new ObjectName(new SchemaName(db, fk.TargetTable().Namespace().nspname, isPostgres), fk.TargetTable().relname, isPostgres), - Columns = PostgresFunctions.generate_subscripts(fk.conkey, 0).Select(i => new DiffForeignKeyColumn + Columns = PostgresFunctions.generate_subscripts(fk.conkey, 1).Select(i => new DiffForeignKeyColumn { Parent = t.Attributes().Single(c => c.attnum == fk.conkey[i]).attname, Referenced = fk.TargetTable().Attributes().Single(c => c.attnum == fk.confkey[i]).attname, @@ -104,7 +105,6 @@ from t in s.Tables() }).ToList(), SimpleIndices = (from i in t.Indices() - where !i.indisprimary select new DiffIndex { IsUnique = i.indisunique, @@ -114,7 +114,7 @@ from t in s.Tables() Type = DiffIndexType.NonClustered, Columns = (from at in i.Class().Attributes() orderby at.attnum - select new DiffIndexColumn { ColumnName = at.attname, IsIncluded = !i.indkey.Contains(at.attnum) }).ToList() + select new DiffIndexColumn { ColumnName = at.attname, IsIncluded = at.attnum > i.indnkeyatts }).ToList() }).ToList(), ViewIndices = new List(), @@ -137,9 +137,29 @@ orderby at.attnum var database = allTables.ToDictionary(t => t.Name.ToString()); + var historyTables = database.Values.Select(a => a.TemporalTableName).NotNull().ToList(); + + historyTables.ForEach(h => + { + var t = database.TryGetC(h.ToString()); + if (t != null) + t.TemporalType = SysTableTemporalType.HistoryTable; + }); + return database; } + private static ObjectName? ParseVersionFunctionParam(byte[]? tgargs) + { + if (tgargs == null) + return null; + + var str = Encoding.UTF8.GetString(tgargs!); + + var args = str.Split("\0"); + + return ObjectName.Parse(args[1], isPostgres: true); + } public static NpgsqlDbType ToNpgsqlDbType(string str) { @@ -210,7 +230,7 @@ public static HashSet GetSchemaNames(List list) { var schemaNames = Database.View().Select(s => s.nspname).ToList(); - result.AddRange(schemaNames.Select(sn => new SchemaName(db, sn, isPostgres)).Where(a => !SchemaSynchronizer.IgnoreSchema(a))); + result.AddRange(schemaNames.Except(systemSchemas).Select(sn => new SchemaName(db, sn, isPostgres)).Where(a => !SchemaSynchronizer.IgnoreSchema(a))); } } return result; diff --git a/Signum.Engine/Engine/PostgresFunctions.cs b/Signum.Engine/Engine/PostgresFunctions.cs index 4c15b73b6d..356043d250 100644 --- a/Signum.Engine/Engine/PostgresFunctions.cs +++ b/Signum.Engine/Engine/PostgresFunctions.cs @@ -16,6 +16,15 @@ public class PostgresFunctions [SqlMethod(Name = "pg_catalog.pg_get_expr")] public static string pg_get_expr(string adbin, int adrelid) => throw new NotImplementedException(); + [SqlMethod(Name = "pg_catalog.pg_get_viewdef")] + public static string pg_get_viewdef(int oid) => throw new NotImplementedException(); + + [SqlMethod(Name = "pg_catalog.pg_get_functiondef")] + public static string pg_get_functiondef(int oid) => throw new NotImplementedException(); + + [SqlMethod(Name = "information_schema._pg_char_max_length")] + public static int? _pg_char_max_length(int atttypeid, int atttypmod) => throw new NotImplementedException(); + [SqlMethod(Name = "pg_catalog.unnest")] public static IQueryable unnest(T[] array) => throw new NotImplementedException(); @@ -30,6 +39,8 @@ public class PostgresFunctions [SqlMethod(Name = "pg_catalog.generate_subscripts")] public static IQueryable generate_subscripts(Array array, int dimension, bool reverse) => throw new NotImplementedException(); + + } } diff --git a/Signum.Engine/Engine/SchemaGenerator.cs b/Signum.Engine/Engine/SchemaGenerator.cs index 88c0fcec64..1ce35d8568 100644 --- a/Signum.Engine/Engine/SchemaGenerator.cs +++ b/Signum.Engine/Engine/SchemaGenerator.cs @@ -38,7 +38,7 @@ public static class SchemaGenerator SqlPreCommand? indices = tables.Select(t => { - var allIndexes = t.GeneratAllIndexes().Where(a => !(a is PrimaryClusteredIndex)); ; + var allIndexes = t.GeneratAllIndexes().Where(a => !(a is PrimaryKeyIndex)); ; var mainIndices = allIndexes.Select(ix => sqlBuilder.CreateIndex(ix, checkUnique: null)).Combine(Spacing.Simple); diff --git a/Signum.Engine/Engine/SchemaSynchronizer.cs b/Signum.Engine/Engine/SchemaSynchronizer.cs index 44af5fb8e0..c4c267182d 100644 --- a/Signum.Engine/Engine/SchemaSynchronizer.cs +++ b/Signum.Engine/Engine/SchemaSynchronizer.cs @@ -150,7 +150,7 @@ public static class SchemaSynchronizer var removedColums = dif.Columns.Keys.Except(tab.Columns.Keys).ToHashSet(); var changes = Synchronizer.SynchronizeScript(Spacing.Simple, - modelIxs.Where(kvp => !(kvp.Value is PrimaryClusteredIndex)).ToDictionary(), + modelIxs.Where(kvp => !(kvp.Value is PrimaryKeyIndex)).ToDictionary(), dif.Indices.Where(kvp =>!kvp.Value.IsPrimary).ToDictionary(), createNew: null, removeOld: (i, dix) => dix.Columns.Any(c => removedColums.Contains(c.ColumnName)) || dix.IsControlledIndex ? sqlBuilder.DropIndex(dif.Name, dix) : null, @@ -163,7 +163,7 @@ public static class SchemaSynchronizer SqlPreCommand? dropIndicesHistory = Synchronizer.SynchronizeScript(Spacing.Double, modelTablesHistory, databaseTablesHistory, createNew: null, - removeOld: (tn, dif) => dif.Indices.Values.Where(ix => ix.Type != DiffIndexType.Clustered).Select(ix => sqlBuilder.DropIndex(dif.Name, ix)).Combine(Spacing.Simple), + removeOld: (tn, dif) => dif.Indices.Values.Where(ix => !ix.IsPrimary).Select(ix => sqlBuilder.DropIndex(dif.Name, ix)).Combine(Spacing.Simple), mergeBoth: (tn, tab, dif) => { Dictionary modelIxs = modelIndices[tab]; @@ -172,7 +172,7 @@ public static class SchemaSynchronizer var changes = Synchronizer.SynchronizeScript(Spacing.Simple, modelIxs.Where(kvp => kvp.Value.GetType() == typeof(TableIndex)).ToDictionary(), - dif.Indices.Where(kvp => kvp.Value.Type != DiffIndexType.Clustered).ToDictionary(), + dif.Indices.Where(kvp => !kvp.Value.IsPrimary).ToDictionary(), createNew: null, removeOld: (i, dix) => dix.Columns.Any(c => removedColums.Contains(c.ColumnName)) || dix.IsControlledIndex ? sqlBuilder.DropIndex(dif.Name, dix) : null, mergeBoth: (i, mix, dix) => !dix.IndexEquals(dif, mix) ? sqlBuilder.DropIndex(dif.Name, dix) : null @@ -223,19 +223,19 @@ public static class SchemaSynchronizer bool disableEnableSystemVersioning = false; - var disableSystemVersioning = (dif.TemporalType != SysTableTemporalType.None && + var disableSystemVersioning = !sqlBuilder.IsPostgres && dif.TemporalType != SysTableTemporalType.None && (tab.SystemVersioned == null || !object.Equals(replacements.Apply(Replacements.KeyTables, dif.TemporalTableName!.ToString()), tab.SystemVersioned.TableName.ToString()) || - (disableEnableSystemVersioning = StrongColumnChanges(tab, dif)))) ? + (disableEnableSystemVersioning = StrongColumnChanges(tab, dif))) ? sqlBuilder.AlterTableDisableSystemVersioning(tab.Name).Do(a => a.GoAfter = true) : null; - var dropPeriod = (!sqlBuilder.IsPostgres && dif.Period != null && + var dropPeriod = !sqlBuilder.IsPostgres && dif.Period != null && (tab.SystemVersioned == null || !dif.Period.PeriodEquals(tab.SystemVersioned)) ? - sqlBuilder.AlterTableDropPeriod(tab) : null); + sqlBuilder.AlterTableDropPeriod(tab) : null; - var modelPK = modelIndices[tab].Values.OfType().SingleOrDefaultEx(); - var diffPK = dif.Indices.Values.SingleOrDefaultEx(a => a.Type == DiffIndexType.Clustered); + var modelPK = modelIndices[tab].Values.OfType().SingleOrDefaultEx(); + var diffPK = dif.Indices.Values.SingleOrDefaultEx(a => a.IsPrimary); var dropPrimaryKey = diffPK != null && (modelPK == null || !diffPK.IndexEquals(dif, modelPK)) ? sqlBuilder.DropIndex(tab.Name, diffPK) : null; @@ -307,11 +307,11 @@ public static class SchemaSynchronizer var columnsHistory = columns != null && disableEnableSystemVersioning ? ForHistoryTable(columns, tab).Replace(new Regex(" IDENTITY "), m => " ") : null;/*HACK*/ - var addPeriod = ((!sqlBuilder.IsPostgres && tab.SystemVersioned != null && + var addPeriod = (!sqlBuilder.IsPostgres && tab.SystemVersioned != null && (dif.Period == null || !dif.Period.PeriodEquals(tab.SystemVersioned))) ? - (SqlPreCommandSimple)sqlBuilder.AlterTableAddPeriod(tab) : null); + (SqlPreCommandSimple)sqlBuilder.AlterTableAddPeriod(tab) : null; - var addSystemVersioning = (tab.SystemVersioned != null && + var addSystemVersioning = (!sqlBuilder.IsPostgres && tab.SystemVersioned != null && (dif.Period == null || dif.TemporalTableName == null || !object.Equals(replacements.Apply(Replacements.KeyTables, dif.TemporalTableName.ToString()), tab.SystemVersioned.TableName.ToString()) || disableEnableSystemVersioning) ? @@ -393,7 +393,7 @@ public static class SchemaSynchronizer SqlPreCommand? addIndices = Synchronizer.SynchronizeScript(Spacing.Double, modelTables, databaseTables, - createNew: (tn, tab) => modelIndices[tab].Values.Where(a => !(a is PrimaryClusteredIndex)).Select(index => sqlBuilder.CreateIndex(index, null)).Combine(Spacing.Simple), + createNew: (tn, tab) => modelIndices[tab].Values.Where(a => !(a is PrimaryKeyIndex)).Select(index => sqlBuilder.CreateIndex(index, null)).Combine(Spacing.Simple), removeOld: null, mergeBoth: (tn, tab, dif) => { @@ -404,7 +404,7 @@ public static class SchemaSynchronizer Dictionary modelIxs = modelIndices[tab]; var controlledIndexes = Synchronizer.SynchronizeScript(Spacing.Simple, - modelIxs.Where(kvp => !(kvp.Value is PrimaryClusteredIndex)).ToDictionary(), + modelIxs.Where(kvp => !(kvp.Value is PrimaryKeyIndex)).ToDictionary(), dif.Indices.Where(kvp => !kvp.Value.IsPrimary).ToDictionary(), createNew: (i, mix) => mix is UniqueTableIndex || mix.Columns.Any(isNew) || (replacements.Interactive ? SafeConsole.Ask(ref createMissingFreeIndexes, "Create missing non-unique index {0} in {1}?".FormatWith(mix.IndexName, tab.Name)) : true) ? sqlBuilder.CreateIndex(mix, checkUnique: replacements) : null, removeOld: null, @@ -428,11 +428,11 @@ public static class SchemaSynchronizer var controlledIndexes = Synchronizer.SynchronizeScript(Spacing.Simple, modelIxs.Where(kvp => kvp.Value.GetType() == typeof(TableIndex)).ToDictionary(), - dif.Indices.Where(kvp => kvp.Value.Type != DiffIndexType.Clustered).ToDictionary(), + dif.Indices.Where(kvp => !kvp.Value.IsPrimary).ToDictionary(), createNew: (i, mix) => mix is UniqueTableIndex || mix.Columns.Any(isNew) || (replacements.Interactive ? SafeConsole.Ask(ref createMissingFreeIndexes, "Create missing non-unique index {0} in {1}?".FormatWith(mix.IndexName, tab.Name)) : true) ? sqlBuilder.CreateIndexBasic(mix, forHistoryTable: true) : null, removeOld: null, mergeBoth: (i, mix, dix) => !dix.IndexEquals(dif, mix) ? sqlBuilder.CreateIndexBasic(mix, forHistoryTable: true) : - mix.IndexName != dix.IndexName ? sqlBuilder.RenameIndex(tab.SystemVersioned!.TableName, dix.IndexName, mix.IndexName) : null); + mix.GetIndexName(tab.SystemVersioned!.TableName) != dix.IndexName ? sqlBuilder.RenameIndex(tab.SystemVersioned!.TableName, dix.IndexName, mix.GetIndexName(tab.SystemVersioned!.TableName)) : null); return SqlPreCommand.Combine(Spacing.Simple, controlledIndexes); }); @@ -666,10 +666,10 @@ private static Dictionary ApplyIndexAutoReplacements(DiffTabl var nIx = newOnly.FirstOrDefault(n => { var newIx = dictionary[n]; - if (oldIx.IsPrimary && newIx is PrimaryClusteredIndex) + if (oldIx.IsPrimary && newIx is PrimaryKeyIndex) return true; - if (oldIx.IsPrimary || newIx is PrimaryClusteredIndex) + if (oldIx.IsPrimary || newIx is PrimaryKeyIndex) return false; if (oldIx.IsUnique != (newIx is UniqueTableIndex)) @@ -945,6 +945,16 @@ public override string ToString() { return Name.ToString(); } + + internal void FixSqlColumnLengthSqlServer() + { + foreach (var c in Columns.Values.Where(c => c.Length != -1)) + { + var sqlDbType = c.DbType.SqlServer; + if (sqlDbType == SqlDbType.NChar || sqlDbType == SqlDbType.NText || sqlDbType == SqlDbType.NVarChar) + c.Length /= 2; + } + } } @@ -992,7 +1002,7 @@ internal bool IndexEquals(DiffTable dif, Maps.TableIndex mix) if (this.ColumnsChanged(dif, mix)) return false; - if (this.IsPrimary != mix is PrimaryClusteredIndex) + if (this.IsPrimary != mix is PrimaryKeyIndex) return false; if (this.Type != GetIndexType(mix)) @@ -1006,8 +1016,8 @@ internal bool IndexEquals(DiffTable dif, Maps.TableIndex mix) if (mix is UniqueTableIndex && ((UniqueTableIndex)mix).ViewName != null) return null; - if (mix is PrimaryClusteredIndex) - return DiffIndexType.Clustered; + if (mix is PrimaryKeyIndex) + return Schema.Current.Settings.IsPostgres ? DiffIndexType.NonClustered : DiffIndexType.Clustered; return DiffIndexType.NonClustered; } @@ -1093,21 +1103,16 @@ public bool ColumnEquals(IColumn other, bool ignorePrimaryKey, bool ignoreIdenti && Collation == other.Collation && StringComparer.InvariantCultureIgnoreCase.Equals(UserTypeName, other.UserDefinedTypeName) && Nullable == (other.Nullable.ToBool()) - && (other.Size == null || other.Size.Value == Precision || other.Size.Value == Length / BytesPerChar(other.DbType.SqlServer) || other.Size.Value == int.MaxValue && Length == -1) + && (other.Size == null || other.Size.Value == Precision || other.Size.Value == Length || other.Size.Value == int.MaxValue && Length == -1) && (other.Scale == null || other.Scale.Value == Scale) && (ignoreIdentity || Identity == other.Identity) && (ignorePrimaryKey || PrimaryKey == other.PrimaryKey) && (ignoreGenerateAlways || GeneratedAlwaysType == other.GetGeneratedAlwaysType()); - return result; - } - - public static int BytesPerChar(System.Data.SqlDbType sqlDbType) - { - if (sqlDbType == System.Data.SqlDbType.NChar || sqlDbType == System.Data.SqlDbType.NText || sqlDbType == System.Data.SqlDbType.NVarChar) - return 2; + if (!result) + return false; - return 1; + return result; } public bool DefaultEquals(IColumn other) diff --git a/Signum.Engine/Engine/SqlBuilder.cs b/Signum.Engine/Engine/SqlBuilder.cs index c229d62b2d..a6966aaa04 100644 --- a/Signum.Engine/Engine/SqlBuilder.cs +++ b/Signum.Engine/Engine/SqlBuilder.cs @@ -43,8 +43,8 @@ public SqlPreCommand CreateTableSql(ITable t) { var primaryKeyConstraint = t.PrimaryKey == null ? null : isPostgres ? - "CONSTRAINT {0} PRIMARY KEY ({1})".FormatWith(PrimaryClusteredIndex.GetPrimaryKeyName(t.Name).SqlEscape(isPostgres), t.PrimaryKey.Name.SqlEscape(isPostgres)) : - "CONSTRAINT {0} PRIMARY KEY CLUSTERED ({1} ASC)".FormatWith(PrimaryClusteredIndex.GetPrimaryKeyName(t.Name).SqlEscape(isPostgres), t.PrimaryKey.Name.SqlEscape(isPostgres)); + "CONSTRAINT {0} PRIMARY KEY ({1})".FormatWith(PrimaryKeyIndex.GetPrimaryKeyName(t.Name).SqlEscape(isPostgres), t.PrimaryKey.Name.SqlEscape(isPostgres)) : + "CONSTRAINT {0} PRIMARY KEY CLUSTERED ({1} ASC)".FormatWith(PrimaryKeyIndex.GetPrimaryKeyName(t.Name).SqlEscape(isPostgres), t.PrimaryKey.Name.SqlEscape(isPostgres)); var systemPeriod = t.SystemVersioned == null || IsPostgres ? null : Period(t.SystemVersioned); @@ -248,9 +248,6 @@ public string Quote(AbstractDbType type, string @default) if (type.IsString() && !(@default.StartsWith("'") && @default.StartsWith("'"))) return "'" + @default + "'"; - if (@default == "NEWID()" && IsPostgres) //hacky - return "uuid_generate_v1()"; - return @default; } @@ -299,7 +296,7 @@ public SqlPreCommand DropIndex(ObjectName objectName, string indexName) public SqlPreCommand CreateIndex(TableIndex index, Replacements? checkUnique) { - if (index is PrimaryClusteredIndex) + if (index is PrimaryKeyIndex) { var columns = index.Columns.ToString(c => c.Name.SqlEscape(isPostgres), ", "); @@ -352,7 +349,7 @@ public int DuplicateCount(UniqueTableIndex uniqueIndex, Replacements rep) var oldPrimaryKey = columnReplacement.TryGetC(primaryKey.Name) ?? primaryKey.Name; - return (int)Executor.ExecuteScalar( + return Convert.ToInt32(Executor.ExecuteScalar( $@"SELECT Count(*) FROM {oldTableName} WHERE {oldPrimaryKey.SqlEscape(IsPostgres)} NOT IN ( @@ -360,7 +357,7 @@ SELECT MIN({oldPrimaryKey.SqlEscape(IsPostgres)}) FROM {oldTableName} {(!uniqueIndex.Where.HasText() ? "" : "WHERE " + uniqueIndex.Where.Replace(columnReplacement))} GROUP BY {oldColumns} -){(!uniqueIndex.Where.HasText() ? "" : "AND " + uniqueIndex.Where.Replace(columnReplacement))}")!; +){(!uniqueIndex.Where.HasText() ? "" : "AND " + uniqueIndex.Where.Replace(columnReplacement))}")!); } public SqlPreCommand? RemoveDuplicatesIfNecessary(UniqueTableIndex uniqueIndex, Replacements rep) @@ -470,7 +467,7 @@ public SqlPreCommandSimple AlterTableAddDefaultConstraint(ObjectName tableName, return new SqlPreCommandSimple("ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3}({4});".FormatWith( parentTable, - ForeignKeyName(parentTable.Name, parentColumn), + ForeignKeyName(parentTable.Name, parentColumn).SqlEscape(isPostgres), parentColumn.SqlEscape(isPostgres), targetTable, targetPrimaryKey.SqlEscape(isPostgres))); @@ -480,7 +477,7 @@ public string ForeignKeyName(string table, string fieldName) { var result = "FK_{0}_{1}".FormatWith(table, fieldName); - return StringHashEncoder.ChopHash(result, this.connector.MaxNameLength).SqlEscape(isPostgres); + return StringHashEncoder.ChopHash(result, this.connector.MaxNameLength); } public SqlPreCommand RenameForeignKey(ObjectName foreignKeyName, string newName) diff --git a/Signum.Engine/Engine/Synchronizer.cs b/Signum.Engine/Engine/Synchronizer.cs index 86009821ec..da46ce4dd5 100644 --- a/Signum.Engine/Engine/Synchronizer.cs +++ b/Signum.Engine/Engine/Synchronizer.cs @@ -122,8 +122,15 @@ public static void SynchronizeReplacing( where N : class where K : notnull { - return newDictionary.OuterJoinDictionaryCC(oldDictionary, (key, newVal, oldVal) => + HashSet set = new HashSet(); + set.UnionWith(newDictionary.Keys); + set.UnionWith(oldDictionary.Keys); + + return set.Select(key => { + var newVal = newDictionary.TryGetC(key); + var oldVal = oldDictionary.TryGetC(key); + if (newVal == null) return removeOld == null ? null : removeOld(key, oldVal!); @@ -131,7 +138,7 @@ public static void SynchronizeReplacing( return createNew == null ? null : createNew(key, newVal); return mergeBoth == null ? null : mergeBoth(key, newVal, oldVal); - }).Values.Combine(spacing); + }).Combine(spacing); } diff --git a/Signum.Engine/Engine/SysTablesSchema.cs b/Signum.Engine/Engine/SysTablesSchema.cs index da5a20fed3..2a98d2732d 100644 --- a/Signum.Engine/Engine/SysTablesSchema.cs +++ b/Signum.Engine/Engine/SysTablesSchema.cs @@ -142,6 +142,7 @@ join c in t.Columns() on ic.column_id equals c.column_id if (SchemaSynchronizer.IgnoreTable != null) tables.RemoveAll(SchemaSynchronizer.IgnoreTable); + tables.ForEach(t => t.FixSqlColumnLengthSqlServer()); tables.ForEach(t => t.ForeignKeysToColumns()); allTables.AddRange(tables); diff --git a/Signum.Engine/Linq/ExpressionVisitor/ChildProjectionFlattener.cs b/Signum.Engine/Linq/ExpressionVisitor/ChildProjectionFlattener.cs index 5da721b4f5..b0ddc9f843 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/ChildProjectionFlattener.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/ChildProjectionFlattener.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -130,7 +131,7 @@ protected internal override Expression VisitProjection(ProjectionExpression proj currentSource = old; - Expression key = TupleReflection.TupleChainConstructor(columnsSMExternal.Select(cd => cd.GetReference(aliasSM).Nullify())); + Expression key = TupleReflection.TupleChainConstructor(columnsSMExternal.Select(cd => MakeEquatable(cd.GetReference(aliasSM)))); Type kvpType = typeof(KeyValuePair<,>).MakeGenericType(key.Type, projector.Type); ConstructorInfo ciKVP = kvpType.GetConstructor(new[] { key.Type, projector.Type })!; Type projType = proj.UniqueFunction == null ? typeof(IEnumerable<>).MakeGenericType(kvpType) : kvpType; @@ -139,11 +140,19 @@ protected internal override Expression VisitProjection(ProjectionExpression proj Expression.New(ciKVP, key, projector), proj.UniqueFunction, projType); return new ChildProjectionExpression(childProj, - TupleReflection.TupleChainConstructor(columns.Select(a => a.Nullify())), inMList != null, inMList ?? proj.Type, new LookupToken()); + TupleReflection.TupleChainConstructor(columns.Select(a => MakeEquatable(a))), inMList != null, inMList ?? proj.Type, new LookupToken()); } } } + public Expression MakeEquatable(Expression expression) + { + if (expression.Type.IsArray) + return Expression.New(typeof(ArrayBox<>).MakeGenericType(expression.Type.ElementType()!).GetConstructors().SingleEx(), expression); + + return expression.Nullify(); + } + private SelectExpression WithoutOrder(SelectExpression sel) { if (sel.Top != null || (sel.OrderBy.Count == 0)) @@ -329,4 +338,27 @@ protected internal override Expression VisitColumn(ColumnExpression column) } } + + class ArrayBox : IEquatable> + { + readonly int hashCode; + public readonly T[]? Array; + + public ArrayBox(T[]? array) + { + this.Array = array; + this.hashCode = 0; + if(array != null) + { + foreach (var item in array) + { + this.hashCode = (this.hashCode << 1) ^ (item == null ? 0 : item.GetHashCode()); + } + } + } + + public override int GetHashCode() => hashCode; + public override bool Equals(object? obj) => obj is ArrayBox a && Equals(a); + public bool Equals([AllowNull] ArrayBox other) => Enumerable.SequenceEqual(Array, other.Array); + } } diff --git a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs index a80f08d6d5..af3ee90aca 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs @@ -1281,16 +1281,18 @@ string getDatePart() SqlMethodAttribute? sma = m.Method.GetCustomAttribute(); if (sma != null) { - if (m.Method.IsExtensionMethod()) - using (ForceFullNominate()) - return TrySqlFunction(m.Arguments[0], m.Method.Name, m.Type, m.Arguments.Skip(1).ToArray()); - - if (m.Object != null) - using (ForceFullNominate()) - return TrySqlFunction(m.Object, sma.Name ?? m.Method.Name, m.Type, m.Arguments.ToArray()); + using (ForceFullNominate()) + { + if (m.Method.IsExtensionMethod()) + using (ForceFullNominate()) + return TrySqlFunction(m.Arguments[0], m.Method.Name, m.Type, m.Arguments.Skip(1).ToArray()); - return TrySqlFunction(m.Object, ObjectName.Parse(sma.Name ?? m.Method.Name, isPostgres).ToString(), m.Type, m.Arguments.ToArray()); + if (m.Object != null) + using (ForceFullNominate()) + return TrySqlFunction(m.Object, sma.Name ?? m.Method.Name, m.Type, m.Arguments.ToArray()); + return TrySqlFunction(m.Object, ObjectName.Parse(sma.Name ?? m.Method.Name, isPostgres).ToString(), m.Type, m.Arguments.ToArray()); + } } return base.VisitMethodCall(m); diff --git a/Signum.Engine/Schema/Schema.Basics.cs b/Signum.Engine/Schema/Schema.Basics.cs index f2b85b9338..021a7f1909 100644 --- a/Signum.Engine/Schema/Schema.Basics.cs +++ b/Signum.Engine/Schema/Schema.Basics.cs @@ -524,7 +524,7 @@ public override IEnumerable GenerateIndexes(ITable table) if (this.UniqueIndex != null) throw new InvalidOperationException("Changing IndexType is not allowed for FieldPrimaryKey"); - return new[] { new PrimaryClusteredIndex(table) }; + return new[] { new PrimaryKeyIndex(table) }; } internal override IEnumerable> GetTables() @@ -1222,7 +1222,7 @@ public List GeneratAllIndexes() { var result = new List { - new PrimaryClusteredIndex(this) + new PrimaryKeyIndex(this) }; result.AddRange(BackReference.GenerateIndexes(this)); @@ -1300,6 +1300,8 @@ public struct AbstractDbType : IEquatable NpgsqlDbType? postgreSql; public NpgsqlDbType PostgreSql => postgreSql ?? throw new InvalidOperationException("No PostgresSql type defined"); + public bool IsPostgres => postgreSql.HasValue; + public AbstractDbType(SqlDbType sqlDbType) { this.sqlServer = sqlDbType; @@ -1319,7 +1321,10 @@ public AbstractDbType(SqlDbType sqlDbType, NpgsqlDbType npgsqlDbType) } public override bool Equals(object? obj) => obj is AbstractDbType adt && Equals(adt); - public bool Equals(AbstractDbType adt) => this.postgreSql == adt.postgreSql && this.sqlServer == adt.sqlServer; + public bool Equals(AbstractDbType adt) => + Schema.Current.Settings.IsPostgres ? + this.postgreSql == adt.postgreSql : + this.sqlServer == adt.sqlServer; public override int GetHashCode() => this.postgreSql.GetHashCode() ^ this.sqlServer.GetHashCode(); public bool IsDate() diff --git a/Signum.Engine/Schema/SchemaAssets.cs b/Signum.Engine/Schema/SchemaAssets.cs index 607c30c497..bec86cd647 100644 --- a/Signum.Engine/Schema/SchemaAssets.cs +++ b/Signum.Engine/Schema/SchemaAssets.cs @@ -2,6 +2,9 @@ using System.Linq; using Signum.Utilities; using Signum.Engine.SchemaInfoTables; +using System; +using Signum.Engine.PostgresCatalog; +using Signum.Engine.Engine; namespace Signum.Engine.Maps { @@ -73,12 +76,27 @@ public View IncludeView(ObjectName viewName, string viewDefinition) var isPostgres = Schema.Current.Settings.IsPostgres; var oldView = Schema.Current.DatabaseNames().SelectMany(db => { - using (Administrator.OverrideDatabaseInSysViews(db)) + if (isPostgres) { - return (from v in Database.View() - join s in Database.View() on v.schema_id equals s.schema_id - join m in Database.View() on v.object_id equals m.object_id - select KeyValuePair.Create(new ObjectName(new SchemaName(db, s.name, isPostgres), v.name, isPostgres), m.definition)).ToList(); + if (db != null) + throw new InvalidOperationException("Multi-database not supported in postgress"); + + return (from p in Database.View() + where p.relkind == RelKind.View + let ns = p.Namespace() + where !PostgresCatalogSchema.systemSchemas.Contains(ns.nspname) + let definition = PostgresFunctions.pg_get_viewdef(p.oid) + select KeyValuePair.Create(new ObjectName(new SchemaName(db, ns.nspname, isPostgres), p.relname, isPostgres), definition)).ToList(); + } + else + { + using (Administrator.OverrideDatabaseInSysViews(db)) + { + return (from v in Database.View() + join s in Database.View() on v.schema_id equals s.schema_id + join m in Database.View() on v.object_id equals m.object_id + select KeyValuePair.Create(new ObjectName(new SchemaName(db, s.name, isPostgres), v.name, isPostgres), m.definition)).ToList(); + } } }).ToDictionary(); @@ -127,13 +145,27 @@ public Procedure IncludeUserDefinedFunction(ObjectName functionName, string func var isPostgres = Schema.Current.Settings.IsPostgres; var oldProcedures = Schema.Current.DatabaseNames().SelectMany(db => { - using (Administrator.OverrideDatabaseInSysViews(db)) + if (isPostgres) + { + if (db != null) + throw new InvalidOperationException("Multi-database not supported in postgress"); + + return (from v in Database.View() + let ns = v.Namespace() + where !PostgresCatalogSchema.systemSchemas.Contains(ns.nspname) + let definition = PostgresFunctions.pg_get_viewdef(v.oid) + select KeyValuePair.Create(new ObjectName(new SchemaName(db, ns.nspname, isPostgres), v.proname, isPostgres), definition)).ToList(); + } + else { - return (from p in Database.View() - join s in Database.View() on p.schema_id equals s.schema_id - where p.type == "P" || p.type == "IF" || p.type == "FN" - join m in Database.View() on p.object_id equals m.object_id - select KeyValuePair.Create(new ObjectName(new SchemaName(db, s.name, isPostgres), p.name, isPostgres), m.definition)).ToList(); + using (Administrator.OverrideDatabaseInSysViews(db)) + { + return (from p in Database.View() + join s in Database.View() on p.schema_id equals s.schema_id + where p.type == "P" || p.type == "IF" || p.type == "FN" + join m in Database.View() on p.object_id equals m.object_id + select KeyValuePair.Create(new ObjectName(new SchemaName(db, s.name, isPostgres), p.name, isPostgres), m.definition)).ToList(); + } } }).ToDictionary(); diff --git a/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs b/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs index 15dd94f2f9..f242e801e3 100644 --- a/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs +++ b/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs @@ -543,7 +543,7 @@ protected virtual Field GenerateFieldPrimaryKey(Table table, PropertyRoute route DbType = pair.DbType, Collation = Settings.GetCollate(attr), UserDefinedTypeName = pair.UserDefinedTypeName, - Default = attr.Default, + Default = attr.GetDefault(Settings.IsPostgres), Identity = attr.Identity, }; } @@ -582,7 +582,7 @@ protected virtual FieldValue GenerateFieldTicks(Table table, PropertyRoute route Nullable = IsNullable.No, Size = Settings.GetSqlSize(ticksAttr, null, pair.DbType), Scale = Settings.GetSqlScale(ticksAttr, null, pair.DbType), - Default = ticksAttr?.Default, + Default = ticksAttr?.GetDefault(Settings.IsPostgres), }; } @@ -590,7 +590,7 @@ protected virtual FieldValue GenerateFieldValue(ITable table, PropertyRoute rout { var att = Settings.FieldAttribute(route); - DbTypePair pair = Settings.IsPostgres && route.Type.IsArray ? + DbTypePair pair = Settings.IsPostgres && route.Type.IsArray && route.Type != typeof(byte[]) ? Settings.GetSqlDbType(att, route.Type.ElementType()!) : Settings.GetSqlDbType(att, route.Type); @@ -602,7 +602,7 @@ protected virtual FieldValue GenerateFieldValue(ITable table, PropertyRoute rout Nullable = Settings.GetIsNullable(route, forceNull), Size = Settings.GetSqlSize(att, route, pair.DbType), Scale = Settings.GetSqlScale(att, route, pair.DbType), - Default = att?.Default, + Default = att?.GetDefault(Settings.IsPostgres), }.Do(f => f.UniqueIndex = f.GenerateUniqueIndex(table, Settings.FieldAttribute(route))); } @@ -619,7 +619,7 @@ protected virtual FieldEnum GenerateFieldEnum(ITable table, PropertyRoute route, Nullable = Settings.GetIsNullable(route, forceNull), IsLite = false, AvoidForeignKey = Settings.FieldAttribute(route) != null, - Default = att?.Default, + Default = att?.GetDefault(Settings.IsPostgres), }.Do(f => f.UniqueIndex = f.GenerateUniqueIndex(table, Settings.FieldAttribute(route))); } @@ -635,7 +635,7 @@ protected virtual FieldReference GenerateFieldReference(ITable table, PropertyRo IsLite = route.Type.IsLite(), AvoidForeignKey = Settings.FieldAttribute(route) != null, AvoidExpandOnRetrieving = Settings.FieldAttribute(route) != null, - Default = Settings.FieldAttribute(route)?.Default + Default = Settings.FieldAttribute(route)?.GetDefault(Settings.IsPostgres) }.Do(f => f.UniqueIndex = f.GenerateUniqueIndex(table, Settings.FieldAttribute(route))); } @@ -733,7 +733,7 @@ protected virtual FieldMList GenerateFieldMList(Table table, PropertyRoute route DbType = pair.DbType, Collation = Settings.GetCollate(orderAttr), UserDefinedTypeName = pair.UserDefinedTypeName, - Default = keyAttr.Default, + Default = keyAttr.GetDefault(Settings.IsPostgres), Identity = keyAttr.Identity, }; } @@ -1052,7 +1052,7 @@ protected override FieldEnum GenerateFieldEnum(ITable table, PropertyRoute route Nullable = Settings.GetIsNullable(route, forceNull), IsLite = false, AvoidForeignKey = Settings.FieldAttribute(route) != null, - Default = att?.Default, + Default = att?.GetDefault(Settings.IsPostgres), }.Do(f => f.UniqueIndex = f.GenerateUniqueIndex(table, Settings.FieldAttribute(route))); } } diff --git a/Signum.Engine/Schema/UniqueTableIndex.cs b/Signum.Engine/Schema/UniqueTableIndex.cs index 4f9fb00d8f..ecaf3cbd3d 100644 --- a/Signum.Engine/Schema/UniqueTableIndex.cs +++ b/Signum.Engine/Schema/UniqueTableIndex.cs @@ -83,9 +83,9 @@ public string HintText() } } - public class PrimaryClusteredIndex : TableIndex + public class PrimaryKeyIndex : TableIndex { - public PrimaryClusteredIndex(ITable table) : base(table, new[] { table.PrimaryKey }) + public PrimaryKeyIndex(ITable table) : base(table, new[] { table.PrimaryKey }) { } diff --git a/Signum.Entities/FieldAttributes.cs b/Signum.Entities/FieldAttributes.cs index 0a9e160065..037b0a4978 100644 --- a/Signum.Entities/FieldAttributes.cs +++ b/Signum.Entities/FieldAttributes.cs @@ -283,10 +283,19 @@ public int Scale public string? Default { get; set; } + public string? DefaultSqlServer { get; set; } + public string? DefaultPostgres { get; set; } + + public string? GetDefault(bool isPostgres) + { + return (isPostgres ? DefaultPostgres : DefaultSqlServer) ?? Default; + } + public string? Collation { get; set; } - public const string NewId = "NEWID()"; - public const string NewSequentialId = "NEWSEQUENTIALID()"; + public const string SqlServer_NewId = "NEWID()"; + public const string SqlServer_NewSequentialId = "NEWSEQUENTIALID()"; + public const string Postgres_UuidGenerateV1= "uuid_generate_v1()"; } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Field | AttributeTargets.Property /*MList fields*/, Inherited = true, AllowMultiple = false)] @@ -307,9 +316,10 @@ public bool IdentityBehaviour set { identityBehaviour = value; - if (Type == typeof(Guid)) + if (Type == typeof(Guid) && identityBehaviour) { - this.Default = identityBehaviour ? NewId : null; + this.DefaultSqlServer = SqlServer_NewId; + this.DefaultPostgres = Postgres_UuidGenerateV1; } } } From 197ad79173509ef642031c0a547a81a12db38312 Mon Sep 17 00:00:00 2001 From: Olmo del Corral Date: Wed, 15 Jan 2020 18:16:04 +0100 Subject: [PATCH 06/13] more on Postgres schema --- Signum.Engine/Administrator.cs | 4 +-- Signum.Engine/Engine/PostgresCatalogSchema.cs | 17 ++++----- Signum.Engine/Engine/SchemaSynchronizer.cs | 2 +- Signum.Engine/Engine/SqlBuilder.cs | 17 ++++++++- Signum.Engine/Schema/Schema.Delete.cs | 31 +++++++++++++--- Signum.Engine/Schema/Schema.Save.cs | 14 +++++--- Signum.Utilities/SafeConsole.cs | 36 +++++++++++++++++-- 7 files changed, 96 insertions(+), 25 deletions(-) diff --git a/Signum.Engine/Administrator.cs b/Signum.Engine/Administrator.cs index 52f7b71e1f..8eb2fc9d16 100644 --- a/Signum.Engine/Administrator.cs +++ b/Signum.Engine/Administrator.cs @@ -673,10 +673,10 @@ public static SqlPreCommand DeleteWhereScript(Table table, IColumn column, Prima throw new InvalidOperationException($"DeleteWhereScript can not be used for {table.Type.Name} because contains MLists"); if(id.VariableName.HasText()) - return new SqlPreCommandSimple("DELETE FROM {0} WHERE {1} = {2}".FormatWith(table.Name, column.Name, id.VariableName)); + return new SqlPreCommandSimple("DELETE FROM {0} WHERE {1} = {2};".FormatWith(table.Name, column.Name, id.VariableName)); var param = Connector.Current.ParameterBuilder.CreateReferenceParameter("@id", id, column); - return new SqlPreCommandSimple("DELETE FROM {0} WHERE {1} = {2}".FormatWith(table.Name, column.Name, param.ParameterName), new List { param }); + return new SqlPreCommandSimple("DELETE FROM {0} WHERE {1} = {2}:".FormatWith(table.Name, column.Name, param.ParameterName), new List { param }); } diff --git a/Signum.Engine/Engine/PostgresCatalogSchema.cs b/Signum.Engine/Engine/PostgresCatalogSchema.cs index 08ee2df499..8a280a9f4d 100644 --- a/Signum.Engine/Engine/PostgresCatalogSchema.cs +++ b/Signum.Engine/Engine/PostgresCatalogSchema.cs @@ -104,17 +104,18 @@ from t in s.Tables() }).ToList(), }).ToList(), - SimpleIndices = (from i in t.Indices() + SimpleIndices = (from ix in t.Indices() select new DiffIndex { - IsUnique = i.indisunique, - IsPrimary = i.indisprimary, - IndexName = i.Class().relname, - FilterDefinition = PostgresFunctions.pg_get_expr(i.indpred!, i.indrelid), + IsUnique = ix.indisunique, + IsPrimary = ix.indisprimary, + IndexName = ix.Class().relname, + FilterDefinition = PostgresFunctions.pg_get_expr(ix.indpred!, ix.indrelid), Type = DiffIndexType.NonClustered, - Columns = (from at in i.Class().Attributes() - orderby at.attnum - select new DiffIndexColumn { ColumnName = at.attname, IsIncluded = at.attnum > i.indnkeyatts }).ToList() + Columns = (from i in PostgresFunctions.generate_subscripts(ix.indkey, 1) + let at = t.Attributes().Single(a => a.attnum == ix.indkey[i]) + orderby i + select new DiffIndexColumn { ColumnName = at.attname, IsIncluded = i >= ix.indnkeyatts }).ToList() }).ToList(), ViewIndices = new List(), diff --git a/Signum.Engine/Engine/SchemaSynchronizer.cs b/Signum.Engine/Engine/SchemaSynchronizer.cs index c4c267182d..94653bda33 100644 --- a/Signum.Engine/Engine/SchemaSynchronizer.cs +++ b/Signum.Engine/Engine/SchemaSynchronizer.cs @@ -385,7 +385,7 @@ public static class SchemaSynchronizer var name = sqlBuilder.ForeignKeyName(tab.Name.Name, tabCol.Name); return SqlPreCommand.Combine(Spacing.Simple, - name != difCol.ForeignKey.Name.Name ? sqlBuilder.RenameForeignKey(difCol.ForeignKey.Name.OnSchema(tab.Name.Schema), name) : null, + name != difCol.ForeignKey.Name.Name ? sqlBuilder.RenameForeignKey(tab.Name, difCol.ForeignKey.Name.OnSchema(tab.Name.Schema), name) : null, (difCol.ForeignKey.IsDisabled || difCol.ForeignKey.IsNotTrusted) && !replacements.SchemaOnly ? sqlBuilder.EnableForeignKey(tab.Name, name) : null); }) ); diff --git a/Signum.Engine/Engine/SqlBuilder.cs b/Signum.Engine/Engine/SqlBuilder.cs index a6966aaa04..21d1e1ce40 100644 --- a/Signum.Engine/Engine/SqlBuilder.cs +++ b/Signum.Engine/Engine/SqlBuilder.cs @@ -480,8 +480,11 @@ public string ForeignKeyName(string table, string fieldName) return StringHashEncoder.ChopHash(result, this.connector.MaxNameLength); } - public SqlPreCommand RenameForeignKey(ObjectName foreignKeyName, string newName) + public SqlPreCommand RenameForeignKey(ObjectName tn, ObjectName foreignKeyName, string newName) { + if (IsPostgres) + return new SqlPreCommandSimple($"ALTER TABLE {tn} RENAME CONSTRAINT {foreignKeyName.Name.SqlEscape(IsPostgres)} TO {newName.SqlEscape(IsPostgres)};"); + return SP_RENAME(foreignKeyName.Schema.Database, foreignKeyName.OnDatabase(null).ToString(), newName, "OBJECT"); } @@ -540,21 +543,33 @@ public SqlPreCommand MoveRows(ObjectName oldTable, ObjectName newTable, IEnumera public SqlPreCommand RenameTable(ObjectName oldName, string newName) { + if (IsPostgres) + return new SqlPreCommandSimple($"ALTER TABLE {oldName} RENAME TO {newName.SqlEscape(IsPostgres)};"); + return SP_RENAME(oldName.Schema.Database, oldName.OnDatabase(null).ToString(), newName, null); } public SqlPreCommandSimple AlterSchema(ObjectName oldName, SchemaName schemaName) { + if (IsPostgres) + return new SqlPreCommandSimple($"ALTER TABLE {oldName} SET SCHEMA{schemaName.Name.SqlEscape(IsPostgres)};"); + return new SqlPreCommandSimple("ALTER SCHEMA {0} TRANSFER {1};".FormatWith(schemaName.Name.SqlEscape(isPostgres), oldName)); } public SqlPreCommand RenameColumn(ObjectName tableName, string oldName, string newName) { + if (IsPostgres) + return new SqlPreCommandSimple($"ALTER TABLE {tableName} RENAME COLUMN {oldName.SqlEscape(IsPostgres)} TO {newName.SqlEscape(IsPostgres)};"); + return SP_RENAME(tableName.Schema.Database, tableName.OnDatabase(null) + "." + oldName, newName, "COLUMN"); } public SqlPreCommand RenameIndex(ObjectName tableName, string oldName, string newName) { + if (IsPostgres) + return new SqlPreCommandSimple($"ALTER INDEX {oldName.SqlEscape(IsPostgres)} RENAME TO {newName.SqlEscape(IsPostgres)};"); + return SP_RENAME(tableName.Schema.Database, tableName.OnDatabase(null) + "." + oldName, newName, "INDEX"); } #endregion diff --git a/Signum.Engine/Schema/Schema.Delete.cs b/Signum.Engine/Schema/Schema.Delete.cs index 384b682066..89c4ae18cf 100644 --- a/Signum.Engine/Schema/Schema.Delete.cs +++ b/Signum.Engine/Schema/Schema.Delete.cs @@ -21,30 +21,51 @@ public SqlPreCommand DeleteSqlSync(T entity, Expression>? where var isPostgres = Schema.Current.Settings.IsPostgres; var pre = OnPreDeleteSqlSync(entity); var collections = (from tml in this.TablesMList() - select new SqlPreCommandSimple("DELETE {0} WHERE {1} = {2} --{3}" + select new SqlPreCommandSimple("DELETE FROM {0} WHERE {1} = {2}; --{3}" .FormatWith(tml.Name, tml.BackReference.Name.SqlEscape(isPostgres), variableOrId, comment ?? entity.ToString()))).Combine(Spacing.Simple); - var main = new SqlPreCommandSimple("DELETE {0} WHERE {1} = {2} --{3}" + var main = new SqlPreCommandSimple("DELETE FROM {0} WHERE {1} = {2}; --{3}" .FormatWith(Name, this.PrimaryKey.Name.SqlEscape(isPostgres), variableOrId, comment ?? entity.ToString())); + if (isPostgres && declaration != null) + return PostgresDoBlock(entity.Id.VariableName!, declaration, SqlPreCommand.Combine(Spacing.Simple, pre, collections, main)!); + return SqlPreCommand.Combine(Spacing.Simple, declaration, pre, collections, main)!; } int parameterIndex; - private SqlPreCommand DeclarePrimaryKeyVariable(T entity, Expression> where) where T : Entity + private SqlPreCommandSimple DeclarePrimaryKeyVariable(T entity, Expression> where) where T : Entity { var query = DbQueryProvider.Single.GetMainSqlCommand(Database.Query().Where(where).Select(a => a.Id).Expression); - string variableName = SqlParameterBuilder.GetParameterName(this.Name.Name + "Id_" + (parameterIndex++)); + string variableName = this.Name.Name + "Id_" + (parameterIndex++); + if (!Schema.Current.Settings.IsPostgres) + variableName = SqlParameterBuilder.GetParameterName(variableName); + entity.SetId(new Entities.PrimaryKey(entity.id!.Value.Object, variableName)); string queryString = query.PlainSql().Lines().ToString(" "); - var result = new SqlPreCommandSimple($"DECLARE {variableName} {Connector.Current.SqlBuilder.GetColumnType(this.PrimaryKey)}; SET {variableName} = COALESCE(({queryString}), 1 / 0)"); + var result = Schema.Current.Settings.IsPostgres ? + new SqlPreCommandSimple(@$"{variableName} {Connector.Current.SqlBuilder.GetColumnType(this.PrimaryKey)} = ({queryString});") : + new SqlPreCommandSimple($"DECLARE {variableName} {Connector.Current.SqlBuilder.GetColumnType(this.PrimaryKey)}; SET {variableName} = COALESCE(({queryString}), 1 / 0);"); return result; } + private SqlPreCommandSimple PostgresDoBlock(string variableName, SqlPreCommandSimple declaration, SqlPreCommand block) + { + return new SqlPreCommandSimple(@$"DO $$ +DECLARE +{declaration.PlainSql().Indent(4)} +BEGIN + IF {variableName} IS NULL THEN + RAISE EXCEPTION 'Not found'; + END IF; +{block.PlainSql().Indent(4)} +END $$;"); + } + public event Func PreDeleteSqlSync; SqlPreCommand? OnPreDeleteSqlSync(Entity entity) diff --git a/Signum.Engine/Schema/Schema.Save.cs b/Signum.Engine/Schema/Schema.Save.cs index 9eb3058f4b..48851dc871 100644 --- a/Signum.Engine/Schema/Schema.Save.cs +++ b/Signum.Engine/Schema/Schema.Save.cs @@ -575,7 +575,7 @@ internal static UpdateCache InitializeUpdate(Table table) (output && isPostgres ? $"RETURNING ({id})" : null); if (!(isPostgres && output)) - return result; + return result.Trim() + ";"; return $@"WITH rows AS ( {result.Indent(4)} @@ -707,7 +707,6 @@ public SqlPreCommand InsertSqlSync(Entity ident, bool includeCollections = true, } else { - return SqlPreCommand.Combine(Spacing.Simple, new SqlPreCommandSimple($"DECLARE {parentId} {pkType};") { GoBefore = true }, insert, @@ -737,9 +736,14 @@ public SqlPreCommand InsertSqlSync(Entity ident, bool includeCollections = true, SqlPreCommand? update; if (where != null) { - update = SqlPreCommand.Combine(Spacing.Simple, - DeclarePrimaryKeyVariable(entity, where), - new SqlPreCommandSimple(sql, parameters).AddComment(comment).ReplaceFirstParameter(entity.Id.VariableName)); + bool isPostgres = Schema.Current.Settings.IsPostgres; + + var declare = DeclarePrimaryKeyVariable(entity, where); + var updateSql = new SqlPreCommandSimple(sql, parameters).AddComment(comment).ReplaceFirstParameter(entity.Id.VariableName); + + update = isPostgres ? + PostgresDoBlock(entity.Id.VariableName!, declare, updateSql) : + SqlPreCommand.Combine(Spacing.Simple, declare, updateSql);; } else { diff --git a/Signum.Utilities/SafeConsole.cs b/Signum.Utilities/SafeConsole.cs index 9efa2e72cf..d6fa15d50d 100644 --- a/Signum.Utilities/SafeConsole.cs +++ b/Signum.Utilities/SafeConsole.cs @@ -98,12 +98,25 @@ public static bool Ask(string question) return Ask(question, "yes", "no") == "yes"; } - public static string Ask(string question, params string[] answers) + public static string AskRetry(string question, params string[] answers) + { + retry: + var result = Ask(question, answers); + if (result == null) + goto retry; + + return result; + } + + public static string? Ask(string question, params string[] answers) { Console.Write(question + " ({0}) ".FormatWith(answers.ToString("/"))); do { var userAnswer = Console.ReadLine().ToLower(); + if (!userAnswer.HasText()) + return null; + var result = answers.FirstOrDefault(a => a.StartsWith(userAnswer, StringComparison.CurrentCultureIgnoreCase)); if (result != null) return result; @@ -112,6 +125,16 @@ public static string Ask(string question, params string[] answers) } while (true); } + public static string AskMultilineRetry(string question, params string[] answers) + { + retry: + var result = AskMultiLine(question, answers); + if (result == null) + goto retry; + + return result; + } + public static string? AskMultiLine(string question, params string[] answers) { Console.WriteLine(question); @@ -149,7 +172,11 @@ public static bool Ask(ref bool? rememberedAnswer, string question) string? answerString = null; - string result = Ask(ref answerString, question, "yes", "no"); + retry: + string? result = Ask(ref answerString, question, "yes", "no"); + + if (result == null) + goto retry; if (answerString.HasText()) rememberedAnswer = answerString == "yes"; @@ -157,7 +184,7 @@ public static bool Ask(ref bool? rememberedAnswer, string question) return result == "yes"; } - public static string Ask(ref string? rememberedAnswer, string question, params string[] answers) + public static string? Ask(ref string? rememberedAnswer, string question, params string[] answers) { if (rememberedAnswer != null) return rememberedAnswer; @@ -172,6 +199,9 @@ public static string Ask(ref string? rememberedAnswer, string question, params s if (remember) userAnswer = userAnswer.Replace("!", ""); + if (!userAnswer.HasText()) + return null; + var result = answers.FirstOrDefault(a => a.StartsWith(userAnswer, StringComparison.CurrentCultureIgnoreCase)); if (result != null) { From dc9f665dab09cbee948debc461f9e4c7915cf59d Mon Sep 17 00:00:00 2001 From: Olmo del Corral Date: Wed, 15 Jan 2020 18:16:39 +0100 Subject: [PATCH 07/13] allow execute sync scripts (not only migrations) directly in load app --- Signum.Engine/Engine/SqlPreCommand.cs | 120 +++++++++++++++++++++++--- 1 file changed, 108 insertions(+), 12 deletions(-) diff --git a/Signum.Engine/Engine/SqlPreCommand.cs b/Signum.Engine/Engine/SqlPreCommand.cs index e0455c4985..4852449b06 100644 --- a/Signum.Engine/Engine/SqlPreCommand.cs +++ b/Signum.Engine/Engine/SqlPreCommand.cs @@ -84,27 +84,112 @@ public static SqlPreCommand PlainSqlCommand(this SqlPreCommand command) .Combine(Spacing.Simple)!; } - public static bool AvoidOpenOpenSqlFileRetry = true; - public static void OpenSqlFileRetry(this SqlPreCommand command) { SafeConsole.WriteLineColor(ConsoleColor.Yellow, "There are changes!"); - string file = command.OpenSqlFile(); - if (!AvoidOpenOpenSqlFileRetry && SafeConsole.Ask("Open again?")) - Process.Start(file); + var fileName = "Sync {0:dd-MM-yyyy hh_mm_ss}.sql".FormatWith(DateTime.Now); + + Save(command, fileName); + SafeConsole.WriteLineColor(ConsoleColor.DarkYellow, command.PlainSql()); + + Console.WriteLine("Script saved in: " + Path.Combine(Directory.GetCurrentDirectory(), fileName)); + var answer = SafeConsole.AskRetry("Open or run?", "open", "run", "exit"); + + if(answer == "open") + { + Thread.Sleep(1000); + Open(fileName); + if (SafeConsole.Ask("run now?")) + ExecuteRetry(fileName); + } + else if(answer == "run") + { + ExecuteRetry(fileName); + } } - public static string OpenSqlFile(this SqlPreCommand command) + static void ExecuteRetry(string fileName) { - return OpenSqlFile(command, "Sync {0:dd-MM-yyyy hh_mm_ss}.sql".FormatWith(DateTime.Now)); + retry: + try + { + var script = File.ReadAllText(fileName); + ExecuteScript("script", script); + } + catch (ExecuteSqlScriptException) + { + Console.WriteLine("The current script is in saved in: " + Path.Combine(Directory.GetCurrentDirectory(), fileName)); + if (SafeConsole.Ask("retry?")) + goto retry; + + } } - public static string OpenSqlFile(this SqlPreCommand command, string fileName) + public static int Timeout = 20 * 60; + + public static void ExecuteScript(string title, string script) { - Save(command, fileName); + using (Connector.CommandTimeoutScope(Timeout)) + { + var regex = new Regex(@" *(GO|USE \w+|USE \[[^\]]+\]) *(\r?\n|$)", RegexOptions.IgnoreCase); + + var parts = regex.Split(script); + + var realParts = parts.Where(a => !string.IsNullOrWhiteSpace(a) && !regex.IsMatch(a)).ToArray(); + + int pos = 0; + + try + { + for (pos = 0; pos < realParts.Length; pos++) + { + SafeConsole.WaitExecute("Executing {0} [{1}/{2}]".FormatWith(title, pos + 1, realParts.Length), () => Executor.ExecuteNonQuery(realParts[pos])); + } + } + catch (Exception ex) + { + var sqlE = ex as SqlException ?? ex.InnerException as SqlException; + var pgE = ex as PostgresException ?? ex.InnerException as PostgresException; + if (sqlE == null && pgE == null) + throw; + + Console.WriteLine(); + Console.WriteLine(); + + var list = script.Lines(); + + var lineNumer = (pgE?.Line?.ToInt() ?? sqlE!.LineNumber - 1) + pos + parts.Take(parts.IndexOf(realParts[pos])).Sum(a => a.Lines().Length); + + SafeConsole.WriteLineColor(ConsoleColor.Red, "ERROR:"); + + var min = Math.Max(0, lineNumer - 20); + var max = Math.Min(list.Length - 1, lineNumer + 20); + + if (min > 0) + Console.WriteLine("..."); + + for (int i = min; i <= max; i++) + { + Console.Write(i + ": "); + SafeConsole.WriteLineColor(i == lineNumer ? ConsoleColor.Red : ConsoleColor.DarkRed, list[i]); + } + + if (max < list.Length - 1) + Console.WriteLine("..."); + + Console.WriteLine(); + SafeConsole.WriteLineColor(ConsoleColor.DarkRed, ex.GetType().Name + " (Number {0}): ".FormatWith(pgE?.SqlState ?? sqlE?.Number.ToString())); + SafeConsole.WriteLineColor(ConsoleColor.Red, ex.Message); - Thread.Sleep(1000); + Console.WriteLine(); + throw new ExecuteSqlScriptException(ex.Message, ex); + } + } + } + + private static void Open(string fileName) + { new Process { StartInfo = new ProcessStartInfo(Path.Combine(Directory.GetCurrentDirectory(), fileName)) @@ -112,10 +197,9 @@ public static string OpenSqlFile(this SqlPreCommand command, string fileName) UseShellExecute = true } }.Start(); - - return fileName; } + public static void Save(this SqlPreCommand command, string fileName) { string content = command.PlainSql(); @@ -124,6 +208,18 @@ public static void Save(this SqlPreCommand command, string fileName) } } + + [Serializable] + public class ExecuteSqlScriptException : Exception + { + public ExecuteSqlScriptException() { } + public ExecuteSqlScriptException(string message) : base(message) { } + public ExecuteSqlScriptException(string message, Exception inner) : base(message, inner) { } + protected ExecuteSqlScriptException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } + public class SqlPreCommandSimple : SqlPreCommand { public override bool GoBefore { get; set; } From c834179a219e06f037429b2837848b485742e559 Mon Sep 17 00:00:00 2001 From: Olmo del Corral Date: Sun, 19 Jan 2020 18:14:25 +0100 Subject: [PATCH 08/13] more on Postgres Sync and tests --- Signum.Engine/Connection/SqlConnector.cs | 3 + Signum.Engine/Engine/PostgresCatalog.cs | 4 +- Signum.Engine/Engine/PostgresFunctions.cs | 3 +- Signum.Engine/Engine/SqlBuilder.cs | 46 +++++---- Signum.Engine/Engine/SqlPreCommand.cs | 11 ++- Signum.Engine/Linq/DbExpressions.Sql.cs | 18 +++- .../DbExpressionNominator.cs | 94 ++++++++++++------- .../Linq/ExpressionVisitor/QueryBinder.cs | 2 +- .../Linq/ExpressionVisitor/QueryFormatter.cs | 19 ++-- .../Linq/ExpressionVisitor/SmartEqualizer.cs | 22 +++-- .../ExpressionVisitor/TranslatorBuilder.cs | 10 +- Signum.Engine/Schema/ObjectName.cs | 11 ++- Signum.Engine/Schema/Schema.Save.cs | 5 +- .../Schema/SchemaBuilder/SchemaBuilder.cs | 2 +- Signum.Test/LinqProvider/SelectTest.cs | 9 +- Signum.Test/LinqProvider/SqlFunctionsTest.cs | 26 ++++- Signum.Test/ObjectNameTest.cs | 2 +- 17 files changed, 196 insertions(+), 91 deletions(-) diff --git a/Signum.Engine/Connection/SqlConnector.cs b/Signum.Engine/Connection/SqlConnector.cs index 06486db20a..2c56ca8c25 100644 --- a/Signum.Engine/Connection/SqlConnector.cs +++ b/Signum.Engine/Connection/SqlConnector.cs @@ -81,6 +81,9 @@ public enum EngineEdition public class SqlConnector : Connector { + public static ResetLazy> DateFirstLazy = new ResetLazy>(() => Tuple.Create((byte)Executor.ExecuteScalar("SELECT @@DATEFIRST")!)); + public byte DateFirst => DateFirstLazy.Value.Item1; + public SqlServerVersion Version { get; set; } public SqlConnector(string connectionString, Schema schema, SqlServerVersion version) : base(schema) diff --git a/Signum.Engine/Engine/PostgresCatalog.cs b/Signum.Engine/Engine/PostgresCatalog.cs index 4cd6fbafb1..1175de4b36 100644 --- a/Signum.Engine/Engine/PostgresCatalog.cs +++ b/Signum.Engine/Engine/PostgresCatalog.cs @@ -29,7 +29,7 @@ public class PgClass : IView public string relname; public int relnamespace; public char relkind; - + public int reltuples; [AutoExpressionField] public IQueryable Triggers() => @@ -53,7 +53,7 @@ public PgNamespace Namespace() => As.Expression(() => Database.View().SingleOrDefault(t => t.oid == this.relnamespace)); } - static class RelKind + public static class RelKind { public const char Table = 'r'; public const char Index = 'i'; diff --git a/Signum.Engine/Engine/PostgresFunctions.cs b/Signum.Engine/Engine/PostgresFunctions.cs index 356043d250..ec2ee8b7cd 100644 --- a/Signum.Engine/Engine/PostgresFunctions.cs +++ b/Signum.Engine/Engine/PostgresFunctions.cs @@ -40,7 +40,8 @@ public class PostgresFunctions [SqlMethod(Name = "pg_catalog.generate_subscripts")] public static IQueryable generate_subscripts(Array array, int dimension, bool reverse) => throw new NotImplementedException(); - + [SqlMethod(Name = "pg_catalog.pg_total_relation_size")] + public static int pg_total_relation_size(int oid) => throw new NotImplementedException(); } } diff --git a/Signum.Engine/Engine/SqlBuilder.cs b/Signum.Engine/Engine/SqlBuilder.cs index 21d1e1ce40..1b3d399315 100644 --- a/Signum.Engine/Engine/SqlBuilder.cs +++ b/Signum.Engine/Engine/SqlBuilder.cs @@ -57,7 +57,7 @@ public SqlPreCommand CreateTableSql(ITable t) var systemVersioning = t.SystemVersioned == null || IsPostgres ? null : $"\r\nWITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = {t.SystemVersioned.TableName}))"; - var result = new SqlPreCommandSimple($"CREATE TABLE {t.Name}(\r\n{columns}\r\n)" + systemVersioning + ";"); + var result = new SqlPreCommandSimple($"CREATE {(IsPostgres && t.Name.IsTemporal ? "TEMPORARY " : "")}TABLE {t.Name}(\r\n{columns}\r\n)" + systemVersioning + ";"); if (!(IsPostgres && t.SystemVersioned != null)) return result; @@ -112,7 +112,7 @@ SqlPreCommand DropViewIndex(ObjectName viewName, string index) public SqlPreCommand AlterTableAddPeriod(ITable table) { - return new SqlPreCommandSimple($"ALTER TABLE {table.Name} ADD {Period(table.SystemVersioned!)}"); + return new SqlPreCommandSimple($"ALTER TABLE {table.Name} ADD {Period(table.SystemVersioned!)};"); } string? Period(SystemVersionedInfo sv) { @@ -125,37 +125,37 @@ public SqlPreCommand AlterTableAddPeriod(ITable table) public SqlPreCommand AlterTableDropPeriod(ITable table) { - return new SqlPreCommandSimple($"ALTER TABLE {table.Name} DROP PERIOD FOR SYSTEM_TIME"); + return new SqlPreCommandSimple($"ALTER TABLE {table.Name} DROP PERIOD FOR SYSTEM_TIME;"); } public SqlPreCommand AlterTableEnableSystemVersioning(ITable table) { - return new SqlPreCommandSimple($"ALTER TABLE {table.Name} SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = {table.SystemVersioned!.TableName}))"); + return new SqlPreCommandSimple($"ALTER TABLE {table.Name} SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = {table.SystemVersioned!.TableName}));"); } public SqlPreCommandSimple AlterTableDisableSystemVersioning(ObjectName tableName) { - return new SqlPreCommandSimple($"ALTER TABLE {tableName} SET (SYSTEM_VERSIONING = OFF)"); + return new SqlPreCommandSimple($"ALTER TABLE {tableName} SET (SYSTEM_VERSIONING = OFF);"); } public SqlPreCommand AlterTableDropColumn(ITable table, string columnName) { - return new SqlPreCommandSimple("ALTER TABLE {0} DROP COLUMN {1}".FormatWith(table.Name, columnName.SqlEscape(isPostgres))); + return new SqlPreCommandSimple("ALTER TABLE {0} DROP COLUMN {1};".FormatWith(table.Name, columnName.SqlEscape(isPostgres))); } public SqlPreCommand AlterTableAddColumn(ITable table, IColumn column, SqlBuilder.DefaultConstraint? tempDefault = null) { - return new SqlPreCommandSimple("ALTER TABLE {0} ADD {1}".FormatWith(table.Name, CreateColumn(column, tempDefault ?? GetDefaultConstaint(table, column), isChange: false))); + return new SqlPreCommandSimple("ALTER TABLE {0} ADD {1};".FormatWith(table.Name, CreateColumn(column, tempDefault ?? GetDefaultConstaint(table, column), isChange: false))); } public SqlPreCommand AlterTableAddOldColumn(ITable table, DiffColumn column) { - return new SqlPreCommandSimple("ALTER TABLE {0} ADD {1}".FormatWith(table.Name, CreateOldColumn(column))); + return new SqlPreCommandSimple("ALTER TABLE {0} ADD {1};".FormatWith(table.Name, CreateOldColumn(column))); } public SqlPreCommand AlterTableAlterColumn(ITable table, IColumn column, string? defaultConstraintName = null, ObjectName? forceTableName = null) { - var alterColumn = new SqlPreCommandSimple("ALTER TABLE {0} ALTER COLUMN {1}".FormatWith(forceTableName ?? table.Name, CreateColumn(column, null, isChange: true))); + var alterColumn = new SqlPreCommandSimple("ALTER TABLE {0} ALTER COLUMN {1};".FormatWith(forceTableName ?? table.Name, CreateColumn(column, null, isChange: true))); if (column.Default == null) return alterColumn; @@ -357,7 +357,7 @@ SELECT MIN({oldPrimaryKey.SqlEscape(IsPostgres)}) FROM {oldTableName} {(!uniqueIndex.Where.HasText() ? "" : "WHERE " + uniqueIndex.Where.Replace(columnReplacement))} GROUP BY {oldColumns} -){(!uniqueIndex.Where.HasText() ? "" : "AND " + uniqueIndex.Where.Replace(columnReplacement))}")!); +){(!uniqueIndex.Where.HasText() ? "" : "AND " + uniqueIndex.Where.Replace(columnReplacement))};")!); } public SqlPreCommand? RemoveDuplicatesIfNecessary(UniqueTableIndex uniqueIndex, Replacements rep) @@ -529,16 +529,16 @@ public SqlPreCommand MoveRows(ObjectName oldTable, ObjectName newTable, IEnumera SqlPreCommandSimple command = new SqlPreCommandSimple( @"INSERT INTO {0} ({2}) SELECT {3} -FROM {1} as [table]".FormatWith( +FROM {1} as [table];".FormatWith( newTable, oldTable, columnNames.ToString(a => a.SqlEscape(isPostgres), ", "), columnNames.ToString(a => "[table]." + a.SqlEscape(isPostgres), ", "))); return SqlPreCommand.Combine(Spacing.Simple, - new SqlPreCommandSimple("SET IDENTITY_INSERT {0} ON".FormatWith(newTable)) { GoBefore = true }, + new SqlPreCommandSimple("SET IDENTITY_INSERT {0} ON;".FormatWith(newTable)) { GoBefore = true }, command, - new SqlPreCommandSimple("SET IDENTITY_INSERT {0} OFF".FormatWith(newTable)) { GoAfter = true })!; + new SqlPreCommandSimple("SET IDENTITY_INSERT {0} OFF;".FormatWith(newTable)) { GoAfter = true })!; } public SqlPreCommand RenameTable(ObjectName oldName, string newName) @@ -592,12 +592,12 @@ public SqlPreCommandSimple SetMultiUser(string databaseName) public SqlPreCommandSimple SetSnapshotIsolation(string databaseName, bool value) { - return new SqlPreCommandSimple("ALTER DATABASE {0} SET ALLOW_SNAPSHOT_ISOLATION {1}".FormatWith(databaseName, value ? "ON" : "OFF")); + return new SqlPreCommandSimple("ALTER DATABASE {0} SET ALLOW_SNAPSHOT_ISOLATION {1};".FormatWith(databaseName, value ? "ON" : "OFF")); } public SqlPreCommandSimple MakeSnapshotIsolationDefault(string databaseName, bool value) { - return new SqlPreCommandSimple("ALTER DATABASE {0} SET READ_COMMITTED_SNAPSHOT {1}".FormatWith(databaseName, value ? "ON" : "OFF")); + return new SqlPreCommandSimple("ALTER DATABASE {0} SET READ_COMMITTED_SNAPSHOT {1};".FormatWith(databaseName, value ? "ON" : "OFF")); } public SqlPreCommandSimple SelectRowCount() @@ -615,27 +615,27 @@ public SqlPreCommand CreateSchema(SchemaName schemaName) public SqlPreCommand DropSchema(SchemaName schemaName) { - return new SqlPreCommandSimple("DROP SCHEMA {0}".FormatWith(schemaName)) { GoAfter = true, GoBefore = true }; + return new SqlPreCommandSimple("DROP SCHEMA {0};".FormatWith(schemaName)) { GoAfter = true, GoBefore = true }; } public SqlPreCommandSimple DisableForeignKey(ObjectName tableName, string foreignKey) { - return new SqlPreCommandSimple("ALTER TABLE {0} NOCHECK CONSTRAINT {1}".FormatWith(tableName, foreignKey)); + return new SqlPreCommandSimple("ALTER TABLE {0} NOCHECK CONSTRAINT {1};".FormatWith(tableName, foreignKey)); } public SqlPreCommandSimple EnableForeignKey(ObjectName tableName, string foreignKey) { - return new SqlPreCommandSimple("ALTER TABLE {0} WITH CHECK CHECK CONSTRAINT {1}".FormatWith(tableName, foreignKey)); + return new SqlPreCommandSimple("ALTER TABLE {0} WITH CHECK CHECK CONSTRAINT {1};".FormatWith(tableName, foreignKey)); } public SqlPreCommandSimple DisableIndex(ObjectName tableName, string indexName) { - return new SqlPreCommandSimple("ALTER INDEX [{0}] ON {1} DISABLE".FormatWith(indexName, tableName)); + return new SqlPreCommandSimple("ALTER INDEX [{0}] ON {1} DISABLE;".FormatWith(indexName, tableName)); } public SqlPreCommandSimple RebuildIndex(ObjectName tableName, string indexName) { - return new SqlPreCommandSimple("ALTER INDEX [{0}] ON {1} REBUILD".FormatWith(indexName, tableName)); + return new SqlPreCommandSimple("ALTER INDEX [{0}] ON {1} REBUILD;".FormatWith(indexName, tableName)); } public SqlPreCommandSimple DropPrimaryKeyConstraint(ObjectName tableName) @@ -660,16 +660,14 @@ EXEC DB.dbo.sp_executesql @sql" return new SqlPreCommandSimple(command); } - internal SqlPreCommand? DropStatistics(string tn, List list) { if (list.IsEmpty()) return null; - return new SqlPreCommandSimple("DROP STATISTICS " + list.ToString(s => tn.SqlEscape(isPostgres) + "." + s.StatsName.SqlEscape(isPostgres), ",\r\n")); + return new SqlPreCommandSimple("DROP STATISTICS " + list.ToString(s => tn.SqlEscape(isPostgres) + "." + s.StatsName.SqlEscape(isPostgres), ",\r\n") + ";"); } - - public SqlPreCommand TruncateTable(ObjectName tableName) => new SqlPreCommandSimple($"TRUNCATE TABLE {tableName}"); + public SqlPreCommand TruncateTable(ObjectName tableName) => new SqlPreCommandSimple($"TRUNCATE TABLE {tableName};"); } } diff --git a/Signum.Engine/Engine/SqlPreCommand.cs b/Signum.Engine/Engine/SqlPreCommand.cs index 4852449b06..d209f7d9d1 100644 --- a/Signum.Engine/Engine/SqlPreCommand.cs +++ b/Signum.Engine/Engine/SqlPreCommand.cs @@ -271,10 +271,17 @@ internal static string Encode(object? value) return "\'" + g.ToString() + "'"; if (value is DateTime dt) - return "convert(datetime, '{0}', 126)".FormatWith(dt.ToString("yyyy-MM-ddThh:mm:ss.fff", CultureInfo.InvariantCulture)); + { + return Schema.Current.Settings.IsPostgres ? + "'{0}'".FormatWith(dt.ToString("yyyy-MM-dd hh:mm:ss.fff", CultureInfo.InvariantCulture)) : + "convert(datetime, '{0}', 126)".FormatWith(dt.ToString("yyyy-MM-ddThh:mm:ss.fff", CultureInfo.InvariantCulture)); + } if (value is TimeSpan ts) - return "convert(time, '{0:g}')".FormatWith(ts.ToString("g", CultureInfo.InvariantCulture)); + { + var str = ts.ToString("g", CultureInfo.InvariantCulture); + return Schema.Current.Settings.IsPostgres ? str : "convert(time, '{0}')".FormatWith(str); + } if (value is bool b) { diff --git a/Signum.Engine/Linq/DbExpressions.Sql.cs b/Signum.Engine/Linq/DbExpressions.Sql.cs index bbf9c114c4..c4d21ed789 100644 --- a/Signum.Engine/Linq/DbExpressions.Sql.cs +++ b/Signum.Engine/Linq/DbExpressions.Sql.cs @@ -345,7 +345,7 @@ public AggregateExpression(Type type, AggregateSqlFunction aggregateFunction, IE public override string ToString() { - return $"{AggregateFunction}({(AggregateFunction == AggregateSqlFunction.CountDistinct ? "Distinct " : "")}{Arguments?.ToString(", ") ?? "*"})"; + return $"{AggregateFunction}({(AggregateFunction == AggregateSqlFunction.CountDistinct ? "Distinct " : "")}{Arguments.ToString(", ") ?? "*"})"; } protected override Expression Accept(DbExpressionVisitor visitor) @@ -647,6 +647,8 @@ internal enum PostgresFunction trunc, substr, repeat, + date_trunc, + age, } internal enum SqlEnums @@ -709,10 +711,18 @@ protected override Expression Accept(DbExpressionVisitor visitor) return visitor.VisitToDayOfWeek(this); } - public static ResetLazy> DateFirst = new ResetLazy>(() => Tuple.Create(Schema.Current.Settings.IsPostgres ? (byte)1 /*no idea :D*/ : (byte)Executor.ExecuteScalar("SELECT @@DATEFIRST")!)); + internal static MethodInfo miToDayOfWeekPostgres = ReflectionTools.GetMethodInfo(() => ToDayOfWeekPostgres(1)); + public static DayOfWeek? ToDayOfWeekPostgres(int? postgressWeekDay) + { + if (postgressWeekDay == null) + return null; + + return (DayOfWeek)(postgressWeekDay); + } + - internal static MethodInfo miToDayOfWeek = ReflectionTools.GetMethodInfo(() => ToDayOfWeek(1, 1)); - public static DayOfWeek? ToDayOfWeek(int? sqlServerWeekDay, byte dateFirst) + internal static MethodInfo miToDayOfWeekSql = ReflectionTools.GetMethodInfo(() => ToDayOfWeekSql(1, 1)); + public static DayOfWeek? ToDayOfWeekSql(int? sqlServerWeekDay, byte dateFirst) { if (sqlServerWeekDay == null) return null; diff --git a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs index af3ee90aca..31de546958 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs @@ -258,7 +258,9 @@ protected override Expression VisitConstant(ConstantExpression c) { if (ut == typeof(DayOfWeek)) { - var dayNumber = c.Value == null ? (int?)null : ToDayOfWeekExpression.ToSqlWeekDay((DayOfWeek)c.Value, ToDayOfWeekExpression.DateFirst.Value.Item1); + var dayNumber = c.Value == null ? (int?)null : + isPostgres ? (int)(DayOfWeek)c.Value : + ToDayOfWeekExpression.ToSqlWeekDay((DayOfWeek)c.Value, ((SqlConnector)Connector.Current).DateFirst); return new ToDayOfWeekExpression(Add(Expression.Constant(dayNumber, typeof(int?)))); } @@ -549,36 +551,43 @@ protected Expression GetFormatToString(MethodCallExpression m, string? defaultFo return new ToDayOfWeekExpression(number).TryConvert(typeof(DayOfWeek)); } - + private Expression? TrySqlStartOf(Expression expression, SqlEnums part) { Expression expr = Visit(expression); if (innerProjection || !Has(expr)) return null; - Expression result = - TrySqlFunction(null, SqlFunction.DATEADD, expr.Type, new SqlLiteralExpression(part), - TrySqlFunction(null, SqlFunction.DATEDIFF, typeof(int), new SqlLiteralExpression(part), new SqlConstantExpression(0), expr)!, - new SqlConstantExpression(0))!; - - return Add(result); - } - - private Expression? TrySqlSecondsStart(Expression expression) - { - Expression expr = Visit(expression); - if (innerProjection || !Has(expr)) - return null; + if (isPostgres) + { + Expression? result = + TrySqlFunction(null, PostgresFunction.date_trunc, expr.Type, + new SqlConstantExpression(part.ToString()), expr); + return Add(result); + } + else + { + if (part == SqlEnums.second) + { + Expression result = + TrySqlFunction(null, SqlFunction.DATEADD, expr.Type, new SqlLiteralExpression(SqlEnums.millisecond), + Expression.Negate(TrySqlFunction(null, SqlFunction.DATEPART, typeof(int), new SqlLiteralExpression(SqlEnums.millisecond), expr)), expr)!; - Expression result = - TrySqlFunction(null, SqlFunction.DATEADD, expr.Type, new SqlLiteralExpression(SqlEnums.millisecond), - Expression.Negate(TrySqlFunction(null, SqlFunction.DATEPART, typeof(int), new SqlLiteralExpression(SqlEnums.millisecond), expr)), expr)!; + return Add(result); + } + else + { + Expression result = + TrySqlFunction(null, SqlFunction.DATEADD, expr.Type, new SqlLiteralExpression(part), + TrySqlFunction(null, SqlFunction.DATEDIFF, typeof(int), new SqlLiteralExpression(part), new SqlConstantExpression(0), expr)!, + new SqlConstantExpression(0))!; - return Add(result); + return Add(result); + } + } } - private Expression? TryAddSubtractDateTimeTimeSpan(Expression date, Expression time, bool add) { Expression exprDate = Visit(date); @@ -586,8 +595,8 @@ protected Expression GetFormatToString(MethodCallExpression m, string? defaultFo if (innerProjection || !Has(exprDate) || !Has(exprTime)) return null; - var castDate = new SqlCastExpression(typeof(DateTime), exprDate, new AbstractDbType(SqlDbType.DateTime)); //Just in case is a Date - var castTime = new SqlCastExpression(typeof(TimeSpan), exprTime, new AbstractDbType(SqlDbType.Time)); //Just in case is a Date + var castDate = new SqlCastExpression(typeof(DateTime), exprDate, new AbstractDbType(SqlDbType.DateTime, NpgsqlDbType.Timestamp)); //Just in case is a Date + var castTime = new SqlCastExpression(typeof(TimeSpan), exprTime, new AbstractDbType(SqlDbType.Time, NpgsqlDbType.Time)); //Just in case is a Date var result = add ? Expression.Add(castDate, castTime) : Expression.Subtract(castDate, castTime); @@ -595,22 +604,43 @@ protected Expression GetFormatToString(MethodCallExpression m, string? defaultFo return Add(result); } - private Expression? TryDatePartTo(SqlLiteralExpression datePart, Expression start, Expression end) + private Expression? TryDatePartTo(SqlEnums unit, Expression start, Expression end) { Expression exprStart = Visit(start); Expression exprEnd = Visit(end); if (innerProjection || !Has(exprStart) || !Has(exprEnd)) return null; - var diff = new SqlFunctionExpression(typeof(int), null, SqlFunction.DATEDIFF.ToString(), - new[] { datePart, exprStart, exprEnd }); + if (isPostgres) + { + var age = new SqlFunctionExpression(typeof(DateSpan), null, PostgresFunction.age.ToString(), new[] { exprStart, exprEnd }); + + SqlFunctionExpression Extract( SqlEnums part, Expression period) + { + return new SqlFunctionExpression(typeof(int), null, PostgresFunction.EXTRACT.ToString(), new[] { new SqlLiteralExpression(part), period }); + } + + if (unit == SqlEnums.month) + return Add(Expression.Add(Extract(SqlEnums.year, age), Expression.Multiply(Extract(SqlEnums.month, age), new SqlConstantExpression(12, typeof(int))))); + else if (unit == SqlEnums.year) + return Add(Extract(SqlEnums.year, age)); + else + throw new UnexpectedValueException(unit); + } + else + { + SqlLiteralExpression datePart = new SqlLiteralExpression(unit); + + var diff = new SqlFunctionExpression(typeof(int), null, SqlFunction.DATEDIFF.ToString(), + new[] { datePart, exprStart, exprEnd }); - var add = new SqlFunctionExpression(typeof(DateTime), null, SqlFunction.DATEADD.ToString(), - new[] { datePart, diff, exprStart }); + var add = new SqlFunctionExpression(typeof(DateTime), null, SqlFunction.DATEADD.ToString(), + new[] { datePart, diff, exprStart }); - return Add(new CaseExpression(new[]{ + return Add(new CaseExpression(new[]{ new When(Expression.GreaterThan(add, exprEnd), Expression.Subtract(diff, Expression.Constant(1)))}, - diff)); + diff)); + } } @@ -1443,9 +1473,9 @@ string getDatePart() case "DateTimeExtensions.WeekStart": return TrySqlStartOf(m.GetArgument("dateTime"), SqlEnums.week); case "DateTimeExtensions.HourStart": return TrySqlStartOf(m.GetArgument("dateTime"), SqlEnums.hour); case "DateTimeExtensions.MinuteStart": return TrySqlStartOf(m.GetArgument("dateTime"), SqlEnums.minute); - case "DateTimeExtensions.SecondStart": return TrySqlSecondsStart(m.GetArgument("dateTime")); - case "DateTimeExtensions.YearsTo": return TryDatePartTo(new SqlLiteralExpression(SqlEnums.year), m.GetArgument("start"), m.GetArgument("end")); - case "DateTimeExtensions.MonthsTo": return TryDatePartTo(new SqlLiteralExpression(SqlEnums.month), m.GetArgument("start"), m.GetArgument("end")); + case "DateTimeExtensions.SecondStart": return TrySqlStartOf(m.GetArgument("dateTime"), SqlEnums.second); + case "DateTimeExtensions.YearsTo": return TryDatePartTo(SqlEnums.year, m.GetArgument("start"), m.GetArgument("end")); + case "DateTimeExtensions.MonthsTo": return TryDatePartTo(SqlEnums.month, m.GetArgument("start"), m.GetArgument("end")); case "DateTimeExtensions.Quarter": return TrySqlFunction(null, getDatePart(), m.Type, new SqlLiteralExpression(SqlEnums.quarter), m.Arguments.Single()); case "DateTimeExtensions.WeekNumber": return TrySqlFunction(null, getDatePart(), m.Type, new SqlLiteralExpression(SqlEnums.week), m.Arguments.Single()); diff --git a/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs b/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs index 2b0d263887..df4ae73146 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs @@ -963,7 +963,7 @@ private Expression BindContains(Type resultType, Expression source, Expression i if (newItem.Type.UnNullify() == typeof(PrimaryKey)) return SmartEqualizer.InPrimaryKey(newItem, col.Cast().ToArray()); - return SmartEqualizer.In(newItem, col.Cast().ToArray()); + return SmartEqualizer.In(newItem, col.Cast().ToArray(), isPostgres); } else { diff --git a/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs b/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs index bee53a9c36..cf5e45c650 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs @@ -554,12 +554,19 @@ protected internal override Expression VisitAggregate(AggregateExpression aggreg if (aggregate.AggregateFunction == AggregateSqlFunction.CountDistinct) sb.Append("DISTINCT "); - for (int i = 0, n = aggregate.Arguments.Count; i < n; i++) + if (aggregate.Arguments.Count == 1 && aggregate.Arguments[0] == null && aggregate.AggregateFunction == AggregateSqlFunction.Count) { - Expression exp = aggregate.Arguments[i]; - if (i > 0) - sb.Append(", "); - this.Visit(exp); + sb.Append("*"); + } + else + { + for (int i = 0, n = aggregate.Arguments.Count; i < n; i++) + { + Expression exp = aggregate.Arguments[i]; + if (i > 0) + sb.Append(", "); + this.Visit(exp); + } } sb.Append(")"); @@ -765,7 +772,7 @@ protected internal override Expression VisitJoin(JoinExpression join) this.Visit(join.Condition); this.Indent(Indentation.Outer); } - else if (isPostgres) + else if (isPostgres && join.JoinType != JoinType.CrossJoin) { this.AppendNewLine(Indentation.Inner); sb.Append("ON true"); diff --git a/Signum.Engine/Linq/ExpressionVisitor/SmartEqualizer.cs b/Signum.Engine/Linq/ExpressionVisitor/SmartEqualizer.cs index e20c807ba2..8e97e761d7 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/SmartEqualizer.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/SmartEqualizer.cs @@ -571,18 +571,28 @@ internal static Expression TypeIn(Expression typeExpr, IEnumerable collect throw new InvalidOperationException("Impossible to resolve '{0}' in '{1}'".FormatWith(typeExpr.ToString(), collection.ToString(t=>t.TypeName(), ", "))); } - public static Expression In(Expression element, object[] values) + public static Expression In(Expression element, object[] values, bool isPostgres) { var nominate = DbExpressionNominator.FullNominate(element)!; if (nominate is ToDayOfWeekExpression dowe) { - byte dateFirs = ToDayOfWeekExpression.DateFirst.Value.Item1; - var sqlWeekDays = values.Cast() - .Select(a => (object)ToDayOfWeekExpression.ToSqlWeekDay(a, dateFirs)) - .ToArray(); + if (isPostgres) + { + var sqlWeekDays = values.Cast() + .Select(a => (object)(int)a) + .ToArray(); + return InExpression.FromValues(dowe.Expression, sqlWeekDays); + } + else + { - return InExpression.FromValues(dowe.Expression, sqlWeekDays); + byte dateFirs = ((SqlConnector)Connector.Current).DateFirst; + var sqlWeekDays = values.Cast() + .Select(a => (object)ToDayOfWeekExpression.ToSqlWeekDay(a, dateFirs)) + .ToArray(); + return InExpression.FromValues(dowe.Expression, sqlWeekDays); + } } else return InExpression.FromValues(nominate, values); diff --git a/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs b/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs index fa2cd895f0..d187e17278 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs @@ -546,7 +546,15 @@ protected internal override Expression VisitToDayOfWeek(ToDayOfWeekExpression to { var result = this.Visit(toDayOfWeek.Expression); - return Expression.Call(ToDayOfWeekExpression.miToDayOfWeek, result, Expression.Constant(ToDayOfWeekExpression.DateFirst.Value.Item1, typeof(byte))); + if (Schema.Current.Settings.IsPostgres) + { + return Expression.Call(ToDayOfWeekExpression.miToDayOfWeekPostgres, result); + } + else + { + var dateFirst = ((SqlConnector)Connector.Current).DateFirst; + return Expression.Call(ToDayOfWeekExpression.miToDayOfWeekSql, result, Expression.Constant(dateFirst, typeof(byte))); + } } protected override Expression VisitNew(NewExpression node) diff --git a/Signum.Engine/Schema/ObjectName.cs b/Signum.Engine/Schema/ObjectName.cs index 7fe5b0c945..74433ade6a 100644 --- a/Signum.Engine/Schema/ObjectName.cs +++ b/Signum.Engine/Schema/ObjectName.cs @@ -193,7 +193,7 @@ public class ObjectName : IEquatable public string Name { get; private set; } public bool IsPostgres { get; private set; } - public SchemaName Schema { get; private set; } + public SchemaName Schema { get; private set; } // null only for postgres temporary public ObjectName(SchemaName schema, string name, bool isPostgres) { @@ -201,12 +201,15 @@ public ObjectName(SchemaName schema, string name, bool isPostgres) if (isPostgres && this.Name.Length > MaxPostgreeSize) throw new InvalidOperationException($"The name '{name}' is too long, consider using TableNameAttribute/ColumnNameAttribute"); - this.Schema = schema ?? throw new ArgumentNullException(nameof(schema)); + this.Schema = schema ?? (isPostgres && name.StartsWith("#") ? (SchemaName)null! : throw new ArgumentNullException(nameof(schema))); this.IsPostgres = isPostgres; } public override string ToString() { + if (Schema == null) + return Name.SqlEscape(IsPostgres); + return Schema.ToString() + "." + Name.SqlEscape(IsPostgres); } @@ -219,7 +222,7 @@ public bool Equals(ObjectName other) public override int GetHashCode() { - return Name.GetHashCode() ^ Schema.GetHashCode(); + return Name.GetHashCode() ^ Schema?.GetHashCode() ?? 0; } public static ObjectName Parse(string? name, bool isPostgres) @@ -276,7 +279,7 @@ public ObjectName OnDatabase(DatabaseName? databaseName) if (databaseName != null && databaseName.IsPostgres != this.IsPostgres) throw new Exception("Inconsitent IsPostgres"); - return new ObjectName(new SchemaName(databaseName, Schema.Name, IsPostgres), Name, IsPostgres); + return new ObjectName(new SchemaName(databaseName, Schema!.Name, IsPostgres), Name, IsPostgres); } public ObjectName OnSchema(SchemaName schemaName) diff --git a/Signum.Engine/Schema/Schema.Save.cs b/Signum.Engine/Schema/Schema.Save.cs index 48851dc871..6c027b3d8e 100644 --- a/Signum.Engine/Schema/Schema.Save.cs +++ b/Signum.Engine/Schema/Schema.Save.cs @@ -220,9 +220,10 @@ internal static InsertCacheDisableIdentity InitializeInsertDisableIdentity(Table var isPostgres = Schema.Current.Settings.IsPostgres; Func insertPattern = (suffixes) => - "INSERT INTO {0}\r\n ({1})\r\nVALUES\r\n{2};".FormatWith(table.Name, + "INSERT INTO {0}\r\n ({1})\r\n{2}VALUES\r\n{3};".FormatWith(table.Name, trios.ToString(p => p.SourceColumn.SqlEscape(isPostgres), ", "), - suffixes.ToString(s => " (" + trios.ToString(p => p.ParameterName + s, ", ") + ")", "\r\n")); + isPostgres? "OVERRIDING SYSTEM VALUE\r\n" : null, + suffixes.ToString(s => " (" + trios.ToString(p => p.ParameterName + s, ", ") + ")", ",\r\n")); var expr = Expression.Lambda>>( CreateBlock(trios.Select(a => a.ParameterBuilder), assigments, paramList), paramIdent, paramForbidden, paramSuffix, paramList); diff --git a/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs b/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs index f242e801e3..b437d402d6 100644 --- a/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs +++ b/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs @@ -822,7 +822,7 @@ private SchemaName GetSchemaName(TableNameAttribute tn) ServerName? server = tn.ServerName == null ? null : new ServerName(tn.ServerName, isPostgres); DatabaseName? dataBase = tn.DatabaseName == null && server == null ? null : new DatabaseName(server, tn.DatabaseName!, isPostgres); - SchemaName schema = tn.SchemaName == null && dataBase == null ? SchemaName.Default(isPostgres) : new SchemaName(dataBase, tn.SchemaName!, isPostgres); + SchemaName schema = tn.SchemaName == null && dataBase == null ? (tn.Name.StartsWith("#") && isPostgres ? null! : SchemaName.Default(isPostgres)) : new SchemaName(dataBase, tn.SchemaName!, isPostgres); return schema; } diff --git a/Signum.Test/LinqProvider/SelectTest.cs b/Signum.Test/LinqProvider/SelectTest.cs index 18915180d6..11e62e7f77 100644 --- a/Signum.Test/LinqProvider/SelectTest.cs +++ b/Signum.Test/LinqProvider/SelectTest.cs @@ -636,7 +636,14 @@ public void SelectEmbedded() [Fact] public void SelectView() { - var list = Database.View().ToList(); + if (Schema.Current.Settings.IsPostgres) + { + var list = Database.View().ToList(); + } + else + { + var list = Database.View().ToList(); + } } [Fact] diff --git a/Signum.Test/LinqProvider/SqlFunctionsTest.cs b/Signum.Test/LinqProvider/SqlFunctionsTest.cs index dd35f699a2..77ae50ecf4 100644 --- a/Signum.Test/LinqProvider/SqlFunctionsTest.cs +++ b/Signum.Test/LinqProvider/SqlFunctionsTest.cs @@ -96,6 +96,17 @@ public void DateTimeFunctions() Dump((NoteWithDateEntity n) => n.CreationTime.Millisecond); } + [Fact] + public void DateTimeFunctionsStart() + { + Dump((NoteWithDateEntity n) => n.CreationTime.MonthStart()); + Dump((NoteWithDateEntity n) => n.CreationTime.Date); + Dump((NoteWithDateEntity n) => n.CreationTime.WeekStart()); + Dump((NoteWithDateEntity n) => n.CreationTime.HourStart()); + Dump((NoteWithDateEntity n) => n.CreationTime.MinuteStart()); + Dump((NoteWithDateEntity n) => n.CreationTime.SecondStart()); + } + [Fact] public void DayOfWeekWhere() { @@ -161,6 +172,13 @@ public void DateDiffFunctions() Dump((NoteWithDateEntity n) => (n.CreationTime.AddDays(1) - n.CreationTime).TotalMilliseconds.InSql()); } + [Fact] + public void DateDiffFunctionsTo() + { + Dump((NoteWithDateEntity n) => n.CreationTime.YearsTo(n.CreationTime).InSql()); + Dump((NoteWithDateEntity n) => n.CreationTime.MonthsTo(n.CreationTime).InSql()); + } + [Fact] public void DateFunctions() { @@ -355,9 +373,11 @@ from s4 in songs select MinimumExtensions.MinimumScalar(x, y)).ToList(); var t4 = PerfCounter.Ticks; - - Assert.True(PerfCounter.ToMilliseconds(t1, t2) < PerfCounter.ToMilliseconds(t3, t4)); - Assert.True(PerfCounter.ToMilliseconds(t2, t3) < PerfCounter.ToMilliseconds(t3, t4)); + if (!Schema.Current.Settings.IsPostgres) + { + Assert.True(PerfCounter.ToMilliseconds(t1, t2) < PerfCounter.ToMilliseconds(t3, t4)); + Assert.True(PerfCounter.ToMilliseconds(t2, t3) < PerfCounter.ToMilliseconds(t3, t4)); + } } [Fact] diff --git a/Signum.Test/ObjectNameTest.cs b/Signum.Test/ObjectNameTest.cs index 35c018a633..bee944447c 100644 --- a/Signum.Test/ObjectNameTest.cs +++ b/Signum.Test/ObjectNameTest.cs @@ -66,7 +66,7 @@ public void ParseSchemaNameEscaped() { if (isPostgres) { - var simple = ObjectName.Parse("\"Select\".MyTable", isPostgres); + var simple = ObjectName.Parse("\"Select\".mytable", isPostgres); Assert.Equal("mytable", simple.Name); Assert.Equal("Select", simple.Schema.Name); Assert.Equal("\"Select\".mytable", simple.ToString()); From 7b358f18ebfebff7cdaa2ac05c66081e5d46d351 Mon Sep 17 00:00:00 2001 From: Olmo del Corral Date: Tue, 21 Jan 2020 23:12:38 +0100 Subject: [PATCH 09/13] add IntervalExpression --- Signum.Engine/Connection/FieldReader.cs | 18 +++++ Signum.Engine/Linq/DbExpressions.Signum.cs | 26 +++---- Signum.Engine/Linq/DbExpressions.Sql.cs | 67 +++++++++++++++++++ Signum.Engine/Linq/DbQueryProvider.cs | 7 +- .../ExpressionVisitor/DbExpressionVisitor.cs | 22 ++++-- .../Linq/ExpressionVisitor/QueryBinder.cs | 1 - Signum.Engine/Schema/Schema.Expressions.cs | 49 +++++++------- Signum.Engine/Schema/Schema.cs | 2 +- Signum.Entities/SystemTime.cs | 54 +-------------- 9 files changed, 148 insertions(+), 98 deletions(-) diff --git a/Signum.Engine/Connection/FieldReader.cs b/Signum.Engine/Connection/FieldReader.cs index 7686e1a599..09e8349a5f 100644 --- a/Signum.Engine/Connection/FieldReader.cs +++ b/Signum.Engine/Connection/FieldReader.cs @@ -558,6 +558,19 @@ public T[] GetArray(int ordinal) return (T[])this.reader[ordinal]; } + static MethodInfo miGetRange = ReflectionTools.GetMethodInfo((FieldReader r) => r.GetRange(0)).GetGenericMethodDefinition(); + + public NpgsqlTypes.NpgsqlRange? GetRange(int ordinal) + { + LastOrdinal = ordinal; + if (reader.IsDBNull(ordinal)) + { + return (NpgsqlTypes.NpgsqlRange)(object)null!; + } + + return (NpgsqlTypes.NpgsqlRange)this.reader[ordinal]; + } + static Dictionary methods = typeof(FieldReader).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) .Where(m => m.Name != "GetExpression" && m.Name != "IsNull") @@ -583,6 +596,11 @@ public static Expression GetExpression(Expression reader, int ordinal, Type type return Expression.Call(reader, miGetArray.MakeGenericMethod(type.ElementType()!), Expression.Constant(ordinal)); } + if (type.IsInstantiationOf(typeof(NpgsqlTypes.NpgsqlRange<>))) + { + return Expression.Call(reader, miGetRange.MakeGenericMethod(type.GetGenericArguments()[0]!), Expression.Constant(ordinal)); + } + throw new InvalidOperationException("Type {0} not supported".FormatWith(type)); } diff --git a/Signum.Engine/Linq/DbExpressions.Signum.cs b/Signum.Engine/Linq/DbExpressions.Signum.cs index c4c59ef8a4..4dcebe4f50 100644 --- a/Signum.Engine/Linq/DbExpressions.Signum.cs +++ b/Signum.Engine/Linq/DbExpressions.Signum.cs @@ -22,7 +22,7 @@ internal class EntityExpression : DbExpression public readonly Table Table; public readonly PrimaryKeyExpression ExternalId; - public readonly NewExpression? ExternalPeriod; + public readonly IntervalExpression? ExternalPeriod; //Optional public readonly Alias? TableAlias; @@ -31,15 +31,15 @@ internal class EntityExpression : DbExpression public readonly bool AvoidExpandOnRetrieving; - public readonly NewExpression? TablePeriod; + public readonly IntervalExpression? TablePeriod; public EntityExpression(Type type, PrimaryKeyExpression externalId, - NewExpression? externalPeriod, + IntervalExpression? externalPeriod, Alias? tableAlias, IEnumerable? bindings, - IEnumerable? mixins, - NewExpression? tablePeriod, bool avoidExpandOnRetrieving) + IEnumerable? mixins, + IntervalExpression? tablePeriod, bool avoidExpandOnRetrieving) : base(DbExpressionType.Entity, type) { if (type == null) @@ -254,10 +254,10 @@ internal class ImplementedByAllExpression : DbExpression { public readonly Expression Id; public readonly TypeImplementedByAllExpression TypeId; - public readonly NewExpression? ExternalPeriod; + public readonly IntervalExpression? ExternalPeriod; - public ImplementedByAllExpression(Type type, Expression id, TypeImplementedByAllExpression typeId, NewExpression? externalPeriod) + public ImplementedByAllExpression(Type type, Expression id, TypeImplementedByAllExpression typeId, IntervalExpression? externalPeriod) : base(DbExpressionType.ImplementedByAll, type) { if (id == null) @@ -430,9 +430,9 @@ internal class MListExpression : DbExpression { public readonly PrimaryKeyExpression BackID; // not readonly public readonly TableMList TableMList; - public readonly NewExpression? ExternalPeriod; + public readonly IntervalExpression? ExternalPeriod; - public MListExpression(Type type, PrimaryKeyExpression backID, NewExpression? externalPeriod, TableMList tr) + public MListExpression(Type type, PrimaryKeyExpression backID, IntervalExpression? externalPeriod, TableMList tr) : base(DbExpressionType.MList, type) { this.BackID = backID; @@ -454,10 +454,10 @@ protected override Expression Accept(DbExpressionVisitor visitor) internal class AdditionalFieldExpression : DbExpression { public readonly PrimaryKeyExpression BackID; // not readonly - public readonly NewExpression? ExternalPeriod; + public readonly IntervalExpression? ExternalPeriod; public readonly PropertyRoute Route; - public AdditionalFieldExpression(Type type, PrimaryKeyExpression backID, NewExpression? externalPeriod, PropertyRoute route) + public AdditionalFieldExpression(Type type, PrimaryKeyExpression backID, IntervalExpression? externalPeriod, PropertyRoute route) : base(DbExpressionType.AdditionalField, type) { this.BackID = backID; @@ -511,9 +511,9 @@ internal class MListElementExpression : DbExpression public readonly Alias Alias; - public readonly NewExpression? TablePeriod; + public readonly IntervalExpression? TablePeriod; - public MListElementExpression(PrimaryKeyExpression rowId, EntityExpression parent, Expression? order, Expression element, NewExpression? systemPeriod, TableMList table, Alias alias) + public MListElementExpression(PrimaryKeyExpression rowId, EntityExpression parent, Expression? order, Expression element, IntervalExpression? systemPeriod, TableMList table, Alias alias) : base(DbExpressionType.MListElement, typeof(MListElement<,>).MakeGenericType(parent.Type, element.Type)) { this.RowId = rowId; diff --git a/Signum.Engine/Linq/DbExpressions.Sql.cs b/Signum.Engine/Linq/DbExpressions.Sql.cs index c4d21ed789..032f27fb3e 100644 --- a/Signum.Engine/Linq/DbExpressions.Sql.cs +++ b/Signum.Engine/Linq/DbExpressions.Sql.cs @@ -952,6 +952,73 @@ protected override Expression Accept(DbExpressionVisitor visitor) } } + internal class IntervalExpression : DbExpression + { + public readonly Expression? Min; + public readonly Expression? Max; + public readonly Expression? PostgresRange; + + public IntervalExpression(Type type, Expression? min, Expression? max, Expression? postgresRange) + :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); + } + + public override string ToString() + { + var type = this.Type.GetGenericArguments()[0].TypeName(); + + if (PostgresRange != null) + return $"new Interval<{type}({this.PostgresRange})"; + else + return $"new Interval<{type}({this.Min}, {this.Max})"; + } + + protected override Expression Accept(DbExpressionVisitor visitor) + { + return visitor.VisitLike(this); + } + } + + public static class SystemTimeExpressions + { + static MethodInfo miOverlaps = ReflectionTools.GetMethodInfo((Interval pair) => pair.Overlaps(new Interval())); + internal static Expression? Overlaps(this IntervalExpression? interval1, IntervalExpression? interval2) + { + if (interval1 == null) + return null; + + if (interval2 == null) + return null; + + if(interval1.PostgresRange != null) + { + return new SqlFunctionExpression(typeof(bool), null, "&&", new Expression[] { interval1.PostgresRange!, interval2.PostgresRange! }); + } + + var min1 = interval1.Min; + var max1 = interval1.Max; + var min2 = interval2.Min; + var max2 = interval2.Max; + + return Expression.And( + Expression.GreaterThan(max1, min2), + Expression.GreaterThan(max2, min1) + ); + } + + public static Expression And(this Expression expression, Expression? other) + { + if (other == null) + return expression; + + return Expression.And(expression, other); + } + } + internal class LikeExpression : DbExpression { public readonly Expression Expression; diff --git a/Signum.Engine/Linq/DbQueryProvider.cs b/Signum.Engine/Linq/DbQueryProvider.cs index d0eb322475..0ecd8fc6be 100644 --- a/Signum.Engine/Linq/DbQueryProvider.cs +++ b/Signum.Engine/Linq/DbQueryProvider.cs @@ -85,10 +85,13 @@ internal static Expression Optimize(Expression binded, QueryBinder binder, Alias { var isPostgres = Schema.Current.Settings.IsPostgres; + log.Switch("Aggregate"); - Expression rewrited = AggregateRewriter.Rewrite(binded); + Expression rewriten = AggregateRewriter.Rewrite(binded); + log.Switch("DupHistory"); + Expression dupHistory = DuplicateHistory.Rewrite(rewriten, aliasGenerator); log.Switch("EntityCompleter"); - Expression completed = EntityCompleter.Complete(rewrited, binder); + Expression completed = EntityCompleter.Complete(dupHistory, binder); log.Switch("AliasReplacer"); Expression replaced = AliasProjectionReplacer.Replace(completed, aliasGenerator); log.Switch("OrderBy"); diff --git a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs index e7e2aa77c6..737fcde31e 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs @@ -142,7 +142,7 @@ protected internal virtual Expression VisitTypeImplementedByAll(TypeImplementedB protected internal virtual Expression VisitMList(MListExpression ml) { var newBackID = (PrimaryKeyExpression)Visit(ml.BackID); - var externalPeriod = (NewExpression)Visit(ml.ExternalPeriod); + var externalPeriod = (IntervalExpression)Visit(ml.ExternalPeriod); if (newBackID != ml.BackID || externalPeriod != ml.ExternalPeriod) return new MListExpression(ml.Type, newBackID, externalPeriod, ml.TableMList); return ml; @@ -162,7 +162,7 @@ protected internal virtual Expression VisitMListElement(MListElementExpression m var parent = (EntityExpression)Visit(mle.Parent); var order = Visit(mle.Order); var element = Visit(mle.Element); - var period = (NewExpression)Visit(mle.TablePeriod); + var period = (IntervalExpression)Visit(mle.TablePeriod); if (rowId != mle.RowId || parent != mle.Parent || order != mle.Order || element != mle.Element || period != mle.TablePeriod) return new MListElementExpression(rowId, parent, order, element, period, mle.Table, mle.Alias); return mle; @@ -171,7 +171,7 @@ protected internal virtual Expression VisitMListElement(MListElementExpression m protected internal virtual Expression VisitAdditionalField(AdditionalFieldExpression ml) { var newBackID = (PrimaryKeyExpression)Visit(ml.BackID); - var externalPeriod = (NewExpression)Visit(ml.ExternalPeriod); + var externalPeriod = (IntervalExpression)Visit(ml.ExternalPeriod); if (newBackID != ml.BackID || externalPeriod != ml.ExternalPeriod) return new AdditionalFieldExpression(ml.Type, newBackID, externalPeriod, ml.Route); return ml; @@ -204,7 +204,7 @@ protected internal virtual Expression VisitImplementedByAll(ImplementedByAllExpr { var id = Visit(iba.Id); var typeId = (TypeImplementedByAllExpression)Visit(iba.TypeId); - var externalPeriod = (NewExpression)Visit(iba.ExternalPeriod); + var externalPeriod = (IntervalExpression)Visit(iba.ExternalPeriod); if (id != iba.Id || typeId != iba.TypeId || externalPeriod != iba.ExternalPeriod) return new ImplementedByAllExpression(iba.Type, id, typeId, externalPeriod); @@ -226,9 +226,9 @@ protected internal virtual Expression VisitEntity(EntityExpression ee) var mixins = Visit(ee.Mixins, VisitMixinEntity); var externalId = (PrimaryKeyExpression)Visit(ee.ExternalId); - var externalPeriod = (NewExpression)Visit(ee.ExternalPeriod); + var externalPeriod = (IntervalExpression)Visit(ee.ExternalPeriod); - var period = (NewExpression)Visit(ee.TablePeriod); + var period = (IntervalExpression)Visit(ee.TablePeriod); if (ee.Bindings != bindings || ee.ExternalId != externalId || ee.ExternalPeriod != externalPeriod || ee.Mixins != mixins || ee.TablePeriod != period) return new EntityExpression(ee.Type, externalId, externalPeriod, ee.TableAlias, bindings, mixins, period, ee.AvoidExpandOnRetrieving); @@ -506,5 +506,15 @@ protected internal virtual Expression VisitPrimaryKeyString(PrimaryKeyStringExpr return new PrimaryKeyStringExpression(id, (TypeImplementedByAllExpression)typeId); } + + protected internal virtual Expression VisitInterval(IntervalExpression interval) + { + Expression min = Visit(interval.Min); + 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 interval; + } } } diff --git a/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs b/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs index df4ae73146..04f1116c6a 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs @@ -2733,7 +2733,6 @@ public EntityExpression Completed(EntityExpression entity) var newAlias = NextTableAlias(table.Name); var id = table.GetIdExpression(newAlias)!; var period = table.GenerateSystemPeriod(newAlias, this); - var intersect = period.Intesection(entity.ExternalPeriod); //TODO intersect not used! var bindings = table.GenerateBindings(newAlias, this, id, period); var mixins = table.GenerateMixins(newAlias, this, id, period); diff --git a/Signum.Engine/Schema/Schema.Expressions.cs b/Signum.Engine/Schema/Schema.Expressions.cs index 9be3625efd..771983db82 100644 --- a/Signum.Engine/Schema/Schema.Expressions.cs +++ b/Signum.Engine/Schema/Schema.Expressions.cs @@ -11,6 +11,7 @@ using Signum.Utilities.Reflection; using Signum.Utilities.ExpressionTrees; using Signum.Utilities.DataStructures; +using NpgsqlTypes; namespace Signum.Engine.Maps { @@ -44,15 +45,16 @@ internal Expression GetProjectorExpression(Alias tableAlias, QueryBinder binder) internal static ConstructorInfo intervalConstructor = typeof(Interval).GetConstructor(new[] { typeof(DateTime), typeof(DateTime) })!; - internal NewExpression? GenerateSystemPeriod(Alias tableAlias, QueryBinder binder, bool force = false) + internal IntervalExpression? GenerateSystemPeriod(Alias tableAlias, QueryBinder binder, bool force = false) { - return this.SystemVersioned != null && (force || binder.systemTime is SystemTime.Interval) ? Expression.New(intervalConstructor, - new ColumnExpression(typeof(DateTime), tableAlias, this.SystemVersioned.StartColumnName!), - new ColumnExpression(typeof(DateTime), tableAlias, this.SystemVersioned.EndColumnName!) + return this.SystemVersioned != null && (force || binder.systemTime is SystemTime.Interval) ? new IntervalExpression(typeof(Interval), + this.SystemVersioned.StartColumnName?.Let(c => new ColumnExpression(typeof(DateTime), tableAlias, c)), + this.SystemVersioned.EndColumnName?.Let(c => new ColumnExpression(typeof(DateTime), tableAlias, c)), + this.SystemVersioned.PostgreeSysPeriodColumnName?.Let(c => new ColumnExpression(typeof(NpgsqlRange), tableAlias, c)) ) : null; } - internal ReadOnlyCollection GenerateBindings(Alias tableAlias, QueryBinder binder, Expression id, NewExpression? period) + internal ReadOnlyCollection GenerateBindings(Alias tableAlias, QueryBinder binder, Expression id, IntervalExpression? period) { List result = new List { @@ -73,7 +75,7 @@ internal ReadOnlyCollection GenerateBindings(Alias tableAlias, Que return result.ToReadOnly(); } - internal ReadOnlyCollection? GenerateMixins(Alias tableAlias, QueryBinder binder, Expression id, NewExpression? period) + internal ReadOnlyCollection? GenerateMixins(Alias tableAlias, QueryBinder binder, Expression id, IntervalExpression? period) { if (this.Mixins == null) return null; @@ -133,7 +135,7 @@ internal ColumnExpression OrderExpression(Alias tableAlias) return new ColumnExpression(typeof(int), tableAlias, ((IColumn)this.Order!).Name); } - internal Expression FieldExpression(Alias tableAlias, QueryBinder binder, NewExpression? externalPeriod, bool withRowId) + internal Expression FieldExpression(Alias tableAlias, QueryBinder binder, IntervalExpression? externalPeriod, bool withRowId) { var rowId = RowIdExpression(tableAlias); @@ -158,7 +160,7 @@ internal Expression GetProjectorExpression(Alias tableAlias, QueryBinder binder) Type elementType = typeof(MListElement<,>).MakeGenericType(BackReference.FieldType, Field.FieldType); var rowId = RowIdExpression(tableAlias); - NewExpression? period = GenerateSystemPeriod(tableAlias, binder); + IntervalExpression? period = GenerateSystemPeriod(tableAlias, binder); return new MListElementExpression( rowId, @@ -170,11 +172,12 @@ internal Expression GetProjectorExpression(Alias tableAlias, QueryBinder binder) tableAlias); } - internal NewExpression? GenerateSystemPeriod(Alias tableAlias, QueryBinder binder, bool force = false) + internal IntervalExpression? GenerateSystemPeriod(Alias tableAlias, QueryBinder binder, bool force = false) { - return this.SystemVersioned != null && (force || binder.systemTime is SystemTime.Interval) ? Expression.New(Table.intervalConstructor, - new ColumnExpression(typeof(DateTime), tableAlias, this.SystemVersioned.StartColumnName!), - new ColumnExpression(typeof(DateTime), tableAlias, this.SystemVersioned.EndColumnName!) + return this.SystemVersioned != null && (force || binder.systemTime is SystemTime.Interval) ? new IntervalExpression(typeof(Interval), + this.SystemVersioned.StartColumnName?.Let(c => new ColumnExpression(typeof(DateTime), tableAlias, c)), + this.SystemVersioned.EndColumnName?.Let(c => new ColumnExpression(typeof(DateTime), tableAlias, c)), + this.SystemVersioned.PostgreeSysPeriodColumnName?.Let(c => new ColumnExpression(typeof(NpgsqlRange), tableAlias, c)) ) : null; } @@ -186,12 +189,12 @@ ColumnExpression ITablePrivate.GetPrimaryOrder(Alias alias) public abstract partial class Field { - internal abstract Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, NewExpression? period); + internal abstract Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, IntervalExpression? period); } public partial class FieldPrimaryKey { - internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, NewExpression? period) + internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, IntervalExpression? period) { return new PrimaryKeyExpression(new ColumnExpression(this.Type.Nullify(), tableAlias, this.Name).Nullify()); } @@ -199,7 +202,7 @@ internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, public partial class FieldValue { - internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, NewExpression? period) + internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, IntervalExpression? period) { var column = new ColumnExpression(this.Type, tableAlias, this.Name); @@ -214,7 +217,7 @@ public partial class FieldTicks { public static readonly PropertyInfo piDateTimeTicks = ReflectionTools.GetPropertyInfo((DateTime d) => d.Ticks); - internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, NewExpression? period) + internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, IntervalExpression? period) { if (this.Type == this.FieldType) return new ColumnExpression(this.Type, tableAlias, this.Name); @@ -228,7 +231,7 @@ internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, public partial class FieldReference { - internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, NewExpression? period) + internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, IntervalExpression? period) { Type cleanType = IsLite ? Lite.Extract(FieldType)! : FieldType; @@ -243,7 +246,7 @@ internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, public partial class FieldEnum { - internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, NewExpression? period) + internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, IntervalExpression? period) { return Expression.Convert(new ColumnExpression(this.Type, tableAlias, Name), FieldType); } @@ -251,7 +254,7 @@ internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, public partial class FieldMList { - internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, NewExpression? period) + internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, IntervalExpression? period) { return new MListExpression(FieldType, (PrimaryKeyExpression)id, period, TableMList); // keep back id empty for some seconds } @@ -259,7 +262,7 @@ internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, public partial class FieldEmbedded { - internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, NewExpression? period) + internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, IntervalExpression? period) { var bindings = (from kvp in EmbeddedFields let fi = kvp.Value.FieldInfo @@ -277,7 +280,7 @@ internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, public partial class FieldMixin { - internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, NewExpression? period) + internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, IntervalExpression? period) { var bindings = (from kvp in Fields let fi = kvp.Value.FieldInfo @@ -291,7 +294,7 @@ internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, public partial class FieldImplementedBy { - internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, NewExpression? period) + internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, IntervalExpression? period) { var implementations = ImplementationColumns.SelectDictionary(t => t, (t, ic) => new EntityExpression(t, new PrimaryKeyExpression(new ColumnExpression(ic.Type.Nullify(), tableAlias, ic.Name)), period, null, null, null, null, AvoidExpandOnRetrieving)); @@ -307,7 +310,7 @@ internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, public partial class FieldImplementedByAll { - internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, NewExpression? period) + internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, Expression id, IntervalExpression? period) { Expression result = new ImplementedByAllExpression( IsLite ? Lite.Extract(FieldType)! : FieldType, diff --git a/Signum.Engine/Schema/Schema.cs b/Signum.Engine/Schema/Schema.cs index ab9cfd713c..d76d463f53 100644 --- a/Signum.Engine/Schema/Schema.cs +++ b/Signum.Engine/Schema/Schema.cs @@ -243,7 +243,7 @@ internal void OnPreBulkInsert(Type type, bool inMListTable) return ee.CacheController; } - internal IEnumerable GetAdditionalQueryBindings(PropertyRoute parent, PrimaryKeyExpression id, NewExpression? period) + internal IEnumerable GetAdditionalQueryBindings(PropertyRoute parent, PrimaryKeyExpression id, IntervalExpression? period) { //AssertAllowed(parent.RootType, inUserInterface: false); diff --git a/Signum.Entities/SystemTime.cs b/Signum.Entities/SystemTime.cs index e3a1993ec8..0293ce663a 100644 --- a/Signum.Entities/SystemTime.cs +++ b/Signum.Entities/SystemTime.cs @@ -12,7 +12,7 @@ public abstract class SystemTime static Variable currentVariable = Statics.ThreadVariable("systemTime"); public static SystemTime? Current => currentVariable.Value; - + public static IDisposable Override(DateTime asOf) => Override(new SystemTime.AsOf(asOf)); public static IDisposable Override(SystemTime? systemTime) { @@ -48,7 +48,7 @@ public AsOf(DateTime dateTime) public abstract class Interval : SystemTime { - + } @@ -116,55 +116,5 @@ public static Interval SystemPeriod(this MListElement mlis { throw new InvalidOperationException("Only for queries"); } - - - static MethodInfo miOverlaps = ReflectionTools.GetMethodInfo((Interval pair) => pair.Overlaps(new Interval())); - internal static Expression? Overlaps(this NewExpression? interval1, NewExpression? interval2) - { - if (interval1 == null) - return null; - - if (interval2 == null) - return null; - - var min1 = interval1.Arguments[0]; - var max1 = interval1.Arguments[1]; - var min2 = interval2.Arguments[0]; - var max2 = interval2.Arguments[1]; - - return Expression.And( - Expression.GreaterThan(max1, min2), - Expression.GreaterThan(max2, min1) - ); - } - - - - static ConstructorInfo ciInterval = ReflectionTools.GetConstuctorInfo(() => new Interval(new DateTime(), new DateTime())); - internal static Expression? Intesection(this NewExpression? interval1, NewExpression? interval2) - { - if (interval1 == null) - return interval2; - - if (interval2 == null) - return interval1; - - var min1 = interval1.Arguments[0]; - var max1 = interval1.Arguments[1]; - var min2 = interval2.Arguments[0]; - var max2 = interval2.Arguments[1]; - - return Expression.New(ciInterval, - Expression.Condition(Expression.LessThan(min1, min2), min1, min2), - Expression.Condition(Expression.GreaterThan(max1, max2), max1, max2)); - } - - public static Expression And(this Expression expression, Expression? other) - { - if (other == null) - return expression; - - return Expression.And(expression, other); - } } } From bdbc99ab4fcea5ce9f2d49af01852576d9262442 Mon Sep 17 00:00:00 2001 From: Olmo del Corral Date: Wed, 22 Jan 2020 15:52:01 +0100 Subject: [PATCH 10/13] fix AlterSchema --- Signum.Engine/Engine/SqlBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Signum.Engine/Engine/SqlBuilder.cs b/Signum.Engine/Engine/SqlBuilder.cs index 1b3d399315..30a4a7dc34 100644 --- a/Signum.Engine/Engine/SqlBuilder.cs +++ b/Signum.Engine/Engine/SqlBuilder.cs @@ -552,7 +552,7 @@ public SqlPreCommand RenameTable(ObjectName oldName, string newName) public SqlPreCommandSimple AlterSchema(ObjectName oldName, SchemaName schemaName) { if (IsPostgres) - return new SqlPreCommandSimple($"ALTER TABLE {oldName} SET SCHEMA{schemaName.Name.SqlEscape(IsPostgres)};"); + return new SqlPreCommandSimple($"ALTER TABLE {oldName} SET SCHEMA {schemaName.Name.SqlEscape(IsPostgres)};"); return new SqlPreCommandSimple("ALTER SCHEMA {0} TRANSFER {1};".FormatWith(schemaName.Name.SqlEscape(isPostgres), oldName)); } From e1c686a3d2af8643fdea63dc08fae48796a140a0 Mon Sep 17 00:00:00 2001 From: Olmo del Corral Date: Wed, 22 Jan 2020 21:14:00 +0100 Subject: [PATCH 11/13] fix MSBuildTask in docker --- Signum.MSBuildTask/Signum.MSBuildTask.csproj | 4 ++-- Signum.MSBuildTask/Signum.MSBuildTask.nuspec | 2 +- Signum.MSBuildTask/Signum.MSBuildTask.targets | 4 ++-- .../Signum.TSGenerator.2.2.2.nupkg | Bin 263510 -> 0 bytes 4 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 Signum.TSGenerator/Signum.TSGenerator.2.2.2.nupkg diff --git a/Signum.MSBuildTask/Signum.MSBuildTask.csproj b/Signum.MSBuildTask/Signum.MSBuildTask.csproj index e811125ed2..79d08e65f0 100644 --- a/Signum.MSBuildTask/Signum.MSBuildTask.csproj +++ b/Signum.MSBuildTask/Signum.MSBuildTask.csproj @@ -10,11 +10,11 @@ latest x64;AnyCPU Signum.MSBuildTask.nuspec - 1.0.7 + 1.0.8 - \ No newline at end of file + diff --git a/Signum.MSBuildTask/Signum.MSBuildTask.nuspec b/Signum.MSBuildTask/Signum.MSBuildTask.nuspec index 1e51edb86a..56b82c423f 100644 --- a/Signum.MSBuildTask/Signum.MSBuildTask.nuspec +++ b/Signum.MSBuildTask/Signum.MSBuildTask.nuspec @@ -2,7 +2,7 @@ Signum.MSBuildTask - 1.0.7 + 1.0.8 IL rewriter for Signum Framework applications Olmo del Corral MIT diff --git a/Signum.MSBuildTask/Signum.MSBuildTask.targets b/Signum.MSBuildTask/Signum.MSBuildTask.targets index 35aba2376c..fd12a43e31 100644 --- a/Signum.MSBuildTask/Signum.MSBuildTask.targets +++ b/Signum.MSBuildTask/Signum.MSBuildTask.targets @@ -1,8 +1,8 @@  - + - \ No newline at end of file + diff --git a/Signum.TSGenerator/Signum.TSGenerator.2.2.2.nupkg b/Signum.TSGenerator/Signum.TSGenerator.2.2.2.nupkg deleted file mode 100644 index 71408b517aa4fbfca4e8f67235eaa78b5e196d31..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 263510 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%0A)o*iVib9vB!49$*meI%PQjU$d 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 z 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 b{jozq8WQS{KY{fA_4)lt;1=Zl3gmwQSWvvm From e829c4c77866bdafb8973789c4953b21e7e596ba Mon Sep 17 00:00:00 2001 From: Olmo del Corral Date: Thu, 23 Jan 2020 00:04:15 +0100 Subject: [PATCH 12/13] finish HistoryTable in postgres --- Signum.Engine/Connection/FieldReader.cs | 17 +- Signum.Engine/Linq/DbExpressions.Sql.cs | 39 ++++- .../ExpressionVisitor/DbExpressionVisitor.cs | 2 +- .../ExpressionVisitor/DuplicateHistory.cs | 165 ++++++++++++++++++ .../Linq/ExpressionVisitor/QueryFormatter.cs | 35 ++-- .../RedundantSubqueryRemover.cs | 16 ++ .../ExpressionVisitor/TranslatorBuilder.cs | 18 ++ Signum.Engine/Schema/Schema.Expressions.cs | 6 +- .../Schema/SchemaBuilder/SchemaSettings.cs | 2 +- Signum.Entities/SystemTime.cs | 14 -- .../ApiControllers/QueryController.cs | 8 +- Signum.Test/LinqProvider/SystemTimeTest.cs | 12 +- 12 files changed, 279 insertions(+), 55 deletions(-) create mode 100644 Signum.Engine/Linq/ExpressionVisitor/DuplicateHistory.cs diff --git a/Signum.Engine/Connection/FieldReader.cs b/Signum.Engine/Connection/FieldReader.cs index 09e8349a5f..857477e24c 100644 --- a/Signum.Engine/Connection/FieldReader.cs +++ b/Signum.Engine/Connection/FieldReader.cs @@ -558,9 +558,8 @@ public T[] GetArray(int ordinal) return (T[])this.reader[ordinal]; } - static MethodInfo miGetRange = ReflectionTools.GetMethodInfo((FieldReader r) => r.GetRange(0)).GetGenericMethodDefinition(); - - public NpgsqlTypes.NpgsqlRange? GetRange(int ordinal) + static MethodInfo miNullableGetRange = ReflectionTools.GetMethodInfo((FieldReader r) => r.GetNullableRange(0)).GetGenericMethodDefinition(); + public NpgsqlTypes.NpgsqlRange? GetNullableRange(int ordinal) { LastOrdinal = ordinal; if (reader.IsDBNull(ordinal)) @@ -571,6 +570,13 @@ public T[] GetArray(int ordinal) return (NpgsqlTypes.NpgsqlRange)this.reader[ordinal]; } + static MethodInfo miGetRange = ReflectionTools.GetMethodInfo((FieldReader r) => r.GetRange(0)).GetGenericMethodDefinition(); + public NpgsqlTypes.NpgsqlRange GetRange(int ordinal) + { + LastOrdinal = ordinal; + return (NpgsqlTypes.NpgsqlRange)this.reader[ordinal]; + } + static Dictionary methods = typeof(FieldReader).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) .Where(m => m.Name != "GetExpression" && m.Name != "IsNull") @@ -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)); } diff --git a/Signum.Engine/Linq/DbExpressions.Sql.cs b/Signum.Engine/Linq/DbExpressions.Sql.cs index 032f27fb3e..264194ea0c 100644 --- a/Signum.Engine/Linq/DbExpressions.Sql.cs +++ b/Signum.Engine/Linq/DbExpressions.Sql.cs @@ -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; @@ -67,6 +68,7 @@ internal enum DbExpressionType PrimaryKey, PrimaryKeyString, ToDayOfWeek, + Interval, } @@ -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}"; } @@ -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() @@ -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 @@ -957,14 +980,16 @@ 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() @@ -972,14 +997,14 @@ 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); } } diff --git a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs index 737fcde31e..0f91c8cecb 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs @@ -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; } } diff --git a/Signum.Engine/Linq/ExpressionVisitor/DuplicateHistory.cs b/Signum.Engine/Linq/ExpressionVisitor/DuplicateHistory.cs new file mode 100644 index 0000000000..e809cfdd79 --- /dev/null +++ b/Signum.Engine/Linq/ExpressionVisitor/DuplicateHistory.cs @@ -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 +{ + /// + /// Rewrite aggregate expressions, moving them into same select expression that has the group-by clause + /// + 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> columnReplacements = new Dictionary>(); + + 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), tableExp.Alias, table.Table.SystemVersioned!.PostgreeSysPeriodColumnName); + SqlFunctionExpression tstzrange(DateTime start, DateTime end) => new SqlFunctionExpression(typeof(NpgsqlTypes.NpgsqlRange), 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 columns = Visit(select.Columns, VisitColumnDeclaration); + ReadOnlyCollection orderBy = Visit(select.OrderBy, VisitOrderBy); + ReadOnlyCollection 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; + } + + + + } +} diff --git a/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs b/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs index cf5e45c650..e85c7530f6 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs @@ -575,21 +575,32 @@ 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]; @@ -597,8 +608,8 @@ protected internal override Expression VisitSqlFunction(SqlFunctionExpression sq sb.Append(", "); this.Visit(exp); } + sb.Append(")"); } - sb.Append(")"); return sqlFunction; } @@ -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 "); diff --git a/Signum.Engine/Linq/ExpressionVisitor/RedundantSubqueryRemover.cs b/Signum.Engine/Linq/ExpressionVisitor/RedundantSubqueryRemover.cs index e3a5d52e03..6e7dd18d6f 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/RedundantSubqueryRemover.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/RedundantSubqueryRemover.cs @@ -148,6 +148,22 @@ protected internal override Expression VisitJoin(JoinExpression join) return result; } + protected internal override Expression VisitSetOperator(SetOperatorExpression set) + { + var result = (SetOperatorExpression)base.VisitSetOperator(set); + + if(this.redundant != null) + { + if (result.Left is SelectExpression l) + this.redundant.Remove(l); + + if (result.Right is SelectExpression r) + this.redundant.Remove(r); + } + + return result; + } + static bool HasJoins(SelectExpression s) { return s.From is JoinExpression || s.From is SelectExpression s2 && HasJoins(s2); diff --git a/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs b/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs index d187e17278..f0c546c622 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs @@ -11,6 +11,7 @@ using Signum.Engine.Maps; using Signum.Entities.Basics; using Signum.Entities.Internal; +using Signum.Utilities.DataStructures; namespace Signum.Engine.Linq { @@ -557,6 +558,23 @@ protected internal override Expression VisitToDayOfWeek(ToDayOfWeekExpression to } } + static MethodInfo miToInterval = ReflectionTools.GetMethodInfo(() => ToInterval(new NpgsqlTypes.NpgsqlRange())).GetGenericMethodDefinition(); + static Interval ToInterval(NpgsqlTypes.NpgsqlRange range) where T : struct, IComparable, IEquatable + => new Interval(range.LowerBound, range.UpperBound); + + protected internal override Expression VisitInterval(IntervalExpression interval) + { + var intervalType = interval.Type.GetGenericArguments()[0]; + if (Schema.Current.Settings.IsPostgres) + { + return Expression.Call(miToInterval.MakeGenericMethod(intervalType), Visit(interval.PostgresRange)); + } + else + { + return Expression.New(typeof(Interval<>).MakeGenericType(intervalType).GetConstructor(new[] { intervalType, intervalType })!, Visit(interval.Min), Visit(interval.Max)); + } + } + protected override Expression VisitNew(NewExpression node) { var expressions = this.Visit(node.Arguments); diff --git a/Signum.Engine/Schema/Schema.Expressions.cs b/Signum.Engine/Schema/Schema.Expressions.cs index 771983db82..2296b07ffd 100644 --- a/Signum.Engine/Schema/Schema.Expressions.cs +++ b/Signum.Engine/Schema/Schema.Expressions.cs @@ -50,7 +50,8 @@ internal Expression GetProjectorExpression(Alias tableAlias, QueryBinder binder) return this.SystemVersioned != null && (force || binder.systemTime is SystemTime.Interval) ? new IntervalExpression(typeof(Interval), this.SystemVersioned.StartColumnName?.Let(c => new ColumnExpression(typeof(DateTime), tableAlias, c)), this.SystemVersioned.EndColumnName?.Let(c => new ColumnExpression(typeof(DateTime), tableAlias, c)), - this.SystemVersioned.PostgreeSysPeriodColumnName?.Let(c => new ColumnExpression(typeof(NpgsqlRange), tableAlias, c)) + this.SystemVersioned.PostgreeSysPeriodColumnName?.Let(c => new ColumnExpression(typeof(NpgsqlRange), tableAlias, c)), + asUtc: true ) : null; } @@ -177,7 +178,8 @@ internal Expression GetProjectorExpression(Alias tableAlias, QueryBinder binder) return this.SystemVersioned != null && (force || binder.systemTime is SystemTime.Interval) ? new IntervalExpression(typeof(Interval), this.SystemVersioned.StartColumnName?.Let(c => new ColumnExpression(typeof(DateTime), tableAlias, c)), this.SystemVersioned.EndColumnName?.Let(c => new ColumnExpression(typeof(DateTime), tableAlias, c)), - this.SystemVersioned.PostgreeSysPeriodColumnName?.Let(c => new ColumnExpression(typeof(NpgsqlRange), tableAlias, c)) + this.SystemVersioned.PostgreeSysPeriodColumnName?.Let(c => new ColumnExpression(typeof(NpgsqlRange), tableAlias, c)), + asUtc: true ) : null; } diff --git a/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs b/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs index 6d86835af1..66a28058ab 100644 --- a/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs +++ b/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs @@ -73,7 +73,7 @@ public void Desambiguate(Type type, string cleanName) {typeof(string), new AbstractDbType(SqlDbType.NVarChar, NpgsqlDbType.Varchar)}, {typeof(Date), new AbstractDbType(SqlDbType.Date, NpgsqlDbType.Date)}, {typeof(DateTime), new AbstractDbType(SqlDbType.DateTime2, NpgsqlDbType.Timestamp)}, - {typeof(DateTimeOffset), new AbstractDbType(SqlDbType.DateTimeOffset/*, NpgsqlDbType.TimestampTz*/)}, + {typeof(DateTimeOffset), new AbstractDbType(SqlDbType.DateTimeOffset, NpgsqlDbType.TimestampTz)}, {typeof(TimeSpan), new AbstractDbType(SqlDbType.Time, NpgsqlDbType.Time)}, {typeof(byte[]), new AbstractDbType(SqlDbType.VarBinary, NpgsqlDbType.Bytea)}, diff --git a/Signum.Entities/SystemTime.cs b/Signum.Entities/SystemTime.cs index 0293ce663a..c6907035cb 100644 --- a/Signum.Entities/SystemTime.cs +++ b/Signum.Entities/SystemTime.cs @@ -52,20 +52,6 @@ public abstract class Interval : SystemTime } - public class FromTo : Interval - { - public DateTime StartDateTime { get; private set; } - public DateTime EndtDateTime { get; private set; } - - public FromTo(DateTime startDateTime, DateTime endDateTime) - { - this.StartDateTime = ValidateUTC(startDateTime); - this.EndtDateTime = ValidateUTC(endDateTime); - } - - public override string ToString() => $"FROM {StartDateTime:u} TO {EndtDateTime:u}"; - } - public class Between : Interval { public DateTime StartDateTime { get; private set; } diff --git a/Signum.React/ApiControllers/QueryController.cs b/Signum.React/ApiControllers/QueryController.cs index 4f23a43b38..58c2368bfd 100644 --- a/Signum.React/ApiControllers/QueryController.cs +++ b/Signum.React/ApiControllers/QueryController.cs @@ -393,15 +393,9 @@ public SystemTimeTS(SystemTime systemTime) startDate = between.StartDateTime; endDate = between.EndtDateTime; } - else if (systemTime is SystemTime.FromTo fromTo) - { - mode = SystemTimeMode.Between; //Same - startDate = fromTo.StartDateTime; - endDate = fromTo.EndtDateTime; - } else if (systemTime is SystemTime.ContainedIn containedIn) { - mode = SystemTimeMode.Between; //Same + mode = SystemTimeMode.ContainedIn; startDate = containedIn.StartDateTime; endDate = containedIn.EndtDateTime; } diff --git a/Signum.Test/LinqProvider/SystemTimeTest.cs b/Signum.Test/LinqProvider/SystemTimeTest.cs index 44dde0a7ed..c5e727f346 100644 --- a/Signum.Test/LinqProvider/SystemTimeTest.cs +++ b/Signum.Test/LinqProvider/SystemTimeTest.cs @@ -66,19 +66,23 @@ public void TimeBetween() period = Database.Query().Where(a => a.Name == "X2").Select(a => a.SystemPeriod()).Single(); } + period = new Interval( + new DateTime(period.Min.Ticks, DateTimeKind.Utc), + new DateTime(period.Max.Ticks, DateTimeKind.Utc)); //Hack + using (SystemTime.Override(new SystemTime.AsOf(period.Min))) { - var a = Database.Query().Where(f1 => f1.Name == "X2").ToList(); + var list = Database.Query().Where(f1 => f1.Name == "X2").Select(a => a.SystemPeriod()).ToList(); } using (SystemTime.Override(new SystemTime.Between(period.Max, period.Max.AddSeconds(1)))) { - var a = Database.Query().Where(f1 => f1.Name == "X2").ToList(); + var list = Database.Query().Where(f1 => f1.Name == "X2").Select(a => a.SystemPeriod()).ToList(); } - using (SystemTime.Override(new SystemTime.FromTo(period.Max, period.Max.AddSeconds(1)))) + using (SystemTime.Override(new SystemTime.ContainedIn(period.Max, period.Max.AddSeconds(1)))) { - var b = Database.Query().Where(f2 => f2.Name == "X2").ToList(); + var list = Database.Query().Where(f2 => f2.Name == "X2").Select(a => a.SystemPeriod()).ToList(); } } } From f78f5f75a168694e55783a2a570f26b39c9cbf1d Mon Sep 17 00:00:00 2001 From: Olmo del Corral Date: Fri, 24 Jan 2020 07:00:43 +0100 Subject: [PATCH 13/13] update nugets and remove LangVersion --- Signum.Engine/Signum.Engine.csproj | 3 +-- Signum.Entities/Signum.Entities.csproj | 10 ++++++++-- Signum.MSBuildTask/Program.cs | 2 -- Signum.MSBuildTask/Properties/launchSettings.json | 4 ++-- Signum.MSBuildTask/Signum.MSBuildTask.csproj | 6 +++--- Signum.MSBuildTask/Signum.MSBuildTask.nuspec | 2 +- Signum.React/Signum.React.csproj | 7 +++---- Signum.TSGenerator/Program.cs | 2 -- Signum.TSGenerator/Signum.TSGenerator.csproj | 6 +++--- Signum.TSGenerator/Signum.TSGenerator.nuspec | 2 +- Signum.Test/Signum.Test.csproj | 9 ++++----- Signum.Utilities/Signum.Utilities.csproj | 1 - 12 files changed, 26 insertions(+), 28 deletions(-) diff --git a/Signum.Engine/Signum.Engine.csproj b/Signum.Engine/Signum.Engine.csproj index 5ed17ef24f..8d9d72e2db 100644 --- a/Signum.Engine/Signum.Engine.csproj +++ b/Signum.Engine/Signum.Engine.csproj @@ -2,7 +2,6 @@ netcoreapp3.1 - preview true enable true @@ -21,7 +20,7 @@ - + diff --git a/Signum.Entities/Signum.Entities.csproj b/Signum.Entities/Signum.Entities.csproj index 9e6920db58..db445a664a 100644 --- a/Signum.Entities/Signum.Entities.csproj +++ b/Signum.Entities/Signum.Entities.csproj @@ -2,7 +2,6 @@ netcoreapp3.1 - preview true enable true @@ -13,13 +12,20 @@ - + + + PreserveNewest diff --git a/Signum.MSBuildTask/Program.cs b/Signum.MSBuildTask/Program.cs index 40797b3468..d80af683cb 100644 --- a/Signum.MSBuildTask/Program.cs +++ b/Signum.MSBuildTask/Program.cs @@ -51,8 +51,6 @@ public static int Main(string[] args) return 0; } - log.WriteLine("Signum.MSBuildTask doing nothing"); - bool errors = false; errors |= new ExpressionFieldGenerator(assembly, resolver, log).FixAutoExpressionField(); errors |= new FieldAutoInitializer(assembly, resolver, log).FixAutoInitializer(); diff --git a/Signum.MSBuildTask/Properties/launchSettings.json b/Signum.MSBuildTask/Properties/launchSettings.json index a1cc64f38b..4ff5492d03 100644 --- a/Signum.MSBuildTask/Properties/launchSettings.json +++ b/Signum.MSBuildTask/Properties/launchSettings.json @@ -2,8 +2,8 @@ "profiles": { "Signum.Entities": { "commandName": "Project", - "commandLineArgs": "\"obj\\x64\\Debug\\netcoreapp2.2\\Signum.Entities.Extensions.dll\" \"D:\\Signum\\southwind\\Extensions\\Signum.Entities.Extensions\\obj\\SignumReferences.txt\"", - "workingDirectory": "D:\\Signum\\southwind\\Extensions\\Signum.Entities.Extensions\\" + "commandLineArgs": "\"obj\\x64\\Debug\\netcoreapp3.1\\Signum.Entities.dll\" \"D:\\Signum\\southwind\\Framework\\Signum.Entities\\obj\\SignumReferences.txt\"", + "workingDirectory": "D:\\Signum\\southwind\\Framework\\Signum.Entities\\" } } } diff --git a/Signum.MSBuildTask/Signum.MSBuildTask.csproj b/Signum.MSBuildTask/Signum.MSBuildTask.csproj index 79d08e65f0..fa6b1020e4 100644 --- a/Signum.MSBuildTask/Signum.MSBuildTask.csproj +++ b/Signum.MSBuildTask/Signum.MSBuildTask.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1 @@ -10,11 +10,11 @@ latest x64;AnyCPU Signum.MSBuildTask.nuspec - 1.0.8 + 1.1.4 - + diff --git a/Signum.MSBuildTask/Signum.MSBuildTask.nuspec b/Signum.MSBuildTask/Signum.MSBuildTask.nuspec index 56b82c423f..c9bc462d14 100644 --- a/Signum.MSBuildTask/Signum.MSBuildTask.nuspec +++ b/Signum.MSBuildTask/Signum.MSBuildTask.nuspec @@ -2,7 +2,7 @@ Signum.MSBuildTask - 1.0.8 + 1.1.4 IL rewriter for Signum Framework applications Olmo del Corral MIT diff --git a/Signum.React/Signum.React.csproj b/Signum.React/Signum.React.csproj index 963ecbfe3e..32b2b93f8d 100644 --- a/Signum.React/Signum.React.csproj +++ b/Signum.React/Signum.React.csproj @@ -4,7 +4,6 @@ netcoreapp3.1 3.7 true - preview true enable true @@ -35,8 +34,8 @@ - - + + runtime; build; native; contentfiles; analyzers all @@ -47,7 +46,7 @@ - + diff --git a/Signum.TSGenerator/Program.cs b/Signum.TSGenerator/Program.cs index 1b92311583..0d4b280f3c 100644 --- a/Signum.TSGenerator/Program.cs +++ b/Signum.TSGenerator/Program.cs @@ -13,8 +13,6 @@ namespace Signum.TSGenerator { public static class Program { - - public static int Main(string[] args) { var log = Console.Out; diff --git a/Signum.TSGenerator/Signum.TSGenerator.csproj b/Signum.TSGenerator/Signum.TSGenerator.csproj index 0fb225f910..4ea51f8d40 100644 --- a/Signum.TSGenerator/Signum.TSGenerator.csproj +++ b/Signum.TSGenerator/Signum.TSGenerator.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1 @@ -9,7 +9,7 @@ true latest x64;AnyCPU - 2.2.2 + 2.2.5 @@ -17,6 +17,6 @@ - + diff --git a/Signum.TSGenerator/Signum.TSGenerator.nuspec b/Signum.TSGenerator/Signum.TSGenerator.nuspec index 2af3932246..cda81921e4 100644 --- a/Signum.TSGenerator/Signum.TSGenerator.nuspec +++ b/Signum.TSGenerator/Signum.TSGenerator.nuspec @@ -2,7 +2,7 @@ Signum.TSGenerator - 2.2.2 + 2.2.5 TypeScript generator for Signum Framework applications Olmo del Corral MIT diff --git a/Signum.Test/Signum.Test.csproj b/Signum.Test/Signum.Test.csproj index 8d1ab8a05e..433c449bb8 100644 --- a/Signum.Test/Signum.Test.csproj +++ b/Signum.Test/Signum.Test.csproj @@ -2,7 +2,6 @@ netcoreapp3.1 - preview SignumTest true enable @@ -16,9 +15,9 @@ - - - + + + @@ -35,7 +34,7 @@ - + diff --git a/Signum.Utilities/Signum.Utilities.csproj b/Signum.Utilities/Signum.Utilities.csproj index cb24bf3058..33ccc93116 100644 --- a/Signum.Utilities/Signum.Utilities.csproj +++ b/Signum.Utilities/Signum.Utilities.csproj @@ -2,7 +2,6 @@ netcoreapp3.1 - preview true enable true