gpcontrib/orafce/dbms_sql.c (1,676 lines of code) (raw):
#include "postgres.h"
#include "fmgr.h"
#include "funcapi.h"
#if PG_VERSION_NUM < 120000
#include "access/htup_details.h"
#endif
#include "access/tupconvert.h"
#include "catalog/pg_type_d.h"
#include "catalog/pg_type.h"
#include "executor/spi.h"
#include "lib/stringinfo.h"
#include "parser/parse_coerce.h"
#include "parser/scansup.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/elog.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "utils/memutils.h"
#include "utils/typcache.h"
#include "executor/spi_priv.h"
#include "orafce.h"
#include "builtins.h"
#define MAX_CURSORS 100
/*
* bind variable data
*/
typedef struct
{
char *refname;
int position;
Datum value;
Oid typoid;
bool typbyval;
int16 typlen;
bool isnull;
unsigned int varno; /* number of assigned placeholder of parsed query */
bool is_array; /* true, when a value is assigned via bind_array */
Oid typelemid; /* Oid of element of a array */
bool typelembyval;
int16 typelemlen;
int index1;
int index2;
} VariableData;
/*
* Query result column definition
*/
typedef struct
{
int position;
Oid typoid;
bool typbyval;
int16 typlen;
int32 typmod;
bool typisstr;
Oid typarrayoid; /* oid of requested array output value */
uint64 rowcount; /* maximal rows of requested array */
int index1; /* output array should be rewrited from this index */
} ColumnData;
/*
* It is used for transformation result data to form
* generated by column_value procedure or column
* value function.
*/
typedef struct
{
bool isvalid; /* true, when this cast can be used */
bool without_cast; /* true, when cast is not necessary */
Oid targettypid; /* used for domains */
Oid array_targettypid; /* used for array domains */
int32 targettypmod; /* used for strings */
bool typbyval; /* used for copy result to outer memory context */
int16 typlen; /* used for copy result to outer memory context */
bool is_array;
Oid funcoid;
Oid funcoid_typmod;
CoercionPathType path;
CoercionPathType path_typmod;
FmgrInfo finfo;
FmgrInfo finfo_typmod;
FmgrInfo finfo_out;
FmgrInfo finfo_in;
Oid typIOParam;
} CastCacheData;
/*
* dbms_sql cursor definition
*/
typedef struct
{
int16 cid;
char *parsed_query;
char *original_query;
unsigned int nvariables;
int max_colpos;
List *variables;
List *columns;
char cursorname[32];
Portal portal; /* one shot (execute) plan */
SPIPlanPtr plan;
MemoryContext cursor_cxt;
MemoryContext cursor_xact_cxt;
MemoryContext tuples_cxt;
MemoryContext result_cxt; /* short life memory context */
HeapTuple tuples[1000];
TupleDesc coltupdesc;
TupleDesc tupdesc;
CastCacheData *casts;
uint64 processed;
uint64 nread;
uint64 start_read;
bool assigned;
bool executed;
Bitmapset *array_columns; /* set of array columns */
uint64 batch_rows; /* how much rows should be fetched to fill target arrays */
} CursorData;
typedef enum
{
TOKEN_SPACES,
TOKEN_COMMENT,
TOKEN_NUMBER,
TOKEN_BIND_VAR,
TOKEN_STR,
TOKEN_EXT_STR,
TOKEN_DOLAR_STR,
TOKEN_IDENTIF,
TOKEN_QIDENTIF,
TOKEN_DOUBLE_COLON,
TOKEN_OTHER,
TOKEN_NONE
} orafceTokenType;
static char *next_token(char *str, char **start, size_t *len, orafceTokenType *typ, char **sep, size_t *seplen);
PG_FUNCTION_INFO_V1(dbms_sql_is_open);
PG_FUNCTION_INFO_V1(dbms_sql_open_cursor);
PG_FUNCTION_INFO_V1(dbms_sql_close_cursor);
PG_FUNCTION_INFO_V1(dbms_sql_parse);
PG_FUNCTION_INFO_V1(dbms_sql_bind_variable);
PG_FUNCTION_INFO_V1(dbms_sql_bind_variable_f);
PG_FUNCTION_INFO_V1(dbms_sql_bind_array_3);
PG_FUNCTION_INFO_V1(dbms_sql_bind_array_5);
PG_FUNCTION_INFO_V1(dbms_sql_define_column);
PG_FUNCTION_INFO_V1(dbms_sql_define_array);
PG_FUNCTION_INFO_V1(dbms_sql_execute);
PG_FUNCTION_INFO_V1(dbms_sql_fetch_rows);
PG_FUNCTION_INFO_V1(dbms_sql_execute_and_fetch);
PG_FUNCTION_INFO_V1(dbms_sql_column_value);
PG_FUNCTION_INFO_V1(dbms_sql_column_value_f);
PG_FUNCTION_INFO_V1(dbms_sql_last_row_count);
PG_FUNCTION_INFO_V1(dbms_sql_describe_columns);
PG_FUNCTION_INFO_V1(dbms_sql_describe_columns_f);
PG_FUNCTION_INFO_V1(dbms_sql_debug_cursor);
static uint64 last_row_count = 0;
static MemoryContext persist_cxt = NULL;
static CursorData cursors[MAX_CURSORS];
static void
open_cursor(CursorData *c, int cid)
{
c->cid = cid;
if (!persist_cxt)
{
persist_cxt = AllocSetContextCreate(NULL,
"dbms_sql persist context",
ALLOCSET_DEFAULT_SIZES);
memset(cursors, 0, sizeof(cursors));
}
c->cursor_cxt = AllocSetContextCreate(persist_cxt,
"dbms_sql cursor context",
ALLOCSET_DEFAULT_SIZES);
c->assigned = true;
}
/*
* FUNCTION dbms_sql.open_cursor() RETURNS int
*/
Datum
dbms_sql_open_cursor(PG_FUNCTION_ARGS)
{
int i;
(void) fcinfo;
/* find and initialize first free slot */
for (i = 0; i < MAX_CURSORS; i++)
{
if (!cursors[i].assigned)
{
open_cursor(&cursors[i], i);
PG_RETURN_INT32(i);
}
}
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("too many opened cursors"),
errdetail("There is not free slot for new dbms_sql's cursor."),
errhint("You should to close unused cursors")));
/* be msvc quiet */
return (Datum) 0;
}
static CursorData *
get_cursor(FunctionCallInfo fcinfo, bool should_be_assigned)
{
CursorData *cursor;
int cid;
if (PG_ARGISNULL(0))
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("cursor id is NULL")));
cid = PG_GETARG_INT32(0);
if (cid < 0 || cid >= MAX_CURSORS)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("value of cursor id is out of range")));
cursor = &cursors[cid];
if (!cursor->assigned && should_be_assigned)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_CURSOR),
errmsg("cursor is not valid")));
return cursor;
}
/*
* CREATE FUNCTION dbms_sql.is_open(c int) RETURNS bool;
*/
Datum
dbms_sql_is_open(PG_FUNCTION_ARGS)
{
CursorData *c;
c = get_cursor(fcinfo, false);
PG_RETURN_BOOL(c->assigned);
}
/*
* Release all sources assigned to cursor
*/
static void
close_cursor(CursorData *c)
{
if (c->executed && c->portal)
SPI_cursor_close(c->portal);
/* release all assigned memory */
if (c->cursor_cxt)
MemoryContextDelete(c->cursor_cxt);
if (c->cursor_xact_cxt)
MemoryContextDelete(c->cursor_xact_cxt);
if (c->plan)
SPI_freeplan(c->plan);
memset(c, 0, sizeof(CursorData));
}
/*
* PROCEDURE dbms_sql.close_cursor(c int)
*/
Datum
dbms_sql_close_cursor(PG_FUNCTION_ARGS)
{
CursorData *c;
c = get_cursor(fcinfo, false);
close_cursor(c);
return (Datum) 0;
}
/*
* Print state of cursor - just for debug purposes
*/
Datum
dbms_sql_debug_cursor(PG_FUNCTION_ARGS)
{
CursorData *c;
ListCell *lc;
c = get_cursor(fcinfo, false);
if (c->assigned)
{
if (c->original_query)
elog(NOTICE, "orig query: \"%s\"", c->original_query);
if (c->parsed_query)
elog(NOTICE, "parsed query: \"%s\"", c->parsed_query);
}
else
elog(NOTICE, "cursor is not assigned");
foreach(lc, c->variables)
{
VariableData *var = (VariableData *) lfirst(lc);
if (var->typoid != InvalidOid)
{
if (!var->isnull)
{
Oid typOutput;
bool isVarlena;
char *str;
getTypeOutputInfo(var->typoid, &typOutput, &isVarlena);
str = OidOutputFunctionCall(typOutput, var->value);
elog(NOTICE, "variable \"%s\" is assigned to \"%s\"", var->refname, str);
}
else
elog(NOTICE, "variable \"%s\" is NULL", var->refname);
}
else
elog(NOTICE, "variable \"%s\" is not assigned", var->refname);
}
foreach(lc, c->columns)
{
ColumnData *col = (ColumnData *) lfirst(lc);
elog(NOTICE, "column definition for position %d is %s",
col->position,
format_type_with_typemod(col->typoid, col->typmod));
}
return (Datum) 0;
}
/*
* Search a variable in cursor's variable list
*/
static VariableData *
get_var(CursorData *c, char *refname, int position, bool append)
{
ListCell *lc;
foreach(lc, c->variables)
{
VariableData *var = (VariableData *) lfirst(lc);
if (strcmp(var->refname, refname) == 0)
return var;
}
if (append)
{
VariableData *nvar;
MemoryContext oldcxt;
oldcxt = MemoryContextSwitchTo(c->cursor_cxt);
nvar = palloc0(sizeof(VariableData));
nvar->refname = pstrdup(refname);
nvar->varno = c->nvariables + 1;
nvar->position = position;
c->variables = lappend(c->variables, nvar);
c->nvariables += 1;
MemoryContextSwitchTo(oldcxt);
return nvar;
}
else
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_PARAMETER),
errmsg("variable \"%s\" doesn't exists", refname)));
/* be msvc quite */
return NULL;
}
/*
* PROCEDURE dbms_sql.parse(c int, stmt varchar)
*/
Datum
dbms_sql_parse(PG_FUNCTION_ARGS)
{
char *query,
*ptr;
char *start;
size_t len;
orafceTokenType typ;
StringInfoData sinfo;
CursorData *c;
MemoryContext oldcxt;
c = get_cursor(fcinfo, true);
if (PG_ARGISNULL(1))
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("parsed query string is NULL")));
if (c->parsed_query)
{
int cid = c->cid;
close_cursor(c);
open_cursor(c, cid);
}
query = text_to_cstring(PG_GETARG_TEXT_P(1));
ptr = query;
initStringInfo(&sinfo);
while (ptr)
{
char *startsep;
char *next_ptr;
size_t seplen;
next_ptr = next_token(ptr, &start, &len, &typ, &startsep, &seplen);
if (next_ptr)
{
if (typ == TOKEN_DOLAR_STR)
{
appendStringInfo(&sinfo, "%.*s", (int) seplen, startsep);
appendStringInfo(&sinfo, "%.*s", (int) len, start);
appendStringInfo(&sinfo, "%.*s", (int) seplen, startsep);
}
else if (typ == TOKEN_BIND_VAR)
{
char *name = downcase_identifier(start, (int) len, false, true);
VariableData *var = get_var(c, name, (int) (ptr - query), true);
appendStringInfo(&sinfo, "$%d", var->varno);
pfree(name);
}
else if (typ == TOKEN_EXT_STR)
{
appendStringInfo(&sinfo, "e\'%.*s\'", (int) len, start);
}
else if (typ == TOKEN_STR)
{
appendStringInfo(&sinfo, "\'%.*s\'", (int) len, start);
}
else if (typ == TOKEN_QIDENTIF)
{
appendStringInfo(&sinfo, "\"%.*s\"", (int) len, start);
}
else if (typ != TOKEN_NONE)
{
appendStringInfo(&sinfo, "%.*s", (int) len, start);
}
}
ptr = next_ptr;
}
/* save result to persist context */
oldcxt = MemoryContextSwitchTo(c->cursor_cxt);
c->original_query = pstrdup(query);
c->parsed_query = pstrdup(sinfo.data);
MemoryContextSwitchTo(oldcxt);
pfree(query);
pfree(sinfo.data);
return (Datum) 0;
}
/*
* Calling procedure can be slow, so there is a function alternative
*/
static Datum
bind_variable(PG_FUNCTION_ARGS)
{
CursorData *c;
VariableData *var;
char *varname, *varname_downcase;
Oid valtype;
bool is_unknown = false;
c = get_cursor(fcinfo, true);
if (PG_ARGISNULL(1))
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("name of bind variable is NULL")));
varname = text_to_cstring(PG_GETARG_TEXT_P(1));
if (*varname == ':')
varname += 1;
varname_downcase = downcase_identifier(varname, (int) strlen(varname), false, true);
var = get_var(c, varname_downcase, -1, false);
valtype = get_fn_expr_argtype(fcinfo->flinfo, 2);
if (valtype == RECORDOID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot to bind a value of record type")));
valtype = getBaseType(valtype);
if (valtype == UNKNOWNOID)
{
is_unknown = true;
valtype = TEXTOID;
}
if (var->typoid != InvalidOid)
{
if (!var->typbyval && !var->isnull)
{
pfree(DatumGetPointer(var->value));
var->value = (Datum) 0;
}
var->isnull = true;
}
var->typoid = valtype;
if (!PG_ARGISNULL(2))
{
MemoryContext oldcxt;
get_typlenbyval(var->typoid, &var->typlen, &var->typbyval);
oldcxt = MemoryContextSwitchTo(c->cursor_cxt);
if (is_unknown)
var->value = CStringGetTextDatum(DatumGetPointer(PG_GETARG_DATUM(2)));
else
var->value = datumCopy(PG_GETARG_DATUM(2), var->typbyval, var->typlen);
var->isnull = false;
MemoryContextSwitchTo(oldcxt);
}
else
var->isnull = true;
return (Datum) 0;
}
/*
* CREATE PROCEDURE dbms_sql.bind_variable(c int, name varchar2, value "any");
*/
Datum
dbms_sql_bind_variable(PG_FUNCTION_ARGS)
{
return bind_variable(fcinfo);
}
/*
* CREATE FUNCTION dbms_sql.bind_variable_f(c int, name varchar2, value "any") RETURNS void;
*/
Datum
dbms_sql_bind_variable_f(PG_FUNCTION_ARGS)
{
return bind_variable(fcinfo);
}
static void
bind_array(FunctionCallInfo fcinfo, int index1, int index2)
{
CursorData *c;
VariableData *var;
char *varname, *varname_downcase;
Oid valtype;
Oid elementtype;
c = get_cursor(fcinfo, true);
if (PG_ARGISNULL(1))
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("name of bind variable is NULL")));
varname = text_to_cstring(PG_GETARG_TEXT_P(1));
if (*varname == ':')
varname += 1;
varname_downcase = downcase_identifier(varname, (int) strlen(varname), false, true);
var = get_var(c, varname_downcase, -1, false);
valtype = get_fn_expr_argtype(fcinfo->flinfo, 2);
if (valtype == RECORDOID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot to bind a value of record type")));
valtype = getBaseType(valtype);
elementtype = get_element_type(valtype);
if (!OidIsValid(elementtype))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("value is not a array")));
if (var->typoid != InvalidOid)
{
if (!var->typbyval && !var->isnull)
{
pfree(DatumGetPointer(var->value));
var->value = (Datum) 0;
}
var->isnull = true;
}
var->is_array = true;
var->typoid = valtype;
var->typelemid = elementtype;
get_typlenbyval(elementtype, &var->typelemlen, &var->typelembyval);
if (!PG_ARGISNULL(2))
{
MemoryContext oldcxt;
get_typlenbyval(var->typoid, &var->typlen, &var->typbyval);
oldcxt = MemoryContextSwitchTo(c->cursor_cxt);
var->value = datumCopy(PG_GETARG_DATUM(2), var->typbyval, var->typlen);
var->isnull = false;
MemoryContextSwitchTo(oldcxt);
}
else
var->isnull = true;
var->index1 = index1;
var->index2 = index2;
}
/*
* CREATE PROCEDURE dbms_sql.bind_array(c int, name varchar2, value anyarray);
*/
Datum
dbms_sql_bind_array_3(PG_FUNCTION_ARGS)
{
bind_array(fcinfo, -1, -1);
return (Datum) 0;
}
/*
* CREATE PROCEDURE dbms_sql.bind_array(c int, name varchar2, value anyarray, index1 int, index2 int);
*/
Datum
dbms_sql_bind_array_5(PG_FUNCTION_ARGS)
{
int index1, index2;
if (PG_ARGISNULL(3) || PG_ARGISNULL(4))
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("index is NULL")));
index1 = PG_GETARG_INT32(3);
index2 = PG_GETARG_INT32(4);
if (index1 < 0 || index2 < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("index is below zero")));
if (index1 > index2)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("index1 is greater than index2")));
bind_array(fcinfo, index1, index2);
return (Datum) 0;
}
static ColumnData *
get_col(CursorData *c, int position, bool append)
{
ListCell *lc;
foreach(lc, c->columns)
{
ColumnData *col = (ColumnData *) lfirst(lc);
if (col->position == position)
return col;
}
if (append)
{
ColumnData *ncol;
MemoryContext oldcxt;
oldcxt = MemoryContextSwitchTo(c->cursor_cxt);
ncol = palloc0(sizeof(ColumnData));
ncol->position = position;
if (c->max_colpos < position)
c->max_colpos = position;
c->columns = lappend(c->columns, ncol);
MemoryContextSwitchTo(oldcxt);
return ncol;
}
else
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column no %d is not defined", position)));
/* be msvc quite */
return NULL;
}
/*
* CREATE PROCEDURE dbms_sql.define_column(c int, col int, value "any", column_size int DEFAULT -1);
*/
Datum
dbms_sql_define_column(PG_FUNCTION_ARGS)
{
CursorData *c;
ColumnData *col;
Oid valtype;
Oid basetype;
int position;
int colsize;
TYPCATEGORY category;
bool ispreferred;
c = get_cursor(fcinfo, true);
if (PG_ARGISNULL(1))
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("column position (number) is NULL")));
position = PG_GETARG_INT32(1);
col = get_col(c, position, true);
valtype = get_fn_expr_argtype(fcinfo->flinfo, 2);
if (valtype == RECORDOID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot to define a column of record type")));
if (valtype == UNKNOWNOID)
valtype = TEXTOID;
basetype = getBaseType(valtype);
if (col->typoid != InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column is defined already")));
col->typoid = valtype;
if (PG_ARGISNULL(3))
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("column_size is NULL")));
colsize = PG_GETARG_INT32(3);
get_type_category_preferred(basetype, &category, &ispreferred);
col->typisstr = category == TYPCATEGORY_STRING;
col->typmod = (col->typisstr && colsize != -1) ? colsize + 4 : -1;
get_typlenbyval(basetype, &col->typlen, &col->typbyval);
col->rowcount = 1;
return (Datum) 0;
}
/*
* CREATE PROCEDURE dbms_sql.define_array(c int, col int, value "anyarray", rowcount int, index1 int);
*/
Datum
dbms_sql_define_array(PG_FUNCTION_ARGS)
{
CursorData *c;
ColumnData *col;
Oid valtype;
Oid basetype;
int position;
int rowcount;
int index1;
Oid elementtype;
TYPCATEGORY category;
bool ispreferred;
c = get_cursor(fcinfo, true);
if (PG_ARGISNULL(1))
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("column position (number) is NULL")));
position = PG_GETARG_INT32(1);
col = get_col(c, position, true);
valtype = get_fn_expr_argtype(fcinfo->flinfo, 2);
if (valtype == RECORDOID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot to define a column of record type")));
get_type_category_preferred(valtype, &category, &ispreferred);
if (category != TYPCATEGORY_ARRAY)
elog(ERROR, "defined value is not array");
col->typarrayoid = valtype;
basetype = getBaseType(valtype);
elementtype = get_element_type(basetype);
if (!OidIsValid(elementtype))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("column is not a array")));
if (col->typoid != InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column is defined already")));
col->typoid = elementtype;
if (PG_ARGISNULL(3))
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("cnt is NULL")));
rowcount = PG_GETARG_INT32(3);
if (rowcount <= 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cnt is less or equal to zero")));
col->rowcount = (uint64) rowcount;
if (PG_ARGISNULL(4))
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("lower_bnd is NULL")));
index1 = PG_GETARG_INT32(4);
if (index1 < 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("lower_bnd is less than one")));
if (index1 != 1)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("lower_bnd can be only only \"1\"")));
col->index1 = index1;
get_typlenbyval(col->typoid, &col->typlen, &col->typbyval);
return (Datum) 0;
}
static void
cursor_xact_cxt_deletion_callback(void *arg)
{
CursorData *cur = (CursorData *) arg;
cur->cursor_xact_cxt = NULL;
cur->result_cxt = NULL;
cur->tuples_cxt = NULL;
cur->processed = 0;
cur->nread = 0;
cur->executed = false;
cur->tupdesc = NULL;
cur->coltupdesc = NULL;
cur->casts = NULL;
cur->array_columns = NULL;
}
static uint64
execute(CursorData *c)
{
last_row_count = 0;
/* clean space with saved result */
if (!c->cursor_xact_cxt)
{
MemoryContextCallback *mcb;
MemoryContext oldcxt;
c->cursor_xact_cxt = AllocSetContextCreate(TopTransactionContext,
"dbms_sql transaction context",
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(c->cursor_xact_cxt);
mcb = palloc0(sizeof(MemoryContextCallback));
mcb->func = cursor_xact_cxt_deletion_callback;
mcb->arg = c;
MemoryContextRegisterResetCallback(c->cursor_xact_cxt, mcb);
MemoryContextSwitchTo(oldcxt);
}
else
{
MemoryContext save_cxt = c->cursor_xact_cxt;
MemoryContextReset(c->cursor_xact_cxt);
c->cursor_xact_cxt = save_cxt;
c->casts = NULL;
c->tupdesc = NULL;
c->tuples_cxt = NULL;
}
c->result_cxt = AllocSetContextCreate(c->cursor_xact_cxt,
"dbms_sql short life context",
ALLOCSET_DEFAULT_SIZES);
/*
* When column definitions are available, build final query
* and open cursor for fetching. When column definitions are
* missing, then the statement can be called with high frequency
* etc INSERT, UPDATE, so use cached plan.
*/
if (c->columns)
{
Datum *values;
Oid *types;
char *nulls;
ListCell *lc;
int i;
MemoryContext oldcxt;
uint64 batch_rows = 0;
oldcxt = MemoryContextSwitchTo(c->cursor_xact_cxt);
/* prepare query arguments */
values = palloc(sizeof(Datum) * c->nvariables);
types = palloc(sizeof(Oid) * c->nvariables);
nulls = palloc(sizeof(char) * c->nvariables);
i = 0;
foreach(lc, c->variables)
{
VariableData *var = (VariableData *) lfirst(lc);
if (var->is_array)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("a array (bulk) variable can be used only when no column is defined")));
if (!var->isnull)
{
/* copy a value to xact memory context, to be independent on a outside */
values[i] = datumCopy(var->value, var->typbyval, var->typlen);
nulls[i] = ' ';
}
else
nulls[i] = 'n';
if (var->typoid == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_PARAMETER),
errmsg("variable \"%s\" has not a value", var->refname)));
types[i] = var->typoid;
i += 1;
}
/* prepare or refresh target tuple descriptor, used for final tupconversion */
if (c->tupdesc)
FreeTupleDesc(c->tupdesc);
#if PG_VERSION_NUM >= 120000
c->coltupdesc = CreateTemplateTupleDesc(c->max_colpos);
#else
c->coltupdesc = CreateTemplateTupleDesc(c->max_colpos, false);
#endif
/* prepare current result column tupdesc */
for (i = 1; i <= c->max_colpos; i++)
{
ColumnData *col = get_col(c, i, false);
char genname[32];
snprintf(genname, 32, "col%d", i);
Assert(col->rowcount > 0);
if (col->typarrayoid)
{
if (batch_rows != 0)
batch_rows = col->rowcount < batch_rows ? col->rowcount : batch_rows;
else
batch_rows = col->rowcount;
c->array_columns = bms_add_member(c->array_columns, i);
}
else
{
/* in this case we cannot do batch of rows */
batch_rows = 1;
}
TupleDescInitEntry(c->coltupdesc, (AttrNumber) i, genname, col->typoid, col->typmod, 0);
}
c->batch_rows = batch_rows;
Assert(c->coltupdesc->natts >= 0);
c->casts = palloc0(sizeof(CastCacheData) * ((unsigned int) c->coltupdesc->natts));
MemoryContextSwitchTo(oldcxt);
snprintf(c->cursorname, sizeof(c->cursorname), "__orafce_dbms_sql_cursor_%d", c->cid);
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connact failed");
c->portal = SPI_cursor_open_with_args(c->cursorname,
c->parsed_query,
(int) c->nvariables,
types,
values,
nulls,
false,
0);
/* internal error */
if (c->portal == NULL)
elog(ERROR,
"could not open cursor for query \"%s\": %s",
c->parsed_query,
SPI_result_code_string(SPI_result));
SPI_finish();
/* Describe portal and prepare cast cache */
if (c->portal->tupDesc)
{
int natts = 0;
TupleDesc tupdesc = c->portal->tupDesc;
for (i = 0; i < tupdesc->natts; i++)
{
Form_pg_attribute att = TupleDescAttr(tupdesc, i);
if (att->attisdropped)
continue;
natts += 1;
}
if (natts != c->coltupdesc->natts)
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
errmsg("number of defined columns is different than number of query's columns")));
}
c->executed = true;
}
else
{
MemoryContext oldcxt;
Datum *values;
char *nulls;
ArrayIterator *iterators;
bool has_iterator = false;
bool has_value = true;
int max_index1 = -1;
int min_index2 = -1;
uint64 result = 0;
ListCell *lc;
int i;
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connact failed");
/* prepare, or reuse cached plan */
if (!c->plan)
{
Oid *types = NULL;
SPIPlanPtr plan;
types = palloc(sizeof(Oid) * c->nvariables);
i = 0;
foreach(lc, c->variables)
{
VariableData *var = (VariableData *) lfirst(lc);
if (var->typoid == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_PARAMETER),
errmsg("variable \"%s\" has not a value", var->refname)));
types[i++] = var->is_array ? var->typelemid : var->typoid;
}
plan = SPI_prepare(c->parsed_query, (int) c->nvariables, types);
if (!plan)
/* internal error */
elog(ERROR, "cannot to prepare plan");
if (types)
pfree(types);
SPI_keepplan(plan);
c->plan = plan;
}
oldcxt = MemoryContextSwitchTo(c->result_cxt);
/* prepare query arguments */
values = palloc(sizeof(Datum) * c->nvariables);
nulls = palloc(sizeof(char) * c->nvariables);
iterators = palloc(sizeof(ArrayIterator *) * c->nvariables);
has_value = true;
i = 0;
foreach(lc, c->variables)
{
VariableData *var = (VariableData *) lfirst(lc);
if (var->is_array)
{
if (!var->isnull)
{
iterators[i] = array_create_iterator(DatumGetArrayTypeP(var->value),
0,
NULL);
/* search do lowest common denominator */
if (var->index1 != -1)
{
if (max_index1 != -1)
{
max_index1 = max_index1 < var->index1 ? var->index1 : max_index1;
min_index2 = min_index2 > var->index2 ? var->index2 : min_index2;
}
else
{
max_index1 = var->index1;
min_index2 = var->index2;
}
}
has_iterator = true;
}
else
{
/* cannot to read data from NULL array */
has_value = false;
break;
}
}
else
{
values[i] = var->value;
nulls[i] = var->isnull ? 'n' : ' ';
}
i += 1;
}
if (has_iterator)
{
int max_rows = -1;
if (has_value)
{
if (max_index1 != -1)
{
max_rows = min_index2 - max_index1 + 1;
has_value = max_rows > 0;
if (has_value && max_index1 > 1)
{
i = 0;
foreach(lc, c->variables)
{
VariableData *var = (VariableData *) lfirst(lc);
if (var->is_array)
{
int j;
Assert(iterators[i]);
for (j = 1; j < max_index1; j++)
{
Datum value;
bool isnull;
has_value = array_iterate(iterators[i], &value, &isnull);
if (!has_value)
break;
}
if (!has_value)
break;
}
i += 1;
}
}
}
}
while (has_value && (max_rows == -1 || max_rows > 0))
{
int rc;
i = 0;
foreach(lc, c->variables)
{
VariableData *var = (VariableData *) lfirst(lc);
if (var->is_array)
{
Datum value;
bool isnull;
has_value = array_iterate(iterators[i], &value, &isnull);
if (!has_value)
break;
values[i] = value;
nulls[i] = isnull ? 'n' : ' ';
}
i += 1;
}
if (!has_value)
break;
rc = SPI_execute_plan(c->plan, values, nulls, false, 0);
if (rc < 0)
/* internal error */
elog(ERROR, "cannot to execute a query");
result += SPI_processed;
if (max_rows > 0)
max_rows -= 1;
}
MemoryContextReset(c->result_cxt);
}
else if (has_value)
{
int rc;
rc = SPI_execute_plan(c->plan, values, nulls, false, 0);
if (rc < 0)
/* internal error */
elog(ERROR, "cannot to execute a query");
result = SPI_processed;
}
SPI_finish();
MemoryContextSwitchTo(oldcxt);
return result;
}
return 0L;
}
/*
* CREATE FUNCTION dbms_sql.execute(c int) RETURNS bigint;
*/
Datum
dbms_sql_execute(PG_FUNCTION_ARGS)
{
CursorData *c;
c = get_cursor(fcinfo, true);
PG_RETURN_INT64((int64) execute(c));
}
static uint64
fetch_rows(CursorData *c, bool exact)
{
uint64 can_read_rows;
if (!c->executed)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_STATE),
errmsg("cursor is not executed")));
if (!c->portal)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cursor has not portal")));
if (c->nread == c->processed)
{
MemoryContext oldcxt;
uint64 i;
int batch_rows;
if (!exact)
{
if (c->array_columns)
batch_rows = (1000 / c->batch_rows) * c->batch_rows;
else
batch_rows = 1000;
}
else
batch_rows = 2;
/* create or reset context for tuples */
if (!c->tuples_cxt)
c->tuples_cxt = AllocSetContextCreate(c->cursor_xact_cxt,
"dbms_sql tuples context",
ALLOCSET_DEFAULT_SIZES);
else
MemoryContextReset(c->tuples_cxt);
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connact failed");
/* try to fetch data from cursor */
SPI_cursor_fetch(c->portal, true, batch_rows);
if (SPI_tuptable == NULL)
elog(ERROR, "cannot fetch data");
if (exact && SPI_processed > 1)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ROWS),
errmsg("too many rows"),
errdetail("In exact mode only one row is expected")));
if (exact && SPI_processed == 0)
ereport(ERROR,
(errcode(ERRCODE_NO_DATA_FOUND),
errmsg("no data found"),
errdetail("In exact mode only one row is expected")));
oldcxt = MemoryContextSwitchTo(c->tuples_cxt);
c->tupdesc = CreateTupleDescCopy(SPI_tuptable->tupdesc);
for (i = 0; i < SPI_processed; i++)
c->tuples[i] = heap_copytuple(SPI_tuptable->vals[i]);
MemoryContextSwitchTo(oldcxt);
c->processed = SPI_processed;
c->nread = 0;
SPI_finish();
}
if (c->processed - c->nread >= c->batch_rows)
can_read_rows = c->batch_rows;
else
can_read_rows = c->processed - c->nread;
c->start_read = c->nread;
c->nread += can_read_rows;
last_row_count = can_read_rows;
return can_read_rows;
}
/*
* CREATE FUNCTION dbms_sql.fetch_rows(c int) RETURNS int;
*/
Datum
dbms_sql_fetch_rows(PG_FUNCTION_ARGS)
{
CursorData *c;
c = get_cursor(fcinfo, true);
PG_RETURN_INT32(fetch_rows(c, false));
}
/*
* CREATE FUNCTION dbms_sql.execute_and_fetch(c int, exact bool DEFAULT false) RETURNS int;
*/
Datum
dbms_sql_execute_and_fetch(PG_FUNCTION_ARGS)
{
CursorData *c;
bool exact;
c = get_cursor(fcinfo, true);
if (PG_ARGISNULL(1))
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("exact option is NULL")));
exact = PG_GETARG_BOOL(1);
execute(c);
PG_RETURN_INT32(fetch_rows(c, exact));
}
/*
* CREATE FUNCTION dbms_sql.last_row_count() RETURNS int;
*/
Datum
dbms_sql_last_row_count(PG_FUNCTION_ARGS)
{
(void) fcinfo;
PG_RETURN_INT32(last_row_count);
}
/*
* Initialize cast case entry.
*/
static void
init_cast_cache_entry(CastCacheData *ccast,
Oid targettypid,
int32 targettypmod,
Oid sourcetypid)
{
Oid funcoid;
Oid basetypid;
basetypid = getBaseType(targettypid);
if (targettypid != basetypid)
ccast->targettypid = targettypid;
else
ccast->targettypid = InvalidOid;
ccast->targettypmod = targettypmod;
if (sourcetypid == targettypid)
ccast->without_cast = targettypmod == -1;
else
ccast->without_cast = false;
if (!ccast->without_cast)
{
ccast->path = find_coercion_pathway(basetypid,
sourcetypid,
COERCION_ASSIGNMENT,
&funcoid);
if (ccast->path == COERCION_PATH_NONE)
ereport(ERROR,
(errcode(ERRCODE_CANNOT_COERCE),
errmsg("cannot to find cast from source type \"%s\" to target type \"%s\"",
format_type_be(sourcetypid),
format_type_be(basetypid))));
if (ccast->path == COERCION_PATH_FUNC)
{
fmgr_info(funcoid, &ccast->finfo);
}
else if (ccast->path == COERCION_PATH_COERCEVIAIO)
{
bool typisvarlena;
getTypeOutputInfo(sourcetypid, &funcoid, &typisvarlena);
fmgr_info(funcoid, &ccast->finfo_out);
getTypeInputInfo(targettypid, &funcoid, &ccast->typIOParam);
fmgr_info(funcoid, &ccast->finfo_in);
}
if (targettypmod != -1)
{
ccast->path_typmod = find_typmod_coercion_function(targettypid,
&funcoid);
if (ccast->path_typmod == COERCION_PATH_FUNC)
fmgr_info(funcoid, &ccast->finfo_typmod);
}
}
ccast->isvalid = true;
}
/*
* Apply cast rules to a value
*/
static Datum
cast_value(CastCacheData *ccast, Datum value, bool isnull)
{
if (!isnull && !ccast->without_cast)
{
if (ccast->path == COERCION_PATH_FUNC)
value = FunctionCall1(&ccast->finfo, value);
else if (ccast->path == COERCION_PATH_RELABELTYPE)
{
/* do nothing */
}
else if (ccast->path == COERCION_PATH_COERCEVIAIO)
{
char *str;
str = OutputFunctionCall(&ccast->finfo_out, value);
value = InputFunctionCall(&ccast->finfo_in,
str,
ccast->typIOParam,
ccast->targettypmod);
}
else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported cast path yet %d", ccast->path)));
if (ccast->targettypmod != -1 && ccast->path_typmod == COERCION_PATH_FUNC)
value = FunctionCall3(&ccast->finfo_typmod,
value,
Int32GetDatum(ccast->targettypmod),
BoolGetDatum(true));
}
if (ccast->targettypid != InvalidOid)
domain_check(value, isnull, ccast->targettypid, NULL, NULL);
return value;
}
/*
* CALL statement is relatily slow in PLpgSQL - due repated parsing, planning.
* So I wrote two variant of this routine. When spi_transfer is true, then
* the value is copyied to SPI outer memory context.
*/
static Datum
column_value(CursorData *c, int pos, Oid targetTypeId, bool *isnull, bool spi_transfer)
{
Datum value;
int32 columnTypeMode;
Oid columnTypeId;
CastCacheData *ccast;
Assert(c->executed);
if (last_row_count == 0)
ereport(ERROR,
(errcode(ERRCODE_NO_DATA_FOUND),
errmsg("no data found")));
if (!c->tupdesc)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_STATE),
errmsg("cursor is not fetched")));
if (!c->coltupdesc)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("no column is defined")));
if (pos < 1 && pos > c->coltupdesc->natts)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("column position is of of range [1, %d]",
c->coltupdesc->natts)));
columnTypeId = (TupleDescAttr(c->coltupdesc, pos - 1))->atttypid;
columnTypeMode = (TupleDescAttr(c->coltupdesc, pos - 1))->atttypmod;
Assert(c->casts);
ccast = &c->casts[pos - 1];
if (!ccast->isvalid)
{
Oid basetype = getBaseType(targetTypeId);
init_cast_cache_entry(ccast,
columnTypeId,
columnTypeMode,
SPI_gettypeid(c->tupdesc, pos));
ccast->is_array = bms_is_member(pos, c->array_columns);
if (ccast->is_array)
{
ccast->array_targettypid = basetype != targetTypeId ? targetTypeId : InvalidOid;
if (get_array_type(getBaseType(columnTypeId)) != basetype)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("unexpected target type \"%s\" (expected type \"%s\")",
format_type_be(basetype),
format_type_be(get_array_type(getBaseType(columnTypeId))))));
}
else
ccast->array_targettypid = InvalidOid;
get_typlenbyval(basetype, &ccast->typlen, &ccast->typbyval);
}
if (ccast->is_array)
{
ArrayBuildState *abs;
uint64 idx;
uint64 i;
abs = initArrayResult(columnTypeId, CurrentMemoryContext, false);
idx = c->start_read;
for (i = 0; i < c->batch_rows; i++)
{
if (idx < c->processed)
{
value = SPI_getbinval(c->tuples[idx], c->tupdesc, pos, isnull);
value = cast_value(ccast, value, *isnull);
abs = accumArrayResult(abs,
value,
*isnull,
columnTypeId,
CurrentMemoryContext);
idx += 1;
}
}
value = makeArrayResult(abs, CurrentMemoryContext);
if (ccast->array_targettypid != InvalidOid)
domain_check(value, isnull, ccast->array_targettypid, NULL, NULL);
}
else
{
/* Maybe it can be solved by uncached slower cast */
if (targetTypeId != columnTypeId)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("unexpected target type \"%s\" (expected type \"%s\")",
format_type_be(targetTypeId),
format_type_be(columnTypeId))));
value = SPI_getbinval(c->tuples[c->start_read], c->tupdesc, pos, isnull);
value = cast_value(ccast, value, *isnull);
}
if (1)//spi_transfer)
value = SPI_datumTransfer(value, ccast->typbyval, ccast->typlen);
return value;
}
/*
* CREATE PROCEDURE dbms_sql.column_value(c int, pos int, INOUT value "any");
* Note - CALL statement is slow from PLpgSQL block (against function execution).
* This is reason why this routine is in function form too.
*/
Datum
dbms_sql_column_value(PG_FUNCTION_ARGS)
{
CursorData *c;
Datum value;
Datum result;
int pos;
bool isnull;
Oid targetTypeId;
Oid resultTypeId;
TupleDesc resulttupdesc;
HeapTuple resulttuple;
MemoryContext oldcxt;
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connact failed");
c = get_cursor(fcinfo, true);
if (PG_ARGISNULL(1))
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("column position (number) is NULL")));
pos = PG_GETARG_INT32(1);
if (!c->executed)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_STATE),
errmsg("cursor is not executed")));
oldcxt = MemoryContextSwitchTo(c->result_cxt);
/*
* Setting of OUT field is little bit more complex, because although
* there is only one output field, the result should be compisite type.
*/
if (get_call_result_type(fcinfo, &resultTypeId, &resulttupdesc) == TYPEFUNC_COMPOSITE)
{
/* check target types */
if (resulttupdesc->natts != 1)
/* internal error, should not to be */
elog(ERROR, "unexpected number of result composite fields");
targetTypeId = get_fn_expr_argtype(fcinfo->flinfo, 2);
Assert((TupleDescAttr(resulttupdesc, 0))->atttypid == targetTypeId);
}
else
/* internal error, should not to be */
elog(ERROR, "unexpected function result type");
value = column_value(c, pos, targetTypeId, &isnull, false);
resulttuple = heap_form_tuple(resulttupdesc, &value, &isnull);
result = PointerGetDatum(SPI_returntuple(resulttuple, CreateTupleDescCopy(resulttupdesc)));
SPI_finish();
MemoryContextSwitchTo(oldcxt);
MemoryContextReset(c->result_cxt);
PG_RETURN_DATUM(result);
}
/*
* CREATE FUNCTION dbms_sql.column_value(c int, pos int, value anyelement) RETURNS anyelement;
* Note - CALL statement is slow from PLpgSQL block (against function execution).
* This is reason why this routine is in function form too.
*/
Datum
dbms_sql_column_value_f(PG_FUNCTION_ARGS)
{
CursorData *c;
Datum value;
int pos;
bool isnull;
Oid targetTypeId;
MemoryContext oldcxt;
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connact failed");
c = get_cursor(fcinfo, true);
if (PG_ARGISNULL(1))
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("column position (number) is NULL")));
pos = PG_GETARG_INT32(1);
if (!c->executed)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_STATE),
errmsg("cursor is not executed")));
oldcxt = MemoryContextSwitchTo(c->result_cxt);
targetTypeId = get_fn_expr_argtype(fcinfo->flinfo, 2);
value = column_value(c, pos, targetTypeId, &isnull, true);
SPI_finish();
MemoryContextSwitchTo(oldcxt);
PG_RETURN_DATUM(value);
}
/******************************************************************
* Simple parser - just for replacement of bind variables by
* PostgreSQL $ param placeholders.
*
******************************************************************
*/
/*
* It doesn't work for multibyte encodings, but same implementation
* is in Postgres too.
*/
static bool
is_identif(unsigned char c)
{
if (c >= 'a' && c <= 'z')
return true;
else if (c >= 'A' && c <= 'Z')
return true;
else if (c >= 0200)
return true;
else
return false;
}
/*
* simple parser to detect :identif symbols in query
*/
static char *
next_token(char *str, char **start, size_t *len, orafceTokenType *typ, char **sep, size_t *seplen)
{
if (*str == '\0')
{
*typ = TOKEN_NONE;
return NULL;
}
/* reduce spaces */
if (*str == ' ')
{
*start = str++;
while (*str == ' ')
str++;
*typ = TOKEN_SPACES;
*len = 1;
return str;
}
/* Postgres's dolar strings */
if (*str == '$' && (str[1] == '$' ||
is_identif((unsigned char) str[1]) || str[1] == '_'))
{
char *aux = str + 1;
char *endstr;
bool is_valid = false;
char *buffer;
/* try to find end of separator */
while (*aux)
{
if (*aux == '$')
{
is_valid = true;
aux++;
break;
}
else if (is_identif((unsigned char) *aux) ||
isdigit(*aux) ||
*aux == '_')
{
aux++;
}
else
break;
}
if (!is_valid)
{
*typ = TOKEN_OTHER;
*len = 1;
*start = str;
return str + 1;
}
/* now it looks like correct $ separator */
*start = aux; *sep = str;
Assert(aux >= str);
*seplen = (size_t) (aux - str);
*typ = TOKEN_DOLAR_STR;
/* try to find second instance */
buffer = palloc(*seplen + 1);
memcpy(buffer, *sep, *seplen);
buffer[*seplen] = '\0';
endstr = strstr(aux, buffer);
if (endstr)
{
Assert(endstr >= *start);
*len = (size_t) (endstr - *start);
return endstr + *seplen;
}
else
{
while (*aux)
aux++;
Assert(aux >= *start);
*len = (size_t) (aux - *start);
return aux;
}
return aux;
}
/* Pair comments */
if (*str == '/' && str[1] == '*')
{
*start = str; str += 2;
while (*str)
{
if (*str == '*' && str[1] == '/')
{
str += 2;
break;
}
str++;
}
*typ = TOKEN_COMMENT;
Assert(str >= *start);
*len = (size_t) (str - *start);
return str;
}
/* Number */
if (isdigit(*str) || (*str == '.' && isdigit(str[1])))
{
bool point = *str == '.';
*start = str++;
while (*str)
{
if (isdigit(*str))
str++;
else if (*str == '.' && !point)
{
str++; point = true;
}
else
break;
}
*typ = TOKEN_NUMBER;
Assert(str >= *start);
*len = (size_t) (str - *start);
return str;
}
/* Double colon :: */
if (*str == ':' && str[1] == ':')
{
*start = str;
*typ = TOKEN_DOUBLE_COLON;
*len = 2;
return str + 2;
}
/* Bind variable placeholder */
if (*str == ':' &&
(is_identif((unsigned char) str[1]) || str[1] == '_'))
{
*start = &str[1]; str += 2;
while (*str)
{
if (is_identif((unsigned char) *str) ||
isdigit(*str) ||
*str == '_')
str++;
else
break;
}
*typ = TOKEN_BIND_VAR;
Assert(str >= *start);
*len = (size_t) (str - *start);
return str;
}
/* Extended string literal */
if ((*str == 'e' || *str == 'E') && str[1] == '\'')
{
*start = &str[2]; str += 2;
while (*str)
{
if (*str == '\'')
{
*typ = TOKEN_EXT_STR;
Assert(str >= *start);
*len = (size_t) (str - *start);
return str + 1;
}
if (*str == '\\' && str[1] == '\'')
str += 2;
else if (*str == '\\' && str[1] == '\\')
str += 2;
else
str += 1;
}
*typ = TOKEN_EXT_STR;
Assert(str >= *start);
*len = (size_t) (str - *start);
return str;
}
/* String literal */
if (*str == '\'')
{
*start = &str[1]; str += 1;
while (*str)
{
if (*str == '\'')
{
if (str[1] != '\'')
{
*typ = TOKEN_STR;
Assert(str >= *start);
*len = (size_t) (str - *start);
return str + 1;
}
str += 2;
}
else
str += 1;
}
*typ = TOKEN_STR;
Assert(str >= *start);
*len = (size_t) (str - *start);
return str;
}
/* Quoted identifier */
if (*str == '"')
{
*start = &str[1]; str += 1;
while (*str)
{
if (*str == '"')
{
if (str[1] != '"')
{
*typ = TOKEN_QIDENTIF;
Assert(str >= *start);
*len = (size_t) (str - *start);
return str + 1;
}
str += 2;
}
else
str += 1;
}
*typ = TOKEN_QIDENTIF;
Assert(str >= *start);
*len = (size_t) (str - *start);
return str;
}
/* Identifiers */
if (is_identif((unsigned char) *str) || *str == '_')
{
*start = str++;
while (*str)
{
if (is_identif((unsigned char) *str) ||
isdigit(*str) ||
*str == '_')
str++;
else
break;
}
*typ = TOKEN_IDENTIF;
Assert(str >= *start);
*len = (size_t) (str - *start);
return str;
}
/* Others */
*typ = TOKEN_OTHER;
*start = str;
*len = 1;
return str + 1;
}
/*
* CREATE PROCEDURE dbms_sql.describe_columns(c int, OUT col_cnt int, OUT desc_t dbms_sql.desc_rec[])
*
* Returns an array of column's descriptions. Attention, the typid are related to PostgreSQL type
* system.
*/
Datum
dbms_sql_describe_columns(PG_FUNCTION_ARGS)
{
CursorData *c;
Datum values[13];
bool nulls[13];
TupleDesc tupdesc;
TupleDesc desc_rec_tupdesc;
TupleDesc cursor_tupdesc;
HeapTuple tuple;
Oid arraytypid;
Oid desc_rec_typid;
Oid *types = NULL;
ArrayBuildState *abuilder;
SPIPlanPtr plan;
CachedPlanSource *plansource = NULL;
int ncolumns = 0;
int rc;
int i = 0;
bool nonatomic;
MemoryContext callercxt = CurrentMemoryContext;
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
arraytypid = TupleDescAttr(tupdesc, 1)->atttypid;
desc_rec_typid = get_element_type(arraytypid);
if (!OidIsValid(desc_rec_typid))
elog(ERROR, "second output field must be an array");
desc_rec_tupdesc = lookup_rowtype_tupdesc_copy(desc_rec_typid, -1);
abuilder = initArrayResult(desc_rec_typid, callercxt, true);
c = get_cursor(fcinfo, true);
if (c->variables)
{
ListCell *lc;
types = palloc(sizeof(Oid) * c->nvariables);
i = 0;
foreach(lc, c->variables)
{
VariableData *var = (VariableData *) lfirst(lc);
if (var->typoid == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_PARAMETER),
errmsg("variable \"%s\" has not a value", var->refname)));
types[i++] = var->is_array ? var->typelemid : var->typoid;
}
}
/*
* Connect to SPI manager
*/
nonatomic = fcinfo->context &&
IsA(fcinfo->context, CallContext) &&
!castNode(CallContext, fcinfo->context)->atomic;
if ((rc = SPI_connect_ext(nonatomic ? SPI_OPT_NONATOMIC : 0)) != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
plan = SPI_prepare(c->parsed_query, (int) c->nvariables, types);
if (!plan || plan->magic != _SPI_PLAN_MAGIC)
elog(ERROR, "plan is not valid");
if (list_length(plan->plancache_list) != 1)
elog(ERROR, "plan is not single execution plany");
plansource = (CachedPlanSource *) linitial(plan->plancache_list);
cursor_tupdesc = plansource->resultDesc;
ncolumns = cursor_tupdesc->natts;
for (i = 0; i < ncolumns; i++)
{
HeapTuple tp;
Form_pg_type typtup;
text *txt;
Form_pg_attribute attr = TupleDescAttr(cursor_tupdesc, i);
/*
* 0. col_type BINARY_INTEGER := 0,
* 1. col_max_len BINARY_INTEGER := 0,
* 2. col_name VARCHAR2(32) := '',
* 3. col_name_len BINARY_INTEGER := 0,
* 4. col_schema_name VARCHAR2(32) := '',
* 5. col_schema_name_len BINARY_INTEGER := 0,
* 6. col_precision BINARY_INTEGER := 0,
* 7. col_scale BINARY_INTEGER := 0,
* 8. col_charsetid BINARY_INTEGER := 0,
* 9. col_charsetform BINARY_INTEGER := 0,
* 10. col_null_ok BOOLEAN := TRUE
* 11. col_type_name varchar2 := '',
* 12. col_type_name_len BINARY_INTEGER := 0 );
*/
values[0] = Int32GetDatum(attr->atttypid);
tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(attr->atttypid));
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for type %u", attr->atttypid);
typtup = (Form_pg_type) GETSTRUCT(tp);
values[1] = Int32GetDatum(0);
values[6] = Int32GetDatum(0);
values[7] = Int32GetDatum(0);
if (attr->attlen != -1)
values[1] = Int32GetDatum(attr->attlen);
else if (typtup->typcategory == 'S' && attr->atttypmod > VARHDRSZ)
values[1] = Int32GetDatum(attr->atttypmod - VARHDRSZ);
else if (attr->atttypid == NUMERICOID && attr->atttypmod > VARHDRSZ)
{
values[6] = Int32GetDatum(((attr->atttypmod - VARHDRSZ) >> 16) & 0xffff);
values[7] = Int32GetDatum((((attr->atttypmod - VARHDRSZ) & 0x7ff) ^ 1024) - 1024);
}
txt = cstring_to_text(NameStr(attr->attname));
values[2] = PointerGetDatum(txt);
values[3] = DirectFunctionCall1(textlen, PointerGetDatum(txt));
txt = cstring_to_text(get_namespace_name(typtup->typnamespace));
values[4] = PointerGetDatum(txt);
values[5] = DirectFunctionCall1(textlen, PointerGetDatum(txt));
values[8] = Int32GetDatum(0);
values[9] = Int32GetDatum(0);
values[10] = BoolGetDatum(true);
if (attr->attnotnull)
values[10] = BoolGetDatum(false);
else if (typtup->typnotnull)
values[10] = BoolGetDatum(false);
txt = cstring_to_text(NameStr(typtup->typname));
values[11] = PointerGetDatum(txt);
values[12] = DirectFunctionCall1(textlen, PointerGetDatum(txt));
memset(nulls, 0, sizeof(nulls));
tuple = heap_form_tuple(desc_rec_tupdesc, values, nulls);
abuilder = accumArrayResult(abuilder,
HeapTupleGetDatum(tuple),
false,
desc_rec_typid,
CurrentMemoryContext);
ReleaseSysCache(tp);
}
SPI_freeplan(plan);
if ((rc = SPI_finish()) != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
MemoryContextSwitchTo(callercxt);
memset(values, 0, sizeof(values));
memset(nulls, 0, sizeof(nulls));
values[0] = Int32GetDatum(ncolumns);
nulls[0] = false;
values[1] = makeArrayResult(abuilder, callercxt);
nulls[1] = false;
tuple = heap_form_tuple(tupdesc, values, nulls);
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
}
Datum
dbms_sql_describe_columns_f(PG_FUNCTION_ARGS)
{
return dbms_sql_describe_columns(fcinfo);
}