blob: 288ca591661d76555f76ec0763c4fff09392db49 [file] [log] [blame]
#include "postgres.h"
#include "funcapi.h"
#include "access/heapam.h"
#include "catalog/pg_type.h"
#include "lib/stringinfo.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "utils/memutils.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "orafunc.h"
#include "builtins.h"
extern PGDLLIMPORT ProtocolVersion FrontendProtocol;
/*
* TODO: BUFSIZE_UNLIMITED to be truely unlimited (or INT_MAX),
* and allocate buffers on-demand.
*/
#define BUFSIZE_DEFAULT 20000
#define BUFSIZE_MIN 2000
#define BUFSIZE_MAX 1000000
#define BUFSIZE_UNLIMITED BUFSIZE_MAX
static bool is_server_output = false;
static char *buffer = NULL;
static int buffer_size = 0; /* allocated bytes in buffer */
static int buffer_len = 0; /* used bytes in buffer */
static int buffer_get = 0; /* retrieved bytes in buffer */
static void add_str(const char *str, int len);
static void add_text(text *str);
static void add_newline(void);
static void send_buffer(void);
/*
* Aux. buffer functionality
*/
static void
add_str(const char *str, int len)
{
/* Discard all buffers if get_line was called. */
if (buffer_get > 0)
{
buffer_get = 0;
buffer_len = 0;
}
if (buffer_len + len > buffer_size)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
errmsg("buffer overflow"),
errdetail("Buffer overflow, limit of %d bytes", buffer_size),
errhint("Increase buffer size in dbms_output.enable() next time")));
memcpy(buffer + buffer_len, str, len);
buffer_len += len;
buffer[buffer_len] = '\0';
}
static void
add_text(text *str)
{
add_str(VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str));
}
static void
add_newline(void)
{
add_str("", 1); /* add \0 */
if (is_server_output)
send_buffer();
}
static void
send_buffer()
{
if (buffer_len > 0)
{
StringInfoData msgbuf;
char *cursor = buffer;
while (--buffer_len > 0)
{
if (*cursor == '\0')
*cursor = '\n';
cursor++;
}
if (*cursor != '\0')
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("internal error"),
errdetail("Wrong message format detected")));
pq_beginmessage(&msgbuf, 'N');
if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
{
pq_sendbyte(&msgbuf, PG_DIAG_MESSAGE_PRIMARY);
pq_sendstring(&msgbuf, buffer);
pq_sendbyte(&msgbuf, '\0');
}
else
{
*cursor++ = '\n';
*cursor = '\0';
pq_sendstring(&msgbuf, buffer);
}
pq_endmessage(&msgbuf);
pq_flush();
}
}
/*
* Aux db functions
*
*/
static void
dbms_output_enable_internal(int32 n_buf_size)
{
/* We allocate +2 bytes for an end-of-line and a string terminator. */
if (buffer == NULL)
{
buffer = MemoryContextAlloc(TopMemoryContext, n_buf_size + 2);
buffer_size = n_buf_size;
buffer_len = 0;
buffer_get = 0;
}
else if (n_buf_size > buffer_len)
{
/* We cannot shrink buffer less than current length. */
buffer = repalloc(buffer, n_buf_size + 2);
buffer_size = n_buf_size;
}
}
PG_FUNCTION_INFO_V1(dbms_output_enable_default);
Datum
dbms_output_enable_default(PG_FUNCTION_ARGS)
{
dbms_output_enable_internal(BUFSIZE_DEFAULT);
PG_RETURN_VOID();
}
PG_FUNCTION_INFO_V1(dbms_output_enable);
Datum
dbms_output_enable(PG_FUNCTION_ARGS)
{
int32 n_buf_size;
if (PG_ARGISNULL(0))
n_buf_size = BUFSIZE_UNLIMITED;
else
{
n_buf_size = PG_GETARG_INT32(0);
if (n_buf_size > BUFSIZE_MAX)
{
n_buf_size = BUFSIZE_MAX;
elog(WARNING, "Limit decreased to %d bytes.", BUFSIZE_MAX);
}
else if (n_buf_size < BUFSIZE_MIN)
{
n_buf_size = BUFSIZE_MIN;
elog(WARNING, "Limit increased to %d bytes.", BUFSIZE_MIN);
}
}
dbms_output_enable_internal(n_buf_size);
PG_RETURN_VOID();
}
PG_FUNCTION_INFO_V1(dbms_output_disable);
Datum
dbms_output_disable(PG_FUNCTION_ARGS)
{
if (buffer)
pfree(buffer);
buffer = NULL;
buffer_size = 0;
buffer_len = 0;
buffer_get = 0;
PG_RETURN_VOID();
}
PG_FUNCTION_INFO_V1(dbms_output_serveroutput);
Datum
dbms_output_serveroutput(PG_FUNCTION_ARGS)
{
is_server_output = PG_GETARG_BOOL(0);
if (is_server_output && !buffer)
dbms_output_enable_internal(BUFSIZE_DEFAULT);
PG_RETURN_VOID();
}
/*
* main functions
*/
PG_FUNCTION_INFO_V1(dbms_output_put);
Datum
dbms_output_put(PG_FUNCTION_ARGS)
{
if (buffer)
add_text(PG_GETARG_TEXT_PP(0));
PG_RETURN_VOID();
}
PG_FUNCTION_INFO_V1(dbms_output_put_line);
Datum
dbms_output_put_line(PG_FUNCTION_ARGS)
{
if (buffer)
{
add_text(PG_GETARG_TEXT_PP(0));
add_newline();
}
PG_RETURN_VOID();
}
PG_FUNCTION_INFO_V1(dbms_output_new_line);
Datum
dbms_output_new_line(PG_FUNCTION_ARGS)
{
if (buffer)
add_newline();
PG_RETURN_VOID();
}
static text *
dbms_output_next(void)
{
if (buffer_get < buffer_len)
{
text *line = cstring_to_text(buffer + buffer_get);
buffer_get += VARSIZE_ANY_EXHDR(line) + 1;
return line;
}
else
return NULL;
}
PG_FUNCTION_INFO_V1(dbms_output_get_line);
Datum
dbms_output_get_line(PG_FUNCTION_ARGS)
{
TupleDesc tupdesc;
Datum result;
HeapTuple tuple;
Datum values[2];
bool nulls[2] = { false, false };
text *line;
/* Build a tuple descriptor for our result type */
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
if ((line = dbms_output_next()) != NULL)
{
values[0] = PointerGetDatum(line);
values[1] = Int32GetDatum(0); /* 0: succeeded */
}
else
{
nulls[0] = true;
values[1] = Int32GetDatum(1); /* 1: failed */
}
tuple = heap_form_tuple(tupdesc, values, nulls);
result = HeapTupleGetDatum(tuple);
PG_RETURN_DATUM(result);
}
PG_FUNCTION_INFO_V1(dbms_output_get_lines);
Datum
dbms_output_get_lines(PG_FUNCTION_ARGS)
{
TupleDesc tupdesc;
Datum result;
HeapTuple tuple;
Datum values[2];
bool nulls[2] = { false, false };
text *line;
int32 max_lines = PG_GETARG_INT32(0);
int32 n;
ArrayBuildState *astate = NULL;
/* Build a tuple descriptor for our result type */
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
for (n = 0; n < max_lines && (line = dbms_output_next()) != NULL; n++)
{
astate = accumArrayResult(astate, PointerGetDatum(line), false,
TEXTOID, CurrentMemoryContext);
}
/* 0: lines as text array */
if (n > 0)
values[0] = makeArrayResult(astate, CurrentMemoryContext);
else
{
int16 typlen;
bool typbyval;
char typalign;
ArrayType *arr;
get_typlenbyvalalign(TEXTOID, &typlen, &typbyval, &typalign);
arr = construct_md_array(
NULL,
#if PG_VERSION_NUM >= 80200
NULL,
#endif
0, NULL, NULL, TEXTOID, typlen, typbyval, typalign);
values[0] = PointerGetDatum(arr);
}
/* 1: # of lines as integer */
values[1] = Int32GetDatum(n);
tuple = heap_form_tuple(tupdesc, values, nulls);
result = HeapTupleGetDatum(tuple);
PG_RETURN_DATUM(result);
}