Skip to content

Commit

Permalink
Sync to EF 9.0.0-preview.2.24128.4 (#3125)
Browse files Browse the repository at this point in the history
Closes #3123
  • Loading branch information
roji authored Mar 5, 2024
1 parent 707c89e commit 08f341d
Show file tree
Hide file tree
Showing 33 changed files with 815 additions and 226 deletions.
4 changes: 2 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<EFCoreVersion>9.0.0-preview.1.24081.2</EFCoreVersion>
<MicrosoftExtensionsVersion>9.0.0-preview.1.24080.9</MicrosoftExtensionsVersion>
<EFCoreVersion>9.0.0-preview.2.24128.4</EFCoreVersion>
<MicrosoftExtensionsVersion>9.0.0-preview.2.24128.5</MicrosoftExtensionsVersion>
<NpgsqlVersion>8.0.2</NpgsqlVersion>
</PropertyGroup>

Expand Down
62 changes: 62 additions & 0 deletions src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,68 @@ protected override void Generate(RenameSequenceOperation operation, IModel? mode
EndStatement(builder);
}

/// <inheritdoc />
protected override void SequenceOptions(
string? schema,
string name,
SequenceOperation operation,
IModel? model,
MigrationCommandListBuilder builder,
bool forAlter)
{
var intTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(int));
var longTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(long));

builder
.Append(" INCREMENT BY ")
.Append(intTypeMapping.GenerateSqlLiteral(operation.IncrementBy));

if (operation.MinValue != null)
{
builder
.Append(" MINVALUE ")
.Append(longTypeMapping.GenerateSqlLiteral(operation.MinValue));
}
else if (forAlter)
{
builder
.Append(" NO MINVALUE");
}

if (operation.MaxValue != null)
{
builder
.Append(" MAXVALUE ")
.Append(longTypeMapping.GenerateSqlLiteral(operation.MaxValue));
}
else if (forAlter)
{
builder
.Append(" NO MAXVALUE");
}

builder.Append(operation.IsCyclic ? " CYCLE" : " NO CYCLE");

if (!operation.IsCached)
{
// The base implementation appends NO CACHE, which isn't supported by PG
builder
.Append(" CACHE 1");
}
else if (operation.CacheSize != null)
{
builder
.Append(" CACHE ")
.Append(intTypeMapping.GenerateSqlLiteral(operation.CacheSize.Value));
}
else if (forAlter)
{
// The base implementation just appends CACHE, which isn't supported by PG
builder
.Append(" CACHE 1");
}
}

/// <inheritdoc />
protected override void Generate(RestartSequenceOperation operation, IModel? model, MigrationCommandListBuilder builder)
{
Expand Down
125 changes: 96 additions & 29 deletions src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Scaffolding.Internal;

// ReSharper disable StringLiteralTypo

/// <summary>
/// The default database model factory for Npgsql.
/// </summary>
Expand Down Expand Up @@ -458,7 +460,7 @@ ORDER BY attnum
MaxValue = seqInfo.MaxValue,
IncrementBy = (int)(seqInfo.IncrementBy ?? 1),
IsCyclic = seqInfo.IsCyclic ?? false,
NumbersToCache = seqInfo.NumbersToCache ?? 1
NumbersToCache = seqInfo.CacheSize ?? 1
};

