@@ -26,12 +26,17 @@ protected sealed class ExtractionContext
2626 /// <summary>
2727 /// Specific schemas to extract
2828 /// </summary>
29- public readonly Dictionary < string , Schema > TargetSchemes = new Dictionary < string , Schema > ( ) ;
29+ public readonly Dictionary < string , Schema > TargetSchemes = new ( ) ;
3030
3131 /// <summary>
32- /// Extracted users.
32+ /// Extracted users (subset of <see cref="RoleLookup"/>) .
3333 /// </summary>
34- public readonly Dictionary < long , string > UserLookup = new Dictionary < long , string > ( ) ;
34+ public readonly Dictionary < long , string > UserLookup = new ( ) ;
35+
36+ /// <summary>
37+ /// Extracted roles.
38+ /// </summary>
39+ public readonly Dictionary < long , string > RoleLookup = new ( ) ;
3540
3641 /// <summary>
3742 /// Catalog to extract information.
@@ -41,46 +46,54 @@ protected sealed class ExtractionContext
4146 /// <summary>
4247 /// Extracted schemas.
4348 /// </summary>
44- public readonly Dictionary < long , Schema > SchemaMap = new Dictionary < long , Schema > ( ) ;
49+ public readonly Dictionary < long , Schema > SchemaMap = new ( ) ;
4550
4651 /// <summary>
4752 /// Extracted schemas identifiers.
4853 /// </summary>
49- public readonly Dictionary < Schema , long > ReversedSchemaMap = new Dictionary < Schema , long > ( ) ;
54+ public readonly Dictionary < Schema , long > ReversedSchemaMap = new ( ) ;
5055
5156 /// <summary>
5257 /// Extracted tables.
5358 /// </summary>
54- public readonly Dictionary < long , Table > TableMap = new Dictionary < long , Table > ( ) ;
59+ public readonly Dictionary < long , Table > TableMap = new ( ) ;
5560
5661 /// <summary>
5762 /// Extracted views.
5863 /// </summary>
59- public readonly Dictionary < long , View > ViewMap = new Dictionary < long , View > ( ) ;
64+ public readonly Dictionary < long , View > ViewMap = new ( ) ;
6065
6166 /// <summary>
6267 /// Extracted sequences.
6368 /// </summary>
64- public readonly Dictionary < long , Sequence > SequenceMap = new Dictionary < long , Sequence > ( ) ;
69+ public readonly Dictionary < long , Sequence > SequenceMap = new ( ) ;
6570
6671 /// <summary>
6772 /// Extracted index expressions.
6873 /// </summary>
69- public readonly Dictionary < long , ExpressionIndexInfo > ExpressionIndexMap = new Dictionary < long , ExpressionIndexInfo > ( ) ;
74+ public readonly Dictionary < long , ExpressionIndexInfo > ExpressionIndexMap = new ( ) ;
7075
7176 /// <summary>
7277 /// Extracted domains.
7378 /// </summary>
74- public readonly Dictionary < long , Domain > DomainMap = new Dictionary < long , Domain > ( ) ;
79+ public readonly Dictionary < long , Domain > DomainMap = new ( ) ;
7580
7681 /// <summary>
7782 /// Extracted columns connected grouped by owner (table or view)
7883 /// </summary>
79- public readonly Dictionary < long , Dictionary < long , TableColumn > > TableColumnMap = new Dictionary < long , Dictionary < long , TableColumn > > ( ) ;
84+ public readonly Dictionary < long , Dictionary < long , TableColumn > > TableColumnMap = new ( ) ;
8085
86+ /// <summary>
87+ /// Roles in which current user is a member, self included.
88+ /// </summary>
89+ public readonly List < long > CurrentUserRoles = new ( ) ;
90+
91+ public string CurrentUserName { get ; set ; }
8192 public long CurrentUserSysId { get ; set ; } = - 1 ;
8293 public long ? CurrentUserIdentifier { get ; set ; }
8394
95+ public bool IsMe ( string name ) => name == CurrentUserName ;
96+
8497 public ExtractionContext ( Catalog catalog )
8598 {
8699 Catalog = catalog ;
@@ -332,7 +345,7 @@ public override Catalog ExtractSchemes(string catalogName, string[] schemaNames)
332345 {
333346 var ( catalog , context ) = CreateCatalogAndContext ( catalogName , schemaNames ) ;
334347
335- ExtractUsers ( context ) ;
348+ _ = ExtractRoles ( context , false ) ;
336349 ExtractSchemas ( context ) ;
337350 return catalog ;
338351 }
@@ -343,7 +356,7 @@ public override async Task<Catalog> ExtractSchemesAsync(
343356 {
344357 var ( catalog , context ) = CreateCatalogAndContext ( catalogName , schemaNames ) ;
345358
346- await ExtractUsersAsync ( context , token ) . ConfigureAwait ( false ) ;
359+ await ExtractRoles ( context , true , token ) . ConfigureAwait ( false ) ;
347360 await ExtractSchemasAsync ( context , token ) . ConfigureAwait ( false ) ;
348361 return catalog ;
349362 }
@@ -360,48 +373,62 @@ private static (Catalog catalog, ExtractionContext context) CreateCatalogAndCont
360373 return ( catalog , context ) ;
361374 }
362375
363- private void ExtractUsers ( ExtractionContext context )
376+ private async ValueTask ExtractRoles ( ExtractionContext context , bool isAsync , CancellationToken token = default )
364377 {
365378 context . UserLookup . Clear ( ) ;
366- string me ;
367- using ( var command = Connection . CreateCommand ( "SELECT user" ) ) {
368- me = ( string ) command . ExecuteScalar ( ) ;
369- }
370379
371- using ( var cmd = Connection . CreateCommand ( "SELECT usename, usesysid FROM pg_user" ) )
372- using ( var dr = cmd . ExecuteReader ( ) ) {
373- while ( dr . Read ( ) ) {
374- ReadUserData ( dr , context , me ) ;
380+ var extractCurentUserCommand = Connection . CreateCommand ( "SELECT user" ) ;
381+ // Roles include users.
382+ // Users also can have members for some reason and it doesn't make them groups,
383+ // the only thing that defines user is ability to login :-)
384+ const string ExtractRolesQueryTemplate = "SELECT rolname, oid, rolcanlogin, pg_has_role('{0}', oid,'member') FROM pg_roles" ;
385+
386+
387+ if ( isAsync ) {
388+ await using ( extractCurentUserCommand . ConfigureAwait ( false ) ) {
389+ context . CurrentUserName = ( string ) await extractCurentUserCommand . ExecuteScalarAsync ( token ) . ConfigureAwait ( false ) ;
375390 }
376- }
377- }
378391
379- private async Task ExtractUsersAsync ( ExtractionContext context , CancellationToken token = default )
380- {
381- context . UserLookup . Clear ( ) ;
382- string me ;
383- var command = Connection . CreateCommand ( "SELECT user" ) ;
384- await using ( command . ConfigureAwait ( false ) ) {
385- me = ( string ) await command . ExecuteScalarAsync ( token ) . ConfigureAwait ( false ) ;
392+ var getAllUsersCommand = Connection . CreateCommand ( string . Format ( ExtractRolesQueryTemplate , context . CurrentUserName ) ) ;
393+ await using ( getAllUsersCommand . ConfigureAwait ( false ) ) {
394+ var reader = await getAllUsersCommand . ExecuteReaderAsync ( token ) . ConfigureAwait ( false ) ;
395+ await using ( reader . ConfigureAwait ( false ) ) {
396+ while ( await reader . ReadAsync ( token ) . ConfigureAwait ( false ) ) {
397+ ReadUserData ( reader , context ) ;
398+ }
399+ }
400+ }
386401 }
402+ else {
403+ using ( extractCurentUserCommand ) {
404+ context . CurrentUserName = ( string ) extractCurentUserCommand . ExecuteScalar ( ) ;
405+ }
387406
388- command = Connection . CreateCommand ( "SELECT usename, usesysid FROM pg_user" ) ;
389- await using ( command . ConfigureAwait ( false ) ) {
390- var reader = await command . ExecuteReaderAsync ( token ) . ConfigureAwait ( false ) ;
391- await using ( reader . ConfigureAwait ( false ) ) {
392- while ( await reader . ReadAsync ( token ) . ConfigureAwait ( false ) ) {
393- ReadUserData ( reader , context , me ) ;
407+ var getAllUsersCommand = Connection . CreateCommand ( string . Format ( ExtractRolesQueryTemplate , context . CurrentUserName ) ) ;
408+ using ( getAllUsersCommand )
409+ using ( var dr = getAllUsersCommand . ExecuteReader ( ) ) {
410+ while ( dr . Read ( ) ) {
411+ ReadUserData ( dr , context ) ;
394412 }
395413 }
396414 }
397415 }
398416
399- private static void ReadUserData ( DbDataReader dr , ExtractionContext context , string me )
417+ private static void ReadUserData ( DbDataReader dr , ExtractionContext context )
400418 {
401- var name = dr [ 0 ] . ToString ( ) ;
419+ var name = dr . GetString ( 0 ) ;
420+ // oid, which is basically a number, has its own type - oid! can't be read as int or long (facepalm)
402421 var sysId = Convert . ToInt64 ( dr [ 1 ] ) ;
403- context . UserLookup . Add ( sysId , name ) ;
404- if ( name == me ) {
422+ var canLogin = dr . GetBoolean ( 2 ) ;
423+ var containsCurrentUser = dr . GetBoolean ( 3 ) ;
424+ context . RoleLookup . Add ( sysId , name ) ;
425+ if ( containsCurrentUser ) {
426+ context . CurrentUserRoles . Add ( sysId ) ;
427+ }
428+ if ( canLogin ) {
429+ context . UserLookup . Add ( sysId , name ) ;
430+ }
431+ if ( context . IsMe ( name ) ) {
405432 context . CurrentUserSysId = sysId ;
406433 }
407434 }
@@ -499,7 +526,11 @@ protected virtual SqlQueryExpression BuildExtractSchemasQuery(ExtractionContext
499526 selectPublic . Columns . Add ( namespaceTable1 [ "nspowner" ] ) ;
500527
501528 var selectMine = SqlDml . Select ( namespaceTable2 ) ;
502- selectMine . Where = namespaceTable2 [ "nspowner" ] == context . CurrentUserIdentifier ;
529+ if ( context . CurrentUserRoles . Count == 0 )
530+ selectMine . Where = namespaceTable2 [ "nspowner" ] == context . CurrentUserIdentifier ;
531+ else {
532+ selectMine . Where = SqlDml . In ( namespaceTable2 [ "nspowner" ] , SqlDml . Array ( context . CurrentUserRoles . ToArray ( ) ) ) ;
533+ }
503534 selectMine . Columns . Add ( namespaceTable2 [ "nspname" ] ) ;
504535 selectMine . Columns . Add ( namespaceTable2 [ "oid" ] ) ;
505536 selectMine . Columns . Add ( namespaceTable2 [ "nspowner" ] ) ;
@@ -522,11 +553,11 @@ protected virtual void ReadSchemaData(DbDataReader dataReader, ExtractionContext
522553 catalog . DefaultSchema = schema ;
523554 }
524555
525- if ( context . UserLookup . TryGetValue ( owner , out var ownerName ) ) {
526- schema . Owner = ownerName ;
556+ if ( context . RoleLookup . TryGetValue ( owner , out var userName ) ) {
557+ schema . Owner = userName ;
527558 }
528559 else {
529- throw new InvalidOperationException ( string . Format ( Resources . Strings . ExCantFindSchemaXOwnerWithIdYInTheListOfUsers , name , owner ) ) ;
560+ throw new InvalidOperationException ( string . Format ( Resources . Strings . ExCantFindSchemaXOwnerWithIdYInTheListOfRoles , name , owner ) ) ;
530561 }
531562 context . SchemaMap [ oid ] = schema ;
532563 context . ReversedSchemaMap [ schema ] = oid ;
0 commit comments