Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stored the permissions in a single column using bits #2044

Conversation

shalinilohia50
Copy link
Contributor

@shalinilohia50 shalinilohia50 commented Nov 21, 2023

Description

Store the permissions in a single column using bits

Issues Resolved

Task: BABEL-4344
Co-authored-by: Anju Bharti
Signed-off-by: Shalini Lohia

Test Scenarios Covered

  • Use case based -

  • Boundary conditions -

  • Arbitrary inputs -

  • Negative test cases -

  • Minor version upgrade tests -

  • Major version upgrade tests -

  • Performance tests -

  • Tooling impact -

  • Client tests -

Check List

  • Commits are signed per the DCO using --signoff

By submitting this pull request, I confirm that my contribution is under the terms of the Apache 2.0 and PostgreSQL licenses, and grant any person obtaining a copy of the contribution permission to relicense all or a portion of my contribution to the PostgreSQL License solely to contribute all or a portion of my contribution to the PostgreSQL open source project.

For more information on following Developer Certificate of Origin and signing off your commits, please check here.

@@ -19,10 +19,10 @@ CREATE TABLE sys.babelfish_schema_permissions (
dbid smallint NOT NULL,
schema_name NAME NOT NULL COLLATE sys.database_default,
object_name NAME NOT NULL COLLATE sys.database_default,
permission NAME NOT NULL COLLATE sys.database_default,
permission SMALLINT,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be NOT NULL, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

table_endscan(scan);
table_close(bbf_schema_rel, RowExclusiveLock);

CommandCounterIncrement();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed here.

Comment on lines 3153 to 3154
if((permission & priv) == permission)
return true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not ending the scan and not closing the table. Have you added a test for this? Otherwise, the test should have complained.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, but that would only come into consideration if you check the logs. There would have been explicit cache leak references

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed it.

Comment on lines +3395 to +3406
if((permission & PRIVILEGE_BIT_FOR_EXECUTE) == PRIVILEGE_BIT_FOR_EXECUTE)
grant_perms_to_each_obj(db_name, object_type, schema_name, object_name, grantee, "execute");
if((permission & PRIVILEGE_BIT_FOR_SELECT) == PRIVILEGE_BIT_FOR_SELECT)
grant_perms_to_each_obj(db_name, object_type, schema_name, object_name, grantee, "select");
if((permission & PRIVILEGE_BIT_FOR_INSERT) == PRIVILEGE_BIT_FOR_INSERT)
grant_perms_to_each_obj(db_name, object_type, schema_name, object_name, grantee, "insert");
if((permission & PRIVILEGE_BIT_FOR_UPDATE) == PRIVILEGE_BIT_FOR_UPDATE)
grant_perms_to_each_obj(db_name, object_type, schema_name, object_name, grantee, "update");
if((permission & PRIVILEGE_BIT_FOR_DELETE) == PRIVILEGE_BIT_FOR_DELETE)
grant_perms_to_each_obj(db_name, object_type, schema_name, object_name, grantee, "delete");
if((permission & PRIVILEGE_BIT_FOR_REFERENCES) == PRIVILEGE_BIT_FOR_REFERENCES)
grant_perms_to_each_obj(db_name, object_type, schema_name, object_name, grantee, "references");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to execute a single GRANT statement with all the permissions at once. Otherwise, it'll execute individual GRANT statement

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have separate sub commands for below privileges:

  1. [select, insert, update, references] -> Applicable for Tables
  2. [execute] -> Applicable for Functions and Procedures
    So, we cannot execute a single statement if a grant statement specifies multiple privileges like insert, select, update

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But, in a single iteration of the loop, the object type won't change, right?

{
List *parsetree_list;
ListCell *parsetree_item;
char *dbname = get_cur_db_name();
char *login = GetUserNameFromId(GetSessionUserId(), false);
parsetree_list = gen_grantschema_subcmds(schema_name, rolname, stmt->is_grant, stmt->with_grant_option, priv_name);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't you free this list?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's needed. References

errmsg("Cannot find the schema \"%s\", because it does not exist or you do not have permission.", stmt->schema_name)));

if((privilege_maskInt & PRIVILEGE_BIT_FOR_EXECUTE) == PRIVILEGE_BIT_FOR_EXECUTE)
exec_gen_grantschema(schema_name, rolname, stmt, "execute");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Send privilege names as a list and execute a single statement

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have separate sub commands for below privileges:

  1. [select, insert, update, references] -> Applicable for Tables
  2. [execute] -> Applicable for Functions and Procedures
    So, we cannot execute a single statement if a grant statement specifies multiple privileges like insert, select, update

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, we can write a single batch for all grant queries and get it executed as a batch, if thats possible. Is it? The code will be cleaner in that case

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shalinilohia50 But, for a single exec_stmt_grantschema execution, the object type won't change, right?

