From 2ac6aa84ab5757ce88e86d19ce660cc08486afd6 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 12 Nov 2024 19:24:43 +0500 Subject: [PATCH 1/5] Add Postgre 16 to list of connections --- Orm/Xtensive.Orm.Tests.Framework/Orm.config | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Orm/Xtensive.Orm.Tests.Framework/Orm.config b/Orm/Xtensive.Orm.Tests.Framework/Orm.config index 06ba301375..8e13062b0d 100644 --- a/Orm/Xtensive.Orm.Tests.Framework/Orm.config +++ b/Orm/Xtensive.Orm.Tests.Framework/Orm.config @@ -72,6 +72,9 @@ + + @@ -167,6 +170,9 @@ + + From ea1bb4a4ca6eb3af63cc90dcd46459051f159b44 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 12 Nov 2024 20:32:33 +0500 Subject: [PATCH 2/5] Add exception if schema owner cannot be resolved --- .../Sql.Drivers.PostgreSql/Resources/Strings.Designer.cs | 9 +++++++++ .../Sql.Drivers.PostgreSql/Resources/Strings.resx | 3 +++ .../Sql.Drivers.PostgreSql/v8_0/Extractor.cs | 7 ++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Resources/Strings.Designer.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Resources/Strings.Designer.cs index 5e37553930..2ae26c41ef 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Resources/Strings.Designer.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Resources/Strings.Designer.cs @@ -60,6 +60,15 @@ internal Strings() { } } + /// + /// Looks up a localized string similar to Can't find schema '{0}' owner with oid '{1}' in the list of users.. + /// + internal static string ExCantFindSchemaXOwnerWithIdYInTheListOfUsers { + get { + return ResourceManager.GetString("ExCantFindSchemaXOwnerWithIdYInTheListOfUsers", resourceCulture); + } + } + /// /// Looks up a localized string similar to FreeText search on custom columns not supported.. /// diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Resources/Strings.resx b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Resources/Strings.resx index 271bfaee3d..1adf6d19b7 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Resources/Strings.resx +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Resources/Strings.resx @@ -135,4 +135,7 @@ Schema '{0}' either does not exist or belongs to another user. + + Can't find schema '{0}' owner with oid '{1}' in the list of users. + \ No newline at end of file diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Extractor.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Extractor.cs index d1c25626f4..9cd0572003 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Extractor.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Extractor.cs @@ -522,7 +522,12 @@ protected virtual void ReadSchemaData(DbDataReader dataReader, ExtractionContext catalog.DefaultSchema = schema; } - schema.Owner = context.UserLookup[owner]; + if (context.UserLookup.TryGetValue(owner, out var ownerName)) { + schema.Owner = ownerName; + } + else { + throw new InvalidOperationException(string.Format(Resources.Strings.ExCantFindSchemaXOwnerWithIdYInTheListOfUsers, name, owner)); + } context.SchemaMap[oid] = schema; context.ReversedSchemaMap[schema] = oid; } From 12b46663b1b20757a3472f12f0e7b5a506d6afc9 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Wed, 13 Nov 2024 19:14:52 +0500 Subject: [PATCH 3/5] Add support not only users but roles as owners of schema There is only one differenct between user and role - ability to login, see "CREATE USER" statement in documentation for proof --- .../Sql.Drivers.PostgreSql/DriverFactory.cs | 22 ++-- .../Resources/Strings.Designer.cs | 6 +- .../Resources/Strings.resx | 4 +- .../Sql.Drivers.PostgreSql/v8_0/Extractor.cs | 121 +++++++++++------- 4 files changed, 90 insertions(+), 63 deletions(-) diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs index bf4db22e50..f0dbb07d50 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs @@ -111,19 +111,15 @@ private static SqlDriver CreateDriverInstance( // We support 8.3, 8.4 and any 9.0+ - if (version.Major == 8) { - return version.Minor == 3 ? new v8_3.Driver(coreServerInfo) : new v8_4.Driver(coreServerInfo); - } - - if (version.Major == 9) { - return version.Minor == 0 ? new v9_0.Driver(coreServerInfo) : new v9_1.Driver(coreServerInfo); - } - - if (version.Major < 12) { - return new v10_0.Driver(coreServerInfo); - } - - return new v12_0.Driver(coreServerInfo); + return version.Major switch { + 8 when version.Minor == 3 => new v8_3.Driver(coreServerInfo), + 8 when version.Minor > 3 => new v8_4.Driver(coreServerInfo), + 9 when version.Minor == 0 => new v9_0.Driver(coreServerInfo), + 9 when version.Minor > 0 => new v9_1.Driver(coreServerInfo), + 10 => new v10_0.Driver(coreServerInfo), + 11 => new v10_0.Driver(coreServerInfo), + _ => new v12_0.Driver(coreServerInfo) + }; } /// diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Resources/Strings.Designer.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Resources/Strings.Designer.cs index 2ae26c41ef..0cea618846 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Resources/Strings.Designer.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Resources/Strings.Designer.cs @@ -61,11 +61,11 @@ internal Strings() { } /// - /// Looks up a localized string similar to Can't find schema '{0}' owner with oid '{1}' in the list of users.. + /// Looks up a localized string similar to Can't find schema '{0}' owner with oid '{1}' in the list of roles.. /// - internal static string ExCantFindSchemaXOwnerWithIdYInTheListOfUsers { + internal static string ExCantFindSchemaXOwnerWithIdYInTheListOfRoles { get { - return ResourceManager.GetString("ExCantFindSchemaXOwnerWithIdYInTheListOfUsers", resourceCulture); + return ResourceManager.GetString("ExCantFindSchemaXOwnerWithIdYInTheListOfRoles", resourceCulture); } } diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Resources/Strings.resx b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Resources/Strings.resx index 1adf6d19b7..4642cceba4 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Resources/Strings.resx +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/Resources/Strings.resx @@ -135,7 +135,7 @@ Schema '{0}' either does not exist or belongs to another user. - - Can't find schema '{0}' owner with oid '{1}' in the list of users. + + Can't find schema '{0}' owner with oid '{1}' in the list of roles. \ No newline at end of file diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Extractor.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Extractor.cs index 9cd0572003..6b425b5248 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Extractor.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Extractor.cs @@ -26,12 +26,17 @@ protected sealed class ExtractionContext /// /// Specific schemas to extract /// - public readonly Dictionary TargetSchemes = new Dictionary(); + public readonly Dictionary TargetSchemes = new(); /// - /// Extracted users. + /// Extracted users (subset of ). /// - public readonly Dictionary UserLookup = new Dictionary(); + public readonly Dictionary UserLookup = new(); + + /// + /// Extracted roles. + /// + public readonly Dictionary RoleLookup = new(); /// /// Catalog to extract information. @@ -41,46 +46,54 @@ protected sealed class ExtractionContext /// /// Extracted schemas. /// - public readonly Dictionary SchemaMap = new Dictionary(); + public readonly Dictionary SchemaMap = new(); /// /// Extracted schemas identifiers. /// - public readonly Dictionary ReversedSchemaMap = new Dictionary(); + public readonly Dictionary ReversedSchemaMap = new(); /// /// Extracted tables. /// - public readonly Dictionary TableMap = new Dictionary(); + public readonly Dictionary TableMap = new(); /// /// Extracted views. /// - public readonly Dictionary ViewMap = new Dictionary(); + public readonly Dictionary ViewMap = new(); /// /// Extracted sequences. /// - public readonly Dictionary SequenceMap = new Dictionary(); + public readonly Dictionary SequenceMap = new(); /// /// Extracted index expressions. /// - public readonly Dictionary ExpressionIndexMap = new Dictionary(); + public readonly Dictionary ExpressionIndexMap = new(); /// /// Extracted domains. /// - public readonly Dictionary DomainMap = new Dictionary(); + public readonly Dictionary DomainMap = new(); /// /// Extracted columns connected grouped by owner (table or view) /// - public readonly Dictionary> TableColumnMap = new Dictionary>(); + public readonly Dictionary> TableColumnMap = new(); + /// + /// Roles in which current user is a member, self included. + /// + public readonly List CurrentUserRoles = new(); + + public string CurrentUserName { get; set; } public long CurrentUserSysId { get; set; } = -1; public long? CurrentUserIdentifier { get; set; } + public bool IsMe(string name) => name == CurrentUserName; + public ExtractionContext(Catalog catalog) { Catalog = catalog; @@ -332,7 +345,7 @@ public override Catalog ExtractSchemes(string catalogName, string[] schemaNames) { var (catalog, context) = CreateCatalogAndContext(catalogName, schemaNames); - ExtractUsers(context); + _ = ExtractRoles(context, false); ExtractSchemas(context); return catalog; } @@ -343,7 +356,7 @@ public override async Task ExtractSchemesAsync( { var (catalog, context) = CreateCatalogAndContext(catalogName, schemaNames); - await ExtractUsersAsync(context, token).ConfigureAwait(false); + await ExtractRoles(context, true, token).ConfigureAwait(false); await ExtractSchemasAsync(context, token).ConfigureAwait(false); return catalog; } @@ -360,48 +373,62 @@ private static (Catalog catalog, ExtractionContext context) CreateCatalogAndCont return (catalog, context); } - private void ExtractUsers(ExtractionContext context) + private async ValueTask ExtractRoles(ExtractionContext context, bool isAsync, CancellationToken token = default) { context.UserLookup.Clear(); - string me; - using (var command = Connection.CreateCommand("SELECT user")) { - me = (string) command.ExecuteScalar(); - } - using (var cmd = Connection.CreateCommand("SELECT usename, usesysid FROM pg_user")) - using (var dr = cmd.ExecuteReader()) { - while (dr.Read()) { - ReadUserData(dr, context, me); + var extractCurentUserCommand = Connection.CreateCommand("SELECT user"); + // Roles include users. + // Users also can have members for some reason and it doesn't make them groups, + // the only thing that defines user is ability to login :-) + const string ExtractRolesQueryTemplate = "SELECT rolname, oid, rolcanlogin, pg_has_role('{0}', oid,'member') FROM pg_roles"; + + + if (isAsync) { + await using (extractCurentUserCommand.ConfigureAwait(false)) { + context.CurrentUserName = (string) await extractCurentUserCommand.ExecuteScalarAsync(token).ConfigureAwait(false); } - } - } - private async Task ExtractUsersAsync(ExtractionContext context, CancellationToken token = default) - { - context.UserLookup.Clear(); - string me; - var command = Connection.CreateCommand("SELECT user"); - await using (command.ConfigureAwait(false)) { - me = (string) await command.ExecuteScalarAsync(token).ConfigureAwait(false); + var getAllUsersCommand = Connection.CreateCommand(string.Format(ExtractRolesQueryTemplate, context.CurrentUserName)); + await using (getAllUsersCommand.ConfigureAwait(false)) { + var reader = await getAllUsersCommand.ExecuteReaderAsync(token).ConfigureAwait(false); + await using (reader.ConfigureAwait(false)) { + while (await reader.ReadAsync(token).ConfigureAwait(false)) { + ReadUserData(reader, context); + } + } + } } + else { + using (extractCurentUserCommand) { + context.CurrentUserName = (string) extractCurentUserCommand.ExecuteScalar(); + } - command = Connection.CreateCommand("SELECT usename, usesysid FROM pg_user"); - await using (command.ConfigureAwait(false)) { - var reader = await command.ExecuteReaderAsync(token).ConfigureAwait(false); - await using (reader.ConfigureAwait(false)) { - while (await reader.ReadAsync(token).ConfigureAwait(false)) { - ReadUserData(reader, context, me); + var getAllUsersCommand = Connection.CreateCommand(string.Format(ExtractRolesQueryTemplate, context.CurrentUserName)); + using (getAllUsersCommand) + using (var dr = getAllUsersCommand.ExecuteReader()) { + while (dr.Read()) { + ReadUserData(dr, context); } } } } - private static void ReadUserData(DbDataReader dr, ExtractionContext context, string me) + private static void ReadUserData(DbDataReader dr, ExtractionContext context) { - var name = dr[0].ToString(); + var name = dr.GetString(0); + // oid, which is basically a number, has its own type - oid! can't be read as int or long (facepalm) var sysId = Convert.ToInt64(dr[1]); - context.UserLookup.Add(sysId, name); - if (name == me) { + var canLogin = dr.GetBoolean(2); + var containsCurrentUser = dr.GetBoolean(3); + context.RoleLookup.Add(sysId, name); + if(containsCurrentUser) { + context.CurrentUserRoles.Add(sysId); + } + if (canLogin) { + context.UserLookup.Add(sysId, name); + } + if (context.IsMe(name)) { context.CurrentUserSysId = sysId; } } @@ -499,7 +526,11 @@ protected virtual SqlQueryExpression BuildExtractSchemasQuery(ExtractionContext selectPublic.Columns.Add(namespaceTable1["nspowner"]); var selectMine = SqlDml.Select(namespaceTable2); - selectMine.Where = namespaceTable2["nspowner"] == context.CurrentUserIdentifier; + if (context.CurrentUserRoles.Count == 0) + selectMine.Where = namespaceTable2["nspowner"] == context.CurrentUserIdentifier; + else { + selectMine.Where = SqlDml.In(namespaceTable2["nspowner"], SqlDml.Array(context.CurrentUserRoles.ToArray())); + } selectMine.Columns.Add(namespaceTable2["nspname"]); selectMine.Columns.Add(namespaceTable2["oid"]); selectMine.Columns.Add(namespaceTable2["nspowner"]); @@ -522,11 +553,11 @@ protected virtual void ReadSchemaData(DbDataReader dataReader, ExtractionContext catalog.DefaultSchema = schema; } - if (context.UserLookup.TryGetValue(owner, out var ownerName)) { - schema.Owner = ownerName; + if (context.RoleLookup.TryGetValue(owner, out var userName)) { + schema.Owner = userName; } else { - throw new InvalidOperationException(string.Format(Resources.Strings.ExCantFindSchemaXOwnerWithIdYInTheListOfUsers, name, owner)); + throw new InvalidOperationException(string.Format(Resources.Strings.ExCantFindSchemaXOwnerWithIdYInTheListOfRoles, name, owner)); } context.SchemaMap[oid] = schema; context.ReversedSchemaMap[schema] = oid; From e4fe07bab0997d6113ad52504d37821fe2e281db Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Wed, 13 Nov 2024 19:17:39 +0500 Subject: [PATCH 4/5] Code styling improvements --- .../Sql.Drivers.PostgreSql/v8_0/Extractor.cs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Extractor.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Extractor.cs index 6b425b5248..89e9313596 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Extractor.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Extractor.cs @@ -630,8 +630,8 @@ protected virtual ISqlCompileUnit BuildExtractSchemaContentsQuery(ExtractionCont select.Columns.Add(relationsTable["relnamespace"]); select.Columns.Add(tablespacesTable["spcname"]); select.Columns.Add(new Func(() => { - var defCase = SqlDml.Case(relationsTable["relkind"]); - defCase.Add('v', SqlDml.FunctionCall("pg_get_viewdef", relationsTable["oid"])); + var defCase = SqlDml.Case(relationsTable["relkind"]) + .Add('v', SqlDml.FunctionCall("pg_get_viewdef", relationsTable["oid"])); return defCase; })(), "definition"); return select; @@ -777,7 +777,7 @@ protected virtual void ReadColumnData(DbDataReader dataReader, ExtractionContext } else { var view = viewMap[columnOwnerId]; - view.CreateColumn(columnName); + _ = view.CreateColumn(columnName); } } @@ -948,8 +948,9 @@ protected virtual int ReadTableIndexData(DbDataReader dataReader, ExtractionCont else { for (int j = 0; j < indexKey.Length; j++) { int colIndex = indexKey[j]; - if (colIndex > 0) - index.CreateIndexColumn(tableColumns[tableIdentifier][colIndex], true); + if (colIndex > 0) { + _ = index.CreateIndexColumn(tableColumns[tableIdentifier][colIndex], true); + } else { //column index is 0 //this means that this index column is an expression @@ -1003,12 +1004,9 @@ protected virtual void ReadIndexColumnsData(DbDataReader dataReader, ExtractionC var exprIndexInfo = expressionIndexMap[Convert.ToInt64(dataReader[1])]; for (var j = 0; j < exprIndexInfo.Columns.Length; j++) { int colIndex = exprIndexInfo.Columns[j]; - if (colIndex > 0) { - exprIndexInfo.Index.CreateIndexColumn(tableColumns[Convert.ToInt64(dataReader[0])][colIndex], true); - } - else { - exprIndexInfo.Index.CreateIndexColumn(SqlDml.Native(dataReader[(j + 1).ToString()].ToString())); - } + _ = colIndex > 0 + ? exprIndexInfo.Index.CreateIndexColumn(tableColumns[Convert.ToInt64(dataReader[0])][colIndex], true) + : exprIndexInfo.Index.CreateIndexColumn(SqlDml.Native(dataReader[(j + 1).ToString()].ToString())); } } From e462cfeaa565830c16a54fe0ba503b3064987059 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Thu, 14 Nov 2024 19:16:53 +0500 Subject: [PATCH 5/5] Improve changelog --- ChangeLog/7.1.3_dev.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog/7.1.3_dev.txt b/ChangeLog/7.1.3_dev.txt index e69de29bb2..5836253c59 100644 --- a/ChangeLog/7.1.3_dev.txt +++ b/ChangeLog/7.1.3_dev.txt @@ -0,0 +1 @@ +[postgresql] Improved database structucture extraction \ No newline at end of file