/*------------------------------------------------------------------------- * * readfuncs.c * Reader functions for Postgres tree nodes. * * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/backend/nodes/readfuncs.c * * NOTES * Parse location fields are written out by outfuncs.c, but only for * debugging use. When reading a location field, we normally discard * the stored value and set the location field to -1 (ie, "unknown"). * This is because nodes coming from a stored rule should not be thought * to have a known location in the current query's text. * * However, if restore_location_fields is true, we do restore location * fields from the string. This is currently intended only for use by the * WRITE_READ_PARSE_PLAN_TREES test code, which doesn't want to cause * any change in the node contents. * *------------------------------------------------------------------------- */ #include "postgres.h" #include #include "miscadmin.h" #include "nodes/bitmapset.h" #include "nodes/readfuncs.h" /* * Macros to simplify reading of different kinds of fields. Use these * wherever possible to reduce the chance for silly typos. Note that these * hard-wire conventions about the names of the local variables in a Read * routine. */ /* Macros for declaring appropriate local variables */ /* A few guys need only local_node */ #define READ_LOCALS_NO_FIELDS(nodeTypeName) \ nodeTypeName *local_node = makeNode(nodeTypeName) /* And a few guys need only the pg_strtok support fields */ #define READ_TEMP_LOCALS() \ const char *token; \ int length /* ... but most need both */ #define READ_LOCALS(nodeTypeName) \ READ_LOCALS_NO_FIELDS(nodeTypeName); \ READ_TEMP_LOCALS() /* Read an integer field (anything written as ":fldname %d") */ #define READ_INT_FIELD(fldname) \ token = pg_strtok(&length); /* skip :fldname */ \ token = pg_strtok(&length); /* get field value */ \ local_node->fldname = atoi(token) /* Read an unsigned integer field (anything written as ":fldname %u") */ #define READ_UINT_FIELD(fldname) \ token = pg_strtok(&length); /* skip :fldname */ \ token = pg_strtok(&length); /* get field value */ \ local_node->fldname = atoui(token) /* Read an unsigned integer field (anything written using UINT64_FORMAT) */ #define READ_UINT64_FIELD(fldname) \ token = pg_strtok(&length); /* skip :fldname */ \ token = pg_strtok(&length); /* get field value */ \ local_node->fldname = strtou64(token, NULL, 10) /* Read a long integer field (anything written as ":fldname %ld") */ #define READ_LONG_FIELD(fldname) \ token = pg_strtok(&length); /* skip :fldname */ \ token = pg_strtok(&length); /* get field value */ \ local_node->fldname = atol(token) /* Read an OID field (don't hard-wire assumption that OID is same as uint) */ #define READ_OID_FIELD(fldname) \ token = pg_strtok(&length); /* skip :fldname */ \ token = pg_strtok(&length); /* get field value */ \ local_node->fldname = atooid(token) /* Read a char field (ie, one ascii character) */ #define READ_CHAR_FIELD(fldname) \ token = pg_strtok(&length); /* skip :fldname */ \ token = pg_strtok(&length); /* get field value */ \ /* avoid overhead of calling debackslash() for one char */ \ local_node->fldname = (length == 0) ? '\0' : (token[0] == '\\' ? token[1] : token[0]) /* Read an enumerated-type field that was written as an integer code */ #define READ_ENUM_FIELD(fldname, enumtype) \ token = pg_strtok(&length); /* skip :fldname */ \ token = pg_strtok(&length); /* get field value */ \ local_node->fldname = (enumtype) atoi(token) /* Read a float field */ #define READ_FLOAT_FIELD(fldname) \ token = pg_strtok(&length); /* skip :fldname */ \ token = pg_strtok(&length); /* get field value */ \ local_node->fldname = atof(token) /* Read a boolean field */ #define READ_BOOL_FIELD(fldname) \ token = pg_strtok(&length); /* skip :fldname */ \ token = pg_strtok(&length); /* get field value */ \ local_node->fldname = strtobool(token) /* Read a character-string field */ #define READ_STRING_FIELD(fldname) \ token = pg_strtok(&length); /* skip :fldname */ \ token = pg_strtok(&length); /* get field value */ \ local_node->fldname = nullable_string(token, length) /* Read a parse location field (and possibly throw away the value) */ #ifdef WRITE_READ_PARSE_PLAN_TREES #define READ_LOCATION_FIELD(fldname) \ token = pg_strtok(&length); /* skip :fldname */ \ token = pg_strtok(&length); /* get field value */ \ local_node->fldname = restore_location_fields ? atoi(token) : -1 #else #define READ_LOCATION_FIELD(fldname) \ token = pg_strtok(&length); /* skip :fldname */ \ token = pg_strtok(&length); /* get field value */ \ (void) token; /* in case not used elsewhere */ \ local_node->fldname = -1 /* set field to "unknown" */ #endif /* Read a Node field */ #define READ_NODE_FIELD(fldname) \ token = pg_strtok(&length); /* skip :fldname */ \ (void) token; /* in case not used elsewhere */ \ local_node->fldname = nodeRead(NULL, 0) /* Read a bitmapset field */ #define READ_BITMAPSET_FIELD(fldname) \ token = pg_strtok(&length); /* skip :fldname */ \ (void) token; /* in case not used elsewhere */ \ local_node->fldname = _readBitmapset() /* Read an attribute number array */ #define READ_ATTRNUMBER_ARRAY(fldname, len) \ token = pg_strtok(&length); /* skip :fldname */ \ local_node->fldname = readAttrNumberCols(len) /* Read an oid array */ #define READ_OID_ARRAY(fldname, len) \ token = pg_strtok(&length); /* skip :fldname */ \ local_node->fldname = readOidCols(len) /* Read an int array */ #define READ_INT_ARRAY(fldname, len) \ token = pg_strtok(&length); /* skip :fldname */ \ local_node->fldname = readIntCols(len) /* Read a bool array */ #define READ_BOOL_ARRAY(fldname, len) \ token = pg_strtok(&length); /* skip :fldname */ \ local_node->fldname = readBoolCols(len) /* Routine exit */ #define READ_DONE() \ return local_node /* * NOTE: use atoi() to read values written with %d, or atoui() to read * values written with %u in outfuncs.c. An exception is OID values, * for which use atooid(). (As of 7.1, outfuncs.c writes OIDs as %u, * but this will probably change in the future.) */ #define atoui(x) ((unsigned int) strtoul((x), NULL, 10)) #define strtobool(x) ((*(x) == 't') ? true : false) static char * nullable_string(const char *token, int length) { /* outToken emits <> for NULL, and pg_strtok makes that an empty string */ if (length == 0) return NULL; /* outToken emits "" for empty string */ if (length == 2 && token[0] == '"' && token[1] == '"') return pstrdup(""); /* otherwise, we must remove protective backslashes added by outToken */ return debackslash(token, length); } /* * _readBitmapset * * Note: this code is used in contexts where we know that a Bitmapset * is expected. There is equivalent code in nodeRead() that can read a * Bitmapset when we come across one in other contexts. */ static Bitmapset * _readBitmapset(void) { Bitmapset *result = NULL; READ_TEMP_LOCALS(); token = pg_strtok(&length); if (token == NULL) elog(ERROR, "incomplete Bitmapset structure"); if (length != 1 || token[0] != '(') elog(ERROR, "unrecognized token: \"%.*s\"", length, token); token = pg_strtok(&length); if (token == NULL) elog(ERROR, "incomplete Bitmapset structure"); if (length != 1 || token[0] != 'b') elog(ERROR, "unrecognized token: \"%.*s\"", length, token); for (;;) { int val; char *endptr; token = pg_strtok(&length); if (token == NULL) elog(ERROR, "unterminated Bitmapset structure"); if (length == 1 && token[0] == ')') break; val = (int) strtol(token, &endptr, 10); if (endptr != token + length) elog(ERROR, "unrecognized integer: \"%.*s\"", length, token); result = bms_add_member(result, val); } return result; } /* * We export this function for use by extensions that define extensible nodes. * That's somewhat historical, though, because calling nodeRead() will work. */ Bitmapset * readBitmapset(void) { return _readBitmapset(); } #include "readfuncs.funcs.c" /* * Support functions for nodes with custom_read_write attribute or * special_read_write attribute */ static Const * _readConst(void) { READ_LOCALS(Const); READ_OID_FIELD(consttype); READ_INT_FIELD(consttypmod); READ_OID_FIELD(constcollid); READ_INT_FIELD(constlen); READ_BOOL_FIELD(constbyval); READ_BOOL_FIELD(constisnull); READ_LOCATION_FIELD(location); token = pg_strtok(&length); /* skip :constvalue */ if (local_node->constisnull) token = pg_strtok(&length); /* skip "<>" */ else local_node->constvalue = readDatum(local_node->constbyval); READ_DONE(); } static BoolExpr * _readBoolExpr(void) { READ_LOCALS(BoolExpr); /* do-it-yourself enum representation */ token = pg_strtok(&length); /* skip :boolop */ token = pg_strtok(&length); /* get field value */ if (length == 3 && strncmp(token, "and", 3) == 0) local_node->boolop = AND_EXPR; else if (length == 2 && strncmp(token, "or", 2) == 0) local_node->boolop = OR_EXPR; else if (length == 3 && strncmp(token, "not", 3) == 0) local_node->boolop = NOT_EXPR; else elog(ERROR, "unrecognized boolop \"%.*s\"", length, token); READ_NODE_FIELD(args); READ_LOCATION_FIELD(location); READ_DONE(); } static A_Const * _readA_Const(void) { READ_LOCALS(A_Const); /* We expect either NULL or :val here */ token = pg_strtok(&length); if (length == 4 && strncmp(token, "NULL", 4) == 0) local_node->isnull = true; else { union ValUnion *tmp = nodeRead(NULL, 0); /* To forestall valgrind complaints, copy only the valid data */ switch (nodeTag(tmp)) { case T_Integer: memcpy(&local_node->val, tmp, sizeof(Integer)); break; case T_Float: memcpy(&local_node->val, tmp, sizeof(Float)); break; case T_Boolean: memcpy(&local_node->val, tmp, sizeof(Boolean)); break; case T_String: memcpy(&local_node->val, tmp, sizeof(String)); break; case T_BitString: memcpy(&local_node->val, tmp, sizeof(BitString)); break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(tmp)); break; } } READ_LOCATION_FIELD(location); READ_DONE(); } /* * _readConstraint */ static Constraint * _readConstraint(void) { READ_LOCALS(Constraint); READ_STRING_FIELD(conname); READ_BOOL_FIELD(deferrable); READ_BOOL_FIELD(initdeferred); READ_LOCATION_FIELD(location); token = pg_strtok(&length); /* skip :contype */ token = pg_strtok(&length); /* get field value */ if (length == 4 && strncmp(token, "NULL", 4) == 0) local_node->contype = CONSTR_NULL; else if (length == 8 && strncmp(token, "NOT_NULL", 8) == 0) local_node->contype = CONSTR_NOTNULL; else if (length == 7 && strncmp(token, "DEFAULT", 7) == 0) local_node->contype = CONSTR_DEFAULT; else if (length == 8 && strncmp(token, "IDENTITY", 8) == 0) local_node->contype = CONSTR_IDENTITY; else if (length == 9 && strncmp(token, "GENERATED", 9) == 0) local_node->contype = CONSTR_GENERATED; else if (length == 5 && strncmp(token, "CHECK", 5) == 0) local_node->contype = CONSTR_CHECK; else if (length == 11 && strncmp(token, "PRIMARY_KEY", 11) == 0) local_node->contype = CONSTR_PRIMARY; else if (length == 6 && strncmp(token, "UNIQUE", 6) == 0) local_node->contype = CONSTR_UNIQUE; else if (length == 9 && strncmp(token, "EXCLUSION", 9) == 0) local_node->contype = CONSTR_EXCLUSION; else if (length == 11 && strncmp(token, "FOREIGN_KEY", 11) == 0) local_node->contype = CONSTR_FOREIGN; else if (length == 15 && strncmp(token, "ATTR_DEFERRABLE", 15) == 0) local_node->contype = CONSTR_ATTR_DEFERRABLE; else if (length == 19 && strncmp(token, "ATTR_NOT_DEFERRABLE", 19) == 0) local_node->contype = CONSTR_ATTR_NOT_DEFERRABLE; else if (length == 13 && strncmp(token, "ATTR_DEFERRED", 13) == 0) local_node->contype = CONSTR_ATTR_DEFERRED; else if (length == 14 && strncmp(token, "ATTR_IMMEDIATE", 14) == 0) local_node->contype = CONSTR_ATTR_IMMEDIATE; switch (local_node->contype) { case CONSTR_NULL: /* no extra fields */ break; case CONSTR_NOTNULL: READ_NODE_FIELD(keys); READ_INT_FIELD(inhcount); READ_BOOL_FIELD(is_no_inherit); READ_BOOL_FIELD(skip_validation); READ_BOOL_FIELD(initially_valid); break; case CONSTR_DEFAULT: READ_NODE_FIELD(raw_expr); READ_STRING_FIELD(cooked_expr); break; case CONSTR_IDENTITY: READ_NODE_FIELD(options); READ_CHAR_FIELD(generated_when); break; case CONSTR_GENERATED: READ_NODE_FIELD(raw_expr); READ_STRING_FIELD(cooked_expr); READ_CHAR_FIELD(generated_when); break; case CONSTR_CHECK: READ_BOOL_FIELD(is_no_inherit); READ_NODE_FIELD(raw_expr); READ_STRING_FIELD(cooked_expr); READ_BOOL_FIELD(skip_validation); READ_BOOL_FIELD(initially_valid); break; case CONSTR_PRIMARY: READ_NODE_FIELD(keys); READ_BOOL_FIELD(without_overlaps); READ_NODE_FIELD(including); READ_NODE_FIELD(options); READ_STRING_FIELD(indexname); READ_STRING_FIELD(indexspace); READ_BOOL_FIELD(reset_default_tblspc); /* access_method and where_clause not currently used */ break; case CONSTR_UNIQUE: READ_BOOL_FIELD(nulls_not_distinct); READ_NODE_FIELD(keys); READ_BOOL_FIELD(without_overlaps); READ_NODE_FIELD(including); READ_NODE_FIELD(options); READ_STRING_FIELD(indexname); READ_STRING_FIELD(indexspace); READ_BOOL_FIELD(reset_default_tblspc); /* access_method and where_clause not currently used */ break; case CONSTR_EXCLUSION: READ_NODE_FIELD(exclusions); READ_NODE_FIELD(including); READ_NODE_FIELD(options); READ_STRING_FIELD(indexname); READ_STRING_FIELD(indexspace); READ_BOOL_FIELD(reset_default_tblspc); READ_STRING_FIELD(access_method); READ_NODE_FIELD(where_clause); break; case CONSTR_FOREIGN: READ_NODE_FIELD(pktable); READ_NODE_FIELD(fk_attrs); READ_NODE_FIELD(pk_attrs); READ_CHAR_FIELD(fk_matchtype); READ_CHAR_FIELD(fk_upd_action); READ_CHAR_FIELD(fk_del_action); READ_NODE_FIELD(fk_del_set_cols); READ_NODE_FIELD(old_conpfeqop); READ_OID_FIELD(old_pktable_oid); READ_BOOL_FIELD(skip_validation); READ_BOOL_FIELD(initially_valid); break; case CONSTR_ATTR_DEFERRABLE: case CONSTR_ATTR_NOT_DEFERRABLE: case CONSTR_ATTR_DEFERRED: case CONSTR_ATTR_IMMEDIATE: /* no extra fields */ break; default: elog(ERROR, "unrecognized ConstrType: %d", (int) local_node->contype); break; } READ_DONE(); } static RangeTblEntry * _readRangeTblEntry(void) { READ_LOCALS(RangeTblEntry); /* put alias + eref first to make dump more legible */ READ_NODE_FIELD(alias); READ_NODE_FIELD(eref); READ_ENUM_FIELD(rtekind, RTEKind); switch (local_node->rtekind) { case RTE_RELATION: READ_OID_FIELD(relid); READ_CHAR_FIELD(relkind); READ_INT_FIELD(rellockmode); READ_NODE_FIELD(tablesample); READ_UINT_FIELD(perminfoindex); break; case RTE_SUBQUERY: READ_NODE_FIELD(subquery); READ_BOOL_FIELD(security_barrier); /* we re-use these RELATION fields, too: */ READ_OID_FIELD(relid); READ_CHAR_FIELD(relkind); READ_INT_FIELD(rellockmode); READ_UINT_FIELD(perminfoindex); break; case RTE_JOIN: READ_ENUM_FIELD(jointype, JoinType); READ_INT_FIELD(joinmergedcols); READ_NODE_FIELD(joinaliasvars); READ_NODE_FIELD(joinleftcols); READ_NODE_FIELD(joinrightcols); READ_NODE_FIELD(join_using_alias); break; case RTE_FUNCTION: READ_NODE_FIELD(functions); READ_BOOL_FIELD(funcordinality); break; case RTE_TABLEFUNC: READ_NODE_FIELD(tablefunc); /* The RTE must have a copy of the column type info, if any */ if (local_node->tablefunc) { TableFunc *tf = local_node->tablefunc; local_node->coltypes = tf->coltypes; local_node->coltypmods = tf->coltypmods; local_node->colcollations = tf->colcollations; } break; case RTE_VALUES: READ_NODE_FIELD(values_lists); READ_NODE_FIELD(coltypes); READ_NODE_FIELD(coltypmods); READ_NODE_FIELD(colcollations); break; case RTE_CTE: READ_STRING_FIELD(ctename); READ_UINT_FIELD(ctelevelsup); READ_BOOL_FIELD(self_reference); READ_NODE_FIELD(coltypes); READ_NODE_FIELD(coltypmods); READ_NODE_FIELD(colcollations); break; case RTE_NAMEDTUPLESTORE: READ_STRING_FIELD(enrname); READ_FLOAT_FIELD(enrtuples); READ_NODE_FIELD(coltypes); READ_NODE_FIELD(coltypmods); READ_NODE_FIELD(colcollations); /* we re-use these RELATION fields, too: */ READ_OID_FIELD(relid); break; case RTE_RESULT: /* no extra fields */ break; default: elog(ERROR, "unrecognized RTE kind: %d", (int) local_node->rtekind); break; } READ_BOOL_FIELD(lateral); READ_BOOL_FIELD(inh); READ_BOOL_FIELD(inFromCl); READ_NODE_FIELD(securityQuals); READ_DONE(); } static A_Expr * _readA_Expr(void) { READ_LOCALS(A_Expr); token = pg_strtok(&length); if (length == 3 && strncmp(token, "ANY", 3) == 0) { local_node->kind = AEXPR_OP_ANY; READ_NODE_FIELD(name); } else if (length == 3 && strncmp(token, "ALL", 3) == 0) { local_node->kind = AEXPR_OP_ALL; READ_NODE_FIELD(name); } else if (length == 8 && strncmp(token, "DISTINCT", 8) == 0) { local_node->kind = AEXPR_DISTINCT; READ_NODE_FIELD(name); } else if (length == 12 && strncmp(token, "NOT_DISTINCT", 12) == 0) { local_node->kind = AEXPR_NOT_DISTINCT; READ_NODE_FIELD(name); } else if (length == 6 && strncmp(token, "NULLIF", 6) == 0) { local_node->kind = AEXPR_NULLIF; READ_NODE_FIELD(name); } else if (length == 2 && strncmp(token, "IN", 2) == 0) { local_node->kind = AEXPR_IN; READ_NODE_FIELD(name); } else if (length == 4 && strncmp(token, "LIKE", 4) == 0) { local_node->kind = AEXPR_LIKE; READ_NODE_FIELD(name); } else if (length == 5 && strncmp(token, "ILIKE", 5) == 0) { local_node->kind = AEXPR_ILIKE; READ_NODE_FIELD(name); } else if (length == 7 && strncmp(token, "SIMILAR", 7) == 0) { local_node->kind = AEXPR_SIMILAR; READ_NODE_FIELD(name); } else if (length == 7 && strncmp(token, "BETWEEN", 7) == 0) { local_node->kind = AEXPR_BETWEEN; READ_NODE_FIELD(name); } else if (length == 11 && strncmp(token, "NOT_BETWEEN", 11) == 0) { local_node->kind = AEXPR_NOT_BETWEEN; READ_NODE_FIELD(name); } else if (length == 11 && strncmp(token, "BETWEEN_SYM", 11) == 0) { local_node->kind = AEXPR_BETWEEN_SYM; READ_NODE_FIELD(name); } else if (length == 15 && strncmp(token, "NOT_BETWEEN_SYM", 15) == 0) { local_node->kind = AEXPR_NOT_BETWEEN_SYM; READ_NODE_FIELD(name); } else if (length == 5 && strncmp(token, ":name", 5) == 0) { local_node->kind = AEXPR_OP; local_node->name = nodeRead(NULL, 0); } else elog(ERROR, "unrecognized A_Expr kind: \"%.*s\"", length, token); READ_NODE_FIELD(lexpr); READ_NODE_FIELD(rexpr); READ_LOCATION_FIELD(location); READ_DONE(); } static ExtensibleNode * _readExtensibleNode(void) { const ExtensibleNodeMethods *methods; ExtensibleNode *local_node; const char *extnodename; READ_TEMP_LOCALS(); token = pg_strtok(&length); /* skip :extnodename */ token = pg_strtok(&length); /* get extnodename */ extnodename = nullable_string(token, length); if (!extnodename) elog(ERROR, "extnodename has to be supplied"); methods = GetExtensibleNodeMethods(extnodename, false); local_node = (ExtensibleNode *) newNode(methods->node_size, T_ExtensibleNode); local_node->extnodename = extnodename; /* deserialize the private fields */ methods->nodeRead(local_node); READ_DONE(); } /* * parseNodeString * * Given a character string representing a node tree, parseNodeString creates * the internal node structure. * * The string to be read must already have been loaded into pg_strtok(). */ Node * parseNodeString(void) { READ_TEMP_LOCALS(); /* Guard against stack overflow due to overly complex expressions */ check_stack_depth(); token = pg_strtok(&length); #define MATCH(tokname, namelen) \ (length == namelen && memcmp(token, tokname, namelen) == 0) #include "readfuncs.switch.c" elog(ERROR, "badly formatted node string \"%.32s\"...", token); return NULL; /* keep compiler quiet */ } /* * readDatum * * Given a string representation of a constant, recreate the appropriate * Datum. The string representation embeds length info, but not byValue, * so we must be told that. */ Datum readDatum(bool typbyval) { Size length, i; int tokenLength; const char *token; Datum res; char *s; /* * read the actual length of the value */ token = pg_strtok(&tokenLength); length = atoui(token); token = pg_strtok(&tokenLength); /* read the '[' */ if (token == NULL || token[0] != '[') elog(ERROR, "expected \"[\" to start datum, but got \"%s\"; length = %zu", token ? token : "[NULL]", length); if (typbyval) { if (length > (Size) sizeof(Datum)) elog(ERROR, "byval datum but length = %zu", length); res = (Datum) 0; s = (char *) (&res); for (i = 0; i < (Size) sizeof(Datum); i++) { token = pg_strtok(&tokenLength); s[i] = (char) atoi(token); } } else if (length <= 0) res = (Datum) NULL; else { s = (char *) palloc(length); for (i = 0; i < length; i++) { token = pg_strtok(&tokenLength); s[i] = (char) atoi(token); } res = PointerGetDatum(s); } token = pg_strtok(&tokenLength); /* read the ']' */ if (token == NULL || token[0] != ']') elog(ERROR, "expected \"]\" to end datum, but got \"%s\"; length = %zu", token ? token : "[NULL]", length); return res; } /* * common implementation for scalar-array-reading functions * * The data format is either "<>" for a NULL pointer (in which case numCols * is ignored) or "(item item item)" where the number of items must equal * numCols. The convfunc must be okay with stopping at whitespace or a * right parenthesis, since pg_strtok won't null-terminate the token. */ #define READ_SCALAR_ARRAY(fnname, datatype, convfunc) \ datatype * \ fnname(int numCols) \ { \ datatype *vals; \ READ_TEMP_LOCALS(); \ token = pg_strtok(&length); \ if (token == NULL) \ elog(ERROR, "incomplete scalar array"); \ if (length == 0) \ return NULL; /* it was "<>", so return NULL pointer */ \ if (length != 1 || token[0] != '(') \ elog(ERROR, "unrecognized token: \"%.*s\"", length, token); \ vals = (datatype *) palloc(numCols * sizeof(datatype)); \ for (int i = 0; i < numCols; i++) \ { \ token = pg_strtok(&length); \ if (token == NULL || token[0] == ')') \ elog(ERROR, "incomplete scalar array"); \ vals[i] = convfunc(token); \ } \ token = pg_strtok(&length); \ if (token == NULL || length != 1 || token[0] != ')') \ elog(ERROR, "incomplete scalar array"); \ return vals; \ } /* * Note: these functions are exported in nodes.h for possible use by * extensions, so don't mess too much with their names or API. */ READ_SCALAR_ARRAY(readAttrNumberCols, int16, atoi) READ_SCALAR_ARRAY(readOidCols, Oid, atooid) /* outfuncs.c has writeIndexCols, but we don't yet need that here */ /* READ_SCALAR_ARRAY(readIndexCols, Index, atoui) */ READ_SCALAR_ARRAY(readIntCols, int, atoi) READ_SCALAR_ARRAY(readBoolCols, bool, strtobool)