未验证 提交 61114c4f 编写于 作者: S Shay Rojansky 提交者: GitHub

Support PG extensions with schema (#1662)

Closes #1220
上级 eaf1c10d
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
......@@ -86,8 +85,11 @@ protected override MethodCallCodeFragment GenerateFluentApi(IModel model, IAnnot
{
var extension = new PostgresExtension(model, annotation.Name);
return new MethodCallCodeFragment(nameof(NpgsqlModelBuilderExtensions.HasPostgresExtension),
extension.Name);
return extension.Schema == "public" || extension.Schema is null
? new MethodCallCodeFragment(nameof(NpgsqlModelBuilderExtensions.HasPostgresExtension),
extension.Name)
: new MethodCallCodeFragment(nameof(NpgsqlModelBuilderExtensions.HasPostgresExtension),
extension.Schema, extension.Name);
}
if (annotation.Name.StartsWith(NpgsqlAnnotationNames.EnumPrefix, StringComparison.Ordinal))
......
......@@ -924,18 +924,25 @@ protected override void Generate(EnsureSchemaOperation operation, IModel model,
[NotNull] IModel model,
[NotNull] MigrationCommandListBuilder builder)
{
var schema = extension.Schema ?? model.GetDefaultSchema();
// 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 extensions, so we always ensure schema on enum creation.
if (schema is not null)
Generate(new EnsureSchemaOperation { Name = schema }, model, builder);
builder
.Append("CREATE EXTENSION IF NOT EXISTS ")
.Append(DelimitIdentifier(extension.Name));
if (extension.Schema != null)
if (extension.Schema is not null)
{
builder
.Append(" SCHEMA ")
.Append(DelimitIdentifier(extension.Schema));
}
if (extension.Version != null)
if (extension.Version is not null)
{
builder
.Append(" VERSION ")
......@@ -1098,7 +1105,7 @@ protected override void Generate(EnsureSchemaOperation operation, IModel model,
// 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.
if (schema != null)
if (schema is not null)
Generate(new EnsureSchemaOperation { Name = schema }, model, builder);
builder
......@@ -1145,7 +1152,7 @@ protected override void Generate(EnsureSchemaOperation operation, IModel model,
.Append(" ADD VALUE ")
.Append(_stringTypeMapping.GenerateSqlLiteral(addedLabel));
if (beforeLabel != null)
if (beforeLabel is not null)
{
builder
.Append(" BEFORE ")
......@@ -1197,7 +1204,7 @@ protected override void Generate(EnsureSchemaOperation operation, IModel model,
// 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 ranges, so we always ensure schema on range creation.
if (schema != null)
if (schema is not null)
Generate(new EnsureSchemaOperation { Name = schema }, model, builder);
builder
......
......@@ -930,24 +930,22 @@ FROM pg_enum
/// </summary>
static void GetExtensions(NpgsqlConnection connection, DatabaseModel databaseModel)
{
const string commandText = "SELECT name, default_version, installed_version FROM pg_available_extensions";
const string commandText = @"
SELECT ns.nspname, extname, extversion FROM pg_extension
JOIN pg_namespace ns ON ns.oid=extnamespace";
using var command = new NpgsqlCommand(commandText, connection);
using var reader = command.ExecuteReader();
while (reader.Read())
{
var name = reader.GetString(reader.GetOrdinal("name"));
var _ = reader.GetValueOrDefault<string>("default_version");
var installedVersion = reader.GetValueOrDefault<string>("installed_version");
if (installedVersion == null)
continue;
var schema = reader.GetFieldValue<string?>("nspname");
var name = reader.GetString(reader.GetOrdinal("extname"));
var version = reader.GetValueOrDefault<string>("extversion");
if (name == "plpgsql") // Implicitly installed in all PG databases
continue;
// TODO: how/should we query the schema?
databaseModel.GetOrAddPostgresExtension(null, name, installedVersion);
databaseModel.GetOrAddPostgresExtension(schema, name, version);
}
}
......
......@@ -2191,30 +2191,30 @@ public virtual async Task Ensure_postgres_extension()
{
var citext = Assert.Single(model.GetPostgresExtensions());
Assert.Equal("citext", citext.Name);
Assert.Null(citext.Schema);
Assert.Equal("public", citext.Schema);
});
AssertSql(
@"CREATE EXTENSION IF NOT EXISTS citext;");
}
public virtual Task Ensure_postgres_extension_with_extension()
[Fact]
public virtual async Task Ensure_postgres_extension_with_schema()
{
// See https://github.com/npgsql/efcore.pg/issues/1220
return Task.CompletedTask;
await Test(
_ => { },
builder => builder.HasPostgresExtension("some_schema", "citext"),
model =>
{
var citext = Assert.Single(model.GetPostgresExtensions());
Assert.Equal("citext", citext.Name);
Assert.Equal("some_schema", citext.Schema);
});
// await Test(
// builder => { },
// builder => builder.HasPostgresExtension("some_schema", "citext"),
// model =>
// {
// var citext = Assert.Single(model.GetPostgresExtensions());
// Assert.Equal("citext", citext.Name);
// Assert.Equal("some_schema", citext.Schema);
// });
//
// AssertSql(
// @"CREATE EXTENSION IF NOT EXISTS citext;");
AssertSql(
@"CREATE SCHEMA IF NOT EXISTS some_schema;",
//
@"CREATE EXTENSION IF NOT EXISTS citext SCHEMA some_schema;");
}
#endregion
......
......@@ -1799,15 +1799,23 @@ public void Dropped_columns()
public void Postgres_extensions()
=> Test(@"
CREATE EXTENSION hstore;
CREATE EXTENSION pgcrypto;",
CREATE EXTENSION pgcrypto SCHEMA db2;",
Enumerable.Empty<string>(),
Enumerable.Empty<string>(),
dbModel =>
{
var extensions = dbModel.GetPostgresExtensions();
Assert.Equal(2, extensions.Count);
Assert.Single(extensions, e => e.Name == "hstore");
Assert.Single(extensions, e => e.Name == "pgcrypto");
Assert.Collection(extensions.OrderBy(e => e.Name),
e =>
{
Assert.Equal("hstore", e.Name);
Assert.Equal("public", e.Schema);
},
e =>
{
Assert.Equal("pgcrypto", e.Name);
Assert.Equal("db2", e.Schema);
});
},
"DROP EXTENSION hstore; DROP EXTENSION pgcrypto");
......
......@@ -147,6 +147,53 @@ public void GenerateFluentApi_IProperty_works_with_HiLo()
schema => Assert.Equal("HiLoIndexSchema", schema));
}
[ConditionalFact]
public void Extension()
{
var generator = CreateGenerator();
var modelBuilder = new ModelBuilder(NpgsqlConventionSetBuilder.Build());
modelBuilder.HasPostgresExtension("postgis");
var model = modelBuilder.Model;
var annotations = model.GetAnnotations().ToDictionary(a => a.Name, a => a);
var result = generator.GenerateFluentApiCalls(model, annotations)
.Single(c => c.Method == nameof(NpgsqlModelBuilderExtensions.HasPostgresExtension));
Assert.Collection(result.Arguments, name => Assert.Equal("postgis", name));
}
[ConditionalFact]
public void Extension_with_schema()
{
var generator = CreateGenerator();
var modelBuilder = new ModelBuilder(NpgsqlConventionSetBuilder.Build());
modelBuilder.HasPostgresExtension("some_schema", "postgis");
var model = modelBuilder.Model;
var annotations = model.GetAnnotations().ToDictionary(a => a.Name, a => a);
var result = generator.GenerateFluentApiCalls(model, annotations)
.Single(c => c.Method == nameof(NpgsqlModelBuilderExtensions.HasPostgresExtension));
Assert.Collection(result.Arguments,
schema => Assert.Equal("some_schema", schema),
name => Assert.Equal("postgis", name));
}
[ConditionalFact]
public void Extension_with_null_schema()
{
var generator = CreateGenerator();
var modelBuilder = new ModelBuilder(NpgsqlConventionSetBuilder.Build());
modelBuilder.HasPostgresExtension(null, "postgis");
var model = modelBuilder.Model;
var annotations = model.GetAnnotations().ToDictionary(a => a.Name, a => a);
var result = generator.GenerateFluentApiCalls(model, annotations)
.Single(c => c.Method == nameof(NpgsqlModelBuilderExtensions.HasPostgresExtension));
Assert.Collection(result.Arguments, name => Assert.Equal("postgis", name));
}
NpgsqlAnnotationCodeGenerator CreateGenerator()
=> new(new AnnotationCodeGeneratorDependencies(
new NpgsqlTypeMappingSource(
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册