diff --git a/ChangeLog/7.1.3_dev.txt b/ChangeLog/7.1.3_dev.txt index ae0d7d4e6e..b8a5677059 100644 --- a/ChangeLog/7.1.3_dev.txt +++ b/ChangeLog/7.1.3_dev.txt @@ -1 +1,2 @@ -[main] Addressed race condition issue with TranslatorState.NonVisitableExpressions \ No newline at end of file +[main] Addressed race condition issue with TranslatorState.NonVisitableExpressions +[postgresql] Improved database structucture extraction 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 5e37553930..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 @@ -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 roles.. + /// + internal static string ExCantFindSchemaXOwnerWithIdYInTheListOfRoles { + get { + return ResourceManager.GetString("ExCantFindSchemaXOwnerWithIdYInTheListOfRoles", 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..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,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 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 d1c25626f4..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 @@ -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,7 +553,12 @@ protected virtual void ReadSchemaData(DbDataReader dataReader, ExtractionContext catalog.DefaultSchema = schema; } - schema.Owner = context.UserLookup[owner]; + if (context.RoleLookup.TryGetValue(owner, out var userName)) { + schema.Owner = userName; + } + else { + throw new InvalidOperationException(string.Format(Resources.Strings.ExCantFindSchemaXOwnerWithIdYInTheListOfRoles, name, owner)); + } context.SchemaMap[oid] = schema; context.ReversedSchemaMap[schema] = oid; } @@ -594,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; @@ -741,7 +777,7 @@ protected virtual void ReadColumnData(DbDataReader dataReader, ExtractionContext } else { var view = viewMap[columnOwnerId]; - view.CreateColumn(columnName); + _ = view.CreateColumn(columnName); } } @@ -912,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 @@ -967,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())); } } 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 @@ + +