Expose object name error fields in PL/pgSQL.
Specifically, permit attaching them to the error in RAISE and retrieving them from a caught error in GET STACKED DIAGNOSTICS. RAISE enforces nothing about the content of the fields; for its purposes, they are just additional string fields. Consequently, clarify in the protocol and libpq documentation that the usual relationships between error fields, like a schema name appearing wherever a table name appears, are not universal. This freedom has other applications; consider a FDW propagating an error from an RDBMS having no schema support. Back-patch to 9.3, where core support for the error fields was introduced. This prevents the confusion of having a release where libpq exposes the fields and PL/pgSQL does not. Pavel Stehule, lexical revisions by Noah Misch.
This commit is contained in:
parent
69e4fd4541
commit
7cd9b1371d
@ -2712,9 +2712,9 @@ char *PQresultErrorField(const PGresult *res, int fieldcode);
|
|||||||
<term><symbol>PG_DIAG_TABLE_NAME</></term>
|
<term><symbol>PG_DIAG_TABLE_NAME</></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
If the error was associated with a specific table, the name of
|
If the error was associated with a specific table, the name of the
|
||||||
the table. (When this field is present, the schema name field
|
table. (Refer to the schema name field for the name of the
|
||||||
provides the name of the table's schema.)
|
table's schema.)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -2723,9 +2723,9 @@ char *PQresultErrorField(const PGresult *res, int fieldcode);
|
|||||||
<term><symbol>PG_DIAG_COLUMN_NAME</></term>
|
<term><symbol>PG_DIAG_COLUMN_NAME</></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
If the error was associated with a specific table column, the
|
If the error was associated with a specific table column, the name
|
||||||
name of the column. (When this field is present, the schema
|
of the column. (Refer to the schema and table name fields to
|
||||||
and table name fields identify the table.)
|
identify the table.)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -2734,9 +2734,9 @@ char *PQresultErrorField(const PGresult *res, int fieldcode);
|
|||||||
<term><symbol>PG_DIAG_DATATYPE_NAME</></term>
|
<term><symbol>PG_DIAG_DATATYPE_NAME</></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
If the error was associated with a specific data type, the name
|
If the error was associated with a specific data type, the name of
|
||||||
of the data type. (When this field is present, the schema name
|
the data type. (Refer to the schema name field for the name of
|
||||||
field provides the name of the data type's schema.)
|
the data type's schema.)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -2745,11 +2745,11 @@ char *PQresultErrorField(const PGresult *res, int fieldcode);
|
|||||||
<term><symbol>PG_DIAG_CONSTRAINT_NAME</></term>
|
<term><symbol>PG_DIAG_CONSTRAINT_NAME</></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
If the error was associated with a specific constraint,
|
If the error was associated with a specific constraint, the name
|
||||||
the name of the constraint. The table or domain that the
|
of the constraint. Refer to fields listed above for the
|
||||||
constraint belongs to is reported using the fields listed
|
associated table or domain. (For this purpose, indexes are
|
||||||
above. (For this purpose, indexes are treated as constraints,
|
treated as constraints, even if they weren't created with
|
||||||
even if they weren't created with constraint syntax.)
|
constraint syntax.)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -2787,9 +2787,14 @@ char *PQresultErrorField(const PGresult *res, int fieldcode);
|
|||||||
|
|
||||||
<note>
|
<note>
|
||||||
<para>
|
<para>
|
||||||
The fields for schema name, table name, column name, data type
|
The fields for schema name, table name, column name, data type name,
|
||||||
name, and constraint name are supplied only for a limited number
|
and constraint name are supplied only for a limited number of error
|
||||||
of error types; see <xref linkend="errcodes-appendix">.
|
types; see <xref linkend="errcodes-appendix">. Do not assume that
|
||||||
|
the presence of any of these fields guarantees the presence of
|
||||||
|
another field. Core error sources observe the interrelationships
|
||||||
|
noted above, but user-defined functions may use these fields in other
|
||||||
|
ways. In the same vein, do not assume that these fields denote
|
||||||
|
contemporary objects in the current database.
|
||||||
</para>
|
</para>
|
||||||
</note>
|
</note>
|
||||||
|
|
||||||
|
@ -2664,11 +2664,36 @@ GET STACKED DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item<
|
|||||||
<entry>text</entry>
|
<entry>text</entry>
|
||||||
<entry>the SQLSTATE error code of the exception</entry>
|
<entry>the SQLSTATE error code of the exception</entry>
|
||||||
</row>
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>COLUMN_NAME</literal></entry>
|
||||||
|
<entry>text</entry>
|
||||||
|
<entry>the name of column related to exception</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>CONSTRAINT_NAME</literal></entry>
|
||||||
|
<entry>text</entry>
|
||||||
|
<entry>the name of constraint related to exception</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>PG_DATATYPE_NAME</literal></entry>
|
||||||
|
<entry>text</entry>
|
||||||
|
<entry>the name of datatype related to exception</entry>
|
||||||
|
</row>
|
||||||
<row>
|
<row>
|
||||||
<entry><literal>MESSAGE_TEXT</literal></entry>
|
<entry><literal>MESSAGE_TEXT</literal></entry>
|
||||||
<entry>text</entry>
|
<entry>text</entry>
|
||||||
<entry>the text of the exception's primary message</entry>
|
<entry>the text of the exception's primary message</entry>
|
||||||
</row>
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>TABLE_NAME</literal></entry>
|
||||||
|
<entry>text</entry>
|
||||||
|
<entry>the name of table related to exception</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>SCHEMA_NAME</literal></entry>
|
||||||
|
<entry>text</entry>
|
||||||
|
<entry>the name of schema related to exception</entry>
|
||||||
|
</row>
|
||||||
<row>
|
<row>
|
||||||
<entry><literal>PG_EXCEPTION_DETAIL</literal></entry>
|
<entry><literal>PG_EXCEPTION_DETAIL</literal></entry>
|
||||||
<entry>text</entry>
|
<entry>text</entry>
|
||||||
@ -3355,6 +3380,17 @@ RAISE NOTICE 'Calling cs_create_job(%)', v_job_id;
|
|||||||
five-character SQLSTATE code.</para>
|
five-character SQLSTATE code.</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><literal>COLUMN</literal></term>
|
||||||
|
<term><literal>CONSTRAINT</literal></term>
|
||||||
|
<term><literal>DATATYPE</literal></term>
|
||||||
|
<term><literal>TABLE</literal></term>
|
||||||
|
<term><literal>SCHEMA</literal></term>
|
||||||
|
<listitem>
|
||||||
|
<para>Supplies the name of a related object.</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
@ -4788,8 +4788,8 @@ message.
|
|||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Table name: if the error was associated with a specific table, the
|
Table name: if the error was associated with a specific table, the
|
||||||
name of the table. (When this field is present, the schema name field
|
name of the table. (Refer to the schema name field for the name of
|
||||||
provides the name of the table's schema.)
|
the table's schema.)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -4801,8 +4801,8 @@ message.
|
|||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Column name: if the error was associated with a specific table column,
|
Column name: if the error was associated with a specific table column,
|
||||||
the name of the column. (When this field is present, the schema and
|
the name of the column. (Refer to the schema and table name fields to
|
||||||
table name fields identify the table.)
|
identify the table.)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -4814,8 +4814,8 @@ message.
|
|||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Data type name: if the error was associated with a specific data type,
|
Data type name: if the error was associated with a specific data type,
|
||||||
the name of the data type. (When this field is present, the schema
|
the name of the data type. (Refer to the schema name field for the
|
||||||
name field provides the name of the data type's schema.)
|
name of the data type's schema.)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -4827,10 +4827,10 @@ message.
|
|||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Constraint name: if the error was associated with a specific
|
Constraint name: if the error was associated with a specific
|
||||||
constraint, the name of the constraint. The table or domain that the
|
constraint, the name of the constraint. Refer to fields listed above
|
||||||
constraint belongs to is reported using the fields listed above. (For
|
for the associated table or domain. (For this purpose, indexes are
|
||||||
this purpose, indexes are treated as constraints, even if they weren't
|
treated as constraints, even if they weren't created with constraint
|
||||||
created with constraint syntax.)
|
syntax.)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -4876,7 +4876,12 @@ message.
|
|||||||
<para>
|
<para>
|
||||||
The fields for schema name, table name, column name, data type name, and
|
The fields for schema name, table name, column name, data type name, and
|
||||||
constraint name are supplied only for a limited number of error types;
|
constraint name are supplied only for a limited number of error types;
|
||||||
see <xref linkend="errcodes-appendix">.
|
see <xref linkend="errcodes-appendix">. Frontends should not assume that
|
||||||
|
the presence of any of these fields guarantees the presence of another
|
||||||
|
field. Core error sources observe the interrelationships noted above, but
|
||||||
|
user-defined functions may use these fields in other ways. In the same
|
||||||
|
vein, clients should not assume that these fields denote contemporary
|
||||||
|
objects in the current database.
|
||||||
</para>
|
</para>
|
||||||
</note>
|
</note>
|
||||||
|
|
||||||
|
@ -1569,11 +1569,36 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
|
|||||||
unpack_sql_state(estate->cur_error->sqlerrcode));
|
unpack_sql_state(estate->cur_error->sqlerrcode));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PLPGSQL_GETDIAG_COLUMN_NAME:
|
||||||
|
exec_assign_c_string(estate, var,
|
||||||
|
estate->cur_error->column_name);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PLPGSQL_GETDIAG_CONSTRAINT_NAME:
|
||||||
|
exec_assign_c_string(estate, var,
|
||||||
|
estate->cur_error->constraint_name);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PLPGSQL_GETDIAG_DATATYPE_NAME:
|
||||||
|
exec_assign_c_string(estate, var,
|
||||||
|
estate->cur_error->datatype_name);
|
||||||
|
break;
|
||||||
|
|
||||||
case PLPGSQL_GETDIAG_MESSAGE_TEXT:
|
case PLPGSQL_GETDIAG_MESSAGE_TEXT:
|
||||||
exec_assign_c_string(estate, var,
|
exec_assign_c_string(estate, var,
|
||||||
estate->cur_error->message);
|
estate->cur_error->message);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PLPGSQL_GETDIAG_TABLE_NAME:
|
||||||
|
exec_assign_c_string(estate, var,
|
||||||
|
estate->cur_error->table_name);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PLPGSQL_GETDIAG_SCHEMA_NAME:
|
||||||
|
exec_assign_c_string(estate, var,
|
||||||
|
estate->cur_error->schema_name);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "unrecognized diagnostic item kind: %d",
|
elog(ERROR, "unrecognized diagnostic item kind: %d",
|
||||||
diag_item->kind);
|
diag_item->kind);
|
||||||
@ -2799,6 +2824,16 @@ exec_init_tuple_store(PLpgSQL_execstate *estate)
|
|||||||
estate->rettupdesc = rsi->expectedDesc;
|
estate->rettupdesc = rsi->expectedDesc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define SET_RAISE_OPTION_TEXT(opt, name) \
|
||||||
|
do { \
|
||||||
|
if (opt) \
|
||||||
|
ereport(ERROR, \
|
||||||
|
(errcode(ERRCODE_SYNTAX_ERROR), \
|
||||||
|
errmsg("RAISE option already specified: %s", \
|
||||||
|
name))); \
|
||||||
|
opt = pstrdup(extval); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
/* ----------
|
/* ----------
|
||||||
* exec_stmt_raise Build a message and throw it with elog()
|
* exec_stmt_raise Build a message and throw it with elog()
|
||||||
* ----------
|
* ----------
|
||||||
@ -2811,6 +2846,11 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
|
|||||||
char *err_message = NULL;
|
char *err_message = NULL;
|
||||||
char *err_detail = NULL;
|
char *err_detail = NULL;
|
||||||
char *err_hint = NULL;
|
char *err_hint = NULL;
|
||||||
|
char *err_column = NULL;
|
||||||
|
char *err_constraint = NULL;
|
||||||
|
char *err_datatype = NULL;
|
||||||
|
char *err_table = NULL;
|
||||||
|
char *err_schema = NULL;
|
||||||
ListCell *lc;
|
ListCell *lc;
|
||||||
|
|
||||||
/* RAISE with no parameters: re-throw current exception */
|
/* RAISE with no parameters: re-throw current exception */
|
||||||
@ -2927,28 +2967,28 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
|
|||||||
condname = pstrdup(extval);
|
condname = pstrdup(extval);
|
||||||
break;
|
break;
|
||||||
case PLPGSQL_RAISEOPTION_MESSAGE:
|
case PLPGSQL_RAISEOPTION_MESSAGE:
|
||||||
if (err_message)
|
SET_RAISE_OPTION_TEXT(err_message, "MESSAGE");
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
||||||
errmsg("RAISE option already specified: %s",
|
|
||||||
"MESSAGE")));
|
|
||||||
err_message = pstrdup(extval);
|
|
||||||
break;
|
break;
|
||||||
case PLPGSQL_RAISEOPTION_DETAIL:
|
case PLPGSQL_RAISEOPTION_DETAIL:
|
||||||
if (err_detail)
|
SET_RAISE_OPTION_TEXT(err_detail, "DETAIL");
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
||||||
errmsg("RAISE option already specified: %s",
|
|
||||||
"DETAIL")));
|
|
||||||
err_detail = pstrdup(extval);
|
|
||||||
break;
|
break;
|
||||||
case PLPGSQL_RAISEOPTION_HINT:
|
case PLPGSQL_RAISEOPTION_HINT:
|
||||||
if (err_hint)
|
SET_RAISE_OPTION_TEXT(err_hint, "HINT");
|
||||||
ereport(ERROR,
|
break;
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
case PLPGSQL_RAISEOPTION_COLUMN:
|
||||||
errmsg("RAISE option already specified: %s",
|
SET_RAISE_OPTION_TEXT(err_column, "COLUMN");
|
||||||
"HINT")));
|
break;
|
||||||
err_hint = pstrdup(extval);
|
case PLPGSQL_RAISEOPTION_CONSTRAINT:
|
||||||
|
SET_RAISE_OPTION_TEXT(err_constraint, "CONSTRAINT");
|
||||||
|
break;
|
||||||
|
case PLPGSQL_RAISEOPTION_DATATYPE:
|
||||||
|
SET_RAISE_OPTION_TEXT(err_datatype, "DATATYPE");
|
||||||
|
break;
|
||||||
|
case PLPGSQL_RAISEOPTION_TABLE:
|
||||||
|
SET_RAISE_OPTION_TEXT(err_table, "TABLE");
|
||||||
|
break;
|
||||||
|
case PLPGSQL_RAISEOPTION_SCHEMA:
|
||||||
|
SET_RAISE_OPTION_TEXT(err_schema, "SCHEMA");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "unrecognized raise option: %d", opt->opt_type);
|
elog(ERROR, "unrecognized raise option: %d", opt->opt_type);
|
||||||
@ -2982,7 +3022,17 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
|
|||||||
(err_code ? errcode(err_code) : 0,
|
(err_code ? errcode(err_code) : 0,
|
||||||
errmsg_internal("%s", err_message),
|
errmsg_internal("%s", err_message),
|
||||||
(err_detail != NULL) ? errdetail_internal("%s", err_detail) : 0,
|
(err_detail != NULL) ? errdetail_internal("%s", err_detail) : 0,
|
||||||
(err_hint != NULL) ? errhint("%s", err_hint) : 0));
|
(err_hint != NULL) ? errhint("%s", err_hint) : 0,
|
||||||
|
(err_column != NULL) ?
|
||||||
|
err_generic_string(PG_DIAG_COLUMN_NAME, err_column) : 0,
|
||||||
|
(err_constraint != NULL) ?
|
||||||
|
err_generic_string(PG_DIAG_CONSTRAINT_NAME, err_constraint) : 0,
|
||||||
|
(err_datatype != NULL) ?
|
||||||
|
err_generic_string(PG_DIAG_DATATYPE_NAME, err_datatype) : 0,
|
||||||
|
(err_table != NULL) ?
|
||||||
|
err_generic_string(PG_DIAG_TABLE_NAME, err_table) : 0,
|
||||||
|
(err_schema != NULL) ?
|
||||||
|
err_generic_string(PG_DIAG_SCHEMA_NAME, err_schema) : 0));
|
||||||
|
|
||||||
estate->err_text = NULL; /* un-suppress... */
|
estate->err_text = NULL; /* un-suppress... */
|
||||||
|
|
||||||
@ -2994,6 +3044,16 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
|
|||||||
pfree(err_detail);
|
pfree(err_detail);
|
||||||
if (err_hint != NULL)
|
if (err_hint != NULL)
|
||||||
pfree(err_hint);
|
pfree(err_hint);
|
||||||
|
if (err_column != NULL)
|
||||||
|
pfree(err_column);
|
||||||
|
if (err_constraint != NULL)
|
||||||
|
pfree(err_constraint);
|
||||||
|
if (err_datatype != NULL)
|
||||||
|
pfree(err_datatype);
|
||||||
|
if (err_table != NULL)
|
||||||
|
pfree(err_table);
|
||||||
|
if (err_schema != NULL)
|
||||||
|
pfree(err_schema);
|
||||||
|
|
||||||
return PLPGSQL_RC_OK;
|
return PLPGSQL_RC_OK;
|
||||||
}
|
}
|
||||||
|
@ -285,8 +285,18 @@ plpgsql_getdiag_kindname(int kind)
|
|||||||
return "PG_EXCEPTION_HINT";
|
return "PG_EXCEPTION_HINT";
|
||||||
case PLPGSQL_GETDIAG_RETURNED_SQLSTATE:
|
case PLPGSQL_GETDIAG_RETURNED_SQLSTATE:
|
||||||
return "RETURNED_SQLSTATE";
|
return "RETURNED_SQLSTATE";
|
||||||
|
case PLPGSQL_GETDIAG_COLUMN_NAME:
|
||||||
|
return "COLUMN_NAME";
|
||||||
|
case PLPGSQL_GETDIAG_CONSTRAINT_NAME:
|
||||||
|
return "CONSTRAINT_NAME";
|
||||||
|
case PLPGSQL_GETDIAG_DATATYPE_NAME:
|
||||||
|
return "PG_DATATYPE_NAME";
|
||||||
case PLPGSQL_GETDIAG_MESSAGE_TEXT:
|
case PLPGSQL_GETDIAG_MESSAGE_TEXT:
|
||||||
return "MESSAGE_TEXT";
|
return "MESSAGE_TEXT";
|
||||||
|
case PLPGSQL_GETDIAG_TABLE_NAME:
|
||||||
|
return "TABLE_NAME";
|
||||||
|
case PLPGSQL_GETDIAG_SCHEMA_NAME:
|
||||||
|
return "SCHEMA_NAME";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "unknown";
|
return "unknown";
|
||||||
@ -1317,6 +1327,21 @@ dump_raise(PLpgSQL_stmt_raise *stmt)
|
|||||||
case PLPGSQL_RAISEOPTION_HINT:
|
case PLPGSQL_RAISEOPTION_HINT:
|
||||||
printf(" HINT = ");
|
printf(" HINT = ");
|
||||||
break;
|
break;
|
||||||
|
case PLPGSQL_RAISEOPTION_COLUMN:
|
||||||
|
printf(" COLUMN = ");
|
||||||
|
break;
|
||||||
|
case PLPGSQL_RAISEOPTION_CONSTRAINT:
|
||||||
|
printf(" CONSTRAINT = ");
|
||||||
|
break;
|
||||||
|
case PLPGSQL_RAISEOPTION_DATATYPE:
|
||||||
|
printf(" DATATYPE = ");
|
||||||
|
break;
|
||||||
|
case PLPGSQL_RAISEOPTION_TABLE:
|
||||||
|
printf(" TABLE = ");
|
||||||
|
break;
|
||||||
|
case PLPGSQL_RAISEOPTION_SCHEMA:
|
||||||
|
printf(" SCHEMA = ");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
dump_expr(opt->expr);
|
dump_expr(opt->expr);
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
@ -251,10 +251,15 @@ static List *read_raise_options(void);
|
|||||||
%token <keyword> K_CASE
|
%token <keyword> K_CASE
|
||||||
%token <keyword> K_CLOSE
|
%token <keyword> K_CLOSE
|
||||||
%token <keyword> K_COLLATE
|
%token <keyword> K_COLLATE
|
||||||
|
%token <keyword> K_COLUMN
|
||||||
|
%token <keyword> K_COLUMN_NAME
|
||||||
%token <keyword> K_CONSTANT
|
%token <keyword> K_CONSTANT
|
||||||
|
%token <keyword> K_CONSTRAINT
|
||||||
|
%token <keyword> K_CONSTRAINT_NAME
|
||||||
%token <keyword> K_CONTINUE
|
%token <keyword> K_CONTINUE
|
||||||
%token <keyword> K_CURRENT
|
%token <keyword> K_CURRENT
|
||||||
%token <keyword> K_CURSOR
|
%token <keyword> K_CURSOR
|
||||||
|
%token <keyword> K_DATATYPE
|
||||||
%token <keyword> K_DEBUG
|
%token <keyword> K_DEBUG
|
||||||
%token <keyword> K_DECLARE
|
%token <keyword> K_DECLARE
|
||||||
%token <keyword> K_DEFAULT
|
%token <keyword> K_DEFAULT
|
||||||
@ -298,6 +303,7 @@ static List *read_raise_options(void);
|
|||||||
%token <keyword> K_OPTION
|
%token <keyword> K_OPTION
|
||||||
%token <keyword> K_OR
|
%token <keyword> K_OR
|
||||||
%token <keyword> K_PERFORM
|
%token <keyword> K_PERFORM
|
||||||
|
%token <keyword> K_PG_DATATYPE_NAME
|
||||||
%token <keyword> K_PG_EXCEPTION_CONTEXT
|
%token <keyword> K_PG_EXCEPTION_CONTEXT
|
||||||
%token <keyword> K_PG_EXCEPTION_DETAIL
|
%token <keyword> K_PG_EXCEPTION_DETAIL
|
||||||
%token <keyword> K_PG_EXCEPTION_HINT
|
%token <keyword> K_PG_EXCEPTION_HINT
|
||||||
@ -311,11 +317,15 @@ static List *read_raise_options(void);
|
|||||||
%token <keyword> K_REVERSE
|
%token <keyword> K_REVERSE
|
||||||
%token <keyword> K_ROWTYPE
|
%token <keyword> K_ROWTYPE
|
||||||
%token <keyword> K_ROW_COUNT
|
%token <keyword> K_ROW_COUNT
|
||||||
|
%token <keyword> K_SCHEMA
|
||||||
|
%token <keyword> K_SCHEMA_NAME
|
||||||
%token <keyword> K_SCROLL
|
%token <keyword> K_SCROLL
|
||||||
%token <keyword> K_SLICE
|
%token <keyword> K_SLICE
|
||||||
%token <keyword> K_SQLSTATE
|
%token <keyword> K_SQLSTATE
|
||||||
%token <keyword> K_STACKED
|
%token <keyword> K_STACKED
|
||||||
%token <keyword> K_STRICT
|
%token <keyword> K_STRICT
|
||||||
|
%token <keyword> K_TABLE
|
||||||
|
%token <keyword> K_TABLE_NAME
|
||||||
%token <keyword> K_THEN
|
%token <keyword> K_THEN
|
||||||
%token <keyword> K_TO
|
%token <keyword> K_TO
|
||||||
%token <keyword> K_TYPE
|
%token <keyword> K_TYPE
|
||||||
@ -896,7 +906,12 @@ stmt_getdiag : K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';'
|
|||||||
case PLPGSQL_GETDIAG_ERROR_DETAIL:
|
case PLPGSQL_GETDIAG_ERROR_DETAIL:
|
||||||
case PLPGSQL_GETDIAG_ERROR_HINT:
|
case PLPGSQL_GETDIAG_ERROR_HINT:
|
||||||
case PLPGSQL_GETDIAG_RETURNED_SQLSTATE:
|
case PLPGSQL_GETDIAG_RETURNED_SQLSTATE:
|
||||||
|
case PLPGSQL_GETDIAG_COLUMN_NAME:
|
||||||
|
case PLPGSQL_GETDIAG_CONSTRAINT_NAME:
|
||||||
|
case PLPGSQL_GETDIAG_DATATYPE_NAME:
|
||||||
case PLPGSQL_GETDIAG_MESSAGE_TEXT:
|
case PLPGSQL_GETDIAG_MESSAGE_TEXT:
|
||||||
|
case PLPGSQL_GETDIAG_TABLE_NAME:
|
||||||
|
case PLPGSQL_GETDIAG_SCHEMA_NAME:
|
||||||
if (!new->is_stacked)
|
if (!new->is_stacked)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
@ -970,9 +985,24 @@ getdiag_item :
|
|||||||
else if (tok_is_keyword(tok, &yylval,
|
else if (tok_is_keyword(tok, &yylval,
|
||||||
K_PG_EXCEPTION_CONTEXT, "pg_exception_context"))
|
K_PG_EXCEPTION_CONTEXT, "pg_exception_context"))
|
||||||
$$ = PLPGSQL_GETDIAG_ERROR_CONTEXT;
|
$$ = PLPGSQL_GETDIAG_ERROR_CONTEXT;
|
||||||
|
else if (tok_is_keyword(tok, &yylval,
|
||||||
|
K_COLUMN_NAME, "column_name"))
|
||||||
|
$$ = PLPGSQL_GETDIAG_COLUMN_NAME;
|
||||||
|
else if (tok_is_keyword(tok, &yylval,
|
||||||
|
K_CONSTRAINT_NAME, "constraint_name"))
|
||||||
|
$$ = PLPGSQL_GETDIAG_CONSTRAINT_NAME;
|
||||||
|
else if (tok_is_keyword(tok, &yylval,
|
||||||
|
K_PG_DATATYPE_NAME, "pg_datatype_name"))
|
||||||
|
$$ = PLPGSQL_GETDIAG_DATATYPE_NAME;
|
||||||
else if (tok_is_keyword(tok, &yylval,
|
else if (tok_is_keyword(tok, &yylval,
|
||||||
K_MESSAGE_TEXT, "message_text"))
|
K_MESSAGE_TEXT, "message_text"))
|
||||||
$$ = PLPGSQL_GETDIAG_MESSAGE_TEXT;
|
$$ = PLPGSQL_GETDIAG_MESSAGE_TEXT;
|
||||||
|
else if (tok_is_keyword(tok, &yylval,
|
||||||
|
K_TABLE_NAME, "table_name"))
|
||||||
|
$$ = PLPGSQL_GETDIAG_TABLE_NAME;
|
||||||
|
else if (tok_is_keyword(tok, &yylval,
|
||||||
|
K_SCHEMA_NAME, "schema_name"))
|
||||||
|
$$ = PLPGSQL_GETDIAG_SCHEMA_NAME;
|
||||||
else if (tok_is_keyword(tok, &yylval,
|
else if (tok_is_keyword(tok, &yylval,
|
||||||
K_RETURNED_SQLSTATE, "returned_sqlstate"))
|
K_RETURNED_SQLSTATE, "returned_sqlstate"))
|
||||||
$$ = PLPGSQL_GETDIAG_RETURNED_SQLSTATE;
|
$$ = PLPGSQL_GETDIAG_RETURNED_SQLSTATE;
|
||||||
@ -2231,9 +2261,14 @@ unreserved_keyword :
|
|||||||
| K_ALIAS
|
| K_ALIAS
|
||||||
| K_ARRAY
|
| K_ARRAY
|
||||||
| K_BACKWARD
|
| K_BACKWARD
|
||||||
|
| K_COLUMN
|
||||||
|
| K_COLUMN_NAME
|
||||||
| K_CONSTANT
|
| K_CONSTANT
|
||||||
|
| K_CONSTRAINT
|
||||||
|
| K_CONSTRAINT_NAME
|
||||||
| K_CURRENT
|
| K_CURRENT
|
||||||
| K_CURSOR
|
| K_CURSOR
|
||||||
|
| K_DATATYPE
|
||||||
| K_DEBUG
|
| K_DEBUG
|
||||||
| K_DETAIL
|
| K_DETAIL
|
||||||
| K_DUMP
|
| K_DUMP
|
||||||
@ -2252,6 +2287,7 @@ unreserved_keyword :
|
|||||||
| K_NO
|
| K_NO
|
||||||
| K_NOTICE
|
| K_NOTICE
|
||||||
| K_OPTION
|
| K_OPTION
|
||||||
|
| K_PG_DATATYPE_NAME
|
||||||
| K_PG_EXCEPTION_CONTEXT
|
| K_PG_EXCEPTION_CONTEXT
|
||||||
| K_PG_EXCEPTION_DETAIL
|
| K_PG_EXCEPTION_DETAIL
|
||||||
| K_PG_EXCEPTION_HINT
|
| K_PG_EXCEPTION_HINT
|
||||||
@ -2263,10 +2299,14 @@ unreserved_keyword :
|
|||||||
| K_REVERSE
|
| K_REVERSE
|
||||||
| K_ROW_COUNT
|
| K_ROW_COUNT
|
||||||
| K_ROWTYPE
|
| K_ROWTYPE
|
||||||
|
| K_SCHEMA
|
||||||
|
| K_SCHEMA_NAME
|
||||||
| K_SCROLL
|
| K_SCROLL
|
||||||
| K_SLICE
|
| K_SLICE
|
||||||
| K_SQLSTATE
|
| K_SQLSTATE
|
||||||
| K_STACKED
|
| K_STACKED
|
||||||
|
| K_TABLE
|
||||||
|
| K_TABLE_NAME
|
||||||
| K_TYPE
|
| K_TYPE
|
||||||
| K_USE_COLUMN
|
| K_USE_COLUMN
|
||||||
| K_USE_VARIABLE
|
| K_USE_VARIABLE
|
||||||
@ -3631,6 +3671,21 @@ read_raise_options(void)
|
|||||||
else if (tok_is_keyword(tok, &yylval,
|
else if (tok_is_keyword(tok, &yylval,
|
||||||
K_HINT, "hint"))
|
K_HINT, "hint"))
|
||||||
opt->opt_type = PLPGSQL_RAISEOPTION_HINT;
|
opt->opt_type = PLPGSQL_RAISEOPTION_HINT;
|
||||||
|
else if (tok_is_keyword(tok, &yylval,
|
||||||
|
K_COLUMN, "column"))
|
||||||
|
opt->opt_type = PLPGSQL_RAISEOPTION_COLUMN;
|
||||||
|
else if (tok_is_keyword(tok, &yylval,
|
||||||
|
K_CONSTRAINT, "constraint"))
|
||||||
|
opt->opt_type = PLPGSQL_RAISEOPTION_CONSTRAINT;
|
||||||
|
else if (tok_is_keyword(tok, &yylval,
|
||||||
|
K_DATATYPE, "datatype"))
|
||||||
|
opt->opt_type = PLPGSQL_RAISEOPTION_DATATYPE;
|
||||||
|
else if (tok_is_keyword(tok, &yylval,
|
||||||
|
K_TABLE, "table"))
|
||||||
|
opt->opt_type = PLPGSQL_RAISEOPTION_TABLE;
|
||||||
|
else if (tok_is_keyword(tok, &yylval,
|
||||||
|
K_SCHEMA, "schema"))
|
||||||
|
opt->opt_type = PLPGSQL_RAISEOPTION_SCHEMA;
|
||||||
else
|
else
|
||||||
yyerror("unrecognized RAISE statement option");
|
yyerror("unrecognized RAISE statement option");
|
||||||
|
|
||||||
|
@ -109,9 +109,14 @@ static const ScanKeyword unreserved_keywords[] = {
|
|||||||
PG_KEYWORD("alias", K_ALIAS, UNRESERVED_KEYWORD)
|
PG_KEYWORD("alias", K_ALIAS, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD)
|
PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD)
|
PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD)
|
||||||
|
PG_KEYWORD("column", K_COLUMN, UNRESERVED_KEYWORD)
|
||||||
|
PG_KEYWORD("column_name", K_COLUMN_NAME, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("constant", K_CONSTANT, UNRESERVED_KEYWORD)
|
PG_KEYWORD("constant", K_CONSTANT, UNRESERVED_KEYWORD)
|
||||||
|
PG_KEYWORD("constraint", K_CONSTRAINT, UNRESERVED_KEYWORD)
|
||||||
|
PG_KEYWORD("constraint_name", K_CONSTRAINT_NAME, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("current", K_CURRENT, UNRESERVED_KEYWORD)
|
PG_KEYWORD("current", K_CURRENT, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("cursor", K_CURSOR, UNRESERVED_KEYWORD)
|
PG_KEYWORD("cursor", K_CURSOR, UNRESERVED_KEYWORD)
|
||||||
|
PG_KEYWORD("datatype", K_DATATYPE, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("debug", K_DEBUG, UNRESERVED_KEYWORD)
|
PG_KEYWORD("debug", K_DEBUG, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("detail", K_DETAIL, UNRESERVED_KEYWORD)
|
PG_KEYWORD("detail", K_DETAIL, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("dump", K_DUMP, UNRESERVED_KEYWORD)
|
PG_KEYWORD("dump", K_DUMP, UNRESERVED_KEYWORD)
|
||||||
@ -130,6 +135,7 @@ static const ScanKeyword unreserved_keywords[] = {
|
|||||||
PG_KEYWORD("no", K_NO, UNRESERVED_KEYWORD)
|
PG_KEYWORD("no", K_NO, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("notice", K_NOTICE, UNRESERVED_KEYWORD)
|
PG_KEYWORD("notice", K_NOTICE, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("option", K_OPTION, UNRESERVED_KEYWORD)
|
PG_KEYWORD("option", K_OPTION, UNRESERVED_KEYWORD)
|
||||||
|
PG_KEYWORD("pg_datatype_name", K_PG_DATATYPE_NAME, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("pg_exception_context", K_PG_EXCEPTION_CONTEXT, UNRESERVED_KEYWORD)
|
PG_KEYWORD("pg_exception_context", K_PG_EXCEPTION_CONTEXT, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("pg_exception_detail", K_PG_EXCEPTION_DETAIL, UNRESERVED_KEYWORD)
|
PG_KEYWORD("pg_exception_detail", K_PG_EXCEPTION_DETAIL, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("pg_exception_hint", K_PG_EXCEPTION_HINT, UNRESERVED_KEYWORD)
|
PG_KEYWORD("pg_exception_hint", K_PG_EXCEPTION_HINT, UNRESERVED_KEYWORD)
|
||||||
@ -141,10 +147,14 @@ static const ScanKeyword unreserved_keywords[] = {
|
|||||||
PG_KEYWORD("reverse", K_REVERSE, UNRESERVED_KEYWORD)
|
PG_KEYWORD("reverse", K_REVERSE, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("row_count", K_ROW_COUNT, UNRESERVED_KEYWORD)
|
PG_KEYWORD("row_count", K_ROW_COUNT, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("rowtype", K_ROWTYPE, UNRESERVED_KEYWORD)
|
PG_KEYWORD("rowtype", K_ROWTYPE, UNRESERVED_KEYWORD)
|
||||||
|
PG_KEYWORD("schema", K_SCHEMA, UNRESERVED_KEYWORD)
|
||||||
|
PG_KEYWORD("schema_name", K_SCHEMA_NAME, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("scroll", K_SCROLL, UNRESERVED_KEYWORD)
|
PG_KEYWORD("scroll", K_SCROLL, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("slice", K_SLICE, UNRESERVED_KEYWORD)
|
PG_KEYWORD("slice", K_SLICE, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("sqlstate", K_SQLSTATE, UNRESERVED_KEYWORD)
|
PG_KEYWORD("sqlstate", K_SQLSTATE, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("stacked", K_STACKED, UNRESERVED_KEYWORD)
|
PG_KEYWORD("stacked", K_STACKED, UNRESERVED_KEYWORD)
|
||||||
|
PG_KEYWORD("table", K_TABLE, UNRESERVED_KEYWORD)
|
||||||
|
PG_KEYWORD("table_name", K_TABLE_NAME, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("type", K_TYPE, UNRESERVED_KEYWORD)
|
PG_KEYWORD("type", K_TYPE, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("use_column", K_USE_COLUMN, UNRESERVED_KEYWORD)
|
PG_KEYWORD("use_column", K_USE_COLUMN, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("use_variable", K_USE_VARIABLE, UNRESERVED_KEYWORD)
|
PG_KEYWORD("use_variable", K_USE_VARIABLE, UNRESERVED_KEYWORD)
|
||||||
|
@ -128,7 +128,12 @@ enum
|
|||||||
PLPGSQL_GETDIAG_ERROR_DETAIL,
|
PLPGSQL_GETDIAG_ERROR_DETAIL,
|
||||||
PLPGSQL_GETDIAG_ERROR_HINT,
|
PLPGSQL_GETDIAG_ERROR_HINT,
|
||||||
PLPGSQL_GETDIAG_RETURNED_SQLSTATE,
|
PLPGSQL_GETDIAG_RETURNED_SQLSTATE,
|
||||||
PLPGSQL_GETDIAG_MESSAGE_TEXT
|
PLPGSQL_GETDIAG_COLUMN_NAME,
|
||||||
|
PLPGSQL_GETDIAG_CONSTRAINT_NAME,
|
||||||
|
PLPGSQL_GETDIAG_DATATYPE_NAME,
|
||||||
|
PLPGSQL_GETDIAG_MESSAGE_TEXT,
|
||||||
|
PLPGSQL_GETDIAG_TABLE_NAME,
|
||||||
|
PLPGSQL_GETDIAG_SCHEMA_NAME
|
||||||
};
|
};
|
||||||
|
|
||||||
/* --------
|
/* --------
|
||||||
@ -140,7 +145,12 @@ enum
|
|||||||
PLPGSQL_RAISEOPTION_ERRCODE,
|
PLPGSQL_RAISEOPTION_ERRCODE,
|
||||||
PLPGSQL_RAISEOPTION_MESSAGE,
|
PLPGSQL_RAISEOPTION_MESSAGE,
|
||||||
PLPGSQL_RAISEOPTION_DETAIL,
|
PLPGSQL_RAISEOPTION_DETAIL,
|
||||||
PLPGSQL_RAISEOPTION_HINT
|
PLPGSQL_RAISEOPTION_HINT,
|
||||||
|
PLPGSQL_RAISEOPTION_COLUMN,
|
||||||
|
PLPGSQL_RAISEOPTION_CONSTRAINT,
|
||||||
|
PLPGSQL_RAISEOPTION_DATATYPE,
|
||||||
|
PLPGSQL_RAISEOPTION_TABLE,
|
||||||
|
PLPGSQL_RAISEOPTION_SCHEMA
|
||||||
};
|
};
|
||||||
|
|
||||||
/* --------
|
/* --------
|
||||||
|
@ -3974,6 +3974,40 @@ select raise_test();
|
|||||||
NOTICE: 22012
|
NOTICE: 22012
|
||||||
ERROR: substitute message
|
ERROR: substitute message
|
||||||
drop function raise_test();
|
drop function raise_test();
|
||||||
|
-- test passing column_name, constraint_name, datatype_name, table_name
|
||||||
|
-- and schema_name error fields
|
||||||
|
create or replace function stacked_diagnostics_test() returns void as $$
|
||||||
|
declare _column_name text;
|
||||||
|
_constraint_name text;
|
||||||
|
_datatype_name text;
|
||||||
|
_table_name text;
|
||||||
|
_schema_name text;
|
||||||
|
begin
|
||||||
|
raise exception using
|
||||||
|
column = '>>some column name<<',
|
||||||
|
constraint = '>>some constraint name<<',
|
||||||
|
datatype = '>>some datatype name<<',
|
||||||
|
table = '>>some table name<<',
|
||||||
|
schema = '>>some schema name<<';
|
||||||
|
exception when others then
|
||||||
|
get stacked diagnostics
|
||||||
|
_column_name = column_name,
|
||||||
|
_constraint_name = constraint_name,
|
||||||
|
_datatype_name = pg_datatype_name,
|
||||||
|
_table_name = table_name,
|
||||||
|
_schema_name = schema_name;
|
||||||
|
raise notice 'column %, constraint %, type %, table %, schema %',
|
||||||
|
_column_name, _constraint_name, _datatype_name, _table_name, _schema_name;
|
||||||
|
end;
|
||||||
|
$$ language plpgsql;
|
||||||
|
select stacked_diagnostics_test();
|
||||||
|
NOTICE: column >>some column name<<, constraint >>some constraint name<<, type >>some datatype name<<, table >>some table name<<, schema >>some schema name<<
|
||||||
|
stacked_diagnostics_test
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
drop function stacked_diagnostics_test();
|
||||||
-- test CASE statement
|
-- test CASE statement
|
||||||
create or replace function case_test(bigint) returns text as $$
|
create or replace function case_test(bigint) returns text as $$
|
||||||
declare a int = 10;
|
declare a int = 10;
|
||||||
|
@ -3262,6 +3262,38 @@ select raise_test();
|
|||||||
|
|
||||||
drop function raise_test();
|
drop function raise_test();
|
||||||
|
|
||||||
|
-- test passing column_name, constraint_name, datatype_name, table_name
|
||||||
|
-- and schema_name error fields
|
||||||
|
|
||||||
|
create or replace function stacked_diagnostics_test() returns void as $$
|
||||||
|
declare _column_name text;
|
||||||
|
_constraint_name text;
|
||||||
|
_datatype_name text;
|
||||||
|
_table_name text;
|
||||||
|
_schema_name text;
|
||||||
|
begin
|
||||||
|
raise exception using
|
||||||
|
column = '>>some column name<<',
|
||||||
|
constraint = '>>some constraint name<<',
|
||||||
|
datatype = '>>some datatype name<<',
|
||||||
|
table = '>>some table name<<',
|
||||||
|
schema = '>>some schema name<<';
|
||||||
|
exception when others then
|
||||||
|
get stacked diagnostics
|
||||||
|
_column_name = column_name,
|
||||||
|
_constraint_name = constraint_name,
|
||||||
|
_datatype_name = pg_datatype_name,
|
||||||
|
_table_name = table_name,
|
||||||
|
_schema_name = schema_name;
|
||||||
|
raise notice 'column %, constraint %, type %, table %, schema %',
|
||||||
|
_column_name, _constraint_name, _datatype_name, _table_name, _schema_name;
|
||||||
|
end;
|
||||||
|
$$ language plpgsql;
|
||||||
|
|
||||||
|
select stacked_diagnostics_test();
|
||||||
|
|
||||||
|
drop function stacked_diagnostics_test();
|
||||||
|
|
||||||
-- test CASE statement
|
-- test CASE statement
|
||||||
|
|
||||||
create or replace function case_test(bigint) returns text as $$
|
create or replace function case_test(bigint) returns text as $$
|
||||||
|
Loading…
x
Reference in New Issue
Block a user