提交 27c74d6d 编写于 作者: S Shay Rojansky

Basic column & database collation support

Support only for collation support implemented upstream, PostgreSQL-
specific functionality still remains.

Part of #406
上级 b0b4ccab
......@@ -10,7 +10,6 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Storage;
using Npgsql.EntityFrameworkCore.PostgreSQL;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Internal;
......@@ -353,11 +352,15 @@ protected override void Generate(AlterColumnOperation operation, IModel model, M
var alterBase = $"ALTER TABLE {DelimitIdentifier(operation.Table, operation.Schema)} " +
$"ALTER COLUMN {DelimitIdentifier(operation.Name)} ";
// TYPE
// TYPE + COLLATION
builder.Append(alterBase)
.Append("TYPE ")
.Append(type)
.AppendLine(";");
.Append(type);
if (operation.Collation != operation.OldColumn.Collation)
builder.Append(" COLLATE ").Append(DelimitIdentifier(operation.Collation ?? "default"));
builder.AppendLine(";");
// NOT NULL
builder.Append(alterBase)
......@@ -736,20 +739,30 @@ protected virtual void Generate(NpgsqlCreateDatabaseOperation operation, [CanBeN
.Append("CREATE DATABASE ")
.Append(DelimitIdentifier(operation.Name));
if (operation.Template != null)
if (!string.IsNullOrEmpty(operation.Template))
{
builder
.Append(" TEMPLATE ")
.AppendLine()
.Append("TEMPLATE ")
.Append(DelimitIdentifier(operation.Template));
}
if (operation.Tablespace != null)
if (!string.IsNullOrEmpty(operation.Tablespace))
{
builder
.Append(" TABLESPACE ")
.AppendLine()
.Append("TABLESPACE ")
.Append(DelimitIdentifier(operation.Tablespace));
}
if (!string.IsNullOrEmpty(operation.Collation))
{
builder
.AppendLine()
.Append("COLLATE ")
.Append(DelimitIdentifier(operation.Collation));
}
builder.AppendLine(";");
EndStatement(builder, suppressTransaction: true);
......@@ -782,6 +795,9 @@ public virtual void Generate(NpgsqlDropDatabaseOperation operation, [CanBeNull]
Check.NotNull(model, nameof(model));
Check.NotNull(builder, nameof(builder));
if (operation.Collation != operation.OldDatabase.Collation)
throw new NotSupportedException("PostgreSQL does not support altering the collation on an existing database.");
GenerateEnumStatements(operation, model, builder);
GenerateRangeStatements(operation, model, builder);
......@@ -1174,13 +1190,29 @@ protected override void Generate(CreateSequenceOperation operation, IModel model
operation.ComputedColumnSql = ColumnsToTsVector(tsVectorIncludedColumns, tsVectorConfig, model, schema, table);
}
base.ColumnDefinition(
schema,
table,
name,
operation,
model,
builder);
if (operation.ComputedColumnSql != null)
{
ComputedColumnDefinition(schema, table, name, operation, model, builder);
return;
}
var columnType = operation.ColumnType ?? GetColumnType(schema, table, name, operation, model);
builder
.Append(DelimitIdentifier(name))
.Append(" ")
.Append(columnType);
if (operation.Collation != null)
{
builder
.Append(" COLLATE ")
.Append(DelimitIdentifier(operation.Collation));
}
builder.Append(operation.IsNullable ? " NULL" : " NOT NULL");
DefaultValue(operation.DefaultValue, operation.DefaultValueSql, columnType, builder);
if (valueGenerationStrategy.IsIdentity())
IdentityDefinition(operation, builder);
......@@ -1302,7 +1334,16 @@ long Max(Type type)
builder
.Append(DelimitIdentifier(name))
.Append(" ")
.Append(operation.ColumnType ?? GetColumnType(schema, table, name, operation, model))
.Append(operation.ColumnType ?? GetColumnType(schema, table, name, operation, model));
if (operation.Collation != null)
{
builder
.Append(" COLLATE ")
.Append(DelimitIdentifier(operation.Collation));
}
builder
.Append(" GENERATED ALWAYS AS (")
.Append(operation.ComputedColumnSql)
.Append(") STORED");
......
......@@ -3,7 +3,7 @@
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Migrations.Operations
{
public class NpgsqlCreateDatabaseOperation : MigrationOperation
public class NpgsqlCreateDatabaseOperation : DatabaseOperation
{
public virtual string Name { get;[param: NotNull] set; }
[CanBeNull]
......
......@@ -245,6 +245,7 @@ public override DatabaseModel Create(DbConnection dbConnection, DatabaseModelFac
basetyp.typname AS basetypname,
attname,
description,
collname,
attisdropped,
{(connection.PostgreSqlVersion >= new Version(10, 0) ? "attidentity" : "''::\"char\" as attidentity")},
{(connection.PostgreSqlVersion >= new Version(12, 0) ? "attgenerated" : "''::\"char\" as attgenerated")},
......@@ -277,6 +278,7 @@ ELSE NULL
LEFT JOIN pg_type AS elemtyp ON (elemtyp.oid = typ.typelem)
LEFT JOIN pg_type AS basetyp ON (basetyp.oid = typ.typbasetype)
LEFT JOIN pg_description AS des ON des.objoid = cls.oid AND des.objsubid = attnum
LEFT JOIN pg_collation as coll ON coll.oid = attr.attcollation
-- Bring in identity sequences the depend on this column
LEFT JOIN pg_depend AS dep ON dep.refobjid = cls.oid AND dep.refobjsubid = attr.attnum AND dep.deptype = 'i'
{(connection.PostgreSqlVersion >= new Version(10, 0) ? "LEFT JOIN pg_sequence AS seq ON seq.seqrelid = dep.objid" : "")}
......@@ -410,6 +412,9 @@ ELSE NULL
if (record.GetValueOrDefault<string>("description") is string comment)
column.Comment = comment;
if (record.GetValueOrDefault<string>("collname") is string collation && collation != "default")
column.Collation = collation;
logger.ColumnFound(
DisplayName(tableSchema, tableName),
column.Name,
......
......@@ -53,7 +53,7 @@ public override async Task Create_table_all_settings()
@"CREATE TABLE dbo2.""People"" (
""CustomId"" integer NOT NULL,
""EmployerId"" integer NOT NULL,
""SSN"" character varying(11) NOT NULL,
""SSN"" character varying(11) COLLATE ""POSIX"" NOT NULL,
CONSTRAINT ""PK_People"" PRIMARY KEY (""CustomId""),
CONSTRAINT ""AK_People_SSN"" UNIQUE (""SSN""),
CONSTRAINT ""CK_EmployerId"" CHECK (""EmployerId"" > 0),
......@@ -533,6 +533,30 @@ public override async Task Add_column_with_comment()
COMMENT ON COLUMN ""People"".""FullName"" IS 'My comment';");
}
[ConditionalFact]
public override async Task Add_column_with_collation()
{
await base.Add_column_with_collation();
AssertSql(
@"ALTER TABLE ""People"" ADD ""Name"" text COLLATE ""POSIX"" NULL;");
}
[ConditionalFact]
public override async Task Add_column_computed_with_collation()
{
if (TestEnvironment.PostgresVersion.IsUnder(12))
{
await Assert.ThrowsAsync<NotSupportedException>(() => base.Add_column_computed_with_collation());
return;
}
await base.Add_column_computed_with_collation();
AssertSql(
@"ALTER TABLE ""People"" ADD ""Name"" text COLLATE ""POSIX"" GENERATED ALWAYS AS ('hello') STORED;");
}
public override async Task Add_column_shared()
{
await base.Add_column_shared();
......@@ -1267,6 +1291,28 @@ public virtual async Task Alter_column_restart_identity()
ALTER TABLE ""People"" ALTER COLUMN ""Id"" RESTART WITH 20;");
}
[Fact]
public override async Task Alter_column_set_collation()
{
await base.Alter_column_set_collation();
AssertSql(
@"ALTER TABLE ""People"" ALTER COLUMN ""Name"" TYPE text COLLATE ""POSIX"";
ALTER TABLE ""People"" ALTER COLUMN ""Name"" DROP NOT NULL;
ALTER TABLE ""People"" ALTER COLUMN ""Name"" DROP DEFAULT;");
}
[Fact]
public override async Task Alter_column_reset_collation()
{
await base.Alter_column_reset_collation();
AssertSql(
@"ALTER TABLE ""People"" ALTER COLUMN ""Name"" TYPE text COLLATE ""default"";
ALTER TABLE ""People"" ALTER COLUMN ""Name"" DROP NOT NULL;
ALTER TABLE ""People"" ALTER COLUMN ""Name"" DROP DEFAULT;");
}
public override async Task Drop_column()
{
await base.Drop_column();
......@@ -2129,6 +2175,8 @@ public virtual Task Alter_enum_change_label_not_supported()
#endregion
protected override string NonDefaultCollation => "POSIX";
public class MigrationsNpgsqlFixture : MigrationsFixtureBase
{
protected override string StoreName { get; } = nameof(MigrationsNpgsqlTest);
......
......@@ -1549,6 +1549,27 @@ public void Identity_with_sequence_options_all()
},
"DROP TABLE identity");
[Fact]
public void Column_collation_is_set()
=> Test(
@"
CREATE TABLE columns_with_collation (
id int,
default_collation TEXT,
non_default_collation TEXT COLLATE ""POSIX""
);",
Enumerable.Empty<string>(),
Enumerable.Empty<string>(),
dbModel =>
{
var columns = dbModel.Tables.Single().Columns;
Assert.Null(columns.Single(c => c.Name == "default_collation").Collation);
Assert.Equal("POSIX", columns.Single(c => c.Name == "non_default_collation").Collation);
},
@"DROP TABLE columns_with_collation");
[Fact]
public void Index_method()
=> Test(@"
......
......@@ -23,6 +23,18 @@ public virtual void CreateDatabaseOperation()
");
}
[ConditionalFact]
public virtual void CreateDatabaseOperation_with_collation()
{
Generate(
new NpgsqlCreateDatabaseOperation { Name = "Northwind", Collation = "POSIX" });
AssertSql(
@"CREATE DATABASE ""Northwind""
COLLATE ""POSIX"";
");
}
[Fact]
public virtual void CreateDatabaseOperation_with_template()
{
......@@ -33,7 +45,8 @@ public virtual void CreateDatabaseOperation_with_template()
});
AssertSql(
@"CREATE DATABASE ""Northwind"" TEMPLATE ""MyTemplate"";
@"CREATE DATABASE ""Northwind""
TEMPLATE ""MyTemplate"";
");
}
......@@ -47,7 +60,8 @@ public virtual void CreateDatabaseOperation_with_tablespace()
});
AssertSql(
@"CREATE DATABASE some_db TABLESPACE ""MyTablespace"";
@"CREATE DATABASE some_db
TABLESPACE ""MyTablespace"";
");
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册