1 /*-------------------------------------------------------------------------
2  *
3  * deparse_function_stmts.c
4  *
5  *	  All routines to deparse function and procedure statements.
6  *	  This file contains all entry points specific for function and procedure statement
7  *    deparsing
8  *
9  *	  Functions that could move later are AppendDefElem, AppendDefElemStrict, etc. These
10  *	  should be reused across multiple statements and should live in their own deparse
11  *	  file.
12  *
13  * Copyright (c), Citus Data, Inc.
14  *
15  *-------------------------------------------------------------------------
16  */
17 
18 #include "postgres.h"
19 
20 #include "access/htup_details.h"
21 #include "catalog/namespace.h"
22 #include "catalog/pg_proc.h"
23 #include "catalog/pg_type.h"
24 #include "commands/defrem.h"
25 #include "distributed/citus_ruleutils.h"
26 #include "distributed/commands.h"
27 #include "distributed/deparser.h"
28 #include "distributed/version_compat.h"
29 #include "lib/stringinfo.h"
30 #include "nodes/makefuncs.h"
31 #include "nodes/nodes.h"
32 #include "nodes/value.h"
33 #include "parser/parse_func.h"
34 #include "parser/parse_type.h"
35 #include "utils/builtins.h"
36 #include "utils/fmgroids.h"
37 #include "utils/fmgrprotos.h"
38 #include "utils/guc.h"
39 #include "utils/lsyscache.h"
40 #include "utils/memutils.h"
41 #include "utils/syscache.h"
42 #include "utils/regproc.h"
43 
44 
45 /* forward declaration for deparse functions */
46 static char * ObjectTypeToKeyword(ObjectType objtype);
47 
48 static void AppendAlterFunctionStmt(StringInfo buf, AlterFunctionStmt *stmt);
49 static void AppendDropFunctionStmt(StringInfo buf, DropStmt *stmt);
50 static void AppendFunctionName(StringInfo buf, ObjectWithArgs *func, ObjectType objtype);
51 static void AppendFunctionNameList(StringInfo buf, List *objects, ObjectType objtype);
52 
53 static void AppendDefElem(StringInfo buf, DefElem *def);
54 static void AppendDefElemStrict(StringInfo buf, DefElem *def);
55 static void AppendDefElemVolatility(StringInfo buf, DefElem *def);
56 static void AppendDefElemLeakproof(StringInfo buf, DefElem *def);
57 static void AppendDefElemSecurity(StringInfo buf, DefElem *def);
58 static void AppendDefElemParallel(StringInfo buf, DefElem *def);
59 static void AppendDefElemCost(StringInfo buf, DefElem *def);
60 static void AppendDefElemRows(StringInfo buf, DefElem *def);
61 static void AppendDefElemSet(StringInfo buf, DefElem *def);
62 
63 static void AppendVarSetValue(StringInfo buf, VariableSetStmt *setStmt);
64 static void AppendRenameFunctionStmt(StringInfo buf, RenameStmt *stmt);
65 static void AppendAlterFunctionSchemaStmt(StringInfo buf, AlterObjectSchemaStmt *stmt);
66 static void AppendAlterFunctionOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt);
67 static void AppendAlterFunctionDependsStmt(StringInfo buf, AlterObjectDependsStmt *stmt);
68 
69 static char * CopyAndConvertToUpperCase(const char *str);
70 
71 /*
72  * DeparseAlterFunctionStmt builds and returns a string representing the AlterFunctionStmt
73  */
74 char *
DeparseAlterFunctionStmt(Node * node)75 DeparseAlterFunctionStmt(Node *node)
76 {
77 	AlterFunctionStmt *stmt = castNode(AlterFunctionStmt, node);
78 	StringInfoData str = { 0 };
79 	initStringInfo(&str);
80 
81 	AppendAlterFunctionStmt(&str, stmt);
82 
83 	return str.data;
84 }
85 
86 
87 /*
88  * ObjectTypeToKeyword returns an appropriate string for the given ObjectType
89  * Where the string will be one of "FUNCTION", "PROCEDURE", or "AGGREGATE"
90  */
91 static char *
ObjectTypeToKeyword(ObjectType objtype)92 ObjectTypeToKeyword(ObjectType objtype)
93 {
94 	switch (objtype)
95 	{
96 		case OBJECT_FUNCTION:
97 		{
98 			return "FUNCTION";
99 		}
100 
101 		case OBJECT_PROCEDURE:
102 		{
103 			return "PROCEDURE";
104 		}
105 
106 		case OBJECT_AGGREGATE:
107 		{
108 			return "AGGREGATE";
109 		}
110 
111 		case OBJECT_ROUTINE:
112 		{
113 			return "ROUTINE";
114 		}
115 
116 		default:
117 			elog(ERROR, "Unknown object type: %d", objtype);
118 			return NULL;
119 	}
120 }
121 
122 
123 /*
124  * AppendAlterFunctionStmt appends a string representing the AlterFunctionStmt to a buffer
125  */
126 static void
AppendAlterFunctionStmt(StringInfo buf,AlterFunctionStmt * stmt)127 AppendAlterFunctionStmt(StringInfo buf, AlterFunctionStmt *stmt)
128 {
129 	ListCell *actionCell = NULL;
130 
131 	appendStringInfo(buf, "ALTER %s ", ObjectTypeToKeyword(stmt->objtype));
132 	AppendFunctionName(buf, stmt->func, stmt->objtype);
133 
134 	foreach(actionCell, stmt->actions)
135 	{
136 		DefElem *def = castNode(DefElem, lfirst(actionCell));
137 		AppendDefElem(buf, def);
138 	}
139 
140 	appendStringInfoString(buf, ";");
141 }
142 
143 
144 /*
145  * AppendDefElem appends a string representing the DefElem to a buffer
146  */
147 static void
AppendDefElem(StringInfo buf,DefElem * def)148 AppendDefElem(StringInfo buf, DefElem *def)
149 {
150 	if (strcmp(def->defname, "strict") == 0)
151 	{
152 		AppendDefElemStrict(buf, def);
153 	}
154 	else if (strcmp(def->defname, "volatility") == 0)
155 	{
156 		AppendDefElemVolatility(buf, def);
157 	}
158 	else if (strcmp(def->defname, "leakproof") == 0)
159 	{
160 		AppendDefElemLeakproof(buf, def);
161 	}
162 	else if (strcmp(def->defname, "security") == 0)
163 	{
164 		AppendDefElemSecurity(buf, def);
165 	}
166 	else if (strcmp(def->defname, "parallel") == 0)
167 	{
168 		AppendDefElemParallel(buf, def);
169 	}
170 	else if (strcmp(def->defname, "cost") == 0)
171 	{
172 		AppendDefElemCost(buf, def);
173 	}
174 	else if (strcmp(def->defname, "rows") == 0)
175 	{
176 		AppendDefElemRows(buf, def);
177 	}
178 	else if (strcmp(def->defname, "set") == 0)
179 	{
180 		AppendDefElemSet(buf, def);
181 	}
182 }
183 
184 
185 /*
186  * AppendDefElemStrict appends a string representing the DefElem to a buffer
187  */
188 static void
AppendDefElemStrict(StringInfo buf,DefElem * def)189 AppendDefElemStrict(StringInfo buf, DefElem *def)
190 {
191 	if (intVal(def->arg) == 1)
192 	{
193 		appendStringInfo(buf, " STRICT");
194 	}
195 	else
196 	{
197 		appendStringInfo(buf, " CALLED ON NULL INPUT");
198 	}
199 }
200 
201 
202 /*
203  * AppendDefElemVolatility appends a string representing the DefElem to a buffer
204  */
205 static void
AppendDefElemVolatility(StringInfo buf,DefElem * def)206 AppendDefElemVolatility(StringInfo buf, DefElem *def)
207 {
208 	appendStringInfo(buf, " %s", CopyAndConvertToUpperCase(strVal(def->arg)));
209 }
210 
211 
212 /*
213  * AppendDefElemLeakproof appends a string representing the DefElem to a buffer
214  */
215 static void
AppendDefElemLeakproof(StringInfo buf,DefElem * def)216 AppendDefElemLeakproof(StringInfo buf, DefElem *def)
217 {
218 	if (intVal(def->arg) == 0)
219 	{
220 		appendStringInfo(buf, " NOT");
221 	}
222 	appendStringInfo(buf, " LEAKPROOF");
223 }
224 
225 
226 /*
227  * AppendDefElemSecurity appends a string representing the DefElem to a buffer
228  */
229 static void
AppendDefElemSecurity(StringInfo buf,DefElem * def)230 AppendDefElemSecurity(StringInfo buf, DefElem *def)
231 {
232 	if (intVal(def->arg) == 0)
233 	{
234 		appendStringInfo(buf, " SECURITY INVOKER");
235 	}
236 	else
237 	{
238 		appendStringInfo(buf, " SECURITY DEFINER");
239 	}
240 }
241 
242 
243 /*
244  * AppendDefElemParallel appends a string representing the DefElem to a buffer
245  */
246 static void
AppendDefElemParallel(StringInfo buf,DefElem * def)247 AppendDefElemParallel(StringInfo buf, DefElem *def)
248 {
249 	appendStringInfo(buf, " PARALLEL %s", CopyAndConvertToUpperCase(strVal(def->arg)));
250 }
251 
252 
253 /*
254  * AppendDefElemCost appends a string representing the DefElem to a buffer
255  */
256 static void
AppendDefElemCost(StringInfo buf,DefElem * def)257 AppendDefElemCost(StringInfo buf, DefElem *def)
258 {
259 	appendStringInfo(buf, " COST %lf", defGetNumeric(def));
260 }
261 
262 
263 /*
264  * AppendDefElemRows appends a string representing the DefElem to a buffer
265  */
266 static void
AppendDefElemRows(StringInfo buf,DefElem * def)267 AppendDefElemRows(StringInfo buf, DefElem *def)
268 {
269 	appendStringInfo(buf, " ROWS %lf", defGetNumeric(def));
270 }
271 
272 
273 /*
274  * AppendDefElemSet appends a string representing the DefElem to a buffer
275  */
276 static void
AppendDefElemSet(StringInfo buf,DefElem * def)277 AppendDefElemSet(StringInfo buf, DefElem *def)
278 {
279 	VariableSetStmt *setStmt = castNode(VariableSetStmt, def->arg);
280 
281 	AppendVariableSet(buf, setStmt);
282 }
283 
284 
285 /*
286  * AppendVariableSet appends a string representing the VariableSetStmt to a buffer
287  */
288 void
AppendVariableSet(StringInfo buf,VariableSetStmt * setStmt)289 AppendVariableSet(StringInfo buf, VariableSetStmt *setStmt)
290 {
291 	switch (setStmt->kind)
292 	{
293 		case VAR_SET_VALUE:
294 		{
295 			AppendVarSetValue(buf, setStmt);
296 			break;
297 		}
298 
299 		case VAR_SET_CURRENT:
300 		{
301 			appendStringInfo(buf, " SET %s FROM CURRENT", quote_identifier(
302 								 setStmt->name));
303 			break;
304 		}
305 
306 		case VAR_SET_DEFAULT:
307 		{
308 			appendStringInfo(buf, " SET %s TO DEFAULT", quote_identifier(setStmt->name));
309 			break;
310 		}
311 
312 		case VAR_RESET:
313 		{
314 			appendStringInfo(buf, " RESET %s", quote_identifier(setStmt->name));
315 			break;
316 		}
317 
318 		case VAR_RESET_ALL:
319 		{
320 			appendStringInfoString(buf, " RESET ALL");
321 			break;
322 		}
323 
324 		/* VAR_SET_MULTI is a special case for SET TRANSACTION that should not occur here */
325 		case VAR_SET_MULTI:
326 		default:
327 		{
328 			ereport(ERROR, (errmsg("Unable to deparse SET statement")));
329 			break;
330 		}
331 	}
332 }
333 
334 
335 /*
336  * AppendVarSetValue deparses a VariableSetStmt with VAR_SET_VALUE kind.
337  * It takes from flatten_set_variable_args in postgres's utils/misc/guc.c,
338  * however flatten_set_variable_args does not apply correct quoting.
339  */
340 static void
AppendVarSetValue(StringInfo buf,VariableSetStmt * setStmt)341 AppendVarSetValue(StringInfo buf, VariableSetStmt *setStmt)
342 {
343 	ListCell *varArgCell = NULL;
344 	ListCell *firstCell = list_head(setStmt->args);
345 
346 	Assert(setStmt->kind == VAR_SET_VALUE);
347 
348 	foreach(varArgCell, setStmt->args)
349 	{
350 		Node *varArgNode = lfirst(varArgCell);
351 		A_Const *varArgConst = NULL;
352 		TypeName *typeName = NULL;
353 
354 		if (IsA(varArgNode, A_Const))
355 		{
356 			varArgConst = (A_Const *) varArgNode;
357 		}
358 		else if (IsA(varArgNode, TypeCast))
359 		{
360 			TypeCast *varArgTypeCast = (TypeCast *) varArgNode;
361 
362 			varArgConst = castNode(A_Const, varArgTypeCast->arg);
363 			typeName = varArgTypeCast->typeName;
364 		}
365 		else
366 		{
367 			elog(ERROR, "unrecognized node type: %d", varArgNode->type);
368 		}
369 
370 		/* don't know how to start SET until we inspect first arg */
371 		if (varArgCell != firstCell)
372 		{
373 			appendStringInfoChar(buf, ',');
374 		}
375 		else if (typeName != NULL)
376 		{
377 			appendStringInfoString(buf, " SET TIME ZONE");
378 		}
379 		else
380 		{
381 			appendStringInfo(buf, " SET %s =", quote_identifier(setStmt->name));
382 		}
383 
384 		Value value = varArgConst->val;
385 		switch (value.type)
386 		{
387 			case T_Integer:
388 			{
389 				appendStringInfo(buf, " %d", intVal(&value));
390 				break;
391 			}
392 
393 			case T_Float:
394 			{
395 				appendStringInfo(buf, " %s", strVal(&value));
396 				break;
397 			}
398 
399 			case T_String:
400 			{
401 				if (typeName != NULL)
402 				{
403 					/*
404 					 * Must be a ConstInterval argument for TIME ZONE. Coerce
405 					 * to interval and back to normalize the value and account
406 					 * for any typmod.
407 					 */
408 					Oid typoid = InvalidOid;
409 					int32 typmod = -1;
410 
411 					typenameTypeIdAndMod(NULL, typeName, &typoid, &typmod);
412 					Assert(typoid == INTERVALOID);
413 
414 					Datum interval =
415 						DirectFunctionCall3(interval_in,
416 											CStringGetDatum(strVal(&value)),
417 											ObjectIdGetDatum(InvalidOid),
418 											Int32GetDatum(typmod));
419 
420 					char *intervalout =
421 						DatumGetCString(DirectFunctionCall1(interval_out,
422 															interval));
423 					appendStringInfo(buf, " INTERVAL '%s'", intervalout);
424 				}
425 				else
426 				{
427 					appendStringInfo(buf, " %s", quote_literal_cstr(strVal(
428 																		&value)));
429 				}
430 				break;
431 			}
432 
433 			default:
434 			{
435 				elog(ERROR, "Unexpected Value type in VAR_SET_VALUE arguments.");
436 				break;
437 			}
438 		}
439 	}
440 }
441 
442 
443 /*
444  * DeparseRenameFunctionStmt builds and returns a string representing the RenameStmt
445  */
446 char *
DeparseRenameFunctionStmt(Node * node)447 DeparseRenameFunctionStmt(Node *node)
448 {
449 	RenameStmt *stmt = castNode(RenameStmt, node);
450 	StringInfoData str = { 0 };
451 	initStringInfo(&str);
452 
453 	AssertObjectTypeIsFunctional(stmt->renameType);
454 
455 	AppendRenameFunctionStmt(&str, stmt);
456 
457 	return str.data;
458 }
459 
460 
461 /*
462  * AppendRenameFunctionStmt appends a string representing the RenameStmt to a buffer
463  */
464 static void
AppendRenameFunctionStmt(StringInfo buf,RenameStmt * stmt)465 AppendRenameFunctionStmt(StringInfo buf, RenameStmt *stmt)
466 {
467 	ObjectWithArgs *func = castNode(ObjectWithArgs, stmt->object);
468 
469 	appendStringInfo(buf, "ALTER %s ", ObjectTypeToKeyword(stmt->renameType));
470 	AppendFunctionName(buf, func, stmt->renameType);
471 	appendStringInfo(buf, " RENAME TO %s;", quote_identifier(stmt->newname));
472 }
473 
474 
475 /*
476  * DeparseAlterFunctionSchemaStmt builds and returns a string representing the AlterObjectSchemaStmt
477  */
478 char *
DeparseAlterFunctionSchemaStmt(Node * node)479 DeparseAlterFunctionSchemaStmt(Node *node)
480 {
481 	AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
482 	StringInfoData str = { 0 };
483 	initStringInfo(&str);
484 
485 	AssertObjectTypeIsFunctional(stmt->objectType);
486 
487 	AppendAlterFunctionSchemaStmt(&str, stmt);
488 
489 	return str.data;
490 }
491 
492 
493 /*
494  * AppendAlterFunctionSchemaStmt appends a string representing the AlterObjectSchemaStmt to a buffer
495  */
496 static void
AppendAlterFunctionSchemaStmt(StringInfo buf,AlterObjectSchemaStmt * stmt)497 AppendAlterFunctionSchemaStmt(StringInfo buf, AlterObjectSchemaStmt *stmt)
498 {
499 	ObjectWithArgs *func = castNode(ObjectWithArgs, stmt->object);
500 
501 	appendStringInfo(buf, "ALTER %s ", ObjectTypeToKeyword(stmt->objectType));
502 	AppendFunctionName(buf, func, stmt->objectType);
503 	appendStringInfo(buf, " SET SCHEMA %s;", quote_identifier(stmt->newschema));
504 }
505 
506 
507 /*
508  * DeparseAlterFunctionOwnerStmt builds and returns a string representing the AlterOwnerStmt
509  */
510 char *
DeparseAlterFunctionOwnerStmt(Node * node)511 DeparseAlterFunctionOwnerStmt(Node *node)
512 {
513 	AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
514 	StringInfoData str = { 0 };
515 	initStringInfo(&str);
516 
517 	AssertObjectTypeIsFunctional(stmt->objectType);
518 
519 	AppendAlterFunctionOwnerStmt(&str, stmt);
520 
521 	return str.data;
522 }
523 
524 
525 /*
526  * AppendAlterFunctionOwnerStmt appends a string representing the AlterOwnerStmt to a buffer
527  */
528 static void
AppendAlterFunctionOwnerStmt(StringInfo buf,AlterOwnerStmt * stmt)529 AppendAlterFunctionOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt)
530 {
531 	ObjectWithArgs *func = castNode(ObjectWithArgs, stmt->object);
532 
533 	appendStringInfo(buf, "ALTER %s ", ObjectTypeToKeyword(stmt->objectType));
534 	AppendFunctionName(buf, func, stmt->objectType);
535 	appendStringInfo(buf, " OWNER TO %s;", RoleSpecString(stmt->newowner, true));
536 }
537 
538 
539 /*
540  * DeparseAlterFunctionDependsStmt builds and returns a string representing the AlterObjectDependsStmt
541  */
542 char *
DeparseAlterFunctionDependsStmt(Node * node)543 DeparseAlterFunctionDependsStmt(Node *node)
544 {
545 	AlterObjectDependsStmt *stmt = castNode(AlterObjectDependsStmt, node);
546 	StringInfoData str = { 0 };
547 	initStringInfo(&str);
548 
549 	AssertObjectTypeIsFunctional(stmt->objectType);
550 
551 	AppendAlterFunctionDependsStmt(&str, stmt);
552 
553 	return str.data;
554 }
555 
556 
557 /*
558  * AppendAlterFunctionDependsStmt appends a string representing the AlterObjectDependsStmt to a buffer
559  */
560 static void
AppendAlterFunctionDependsStmt(StringInfo buf,AlterObjectDependsStmt * stmt)561 AppendAlterFunctionDependsStmt(StringInfo buf, AlterObjectDependsStmt *stmt)
562 {
563 	ObjectWithArgs *func = castNode(ObjectWithArgs, stmt->object);
564 
565 	appendStringInfo(buf, "ALTER %s ", ObjectTypeToKeyword(stmt->objectType));
566 	AppendFunctionName(buf, func, stmt->objectType);
567 	appendStringInfo(buf, " DEPENDS ON EXTENSION %s;", strVal(stmt->extname));
568 }
569 
570 
571 /*
572  * DeparseDropFunctionStmt builds and returns a string representing the DropStmt
573  */
574 char *
DeparseDropFunctionStmt(Node * node)575 DeparseDropFunctionStmt(Node *node)
576 {
577 	DropStmt *stmt = castNode(DropStmt, node);
578 	StringInfoData str = { 0 };
579 	initStringInfo(&str);
580 
581 	AssertObjectTypeIsFunctional(stmt->removeType);
582 
583 	AppendDropFunctionStmt(&str, stmt);
584 
585 	return str.data;
586 }
587 
588 
589 /*
590  * AppendDropFunctionStmt appends a string representing the DropStmt to a buffer
591  */
592 static void
AppendDropFunctionStmt(StringInfo buf,DropStmt * stmt)593 AppendDropFunctionStmt(StringInfo buf, DropStmt *stmt)
594 {
595 	appendStringInfo(buf, "DROP %s ", ObjectTypeToKeyword(stmt->removeType));
596 
597 	if (stmt->missing_ok)
598 	{
599 		appendStringInfoString(buf, "IF EXISTS ");
600 	}
601 
602 	AppendFunctionNameList(buf, stmt->objects, stmt->removeType);
603 
604 	if (stmt->behavior == DROP_CASCADE)
605 	{
606 		appendStringInfoString(buf, " CASCADE");
607 	}
608 
609 	appendStringInfoString(buf, ";");
610 }
611 
612 
613 /*
614  * AppendFunctionNameList appends a string representing the list of function names to a buffer
615  */
616 static void
AppendFunctionNameList(StringInfo buf,List * objects,ObjectType objtype)617 AppendFunctionNameList(StringInfo buf, List *objects, ObjectType objtype)
618 {
619 	ListCell *objectCell = NULL;
620 	foreach(objectCell, objects)
621 	{
622 		Node *object = lfirst(objectCell);
623 
624 		if (objectCell != list_head(objects))
625 		{
626 			appendStringInfo(buf, ", ");
627 		}
628 
629 		ObjectWithArgs *func = castNode(ObjectWithArgs, object);
630 
631 		AppendFunctionName(buf, func, objtype);
632 	}
633 }
634 
635 
636 /*
637  * AppendFunctionName appends a string representing a single function name to a buffer
638  */
639 static void
AppendFunctionName(StringInfo buf,ObjectWithArgs * func,ObjectType objtype)640 AppendFunctionName(StringInfo buf, ObjectWithArgs *func, ObjectType objtype)
641 {
642 	Oid funcid = LookupFuncWithArgs(objtype, func, true);
643 
644 	if (funcid == InvalidOid)
645 	{
646 		/*
647 		 * DROP FUNCTION IF EXISTS absent_function arrives here
648 		 *
649 		 * There is no namespace associated with the nonexistent function,
650 		 * thus we return the function name as it is provided
651 		 */
652 		char *functionName = NULL;
653 		char *schemaName = NULL;
654 
655 		DeconstructQualifiedName(func->objname, &schemaName, &functionName);
656 
657 		char *qualifiedFunctionName = quote_qualified_identifier(schemaName,
658 																 functionName);
659 		appendStringInfoString(buf, qualifiedFunctionName);
660 
661 		if (!func->args_unspecified)
662 		{
663 			/*
664 			 * The function is not found, but there is an argument list specified, this has
665 			 * some known issues with the "any" type. However this is mostly a bug in
666 			 * postgres' TypeNameListToString. For now the best we can do until we understand
667 			 * the underlying cause better.
668 			 */
669 
670 			const char *args = TypeNameListToString(func->objargs);
671 			appendStringInfo(buf, "(%s)", args);
672 		}
673 	}
674 	else
675 	{
676 		char *functionSignature = format_procedure_qualified(funcid);
677 		appendStringInfoString(buf, functionSignature);
678 	}
679 }
680 
681 
682 /*
683  * CopyAndConvertToUpperCase copies a string and converts all characters to uppercase
684  */
685 static char *
CopyAndConvertToUpperCase(const char * str)686 CopyAndConvertToUpperCase(const char *str)
687 {
688 	char *result, *p;
689 
690 	result = pstrdup(str);
691 
692 	for (p = result; *p; p++)
693 	{
694 		*p = pg_toupper((unsigned char) *p);
695 	}
696 
697 	return result;
698 }
699