1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-11 10:01:57 +03:00

Allow CURRENT/SESSION_USER to be used in certain commands

Commands such as ALTER USER, ALTER GROUP, ALTER ROLE, GRANT, and the
various ALTER OBJECT / OWNER TO, as well as ad-hoc clauses related to
roles such as the AUTHORIZATION clause of CREATE SCHEMA, the FOR clause
of CREATE USER MAPPING, and the FOR ROLE clause of ALTER DEFAULT
PRIVILEGES can now take the keywords CURRENT_USER and SESSION_USER as
user specifiers in place of an explicit user name.

This commit also fixes some quite ugly handling of special standards-
mandated syntax in CREATE USER MAPPING, which in particular would fail
to work in presence of a role named "current_user".

The special role specifiers PUBLIC and NONE also have more consistent
handling now.

Also take the opportunity to add location tracking to user specifiers.

Authors: Kyotaro Horiguchi.  Heavily reworked by Álvaro Herrera.
Reviewed by: Rushabh Lathia, Adam Brightwell, Marti Raudsepp.
This commit is contained in:
Alvaro Herrera
2015-03-09 15:41:54 -03:00
parent fa83f80945
commit 31eae6028e
52 changed files with 2000 additions and 347 deletions

View File

@ -143,6 +143,7 @@ static Node *makeBitStringConst(char *str, int location);
static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static Node *makeRoleSpec(RoleSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@ -291,7 +292,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> opt_type
%type <str> foreign_server_version opt_foreign_server_version
%type <str> auth_ident
%type <str> opt_in_database
%type <str> OptSchemaName
@ -474,12 +474,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> Iconst SignedIconst
%type <str> Sconst comment_text notify_payload
%type <str> RoleId opt_granted_by opt_boolean_or_string
%type <str> RoleId opt_boolean_or_string
%type <list> var_list
%type <str> ColId ColLabel var_name type_function_name param_name
%type <str> NonReservedWord NonReservedWord_or_Sconst
%type <str> createdb_opt_name
%type <node> var_value zone_value
%type <node> auth_ident RoleSpec opt_granted_by
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@ -494,7 +495,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> constraints_set_list
%type <boolean> constraints_set_mode
%type <str> OptTableSpace OptConsTableSpace OptTableSpaceOwner
%type <str> OptTableSpace OptConsTableSpace
%type <node> OptTableSpaceOwner
%type <ival> opt_check_option
%type <str> opt_provider security_label
@ -1037,7 +1039,7 @@ CreateUserStmt:
*****************************************************************************/
AlterRoleStmt:
ALTER ROLE RoleId opt_with AlterOptRoleList
ALTER ROLE RoleSpec opt_with AlterOptRoleList
{
AlterRoleStmt *n = makeNode(AlterRoleStmt);
n->role = $3;
@ -1053,7 +1055,7 @@ opt_in_database:
;
AlterRoleSetStmt:
ALTER ROLE RoleId opt_in_database SetResetClause
ALTER ROLE RoleSpec opt_in_database SetResetClause
{
AlterRoleSetStmt *n = makeNode(AlterRoleSetStmt);
n->role = $3;
@ -1079,7 +1081,7 @@ AlterRoleSetStmt:
*****************************************************************************/
AlterUserStmt:
ALTER USER RoleId opt_with AlterOptRoleList
ALTER USER RoleSpec opt_with AlterOptRoleList
{
AlterRoleStmt *n = makeNode(AlterRoleStmt);
n->role = $3;
@ -1091,7 +1093,7 @@ AlterUserStmt:
AlterUserSetStmt:
ALTER USER RoleId SetResetClause
ALTER USER RoleSpec SetResetClause
{
AlterRoleSetStmt *n = makeNode(AlterRoleSetStmt);
n->role = $3;
@ -1180,7 +1182,7 @@ CreateGroupStmt:
*****************************************************************************/
AlterGroupStmt:
ALTER GROUP_P RoleId add_drop USER role_list
ALTER GROUP_P RoleSpec add_drop USER role_list
{
AlterRoleStmt *n = makeNode(AlterRoleStmt);
n->role = $3;
@ -1228,15 +1230,12 @@ DropGroupStmt:
*****************************************************************************/
CreateSchemaStmt:
CREATE SCHEMA OptSchemaName AUTHORIZATION RoleId OptSchemaEltList
CREATE SCHEMA OptSchemaName AUTHORIZATION RoleSpec OptSchemaEltList
{
CreateSchemaStmt *n = makeNode(CreateSchemaStmt);
/* One can omit the schema name or the authorization id. */
if ($3 != NULL)
n->schemaname = $3;
else
n->schemaname = $5;
n->authid = $5;
n->schemaname = $3;
n->authrole = $5;
n->schemaElts = $6;
n->if_not_exists = false;
$$ = (Node *)n;
@ -1246,20 +1245,17 @@ CreateSchemaStmt:
CreateSchemaStmt *n = makeNode(CreateSchemaStmt);
/* ...but not both */
n->schemaname = $3;
n->authid = NULL;
n->authrole = NULL;
n->schemaElts = $4;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE SCHEMA IF_P NOT EXISTS OptSchemaName AUTHORIZATION RoleId OptSchemaEltList
| CREATE SCHEMA IF_P NOT EXISTS OptSchemaName AUTHORIZATION RoleSpec OptSchemaEltList
{
CreateSchemaStmt *n = makeNode(CreateSchemaStmt);
/* One can omit the schema name or the authorization id. */
if ($6 != NULL)
n->schemaname = $6;
else
n->schemaname = $8;
n->authid = $8;
/* schema name can be omitted here, too */
n->schemaname = $6;
n->authrole = $8;
if ($9 != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@ -1272,9 +1268,9 @@ CreateSchemaStmt:
| CREATE SCHEMA IF_P NOT EXISTS ColId OptSchemaEltList
{
CreateSchemaStmt *n = makeNode(CreateSchemaStmt);
/* ...but not both */
/* ...but not here */
n->schemaname = $6;
n->authid = NULL;
n->authrole = NULL;
if ($7 != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@ -2259,12 +2255,12 @@ alter_table_cmd:
n->subtype = AT_DropOf;
$$ = (Node *)n;
}
/* ALTER TABLE <name> OWNER TO RoleId */
| OWNER TO RoleId
/* ALTER TABLE <name> OWNER TO RoleSpec */
| OWNER TO RoleSpec
{
AlterTableCmd *n = makeNode(AlterTableCmd);
n->subtype = AT_ChangeOwner;
n->name = $3;
n->newowner = $3;
$$ = (Node *)n;
}
/* ALTER TABLE <name> SET TABLESPACE <tablespacename> */
@ -3756,7 +3752,7 @@ CreateTableSpaceStmt: CREATE TABLESPACE name OptTableSpaceOwner LOCATION Sconst
}
;
OptTableSpaceOwner: OWNER name { $$ = $2; }
OptTableSpaceOwner: OWNER RoleSpec { $$ = $2; }
| /*EMPTY */ { $$ = NULL; }
;
@ -4478,7 +4474,7 @@ import_qualification:
CreateUserMappingStmt: CREATE USER MAPPING FOR auth_ident SERVER name create_generic_options
{
CreateUserMappingStmt *n = makeNode(CreateUserMappingStmt);
n->username = $5;
n->user = $5;
n->servername = $7;
n->options = $8;
$$ = (Node *) n;
@ -4486,10 +4482,8 @@ CreateUserMappingStmt: CREATE USER MAPPING FOR auth_ident SERVER name create_gen
;
/* User mapping authorization identifier */
auth_ident:
CURRENT_USER { $$ = "current_user"; }
| USER { $$ = "current_user"; }
| RoleId { $$ = (strcmp($1, "public") == 0) ? NULL : $1; }
auth_ident: RoleSpec { $$ = $1; }
| USER { $$ = makeRoleSpec(ROLESPEC_CURRENT_USER, @1); }
;
/*****************************************************************************
@ -4502,7 +4496,7 @@ auth_ident:
DropUserMappingStmt: DROP USER MAPPING FOR auth_ident SERVER name
{
DropUserMappingStmt *n = makeNode(DropUserMappingStmt);
n->username = $5;
n->user = $5;
n->servername = $7;
n->missing_ok = false;
$$ = (Node *) n;
@ -4510,7 +4504,7 @@ DropUserMappingStmt: DROP USER MAPPING FOR auth_ident SERVER name
| DROP USER MAPPING IF_P EXISTS FOR auth_ident SERVER name
{
DropUserMappingStmt *n = makeNode(DropUserMappingStmt);
n->username = $7;
n->user = $7;
n->servername = $9;
n->missing_ok = true;
$$ = (Node *) n;
@ -4527,7 +4521,7 @@ DropUserMappingStmt: DROP USER MAPPING FOR auth_ident SERVER name
AlterUserMappingStmt: ALTER USER MAPPING FOR auth_ident SERVER name alter_generic_options
{
AlterUserMappingStmt *n = makeNode(AlterUserMappingStmt);
n->username = $5;
n->user = $5;
n->servername = $7;
n->options = $8;
$$ = (Node *) n;
@ -4612,7 +4606,7 @@ RowSecurityOptionalWithCheck:
RowSecurityDefaultToRole:
TO role_list { $$ = $2; }
| /* EMPTY */ { $$ = list_make1(makeString("public")); }
| /* EMPTY */ { $$ = list_make1(makeRoleSpec(ROLESPEC_PUBLIC, -1)); }
;
RowSecurityOptionalToRole:
@ -5432,7 +5426,7 @@ DropOwnedStmt:
;
ReassignOwnedStmt:
REASSIGN OWNED BY role_list TO name
REASSIGN OWNED BY role_list TO RoleSpec
{
ReassignOwnedStmt *n = makeNode(ReassignOwnedStmt);
n->roles = $4;
@ -6348,26 +6342,9 @@ grantee_list:
| grantee_list ',' grantee { $$ = lappend($1, $3); }
;
grantee: RoleId
{
PrivGrantee *n = makeNode(PrivGrantee);
/* This hack lets us avoid reserving PUBLIC as a keyword*/
if (strcmp($1, "public") == 0)
n->rolname = NULL;
else
n->rolname = $1;
$$ = (Node *)n;
}
| GROUP_P RoleId
{
PrivGrantee *n = makeNode(PrivGrantee);
/* Treat GROUP PUBLIC as a synonym for PUBLIC */
if (strcmp($2, "public") == 0)
n->rolname = NULL;
else
n->rolname = $2;
$$ = (Node *)n;
}
grantee:
RoleSpec { $$ = $1; }
| GROUP_P RoleSpec { $$ = $2; }
;
@ -6438,7 +6415,7 @@ opt_grant_admin_option: WITH ADMIN OPTION { $$ = TRUE; }
| /*EMPTY*/ { $$ = FALSE; }
;
opt_granted_by: GRANTED BY RoleId { $$ = $3; }
opt_granted_by: GRANTED BY RoleSpec { $$ = $3; }
| /*EMPTY*/ { $$ = NULL; }
;
@ -8104,7 +8081,7 @@ AlterObjectSchemaStmt:
*
*****************************************************************************/
AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_AGGREGATE;
@ -8113,7 +8090,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
n->newowner = $7;
$$ = (Node *)n;
}
| ALTER COLLATION any_name OWNER TO RoleId
| ALTER COLLATION any_name OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_COLLATION;
@ -8121,7 +8098,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
n->newowner = $6;
$$ = (Node *)n;
}
| ALTER CONVERSION_P any_name OWNER TO RoleId
| ALTER CONVERSION_P any_name OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_CONVERSION;
@ -8129,7 +8106,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
n->newowner = $6;
$$ = (Node *)n;
}
| ALTER DATABASE database_name OWNER TO RoleId
| ALTER DATABASE database_name OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_DATABASE;
@ -8137,7 +8114,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
n->newowner = $6;
$$ = (Node *)n;
}
| ALTER DOMAIN_P any_name OWNER TO RoleId
| ALTER DOMAIN_P any_name OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_DOMAIN;
@ -8145,7 +8122,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
n->newowner = $6;
$$ = (Node *)n;
}
| ALTER FUNCTION function_with_argtypes OWNER TO RoleId
| ALTER FUNCTION function_with_argtypes OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_FUNCTION;
@ -8154,7 +8131,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
n->newowner = $6;
$$ = (Node *)n;
}
| ALTER opt_procedural LANGUAGE name OWNER TO RoleId
| ALTER opt_procedural LANGUAGE name OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_LANGUAGE;
@ -8162,7 +8139,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
n->newowner = $7;
$$ = (Node *)n;
}
| ALTER LARGE_P OBJECT_P NumericOnly OWNER TO RoleId
| ALTER LARGE_P OBJECT_P NumericOnly OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_LARGEOBJECT;
@ -8170,7 +8147,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
n->newowner = $7;
$$ = (Node *)n;
}
| ALTER OPERATOR any_operator oper_argtypes OWNER TO RoleId
| ALTER OPERATOR any_operator oper_argtypes OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_OPERATOR;
@ -8179,7 +8156,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
n->newowner = $7;
$$ = (Node *)n;
}
| ALTER OPERATOR CLASS any_name USING access_method OWNER TO RoleId
| ALTER OPERATOR CLASS any_name USING access_method OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_OPCLASS;
@ -8188,7 +8165,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
n->newowner = $9;
$$ = (Node *)n;
}
| ALTER OPERATOR FAMILY any_name USING access_method OWNER TO RoleId
| ALTER OPERATOR FAMILY any_name USING access_method OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_OPFAMILY;
@ -8197,7 +8174,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
n->newowner = $9;
$$ = (Node *)n;
}
| ALTER SCHEMA name OWNER TO RoleId
| ALTER SCHEMA name OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_SCHEMA;
@ -8205,7 +8182,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
n->newowner = $6;
$$ = (Node *)n;
}
| ALTER TYPE_P any_name OWNER TO RoleId
| ALTER TYPE_P any_name OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_TYPE;
@ -8213,7 +8190,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
n->newowner = $6;
$$ = (Node *)n;
}
| ALTER TABLESPACE name OWNER TO RoleId
| ALTER TABLESPACE name OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_TABLESPACE;
@ -8221,7 +8198,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
n->newowner = $6;
$$ = (Node *)n;
}
| ALTER TEXT_P SEARCH DICTIONARY any_name OWNER TO RoleId
| ALTER TEXT_P SEARCH DICTIONARY any_name OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_TSDICTIONARY;
@ -8229,7 +8206,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
n->newowner = $8;
$$ = (Node *)n;
}
| ALTER TEXT_P SEARCH CONFIGURATION any_name OWNER TO RoleId
| ALTER TEXT_P SEARCH CONFIGURATION any_name OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_TSCONFIGURATION;
@ -8237,7 +8214,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
n->newowner = $8;
$$ = (Node *)n;
}
| ALTER FOREIGN DATA_P WRAPPER name OWNER TO RoleId
| ALTER FOREIGN DATA_P WRAPPER name OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_FDW;
@ -8245,7 +8222,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
n->newowner = $8;
$$ = (Node *)n;
}
| ALTER SERVER name OWNER TO RoleId
| ALTER SERVER name OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_FOREIGN_SERVER;
@ -8253,7 +8230,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
n->newowner = $6;
$$ = (Node *)n;
}
| ALTER EVENT TRIGGER name OWNER TO RoleId
| ALTER EVENT TRIGGER name OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_EVENT_TRIGGER;
@ -13113,19 +13090,86 @@ AexprConst: Iconst
Iconst: ICONST { $$ = $1; };
Sconst: SCONST { $$ = $1; };
RoleId: NonReservedWord { $$ = $1; };
role_list: RoleId
{ $$ = list_make1(makeString($1)); }
| role_list ',' RoleId
{ $$ = lappend($1, makeString($3)); }
;
SignedIconst: Iconst { $$ = $1; }
| '+' Iconst { $$ = + $2; }
| '-' Iconst { $$ = - $2; }
;
/* Role specifications */
RoleId: RoleSpec
{
RoleSpec *spc = (RoleSpec *) $1;
switch (spc->roletype)
{
case ROLESPEC_CSTRING:
$$ = spc->rolename;
break;
case ROLESPEC_PUBLIC:
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("role name \"%s\" is reserved",
"public"),
parser_errposition(@1)));
case ROLESPEC_SESSION_USER:
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("%s cannot be used as a role name",
"SESSION_USER"),
parser_errposition(@1)));
case ROLESPEC_CURRENT_USER:
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("%s cannot be used as a role name",
"CURRENT_USER"),
parser_errposition(@1)));
}
}
;
RoleSpec: NonReservedWord
{
/*
* "public" and "none" are not keywords, but they must
* be treated specially here.
*/
RoleSpec *n;
if (strcmp($1, "public") == 0)
{
n = (RoleSpec *) makeRoleSpec(ROLESPEC_PUBLIC, @1);
n->roletype = ROLESPEC_PUBLIC;
}
else if (strcmp($1, "none") == 0)
{
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("role name \"%s\" is reserved",
"none"),
parser_errposition(@1)));
}
else
{
n = (RoleSpec *) makeRoleSpec(ROLESPEC_CSTRING, @1);
n->rolename = pstrdup($1);
}
$$ = (Node *) n;
}
| CURRENT_USER
{
$$ = makeRoleSpec(ROLESPEC_CURRENT_USER, @1);
}
| SESSION_USER
{
$$ = makeRoleSpec(ROLESPEC_SESSION_USER, @1);
}
;
role_list: RoleSpec
{ $$ = list_make1($1); }
| role_list ',' RoleSpec
{ $$ = lappend($1, $3); }
;
/*
* Name classification hierarchy.
*
@ -13812,6 +13856,20 @@ makeBoolAConst(bool state, int location)
return makeTypeCast((Node *)n, SystemTypeName("bool"), -1);
}
/* makeRoleSpec
* Create a RoleSpec with the given type
*/
static Node *
makeRoleSpec(RoleSpecType type, int location)
{
RoleSpec *spec = makeNode(RoleSpec);
spec->roletype = type;
spec->location = location;
return (Node *) spec;
}
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow

View File

@ -90,7 +90,7 @@ typedef struct
{
const char *stmtType; /* "CREATE SCHEMA" or "ALTER SCHEMA" */
char *schemaname; /* name of schema */
char *authid; /* owner of schema */
RoleSpec *authrole; /* owner of schema */
List *sequences; /* CREATE SEQUENCE items */
List *tables; /* CREATE TABLE items */
List *views; /* CREATE VIEW items */
@ -2723,7 +2723,7 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
cxt.stmtType = "CREATE SCHEMA";
cxt.schemaname = stmt->schemaname;
cxt.authid = stmt->authid;
cxt.authrole = (RoleSpec *) stmt->authrole;
cxt.sequences = NIL;
cxt.tables = NIL;
cxt.views = NIL;