diff --git a/src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs b/src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs index 25b2381c0..5ad63448b 100644 --- a/src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs +++ b/src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs @@ -75,7 +75,7 @@ public override MethodCallCodeFragment GenerateFluentApi(IModel model, IAnnotati extension.Name); } - if (annotation.Name.StartsWith(NpgsqlAnnotationNames.EnumPrefix)) + if (annotation.Name.StartsWith(NpgsqlAnnotationNames.EnumPrefix, StringComparison.Ordinal)) { var enumTypeDef = new PostgresEnum(model, annotation.Name); diff --git a/src/EFCore.PG/Metadata/PostgresEnum.cs b/src/EFCore.PG/Metadata/PostgresEnum.cs index 929da007c..c42566ef6 100644 --- a/src/EFCore.PG/Metadata/PostgresEnum.cs +++ b/src/EFCore.PG/Metadata/PostgresEnum.cs @@ -9,43 +9,101 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Metadata { + /// + /// Represents the metadata for a PostgreSQL enum. + /// + [PublicAPI] public class PostgresEnum { - readonly IAnnotatable _annotatable; - readonly string _annotationName; - - internal PostgresEnum(IAnnotatable annotatable, string annotationName) + [NotNull] readonly IAnnotatable _annotatable; + [NotNull] readonly string _annotationName; + + /// + /// Creates a . + /// + /// The annotatable to search for the annotation. + /// The annotation name to search for in the annotatable. + /// + /// + internal PostgresEnum([NotNull] IAnnotatable annotatable, [NotNull] string annotationName) { - _annotatable = annotatable; - _annotationName = annotationName; + _annotatable = Check.NotNull(annotatable, nameof(annotatable)); + _annotationName = Check.NotNull(annotationName, nameof(annotationName)); } + /// + /// Gets or adds a from or to the . + /// + /// The annotatable from which to get or add the enum. + /// The enum schema or null to use the model's default schema. + /// The enum name. + /// The enum labels. + /// + /// The from the . + /// + /// + /// + /// + /// + [NotNull] public static PostgresEnum GetOrAddPostgresEnum( [NotNull] IMutableAnnotatable annotatable, [CanBeNull] string schema, [NotNull] string name, [NotNull] string[] labels) { + Check.NotNull(annotatable, nameof(annotatable)); + Check.NullButNotEmpty(schema, nameof(schema)); + Check.NotEmpty(name, nameof(name)); + Check.NotNull(labels, nameof(labels)); + if (FindPostgresEnum(annotatable, schema, name) is PostgresEnum enumType) return enumType; - enumType = new PostgresEnum(annotatable, BuildAnnotationName(schema, name)); - enumType.SetData(labels); - return enumType; + var annotationName = BuildAnnotationName(schema, name); + + return new PostgresEnum(annotatable, annotationName) { Labels = labels }; } + /// + /// Gets or adds a from or to the . + /// + /// The annotatable from which to get or add the enum. + /// The enum name. + /// The enum labels. + /// + /// The from the . + /// + /// + /// + /// + [NotNull] public static PostgresEnum GetOrAddPostgresEnum( [NotNull] IMutableAnnotatable annotatable, [NotNull] string name, [NotNull] string[] labels) => GetOrAddPostgresEnum(annotatable, null, name, labels); + /// + /// Finds a in the , or returns null if not found. + /// + /// The annotatable to search for the enum. + /// The enum schema or null to use the model's default schema. + /// The enum name. + /// + /// The from the . + /// + /// + /// + /// + [CanBeNull] public static PostgresEnum FindPostgresEnum( [NotNull] IAnnotatable annotatable, [CanBeNull] string schema, [NotNull] string name) { Check.NotNull(annotatable, nameof(annotatable)); + Check.NullButNotEmpty(schema, nameof(schema)); Check.NotEmpty(name, nameof(name)); var annotationName = BuildAnnotationName(schema, name); @@ -53,50 +111,72 @@ public static PostgresEnum FindPostgresEnum( return annotatable[annotationName] == null ? null : new PostgresEnum(annotatable, annotationName); } + [NotNull] static string BuildAnnotationName(string schema, string name) - => NpgsqlAnnotationNames.EnumPrefix + (schema == null ? name : schema + '.' + name); - + => schema != null + ? $"{NpgsqlAnnotationNames.EnumPrefix}{schema}.{name}" + : $"{NpgsqlAnnotationNames.EnumPrefix}{name}"; + + /// + /// Gets the collection of stored in the . + /// + /// The annotatable to search for annotations. + /// + /// The collection of stored in the . + /// + /// + [NotNull] + [ItemNotNull] public static IEnumerable GetPostgresEnums([NotNull] IAnnotatable annotatable) - { - Check.NotNull(annotatable, nameof(annotatable)); - - return annotatable.GetAnnotations() - .Where(a => a.Name.StartsWith(NpgsqlAnnotationNames.EnumPrefix, StringComparison.Ordinal)) - .Select(a => new PostgresEnum(annotatable, a.Name)); - } - + => Check.NotNull(annotatable, nameof(annotatable)) + .GetAnnotations() + .Where(a => a.Name.StartsWith(NpgsqlAnnotationNames.EnumPrefix, StringComparison.Ordinal)) + .Select(a => new PostgresEnum(annotatable, a.Name)); + + /// + /// The that stores the enum. + /// + [NotNull] public Annotatable Annotatable => (Annotatable)_annotatable; + /// + /// The enum schema or null to represent the default schema. + /// + [CanBeNull] public string Schema => GetData().Schema; + /// + /// The enum name. + /// + [NotNull] public string Name => GetData().Name; - public string[] Labels + /// + /// The enum labels. + /// + [NotNull] + public IReadOnlyList Labels { get => GetData().Labels; set => SetData(value); } (string Schema, string Name, string[] Labels) GetData() - { - return !(Annotatable[_annotationName] is string annotationValue) - ? (null, null, null) - : Deserialize(_annotationName, annotationValue); - } + => Deserialize(Annotatable.FindAnnotation(_annotationName)); - void SetData(string[] labels) + void SetData([NotNull] IEnumerable labels) => Annotatable[_annotationName] = string.Join(",", labels); - static (string schema, string name, string[] labels) Deserialize( - [NotNull] string annotationName, - [NotNull] string annotationValue) + static (string Schema, string Name, string[] Labels) Deserialize([CanBeNull] IAnnotation annotation) { - Check.NotEmpty(annotationValue, nameof(annotationValue)); + if (annotation == null || !(annotation.Value is string value) || string.IsNullOrEmpty(value)) + return (null, null, null); - var labels = annotationValue.Split(',').ToArray(); + var labels = value.Split(','); + // TODO: This would be a safer operation if we stored schema and name in the annotation value (see Sequence.cs). // Yes, this doesn't support dots in the schema/enum name, let somebody complain first. - var schemaAndName = annotationName.Substring(NpgsqlAnnotationNames.EnumPrefix.Length).Split('.'); + var schemaAndName = annotation.Name.Substring(NpgsqlAnnotationNames.EnumPrefix.Length).Split('.'); switch (schemaAndName.Length) { case 1: @@ -104,7 +184,7 @@ void SetData(string[] labels) case 2: return (schemaAndName[0], schemaAndName[1], labels); default: - throw new ArgumentException("Cannot parse enum name from annotation: " + annotationName); + throw new ArgumentException($"Cannot parse enum name from annotation: {annotation.Name}"); } } } diff --git a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs index bfc10b98e..9d335b33c 100644 --- a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs +++ b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs @@ -638,6 +638,7 @@ protected override void Generate( MigrationCommandListBuilder builder) { Check.NotNull(operation, nameof(operation)); + Check.NotNull(model, nameof(model)); Check.NotNull(builder, nameof(builder)); GenerateEnumStatements(operation, model, builder); @@ -691,7 +692,7 @@ protected virtual void GenerateEnumStatements( foreach (var enumTypeToDrop in operation.Npgsql().OldPostgresEnums .Where(oe => operation.Npgsql().PostgresEnums.All(ne => ne.Name != oe.Name))) { - GenerateDropEnum(enumTypeToDrop, operation.OldDatabase, builder); + GenerateDropEnum(enumTypeToDrop, model, builder); } // TODO: Some forms of enum alterations are actually supported... @@ -708,7 +709,7 @@ protected virtual void GenerateCreateEnum( [NotNull] IModel model, [NotNull] MigrationCommandListBuilder builder) { - var schema = GetSchemaOrDefault(enumType.Schema, model); + var schema = enumType.Schema ?? model.Relational().DefaultSchema; // Schemas are normally created (or rather ensured) by the model differ, which scans all tables, sequences // and other database objects. However, it isn't aware of enums, so we always ensure schema on enum creation. @@ -723,10 +724,10 @@ protected virtual void GenerateCreateEnum( var stringTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(string)); var labels = enumType.Labels; - for (var i = 0; i < labels.Length; i++) + for (var i = 0; i < labels.Count; i++) { builder.Append(stringTypeMapping.GenerateSqlLiteral(labels[i])); - if (i < labels.Length - 1) + if (i < labels.Count - 1) builder.Append(", "); } @@ -735,10 +736,10 @@ protected virtual void GenerateCreateEnum( protected virtual void GenerateDropEnum( [NotNull] PostgresEnum enumType, - [CanBeNull] IAnnotatable oldDatabase, + [NotNull] IModel model, [NotNull] MigrationCommandListBuilder builder) { - var schema = GetSchemaOrDefault(enumType.Schema, oldDatabase); + var schema = enumType.Schema ?? model.Relational().DefaultSchema; builder .Append("DROP TYPE ") @@ -1117,10 +1118,6 @@ static string GenerateStorageParameterValue(object value) #region Helpers - [CanBeNull] - static string GetSchemaOrDefault([CanBeNull] string schema, [CanBeNull] IAnnotatable model) - => schema ?? model?.FindAnnotation(RelationalAnnotationNames.DefaultSchema)?.Value as string; - /// /// True if is null, greater than, or equal to the specified version. ///