From 66081cc0204d699c9d711eb1dd8b129082f2aa77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Karla=C5=A1?= Date: Fri, 31 Jan 2014 18:24:09 +0100 Subject: [PATCH 01/10] Support for EFMigration and Database creation in EF6+ --- .../Npgsql.EntityFramework.csproj | 3 +- .../NpgsqlMigrationSqlGenerator.cs | 802 ++++++++++++++++++ .../NpgsqlProviderManifest.Manifest.xml | 6 + .../NpgsqlProviderManifest.cs | 10 +- Npgsql.EntityFramework/NpgsqlServices.cs | 86 +- Npgsql.EntityFramework/packages.config | 2 +- tests/App.config | 25 + tests/EntityFrameworkMigrationTests.cs | 757 +++++++++++++++++ tests/NpgsqlTests.csproj | 19 +- tests/packages.config | 1 + 10 files changed, 1687 insertions(+), 24 deletions(-) create mode 100644 Npgsql.EntityFramework/NpgsqlMigrationSqlGenerator.cs create mode 100644 tests/App.config create mode 100644 tests/EntityFrameworkMigrationTests.cs diff --git a/Npgsql.EntityFramework/Npgsql.EntityFramework.csproj b/Npgsql.EntityFramework/Npgsql.EntityFramework.csproj index b0599a4c54..613022465d 100644 --- a/Npgsql.EntityFramework/Npgsql.EntityFramework.csproj +++ b/Npgsql.EntityFramework/Npgsql.EntityFramework.csproj @@ -99,6 +99,7 @@ + @@ -123,4 +124,4 @@ - + \ No newline at end of file diff --git a/Npgsql.EntityFramework/NpgsqlMigrationSqlGenerator.cs b/Npgsql.EntityFramework/NpgsqlMigrationSqlGenerator.cs new file mode 100644 index 0000000000..78e5249275 --- /dev/null +++ b/Npgsql.EntityFramework/NpgsqlMigrationSqlGenerator.cs @@ -0,0 +1,802 @@ +// NpgsqlMigrationSqlGenerator.cs +// +// Author: +// David Karlaš (david.karlas@gmail.com) +// +// Copyright (C) 2014 David Karlaš +// +// 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. + +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; + +namespace Npgsql +{ + public class NpgsqlMigrationSqlGenerator : MigrationSqlGenerator + { + List migrationStatments; + private List addedSchemas; + private List addedExtensions; + private Version serverVersion; + + 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 + + private 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 + + private void Convert(HistoryOperation historyOperation) + { + foreach (var command in historyOperation.CommandTrees) + { + AddStatment(NpgsqlServices.Instance.CreateDbCommand(command).CommandText); + } + } + + #endregion + + #region Tables + + private void Convert(CreateTableOperation createTableOperation) + { + StringBuilder sql = new StringBuilder(); + CreateSchema(createTableOperation.Name); + sql.Append("CREATE TABLE "); + AppendTableName(createTableOperation.Name, sql); + sql.Append('('); + foreach (var column in createTableOperation.Columns) + { + AppendColumn(column, sql); + sql.Append(","); + } + 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); + } + + private void Convert(DropTableOperation dropTableOperation) + { + StringBuilder sql = new StringBuilder(); + sql.Append("DROP TABLE "); + AppendTableName(dropTableOperation.Name, sql); + AddStatment(sql); + } + + private 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) + { + int dotIndex = schemaName.IndexOf('.'); + if (dotIndex != -1) + schemaName = schemaName.Remove(dotIndex); + if (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 + "\""); + } + } + + private 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 + private 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); + } + + private 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); + } + + private 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"); + 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('"'); + } + + private 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 + + private 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); + } + + private 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); + } + + private 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; + } + + private 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); + } + + private 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); + } + + private 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 && column.Type == PrimitiveTypeKind.Guid) + { + CreateExtension("uuid-ossp"); + sql.Append(" DEFAULT uuid_generate_v4()"); + } + } + + 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: + //Seems like bug in EF 6.0.2 to set MaxLength to 1 instead of 0 if not specified + //http://entityframework.codeplex.com/workitem/1784 TODO: Investigate and put back to + //if (column.MaxLength != null && column.MaxLength > 0) + if (column.MaxLength != null && column.MaxLength > 1) + { + sql.Append("varchar("); + sql.Append(column.MaxLength); + sql.Append(")"); + } + 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) + { + 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(((NpgsqlTypes.NpgsqlTimeStamp)value).ToString()); + sql.Append("'"); + } + + private void AppendValue(DateTimeOffset value, StringBuilder sql) + { + sql.Append("'"); + sql.Append(((NpgsqlTypes.NpgsqlTimeStampTZ)value).ToString()); + 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.NpgsqlInterval(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/Npgsql.EntityFramework/NpgsqlProviderManifest.Manifest.xml b/Npgsql.EntityFramework/NpgsqlProviderManifest.Manifest.xml index 32e2698820..78e4d75a45 100644 --- a/Npgsql.EntityFramework/NpgsqlProviderManifest.Manifest.xml +++ b/Npgsql.EntityFramework/NpgsqlProviderManifest.Manifest.xml @@ -52,6 +52,12 @@ + + + + + + diff --git a/Npgsql.EntityFramework/NpgsqlProviderManifest.cs b/Npgsql.EntityFramework/NpgsqlProviderManifest.cs index 6ff4223075..dbef2dec4e 100644 --- a/Npgsql.EntityFramework/NpgsqlProviderManifest.cs +++ b/Npgsql.EntityFramework/NpgsqlProviderManifest.cs @@ -140,6 +140,10 @@ public override TypeUsage GetEdmType(TypeUsage storeType) } return TypeUsage.CreateBinaryTypeUsage(primitiveType, false); } + case "rowversion": + { + return TypeUsage.CreateBinaryTypeUsage(primitiveType, true, 8); + } //TypeUsage.CreateBinaryTypeUsage //TypeUsage.CreateDateTimeTypeUsage //TypeUsage.CreateDecimalTypeUsage @@ -256,9 +260,9 @@ public override TypeUsage GetStoreType(TypeUsage edmType) } case PrimitiveTypeKind.Guid: return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["uuid"]); - // notably missing - // PrimitiveTypeKind.Byte: - // PrimitiveTypeKind.SByte: + case PrimitiveTypeKind.Byte: + case PrimitiveTypeKind.SByte: + return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["int2"]); } throw new NotSupportedException(); diff --git a/Npgsql.EntityFramework/NpgsqlServices.cs b/Npgsql.EntityFramework/NpgsqlServices.cs index 10fb892b7f..ec5c586160 100644 --- a/Npgsql.EntityFramework/NpgsqlServices.cs +++ b/Npgsql.EntityFramework/NpgsqlServices.cs @@ -6,6 +6,8 @@ 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; @@ -18,6 +20,14 @@ internal class NpgsqlServices : DbProviderServices { 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; } @@ -28,7 +38,7 @@ protected override DbCommandDefinition CreateDbCommandDefinition(DbProviderManif return CreateCommandDefinition(CreateDbCommand(commandTree)); } - private DbCommand CreateDbCommand(DbCommandTree commandTree) + internal DbCommand CreateDbCommand(DbCommandTree commandTree) { if (commandTree == null) throw new ArgumentNullException("commandTree"); @@ -84,32 +94,72 @@ protected override string GetDbProviderManifestToken(DbConnection connection) { if (connection == null) throw new ArgumentNullException("connection"); - // TODO: used to use connection.ServerVersion - // that doesn't work with a closed connection. - bool openedConnection = false; - try + string serverVersion = ""; + UsingPostgresDBConnection((NpgsqlConnection)connection, conn => { - if (connection.State != System.Data.ConnectionState.Open) + 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)) { - connection.Open(); - openedConnection = true; + exists = Convert.ToInt32(command.ExecuteScalar()) > 0; } - return connection.ServerVersion; - } - finally + }); + return exists; + } + + protected override void DbCreateDatabase(DbConnection connection, int? commandTimeout, StoreItemCollection storeItemCollection) + { + UsingPostgresDBConnection((NpgsqlConnection)connection, conn => { - if (openedConnection) + using (NpgsqlCommand command = new NpgsqlCommand("CREATE DATABASE \"" + connection.Database + "\";", conn)) { - connection.Close(); + command.ExecuteNonQuery(); } - } + }); } - protected override DbProviderManifest GetDbProviderManifest(string versionHint) + protected override void DbDeleteDatabase(DbConnection connection, int? commandTimeout, StoreItemCollection storeItemCollection) { - if (versionHint == null) - throw new ArgumentNullException("versionHint"); - return new NpgsqlProviderManifest(versionHint); + 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 = "postgres" + }; + + using (var masterConnection = new NpgsqlConnection(connectionBuilder.ConnectionString)) + { + masterConnection.Open();//using's Dispose will close it even if exception... + action(masterConnection); + } } } } diff --git a/Npgsql.EntityFramework/packages.config b/Npgsql.EntityFramework/packages.config index 4fef52d215..2962f8dc4b 100755 --- a/Npgsql.EntityFramework/packages.config +++ b/Npgsql.EntityFramework/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/tests/App.config b/tests/App.config new file mode 100644 index 0000000000..302b4fe977 --- /dev/null +++ b/tests/App.config @@ -0,0 +1,25 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/EntityFrameworkMigrationTests.cs b/tests/EntityFrameworkMigrationTests.cs new file mode 100644 index 0000000000..a62573bc66 --- /dev/null +++ b/tests/EntityFrameworkMigrationTests.cs @@ -0,0 +1,757 @@ +// EntityFrameworkMigrationTests.cs +// +// Author: +// David Karlaš (david.karlas@gmail.com) +// +// Copyright (C) 2014 David Karlaš +// +// 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. + +#if NET40 +using Npgsql; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Data.Entity; +using System.Data.Entity.Core.Metadata.Edm; +using System.Data.Entity.Migrations; +using System.Data.Entity.Migrations.History; +using System.Data.Entity.Migrations.Model; +using System.Data.Entity.Spatial; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading; + +namespace NpgsqlTests +{ + [TestFixture] + public class EntityFrameworkMigrationTests : TestBase + { + public EntityFrameworkMigrationTests(string backendVersion) : base(backendVersion) { } + + #region Helper method + + /// + /// Helper function which I put behind var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + /// after filling operations list run test and then copy-paste content from D:\temp\printedCode.txt after var statments = new Np.. + /// Ofcourse check if generated code makes sense or UnitTests would be useless :D + /// + /// + private void PrintCode(IEnumerable statments) + { + + using (var SW = new System.IO.StreamWriter(@"D:\temp\printedCode.txt")) + { + SW.WriteLine("Assert.AreEqual(" + statments.Count() + ", statments.Count());"); + int i = 0; + foreach (var statement in statments) + SW.WriteLine("Assert.AreEqual(\"" + statement.Sql.Replace("\"", "\\\"") + "\", statments.ElementAt(" + i++ + ").Sql);"); + } + Assert.Fail(); + } + + #endregion + + #region Actual test against database + + [Test] + public void CreateBloggingContext() + { + using (var db = new BloggingContext()) + { + if (!(db.Database.Connection is NpgsqlConnection)) + { + Assert.Fail( + "Connection type is \"" + db.Database.Connection.GetType() + "\" should be \"" + typeof(NpgsqlConnection) + "\"." + Environment.NewLine + + "Most likely wrong configuration in App.config file of Tests project."); + } + db.Database.Delete(); + var blog = new Blog { Name = "blogNameTest1" }; + db.Blogs.Add(blog); + db.SaveChanges(); + + var query = from b in db.Blogs + where b.Name == "blogNameTest1" + select b; + Assert.AreEqual(1, query.Count()); + + db.Database.Connection.Open(); + using (var command = db.Database.Connection.CreateCommand()) + { + command.CommandText = "select column_name, data_type, is_nullable, column_default from information_schema.columns where table_name = 'Blogs';"; + List expectedColumns = new List(new string[] { "Name", "BlogId" }); + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + Console.WriteLine((string)reader[0] + " " + (string)reader[1] + " " + (string)reader[2] + " " + (reader[3] ?? "").ToString()); + switch ((string)reader[0]) + { + case "Name": + expectedColumns.Remove((string)reader[0]); + Assert.AreEqual("text", (string)reader[1]); + Assert.AreEqual("YES", (string)reader[2]); + Assert.IsNullOrEmpty(reader[3] as string); + break; + case "BlogId": + expectedColumns.Remove((string)reader[0]); + Assert.AreEqual("integer", (string)reader[1]); + Assert.AreEqual("NO", (string)reader[2]); + Assert.AreEqual("nextval('dbo.\"Blogs_BlogId_seq\"'::regclass)", reader[3] as string); + break; + default: + Assert.Fail("Unknown column '" + (string)reader[0] + "' in Blogs table."); + break; + } + } + } + foreach (var columnName in expectedColumns) + { + Assert.Fail("Column '" + columnName + "' was not created in Blogs table."); + } + } + + using (var command = db.Database.Connection.CreateCommand()) + { + command.CommandText = "select column_name, data_type, is_nullable, column_default from information_schema.columns where table_name = 'Posts';"; + List expectedColumns = new List(new string[] { "PostId", "Title", "Content", "BlogId" }); + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + Console.WriteLine((string)reader[0] + " " + (string)reader[1] + " " + (string)reader[2] + " " + (reader[3] ?? "").ToString()); + switch ((string)reader[0]) + { + case "PostId": + expectedColumns.Remove((string)reader[0]); + Assert.AreEqual("integer", (string)reader[1]); + Assert.AreEqual("NO", (string)reader[2]); + Assert.IsNullOrEmpty(reader[3] as string); + break; + case "Title": + expectedColumns.Remove((string)reader[0]); + Assert.AreEqual("text", (string)reader[1]); + Assert.AreEqual("YES", (string)reader[2]); + Assert.IsNullOrEmpty(reader[3] as string); + break; + case "Content": + expectedColumns.Remove((string)reader[0]); + Assert.AreEqual("text", (string)reader[1]); + Assert.AreEqual("YES", (string)reader[2]); + Assert.IsNullOrEmpty(reader[3] as string); + break; + case "BlogId": + expectedColumns.Remove((string)reader[0]); + Assert.AreEqual("integer", (string)reader[1]); + Assert.AreEqual("NO", (string)reader[2]); + Assert.IsNullOrEmpty(reader[3] as string); + break; + case "UniqueId": + expectedColumns.Remove((string)reader[0]); + Assert.AreEqual("uuid", (string)reader[1]); + Assert.AreEqual("NO", (string)reader[2]); + Assert.AreEqual("uuid_generate_v4()", reader[3] as string); + break; + case "Rating": + expectedColumns.Remove((string)reader[0]); + Assert.AreEqual("smallint", (string)reader[1]); + Assert.AreEqual("YES", (string)reader[2]); + Assert.IsNullOrEmpty(reader[3] as string); + break; + default: + Assert.Fail("Unknown column '" + (string)reader[0] + "' in Post table."); + break; + } + } + } + foreach (var columnName in expectedColumns) + { + Assert.Fail("Column '" + columnName + "' was not created in Posts table."); + } + } + } + } + + + + public class Blog + { + public int BlogId { get; set; } + public string Name { get; set; } + + public virtual List Posts { get; set; } + } + + public class Post + { + public int PostId { get; set; } + public string Title { get; set; } + public string Content { get; set; } + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid UniqueId { get; set; } + public byte? Rating { get; set; } + + public int BlogId { get; set; } + public virtual Blog Blog { get; set; } + } + + public class BloggingContext : DbContext + { + public BloggingContext() + : base("BloggingContext") + { + } + + public DbSet Blogs { get; set; } + public DbSet Posts { get; set; } + } + + #endregion + + [Test] + public void DatabaseExistsCreateDelete() + { + using (var db = new BloggingContext()) + { + if (db.Database.Exists()) + { + db.Database.Delete(); + Assert.IsFalse(db.Database.Exists()); + db.Database.Create(); + } + else + { + db.Database.Create(); + } + Assert.IsTrue(db.Database.Exists()); + db.Database.Delete(); + Assert.IsFalse(db.Database.Exists()); + } + } + + [Test] + public void TestAddColumnOperation() + { + var operations = new List(); + operations.Add(new AddColumnOperation("tableName", new ColumnModel(PrimitiveTypeKind.Double) + { + Name = "columnName" + })); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(1, statments.Count()); + Assert.AreEqual("ALTER TABLE \"tableName\" ADD \"columnName\" float8", statments.ElementAt(0).Sql); + } + + [Test] + public void TestAddColumnOperationDefaultValue() + { + var operations = new List(); + operations.Add(new AddColumnOperation("tableName", new ColumnModel(PrimitiveTypeKind.Single) + { + Name = "columnName", + DefaultValue = 4.4f + })); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(1, statments.Count()); + Assert.AreEqual("ALTER TABLE \"tableName\" ADD \"columnName\" float4 DEFAULT 4.4", statments.ElementAt(0).Sql); + } + + [Test] + public void TestAddColumnOperationDefaultValueSql() + { + var operations = new List(); + operations.Add(new AddColumnOperation("tableName", new ColumnModel(PrimitiveTypeKind.Single) + { + Name = "columnName", + DefaultValueSql = "4.6" + })); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(1, statments.Count()); + Assert.AreEqual("ALTER TABLE \"tableName\" ADD \"columnName\" float4 DEFAULT 4.6", statments.ElementAt(0).Sql); + } + + [Test] + public void TestAlterColumnOperation() + { + var operations = new List(); + operations.Add(new AlterColumnOperation("tableName", new ColumnModel(PrimitiveTypeKind.Double) + { + Name = "columnName" + }, false)); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(3, statments.Count()); + Assert.AreEqual("ALTER TABLE \"tableName\" ALTER COLUMN \"columnName\" TYPE float8", statments.ElementAt(0).Sql); + Assert.AreEqual("ALTER TABLE \"tableName\" ALTER COLUMN \"columnName\" DROP NOT NULL", statments.ElementAt(1).Sql); + Assert.AreEqual("ALTER TABLE \"tableName\" ALTER COLUMN \"columnName\" DROP DEFAULT", statments.ElementAt(2).Sql); + } + + [Test] + public void TestAlterColumnOperationDefaultAndNullable() + { + var operations = new List(); + operations.Add(new AlterColumnOperation("tableName", new ColumnModel(PrimitiveTypeKind.Double) + { + Name = "columnName", + DefaultValue = 2.3, + IsNullable = false + }, false)); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(3, statments.Count()); + Assert.AreEqual("ALTER TABLE \"tableName\" ALTER COLUMN \"columnName\" TYPE float8", statments.ElementAt(0).Sql); + Assert.AreEqual("ALTER TABLE \"tableName\" ALTER COLUMN \"columnName\" SET NOT NULL", statments.ElementAt(1).Sql); + Assert.AreEqual("ALTER TABLE \"tableName\" ALTER COLUMN \"columnName\" SET DEFAULT 2.3", statments.ElementAt(2).Sql); + } + + [Test] + public void TestCreateTableOperation() + { + var operations = new List(); + var operation = new CreateTableOperation("someSchema.someTable"); + + operation.Columns.Add( + new ColumnModel(PrimitiveTypeKind.String) + { + Name = "SomeString", + MaxLength = 233, + IsNullable = false + }); + + operation.Columns.Add( + new ColumnModel(PrimitiveTypeKind.String) + { + Name = "AnotherString", + IsNullable = true + }); + + operation.Columns.Add( + new ColumnModel(PrimitiveTypeKind.Binary) + { + Name = "SomeBytes" + }); + + operation.Columns.Add( + new ColumnModel(PrimitiveTypeKind.Int64) + { + Name = "SomeLong", + IsIdentity = true + }); + + operation.Columns.Add( + new ColumnModel(PrimitiveTypeKind.DateTime) + { + Name = "SomeDateTime" + }); + + operations.Add(operation); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(2, statments.Count()); + Assert.AreEqual("CREATE SCHEMA IF NOT EXISTS someSchema", statments.ElementAt(0).Sql); + Assert.AreEqual("CREATE TABLE \"someSchema\".\"someTable\"(\"SomeString\" varchar(233) NOT NULL,\"AnotherString\" text,\"SomeBytes\" bytea,\"SomeLong\" serial8,\"SomeDateTime\" timestamp)", statments.ElementAt(1).Sql); + } + + [Test] + public void TestDropColumnOperation() + { + var operations = new List(); + operations.Add(new DropColumnOperation("someTable", "someColumn")); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(1, statments.Count()); + Assert.AreEqual("ALTER TABLE \"someTable\" DROP COLUMN \"someColumn\"", statments.ElementAt(0).Sql); + } + + [Test] + public void TestDropTableOperation() + { + var operations = new List(); + operations.Add(new DropTableOperation("someTable")); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(1, statments.Count()); + Assert.AreEqual("DROP TABLE \"someTable\"", statments.ElementAt(0).Sql); + } + + [Test] + public void TestRenameTableOperation() + { + var operations = new List(); + operations.Add(new RenameTableOperation("schema.someOldTableName", "someNewTablename")); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(1, statments.Count()); + Assert.AreEqual("ALTER TABLE \"schema\".\"someOldTableName\" RENAME TO \"someNewTablename\"", statments.ElementAt(0).Sql); + } + + [Test] + public void TestHistoryOperation() + { + var operations = new List(); + //TODO: fill operations + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + //TODO: check statments + } + + [Test] + public void TestDropIndexOperation() + { + var operations = new List(); + operations.Add(new DropIndexOperation() + { + Name = "someIndex", + Table = "someTable" + }); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(1, statments.Count()); + Assert.AreEqual("DROP INDEX IF EXISTS dto.\"someTable_someIndex\"", statments.ElementAt(0).Sql); + } + + [Test] + public void TestDropIndexOperationTableNameWithSchema() + { + var operations = new List(); + operations.Add(new DropIndexOperation() + { + Name = "someIndex", + Table = "someSchema.someTable" + }); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(1, statments.Count()); + Assert.AreEqual("DROP INDEX IF EXISTS someSchema.\"someTable_someIndex\"", statments.ElementAt(0).Sql); + } + + [Test] + public void TestCreateIndexOperation() + { + var operations = new List(); + var operation = new CreateIndexOperation(); + operation.Table = "someTable"; + operation.Name = "someIndex"; + operation.Columns.Add("column1"); + operation.Columns.Add("column2"); + operation.Columns.Add("column3"); + operation.IsUnique = false; + operations.Add(operation); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(1, statments.Count()); + Assert.AreEqual("CREATE INDEX \"someTable_someIndex\" ON \"someTable\" (\"column1\",\"column2\",\"column3\")", statments.ElementAt(0).Sql); + } + + [Test] + public void TestCreateIndexOperationUnique() + { + var operations = new List(); + var operation = new CreateIndexOperation(); + operation.Table = "someTable"; + operation.Name = "someIndex"; + operation.Columns.Add("column1"); + operation.Columns.Add("column2"); + operation.Columns.Add("column3"); + operation.IsUnique = true; + operations.Add(operation); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(1, statments.Count()); + Assert.AreEqual("CREATE UNIQUE INDEX \"someTable_someIndex\" ON \"someTable\" (\"column1\",\"column2\",\"column3\")", statments.ElementAt(0).Sql); + } + + [Test] + public void TestMoveTableOperation() + { + var operations = new List(); + operations.Add(new MoveTableOperation("someOldSchema.someTable", "someNewSchema")); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(2, statments.Count()); + Assert.AreEqual("CREATE SCHEMA IF NOT EXISTS someNewSchema", statments.ElementAt(0).Sql); + Assert.AreEqual("ALTER TABLE \"someOldSchema\".\"someTable\" SET SCHEMA someNewSchema", statments.ElementAt(1).Sql); + } + + [Test] + public void TestMoveTableOperationNewSchemaIsNull() + { + var operations = new List(); + operations.Add(new MoveTableOperation("someOldSchema.someTable", null)); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(2, statments.Count()); + Assert.AreEqual("CREATE SCHEMA IF NOT EXISTS dbo", statments.ElementAt(0).Sql); + Assert.AreEqual("ALTER TABLE \"someOldSchema\".\"someTable\" SET SCHEMA dbo", statments.ElementAt(1).Sql); + } + + [Test] + public void TestAddPrimaryKeyOperation() + { + var operations = new List(); + var operation = new AddPrimaryKeyOperation(); + operation.Table = "someTable"; + operation.Name = "somePKName"; + operation.Columns.Add("column1"); + operation.Columns.Add("column2"); + operation.Columns.Add("column3"); + operation.IsClustered = false; + operations.Add(operation); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(1, statments.Count()); + Assert.AreEqual("ALTER TABLE \"someTable\" ADD CONSTRAINT \"somePKName\" PRIMARY KEY (\"column1\",\"column2\",\"column3\")", statments.ElementAt(0).Sql); + } + + [Test] + public void TestAddPrimaryKeyOperationClustered() + { + var operations = new List(); + var operation = new AddPrimaryKeyOperation(); + operation.Table = "someTable"; + operation.Name = "somePKName"; + operation.Columns.Add("column1"); + operation.Columns.Add("column2"); + operation.Columns.Add("column3"); + operation.IsClustered = true; + //TODO: PostgreSQL support something like IsClustered? + operations.Add(operation); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(1, statments.Count()); + Assert.AreEqual("ALTER TABLE \"someTable\" ADD CONSTRAINT \"somePKName\" PRIMARY KEY (\"column1\",\"column2\",\"column3\")", statments.ElementAt(0).Sql); + } + + [Test] + public void TestDropPrimaryKeyOperation() + { + var operations = new List(); + var operation = new DropPrimaryKeyOperation(); + operation.Table = "someTable"; + operation.Name = "somePKName"; + operations.Add(operation); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(1, statments.Count()); + Assert.AreEqual("ALTER TABLE \"someTable\" DROP CONSTRAINT \"somePKName\"", statments.ElementAt(0).Sql); + } + + [Test] + public void TestRenameColumnOperation() + { + var operations = new List(); + operations.Add(new RenameColumnOperation("someTable", "someOldColumnName", "someNewColumnName")); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(1, statments.Count()); + Assert.AreEqual("ALTER TABLE \"someTable\" RENAME COLUMN \"someOldColumnName\" TO \"someNewColumnName\"", statments.ElementAt(0).Sql); + } + + [Test] + public void TestSqlOperation() + { + var operations = new List(); + operations.Add(new SqlOperation("SELECT someColumn FROM someTable")); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(1, statments.Count()); + Assert.AreEqual("SELECT someColumn FROM someTable", statments.ElementAt(0).Sql); + } + + [Test] + public void TestUpdateDatabaseOperation() + { + var operations = new List(); + //TODO: fill operations + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + //TODO: check statments + } + + [Test] + public void TestAddForeignKeyOperation() + { + var operations = new List(); + var operation = new AddForeignKeyOperation(); + operation.Name = "someFK"; + operation.PrincipalTable = "somePrincipalTable"; + operation.DependentTable = "someDependentTable"; + operation.DependentColumns.Add("column1"); + operation.DependentColumns.Add("column2"); + operation.DependentColumns.Add("column3"); + operation.CascadeDelete = false; + operations.Add(operation); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(1, statments.Count()); + Assert.AreEqual("ALTER TABLE \"someDependentTable\" ADD CONSTRAINT \"someFK\" FOREIGN KEY (\"column1\",\"column2\",\"column3\") REFERENCES \"somePrincipalTable\" )", statments.ElementAt(0).Sql); + } + + [Test] + public void TestAddForeignKeyOperationCascadeDelete() + { + var operations = new List(); + var operation = new AddForeignKeyOperation(); + operation.Name = "someFK"; + operation.PrincipalTable = "somePrincipalTable"; + operation.DependentTable = "someDependentTable"; + operation.DependentColumns.Add("column1"); + operation.DependentColumns.Add("column2"); + operation.DependentColumns.Add("column3"); + operation.CascadeDelete = true; + operations.Add(operation); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(1, statments.Count()); + Assert.AreEqual("ALTER TABLE \"someDependentTable\" ADD CONSTRAINT \"someFK\" FOREIGN KEY (\"column1\",\"column2\",\"column3\") REFERENCES \"somePrincipalTable\" ) ON DELETE CASCADE", statments.ElementAt(0).Sql); + } + + + [Test] + public void TestDropForeignKeyOperation() + { + var operations = new List(); + var operation = new DropForeignKeyOperation(); + operation.Name = "someFK"; + operation.DependentTable = "someTable"; + operations.Add(operation); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(1, statments.Count()); + Assert.AreEqual("ALTER TABLE \"someTable\" DROP CONSTRAINT IF EXISTS \"someFK\"", statments.ElementAt(0).Sql); + } + + [Test] + public void TestDefaultTypes() + { + var operations = new List(); + operations.Add(new AddColumnOperation("someTable", + new ColumnModel(PrimitiveTypeKind.Binary) + { + Name = "someByteaColumn", + DefaultValue = new byte[6] { 1, 2, 127, 128, 254, 255 } + }, false) + ); + operations.Add(new AddColumnOperation("someTable", + new ColumnModel(PrimitiveTypeKind.Boolean) + { + Name = "someFalseBooleanColumn", + DefaultValue = false + }, false) + ); + operations.Add(new AddColumnOperation("someTable", + new ColumnModel(PrimitiveTypeKind.Boolean) + { + Name = "someTrueBooleanColumn", + DefaultValue = true + }, false) + ); + operations.Add(new AddColumnOperation("someTable", + new ColumnModel(PrimitiveTypeKind.Byte) + { + Name = "someByteColumn", + DefaultValue = 15 + }, false) + ); + operations.Add(new AddColumnOperation("someTable", + new ColumnModel(PrimitiveTypeKind.DateTime) + { + Name = "someDateTimeColumn", + DefaultValue = new DateTime(2014, 1, 31, 5, 15, 23, 435) + }, false) + ); + operations.Add(new AddColumnOperation("someTable", + new ColumnModel(PrimitiveTypeKind.DateTimeOffset) + { + Name = "someDateTimeOffsetColumn", + DefaultValue = new DateTimeOffset(new DateTime(2014, 1, 31, 5, 18, 43, 186), TimeSpan.FromHours(1)) + }, false) + ); + operations.Add(new AddColumnOperation("someTable", + new ColumnModel(PrimitiveTypeKind.Decimal) + { + Name = "someDecimalColumn", + DefaultValue = 23432423.534534m + }, false) + ); + operations.Add(new AddColumnOperation("someTable", + new ColumnModel(PrimitiveTypeKind.Double) + { + Name = "someDoubleColumn", + DefaultValue = 44.66 + }, false) + ); + operations.Add(new AddColumnOperation("someTable", + new ColumnModel(PrimitiveTypeKind.Guid) + { + Name = "someGuidColumn", + DefaultValue = new Guid("de303070-afb8-4ec1-bcb0-c637f3316501") + }, false) + ); + operations.Add(new AddColumnOperation("someTable", + new ColumnModel(PrimitiveTypeKind.Int16) + { + Name = "someInt16Column", + DefaultValue = 16 + }, false) + ); + operations.Add(new AddColumnOperation("someTable", + new ColumnModel(PrimitiveTypeKind.Int32) + { + Name = "someInt32Column", + DefaultValue = 32 + }, false) + ); + operations.Add(new AddColumnOperation("someTable", + new ColumnModel(PrimitiveTypeKind.Int64) + { + Name = "someInt64Column", + DefaultValue = 64 + }, false) + ); + operations.Add(new AddColumnOperation("someTable", + new ColumnModel(PrimitiveTypeKind.SByte) + { + Name = "someSByteColumn", + DefaultValue = -24 + }, false) + ); + operations.Add(new AddColumnOperation("someTable", + new ColumnModel(PrimitiveTypeKind.Single) + { + Name = "someSingleColumn", + DefaultValue = 12.4f + }, false) + ); + operations.Add(new AddColumnOperation("someTable", + new ColumnModel(PrimitiveTypeKind.String) + { + Name = "someStringColumn", + DefaultValue = "Hello EF" + }, false) + ); + operations.Add(new AddColumnOperation("someTable", + new ColumnModel(PrimitiveTypeKind.Time) + { + Name = "someColumn", + DefaultValue = new TimeSpan(937840050067)//1 day, 2 hours, 3 minutes, 4 seconds, 5 miliseconds, 6 microseconds, 700 nanoseconds + }, false) + ); + var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); + Assert.AreEqual(16, statments.Count()); + Assert.AreEqual("ALTER TABLE \"someTable\" ADD \"someByteaColumn\" bytea DEFAULT E'\\\\01027F80FEFF'", statments.ElementAt(0).Sql); + Assert.AreEqual("ALTER TABLE \"someTable\" ADD \"someFalseBooleanColumn\" boolean DEFAULT FALSE", statments.ElementAt(1).Sql); + Assert.AreEqual("ALTER TABLE \"someTable\" ADD \"someTrueBooleanColumn\" boolean DEFAULT TRUE", statments.ElementAt(2).Sql); + Assert.AreEqual("ALTER TABLE \"someTable\" ADD \"someByteColumn\" int2 DEFAULT 15", statments.ElementAt(3).Sql); + Assert.AreEqual("ALTER TABLE \"someTable\" ADD \"someDateTimeColumn\" timestamp DEFAULT '2014-01-31 05:15:23.435'", statments.ElementAt(4).Sql); + Assert.AreEqual("ALTER TABLE \"someTable\" ADD \"someDateTimeOffsetColumn\" timestamptz DEFAULT '2014-01-31 05:18:43.186+01'", statments.ElementAt(5).Sql); + Assert.AreEqual("ALTER TABLE \"someTable\" ADD \"someDecimalColumn\" numeric DEFAULT 23432423.534534", statments.ElementAt(6).Sql); + Assert.AreEqual("ALTER TABLE \"someTable\" ADD \"someDoubleColumn\" float8 DEFAULT 44.66", statments.ElementAt(7).Sql); + Assert.AreEqual("ALTER TABLE \"someTable\" ADD \"someGuidColumn\" uuid DEFAULT 'de303070-afb8-4ec1-bcb0-c637f3316501'", statments.ElementAt(8).Sql); + Assert.AreEqual("ALTER TABLE \"someTable\" ADD \"someInt16Column\" int2 DEFAULT 16", statments.ElementAt(9).Sql); + Assert.AreEqual("ALTER TABLE \"someTable\" ADD \"someInt32Column\" int4 DEFAULT 32", statments.ElementAt(10).Sql); + Assert.AreEqual("ALTER TABLE \"someTable\" ADD \"someInt64Column\" int8 DEFAULT 64", statments.ElementAt(11).Sql); + Assert.AreEqual("ALTER TABLE \"someTable\" ADD \"someSByteColumn\" int2 DEFAULT -24", statments.ElementAt(12).Sql); + Assert.AreEqual("ALTER TABLE \"someTable\" ADD \"someSingleColumn\" float4 DEFAULT 12.4", statments.ElementAt(13).Sql); + Assert.AreEqual("ALTER TABLE \"someTable\" ADD \"someStringColumn\" text DEFAULT 'Hello EF'", statments.ElementAt(14).Sql); + Assert.AreEqual("ALTER TABLE \"someTable\" ADD \"someColumn\" interval DEFAULT '26:03:04.005007'", statments.ElementAt(15).Sql); + + } + } +} +#endif \ No newline at end of file diff --git a/tests/NpgsqlTests.csproj b/tests/NpgsqlTests.csproj index 1b380a8632..03d40e6ca3 100644 --- a/tests/NpgsqlTests.csproj +++ b/tests/NpgsqlTests.csproj @@ -81,6 +81,7 @@ true 4 v4.0 + NET35;NET40 bin\Release-net40\ @@ -90,6 +91,7 @@ true 4 v4.0 + NET35;NET40 bin\Debug-net45\ @@ -101,6 +103,7 @@ true 4 v4.5 + NET35;NET40;NET45 bin\Release-net45\ @@ -110,6 +113,7 @@ true 4 v4.5 + NET35;NET40;NET45 bin\Debug-net45-EF6\ @@ -121,6 +125,7 @@ true 4 v4.5 + NET35;NET40;NET45 bin\Release-net45-EF6\ @@ -130,12 +135,18 @@ true 4 v4.5 + NET35;NET40;NET45 + + ..\packages\EntityFramework.6.0.2\lib\net40\EntityFramework.dll + + + ..\packages\EntityFramework.6.0.2\lib\net40\EntityFramework.SqlServer.dll + ..\packages\NUnit.2.6.2\lib\nunit.framework.dll - 3.5 @@ -154,6 +165,7 @@ + Code @@ -175,6 +187,7 @@ + Always @@ -214,6 +227,10 @@ + + {3ec85cba-5b79-11e3-8104-0022198ab089} + Npgsql.EntityFramework + {9D13B739-62B1-4190-B386-7A9547304EB3} Npgsql2012 diff --git a/tests/packages.config b/tests/packages.config index 5c3ca54dd7..d8e1560a45 100644 --- a/tests/packages.config +++ b/tests/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file From d262dd20fd5e88c5fd0e4517ad813e064f43d159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Karla=C5=A1?= Date: Sat, 1 Feb 2014 17:57:40 +0100 Subject: [PATCH 02/10] Setting default value in case of NOT NULL --- .../NpgsqlMigrationSqlGenerator.cs | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/Npgsql.EntityFramework/NpgsqlMigrationSqlGenerator.cs b/Npgsql.EntityFramework/NpgsqlMigrationSqlGenerator.cs index 78e5249275..541ac017f7 100644 --- a/Npgsql.EntityFramework/NpgsqlMigrationSqlGenerator.cs +++ b/Npgsql.EntityFramework/NpgsqlMigrationSqlGenerator.cs @@ -537,10 +537,27 @@ private void AppendColumn(ColumnModel column, StringBuilder sql) sql.Append(" DEFAULT "); sql.Append(column.DefaultValueSql); } - else if (column.IsIdentity && column.Type == PrimitiveTypeKind.Guid) + else if (column.IsIdentity) { - CreateExtension("uuid-ossp"); - sql.Append(" DEFAULT uuid_generate_v4()"); + switch (column.Type) + { + case PrimitiveTypeKind.Guid: + CreateExtension("uuid-ossp"); + 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) + { + sql.Append(" DEFAULT "); + AppendValue(column.ClrDefaultValue, sql); } } From 64f4de31b0da2c3f9a0bff71d88c6ea6845a4a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Karla=C5=A1?= Date: Sat, 1 Feb 2014 19:18:15 +0100 Subject: [PATCH 03/10] Support for default empty byte array and removed rowversion from setting default updated tests --- .../NpgsqlMigrationSqlGenerator.cs | 20 ++++++++++++++----- tests/App.config | 1 - tests/EntityFrameworkMigrationTests.cs | 9 +++++---- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Npgsql.EntityFramework/NpgsqlMigrationSqlGenerator.cs b/Npgsql.EntityFramework/NpgsqlMigrationSqlGenerator.cs index 541ac017f7..f85f9f7e7b 100644 --- a/Npgsql.EntityFramework/NpgsqlMigrationSqlGenerator.cs +++ b/Npgsql.EntityFramework/NpgsqlMigrationSqlGenerator.cs @@ -554,7 +554,10 @@ private void AppendColumn(ColumnModel column, StringBuilder sql) break; } } - else if (column.IsNullable != null && !column.IsNullable.Value) + 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); @@ -721,10 +724,17 @@ private void AppendTableName(string tableName, StringBuilder sql) private void AppendValue(byte[] values, StringBuilder sql) { - sql.Append("E'\\\\"); - foreach (var value in values) - sql.Append(value.ToString("X2")); - sql.Append("'"); + 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) diff --git a/tests/App.config b/tests/App.config index 302b4fe977..920c23d4dd 100644 --- a/tests/App.config +++ b/tests/App.config @@ -2,7 +2,6 @@
- diff --git a/tests/EntityFrameworkMigrationTests.cs b/tests/EntityFrameworkMigrationTests.cs index a62573bc66..fbb19bba0c 100644 --- a/tests/EntityFrameworkMigrationTests.cs +++ b/tests/EntityFrameworkMigrationTests.cs @@ -56,7 +56,7 @@ public EntityFrameworkMigrationTests(string backendVersion) : base(backendVersio /// private void PrintCode(IEnumerable statments) { - + using (var SW = new System.IO.StreamWriter(@"D:\temp\printedCode.txt")) { SW.WriteLine("Assert.AreEqual(" + statments.Count() + ", statments.Count());"); @@ -143,7 +143,7 @@ public void CreateBloggingContext() expectedColumns.Remove((string)reader[0]); Assert.AreEqual("integer", (string)reader[1]); Assert.AreEqual("NO", (string)reader[2]); - Assert.IsNullOrEmpty(reader[3] as string); + Assert.AreEqual("nextval('dbo.\"Posts_PostId_seq\"'::regclass)", (string)reader[3]); break; case "Title": expectedColumns.Remove((string)reader[0]); @@ -161,7 +161,7 @@ public void CreateBloggingContext() expectedColumns.Remove((string)reader[0]); Assert.AreEqual("integer", (string)reader[1]); Assert.AreEqual("NO", (string)reader[2]); - Assert.IsNullOrEmpty(reader[3] as string); + Assert.AreEqual("0", (string)reader[3]); break; case "UniqueId": expectedColumns.Remove((string)reader[0]); @@ -201,6 +201,7 @@ public class Blog public class Post { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } @@ -363,7 +364,7 @@ public void TestCreateTableOperation() var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); Assert.AreEqual(2, statments.Count()); Assert.AreEqual("CREATE SCHEMA IF NOT EXISTS someSchema", statments.ElementAt(0).Sql); - Assert.AreEqual("CREATE TABLE \"someSchema\".\"someTable\"(\"SomeString\" varchar(233) NOT NULL,\"AnotherString\" text,\"SomeBytes\" bytea,\"SomeLong\" serial8,\"SomeDateTime\" timestamp)", statments.ElementAt(1).Sql); + Assert.AreEqual("CREATE TABLE \"someSchema\".\"someTable\"(\"SomeString\" varchar(233) NOT NULL DEFAULT '',\"AnotherString\" text,\"SomeBytes\" bytea,\"SomeLong\" serial8,\"SomeDateTime\" timestamp)", statments.ElementAt(1).Sql); } [Test] From 47df2b13602d0dde7d49bcfd2fd00a7db8e70839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Karla=C5=A1?= Date: Mon, 10 Feb 2014 23:23:20 +0100 Subject: [PATCH 04/10] EF Migrations unit tests use connectionStrings provided by TestBase instead app.config --- tests/App.config | 4 ---- tests/EntityFrameworkMigrationTests.cs | 29 +++++++++++++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/tests/App.config b/tests/App.config index 920c23d4dd..015c9b4d35 100644 --- a/tests/App.config +++ b/tests/App.config @@ -10,11 +10,7 @@ - - - - diff --git a/tests/EntityFrameworkMigrationTests.cs b/tests/EntityFrameworkMigrationTests.cs index fbb19bba0c..17ed47e5dd 100644 --- a/tests/EntityFrameworkMigrationTests.cs +++ b/tests/EntityFrameworkMigrationTests.cs @@ -28,6 +28,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; +using System.Data.Common; using System.Data.Entity; using System.Data.Entity.Core.Metadata.Edm; using System.Data.Entity.Migrations; @@ -46,6 +47,23 @@ public class EntityFrameworkMigrationTests : TestBase { public EntityFrameworkMigrationTests(string backendVersion) : base(backendVersion) { } + protected override void SetUp() + { + //Prevent TestBase to open connection since we want to drop DB in our tests + //base.SetUp(); + if (Conn != null) + { + Conn.Close(); + Conn.Dispose(); + } + } + + protected override void TearDown() + { + //Conn is already closed by SetUp... + //base.TearDown(); + } + #region Helper method /// @@ -74,12 +92,12 @@ private void PrintCode(IEnumerable Date: Wed, 12 Feb 2014 07:24:42 +0100 Subject: [PATCH 05/10] Adding EFTestBase for EntityFramework tests --- .../SqlGenerators/SqlBaseGenerator.cs | 2 + Npgsql/Npgsql/NpgsqlDataReader.cs | 4 +- tests/EFTestBase.cs | 215 +++++++++++++++++ tests/EntityFrameworkBasicTests.cs | 218 ++++++++++++++++++ tests/EntityFrameworkMigrationTests.cs | 22 +- tests/NpgsqlTests.csproj | 2 + 6 files changed, 441 insertions(+), 22 deletions(-) create mode 100644 tests/EFTestBase.cs create mode 100644 tests/EntityFrameworkBasicTests.cs diff --git a/Npgsql.EntityFramework/SqlGenerators/SqlBaseGenerator.cs b/Npgsql.EntityFramework/SqlGenerators/SqlBaseGenerator.cs index 23c16349af..29b77a3a49 100644 --- a/Npgsql.EntityFramework/SqlGenerators/SqlBaseGenerator.cs +++ b/Npgsql.EntityFramework/SqlGenerators/SqlBaseGenerator.cs @@ -825,6 +825,8 @@ protected string GetDbType(EdmType edmType) { case PrimitiveTypeKind.Boolean: return "bool"; + case PrimitiveTypeKind.SByte: + case PrimitiveTypeKind.Byte: case PrimitiveTypeKind.Int16: return "int2"; case PrimitiveTypeKind.Int32: diff --git a/Npgsql/Npgsql/NpgsqlDataReader.cs b/Npgsql/Npgsql/NpgsqlDataReader.cs index a8147f48aa..208c1fdac7 100644 --- a/Npgsql/Npgsql/NpgsqlDataReader.cs +++ b/Npgsql/Npgsql/NpgsqlDataReader.cs @@ -403,11 +403,11 @@ public override Boolean GetBoolean(Int32 i) } /// - /// Gets the value of a column as Byte. Not implemented. + /// Gets the value of a column as Byte. /// public override Byte GetByte(Int32 i) { - throw new NotImplementedException(); + return (Byte)GetValue(i); } /// diff --git a/tests/EFTestBase.cs b/tests/EFTestBase.cs new file mode 100644 index 0000000000..d0fe08d658 --- /dev/null +++ b/tests/EFTestBase.cs @@ -0,0 +1,215 @@ +// +// Author: +// Francisco Figueiredo Jr. +// +// Copyright (C) 2002-2005 The Npgsql Development Team +// npgsql-general@gborg.postgresql.org +// http://gborg.postgresql.org/project/npgsql/projdisplay.php +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Configuration; +using System.Diagnostics; +using System.Reflection; +using Npgsql; + +using NpgsqlTypes; + +using NUnit.Framework; + +namespace NpgsqlTests +{ + [TestFixture("9.3")] + [TestFixture("9.2")] + [TestFixture("9.1")] + [TestFixture("9.0")] + [TestFixture("8.4")] + public abstract class EFTestBase + { + protected Version BackendVersion { get; private set; } + protected ProtocolVersion BackendProtocolVersion = ProtocolVersion.Version3; + + /// + /// Constructs the parameterized test fixture + /// + /// + /// The version of the Postgres backend to be used, major and minor veresions (e.g. 9.3). + /// Used to select the conn string environment variable to be used. + /// + protected EFTestBase(string backendVersion) + { + BackendVersion = new Version(backendVersion); + } + + /// + /// The connection string that will be used when opening the connection to the tests database. + /// May be overridden in fixtures, e.g. to set special connection parameters + /// + protected virtual string ConnectionString { get { return _connectionString; } } + private string _connectionString; + + /// + /// Unless the NPGSQL_TEST_DB environment variable is defined, this is used as the connection string for the + /// test database. + /// + private const string DEFAULT_CONNECTION_STRING = "Server=localhost;User ID=npgsql_tests;Password=npgsql_tests;Database=npgsql_tests;syncnotification=false"; + + #region Setup / Teardown + + [TestFixtureSetUp] + public virtual void TestFixtureSetup() + { + var connStringEnvVar = "NPGSQL_TEST_DB_" + BackendVersion; + _connectionString = Environment.GetEnvironmentVariable(connStringEnvVar); + if (_connectionString == null) + { + if (BackendVersion == LatestBackendVersion) + { + _connectionString = DEFAULT_CONNECTION_STRING; + Console.WriteLine("Using internal default connection string: " + _connectionString); + } + else + { + Assert.Ignore("Skipping tests for backend version {0}, environment variable {1} isn't defined", BackendVersion, connStringEnvVar); + return; + } + } + else + Console.WriteLine("Using connection string provided in env var {0}: {1}", connStringEnvVar, _connectionString); + + if (_connectionString.Contains("protocol")) + throw new Exception("Connection string base cannot contain protocol"); + _connectionString += ";protocol=" + (int)BackendProtocolVersion; + + //Reuse all strings just add _ef at end of database name for + var connectionSB = new NpgsqlConnectionStringBuilder(_connectionString); + connectionSB.Database += "_ef"; + _connectionString = connectionSB.ConnectionString; + + try + { + SuppressBinaryBackendEncoding = InitBinaryBackendSuppression(); + } + catch + // Throwing an exception here causes all tests to fail without running. + // CommandTests.SuppressBinaryBackendEncodingInitTest() provides error information in event of failure. + { } + } + + /// + /// Uses reflection to read the [TextFixture] attributes on this class and extract the latest + /// Postgres backend version specified within them. + /// + private Version LatestBackendVersion + { + get + { + return typeof(TestBase) + .GetCustomAttributes(typeof(TestFixtureAttribute), false) + .Cast() + .Select(a => new Version((string)a.Arguments[0])) + .Max(); + } + } + + #endregion + + #region Binary backend suppression + + // Some tests need to suppress binary backend formatting of parameters and result values, + // so that both binary and text formatting can be tested. + // Setting NpgsqlTypes.NpgsqlTypesHelper.SuppressBinaryBackendEncoding accomplishes this, but it is intentionally internal. + // Since it's internal, reflection is required to observe and set it. + protected FieldInfo SuppressBinaryBackendEncoding; + + // Use reflection to bind to NpgsqlTypes.NpgsqlTypesHelper.SuppressBinaryBackendEncoding. + protected FieldInfo InitBinaryBackendSuppression() + { + Assembly npgsql; + Type typesHelper; + FieldInfo fi; + + npgsql = Assembly.Load("Npgsql"); + + // GetType() can return null. Check for this situation and report it. + try + { + typesHelper = npgsql.GetType("NpgsqlTypes.NpgsqlTypesHelper"); + + Assert.IsNotNull(typesHelper, "GetType(\"NpgsqlTypes.NpgsqlTypesHelper\") returned null indicating class not found"); + } + catch (Exception e) + { + throw new Exception("Failed to bind to class NpgsqlTypes.NpgsqlTypesHelper", e); + } + + // GetField() can return null. Check for this situation and report it. + try + { + fi = typesHelper.GetField("SuppressBinaryBackendEncoding", BindingFlags.Static | BindingFlags.NonPublic); + + Assert.IsNotNull(fi, "GetField(\"SuppressBinaryBackendEncoding\") returned null indicating field not found"); + } + catch (Exception e) + { + throw new Exception("Failed to bind to field NpgsqlTypes.NpgsqlTypesHelper.SuppressBinaryBackendEncoding", e); + } + + Assert.IsTrue(fi.FieldType == typeof(bool), "Field NpgsqlTypes.NpgsqlTypesHelper.SuppressBinaryBackendEncoding is not boolean"); + + return fi; + } + + protected class BackendBinarySuppressor : IDisposable + { + private FieldInfo suppressionField; + + internal BackendBinarySuppressor(FieldInfo suppressionField) + { + this.suppressionField = suppressionField; + suppressionField.SetValue(null, true); + } + + public void Dispose() + { + suppressionField.SetValue(null, false); + } + } + + /// + /// Return a BackendBinarySuppressor which has suppressed backend binary encoding. + /// When it is disposed, suppression will be ended. Example: + /// using (SuppressBackendBinary()) + /// { + /// // Test text encoding functionality here. + /// } + /// + protected BackendBinarySuppressor SuppressBackendBinary() + { + Assert.IsTrue( + SuppressBinaryBackendEncoding != null && SuppressBinaryBackendEncoding.FieldType == typeof(bool), + "SuppressBinaryBackendEncoding is null or not boolean; binary backend encoding cannot be suppressed. Check test CommandTests.__SuppressBinaryBackendEncodingInitTest() for more information" + ); + + return new BackendBinarySuppressor(SuppressBinaryBackendEncoding); + } + + #endregion + } +} diff --git a/tests/EntityFrameworkBasicTests.cs b/tests/EntityFrameworkBasicTests.cs new file mode 100644 index 0000000000..53231a82df --- /dev/null +++ b/tests/EntityFrameworkBasicTests.cs @@ -0,0 +1,218 @@ +using Npgsql; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Data.Entity; +using System.Linq; +using System.Text; + +namespace NpgsqlTests +{ + [TestFixture] + public class EntityFrameworkBasicTests : EFTestBase + { + public EntityFrameworkBasicTests(string backendVersion) + : base(backendVersion) + { + } + + [TestFixtureSetUp] + public override void TestFixtureSetup() + { + base.TestFixtureSetup(); + using (var context = new BloggingContext(ConnectionString)) + { + if (context.Database.Exists()) + context.Database.Delete();//We delete to be 100% schema is synced + context.Database.Create(); + } + } + + /// + /// Clean any previous entites before our test + /// + [SetUp] + public void ClearContext() + { + using (var context = new BloggingContext(ConnectionString)) + { + context.Blogs.RemoveRange(context.Blogs); + context.Posts.RemoveRange(context.Posts); + context.SaveChanges(); + } + } + + public class Blog + { + public int BlogId { get; set; } + public string Name { get; set; } + + public virtual List Posts { get; set; } + } + + public class Post + { + public int PostId { get; set; } + public string Title { get; set; } + public string Content { get; set; } + public byte Rating { get; set; } + + public int BlogId { get; set; } + public virtual Blog Blog { get; set; } + } + + public class BloggingContext : DbContext + { + public BloggingContext(string connection) + : base(new NpgsqlConnection(connection), true) + { + } + + public DbSet Blogs { get; set; } + public DbSet Posts { get; set; } + } + + [Test] + public void InsertAndSelect() + { + using (var context = new BloggingContext(ConnectionString)) + { + var blog = new Blog() + { + Name = "Some blog name" + }; + blog.Posts = new List(); + for (int i = 0; i < 5; i++) + blog.Posts.Add(new Post() + { + Content = "Some post content " + i, + Rating = (byte)i, + Title = "Some post Title " + i + }); + context.Blogs.Add(blog); + context.SaveChanges(); + } + + using (var context = new BloggingContext(ConnectionString)) + { + var posts = from p in context.Posts + select p; + Assert.AreEqual(5, posts.Count()); + foreach (var post in posts) + { + StringAssert.StartsWith("Some post Title ", post.Title); + } + } + } + + [Test] + public void SelectWithWhere() + { + using (var context = new BloggingContext(ConnectionString)) + { + var blog = new Blog() + { + Name = "Some blog name" + }; + blog.Posts = new List(); + for (int i = 0; i < 5; i++) + blog.Posts.Add(new Post() + { + Content = "Some post content " + i, + Rating = (byte)i, + Title = "Some post Title " + i + }); + context.Blogs.Add(blog); + context.SaveChanges(); + } + + using (var context = new BloggingContext(ConnectionString)) + { + var posts = from p in context.Posts + where p.Rating < 3 + select p; + Assert.AreEqual(3, posts.Count()); + foreach (var post in posts) + { + Assert.Less(post.Rating, 3); + } + } + } + + [Test] + public void OrderBy() + { + using (var context = new BloggingContext(ConnectionString)) + { + Random random = new Random(); + var blog = new Blog() + { + Name = "Some blog name" + }; + + blog.Posts = new List(); + for (int i = 0; i < 10; i++) + blog.Posts.Add(new Post() + { + Content = "Some post content " + i, + Rating = (byte)random.Next(0, 255), + Title = "Some post Title " + i + }); + context.Blogs.Add(blog); + context.SaveChanges(); + } + + using (var context = new BloggingContext(ConnectionString)) + { + var posts = from p in context.Posts + orderby p.Rating + select p; + Assert.AreEqual(10, posts.Count()); + byte previousValue = 0; + foreach (var post in posts) + { + Assert.GreaterOrEqual(post.Rating, previousValue); + previousValue = post.Rating; + } + } + } + + [Test] + public void OrderByThenBy() + { + using (var context = new BloggingContext(ConnectionString)) + { + Random random = new Random(); + var blog = new Blog() + { + Name = "Some blog name" + }; + + blog.Posts = new List(); + for (int i = 0; i < 10; i++) + blog.Posts.Add(new Post() + { + Content = "Some post content " + i, + Rating = (byte)random.Next(0, 255), + Title = "Some post Title " + (i % 3) + }); + context.Blogs.Add(blog); + context.SaveChanges(); + } + + using (var context = new BloggingContext(ConnectionString)) + { + var posts = context.Posts.AsQueryable().OrderBy((p) => p.Title).ThenByDescending((p) => p.Rating); + Assert.AreEqual(10, posts.Count()); + foreach (var post in posts) + { + //TODO: Check outcome + Console.WriteLine(post.Title + " " + post.Rating); + } + } + } + + //Hunting season is open Happy hunting on OrderBy,GroupBy,Min,Max,Skip,Take,ThenBy... and all posible combinations + } +} diff --git a/tests/EntityFrameworkMigrationTests.cs b/tests/EntityFrameworkMigrationTests.cs index 17ed47e5dd..5cbc07efc5 100644 --- a/tests/EntityFrameworkMigrationTests.cs +++ b/tests/EntityFrameworkMigrationTests.cs @@ -43,27 +43,10 @@ namespace NpgsqlTests { [TestFixture] - public class EntityFrameworkMigrationTests : TestBase + public class EntityFrameworkMigrationTests : EFTestBase { public EntityFrameworkMigrationTests(string backendVersion) : base(backendVersion) { } - - protected override void SetUp() - { - //Prevent TestBase to open connection since we want to drop DB in our tests - //base.SetUp(); - if (Conn != null) - { - Conn.Close(); - Conn.Dispose(); - } - } - - protected override void TearDown() - { - //Conn is already closed by SetUp... - //base.TearDown(); - } - + #region Helper method /// @@ -262,7 +245,6 @@ public void DatabaseExistsCreateDelete() Assert.IsTrue(db.Database.Exists()); db.Database.Delete(); Assert.IsFalse(db.Database.Exists()); - db.Database.Create(); } } diff --git a/tests/NpgsqlTests.csproj b/tests/NpgsqlTests.csproj index 03d40e6ca3..bf38db7639 100644 --- a/tests/NpgsqlTests.csproj +++ b/tests/NpgsqlTests.csproj @@ -165,6 +165,8 @@ + + Code From 91d36542c389c04bdfac200eb9de62b25b55c139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Karla=C5=A1?= Date: Wed, 12 Feb 2014 17:40:30 +0100 Subject: [PATCH 06/10] Going back to TestBase --- tests/EFTestBase.cs | 215 ------------------------- tests/EntityFrameworkBasicTests.cs | 25 +-- tests/EntityFrameworkMigrationTests.cs | 6 +- tests/NpgsqlTests.csproj | 1 - tests/TestBase.cs | 23 ++- 5 files changed, 38 insertions(+), 232 deletions(-) delete mode 100644 tests/EFTestBase.cs diff --git a/tests/EFTestBase.cs b/tests/EFTestBase.cs deleted file mode 100644 index d0fe08d658..0000000000 --- a/tests/EFTestBase.cs +++ /dev/null @@ -1,215 +0,0 @@ -// -// Author: -// Francisco Figueiredo Jr. -// -// Copyright (C) 2002-2005 The Npgsql Development Team -// npgsql-general@gborg.postgresql.org -// http://gborg.postgresql.org/project/npgsql/projdisplay.php -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Configuration; -using System.Diagnostics; -using System.Reflection; -using Npgsql; - -using NpgsqlTypes; - -using NUnit.Framework; - -namespace NpgsqlTests -{ - [TestFixture("9.3")] - [TestFixture("9.2")] - [TestFixture("9.1")] - [TestFixture("9.0")] - [TestFixture("8.4")] - public abstract class EFTestBase - { - protected Version BackendVersion { get; private set; } - protected ProtocolVersion BackendProtocolVersion = ProtocolVersion.Version3; - - /// - /// Constructs the parameterized test fixture - /// - /// - /// The version of the Postgres backend to be used, major and minor veresions (e.g. 9.3). - /// Used to select the conn string environment variable to be used. - /// - protected EFTestBase(string backendVersion) - { - BackendVersion = new Version(backendVersion); - } - - /// - /// The connection string that will be used when opening the connection to the tests database. - /// May be overridden in fixtures, e.g. to set special connection parameters - /// - protected virtual string ConnectionString { get { return _connectionString; } } - private string _connectionString; - - /// - /// Unless the NPGSQL_TEST_DB environment variable is defined, this is used as the connection string for the - /// test database. - /// - private const string DEFAULT_CONNECTION_STRING = "Server=localhost;User ID=npgsql_tests;Password=npgsql_tests;Database=npgsql_tests;syncnotification=false"; - - #region Setup / Teardown - - [TestFixtureSetUp] - public virtual void TestFixtureSetup() - { - var connStringEnvVar = "NPGSQL_TEST_DB_" + BackendVersion; - _connectionString = Environment.GetEnvironmentVariable(connStringEnvVar); - if (_connectionString == null) - { - if (BackendVersion == LatestBackendVersion) - { - _connectionString = DEFAULT_CONNECTION_STRING; - Console.WriteLine("Using internal default connection string: " + _connectionString); - } - else - { - Assert.Ignore("Skipping tests for backend version {0}, environment variable {1} isn't defined", BackendVersion, connStringEnvVar); - return; - } - } - else - Console.WriteLine("Using connection string provided in env var {0}: {1}", connStringEnvVar, _connectionString); - - if (_connectionString.Contains("protocol")) - throw new Exception("Connection string base cannot contain protocol"); - _connectionString += ";protocol=" + (int)BackendProtocolVersion; - - //Reuse all strings just add _ef at end of database name for - var connectionSB = new NpgsqlConnectionStringBuilder(_connectionString); - connectionSB.Database += "_ef"; - _connectionString = connectionSB.ConnectionString; - - try - { - SuppressBinaryBackendEncoding = InitBinaryBackendSuppression(); - } - catch - // Throwing an exception here causes all tests to fail without running. - // CommandTests.SuppressBinaryBackendEncodingInitTest() provides error information in event of failure. - { } - } - - /// - /// Uses reflection to read the [TextFixture] attributes on this class and extract the latest - /// Postgres backend version specified within them. - /// - private Version LatestBackendVersion - { - get - { - return typeof(TestBase) - .GetCustomAttributes(typeof(TestFixtureAttribute), false) - .Cast() - .Select(a => new Version((string)a.Arguments[0])) - .Max(); - } - } - - #endregion - - #region Binary backend suppression - - // Some tests need to suppress binary backend formatting of parameters and result values, - // so that both binary and text formatting can be tested. - // Setting NpgsqlTypes.NpgsqlTypesHelper.SuppressBinaryBackendEncoding accomplishes this, but it is intentionally internal. - // Since it's internal, reflection is required to observe and set it. - protected FieldInfo SuppressBinaryBackendEncoding; - - // Use reflection to bind to NpgsqlTypes.NpgsqlTypesHelper.SuppressBinaryBackendEncoding. - protected FieldInfo InitBinaryBackendSuppression() - { - Assembly npgsql; - Type typesHelper; - FieldInfo fi; - - npgsql = Assembly.Load("Npgsql"); - - // GetType() can return null. Check for this situation and report it. - try - { - typesHelper = npgsql.GetType("NpgsqlTypes.NpgsqlTypesHelper"); - - Assert.IsNotNull(typesHelper, "GetType(\"NpgsqlTypes.NpgsqlTypesHelper\") returned null indicating class not found"); - } - catch (Exception e) - { - throw new Exception("Failed to bind to class NpgsqlTypes.NpgsqlTypesHelper", e); - } - - // GetField() can return null. Check for this situation and report it. - try - { - fi = typesHelper.GetField("SuppressBinaryBackendEncoding", BindingFlags.Static | BindingFlags.NonPublic); - - Assert.IsNotNull(fi, "GetField(\"SuppressBinaryBackendEncoding\") returned null indicating field not found"); - } - catch (Exception e) - { - throw new Exception("Failed to bind to field NpgsqlTypes.NpgsqlTypesHelper.SuppressBinaryBackendEncoding", e); - } - - Assert.IsTrue(fi.FieldType == typeof(bool), "Field NpgsqlTypes.NpgsqlTypesHelper.SuppressBinaryBackendEncoding is not boolean"); - - return fi; - } - - protected class BackendBinarySuppressor : IDisposable - { - private FieldInfo suppressionField; - - internal BackendBinarySuppressor(FieldInfo suppressionField) - { - this.suppressionField = suppressionField; - suppressionField.SetValue(null, true); - } - - public void Dispose() - { - suppressionField.SetValue(null, false); - } - } - - /// - /// Return a BackendBinarySuppressor which has suppressed backend binary encoding. - /// When it is disposed, suppression will be ended. Example: - /// using (SuppressBackendBinary()) - /// { - /// // Test text encoding functionality here. - /// } - /// - protected BackendBinarySuppressor SuppressBackendBinary() - { - Assert.IsTrue( - SuppressBinaryBackendEncoding != null && SuppressBinaryBackendEncoding.FieldType == typeof(bool), - "SuppressBinaryBackendEncoding is null or not boolean; binary backend encoding cannot be suppressed. Check test CommandTests.__SuppressBinaryBackendEncodingInitTest() for more information" - ); - - return new BackendBinarySuppressor(SuppressBinaryBackendEncoding); - } - - #endregion - } -} diff --git a/tests/EntityFrameworkBasicTests.cs b/tests/EntityFrameworkBasicTests.cs index 53231a82df..44f76bcd4c 100644 --- a/tests/EntityFrameworkBasicTests.cs +++ b/tests/EntityFrameworkBasicTests.cs @@ -10,7 +10,7 @@ namespace NpgsqlTests { [TestFixture] - public class EntityFrameworkBasicTests : EFTestBase + public class EntityFrameworkBasicTests : TestBase { public EntityFrameworkBasicTests(string backendVersion) : base(backendVersion) @@ -21,7 +21,7 @@ public EntityFrameworkBasicTests(string backendVersion) public override void TestFixtureSetup() { base.TestFixtureSetup(); - using (var context = new BloggingContext(ConnectionString)) + using (var context = new BloggingContext(ConnectionStringEF)) { if (context.Database.Exists()) context.Database.Delete();//We delete to be 100% schema is synced @@ -33,9 +33,10 @@ public override void TestFixtureSetup() /// Clean any previous entites before our test /// [SetUp] - public void ClearContext() + protected override void SetUp() { - using (var context = new BloggingContext(ConnectionString)) + base.SetUp(); + using (var context = new BloggingContext(ConnectionStringEF)) { context.Blogs.RemoveRange(context.Blogs); context.Posts.RemoveRange(context.Posts); @@ -76,7 +77,7 @@ public BloggingContext(string connection) [Test] public void InsertAndSelect() { - using (var context = new BloggingContext(ConnectionString)) + using (var context = new BloggingContext(ConnectionStringEF)) { var blog = new Blog() { @@ -94,7 +95,7 @@ public void InsertAndSelect() context.SaveChanges(); } - using (var context = new BloggingContext(ConnectionString)) + using (var context = new BloggingContext(ConnectionStringEF)) { var posts = from p in context.Posts select p; @@ -109,7 +110,7 @@ public void InsertAndSelect() [Test] public void SelectWithWhere() { - using (var context = new BloggingContext(ConnectionString)) + using (var context = new BloggingContext(ConnectionStringEF)) { var blog = new Blog() { @@ -127,7 +128,7 @@ public void SelectWithWhere() context.SaveChanges(); } - using (var context = new BloggingContext(ConnectionString)) + using (var context = new BloggingContext(ConnectionStringEF)) { var posts = from p in context.Posts where p.Rating < 3 @@ -143,7 +144,7 @@ where p.Rating < 3 [Test] public void OrderBy() { - using (var context = new BloggingContext(ConnectionString)) + using (var context = new BloggingContext(ConnectionStringEF)) { Random random = new Random(); var blog = new Blog() @@ -163,7 +164,7 @@ public void OrderBy() context.SaveChanges(); } - using (var context = new BloggingContext(ConnectionString)) + using (var context = new BloggingContext(ConnectionStringEF)) { var posts = from p in context.Posts orderby p.Rating @@ -181,7 +182,7 @@ orderby p.Rating [Test] public void OrderByThenBy() { - using (var context = new BloggingContext(ConnectionString)) + using (var context = new BloggingContext(ConnectionStringEF)) { Random random = new Random(); var blog = new Blog() @@ -201,7 +202,7 @@ public void OrderByThenBy() context.SaveChanges(); } - using (var context = new BloggingContext(ConnectionString)) + using (var context = new BloggingContext(ConnectionStringEF)) { var posts = context.Posts.AsQueryable().OrderBy((p) => p.Title).ThenByDescending((p) => p.Rating); Assert.AreEqual(10, posts.Count()); diff --git a/tests/EntityFrameworkMigrationTests.cs b/tests/EntityFrameworkMigrationTests.cs index 5cbc07efc5..6dad5b798a 100644 --- a/tests/EntityFrameworkMigrationTests.cs +++ b/tests/EntityFrameworkMigrationTests.cs @@ -43,7 +43,7 @@ namespace NpgsqlTests { [TestFixture] - public class EntityFrameworkMigrationTests : EFTestBase + public class EntityFrameworkMigrationTests : TestBase { public EntityFrameworkMigrationTests(string backendVersion) : base(backendVersion) { } @@ -75,7 +75,7 @@ private void PrintCode(IEnumerable - diff --git a/tests/TestBase.cs b/tests/TestBase.cs index 0742a7415e..f02e401d38 100644 --- a/tests/TestBase.cs +++ b/tests/TestBase.cs @@ -69,6 +69,27 @@ protected TestBase(string backendVersion) protected virtual string ConnectionString { get { return _connectionString; } } private string _connectionString; + + /// + /// New ConectionString property crafted to change the database name from original TestBase.ConnectionString to append a "_ef" suffix. + /// i.e.: the TestBase.ConnectionString database is npgsql_tests. Entity Framework database will be npgsql_tests_ef. + /// + protected virtual string ConnectionStringEF + { + get + { + if (connectionStringEF == null) + { + //Reuse all strings just add _ef at end of database name for + var connectionSB = new NpgsqlConnectionStringBuilder(ConnectionString); + connectionSB.Database += "_ef"; + connectionStringEF = connectionSB.ConnectionString; + } + return connectionStringEF; + } + } + private string connectionStringEF; + /// /// Unless the NPGSQL_TEST_DB environment variable is defined, this is used as the connection string for the /// test database. @@ -84,7 +105,7 @@ protected TestBase(string backendVersion) #region Setup / Teardown [TestFixtureSetUp] - public void TestFixtureSetup() + public virtual void TestFixtureSetup() { var connStringEnvVar = "NPGSQL_TEST_DB_" + BackendVersion; _connectionString = Environment.GetEnvironmentVariable(connStringEnvVar); From 9ff75aa5a132d8b9551476d4ed98e698d554390c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Karla=C5=A1?= Date: Wed, 12 Feb 2014 20:29:25 +0100 Subject: [PATCH 07/10] Removing CREATE EXTENSION and adding "select * from uuid_generate_v4()" check... --- .../NpgsqlMigrationSqlGenerator.cs | 30 +++++++++++-------- tests/EntityFrameworkMigrationTests.cs | 5 ++-- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/Npgsql.EntityFramework/NpgsqlMigrationSqlGenerator.cs b/Npgsql.EntityFramework/NpgsqlMigrationSqlGenerator.cs index f85f9f7e7b..c19a567671 100644 --- a/Npgsql.EntityFramework/NpgsqlMigrationSqlGenerator.cs +++ b/Npgsql.EntityFramework/NpgsqlMigrationSqlGenerator.cs @@ -238,17 +238,17 @@ private void CreateSchema(string 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 + "\""); - } - } + //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 + "\""); + // } + //} private void Convert(MoveTableOperation moveTableOperation) { @@ -333,7 +333,9 @@ private void Convert(AlterColumnOperation alterColumnOperation) //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"); + //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: @@ -542,7 +544,9 @@ private void AppendColumn(ColumnModel column, StringBuilder sql) switch (column.Type) { case PrimitiveTypeKind.Guid: - CreateExtension("uuid-ossp"); + //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: diff --git a/tests/EntityFrameworkMigrationTests.cs b/tests/EntityFrameworkMigrationTests.cs index 6dad5b798a..55ec289daf 100644 --- a/tests/EntityFrameworkMigrationTests.cs +++ b/tests/EntityFrameworkMigrationTests.cs @@ -168,7 +168,8 @@ public void CreateBloggingContext() expectedColumns.Remove((string)reader[0]); Assert.AreEqual("uuid", (string)reader[1]); Assert.AreEqual("NO", (string)reader[2]); - Assert.AreEqual("uuid_generate_v4()", reader[3] as string); + Assert.AreEqual("'00000000-0000-0000-0000-000000000000'::uuid", reader[3] as string); + //Assert.AreEqual("uuid_generate_v4()", reader[3] as string); break; case "Rating": expectedColumns.Remove((string)reader[0]); @@ -206,7 +207,7 @@ public class Post public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + //[DatabaseGenerated(DatabaseGeneratedOption.Identity)] public Guid UniqueId { get; set; } public byte? Rating { get; set; } From 2d8e6d7ab06eec0dc1a936489f883f80df80c8f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Karla=C5=A1?= Date: Mon, 17 Feb 2014 21:40:41 +0100 Subject: [PATCH 08/10] Switching EF 6.0.2 to EF 6.0.1 --- Npgsql.EntityFramework/packages.config | 2 +- tests/NpgsqlTests.csproj | 4 ++-- tests/packages.config | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Npgsql.EntityFramework/packages.config b/Npgsql.EntityFramework/packages.config index 2962f8dc4b..4fef52d215 100755 --- a/Npgsql.EntityFramework/packages.config +++ b/Npgsql.EntityFramework/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/tests/NpgsqlTests.csproj b/tests/NpgsqlTests.csproj index 52b6ea9fa5..63fce33c07 100644 --- a/tests/NpgsqlTests.csproj +++ b/tests/NpgsqlTests.csproj @@ -139,10 +139,10 @@ - ..\packages\EntityFramework.6.0.2\lib\net40\EntityFramework.dll + ..\packages\EntityFramework.6.0.1\lib\net40\EntityFramework.dll - ..\packages\EntityFramework.6.0.2\lib\net40\EntityFramework.SqlServer.dll + ..\packages\EntityFramework.6.0.1\lib\net40\EntityFramework.SqlServer.dll ..\packages\NUnit.2.6.2\lib\nunit.framework.dll diff --git a/tests/packages.config b/tests/packages.config index d8e1560a45..fbdacc1a3b 100644 --- a/tests/packages.config +++ b/tests/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file From d1dc366dfb4d4c305f96390bf7dd00da7f32b9a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Karla=C5=A1?= Date: Thu, 20 Feb 2014 06:16:38 +0100 Subject: [PATCH 09/10] Corrected test expected results for backends < 9.3 --- tests/EntityFrameworkMigrationTests.cs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/EntityFrameworkMigrationTests.cs b/tests/EntityFrameworkMigrationTests.cs index 55ec289daf..c669d79db0 100644 --- a/tests/EntityFrameworkMigrationTests.cs +++ b/tests/EntityFrameworkMigrationTests.cs @@ -365,7 +365,10 @@ public void TestCreateTableOperation() operations.Add(operation); var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); Assert.AreEqual(2, statments.Count()); - Assert.AreEqual("CREATE SCHEMA IF NOT EXISTS someSchema", statments.ElementAt(0).Sql); + if (BackendVersion.Major > 9 || (BackendVersion.Major == 9 && BackendVersion.Minor > 2)) + Assert.AreEqual("CREATE SCHEMA IF NOT EXISTS someSchema", statments.ElementAt(0).Sql); + else + Assert.AreEqual("CREATE SCHEMA someSchema", statments.ElementAt(0).Sql); Assert.AreEqual("CREATE TABLE \"someSchema\".\"someTable\"(\"SomeString\" varchar(233) NOT NULL DEFAULT '',\"AnotherString\" text,\"SomeBytes\" bytea,\"SomeLong\" serial8,\"SomeDateTime\" timestamp)", statments.ElementAt(1).Sql); } @@ -477,7 +480,10 @@ public void TestMoveTableOperation() operations.Add(new MoveTableOperation("someOldSchema.someTable", "someNewSchema")); var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); Assert.AreEqual(2, statments.Count()); - Assert.AreEqual("CREATE SCHEMA IF NOT EXISTS someNewSchema", statments.ElementAt(0).Sql); + if (BackendVersion.Major > 9 || (BackendVersion.Major == 9 && BackendVersion.Minor > 2)) + Assert.AreEqual("CREATE SCHEMA IF NOT EXISTS someNewSchema", statments.ElementAt(0).Sql); + else + Assert.AreEqual("CREATE SCHEMA someNewSchema", statments.ElementAt(0).Sql); Assert.AreEqual("ALTER TABLE \"someOldSchema\".\"someTable\" SET SCHEMA someNewSchema", statments.ElementAt(1).Sql); } @@ -488,7 +494,10 @@ public void TestMoveTableOperationNewSchemaIsNull() operations.Add(new MoveTableOperation("someOldSchema.someTable", null)); var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); Assert.AreEqual(2, statments.Count()); - Assert.AreEqual("CREATE SCHEMA IF NOT EXISTS dbo", statments.ElementAt(0).Sql); + if (BackendVersion.Major > 9 || (BackendVersion.Major == 9 && BackendVersion.Minor > 2)) + Assert.AreEqual("CREATE SCHEMA IF NOT EXISTS dbo", statments.ElementAt(0).Sql); + else + Assert.AreEqual("CREATE SCHEMA dbo", statments.ElementAt(0).Sql); Assert.AreEqual("ALTER TABLE \"someOldSchema\".\"someTable\" SET SCHEMA dbo", statments.ElementAt(1).Sql); } @@ -616,7 +625,10 @@ public void TestDropForeignKeyOperation() operations.Add(operation); var statments = new NpgsqlMigrationSqlGenerator().Generate(operations, BackendVersion.ToString()); Assert.AreEqual(1, statments.Count()); - Assert.AreEqual("ALTER TABLE \"someTable\" DROP CONSTRAINT IF EXISTS \"someFK\"", statments.ElementAt(0).Sql); + if (BackendVersion.Major > 8) + Assert.AreEqual("ALTER TABLE \"someTable\" DROP CONSTRAINT IF EXISTS \"someFK\"", statments.ElementAt(0).Sql); + else + Assert.AreEqual("ALTER TABLE \"someTable\" DROP CONSTRAINT \"someFK\"", statments.ElementAt(0).Sql); } [Test] From 5ec156aa5abf490c8e50335ec4dce4c1ddde0ee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Karla=C5=A1?= Date: Thu, 20 Feb 2014 17:27:49 +0100 Subject: [PATCH 10/10] EFBasicTests only included in .Net4+ --- tests/EntityFrameworkBasicTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/EntityFrameworkBasicTests.cs b/tests/EntityFrameworkBasicTests.cs index 44f76bcd4c..032403a9e0 100644 --- a/tests/EntityFrameworkBasicTests.cs +++ b/tests/EntityFrameworkBasicTests.cs @@ -1,4 +1,5 @@ -using Npgsql; +#if NET40 +using Npgsql; using NUnit.Framework; using System; using System.Collections.Generic; @@ -217,3 +218,4 @@ public void OrderByThenBy() //Hunting season is open Happy hunting on OrderBy,GroupBy,Min,Max,Skip,Take,ThenBy... and all posible combinations } } +#endif \ No newline at end of file