Skip to content

Commit

Permalink
Merge #106351
Browse files Browse the repository at this point in the history
106351: plpgsql: implement RAISE statements r=DrewKimball a=DrewKimball

#### builtins: add builtin function to raise a notice synchronously to the client

This patch adds a builtin function, `crdb_internal.plpgsql_raise`, which allows
the caller to send a notice to the client with specified severity, message,
detail, hint, and PG code. The notice is immediately flushed to the client
instead of being buffered until the query result is closed. This functionality
will be used to implement the PLpgSQL `RAISE` statement.

The `crdb_internal.plpgsql_raise` builtin is undocumented and intended only
for internal use.

#### plpgsql: implement parsing for RAISE statements

This patch adds parser support for PLpgSQL `RAISE` statements. This ncludes
all syntax forms apart from the empty `RAISE`, which is only valid in
combination with (currently unimplemented) `EXCEPTION` blocks. A future
commit will add support in the optbuilder as well.

#### plpgsql: implement RAISE statement

This patch adds support for the PLpgSQL `RAISE` statement. The `RAISE`
statement can send messages back to the client during execution, as well
as raise a user-specified error. There are a few variations on the syntax,
but in general `RAISE` statements have a log level (default `EXCEPTION`),
a message (if not specified, the code string is used), and various options:
`DETAIL`, `HINT`, `ERRCODE` etc.

With log level `EXCEPTION` the error is returned just like any other error,
but for other levels it is sent as a notice to the client and flushed
synchronously before execution continues. This feature is often used to
track progress, since the notices are sent before execution finishes.

Fixes #105251

Release note (sql change): Added support for the PLpgSQL `RAISE` statement,
which allows sending notices to the client and raising errors. Currently the
notice is only sent to the client; support for logging notices is left for
future work.

Co-authored-by: Drew Kimball <drewk@cockroachlabs.com>
  • Loading branch information
