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 @@
+
+