diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml new file mode 100644 index b3b78d2..b7a2cc2 *** a/doc/src/sgml/func.sgml --- b/doc/src/sgml/func.sgml *************** postgres=# SELECT * FROM pg_xlogfile_nam *** 17925,17930 **** --- 17925,17939 ---- Return information about a file. + + + pg_report_log(logleveltext, message anyelement[, ishidestmt boolean ] [, detail text] [, hint text] [, sqlstate text]) + + void + + Report message or error. + + *************** SELECT (pg_stat_file('filename')).modifi *** 17993,17998 **** --- 18002,18033 ---- + + pg_report_log + + + pg_report_log is useful to write custom messages + or raise exception. This function don't support the PANIC, FATAL + log levels due to their unique internal DB usage, which may cause + the database instability. Using ishidestmt which default values + is true, function can write or ignore the current SQL statement + into log destination. Also, we can have DETAIL, HINT log messages + by provding detail, hint as function + arguments, which are NULL by default. The parameter sqlstate + allows to set a SQLSTATE of raised exception. Default value of this + parameter is 'P0001' for ERROR only level. + + Typical usages include: + + postgres=# SELECT pg_report_log('NOTICE', 'Custom Message', true); + NOTICE: Custom Message + pg_report_log + --------------- + + (1 row) + + + diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql new file mode 100644 index ccc030f..7e551f2 *** a/src/backend/catalog/system_views.sql --- b/src/backend/catalog/system_views.sql *************** RETURNS jsonb *** 940,942 **** --- 940,961 ---- LANGUAGE INTERNAL STRICT IMMUTABLE AS 'jsonb_set'; + + CREATE OR REPLACE FUNCTION pg_report_log(loglevel text, message text, + ishidestmt boolean DEFAULT true, detail text DEFAULT NULL, + hint text DEFAULT NULL, sqlstate text DEFAULT 'P0001') + RETURNS VOID + LANGUAGE INTERNAL + VOLATILE + AS 'pg_report_log'; + + CREATE OR REPLACE FUNCTION pg_report_log(loglevel text, message anyelement, + ishidestmt boolean DEFAULT true, detail text DEFAULT NULL, + hint text DEFAULT NULL, sqlstate text DEFAULT 'P0001') + RETURNS VOID + VOLATILE + AS + $$ + SELECT pg_report_log($1::pg_catalog.text, $2::pg_catalog.text, $3, $4, $5, $6) + $$ + LANGUAGE SQL; diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c new file mode 100644 index c0495d9..b1275bd *** a/src/backend/utils/adt/misc.c --- b/src/backend/utils/adt/misc.c *************** current_query(PG_FUNCTION_ARGS) *** 75,80 **** --- 75,212 ---- PG_RETURN_NULL(); } + + /* + * Parsing error levels + */ + typedef struct + { + char *err_level; + int ival; + } error_levels; + + /* + * Translate text based elog level to integer value. + * + * Returns true, when it found known elog elevel else + * returns false; + */ + static bool + parse_error_level(const char* err_level, int *ival) + { + error_levels err_levels[]={ + {"DEBUG5", DEBUG5}, + {"DEBUG4", DEBUG4}, + {"DEBUG3", DEBUG3}, + {"DEBUG2", DEBUG2}, + {"DEBUG1", DEBUG1}, + {"LOG", LOG}, + {"INFO", INFO}, + {"NOTICE", NOTICE}, + {"WARNING", WARNING}, + {"ERROR", ERROR}, + /* + * Adding PGERROR to elevels if WIN32 + */ + #ifdef WIN32 + {"PGERROR", PGERROR}, + #endif + {NULL, 0} + }; + + error_levels *current; + + for (current = err_levels; current->err_level != NULL; current++) + { + if (pg_strcasecmp(current->err_level, err_level) == 0) + { + *ival = current->ival; + + return true; + } + } + + return false; + } + + /* + * pg_report_log() + * + * Printing custom log messages in log file. + */ + Datum + pg_report_log(PG_FUNCTION_ARGS) + { + int elog_level; + char *elog_level_str; + int sqlstate = 0; + char *sqlstate_str; + bool ishidestmt = false; + char *err_message = NULL; + char *err_detail = NULL; + char *err_hint = NULL; + + /* log level */ + if (PG_ARGISNULL(0)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("log level must not be null"))); + + elog_level_str = text_to_cstring(PG_GETARG_TEXT_PP(0)); + if (!parse_error_level(elog_level_str, &elog_level)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid or disallowed log level: \'%s\'", elog_level_str))); + + /* message */ + if (PG_ARGISNULL(1)) + err_message = "The message is null"; + else + err_message = text_to_cstring(PG_GETARG_TEXT_PP(1)); + + /* ishidestmt */ + if (!PG_ARGISNULL(2)) + ishidestmt = PG_GETARG_BOOL(2); + + /* detail */ + if (!PG_ARGISNULL(3)) + err_detail = text_to_cstring(PG_GETARG_TEXT_PP(3)); + + /* hint */ + if (!PG_ARGISNULL(4)) + err_hint = text_to_cstring(PG_GETARG_TEXT_PP(4)); + + /* sqlstate */ + if (!PG_ARGISNULL(5)) + { + sqlstate_str = text_to_cstring(PG_GETARG_TEXT_PP(5)); + if (strlen(sqlstate_str) != 5 || + strspn(sqlstate_str, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid SQLSTATE code: \'%s\'", sqlstate_str))); + + sqlstate = MAKE_SQLSTATE(sqlstate_str[0], + sqlstate_str[1], + sqlstate_str[2], + sqlstate_str[3], + sqlstate_str[4]); + } + else if (elog_level >= ERROR) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("sqlstate must not be null when loglevel is ERROR"))); + + ereport(elog_level, + ((sqlstate != 0) ? errcode(sqlstate) : 0, + errmsg_internal("%s", err_message), + (err_detail != NULL) ? errdetail_internal("%s", err_detail) : 0, + (err_hint != NULL) ? errhint("%s", err_hint) : 0, + errhidestmt(ishidestmt))); + + PG_RETURN_VOID(); + } + /* * Send a signal to another backend. * diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h new file mode 100644 index ddf7c67..d82db09 *** a/src/include/catalog/pg_proc.h --- b/src/include/catalog/pg_proc.h *************** DESCR("row security for current context *** 5349,5354 **** --- 5349,5361 ---- DATA(insert OID = 3299 ( row_security_active PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 16 "25" _null_ _null_ _null_ _null_ _null_ row_security_active_name _null_ _null_ _null_ )); DESCR("row security for current context active on table by table name"); + /* Logging function */ + + DATA(insert OID = 6015 ( pg_report_log PGNSP PGUID 12 1 0 0 0 f f f f f f v 6 0 2278 "25 25 16 25 25 25" _null_ _null_ "{loglevel, message, ishidestmt, detail, hint, sqlstate}" _null_ _null_ pg_report_log _null_ _null_ _null_ )); + DESCR("write message to log file"); + DATA(insert OID = 6016 ( pg_report_log PGNSP PGUID 14 1 0 0 0 f f f f f f v 6 0 2278 "25 2283 16 25 25 25" _null_ _null_ "{loglevel, message, ishidestmt, detail, hint, sqlstate}" _null_ _null_ "SELECT pg_report_log($1, $2::pg_catalog.text, $3, $4, $5, $6)" _null_ _null_ _null_ )); + DESCR("write message to log file"); + /* * Symbolic values for proargmodes column. Note that these must agree with * the FunctionParameterMode enum in parsenodes.h; we declare them here to diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h new file mode 100644 index fc1679e..0dd1425 *** a/src/include/utils/builtins.h --- b/src/include/utils/builtins.h *************** extern Datum pg_typeof(PG_FUNCTION_ARGS) *** 495,500 **** --- 495,501 ---- extern Datum pg_collation_for(PG_FUNCTION_ARGS); extern Datum pg_relation_is_updatable(PG_FUNCTION_ARGS); extern Datum pg_column_is_updatable(PG_FUNCTION_ARGS); + extern Datum pg_report_log(PG_FUNCTION_ARGS); /* oid.c */ extern Datum oidin(PG_FUNCTION_ARGS); diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h new file mode 100644 index 7684717..4ec2eb1 *** a/src/include/utils/elog.h --- b/src/include/utils/elog.h *************** *** 16,21 **** --- 16,28 ---- #include + /* + * XXX + * If you are adding another elevel, make sure you update the + * parse_error_level() in src/backend/utils/adt/misc.c, with the + * new elevel + */ + /* Error level codes */ #define DEBUG5 10 /* Debugging messages, in categories of * decreasing detail. */ diff --git a/src/test/regress/expected/reportlog.out b/src/test/regress/expected/reportlog.out new file mode 100644 index ...9d1a7bc *** a/src/test/regress/expected/reportlog.out --- b/src/test/regress/expected/reportlog.out *************** *** 0 **** --- 1,104 ---- + -- + -- Test for Report Log With WARNING + -- + SELECT pg_catalog.pg_report_log('WARNING', 'Custom Message'); --OK + WARNING: Custom Message + pg_report_log + --------------- + + (1 row) + + -- + -- Test for ERROR with default ishidestmt + -- + SELECT pg_catalog.pg_report_log('ERROR', 'Custom Message'); --ERROR + ERROR: Custom Message + -- + -- Test for ERROR with ishidestmt + -- + SELECT pg_catalog.pg_report_log('ERROR', 'Custom Message', true); --ERROR + ERROR: Custom Message + -- + -- Test for anyelement + -- + SELECT pg_catalog.pg_report_log('WARNING', -1234.34); --OK + WARNING: -1234.34 + pg_report_log + --------------- + + (1 row) + + -- + -- Test for denial of FATAL + -- + SELECT pg_catalog.pg_report_log('FATAL', 'Fatal Message'); -- ERROR + ERROR: invalid or disallowed log level: 'FATAL' + -- + -- Test for optional arguements + -- + SELECT pg_catalog.pg_report_log('WARNING', 'Warning Message', true, 'WARNING DETAIL'); --OK + WARNING: Warning Message + DETAIL: WARNING DETAIL + pg_report_log + --------------- + + (1 row) + + -- + -- Test for NULL log level + -- + SELECT pg_catalog.pg_report_log(NULL, NULL); --ERROR + ERROR: log level must not be null + -- + -- Test for NULL Message + -- + SELECT pg_catalog.pg_report_log('NOTICE', NULL); --OK + NOTICE: The message is null + pg_report_log + --------------- + + (1 row) + + -- + -- Test for SQLSTATE. The below test should print P0001, + -- which is an error code for ERRCODE_RAISE_EXCEPTION + -- + DO $$ + BEGIN + BEGIN + PERFORM pg_catalog.pg_report_log('ERROR', NULL, NULL, NULL, NULL); + EXCEPTION WHEN SQLSTATE 'P0001' THEN + RAISE NOTICE 'handled exception with SQLSTATE: %', SQLSTATE; + END; + + BEGIN + PERFORM pg_catalog.pg_report_log('ERROR', 'custom error', sqlstate => 'P1234'); + EXCEPTION WHEN SQLSTATE 'P1234' THEN + RAISE NOTICE 'handled exception with SQLSTATE: %', SQLSTATE; + END; + + END $$; + NOTICE: handled exception with SQLSTATE: P0001 + NOTICE: handled exception with SQLSTATE: P1234 + -- + -- Test for all NULL inputs, except elevel + -- + SELECT pg_catalog.pg_report_log('WARNING', NULL, NULL, NULL, NULL, NULL); --OK + WARNING: The message is null + pg_report_log + --------------- + + (1 row) + + -- + -- Test for all NOT NULL arguments + -- + SELECT pg_catalog.pg_report_log('WARNING', 'Warning Message', true, 'some detail', 'some hint', 'P1234'); --OK + WARNING: Warning Message + DETAIL: some detail + HINT: some hint + pg_report_log + --------------- + + (1 row) + diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule new file mode 100644 index 6fc5d1e..4cd193d *** a/src/test/regress/parallel_schedule --- b/src/test/regress/parallel_schedule *************** test: rules *** 97,103 **** # ---------- # Another group of parallel tests # ---------- ! test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb indirect_toast equivclass # ---------- # Another group of parallel tests # NB: temp.sql does a reconnect which transiently uses 2 connections, --- 97,103 ---- # ---------- # Another group of parallel tests # ---------- ! test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb indirect_toast reportlog equivclass # ---------- # Another group of parallel tests # NB: temp.sql does a reconnect which transiently uses 2 connections, diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule new file mode 100644 index 2ae51cf..6c9f5c3 *** a/src/test/regress/serial_schedule --- b/src/test/regress/serial_schedule *************** test: with *** 158,160 **** --- 158,161 ---- test: xml test: event_trigger test: stats + test: reportlog diff --git a/src/test/regress/sql/reportlog.sql b/src/test/regress/sql/reportlog.sql new file mode 100644 index ...2ac4bd5 *** a/src/test/regress/sql/reportlog.sql --- b/src/test/regress/sql/reportlog.sql *************** *** 0 **** --- 1,70 ---- + -- + -- Test for Report Log With WARNING + -- + SELECT pg_catalog.pg_report_log('WARNING', 'Custom Message'); --OK + + -- + -- Test for ERROR with default ishidestmt + -- + SELECT pg_catalog.pg_report_log('ERROR', 'Custom Message'); --ERROR + + -- + -- Test for ERROR with ishidestmt + -- + SELECT pg_catalog.pg_report_log('ERROR', 'Custom Message', true); --ERROR + + -- + -- Test for anyelement + -- + SELECT pg_catalog.pg_report_log('WARNING', -1234.34); --OK + + -- + -- Test for denial of FATAL + -- + SELECT pg_catalog.pg_report_log('FATAL', 'Fatal Message'); -- ERROR + + -- + -- Test for optional arguements + -- + SELECT pg_catalog.pg_report_log('WARNING', 'Warning Message', true, 'WARNING DETAIL'); --OK + + -- + -- Test for NULL log level + -- + SELECT pg_catalog.pg_report_log(NULL, NULL); --ERROR + + + -- + -- Test for NULL Message + -- + SELECT pg_catalog.pg_report_log('NOTICE', NULL); --OK + + -- + -- Test for SQLSTATE. The below test should print P0001, + -- which is an error code for ERRCODE_RAISE_EXCEPTION + -- + DO $$ + BEGIN + BEGIN + PERFORM pg_catalog.pg_report_log('ERROR', NULL, NULL, NULL, NULL); + EXCEPTION WHEN SQLSTATE 'P0001' THEN + RAISE NOTICE 'handled exception with SQLSTATE: %', SQLSTATE; + END; + + BEGIN + PERFORM pg_catalog.pg_report_log('ERROR', 'custom error', sqlstate => 'P1234'); + EXCEPTION WHEN SQLSTATE 'P1234' THEN + RAISE NOTICE 'handled exception with SQLSTATE: %', SQLSTATE; + END; + + END $$; + + -- + -- Test for all NULL inputs, except elevel + -- + SELECT pg_catalog.pg_report_log('WARNING', NULL, NULL, NULL, NULL, NULL); --OK + + -- + -- Test for all NOT NULL arguments + -- + SELECT pg_catalog.pg_report_log('WARNING', 'Warning Message', true, 'some detail', 'some hint', 'P1234'); --OK