blob: 603f92af70265a1e16760d3c0e0077491db398a6 [file] [log] [blame]
/*-------------------------------------------------------------------------
*
* pg_proc.c
* routines to support manipulation of the pg_proc relation
*
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.141 2006/10/19 18:32:46 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "access/xact.h"
#include "catalog/catquery.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_language.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_proc_callback.h"
#include "catalog/pg_type.h"
#include "catalog/pg_rewrite.h"
#include "executor/functions.h"
#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "parser/parse_type.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "cdb/cdbvars.h"
Datum fmgr_internal_validator(PG_FUNCTION_ARGS);
Datum fmgr_c_validator(PG_FUNCTION_ARGS);
Datum fmgr_sql_validator(PG_FUNCTION_ARGS);
static void sql_function_parse_error_callback(void *arg);
static int match_prosrc_to_query(const char *prosrc, const char *queryText,
int cursorpos);
static bool match_prosrc_to_literal(const char *prosrc, const char *literal,
int cursorpos, int *newcursorpos);
/* ----------------------------------------------------------------
* ProcedureCreate
*
* Note: allParameterTypes, parameterModes, parameterNames are either arrays
* of the proper types or NULL. We declare them Datum, not "ArrayType *",
* to avoid importing array.h into pg_proc.h.
* ----------------------------------------------------------------
*/
Oid
ProcedureCreate(const char *procedureName,
Oid procNamespace,
bool replace,
bool returnsSet,
Oid returnType,
Oid languageObjectId,
Oid languageValidator,
Oid describeFuncOid,
const char *prosrc,
const char *probin,
bool isAgg,
bool isWin,
bool security_definer,
bool isStrict,
char volatility,
const oidvector *parameterTypes,
Datum allParameterTypes,
Datum parameterModes,
Datum parameterNames,
char prodataaccess,
Oid funcOid)
{
Oid retval;
int parameterCount;
int allParamCount;
const Oid *allParams;
bool genericInParam = false;
bool genericOutParam = false;
bool internalInParam = false;
bool internalOutParam = false;
Relation rel;
HeapTuple tup;
HeapTuple oldtup;
bool nulls[Natts_pg_proc];
Datum values[Natts_pg_proc];
bool replaces[Natts_pg_proc];
Oid relid;
NameData procname;
bool is_update;
ObjectAddress myself,
referenced;
int i;
cqContext cqc;
cqContext *pcqCtx;
/*
* sanity checks
*/
Assert(PointerIsValid(prosrc));
Assert(PointerIsValid(probin));
parameterCount = parameterTypes->dim1;
if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
errmsg("functions cannot have more than %d arguments",
FUNC_MAX_ARGS)));
/* note: the above is correct, we do NOT count output arguments */
if (allParameterTypes != PointerGetDatum(NULL))
{
/*
* We expect the array to be a 1-D OID array; verify that. We don't
* need to use deconstruct_array() since the array data is just going
* to look like a C array of OID values.
*/
ArrayType *allParamArray = (ArrayType *) DatumGetPointer(allParameterTypes);
allParamCount = ARR_DIMS(allParamArray)[0];
if (ARR_NDIM(allParamArray) != 1 ||
allParamCount <= 0 ||
ARR_HASNULL(allParamArray) ||
ARR_ELEMTYPE(allParamArray) != OIDOID)
elog(ERROR, "allParameterTypes is not a 1-D Oid array");
allParams = (Oid *) ARR_DATA_PTR(allParamArray);
Assert(allParamCount >= parameterCount);
/* we assume caller got the contents right */
}
else
{
allParamCount = parameterCount;
allParams = parameterTypes->values;
}
/*
* Do not allow return type ANYARRAY or ANYELEMENT unless at least one
* input argument is ANYARRAY or ANYELEMENT. Also, do not allow return
* type INTERNAL unless at least one input argument is INTERNAL.
*/
for (i = 0; i < parameterCount; i++)
{
switch (parameterTypes->values[i])
{
case ANYARRAYOID:
case ANYELEMENTOID:
genericInParam = true;
break;
case INTERNALOID:
internalInParam = true;
break;
}
}
if (allParameterTypes != PointerGetDatum(NULL))
{
for (i = 0; i < allParamCount; i++)
{
/*
* We don't bother to distinguish input and output params here, so
* if there is, say, just an input INTERNAL param then we will
* still set internalOutParam. This is OK since we don't really
* care.
*/
switch (allParams[i])
{
case ANYARRAYOID:
case ANYELEMENTOID:
genericOutParam = true;
break;
case INTERNALOID:
internalOutParam = true;
break;
}
}
}
/* Normally, we don't allow ANY* return type without an ANY* input type.
* But during upgrade, we're creating "special" function used by aggregate
* and we'll bypass this check for those functions.
*/
if ((returnType == ANYARRAYOID || returnType == ANYELEMENTOID ||
genericOutParam) && !genericInParam && !gp_upgrade_mode)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("cannot determine result data type"),
errdetail("A function returning \"anyarray\" or \"anyelement\" must have at least one argument of either type."),
errOmitLocation(true)));
if ((returnType == INTERNALOID || internalOutParam) && !internalInParam)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("unsafe use of pseudo-type \"internal\""),
errdetail("A function returning \"internal\" must have at least one \"internal\" argument."),
errOmitLocation(true)));
/*
* don't allow functions of complex types that have the same name as
* existing attributes of the type
*/
if (parameterCount == 1 &&
OidIsValid(parameterTypes->values[0]) &&
(relid = typeidTypeRelid(parameterTypes->values[0])) != InvalidOid &&
get_attnum(relid, procedureName) != InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("\"%s\" is already an attribute of type %s",
procedureName,
format_type_be(parameterTypes->values[0])),
errOmitLocation(true)));
/*
* All seems OK; prepare the data to be inserted into pg_proc.
*/
for (i = 0; i < Natts_pg_proc; ++i)
{
nulls[i] = false;
values[i] = (Datum) 0;
replaces[i] = true;
}
namestrcpy(&procname, procedureName);
values[Anum_pg_proc_proname - 1] = NameGetDatum(&procname);
values[Anum_pg_proc_pronamespace - 1] = ObjectIdGetDatum(procNamespace);
values[Anum_pg_proc_proowner - 1] = ObjectIdGetDatum(GetUserId());
values[Anum_pg_proc_prolang - 1] = ObjectIdGetDatum(languageObjectId);
values[Anum_pg_proc_proisagg - 1] = BoolGetDatum(isAgg);
values[Anum_pg_proc_proiswin - 1] = BoolGetDatum(isWin);
values[Anum_pg_proc_prosecdef - 1] = BoolGetDatum(security_definer);
values[Anum_pg_proc_proisstrict - 1] = BoolGetDatum(isStrict);
values[Anum_pg_proc_proretset - 1] = BoolGetDatum(returnsSet);
values[Anum_pg_proc_provolatile - 1] = CharGetDatum(volatility);
values[Anum_pg_proc_pronargs - 1] = UInt16GetDatum(parameterCount);
values[Anum_pg_proc_prorettype - 1] = ObjectIdGetDatum(returnType);
values[Anum_pg_proc_proargtypes - 1] = PointerGetDatum(parameterTypes);
if (allParameterTypes != PointerGetDatum(NULL))
values[Anum_pg_proc_proallargtypes - 1] = allParameterTypes;
else
nulls[Anum_pg_proc_proallargtypes - 1] = true;
if (parameterModes != PointerGetDatum(NULL))
values[Anum_pg_proc_proargmodes - 1] = parameterModes;
else
nulls[Anum_pg_proc_proargmodes - 1] = true;
if (parameterNames != PointerGetDatum(NULL))
values[Anum_pg_proc_proargnames - 1] = parameterNames;
else
nulls[Anum_pg_proc_proargnames - 1] = true;
values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
if (probin)
values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin);
else
nulls[Anum_pg_proc_probin - 1] = true;
/* start out with empty permissions */
nulls[Anum_pg_proc_proacl - 1] = true;
values[Anum_pg_proc_prodataaccess - 1] = CharGetDatum(prodataaccess);
rel = heap_open(ProcedureRelationId, RowExclusiveLock);
/* Check for pre-existing definition */
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), rel),
cql("SELECT * FROM pg_proc "
" WHERE proname = :1 "
" AND proargtypes = :2 "
" AND pronamespace = :3 "
" FOR UPDATE ",
PointerGetDatum((char *) procedureName),
PointerGetDatum(parameterTypes),
ObjectIdGetDatum(procNamespace)));
oldtup = caql_getnext(pcqCtx);
if (HeapTupleIsValid(oldtup))
{
/* There is one; okay to replace it? */
Form_pg_proc oldproc = (Form_pg_proc) GETSTRUCT(oldtup);
Oid oldOid = HeapTupleGetOid(oldtup);
if (!replace)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_FUNCTION),
errmsg("function \"%s\" already exists with same argument types",
procedureName),
errOmitLocation(true)));
if (!pg_proc_ownercheck(oldOid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC,
procedureName);
/*
* Not okay to change the return type of the existing proc, since
* existing rules, views, etc may depend on the return type.
*/
if (returnType != oldproc->prorettype ||
returnsSet != oldproc->proretset)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("cannot change return type of existing function"),
errhint("Use DROP FUNCTION first."),
errOmitLocation(true)));
/*
* If it returns RECORD, check for possible change of record type
* implied by OUT parameters
*/
if (returnType == RECORDOID)
{
TupleDesc olddesc;
TupleDesc newdesc;
olddesc = build_function_result_tupdesc_t(oldtup);
newdesc = build_function_result_tupdesc_d(allParameterTypes,
parameterModes,
parameterNames);
if (olddesc == NULL && newdesc == NULL)
/* ok, both are runtime-defined RECORDs */ ;
else if (olddesc == NULL || newdesc == NULL ||
!equalTupleDescs(olddesc, newdesc, true))
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("cannot change return type of existing function"),
errdetail("Row type defined by OUT parameters is different."),
errhint("Use DROP FUNCTION first."),
errOmitLocation(true)));
}
/*
* Cannot add a describe callback to a function that has views defined
* on it. This restriction is for the same set of reasons that we
* cannot change the return type of existing functions.
*/
if (OidIsValid(describeFuncOid))
{
HeapTuple depTup;
cqContext *pcqCtx2;
/* XXX XXX: SELECT COUNT(*) ... AND classid = :3, RewriteRelationId) */
pcqCtx2 = caql_beginscan(
NULL,
cql("SELECT * FROM pg_depend "
" WHERE refclassid = :1 "
" AND refobjid = :2 ",
ObjectIdGetDatum(ProcedureRelationId),
ObjectIdGetDatum(oldOid)));
while (HeapTupleIsValid(depTup = caql_getnext(pcqCtx2)))
{
Form_pg_depend depData = (Form_pg_depend) GETSTRUCT(depTup);
if (depData->classid == RewriteRelationId)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("cannot add DESCRIBE callback to function used in view(s)"),
errOmitLocation(true)));
}
}
caql_endscan(pcqCtx2);
}
/* Can't change aggregate or window status, either */
if (oldproc->proisagg != isAgg)
{
if (oldproc->proisagg)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("function \"%s\" is an aggregate",
procedureName),
errOmitLocation(true)));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("function \"%s\" is not an aggregate",
procedureName),
errOmitLocation(true)));
}
if (oldproc->proiswin != isWin)
{
if (oldproc->proiswin)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("function \"%s\" is a window function",
procedureName),
errOmitLocation(true)));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("function \"%s\" is not a window function",
procedureName),
errOmitLocation(true)));
}
/* do not change existing ownership or permissions, either */
replaces[Anum_pg_proc_proowner - 1] = false;
replaces[Anum_pg_proc_proacl - 1] = false;
/* Okay, do it... */
tup = caql_modify_current(pcqCtx, values, nulls, replaces);
caql_update_current(pcqCtx, tup); /* implicit update of index as well*/
caql_endscan(pcqCtx);
is_update = true;
}
else
{
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), rel),
cql("INSERT INTO pg_proc ",
NULL));
/* Creating a new procedure */
tup = caql_form_tuple(pcqCtx, values, nulls);
if (OidIsValid(funcOid))
HeapTupleSetOid(tup, funcOid);
/* Insert tuple into the relation */
caql_insert(pcqCtx, tup); /* implicit update of index as well */
caql_endscan(pcqCtx);
is_update = false;
}
retval = HeapTupleGetOid(tup);
/*
* Create dependencies for the new function. If we are updating an
* existing function, first delete any existing pg_depend entries.
*/
if (is_update)
{
deleteDependencyRecordsFor(ProcedureRelationId, retval);
deleteSharedDependencyRecordsFor(ProcedureRelationId, retval);
deleteProcCallbacks(retval);
}
myself.classId = ProcedureRelationId;
myself.objectId = retval;
myself.objectSubId = 0;
/* dependency on namespace */
referenced.classId = NamespaceRelationId;
referenced.objectId = procNamespace;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
/* dependency on implementation language */
referenced.classId = LanguageRelationId;
referenced.objectId = languageObjectId;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
/* dependency on return type */
referenced.classId = TypeRelationId;
referenced.objectId = returnType;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
/* dependency on describe function */
if (OidIsValid(describeFuncOid))
{
referenced.classId = ProcedureRelationId;
referenced.objectId = describeFuncOid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
addProcCallback(retval, describeFuncOid, PROMETHOD_DESCRIBE);
}
/* dependency on parameter types */
for (i = 0; i < allParamCount; i++)
{
referenced.classId = TypeRelationId;
referenced.objectId = allParams[i];
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
/* dependency on owner */
recordDependencyOnOwner(ProcedureRelationId, retval, GetUserId());
heap_freetuple(tup);
heap_close(rel, RowExclusiveLock);
/* Verify function body */
if (OidIsValid(languageValidator))
{
/* Advance command counter so new tuple can be seen by validator */
CommandCounterIncrement();
OidFunctionCall1(languageValidator, ObjectIdGetDatum(retval));
}
return retval;
}
/*
* Validator for internal functions
*
* Check that the given internal function name (the "prosrc" value) is
* a known builtin function.
*/
Datum
fmgr_internal_validator(PG_FUNCTION_ARGS)
{
Oid funcoid = PG_GETARG_OID(0);
bool isnull;
char *prosrc;
int fetchCount;
if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
PG_RETURN_VOID();
/*
* We do not honor check_function_bodies since it's unlikely the function
* name will be found later if it isn't there now.
*/
prosrc = caql_getcstring_plus(
NULL,
&fetchCount,
&isnull,
cql("SELECT prosrc FROM pg_proc "
" WHERE oid = :1 ",
ObjectIdGetDatum(funcoid)));
if (!fetchCount)
elog(ERROR, "cache lookup failed for function %u", funcoid);
if (isnull)
elog(ERROR, "null prosrc");
if (fmgr_internal_function(prosrc) == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("there is no built-in function named \"%s\"",
prosrc),
errOmitLocation(true)));
PG_RETURN_VOID();
}
/*
* Validator for C language functions
*
* Make sure that the library file exists, is loadable, and contains
* the specified link symbol. Also check for a valid function
* information record.
*/
Datum
fmgr_c_validator(PG_FUNCTION_ARGS)
{
Oid funcoid = PG_GETARG_OID(0);
void *libraryhandle;
HeapTuple tuple;
bool isnull;
Datum tmp;
char *prosrc;
char *probin;
cqContext *pcqCtx;
if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
PG_RETURN_VOID();
/*
* It'd be most consistent to skip the check if !check_function_bodies,
* but the purpose of that switch is to be helpful for pg_dump loading,
* and for pg_dump loading it's much better if we *do* check.
*/
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_proc "
" WHERE oid = :1 ",
ObjectIdGetDatum(funcoid)));
tuple = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for function %u", funcoid);
tmp = caql_getattr(pcqCtx, Anum_pg_proc_prosrc, &isnull);
if (isnull)
elog(ERROR, "null prosrc");
prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
tmp = caql_getattr(pcqCtx, Anum_pg_proc_probin, &isnull);
if (isnull)
elog(ERROR, "null probin");
probin = DatumGetCString(DirectFunctionCall1(textout, tmp));
(void) load_external_function(probin, prosrc, true, &libraryhandle);
(void) fetch_finfo_record(libraryhandle, prosrc);
caql_endscan(pcqCtx);
PG_RETURN_VOID();
}
/*
* Validator for SQL language functions
*
* Parse it here in order to be sure that it contains no syntax errors.
*/
Datum
fmgr_sql_validator(PG_FUNCTION_ARGS)
{
Oid funcoid = PG_GETARG_OID(0);
HeapTuple tuple;
Form_pg_proc proc;
List *querytree_list;
bool isnull;
Datum tmp;
char *prosrc;
ErrorContextCallback sqlerrcontext;
bool haspolyarg;
int i;
cqContext *pcqCtx;
if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
PG_RETURN_VOID();
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_proc "
" WHERE oid = :1 ",
ObjectIdGetDatum(funcoid)));
tuple = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for function %u", funcoid);
proc = (Form_pg_proc) GETSTRUCT(tuple);
/* Disallow pseudotype result */
/* except for RECORD, VOID, ANYARRAY, or ANYELEMENT */
if (get_typtype(proc->prorettype) == 'p' &&
proc->prorettype != RECORDOID &&
proc->prorettype != VOIDOID &&
proc->prorettype != ANYARRAYOID &&
proc->prorettype != ANYELEMENTOID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("SQL functions cannot return type %s",
format_type_be(proc->prorettype))));
/* Disallow pseudotypes in arguments */
/* except for ANYARRAY or ANYELEMENT */
haspolyarg = false;
for (i = 0; i < proc->pronargs; i++)
{
if (get_typtype(proc->proargtypes.values[i]) == 'p')
{
if (proc->proargtypes.values[i] == ANYARRAYOID ||
proc->proargtypes.values[i] == ANYELEMENTOID)
haspolyarg = true;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("SQL functions cannot have arguments of type %s",
format_type_be(proc->proargtypes.values[i]))));
}
}
/* Postpone body checks if !check_function_bodies */
if (check_function_bodies)
{
tmp = caql_getattr(pcqCtx, Anum_pg_proc_prosrc, &isnull);
if (isnull)
elog(ERROR, "null prosrc");
prosrc = TextDatumGetCString(tmp);
/*
* Setup error traceback support for ereport().
*/
sqlerrcontext.callback = sql_function_parse_error_callback;
sqlerrcontext.arg = tuple;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
/*
* We can't do full prechecking of the function definition if there
* are any polymorphic input types, because actual datatypes of
* expression results will be unresolvable. The check will be done at
* runtime instead.
*
* We can run the text through the raw parser though; this will at
* least catch silly syntactic errors.
*/
if (!haspolyarg)
{
querytree_list = pg_parse_and_rewrite(prosrc,
proc->proargtypes.values,
proc->pronargs);
(void) check_sql_fn_retval(funcoid, proc->prorettype,
querytree_list, NULL);
}
else
querytree_list = pg_parse_query(prosrc);
error_context_stack = sqlerrcontext.previous;
}
caql_endscan(pcqCtx);
PG_RETURN_VOID();
}
/*
* Error context callback for handling errors in SQL function definitions
*/
static void
sql_function_parse_error_callback(void *arg)
{
HeapTuple tuple = (HeapTuple) arg;
Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(tuple);
bool isnull;
Datum tmp;
char *prosrc;
/* See if it's a syntax error; if so, transpose to CREATE FUNCTION */
tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
if (isnull)
elog(ERROR, "null prosrc");
prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
if (!function_parse_error_transpose(prosrc))
{
/* If it's not a syntax error, push info onto context stack */
errcontext("SQL function \"%s\"", NameStr(proc->proname));
}
pfree(prosrc);
}
/*
* Adjust a syntax error occurring inside the function body of a CREATE
* FUNCTION command. This can be used by any function validator, not only
* for SQL-language functions. It is assumed that the syntax error position
* is initially relative to the function body string (as passed in). If
* possible, we adjust the position to reference the original CREATE command;
* if we can't manage that, we set up an "internal query" syntax error instead.
*
* Returns true if a syntax error was processed, false if not.
*/
bool
function_parse_error_transpose(const char *prosrc)
{
int origerrposition;
int newerrposition;
const char *queryText;
/*
* Nothing to do unless we are dealing with a syntax error that has a
* cursor position.
*
* Some PLs may prefer to report the error position as an internal error
* to begin with, so check that too.
*/
origerrposition = geterrposition();
if (origerrposition <= 0)
{
origerrposition = getinternalerrposition();
if (origerrposition <= 0)
return false;
}
/* We can get the original query text from the active portal (hack...) */
/* Assert(ActivePortal && PortalGetStatus(ActivePortal) == PORTAL_ACTIVE); */
queryText = ActivePortal->sourceText;
/* Try to locate the prosrc in the original text */
newerrposition = match_prosrc_to_query(prosrc, queryText, origerrposition);
if (newerrposition > 0)
{
/* Successful, so fix error position to reference original query */
errposition(newerrposition);
/* Get rid of any report of the error as an "internal query" */
internalerrposition(0);
internalerrquery(NULL);
}
else
{
/*
* If unsuccessful, convert the position to an internal position
* marker and give the function text as the internal query.
*/
errposition(0);
internalerrposition(origerrposition);
internalerrquery(prosrc);
}
return true;
}
/*
* Try to locate the string literal containing the function body in the
* given text of the CREATE FUNCTION command. If successful, return the
* character (not byte) index within the command corresponding to the
* given character index within the literal. If not successful, return 0.
*/
static int
match_prosrc_to_query(const char *prosrc, const char *queryText,
int cursorpos)
{
/*
* Rather than fully parsing the CREATE FUNCTION command, we just scan the
* command looking for $prosrc$ or 'prosrc'. This could be fooled (though
* not in any very probable scenarios), so fail if we find more than one
* match.
*/
int prosrclen = strlen(prosrc);
int querylen = strlen(queryText);
int matchpos = 0;
int curpos;
int newcursorpos;
for (curpos = 0; curpos < querylen - prosrclen; curpos++)
{
if (queryText[curpos] == '$' &&
strncmp(prosrc, &queryText[curpos + 1], prosrclen) == 0 &&
queryText[curpos + 1 + prosrclen] == '$')
{
/*
* Found a $foo$ match. Since there are no embedded quoting
* characters in a dollar-quoted literal, we don't have to do any
* fancy arithmetic; just offset by the starting position.
*/
if (matchpos)
return 0; /* multiple matches, fail */
matchpos = pg_mbstrlen_with_len(queryText, curpos + 1)
+ cursorpos;
}
else if (queryText[curpos] == '\'' &&
match_prosrc_to_literal(prosrc, &queryText[curpos + 1],
cursorpos, &newcursorpos))
{
/*
* Found a 'foo' match. match_prosrc_to_literal() has adjusted
* for any quotes or backslashes embedded in the literal.
*/
if (matchpos)
return 0; /* multiple matches, fail */
matchpos = pg_mbstrlen_with_len(queryText, curpos + 1)
+ newcursorpos;
}
}
return matchpos;
}
/*
* Try to match the given source text to a single-quoted literal.
* If successful, adjust newcursorpos to correspond to the character
* (not byte) index corresponding to cursorpos in the source text.
*
* At entry, literal points just past a ' character. We must check for the
* trailing quote.
*/
static bool
match_prosrc_to_literal(const char *prosrc, const char *literal,
int cursorpos, int *newcursorpos)
{
int newcp = cursorpos;
int chlen;
/*
* This implementation handles backslashes and doubled quotes in the
* string literal. It does not handle the SQL syntax for literals
* continued across line boundaries.
*
* We do the comparison a character at a time, not a byte at a time, so
* that we can do the correct cursorpos math.
*/
while (*prosrc)
{
cursorpos--; /* characters left before cursor */
/*
* Check for backslashes and doubled quotes in the literal; adjust
* newcp when one is found before the cursor.
*/
if (*literal == '\\')
{
literal++;
if (cursorpos > 0)
newcp++;
}
else if (*literal == '\'')
{
if (literal[1] != '\'')
goto fail;
literal++;
if (cursorpos > 0)
newcp++;
}
chlen = pg_mblen(prosrc);
if (strncmp(prosrc, literal, chlen) != 0)
goto fail;
prosrc += chlen;
literal += chlen;
}
if (*literal == '\'' && literal[1] != '\'')
{
/* success */
*newcursorpos = newcp;
return true;
}
fail:
/* Must set *newcursorpos to suppress compiler warning */
*newcursorpos = newcp;
return false;
}