-
Notifications
You must be signed in to change notification settings - Fork 98
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
Stored the permissions in a single column using bits #2044
Conversation
@@ -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, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not needed here.
if((permission & priv) == permission) | ||
return true; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed it.
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"); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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:
- [select, insert, update, references] -> Applicable for Tables
- [execute] -> Applicable for Functions and Procedures
So, we cannot execute a single statement if a grant statement specifies multiple privileges like insert, select, update
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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:
- [select, insert, update, references] -> Applicable for Tables
- [execute] -> Applicable for Functions and Procedures
So, we cannot execute a single statement if a grant statement specifies multiple privileges likeinsert, select, update
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
if (prev_ProcessUtility) | ||
prev_ProcessUtility(pstmt, queryString, readOnlyTree, context, params, | ||
queryEnv, dest, qc); | ||
else | ||
standard_ProcessUtility(pstmt, queryString, readOnlyTree, context, params, | ||
queryEnv, dest, qc); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
/* 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 | ||
|
There was a problem hiding this comment.
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.
There was a problem hiding this 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
- 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? - 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 - 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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
if((permission & priv) == permission) | ||
return true; |
There was a problem hiding this comment.
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
char *schema; | ||
List *res; | ||
Node *res_stmt; | ||
PlannedStmt *wrapper; |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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!
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
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)); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whats the next step?
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"); |
There was a problem hiding this comment.
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?
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"); |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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?
if (prev_ProcessUtility) | ||
prev_ProcessUtility(pstmt, queryString, readOnlyTree, context, params, | ||
queryEnv, dest, qc); | ||
else | ||
standard_ProcessUtility(pstmt, queryString, readOnlyTree, context, params, | ||
queryEnv, dest, qc); |
There was a problem hiding this comment.
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?
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; |
There was a problem hiding this comment.
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.
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
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.