@@ -3623,7 +3626,6 @@ bbf_ProcessUtility(PlannedStmt *pstmt,
const char *logical_schema = NULL;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you handling CREATE LOGICAL DATABASE in OBJECT_TABLE? What other subcommand can fall under this category?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the CREATE DATABASE subcommands was GRANT SELECT ON dummy.sysdatabases TO dummy; which we avoided here.
With this commit, looks like it's not needed anymore.

Comment on lines 3682 to 3687
if (prev_ProcessUtility)
prev_ProcessUtility(pstmt, queryString, readOnlyTree, context, params,
queryEnv, dest, qc);
else
standard_ProcessUtility(pstmt, queryString, readOnlyTree, context, params,
queryEnv, dest, qc);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this call exec_stmt_grantschema internally and update the catalog? If not, when exec_stmt_grantschema is called exactly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have defined a separate node PLtsql_stmt_grantschema for GRANT/REVOKE on SCHEMA statement which executes exec_stmt_grantschema

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. But, exec_stmt_grantschema also updates the catalog. So, do you need to update it here as well?

AccessPriv *ap = (AccessPriv *) lfirst(lc1);
if(ap->cols != NULL)
{
flag = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this flag indicate?

Copy link
Contributor Author

@shalinilohia50 shalinilohia50 Nov 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This specifies if there is permission granted to a specific column in a table.
Renaming it to something meaningful.

Comment on lines +1917 to +1925
/* Privilege Bits for GRANT SCHEMA */
#define PRIVILEGE_BIT_FOR_EXECUTE 32
#define ALL_PRIVILEGE_EXCEPT_EXECUTE 31
#define PRIVILEGE_BIT_FOR_SELECT 16
#define PRIVILEGE_BIT_FOR_INSERT 8
#define PRIVILEGE_BIT_FOR_UPDATE 4
#define PRIVILEGE_BIT_FOR_DELETE 2
#define PRIVILEGE_BIT_FOR_REFERENCES 1

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the same ACL defined in PG. This will help us reusing some internal functions/macros if required. For example you can use privilege_to_string() to retrieve the values or ACLITEM_SET_PRIVS to set the privileges.

Copy link
Contributor

@KushaalShroff KushaalShroff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I came across a flaw with the GRANT/REVOKE on schema code/design. In the following example you see Babelfish allows us to create objects with same names and also overload procedures and functions:

1> create table t (a int)
2> go

1> create procedure t as select 1
2> go

1>  create procedure t(@a int) as select @a
2> go
  1. You are not scanning/looking up based on object_type at multiple places in the code.
    a. You are going to return the first matching entry in table irrespective of the object_type.
    b. You are inserting NULL for object_type at one place, why?
  2. How are you gonna support Grants/Revokes on overloaded procedures and functions?
    a. In the above example One grant/revoke statement will affect unintended objects, that seems like a critical bug to me
  3. We need to add these test cases

@@ -19,10 +19,10 @@ CREATE TABLE sys.babelfish_schema_permissions (
dbid smallint NOT NULL,
schema_name NAME NOT NULL COLLATE sys.database_default,
object_name NAME NOT NULL COLLATE sys.database_default,
permission NAME NOT NULL COLLATE sys.database_default,
permission SMALLINT,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Comment on lines 3153 to 3154
if((permission & priv) == permission)
return true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, but that would only come into consideration if you check the logs. There would have been explicit cache leak references

Comment on lines +3295 to +3298
char *schema;
List *res;
Node *res_stmt;
PlannedStmt *wrapper;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to free 3 of these, right?


/* make sure later steps can see the object created here */
CommandCounterIncrement();
if((permission & PRIVILEGE_BIT_FOR_EXECUTE) == PRIVILEGE_BIT_FOR_EXECUTE)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to equate???
Simple if(permission & PRIVILEGE_BIT_FOR_EXECUTE) should work!

Comment on lines +2004 to +2032
int16
create_privilege_bitmask(const List *l, bool grant_schema_stmt)
{
int16 privilege_maskInt = 0;
ListCell *lc;
foreach(lc, l)
{
char *priv_name;
if (grant_schema_stmt)
priv_name = (char *) lfirst(lc);
else
{
AccessPriv *ap = (AccessPriv *) lfirst(lc);
priv_name = ap->priv_name;
}
if (!strcmp(priv_name,"execute"))
privilege_maskInt |= PRIVILEGE_BIT_FOR_EXECUTE;
else if (!strcmp(priv_name,"select"))
privilege_maskInt |= PRIVILEGE_BIT_FOR_SELECT;
else if (!strcmp(priv_name,"insert"))
privilege_maskInt |= PRIVILEGE_BIT_FOR_INSERT;
else if (!strcmp(priv_name,"update"))
privilege_maskInt |= PRIVILEGE_BIT_FOR_UPDATE;
else if (!strcmp(priv_name,"delete"))
privilege_maskInt |= PRIVILEGE_BIT_FOR_DELETE;
else if (!strcmp(priv_name,"references"))
privilege_maskInt |= PRIVILEGE_BIT_FOR_REFERENCES;
}
return privilege_maskInt;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WHY?
Why cant we simply store privilege_maskInt during parsing? You are parsing a string and storing a list of strings in antlr and then coming here to convert List of strings to int. Seems unnecessarily costly to me.

Copy link
Contributor

@anju15bharti anju15bharti Nov 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried storing bitmask during parsing but then to do this change was eventually leading change in structures' definition of postgres, hence we need to store strings only during parsing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned, use ACL defined in PG. It provides a lot of APIs for these.

int16 new_priv,
int16 old_priv,
const char *grantee,
const char *object_type,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot the use of object_type, why is it needed?
Was there no compiler warning at the time of build?

grantee NAME NOT NULL COLLATE sys.database_default,
object_type NAME COLLATE sys.database_default,
PRIMARY KEY(dbid, schema_name, object_name, permission, grantee)
PRIMARY KEY(dbid, schema_name, object_name, grantee)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OBJECT_TYPE should be part of primary key too, right?

tsql_get_server_collation_oid_internal(false),
F_NAMEEQ,
CStringGetDatum(schema_name));
ScanKeyEntryInitialize(&scanKey[2], 0,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to scan based on object type too!!!

How are you going to distinguish between a table and procedure?

1> create table t (a int)
2> go

1> create procedure t as select 1
2> go

Comment on lines +2900 to +2924
ScanKeyInit(&scanKey[0],
Anum_bbf_schema_perms_dbid,
BTEqualStrategyNumber, F_INT2EQ,
Int16GetDatum(dbid));
ScanKeyEntryInitialize(&scanKey[1], 0,
Anum_bbf_schema_perms_schema_name,
BTEqualStrategyNumber,
InvalidOid,
tsql_get_server_collation_oid_internal(false),
F_NAMEEQ,
CStringGetDatum(schema_name));
ScanKeyEntryInitialize(&scanKey[2], 0,
Anum_bbf_schema_perms_object_name,
BTEqualStrategyNumber,
InvalidOid,
tsql_get_server_collation_oid_internal(false),
F_NAMEEQ,
CStringGetDatum(object_name));
ScanKeyEntryInitialize(&scanKey[3], 0,
Anum_bbf_schema_perms_grantee,
BTEqualStrategyNumber,
InvalidOid,
tsql_get_server_collation_oid_internal(false),
F_NAMEEQ,
CStringGetDatum(grantee));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on this logic you are going to return the first object which matches irrespective of the object_type.
What if the first one was a table but user was granting for a procedure?


/* Add entry for each grant statement. */
if (stmt->is_grant && !check_bbf_schema_for_entry(stmt->schema_name, PERMISSIONS_FOR_ALL_OBJECTS_IN_SCHEMA, rolname))
add_entry_to_bbf_schema(stmt->schema_name, PERMISSIONS_FOR_ALL_OBJECTS_IN_SCHEMA, privilege_maskInt, rolname, NULL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you inserting NULL for object_type?

CStringGetDatum(grantee));

scan = table_beginscan_catalog(bbf_schema_rel, 5, scanKey);
tuple_bbf_schema = heap_getnext(scan, ForwardScanDirection);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, in some place, it's a heap_getnext and in some place it's a sys scan.

NULL);

/* make sure later steps can see the object created here */
CommandCounterIncrement();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whats the next step?

Comment on lines +3395 to +3406
if((permission & PRIVILEGE_BIT_FOR_EXECUTE) == PRIVILEGE_BIT_FOR_EXECUTE)
grant_perms_to_each_obj(db_name, object_type, schema_name, object_name, grantee, "execute");
if((permission & PRIVILEGE_BIT_FOR_SELECT) == PRIVILEGE_BIT_FOR_SELECT)
grant_perms_to_each_obj(db_name, object_type, schema_name, object_name, grantee, "select");
if((permission & PRIVILEGE_BIT_FOR_INSERT) == PRIVILEGE_BIT_FOR_INSERT)
grant_perms_to_each_obj(db_name, object_type, schema_name, object_name, grantee, "insert");
if((permission & PRIVILEGE_BIT_FOR_UPDATE) == PRIVILEGE_BIT_FOR_UPDATE)
grant_perms_to_each_obj(db_name, object_type, schema_name, object_name, grantee, "update");
if((permission & PRIVILEGE_BIT_FOR_DELETE) == PRIVILEGE_BIT_FOR_DELETE)
grant_perms_to_each_obj(db_name, object_type, schema_name, object_name, grantee, "delete");
if((permission & PRIVILEGE_BIT_FOR_REFERENCES) == PRIVILEGE_BIT_FOR_REFERENCES)
grant_perms_to_each_obj(db_name, object_type, schema_name, object_name, grantee, "references");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But, in a single iteration of the loop, the object type won't change, right?

Comment on lines +3396 to +3406
grant_perms_to_each_obj(db_name, object_type, schema_name, object_name, grantee, "execute");
if((permission & PRIVILEGE_BIT_FOR_SELECT) == PRIVILEGE_BIT_FOR_SELECT)
grant_perms_to_each_obj(db_name, object_type, schema_name, object_name, grantee, "select");
if((permission & PRIVILEGE_BIT_FOR_INSERT) == PRIVILEGE_BIT_FOR_INSERT)
grant_perms_to_each_obj(db_name, object_type, schema_name, object_name, grantee, "insert");
if((permission & PRIVILEGE_BIT_FOR_UPDATE) == PRIVILEGE_BIT_FOR_UPDATE)
grant_perms_to_each_obj(db_name, object_type, schema_name, object_name, grantee, "update");
if((permission & PRIVILEGE_BIT_FOR_DELETE) == PRIVILEGE_BIT_FOR_DELETE)
grant_perms_to_each_obj(db_name, object_type, schema_name, object_name, grantee, "delete");
if((permission & PRIVILEGE_BIT_FOR_REFERENCES) == PRIVILEGE_BIT_FOR_REFERENCES)
grant_perms_to_each_obj(db_name, object_type, schema_name, object_name, grantee, "references");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case of an error, the table will be closed automatically unless we catch the error explicitly.

errmsg("Cannot find the schema \"%s\", because it does not exist or you do not have permission.", stmt->schema_name)));

if((privilege_maskInt & PRIVILEGE_BIT_FOR_EXECUTE) == PRIVILEGE_BIT_FOR_EXECUTE)
exec_gen_grantschema(schema_name, rolname, stmt, "execute");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shalinilohia50 But, for a single exec_stmt_grantschema execution, the object type won't change, right?

Comment on lines 3682 to 3687
if (prev_ProcessUtility)
prev_ProcessUtility(pstmt, queryString, readOnlyTree, context, params,
queryEnv, dest, qc);
else
standard_ProcessUtility(pstmt, queryString, readOnlyTree, context, params,
queryEnv, dest, qc);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. But, exec_stmt_grantschema also updates the catalog. So, do you need to update it here as well?

Comment on lines +2004 to +2032
int16
create_privilege_bitmask(const List *l, bool grant_schema_stmt)
{
int16 privilege_maskInt = 0;
ListCell *lc;
foreach(lc, l)
{
char *priv_name;
if (grant_schema_stmt)
priv_name = (char *) lfirst(lc);
else
{
AccessPriv *ap = (AccessPriv *) lfirst(lc);
priv_name = ap->priv_name;
}
if (!strcmp(priv_name,"execute"))
privilege_maskInt |= PRIVILEGE_BIT_FOR_EXECUTE;
else if (!strcmp(priv_name,"select"))
privilege_maskInt |= PRIVILEGE_BIT_FOR_SELECT;
else if (!strcmp(priv_name,"insert"))
privilege_maskInt |= PRIVILEGE_BIT_FOR_INSERT;
else if (!strcmp(priv_name,"update"))
privilege_maskInt |= PRIVILEGE_BIT_FOR_UPDATE;
else if (!strcmp(priv_name,"delete"))
privilege_maskInt |= PRIVILEGE_BIT_FOR_DELETE;
else if (!strcmp(priv_name,"references"))
privilege_maskInt |= PRIVILEGE_BIT_FOR_REFERENCES;
}
return privilege_maskInt;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned, use ACL defined in PG. It provides a lot of APIs for these.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants