/*------------------------------------------------------------------------- * * jsquery_op.c * Functions and operations over jsquery/jsonb datatypes * * Copyright (c) 2014, PostgreSQL Global Development Group * Author: Teodor Sigaev * * IDENTIFICATION * contrib/jsquery/jsquery_op.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "miscadmin.h" #include "utils/builtins.h" #include "utils/pg_crc.h" #if PG_VERSION_NUM >= 90500 /* * We have to keep same checksum algorithm as in pre-9.5 in order to be * pg_upgradeable. */ #define INIT_CRC32 INIT_LEGACY_CRC32 #define FIN_CRC32 FIN_LEGACY_CRC32 #define COMP_CRC32 COMP_LEGACY_CRC32 #endif #include "jsquery.h" static bool recursiveExecute(JsQueryItem *jsq, JsonbValue *jb, JsQueryItem *jsqLeftArg); static int compareNumeric(Numeric a, Numeric b) { return DatumGetInt32( DirectFunctionCall2( numeric_cmp, PointerGetDatum(a), PointerGetDatum(b) ) ); } #define jbvScalar jbvBinary static int JsonbType(JsonbValue *jb) { int type = jb->type; if (jb->type == jbvBinary) { JsonbContainer *jbc = jb->val.binary.data; if (jbc->header & JB_FSCALAR) type = jbvScalar; else if (jbc->header & JB_FOBJECT) type = jbvObject; else if (jbc->header & JB_FARRAY) type = jbvArray; else elog(ERROR, "Unknown container type: 0x%08x", jbc->header); } return type; } static bool recursiveAny(JsQueryItem *jsq, JsonbValue *jb) { bool res = false; JsonbIterator *it; int32 r; JsonbValue v; check_stack_depth(); it = JsonbIteratorInit(jb->val.binary.data); while(res == false && (r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) { if (r == WJB_KEY) { r = JsonbIteratorNext(&it, &v, true); Assert(r == WJB_VALUE); } if (r == WJB_VALUE || r == WJB_ELEM) { res = recursiveExecute(jsq, &v, NULL); if (res == false && v.type == jbvBinary) res = recursiveAny(jsq, &v); } } return res; } static bool recursiveAll(JsQueryItem *jsq, JsonbValue *jb) { bool res = true; JsonbIterator *it; int32 r; JsonbValue v; check_stack_depth(); it = JsonbIteratorInit(jb->val.binary.data); while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) { if (r == WJB_KEY) { r = JsonbIteratorNext(&it, &v, true); Assert(r == WJB_VALUE); } if (r == WJB_VALUE || r == WJB_ELEM) { if ((res = recursiveExecute(jsq, &v, NULL)) == true) { if (v.type == jbvBinary) res = recursiveAll(jsq, &v); } if (res == false) break; } } return res; } static bool checkScalarEquality(JsQueryItem *jsq, JsonbValue *jb) { int len; char *s; if (jsq->type == jqiAny) return true; if (jb->type == jbvBinary) return false; if ((int)jb->type != (int)jsq->type /* see enums */) return false; switch(jsq->type) { case jqiNull: return true; case jqiString: s = jsqGetString(jsq, &len); return (len == jb->val.string.len && memcmp(jb->val.string.val, s, len) == 0); case jqiBool: return (jb->val.boolean == jsqGetBool(jsq)); case jqiNumeric: return (compareNumeric(jsqGetNumeric(jsq), jb->val.numeric) == 0); default: elog(ERROR,"Wrong state"); } return false; } static bool checkArrayEquality(JsQueryItem *jsq, JsonbValue *jb) { int32 r; JsonbIterator *it; JsonbValue v; JsQueryItem elem; if (!(jsq->type == jqiArray && JsonbType(jb) == jbvArray)) return false; it = JsonbIteratorInit(jb->val.binary.data); r = JsonbIteratorNext(&it, &v, true); Assert(r == WJB_BEGIN_ARRAY); if (v.val.array.nElems != jsq->array.nelems) return false; while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) { if (r != WJB_ELEM) continue; jsqIterateArray(jsq, &elem); if (checkScalarEquality(&elem, &v) == false) return false; } return true; } static bool checkScalarIn(JsQueryItem *jsq, JsonbValue *jb) { JsQueryItem elem; if (jb->type == jbvBinary) return false; if (jsq->type != jqiArray) return false; while(jsqIterateArray(jsq, &elem)) if (checkScalarEquality(&elem, jb)) return true; return false; } static bool executeArrayOp(JsQueryItem *jsq, int32 op, JsonbValue *jb) { int32 r; JsonbIterator *it; JsonbValue v; JsQueryItem elem; if (JsonbType(jb) != jbvArray) return false; if (jsq->type != jqiArray) return false; if (op == jqiContains) { while(jsqIterateArray(jsq, &elem)) { bool res = false; it = JsonbIteratorInit(jb->val.binary.data); while(res == false && (r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) { if (r == WJB_ELEM && checkScalarEquality(&elem, &v)) res = true; } if (res == false) return false; } } else { it = JsonbIteratorInit(jb->val.binary.data); while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) { if (r == WJB_ELEM) { bool res = false; jsqIterateInit(jsq); while(jsqIterateArray(jsq, &elem)) { if (checkScalarEquality(&elem, &v)) { if (op == jqiOverlap) return true; res = true; break; } } if (op == jqiContained && res == false) return false; } } if (op == jqiOverlap) return false; } return true; } static bool makeCompare(JsQueryItem *jsq, int32 op, JsonbValue *jb) { int res; if (jb->type != jbvNumeric) return false; if (jsq->type != jqiNumeric) return false; res = compareNumeric(jb->val.numeric, jsqGetNumeric(jsq)); switch(op) { case jqiEqual: return (res == 0); case jqiLess: return (res < 0); case jqiGreater: return (res > 0); case jqiLessOrEqual: return (res <= 0); case jqiGreaterOrEqual: return (res >= 0); default: elog(ERROR, "Unknown operation"); } return false; } static bool executeExpr(JsQueryItem *jsq, int32 op, JsonbValue *jb, JsQueryItem *jsqLeftArg) { bool res = false; /* * read arg type */ Assert(jsqGetNext(jsq, NULL) == false); Assert(jsq->type == jqiAny || jsq->type == jqiString || jsq->type == jqiNumeric || jsq->type == jqiNull || jsq->type == jqiBool || jsq->type == jqiArray); if (jsqLeftArg && jsqLeftArg->type == jqiLength) { if (JsonbType(jb) == jbvArray || JsonbType(jb) == jbvObject) { int32 length; JsonbIterator *it; JsonbValue v; int r; it = JsonbIteratorInit(jb->val.binary.data); r = JsonbIteratorNext(&it, &v, true); Assert(r == WJB_BEGIN_ARRAY || r == WJB_BEGIN_OBJECT); length = (r == WJB_BEGIN_ARRAY) ? v.val.array.nElems : v.val.object.nPairs; v.type = jbvNumeric; v.val.numeric = DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(length))); switch(op) { case jqiEqual: case jqiLess: case jqiGreater: case jqiLessOrEqual: case jqiGreaterOrEqual: res = makeCompare(jsq, op, &v); break; case jqiIn: res = checkScalarIn(jsq, &v); break; case jqiOverlap: case jqiContains: case jqiContained: break; default: elog(ERROR, "Unknown operation"); } } } else { switch(op) { case jqiEqual: if (JsonbType(jb) == jbvArray && jsq->type == jqiArray) res = checkArrayEquality(jsq, jb); else res = checkScalarEquality(jsq, jb); break; case jqiIn: res = checkScalarIn(jsq, jb); break; case jqiOverlap: case jqiContains: case jqiContained: res = executeArrayOp(jsq, op, jb); break; case jqiLess: case jqiGreater: case jqiLessOrEqual: case jqiGreaterOrEqual: res = makeCompare(jsq, op, jb); break; default: elog(ERROR, "Unknown operation"); } } return res; } 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; } PG_FUNCTION_INFO_V1(jsquery_json_exec); Datum jsquery_json_exec(PG_FUNCTION_ARGS) { JsQuery *jq = PG_GETARG_JSQUERY(0); Jsonb *jb = PG_GETARG_JSONB(1); bool res; JsonbValue jbv; JsQueryItem jsq; jbv.type = jbvBinary; jbv.val.binary.data = &jb->root; jbv.val.binary.len = VARSIZE_ANY_EXHDR(jb); jsqInit(&jsq, jq); res = recursiveExecute(&jsq, &jbv, NULL); PG_FREE_IF_COPY(jq, 0); PG_FREE_IF_COPY(jb, 1); PG_RETURN_BOOL(res); } PG_FUNCTION_INFO_V1(json_jsquery_exec); Datum json_jsquery_exec(PG_FUNCTION_ARGS) { Jsonb *jb = PG_GETARG_JSONB(0); JsQuery *jq = PG_GETARG_JSQUERY(1); bool res; JsonbValue jbv; JsQueryItem jsq; jbv.type = jbvBinary; jbv.val.binary.data = &jb->root; jbv.val.binary.len = VARSIZE_ANY_EXHDR(jb); jsqInit(&jsq, jq); res = recursiveExecute(&jsq, &jbv, NULL); PG_FREE_IF_COPY(jb, 0); PG_FREE_IF_COPY(jq, 1); PG_RETURN_BOOL(res); } 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; } PG_FUNCTION_INFO_V1(jsquery_cmp); Datum jsquery_cmp(PG_FUNCTION_ARGS) { JsQuery *jq1 = PG_GETARG_JSQUERY(0); JsQuery *jq2 = PG_GETARG_JSQUERY(1); int32 res; JsQueryItem v1, v2; jsqInit(&v1, jq1); jsqInit(&v2, jq2); res = compareJsQuery(&v1, &v2); PG_FREE_IF_COPY(jq1, 0); PG_FREE_IF_COPY(jq2, 1); PG_RETURN_INT32(res); } PG_FUNCTION_INFO_V1(jsquery_lt); Datum jsquery_lt(PG_FUNCTION_ARGS) { JsQuery *jq1 = PG_GETARG_JSQUERY(0); JsQuery *jq2 = PG_GETARG_JSQUERY(1); int32 res; JsQueryItem v1, v2; jsqInit(&v1, jq1); jsqInit(&v2, jq2); res = compareJsQuery(&v1, &v2); PG_FREE_IF_COPY(jq1, 0); PG_FREE_IF_COPY(jq2, 1); PG_RETURN_BOOL(res < 0); } PG_FUNCTION_INFO_V1(jsquery_le); Datum jsquery_le(PG_FUNCTION_ARGS) { JsQuery *jq1 = PG_GETARG_JSQUERY(0); JsQuery *jq2 = PG_GETARG_JSQUERY(1); int32 res; JsQueryItem v1, v2; jsqInit(&v1, jq1); jsqInit(&v2, jq2); res = compareJsQuery(&v1, &v2); PG_FREE_IF_COPY(jq1, 0); PG_FREE_IF_COPY(jq2, 1); PG_RETURN_BOOL(res <= 0); } PG_FUNCTION_INFO_V1(jsquery_eq); Datum jsquery_eq(PG_FUNCTION_ARGS) { JsQuery *jq1 = PG_GETARG_JSQUERY(0); JsQuery *jq2 = PG_GETARG_JSQUERY(1); int32 res; JsQueryItem v1, v2; jsqInit(&v1, jq1); jsqInit(&v2, jq2); res = compareJsQuery(&v1, &v2); PG_FREE_IF_COPY(jq1, 0); PG_FREE_IF_COPY(jq2, 1); PG_RETURN_BOOL(res == 0); } PG_FUNCTION_INFO_V1(jsquery_ne); Datum jsquery_ne(PG_FUNCTION_ARGS) { JsQuery *jq1 = PG_GETARG_JSQUERY(0); JsQuery *jq2 = PG_GETARG_JSQUERY(1); int32 res; JsQueryItem v1, v2; jsqInit(&v1, jq1); jsqInit(&v2, jq2); res = compareJsQuery(&v1, &v2); PG_FREE_IF_COPY(jq1, 0); PG_FREE_IF_COPY(jq2, 1); PG_RETURN_BOOL(res != 0); } PG_FUNCTION_INFO_V1(jsquery_ge); Datum jsquery_ge(PG_FUNCTION_ARGS) { JsQuery *jq1 = PG_GETARG_JSQUERY(0); JsQuery *jq2 = PG_GETARG_JSQUERY(1); int32 res; JsQueryItem v1, v2; jsqInit(&v1, jq1); jsqInit(&v2, jq2); res = compareJsQuery(&v1, &v2); PG_FREE_IF_COPY(jq1, 0); PG_FREE_IF_COPY(jq2, 1); PG_RETURN_BOOL(res >= 0); } PG_FUNCTION_INFO_V1(jsquery_gt); Datum jsquery_gt(PG_FUNCTION_ARGS) { JsQuery *jq1 = PG_GETARG_JSQUERY(0); JsQuery *jq2 = PG_GETARG_JSQUERY(1); int32 res; JsQueryItem v1, v2; jsqInit(&v1, jq1); jsqInit(&v2, jq2); res = compareJsQuery(&v1, &v2); PG_FREE_IF_COPY(jq1, 0); PG_FREE_IF_COPY(jq2, 1); PG_RETURN_BOOL(res > 0); } 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); } } PG_FUNCTION_INFO_V1(jsquery_hash); Datum jsquery_hash(PG_FUNCTION_ARGS) { JsQuery *jq = PG_GETARG_JSQUERY(0); JsQueryItem v; pg_crc32 res; INIT_CRC32(res); jsqInit(&v, jq); hashJsQuery(&v, &res); FIN_CRC32(res); PG_FREE_IF_COPY(jq, 0); PG_RETURN_INT32(res); }