postgresql/src/backend/catalog/pg_proc.c

756 lines
21 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* pg_proc.c
* routines to support manipulation of the pg_proc relation
*
* Portions Copyright (c) 1996-2005, 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.122 2004/12/31 21:59:38 pgsql Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/heapam.h"
#include "catalog/catname.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "executor/functions.h"
#include "miscadmin.h"
#include "mb/pg_wchar.h"
1999-07-16 07:00:38 +02:00
#include "parser/parse_type.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
/* GUC parameter */
bool check_function_bodies = true;
2002-09-04 22:31:48 +02:00
Datum fmgr_internal_validator(PG_FUNCTION_ARGS);
Datum fmgr_c_validator(PG_FUNCTION_ARGS);
Datum fmgr_sql_validator(PG_FUNCTION_ARGS);
static Datum create_parameternames_array(int parameterCount,
2004-08-29 07:07:03 +02:00
const char *parameterNames[]);
static void sql_function_parse_error_callback(void *arg);
2004-08-29 07:07:03 +02:00
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,
2004-08-29 07:07:03 +02:00
int cursorpos, int *newcursorpos);
/* ----------------------------------------------------------------
* ProcedureCreate
* ----------------------------------------------------------------
*/
Oid
ProcedureCreate(const char *procedureName,
Oid procNamespace,
bool replace,
bool returnsSet,
Oid returnType,
Oid languageObjectId,
Oid languageValidator,
const char *prosrc,
const char *probin,
bool isAgg,
bool security_definer,
bool isStrict,
char volatility,
int parameterCount,
const Oid *parameterTypes,
const char *parameterNames[])
{
int i;
Relation rel;
HeapTuple tup;
HeapTuple oldtup;
char nulls[Natts_pg_proc];
Datum values[Natts_pg_proc];
char replaces[Natts_pg_proc];
Oid typev[FUNC_MAX_ARGS];
Datum namesarray;
Oid relid;
NameData procname;
TupleDesc tupDesc;
Oid retval;
bool is_update;
2002-09-04 22:31:48 +02:00
ObjectAddress myself,
referenced;
/*
* sanity checks
*/
Assert(PointerIsValid(prosrc));
Assert(PointerIsValid(probin));
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)));
/*
* Do not allow return type ANYARRAY or ANYELEMENT unless at least one
* argument is also ANYARRAY or ANYELEMENT
*/
if (returnType == ANYARRAYOID || returnType == ANYELEMENTOID)
{
2003-08-04 02:43:34 +02:00
bool genericParam = false;
for (i = 0; i < parameterCount; i++)
{
if (parameterTypes[i] == ANYARRAYOID ||
parameterTypes[i] == ANYELEMENTOID)
{
genericParam = true;
break;
}
}
if (!genericParam)
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.")));
}
/* Make sure we have a zero-padded param type array */
MemSet(typev, 0, FUNC_MAX_ARGS * sizeof(Oid));
if (parameterCount > 0)
memcpy(typev, parameterTypes, parameterCount * sizeof(Oid));
/* Process param names, if given */
namesarray = create_parameternames_array(parameterCount, parameterNames);
/*
* don't allow functions of complex types that have the same name as
* existing attributes of the type
*/
if (parameterCount == 1 && OidIsValid(typev[0]) &&
(relid = typeidTypeRelid(typev[0])) != InvalidOid &&
2004-10-18 03:45:38 +02:00
get_attnum(relid, procedureName) != InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("\"%s\" is already an attribute of type %s",
procedureName, format_type_be(typev[0]))));
/*
* All seems OK; prepare the data to be inserted into pg_proc.
*/
for (i = 0; i < Natts_pg_proc; ++i)
{
nulls[i] = ' ';
values[i] = (Datum) NULL;
replaces[i] = 'r';
}
i = 0;
namestrcpy(&procname, procedureName);
values[i++] = NameGetDatum(&procname); /* proname */
2002-09-04 22:31:48 +02:00
values[i++] = ObjectIdGetDatum(procNamespace); /* pronamespace */
values[i++] = Int32GetDatum(GetUserId()); /* proowner */
2002-09-04 22:31:48 +02:00
values[i++] = ObjectIdGetDatum(languageObjectId); /* prolang */
values[i++] = BoolGetDatum(isAgg); /* proisagg */
values[i++] = BoolGetDatum(security_definer); /* prosecdef */
values[i++] = BoolGetDatum(isStrict); /* proisstrict */
values[i++] = BoolGetDatum(returnsSet); /* proretset */
values[i++] = CharGetDatum(volatility); /* provolatile */
2002-09-04 22:31:48 +02:00
values[i++] = UInt16GetDatum(parameterCount); /* pronargs */
values[i++] = ObjectIdGetDatum(returnType); /* prorettype */
values[i++] = PointerGetDatum(typev); /* proargtypes */
2004-08-29 07:07:03 +02:00
values[i++] = namesarray; /* proargnames */
if (namesarray == PointerGetDatum(NULL))
nulls[Anum_pg_proc_proargnames - 1] = 'n';
values[i++] = DirectFunctionCall1(textin, /* prosrc */
CStringGetDatum(prosrc));
values[i++] = DirectFunctionCall1(textin, /* probin */
CStringGetDatum(probin));
/* proacl will be handled below */
rel = heap_openr(ProcedureRelationName, RowExclusiveLock);
tupDesc = rel->rd_att;
/* Check for pre-existing definition */
oldtup = SearchSysCache(PROCNAMENSP,
PointerGetDatum(procedureName),
UInt16GetDatum(parameterCount),
PointerGetDatum(typev),
ObjectIdGetDatum(procNamespace));
if (HeapTupleIsValid(oldtup))
{
/* There is one; okay to replace it? */
Form_pg_proc oldproc = (Form_pg_proc) GETSTRUCT(oldtup);
if (!replace)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_FUNCTION),
errmsg("function \"%s\" already exists with same argument types",
procedureName)));
if (GetUserId() != oldproc->proowner && !superuser())
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),
2003-08-04 02:43:34 +02:00
errmsg("cannot change return type of existing function"),
errhint("Use DROP FUNCTION first.")));
/* Can't change aggregate status, either */
if (oldproc->proisagg != isAgg)
{
if (oldproc->proisagg)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("function \"%s\" is an aggregate",
procedureName)));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("function \"%s\" is not an aggregate",
procedureName)));
}
2002-05-22 20:33:15 +02:00
/* do not change existing ownership or permissions, either */
2002-09-04 22:31:48 +02:00
replaces[Anum_pg_proc_proowner - 1] = ' ';
replaces[Anum_pg_proc_proacl - 1] = ' ';
/* Okay, do it... */
tup = heap_modifytuple(oldtup, rel, values, nulls, replaces);
simple_heap_update(rel, &tup->t_self, tup);
ReleaseSysCache(oldtup);
is_update = true;
}
else
{
/* Creating a new procedure */
/* start out with empty permissions */
2002-09-04 22:31:48 +02:00
nulls[Anum_pg_proc_proacl - 1] = 'n';
tup = heap_formtuple(tupDesc, values, nulls);
simple_heap_insert(rel, tup);
is_update = false;
}
/* Need to update indexes for either the insert or update case */
CatalogUpdateIndexes(rel, tup);
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(RelOid_pg_proc, retval);
myself.classId = RelOid_pg_proc;
myself.objectId = retval;
myself.objectSubId = 0;
/* dependency on namespace */
referenced.classId = get_system_catalog_relid(NamespaceRelationName);
referenced.objectId = procNamespace;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
/* dependency on implementation language */
referenced.classId = get_system_catalog_relid(LanguageRelationName);
referenced.objectId = languageObjectId;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
/* dependency on return type */
referenced.classId = RelOid_pg_type;
referenced.objectId = returnType;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
/* dependency on input types */
for (i = 0; i < parameterCount; i++)
{
referenced.classId = RelOid_pg_type;
referenced.objectId = typev[i];
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
heap_freetuple(tup);
heap_close(rel, RowExclusiveLock);
/* Verify function body */
if (OidIsValid(languageValidator))
{
2002-05-22 20:33:15 +02:00
/* Advance command counter so new tuple can be seen by validator */
CommandCounterIncrement();
2002-05-22 20:33:15 +02:00
OidFunctionCall1(languageValidator, ObjectIdGetDatum(retval));
}
return retval;
}
/*
* create_parameternames_array - build proargnames value from an array
* of C strings. Returns a NULL pointer if no names provided.
*/
static Datum
create_parameternames_array(int parameterCount, const char *parameterNames[])
{
Datum elems[FUNC_MAX_ARGS];
bool found = false;
ArrayType *names;
int i;
if (!parameterNames)
return PointerGetDatum(NULL);
2004-08-29 07:07:03 +02:00
for (i = 0; i < parameterCount; i++)
{
const char *s = parameterNames[i];
if (s && *s)
found = true;
else
s = "";
elems[i] = DirectFunctionCall1(textin, CStringGetDatum(s));
}
if (!found)
return PointerGetDatum(NULL);
names = construct_array(elems, parameterCount, TEXTOID, -1, false, 'i');
return PointerGetDatum(names);
}
/*
* 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);
HeapTuple tuple;
Form_pg_proc proc;
bool isnull;
Datum tmp;
char *prosrc;
/*
* We do not honor check_function_bodies since it's unlikely the
* function name will be found later if it isn't there now.
*/
tuple = SearchSysCache(PROCOID,
ObjectIdGetDatum(funcoid),
0, 0, 0);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for function %u", funcoid);
proc = (Form_pg_proc) GETSTRUCT(tuple);
tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
if (isnull)
elog(ERROR, "null prosrc");
prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
if (fmgr_internal_function(prosrc) == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("there is no built-in function named \"%s\"",
prosrc)));
ReleaseSysCache(tuple);
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;
Form_pg_proc proc;
bool isnull;
Datum tmp;
char *prosrc;
char *probin;
/*
2004-08-29 07:07:03 +02:00
* 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.
*/
tuple = SearchSysCache(PROCOID,
ObjectIdGetDatum(funcoid),
0, 0, 0);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for function %u", funcoid);
proc = (Form_pg_proc) GETSTRUCT(tuple);
tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
if (isnull)
elog(ERROR, "null prosrc");
prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_probin, &isnull);
if (isnull)
elog(ERROR, "null probin");
probin = DatumGetCString(DirectFunctionCall1(textout, tmp));
2002-09-04 22:31:48 +02:00
(void) load_external_function(probin, prosrc, true, &libraryhandle);
(void) fetch_finfo_record(libraryhandle, prosrc);
ReleaseSysCache(tuple);
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;
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
char functyptype;
bool haspolyarg;
int i;
tuple = SearchSysCache(PROCOID,
ObjectIdGetDatum(funcoid),
0, 0, 0);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for function %u", funcoid);
proc = (Form_pg_proc) GETSTRUCT(tuple);
functyptype = get_typtype(proc->prorettype);
/* Disallow pseudotype result */
/* except for RECORD, VOID, ANYARRAY, or ANYELEMENT */
if (functyptype == '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[i]) == 'p')
{
if (proc->proargtypes[i] == ANYARRAYOID ||
proc->proargtypes[i] == ANYELEMENTOID)
haspolyarg = true;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
2003-08-04 02:43:34 +02:00
errmsg("SQL functions cannot have arguments of type %s",
format_type_be(proc->proargtypes[i]))));
}
}
/* Postpone body checks if !check_function_bodies */
if (check_function_bodies)
{
tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
if (isnull)
elog(ERROR, "null prosrc");
prosrc = DatumGetCString(DirectFunctionCall1(textout, 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;
/*
2004-08-29 07:07:03 +02:00
* 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,
proc->pronargs);
(void) check_sql_fn_retval(proc->prorettype, functyptype,
querytree_list, NULL);
}
else
querytree_list = pg_parse_query(prosrc);
error_context_stack = sqlerrcontext.previous;
}
ReleaseSysCache(tuple);
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
2004-08-29 07:07:03 +02:00
* 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;
/*
2004-08-29 07:07:03 +02:00
* Nothing to do unless we are dealing with a syntax error that has a
* cursor position.
*
2004-08-29 07:07:03 +02:00
* 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 && ActivePortal->status == 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.
*/
2004-08-29 07:07:03 +02:00
int prosrclen = strlen(prosrc);
int querylen = strlen(queryText);
int matchpos = 0;
int curpos;
int newcursorpos;
2004-08-29 07:07:03 +02:00
for (curpos = 0; curpos < querylen - prosrclen; curpos++)
{
if (queryText[curpos] == '$' &&
2004-08-29 07:07:03 +02:00
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 */
2004-08-29 07:07:03 +02:00
matchpos = pg_mbstrlen_with_len(queryText, curpos + 1)
+ cursorpos;
}
else if (queryText[curpos] == '\'' &&
2004-08-29 07:07:03 +02:00
match_prosrc_to_literal(prosrc, &queryText[curpos + 1],
cursorpos, &newcursorpos))
{
/*
2004-08-29 07:07:03 +02:00
* 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 */
2004-08-29 07:07:03 +02:00
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
2004-08-29 07:07:03 +02:00
* string literal. It does not handle the SQL syntax for literals
* continued across line boundaries.
*
2004-08-29 07:07:03 +02:00
* 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 */
2004-08-29 07:07:03 +02:00
/*
* 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] != '\'')
return false;
literal++;
if (cursorpos > 0)
newcp++;
}
chlen = pg_mblen(prosrc);
if (strncmp(prosrc, literal, chlen) != 0)
return false;
prosrc += chlen;
literal += chlen;
}
*newcursorpos = newcp;
if (*literal == '\'' && literal[1] != '\'')
return true;
return false;
}