/* * Evaluate tsquery boolean expression using ternary logic. * * chkcond is a callback function used to evaluate each VAL node in the query. * checkval can be used to pass information to the callback. TS_execute doesn't * do anything with it. */ static GinTernaryValue TS_execute_ternary(QueryItem *curitem, void *checkval, GinTernaryValue (*chkcond) (void *checkval, QueryOperand *val)) { GinTernaryValue val1, val2, result; /* since this function recurses, it could be driven to stack overflow */ check_stack_depth(); if (curitem->type == QI_VAL) return chkcond(checkval, (QueryOperand *) curitem); switch (curitem->qoperator.oper) { case OP_NOT: result = TS_execute_ternary(curitem + 1, checkval, chkcond); if (result == GIN_MAYBE) return result; return !result; case OP_AND: val1 = TS_execute_ternary(curitem + curitem->qoperator.left, checkval, chkcond); if (val1 == GIN_FALSE) return GIN_FALSE; val2 = TS_execute_ternary(curitem + 1, checkval, chkcond); if (val2 == GIN_FALSE) return GIN_FALSE; if (val1 == GIN_TRUE && val2 == GIN_TRUE) return GIN_TRUE; else return GIN_MAYBE; case OP_OR: val1 = TS_execute_ternary(curitem + curitem->qoperator.left, checkval, chkcond); if (val1 == GIN_TRUE) return GIN_TRUE; val2 = TS_execute_ternary(curitem + 1, checkval, chkcond); if (val2 == GIN_TRUE) return GIN_TRUE; if (val1 == GIN_FALSE && val2 == GIN_FALSE) return GIN_FALSE; else return GIN_MAYBE; default: elog(ERROR, "unrecognized operator___: %d", curitem->qoperator.oper); } /* not reachable, but keep compiler quiet */ return false; }
/* * clean tree for ! operator. * It's useful for debug, but in * other case, such view is used with search in index. * Operator ! always return TRUE */ static NODE * clean_NOT_intree(NODE *node) { /* since this function recurses, it could be driven to stack overflow. */ check_stack_depth(); if (node->valnode->type == QI_VAL) return node; if (node->valnode->qoperator.oper == OP_NOT) { freetree(node); return NULL; } /* operator & or | */ if (node->valnode->qoperator.oper == OP_OR) { if ((node->left = clean_NOT_intree(node->left)) == NULL || (node->right = clean_NOT_intree(node->right)) == NULL) { freetree(node); return NULL; } } else { NODE *res = node; Assert(node->valnode->qoperator.oper == OP_AND || node->valnode->qoperator.oper == OP_PHRASE); node->left = clean_NOT_intree(node->left); node->right = clean_NOT_intree(node->right); if (node->left == NULL && node->right == NULL) { pfree(node); res = NULL; } else if (node->left == NULL) { res = node->right; pfree(node); } else if (node->right == NULL) { res = node->left; pfree(node); } return res; } return node; }
/* * Sort comparator for QTNodes. * * The sort order is somewhat arbitrary. */ int QTNodeCompare(QTNode *an, QTNode *bn) { /* since this function recurses, it could be driven to stack overflow. */ check_stack_depth(); if (an->valnode->type != bn->valnode->type) return (an->valnode->type > bn->valnode->type) ? -1 : 1; if (an->valnode->type == QI_OPR) { QueryOperator *ao = &an->valnode->qoperator; QueryOperator *bo = &bn->valnode->qoperator; if (ao->oper != bo->oper) return (ao->oper > bo->oper) ? -1 : 1; if (an->nchild != bn->nchild) return (an->nchild > bn->nchild) ? -1 : 1; { int i, res; for (i = 0; i < an->nchild; i++) if ((res = QTNodeCompare(an->child[i], bn->child[i])) != 0) return res; } if (ao->oper == OP_PHRASE && ao->distance != bo->distance) return (ao->distance > bo->distance) ? -1 : 1; return 0; } else if (an->valnode->type == QI_VAL) { QueryOperand *ao = &an->valnode->qoperand; QueryOperand *bo = &bn->valnode->qoperand; if (ao->valcrc != bo->valcrc) { return (ao->valcrc > bo->valcrc) ? -1 : 1; } return tsCompareString(an->word, ao->length, bn->word, bo->length, false); } else { elog(ERROR, "unrecognized QueryItem type: %d", an->valnode->type); return 0; /* keep compiler quiet */ } }
static void freetree(NODE *node) { /* since this function recurses, it could be driven to stack overflow. */ check_stack_depth(); if (!node) return; if (node->left) freetree(node->left); if (node->right) freetree(node->right); pfree(node); }
void QTNSort(QTNode *in) { int i; /* since this function recurses, it could be driven to stack overflow. */ check_stack_depth(); if (in->valnode->type != QI_OPR) return; for (i = 0; i < in->nchild; i++) QTNSort(in->child[i]); if (in->nchild > 1) qsort((void *) in->child, in->nchild, sizeof(QTNode *), cmpQTN); }
void QTNClearFlags(QTNode *in, uint32 flags) { /* since this function recurses, it could be driven to stack overflow. */ check_stack_depth(); in->flags &= ~flags; if (in->valnode->type != QI_VAL) { int i; for (i = 0; i < in->nchild; i++) QTNClearFlags(in->child[i], flags); } }
static int addone(int *counters, int last, int total) { /* since this function recurses, it could be driven to stack overflow. */ check_stack_depth(); counters[last]++; if (counters[last] >= total) { if (last == 0) return 0; if (addone(counters, last - 1, total - 1) == 0) return 0; counters[last] = counters[last - 1] + 1; } return 1; }
int QTNodeCompare(QTNode *an, QTNode *bn) { /* since this function recurses, it could be driven to stack overflow. */ check_stack_depth(); if (an->valnode->type != bn->valnode->type) return (an->valnode->type > bn->valnode->type) ? -1 : 1; if (an->valnode->type == QI_OPR) { QueryOperator *ao = &an->valnode->qoperator; QueryOperator *bo = &bn->valnode->qoperator; if (ao->oper != bo->oper) return (ao->oper > bo->oper) ? -1 : 1; if (an->nchild != bn->nchild) return (an->nchild > bn->nchild) ? -1 : 1; { int i, res; for (i = 0; i < an->nchild; i++) if ((res = QTNodeCompare(an->child[i], bn->child[i])) != 0) return res; } return 0; } else { QueryOperand *ao = &an->valnode->qoperand; QueryOperand *bo = &bn->valnode->qoperand; Assert(an->valnode->type == QI_VAL); if (ao->valcrc != bo->valcrc) { return (ao->valcrc > bo->valcrc) ? -1 : 1; } return tsCompareString(an->word, ao->length, bn->word, bo->length, false); } }
static QTNode * dofindsubquery(QTNode *root, QTNode *ex, QTNode *subs, bool *isfind) { /* since this function recurses, it could be driven to stack overflow. */ check_stack_depth(); root = findeq(root, ex, subs, isfind); if (root && (root->flags & QTN_NOCHANGE) == 0 && root->valnode->type == QI_OPR) { int i; for (i = 0; i < root->nchild; i++) root->child[i] = dofindsubquery(root->child[i], ex, subs, isfind); } return root; }
/* * make query tree from plain view of query */ static NODE * maketree(QueryItem *in) { NODE *node = (NODE *) palloc(sizeof(NODE)); /* since this function recurses, it could be driven to stack overflow. */ check_stack_depth(); node->valnode = in; node->right = node->left = NULL; if (in->type == QI_OPR) { node->right = maketree(in + 1); if (in->qoperator.oper != OP_NOT) node->left = maketree(in + in->qoperator.left); } return node; }
/* * Remove unnecessary intermediate nodes. For example: * * OR OR * a OR -> a b c * b c */ void QTNTernary(QTNode *in) { int i; /* since this function recurses, it could be driven to stack overflow. */ check_stack_depth(); if (in->valnode->type != QI_OPR) return; for (i = 0; i < in->nchild; i++) QTNTernary(in->child[i]); /* Only AND and OR are associative, so don't flatten other node types */ if (in->valnode->qoperator.oper != OP_AND && in->valnode->qoperator.oper != OP_OR) return; for (i = 0; i < in->nchild; i++) { QTNode *cc = in->child[i]; if (cc->valnode->type == QI_OPR && in->valnode->qoperator.oper == cc->valnode->qoperator.oper) { int oldnchild = in->nchild; in->nchild += cc->nchild - 1; in->child = (QTNode **) repalloc(in->child, in->nchild * sizeof(QTNode *)); if (i + 1 != oldnchild) memmove(in->child + i + cc->nchild, in->child + i + 1, (oldnchild - i - 1) * sizeof(QTNode *)); memcpy(in->child + i, cc->child, cc->nchild * sizeof(QTNode *)); i += cc->nchild - 1; if (cc->flags & QTN_NEEDFREE) pfree(cc->valnode); pfree(cc); } } }
/* * Count the total length of operand string in tree, including '\0'- * terminators. */ static void cntsize(QTNode *in, int *sumlen, int *nnode) { /* since this function recurses, it could be driven to stack overflow. */ check_stack_depth(); *nnode += 1; if (in->valnode->type == QI_OPR) { int i; for (i = 0; i < in->nchild; i++) cntsize(in->child[i], sumlen, nnode); } else { *sumlen += in->valnode->qoperand.length + 1; } }
/* * Detect whether a tsquery boolean expression requires any positive matches * to values shown in the tsquery. * * This is needed to know whether a GIN index search requires full index scan. * For example, 'x & !y' requires a match of x, so it's sufficient to scan * entries for x; but 'x | !y' could match rows containing neither x nor y. */ bool tsquery_requires_match(QueryItem *curitem) { /* since this function recurses, it could be driven to stack overflow */ check_stack_depth(); if (curitem->type == QI_VAL) return true; switch (curitem->qoperator.oper) { case OP_NOT: /* * Assume there are no required matches underneath a NOT. For * some cases with nested NOTs, we could prove there's a required * match, but it seems unlikely to be worth the trouble. */ return false; case OP_AND: /* If either side requires a match, we're good */ if (tsquery_requires_match(curitem + curitem->qoperator.left)) return true; else return tsquery_requires_match(curitem + 1); case OP_OR: /* Both sides must require a match */ if (tsquery_requires_match(curitem + curitem->qoperator.left)) return tsquery_requires_match(curitem + 1); else return false; default: elog(ERROR, "unrecognized operator: %d", curitem->qoperator.oper); } /* not reachable, but keep compiler quiet */ return false; }
static bool executeExpr(char *jqBase, int32 jqPos, int32 op, JsonbValue *jb) { int32 type; int32 nextPos; check_stack_depth(); /* * read arg type */ jqPos = readJsQueryHeader(jqBase, jqPos, &type, &nextPos); Assert(nextPos == 0); Assert(type == jqiAny || type == jqiString || type == jqiNumeric || type == jqiNull || type == jqiBool || type == jqiArray); switch(op) { case jqiEqual: if (jb->type == jbvBinary && type == jqiArray) return checkArrayEquality(jqBase, jqPos, type, jb); return checkEquality(jqBase, jqPos, type, jb); case jqiIn: return checkIn(jqBase, jqPos, type, jb); case jqiOverlap: case jqiContains: case jqiContained: return executeArrayOp(jqBase, jqPos, type, op, jb); case jqiLess: case jqiGreater: case jqiLessOrEqual: case jqiGreaterOrEqual: return makeCompare(jqBase, jqPos, type, op, jb); default: elog(ERROR, "Unknown operation"); } return false; }
/* * Convert a tree to binary tree by inserting intermediate nodes. * (Opposite of QTNTernary) */ void QTNBinary(QTNode *in) { int i; /* since this function recurses, it could be driven to stack overflow. */ check_stack_depth(); if (in->valnode->type != QI_OPR) return; for (i = 0; i < in->nchild; i++) QTNBinary(in->child[i]); if (in->nchild <= 2) return; while (in->nchild > 2) { QTNode *nn = (QTNode *) palloc0(sizeof(QTNode)); nn->valnode = (QueryItem *) palloc0(sizeof(QueryItem)); nn->child = (QTNode **) palloc0(sizeof(QTNode *) * 2); nn->nchild = 2; nn->flags = QTN_NEEDFREE; nn->child[0] = in->child[0]; nn->child[1] = in->child[1]; nn->sign = nn->child[0]->sign | nn->child[1]->sign; nn->valnode->type = in->valnode->type; nn->valnode->qoperator.oper = in->valnode->qoperator.oper; in->child[0] = nn; in->child[1] = in->child[in->nchild - 1]; in->nchild--; } }
/* * pg_reg_getnumoutarcs() and pg_reg_getoutarcs() mask the existence of LACON * arcs from the caller, treating any LACON as being automatically satisfied. * Since the output representation does not support arcs that consume no * character when traversed, we have to recursively traverse LACON arcs here, * and report whatever normal arcs are reachable by traversing LACON arcs. * Note that this wouldn't work if it were possible to reach the final state * via LACON traversal, but the regex library never builds NFAs that have * LACON arcs leading directly to the final state. (This is because the * regex executor is designed to consume one character beyond the nominal * match end --- possibly an EOS indicator --- so there is always a set of * ordinary arcs leading to the final state.) * * traverse_lacons is a recursive subroutine used by both exported functions * to count and then emit the reachable regular arcs. *arcs_count is * incremented by the number of reachable arcs, and as many as will fit in * arcs_len (possibly 0) are emitted into arcs[]. */ static void traverse_lacons(struct cnfa *cnfa, int st, int *arcs_count, regex_arc_t *arcs, int arcs_len) { struct carc *ca; /* * Since this function recurses, it could theoretically be driven to stack * overflow. In practice, this is mostly useful to backstop against a * failure of the regex compiler to remove a loop of LACON arcs. */ check_stack_depth(); for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++) { if (ca->co < cnfa->ncolors) { /* Ordinary arc, so count and possibly emit it */ int ndx = (*arcs_count)++; if (ndx < arcs_len) { arcs[ndx].co = ca->co; arcs[ndx].to = ca->to; } } else { /* LACON arc --- assume it's satisfied and recurse... */ /* ... but first, assert it doesn't lead directly to post state */ Assert(ca->to != cnfa->post); traverse_lacons(cnfa, ca->to, arcs_count, arcs, arcs_len); } } }
/* * Evaluate tsquery boolean expression. * * chkcond is a callback function used to evaluate each VAL node in the query. * checkval can be used to pass information to the callback. TS_execute doesn't * do anything with it. * if calcnot is false, NOT expressions are always evaluated to be true. This * is used in ranking. */ bool TS_execute(QueryItem *curitem, void *checkval, bool calcnot, bool (*chkcond) (void *checkval, QueryOperand *val)) { /* since this function recurses, it could be driven to stack overflow */ check_stack_depth(); if (curitem->type == QI_VAL) return chkcond(checkval, (QueryOperand *) curitem); switch (curitem->qoperator.oper) { case OP_NOT: if (calcnot) return !TS_execute(curitem + 1, checkval, calcnot, chkcond); else return true; case OP_AND: if (TS_execute(curitem + curitem->qoperator.left, checkval, calcnot, chkcond)) return TS_execute(curitem + 1, checkval, calcnot, chkcond); else return false; case OP_OR: if (TS_execute(curitem + curitem->qoperator.left, checkval, calcnot, chkcond)) return true; else return TS_execute(curitem + 1, checkval, calcnot, chkcond); default: elog(ERROR, "unrecognized operator: %d", curitem->qoperator.oper); } /* not reachable, but keep compiler quiet */ return false; }
static void fillQT(QTN2QTState *state, QTNode *in) { /* since this function recurses, it could be driven to stack overflow. */ check_stack_depth(); if (in->valnode->type == QI_VAL) { memcpy(state->curitem, in->valnode, sizeof(QueryOperand)); memcpy(state->curoperand, in->word, in->valnode->qoperand.length); state->curitem->qoperand.distance = state->curoperand - state->operand; state->curoperand[in->valnode->qoperand.length] = '\0'; state->curoperand += in->valnode->qoperand.length + 1; state->curitem++; } else { QueryItem *curitem = state->curitem; Assert(in->valnode->type == QI_OPR); memcpy(state->curitem, in->valnode, sizeof(QueryOperator)); Assert(in->nchild <= 2); state->curitem++; fillQT(state, in->child[0]); if (in->nchild == 2) { curitem->qoperator.left = state->curitem - curitem; fillQT(state, in->child[1]); } } }
/* Recursively emit all GIN entries found in the node tree */ static void emit_jsp_gin_entries(JsonPathGinNode *node, GinEntries *entries) { check_stack_depth(); switch (node->type) { case JSP_GIN_ENTRY: /* replace datum with its index in the array */ node->val.entryIndex = add_gin_entry(entries, node->val.entryDatum); break; case JSP_GIN_OR: case JSP_GIN_AND: { int i; for (i = 0; i < node->val.nargs; i++) emit_jsp_gin_entries(node->args[i], entries); break; } } }
/* * make polish notation of query */ static int4 makepol(WORKSTATE *state) { int4 val, type; int4 stack[STACKDEPTH]; int4 lenstack = 0; /* since this function recurses, it could be driven to stack overflow */ check_stack_depth(); while ((type = gettoken(state, &val)) != END) { switch (type) { case VAL: pushquery(state, type, val); while (lenstack && (stack[lenstack - 1] == (int4) '&' || stack[lenstack - 1] == (int4) '!')) { lenstack--; pushquery(state, OPR, stack[lenstack]); } break; case OPR: if (lenstack && val == (int4) '|') pushquery(state, OPR, val); else { if (lenstack == STACKDEPTH) ereport(ERROR, (errcode(ERRCODE_STATEMENT_TOO_COMPLEX), errmsg("statement too complex"))); stack[lenstack] = val; lenstack++; } break; case OPEN: if (makepol(state) == ERR) return ERR; while (lenstack && (stack[lenstack - 1] == (int4) '&' || stack[lenstack - 1] == (int4) '!')) { lenstack--; pushquery(state, OPR, stack[lenstack]); } break; case CLOSE: while (lenstack) { lenstack--; pushquery(state, OPR, stack[lenstack]); }; return END; break; case ERR: default: ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("syntax error"))); return ERR; } } while (lenstack) { lenstack--; pushquery(state, OPR, stack[lenstack]); }; return END; }
/* * Evaluate tsquery boolean expression using ternary logic. */ static GinTernaryValue TS_execute_ternary(GinChkVal *gcv, QueryItem *curitem, bool in_phrase) { GinTernaryValue val1, val2, result; /* since this function recurses, it could be driven to stack overflow */ check_stack_depth(); if (curitem->type == QI_VAL) return checkcondition_gin_internal(gcv, (QueryOperand *) curitem, NULL /* don't have position info */ ); switch (curitem->qoperator.oper) { case OP_NOT: /* In phrase search, always return MAYBE since we lack positions */ if (in_phrase) return GIN_MAYBE; result = TS_execute_ternary(gcv, curitem + 1, in_phrase); if (result == GIN_MAYBE) return result; return !result; case OP_PHRASE: /* * GIN doesn't contain any information about positions, so treat * OP_PHRASE as OP_AND with recheck requirement */ *(gcv->need_recheck) = true; /* Pass down in_phrase == true in case there's a NOT below */ in_phrase = true; /* FALL THRU */ case OP_AND: val1 = TS_execute_ternary(gcv, curitem + curitem->qoperator.left, in_phrase); if (val1 == GIN_FALSE) return GIN_FALSE; val2 = TS_execute_ternary(gcv, curitem + 1, in_phrase); if (val2 == GIN_FALSE) return GIN_FALSE; if (val1 == GIN_TRUE && val2 == GIN_TRUE) return GIN_TRUE; else return GIN_MAYBE; case OP_OR: val1 = TS_execute_ternary(gcv, curitem + curitem->qoperator.left, in_phrase); if (val1 == GIN_TRUE) return GIN_TRUE; val2 = TS_execute_ternary(gcv, curitem + 1, in_phrase); if (val2 == GIN_TRUE) return GIN_TRUE; if (val1 == GIN_FALSE && val2 == GIN_FALSE) return GIN_FALSE; else return GIN_MAYBE; default: elog(ERROR, "unrecognized operator: %d", curitem->qoperator.oper); } /* not reachable, but keep compiler quiet */ return false; }
static int32 copyJsQuery(StringInfo buf, JsQueryItem *jsq) { JsQueryItem elem; int32 next, chld; int32 resPos = buf->len - VARHDRSZ; /* position from begining of jsquery data */ check_stack_depth(); Assert((jsq->type & jsq->hint) == 0); Assert((jsq->type & JSQ_HINT_MASK) == 0); appendStringInfoChar(buf, (char)(jsq->type | jsq->hint)); alignStringInfoInt(buf); next = (jsqGetNext(jsq, NULL)) ? buf->len : 0; appendBinaryStringInfo(buf, (char*)&next /* fake value */, sizeof(next)); switch(jsq->type) { case jqiKey: case jqiString: { int32 len; char *s; s = jsqGetString(jsq, &len); appendBinaryStringInfo(buf, (char*)&len, sizeof(len)); appendBinaryStringInfo(buf, s, len + 1 /* \0 */); } break; case jqiNumeric: { Numeric n = jsqGetNumeric(jsq); appendBinaryStringInfo(buf, (char*)n, VARSIZE_ANY(n)); } break; case jqiBool: { bool v = jsqGetBool(jsq); appendBinaryStringInfo(buf, (char*)&v, 1); } break; case jqiArray: { int32 i, arrayStart; appendBinaryStringInfo(buf, (char*)&jsq->array.nelems, sizeof(jsq->array.nelems)); arrayStart = buf->len; /* reserve place for "pointers" to array's elements */ for(i=0; i<jsq->array.nelems; i++) appendBinaryStringInfo(buf, (char*)&i /* fake value */, sizeof(i)); while(jsqIterateArray(jsq, &elem)) { chld = copyJsQuery(buf, &elem); *(int32*)(buf->data + arrayStart + i * sizeof(i)) = chld; i++; } } break; case jqiAnd: case jqiOr: { int32 leftOut, rightOut; leftOut = buf->len; appendBinaryStringInfo(buf, (char*)&leftOut /* fake value */, sizeof(leftOut)); rightOut = buf->len; appendBinaryStringInfo(buf, (char*)&rightOut /* fake value */, sizeof(rightOut)); jsqGetLeftArg(jsq, &elem); chld = copyJsQuery(buf, &elem); *(int32*)(buf->data + leftOut) = chld; jsqGetRightArg(jsq, &elem); chld = copyJsQuery(buf, &elem); *(int32*)(buf->data + rightOut) = chld; } break; case jqiEqual: case jqiIn: case jqiLess: case jqiGreater: case jqiLessOrEqual: case jqiGreaterOrEqual: case jqiContains: case jqiContained: case jqiOverlap: case jqiNot: { int32 argOut = buf->len; appendBinaryStringInfo(buf, (char*)&argOut /* fake value */, sizeof(argOut)); jsqGetArg(jsq, &elem); chld = copyJsQuery(buf, &elem); *(int32*)(buf->data + argOut) = chld; } break; case jqiNull: case jqiCurrent: case jqiLength: case jqiAny: case jqiAnyArray: case jqiAnyKey: case jqiAll: case jqiAllArray: case jqiAllKey: break; default: elog(ERROR, "Unknown type: %d", jsq->type); } if (jsqGetNext(jsq, &elem)) *(int32*)(buf->data + next) = copyJsQuery(buf, &elem); return resPos; }
/* * Turn a Datum into jsonb, adding it to the result JsonbInState. * * tcategory and outfuncoid are from a previous call to json_categorize_type, * except that if is_null is true then they can be invalid. * * If key_scalar is true, the value is stored as a key, so insist * it's of an acceptable type, and force it to be a jbvString. */ static void datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, JsonbTypeCategory tcategory, Oid outfuncoid, bool key_scalar) { char *outputstr; bool numeric_error; JsonbValue jb; bool scalar_jsonb = false; check_stack_depth(); /* Convert val to a JsonbValue in jb (in most cases) */ if (is_null) { Assert(!key_scalar); jb.type = jbvNull; } else if (key_scalar && (tcategory == JSONBTYPE_ARRAY || tcategory == JSONBTYPE_COMPOSITE || tcategory == JSONBTYPE_JSON || tcategory == JSONBTYPE_JSONB || tcategory == JSONBTYPE_JSONCAST)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("key value must be scalar, not array, composite, or json"))); } else { if (tcategory == JSONBTYPE_JSONCAST) val = OidFunctionCall1(outfuncoid, val); switch (tcategory) { case JSONBTYPE_ARRAY: array_to_jsonb_internal(val, result); break; case JSONBTYPE_COMPOSITE: composite_to_jsonb(val, result); break; case JSONBTYPE_BOOL: if (key_scalar) { outputstr = DatumGetBool(val) ? "true" : "false"; jb.type = jbvString; jb.val.string.len = strlen(outputstr); jb.val.string.val = outputstr; } else { jb.type = jbvBool; jb.val.boolean = DatumGetBool(val); } break; case JSONBTYPE_NUMERIC: outputstr = OidOutputFunctionCall(outfuncoid, val); if (key_scalar) { /* always quote keys */ jb.type = jbvString; jb.val.string.len = strlen(outputstr); jb.val.string.val = outputstr; } else { /* * Make it numeric if it's a valid JSON number, otherwise * a string. Invalid numeric output will always have an * 'N' or 'n' in it (I think). */ numeric_error = (strchr(outputstr, 'N') != NULL || strchr(outputstr, 'n') != NULL); if (!numeric_error) { jb.type = jbvNumeric; jb.val.numeric = DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(outputstr), 0, -1)); pfree(outputstr); } else { jb.type = jbvString; jb.val.string.len = strlen(outputstr); jb.val.string.val = outputstr; } } break; case JSONBTYPE_DATE: { DateADT date; struct pg_tm tm; char buf[MAXDATELEN + 1]; date = DatumGetDateADT(val); /* Same as date_out(), but forcing DateStyle */ if (DATE_NOT_FINITE(date)) EncodeSpecialDate(date, buf); else { j2date(date + POSTGRES_EPOCH_JDATE, &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday)); EncodeDateOnly(&tm, USE_XSD_DATES, buf); } jb.type = jbvString; jb.val.string.len = strlen(buf); jb.val.string.val = pstrdup(buf); } break; case JSONBTYPE_TIMESTAMP: { Timestamp timestamp; struct pg_tm tm; fsec_t fsec; char buf[MAXDATELEN + 1]; timestamp = DatumGetTimestamp(val); /* Same as timestamp_out(), but forcing DateStyle */ if (TIMESTAMP_NOT_FINITE(timestamp)) EncodeSpecialTimestamp(timestamp, buf); else if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, NULL) == 0) EncodeDateTime(&tm, fsec, false, 0, NULL, USE_XSD_DATES, buf); else ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); jb.type = jbvString; jb.val.string.len = strlen(buf); jb.val.string.val = pstrdup(buf); } break; case JSONBTYPE_TIMESTAMPTZ: { TimestampTz timestamp; struct pg_tm tm; int tz; fsec_t fsec; const char *tzn = NULL; char buf[MAXDATELEN + 1]; timestamp = DatumGetTimestampTz(val); /* Same as timestamptz_out(), but forcing DateStyle */ if (TIMESTAMP_NOT_FINITE(timestamp)) EncodeSpecialTimestamp(timestamp, buf); else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0) EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf); else ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); jb.type = jbvString; jb.val.string.len = strlen(buf); jb.val.string.val = pstrdup(buf); } break; case JSONBTYPE_JSONCAST: case JSONBTYPE_JSON: { /* parse the json right into the existing result object */ JsonLexContext *lex; JsonSemAction sem; text *json = DatumGetTextP(val); lex = makeJsonLexContext(json, true); memset(&sem, 0, sizeof(sem)); sem.semstate = (void *) result; sem.object_start = jsonb_in_object_start; sem.array_start = jsonb_in_array_start; sem.object_end = jsonb_in_object_end; sem.array_end = jsonb_in_array_end; sem.scalar = jsonb_in_scalar; sem.object_field_start = jsonb_in_object_field_start; pg_parse_json(lex, &sem); } break; case JSONBTYPE_JSONB: { Jsonb *jsonb = DatumGetJsonb(val); JsonbIterator *it; it = JsonbIteratorInit(&jsonb->root); if (JB_ROOT_IS_SCALAR(jsonb)) { (void) JsonbIteratorNext(&it, &jb, true); Assert(jb.type == jbvArray); (void) JsonbIteratorNext(&it, &jb, true); scalar_jsonb = true; } else { JsonbIteratorToken type; while ((type = JsonbIteratorNext(&it, &jb, false)) != WJB_DONE) { if (type == WJB_END_ARRAY || type == WJB_END_OBJECT || type == WJB_BEGIN_ARRAY || type == WJB_BEGIN_OBJECT) result->res = pushJsonbValue(&result->parseState, type, NULL); else result->res = pushJsonbValue(&result->parseState, type, &jb); } } } break; default: outputstr = OidOutputFunctionCall(outfuncoid, val); jb.type = jbvString; jb.val.string.len = checkStringLen(strlen(outputstr)); jb.val.string.val = outputstr; break; } } /* Now insert jb into result, unless we did it recursively */ if (!is_null && !scalar_jsonb && tcategory >= JSONBTYPE_JSON && tcategory <= JSONBTYPE_JSONCAST) { /* work has been done recursively */ return; } else if (result->parseState == NULL) { /* single root scalar */ JsonbValue va; va.type = jbvArray; va.val.array.rawScalar = true; va.val.array.nElems = 1; result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_ARRAY, &va); result->res = pushJsonbValue(&result->parseState, WJB_ELEM, &jb); result->res = pushJsonbValue(&result->parseState, WJB_END_ARRAY, NULL); } else { JsonbValue *o = &result->parseState->contVal; switch (o->type) { case jbvArray: result->res = pushJsonbValue(&result->parseState, WJB_ELEM, &jb); break; case jbvObject: result->res = pushJsonbValue(&result->parseState, key_scalar ? WJB_KEY : WJB_VALUE, &jb); break; default: elog(ERROR, "unexpected parent of nested structure"); } } }
static int compareJsQuery(JsQueryItem *v1, JsQueryItem *v2) { JsQueryItem elem1, elem2; int32 res = 0; check_stack_depth(); if (v1->type != v2->type) return (v1->type > v2->type) ? 1 : -1; switch(v1->type) { case jqiNull: case jqiCurrent: case jqiLength: case jqiAny: case jqiAnyArray: case jqiAnyKey: case jqiAll: case jqiAllArray: case jqiAllKey: break; case jqiKey: case jqiString: { int32 len1, len2; char *s1, *s2; s1 = jsqGetString(v1, &len1); s2 = jsqGetString(v2, &len2); if (len1 != len2) res = (len1 > len2) ? 1 : -1; else res = memcmp(s1, s2, len1); } break; case jqiNumeric: res = compareNumeric(jsqGetNumeric(v1), jsqGetNumeric(v2)); break; case jqiBool: if (jsqGetBool(v1) != jsqGetBool(v2)) res = (jsqGetBool(v1) > jsqGetBool(v2)) ? 1 : -1; break; case jqiArray: if (v1->array.nelems != v2->array.nelems) res = (v1->array.nelems > v2->array.nelems) ? 1 : -1; while(res == 0 && jsqIterateArray(v1, &elem1) && jsqIterateArray(v2, &elem2)) res = compareJsQuery(&elem1, &elem2); break; case jqiAnd: case jqiOr: jsqGetLeftArg(v1, &elem1); jsqGetLeftArg(v2, &elem2); res = compareJsQuery(&elem1, &elem2); if (res == 0) { jsqGetRightArg(v1, &elem1); jsqGetRightArg(v2, &elem2); res = compareJsQuery(&elem1, &elem2); } break; case jqiEqual: case jqiIn: case jqiLess: case jqiGreater: case jqiLessOrEqual: case jqiGreaterOrEqual: case jqiContains: case jqiContained: case jqiOverlap: case jqiNot: jsqGetArg(v1, &elem1); jsqGetArg(v2, &elem2); res = compareJsQuery(&elem1, &elem2); break; default: elog(ERROR, "Unknown JsQueryItem type: %d", v1->type); } if (res == 0) { if (jsqGetNext(v1, &elem1)) { if (jsqGetNext(v2, &elem2)) res = compareJsQuery(&elem1, &elem2); else res = 1; } else if (jsqGetNext(v2, &elem2)) { res = -1; } } return res; }
static void hashJsQuery(JsQueryItem *v, pg_crc32 *crc) { JsQueryItem elem; check_stack_depth(); COMP_CRC32(*crc, &v->type, sizeof(v->type)); switch(v->type) { case jqiNull: COMP_CRC32(*crc, "null", 5); break; case jqiKey: case jqiString: { int32 len; char *s; s = jsqGetString(v, &len); if (v->type == jqiKey) len++; /* include trailing '\0' */ COMP_CRC32(*crc, s, len); } break; case jqiNumeric: *crc ^= (pg_crc32)DatumGetInt32(DirectFunctionCall1( hash_numeric, PointerGetDatum(jsqGetNumeric(v)))); break; case jqiBool: { bool b = jsqGetBool(v); COMP_CRC32(*crc, &b, 1); } break; case jqiArray: COMP_CRC32(*crc, &v->array.nelems, sizeof(v->array.nelems)); while(jsqIterateArray(v, &elem)) hashJsQuery(&elem, crc); break; case jqiAnd: case jqiOr: jsqGetLeftArg(v, &elem); hashJsQuery(&elem, crc); jsqGetRightArg(v, &elem); hashJsQuery(&elem, crc); break; case jqiNot: case jqiEqual: case jqiIn: case jqiLess: case jqiGreater: case jqiLessOrEqual: case jqiGreaterOrEqual: case jqiContains: case jqiContained: case jqiOverlap: jsqGetArg(v, &elem); hashJsQuery(&elem, crc); break; case jqiCurrent: case jqiLength: case jqiAny: case jqiAnyArray: case jqiAnyKey: case jqiAll: case jqiAllArray: case jqiAllKey: break; default: elog(ERROR, "Unknown JsQueryItem type: %d", v->type); } }
static bool recursiveExecute(JsQueryItem *jsq, JsonbValue *jb, JsQueryItem *jsqLeftArg) { JsQueryItem elem; bool res = false; check_stack_depth(); switch(jsq->type) { case jqiAnd: jsqGetLeftArg(jsq, &elem); res = recursiveExecute(&elem, jb, jsqLeftArg); if (res == true) { jsqGetRightArg(jsq, &elem); res = recursiveExecute(&elem, jb, jsqLeftArg); } break; case jqiOr: jsqGetLeftArg(jsq, &elem); res = recursiveExecute(&elem, jb, jsqLeftArg); if (res == false) { jsqGetRightArg(jsq, &elem); res = recursiveExecute(&elem, jb, jsqLeftArg); } break; case jqiNot: jsqGetArg(jsq, &elem); res = !recursiveExecute(&elem, jb, jsqLeftArg); break; case jqiKey: if (JsonbType(jb) == jbvObject) { JsonbValue *v, key; key.type = jbvString; key.val.string.val = jsqGetString(jsq, &key.val.string.len); v = findJsonbValueFromContainer(jb->val.binary.data, JB_FOBJECT, &key); if (v != NULL) { jsqGetNext(jsq, &elem); res = recursiveExecute(&elem, v, NULL); pfree(v); } } break; case jqiCurrent: jsqGetNext(jsq, &elem); if (JsonbType(jb) == jbvScalar) { JsonbIterator *it; int32 r; JsonbValue v; it = JsonbIteratorInit(jb->val.binary.data); r = JsonbIteratorNext(&it, &v, true); Assert(r == WJB_BEGIN_ARRAY); Assert(v.val.array.rawScalar == 1); Assert(v.val.array.nElems == 1); r = JsonbIteratorNext(&it, &v, true); Assert(r == WJB_ELEM); res = recursiveExecute(&elem, &v, jsqLeftArg); } else { res = recursiveExecute(&elem, jb, jsqLeftArg); } break; case jqiAny: jsqGetNext(jsq, &elem); if (recursiveExecute(&elem, jb, NULL)) res = true; else if (jb->type == jbvBinary) res = recursiveAny(&elem, jb); break; case jqiAll: jsqGetNext(jsq, &elem); if ((res = recursiveExecute(&elem, jb, NULL)) == true) { if (jb->type == jbvBinary) res = recursiveAll(&elem, jb); } break; case jqiAnyArray: case jqiAllArray: if (JsonbType(jb) == jbvArray) { JsonbIterator *it; int32 r; JsonbValue v; jsqGetNext(jsq, &elem); it = JsonbIteratorInit(jb->val.binary.data); if (jsq->type == jqiAllArray) res = true; while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) { if (r == WJB_ELEM) { res = recursiveExecute(&elem, &v, NULL); if (jsq->type == jqiAnyArray) { if (res == true) break; } else if (jsq->type == jqiAllArray) { if (res == false) break; } } } } break; case jqiAnyKey: case jqiAllKey: if (JsonbType(jb) == jbvObject) { JsonbIterator *it; int32 r; JsonbValue v; jsqGetNext(jsq, &elem); it = JsonbIteratorInit(jb->val.binary.data); if (jsq->type == jqiAllKey) res = true; while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) { if (r == WJB_VALUE) { res = recursiveExecute(&elem, &v, NULL); if (jsq->type == jqiAnyKey) { if (res == true) break; } else if (jsq->type == jqiAllKey) { if (res == false) break; } } } } break; case jqiEqual: case jqiIn: case jqiLess: case jqiGreater: case jqiLessOrEqual: case jqiGreaterOrEqual: case jqiContains: case jqiContained: case jqiOverlap: jsqGetArg(jsq, &elem); res = executeExpr(&elem, jsq->type, jb, jsqLeftArg); break; case jqiLength: jsqGetNext(jsq, &elem); res = recursiveExecute(&elem, jb, jsq); break; case jqiIs: if (JsonbType(jb) == jbvScalar) { JsonbIterator *it; int32 r; JsonbValue v; it = JsonbIteratorInit(jb->val.binary.data); r = JsonbIteratorNext(&it, &v, true); Assert(r == WJB_BEGIN_ARRAY); Assert(v.val.array.rawScalar == 1); Assert(v.val.array.nElems == 1); r = JsonbIteratorNext(&it, &v, true); Assert(r == WJB_ELEM); res = (jsqGetIsType(jsq) == JsonbType(&v)); } else { res = (jsqGetIsType(jsq) == JsonbType(jb)); } break; default: elog(ERROR,"Wrong state: %d", jsq->type); } return res; }
/* * Estimate selectivity of single intquery operator */ static Selectivity int_query_opr_selec(ITEM *item, Datum *mcelems, float4 *mcefreqs, int nmcelems, float4 minfreq) { Selectivity selec; /* since this function recurses, it could be driven to stack overflow */ check_stack_depth(); if (item->type == VAL) { Datum *searchres; if (mcelems == NULL) return (Selectivity) DEFAULT_EQ_SEL; searchres = (Datum *) bsearch(&item->val, mcelems, nmcelems, sizeof(Datum), compare_val_int4); if (searchres) { /* * The element is in MCELEM. Return precise selectivity (or at * least as precise as ANALYZE could find out). */ selec = mcefreqs[searchres - mcelems]; } else { /* * The element is not in MCELEM. Punt, but assume that the * selectivity cannot be more than minfreq / 2. */ selec = Min(DEFAULT_EQ_SEL, minfreq / 2); } } else if (item->type == OPR) { /* Current query node is an operator */ Selectivity s1, s2; s1 = int_query_opr_selec(item - 1, mcelems, mcefreqs, nmcelems, minfreq); switch (item->val) { case (int32) '!': selec = 1.0 - s1; break; case (int32) '&': s2 = int_query_opr_selec(item + item->left, mcelems, mcefreqs, nmcelems, minfreq); selec = s1 * s2; break; case (int32) '|': s2 = int_query_opr_selec(item + item->left, mcelems, mcefreqs, nmcelems, minfreq); selec = s1 + s2 - s1 * s2; break; default: elog(ERROR, "unrecognized operator: %d", item->val); selec = 0; /* keep compiler quiet */ break; } } else { elog(ERROR, "unrecognized int query item type: %u", item->type); selec = 0; /* keep compiler quiet */ } /* Clamp intermediate results to stay sane despite roundoff error */ CLAMP_PROBABILITY(selec); return selec; }
static bool Cover(DocRepresentation *doc, int len, QueryRepresentation *qr, Extention *ext) { DocRepresentation *ptr; int lastpos = ext->pos; int i; bool found = false; /* * since this function recurses, it could be driven to stack overflow. * (though any decent compiler will optimize away the tail-recursion. */ check_stack_depth(); memset(qr->operandexist, 0, sizeof(bool) * qr->query->size); ext->p = 0x7fffffff; ext->q = 0; ptr = doc + ext->pos; /* find upper bound of cover from current position, move up */ while (ptr - doc < len) { for (i = 0; i < ptr->nitem; i++) { if (ptr->item[i]->type == QI_VAL) QR_SET_OPERAND_EXISTS(qr, ptr->item[i]); } if (TS_execute(GETQUERY(qr->query), (void *) qr, false, checkcondition_QueryOperand)) { if (ptr->pos > ext->q) { ext->q = ptr->pos; ext->end = ptr; lastpos = ptr - doc; found = true; } break; } ptr++; } if (!found) return false; memset(qr->operandexist, 0, sizeof(bool) * qr->query->size); ptr = doc + lastpos; /* find lower bound of cover from found upper bound, move down */ while (ptr >= doc + ext->pos) { for (i = 0; i < ptr->nitem; i++) if (ptr->item[i]->type == QI_VAL) QR_SET_OPERAND_EXISTS(qr, ptr->item[i]); if (TS_execute(GETQUERY(qr->query), (void *) qr, true, checkcondition_QueryOperand)) { if (ptr->pos < ext->p) { ext->begin = ptr; ext->p = ptr->pos; } break; } ptr--; } if (ext->p <= ext->q) { /* * set position for next try to next lexeme after beginning of found * cover */ ext->pos = (ptr - doc) + 1; return true; } ext->pos++; return Cover(doc, len, qr, ext); }
/* * Traverse the tsquery in preorder, calculating selectivity as: * * selec(left_oper) * selec(right_oper) in AND & PHRASE nodes, * * selec(left_oper) + selec(right_oper) - * selec(left_oper) * selec(right_oper) in OR nodes, * * 1 - select(oper) in NOT nodes * * histogram-based estimation in prefix VAL nodes * * freq[val] in exact VAL nodes, if the value is in MCELEM * min(freq[MCELEM]) / 2 in VAL nodes, if it is not * * The MCELEM array is already sorted (see ts_typanalyze.c), so we can use * binary search for determining freq[MCELEM]. * * If we don't have stats for the tsvector, we still use this logic, * except we use default estimates for VAL nodes. This case is signaled * by lookup == NULL. */ static Selectivity tsquery_opr_selec(QueryItem *item, char *operand, TextFreq *lookup, int length, float4 minfreq) { Selectivity selec; /* since this function recurses, it could be driven to stack overflow */ check_stack_depth(); if (item->type == QI_VAL) { QueryOperand *oper = (QueryOperand *) item; LexemeKey key; /* * Prepare the key for bsearch(). */ key.lexeme = operand + oper->distance; key.length = oper->length; if (oper->prefix) { /* Prefix match, ie the query item is lexeme:* */ Selectivity matched, allmces; int i, n_matched; /* * Our strategy is to scan through the MCELEM list and combine the * frequencies of the ones that match the prefix. We then * extrapolate the fraction of matching MCELEMs to the remaining * rows, assuming that the MCELEMs are representative of the whole * lexeme population in this respect. (Compare * histogram_selectivity().) Note that these are most common * elements not most common values, so they're not mutually * exclusive. We treat occurrences as independent events. * * This is only a good plan if we have a pretty fair number of * MCELEMs available; we set the threshold at 100. If no stats or * insufficient stats, arbitrarily use DEFAULT_TS_MATCH_SEL*4. */ if (lookup == NULL || length < 100) return (Selectivity) (DEFAULT_TS_MATCH_SEL * 4); matched = allmces = 0; n_matched = 0; for (i = 0; i < length; i++) { TextFreq *t = lookup + i; int tlen = VARSIZE_ANY_EXHDR(t->element); if (tlen >= key.length && strncmp(key.lexeme, VARDATA_ANY(t->element), key.length) == 0) { matched += t->frequency - matched * t->frequency; n_matched++; } allmces += t->frequency - allmces * t->frequency; } /* Clamp to ensure sanity in the face of roundoff error */ CLAMP_PROBABILITY(matched); CLAMP_PROBABILITY(allmces); selec = matched + (1.0 - allmces) * ((double) n_matched / length); /* * In any case, never believe that a prefix match has selectivity * less than we would assign for a non-MCELEM lexeme. This * preserves the property that "word:*" should be estimated to * match at least as many rows as "word" would be. */ selec = Max(Min(DEFAULT_TS_MATCH_SEL, minfreq / 2), selec); } else { /* Regular exact lexeme match */ TextFreq *searchres; /* If no stats for the variable, use DEFAULT_TS_MATCH_SEL */ if (lookup == NULL) return (Selectivity) DEFAULT_TS_MATCH_SEL; searchres = (TextFreq *) bsearch(&key, lookup, length, sizeof(TextFreq), compare_lexeme_textfreq); if (searchres) { /* * The element is in MCELEM. Return precise selectivity (or * at least as precise as ANALYZE could find out). */ selec = searchres->frequency; } else { /* * The element is not in MCELEM. Punt, but assume that the * selectivity cannot be more than minfreq / 2. */ selec = Min(DEFAULT_TS_MATCH_SEL, minfreq / 2); } } } else { /* Current TSQuery node is an operator */ Selectivity s1, s2; switch (item->qoperator.oper) { case OP_NOT: selec = 1.0 - tsquery_opr_selec(item + 1, operand, lookup, length, minfreq); break; case OP_PHRASE: case OP_AND: s1 = tsquery_opr_selec(item + 1, operand, lookup, length, minfreq); s2 = tsquery_opr_selec(item + item->qoperator.left, operand, lookup, length, minfreq); selec = s1 * s2; break; case OP_OR: s1 = tsquery_opr_selec(item + 1, operand, lookup, length, minfreq); s2 = tsquery_opr_selec(item + item->qoperator.left, operand, lookup, length, minfreq); selec = s1 + s2 - s1 * s2; break; default: elog(ERROR, "unrecognized operator: %d", item->qoperator.oper); selec = 0; /* keep compiler quiet */ break; } } /* Clamp intermediate results to stay sane despite roundoff error */ CLAMP_PROBABILITY(selec); return selec; }
/* * ExecMakeFunctionResultSet * * Evaluate the arguments to a set-returning function and then call the * function itself. The argument expressions may not contain set-returning * functions (the planner is supposed to have separated evaluation for those). * * This should be called in a short-lived (per-tuple) context, argContext * needs to live until all rows have been returned (i.e. *isDone set to * ExprEndResult or ExprSingleResult). * * This is used by nodeProjectSet.c. */ Datum ExecMakeFunctionResultSet(SetExprState *fcache, ExprContext *econtext, MemoryContext argContext, bool *isNull, ExprDoneCond *isDone) { List *arguments; Datum result; FunctionCallInfo fcinfo; PgStat_FunctionCallUsage fcusage; ReturnSetInfo rsinfo; bool callit; int i; restart: /* Guard against stack overflow due to overly complex expressions */ check_stack_depth(); /* * If a previous call of the function returned a set result in the form of * a tuplestore, continue reading rows from the tuplestore until it's * empty. */ if (fcache->funcResultStore) { TupleTableSlot *slot = fcache->funcResultSlot; MemoryContext oldContext; bool foundTup; /* * Have to make sure tuple in slot lives long enough, otherwise * clearing the slot could end up trying to free something already * freed. */ oldContext = MemoryContextSwitchTo(slot->tts_mcxt); foundTup = tuplestore_gettupleslot(fcache->funcResultStore, true, false, fcache->funcResultSlot); MemoryContextSwitchTo(oldContext); if (foundTup) { *isDone = ExprMultipleResult; if (fcache->funcReturnsTuple) { /* We must return the whole tuple as a Datum. */ *isNull = false; return ExecFetchSlotTupleDatum(fcache->funcResultSlot); } else { /* Extract the first column and return it as a scalar. */ return slot_getattr(fcache->funcResultSlot, 1, isNull); } } /* Exhausted the tuplestore, so clean up */ tuplestore_end(fcache->funcResultStore); fcache->funcResultStore = NULL; *isDone = ExprEndResult; *isNull = true; return (Datum) 0; } /* * arguments is a list of expressions to evaluate before passing to the * function manager. We skip the evaluation if it was already done in the * previous call (ie, we are continuing the evaluation of a set-valued * function). Otherwise, collect the current argument values into fcinfo. * * The arguments have to live in a context that lives at least until all * rows from this SRF have been returned, otherwise ValuePerCall SRFs * would reference freed memory after the first returned row. */ fcinfo = &fcache->fcinfo_data; arguments = fcache->args; if (!fcache->setArgsValid) { MemoryContext oldContext = MemoryContextSwitchTo(argContext); ExecEvalFuncArgs(fcinfo, arguments, econtext); MemoryContextSwitchTo(oldContext); } else { /* Reset flag (we may set it again below) */ fcache->setArgsValid = false; } /* * Now call the function, passing the evaluated parameter values. */ /* Prepare a resultinfo node for communication. */ fcinfo->resultinfo = (Node *) &rsinfo; rsinfo.type = T_ReturnSetInfo; rsinfo.econtext = econtext; rsinfo.expectedDesc = fcache->funcResultDesc; rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); /* note we do not set SFRM_Materialize_Random or _Preferred */ rsinfo.returnMode = SFRM_ValuePerCall; /* isDone is filled below */ rsinfo.setResult = NULL; rsinfo.setDesc = NULL; /* * If function is strict, and there are any NULL arguments, skip calling * the function. */ callit = true; if (fcache->func.fn_strict) { for (i = 0; i < fcinfo->nargs; i++) { if (fcinfo->argnull[i]) { callit = false; break; } } } if (callit) { pgstat_init_function_usage(fcinfo, &fcusage); fcinfo->isnull = false; rsinfo.isDone = ExprSingleResult; result = FunctionCallInvoke(fcinfo); *isNull = fcinfo->isnull; *isDone = rsinfo.isDone; pgstat_end_function_usage(&fcusage, rsinfo.isDone != ExprMultipleResult); } else { /* for a strict SRF, result for NULL is an empty set */ result = (Datum) 0; *isNull = true; *isDone = ExprEndResult; } /* Which protocol does function want to use? */ if (rsinfo.returnMode == SFRM_ValuePerCall) { if (*isDone != ExprEndResult) { /* * Save the current argument values to re-use on the next call. */ if (*isDone == ExprMultipleResult) { fcache->setArgsValid = true; /* Register cleanup callback if we didn't already */ if (!fcache->shutdown_reg) { RegisterExprContextCallback(econtext, ShutdownSetExpr, PointerGetDatum(fcache)); fcache->shutdown_reg = true; } } } } else if (rsinfo.returnMode == SFRM_Materialize) { /* check we're on the same page as the function author */ if (rsinfo.isDone != ExprSingleResult) ereport(ERROR, (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), errmsg("table-function protocol for materialize mode was not followed"))); if (rsinfo.setResult != NULL) { /* prepare to return values from the tuplestore */ ExecPrepareTuplestoreResult(fcache, econtext, rsinfo.setResult, rsinfo.setDesc); /* loop back to top to start returning from tuplestore */ goto restart; } /* if setResult was left null, treat it as empty set */ *isDone = ExprEndResult; *isNull = true; result = (Datum) 0; } else ereport(ERROR, (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), errmsg("unrecognized table-function returnMode: %d", (int) rsinfo.returnMode))); return result; }