Skip to content

Commit d644272

Browse files
Refactor range metadata following 685 (#728)
1 parent fefaaec commit d644272

3 files changed

Lines changed: 135 additions & 66 deletions

File tree

src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public override MethodCallCodeFragment GenerateFluentApi(IModel model, IAnnotati
8686
enumTypeDef.Schema, enumTypeDef.Name, enumTypeDef.Labels);
8787
}
8888

89-
if (annotation.Name.StartsWith(NpgsqlAnnotationNames.RangePrefix))
89+
if (annotation.Name.StartsWith(NpgsqlAnnotationNames.RangePrefix, StringComparison.Ordinal))
9090
{
9191
var rangeTypeDef = new PostgresRange(model, annotation.Name);
9292

src/EFCore.PG/Metadata/PostgresRange.cs

Lines changed: 121 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,47 @@
99

1010
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Metadata
1111
{
12+
/// <summary>
13+
/// Represents the metadata for a PostgreSQL range.
14+
/// </summary>
15+
[PublicAPI]
1216
public class PostgresRange
1317
{
1418
[NotNull] readonly IAnnotatable _annotatable;
1519
[NotNull] readonly string _annotationName;
1620

21+
/// <summary>
22+
/// Creates a <see cref="PostgresRange"/>.
23+
/// </summary>
24+
/// <param name="annotatable">The annotatable to search for the annotation.</param>
25+
/// <param name="annotationName">The annotation name to search for in the annotatable.</param>
26+
/// <exception cref="ArgumentNullException"><paramref name="annotatable"/></exception>
27+
/// <exception cref="ArgumentNullException"><paramref name="annotationName"/></exception>
1728
internal PostgresRange([NotNull] IAnnotatable annotatable, [NotNull] string annotationName)
1829
{
19-
_annotatable = annotatable;
20-
_annotationName = annotationName;
30+
_annotatable = Check.NotNull(annotatable, nameof(annotatable));
31+
_annotationName = Check.NotNull(annotationName, nameof(annotationName));
2132
}
2233

34+
/// <summary>
35+
/// Gets or adds a <see cref="PostgresRange"/> from or to the <see cref="IMutableAnnotatable"/>.
36+
/// </summary>
37+
/// <param name="annotatable">The annotatable from which to get or add the range.</param>
38+
/// <param name="schema">The range schema or null to use the model's default schema.</param>
39+
/// <param name="name">The range name.</param>
40+
/// <param name="subtype">The range subtype.</param>
41+
/// <param name="canonicalFunction"></param>
42+
/// <param name="subtypeOpClass"></param>
43+
/// <param name="collation"></param>
44+
/// <param name="subtypeDiff"></param>
45+
/// <returns>
46+
/// The <see cref="PostgresRange"/> from the <see cref="IMutableAnnotatable"/>.
47+
/// </returns>
48+
/// <exception cref="ArgumentException"><paramref name="schema"/></exception>
49+
/// <exception cref="ArgumentNullException"><paramref name="annotatable"/></exception>
50+
/// <exception cref="ArgumentNullException"><paramref name="name"/></exception>
51+
/// <exception cref="ArgumentNullException"><paramref name="subtype"/></exception>
52+
[NotNull]
2353
public static PostgresRange GetOrAddPostgresRange(
2454
[NotNull] IMutableAnnotatable annotatable,
2555
[CanBeNull] string schema,
@@ -30,143 +60,175 @@ public static PostgresRange GetOrAddPostgresRange(
3060
string collation = null,
3161
string subtypeDiff = null)
3262
{
33-
if (FindPostgresRange(annotatable, schema, name) is PostgresRange rangeType)
34-
return rangeType;
63+
Check.NotNull(annotatable, nameof(annotatable));
64+
Check.NullButNotEmpty(schema, nameof(schema));
65+
Check.NotEmpty(name, nameof(name));
66+
Check.NotNull(subtype, nameof(subtype));
67+
68+
if (FindPostgresRange(annotatable, schema, name) is PostgresRange postgresRange)
69+
return postgresRange;
70+
71+
var annotationName = BuildAnnotationName(schema, name);
3572

36-
rangeType = new PostgresRange(annotatable, BuildAnnotationName(annotatable, schema, name));
37-
rangeType.SetData(subtype, canonicalFunction, subtypeOpClass, collation, subtypeDiff);
38-
return rangeType;
73+
return new PostgresRange(annotatable, annotationName)
74+
{
75+
Subtype = subtype,
76+
CanonicalFunction = canonicalFunction,
77+
SubtypeOpClass = subtypeOpClass,
78+
Collation = collation,
79+
SubtypeDiff = subtypeDiff,
80+
};
3981
}
4082

83+
/// <summary>
84+
/// Finds a <see cref="PostgresRange"/> in the <see cref="IAnnotatable"/>, or returns null if not found.
85+
/// </summary>
86+
/// <param name="annotatable">The annotatable to search for the range.</param>
87+
/// <param name="schema">The range schema or null to use the model's default schema.</param>
88+
/// <param name="name">The range name.</param>
89+
/// <returns>
90+
/// The <see cref="PostgresRange"/> from the <see cref="IAnnotatable"/>.
91+
/// </returns>
92+
/// <exception cref="ArgumentException"><paramref name="schema"/></exception>
93+
/// <exception cref="ArgumentNullException"><paramref name="annotatable"/></exception>
94+
/// <exception cref="ArgumentNullException"><paramref name="name"/></exception>
4195
[CanBeNull]
4296
public static PostgresRange FindPostgresRange(
4397
[NotNull] IAnnotatable annotatable,
4498
[CanBeNull] string schema,
4599
[NotNull] string name)
46100
{
47101
Check.NotNull(annotatable, nameof(annotatable));
102+
Check.NullButNotEmpty(schema, nameof(schema));
48103
Check.NotEmpty(name, nameof(name));
49104

50-
var annotationName = BuildAnnotationName(annotatable, schema, name);
105+
var annotationName = BuildAnnotationName(schema, name);
51106

52107
return annotatable[annotationName] == null ? null : new PostgresRange(annotatable, annotationName);
53108
}
54109

55110
[NotNull]
56-
static string BuildAnnotationName([NotNull] IAnnotatable annotatable, [CanBeNull] string schema, [NotNull] string name)
57-
=> !string.IsNullOrEmpty(schema)
111+
static string BuildAnnotationName(string schema, string name)
112+
=> schema != null
58113
? $"{NpgsqlAnnotationNames.RangePrefix}{schema}.{name}"
59-
: annotatable[RelationalAnnotationNames.DefaultSchema] is string defaultSchema && !string.IsNullOrEmpty(defaultSchema)
60-
? $"{NpgsqlAnnotationNames.RangePrefix}{defaultSchema}.{name}"
61-
: $"{NpgsqlAnnotationNames.RangePrefix}{name}";
62-
114+
: $"{NpgsqlAnnotationNames.RangePrefix}{name}";
115+
116+
/// <summary>
117+
/// Gets the collection of <see cref="PostgresRange"/> stored in the <see cref="IAnnotatable"/>.
118+
/// </summary>
119+
/// <param name="annotatable">The annotatable to search for <see cref="PostgresRange"/> annotations.</param>
120+
/// <returns>
121+
/// The collection of <see cref="PostgresRange"/> stored in the <see cref="IAnnotatable"/>.
122+
/// </returns>
123+
/// <exception cref="ArgumentNullException"><paramref name="annotatable"/></exception>
63124
[NotNull]
125+
[ItemNotNull]
64126
public static IEnumerable<PostgresRange> GetPostgresRanges([NotNull] IAnnotatable annotatable)
65127
=> Check.NotNull(annotatable, nameof(annotatable))
66128
.GetAnnotations()
67129
.Where(a => a.Name.StartsWith(NpgsqlAnnotationNames.RangePrefix, StringComparison.Ordinal))
68130
.Select(a => new PostgresRange(annotatable, a.Name));
69131

132+
/// <summary>
133+
/// The <see cref="Annotatable"/> that stores the range.
134+
/// </summary>
70135
[NotNull]
71136
public Annotatable Annotatable => (Annotatable)_annotatable;
72137

138+
/// <summary>
139+
/// The range schema or null to represent the default schema.
140+
/// </summary>
73141
[CanBeNull]
74-
public string Schema => GetData().Schema ?? (string)_annotatable[RelationalAnnotationNames.DefaultSchema];
142+
public string Schema => GetData().Schema;
75143

144+
/// <summary>
145+
/// The range name.
146+
/// </summary>
76147
[NotNull]
77148
public string Name => GetData().Name;
78149

150+
/// <summary>
151+
/// The subtype of the range.
152+
/// </summary>
79153
[NotNull]
80154
public string Subtype
81155
{
82156
get => GetData().Subtype;
83-
set
84-
{
85-
var x = GetData();
86-
var (_, _, _, canonicalFunction, subtypeOpClass, collation, subtypeDiff) = GetData();
87-
SetData(value, canonicalFunction, subtypeOpClass, collation, subtypeDiff);
88-
}
157+
set => SetData(subtype: value);
89158
}
90159

160+
/// <summary>
161+
/// The function defining a "step" in a discrete range.
162+
/// </summary>
91163
[CanBeNull]
92164
public string CanonicalFunction
93165
{
94166
get => GetData().CanonicalFunction;
95-
set
96-
{
97-
var x = GetData();
98-
var (_, _, subtype, _, subtypeOpClass, collation, subtypeDiff) = GetData();
99-
SetData(subtype, value, subtypeOpClass, collation, subtypeDiff);
100-
}
167+
set => SetData(canonicalFunction: value);
101168
}
102169

170+
/// <summary>
171+
/// The operator class to use.
172+
/// </summary>
103173
[CanBeNull]
104174
public string SubtypeOpClass
105175
{
106176
get => GetData().SubtypeOpClass;
107-
set
108-
{
109-
var x = GetData();
110-
var (_, _, subtype, canonicalFunction, _, collation, subtypeDiff) = GetData();
111-
SetData(subtype, canonicalFunction, value, collation, subtypeDiff);
112-
}
177+
set => SetData(subtypeOpClass: value);
113178
}
114179

180+
/// <summary>
181+
/// The collation to use.
182+
/// </summary>
115183
[CanBeNull]
116184
public string Collation
117185
{
118186
get => GetData().Collation;
119-
set
120-
{
121-
var x = GetData();
122-
var (_, _, subtype, canonicalFunction, subtypeOpClass, _, subtypeDiff) = GetData();
123-
SetData(subtype, canonicalFunction, subtypeOpClass, value, subtypeDiff);
124-
}
187+
set => SetData(collation: value);
125188
}
126189

190+
/// <summary>
191+
/// The function defining a difference in subtype values.
192+
/// </summary>
127193
[CanBeNull]
128194
public string SubtypeDiff
129195
{
130196
get => GetData().SubtypeDiff;
131-
set
132-
{
133-
var x = GetData();
134-
var (_, _, subtype, canonicalFunction, subtypeOpClass, collation, _) = GetData();
135-
SetData(subtype, canonicalFunction, subtypeOpClass, collation, value);
136-
}
197+
set => SetData(subtypeDiff: value);
137198
}
138199

139-
(string Schema, string Name, string Subtype, string CanonicalFunction, string SubtypeOpClass, string Collation,
140-
string SubtypeDiff) GetData()
141-
=> !(Annotatable[_annotationName] is string annotationValue)
142-
? (null, null, null, null, null, null, null)
143-
: Deserialize(_annotationName, annotationValue);
200+
(string Schema, string Name, string Subtype, string CanonicalFunction, string SubtypeOpClass, string Collation, string SubtypeDiff) GetData()
201+
=> Deserialize(Annotatable.FindAnnotation(_annotationName));
144202

145-
void SetData(string subtype, string canonicalFunction, string subtypeOpClass, string collation, string subtypeDiff)
146-
=> Annotatable[_annotationName] = $"{subtype},{canonicalFunction},{subtypeOpClass},{collation},{subtypeDiff}";
203+
void SetData(string subtype = null, string canonicalFunction = null, string subtypeOpClass = null, string collation = null, string subtypeDiff = null)
204+
=> Annotatable[_annotationName] =
205+
$"{subtype ?? Subtype},{canonicalFunction ?? CanonicalFunction},{subtypeOpClass ?? SubtypeOpClass},{collation ?? Collation},{subtypeDiff ?? SubtypeDiff}";
147206

148-
static (string Schema, string Name, string Subtype, string CanonicalFunction, string SubtypeOpClass, string collation, string SubtypeDiff)
149-
Deserialize([NotNull] string annotationName, [NotNull] string annotationValue)
207+
static (string Schema, string Name, string Subtype, string CanonicalFunction, string SubtypeOpClass, string Collation, string SubtypeDiff)
208+
Deserialize([CanBeNull] IAnnotation annotation)
150209
{
151-
Check.NotEmpty(annotationValue, nameof(annotationValue));
210+
if (annotation == null || !(annotation.Value is string value) || string.IsNullOrEmpty(value))
211+
return (null, null, null, null, null, null, null);
152212

153-
var elements = annotationValue.Split(',').ToArray();
213+
var elements = value.Split(',');
154214
if (elements.Length != 5)
155-
throw new ArgumentException("Cannot parse range annotation value: " + annotationValue);
215+
throw new ArgumentException($"Cannot parse range annotation value: {value}");
216+
156217
for (var i = 0; i < 5; i++)
157218
if (elements[i] == "")
158219
elements[i] = null;
159220

221+
// TODO: This would be a safer operation if we stored schema and name in the annotation value (see Sequence.cs).
160222
// Yes, this doesn't support dots in the schema/range name, let somebody complain first.
161-
var schemaAndName = annotationName.Substring(NpgsqlAnnotationNames.RangePrefix.Length).Split('.');
223+
var schemaAndName = annotation.Name.Substring(NpgsqlAnnotationNames.RangePrefix.Length).Split('.');
162224
switch (schemaAndName.Length)
163225
{
164226
case 1:
165227
return (null, schemaAndName[0], elements[0], elements[1], elements[2], elements[3], elements[4]);
166228
case 2:
167229
return (schemaAndName[0], schemaAndName[1], elements[0], elements[1], elements[2], elements[3], elements[4]);
168230
default:
169-
throw new ArgumentException("Cannot parse enum name from annotation: " + annotationName);
231+
throw new ArgumentException($"Cannot parse range name from annotation: {annotation.Name}");
170232
}
171233
}
172234
}

src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -765,7 +765,7 @@ protected virtual void GenerateRangeStatements(
765765
foreach (var rangeTypeToDrop in operation.Npgsql().OldPostgresRanges
766766
.Where(oe => operation.Npgsql().PostgresRanges.All(ne => ne.Name != oe.Name)))
767767
{
768-
GenerateDropRange(rangeTypeToDrop, builder);
768+
GenerateDropRange(rangeTypeToDrop, model, builder);
769769
}
770770

771771
if (operation.Npgsql().PostgresRanges.FirstOrDefault(nr =>
@@ -781,14 +781,16 @@ protected virtual void GenerateCreateRange(
781781
[NotNull] IModel model,
782782
[NotNull] MigrationCommandListBuilder builder)
783783
{
784+
var schema = rangeType.Schema ?? model.Relational().DefaultSchema;
785+
784786
// Schemas are normally created (or rather ensured) by the model differ, which scans all tables, sequences
785787
// and other database objects. However, it isn't aware of ranges, so we always ensure schema on range creation.
786-
if (rangeType.Schema != null)
787-
Generate(new EnsureSchemaOperation { Name = rangeType.Schema }, model, builder);
788+
if (schema != null)
789+
Generate(new EnsureSchemaOperation { Name = schema }, model, builder);
788790

789791
builder
790792
.Append("CREATE TYPE ")
791-
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(rangeType.Name, rangeType.Schema))
793+
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(rangeType.Name, schema))
792794
.AppendLine($" AS RANGE (")
793795
.IncrementIndent();
794796

@@ -812,11 +814,16 @@ protected virtual void GenerateCreateRange(
812814
.AppendLine(");");
813815
}
814816

815-
protected virtual void GenerateDropRange([NotNull] PostgresRange rangeType, [NotNull] MigrationCommandListBuilder builder)
817+
protected virtual void GenerateDropRange(
818+
[NotNull] PostgresRange rangeType,
819+
[NotNull] IModel model,
820+
[NotNull] MigrationCommandListBuilder builder)
816821
{
822+
var schema = rangeType.Schema ?? model.Relational().DefaultSchema;
823+
817824
builder
818825
.Append("DROP TYPE ")
819-
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(rangeType.Name, rangeType.Schema))
826+
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(rangeType.Name, schema))
820827
.AppendLine(";");
821828
}
822829

0 commit comments

Comments
 (0)