craig[bot] and DrewKimball committed Jul 21, 2023
2 parents 4af7886 + 6de6415 commit acdb0c7
Show file tree
Hide file tree
Showing 33 changed files with 2,971 additions and 91 deletions.
7 changes: 7 additions & 0 deletions pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions pkg/sql/conn_io.go
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,9 @@ type RestrictedCommandResult interface {
// This gets flushed only when the CommandResult is closed.
BufferNotice(notice pgnotice.Notice)

// SendNotice immediately flushes a notice to the client.
SendNotice(ctx context.Context, notice pgnotice.Notice) error

// SetColumns informs the client about the schema of the result. The columns
// can be nil.
//
Expand Down Expand Up @@ -1079,6 +1082,12 @@ func (r *streamingCommandResult) BufferNotice(notice pgnotice.Notice) {
// Unimplemented: the internal executor does not support notices.
}

// SendNotice is part of the RestrictedCommandResult interface.
func (r *streamingCommandResult) SendNotice(ctx context.Context, notice pgnotice.Notice) error {
// Unimplemented: the internal executor does not support notices.
return nil
}

// ResetStmtType is part of the RestrictedCommandResult interface.
func (r *streamingCommandResult) ResetStmtType(stmt tree.Statement) {
panic("unimplemented")
Expand Down
5 changes: 5 additions & 0 deletions pkg/sql/faketreeeval/evalctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,11 @@ var _ eval.ClientNoticeSender = &DummyClientNoticeSender{}
// BufferClientNotice is part of the eval.ClientNoticeSender interface.
func (c *DummyClientNoticeSender) BufferClientNotice(context.Context, pgnotice.Notice) {}

// SendClientNotice is part of the eval.ClientNoticeSender interface.
func (c *DummyClientNoticeSender) SendClientNotice(context.Context, pgnotice.Notice) error {
return nil
}

// DummyTenantOperator implements the tree.TenantOperator interface.
type DummyTenantOperator struct{}

Expand Down
3 changes: 3 additions & 0 deletions pkg/sql/logictest/logic.go
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,9 @@ func (t *logicTest) openDB(pgURL url.URL) *gosql.DB {
if notice.Hint != "" {
t.noticeBuffer = append(t.noticeBuffer, "HINT: "+notice.Hint)
}
if notice.Code != "" && notice.Code != "00000" {
t.noticeBuffer = append(t.noticeBuffer, "SQLSTATE: "+string(notice.Code))
}
})

return gosql.OpenDB(connector)
Expand Down
3 changes: 2 additions & 1 deletion pkg/sql/logictest/testdata/logic_test/notice
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,5 @@ UNLISTEN temp
----
NOTICE: unimplemented: CRDB does not support LISTEN, making UNLISTEN a no-op
HINT: You have attempted to use a feature that is not yet implemented.
See: https://go.crdb.dev/issue-v/41522/*
See: https://go.crdb.dev/issue-v/41522/v23.2
SQLSTATE: 0A000
166 changes: 166 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/raise
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Test different log levels.
query T noticetrace
SELECT crdb_internal.plpgsql_raise('DEBUG1', 'foo', '', '', '');
----

query T noticetrace
SELECT crdb_internal.plpgsql_raise('LOG', 'foo', '', '', '');
----

query T noticetrace
SELECT crdb_internal.plpgsql_raise('INFO', 'foo', '', '', '');
----
INFO: foo
SQLSTATE: XXUUU

query T noticetrace
SELECT crdb_internal.plpgsql_raise('NOTICE', 'foo', '', '', '');
----
NOTICE: foo
SQLSTATE: XXUUU

query T noticetrace
SELECT crdb_internal.plpgsql_raise('WARNING', 'foo', '', '', '');
----
WARNING: foo
SQLSTATE: XXUUU

statement ok
SET client_min_messages = 'debug1';

query T noticetrace
SELECT crdb_internal.plpgsql_raise('DEBUG1', 'foo', '', '', '');
----
DEBUG1: foo
SQLSTATE: XXUUU

query T noticetrace
SELECT crdb_internal.plpgsql_raise('LOG', 'foo', '', '', '');
----
LOG: foo
SQLSTATE: XXUUU

query T noticetrace
SELECT crdb_internal.plpgsql_raise('INFO', 'foo', '', '', '');
----
INFO: foo
SQLSTATE: XXUUU

query T noticetrace
SELECT crdb_internal.plpgsql_raise('NOTICE', 'foo', '', '', '');
----
NOTICE: foo
SQLSTATE: XXUUU

query T noticetrace
SELECT crdb_internal.plpgsql_raise('WARNING', 'foo', '', '', '');
----
WARNING: foo
SQLSTATE: XXUUU

statement ok
SET client_min_messages = 'WARNING';

query T noticetrace
SELECT crdb_internal.plpgsql_raise('DEBUG1', 'foo', '', '', '');
----

query T noticetrace
SELECT crdb_internal.plpgsql_raise('LOG', 'foo', '', '', '');
----

# INFO-level notices are always sent to the client.
query T noticetrace
SELECT crdb_internal.plpgsql_raise('INFO', 'foo', '', '', '');
----
INFO: foo
SQLSTATE: XXUUU

query T noticetrace
SELECT crdb_internal.plpgsql_raise('NOTICE', 'foo', '', '', '');
----

query T noticetrace
SELECT crdb_internal.plpgsql_raise('WARNING', 'foo', '', '', '');
----
WARNING: foo
SQLSTATE: XXUUU

statement ok
RESET client_min_messages;

# Test RAISE options.
query T noticetrace
SELECT crdb_internal.plpgsql_raise('NOTICE', 'bar', 'this is a detail', '', '');
----
NOTICE: bar
DETAIL: this is a detail
SQLSTATE: XXUUU

query T noticetrace
SELECT crdb_internal.plpgsql_raise('NOTICE', 'baz', '', 'this is a hint', '');
----
NOTICE: baz
HINT: this is a hint
SQLSTATE: XXUUU

query T noticetrace
SELECT crdb_internal.plpgsql_raise('NOTICE', 'division by zero', '', '', '22012');
----
NOTICE: division by zero
SQLSTATE: 22012

query T noticetrace
SELECT crdb_internal.plpgsql_raise('WARNING', 'invalid password', '', '', '28P01');
----
WARNING: invalid password
SQLSTATE: 28P01

query T noticetrace
SELECT crdb_internal.plpgsql_raise('NOTICE', 'this is a message', 'this is a detail', 'this is a hint', 'P0001');
----
NOTICE: this is a message
DETAIL: this is a detail
HINT: this is a hint
SQLSTATE: P0001

query T noticetrace
SELECT crdb_internal.plpgsql_raise('NOTICE', 'division by zero msg', '', '', 'division_by_zero');
----
NOTICE: division by zero msg
SQLSTATE: 22012

query T noticetrace
SELECT crdb_internal.plpgsql_raise('NOTICE', '', 'message is empty', '', 'P0001');
----
NOTICE:
DETAIL: message is empty
SQLSTATE: P0001

query T noticetrace
SELECT crdb_internal.plpgsql_raise('NOTICE', '', '', '', '');
----
NOTICE:
SQLSTATE: XXUUU

query error pgcode 42704 pq: unrecognized exception condition: \"this_is_not_valid\"
SELECT crdb_internal.plpgsql_raise('NOTICE', '', '', '', 'this_is_not_valid');

query error pgcode 42704 pq: unrecognized exception condition: \"-50\"
SELECT crdb_internal.plpgsql_raise('NOTICE', '', '', '', '-50');

query error pgcode 22023 pq: severity NOTE is invalid
SELECT crdb_internal.plpgsql_raise('NOTE', '', '', '', '-50');

# Test severity ERROR.
query error pgcode XXUUU pq: foo
SELECT crdb_internal.plpgsql_raise('ERROR', 'foo', '', '', '');

query error pgcode 12345 pq: foo
SELECT crdb_internal.plpgsql_raise('ERROR', 'foo', '', '', '12345');

query error pgcode 12345 pq: msg\nHINT: hint\nDETAIL: detail
SELECT crdb_internal.plpgsql_raise('ERROR', 'msg', 'detail', 'hint', '12345');

query error pgcode XXUUU pq:
SELECT crdb_internal.plpgsql_raise('ERROR', '', '', '', '');
Loading

0 comments on commit acdb0c7

Please sign in to comment.