if (!sequenceData.Equals(IdentitySequenceOptionsData.Empty))
Expand Down Expand Up @@ -976,11 +978,67 @@ private static IEnumerable<DatabaseSequence> GetSequences(
Func<string, string>? schemaFilter,
IDiagnosticsLogger<DbLoggerCategory.Scaffolding> logger)
{
// Note: we consult information_schema.sequences instead of pg_sequence but the latter was only introduced in PG 10
var commandText = $"""
// pg_sequence was only introduced in PG 10; we prefer that (cleaner and also exposes sequence caching info), but retain the old
// code for backwards compat
return connection.PostgreSqlVersion >= new Version(10, 0)
? GetSequencesNew(connection, databaseModel, schemaFilter, logger)
: GetSequencesOld(connection, databaseModel, schemaFilter, logger);

static IEnumerable<DatabaseSequence> GetSequencesNew(
NpgsqlConnection connection,
DatabaseModel databaseModel,
Func<string, string>? schemaFilter,
IDiagnosticsLogger<DbLoggerCategory.Scaffolding> logger)
{
var commandText = $"""
SELECT nspname, relname, typname, seqstart, seqincrement, seqmax, seqmin, seqcache, seqcycle
FROM pg_sequence
JOIN pg_class AS cls ON cls.oid=seqrelid
JOIN pg_namespace AS ns ON ns.oid = cls.relnamespace
JOIN pg_type AS typ ON typ.oid = seqtypid
/* Filter out owned serial and identity sequences */
WHERE NOT EXISTS (SELECT * FROM pg_depend AS dep WHERE dep.objid = cls.oid AND dep.deptype IN ('i', 'I', 'a'))
{(schemaFilter is not null ? $"AND {schemaFilter("nspname")}" : null)}
""";

using var command = new NpgsqlCommand(commandText, connection);
using var reader = command.ExecuteReader();

foreach (var record in reader.Cast<DbDataRecord>())
{
var sequenceSchema = reader.GetFieldValue<string>("nspname");
var sequenceName = reader.GetFieldValue<string>("relname");

var seqInfo = ReadSequenceInfo(record, connection.PostgreSqlVersion);
var sequence = new DatabaseSequence
{
Database = databaseModel,
Name = sequenceName,
Schema = sequenceSchema,
StoreType = seqInfo.StoreType,
StartValue = seqInfo.StartValue,
MinValue = seqInfo.MinValue,
MaxValue = seqInfo.MaxValue,
IncrementBy = (int?)seqInfo.IncrementBy,
IsCyclic = seqInfo.IsCyclic,
IsCached = seqInfo.CacheSize is not null,
CacheSize = seqInfo.CacheSize
};

yield return sequence;
}
}

static IEnumerable<DatabaseSequence> GetSequencesOld(
NpgsqlConnection connection,
DatabaseModel databaseModel,
Func<string, string>? schemaFilter,
IDiagnosticsLogger<DbLoggerCategory.Scaffolding> logger)
{
var commandText = $"""
SELECT
sequence_schema, sequence_name,
data_type AS seqtype,
data_type AS typname,
{(connection.PostgreSqlVersion >= new Version(9, 1) ? "start_value" : "1")}::bigint AS seqstart,
minimum_value::bigint AS seqmin,
maximum_value::bigint AS seqmax,
Expand All @@ -1001,29 +1059,30 @@ AND NOT EXISTS (SELECT * FROM pg_depend AS dep WHERE dep.objid = cls.oid AND dep
{(schemaFilter is not null ? $"AND {schemaFilter("nspname")}" : null)}
""";

using var command = new NpgsqlCommand(commandText, connection);
using var reader = command.ExecuteReader();

foreach (var record in reader.Cast<DbDataRecord>())
{
var sequenceName = reader.GetFieldValue<string>("sequence_name");
var sequenceSchema = reader.GetFieldValue<string>("sequence_schema");
using var command = new NpgsqlCommand(commandText, connection);
using var reader = command.ExecuteReader();

var seqInfo = ReadSequenceInfo(record, connection.PostgreSqlVersion);
var sequence = new DatabaseSequence
foreach (var record in reader.Cast<DbDataRecord>())
{
Database = databaseModel,
Name = sequenceName,
Schema = sequenceSchema,
StoreType = seqInfo.StoreType,
StartValue = seqInfo.StartValue,
MinValue = seqInfo.MinValue,
MaxValue = seqInfo.MaxValue,
IncrementBy = (int?)seqInfo.IncrementBy,
IsCyclic = seqInfo.IsCyclic
};

yield return sequence;
var sequenceName = reader.GetFieldValue<string>("sequence_name");
var sequenceSchema = reader.GetFieldValue<string>("sequence_schema");

var seqInfo = ReadSequenceInfo(record, connection.PostgreSqlVersion);
var sequence = new DatabaseSequence
{
Database = databaseModel,
Name = sequenceName,
Schema = sequenceSchema,
StoreType = seqInfo.StoreType,
StartValue = seqInfo.StartValue,
MinValue = seqInfo.MinValue,
MaxValue = seqInfo.MaxValue,
IncrementBy = (int?)seqInfo.IncrementBy,
IsCyclic = seqInfo.IsCyclic
};

yield return sequence;
}
}
}

Expand Down Expand Up @@ -1225,16 +1284,24 @@ private static void AdjustDefaults(DatabaseColumn column, string systemTypeName)

private static SequenceInfo ReadSequenceInfo(DbDataRecord record, Version postgresVersion)
{
var storeType = record.GetFieldValue<string>("seqtype");
var storeType = record.GetFieldValue<string>("typname");
var startValue = record.GetValueOrDefault<long>("seqstart");
var minValue = record.GetValueOrDefault<long>("seqmin");
var maxValue = record.GetValueOrDefault<long>("seqmax");
var incrementBy = record.GetValueOrDefault<long>("seqincrement");
var isCyclic = record.GetValueOrDefault<bool>("seqcycle");
var numbersToCache = (int)record.GetValueOrDefault<long>("seqcache");
var cacheSize = (int?)record.GetValueOrDefault<long>("seqcache");

long defaultStart, defaultMin, defaultMax;

storeType = storeType switch
{
"int2" => "smallint",
"int4" => "integer",
"int8" => "bigint",
_ => storeType
};

switch (storeType)
{
case "smallint" when incrementBy > 0:
Expand Down Expand Up @@ -1293,7 +1360,7 @@ private static SequenceInfo ReadSequenceInfo(DbDataRecord record, Version postgr
MaxValue = maxValue == defaultMax ? null : maxValue,
IncrementBy = incrementBy == 1 ? null : incrementBy,
IsCyclic = isCyclic == false ? null : true,
NumbersToCache = numbersToCache == 1 ? null : numbersToCache
CacheSize = cacheSize is 1 or null ? null : cacheSize
};
}

Expand All @@ -1305,7 +1372,7 @@ private sealed class SequenceInfo(string storeType)
public long? MaxValue { get; set; }
public long? IncrementBy { get; set; }
public bool? IsCyclic { get; set; }
public long? NumbersToCache { get; set; }
public int? CacheSize { get; set; }
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ public interface INpgsqlSequenceValueGeneratorFactory
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
ValueGenerator Create(
ValueGenerator? TryCreate(
IProperty property,
Type clrType,
NpgsqlSequenceValueGeneratorState generatorState,
INpgsqlRelationalConnection connection,
IRawSqlCommandBuilder rawSqlCommandBuilder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,14 @@ public NpgsqlSequenceValueGeneratorFactory(
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual ValueGenerator Create(
public virtual ValueGenerator? TryCreate(
IProperty property,
Type type,
NpgsqlSequenceValueGeneratorState generatorState,
INpgsqlRelationalConnection connection,
IRawSqlCommandBuilder rawSqlCommandBuilder,
IRelationalCommandDiagnosticsLogger commandLogger)
{
var type = property.ClrType.UnwrapNullableType().UnwrapEnumType();

if (type == typeof(long))
{
return new NpgsqlSequenceHiLoValueGenerator<long>(
Expand Down Expand Up @@ -89,8 +88,6 @@ public virtual ValueGenerator Create(
rawSqlCommandBuilder, _sqlGenerator, generatorState, connection, commandLogger);
}

throw new ArgumentException(
CoreStrings.InvalidValueGeneratorFactoryProperty(
nameof(NpgsqlSequenceValueGeneratorFactory), property.Name, property.DeclaringType.DisplayName()));
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,50 @@ public NpgsqlValueGeneratorSelector(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public override ValueGenerator Select(IProperty property, ITypeBase typeBase)
=> property.GetValueGeneratorFactory() is null
&& property.GetValueGenerationStrategy() == NpgsqlValueGenerationStrategy.SequenceHiLo
? _sequenceFactory.Create(
property,
Cache.GetOrAddSequenceState(property, _connection),
_connection,
_rawSqlCommandBuilder,
_commandLogger)
: base.Select(property, typeBase);
public override bool TrySelect(IProperty property, ITypeBase typeBase, out ValueGenerator? valueGenerator)
{
if (property.GetValueGeneratorFactory() != null
|| property.GetValueGenerationStrategy() != NpgsqlValueGenerationStrategy.SequenceHiLo)
{
return base.TrySelect(property, typeBase, out valueGenerator);
}

var propertyType = property.ClrType.UnwrapNullableType().UnwrapEnumType();

valueGenerator = _sequenceFactory.TryCreate(
property,
propertyType,
Cache.GetOrAddSequenceState(property, _connection),
_connection,
_rawSqlCommandBuilder,
_commandLogger);

if (valueGenerator != null)
{
return true;
}

var converter = property.GetTypeMapping().Converter;
if (converter != null
&& converter.ProviderClrType != propertyType)
{
valueGenerator = _sequenceFactory.TryCreate(
property,
converter.ProviderClrType,
Cache.GetOrAddSequenceState(property, _connection),
_connection,
_rawSqlCommandBuilder,
_commandLogger);

if (valueGenerator != null)
{
valueGenerator = valueGenerator.WithConverter(converter);
return true;
}
}

return false;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
namespace Npgsql.EntityFrameworkCore.PostgreSQL.BulkUpdates;

public class NorthwindBulkUpdatesNpgsqlFixture<TModelCustomizer> : NorthwindBulkUpdatesFixture<TModelCustomizer>
where TModelCustomizer : IModelCustomizer, new()
where TModelCustomizer : ITestModelCustomizer, new()
{
protected override ITestStoreFactory TestStoreFactory
=> NpgsqlNorthwindTestStoreFactory.Instance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL;
public class ManyToManyFieldsLoadNpgsqlTest(ManyToManyFieldsLoadNpgsqlTest.ManyToManyFieldsLoadNpgsqlFixture fixture)
: ManyToManyFieldsLoadTestBase<ManyToManyFieldsLoadNpgsqlTest.ManyToManyFieldsLoadNpgsqlFixture>(fixture)
{
public class ManyToManyFieldsLoadNpgsqlFixture : ManyToManyFieldsLoadFixtureBase
public class ManyToManyFieldsLoadNpgsqlFixture : ManyToManyFieldsLoadFixtureBase, ITestSqlLoggerFactory
{
public TestSqlLoggerFactory TestSqlLoggerFactory
=> (TestSqlLoggerFactory)ListLoggerFactory;
Expand Down
2 changes: 1 addition & 1 deletion test/EFCore.PG.FunctionalTests/ManyToManyLoadNpgsqlTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL;
public class ManyToManyLoadNpgsqlTest(ManyToManyLoadNpgsqlTest.ManyToManyLoadNpgsqlFixture fixture)
: ManyToManyLoadTestBase<ManyToManyLoadNpgsqlTest.ManyToManyLoadNpgsqlFixture>(fixture)
{
public class ManyToManyLoadNpgsqlFixture : ManyToManyLoadFixtureBase
public class ManyToManyLoadNpgsqlFixture : ManyToManyLoadFixtureBase, ITestSqlLoggerFactory
{
public TestSqlLoggerFactory TestSqlLoggerFactory
=> (TestSqlLoggerFactory)ListLoggerFactory;
Expand Down
Loading

0 comments on commit 08f341d

Please sign in to comment.