diff --git a/src/CommonAssemblyInfo.cs b/src/CommonAssemblyInfo.cs index 1a7f45e..27de447 100644 --- a/src/CommonAssemblyInfo.cs +++ b/src/CommonAssemblyInfo.cs @@ -16,6 +16,6 @@ [assembly: NeutralResourcesLanguage("en", UltimateResourceFallbackLocation.MainAssembly)] // The following version attributes get rewritten by GitVersion as part of the build -[assembly: AssemblyVersion("3.1.0")] -[assembly: AssemblyFileVersion("3.1.0")] -[assembly: AssemblyInformationalVersion("3.1.0")] +[assembly: AssemblyVersion("3.1.2")] +[assembly: AssemblyFileVersion("3.1.2")] +[assembly: AssemblyInformationalVersion("3.1.2-ci")] diff --git a/src/EntityFramework5.Npgsql/EntityFramework5.Npgsql.nuspec b/src/EntityFramework5.Npgsql/EntityFramework5.Npgsql.nuspec index 07f54f7..2135c9e 100644 --- a/src/EntityFramework5.Npgsql/EntityFramework5.Npgsql.nuspec +++ b/src/EntityFramework5.Npgsql/EntityFramework5.Npgsql.nuspec @@ -3,7 +3,7 @@ EntityFramework5.Npgsql Npgsql for Entity Framework 5 - 3.1.0 + $version$ Shay Rojansky, Emil Lenngren, Francisco Figueiredo Jr., Kenji Uno, Jon Asher, John Cooley, Federico Di Gregorio, Jon Hanna, Chris Morgan, Dave Page, Glen Parker, Brar Piening, Hiroshi Saito Shay Rojansky, Emil Lenngren, Francisco Figueiredo Jr., Kenji Uno https://github.com/npgsql/EntityFramework6.Npgsql/blob/dev/LICENSE.txt diff --git a/src/EntityFramework6.Npgsql/EntityFramework6.Npgsql.3.1.0.nupkg b/src/EntityFramework6.Npgsql/EntityFramework6.Npgsql.3.1.0.nupkg new file mode 100644 index 0000000..7e74fa9 Binary files /dev/null and b/src/EntityFramework6.Npgsql/EntityFramework6.Npgsql.3.1.0.nupkg differ diff --git a/src/EntityFramework6.Npgsql/EntityFramework6.Npgsql.3.1.0.symbols.nupkg b/src/EntityFramework6.Npgsql/EntityFramework6.Npgsql.3.1.0.symbols.nupkg new file mode 100644 index 0000000..9a5262b Binary files /dev/null and b/src/EntityFramework6.Npgsql/EntityFramework6.Npgsql.3.1.0.symbols.nupkg differ diff --git a/src/EntityFramework6.Npgsql/EntityFramework6.Npgsql.csproj b/src/EntityFramework6.Npgsql/EntityFramework6.Npgsql.csproj index 1d7fb7c..c5b375b 100644 --- a/src/EntityFramework6.Npgsql/EntityFramework6.Npgsql.csproj +++ b/src/EntityFramework6.Npgsql/EntityFramework6.Npgsql.csproj @@ -69,6 +69,7 @@ + diff --git a/src/EntityFramework6.Npgsql/EntityFramework6.Npgsql.nuspec b/src/EntityFramework6.Npgsql/EntityFramework6.Npgsql.nuspec index a3aaaf4..b88c9de 100644 --- a/src/EntityFramework6.Npgsql/EntityFramework6.Npgsql.nuspec +++ b/src/EntityFramework6.Npgsql/EntityFramework6.Npgsql.nuspec @@ -3,7 +3,7 @@ EntityFramework6.Npgsql Npgsql for Entity Framework 6 - 3.1.0 + $version$ Shay Rojansky, Emil Lenngren, Francisco Figueiredo Jr., Kenji Uno, Jon Asher, John Cooley, Federico Di Gregorio, Jon Hanna, Chris Morgan, Dave Page, Glen Parker, Brar Piening, Hiroshi Saito Shay Rojansky, Emil Lenngren, Francisco Figueiredo Jr., Kenji Uno https://github.com/npgsql/EntityFramework6.Npgsql/blob/dev/LICENSE.txt @@ -17,7 +17,7 @@ npgsql postgresql postgres data database entity framework ef orm - + diff --git a/src/EntityFramework6.Npgsql/NpgsqlProviderManifest.cs b/src/EntityFramework6.Npgsql/NpgsqlProviderManifest.cs index b990f72..e055bce 100644 --- a/src/EntityFramework6.Npgsql/NpgsqlProviderManifest.cs +++ b/src/EntityFramework6.Npgsql/NpgsqlProviderManifest.cs @@ -371,20 +371,15 @@ public override bool SupportsInExpression() } public override ReadOnlyCollection GetStoreFunctions() - { - var functions = new List(); - - functions.AddRange( - typeof(NpgsqlTextFunctions).GetTypeInfo() - .GetMethods(BindingFlags.Public | BindingFlags.Static) - .Select(x => new { Method = x, DbFunction = x.GetCustomAttribute() }) - .Where(x => x.DbFunction != null) - .Select(x => CreateFullTextEdmFunction(x.Method, x.DbFunction))); - - return functions.AsReadOnly(); - } + => new[] { typeof(NpgsqlTextFunctions).GetTypeInfo(), typeof(NpgsqlTypeFunctions) } + .SelectMany(x => x.GetMethods(BindingFlags.Public | BindingFlags.Static)) + .Select(x => new { Method = x, DbFunction = x.GetCustomAttribute() }) + .Where(x => x.DbFunction != null) + .Select(x => CreateComposableEdmFunction(x.Method, x.DbFunction)) + .ToList() + .AsReadOnly(); - private static EdmFunction CreateFullTextEdmFunction(MethodInfo method, DbFunctionAttribute dbFunctionInfo) + static EdmFunction CreateComposableEdmFunction(MethodInfo method, DbFunctionAttribute dbFunctionInfo) { if (method == null) throw new ArgumentNullException("method"); if (dbFunctionInfo == null) throw new ArgumentNullException("dbFunctionInfo"); diff --git a/src/EntityFramework6.Npgsql/NpgsqlServices.cs b/src/EntityFramework6.Npgsql/NpgsqlServices.cs index 9fe793d..6280a91 100644 --- a/src/EntityFramework6.Npgsql/NpgsqlServices.cs +++ b/src/EntityFramework6.Npgsql/NpgsqlServices.cs @@ -200,7 +200,7 @@ private static void UsingPostgresDBConnection(NpgsqlConnection connection, Actio Pooling = false }; - using (var masterConnection = new NpgsqlConnection(connectionBuilder.ConnectionString)) + using (var masterConnection = connection.CloneWith(connectionBuilder.ConnectionString)) { masterConnection.Open();//using's Dispose will close it even if exception... action(masterConnection); diff --git a/src/EntityFramework6.Npgsql/NpgsqlTypeFunctions.cs b/src/EntityFramework6.Npgsql/NpgsqlTypeFunctions.cs new file mode 100644 index 0000000..b567668 --- /dev/null +++ b/src/EntityFramework6.Npgsql/NpgsqlTypeFunctions.cs @@ -0,0 +1,20 @@ +using System; +using System.Data.Entity; + +namespace Npgsql +{ + /// + /// Use this class in LINQ queries to emit type manipulation SQL fragments. + /// + public static class NpgsqlTypeFunctions + { + /// + /// Emits an explicit cast for unknown types sent as strings to their correct postgresql type. + /// + [DbFunction("Npgsql", "cast")] + public static string Cast(string unknownTypeValue, string postgresTypeName) + { + throw new NotSupportedException(); + } + } +} \ No newline at end of file diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/SqlBaseGenerator.cs b/src/EntityFramework6.Npgsql/SqlGenerators/SqlBaseGenerator.cs index dd6435e..81dfcbd 100644 --- a/src/EntityFramework6.Npgsql/SqlGenerators/SqlBaseGenerator.cs +++ b/src/EntityFramework6.Npgsql/SqlGenerators/SqlBaseGenerator.cs @@ -333,11 +333,18 @@ private PendingProjectsNode VisitInputWithBinding(DbExpression expression, strin input.Projection = new CommaSeparatedExpression(); DbNewInstanceExpression projection = (DbNewInstanceExpression)exp.Projection; - RowType rowType = projection.ResultType.EdmType as RowType; + RowType rowType = (RowType)projection.ResultType.EdmType; for (int i = 0; i < rowType.Properties.Count && i < projection.Arguments.Count; ++i) { - EdmProperty prop = rowType.Properties[i]; - input.Projection.Arguments.Add(new ColumnExpression(projection.Arguments[i].Accept(this), prop.Name, prop.TypeUsage)); + var prop = rowType.Properties[i]; + var argument = projection.Arguments[i].Accept(this); + var constantArgument = projection.Arguments[i] as DbConstantExpression; + if (constantArgument != null && constantArgument.Value is string) + { + argument = new CastExpression(argument, "varchar"); + } + + input.Projection.Arguments.Add(new ColumnExpression(argument, prop.Name, prop.TypeUsage)); } if (enterScope) LeaveExpression(child); @@ -1210,6 +1217,17 @@ private VisitedExpression VisitFunction(EdmFunction function, IList + + + Debug + AnyCPU + {3EC85CBA-5B79-11E3-8104-0022198AB089} + Library + Properties + EntityFramework6.Npgsql + Npgsql + 512 + true + ..\..\Npgsql.snk + ..\ + true + + + true + full + false + bin\Debug\ + TRACE;DEBUG;NET45;ENTITIES6 + prompt + 4 + bin\Debug\EntityFramework6.Npgsql.xml + v4.5 + 5 + + + pdbonly + true + bin\Release\ + TRACE;NET45;ENTITIES6 + prompt + 4 + bin\Release\EntityFramework6.Npgsql.xml + v4.5 + 5 + + + + + + + + + + + + + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + Properties\CommonAssemblyInfo.cs + Code + + + + + {9d13b739-62b1-4190-b386-7a9547304eb3} + Npgsql + + + + diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/EntityFramework6.Npgsql.nuspec b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/EntityFramework6.Npgsql.nuspec new file mode 100644 index 0000000..6e160f7 --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/EntityFramework6.Npgsql.nuspec @@ -0,0 +1,30 @@ + + + + EntityFramework6.Npgsql + Npgsql for Entity Framework 6 + 0.0.0 + Shay Rojansky, Emil Lenngren, Francisco Figueiredo Jr., Kenji Uno, Jon Asher, John Cooley, Federico Di Gregorio, Jon Hanna, Chris Morgan, Dave Page, Glen Parker, Brar Piening, Hiroshi Saito + Shay Rojansky, Emil Lenngren, Francisco Figueiredo Jr., Kenji Uno + https://github.com/npgsql/npgsql/blob/develop/LICENSE.txt + http://www.npgsql.org + http://www.npgsql.org/img/postgresql.gif + Copyright 2002 - 2015 Npgsql Development Team + false + PostgreSQL provider for Entity Framework 6 + PostgreSQL provider for Entity Framework 6 + en-US + npgsql postgresql postgres data database entity framework ef orm + + + + + + + + + + + + + diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlConnectionFactory.cs b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlConnectionFactory.cs new file mode 100644 index 0000000..2a5603c --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlConnectionFactory.cs @@ -0,0 +1,44 @@ +#region License +// The PostgreSQL License +// +// Copyright (C) 2015 The Npgsql Development Team +// +// Permission to use, copy, modify, and distribute this software and its +// documentation for any purpose, without fee, and without a written +// agreement is hereby granted, provided that the above copyright notice +// and this paragraph and the following two paragraphs appear in all copies. +// +// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY +// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS +// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// +// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS +// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +#endregion + +using System.Data.Common; +using System.Data.Entity.Infrastructure; + +namespace Npgsql +{ + /// + /// Instances of this class are used to create DbConnection objects for Postgresql + /// + public class NpgsqlConnectionFactory : IDbConnectionFactory + { + /// + /// Creates a connection for Postgresql for the given connection string. + /// + /// The connection string. + /// An initialized DbConnection. + public DbConnection CreateConnection(string nameOrConnectionString) + { + return new NpgsqlConnection(nameOrConnectionString); + } + } +} diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlMigrationSqlGenerator.cs b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlMigrationSqlGenerator.cs new file mode 100644 index 0000000..7498ecd --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlMigrationSqlGenerator.cs @@ -0,0 +1,848 @@ +#region License +// The PostgreSQL License +// +// Copyright (C) 2015 The Npgsql Development Team +// +// Permission to use, copy, modify, and distribute this software and its +// documentation for any purpose, without fee, and without a written +// agreement is hereby granted, provided that the above copyright notice +// and this paragraph and the following two paragraphs appear in all copies. +// +// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY +// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS +// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// +// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS +// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +#endregion + +using System; +using System.Collections.Generic; +using System.Data.Entity.Migrations.Model; +using System.Data.Entity.Migrations.Sql; +using System.Globalization; +using System.Text; +using System.Data.Entity.Core.Metadata.Edm; +using System.Data.Entity.Spatial; +using System.Linq; + +namespace Npgsql +{ + /// + /// Used to generate migration sql + /// + public class NpgsqlMigrationSqlGenerator : MigrationSqlGenerator + { + List migrationStatments; + private List addedSchemas; + private List addedExtensions; + private Version serverVersion; + + /// + /// Generates the migration sql. + /// + /// The operations in the migration + /// The provider manifest token used for server versioning. + public override IEnumerable Generate( + IEnumerable migrationOperations, string providerManifestToken) + { + migrationStatments = new List(); + addedSchemas = new List(); + addedExtensions = new List(); + serverVersion = new Version(providerManifestToken); + Convert(migrationOperations); + return migrationStatments; + } + + #region MigrationOperation to MigrationStatement converters + + #region General + + protected virtual void Convert(IEnumerable operations) + { + foreach (var migrationOperation in operations) + { + if (migrationOperation is AddColumnOperation) + { + Convert(migrationOperation as AddColumnOperation); + } + else if (migrationOperation is AlterColumnOperation) + { + Convert(migrationOperation as AlterColumnOperation); + } + else if (migrationOperation is CreateTableOperation) + { + Convert(migrationOperation as CreateTableOperation); + } + else if (migrationOperation is DropForeignKeyOperation) + { + Convert(migrationOperation as DropForeignKeyOperation); + } + else if (migrationOperation is DropTableOperation) + { + Convert(migrationOperation as DropTableOperation); + } + else if (migrationOperation is MoveTableOperation) + { + Convert(migrationOperation as MoveTableOperation); + } + else if (migrationOperation is RenameTableOperation) + { + Convert(migrationOperation as RenameTableOperation); + } + else if (migrationOperation is AddForeignKeyOperation) + { + Convert(migrationOperation as AddForeignKeyOperation); + } + else if (migrationOperation is DropIndexOperation) + { + Convert(migrationOperation as DropIndexOperation); + } + else if (migrationOperation is SqlOperation) + { + AddStatment((migrationOperation as SqlOperation).Sql, (migrationOperation as SqlOperation).SuppressTransaction); + } + else if (migrationOperation is AddPrimaryKeyOperation) + { + Convert(migrationOperation as AddPrimaryKeyOperation); + } + else if (migrationOperation is CreateIndexOperation) + { + Convert(migrationOperation as CreateIndexOperation); + } + else if (migrationOperation is DropColumnOperation) + { + Convert(migrationOperation as DropColumnOperation); + } + else if (migrationOperation is DropPrimaryKeyOperation) + { + Convert(migrationOperation as DropPrimaryKeyOperation); + } + else if (migrationOperation is HistoryOperation) + { + Convert(migrationOperation as HistoryOperation); + } + else if (migrationOperation is RenameColumnOperation) + { + Convert(migrationOperation as RenameColumnOperation); + } + else if (migrationOperation is UpdateDatabaseOperation) + { + Convert((migrationOperation as UpdateDatabaseOperation).Migrations as IEnumerable); + } + else + { + throw new NotImplementedException("Unhandled MigrationOperation " + migrationOperation.GetType().Name + " in " + GetType().Name); + } + } + } + + private void AddStatment(string sql, bool suppressTransacion = false) + { + migrationStatments.Add(new MigrationStatement + { + Sql = sql, + SuppressTransaction = suppressTransacion, + BatchTerminator = ";" + }); + } + + private void AddStatment(StringBuilder sql, bool suppressTransacion = false) + { + AddStatment(sql.ToString(), suppressTransacion); + } + + #endregion + + #region History + + protected virtual void Convert(HistoryOperation historyOperation) + { + foreach (var command in historyOperation.CommandTrees) + { + var npgsqlCommand = new NpgsqlCommand(); + NpgsqlServices.Instance.TranslateCommandTree(serverVersion, command, npgsqlCommand, false); + AddStatment(npgsqlCommand.CommandText); + } + } + + #endregion + + #region Tables + + protected virtual void Convert(CreateTableOperation createTableOperation) + { + StringBuilder sql = new StringBuilder(); + int dotIndex = createTableOperation.Name.IndexOf('.'); + if (dotIndex != -1) + { + CreateSchema(createTableOperation.Name.Remove(dotIndex)); + } + + sql.Append("CREATE TABLE "); + AppendTableName(createTableOperation.Name, sql); + sql.Append('('); + foreach (var column in createTableOperation.Columns) + { + AppendColumn(column, sql); + sql.Append(","); + } + if (createTableOperation.Columns.Any()) + sql.Remove(sql.Length - 1, 1); + if (createTableOperation.PrimaryKey != null) + { + sql.Append(","); + sql.Append("CONSTRAINT "); + sql.Append('"'); + sql.Append(createTableOperation.PrimaryKey.Name); + sql.Append('"'); + sql.Append(" PRIMARY KEY "); + sql.Append("("); + foreach (var column in createTableOperation.PrimaryKey.Columns) + { + sql.Append('"'); + sql.Append(column); + sql.Append("\","); + } + sql.Remove(sql.Length - 1, 1); + sql.Append(")"); + } + sql.Append(")"); + AddStatment(sql); + } + + protected virtual void Convert(DropTableOperation dropTableOperation) + { + StringBuilder sql = new StringBuilder(); + sql.Append("DROP TABLE "); + AppendTableName(dropTableOperation.Name, sql); + AddStatment(sql); + } + + protected virtual void Convert(RenameTableOperation renameTableOperation) + { + StringBuilder sql = new StringBuilder(); + sql.Append("ALTER TABLE "); + AppendTableName(renameTableOperation.Name, sql); + sql.Append(" RENAME TO "); + AppendTableName(renameTableOperation.NewName, sql); + AddStatment(sql); + } + + private void CreateSchema(string schemaName) + { + if (schemaName == "public" || addedSchemas.Contains(schemaName)) + return; + addedSchemas.Add(schemaName); + if (serverVersion.Major > 9 || (serverVersion.Major == 9 && serverVersion.Minor >= 3)) + { + AddStatment("CREATE SCHEMA IF NOT EXISTS " + schemaName); + } + else + { + //TODO: CREATE PROCEDURE that checks if schema already exists on servers < 9.3 + AddStatment("CREATE SCHEMA " + schemaName); + } + } + + //private void CreateExtension(string exensionName) + //{ + // //This is compatible only with server 9.1+ + // if (serverVersion.Major > 9 || (serverVersion.Major == 9 && serverVersion.Minor >= 1)) + // { + // if (addedExtensions.Contains(exensionName)) + // return; + // addedExtensions.Add(exensionName); + // AddStatment("CREATE EXTENSION IF NOT EXISTS \"" + exensionName + "\""); + // } + //} + + protected virtual void Convert(MoveTableOperation moveTableOperation) + { + StringBuilder sql = new StringBuilder(); + var newSchema = moveTableOperation.NewSchema ?? "dbo"; + CreateSchema(newSchema); + sql.Append("ALTER TABLE "); + AppendTableName(moveTableOperation.Name, sql); + sql.Append(" SET SCHEMA "); + sql.Append(newSchema); + AddStatment(sql); + } + + #endregion + + #region Columns + protected virtual void Convert(AddColumnOperation addColumnOperation) + { + StringBuilder sql = new StringBuilder(); + sql.Append("ALTER TABLE "); + AppendTableName(addColumnOperation.Table, sql); + sql.Append(" ADD "); + AppendColumn(addColumnOperation.Column, sql); + AddStatment(sql); + } + + protected virtual void Convert(DropColumnOperation dropColumnOperation) + { + StringBuilder sql = new StringBuilder(); + sql.Append("ALTER TABLE "); + AppendTableName(dropColumnOperation.Table, sql); + sql.Append(" DROP COLUMN \""); + sql.Append(dropColumnOperation.Name); + sql.Append('"'); + AddStatment(sql); + } + + protected virtual void Convert(AlterColumnOperation alterColumnOperation) + { + StringBuilder sql = new StringBuilder(); + + //TYPE + AppendAlterColumn(alterColumnOperation, sql); + sql.Append(" TYPE "); + AppendColumnType(alterColumnOperation.Column, sql, false); + AddStatment(sql); + sql.Clear(); + + //NOT NULL + AppendAlterColumn(alterColumnOperation, sql); + if (alterColumnOperation.Column.IsNullable != null && !alterColumnOperation.Column.IsNullable.Value) + sql.Append(" SET NOT NULL"); + else + sql.Append(" DROP NOT NULL"); + AddStatment(sql); + sql.Clear(); + + //DEFAULT + AppendAlterColumn(alterColumnOperation, sql); + if (alterColumnOperation.Column.DefaultValue != null) + { + sql.Append(" SET DEFAULT "); + AppendValue(alterColumnOperation.Column.DefaultValue, sql); + } + else if (!string.IsNullOrWhiteSpace(alterColumnOperation.Column.DefaultValueSql)) + { + sql.Append(" SET DEFAULT "); + sql.Append(alterColumnOperation.Column.DefaultValueSql); + } + else if (alterColumnOperation.Column.IsIdentity) + { + sql.Append(" SET DEFAULT "); + switch (alterColumnOperation.Column.Type) + { + case PrimitiveTypeKind.Byte: + case PrimitiveTypeKind.SByte: + case PrimitiveTypeKind.Int16: + case PrimitiveTypeKind.Int32: + case PrimitiveTypeKind.Int64: + //TODO: need function CREATE SEQUENCE IF NOT EXISTS and set to it... + //Until this is resolved changing IsIdentity from false to true + //on types int2, int4 and int8 won't switch to type serial2, serial4 and serial8 + throw new NotImplementedException("Not supporting creating sequence for integer types"); + case PrimitiveTypeKind.Guid: + //CreateExtension("uuid-ossp"); + //If uuid-ossp is not enabled migrations throw exception + AddStatment("select * from uuid_generate_v4()"); + sql.Append("uuid_generate_v4()"); + break; + default: + throw new NotImplementedException("Not supporting creating IsIdentity for " + alterColumnOperation.Column.Type); + } + } + else + { + sql.Append(" DROP DEFAULT"); + } + AddStatment(sql); + } + + private void AppendAlterColumn(AlterColumnOperation alterColumnOperation, StringBuilder sql) + { + sql.Append("ALTER TABLE "); + AppendTableName(alterColumnOperation.Table, sql); + sql.Append(" ALTER COLUMN \""); + sql.Append(alterColumnOperation.Column.Name); + sql.Append('"'); + } + + protected virtual void Convert(RenameColumnOperation renameColumnOperation) + { + StringBuilder sql = new StringBuilder(); + sql.Append("ALTER TABLE "); + AppendTableName(renameColumnOperation.Table, sql); + sql.Append(" RENAME COLUMN \""); + sql.Append(renameColumnOperation.Name); + sql.Append("\" TO \""); + sql.Append(renameColumnOperation.NewName); + sql.Append('"'); + AddStatment(sql); + } + + #endregion + + #region Keys and indexes + + protected virtual void Convert(AddForeignKeyOperation addForeignKeyOperation) + { + StringBuilder sql = new StringBuilder(); + sql.Append("ALTER TABLE "); + AppendTableName(addForeignKeyOperation.DependentTable, sql); + sql.Append(" ADD CONSTRAINT \""); + sql.Append(addForeignKeyOperation.Name); + sql.Append("\" FOREIGN KEY ("); + foreach (var column in addForeignKeyOperation.DependentColumns) + { + sql.Append('"'); + sql.Append(column); + sql.Append("\","); + } + sql.Remove(sql.Length - 1, 1); + sql.Append(") REFERENCES "); + AppendTableName(addForeignKeyOperation.PrincipalTable, sql); + sql.Append(" ("); + foreach (var column in addForeignKeyOperation.PrincipalColumns) + { + sql.Append('"'); + sql.Append(column); + sql.Append("\","); + } + sql.Remove(sql.Length - 1, 1); + sql.Append(")"); + + if (addForeignKeyOperation.CascadeDelete) + { + sql.Append(" ON DELETE CASCADE"); + } + AddStatment(sql); + } + + protected virtual void Convert(DropForeignKeyOperation dropForeignKeyOperation) + { + StringBuilder sql = new StringBuilder(); + sql.Append("ALTER TABLE "); + AppendTableName(dropForeignKeyOperation.DependentTable, sql); + if (serverVersion.Major < 9) + sql.Append(" DROP CONSTRAINT \"");//TODO: http://piecesformthewhole.blogspot.com/2011/04/dropping-foreign-key-if-it-exists-in.html ? + else + sql.Append(" DROP CONSTRAINT IF EXISTS \""); + sql.Append(dropForeignKeyOperation.Name); + sql.Append('"'); + AddStatment(sql); + } + + protected virtual void Convert(CreateIndexOperation createIndexOperation) + { + StringBuilder sql = new StringBuilder(); + sql.Append("CREATE "); + + if (createIndexOperation.IsUnique) + sql.Append("UNIQUE "); + + sql.Append("INDEX \""); + sql.Append(GetTableNameFromFullTableName(createIndexOperation.Table) + "_" + createIndexOperation.Name); + sql.Append("\" ON "); + AppendTableName(createIndexOperation.Table, sql); + sql.Append(" ("); + foreach (var column in createIndexOperation.Columns) + { + sql.Append('"'); + sql.Append(column); + sql.Append("\","); + } + sql.Remove(sql.Length - 1, 1); + sql.Append(")"); + AddStatment(sql); + } + + private string GetSchemaNameFromFullTableName(string tableFullName) + { + int dotIndex = tableFullName.IndexOf('.'); + if (dotIndex != -1) + return tableFullName.Remove(dotIndex); + else + return "dto";//TODO: Check always setting dto schema if no schema in table name is not bug + } + + /// + /// Removes schema prefix e.g. "dto.Blogs" returns "Blogs" and "Posts" returns "Posts" + /// + /// + /// + private string GetTableNameFromFullTableName(string tableName) + { + int dotIndex = tableName.IndexOf('.'); + if (dotIndex != -1) + return tableName.Substring(dotIndex + 1); + else + return tableName; + } + + protected virtual void Convert(DropIndexOperation dropIndexOperation) + { + StringBuilder sql = new StringBuilder(); + sql.Append("DROP INDEX IF EXISTS "); + sql.Append(GetSchemaNameFromFullTableName(dropIndexOperation.Table)); + sql.Append(".\""); + sql.Append(GetTableNameFromFullTableName(dropIndexOperation.Table) + "_" + dropIndexOperation.Name); + sql.Append('"'); + AddStatment(sql); + } + + protected virtual void Convert(AddPrimaryKeyOperation addPrimaryKeyOperation) + { + StringBuilder sql = new StringBuilder(); + sql.Append("ALTER TABLE "); + AppendTableName(addPrimaryKeyOperation.Table, sql); + sql.Append(" ADD CONSTRAINT \""); + sql.Append(addPrimaryKeyOperation.Name); + sql.Append("\" PRIMARY KEY "); + + sql.Append("("); + foreach (var column in addPrimaryKeyOperation.Columns) + { + sql.Append('"'); + sql.Append(column); + sql.Append("\","); + } + sql.Remove(sql.Length - 1, 1); + sql.Append(")"); + AddStatment(sql); + } + + protected virtual void Convert(DropPrimaryKeyOperation dropPrimaryKeyOperation) + { + StringBuilder sql = new StringBuilder(); + sql.Append("ALTER TABLE "); + AppendTableName(dropPrimaryKeyOperation.Table, sql); + sql.Append(" DROP CONSTRAINT \""); + sql.Append(dropPrimaryKeyOperation.Name); + sql.Append('"'); + AddStatment(sql); + } + + #endregion + + #endregion + + #region Misc functions + + private void AppendColumn(ColumnModel column, StringBuilder sql) + { + sql.Append('"'); + sql.Append(column.Name); + sql.Append("\" "); + AppendColumnType(column, sql, true); + + if (column.IsNullable != null && !column.IsNullable.Value) + sql.Append(" NOT NULL"); + + if (column.DefaultValue != null) + { + sql.Append(" DEFAULT "); + AppendValue(column.DefaultValue, sql); + } + else if (!string.IsNullOrWhiteSpace(column.DefaultValueSql)) + { + sql.Append(" DEFAULT "); + sql.Append(column.DefaultValueSql); + } + else if (column.IsIdentity) + { + switch (column.Type) + { + case PrimitiveTypeKind.Guid: + //CreateExtension("uuid-ossp"); + //If uuid-ossp is not enabled migrations throw exception + AddStatment("select * from uuid_generate_v4()"); + sql.Append(" DEFAULT uuid_generate_v4()"); + break; + case PrimitiveTypeKind.Byte: + case PrimitiveTypeKind.SByte: + case PrimitiveTypeKind.Int16: + case PrimitiveTypeKind.Int32: + case PrimitiveTypeKind.Int64: + //TODO: Add support for setting "SERIAL" + break; + } + } + else if (column.IsNullable != null + && !column.IsNullable.Value + && (column.StoreType == null || + (column.StoreType.IndexOf("rowversion", StringComparison.OrdinalIgnoreCase) == -1))) + { + sql.Append(" DEFAULT "); + AppendValue(column.ClrDefaultValue, sql); + } + } + + private void AppendColumnType(ColumnModel column, StringBuilder sql, bool setSerial) + { + switch (column.Type) + { + case PrimitiveTypeKind.Binary: + sql.Append("bytea"); + break; + case PrimitiveTypeKind.Boolean: + sql.Append("boolean"); + break; + case PrimitiveTypeKind.DateTime: + if (column.Precision != null) + sql.Append("timestamp(" + column.Precision + ")"); + else + sql.Append("timestamp"); + break; + case PrimitiveTypeKind.Decimal: + //TODO: Check if inside min/max + if (column.Precision == null && column.Scale == null) + { + sql.Append("numeric"); + } + else + { + sql.Append("numeric("); + sql.Append(column.Precision ?? 19); + sql.Append(','); + sql.Append(column.Scale ?? 4); + sql.Append(')'); + } + break; + case PrimitiveTypeKind.Double: + sql.Append("float8"); + break; + case PrimitiveTypeKind.Guid: + sql.Append("uuid"); + break; + case PrimitiveTypeKind.Single: + sql.Append("float4"); + break; + case PrimitiveTypeKind.Byte://postgres doesn't support sbyte :( + case PrimitiveTypeKind.SByte://postgres doesn't support sbyte :( + case PrimitiveTypeKind.Int16: + if (setSerial) + sql.Append(column.IsIdentity ? "serial2" : "int2"); + else + sql.Append("int2"); + break; + case PrimitiveTypeKind.Int32: + if (setSerial) + sql.Append(column.IsIdentity ? "serial4" : "int4"); + else + sql.Append("int4"); + break; + case PrimitiveTypeKind.Int64: + if (setSerial) + sql.Append(column.IsIdentity ? "serial8" : "int8"); + else + sql.Append("int8"); + break; + case PrimitiveTypeKind.String: + if (column.IsFixedLength.HasValue && + column.IsFixedLength.Value && + column.MaxLength.HasValue) + { + sql.AppendFormat("char({0})",column.MaxLength.Value); + } + else if (column.MaxLength.HasValue) + { + sql.AppendFormat("varchar({0})", column.MaxLength); + } + else + { + sql.Append("text"); + } + break; + case PrimitiveTypeKind.Time: + if (column.Precision != null) + { + sql.Append("interval("); + sql.Append(column.Precision); + sql.Append(')'); + } + else + { + sql.Append("interval"); + } + break; + case PrimitiveTypeKind.DateTimeOffset: + if (column.Precision != null) + { + sql.Append("timestamptz("); + sql.Append(column.Precision); + sql.Append(')'); + } + else + { + sql.Append("timestamptz"); + } + break; + case PrimitiveTypeKind.Geometry: + sql.Append("point"); + break; + //case PrimitiveTypeKind.Geography: + // break; + //case PrimitiveTypeKind.GeometryPoint: + // break; + //case PrimitiveTypeKind.GeometryLineString: + // break; + //case PrimitiveTypeKind.GeometryPolygon: + // break; + //case PrimitiveTypeKind.GeometryMultiPoint: + // break; + //case PrimitiveTypeKind.GeometryMultiLineString: + // break; + //case PrimitiveTypeKind.GeometryMultiPolygon: + // break; + //case PrimitiveTypeKind.GeometryCollection: + // break; + //case PrimitiveTypeKind.GeographyPoint: + // break; + //case PrimitiveTypeKind.GeographyLineString: + // break; + //case PrimitiveTypeKind.GeographyPolygon: + // break; + //case PrimitiveTypeKind.GeographyMultiPoint: + // break; + //case PrimitiveTypeKind.GeographyMultiLineString: + // break; + //case PrimitiveTypeKind.GeographyMultiPolygon: + // break; + //case PrimitiveTypeKind.GeographyCollection: + // break; + default: + throw new ArgumentException("Unhandled column type:" + column.Type); + } + } + + private void AppendTableName(string tableName, StringBuilder sql) + { + int dotIndex = tableName.IndexOf('.'); + if (dotIndex == -1) + { + sql.Append('"'); + sql.Append(tableName); + sql.Append('"'); + } + else + { + sql.Append('"'); + sql.Append(tableName.Remove(dotIndex)); + sql.Append("\".\""); + sql.Append(tableName.Substring(dotIndex + 1)); + sql.Append('"'); + } + } + + #endregion + + #region Value appenders + + private void AppendValue(byte[] values, StringBuilder sql) + { + if (values.Length == 0) + { + sql.Append("''"); + } + else + { + sql.Append("E'\\\\"); + foreach (var value in values) + sql.Append(value.ToString("X2")); + sql.Append("'"); + } + } + + private void AppendValue(bool value, StringBuilder sql) + { + sql.Append(value ? "TRUE" : "FALSE"); + } + + private void AppendValue(DateTime value, StringBuilder sql) + { + sql.Append("'"); + sql.Append(new NpgsqlTypes.NpgsqlDateTime(value)); + sql.Append("'"); + } + + private void AppendValue(DateTimeOffset value, StringBuilder sql) + { + sql.Append("'"); + sql.Append(new NpgsqlTypes.NpgsqlDateTime(value.UtcDateTime)); + sql.Append("'"); + } + + private void AppendValue(Guid value, StringBuilder sql) + { + sql.Append("'"); + sql.Append(value); + sql.Append("'"); + } + + private void AppendValue(string value, StringBuilder sql) + { + sql.Append("'"); + sql.Append(value); + sql.Append("'"); + } + + private void AppendValue(TimeSpan value, StringBuilder sql) + { + sql.Append("'"); + sql.Append(new NpgsqlTypes.NpgsqlTimeSpan(value).ToString()); + sql.Append("'"); + } + + private void AppendValue(DbGeometry value, StringBuilder sql) + { + sql.Append("'"); + sql.Append(value); + sql.Append("'"); + } + + private void AppendValue(object value, StringBuilder sql) + { + if (value is byte[]) + { + AppendValue((byte[])value, sql); + } + else if (value is bool) + { + AppendValue((bool)value, sql); + } + else if (value is DateTime) + { + AppendValue((DateTime)value, sql); + } + else if (value is DateTimeOffset) + { + AppendValue((DateTimeOffset)value, sql); + } + else if (value is Guid) + { + AppendValue((Guid)value, sql); + } + else if (value is string) + { + AppendValue((string)value, sql); + } + else if (value is TimeSpan) + { + AppendValue((TimeSpan)value, sql); + } + else if (value is DbGeometry) + { + AppendValue((DbGeometry)value, sql); + } + else + { + sql.Append(string.Format(CultureInfo.InvariantCulture, "{0}", value)); + } + } + + #endregion + } +} diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlProviderManifest.Manifest.xml b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlProviderManifest.Manifest.xml new file mode 100644 index 0000000..78e4d75 --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlProviderManifest.Manifest.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlProviderManifest.cs b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlProviderManifest.cs new file mode 100644 index 0000000..a826943 --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlProviderManifest.cs @@ -0,0 +1,442 @@ +#region License +// The PostgreSQL License +// +// Copyright (C) 2015 The Npgsql Development Team +// +// Permission to use, copy, modify, and distribute this software and its +// documentation for any purpose, without fee, and without a written +// agreement is hereby granted, provided that the above copyright notice +// and this paragraph and the following two paragraphs appear in all copies. +// +// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY +// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS +// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// +// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS +// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +#endregion + +using System; +using System.Collections.Generic; +using System.Text; +using System.Data.Common; +#if ENTITIES6 +using System.Data.Entity; +using System.Data.Entity.Core.Common; +using System.Data.Entity.Core.Metadata.Edm; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reflection; +#else +using System.Data.Metadata.Edm; +#endif +using System.Xml; +using System.Data; +using NpgsqlTypes; + +namespace Npgsql +{ + internal class NpgsqlProviderManifest : DbXmlEnabledProviderManifest + { + private Version _version; + + public Version Version { get { return _version; } } + + public NpgsqlProviderManifest(string serverVersion) + : base(CreateXmlReaderForResource("Npgsql.NpgsqlProviderManifest.Manifest.xml")) + { + if (!Version.TryParse(serverVersion, out _version)) + { + _version = new Version(9, 5); + } + } + + protected override XmlReader GetDbInformation(string informationType) + { + XmlReader xmlReader = null; + + if (informationType == StoreSchemaDefinition) + { + xmlReader = CreateXmlReaderForResource("Npgsql.NpgsqlSchema.ssdl"); + } +#if !NET40 + else if (informationType == StoreSchemaDefinitionVersion3) + { + xmlReader = CreateXmlReaderForResource("Npgsql.NpgsqlSchemaV3.ssdl"); + } +#endif + else if (informationType == StoreSchemaMapping) + { + xmlReader = CreateXmlReaderForResource("Npgsql.NpgsqlSchema.msl"); + } + + if (xmlReader == null) + throw new ArgumentOutOfRangeException("informationType"); + + return xmlReader; + } + + private const string MaxLengthFacet = "MaxLength"; + private const string ScaleFacet = "Scale"; + private const string PrecisionFacet = "Precision"; + private const string FixedLengthFacet = "FixedLength"; + + internal static NpgsqlDbType GetNpgsqlDbType(PrimitiveTypeKind _primitiveType) + { + switch (_primitiveType) + { + case PrimitiveTypeKind.Binary: + return NpgsqlDbType.Bytea; + case PrimitiveTypeKind.Boolean: + return NpgsqlDbType.Boolean; + case PrimitiveTypeKind.Byte: + case PrimitiveTypeKind.SByte: + case PrimitiveTypeKind.Int16: + return NpgsqlDbType.Smallint; + case PrimitiveTypeKind.DateTime: + return NpgsqlDbType.Timestamp; + case PrimitiveTypeKind.DateTimeOffset: + return NpgsqlDbType.TimestampTZ; + case PrimitiveTypeKind.Decimal: + return NpgsqlDbType.Numeric; + case PrimitiveTypeKind.Double: + return NpgsqlDbType.Double; + case PrimitiveTypeKind.Int32: + return NpgsqlDbType.Integer; + case PrimitiveTypeKind.Int64: + return NpgsqlDbType.Bigint; + case PrimitiveTypeKind.Single: + return NpgsqlDbType.Real; + case PrimitiveTypeKind.Time: + return NpgsqlDbType.Interval; + case PrimitiveTypeKind.Guid: + return NpgsqlDbType.Uuid; + case PrimitiveTypeKind.String: + // Send strings as unknowns to be compatible with other datatypes than text + return NpgsqlDbType.Unknown; + default: + return NpgsqlDbType.Unknown; + } + } + + public override TypeUsage GetEdmType(TypeUsage storeType) + { + if (storeType == null) + throw new ArgumentNullException("storeType"); + + string storeTypeName = storeType.EdmType.Name; + PrimitiveType primitiveType = StoreTypeNameToEdmPrimitiveType[storeTypeName]; + // TODO: come up with way to determin if unicode is used + bool isUnicode = true; + Facet facet; + + switch (storeTypeName) + { + case "bool": + case "int2": + case "int4": + case "int8": + case "float4": + case "float8": + case "uuid": + return TypeUsage.CreateDefaultTypeUsage(primitiveType); + case "numeric": + { + byte scale; + byte precision; + if (storeType.Facets.TryGetValue(ScaleFacet, false, out facet) && + !facet.IsUnbounded && facet.Value != null) + { + scale = (byte)facet.Value; + if (storeType.Facets.TryGetValue(PrecisionFacet, false, out facet) && + !facet.IsUnbounded && facet.Value != null) + { + precision = (byte)facet.Value; + return TypeUsage.CreateDecimalTypeUsage(primitiveType, precision, scale); + } + } + return TypeUsage.CreateDecimalTypeUsage(primitiveType); + } + case "bpchar": + if (storeType.Facets.TryGetValue(MaxLengthFacet, false, out facet) && + !facet.IsUnbounded && facet.Value != null) + return TypeUsage.CreateStringTypeUsage(primitiveType, isUnicode, true, (int)facet.Value); + else + return TypeUsage.CreateStringTypeUsage(primitiveType, isUnicode, true); + case "varchar": + if (storeType.Facets.TryGetValue(MaxLengthFacet, false, out facet) && + !facet.IsUnbounded && facet.Value != null) + return TypeUsage.CreateStringTypeUsage(primitiveType, isUnicode, false, (int)facet.Value); + else + return TypeUsage.CreateStringTypeUsage(primitiveType, isUnicode, false); + case "text": + case "xml": + return TypeUsage.CreateStringTypeUsage(primitiveType, isUnicode, false); + case "timestamp": + // TODO: make sure the arguments are correct here + if (storeType.Facets.TryGetValue(PrecisionFacet, false, out facet) && + !facet.IsUnbounded && facet.Value != null) + { + return TypeUsage.CreateDateTimeTypeUsage(primitiveType, (byte)facet.Value); + } + else + { + return TypeUsage.CreateDateTimeTypeUsage(primitiveType, null); + } + case "date": + return TypeUsage.CreateDateTimeTypeUsage(primitiveType, 0); + case "timestamptz": + if (storeType.Facets.TryGetValue(PrecisionFacet, false, out facet) && + !facet.IsUnbounded && facet.Value != null) + { + return TypeUsage.CreateDateTimeOffsetTypeUsage(primitiveType, (byte)facet.Value); + } + else + { + return TypeUsage.CreateDateTimeOffsetTypeUsage(primitiveType, null); + } + case "time": + case "interval": + if (storeType.Facets.TryGetValue(PrecisionFacet, false, out facet) && + !facet.IsUnbounded && facet.Value != null) + { + return TypeUsage.CreateTimeTypeUsage(primitiveType, (byte)facet.Value); + } + else + { + return TypeUsage.CreateTimeTypeUsage(primitiveType, null); + } + case "bytea": + { + if (storeType.Facets.TryGetValue(MaxLengthFacet, false, out facet) && + !facet.IsUnbounded && facet.Value != null) + { + return TypeUsage.CreateBinaryTypeUsage(primitiveType, false, (int)facet.Value); + } + return TypeUsage.CreateBinaryTypeUsage(primitiveType, false); + } + case "rowversion": + { + return TypeUsage.CreateBinaryTypeUsage(primitiveType, true, 8); + } + //TypeUsage.CreateBinaryTypeUsage + //TypeUsage.CreateDateTimeTypeUsage + //TypeUsage.CreateDecimalTypeUsage + //TypeUsage.CreateStringTypeUsage + } + throw new NotSupportedException("Not supported store type: " + storeTypeName); + } + + public override TypeUsage GetStoreType(TypeUsage edmType) + { + if (edmType == null) + throw new ArgumentNullException("edmType"); + + PrimitiveType primitiveType = edmType.EdmType as PrimitiveType; + if (primitiveType == null) + throw new ArgumentException("Store does not support specified edm type"); + + // TODO: come up with way to determin if unicode is used + bool isUnicode = true; + Facet facet; + + switch (primitiveType.PrimitiveTypeKind) + { + case PrimitiveTypeKind.Boolean: + return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["bool"]); + case PrimitiveTypeKind.Int16: + return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["int2"]); + case PrimitiveTypeKind.Int32: + return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["int4"]); + case PrimitiveTypeKind.Int64: + return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["int8"]); + case PrimitiveTypeKind.Single: + return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["float4"]); + case PrimitiveTypeKind.Double: + return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["float8"]); + case PrimitiveTypeKind.Decimal: + { + byte scale; + byte precision; + if (edmType.Facets.TryGetValue(ScaleFacet, false, out facet) && + !facet.IsUnbounded && facet.Value != null) + { + scale = (byte)facet.Value; + if (edmType.Facets.TryGetValue(PrecisionFacet, false, out facet) && + !facet.IsUnbounded && facet.Value != null) + { + precision = (byte)facet.Value; + return TypeUsage.CreateDecimalTypeUsage(StoreTypeNameToStorePrimitiveType["numeric"], precision, scale); + } + } + return TypeUsage.CreateDecimalTypeUsage(StoreTypeNameToStorePrimitiveType["numeric"]); + } + case PrimitiveTypeKind.String: + { + // TODO: could get character, character varying, text + if (edmType.Facets.TryGetValue(FixedLengthFacet, false, out facet) && + !facet.IsUnbounded && facet.Value != null && (bool)facet.Value) + { + PrimitiveType characterPrimitive = StoreTypeNameToStorePrimitiveType["bpchar"]; + if (edmType.Facets.TryGetValue(MaxLengthFacet, false, out facet) && + !facet.IsUnbounded && facet.Value != null) + { + return TypeUsage.CreateStringTypeUsage(characterPrimitive, isUnicode, true, (int)facet.Value); + } + // this may not work well + return TypeUsage.CreateStringTypeUsage(characterPrimitive, isUnicode, true); + } + if (edmType.Facets.TryGetValue(MaxLengthFacet, false, out facet) && + !facet.IsUnbounded && facet.Value != null) + { + return TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["varchar"], isUnicode, false, (int)facet.Value); + } + // assume text since it is not fixed length and has no max length + return TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["text"], isUnicode, false); + } + case PrimitiveTypeKind.DateTime: + if (edmType.Facets.TryGetValue(PrecisionFacet, false, out facet) && + !facet.IsUnbounded && facet.Value != null) + { + return TypeUsage.CreateDateTimeTypeUsage(StoreTypeNameToStorePrimitiveType["timestamp"], (byte)facet.Value); + } + else + { + return TypeUsage.CreateDateTimeTypeUsage(StoreTypeNameToStorePrimitiveType["timestamp"], null); + } + case PrimitiveTypeKind.DateTimeOffset: + if (edmType.Facets.TryGetValue(PrecisionFacet, false, out facet) && + !facet.IsUnbounded && facet.Value != null) + { + return TypeUsage.CreateDateTimeOffsetTypeUsage(StoreTypeNameToStorePrimitiveType["timestamptz"], (byte)facet.Value); + } + else + { + return TypeUsage.CreateDateTimeOffsetTypeUsage(StoreTypeNameToStorePrimitiveType["timestamptz"], null); + } + case PrimitiveTypeKind.Time: + if (edmType.Facets.TryGetValue(PrecisionFacet, false, out facet) && + !facet.IsUnbounded && facet.Value != null) + { + return TypeUsage.CreateTimeTypeUsage(StoreTypeNameToStorePrimitiveType["interval"], (byte)facet.Value); + } + else + { + return TypeUsage.CreateTimeTypeUsage(StoreTypeNameToStorePrimitiveType["interval"], null); + } + case PrimitiveTypeKind.Binary: + { + if (edmType.Facets.TryGetValue(MaxLengthFacet, false, out facet) && + !facet.IsUnbounded && facet.Value != null) + { + return TypeUsage.CreateBinaryTypeUsage(StoreTypeNameToStorePrimitiveType["bytea"], false, (int)facet.Value); + } + return TypeUsage.CreateBinaryTypeUsage(StoreTypeNameToStorePrimitiveType["bytea"], false); + } + case PrimitiveTypeKind.Guid: + return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["uuid"]); + case PrimitiveTypeKind.Byte: + case PrimitiveTypeKind.SByte: + return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["int2"]); + } + + throw new NotSupportedException("Not supported edm type: " + edmType); + } + + private static XmlReader CreateXmlReaderForResource(string resourceName) + { + return XmlReader.Create(System.Reflection.Assembly.GetAssembly(typeof(NpgsqlProviderManifest)).GetManifestResourceStream(resourceName)); + } + + public override bool SupportsEscapingLikeArgument(out char escapeCharacter) + { + escapeCharacter = '\\'; + return true; + } + + public override string EscapeLikeArgument(string argument) + { + return argument.Replace("\\","\\\\").Replace("%", "\\%").Replace("_", "\\_"); + } + +#if ENTITIES6 + public override bool SupportsInExpression() + { + return true; + } + + public override ReadOnlyCollection GetStoreFunctions() + { + var functions = new List(); + + functions.AddRange( + typeof(NpgsqlTextFunctions).GetTypeInfo() + .GetMethods(BindingFlags.Public | BindingFlags.Static) + .Select(x => new { Method = x, DbFunction = x.GetCustomAttribute() }) + .Where(x => x.DbFunction != null) + .Select(x => CreateFullTextEdmFunction(x.Method, x.DbFunction))); + + return functions.AsReadOnly(); + } + + private static EdmFunction CreateFullTextEdmFunction(MethodInfo method, DbFunctionAttribute dbFunctionInfo) + { + if (method == null) throw new ArgumentNullException("method"); + if (dbFunctionInfo == null) throw new ArgumentNullException("dbFunctionInfo"); + + return EdmFunction.Create( + dbFunctionInfo.FunctionName, + dbFunctionInfo.NamespaceName, + DataSpace.SSpace, + new EdmFunctionPayload + { + ParameterTypeSemantics = ParameterTypeSemantics.AllowImplicitConversion, + Schema = string.Empty, + IsBuiltIn = true, + IsAggregate = false, + IsFromProviderManifest = true, + StoreFunctionName = dbFunctionInfo.FunctionName, + IsComposable = true, + ReturnParameters = new[] + { + FunctionParameter.Create( + "ReturnType", + MapTypeToEdmType(method.ReturnType), + ParameterMode.ReturnValue) + }, + Parameters = method.GetParameters().Select( + x => FunctionParameter.Create( + x.Name, + MapTypeToEdmType(x.ParameterType), + ParameterMode.In)).ToList() + }, + new List()); + } + + private static EdmType MapTypeToEdmType(Type type) + { + var fromClrType = PrimitiveType + .GetEdmPrimitiveTypes() + .FirstOrDefault(t => t.ClrEquivalentType == type); + + if (fromClrType != null) + { + return fromClrType; + } + + if (type.IsEnum) + { + return MapTypeToEdmType(Enum.GetUnderlyingType(type)); + } + + throw new NotSupportedException( + string.Format("Unsupported type for mapping to EdmType: {0}", type.FullName)); + } +#endif + } +} diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlRankingNormalization.cs b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlRankingNormalization.cs new file mode 100644 index 0000000..7df6cd5 --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlRankingNormalization.cs @@ -0,0 +1,50 @@ +using System; + +namespace Npgsql +{ + /// + /// Specifies whether and how a document's length should impact its rank. + /// This is used with the ranking functions in . + /// + /// See http://www.postgresql.org/docs/current/static/textsearch-controls.html#TEXTSEARCH-RANKING + /// for more information about the behaviors that are controlled by this value. + /// + [Flags] + public enum NpgsqlRankingNormalization + { + /// + /// Ignores the document length. + /// + Default = 0, + + /// + /// Divides the rank by 1 + the logarithm of the document length. + /// + DivideBy1PlusLogLength = 1, + + /// + /// Divides the rank by the document length. + /// + DivideByLength = 2, + + /// + /// Divides the rank by the mean harmonic distance between extents (this is implemented only by ts_rank_cd). + /// + DivideByMeanHarmonicDistanceBetweenExtents = 4, + + /// + /// Divides the rank by the number of unique words in document. + /// + DivideByUniqueWordCount = 8, + + /// + /// Divides the rank by 1 + the logarithm of the number of unique words in document. + /// + DividesBy1PlusLogUniqueWordCount = 16, + + /// + /// Divides the rank by itself + 1. + /// + DivideByItselfPlusOne = 32 + } +} \ No newline at end of file diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlSchema.msl b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlSchema.msl new file mode 100644 index 0000000..aca1179 --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlSchema.msl @@ -0,0 +1,355 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlSchema.ssdl b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlSchema.ssdl new file mode 100644 index 0000000..50df816 --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlSchema.ssdl @@ -0,0 +1,724 @@ + + + + + + select + cast(c.oid as varchar) as id, + c.relname AS name, + current_database() as catalog_name, + n.nspname AS schema_name + from pg_class c + left join pg_namespace n + on n.oid = c.relnamespace + where c.relkind = 'r' and n.nspname not in ('pg_catalog','information_schema') + + + + + select + cast(a.attrelid as varchar) || '.' || cast (a.attnum as varchar) as id, + cast(c.oid as varchar) as table_id, + a.attname as name, + a.attnum as ordinal, + not a.attnotnull as is_nullable, + t.typname as type_name, + case + when t.typname in ('bpchar', 'varchar') and a.atttypmod != -1 then a.atttypmod - 4 + when t.typname in ('bit', 'varbit') and a.atttypmod != -1 then a.atttypmod + else null + end as max_length, + case + when t.typname = 'numeric' and a.atttypmod != -1 then ((a.atttypmod - 4) >> 16) & 65535 + else null + end as precision, + case + when t.typname in ('time', 'timestamp', 'timestamptz') and a.atttypmod != -1 then a.atttypmod + when t.typname = 'interval' and a.atttypmod & 65535 != 65535 then a.atttypmod & 65535 + else null + end as datetime_precision, + case + when t.typname = 'numeric' and a.atttypmod != -1 then (a.atttypmod - 4) & 65535 + else null + end as scale, + case + when t.typlen = -1 and t.typelem != 0 then true + else false + end as is_multiset, + null as collation_catalog_name, + null as collation_schema_name, + null as collation_name, + null as char_set_catalog_name, + null as char_set_schema_name, + null as char_set_name, + case + when pg_get_expr(ad.adbin, ad.adrelid) like 'nextval%' then true + else false + end as is_identity, + false as is_generated, +-- default value column + ad.adsrc as default_value + from pg_attribute a + join pg_class c + on a.attrelid = c.oid + join pg_type t + on a.atttypid = t.oid + left join pg_attrdef ad + on a.attrelid = ad.adrelid and a.attnum = ad.adnum + where t.typtype = 'b' and c.relkind = 'r' and a.attnum >= 0 and c.relnamespace in + (select oid from pg_namespace where nspname not in ('pg_catalog','information_schema')) + + + + + select + cast(c.oid as varchar) as id, + cast(c.conrelid as varchar) as table_id, + c.conname as name, + c.condeferrable as is_deferrable, + c.condeferred as is_initially_deferred, + c.contype as constraint_type, + c.consrc as expression, + case c.confupdtype + when 'c' then 'CASCADE' + when 'n' then 'SET NULL' + when 'd' then 'SET DEFAULT' + when 'r' then 'RESTRICT' + when 'a' then 'NO ACTION' + else NULL + end AS update_rule, + case c.confdeltype + when 'c' then 'CASCADE' + when 'n' then 'SET NULL' + when 'd' then 'SET DEFAULT' + when 'r' then 'RESTRICT' + when 'a' then 'NO ACTION' + else NULL + end AS delete_rule + from pg_constraint c + + + + + select + cast(c.oid as varchar) || '.' || cast((c.confkey).n as varchar) as id, + (c.confkey).n as ordinal, + cast(pkc.attrelid as varchar) || '.' || cast (pkc.attnum as varchar) as from_columnid, + cast(fkc.attrelid as varchar) || '.' || cast (fkc.attnum as varchar) as to_columnid, + cast(c.oid as varchar) as constraint_id + from (select c.oid, + c.conname, + c.conrelid, + information_schema._pg_expandarray(c.conkey) as conkey, + c.confrelid, + information_schema._pg_expandarray(c.confkey) as confkey + from pg_constraint c + where contype = 'f') c + join pg_class pt on pt.oid = c.conrelid + join pg_class ft on ft.oid = c.confrelid + join pg_attribute pkc on pkc.attrelid = c.conrelid and pkc.attnum = (c.conkey).x + join pg_attribute fkc on fkc.attrelid = c.confrelid and fkc.attnum = (c.confkey).x + + + + + select + cast(c.oid as varchar) as id, + c.relname AS name, + current_database() as catalog_name, + n.nspname AS schema_name, + false as is_updatable, + pg_get_viewdef(c.oid) AS definition + from pg_class c + left join pg_namespace n + on n.oid = c.relnamespace + where c.relkind = 'v' and n.nspname not in ('pg_catalog','information_schema') + + + + + select + cast(a.attrelid as varchar) || '.' || cast (a.attnum as varchar) as id, + cast(c.oid as varchar) as view_id, + a.attname as name, + a.attnum as ordinal, + not a.attnotnull as is_nullable, + t.typname as type_name, + case + when t.typname in ('bpchar', 'varchar') and a.atttypmod != -1 then a.atttypmod - 4 + when t.typname in ('bit', 'varbit') and a.atttypmod != -1 then a.atttypmod + else null + end as max_length, + case + when t.typname = 'numeric' and a.atttypmod != -1 then ((a.atttypmod - 4) >> 16) & 65535 + else null + end as precision, + case + when t.typname in ('time', 'timestamp', 'timestamptz') and a.atttypmod != -1 then a.atttypmod + when t.typname = 'interval' and a.atttypmod & 65535 != 65535 then a.atttypmod & 65535 + else null + end as datetime_precision, + case + when t.typname = 'numeric' and a.atttypmod != -1 then (a.atttypmod - 4) & 65535 + else null + end as scale, + case + when t.typlen = -1 and t.typelem != 0 then true + else false + end as is_multiset, + null as collation_catalog_name, + null as collation_schema_name, + null as collation_name, + null as char_set_catalog_name, + null as char_set_schema_name, + null as char_set_name, + case + when pg_get_expr(ad.adbin, ad.adrelid) like 'nextval%' then true + else false + end as is_identity, + false as is_generated, +-- default value column + ad.adsrc as default_value + from pg_attribute a + join pg_class c + on a.attrelid = c.oid + join pg_type t + on a.atttypid = t.oid + left join pg_attrdef ad + on a.attrelid = ad.adrelid AND a.attnum = ad.adnum + where t.typtype = 'b' and c.relkind = 'v' and a.attnum >= 0 and c.relnamespace in + (select oid from pg_namespace where nspname not in ('pg_catalog','information_schema')) + + + + + select '1'::varchar as id, '1'::varchar as view_id, '1'::varchar as name, false as is_deferrable, false as is_initially_deferred, 'p'::varchar as constraint_type, '1'::varchar as expression, '1'::varchar as update_rule, '1'::varchar as delete_rule where 1=0 + + + + + select '1'::varchar as id, 0 as ordinal where 1=0 + + + + + select + cast(p.oid as varchar) as id, + current_database() as catalog_name, + n.nspname AS schema_name, + p.proname as name, + false as is_builtin, + false as is_niladic, + t.typname as returntype, + null as max_length, + null as precision, + null as datetime_precision, + null as scale, + case + when t.typlen = -1 and t.typelem != 0 then true + else false + end as is_multiset, + null as collation_catalog_name, + null as collation_schema_name, + null as collation_name, + null as char_set_catalog_name, + null as char_set_schema_name, + null as char_set_name, + false as is_aggregate + from pg_proc p + left join pg_namespace n + on n.oid = p.pronamespace + left join pg_type t + on p.prorettype = t.oid + where (p.proretset = false and t.typname != 'void') and n.nspname not in ('pg_catalog','information_schema') and p.proname not in (select pg_proc.proname from pg_proc group by pg_proc.proname having count(pg_proc.proname) > 1) + + + + + select + cast(ss.p_oid as varchar) || '.' || cast((ss.x).n as varchar) as id, + cast(ss.p_oid as varchar) as function_id, + case + when NULLIF(ss.proargnames[(ss.x).n], '') is null then 'x' + else ss.proargnames[(ss.x).n] + end as name, + (ss.x).n as ordinal, + t.typname as type_name, + null as max_length, + null as precision, + null as datetime_precision, + null as scale, + case + when t.typlen = -1 and t.typelem != 0 then true + else false + end as is_multiset, + null as collation_catalog_name, + null as collation_schema_name, + null as collation_name, + null as char_set_catalog_name, + null as char_set_schema_name, + null as char_set_name, + case + when ss.proargmodes IS null then 'IN' + when ss.proargmodes[(ss.x).n] = 'i' then 'IN' + when ss.proargmodes[(ss.x).n] = 'o' then 'OUT' + when ss.proargmodes[(ss.x).n] = 'b' then 'INOUT' + else null + end as mode, + null as default + from pg_type t + join (select + n.nspname AS n_nspname, + p.proname, + p.oid AS p_oid, + p.proargnames, + p.proargmodes, + p.proretset, + p.prorettype, + information_schema._pg_expandarray(COALESCE(p.proallargtypes, p.proargtypes::oid[])) AS x + from pg_namespace n + join pg_proc p + on n.oid = p.pronamespace and p.proname not in (select pg_proc.proname from pg_proc group by pg_proc.proname having count(pg_proc.proname) > 1)) ss + on t.oid = (ss.x).x + join pg_type rt + on ss.prorettype = rt.oid + where (ss.proretset = false and rt.typname != 'void') and ss.n_nspname not in ('pg_catalog','information_schema') + + + + + select + cast(p.oid as varchar) as id, + current_database() as catalog_name, + n.nspname AS schema_name, + p.proname as name, + false as is_builtin, + false as is_niladic, + --t.typname as returntype, + false as is_aggregate + from pg_proc p + left join pg_namespace n + on n.oid = p.pronamespace + left join pg_type t + on p.prorettype = t.oid + where (p.proretset = true or t.typname = 'void') and n.nspname not in ('pg_catalog','information_schema') and p.proname not in (select pg_proc.proname from pg_proc group by pg_proc.proname having count(pg_proc.proname) > 1) + + + + + select + cast(ss.p_oid as varchar) || '.' || cast((ss.x).n as varchar) as id, + cast(ss.p_oid as varchar) as procedure_id, + case + when NULLIF(ss.proargnames[(ss.x).n], '') is null then 'x' + else ss.proargnames[(ss.x).n] + end as name, + (ss.x).n as ordinal, + t.typname as type_name, + null as max_length, + null as precision, + null as datetime_precision, + null as scale, + case + when t.typlen = -1 and t.typelem != 0 then true + else false + end as is_multiset, + null as collation_catalog_name, + null as collation_schema_name, + null as collation_name, + null as char_set_catalog_name, + null as char_set_schema_name, + null as char_set_name, + case + when ss.proargmodes IS null then 'IN' + when ss.proargmodes[(ss.x).n] = 'i' then 'IN' + when ss.proargmodes[(ss.x).n] = 'o' then 'OUT' + when ss.proargmodes[(ss.x).n] = 'b' then 'INOUT' + else null + end as mode, + null as default + from pg_type t + join (select + n.nspname AS n_nspname, + p.proname, + p.oid AS p_oid, + p.proargnames, + p.proargmodes, + p.proretset, + p.prorettype, + information_schema._pg_expandarray(COALESCE(p.proallargtypes, p.proargtypes::oid[])) AS x + from pg_namespace n + join pg_proc p + on n.oid = p.pronamespace and p.proname not in (select pg_proc.proname from pg_proc group by pg_proc.proname having count(pg_proc.proname) > 1)) ss + on t.oid = (ss.x).x + join pg_type rt + on ss.prorettype = rt.oid + where (ss.proretset = true or rt.typname = 'void') and ss.n_nspname not in ('pg_catalog','information_schema') + + + + + select + cast(a.attrelid as varchar) || '.' || cast (a.attnum as varchar) as column_id, + cast(coid as varchar) as constraint_id, + (ss.x).n as ordinal + from pg_attribute a + join (select + r.oid AS roid, + c.oid as coid, + c.conname, + information_schema._pg_expandarray(c.conkey) as x, + r.relnamespace as rrelnamespace + from pg_constraint c + join pg_class r + on r.oid = c.conrelid + where r.relkind = 'r') ss + on a.attrelid = ss.roid and a.attnum = (ss.x).x and not a.attisdropped and rrelnamespace in + (select oid from pg_namespace where nspname not in ('pg_catalog','information_schema')) + + + + + select '1'::varchar as column_id, '1'::varchar as constraint_id where 1=0 + + + + + select '1'::varchar as id, '1'::varchar as constraint_id, '1'::varchar as from_columnid, '1'::varchar as to_columnid, 0 as ordinal where 1=0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlSchemaV3.ssdl b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlSchemaV3.ssdl new file mode 100644 index 0000000..dd04ebd --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlSchemaV3.ssdl @@ -0,0 +1,724 @@ + + + + + + select + cast(c.oid as varchar) as id, + c.relname AS name, + current_database() as catalog_name, + n.nspname AS schema_name + from pg_class c + left join pg_namespace n + on n.oid = c.relnamespace + where c.relkind = 'r' and n.nspname not in ('pg_catalog','information_schema') + + + + + select + cast(a.attrelid as varchar) || '.' || cast (a.attnum as varchar) as id, + cast(c.oid as varchar) as table_id, + a.attname as name, + a.attnum as ordinal, + not a.attnotnull as is_nullable, + t.typname as type_name, + case + when t.typname in ('bpchar', 'varchar') and a.atttypmod != -1 then a.atttypmod - 4 + when t.typname in ('bit', 'varbit') and a.atttypmod != -1 then a.atttypmod + else null + end as max_length, + case + when t.typname = 'numeric' and a.atttypmod != -1 then ((a.atttypmod - 4) >> 16) & 65535 + else null + end as precision, + case + when t.typname in ('time', 'timestamp', 'timestamptz') and a.atttypmod != -1 then a.atttypmod + when t.typname = 'interval' and a.atttypmod & 65535 != 65535 then a.atttypmod & 65535 + else null + end as datetime_precision, + case + when t.typname = 'numeric' and a.atttypmod != -1 then (a.atttypmod - 4) & 65535 + else null + end as scale, + case + when t.typlen = -1 and t.typelem != 0 then true + else false + end as is_multiset, + null as collation_catalog_name, + null as collation_schema_name, + null as collation_name, + null as char_set_catalog_name, + null as char_set_schema_name, + null as char_set_name, + case + when pg_get_expr(ad.adbin, ad.adrelid) like 'nextval%' then true + else false + end as is_identity, + false as is_generated, +-- default value column + ad.adsrc as default_value + from pg_attribute a + join pg_class c + on a.attrelid = c.oid + join pg_type t + on a.atttypid = t.oid + left join pg_attrdef ad + on a.attrelid = ad.adrelid and a.attnum = ad.adnum + where t.typtype = 'b' and c.relkind = 'r' and a.attnum >= 0 and c.relnamespace in + (select oid from pg_namespace where nspname not in ('pg_catalog','information_schema')) + + + + + select + cast(c.oid as varchar) as id, + cast(c.conrelid as varchar) as table_id, + c.conname as name, + c.condeferrable as is_deferrable, + c.condeferred as is_initially_deferred, + c.contype as constraint_type, + c.consrc as expression, + case c.confupdtype + when 'c' then 'CASCADE' + when 'n' then 'SET NULL' + when 'd' then 'SET DEFAULT' + when 'r' then 'RESTRICT' + when 'a' then 'NO ACTION' + else NULL + end AS update_rule, + case c.confdeltype + when 'c' then 'CASCADE' + when 'n' then 'SET NULL' + when 'd' then 'SET DEFAULT' + when 'r' then 'RESTRICT' + when 'a' then 'NO ACTION' + else NULL + end AS delete_rule + from pg_constraint c + + + + + select + cast(c.oid as varchar) || '.' || cast((c.confkey).n as varchar) as id, + (c.confkey).n as ordinal, + cast(pkc.attrelid as varchar) || '.' || cast (pkc.attnum as varchar) as from_columnid, + cast(fkc.attrelid as varchar) || '.' || cast (fkc.attnum as varchar) as to_columnid, + cast(c.oid as varchar) as constraint_id + from (select c.oid, + c.conname, + c.conrelid, + information_schema._pg_expandarray(c.conkey) as conkey, + c.confrelid, + information_schema._pg_expandarray(c.confkey) as confkey + from pg_constraint c + where contype = 'f') c + join pg_class pt on pt.oid = c.conrelid + join pg_class ft on ft.oid = c.confrelid + join pg_attribute pkc on pkc.attrelid = c.conrelid and pkc.attnum = (c.conkey).x + join pg_attribute fkc on fkc.attrelid = c.confrelid and fkc.attnum = (c.confkey).x + + + + + select + cast(c.oid as varchar) as id, + c.relname AS name, + current_database() as catalog_name, + n.nspname AS schema_name, + false as is_updatable, + pg_get_viewdef(c.oid) AS definition + from pg_class c + left join pg_namespace n + on n.oid = c.relnamespace + where c.relkind = 'v' and n.nspname not in ('pg_catalog','information_schema') + + + + + select + cast(a.attrelid as varchar) || '.' || cast (a.attnum as varchar) as id, + cast(c.oid as varchar) as view_id, + a.attname as name, + a.attnum as ordinal, + not a.attnotnull as is_nullable, + t.typname as type_name, + case + when t.typname in ('bpchar', 'varchar') and a.atttypmod != -1 then a.atttypmod - 4 + when t.typname in ('bit', 'varbit') and a.atttypmod != -1 then a.atttypmod + else null + end as max_length, + case + when t.typname = 'numeric' and a.atttypmod != -1 then ((a.atttypmod - 4) >> 16) & 65535 + else null + end as precision, + case + when t.typname in ('time', 'timestamp', 'timestamptz') and a.atttypmod != -1 then a.atttypmod + when t.typname = 'interval' and a.atttypmod & 65535 != 65535 then a.atttypmod & 65535 + else null + end as datetime_precision, + case + when t.typname = 'numeric' and a.atttypmod != -1 then (a.atttypmod - 4) & 65535 + else null + end as scale, + case + when t.typlen = -1 and t.typelem != 0 then true + else false + end as is_multiset, + null as collation_catalog_name, + null as collation_schema_name, + null as collation_name, + null as char_set_catalog_name, + null as char_set_schema_name, + null as char_set_name, + case + when pg_get_expr(ad.adbin, ad.adrelid) like 'nextval%' then true + else false + end as is_identity, + false as is_generated, +-- default value column + ad.adsrc as default_value + from pg_attribute a + join pg_class c + on a.attrelid = c.oid + join pg_type t + on a.atttypid = t.oid + left join pg_attrdef ad + on a.attrelid = ad.adrelid AND a.attnum = ad.adnum + where t.typtype = 'b' and c.relkind = 'v' and a.attnum >= 0 and c.relnamespace in + (select oid from pg_namespace where nspname not in ('pg_catalog','information_schema')) + + + + + select '1'::varchar as id, '1'::varchar as view_id, '1'::varchar as name, false as is_deferrable, false as is_initially_deferred, 'p'::varchar as constraint_type, '1'::varchar as expression, '1'::varchar as update_rule, '1'::varchar as delete_rule where 1=0 + + + + + select '1'::varchar as id, 0 as ordinal where 1=0 + + + + + select + cast(p.oid as varchar) as id, + current_database() as catalog_name, + n.nspname AS schema_name, + p.proname as name, + false as is_builtin, + false as is_niladic, + t.typname as returntype, + null as max_length, + null as precision, + null as datetime_precision, + null as scale, + case + when t.typlen = -1 and t.typelem != 0 then true + else false + end as is_multiset, + null as collation_catalog_name, + null as collation_schema_name, + null as collation_name, + null as char_set_catalog_name, + null as char_set_schema_name, + null as char_set_name, + false as is_aggregate + from pg_proc p + left join pg_namespace n + on n.oid = p.pronamespace + left join pg_type t + on p.prorettype = t.oid + where (p.proretset = false and t.typname != 'void') and n.nspname not in ('pg_catalog','information_schema') and p.proname not in (select pg_proc.proname from pg_proc group by pg_proc.proname having count(pg_proc.proname) > 1) + + + + + select + cast(ss.p_oid as varchar) || '.' || cast((ss.x).n as varchar) as id, + cast(ss.p_oid as varchar) as function_id, + case + when NULLIF(ss.proargnames[(ss.x).n], '') is null then 'x' + else ss.proargnames[(ss.x).n] + end as name, + (ss.x).n as ordinal, + t.typname as type_name, + null as max_length, + null as precision, + null as datetime_precision, + null as scale, + case + when t.typlen = -1 and t.typelem != 0 then true + else false + end as is_multiset, + null as collation_catalog_name, + null as collation_schema_name, + null as collation_name, + null as char_set_catalog_name, + null as char_set_schema_name, + null as char_set_name, + case + when ss.proargmodes IS null then 'IN' + when ss.proargmodes[(ss.x).n] = 'i' then 'IN' + when ss.proargmodes[(ss.x).n] = 'o' then 'OUT' + when ss.proargmodes[(ss.x).n] = 'b' then 'INOUT' + else null + end as mode, + null as default + from pg_type t + join (select + n.nspname AS n_nspname, + p.proname, + p.oid AS p_oid, + p.proargnames, + p.proargmodes, + p.proretset, + p.prorettype, + information_schema._pg_expandarray(COALESCE(p.proallargtypes, p.proargtypes::oid[])) AS x + from pg_namespace n + join pg_proc p + on n.oid = p.pronamespace and p.proname not in (select pg_proc.proname from pg_proc group by pg_proc.proname having count(pg_proc.proname) > 1)) ss + on t.oid = (ss.x).x + join pg_type rt + on ss.prorettype = rt.oid + where (ss.proretset = false and rt.typname != 'void') and ss.n_nspname not in ('pg_catalog','information_schema') + + + + + select + cast(p.oid as varchar) as id, + current_database() as catalog_name, + n.nspname AS schema_name, + p.proname as name, + false as is_builtin, + false as is_niladic, + --t.typname as returntype, + false as is_aggregate + from pg_proc p + left join pg_namespace n + on n.oid = p.pronamespace + left join pg_type t + on p.prorettype = t.oid + where (p.proretset = true or t.typname = 'void') and n.nspname not in ('pg_catalog','information_schema') and p.proname not in (select pg_proc.proname from pg_proc group by pg_proc.proname having count(pg_proc.proname) > 1) + + + + + select + cast(ss.p_oid as varchar) || '.' || cast((ss.x).n as varchar) as id, + cast(ss.p_oid as varchar) as procedure_id, + case + when NULLIF(ss.proargnames[(ss.x).n], '') is null then 'x' + else ss.proargnames[(ss.x).n] + end as name, + (ss.x).n as ordinal, + t.typname as type_name, + null as max_length, + null as precision, + null as datetime_precision, + null as scale, + case + when t.typlen = -1 and t.typelem != 0 then true + else false + end as is_multiset, + null as collation_catalog_name, + null as collation_schema_name, + null as collation_name, + null as char_set_catalog_name, + null as char_set_schema_name, + null as char_set_name, + case + when ss.proargmodes IS null then 'IN' + when ss.proargmodes[(ss.x).n] = 'i' then 'IN' + when ss.proargmodes[(ss.x).n] = 'o' then 'OUT' + when ss.proargmodes[(ss.x).n] = 'b' then 'INOUT' + else null + end as mode, + null as default + from pg_type t + join (select + n.nspname AS n_nspname, + p.proname, + p.oid AS p_oid, + p.proargnames, + p.proargmodes, + p.proretset, + p.prorettype, + information_schema._pg_expandarray(COALESCE(p.proallargtypes, p.proargtypes::oid[])) AS x + from pg_namespace n + join pg_proc p + on n.oid = p.pronamespace and p.proname not in (select pg_proc.proname from pg_proc group by pg_proc.proname having count(pg_proc.proname) > 1)) ss + on t.oid = (ss.x).x + join pg_type rt + on ss.prorettype = rt.oid + where (ss.proretset = true or rt.typname = 'void') and ss.n_nspname not in ('pg_catalog','information_schema') + + + + + select + cast(a.attrelid as varchar) || '.' || cast (a.attnum as varchar) as column_id, + cast(coid as varchar) as constraint_id, + (ss.x).n as ordinal + from pg_attribute a + join (select + r.oid AS roid, + c.oid as coid, + c.conname, + information_schema._pg_expandarray(c.conkey) as x, + r.relnamespace as rrelnamespace + from pg_constraint c + join pg_class r + on r.oid = c.conrelid + where r.relkind = 'r') ss + on a.attrelid = ss.roid and a.attnum = (ss.x).x and not a.attisdropped and rrelnamespace in + (select oid from pg_namespace where nspname not in ('pg_catalog','information_schema')) + + + + + select '1'::varchar as column_id, '1'::varchar as constraint_id where 1=0 + + + + + select '1'::varchar as id, '1'::varchar as constraint_id, '1'::varchar as from_columnid, '1'::varchar as to_columnid, 0 as ordinal where 1=0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlServices.cs b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlServices.cs new file mode 100644 index 0000000..99df212 --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlServices.cs @@ -0,0 +1,211 @@ +#region License +// The PostgreSQL License +// +// Copyright (C) 2015 The Npgsql Development Team +// +// Permission to use, copy, modify, and distribute this software and its +// documentation for any purpose, without fee, and without a written +// agreement is hereby granted, provided that the above copyright notice +// and this paragraph and the following two paragraphs appear in all copies. +// +// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY +// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS +// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// +// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS +// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +#endregion + +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Text; +using System.Xml; +#if ENTITIES6 +using System.Data.Entity.Core.Common; +using System.Data.Entity.Core.Common.CommandTrees; +using System.Data.Entity.Core.Metadata.Edm; +using System.Data.Entity.Migrations.Sql; +using System.Data.Entity.Infrastructure.DependencyResolution; +#else +using System.Data.Common.CommandTrees; +using System.Data.Metadata.Edm; +#endif +using Npgsql.SqlGenerators; +using System.Data; +using NpgsqlTypes; + +namespace Npgsql +{ +#if ENTITIES6 + public class NpgsqlServices : DbProviderServices +#else + internal class NpgsqlServices : DbProviderServices +#endif + { + private static readonly NpgsqlServices _instance = new NpgsqlServices(); + +#if ENTITIES6 + public NpgsqlServices() + { + AddDependencyResolver(new SingletonDependencyResolver>( + () => new NpgsqlMigrationSqlGenerator(), "Npgsql")); + } +#endif + + public static NpgsqlServices Instance + { + get { return _instance; } + } + + protected override DbCommandDefinition CreateDbCommandDefinition(DbProviderManifest providerManifest, DbCommandTree commandTree) + { + return CreateCommandDefinition(CreateDbCommand(((NpgsqlProviderManifest)providerManifest).Version, commandTree)); + } + + internal DbCommand CreateDbCommand(Version serverVersion, DbCommandTree commandTree) + { + if (commandTree == null) + throw new ArgumentNullException("commandTree"); + + NpgsqlCommand command = new NpgsqlCommand(); + + foreach (KeyValuePair parameter in commandTree.Parameters) + { + NpgsqlParameter dbParameter = new NpgsqlParameter(); + dbParameter.ParameterName = parameter.Key; + dbParameter.NpgsqlDbType = NpgsqlProviderManifest.GetNpgsqlDbType(((PrimitiveType)parameter.Value.EdmType).PrimitiveTypeKind); + command.Parameters.Add(dbParameter); + } + + TranslateCommandTree(serverVersion, commandTree, command); + + return command; + } + + internal void TranslateCommandTree(Version serverVersion, DbCommandTree commandTree, DbCommand command, bool createParametersForNonSelect = true) + { + SqlBaseGenerator sqlGenerator = null; + + DbQueryCommandTree select; + DbInsertCommandTree insert; + DbUpdateCommandTree update; + DbDeleteCommandTree delete; + if ((select = commandTree as DbQueryCommandTree) != null) + { + sqlGenerator = new SqlSelectGenerator(select); + } + else if ((insert = commandTree as DbInsertCommandTree) != null) + { + sqlGenerator = new SqlInsertGenerator(insert); + } + else if ((update = commandTree as DbUpdateCommandTree) != null) + { + sqlGenerator = new SqlUpdateGenerator(update); + } + else if ((delete = commandTree as DbDeleteCommandTree) != null) + { + sqlGenerator = new SqlDeleteGenerator(delete); + } + else + { + // TODO: get a message (unsupported DbCommandTree type) + throw new ArgumentException(); + } + sqlGenerator._createParametersForConstants = select != null ? false : createParametersForNonSelect; + sqlGenerator._command = (NpgsqlCommand)command; + sqlGenerator.Version = serverVersion; + + sqlGenerator.BuildCommand(command); + } + + protected override string GetDbProviderManifestToken(DbConnection connection) + { + if (connection == null) + throw new ArgumentNullException("connection"); + string serverVersion = ""; + UsingPostgresDBConnection((NpgsqlConnection)connection, conn => + { + serverVersion = conn.ServerVersion; + }); + return serverVersion; + } + + protected override DbProviderManifest GetDbProviderManifest(string versionHint) + { + if (versionHint == null) + throw new ArgumentNullException("versionHint"); + return new NpgsqlProviderManifest(versionHint); + } + +#if ENTITIES6 + protected override bool DbDatabaseExists(DbConnection connection, int? commandTimeout, StoreItemCollection storeItemCollection) + { + bool exists = false; + UsingPostgresDBConnection((NpgsqlConnection)connection, conn => + { + using (NpgsqlCommand command = new NpgsqlCommand("select count(*) from pg_catalog.pg_database where datname = '" + connection.Database + "';", conn)) + { + exists = Convert.ToInt32(command.ExecuteScalar()) > 0; + } + }); + return exists; + } + + protected override void DbCreateDatabase(DbConnection connection, int? commandTimeout, StoreItemCollection storeItemCollection) + { + UsingPostgresDBConnection((NpgsqlConnection)connection, conn => + { + var sb = new StringBuilder(); + sb.Append("CREATE DATABASE \""); + sb.Append(connection.Database); + sb.Append("\""); + if (conn.EntityTemplateDatabase != null) + { + sb.Append(" TEMPLATE \""); + sb.Append(conn.EntityTemplateDatabase); + sb.Append("\""); + } + + using (NpgsqlCommand command = new NpgsqlCommand(sb.ToString(), conn)) + { + command.ExecuteNonQuery(); + } + }); + } + + protected override void DbDeleteDatabase(DbConnection connection, int? commandTimeout, StoreItemCollection storeItemCollection) + { + UsingPostgresDBConnection((NpgsqlConnection)connection, conn => + { + //Close all connections in pool or exception "database used by another user appears" + NpgsqlConnection.ClearAllPools(); + using (NpgsqlCommand command = new NpgsqlCommand("DROP DATABASE \"" + connection.Database + "\";", conn)) + { + command.ExecuteNonQuery(); + } + }); + } +#endif + + private static void UsingPostgresDBConnection(NpgsqlConnection connection, Action action) + { + var connectionBuilder = new NpgsqlConnectionStringBuilder(connection.ConnectionString) + { + Database = connection.EntityAdminDatabase ?? "template1", + Pooling = false + }; + + using (var masterConnection = new NpgsqlConnection(connectionBuilder.ConnectionString)) + { + masterConnection.Open();//using's Dispose will close it even if exception... + action(masterConnection); + } + } + } +} diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlTextFunctions.cs b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlTextFunctions.cs new file mode 100644 index 0000000..d31a7a5 --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlTextFunctions.cs @@ -0,0 +1,372 @@ +using System; +using System.Data.Entity; +using System.Diagnostics.CodeAnalysis; + +namespace Npgsql +{ + /// + /// Use this class in LINQ queries to generate full-text search expressions using tsvector and tsquery. + /// None of these functions can be called directly. + /// See http://www.postgresql.org/docs/current/static/functions-textsearch.html for the latest documentation. + /// + [SuppressMessage("ReSharper", "UnusedParameter.Global")] + public static class NpgsqlTextFunctions + { + /// + /// Cast to the tsvector data type. + /// + [DbFunction("Npgsql", "as_tsvector")] + public static string AsTsVector(string vector) + { + throw new NotSupportedException(); + } + + /// + /// Reduce to tsvector. + /// http://www.postgresql.org/docs/current/static/textsearch-controls.html#TEXTSEARCH-PARSING-DOCUMENTS + /// + [DbFunction("Npgsql", "to_tsvector")] + public static string ToTsVector(string document) + { + throw new NotSupportedException(); + } + + /// + /// Reduce to tsvector using the text search configuration specified + /// by . + /// http://www.postgresql.org/docs/current/static/textsearch-controls.html#TEXTSEARCH-PARSING-DOCUMENTS + /// + [DbFunction("Npgsql", "to_tsvector")] + public static string ToTsVector(string config, string document) + { + throw new NotSupportedException(); + } + + /// + /// Cast to the tsquery data type. + /// + [DbFunction("Npgsql", "as_tsquery")] + public static string AsTsQuery(string query) + { + throw new NotSupportedException(); + } + + /// + /// Produce tsquery from ignoring punctuation. + /// http://www.postgresql.org/docs/current/static/textsearch-controls.html#TEXTSEARCH-PARSING-QUERIES + /// + [DbFunction("Npgsql", "plainto_tsquery")] + public static string PlainToTsQuery(string query) + { + throw new NotSupportedException(); + } + + /// + /// Produce tsquery from ignoring punctuation and using the text search + /// configuration specified by . + /// http://www.postgresql.org/docs/current/static/textsearch-controls.html#TEXTSEARCH-PARSING-QUERIES + /// + [DbFunction("Npgsql", "plainto_tsquery")] + public static string PlainToTsQuery(string config, string query) + { + throw new NotSupportedException(); + } + + /// + /// Normalize words in and convert to tsquery. If your input + /// contains punctuation that should not be treated as text search operators, use + /// instead. + /// http://www.postgresql.org/docs/current/static/textsearch-controls.html#TEXTSEARCH-PARSING-QUERIES + /// + [DbFunction("Npgsql", "to_tsquery")] + public static string ToTsQuery(string query) + { + throw new NotSupportedException(); + } + + /// + /// Normalize words in and convert to tsquery using the text search + /// configuration specified by . If your input contains punctuation + /// that should not be treated as text search operators, use + /// instead. + /// http://www.postgresql.org/docs/current/static/textsearch-controls.html#TEXTSEARCH-PARSING-QUERIES + /// + [DbFunction("Npgsql", "to_tsquery")] + public static string ToTsQuery(string config, string query) + { + throw new NotSupportedException(); + } + + /// + /// AND tsquerys together. Generates the "&&" operator. + /// http://www.postgresql.org/docs/current/static/textsearch-features.html#TEXTSEARCH-MANIPULATE-TSQUERY + /// + [DbFunction("Npgsql", "operator_tsquery_and")] + public static string QueryAnd(string tsquery1, string tsquery2) + { + throw new NotSupportedException(); + } + + /// + /// OR tsquerys together. Generates the "||" operator. + /// http://www.postgresql.org/docs/current/static/textsearch-features.html#TEXTSEARCH-MANIPULATE-TSQUERY + /// + [DbFunction("Npgsql", "operator_tsquery_or")] + public static string QueryOr(string tsquery1, string tsquery2) + { + throw new NotSupportedException(); + } + + /// + /// Negate a tsquery. Generates the "!!" operator. + /// http://www.postgresql.org/docs/current/static/textsearch-features.html#TEXTSEARCH-MANIPULATE-TSQUERY + /// + [DbFunction("Npgsql", "operator_tsquery_negate")] + public static string QueryNot(string tsquery) + { + throw new NotSupportedException(); + } + + /// + /// Returns whether contains . + /// Generates the "@>" operator. + /// http://www.postgresql.org/docs/current/static/functions-textsearch.html + /// + [DbFunction("Npgsql", "operator_tsquery_contains")] + public static bool QueryContains(string tsquery1, string tsquery2) + { + throw new NotSupportedException(); + } + + /// + /// Returns whether is contained within . + /// Generates the "<@" operator. + /// http://www.postgresql.org/docs/current/static/functions-textsearch.html + /// + [DbFunction("Npgsql", "operator_tsquery_is_contained")] + public static bool QueryIsContained(string tsquery1, string tsquery2) + { + throw new NotSupportedException(); + } + + /// + /// This method generates the "@@" match operator. + /// http://www.postgresql.org/docs/current/static/textsearch-intro.html#TEXTSEARCH-MATCHING + /// + [DbFunction("Npgsql", "@@")] + public static bool Match(string tsvector, string tsquery) + { + throw new NotSupportedException(); + } + + /// + /// Assign weight to each element of and return a new + /// weighted tsvector. + /// http://www.postgresql.org/docs/current/static/textsearch-features.html#TEXTSEARCH-MANIPULATE-TSVECTOR + /// + [DbFunction("Npgsql", "setweight")] + public static string SetWeight(string tsvector, NpgsqlWeightLabel label) + { + throw new NotSupportedException(); + } + + /// + /// Returns the number of lexemes in . + /// http://www.postgresql.org/docs/current/static/textsearch-features.html#TEXTSEARCH-MANIPULATE-TSVECTOR + /// + [DbFunction("Npgsql", "length")] + public static int Length(string tsvector) + { + throw new NotSupportedException(); + } + + /// + /// Returns the number of lexemes plus operators in . + /// http://www.postgresql.org/docs/current/static/textsearch-features.html#TEXTSEARCH-MANIPULATE-TSQUERY + /// + [DbFunction("Npgsql", "numnode")] + public static int NumNode(string tsquery) + { + throw new NotSupportedException(); + } + + /// + /// Removes weights and positions from and returns + /// a new stripped tsvector. + /// http://www.postgresql.org/docs/current/static/textsearch-features.html#TEXTSEARCH-MANIPULATE-TSVECTOR + /// + [DbFunction("Npgsql", "strip")] + public static string Strip(string tsvector) + { + throw new NotSupportedException(); + } + + /// + /// Get indexable part of . + /// http://www.postgresql.org/docs/current/static/textsearch-features.html#TEXTSEARCH-MANIPULATE-TSQUERY + /// + [DbFunction("Npgsql", "querytree")] + public static string QueryTree(string query) + { + throw new NotSupportedException(); + } + + /// + /// Returns a string suitable for display containing a query match. + /// http://www.postgresql.org/docs/current/static/textsearch-controls.html#TEXTSEARCH-HEADLINE + /// + [DbFunction("Npgsql", "ts_headline")] + public static string TsHeadline(string document, string tsquery, string options) + { + throw new NotSupportedException(); + } + + /// + /// Returns a string suitable for display containing a query match using the text + /// search configuration specified by . + /// http://www.postgresql.org/docs/current/static/textsearch-controls.html#TEXTSEARCH-HEADLINE + /// + [DbFunction("Npgsql", "ts_headline")] + public static string TsHeadline(string config, string document, string tsquery, string options) + { + throw new NotSupportedException(); + } + + /// + /// Calculates the rank of for . + /// http://www.postgresql.org/docs/current/static/textsearch-controls.html#TEXTSEARCH-RANKING + /// + [DbFunction("Npgsql", "ts_rank")] + public static float TsRank( + string vector, + string query) + { + throw new NotSupportedException(); + } + + /// + /// Calculates the rank of for while normalizing + /// the result according to the behaviors specified by . + /// http://www.postgresql.org/docs/current/static/textsearch-controls.html#TEXTSEARCH-RANKING + /// + [DbFunction("Npgsql", "ts_rank")] + public static float TsRank( + string vector, + string query, + NpgsqlRankingNormalization normalization) + { + throw new NotSupportedException(); + } + + /// + /// Calculates the rank of for with custom + /// weighting for word instances depending on their labels (D, C, B or A). + /// http://www.postgresql.org/docs/current/static/textsearch-controls.html#TEXTSEARCH-RANKING + /// + [DbFunction("Npgsql", "ts_rank")] + public static float TsRank( + float weightD, + float weightC, + float weightB, + float weightA, + string vector, + string query) + { + throw new NotSupportedException(); + } + + /// + /// Calculates the rank of for while normalizing + /// the result according to the behaviors specified by + /// and using custom weighting for word instances depending on their labels (D, C, B or A). + /// http://www.postgresql.org/docs/current/static/textsearch-controls.html#TEXTSEARCH-RANKING + /// + [DbFunction("Npgsql", "ts_rank")] + public static float TsRank( + float weightD, + float weightC, + float weightB, + float weightA, + string vector, + string query, + NpgsqlRankingNormalization normalization) + { + throw new NotSupportedException(); + } + + /// + /// Calculates the rank of for using the cover + /// density method. + /// http://www.postgresql.org/docs/current/static/textsearch-controls.html#TEXTSEARCH-RANKING + /// + [DbFunction("Npgsql", "ts_rank_cd")] + public static float TsRankCd( + string vector, + string query) + { + throw new NotSupportedException(); + } + + /// + /// Calculates the rank of for using the cover + /// density method while normalizing the result according to the behaviors specified by + /// . + /// http://www.postgresql.org/docs/current/static/textsearch-controls.html#TEXTSEARCH-RANKING + /// + [DbFunction("Npgsql", "ts_rank_cd")] + public static float TsRankCd( + string vector, + string query, + NpgsqlRankingNormalization normalization) + { + throw new NotSupportedException(); + } + + /// + /// Calculates the rank of for using the cover + /// density method with custom weighting for word instances depending on their labels (D, C, B or A). + /// http://www.postgresql.org/docs/current/static/textsearch-controls.html#TEXTSEARCH-RANKING + /// + [DbFunction("Npgsql", "ts_rank_cd")] + public static float TsRankCd( + float weightD, + float weightC, + float weightB, + float weightA, + string vector, + string query) + { + throw new NotSupportedException(); + } + + /// + /// Calculates the rank of for using the cover density + /// method while normalizing the result according to the behaviors specified by + /// and using custom weighting for word instances depending on their labels (D, C, B or A). + /// http://www.postgresql.org/docs/current/static/textsearch-controls.html#TEXTSEARCH-RANKING + /// + [DbFunction("Npgsql", "ts_rank_cd")] + public static float TsRankCd( + float weightD, + float weightC, + float weightB, + float weightA, + string vector, + string query, + NpgsqlRankingNormalization normalization) + { + throw new NotSupportedException(); + } + + /// + /// Searchs for occurrences of , and replaces + /// each occurrence with a . All parameters are of type tsquery. + /// http://www.postgresql.org/docs/current/static/textsearch-features.html#TEXTSEARCH-MANIPULATE-TSQUERY + /// + [DbFunction("Npgsql", "ts_rewrite")] + public static string TsRewrite(string query, string target, string substitute) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlWeightLabel.cs b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlWeightLabel.cs new file mode 100644 index 0000000..ee2e18c --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/NpgsqlWeightLabel.cs @@ -0,0 +1,28 @@ +namespace Npgsql +{ + /// + /// Label given to positions in vectors. + /// + public enum NpgsqlWeightLabel + { + /// + /// D (Default). + /// + D = 0, + + /// + /// C + /// + C = 1, + + /// + /// B + /// + B = 2, + + /// + /// A + /// + A = 3 + } +} \ No newline at end of file diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/Properties/AssemblyInfo.cs b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..aeb1aee --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System; +using System.Runtime.CompilerServices; +using System.Security; +using System.Reflection; +using System.Resources; + +// Additional assembly attributes are defined in GlobalAssemblyInfo.cs + +#if ENTITIES6 +[assembly: AssemblyTitleAttribute("EntityFramework6.Npgsql")] +[assembly: AssemblyDescriptionAttribute("PostgreSQL provider for Entity Framework 6")] +#else +[assembly: AssemblyTitleAttribute("EntityFramework5.Npgsql")] +[assembly: AssemblyDescriptionAttribute("PostgreSQL provider for Entity Framework 5")] +#endif diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/PublisherPolicy.config b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/PublisherPolicy.config new file mode 100644 index 0000000..bbfd05c --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/PublisherPolicy.config @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/PublisherPolicyLegacy.config b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/PublisherPolicyLegacy.config new file mode 100644 index 0000000..db8b53f --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/PublisherPolicyLegacy.config @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/SqlGenerators/SqlBaseGenerator.cs b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/SqlGenerators/SqlBaseGenerator.cs new file mode 100644 index 0000000..025cee1 --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/SqlGenerators/SqlBaseGenerator.cs @@ -0,0 +1,1430 @@ +#region License +// The PostgreSQL License +// +// Copyright (C) 2015 The Npgsql Development Team +// +// Permission to use, copy, modify, and distribute this software and its +// documentation for any purpose, without fee, and without a written +// agreement is hereby granted, provided that the above copyright notice +// and this paragraph and the following two paragraphs appear in all copies. +// +// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY +// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS +// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// +// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS +// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +#endregion + +using System; +using System.Collections.Generic; +using System.Data.Common; +#if ENTITIES6 +using System.Globalization; +using System.Data.Entity.Core.Common.CommandTrees; +using System.Data.Entity.Core.Metadata.Edm; +#else +using System.Data.Common.CommandTrees; +using System.Data.Metadata.Edm; +#endif +using System.Linq; +using NpgsqlTypes; + +namespace Npgsql.SqlGenerators +{ + internal abstract class SqlBaseGenerator : DbExpressionVisitor + { + internal NpgsqlCommand _command; + internal bool _createParametersForConstants; + private Version _version; + internal Version Version { get { return _version; } set { _version = value; _useNewPrecedences = value >= new Version(9, 5); } } + private bool _useNewPrecedences; + + protected Dictionary _refToNode = new Dictionary(); + protected HashSet _currentExpressions = new HashSet(); + protected uint _aliasCounter = 0; + protected uint _parameterCount = 0; + + private static Dictionary AggregateFunctionNames = new Dictionary() + { + {"Avg","avg"}, + {"Count","count"}, + {"Min","min"}, + {"Max","max"}, + {"Sum","sum"}, + {"BigCount","count"}, + {"StDev","stddev_samp"}, + {"StDevP","stddev_pop"}, + {"Var","var_samp"}, + {"VarP","var_pop"}, + }; + +#if ENTITIES6 + private static readonly Dictionary BinaryOperatorFunctionNames = new Dictionary() + { + {"@@",Operator.QueryMatch}, + {"operator_tsquery_and",Operator.QueryAnd}, + {"operator_tsquery_or",Operator.QueryOr}, + {"operator_tsquery_contains",Operator.QueryContains}, + {"operator_tsquery_is_contained",Operator.QueryIsContained} + }; +#endif + + protected SqlBaseGenerator() + { + } + + private void EnterExpression(PendingProjectsNode n) + { + _currentExpressions.Add(n.Last.Exp); + } + private void LeaveExpression(PendingProjectsNode n) + { + _currentExpressions.Remove(n.Last.Exp); + } + + protected string NextAlias() + { + return "Alias" + _aliasCounter++; + } + + private bool IsCompatible(InputExpression child, DbExpressionKind parentKind) + { + switch (parentKind) + { + case DbExpressionKind.Filter: + return + child.Projection == null && + child.GroupBy == null && + child.Skip == null && + child.Limit == null; + case DbExpressionKind.GroupBy: + return + child.Projection == null && + child.GroupBy == null && + child.Distinct == false && + child.OrderBy == null && + child.Skip == null && + child.Limit == null; + case DbExpressionKind.Distinct: + return + child.OrderBy == null && + child.Skip == null && + child.Limit == null; + case DbExpressionKind.Sort: + return + child.Projection == null && + child.GroupBy == null && + child.Skip == null && + child.Limit == null; + case DbExpressionKind.Skip: + return + child.Projection == null && + child.Skip == null && + child.Limit == null; + case DbExpressionKind.Project: + return + child.Projection == null && + child.Distinct == false; + // Limit and NewInstance are always true + default: + throw new ArgumentException("Unexpected parent expression kind"); + } + } + + private PendingProjectsNode GetInput(DbExpression expression, string childBindingName, string parentBindingName, DbExpressionKind parentKind) + { + PendingProjectsNode n = VisitInputWithBinding(expression, childBindingName); + if (!IsCompatible(n.Last.Exp, parentKind)) + { + n.Selects.Add(new NameAndInputExpression(parentBindingName, new InputExpression(n.Last.Exp, n.Last.AsName))); + } + return n; + } + + private PendingProjectsNode VisitInputWithBinding(DbExpression expression, string bindingName) + { + PendingProjectsNode n; + switch (expression.ExpressionKind) + { + case DbExpressionKind.Scan: + { + ScanExpression scan = (ScanExpression)expression.Accept(this); + InputExpression input = new InputExpression(scan, bindingName); + n = new PendingProjectsNode(bindingName, input); + + break; + } + case DbExpressionKind.Filter: + { + DbFilterExpression exp = (DbFilterExpression)expression; + n = GetInput(exp.Input.Expression, exp.Input.VariableName, bindingName, expression.ExpressionKind); + EnterExpression(n); + VisitedExpression pred = exp.Predicate.Accept(this); + if (n.Last.Exp.Where == null) + n.Last.Exp.Where = new WhereExpression(pred); + else + n.Last.Exp.Where.And(pred); + LeaveExpression(n); + + break; + } + case DbExpressionKind.Sort: + { + DbSortExpression exp = (DbSortExpression)expression; + n = GetInput(exp.Input.Expression, exp.Input.VariableName, bindingName, expression.ExpressionKind); + EnterExpression(n); + n.Last.Exp.OrderBy = new OrderByExpression(); + foreach (var order in exp.SortOrder) + { + n.Last.Exp.OrderBy.AppendSort(order.Expression.Accept(this), order.Ascending); + } + LeaveExpression(n); + + break; + } + case DbExpressionKind.Skip: + { + DbSkipExpression exp = (DbSkipExpression)expression; + n = GetInput(exp.Input.Expression, exp.Input.VariableName, bindingName, expression.ExpressionKind); + EnterExpression(n); + n.Last.Exp.OrderBy = new OrderByExpression(); + foreach (var order in exp.SortOrder) + { + n.Last.Exp.OrderBy.AppendSort(order.Expression.Accept(this), order.Ascending); + } + n.Last.Exp.Skip = new SkipExpression(exp.Count.Accept(this)); + LeaveExpression(n); + break; + } + case DbExpressionKind.Distinct: + { + DbDistinctExpression exp = (DbDistinctExpression)expression; + string childBindingName = NextAlias(); + + n = VisitInputWithBinding(exp.Argument, childBindingName); + if (!IsCompatible(n.Last.Exp, expression.ExpressionKind)) + { + InputExpression prev = n.Last.Exp; + string prevName = n.Last.AsName; + InputExpression input = new InputExpression(prev, prevName); + n.Selects.Add(new NameAndInputExpression(bindingName, input)); + + // We need to copy all the projected columns so the DISTINCT keyword will work on the correct columns + // A parent project expression is never compatible with this new expression, + // so these are the columns that finally will be projected, as wanted + foreach (ColumnExpression col in prev.Projection.Arguments) + { + input.ColumnsToProject.Add(new StringPair(prevName, col.Name), col.Name); + input.ProjectNewNames.Add(col.Name); + } + } + n.Last.Exp.Distinct = true; + break; + } + case DbExpressionKind.Limit: + { + DbLimitExpression exp = (DbLimitExpression)expression; + n = VisitInputWithBinding(exp.Argument, NextAlias()); + if (n.Last.Exp.Limit != null) + { + FunctionExpression least = new FunctionExpression("LEAST"); + least.AddArgument(n.Last.Exp.Limit.Arg); + least.AddArgument(exp.Limit.Accept(this)); + n.Last.Exp.Limit.Arg = least; + } + else + { + n.Last.Exp.Limit = new LimitExpression(exp.Limit.Accept(this)); + } + break; + } + case DbExpressionKind.NewInstance: + { + DbNewInstanceExpression exp = (DbNewInstanceExpression)expression; + if (exp.Arguments.Count == 1 && exp.Arguments[0].ExpressionKind == DbExpressionKind.Element) + { + n = VisitInputWithBinding(((DbElementExpression)exp.Arguments[0]).Argument, NextAlias()); + if (n.Last.Exp.Limit != null) + { + FunctionExpression least = new FunctionExpression("LEAST"); + least.AddArgument(n.Last.Exp.Limit.Arg); + least.AddArgument(new LiteralExpression("1")); + n.Last.Exp.Limit.Arg = least; + } + else + { + n.Last.Exp.Limit = new LimitExpression(new LiteralExpression("1")); + } + } + else if (exp.Arguments.Count >= 1) + { + LiteralExpression result = new LiteralExpression("("); + for (int i = 0; i < exp.Arguments.Count; ++i) + { + DbExpression arg = exp.Arguments[i]; + var visitedColumn = arg.Accept(this); + if (!(visitedColumn is ColumnExpression)) + visitedColumn = new ColumnExpression(visitedColumn, "C", arg.ResultType); + + result.Append(i == 0 ? "SELECT " : " UNION ALL SELECT "); + result.Append(visitedColumn); + } + result.Append(")"); + n = new PendingProjectsNode(bindingName, new InputExpression(result, bindingName)); + } + else + { + TypeUsage type = ((CollectionType)exp.ResultType.EdmType).TypeUsage; + LiteralExpression result = new LiteralExpression("(SELECT "); + result.Append(new CastExpression(new LiteralExpression("NULL"), GetDbType(type.EdmType))); + result.Append(" LIMIT 0)"); + n = new PendingProjectsNode(bindingName, new InputExpression(result, bindingName)); + } + break; + } + case DbExpressionKind.UnionAll: + case DbExpressionKind.Intersect: + case DbExpressionKind.Except: + { + DbBinaryExpression exp = (DbBinaryExpression)expression; + DbExpressionKind expKind = exp.ExpressionKind; + List list = new List(); + Action func = null; + func = e => + { + if (e.ExpressionKind == expKind && e.ExpressionKind != DbExpressionKind.Except) + { + DbBinaryExpression binaryExp = (DbBinaryExpression)e; + func(binaryExp.Left); + func(binaryExp.Right); + } + else + { + list.Add(VisitInputWithBinding(e, bindingName + "_" + list.Count).Last.Exp); + } + }; + func(exp.Left); + func(exp.Right); + InputExpression input = new InputExpression(new CombinedProjectionExpression(expression.ExpressionKind, list), bindingName); + n = new PendingProjectsNode(bindingName, input); + break; + } + case DbExpressionKind.Project: + { + DbProjectExpression exp = (DbProjectExpression)expression; + PendingProjectsNode child = VisitInputWithBinding(exp.Input.Expression, exp.Input.VariableName); + InputExpression input = child.Last.Exp; + bool enterScope = false; + if (!IsCompatible(input, expression.ExpressionKind)) + { + input = new InputExpression(input, child.Last.AsName); + } + else enterScope = true; + + if (enterScope) EnterExpression(child); + + input.Projection = new CommaSeparatedExpression(); + + DbNewInstanceExpression projection = (DbNewInstanceExpression)exp.Projection; + RowType rowType = projection.ResultType.EdmType as RowType; + for (int i = 0; i < rowType.Properties.Count && i < projection.Arguments.Count; ++i) + { + EdmProperty prop = rowType.Properties[i]; + input.Projection.Arguments.Add(new ColumnExpression(projection.Arguments[i].Accept(this), prop.Name, prop.TypeUsage)); + } + + if (enterScope) LeaveExpression(child); + + n = new PendingProjectsNode(bindingName, input); + break; + } + case DbExpressionKind.GroupBy: + { + DbGroupByExpression exp = (DbGroupByExpression)expression; + PendingProjectsNode child = VisitInputWithBinding(exp.Input.Expression, exp.Input.VariableName); + + // I don't know why the input for GroupBy in EF have two names + _refToNode[exp.Input.GroupVariableName] = child; + + InputExpression input = child.Last.Exp; + bool enterScope = false; + if (!IsCompatible(input, expression.ExpressionKind)) + { + input = new InputExpression(input, child.Last.AsName); + } + else enterScope = true; + + if (enterScope) EnterExpression(child); + + input.Projection = new CommaSeparatedExpression(); + + input.GroupBy = new GroupByExpression(); + RowType rowType = ((CollectionType)(exp.ResultType.EdmType)).TypeUsage.EdmType as RowType; + int columnIndex = 0; + foreach (var key in exp.Keys) + { + VisitedExpression keyColumnExpression = key.Accept(this); + var prop = rowType.Properties[columnIndex]; + input.Projection.Arguments.Add(new ColumnExpression(keyColumnExpression, prop.Name, prop.TypeUsage)); + // have no idea why EF is generating a group by with a constant expression, + // but postgresql doesn't need it. + if (!(key is DbConstantExpression)) + { + input.GroupBy.AppendGroupingKey(keyColumnExpression); + } + ++columnIndex; + } + foreach (var ag in exp.Aggregates) + { + DbFunctionAggregate function = (DbFunctionAggregate)ag; + VisitedExpression functionExpression = VisitFunction(function); + var prop = rowType.Properties[columnIndex]; + input.Projection.Arguments.Add(new ColumnExpression(functionExpression, prop.Name, prop.TypeUsage)); + ++columnIndex; + } + + if (enterScope) LeaveExpression(child); + + n = new PendingProjectsNode(bindingName, input); + break; + } + case DbExpressionKind.CrossJoin: + case DbExpressionKind.FullOuterJoin: + case DbExpressionKind.InnerJoin: + case DbExpressionKind.LeftOuterJoin: + case DbExpressionKind.CrossApply: + case DbExpressionKind.OuterApply: + { + InputExpression input = new InputExpression(); + n = new PendingProjectsNode(bindingName, input); + + JoinExpression from = VisitJoinChildren(expression, input, n); + + input.From = from; + + break; + } + default: throw new NotImplementedException(); + } + _refToNode[bindingName] = n; + return n; + } + + private bool IsJoin(DbExpressionKind kind) + { + switch (kind) + { + case DbExpressionKind.CrossJoin: + case DbExpressionKind.FullOuterJoin: + case DbExpressionKind.InnerJoin: + case DbExpressionKind.LeftOuterJoin: + case DbExpressionKind.CrossApply: + case DbExpressionKind.OuterApply: + return true; + } + return false; + } + + private JoinExpression VisitJoinChildren(DbExpression expression, InputExpression input, PendingProjectsNode n) + { + DbExpressionBinding left, right; + DbExpression condition = null; + if (expression.ExpressionKind == DbExpressionKind.CrossJoin) + { + left = ((DbCrossJoinExpression)expression).Inputs[0]; + right = ((DbCrossJoinExpression)expression).Inputs[1]; + if (((DbCrossJoinExpression)expression).Inputs.Count > 2) + { + // I have never seen more than 2 inputs in CrossJoin + throw new NotImplementedException(); + } + } + else if (expression.ExpressionKind == DbExpressionKind.CrossApply || expression.ExpressionKind == DbExpressionKind.OuterApply) + { + left = ((DbApplyExpression)expression).Input; + right = ((DbApplyExpression)expression).Apply; + } + else + { + left = ((DbJoinExpression)expression).Left; + right = ((DbJoinExpression)expression).Right; + condition = ((DbJoinExpression)expression).JoinCondition; + } + + return VisitJoinChildren(left.Expression, left.VariableName, right.Expression, right.VariableName, expression.ExpressionKind, condition, input, n); + } + private JoinExpression VisitJoinChildren(DbExpression left, string leftName, DbExpression right, string rightName, DbExpressionKind joinType, DbExpression condition, InputExpression input, PendingProjectsNode n) + { + JoinExpression join = new JoinExpression(); + join.JoinType = joinType; + + if (IsJoin(left.ExpressionKind)) + { + join.Left = VisitJoinChildren(left, input, n); + } + else + { + PendingProjectsNode l = VisitInputWithBinding(left, leftName); + l.JoinParent = n; + join.Left = new FromExpression(l.Last.Exp, l.Last.AsName); + } + + if (joinType == DbExpressionKind.OuterApply || joinType == DbExpressionKind.CrossApply) + { + EnterExpression(n); + PendingProjectsNode r = VisitInputWithBinding(right, rightName); + LeaveExpression(n); + r.JoinParent = n; + join.Right = new FromExpression(r.Last.Exp, r.Last.AsName) { ForceSubquery = true }; + } + else + { + if (IsJoin(right.ExpressionKind)) + { + join.Right = VisitJoinChildren(right, input, n); + } + else + { + PendingProjectsNode r = VisitInputWithBinding(right, rightName); + r.JoinParent = n; + join.Right = new FromExpression(r.Last.Exp, r.Last.AsName); + } + } + + if (condition != null) + { + EnterExpression(n); + join.Condition = condition.Accept(this); + LeaveExpression(n); + } + return join; + } + + public override VisitedExpression Visit(DbVariableReferenceExpression expression) + { + //return new VariableReferenceExpression(expression.VariableName, _variableSubstitution); + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbUnionAllExpression expression) + { + // Handled by VisitInputWithBinding + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbTreatExpression expression) + { + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbSkipExpression expression) + { + // Handled by VisitInputWithBinding + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbSortExpression expression) + { + // Handled by VisitInputWithBinding + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbScanExpression expression) + { + MetadataProperty metadata; + string tableName; + string overrideTable = "http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator:Name"; + if (expression.Target.MetadataProperties.TryGetValue(overrideTable, false, out metadata) && metadata.Value != null) + { + tableName = metadata.Value.ToString(); + } + else if (expression.Target.MetadataProperties.TryGetValue("Table", false, out metadata) && metadata.Value != null) + { + tableName = metadata.Value.ToString(); + } + else + { + tableName = expression.Target.Name; + } + + if (expression.Target.MetadataProperties.Contains("DefiningQuery")) + { + MetadataProperty definingQuery = expression.Target.MetadataProperties.GetValue("DefiningQuery", false); + if (definingQuery.Value != null) + { + return new ScanExpression("(" + definingQuery.Value + ")", expression.Target); + } + } + + ScanExpression scan; + string overrideSchema = "http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator:Schema"; + if (expression.Target.MetadataProperties.TryGetValue(overrideSchema, false, out metadata) && metadata.Value != null) + { + scan = new ScanExpression(QuoteIdentifier(metadata.Value.ToString()) + "." + QuoteIdentifier(tableName), expression.Target); + } + else if (expression.Target.MetadataProperties.TryGetValue("Schema", false, out metadata) && metadata.Value != null) + { + scan = new ScanExpression(QuoteIdentifier(metadata.Value.ToString()) + "." + QuoteIdentifier(tableName), expression.Target); + } + else + { + scan = new ScanExpression(QuoteIdentifier(expression.Target.EntityContainer.Name) + "." + QuoteIdentifier(tableName), expression.Target); + } + + return scan; + } + + public override VisitedExpression Visit(DbRelationshipNavigationExpression expression) + { + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbRefExpression expression) + { + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbQuantifierExpression expression) + { + // TODO: EXISTS or NOT EXISTS depending on expression.ExpressionKind + // comes with it's built in test (subselect for EXISTS) + // This kind of expression is never even created in the EF6 code base + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbProjectExpression expression) + { + return VisitInputWithBinding(expression, NextAlias()).Last.Exp; + } + + public override VisitedExpression Visit(DbParameterReferenceExpression expression) + { + // use parameter in sql + return new LiteralExpression("@" + expression.ParameterName); + } + + public override VisitedExpression Visit(DbOrExpression expression) + { + return OperatorExpression.Build(Operator.Or, _useNewPrecedences, expression.Left.Accept(this), expression.Right.Accept(this)); + } + + public override VisitedExpression Visit(DbOfTypeExpression expression) + { + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbNullExpression expression) + { + // select does something different here. But insert, update, delete, and functions can just use + // a NULL literal. + return new LiteralExpression("NULL"); + } + + public override VisitedExpression Visit(DbNotExpression expression) + { + // argument can be a "NOT EXISTS" or similar operator that can be negated. + // Convert the not if that's the case + VisitedExpression argument = expression.Argument.Accept(this); + return OperatorExpression.Negate(argument, _useNewPrecedences); + } + + public override VisitedExpression Visit(DbNewInstanceExpression expression) + { + // Handled by VisitInputWithBinding + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbLimitExpression expression) + { + // Normally handled by VisitInputWithBinding + + // Otherwise, it is (probably) a child of a DbElementExpression, + // in which case the child of this expression might be a DbProjectExpression, + // then the correct columns will be projected since Limit is compatible with the result of a DbProjectExpression, + // which will result in having a Projection on the node after visiting it. + PendingProjectsNode node = VisitInputWithBinding(expression, NextAlias()); + if (node.Last.Exp.Projection == null) + { + // This DbLimitExpression is (probably) a child of DbElementExpression + // and this expression's child is not a DbProjectExpression, but we should + // find a DbProjectExpression if we look deeper in the command tree. + // The child of this expression is (probably) a DbSortExpression or something else + // that will (probably) be an ancestor to a DbProjectExpression. + + // Since this is (probably) a child of DbElementExpression, we want the first column, + // so make sure it is propagated from the nearest explicit projection. + + CommaSeparatedExpression projection = node.Selects[0].Exp.Projection; + for (int i = 1; i < node.Selects.Count; i++) + { + ColumnExpression column = (ColumnExpression)projection.Arguments[0]; + + node.Selects[i].Exp.ColumnsToProject[new StringPair(node.Selects[i - 1].AsName, column.Name)] = column.Name; + } + } + return node.Last.Exp; + } + + public override VisitedExpression Visit(DbLikeExpression expression) + { + // LIKE keyword + return OperatorExpression.Build(Operator.Like, _useNewPrecedences, expression.Argument.Accept(this), expression.Pattern.Accept(this)); + } + + public override VisitedExpression Visit(DbJoinExpression expression) + { + // Handled by VisitInputWithBinding + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbIsOfExpression expression) + { + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbIsNullExpression expression) + { + return OperatorExpression.Build(Operator.IsNull, _useNewPrecedences, expression.Argument.Accept(this)); + } + + public override VisitedExpression Visit(DbIsEmptyExpression expression) + { + // NOT EXISTS + return OperatorExpression.Negate(new ExistsExpression(expression.Argument.Accept(this)), _useNewPrecedences); + } + + public override VisitedExpression Visit(DbIntersectExpression expression) + { + // INTERSECT keyword + // Handled by VisitInputWithBinding + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbGroupByExpression expression) + { + // Normally handled by VisitInputWithBinding + + // Otherwise, it is (probably) a child of a DbElementExpression. + // Group by always projects the correct columns. + return VisitInputWithBinding(expression, NextAlias()).Last.Exp; + } + + public override VisitedExpression Visit(DbRefKeyExpression expression) + { + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbEntityRefExpression expression) + { + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbFunctionExpression expression) + { + // a function call + // may be built in, canonical, or user defined + return VisitFunction(expression.Function, expression.Arguments, expression.ResultType); + } + + public override VisitedExpression Visit(DbFilterExpression expression) + { + // Handled by VisitInputWithBinding + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbExceptExpression expression) + { + // EXCEPT keyword + // Handled by VisitInputWithBinding + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbElementExpression expression) + { + // If child of DbNewInstanceExpression, this is handled in VisitInputWithBinding + + // a scalar expression (ie ExecuteScalar) + // so it will likely be translated into a select + //throw new NotImplementedException(); + LiteralExpression scalar = new LiteralExpression("("); + scalar.Append(expression.Argument.Accept(this)); + scalar.Append(")"); + return scalar; + } + + public override VisitedExpression Visit(DbDistinctExpression expression) + { + // Handled by VisitInputWithBinding + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbDerefExpression expression) + { + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbCrossJoinExpression expression) + { + // join without ON + // Handled by VisitInputWithBinding + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbConstantExpression expression) + { + if (_createParametersForConstants) + { + NpgsqlParameter parameter = new NpgsqlParameter(); + parameter.ParameterName = "p_" + _parameterCount++; + parameter.NpgsqlDbType = NpgsqlProviderManifest.GetNpgsqlDbType(((PrimitiveType)expression.ResultType.EdmType).PrimitiveTypeKind); + parameter.Value = expression.Value; + _command.Parameters.Add(parameter); + return new LiteralExpression("@" + parameter.ParameterName); + } + else + { + return new ConstantExpression(expression.Value, expression.ResultType); + } + } + + public override VisitedExpression Visit(DbComparisonExpression expression) + { + Operator comparisonOperator; + switch (expression.ExpressionKind) + { + case DbExpressionKind.Equals: comparisonOperator = Operator.Equals; break; + case DbExpressionKind.GreaterThan: comparisonOperator = Operator.GreaterThan; break; + case DbExpressionKind.GreaterThanOrEquals: comparisonOperator = Operator.GreaterThanOrEquals; break; + case DbExpressionKind.LessThan: comparisonOperator = Operator.LessThan; break; + case DbExpressionKind.LessThanOrEquals: comparisonOperator = Operator.LessThanOrEquals; break; + case DbExpressionKind.Like: comparisonOperator = Operator.Like; break; + case DbExpressionKind.NotEquals: comparisonOperator = Operator.NotEquals; break; + default: throw new NotSupportedException(); + } + return OperatorExpression.Build(comparisonOperator, _useNewPrecedences, expression.Left.Accept(this), expression.Right.Accept(this)); + } + + public override VisitedExpression Visit(DbCastExpression expression) + { + return new CastExpression(expression.Argument.Accept(this), GetDbType(expression.ResultType.EdmType)); + } + + protected string GetDbType(EdmType edmType) + { + PrimitiveType primitiveType = edmType as PrimitiveType; + if (primitiveType == null) + throw new NotSupportedException(); + switch (primitiveType.PrimitiveTypeKind) + { + case PrimitiveTypeKind.Boolean: + return "bool"; + case PrimitiveTypeKind.SByte: + case PrimitiveTypeKind.Byte: + case PrimitiveTypeKind.Int16: + return "int2"; + case PrimitiveTypeKind.Int32: + return "int4"; + case PrimitiveTypeKind.Int64: + return "int8"; + case PrimitiveTypeKind.String: + return "text"; + case PrimitiveTypeKind.Decimal: + return "numeric"; + case PrimitiveTypeKind.Single: + return "float4"; + case PrimitiveTypeKind.Double: + return "float8"; + case PrimitiveTypeKind.DateTime: + return "timestamp"; + case PrimitiveTypeKind.DateTimeOffset: + return "timestamptz"; + case PrimitiveTypeKind.Time: + return "interval"; + case PrimitiveTypeKind.Binary: + return "bytea"; + case PrimitiveTypeKind.Guid: + return "uuid"; + } + throw new NotSupportedException(); + } + + public override VisitedExpression Visit(DbCaseExpression expression) + { + LiteralExpression caseExpression = new LiteralExpression(" CASE "); + for (int i = 0; i < expression.When.Count && i < expression.Then.Count; ++i) + { + caseExpression.Append(" WHEN ("); + caseExpression.Append(expression.When[i].Accept(this)); + caseExpression.Append(") THEN ("); + caseExpression.Append(expression.Then[i].Accept(this)); + caseExpression.Append(")"); + } + if (expression.Else is DbNullExpression) + { + caseExpression.Append(" END "); + } + else + { + caseExpression.Append(" ELSE ("); + caseExpression.Append(expression.Else.Accept(this)); + caseExpression.Append(") END "); + } + return caseExpression; + } + + public override VisitedExpression Visit(DbArithmeticExpression expression) + { + Operator arithmeticOperator; + + switch (expression.ExpressionKind) + { + case DbExpressionKind.Divide: + arithmeticOperator = Operator.Div; + break; + case DbExpressionKind.Minus: + arithmeticOperator = Operator.Sub; + break; + case DbExpressionKind.Modulo: + arithmeticOperator = Operator.Mod; + break; + case DbExpressionKind.Multiply: + arithmeticOperator = Operator.Mul; + break; + case DbExpressionKind.Plus: + arithmeticOperator = Operator.Add; + break; + case DbExpressionKind.UnaryMinus: + arithmeticOperator = Operator.UnaryMinus; + break; + default: + throw new NotSupportedException(); + } + + if (expression.ExpressionKind == DbExpressionKind.UnaryMinus) + { + System.Diagnostics.Debug.Assert(expression.Arguments.Count == 1); + return OperatorExpression.Build(arithmeticOperator, _useNewPrecedences, expression.Arguments[0].Accept(this)); + } + else + { + System.Diagnostics.Debug.Assert(expression.Arguments.Count == 2); + return OperatorExpression.Build(arithmeticOperator, _useNewPrecedences, expression.Arguments[0].Accept(this), expression.Arguments[1].Accept(this)); + } + } + + public override VisitedExpression Visit(DbApplyExpression expression) + { + // like a join, but used when the right hand side (the Apply part) is a function. + // it lets you return the results of a function call given values from the + // left hand side (the Input part). + // sql standard is lateral join + + // Handled by VisitInputWithBinding + throw new NotImplementedException(); + } + + public override VisitedExpression Visit(DbAndExpression expression) + { + return OperatorExpression.Build(Operator.And, _useNewPrecedences, expression.Left.Accept(this), expression.Right.Accept(this)); + } + + public override VisitedExpression Visit(DbExpression expression) + { + // only concrete types visited + throw new NotSupportedException(); + } + + public abstract void BuildCommand(DbCommand command); + + internal static string QuoteIdentifier(string identifier) + { + return "\"" + identifier.Replace("\"", "\"\"") + "\""; + } + + private VisitedExpression VisitFunction(DbFunctionAggregate functionAggregate) + { + if (functionAggregate.Function.NamespaceName == "Edm") + { + FunctionExpression aggregate; + try + { + aggregate = new FunctionExpression(AggregateFunctionNames[functionAggregate.Function.Name]); + } catch (KeyNotFoundException) + { + throw new NotSupportedException(); + } + System.Diagnostics.Debug.Assert(functionAggregate.Arguments.Count == 1); + VisitedExpression aggregateArg; + if (functionAggregate.Distinct) + { + aggregateArg = new LiteralExpression("DISTINCT "); + ((LiteralExpression)aggregateArg).Append(functionAggregate.Arguments[0].Accept(this)); + } + else + { + aggregateArg = functionAggregate.Arguments[0].Accept(this); + } + aggregate.AddArgument(aggregateArg); + return new CastExpression(aggregate, GetDbType(functionAggregate.ResultType.EdmType)); + } + throw new NotSupportedException(); + } + + private VisitedExpression VisitFunction(EdmFunction function, IList args, TypeUsage resultType) + { + if (function.NamespaceName == "Edm") + { + VisitedExpression arg; + switch (function.Name) + { + // string functions + case "Concat": + System.Diagnostics.Debug.Assert(args.Count == 2); + return OperatorExpression.Build(Operator.Concat, _useNewPrecedences, args[0].Accept(this), args[1].Accept(this)); + case "Contains": + System.Diagnostics.Debug.Assert(args.Count == 2); + FunctionExpression contains = new FunctionExpression("position"); + arg = args[1].Accept(this); + arg.Append(" in "); + arg.Append(args[0].Accept(this)); + contains.AddArgument(arg); + // if position returns zero, then contains is false + return OperatorExpression.Build(Operator.GreaterThan, _useNewPrecedences, contains, new LiteralExpression("0")); + // case "EndsWith": - depends on a reverse function to be able to implement with parameterized queries + case "IndexOf": + System.Diagnostics.Debug.Assert(args.Count == 2); + FunctionExpression indexOf = new FunctionExpression("position"); + arg = args[0].Accept(this); + arg.Append(" in "); + arg.Append(args[1].Accept(this)); + indexOf.AddArgument(arg); + return indexOf; + case "Left": + System.Diagnostics.Debug.Assert(args.Count == 2); + return Substring(args[0].Accept(this), new LiteralExpression(" 1 "), args[1].Accept(this)); + case "Length": + FunctionExpression length = new FunctionExpression("char_length"); + System.Diagnostics.Debug.Assert(args.Count == 1); + length.AddArgument(args[0].Accept(this)); + return new CastExpression(length, GetDbType(resultType.EdmType)); + case "LTrim": + return StringModifier("ltrim", args); + case "Replace": + FunctionExpression replace = new FunctionExpression("replace"); + System.Diagnostics.Debug.Assert(args.Count == 3); + replace.AddArgument(args[0].Accept(this)); + replace.AddArgument(args[1].Accept(this)); + replace.AddArgument(args[2].Accept(this)); + return replace; + // case "Reverse": + case "Right": + System.Diagnostics.Debug.Assert(args.Count == 2); + { + var arg0 = args[0].Accept(this); + var arg1 = args[1].Accept(this); + var start = new FunctionExpression("char_length"); + start.AddArgument(arg0); + // add one before subtracting count since strings are 1 based in postgresql + return Substring(arg0, OperatorExpression.Build(Operator.Sub, _useNewPrecedences, OperatorExpression.Build(Operator.Add, _useNewPrecedences, start, new LiteralExpression("1")), arg1)); + } + case "RTrim": + return StringModifier("rtrim", args); + case "Substring": + System.Diagnostics.Debug.Assert(args.Count == 3); + return Substring(args[0].Accept(this), args[1].Accept(this), args[2].Accept(this)); + case "StartsWith": + System.Diagnostics.Debug.Assert(args.Count == 2); + FunctionExpression startsWith = new FunctionExpression("position"); + arg = args[1].Accept(this); + arg.Append(" in "); + arg.Append(args[0].Accept(this)); + startsWith.AddArgument(arg); + return OperatorExpression.Build(Operator.Equals, _useNewPrecedences, startsWith, new LiteralExpression("1")); + case "ToLower": + return StringModifier("lower", args); + case "ToUpper": + return StringModifier("upper", args); + case "Trim": + return StringModifier("btrim", args); + + // date functions + // date functions + case "AddDays": + case "AddHours": + case "AddMicroseconds": + case "AddMilliseconds": + case "AddMinutes": + case "AddMonths": + case "AddNanoseconds": + case "AddSeconds": + case "AddYears": + return DateAdd(function.Name, args); + case "DiffDays": + case "DiffHours": + case "DiffMicroseconds": + case "DiffMilliseconds": + case "DiffMinutes": + case "DiffMonths": + case "DiffNanoseconds": + case "DiffSeconds": + case "DiffYears": + System.Diagnostics.Debug.Assert(args.Count == 2); + return DateDiff(function.Name, args[0].Accept(this), args[1].Accept(this)); + case "Day": + case "Hour": + case "Minute": + case "Month": + case "Second": + case "Year": + return DatePart(function.Name, args); + case "Millisecond": + return DatePart("milliseconds", args); + case "GetTotalOffsetMinutes": + VisitedExpression timezone = DatePart("timezone", args); + return OperatorExpression.Build(Operator.Div, _useNewPrecedences, timezone, new LiteralExpression("60")); + case "CurrentDateTime": + return new LiteralExpression("LOCALTIMESTAMP"); + case "CurrentUtcDateTime": + LiteralExpression utcNow = new LiteralExpression("CURRENT_TIMESTAMP"); + utcNow.Append(" AT TIME ZONE 'UTC'"); + return utcNow; + case "CurrentDateTimeOffset": + // TODO: this doesn't work yet because the reader + // doesn't return DateTimeOffset. + return new LiteralExpression("CURRENT_TIMESTAMP"); + + // bitwise operators + case "BitwiseAnd": + return BitwiseOperator(args, Operator.BitwiseAnd); + case "BitwiseOr": + return BitwiseOperator(args, Operator.BitwiseOr); + case "BitwiseXor": + return BitwiseOperator(args, Operator.BitwiseXor); + case "BitwiseNot": + System.Diagnostics.Debug.Assert(args.Count == 1); + return OperatorExpression.Build(Operator.BitwiseNot, _useNewPrecedences, args[0].Accept(this)); + + // math operators + case "Abs": + case "Ceiling": + case "Floor": + return UnaryMath(function.Name, args); + case "Round": + return (args.Count == 1) ? UnaryMath(function.Name, args) : BinaryMath(function.Name, args); + case "Power": + return BinaryMath(function.Name, args); + case "Truncate": + return BinaryMath("trunc", args); + + case "NewGuid": + return new FunctionExpression("uuid_generate_v4"); + case "TruncateTime": + return new TruncateTimeExpression("day", args[0].Accept(this)); + + default: + throw new NotSupportedException("NotSupported " + function.Name); + } + } + +#if ENTITIES6 + var functionName = function.StoreFunctionNameAttribute ?? function.Name; + if (function.NamespaceName == "Npgsql") + { + Operator binaryOperator; + if (BinaryOperatorFunctionNames.TryGetValue(functionName, out binaryOperator)) + { + if (args.Count != 2) + throw new ArgumentException(string.Format("Invalid number of {0} arguments. Expected 2.", function.Name), "args"); + + return OperatorExpression.Build( + binaryOperator, + _useNewPrecedences, + args[0].Accept(this), + args[1].Accept(this)); + } + + if (functionName == "operator_tsquery_negate") + { + if (args.Count != 1) + throw new ArgumentException("Invalid number of operator_tsquery_not arguments. Expected 1.", "args"); + + return OperatorExpression.Build(Operator.QueryNegate, _useNewPrecedences, args[0].Accept(this)); + } + + if (functionName == "ts_rank" || functionName == "ts_rank_cd") + { + if (args.Count > 4) + { + var weightD = args[0] as DbConstantExpression; + var weightC = args[1] as DbConstantExpression; + var weightB = args[2] as DbConstantExpression; + var weightA = args[3] as DbConstantExpression; + + if (weightD == null || weightC == null || weightB == null || weightA == null) + throw new NotSupportedException("All weight values must be constant expressions."); + + var newValue = string.Format( + CultureInfo.InvariantCulture, + "{{ {0:r}, {1:r}, {2:r}, {3:r} }}", + weightD.Value, + weightC.Value, + weightB.Value, + weightA.Value); + + args = new[] { DbExpression.FromString(newValue) }.Concat(args.Skip(4)).ToList(); + } + } + else if (functionName == "setweight") + { + if (args.Count != 2) + throw new ArgumentException("Invalid number of setweight arguments. Expected 2.", "args"); + + var weightLabelExpression = args[1] as DbConstantExpression; + if (weightLabelExpression == null) + throw new NotSupportedException("setweight label argument must be a constant expression."); + + var weightLabel = (NpgsqlWeightLabel)weightLabelExpression.Value; + if (!Enum.IsDefined(typeof(NpgsqlWeightLabel), weightLabelExpression.Value)) + throw new NotSupportedException("Unsupported weight label value: " + weightLabel); + + args = new[] { args[0], DbExpression.FromString(weightLabel.ToString()) }; + } + else if (functionName == "as_tsvector") + { + if (args.Count != 1) + throw new ArgumentException("Invalid number of arguments. Expected 1.", "args"); + + return new CastExpression(args[0].Accept(this), "tsvector"); + } + else if (functionName == "as_tsquery") + { + if (args.Count != 1) + throw new ArgumentException("Invalid number of arguments. Expected 1.", "args"); + + return new CastExpression(args[0].Accept(this), "tsquery"); + } + } + + var customFuncCall = new FunctionExpression( + string.IsNullOrEmpty(function.Schema) + ? QuoteIdentifier(functionName) + : QuoteIdentifier(function.Schema) + "." + QuoteIdentifier(functionName) + ); + + foreach (var a in args) + customFuncCall.AddArgument(a.Accept(this)); + return customFuncCall; +#else + throw new NotSupportedException(); +#endif + } + + private VisitedExpression Substring(VisitedExpression source, VisitedExpression start, VisitedExpression count) + { + FunctionExpression substring = new FunctionExpression("substr"); + substring.AddArgument(source); + substring.AddArgument(start); + substring.AddArgument(count); + return substring; + } + + private VisitedExpression Substring(VisitedExpression source, VisitedExpression start) + { + FunctionExpression substring = new FunctionExpression("substr"); + substring.AddArgument(source); + substring.AddArgument(start); + return substring; + } + + private VisitedExpression UnaryMath(string funcName, IList args) + { + FunctionExpression mathFunction = new FunctionExpression(funcName); + System.Diagnostics.Debug.Assert(args.Count == 1); + mathFunction.AddArgument(args[0].Accept(this)); + return mathFunction; + } + + private VisitedExpression BinaryMath(string funcName, IList args) + { + FunctionExpression mathFunction = new FunctionExpression(funcName); + System.Diagnostics.Debug.Assert(args.Count == 2); + mathFunction.AddArgument(args[0].Accept(this)); + mathFunction.AddArgument(args[1].Accept(this)); + return mathFunction; + } + + private VisitedExpression StringModifier(string modifier, IList args) + { + FunctionExpression modifierFunction = new FunctionExpression(modifier); + System.Diagnostics.Debug.Assert(args.Count == 1); + modifierFunction.AddArgument(args[0].Accept(this)); + return modifierFunction; + } + + private VisitedExpression DatePart(string partName, IList args) + { + + FunctionExpression extract_date = new FunctionExpression("cast(extract"); + System.Diagnostics.Debug.Assert(args.Count == 1); + VisitedExpression arg = new LiteralExpression(partName + " FROM "); + arg.Append(args[0].Accept(this)); + extract_date.AddArgument(arg); + // need to convert to Int32 to match cononical function + extract_date.Append(" as int4)"); + return extract_date; + } + + /// + /// PostgreSQL has no direct functions to implements DateTime canonical functions + /// http://msdn.microsoft.com/en-us/library/bb738626.aspx + /// http://msdn.microsoft.com/en-us/library/bb738626.aspx + /// but we can use workaround: + /// expression + number * INTERVAL '1 number_type' + /// where number_type is the number type (days, years and etc) + /// + /// + /// + /// + private VisitedExpression DateAdd(string functionName, IList args) + { + bool nano = false; + string part = functionName.Substring(3); + + if (part == "Nanoseconds") + { + nano = true; + part = "Microseconds"; + } + + System.Diagnostics.Debug.Assert(args.Count == 2); + VisitedExpression time = args[0].Accept(this); + VisitedExpression mulLeft = args[1].Accept(this); + if (nano) + mulLeft = OperatorExpression.Build(Operator.Div, _useNewPrecedences, mulLeft, new LiteralExpression("1000")); + LiteralExpression mulRight = new LiteralExpression(String.Format("INTERVAL '1 {0}'", part)); + return OperatorExpression.Build(Operator.Add, _useNewPrecedences, time, OperatorExpression.Build(Operator.Mul, _useNewPrecedences, mulLeft, mulRight)); + } + + private VisitedExpression DateDiff(string functionName, VisitedExpression start, VisitedExpression end) + { + switch (functionName) + { + case "DiffDays": + start = new FunctionExpression("date_trunc").AddArgument("'day'").AddArgument(start); + end = new FunctionExpression("date_trunc").AddArgument("'day'").AddArgument(end); + return new FunctionExpression("date_part").AddArgument("'day'").AddArgument( + OperatorExpression.Build(Operator.Sub, _useNewPrecedences, end, start) + ).Append("::int4"); + case "DiffHours": + { + start = new FunctionExpression("date_trunc").AddArgument("'hour'").AddArgument(start); + end = new FunctionExpression("date_trunc").AddArgument("'hour'").AddArgument(end); + LiteralExpression epoch = new LiteralExpression("epoch from "); + OperatorExpression diff = OperatorExpression.Build(Operator.Sub, _useNewPrecedences, end, start); + epoch.Append(diff); + return OperatorExpression.Build(Operator.Div, _useNewPrecedences, new FunctionExpression("extract").AddArgument(epoch).Append("::int4"), new LiteralExpression("3600")); + } + case "DiffMicroseconds": + { + start = new FunctionExpression("date_trunc").AddArgument("'microseconds'").AddArgument(start); + end = new FunctionExpression("date_trunc").AddArgument("'microseconds'").AddArgument(end); + LiteralExpression epoch = new LiteralExpression("epoch from "); + OperatorExpression diff = OperatorExpression.Build(Operator.Sub, _useNewPrecedences, end, start); + epoch.Append(diff); + return new CastExpression(OperatorExpression.Build(Operator.Mul, _useNewPrecedences, new FunctionExpression("extract").AddArgument(epoch), new LiteralExpression("1000000")), "int4"); + } + case "DiffMilliseconds": + { + start = new FunctionExpression("date_trunc").AddArgument("'milliseconds'").AddArgument(start); + end = new FunctionExpression("date_trunc").AddArgument("'milliseconds'").AddArgument(end); + LiteralExpression epoch = new LiteralExpression("epoch from "); + OperatorExpression diff = OperatorExpression.Build(Operator.Sub, _useNewPrecedences, end, start); + epoch.Append(diff); + return new CastExpression(OperatorExpression.Build(Operator.Mul, _useNewPrecedences, new FunctionExpression("extract").AddArgument(epoch), new LiteralExpression("1000")), "int4"); + } + case "DiffMinutes": + { + start = new FunctionExpression("date_trunc").AddArgument("'minute'").AddArgument(start); + end = new FunctionExpression("date_trunc").AddArgument("'minute'").AddArgument(end); + LiteralExpression epoch = new LiteralExpression("epoch from "); + OperatorExpression diff = OperatorExpression.Build(Operator.Sub, _useNewPrecedences, end, start); + epoch.Append(diff); + return OperatorExpression.Build(Operator.Div, _useNewPrecedences, new FunctionExpression("extract").AddArgument(epoch).Append("::int4"), new LiteralExpression("60")); + } + case "DiffMonths": + { + start = new FunctionExpression("date_trunc").AddArgument("'month'").AddArgument(start); + end = new FunctionExpression("date_trunc").AddArgument("'month'").AddArgument(end); + VisitedExpression age = new FunctionExpression("age").AddArgument(end).AddArgument(start); + + // A month is 30 days and a year is 365.25 days after conversion from interval to seconds. + // After rounding and casting, the result will contain the correct number of months as an int4. + FunctionExpression seconds = new FunctionExpression("extract").AddArgument(new LiteralExpression("epoch from ").Append(age)); + VisitedExpression months = OperatorExpression.Build(Operator.Div, _useNewPrecedences, seconds, new LiteralExpression("2629800.0")); + return new FunctionExpression("round").AddArgument(months).Append("::int4"); + } + case "DiffNanoseconds": + { + // PostgreSQL only supports microseconds precision, so the value will be a multiple of 1000 + // This date_trunc will make sure start and end are of type timestamp, e.g. if the arguments is of type date + start = new FunctionExpression("date_trunc").AddArgument("'microseconds'").AddArgument(start); + end = new FunctionExpression("date_trunc").AddArgument("'microseconds'").AddArgument(end); + LiteralExpression epoch = new LiteralExpression("epoch from "); + OperatorExpression diff = OperatorExpression.Build(Operator.Sub, _useNewPrecedences, end, start); + epoch.Append(diff); + return new CastExpression(OperatorExpression.Build(Operator.Mul, _useNewPrecedences, new FunctionExpression("extract").AddArgument(epoch), new LiteralExpression("1000000000")), "int4"); + } + case "DiffSeconds": + { + start = new FunctionExpression("date_trunc").AddArgument("'second'").AddArgument(start); + end = new FunctionExpression("date_trunc").AddArgument("'second'").AddArgument(end); + LiteralExpression epoch = new LiteralExpression("epoch from "); + OperatorExpression diff = OperatorExpression.Build(Operator.Sub, _useNewPrecedences, end, start); + epoch.Append(diff); + return new FunctionExpression("extract").AddArgument(epoch).Append("::int4"); + } + case "DiffYears": + { + start = new FunctionExpression("date_trunc").AddArgument("'year'").AddArgument(start); + end = new FunctionExpression("date_trunc").AddArgument("'year'").AddArgument(end); + VisitedExpression age = new FunctionExpression("age").AddArgument(end).AddArgument(start); + return new FunctionExpression("date_part").AddArgument("'year'").AddArgument(age).Append("::int4"); + } + default: throw new NotSupportedException("Internal error: unknown function name " + functionName); + } + } + + private VisitedExpression BitwiseOperator(IList args, Operator oper) + { + System.Diagnostics.Debug.Assert(args.Count == 2); + return OperatorExpression.Build(oper, _useNewPrecedences, args[0].Accept(this), args[1].Accept(this)); + } + +#if ENTITIES6 + public override VisitedExpression Visit(DbInExpression expression) + { + VisitedExpression item = expression.Item.Accept(this); + + ConstantExpression[] elements = new ConstantExpression[expression.List.Count]; + for (int i = 0; i < expression.List.Count; i++) + { + elements[i] = (ConstantExpression)expression.List[i].Accept(this); + } + + return OperatorExpression.Build(Operator.In, _useNewPrecedences, item, new ConstantListExpression(elements)); + } + + public override VisitedExpression Visit(DbPropertyExpression expression) + { + // This is overridden in the other visitors + throw new NotImplementedException("New in Entity Framework 6"); + } +#endif + } +} diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/SqlGenerators/SqlUpdateGenerator.cs b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/SqlGenerators/SqlUpdateGenerator.cs new file mode 100644 index 0000000..8e2d259 --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/SqlGenerators/SqlUpdateGenerator.cs @@ -0,0 +1,75 @@ +#region License +// The PostgreSQL License +// +// Copyright (C) 2015 The Npgsql Development Team +// +// Permission to use, copy, modify, and distribute this software and its +// documentation for any purpose, without fee, and without a written +// agreement is hereby granted, provided that the above copyright notice +// and this paragraph and the following two paragraphs appear in all copies. +// +// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY +// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS +// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// +// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS +// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +#endregion + +using System; +using System.Collections.Generic; +using System.Data.Common; +#if ENTITIES6 +using System.Data.Entity.Core.Common.CommandTrees; +#else +using System.Data.Common.CommandTrees; +#endif + +namespace Npgsql.SqlGenerators +{ + class SqlUpdateGenerator : SqlBaseGenerator + { + private DbUpdateCommandTree _commandTree; + private string _tableName; + + public SqlUpdateGenerator(DbUpdateCommandTree commandTree) + { + _commandTree = commandTree; + } + + public override VisitedExpression Visit(DbPropertyExpression expression) + { + DbVariableReferenceExpression variable = expression.Instance as DbVariableReferenceExpression; + if (variable == null || variable.VariableName != _tableName) + throw new NotSupportedException(); + return new PropertyExpression(expression.Property); + } + + public override void BuildCommand(DbCommand command) + { + // TODO: handle _commandTree.Parameters + UpdateExpression update = new UpdateExpression(); + _tableName = _commandTree.Target.VariableName; + update.AppendTarget(_commandTree.Target.Expression.Accept(this)); + foreach (DbSetClause clause in _commandTree.SetClauses) + { + update.AppendSet(clause.Property.Accept(this), clause.Value.Accept(this)); + } + if (_commandTree.Predicate != null) + { + update.AppendWhere(_commandTree.Predicate.Accept(this)); + } + if (_commandTree.Returning != null) + { + update.AppendReturning((DbNewInstanceExpression)_commandTree.Returning); + } + _tableName = null; + command.CommandText = update.ToString(); + } + } +} diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/SqlGenerators/StringPair.cs b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/SqlGenerators/StringPair.cs new file mode 100644 index 0000000..2be0e0a --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/SqlGenerators/StringPair.cs @@ -0,0 +1,65 @@ +#region License +// The PostgreSQL License +// +// Copyright (C) 2015 The Npgsql Development Team +// +// Permission to use, copy, modify, and distribute this software and its +// documentation for any purpose, without fee, and without a written +// agreement is hereby granted, provided that the above copyright notice +// and this paragraph and the following two paragraphs appear in all copies. +// +// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY +// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS +// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// +// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS +// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Npgsql.SqlGenerators +{ + /// + /// Used for lookup in a Dictionary, since Tuple is not available in .NET 3.5 + /// + internal class StringPair + { + private string _item1; + private string _item2; + + public string Item1 { get { return _item1; } } + public string Item2 { get { return _item2; } } + + public StringPair(string s1, string s2) + { + _item1 = s1; + _item2 = s2; + } + + public override bool Equals(object obj) + { + if (obj == null) + return false; + + StringPair o = obj as StringPair; + if (o == null) + return false; + + return _item1 == o._item1 && _item2 == o._item2; + } + + public override int GetHashCode() + { + return (_item1 + "." + _item2).GetHashCode(); + } + } +} diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/SqlGenerators/VisitedExpression.cs b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/SqlGenerators/VisitedExpression.cs new file mode 100644 index 0000000..81a5887 --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/SqlGenerators/VisitedExpression.cs @@ -0,0 +1,1240 @@ +#region License +// The PostgreSQL License +// +// Copyright (C) 2015 The Npgsql Development Team +// +// Permission to use, copy, modify, and distribute this software and its +// documentation for any purpose, without fee, and without a written +// agreement is hereby granted, provided that the above copyright notice +// and this paragraph and the following two paragraphs appear in all copies. +// +// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY +// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS +// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// +// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS +// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +#if ENTITIES6 +using System.Data.Entity.Core.Common.CommandTrees; +using System.Data.Entity.Core.Metadata.Edm; +#else +using System.Data.Metadata.Edm; +using System.Data.Common.CommandTrees; +#endif +using NpgsqlTypes; +using System.Data; +using System.Globalization; + +namespace Npgsql.SqlGenerators +{ + internal abstract class VisitedExpression + { + protected VisitedExpression() + { + ExpressionList = new List(); + } + + public VisitedExpression Append(VisitedExpression expression) + { + ExpressionList.Add(expression); + return this; + } + + public VisitedExpression Append(string literal) + { + ExpressionList.Add(new LiteralExpression(literal)); + return this; + } + + public override string ToString() + { + StringBuilder sqlText = new StringBuilder(); + WriteSql(sqlText); + return sqlText.ToString(); + } + + protected List ExpressionList { get; private set; } + + internal virtual void WriteSql(StringBuilder sqlText) + { + foreach (VisitedExpression expression in ExpressionList) + { + expression.WriteSql(sqlText); + } + } + } + + internal class LiteralExpression : VisitedExpression + { + private string _literal; + + public LiteralExpression(string literal) + { + _literal = literal; + } + + public new LiteralExpression Append(VisitedExpression expresion) + { + base.Append(expresion); + return this; + } + + public new void Append(string literal) + { + base.Append(literal); + } + + internal override void WriteSql(StringBuilder sqlText) + { + sqlText.Append(_literal); + base.WriteSql(sqlText); + } + } + + internal class CommaSeparatedExpression : VisitedExpression + { + public readonly List Arguments = new List(); + + internal override void WriteSql(StringBuilder sqlText) + { + for (int i = 0; i < Arguments.Count; ++i) + { + if (i != 0) + sqlText.Append(", "); + Arguments[i].WriteSql(sqlText); + } + base.WriteSql(sqlText); + } + } + + internal class ConstantExpression : VisitedExpression + { + private PrimitiveTypeKind _primitiveType; + private object _value; + + public ConstantExpression(object value, TypeUsage edmType) + { + if (edmType == null) + throw new ArgumentNullException("edmType"); + if (edmType.EdmType == null || edmType.EdmType.BuiltInTypeKind != BuiltInTypeKind.PrimitiveType) + throw new ArgumentException("Require primitive EdmType", "edmType"); + _primitiveType = ((PrimitiveType)edmType.EdmType).PrimitiveTypeKind; + _value = value; + } + + internal override void WriteSql(StringBuilder sqlText) + { + var ni = CultureInfo.InvariantCulture.NumberFormat; + object value = _value; + switch (_primitiveType) + { + case PrimitiveTypeKind.Binary: + { + sqlText.AppendFormat("decode('{0}', 'base64')", Convert.ToBase64String((byte[])_value)); + } + break; + case PrimitiveTypeKind.DateTime: + sqlText.AppendFormat(ni, "TIMESTAMP '{0:o}'", _value); + break; + case PrimitiveTypeKind.DateTimeOffset: + sqlText.AppendFormat(ni, "TIMESTAMP WITH TIME ZONE '{0:o}'", _value); + break; + case PrimitiveTypeKind.Decimal: + if ((decimal)_value < 0) + { + sqlText.AppendFormat(ni, "({0})::numeric", _value); + } + else + { + sqlText.AppendFormat(ni, "{0}::numeric", _value); + } + break; + case PrimitiveTypeKind.Double: + if (double.IsNaN((double)_value)) + { + sqlText.AppendFormat("'NaN'::float8"); + } + else if (double.IsPositiveInfinity((double)_value)) + { + sqlText.AppendFormat("'Infinity'::float8"); + } + else if (double.IsNegativeInfinity((double)_value)) + { + sqlText.AppendFormat("'-Infinity'::float8"); + } + else if ((double)_value < 0) + { + sqlText.AppendFormat(ni, "({0:r})::float8", _value); + } + else + { + sqlText.AppendFormat(ni, "{0:r}::float8", _value); + } + break; + // PostgreSQL has no support for bytes. int2 is used instead in Npgsql. + case PrimitiveTypeKind.Byte: + value = (short)(byte)_value; + goto case PrimitiveTypeKind.Int16; + case PrimitiveTypeKind.SByte: + value = (short)(sbyte)_value; + goto case PrimitiveTypeKind.Int16; + case PrimitiveTypeKind.Int16: + if ((short)value < 0) + { + sqlText.AppendFormat(ni, "({0})::int2", _value); + } + else + { + sqlText.AppendFormat(ni, "{0}::int2", _value); + } + break; + case PrimitiveTypeKind.Int32: + sqlText.AppendFormat(ni, "{0}", _value); + break; + case PrimitiveTypeKind.Int64: + if ((long)_value < 0) + { + sqlText.AppendFormat(ni, "({0})::int8", _value); + } + else + { + sqlText.AppendFormat(ni, "{0}::int8", _value); + } + break; + case PrimitiveTypeKind.Single: + if (float.IsNaN((float)_value)) + { + sqlText.AppendFormat("'NaN'::float4"); + } + else if (float.IsPositiveInfinity((float)_value)) + { + sqlText.AppendFormat("'Infinity'::float4"); + } + else if (float.IsNegativeInfinity((float)_value)) + { + sqlText.AppendFormat("'-Infinity'::float4"); + } + else if ((float)_value < 0) + { + sqlText.AppendFormat(ni, "({0:r})::float4", _value); + } + else + { + sqlText.AppendFormat(ni, "{0:r}::float4", _value); + } + break; + case PrimitiveTypeKind.Boolean: + sqlText.Append(((bool)_value) ? "TRUE" : "FALSE"); + break; + case PrimitiveTypeKind.Guid: + sqlText.Append('\'').Append((Guid)_value).Append('\''); + sqlText.Append("::uuid"); + break; + case PrimitiveTypeKind.String: + sqlText.Append("E'").Append(((string)_value).Replace(@"\", @"\\").Replace("'", @"\'")).Append("'"); + break; + case PrimitiveTypeKind.Time: + sqlText.AppendFormat(ni, "INTERVAL '{0}'", (NpgsqlTimeSpan)(TimeSpan)_value); + break; + default: + // TODO: must support more constant value types. + throw new NotSupportedException(string.Format("NotSupported: {0} {1}", _primitiveType, _value)); + } + base.WriteSql(sqlText); + } + } + + internal class InsertExpression : VisitedExpression + { + public void AppendTarget(VisitedExpression target) + { + Append(target); + } + + public void AppendColumns(IEnumerable columns) + { + if (!columns.Any()) + return; + + Append("("); + bool first = true; + foreach (VisitedExpression expression in columns) + { + if (!first) + Append(","); + Append(expression); + first = false; + } + Append(")"); + } + + public void AppendValues(IEnumerable columns) + { + if (columns.Any()) + { + Append(" VALUES ("); + bool first = true; + foreach (VisitedExpression expression in columns) + { + if (!first) + Append(","); + Append(expression); + first = false; + } + Append(")"); + } + else + { + Append(" DEFAULT VALUES"); + } + } + + internal void AppendReturning(DbNewInstanceExpression expression) + { + Append(" RETURNING ");//Don't put () around columns it will probably have unwanted effect + bool first = true; + foreach (var returingProperty in expression.Arguments) + { + if (!first) + Append(","); + Append(SqlBaseGenerator.QuoteIdentifier((returingProperty as DbPropertyExpression).Property.Name)); + first = false; + } + } + + internal override void WriteSql(StringBuilder sqlText) + { + sqlText.Append("INSERT INTO "); + base.WriteSql(sqlText); + } + } + + internal class UpdateExpression : VisitedExpression + { + private bool _setSeperatorRequired; + + public void AppendTarget(VisitedExpression target) + { + Append(target); + } + + public void AppendSet(VisitedExpression property, VisitedExpression value) + { + if (_setSeperatorRequired) + Append(","); + else + Append(" SET "); + Append(property); + Append("="); + Append(value); + _setSeperatorRequired = true; + } + + public void AppendWhere(VisitedExpression where) + { + Append(" WHERE "); + Append(where); + } + + internal override void WriteSql(StringBuilder sqlText) + { + sqlText.Append("UPDATE "); + base.WriteSql(sqlText); + } + + internal void AppendReturning(DbNewInstanceExpression expression) + { + Append(" RETURNING ");//Don't put () around columns it will probably have unwanted effect + bool first = true; + foreach (var returingProperty in expression.Arguments) + { + if (!first) + Append(","); + Append(SqlBaseGenerator.QuoteIdentifier((returingProperty as DbPropertyExpression).Property.Name)); + first = false; + } + } + } + + internal class DeleteExpression : VisitedExpression + { + public void AppendFrom(VisitedExpression from) + { + Append(from); + } + + public void AppendWhere(VisitedExpression where) + { + Append(" WHERE "); + Append(where); + } + + internal override void WriteSql(StringBuilder sqlText) + { + sqlText.Append("DELETE FROM "); + base.WriteSql(sqlText); + } + } + + internal class ColumnExpression : VisitedExpression + { + private VisitedExpression _column; + private string _columnName; + private TypeUsage _columnType; + + public ColumnExpression(VisitedExpression column, string columnName, TypeUsage columnType) + { + _column = column; + _columnName = columnName; + _columnType = columnType; + } + + public string Name { get { return _columnName; } } + internal TypeUsage ColumnType { get { return _columnType; ;} } + + public Type CLRType + { + get + { + if (_columnType == null) + return null; + PrimitiveType pt = _columnType.EdmType as PrimitiveType; + if (pt != null) + return pt.ClrEquivalentType; + else + return null; + } + } + + internal override void WriteSql(StringBuilder sqlText) + { + _column.WriteSql(sqlText); + + ColumnReferenceExpression column = _column as ColumnReferenceExpression; + if (column == null || column.Name != _columnName) + { + sqlText.Append(" AS "); + sqlText.Append(SqlBaseGenerator.QuoteIdentifier(_columnName)); + } + + base.WriteSql(sqlText); + } + } + + internal class ColumnReferenceExpression : VisitedExpression + { + public string Variable { get; set; } + public string Name { get; set; } + + internal override void WriteSql(StringBuilder sqlText) + { + if (Variable != null) + { + sqlText.Append(SqlBaseGenerator.QuoteIdentifier(Variable)); + sqlText.Append("."); + } + sqlText.Append(SqlBaseGenerator.QuoteIdentifier(Name)); + base.WriteSql(sqlText); + } + } + + internal class ScanExpression : VisitedExpression + { + private string _scanString; + private EntitySetBase _target; + + public ScanExpression(string scanString, EntitySetBase target) + { + _scanString = scanString; + _target = target; + } + + internal EntitySetBase Target { get { return _target; } } + + internal override void WriteSql(StringBuilder sqlText) + { + sqlText.Append(_scanString); + base.WriteSql(sqlText); + } + } + + internal class InputExpression : VisitedExpression + { + public bool Distinct { get; set; } + + public CommaSeparatedExpression Projection { get; set; } + + public readonly Dictionary ColumnsToProject = new Dictionary(); // (from, name) -> newName + public readonly HashSet ProjectNewNames = new HashSet(); + + // Either FromExpression or JoinExpression + public VisitedExpression From { get; set; } + + private WhereExpression _where; + + public WhereExpression Where + { + get { return _where; } + set + { + _where = value; + } + } + + private GroupByExpression _groupBy; + + public GroupByExpression GroupBy + { + get { return _groupBy; } + set + { + _groupBy = value; + } + } + + private OrderByExpression _orderBy; + + public OrderByExpression OrderBy + { + get { return _orderBy; } + set { _orderBy = value; } + } + + private SkipExpression _skip; + + public SkipExpression Skip + { + get { return _skip; } + set { _skip = value; } + } + + private LimitExpression _limit; + + public LimitExpression Limit + { + get { return _limit; } + set + { + _limit = value; + } + } + + public InputExpression() { } + + public InputExpression(VisitedExpression from, string asName) + { + From = new FromExpression(from, asName); + } + + internal override void WriteSql(StringBuilder sqlText) + { + sqlText.Append("SELECT "); + if (Distinct) sqlText.Append("DISTINCT "); + if (Projection != null) Projection.WriteSql(sqlText); + else + { + if (ColumnsToProject.Count == 0) sqlText.Append("1"); // Could be arbitrary, let's pick 1 + else + { + bool first = true; + foreach (var column in ColumnsToProject) + { + if (!first) + { + sqlText.Append(", "); + } + else first = false; + sqlText.Append(SqlBaseGenerator.QuoteIdentifier(column.Key.Item1)); + sqlText.Append("."); + sqlText.Append(SqlBaseGenerator.QuoteIdentifier(column.Key.Item2)); + if (column.Key.Item2 != column.Value) + { + sqlText.Append(" AS "); + sqlText.Append(SqlBaseGenerator.QuoteIdentifier(column.Value)); + } + } + } + } + sqlText.Append(" FROM "); + From.WriteSql(sqlText); + if (Where != null) Where.WriteSql(sqlText); + if (GroupBy != null) GroupBy.WriteSql(sqlText); + if (OrderBy != null) OrderBy.WriteSql(sqlText); + if (Skip != null) Skip.WriteSql(sqlText); + if (Limit != null) Limit.WriteSql(sqlText); + base.WriteSql(sqlText); + } + } + + internal class FromExpression : VisitedExpression + { + private VisitedExpression _from; + private string _name; + + public FromExpression(VisitedExpression from, string name) + { + _from = from; + _name = name; + } + + public string Name + { + get { return _name; } + } + + public bool ForceSubquery { get; set; } + + internal override void WriteSql(StringBuilder sqlText) + { + if (_from is InputExpression) + { + InputExpression input = (InputExpression)_from; + if (!ForceSubquery && input.Projection == null && input.Where == null && input.Distinct == false && input.OrderBy == null && + input.Skip == null && input.Limit == null) + { + // There is no point of writing + // (SELECT ? FROM AS ) AS + // so just write AS + // is always the same for both nodes + // However, PostgreSQL needs a subquery in case we are in the right hand side of an Apply expression + if (((FromExpression)input.From).Name != Name) + throw new ArgumentException(); + input.From.WriteSql(sqlText); + } + else + { + sqlText.Append("("); + input.WriteSql(sqlText); + sqlText.Append(") AS "); + sqlText.Append(SqlBaseGenerator.QuoteIdentifier(_name)); + } + } + else + { + bool wrap = !(_from is LiteralExpression || _from is ScanExpression); + if (wrap) + sqlText.Append("("); + _from.WriteSql(sqlText); + if (wrap) + sqlText.Append(")"); + sqlText.Append(" AS "); + sqlText.Append(SqlBaseGenerator.QuoteIdentifier(_name)); + } + base.WriteSql(sqlText); + } + } + + internal class JoinExpression : VisitedExpression + { + private VisitedExpression _left; + private DbExpressionKind _joinType; + private VisitedExpression _right; + private VisitedExpression _condition; + + public JoinExpression() { } + + public JoinExpression(InputExpression left, DbExpressionKind joinType, InputExpression right, VisitedExpression condition) + { + _left = left; + _joinType = joinType; + _right = right; + _condition = condition; + } + + public VisitedExpression Left { get { return _left; } set { _left = value; } } + public DbExpressionKind JoinType { get { return _joinType; } set { _joinType = value; } } + public VisitedExpression Right { get { return _right; } set { _right = value; } } + + public VisitedExpression Condition + { + get { return _condition; } + set { _condition = value; } + } + + internal override void WriteSql(StringBuilder sqlText) + { + _left.WriteSql(sqlText); + switch (_joinType) + { + case DbExpressionKind.InnerJoin: + sqlText.Append(" INNER JOIN "); + break; + case DbExpressionKind.LeftOuterJoin: + sqlText.Append(" LEFT OUTER JOIN "); + break; + case DbExpressionKind.FullOuterJoin: + sqlText.Append(" FULL OUTER JOIN "); + break; + case DbExpressionKind.CrossJoin: + sqlText.Append(" CROSS JOIN "); + break; + case DbExpressionKind.CrossApply: + sqlText.Append(" CROSS JOIN LATERAL "); + break; + case DbExpressionKind.OuterApply: + sqlText.Append(" LEFT OUTER JOIN LATERAL "); + break; + default: + throw new NotSupportedException(); + } + _right.WriteSql(sqlText); + if (_joinType == DbExpressionKind.OuterApply) + sqlText.Append(" ON TRUE"); + else if (_joinType != DbExpressionKind.CrossJoin && _joinType != DbExpressionKind.CrossApply) + { + sqlText.Append(" ON "); + _condition.WriteSql(sqlText); + } + base.WriteSql(sqlText); + } + } + + internal class WhereExpression : VisitedExpression + { + private VisitedExpression _where; + + public WhereExpression(VisitedExpression where) + { + _where = where; + } + + internal override void WriteSql(StringBuilder sqlText) + { + sqlText.Append(" WHERE "); + _where.WriteSql(sqlText); + base.WriteSql(sqlText); + } + + internal void And(VisitedExpression andAlso) + { + // useNewPrecedence doesn't matter here since there was no change with the AND operator + _where = OperatorExpression.Build(Operator.And, true, _where, andAlso); + } + } + + internal class PropertyExpression : VisitedExpression + { + private EdmMember _property; + + // used for inserts or updates where the column is not qualified + public PropertyExpression(EdmMember property) + { + _property = property; + } + + public string Name { get { return _property.Name; } } + + public TypeUsage PropertyType { get { return _property.TypeUsage; } } + + internal override void WriteSql(StringBuilder sqlText) + { + sqlText.Append(SqlBaseGenerator.QuoteIdentifier(_property.Name)); + base.WriteSql(sqlText); + } + + // override ToString since we don't want variable substitution or identifier quoting + // until writing out the SQL. + public override string ToString() + { + return _property.Name; + } + } + + internal class FunctionExpression : VisitedExpression + { + private string _name; + private List _args = new List(); + + public FunctionExpression(string name) + { + _name = name; + } + + internal FunctionExpression AddArgument(VisitedExpression visitedExpression) + { + _args.Add(visitedExpression); + return this; + } + + internal FunctionExpression AddArgument(string argument) + { + _args.Add(new LiteralExpression(argument)); + return this; + } + + internal override void WriteSql(StringBuilder sqlText) + { + sqlText.Append(_name); + sqlText.Append("("); + bool first = true; + foreach (var arg in _args) + { + if (!first) + sqlText.Append(","); + arg.WriteSql(sqlText); + first = false; + } + sqlText.Append(")"); + base.WriteSql(sqlText); + } + } + + internal class CastExpression : VisitedExpression + { + private VisitedExpression _value; + private string _type; + + public CastExpression(VisitedExpression value, string type) + { + _value = value; + _type = type; + } + + internal override void WriteSql(StringBuilder sqlText) + { + sqlText.Append("CAST ("); + _value.WriteSql(sqlText); + sqlText.AppendFormat(" AS {0})", _type); + base.WriteSql(sqlText); + } + } + + internal class GroupByExpression : VisitedExpression + { + private bool _requiresGroupSeperator; + + public void AppendGroupingKey(VisitedExpression key) + { + if (_requiresGroupSeperator) + Append(","); + Append(key); + _requiresGroupSeperator = true; + } + + internal override void WriteSql(StringBuilder sqlText) + { + if (ExpressionList.Count != 0) + sqlText.Append(" GROUP BY "); + base.WriteSql(sqlText); + } + } + + internal class LimitExpression : VisitedExpression + { + private VisitedExpression _arg; + + public VisitedExpression Arg { get { return _arg; } set { _arg = value; } } + + public LimitExpression(VisitedExpression arg) + { + _arg = arg; + } + + internal override void WriteSql(StringBuilder sqlText) + { + sqlText.Append(" LIMIT "); + _arg.WriteSql(sqlText); + base.WriteSql(sqlText); + } + } + + internal class SkipExpression : VisitedExpression + { + private VisitedExpression _arg; + + public SkipExpression(VisitedExpression arg) + { + _arg = arg; + } + + internal override void WriteSql(StringBuilder sqlText) + { + sqlText.Append(" OFFSET "); + _arg.WriteSql(sqlText); + base.WriteSql(sqlText); + } + } + + internal class Operator + { + private string op; + private int leftPrecedence; + private int rightPrecedence; + private int newPrecedence; // Since PostgreSQL 9.5, the operator precedence was changed + private UnaryTypes unaryType; + private bool rightAssoc; + + public string Op { get { return op; } } + public int LeftPrecedence { get { return leftPrecedence; } } + public int RightPrecedence { get { return rightPrecedence; } } + public int NewPrecedence { get { return newPrecedence; } } + public UnaryTypes UnaryType { get { return unaryType; } } + public bool RightAssoc { get { return rightAssoc; } } + + internal enum UnaryTypes { + Binary, + Prefix, + Postfix + } + + private Operator(string op, int precedence, int newPrecedence) + { + this.op = ' ' + op + ' '; + this.leftPrecedence = precedence; + this.rightPrecedence = precedence; + this.newPrecedence = newPrecedence; + this.unaryType = UnaryTypes.Binary; + } + + private Operator(string op, int leftPrecedence, int rightPrecedence, int newPrecedence) + { + this.op = ' ' + op + ' '; + this.leftPrecedence = leftPrecedence; + this.rightPrecedence = rightPrecedence; + this.newPrecedence = newPrecedence; + this.unaryType = UnaryTypes.Binary; + } + + private Operator(string op, int precedence, int newPrecedence, UnaryTypes unaryType, bool rightAssoc) + { + this.op = unaryType == UnaryTypes.Binary ? ' ' + op + ' ' : unaryType == UnaryTypes.Prefix ? op + ' ' : ' ' + op; + this.leftPrecedence = precedence; + this.rightPrecedence = precedence; + this.newPrecedence = newPrecedence; + this.unaryType = unaryType; + this.rightAssoc = rightAssoc; + } + + /* + * Operator table + * Corresponds to the operator precedence table at + * http://www.postgresql.org/docs/current/interactive/sql-syntax-lexical.html + * + * Note that in versions up to 9.4, NOT IN and NOT LIKE have different precedences depending on + * if the other operator is to the left or to the right. + * For example, "a = b NOT LIKE c" is parsed as "(a = b) NOT LIKE c" + * but "a NOT LIKE b = c" is parsed as "(a NOT LIKE b) = c" + * This is because PostgreSQL's parser uses Bison's automatic + * operator precedence handling, and NOT and LIKE has different precedences, + * so this happens when the two keywords are put together like this. + * + */ + public static readonly Operator UnaryMinus = new Operator("-", 17, 12, UnaryTypes.Prefix, true); + public static readonly Operator Mul = new Operator("*", 15, 10); + public static readonly Operator Div = new Operator("/", 15, 10); + public static readonly Operator Mod = new Operator("%", 15, 10); + public static readonly Operator Add = new Operator("+", 14, 9); + public static readonly Operator Sub = new Operator("-", 14, 9); + public static readonly Operator IsNull = new Operator("IS NULL", 13, 4, UnaryTypes.Postfix, false); + public static readonly Operator IsNotNull = new Operator("IS NOT NULL", 13, 4, UnaryTypes.Postfix, false); + public static readonly Operator LessThanOrEquals = new Operator("<=", 10, 5); + public static readonly Operator GreaterThanOrEquals = new Operator(">=", 10, 5); + public static readonly Operator NotEquals = new Operator("!=", 10, 5); + public static readonly Operator BitwiseAnd = new Operator("&", 10, 8); + public static readonly Operator BitwiseOr = new Operator("|", 10, 8); + public static readonly Operator BitwiseXor = new Operator("#", 10, 8); + public static readonly Operator BitwiseNot = new Operator("~", 10, 8, UnaryTypes.Prefix, false); + public static readonly Operator Concat = new Operator("||", 10, 8); + public static readonly Operator In = new Operator("IN", 9, 6); + public static readonly Operator NotIn = new Operator("NOT IN", 3, 9, 6); + public static readonly Operator Like = new Operator("LIKE", 6, 6); + public static readonly Operator NotLike = new Operator("NOT LIKE", 3, 6, 6); + public static readonly Operator LessThan = new Operator("<", 5, 5); + public static readonly Operator GreaterThan = new Operator(">", 5, 5); + public static readonly new Operator Equals = new Operator("=", 4, 5, UnaryTypes.Binary, true); + public static readonly Operator Not = new Operator("NOT", 3, 3, UnaryTypes.Prefix, true); + public static readonly Operator And = new Operator("AND", 2, 2); + public static readonly Operator Or = new Operator("OR", 1, 1); + + public static readonly Operator QueryMatch = new Operator("@@", 10, 8); + public static readonly Operator QueryAnd = new Operator("&&", 10, 8); + public static readonly Operator QueryOr = Concat; + public static readonly Operator QueryNegate = new Operator("!!", 10, 8, UnaryTypes.Prefix, true); + public static readonly Operator QueryContains = new Operator("@>", 10, 8); + public static readonly Operator QueryIsContained = new Operator("<@", 10, 8); + + public static readonly Dictionary NegateDict; + + static Operator() + { + NegateDict = new Dictionary() + { + {IsNull, IsNotNull}, + {IsNotNull, IsNull}, + {LessThanOrEquals, GreaterThan}, + {GreaterThanOrEquals, LessThan}, + {NotEquals, Equals}, + {In, NotIn}, + {NotIn, In}, + {Like, NotLike}, + {NotLike, Like}, + {LessThan, GreaterThanOrEquals}, + {GreaterThan, LessThanOrEquals}, + {Equals, NotEquals} + }; + } + } + + internal class OperatorExpression : VisitedExpression + { + private Operator op; + private bool useNewPrecedences; + private VisitedExpression left; + private VisitedExpression right; + + private OperatorExpression(Operator op, bool useNewPrecedences, VisitedExpression left, VisitedExpression right) + { + this.op = op; + this.useNewPrecedences = useNewPrecedences; + this.left = left; + this.right = right; + } + + public static OperatorExpression Build(Operator op, bool useNewPrecedences, VisitedExpression left, VisitedExpression right) + { + if (op.UnaryType == Operator.UnaryTypes.Binary) + { + return new OperatorExpression(op, useNewPrecedences, left, right); + } + else + { + throw new InvalidOperationException("Unary operator with two operands"); + } + } + + public static OperatorExpression Build(Operator op, bool useNewPrecedences, VisitedExpression exp) + { + if (op.UnaryType == Operator.UnaryTypes.Prefix) + { + return new OperatorExpression(op, useNewPrecedences, null, exp); + } + else if (op.UnaryType == Operator.UnaryTypes.Postfix) + { + return new OperatorExpression(op, useNewPrecedences, exp, null); + } + else + { + throw new InvalidOperationException("Binary operator with one operand"); + } + } + + /// + /// Negates an expression. + /// If possible, replaces the operator of exp if exp is a negatable OperatorExpression, + /// else return a new OperatorExpression of type Not that wraps exp. + /// + public static VisitedExpression Negate(VisitedExpression exp, bool useNewPrecedences) + { + OperatorExpression expOp = exp as OperatorExpression; + if (expOp != null) + { + Operator op = expOp.op; + Operator newOp = null; + if (Operator.NegateDict.TryGetValue(op, out newOp)) + { + expOp.op = newOp; + return expOp; + } + if (expOp.op == Operator.Not) + { + return expOp.right; + } + } + + return OperatorExpression.Build(Operator.Not, useNewPrecedences, exp); + } + + internal override void WriteSql(StringBuilder sqlText) + { + WriteSql(sqlText, null); + } + + private void WriteSql(StringBuilder sqlText, OperatorExpression rightParent) + { + OperatorExpression leftOp = left as OperatorExpression; + OperatorExpression rightOp = right as OperatorExpression; + + bool wrapLeft, wrapRight; + + if (!useNewPrecedences) + { + wrapLeft = leftOp != null && (op.RightAssoc ? leftOp.op.RightPrecedence <= op.LeftPrecedence : leftOp.op.RightPrecedence < op.LeftPrecedence); + wrapRight = rightOp != null && (!op.RightAssoc ? rightOp.op.LeftPrecedence <= op.RightPrecedence : rightOp.op.LeftPrecedence < op.RightPrecedence); + } + else + { + wrapLeft = leftOp != null && (op.RightAssoc ? leftOp.op.NewPrecedence <= op.NewPrecedence : leftOp.op.NewPrecedence < op.NewPrecedence); + wrapRight = rightOp != null && (!op.RightAssoc ? rightOp.op.NewPrecedence <= op.NewPrecedence : rightOp.op.NewPrecedence < op.NewPrecedence); + } + + // Avoid parentheses for prefix operators if possible, + // e.g. BitwiseNot: (a & (~ b)) & c is written as a & ~ b & c + // but (a + (~ b)) + c must be written as a + (~ b) + c + if (!useNewPrecedences) + { + if (wrapRight && rightOp.left == null && (rightParent == null || (!rightParent.op.RightAssoc ? rightOp.op.RightPrecedence >= rightParent.op.LeftPrecedence : rightOp.op.RightPrecedence > rightParent.op.LeftPrecedence))) + wrapRight = false; + } + else + { + if (wrapRight && rightOp.left == null && (rightParent == null || (!rightParent.op.RightAssoc ? rightOp.op.NewPrecedence >= rightParent.op.NewPrecedence : rightOp.op.NewPrecedence > rightParent.op.NewPrecedence))) + wrapRight = false; + } + + if (left != null) + { + if (wrapLeft) + sqlText.Append("("); + if (leftOp != null && !wrapLeft) + leftOp.WriteSql(sqlText, this); + else + left.WriteSql(sqlText); + if (wrapLeft) + sqlText.Append(")"); + } + + sqlText.Append(op.Op); + + if (right != null) + { + if (wrapRight) + sqlText.Append("("); + if (rightOp != null && !wrapRight) + rightOp.WriteSql(sqlText, rightParent); + else + right.WriteSql(sqlText); + if (wrapRight) + sqlText.Append(")"); + } + + base.WriteSql(sqlText); + } + } + + internal class ConstantListExpression : VisitedExpression + { + private IEnumerable _list; + + public ConstantListExpression(IEnumerable list) + { + _list = list; + } + + internal override void WriteSql(StringBuilder sqlText) + { + sqlText.Append("("); + bool first = true; + foreach (var constant in _list) + { + if (!first) + sqlText.Append(","); + constant.WriteSql(sqlText); + first = false; + } + sqlText.Append(")"); + base.WriteSql(sqlText); + } + } + + internal class CombinedProjectionExpression : VisitedExpression + { + private List _list; + private string _setOperator; + + public CombinedProjectionExpression(DbExpressionKind setOperator, List list) + { + _setOperator = setOperator == DbExpressionKind.UnionAll ? "UNION ALL" : setOperator == DbExpressionKind.Except ? "EXCEPT" : "INTERSECT"; + _list = list; + } + + internal override void WriteSql(StringBuilder sqlText) + { + for (var i = 0; i < _list.Count; i++) + { + if (i != 0) + { + sqlText.Append(' ').Append(_setOperator).Append(' '); + } + sqlText.Append('('); + _list[i].WriteSql(sqlText); + sqlText.Append(')'); + } + + base.WriteSql(sqlText); + } + } + + internal class ExistsExpression : VisitedExpression + { + private VisitedExpression _argument; + + public ExistsExpression(VisitedExpression argument) + { + _argument = argument; + } + + internal override void WriteSql(StringBuilder sqlText) + { + sqlText.Append("EXISTS ("); + _argument.WriteSql(sqlText); + sqlText.Append(")"); + base.WriteSql(sqlText); + } + } + + class OrderByExpression : VisitedExpression + { + private bool _requiresOrderSeperator; + + public void AppendSort(VisitedExpression sort, bool ascending) + { + if (_requiresOrderSeperator) + Append(","); + Append(sort); + if (ascending) + Append(" ASC "); + else + Append(" DESC "); + _requiresOrderSeperator = true; + } + + internal override void WriteSql(StringBuilder sqlText) + { + sqlText.Append(" ORDER BY "); + base.WriteSql(sqlText); + } + } + + internal class TruncateTimeExpression : VisitedExpression + { + readonly VisitedExpression _arg; + readonly string _truncationType; + public TruncateTimeExpression(string truncationType, VisitedExpression visitedExpression) + { + _arg = visitedExpression; + _truncationType = truncationType; + } + + + internal override void WriteSql(StringBuilder sqlText) + { + sqlText.Append("date_trunc"); + sqlText.Append("("); + sqlText.Append("'" + _truncationType + "',"); + _arg.WriteSql(sqlText); + sqlText.Append(")"); + base.WriteSql(sqlText); + } + } +} diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/install.ps1 b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/install.ps1 new file mode 100644 index 0000000..b6b6c15 --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/install.ps1 @@ -0,0 +1,3 @@ +param($installPath, $toolsPath, $package, $project) + +Add-EFProvider $project 'Npgsql' 'Npgsql.NpgsqlServices, EntityFramework6.Npgsql' diff --git a/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/packages.config b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/packages.config new file mode 100644 index 0000000..4168576 --- /dev/null +++ b/src/EntityFramework6.Npgsql/SqlGenerators/src/EntityFramework6.Npgsql/packages.config @@ -0,0 +1,4 @@ + + + + diff --git a/teamcity_set_version.cmd b/teamcity_set_version.cmd index f0f2e36..395c679 100644 --- a/teamcity_set_version.cmd +++ b/teamcity_set_version.cmd @@ -1 +1 @@ -echo ##teamcity[buildNumber '3.1.0-%1'] +echo ##teamcity[buildNumber '3.1.2-ci-%1'] diff --git a/test/EntityFramework6.Npgsql.Tests/EntityFrameworkBasicTests.cs b/test/EntityFramework6.Npgsql.Tests/EntityFrameworkBasicTests.cs index e1e30fc..2ff87fb 100644 --- a/test/EntityFramework6.Npgsql.Tests/EntityFrameworkBasicTests.cs +++ b/test/EntityFramework6.Npgsql.Tests/EntityFrameworkBasicTests.cs @@ -650,5 +650,74 @@ public void TestScalarValuedStoredFunctions_with_null_StoreFunctionName() Assert.That(echo, Is.EqualTo(1337)); } } + + [Test] + public void TestCastFunction() + { + using (var context = new BloggingContext(ConnectionString)) + { + context.Database.Log = Console.Out.WriteLine; + + var varbitVal = "10011"; + + var blog = new Blog + { + Name = "_", + Posts = new List + { + new Post + { + Content = "Some post content", + Rating = 1, + Title = "Some post Title", + VarbitColumn = varbitVal + } + } + }; + context.Blogs.Add(blog); + context.SaveChanges(); + + Assert.IsTrue( + context.Posts.Select( + p => NpgsqlTypeFunctions.Cast(p.VarbitColumn, "varbit") == varbitVal).First()); + + Assert.IsTrue( + context.Posts.Select( + p => NpgsqlTypeFunctions.Cast(p.VarbitColumn, "varbit") == "10011").First()); + } + } + + [Test] + public void Test_issue_27_select_ef_generated_literals_from_inner_select() + { + using (var context = new BloggingContext(ConnectionString)) + { + context.Database.Log = Console.Out.WriteLine; + + var blog = new Blog { Name = "Hello" }; + context.Users.Add(new Administrator { Blogs = new List { blog } }); + context.Users.Add(new Editor()); + context.SaveChanges(); + + var administrator = context.Users + .Where(x => x is Administrator) // Removing this changes the query to using a UNION which doesn't fail. + .Select( + x => new + { + // causes entity framework to emit a literal discriminator + Computed = x is Administrator + ? "I administrate" + : x is Editor + ? "I edit" + : "Unknown", + // causes an inner select to be emitted thus showing the issue + HasBlog = x.Blogs.Any() + }) + .First(); + + Assert.That(administrator.Computed, Is.EqualTo("I administrate")); + Assert.That(administrator.HasBlog, Is.True); + } + } } } diff --git a/test/EntityFramework6.Npgsql.Tests/Support/EntityFrameworkTestBase.cs b/test/EntityFramework6.Npgsql.Tests/Support/EntityFrameworkTestBase.cs index 366a02f..1fcc3ad 100644 --- a/test/EntityFramework6.Npgsql.Tests/Support/EntityFrameworkTestBase.cs +++ b/test/EntityFramework6.Npgsql.Tests/Support/EntityFrameworkTestBase.cs @@ -104,6 +104,20 @@ public class NoColumnsEntity public int Id { get; set; } } + [Table("Users")] + public abstract class User + { + public int Id { get; set; } + + public IList Blogs { get; set; } + } + + [Table("Editors")] + public class Editor : User { } + + [Table("Administrators")] + public class Administrator : User { } + public class BloggingContext : DbContext { public BloggingContext(string connection) @@ -114,6 +128,9 @@ public BloggingContext(string connection) public DbSet Blogs { get; set; } public DbSet Posts { get; set; } public DbSet NoColumnsEntities { get; set; } + public DbSet Users { get; set; } + public DbSet Editors { get; set; } + public DbSet Administrators { get; set; } [DbFunction("BloggingContext", "ClrStoredAddFunction")] public static int StoredAddFunction(int val1, int val2) @@ -135,6 +152,9 @@ private static DbCompiledModel CreateModel(NpgsqlConnection connection) dbModelBuilder.Entity(); dbModelBuilder.Entity(); dbModelBuilder.Entity(); + dbModelBuilder.Entity(); + dbModelBuilder.Entity(); + dbModelBuilder.Entity(); // Import function var dbModel = dbModelBuilder.Build(connection); diff --git a/test/EntityFramework6.Npgsql.Tests/Support/NLogLoggingProvider.cs b/test/EntityFramework6.Npgsql.Tests/Support/NLogLoggingProvider.cs new file mode 100644 index 0000000..2b82189 --- /dev/null +++ b/test/EntityFramework6.Npgsql.Tests/Support/NLogLoggingProvider.cs @@ -0,0 +1,85 @@ +#region License +// The PostgreSQL License +// +// Copyright (C) 2016 The Npgsql Development Team +// +// Permission to use, copy, modify, and distribute this software and its +// documentation for any purpose, without fee, and without a written +// agreement is hereby granted, provided that the above copyright notice +// and this paragraph and the following two paragraphs appear in all copies. +// +// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY +// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS +// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// +// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS +// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +#endregion + +using System; +using NLog; +using Npgsql.Logging; + +// ReSharper disable once CheckNamespace +namespace EntityFramework6.Npgsql.Tests +{ + class NLogLoggingProvider : INpgsqlLoggingProvider + { + public NpgsqlLogger CreateLogger(string name) + + { + return new NLogLogger(name); + } + } + + class NLogLogger : NpgsqlLogger + { + readonly Logger _log; + + internal NLogLogger(string name) + { + _log = LogManager.GetLogger(name); + } + + public override bool IsEnabled(NpgsqlLogLevel level) + { + return _log.IsEnabled(ToNLogLogLevel(level)); + } + + public override void Log(NpgsqlLogLevel level, int connectorId, string msg, Exception exception = null) + { + var ev = new LogEventInfo(ToNLogLogLevel(level), "", msg); + if (exception != null) + ev.Exception = exception; + if (connectorId != 0) + ev.Properties["ConnectorId"] = connectorId; + _log.Log(ev); + } + + static LogLevel ToNLogLogLevel(NpgsqlLogLevel level) + { + switch (level) + { + case NpgsqlLogLevel.Trace: + return LogLevel.Trace; + case NpgsqlLogLevel.Debug: + return LogLevel.Debug; + case NpgsqlLogLevel.Info: + return LogLevel.Info; + case NpgsqlLogLevel.Warn: + return LogLevel.Warn; + case NpgsqlLogLevel.Error: + return LogLevel.Error; + case NpgsqlLogLevel.Fatal: + return LogLevel.Fatal; + default: + throw new ArgumentOutOfRangeException("level"); + } + } + } +} diff --git a/test/EntityFramework6.Npgsql.Tests/xmlModel/XmlTest.Views.EF6.cs b/test/EntityFramework6.Npgsql.Tests/xmlModel/XmlTest.Views.EF6.cs new file mode 100644 index 0000000..9886ff6 --- /dev/null +++ b/test/EntityFramework6.Npgsql.Tests/xmlModel/XmlTest.Views.EF6.cs @@ -0,0 +1,487 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +[assembly: System.Data.Entity.Core.Mapping.EntityViewGenerationAttribute(typeof(Edm_EntityMappingGeneratedViews.ViewsForBaseEntitySets32CD194B04BDC9F607C67C8FAB6BC4DC))] + +namespace Edm_EntityMappingGeneratedViews +{ + + /// + /// The type contains views for EntitySets and AssociationSets that were generated at design time. + /// + public sealed class ViewsForBaseEntitySets32CD194B04BDC9F607C67C8FAB6BC4DC : System.Data.Entity.Core.Mapping.EntityViewContainer + { + + /// + /// The constructor stores the views for the extents and also the hash values generated based on the metadata and mapping closure and views + /// + public ViewsForBaseEntitySets32CD194B04BDC9F607C67C8FAB6BC4DC() + { + this.EdmEntityContainerName = "XmlTestContext"; + this.StoreEntityContainerName = "XmlTestStoreContainer"; + this.HashOverMappingClosure = "f1b01cb63d8b711866cfe7ecbfa9367e"; + this.HashOverAllExtentViews = "b0c3118d79b4c037f19c708219fdab28"; + this.ViewCount = 19; + } + + // The method returns the view for the index given. + protected override System.Collections.Generic.KeyValuePair GetViewAt(int index) + { + if ((index == 0)) + { + // return view for XmlTestStoreContainer.Customer + return new System.Collections.Generic.KeyValuePair("XmlTestStoreContainer.Customer", "\r\n SELECT VALUE -- Constructing Customer\r\n [XmlTest.Store].Customer(T1." + + "Customer_OrderID, T1.Customer_NameStyle, T1.Customer_FirstName, T1.Customer_Last" + + "Name, T1.Customer_Active, T1.Customer_ModifiedDate, T1.Customer_TourNumber, T1.C" + + "ustomer_ExternalNumber, T1.Customer_MainPhone1, T1.Customer_MainPhone2, T1.Custo" + + "mer_PreOrderID, T1.Customer_LastVisit, T1.Customer_Created, T1.Customer_External" + + "Name, T1.Customer_NewCustomer, T1.Customer_Potential, T1.Customer_CustomerID, T1" + + ".Customer_OldExternalNumber, T1.Customer_OldTourNumber, T1.Customer_NewAssigned," + + " T1.Customer_OldActive, T1.Customer_StartDate, T1.Customer_Total)\r\n FROM (\r\n " + + " SELECT \r\n T.OrderID AS Customer_OrderID, \r\n T.NameS" + + "tyle AS Customer_NameStyle, \r\n T.FirstName AS Customer_FirstName, \r\n " + + " T.LastName AS Customer_LastName, \r\n T.Active AS Customer_A" + + "ctive, \r\n T.ModifiedDate AS Customer_ModifiedDate, \r\n T.To" + + "urNumber AS Customer_TourNumber, \r\n T.ExternalNumber AS Customer_Exte" + + "rnalNumber, \r\n T.MainPhone1 AS Customer_MainPhone1, \r\n T.M" + + "ainPhone2 AS Customer_MainPhone2, \r\n T.PreOrderID AS Customer_PreOrde" + + "rID, \r\n T.LastVisit AS Customer_LastVisit, \r\n T.Created AS" + + " Customer_Created, \r\n T.ExternalName AS Customer_ExternalName, \r\n " + + " T.NewCustomer AS Customer_NewCustomer, \r\n T.Potential AS Cust" + + "omer_Potential, \r\n T.CustomerID AS Customer_CustomerID, \r\n " + + " T.OldExternalNumber AS Customer_OldExternalNumber, \r\n T.OldTourNumbe" + + "r AS Customer_OldTourNumber, \r\n T.NewAssigned AS Customer_NewAssigned" + + ", \r\n T.OldActive AS Customer_OldActive, \r\n T.StartDate AS " + + "Customer_StartDate, \r\n T.Total AS Customer_Total, \r\n True " + + "AS _from0\r\n FROM XmlTestContext.Customer AS T\r\n ) AS T1"); + } + else + { + if ((index == 1)) + { + // return view for XmlTestStoreContainer.SalesOrderHeader + return new System.Collections.Generic.KeyValuePair("XmlTestStoreContainer.SalesOrderHeader", "\r\n SELECT VALUE -- Constructing SalesOrderHeader\r\n [XmlTest.Store].Sale" + + "sOrderHeader(T3.SalesOrderHeader_OrderDate, T3.SalesOrderHeader_Status, T3.Sales" + + "OrderHeader_BillToAddressID, T3.SalesOrderHeader_SubTotal, T3.SalesOrderHeader_T" + + "otalDue, T3.SalesOrderHeader_Comment, T3.SalesOrderHeader_ModifiedDate, T3.Sales" + + "OrderHeader_PaymentVersion, T3.SalesOrderHeader_BillID, T3.SalesOrderHeader_Book" + + "Counter, T3.SalesOrderHeader_SendID, T3.SalesOrderHeader_SubSubTotal, T3.SalesOr" + + "derHeader_PriceRabatt15, T3.SalesOrderHeader_Total, T3.SalesOrderHeader_Discount" + + ", T3.SalesOrderHeader_Rabatt, T3.SalesOrderHeader_OrderID, T3.SalesOrderHeader_S" + + "endDate, T3.SalesOrderHeader_ID, T3.SalesOrderHeader_Saison)\r\n FROM (\r\n " + + " SELECT T2.SalesOrderHeader_OrderDate, T2.SalesOrderHeader_Status, T2.SalesOrde" + + "rHeader_BillToAddressID, T2.SalesOrderHeader_SubTotal, T2.SalesOrderHeader_Total" + + "Due, T2.SalesOrderHeader_Comment, T2.SalesOrderHeader_ModifiedDate, T2.SalesOrde" + + "rHeader_PaymentVersion, T2.SalesOrderHeader_BillID, T2.SalesOrderHeader_BookCoun" + + "ter, T2.SalesOrderHeader_SendID, T2.SalesOrderHeader_SubSubTotal, T2.SalesOrderH" + + "eader_PriceRabatt15, T2.SalesOrderHeader_Total, T2.SalesOrderHeader_Discount, T2" + + ".SalesOrderHeader_Rabatt, T1.SalesOrderHeader_OrderID, T2.SalesOrderHeader_SendD" + + "ate, T1.SalesOrderHeader_ID, T2.SalesOrderHeader_Saison, T2._from0, T1._from1\r\n " + + " FROM (\r\n SELECT \r\n Key(T.Customer).OrderID AS " + + "SalesOrderHeader_OrderID, \r\n Key(T.SalesOrderHeader).ID AS SalesO" + + "rderHeader_ID, \r\n True AS _from1\r\n FROM XmlTestContext" + + ".SalesOrderHeader_OrderID_fkey AS T) AS T1\r\n INNER JOIN (\r\n " + + " SELECT \r\n T.OrderDate AS SalesOrderHeader_OrderDate, \r\n " + + " T.Status AS SalesOrderHeader_Status, \r\n T.BillToAddressID" + + " AS SalesOrderHeader_BillToAddressID, \r\n T.SubTotal AS SalesOrder" + + "Header_SubTotal, \r\n T.TotalDue AS SalesOrderHeader_TotalDue, \r\n " + + " T.Comment AS SalesOrderHeader_Comment, \r\n T.Modifie" + + "dDate AS SalesOrderHeader_ModifiedDate, \r\n T.PaymentVersion AS Sa" + + "lesOrderHeader_PaymentVersion, \r\n T.BillID AS SalesOrderHeader_Bi" + + "llID, \r\n T.BookCounter AS SalesOrderHeader_BookCounter, \r\n " + + " T.SendID AS SalesOrderHeader_SendID, \r\n T.SubSubTotal AS" + + " SalesOrderHeader_SubSubTotal, \r\n T.PriceRabatt15 AS SalesOrderHe" + + "ader_PriceRabatt15, \r\n T.Total AS SalesOrderHeader_Total, \r\n " + + " T.Discount AS SalesOrderHeader_Discount, \r\n T.Rabatt A" + + "S SalesOrderHeader_Rabatt, \r\n T.SendDate AS SalesOrderHeader_Send" + + "Date, \r\n T.ID AS SalesOrderHeader_ID, \r\n T.Saison " + + "AS SalesOrderHeader_Saison, \r\n True AS _from0\r\n FROM X" + + "mlTestContext.SalesOrderHeader AS T) AS T2\r\n ON T1.SalesOrderHeader_I" + + "D = T2.SalesOrderHeader_ID\r\n ) AS T3"); + } + else + { + if ((index == 2)) + { + // return view for XmlTestContext.Customer + return new System.Collections.Generic.KeyValuePair("XmlTestContext.Customer", "\r\n SELECT VALUE -- Constructing Customer\r\n XmlTest.Customer(T1.Customer" + + "_OrderID, T1.Customer_NameStyle, T1.Customer_FirstName, T1.Customer_LastName, T1" + + ".Customer_Active, T1.Customer_ModifiedDate, T1.Customer_TourNumber, T1.Customer_" + + "ExternalNumber, T1.Customer_MainPhone1, T1.Customer_MainPhone2, T1.Customer_PreO" + + "rderID, T1.Customer_LastVisit, T1.Customer_Created, T1.Customer_ExternalName, T1" + + ".Customer_NewCustomer, T1.Customer_Potential, T1.Customer_CustomerID, T1.Custome" + + "r_OldExternalNumber, T1.Customer_OldTourNumber, T1.Customer_NewAssigned, T1.Cust" + + "omer_OldActive, T1.Customer_StartDate, T1.Customer_Total)\r\n FROM (\r\n S" + + "ELECT \r\n T.OrderID AS Customer_OrderID, \r\n T.NameStyle AS " + + "Customer_NameStyle, \r\n T.FirstName AS Customer_FirstName, \r\n " + + " T.LastName AS Customer_LastName, \r\n T.Active AS Customer_Active, \r" + + "\n T.ModifiedDate AS Customer_ModifiedDate, \r\n T.TourNumber" + + " AS Customer_TourNumber, \r\n T.ExternalNumber AS Customer_ExternalNumb" + + "er, \r\n T.MainPhone1 AS Customer_MainPhone1, \r\n T.MainPhone" + + "2 AS Customer_MainPhone2, \r\n T.PreOrderID AS Customer_PreOrderID, \r\n " + + " T.LastVisit AS Customer_LastVisit, \r\n T.Created AS Custome" + + "r_Created, \r\n T.ExternalName AS Customer_ExternalName, \r\n " + + "T.NewCustomer AS Customer_NewCustomer, \r\n T.Potential AS Customer_Pot" + + "ential, \r\n T.CustomerID AS Customer_CustomerID, \r\n T.OldEx" + + "ternalNumber AS Customer_OldExternalNumber, \r\n T.OldTourNumber AS Cus" + + "tomer_OldTourNumber, \r\n T.NewAssigned AS Customer_NewAssigned, \r\n " + + " T.OldActive AS Customer_OldActive, \r\n T.StartDate AS Customer" + + "_StartDate, \r\n T.Total AS Customer_Total, \r\n True AS _from" + + "0\r\n FROM XmlTestStoreContainer.Customer AS T\r\n ) AS T1"); + } + else + { + if ((index == 3)) + { + // return view for XmlTestContext.SalesOrderHeader + return new System.Collections.Generic.KeyValuePair("XmlTestContext.SalesOrderHeader", "\r\n SELECT VALUE -- Constructing SalesOrderHeader\r\n XmlTest.SalesOrderHe" + + "ader(T1.SalesOrderHeader_OrderDate, T1.SalesOrderHeader_Status, T1.SalesOrderHea" + + "der_BillToAddressID, T1.SalesOrderHeader_SubTotal, T1.SalesOrderHeader_TotalDue," + + " T1.SalesOrderHeader_Comment, T1.SalesOrderHeader_ModifiedDate, T1.SalesOrderHea" + + "der_PaymentVersion, T1.SalesOrderHeader_BillID, T1.SalesOrderHeader_BookCounter," + + " T1.SalesOrderHeader_SendID, T1.SalesOrderHeader_SubSubTotal, T1.SalesOrderHeade" + + "r_PriceRabatt15, T1.SalesOrderHeader_Total, T1.SalesOrderHeader_Discount, T1.Sal" + + "esOrderHeader_Rabatt, T1.SalesOrderHeader_SendDate, T1.SalesOrderHeader_ID, T1.S" + + "alesOrderHeader_Saison) WITH \r\n RELATIONSHIP(CREATEREF(XmlTestContext.Cus" + + "tomer, ROW(T1.[SalesOrderHeader_OrderID_fkey.Customer.OrderID]),XmlTest.Customer" + + "),XmlTest.SalesOrderHeader_OrderID_fkey,SalesOrderHeader,Customer) \r\n FROM (\r" + + "\n SELECT \r\n T.OrderDate AS SalesOrderHeader_OrderDate, \r\n " + + " T.Status AS SalesOrderHeader_Status, \r\n T.BillToAddressID AS S" + + "alesOrderHeader_BillToAddressID, \r\n T.SubTotal AS SalesOrderHeader_Su" + + "bTotal, \r\n T.TotalDue AS SalesOrderHeader_TotalDue, \r\n T.C" + + "omment AS SalesOrderHeader_Comment, \r\n T.ModifiedDate AS SalesOrderHe" + + "ader_ModifiedDate, \r\n T.PaymentVersion AS SalesOrderHeader_PaymentVer" + + "sion, \r\n T.BillID AS SalesOrderHeader_BillID, \r\n T.BookCou" + + "nter AS SalesOrderHeader_BookCounter, \r\n T.SendID AS SalesOrderHeader" + + "_SendID, \r\n T.SubSubTotal AS SalesOrderHeader_SubSubTotal, \r\n " + + " T.PriceRabatt15 AS SalesOrderHeader_PriceRabatt15, \r\n T.Total AS " + + "SalesOrderHeader_Total, \r\n T.Discount AS SalesOrderHeader_Discount, \r" + + "\n T.Rabatt AS SalesOrderHeader_Rabatt, \r\n T.SendDate AS Sa" + + "lesOrderHeader_SendDate, \r\n T.ID AS SalesOrderHeader_ID, \r\n " + + " T.Saison AS SalesOrderHeader_Saison, \r\n True AS _from0, \r\n " + + " T.OrderID AS [SalesOrderHeader_OrderID_fkey.Customer.OrderID]\r\n FROM X" + + "mlTestStoreContainer.SalesOrderHeader AS T\r\n ) AS T1"); + } + else + { + if ((index == 4)) + { + // return view for XmlTestContext.SalesOrderHeader_OrderID_fkey + return new System.Collections.Generic.KeyValuePair("XmlTestContext.SalesOrderHeader_OrderID_fkey", @" + SELECT VALUE -- Constructing SalesOrderHeader_OrderID_fkey + XmlTest.SalesOrderHeader_OrderID_fkey(T3.[SalesOrderHeader_OrderID_fkey.Customer], T3.[SalesOrderHeader_OrderID_fkey.SalesOrderHeader]) + FROM ( + SELECT -- Constructing Customer + CreateRef(XmlTestContext.Customer, row(T2.[SalesOrderHeader_OrderID_fkey.Customer.OrderID]),XmlTest.Customer) AS [SalesOrderHeader_OrderID_fkey.Customer], + T2.[SalesOrderHeader_OrderID_fkey.SalesOrderHeader] + FROM ( + SELECT -- Constructing SalesOrderHeader + CreateRef(XmlTestContext.SalesOrderHeader, row(T1.[SalesOrderHeader_OrderID_fkey.SalesOrderHeader.ID]),XmlTest.SalesOrderHeader) AS [SalesOrderHeader_OrderID_fkey.SalesOrderHeader], + T1.[SalesOrderHeader_OrderID_fkey.Customer.OrderID] + FROM ( + SELECT + T.OrderID AS [SalesOrderHeader_OrderID_fkey.Customer.OrderID], + T.ID AS [SalesOrderHeader_OrderID_fkey.SalesOrderHeader.ID], + True AS _from0 + FROM XmlTestStoreContainer.SalesOrderHeader AS T + ) AS T1 + ) AS T2 + ) AS T3"); + } + else + { + if ((index == 5)) + { + // return view for XmlTestStoreContainer.User + return new System.Collections.Generic.KeyValuePair("XmlTestStoreContainer.User", @" + SELECT VALUE -- Constructing User + [XmlTest.Store].User(T1.User_UserId, T1.User_Name, T1.User_Login, T1.User_StatusId) + FROM ( + SELECT + T.UserId AS User_UserId, + T.Name AS User_Name, + T.Login AS User_Login, + T.StatusId AS User_StatusId, + True AS _from0 + FROM XmlTestContext.User AS T + ) AS T1"); + } + else + { + if ((index == 6)) + { + // return view for XmlTestStoreContainer.UserDetails + return new System.Collections.Generic.KeyValuePair("XmlTestStoreContainer.UserDetails", @" + SELECT VALUE -- Constructing UserDetails + [XmlTest.Store].UserDetails(T1.UserDetails_UserId, T1.UserDetails_Details) + FROM ( + SELECT + T.UserId AS UserDetails_UserId, + T.Details AS UserDetails_Details, + True AS _from0 + FROM XmlTestContext.UserDetails AS T + ) AS T1"); + } + else + { + if ((index == 7)) + { + // return view for XmlTestStoreContainer.UserToken + return new System.Collections.Generic.KeyValuePair("XmlTestStoreContainer.UserToken", @" + SELECT VALUE -- Constructing UserToken + [XmlTest.Store].UserToken(T1.UserToken_UserId, T1.UserToken_Token) + FROM ( + SELECT + T.UserId AS UserToken_UserId, + T.Token AS UserToken_Token, + True AS _from0 + FROM XmlTestContext.UserToken AS T + ) AS T1"); + } + else + { + if ((index == 8)) + { + // return view for XmlTestContext.User + return new System.Collections.Generic.KeyValuePair("XmlTestContext.User", @" + SELECT VALUE -- Constructing User + XmlTest.User(T1.User_UserId, T1.User_Name, T1.User_Login, T1.User_StatusId) + FROM ( + SELECT + T.UserId AS User_UserId, + T.Name AS User_Name, + T.Login AS User_Login, + T.StatusId AS User_StatusId, + True AS _from0 + FROM XmlTestStoreContainer.User AS T + ) AS T1"); + } + else + { + if ((index == 9)) + { + // return view for XmlTestContext.UserDetails + return new System.Collections.Generic.KeyValuePair("XmlTestContext.UserDetails", @" + SELECT VALUE -- Constructing UserDetails + XmlTest.UserDetails(T1.UserDetails_UserId, T1.UserDetails_Details) + FROM ( + SELECT + T.UserId AS UserDetails_UserId, + T.Details AS UserDetails_Details, + True AS _from0, + T.UserId AS [UserDetails_FK.UserDetails.UserId] + FROM XmlTestStoreContainer.UserDetails AS T + ) AS T1"); + } + else + { + if ((index == 10)) + { + // return view for XmlTestContext.UserToken + return new System.Collections.Generic.KeyValuePair("XmlTestContext.UserToken", @" + SELECT VALUE -- Constructing UserToken + XmlTest.UserToken(T1.UserToken_UserId, T1.UserToken_Token) + FROM ( + SELECT + T.UserId AS UserToken_UserId, + T.Token AS UserToken_Token, + True AS _from0, + T.UserId AS [UserToken_FK.UserToken.UserId] + FROM XmlTestStoreContainer.UserToken AS T + ) AS T1"); + } + else + { + if ((index == 11)) + { + // return view for XmlTestContext.UserDetails_FK + return new System.Collections.Generic.KeyValuePair("XmlTestContext.UserDetails_FK", @" + SELECT VALUE -- Constructing UserDetails_FK + XmlTest.UserDetails_FK(T3.[UserDetails_FK.User], T3.[UserDetails_FK.UserDetails]) + FROM ( + SELECT -- Constructing User + CreateRef(XmlTestContext.User, row(T2.[UserDetails_FK.User.UserId]),XmlTest.User) AS [UserDetails_FK.User], + T2.[UserDetails_FK.UserDetails] + FROM ( + SELECT -- Constructing UserDetails + CreateRef(XmlTestContext.UserDetails, row(T1.[UserDetails_FK.UserDetails.UserId]),XmlTest.UserDetails) AS [UserDetails_FK.UserDetails], + T1.[UserDetails_FK.User.UserId] + FROM ( + SELECT + T.UserId AS [UserDetails_FK.User.UserId], + T.UserId AS [UserDetails_FK.UserDetails.UserId], + True AS _from0 + FROM XmlTestStoreContainer.UserDetails AS T + ) AS T1 + ) AS T2 + ) AS T3"); + } + else + { + if ((index == 12)) + { + // return view for XmlTestContext.UserToken_FK + return new System.Collections.Generic.KeyValuePair("XmlTestContext.UserToken_FK", @" + SELECT VALUE -- Constructing UserToken_FK + XmlTest.UserToken_FK(T3.[UserToken_FK.User], T3.[UserToken_FK.UserToken]) + FROM ( + SELECT -- Constructing User + CreateRef(XmlTestContext.User, row(T2.[UserToken_FK.User.UserId]),XmlTest.User) AS [UserToken_FK.User], + T2.[UserToken_FK.UserToken] + FROM ( + SELECT -- Constructing UserToken + CreateRef(XmlTestContext.UserToken, row(T1.[UserToken_FK.UserToken.UserId]),XmlTest.UserToken) AS [UserToken_FK.UserToken], + T1.[UserToken_FK.User.UserId] + FROM ( + SELECT + T.UserId AS [UserToken_FK.User.UserId], + T.UserId AS [UserToken_FK.UserToken.UserId], + True AS _from0 + FROM XmlTestStoreContainer.UserToken AS T + ) AS T1 + ) AS T2 + ) AS T3"); + } + else + { + if ((index == 13)) + { + // return view for XmlTestStoreContainer.XmlTable + return new System.Collections.Generic.KeyValuePair("XmlTestStoreContainer.XmlTable", @" + SELECT VALUE -- Constructing XmlTable + [XmlTest.Store].XmlTable(T1.XmlTable_key, T1.[XmlTable.test_xml]) + FROM ( + SELECT + T.[key] AS XmlTable_key, + T.test_xml AS [XmlTable.test_xml], + True AS _from0 + FROM XmlTestContext.XmlTable AS T + ) AS T1"); + } + else + { + if ((index == 14)) + { + // return view for XmlTestContext.XmlTable + return new System.Collections.Generic.KeyValuePair("XmlTestContext.XmlTable", @" + SELECT VALUE -- Constructing XmlTable + XmlTest.XmlTable(T1.XmlTable_key, T1.[XmlTable.test_xml]) + FROM ( + SELECT + T.[key] AS XmlTable_key, + T.test_xml AS [XmlTable.test_xml], + True AS _from0 + FROM XmlTestStoreContainer.XmlTable AS T + ) AS T1"); + } + else + { + if ((index == 15)) + { + // return view for XmlTestStoreContainer.dispViews + return new System.Collections.Generic.KeyValuePair("XmlTestStoreContainer.dispViews", @" + SELECT VALUE -- Constructing dispViews + [XmlTest.Store].dispViews(T1.dispViews_ViewName, T1.dispViews_MdsIdPlatformId, T1.dispViews_DisplayName, T1.dispViews_ImageFileName) + FROM ( + SELECT + T.ViewName AS dispViews_ViewName, + T.MdsIdPlatformId AS dispViews_MdsIdPlatformId, + T.DisplayName AS dispViews_DisplayName, + T.ImageFileName AS dispViews_ImageFileName, + True AS _from0 + FROM XmlTestContext.dispViews AS T + ) AS T1"); + } + else + { + if ((index == 16)) + { + // return view for XmlTestStoreContainer.dispTargetViews + return new System.Collections.Generic.KeyValuePair("XmlTestStoreContainer.dispTargetViews", @" + SELECT VALUE -- Constructing dispTargetViews + [XmlTest.Store].dispTargetViews(T1.dispTargetViews_ViewName, T1.dispTargetViews_MdsIdPlatformId, T1.dispTargetViews_TargetViewName, T1.dispTargetViews_TargetMdsIdPlatformId) + FROM ( + SELECT + Key(T.dispViews).ViewName AS dispTargetViews_ViewName, + Key(T.dispViews).MdsIdPlatformId AS dispTargetViews_MdsIdPlatformId, + Key(T.dispViews1).ViewName AS dispTargetViews_TargetViewName, + Key(T.dispViews1).MdsIdPlatformId AS dispTargetViews_TargetMdsIdPlatformId, + True AS _from0 + FROM XmlTestContext.dispTargetViews AS T + ) AS T1"); + } + else + { + if ((index == 17)) + { + // return view for XmlTestContext.dispViews + return new System.Collections.Generic.KeyValuePair("XmlTestContext.dispViews", @" + SELECT VALUE -- Constructing dispViews + XmlTest.dispViews(T1.dispViews_ViewName, T1.dispViews_MdsIdPlatformId, T1.dispViews_DisplayName, T1.dispViews_ImageFileName) + FROM ( + SELECT + T.ViewName AS dispViews_ViewName, + T.MdsIdPlatformId AS dispViews_MdsIdPlatformId, + T.DisplayName AS dispViews_DisplayName, + T.ImageFileName AS dispViews_ImageFileName, + True AS _from0 + FROM XmlTestStoreContainer.dispViews AS T + ) AS T1"); + } + else + { + if ((index == 18)) + { + // return view for XmlTestContext.dispTargetViews + return new System.Collections.Generic.KeyValuePair("XmlTestContext.dispTargetViews", @" + SELECT VALUE -- Constructing dispTargetViews + XmlTest.dispTargetViews(T3.dispTargetViews_dispViews, T3.dispTargetViews_dispViews1) + FROM ( + SELECT -- Constructing dispViews + CreateRef(XmlTestContext.dispViews, row(T2.dispTargetViews_dispViews_ViewName, T2.dispTargetViews_dispViews_MdsIdPlatformId),XmlTest.dispViews) AS dispTargetViews_dispViews, + T2.dispTargetViews_dispViews1 + FROM ( + SELECT -- Constructing dispViews1 + CreateRef(XmlTestContext.dispViews, row(T1.dispTargetViews_dispViews1_ViewName, T1.dispTargetViews_dispViews1_MdsIdPlatformId),XmlTest.dispViews) AS dispTargetViews_dispViews1, + T1.dispTargetViews_dispViews_ViewName, T1.dispTargetViews_dispViews_MdsIdPlatformId + FROM ( + SELECT + T.ViewName AS dispTargetViews_dispViews_ViewName, + T.MdsIdPlatformId AS dispTargetViews_dispViews_MdsIdPlatformId, + T.TargetViewName AS dispTargetViews_dispViews1_ViewName, + T.TargetMdsIdPlatformId AS dispTargetViews_dispViews1_MdsIdPlatformId, + True AS _from0 + FROM XmlTestStoreContainer.dispTargetViews AS T + ) AS T1 + ) AS T2 + ) AS T3"); + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + throw new System.IndexOutOfRangeException(); + } + } +} diff --git a/test/EntityFramework6.Npgsql.Tests/xmlModel/XmlTest.Views.cs b/test/EntityFramework6.Npgsql.Tests/xmlModel/XmlTest.Views.cs new file mode 100644 index 0000000..846f846 --- /dev/null +++ b/test/EntityFramework6.Npgsql.Tests/xmlModel/XmlTest.Views.cs @@ -0,0 +1,488 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +#if ENTITIES +[assembly: System.Data.Mapping.EntityViewGenerationAttribute(typeof(Edm_EntityMappingGeneratedViews.ViewsForBaseEntitySets32CD194B04BDC9F607C67C8FAB6BC4DC))] + +namespace Edm_EntityMappingGeneratedViews +{ + + /// + /// The type contains views for EntitySets and AssociationSets that were generated at design time. + /// + public sealed class ViewsForBaseEntitySets32CD194B04BDC9F607C67C8FAB6BC4DC : System.Data.Mapping.EntityViewContainer + { + + /// + /// The constructor stores the views for the extents and also the hash values generated based on the metadata and mapping closure and views + /// + public ViewsForBaseEntitySets32CD194B04BDC9F607C67C8FAB6BC4DC() + { + this.EdmEntityContainerName = "XmlTestContext"; + this.StoreEntityContainerName = "XmlTestStoreContainer"; + this.HashOverMappingClosure = "f1b01cb63d8b711866cfe7ecbfa9367e"; + this.HashOverAllExtentViews = "b0c3118d79b4c037f19c708219fdab28"; + this.ViewCount = 19; + } + + // The method returns the view for the index given. + protected override System.Collections.Generic.KeyValuePair GetViewAt(int index) + { + if ((index == 0)) + { + // return view for XmlTestStoreContainer.Customer + return new System.Collections.Generic.KeyValuePair("XmlTestStoreContainer.Customer", "\r\n SELECT VALUE -- Constructing Customer\r\n [XmlTest.Store].Customer(T1." + + "Customer_OrderID, T1.Customer_NameStyle, T1.Customer_FirstName, T1.Customer_Last" + + "Name, T1.Customer_Active, T1.Customer_ModifiedDate, T1.Customer_TourNumber, T1.C" + + "ustomer_ExternalNumber, T1.Customer_MainPhone1, T1.Customer_MainPhone2, T1.Custo" + + "mer_PreOrderID, T1.Customer_LastVisit, T1.Customer_Created, T1.Customer_External" + + "Name, T1.Customer_NewCustomer, T1.Customer_Potential, T1.Customer_CustomerID, T1" + + ".Customer_OldExternalNumber, T1.Customer_OldTourNumber, T1.Customer_NewAssigned," + + " T1.Customer_OldActive, T1.Customer_StartDate, T1.Customer_Total)\r\n FROM (\r\n " + + " SELECT \r\n T.OrderID AS Customer_OrderID, \r\n T.NameS" + + "tyle AS Customer_NameStyle, \r\n T.FirstName AS Customer_FirstName, \r\n " + + " T.LastName AS Customer_LastName, \r\n T.Active AS Customer_A" + + "ctive, \r\n T.ModifiedDate AS Customer_ModifiedDate, \r\n T.To" + + "urNumber AS Customer_TourNumber, \r\n T.ExternalNumber AS Customer_Exte" + + "rnalNumber, \r\n T.MainPhone1 AS Customer_MainPhone1, \r\n T.M" + + "ainPhone2 AS Customer_MainPhone2, \r\n T.PreOrderID AS Customer_PreOrde" + + "rID, \r\n T.LastVisit AS Customer_LastVisit, \r\n T.Created AS" + + " Customer_Created, \r\n T.ExternalName AS Customer_ExternalName, \r\n " + + " T.NewCustomer AS Customer_NewCustomer, \r\n T.Potential AS Cust" + + "omer_Potential, \r\n T.CustomerID AS Customer_CustomerID, \r\n " + + " T.OldExternalNumber AS Customer_OldExternalNumber, \r\n T.OldTourNumbe" + + "r AS Customer_OldTourNumber, \r\n T.NewAssigned AS Customer_NewAssigned" + + ", \r\n T.OldActive AS Customer_OldActive, \r\n T.StartDate AS " + + "Customer_StartDate, \r\n T.Total AS Customer_Total, \r\n True " + + "AS _from0\r\n FROM XmlTestContext.Customer AS T\r\n ) AS T1"); + } + else + { + if ((index == 1)) + { + // return view for XmlTestStoreContainer.SalesOrderHeader + return new System.Collections.Generic.KeyValuePair("XmlTestStoreContainer.SalesOrderHeader", "\r\n SELECT VALUE -- Constructing SalesOrderHeader\r\n [XmlTest.Store].Sale" + + "sOrderHeader(T3.SalesOrderHeader_OrderDate, T3.SalesOrderHeader_Status, T3.Sales" + + "OrderHeader_BillToAddressID, T3.SalesOrderHeader_SubTotal, T3.SalesOrderHeader_T" + + "otalDue, T3.SalesOrderHeader_Comment, T3.SalesOrderHeader_ModifiedDate, T3.Sales" + + "OrderHeader_PaymentVersion, T3.SalesOrderHeader_BillID, T3.SalesOrderHeader_Book" + + "Counter, T3.SalesOrderHeader_SendID, T3.SalesOrderHeader_SubSubTotal, T3.SalesOr" + + "derHeader_PriceRabatt15, T3.SalesOrderHeader_Total, T3.SalesOrderHeader_Discount" + + ", T3.SalesOrderHeader_Rabatt, T3.SalesOrderHeader_OrderID, T3.SalesOrderHeader_S" + + "endDate, T3.SalesOrderHeader_ID, T3.SalesOrderHeader_Saison)\r\n FROM (\r\n " + + " SELECT T2.SalesOrderHeader_OrderDate, T2.SalesOrderHeader_Status, T2.SalesOrde" + + "rHeader_BillToAddressID, T2.SalesOrderHeader_SubTotal, T2.SalesOrderHeader_Total" + + "Due, T2.SalesOrderHeader_Comment, T2.SalesOrderHeader_ModifiedDate, T2.SalesOrde" + + "rHeader_PaymentVersion, T2.SalesOrderHeader_BillID, T2.SalesOrderHeader_BookCoun" + + "ter, T2.SalesOrderHeader_SendID, T2.SalesOrderHeader_SubSubTotal, T2.SalesOrderH" + + "eader_PriceRabatt15, T2.SalesOrderHeader_Total, T2.SalesOrderHeader_Discount, T2" + + ".SalesOrderHeader_Rabatt, T1.SalesOrderHeader_OrderID, T2.SalesOrderHeader_SendD" + + "ate, T1.SalesOrderHeader_ID, T2.SalesOrderHeader_Saison, T2._from0, T1._from1\r\n " + + " FROM (\r\n SELECT \r\n Key(T.Customer).OrderID AS " + + "SalesOrderHeader_OrderID, \r\n Key(T.SalesOrderHeader).ID AS SalesO" + + "rderHeader_ID, \r\n True AS _from1\r\n FROM XmlTestContext" + + ".SalesOrderHeader_OrderID_fkey AS T) AS T1\r\n INNER JOIN (\r\n " + + " SELECT \r\n T.OrderDate AS SalesOrderHeader_OrderDate, \r\n " + + " T.Status AS SalesOrderHeader_Status, \r\n T.BillToAddressID" + + " AS SalesOrderHeader_BillToAddressID, \r\n T.SubTotal AS SalesOrder" + + "Header_SubTotal, \r\n T.TotalDue AS SalesOrderHeader_TotalDue, \r\n " + + " T.Comment AS SalesOrderHeader_Comment, \r\n T.Modifie" + + "dDate AS SalesOrderHeader_ModifiedDate, \r\n T.PaymentVersion AS Sa" + + "lesOrderHeader_PaymentVersion, \r\n T.BillID AS SalesOrderHeader_Bi" + + "llID, \r\n T.BookCounter AS SalesOrderHeader_BookCounter, \r\n " + + " T.SendID AS SalesOrderHeader_SendID, \r\n T.SubSubTotal AS" + + " SalesOrderHeader_SubSubTotal, \r\n T.PriceRabatt15 AS SalesOrderHe" + + "ader_PriceRabatt15, \r\n T.Total AS SalesOrderHeader_Total, \r\n " + + " T.Discount AS SalesOrderHeader_Discount, \r\n T.Rabatt A" + + "S SalesOrderHeader_Rabatt, \r\n T.SendDate AS SalesOrderHeader_Send" + + "Date, \r\n T.ID AS SalesOrderHeader_ID, \r\n T.Saison " + + "AS SalesOrderHeader_Saison, \r\n True AS _from0\r\n FROM X" + + "mlTestContext.SalesOrderHeader AS T) AS T2\r\n ON T1.SalesOrderHeader_I" + + "D = T2.SalesOrderHeader_ID\r\n ) AS T3"); + } + else + { + if ((index == 2)) + { + // return view for XmlTestContext.Customer + return new System.Collections.Generic.KeyValuePair("XmlTestContext.Customer", "\r\n SELECT VALUE -- Constructing Customer\r\n XmlTest.Customer(T1.Customer" + + "_OrderID, T1.Customer_NameStyle, T1.Customer_FirstName, T1.Customer_LastName, T1" + + ".Customer_Active, T1.Customer_ModifiedDate, T1.Customer_TourNumber, T1.Customer_" + + "ExternalNumber, T1.Customer_MainPhone1, T1.Customer_MainPhone2, T1.Customer_PreO" + + "rderID, T1.Customer_LastVisit, T1.Customer_Created, T1.Customer_ExternalName, T1" + + ".Customer_NewCustomer, T1.Customer_Potential, T1.Customer_CustomerID, T1.Custome" + + "r_OldExternalNumber, T1.Customer_OldTourNumber, T1.Customer_NewAssigned, T1.Cust" + + "omer_OldActive, T1.Customer_StartDate, T1.Customer_Total)\r\n FROM (\r\n S" + + "ELECT \r\n T.OrderID AS Customer_OrderID, \r\n T.NameStyle AS " + + "Customer_NameStyle, \r\n T.FirstName AS Customer_FirstName, \r\n " + + " T.LastName AS Customer_LastName, \r\n T.Active AS Customer_Active, \r" + + "\n T.ModifiedDate AS Customer_ModifiedDate, \r\n T.TourNumber" + + " AS Customer_TourNumber, \r\n T.ExternalNumber AS Customer_ExternalNumb" + + "er, \r\n T.MainPhone1 AS Customer_MainPhone1, \r\n T.MainPhone" + + "2 AS Customer_MainPhone2, \r\n T.PreOrderID AS Customer_PreOrderID, \r\n " + + " T.LastVisit AS Customer_LastVisit, \r\n T.Created AS Custome" + + "r_Created, \r\n T.ExternalName AS Customer_ExternalName, \r\n " + + "T.NewCustomer AS Customer_NewCustomer, \r\n T.Potential AS Customer_Pot" + + "ential, \r\n T.CustomerID AS Customer_CustomerID, \r\n T.OldEx" + + "ternalNumber AS Customer_OldExternalNumber, \r\n T.OldTourNumber AS Cus" + + "tomer_OldTourNumber, \r\n T.NewAssigned AS Customer_NewAssigned, \r\n " + + " T.OldActive AS Customer_OldActive, \r\n T.StartDate AS Customer" + + "_StartDate, \r\n T.Total AS Customer_Total, \r\n True AS _from" + + "0\r\n FROM XmlTestStoreContainer.Customer AS T\r\n ) AS T1"); + } + else + { + if ((index == 3)) + { + // return view for XmlTestContext.SalesOrderHeader + return new System.Collections.Generic.KeyValuePair("XmlTestContext.SalesOrderHeader", "\r\n SELECT VALUE -- Constructing SalesOrderHeader\r\n XmlTest.SalesOrderHe" + + "ader(T1.SalesOrderHeader_OrderDate, T1.SalesOrderHeader_Status, T1.SalesOrderHea" + + "der_BillToAddressID, T1.SalesOrderHeader_SubTotal, T1.SalesOrderHeader_TotalDue," + + " T1.SalesOrderHeader_Comment, T1.SalesOrderHeader_ModifiedDate, T1.SalesOrderHea" + + "der_PaymentVersion, T1.SalesOrderHeader_BillID, T1.SalesOrderHeader_BookCounter," + + " T1.SalesOrderHeader_SendID, T1.SalesOrderHeader_SubSubTotal, T1.SalesOrderHeade" + + "r_PriceRabatt15, T1.SalesOrderHeader_Total, T1.SalesOrderHeader_Discount, T1.Sal" + + "esOrderHeader_Rabatt, T1.SalesOrderHeader_SendDate, T1.SalesOrderHeader_ID, T1.S" + + "alesOrderHeader_Saison) WITH \r\n RELATIONSHIP(CREATEREF(XmlTestContext.Cus" + + "tomer, ROW(T1.[SalesOrderHeader_OrderID_fkey.Customer.OrderID]),XmlTest.Customer" + + "),XmlTest.SalesOrderHeader_OrderID_fkey,SalesOrderHeader,Customer) \r\n FROM (\r" + + "\n SELECT \r\n T.OrderDate AS SalesOrderHeader_OrderDate, \r\n " + + " T.Status AS SalesOrderHeader_Status, \r\n T.BillToAddressID AS S" + + "alesOrderHeader_BillToAddressID, \r\n T.SubTotal AS SalesOrderHeader_Su" + + "bTotal, \r\n T.TotalDue AS SalesOrderHeader_TotalDue, \r\n T.C" + + "omment AS SalesOrderHeader_Comment, \r\n T.ModifiedDate AS SalesOrderHe" + + "ader_ModifiedDate, \r\n T.PaymentVersion AS SalesOrderHeader_PaymentVer" + + "sion, \r\n T.BillID AS SalesOrderHeader_BillID, \r\n T.BookCou" + + "nter AS SalesOrderHeader_BookCounter, \r\n T.SendID AS SalesOrderHeader" + + "_SendID, \r\n T.SubSubTotal AS SalesOrderHeader_SubSubTotal, \r\n " + + " T.PriceRabatt15 AS SalesOrderHeader_PriceRabatt15, \r\n T.Total AS " + + "SalesOrderHeader_Total, \r\n T.Discount AS SalesOrderHeader_Discount, \r" + + "\n T.Rabatt AS SalesOrderHeader_Rabatt, \r\n T.SendDate AS Sa" + + "lesOrderHeader_SendDate, \r\n T.ID AS SalesOrderHeader_ID, \r\n " + + " T.Saison AS SalesOrderHeader_Saison, \r\n True AS _from0, \r\n " + + " T.OrderID AS [SalesOrderHeader_OrderID_fkey.Customer.OrderID]\r\n FROM X" + + "mlTestStoreContainer.SalesOrderHeader AS T\r\n ) AS T1"); + } + else + { + if ((index == 4)) + { + // return view for XmlTestContext.SalesOrderHeader_OrderID_fkey + return new System.Collections.Generic.KeyValuePair("XmlTestContext.SalesOrderHeader_OrderID_fkey", @" + SELECT VALUE -- Constructing SalesOrderHeader_OrderID_fkey + XmlTest.SalesOrderHeader_OrderID_fkey(T3.[SalesOrderHeader_OrderID_fkey.Customer], T3.[SalesOrderHeader_OrderID_fkey.SalesOrderHeader]) + FROM ( + SELECT -- Constructing Customer + CreateRef(XmlTestContext.Customer, row(T2.[SalesOrderHeader_OrderID_fkey.Customer.OrderID]),XmlTest.Customer) AS [SalesOrderHeader_OrderID_fkey.Customer], + T2.[SalesOrderHeader_OrderID_fkey.SalesOrderHeader] + FROM ( + SELECT -- Constructing SalesOrderHeader + CreateRef(XmlTestContext.SalesOrderHeader, row(T1.[SalesOrderHeader_OrderID_fkey.SalesOrderHeader.ID]),XmlTest.SalesOrderHeader) AS [SalesOrderHeader_OrderID_fkey.SalesOrderHeader], + T1.[SalesOrderHeader_OrderID_fkey.Customer.OrderID] + FROM ( + SELECT + T.OrderID AS [SalesOrderHeader_OrderID_fkey.Customer.OrderID], + T.ID AS [SalesOrderHeader_OrderID_fkey.SalesOrderHeader.ID], + True AS _from0 + FROM XmlTestStoreContainer.SalesOrderHeader AS T + ) AS T1 + ) AS T2 + ) AS T3"); + } + else + { + if ((index == 5)) + { + // return view for XmlTestStoreContainer.User + return new System.Collections.Generic.KeyValuePair("XmlTestStoreContainer.User", @" + SELECT VALUE -- Constructing User + [XmlTest.Store].User(T1.User_UserId, T1.User_Name, T1.User_Login, T1.User_StatusId) + FROM ( + SELECT + T.UserId AS User_UserId, + T.Name AS User_Name, + T.Login AS User_Login, + T.StatusId AS User_StatusId, + True AS _from0 + FROM XmlTestContext.User AS T + ) AS T1"); + } + else + { + if ((index == 6)) + { + // return view for XmlTestStoreContainer.UserDetails + return new System.Collections.Generic.KeyValuePair("XmlTestStoreContainer.UserDetails", @" + SELECT VALUE -- Constructing UserDetails + [XmlTest.Store].UserDetails(T1.UserDetails_UserId, T1.UserDetails_Details) + FROM ( + SELECT + T.UserId AS UserDetails_UserId, + T.Details AS UserDetails_Details, + True AS _from0 + FROM XmlTestContext.UserDetails AS T + ) AS T1"); + } + else + { + if ((index == 7)) + { + // return view for XmlTestStoreContainer.UserToken + return new System.Collections.Generic.KeyValuePair("XmlTestStoreContainer.UserToken", @" + SELECT VALUE -- Constructing UserToken + [XmlTest.Store].UserToken(T1.UserToken_UserId, T1.UserToken_Token) + FROM ( + SELECT + T.UserId AS UserToken_UserId, + T.Token AS UserToken_Token, + True AS _from0 + FROM XmlTestContext.UserToken AS T + ) AS T1"); + } + else + { + if ((index == 8)) + { + // return view for XmlTestContext.User + return new System.Collections.Generic.KeyValuePair("XmlTestContext.User", @" + SELECT VALUE -- Constructing User + XmlTest.User(T1.User_UserId, T1.User_Name, T1.User_Login, T1.User_StatusId) + FROM ( + SELECT + T.UserId AS User_UserId, + T.Name AS User_Name, + T.Login AS User_Login, + T.StatusId AS User_StatusId, + True AS _from0 + FROM XmlTestStoreContainer.User AS T + ) AS T1"); + } + else + { + if ((index == 9)) + { + // return view for XmlTestContext.UserDetails + return new System.Collections.Generic.KeyValuePair("XmlTestContext.UserDetails", @" + SELECT VALUE -- Constructing UserDetails + XmlTest.UserDetails(T1.UserDetails_UserId, T1.UserDetails_Details) + FROM ( + SELECT + T.UserId AS UserDetails_UserId, + T.Details AS UserDetails_Details, + True AS _from0, + T.UserId AS [UserDetails_FK.UserDetails.UserId] + FROM XmlTestStoreContainer.UserDetails AS T + ) AS T1"); + } + else + { + if ((index == 10)) + { + // return view for XmlTestContext.UserToken + return new System.Collections.Generic.KeyValuePair("XmlTestContext.UserToken", @" + SELECT VALUE -- Constructing UserToken + XmlTest.UserToken(T1.UserToken_UserId, T1.UserToken_Token) + FROM ( + SELECT + T.UserId AS UserToken_UserId, + T.Token AS UserToken_Token, + True AS _from0, + T.UserId AS [UserToken_FK.UserToken.UserId] + FROM XmlTestStoreContainer.UserToken AS T + ) AS T1"); + } + else + { + if ((index == 11)) + { + // return view for XmlTestContext.UserDetails_FK + return new System.Collections.Generic.KeyValuePair("XmlTestContext.UserDetails_FK", @" + SELECT VALUE -- Constructing UserDetails_FK + XmlTest.UserDetails_FK(T3.[UserDetails_FK.User], T3.[UserDetails_FK.UserDetails]) + FROM ( + SELECT -- Constructing User + CreateRef(XmlTestContext.User, row(T2.[UserDetails_FK.User.UserId]),XmlTest.User) AS [UserDetails_FK.User], + T2.[UserDetails_FK.UserDetails] + FROM ( + SELECT -- Constructing UserDetails + CreateRef(XmlTestContext.UserDetails, row(T1.[UserDetails_FK.UserDetails.UserId]),XmlTest.UserDetails) AS [UserDetails_FK.UserDetails], + T1.[UserDetails_FK.User.UserId] + FROM ( + SELECT + T.UserId AS [UserDetails_FK.User.UserId], + T.UserId AS [UserDetails_FK.UserDetails.UserId], + True AS _from0 + FROM XmlTestStoreContainer.UserDetails AS T + ) AS T1 + ) AS T2 + ) AS T3"); + } + else + { + if ((index == 12)) + { + // return view for XmlTestContext.UserToken_FK + return new System.Collections.Generic.KeyValuePair("XmlTestContext.UserToken_FK", @" + SELECT VALUE -- Constructing UserToken_FK + XmlTest.UserToken_FK(T3.[UserToken_FK.User], T3.[UserToken_FK.UserToken]) + FROM ( + SELECT -- Constructing User + CreateRef(XmlTestContext.User, row(T2.[UserToken_FK.User.UserId]),XmlTest.User) AS [UserToken_FK.User], + T2.[UserToken_FK.UserToken] + FROM ( + SELECT -- Constructing UserToken + CreateRef(XmlTestContext.UserToken, row(T1.[UserToken_FK.UserToken.UserId]),XmlTest.UserToken) AS [UserToken_FK.UserToken], + T1.[UserToken_FK.User.UserId] + FROM ( + SELECT + T.UserId AS [UserToken_FK.User.UserId], + T.UserId AS [UserToken_FK.UserToken.UserId], + True AS _from0 + FROM XmlTestStoreContainer.UserToken AS T + ) AS T1 + ) AS T2 + ) AS T3"); + } + else + { + if ((index == 13)) + { + // return view for XmlTestStoreContainer.XmlTable + return new System.Collections.Generic.KeyValuePair("XmlTestStoreContainer.XmlTable", @" + SELECT VALUE -- Constructing XmlTable + [XmlTest.Store].XmlTable(T1.XmlTable_key, T1.[XmlTable.test_xml]) + FROM ( + SELECT + T.[key] AS XmlTable_key, + T.test_xml AS [XmlTable.test_xml], + True AS _from0 + FROM XmlTestContext.XmlTable AS T + ) AS T1"); + } + else + { + if ((index == 14)) + { + // return view for XmlTestContext.XmlTable + return new System.Collections.Generic.KeyValuePair("XmlTestContext.XmlTable", @" + SELECT VALUE -- Constructing XmlTable + XmlTest.XmlTable(T1.XmlTable_key, T1.[XmlTable.test_xml]) + FROM ( + SELECT + T.[key] AS XmlTable_key, + T.test_xml AS [XmlTable.test_xml], + True AS _from0 + FROM XmlTestStoreContainer.XmlTable AS T + ) AS T1"); + } + else + { + if ((index == 15)) + { + // return view for XmlTestStoreContainer.dispViews + return new System.Collections.Generic.KeyValuePair("XmlTestStoreContainer.dispViews", @" + SELECT VALUE -- Constructing dispViews + [XmlTest.Store].dispViews(T1.dispViews_ViewName, T1.dispViews_MdsIdPlatformId, T1.dispViews_DisplayName, T1.dispViews_ImageFileName) + FROM ( + SELECT + T.ViewName AS dispViews_ViewName, + T.MdsIdPlatformId AS dispViews_MdsIdPlatformId, + T.DisplayName AS dispViews_DisplayName, + T.ImageFileName AS dispViews_ImageFileName, + True AS _from0 + FROM XmlTestContext.dispViews AS T + ) AS T1"); + } + else + { + if ((index == 16)) + { + // return view for XmlTestStoreContainer.dispTargetViews + return new System.Collections.Generic.KeyValuePair("XmlTestStoreContainer.dispTargetViews", @" + SELECT VALUE -- Constructing dispTargetViews + [XmlTest.Store].dispTargetViews(T1.dispTargetViews_ViewName, T1.dispTargetViews_MdsIdPlatformId, T1.dispTargetViews_TargetViewName, T1.dispTargetViews_TargetMdsIdPlatformId) + FROM ( + SELECT + Key(T.dispViews).ViewName AS dispTargetViews_ViewName, + Key(T.dispViews).MdsIdPlatformId AS dispTargetViews_MdsIdPlatformId, + Key(T.dispViews1).ViewName AS dispTargetViews_TargetViewName, + Key(T.dispViews1).MdsIdPlatformId AS dispTargetViews_TargetMdsIdPlatformId, + True AS _from0 + FROM XmlTestContext.dispTargetViews AS T + ) AS T1"); + } + else + { + if ((index == 17)) + { + // return view for XmlTestContext.dispViews + return new System.Collections.Generic.KeyValuePair("XmlTestContext.dispViews", @" + SELECT VALUE -- Constructing dispViews + XmlTest.dispViews(T1.dispViews_ViewName, T1.dispViews_MdsIdPlatformId, T1.dispViews_DisplayName, T1.dispViews_ImageFileName) + FROM ( + SELECT + T.ViewName AS dispViews_ViewName, + T.MdsIdPlatformId AS dispViews_MdsIdPlatformId, + T.DisplayName AS dispViews_DisplayName, + T.ImageFileName AS dispViews_ImageFileName, + True AS _from0 + FROM XmlTestStoreContainer.dispViews AS T + ) AS T1"); + } + else + { + if ((index == 18)) + { + // return view for XmlTestContext.dispTargetViews + return new System.Collections.Generic.KeyValuePair("XmlTestContext.dispTargetViews", @" + SELECT VALUE -- Constructing dispTargetViews + XmlTest.dispTargetViews(T3.dispTargetViews_dispViews, T3.dispTargetViews_dispViews1) + FROM ( + SELECT -- Constructing dispViews + CreateRef(XmlTestContext.dispViews, row(T2.dispTargetViews_dispViews_ViewName, T2.dispTargetViews_dispViews_MdsIdPlatformId),XmlTest.dispViews) AS dispTargetViews_dispViews, + T2.dispTargetViews_dispViews1 + FROM ( + SELECT -- Constructing dispViews1 + CreateRef(XmlTestContext.dispViews, row(T1.dispTargetViews_dispViews1_ViewName, T1.dispTargetViews_dispViews1_MdsIdPlatformId),XmlTest.dispViews) AS dispTargetViews_dispViews1, + T1.dispTargetViews_dispViews_ViewName, T1.dispTargetViews_dispViews_MdsIdPlatformId + FROM ( + SELECT + T.ViewName AS dispTargetViews_dispViews_ViewName, + T.MdsIdPlatformId AS dispTargetViews_dispViews_MdsIdPlatformId, + T.TargetViewName AS dispTargetViews_dispViews1_ViewName, + T.TargetMdsIdPlatformId AS dispTargetViews_dispViews1_MdsIdPlatformId, + True AS _from0 + FROM XmlTestStoreContainer.dispTargetViews AS T + ) AS T1 + ) AS T2 + ) AS T3"); + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + throw new System.IndexOutOfRangeException(); + } + } +} +#endif