Repository: incubator-hawq Updated Branches: refs/heads/HAWQ-1114_ [created] 3988fa9d6
HAWQ-1114. Implement filter-push down for IN on HAWQ bridge side. Project: http://git-wip-us.apache.org/repos/asf/incubator-hawq/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-hawq/commit/f5369009 Tree: http://git-wip-us.apache.org/repos/asf/incubator-hawq/tree/f5369009 Diff: http://git-wip-us.apache.org/repos/asf/incubator-hawq/diff/f5369009 Branch: refs/heads/HAWQ-1114_ Commit: f5369009872e61bbbc83dfc296671f79f0094826 Parents: cb68111 Author: Oleksandr Diachenko <[email protected]> Authored: Tue Nov 22 15:36:49 2016 -0800 Committer: Oleksandr Diachenko <[email protected]> Committed: Tue Nov 22 15:36:49 2016 -0800 ---------------------------------------------------------------------- src/backend/access/external/pxffilters.c | 527 +++++++++++++++++-- .../access/external/test/pxffilters_test.c | 351 ++++++++++-- src/include/access/pxffilters.h | 37 +- src/include/catalog/pg_type.h | 1 + 4 files changed, 817 insertions(+), 99 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-hawq/blob/f5369009/src/backend/access/external/pxffilters.c ---------------------------------------------------------------------- diff --git a/src/backend/access/external/pxffilters.c b/src/backend/access/external/pxffilters.c index 3961b48..86f11e6 100644 --- a/src/backend/access/external/pxffilters.c +++ b/src/backend/access/external/pxffilters.c @@ -35,8 +35,13 @@ static List* pxf_make_expression_items_list(List *quals, Node *parent, int *logi static void pxf_free_filter(PxfFilterDesc* filter); static char* pxf_serialize_filter_list(List *filters); static bool opexpr_to_pxffilter(OpExpr *expr, PxfFilterDesc *filter); +static bool scalar_array_op_expr_to_pxffilter(ScalarArrayOpExpr *expr, PxfFilterDesc *filter); +static bool var_to_pxffilter(Var *var, PxfFilterDesc *filter); static bool supported_filter_type(Oid type); -static void const_to_str(Const *constval, StringInfo buf); +static bool supported_operator_type_op_expr(Oid type, PxfFilterDesc *filter); +static bool supported_operator_type_scalar_array_op_expr(Oid type, PxfFilterDesc *filter, bool useOr); +static void scalar_const_to_str(Const *constval, StringInfo buf); +static void list_const_to_str(Const *constval, StringInfo buf); static List* append_attr_from_var(Var* var, List* attrs); static void enrich_trivial_expression(List *expressionItems); @@ -46,7 +51,7 @@ static void enrich_trivial_expression(List *expressionItems); * down system catalog operators. * see pg_operator.h */ -dbop_pxfop_map pxf_supported_opr[] = +dbop_pxfop_map pxf_supported_opr_op_expr[] = { /* int2 */ {Int2EqualOperator /* int2eq */, PXFOP_EQ}, @@ -130,7 +135,7 @@ dbop_pxfop_map pxf_supported_opr[] = {1869 /* int82ne */, PXFOP_NE}, /* date */ - {DateEqualOperator /* eq */, PXFOP_EQ}, + {DateEqualOperator /* date_eq */, PXFOP_EQ}, {1095 /* date_lt */, PXFOP_LT}, {1097 /* date_gt */, PXFOP_GT}, {1096 /* date_le */, PXFOP_LE}, @@ -159,7 +164,16 @@ dbop_pxfop_map pxf_supported_opr[] = {1060 /* bpchargt */, PXFOP_GT}, {1059 /* bpcharle */, PXFOP_LE}, {1061 /* bpcharge */, PXFOP_GE}, - {1057 /* bpcharne */, PXFOP_NE} + {1057 /* bpcharne */, PXFOP_NE}, + + /* boolean */ + {BooleanEqualOperator /* booleq */, PXFOP_EQ}, + {58 /* boollt */, PXFOP_LT}, + {59 /* boolgt */, PXFOP_GT}, + {1694 /* boolle */, PXFOP_LE}, + {1695 /* boolge */, PXFOP_GE}, + {85 /* boolne */, PXFOP_NE} + /* bytea */ // TODO: uncomment once HAWQ-1085 is done @@ -172,6 +186,59 @@ dbop_pxfop_map pxf_supported_opr[] = }; + +dbop_pxfop_array_map pxf_supported_opr_scalar_array_op_expr[] = +{ + /* int2 */ + {Int2EqualOperator /* int2eq */, PXFOP_IN, true}, + + /* int4 */ + {Int4EqualOperator /* int4eq */, PXFOP_IN, true}, + + /* int8 */ + {Int8EqualOperator /* int8eq */, PXFOP_IN, true}, + + /* text */ + {TextEqualOperator /* texteq */, PXFOP_IN, true}, + + /* int2 to int4 */ + {Int24EqualOperator /* int24eq */, PXFOP_IN, true}, + + /* int4 to int2 */ + {Int42EqualOperator /* int42eq */, PXFOP_IN, true}, + + /* int8 to int4 */ + {Int84EqualOperator /* int84eq */, PXFOP_IN, true}, + + /* int4 to int8 */ + {Int48EqualOperator /* int48eq */, PXFOP_IN, true}, + + /* int2 to int8 */ + {Int28EqualOperator /* int28eq */, PXFOP_IN, true}, + + /* int8 to int2 */ + {Int82EqualOperator /* int82eq */, PXFOP_IN, true}, + + /* date */ + {DateEqualOperator /* date_eq */, PXFOP_IN, true}, + + /* float8 */ + {Float8EqualOperator /* float8eq */, PXFOP_IN, true}, + + /* float48 */ + {1120 /* float48eq */, PXFOP_IN, true}, + + /* bpchar */ + {BPCharEqualOperator /* bpchareq */, PXFOP_IN, true}, + + /* boolean */ + {BooleanEqualOperator /* booleq */, PXFOP_IN, true}, + + /* bytea */ + // TODO: uncomment once HAWQ-1085 is done + //,{ByteaEqualOperator /* byteaeq */, PXFOP_IN, true}, +}; + Oid pxf_supported_types[] = { INT2OID, @@ -187,7 +254,13 @@ Oid pxf_supported_types[] = BYTEAOID, BOOLOID, DATEOID, - TIMESTAMPOID + TIMESTAMPOID, + /* complex datatypes*/ + INT2ARRAYOID, + INT4ARRAYOID, + INT8ARRAYOID, + BOOLARRAYOID, + TEXTARRAYOID }; static void @@ -248,7 +321,9 @@ pxf_make_expression_items_list(List *quals, Node *parent, int *logicalOpsNum) switch (tag) { + case T_Var: // IN(single_value) case T_OpExpr: + case T_ScalarArrayOpExpr: case T_NullTest: { result = lappend(result, expressionItem); @@ -274,10 +349,27 @@ pxf_make_expression_items_list(List *quals, Node *parent, int *logicalOpsNum) } } - for (int i = 0; i < childNodesNum - 1; i++) + if (expr->boolop == NOT_EXPR) { - result = lappend(result, expressionItem); + for (int i = 0; i < childNodesNum; i++) + { + result = lappend(result, expressionItem); + } + } else if (expr->boolop == AND_EXPR || expr->boolop == OR_EXPR) + { + for (int i = 0; i < childNodesNum - 1; i++) + { + result = lappend(result, expressionItem); + } + } else + { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("internal error in pxffilters.c:pxf_make_expression_items_list. " + "Found unknown boolean expression type"))); } + + break; } default: @@ -364,7 +456,6 @@ pxf_serialize_filter_list(List *expressionItems) return NULL; resbuf = makeStringInfo(); - initStringInfo(resbuf); /* * Iterate through the expression items in the list and serialize them one after the other. @@ -377,6 +468,46 @@ pxf_serialize_filter_list(List *expressionItems) switch (tag) { + case T_Var: + { + elog(DEBUG1, "pxf_serialize_filter_list: node tag %d (T_Var)", tag); + PxfFilterDesc *filter = (PxfFilterDesc *) palloc0(sizeof(PxfFilterDesc)); + Var *var = (Var *) node; + if (var_to_pxffilter(var, filter)) + { + PxfOperand l = filter->l; + PxfOperand r = filter->r; + PxfOperatorCode o = filter->op; + if (pxfoperand_is_attr(l) && pxfoperand_is_scalar_const(r)) + { + appendStringInfo(resbuf, "%c%d%c%d%c%lu%c%s", + PXF_ATTR_CODE, l.attnum - 1, /* Java attrs are 0-based */ + PXF_SCALAR_CONST_CODE, r.consttype, + PXF_SIZE_BYTES, strlen(r.conststr->data), + PXF_CONST_DATA, (r.conststr)->data); + } + else + { + /* var_to_pxffilter() should have never let this happen */ + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("internal error in pxffilters.c:pxf_serialize_" + "filter_list. Found a non const+attr filter"))); + } + appendStringInfo(resbuf, "%c%d", PXF_OPERATOR_CODE, o); + pxf_free_filter(filter); + + } + else + { + /* if at least one expression item is not supported, whole filter doesn't make sense*/ + elog(DEBUG1, "Query will not be optimized to use filter push-down."); + pfree(filter); + pfree(resbuf->data); + return NULL; + } + break; + } case T_OpExpr: { elog(DEBUG1, "pxf_serialize_filter_list: node tag %d (T_OpExpr)", tag); @@ -387,18 +518,18 @@ pxf_serialize_filter_list(List *expressionItems) PxfOperand l = filter->l; PxfOperand r = filter->r; PxfOperatorCode o = filter->op; - if (pxfoperand_is_attr(l) && pxfoperand_is_const(r)) + if (pxfoperand_is_attr(l) && pxfoperand_is_scalar_const(r)) { appendStringInfo(resbuf, "%c%d%c%d%c%lu%c%s", PXF_ATTR_CODE, l.attnum - 1, /* Java attrs are 0-based */ - PXF_CONST_CODE, r.consttype, + PXF_SCALAR_CONST_CODE, r.consttype, PXF_SIZE_BYTES, strlen(r.conststr->data), PXF_CONST_DATA, (r.conststr)->data); } - else if (pxfoperand_is_const(l) && pxfoperand_is_attr(r)) + else if (pxfoperand_is_scalar_const(l) && pxfoperand_is_attr(r)) { appendStringInfo(resbuf, "%c%d%c%lu%c%s%c%d", - PXF_CONST_CODE, l.consttype, + PXF_SCALAR_CONST_CODE, l.consttype, PXF_SIZE_BYTES, strlen(l.conststr->data), PXF_CONST_DATA, (l.conststr)->data, PXF_ATTR_CODE, r.attnum - 1); /* Java attrs are 0-based */ @@ -422,6 +553,49 @@ pxf_serialize_filter_list(List *expressionItems) } break; } + case T_ScalarArrayOpExpr: + { + elog(DEBUG1, "pxf_serialize_filter_list: node tag %d (T_ScalarArrayOpExpr)", tag); + ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node; + PxfFilterDesc *filter = (PxfFilterDesc *) palloc0(sizeof(PxfFilterDesc)); + if (scalar_array_op_expr_to_pxffilter(expr, filter)) + { + PxfOperand l = filter->l; + PxfOperand r = filter->r; + PxfOperatorCode o = filter->op; + if (pxfoperand_is_attr(l) && pxfoperand_is_list_const(r)) + { + appendStringInfo(resbuf, "%c%d%c%d%s", + PXF_ATTR_CODE, l.attnum - 1, /* Java attrs are 0-based */ + PXF_LIST_CONST_CODE, r.consttype, + r.conststr->data); + } + else if (pxfoperand_is_list_const(l) && pxfoperand_is_attr(r)) + { + appendStringInfo(resbuf, "%c%d%s%c%d", + PXF_SCALAR_CONST_CODE, l.consttype, + l.conststr->data, + PXF_ATTR_CODE, r.attnum - 1); /* Java attrs are 0-based */ + } + else + { + /* scalararrayopexpr_to_pxffilter() should have never let this happen */ + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("internal error in pxffilters.c:pxf_serialize_" + "filter_list. Found a non const+attr filter"))); + } + appendStringInfo(resbuf, "%c%d", PXF_OPERATOR_CODE, o); + pxf_free_filter(filter); + } else { + /* if at least one expression item is not supported, whole filter doesn't make sense*/ + elog(DEBUG1, "Query will not be optimized to use filter push-down."); + pfree(filter); + pfree(resbuf->data); + return NULL; + } + break; + } case T_BoolExpr: { BoolExpr *expr = (BoolExpr *) node; @@ -479,8 +653,6 @@ pxf_serialize_filter_list(List *expressionItems) static bool opexpr_to_pxffilter(OpExpr *expr, PxfFilterDesc *filter) { - int i; - int nargs = sizeof(pxf_supported_opr) / sizeof(dbop_pxfop_map); Node *leftop = NULL; Node *rightop = NULL; Oid rightop_type = InvalidOid; @@ -514,6 +686,12 @@ opexpr_to_pxffilter(OpExpr *expr, PxfFilterDesc *filter) if (!supported_filter_type(rightop_type) || !supported_filter_type(leftop_type)) return false; + /* + * check if supported operator - + */ + if (!supported_operator_type_op_expr(expr->opno, filter)) + return false; + /* arguments must be VAR and CONST */ if (IsA(leftop, Var) && IsA(rightop, Const)) { @@ -523,20 +701,18 @@ opexpr_to_pxffilter(OpExpr *expr, PxfFilterDesc *filter) if (filter->l.attnum <= InvalidAttrNumber) return false; /* system attr not supported */ - filter->r.opcode = PXF_CONST_CODE; + filter->r.opcode = PXF_SCALAR_CONST_CODE; filter->r.attnum = InvalidAttrNumber; filter->r.conststr = makeStringInfo(); - initStringInfo(filter->r.conststr); - const_to_str((Const *)rightop, filter->r.conststr); + scalar_const_to_str((Const *)rightop, filter->r.conststr); filter->r.consttype = ((Const *)rightop)->consttype; } else if (IsA(leftop, Const) && IsA(rightop, Var)) { - filter->l.opcode = PXF_CONST_CODE; + filter->l.opcode = PXF_SCALAR_CONST_CODE; filter->l.attnum = InvalidAttrNumber; filter->l.conststr = makeStringInfo(); - initStringInfo(filter->l.conststr); - const_to_str((Const *)leftop, filter->l.conststr); + scalar_const_to_str((Const *)leftop, filter->l.conststr); filter->l.consttype = ((Const *)leftop)->consttype; filter->r.opcode = PXF_ATTR_CODE; @@ -551,23 +727,110 @@ opexpr_to_pxffilter(OpExpr *expr, PxfFilterDesc *filter) return false; } - /* is operator supported? if so, set the corresponding PXFOP */ - for (i = 0; i < nargs; i++) + return true; +} + +static bool +scalar_array_op_expr_to_pxffilter(ScalarArrayOpExpr *expr, PxfFilterDesc *filter) +{ + + Node *leftop = NULL; + Node *rightop = NULL; + + leftop = (Node *) linitial(expr->args); + rightop = (Node *) lsecond(expr->args); + Oid leftop_type = exprType(leftop); + Oid rightop_type = exprType(rightop); + + /* + * check if supported type - + */ + if (!supported_filter_type(rightop_type) || !supported_filter_type(leftop_type)) + return false; + + /* + * check if supported operator - + */ + if(!supported_operator_type_scalar_array_op_expr(expr->opno, filter, expr->useOr)) + return false; + + if (IsA(leftop, Var) && IsA(rightop, Const)) { - /* NOTE: switch to hash table lookup if */ - /* array grows. for now it's cheap enough */ - if(expr->opno == pxf_supported_opr[i].dbop) - { - filter->op = pxf_supported_opr[i].pxfop; - return true; /* filter qualifies! */ - } + filter->l.opcode = PXF_ATTR_CODE; + filter->l.attnum = ((Var *) leftop)->varattno; + filter->l.consttype = InvalidOid; + if (filter->l.attnum <= InvalidAttrNumber) + return false; /* system attr not supported */ + + filter->r.opcode = PXF_LIST_CONST_CODE; + filter->r.attnum = InvalidAttrNumber; + filter->r.conststr = makeStringInfo(); + list_const_to_str((Const *)rightop, filter->r.conststr); + filter->r.consttype = ((Const *)rightop)->consttype; } + else if (IsA(leftop, Const) && IsA(rightop, Var)) + { + filter->l.opcode = PXF_LIST_CONST_CODE; + filter->l.attnum = InvalidAttrNumber; + filter->l.conststr = makeStringInfo(); + list_const_to_str((Const *)leftop, filter->l.conststr); + filter->l.consttype = ((Const *)leftop)->consttype; - elog(DEBUG1, "opexpr_to_pxffilter: operator is not supported, operator code: %d", expr->opno); + filter->r.opcode = PXF_ATTR_CODE; + filter->r.attnum = ((Var *) rightop)->varattno; + filter->r.consttype = InvalidOid; + if (filter->r.attnum <= InvalidAttrNumber) + return false; /* system attr not supported */ + } + else + { + elog(DEBUG1, "pxf_serialize_filter_list: expression is not a Var+Const"); + return false; + } - /* NOTE: if more validation needed, add it before the operators test - * or alternatively change it to use a false flag and return true below */ - return false; + + + return true; +} + +static bool +var_to_pxffilter(Var *var, PxfFilterDesc *filter) +{ + Oid var_type = InvalidOid; + + if ((!var) || (!filter)) + return false; + + var_type = exprType((Node *)var); + + /* + * check if supported type - + */ + if (!supported_filter_type(var_type)) + return false; + + /* arguments must be VAR and CONST */ + if (IsA(var, Var)) + { + filter->l.opcode = PXF_ATTR_CODE; + filter->l.attnum = var->varattno; + filter->l.consttype = InvalidOid; + if (filter->l.attnum <= InvalidAttrNumber) + return false; /* system attr not supported */ + + filter->r.opcode = PXF_SCALAR_CONST_CODE; + filter->r.attnum = InvalidAttrNumber; + filter->r.conststr = makeStringInfo(); + appendStringInfo(filter->r.conststr, TrueConstValue); + filter->r.consttype = BOOLOID; + } + else + { + elog(DEBUG1, "var_to_pxffilter: expression is not a Var"); + return false; + } + + return true; } static List* @@ -654,6 +917,55 @@ supported_filter_type(Oid type) return false; } + +static bool +supported_operator_type_op_expr(Oid type, PxfFilterDesc *filter) +{ + + int nargs = sizeof(pxf_supported_opr_op_expr) / sizeof(dbop_pxfop_map); + int i; + /* is operator supported? if so, set the corresponding PXFOP */ + for (i = 0; i < nargs; i++) + { + /* NOTE: switch to hash table lookup if */ + /* array grows. for now it's cheap enough */ + if(type == pxf_supported_opr_op_expr[i].dbop) + { + filter->op = pxf_supported_opr_op_expr[i].pxfop; + return true; /* filter qualifies! */ + } + } + + elog(DEBUG1, "opexpr_to_pxffilter: operator is not supported, operator code: %d", type); + + return false; +} + +static bool +supported_operator_type_scalar_array_op_expr(Oid type, PxfFilterDesc *filter, bool useOr) +{ + + int nargs = sizeof(pxf_supported_opr_scalar_array_op_expr) / sizeof(dbop_pxfop_array_map); + int i; + /* is operator supported? if so, set the corresponding PXFOP */ + for (i = 0; i < nargs; i++) + { + /* NOTE: switch to hash table lookup if */ + /* array grows. for now it's cheap enough */ + if(useOr == pxf_supported_opr_scalar_array_op_expr[i].useOr && type == pxf_supported_opr_scalar_array_op_expr[i].dbop) + { + filter->op = pxf_supported_opr_scalar_array_op_expr[i].pxfop; + return true; /* filter qualifies! */ + } + } + + elog(DEBUG1, "supported_operator_type_scalar_array_op_expr: operator is not supported, operator code: %d", type); + + return false; +} + + + /* * const_to_str * @@ -661,7 +973,7 @@ supported_filter_type(Oid type) * type is text based, make sure to escape the value with surrounding quotes. */ static void -const_to_str(Const *constval, StringInfo buf) +scalar_const_to_str(Const *constval, StringInfo buf) { Oid typoutput; bool typIsVarlena; @@ -670,7 +982,7 @@ const_to_str(Const *constval, StringInfo buf) if (constval->constisnull) { /* TODO: test this edge case and its consequences */ - appendStringInfo(buf, "\"NULL\""); + appendStringInfo(buf, NullConstValue); return; } @@ -696,19 +1008,17 @@ const_to_str(Const *constval, StringInfo buf) case TIMESTAMPOID: appendStringInfo(buf, "%s", extval); break; - case BOOLOID: if (strcmp(extval, "t") == 0) - appendStringInfo(buf, "true"); + appendStringInfo(buf, TrueConstValue); else - appendStringInfo(buf, "false"); + appendStringInfo(buf, FalseConstValue); break; - default: /* should never happen. we filter on types earlier */ ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("internal error in pxffilters.c:const_to_str. " + errmsg("internal error in pxffilters.c:scalar_const_to_str. " "Using unsupported data type (%d) (value %s)", constval->consttype, extval))); @@ -719,6 +1029,143 @@ const_to_str(Const *constval, StringInfo buf) /* + * list_const_to_str + * + */ +static void +list_const_to_str(Const *constval, StringInfo buf) +{ + StringInfo interm_buf; + Datum *dats; + ArrayType *arr; + int len; + + if (constval->constisnull) + { + elog(DEBUG1, "Null constant is not expected in this context."); + return; + } + + if (constval->constbyval) { + elog(DEBUG1, "Constant passed by value is not expected in this context."); + return; + } + + arr = DatumGetArrayTypeP(constval->constvalue); + + interm_buf = makeStringInfo(); + + switch (constval->consttype) + { + case INT2ARRAYOID: + { + int16 value; + deconstruct_array(arr, INT2OID, sizeof (value), true, 's', &dats, NULL, &len); + + for (int i = 0; i < len; i++) + { + value = DatumGetInt16(dats[i]); + + appendStringInfo(interm_buf, "%hd", value); + + appendStringInfo(buf, "%c%d%c%s", + PXF_SIZE_BYTES, interm_buf->len, + PXF_CONST_DATA, interm_buf->data); + resetStringInfo(interm_buf); + } + break; + } + case INT4ARRAYOID: + { + int32 value; + deconstruct_array(arr, INT4OID, sizeof (value), true, 'i', &dats, NULL, &len); + + for (int i = 0; i < len; i++) + { + value = DatumGetInt32(dats[i]); + + appendStringInfo(interm_buf, "%d", value); + + appendStringInfo(buf, "%c%d%c%s", + PXF_SIZE_BYTES, interm_buf->len, + PXF_CONST_DATA, interm_buf->data); + resetStringInfo(interm_buf); + } + break; + } + case INT8ARRAYOID: + { + int64 value; + deconstruct_array(arr, INT8OID, sizeof (value), true, 'd', &dats, NULL, &len); + + for (int i = 0; i < len; i++) + { + value = DatumGetInt64(dats[i]); + + appendStringInfo(interm_buf, "%ld", value); + + appendStringInfo(buf, "%c%d%c%s", + PXF_SIZE_BYTES, interm_buf->len, + PXF_CONST_DATA, interm_buf->data); + resetStringInfo(interm_buf); + } + break; + } + case TEXTARRAYOID: + { + char *value; + + deconstruct_array(arr, TEXTOID, -1, false, 'i', &dats, NULL, &len); + + for (int i = 0; i < len; i++) + { + value = DatumGetCString(DirectFunctionCall1(textout, dats[i])); + + appendStringInfo(interm_buf, "%s", value); + + appendStringInfo(buf, "%c%d%c%s", + PXF_SIZE_BYTES, interm_buf->len, + PXF_CONST_DATA, interm_buf->data); + resetStringInfo(interm_buf); + } + break; + } + case BOOLARRAYOID: + { + bool value; + deconstruct_array(arr, BOOLOID, sizeof (value), false, 'c', &dats, NULL, &len); + + for (int i = 0; i < len; i++) + { + value = DatumGetBool(dats[i]); + if (value) + appendStringInfo(buf, "%c%lu%c%s", + PXF_SIZE_BYTES, strlen(TrueConstValue), + PXF_CONST_DATA, TrueConstValue); + else + { + appendStringInfo(buf, "%c%lu%c%s", + PXF_SIZE_BYTES, strlen(FalseConstValue), + PXF_CONST_DATA, FalseConstValue); + } + } + break; + } + default: + /* should never happen. we filter on types earlier */ + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("internal error in pxffilters.c:list_const_to_str. " + "Using unsupported data type (%d)", + constval->consttype))); + + } + + pfree(interm_buf->data); +} + + +/* * serializePxfFilterQuals * * Wrapper around pxf_make_filter_list -> pxf_serialize_filter_list. http://git-wip-us.apache.org/repos/asf/incubator-hawq/blob/f5369009/src/backend/access/external/test/pxffilters_test.c ---------------------------------------------------------------------- diff --git a/src/backend/access/external/test/pxffilters_test.c b/src/backend/access/external/test/pxffilters_test.c index e618563..2ed8850 100644 --- a/src/backend/access/external/test/pxffilters_test.c +++ b/src/backend/access/external/test/pxffilters_test.c @@ -25,8 +25,10 @@ #include "c.h" #include "../pxffilters.c" -void run__const_to_str(Const* input, StringInfo result, char* expected); -void run__const_to_str__negative(Const* input, StringInfo result, char* value); +void run__scalar_const_to_str(Const* input, StringInfo result, char* expected); +void run__scalar_const_to_str__negative(Const* input, StringInfo result, char* value); +void run__list_const_to_str(Const* input, StringInfo result, char* expected); +void run__list_const_to_str__negative(Const* input, StringInfo result, int len, Datum *dats); void test__supported_filter_type(void **state) @@ -65,7 +67,7 @@ test__supported_filter_type(void **state) /* go over pxf_supported_types array */ int nargs = sizeof(pxf_supported_types) / sizeof(Oid); - assert_int_equal(nargs, 14); + assert_int_equal(nargs, 19); for (i = 0; i < nargs; ++i) { assert_true(supported_filter_type(pxf_supported_types[i])); @@ -73,11 +75,109 @@ test__supported_filter_type(void **state) } +void +test__supported_operator_type_op_expr(void **state) +{ + Oid operator_oids[13][2] = { + { Int2EqualOperator, PXFOP_EQ }, + { 95, PXFOP_LT }, + { 520, PXFOP_GT }, + { 522, PXFOP_LE }, + { 524, PXFOP_GE }, + { 519, PXFOP_NE }, + { Int4EqualOperator, PXFOP_EQ }, + { 97, PXFOP_LT }, + { 521, PXFOP_GT }, + { 523, PXFOP_LE }, + { 525, PXFOP_GE }, + { 518, PXFOP_NE }, + { InvalidOid, InvalidOid } + }; + + PxfFilterDesc *filter = (PxfFilterDesc*) palloc0(sizeof(PxfFilterDesc)); + + int array_size = sizeof(operator_oids) / sizeof(operator_oids[0]); + bool result = false; + int i = 0; + + /* supported types */ + for (; i < array_size-1; ++i) + { + result = supported_operator_type_op_expr(operator_oids[i][0], filter); + assert_true(result); + assert_true(operator_oids[i][1] == filter->op); + } + + /* unsupported type */ + result = supported_operator_type_op_expr(operator_oids[i][0], filter); + assert_false(result); + + /* go over pxf_supported_opr_op_expr array */ + int nargs = sizeof(pxf_supported_opr_op_expr) / sizeof(dbop_pxfop_map); + assert_int_equal(nargs, 91); + for (i = 0; i < nargs; ++i) + { + assert_true(supported_operator_type_op_expr(pxf_supported_opr_op_expr[i].dbop, filter)); + assert_true(pxf_supported_opr_op_expr[i].pxfop == filter->op); + } + +} + +void +test__supported_operator_type_scalar_array_op_expr(void **state) +{ + Oid operator_oids[15][2] = { + {Int2EqualOperator, PXFOP_IN}, + {Int4EqualOperator, PXFOP_IN}, + {Int8EqualOperator, PXFOP_IN}, + {TextEqualOperator, PXFOP_IN}, + {Int24EqualOperator, PXFOP_IN}, + {Int42EqualOperator, PXFOP_IN}, + {Int84EqualOperator, PXFOP_IN}, + {Int48EqualOperator, PXFOP_IN}, + {Int28EqualOperator, PXFOP_IN}, + {Int82EqualOperator, PXFOP_IN}, + {DateEqualOperator, PXFOP_IN}, + {Float8EqualOperator, PXFOP_IN}, + {1120 , PXFOP_IN}, + {BPCharEqualOperator, PXFOP_IN}, + {BooleanEqualOperator, PXFOP_IN}, + }; + + PxfFilterDesc *filter = (PxfFilterDesc*) palloc0(sizeof(PxfFilterDesc)); + + int array_size = sizeof(operator_oids) / sizeof(operator_oids[0]); + bool result = false; + int i = 0; + + /* supported types */ + for (; i < array_size-1; ++i) + { + result = supported_operator_type_scalar_array_op_expr(operator_oids[i][0], filter, true); + assert_true(result); + assert_true(operator_oids[i][1] == filter->op); + } + + /* unsupported type */ + result = supported_operator_type_op_expr(InvalidOid, filter); + assert_false(result); + + /* go over pxf_supported_opr_scalar_array_op_expr array */ + int nargs = sizeof(pxf_supported_opr_scalar_array_op_expr) / sizeof(dbop_pxfop_array_map); + assert_int_equal(nargs, 15); + for (i = 0; i < nargs; ++i) + { + assert_true(supported_operator_type_scalar_array_op_expr(pxf_supported_opr_op_expr[i].dbop, filter, pxf_supported_opr_scalar_array_op_expr[i].useOr)); + assert_true(pxf_supported_opr_scalar_array_op_expr[i].pxfop == filter->op); + } + +} + /* - * const_value must be palloc'ed, it will be freed by const_to_str + * const_value must be palloc'ed, it will be freed by scalar_const_to_str */ void -mock__const_to_str(Oid const_type, char* const_value) +mock__scalar_const_to_str(Oid const_type, char* const_value) { expect_value(getTypeOutputInfo, type, const_type); expect_any(getTypeOutputInfo, typOutput); @@ -89,8 +189,41 @@ mock__const_to_str(Oid const_type, char* const_value) will_return(OidOutputFunctionCall, const_value); } + +/* + * const_value must be palloc'ed, it will be freed by list_const_to_str + */ +void +mock__list_const_to_str(Oid const_type, int len, Datum *dats) { + + expect_any(pg_detoast_datum, datum); + will_return(pg_detoast_datum, NULL); + + expect_any(deconstruct_array, array); + expect_any(deconstruct_array, elmtype); + expect_any(deconstruct_array, elmlen); + expect_any(deconstruct_array, elmbyval); + expect_any(deconstruct_array, elmalign); + expect_any(deconstruct_array, elemsp); + expect_any(deconstruct_array, nullsp); + expect_any(deconstruct_array, nelemsp); + will_return(deconstruct_array, NULL); + will_assign_value(deconstruct_array, nelemsp, len); + will_assign_value(deconstruct_array, elemsp, dats); + + if (const_type == TEXTARRAYOID) + { + for (int i = 0; i < len; i++) + { + expect_any(DirectFunctionCall1, func); + expect_any(DirectFunctionCall1, arg1); + will_return(DirectFunctionCall1, dats[i]); + } + } +} + void -verify__const_to_str(bool is_null, char* const_value, Oid const_type, char* expected) +verify__scalar_const_to_str(bool is_null, char* const_value, Oid const_type, char* expected) { StringInfo result = makeStringInfo(); char* value = NULL; @@ -101,20 +234,20 @@ verify__const_to_str(bool is_null, char* const_value, Oid const_type, char* expe /* need to prepare inner functions */ if (!is_null) { - value = strdup(const_value); /* will be free'd by const_to_str */ + value = strdup(const_value); /* will be free'd by scalar_const_to_str */ - mock__const_to_str(const_type, value); + mock__scalar_const_to_str(const_type, value); } /* no expected value means it's a negative test */ if (expected) { - run__const_to_str(input, result, expected); + run__scalar_const_to_str(input, result, expected); } else { - run__const_to_str__negative(input, result, value); - pfree(value); /* value was not freed by const_to_str b/c of failure */ + run__scalar_const_to_str__negative(input, result, value); + pfree(value); /* value was not freed by scalar_const_to_str b/c of failure */ } pfree(result->data); @@ -122,25 +255,135 @@ verify__const_to_str(bool is_null, char* const_value, Oid const_type, char* expe pfree(input); } -void run__const_to_str(Const* input, StringInfo result, char* expected) +void +verify__list_const_to_str(Oid const_type, char* expected, int len, Datum *dats) { - const_to_str(input, result); + StringInfo result = makeStringInfo(); + Const* input = (Const*) palloc0(sizeof(Const)); + input->constisnull = false; + input->consttype = const_type; + + /* need to prepare inner functions */ + mock__list_const_to_str(const_type, len, dats); + + /* no expected value means it's a negative test */ + if (expected) + { + run__list_const_to_str(input, result, expected); + } + else + { + run__list_const_to_str__negative(input, result, len, dats); + } + + pfree(result->data); + pfree(result); + pfree(input); +} + +void +test__list_const_to_str__int(void **state) { + + Datum dats8[3] = {Int8GetDatum(1), Int8GetDatum(2), Int8GetDatum(3)}; + + verify__list_const_to_str(INT2ARRAYOID, "s1d1s1d2s1d3", 3, dats8); + + Datum dats16[1] = {Int16GetDatum(42)}; + verify__list_const_to_str(INT4ARRAYOID, "s2d42", 1, dats16); + + Datum dats32[2] = {Int32GetDatum(11), Int32GetDatum(22)}; + verify__list_const_to_str(INT4ARRAYOID, "s2d11s2d22", 2, dats32); +} + + +void +test__list_const_to_str__boolean(void **state) +{ + Datum dats1[2] = {BoolGetDatum(true), BoolGetDatum(false)}; + verify__list_const_to_str(BOOLARRAYOID, "s4dtrues5dfalse", 2, dats1); + + Datum dats2[2] = {BoolGetDatum(false), BoolGetDatum(true)}; + verify__list_const_to_str(BOOLARRAYOID, "s5dfalses4dtrue", 2, dats2); + + Datum dats3[1] = {BoolGetDatum(true)}; + verify__list_const_to_str(BOOLARRAYOID, "s4dtrue", 1, dats3); + + Datum dats4[1] = {BoolGetDatum(false)}; + verify__list_const_to_str(BOOLARRAYOID, "s5dfalse", 1, dats4); +} + +void +test__list_const_to_str__text(void **state) +{ + + Datum dats1[2] = {CStringGetDatum("row1"), CStringGetDatum("row2")}; + verify__list_const_to_str(TEXTARRAYOID, "s4drow1s4drow2", 2, dats1); + + Datum dats2[3] = {CStringGetDatum("r,o,w,1"), CStringGetDatum("r'o'w2"), CStringGetDatum("r\"o\"w3")}; + verify__list_const_to_str(TEXTARRAYOID, "s7dr,o,w,1s6dr'o'w2s6dr\"o\"w3", 3, dats2); +} + +void run__scalar_const_to_str(Const* input, StringInfo result, char* expected) +{ + scalar_const_to_str(input, result); assert_string_equal(result->data, expected); } -void run__const_to_str__negative(Const* input, StringInfo result, char* value) +void run__scalar_const_to_str__negative(Const* input, StringInfo result, char* value) { StringInfo err_msg = makeStringInfo(); appendStringInfo(err_msg, - "internal error in pxffilters.c:const_to_str. " + "internal error in pxffilters.c:scalar_const_to_str. " "Using unsupported data type (%d) (value %s)", input->consttype, value); /* Setting the test -- code omitted -- */ PG_TRY(); { /* This will throw a ereport(ERROR).*/ - const_to_str(input, result); + scalar_const_to_str(input, result); + } + PG_CATCH(); + { + CurrentMemoryContext = 1; + ErrorData *edata = CopyErrorData(); + + /* Validate the type of expected error */ + assert_true(edata->sqlerrcode == ERRCODE_INTERNAL_ERROR); + assert_true(edata->elevel == ERROR); + assert_string_equal(edata->message, err_msg->data); + + pfree(err_msg->data); + pfree(err_msg); + + return; + } + PG_END_TRY(); + + assert_true(false); +} + + + +void run__list_const_to_str(Const* input, StringInfo result, char* expected) +{ + list_const_to_str(input, result); + assert_string_equal(result->data, expected); +} + +void run__list_const_to_str__negative(Const* input, StringInfo result, int len, Datum *dats) +{ + + StringInfo err_msg = makeStringInfo(); + appendStringInfo(err_msg, + "internal error in pxffilters.c:list_const_to_str. " + "Using unsupported data type (%d) (len %d)", input->consttype, len); + + /* Setting the test -- code omitted -- */ + PG_TRY(); + { + /* This will throw a ereport(ERROR).*/ + list_const_to_str(input, result); } PG_CATCH(); { @@ -164,46 +407,48 @@ void run__const_to_str__negative(Const* input, StringInfo result, char* value) void -test__const_to_str__null(void **state) +test__scalar_const_to_str__null(void **state) { - verify__const_to_str(true, NULL, 1, "\"NULL\""); + verify__scalar_const_to_str(true, NULL, 1, NullConstValue); } void -test__const_to_str__int(void **state) +test__scalar_const_to_str__int(void **state) { - verify__const_to_str(false, "1234", INT2OID, "1234"); - verify__const_to_str(false, "1234", INT4OID, "1234"); - verify__const_to_str(false, "1234", INT8OID, "1234"); - verify__const_to_str(false, "1.234", FLOAT4OID, "1.234"); - verify__const_to_str(false, "1.234", FLOAT8OID, "1.234"); - verify__const_to_str(false, "1234", NUMERICOID, "1234"); + verify__scalar_const_to_str(false, "1234", INT2OID, "1234"); + verify__scalar_const_to_str(false, "1234", INT4OID, "1234"); + verify__scalar_const_to_str(false, "1234", INT8OID, "1234"); + verify__scalar_const_to_str(false, "1.234", FLOAT4OID, "1.234"); + verify__scalar_const_to_str(false, "1.234", FLOAT8OID, "1.234"); + verify__scalar_const_to_str(false, "1234", NUMERICOID, "1234"); } void -test__const_to_str__text(void **state) +test__scalar_const_to_str__text(void **state) { - verify__const_to_str(false, "that", TEXTOID, "that"); - verify__const_to_str(false, "joke", VARCHAROID, "joke"); - verify__const_to_str(false, "isn't", BPCHAROID, "isn't"); - verify__const_to_str(false, "funny", CHAROID, "funny"); - verify__const_to_str(false, "anymore", BYTEAOID, "anymore"); - verify__const_to_str(false, "iamdate", DATEOID, "iamdate"); + verify__scalar_const_to_str(false, "that", TEXTOID, "that"); + verify__scalar_const_to_str(false, "joke", VARCHAROID, "joke"); + verify__scalar_const_to_str(false, "isn't", BPCHAROID, "isn't"); + verify__scalar_const_to_str(false, "funny", CHAROID, "funny"); + verify__scalar_const_to_str(false, "anymore", BYTEAOID, "anymore"); + verify__scalar_const_to_str(false, "iamdate", DATEOID, "iamdate"); } void -test__const_to_str__boolean(void **state) +test__scalar_const_to_str__boolean(void **state) { - verify__const_to_str(false, "t", BOOLOID, "true"); - verify__const_to_str(false, "f", BOOLOID, "false"); + verify__scalar_const_to_str(false, "t", BOOLOID, "true"); + verify__scalar_const_to_str(false, "f", BOOLOID, "false"); } void -test__const_to_str__NegativeCircle(void **state) +test__scalar_const_to_str__NegativeCircle(void **state) { - verify__const_to_str(false, "<3,3,9>", CIRCLEOID, NULL); + verify__scalar_const_to_str(false, "<3,3,9>", CIRCLEOID, NULL); } + + void test__opexpr_to_pxffilter__null(void **state) { @@ -305,7 +550,7 @@ Const* build_const(Oid oid, char* value) arg_const->consttype = oid; if (value != NULL) { - mock__const_to_str(oid, value); + mock__scalar_const_to_str(oid, value); } return arg_const; @@ -370,7 +615,7 @@ void run__opexpr_to_pxffilter__positive(Oid dbop, PxfOperatorCode expectedPxfOp) OpExpr *expr = build_op_expr(arg_var, arg_const, dbop); PxfFilterDesc* expected = build_filter( PXF_ATTR_CODE, 1, NULL, - PXF_CONST_CODE, 0, "1984", + PXF_SCALAR_CONST_CODE, 0, "1984", expectedPxfOp); /* run test */ @@ -394,14 +639,14 @@ test__opexpr_to_pxffilter__intGT(void **state) void test__opexpr_to_pxffilter__allSupportedTypes(void **state) { - int nargs = sizeof(pxf_supported_opr) / sizeof(dbop_pxfop_map); + int nargs = sizeof(pxf_supported_opr_op_expr) / sizeof(dbop_pxfop_map); PxfOperatorCode pxfop = 0; Oid dbop = InvalidOid; for (int i = 0; i < nargs; ++i) { - dbop = pxf_supported_opr[i].dbop; - pxfop = pxf_supported_opr[i].pxfop; + dbop = pxf_supported_opr_op_expr[i].dbop; + pxfop = pxf_supported_opr_op_expr[i].pxfop; run__opexpr_to_pxffilter__positive(dbop, pxfop); } } @@ -418,7 +663,7 @@ test__opexpr_to_pxffilter__attributeEqualsNull(void **state) PxfFilterDesc* expected = build_filter( PXF_ATTR_CODE, 1, NULL, - PXF_CONST_CODE, 0, "\"NULL\"", + PXF_SCALAR_CONST_CODE, 0, NullConstValue, PXFOP_EQ); /* run test */ @@ -461,7 +706,7 @@ test__opexpr_to_pxffilter__differentTypes(void **state) /* run test */ assert_true(opexpr_to_pxffilter(expr, filter)); PxfFilterDesc *expected = build_filter( - PXF_CONST_CODE, 0, "13", + PXF_SCALAR_CONST_CODE, 0, "13", PXF_ATTR_CODE, 3, NULL, PXFOP_LT); compare_filters(filter, expected); @@ -510,8 +755,7 @@ test__opexpr_to_pxffilter__unsupportedOpNot(void **state) { PxfFilterDesc *filter = (PxfFilterDesc*) palloc0(sizeof(PxfFilterDesc)); Var *arg_var = build_var(INT2OID, 3); - char* const_value = strdup("not"); /* will be free'd by const_to_str */ - Const *arg_const = build_const(INT2OID, const_value); + Const *arg_const = build_const(INT2OID, NULL); OpExpr *expr = build_op_expr(arg_const, arg_var, 1877 /* int2not */); /* run test */ @@ -577,6 +821,7 @@ test__pxf_serialize_filter_list__manyFilters(void **state) expressionItems = lappend(expressionItems, expressionItem3); expressionItems = lappend(expressionItems, expressionItem4); expressionItems = lappend(expressionItems, expressionItem5); + expressionItems = lappend(expressionItems, expressionItem6); expressionItems = lappend(expressionItems, expressionItem7); @@ -600,11 +845,15 @@ main(int argc, char* argv[]) const UnitTest tests[] = { unit_test(test__supported_filter_type), - unit_test(test__const_to_str__null), - unit_test(test__const_to_str__int), - unit_test(test__const_to_str__text), - unit_test(test__const_to_str__boolean), - unit_test(test__const_to_str__NegativeCircle), + unit_test(test__supported_operator_type_op_expr), + unit_test(test__scalar_const_to_str__null), + unit_test(test__scalar_const_to_str__int), + unit_test(test__scalar_const_to_str__text), + unit_test(test__scalar_const_to_str__boolean), + unit_test(test__scalar_const_to_str__NegativeCircle), + unit_test(test__list_const_to_str__int), + unit_test(test__list_const_to_str__boolean), + unit_test(test__list_const_to_str__text), unit_test(test__opexpr_to_pxffilter__null), unit_test(test__opexpr_to_pxffilter__unary_expr), unit_test(test__opexpr_to_pxffilter__intGT), http://git-wip-us.apache.org/repos/asf/incubator-hawq/blob/f5369009/src/include/access/pxffilters.h ---------------------------------------------------------------------- diff --git a/src/include/access/pxffilters.h b/src/include/access/pxffilters.h index f194966..c7e8aa8 100644 --- a/src/include/access/pxffilters.h +++ b/src/include/access/pxffilters.h @@ -46,7 +46,8 @@ typedef enum PxfOperatorCode PXFOP_NE, PXFOP_LIKE, PXFOP_IS_NULL, - PXFOP_IS_NOTNULL + PXFOP_IS_NOTNULL, + PXFOP_IN } PxfOperatorCode; @@ -56,12 +57,17 @@ typedef enum PxfOperatorCode * string that gets pushed down. */ #define PXF_ATTR_CODE 'a' -#define PXF_CONST_CODE 'c' +#define PXF_SCALAR_CONST_CODE 'c' +#define PXF_LIST_CONST_CODE 'm' #define PXF_SIZE_BYTES 's' #define PXF_CONST_DATA 'd' #define PXF_OPERATOR_CODE 'o' #define PXF_LOGICAL_OPERATOR_CODE 'l' +#define NullConstValue "NULL" +#define TrueConstValue "true" +#define FalseConstValue "false" + /* * An Operand has any of the above codes, and the information specific to * its type. This could be compacted but filter structures are expected to @@ -69,10 +75,10 @@ typedef enum PxfOperatorCode */ typedef struct PxfOperand { - char opcode; /* PXF_ATTR_CODE or PXF_CONST_CODE*/ + char opcode; /* PXF_ATTR_CODE, PXF_SCALAR_CONST_CODE, PXF_LIST_CONST_CODE*/ AttrNumber attnum; /* used when opcode is PXF_ATTR_CODE */ - StringInfo conststr; /* used when opcode is PXF_CONST_CODE */ - Oid consttype; /* used when opcode is PXF_CONST_CODE */ + StringInfo conststr; /* used when opcode is PXF_SCALAR_CONST_CODE or PXF_LIST_CONST_CODE*/ + Oid consttype; /* used when opcode is PXF_SCALAR_CONST_CODE or PXF_LIST_CONST_CODE*/ } PxfOperand; @@ -88,7 +94,7 @@ typedef struct PxfFilterDesc } PxfFilterDesc; /* - * HAWQ operator OID to PXF operator code mapping + * HAWQ operator OID to PXF operator code mapping used for OpExpr */ typedef struct dbop_pxfop_map { @@ -97,6 +103,16 @@ typedef struct dbop_pxfop_map } dbop_pxfop_map; +/* + * HAWQ operator OID to PXF operator code mapping used for ScalarArrayOpExpr + */ +typedef struct dbop_pxfop_array_map +{ + Oid dbop; + PxfOperatorCode pxfop; + bool useOr; + +} dbop_pxfop_array_map; typedef struct ExpressionItem { @@ -110,9 +126,14 @@ static inline bool pxfoperand_is_attr(PxfOperand x) return (x.opcode == PXF_ATTR_CODE); } -static inline bool pxfoperand_is_const(PxfOperand x) +static inline bool pxfoperand_is_scalar_const(PxfOperand x) +{ + return (x.opcode == PXF_SCALAR_CONST_CODE); +} + +static inline bool pxfoperand_is_list_const(PxfOperand x) { - return (x.opcode == PXF_CONST_CODE); + return (x.opcode == PXF_LIST_CONST_CODE); } char *serializePxfFilterQuals(List *quals); http://git-wip-us.apache.org/repos/asf/incubator-hawq/blob/f5369009/src/include/catalog/pg_type.h ---------------------------------------------------------------------- diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index 3411c45..6d4f6d5 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -487,6 +487,7 @@ DESCR("network IP address/netmask, network address"); /* OIDS 1000 - 1099 */ DATA(insert OID = 1000 ( _bool PGNSP PGUID -1 f b t \054 0 16 array_in array_out array_recv array_send - i x f 0 -1 0 _null_ _null_ )); +#define BOOLARRAYOID 1000 DATA(insert OID = 1001 ( _bytea PGNSP PGUID -1 f b t \054 0 17 array_in array_out array_recv array_send - i x f 0 -1 0 _null_ _null_ )); DATA(insert OID = 1002 ( _char PGNSP PGUID -1 f b t \054 0 18 array_in array_out array_recv array_send - i x f 0 -1 0 _null_ _null_ )); DATA(insert OID = 1003 ( _name PGNSP PGUID -1 f b t \054 0 19 array_in array_out array_recv array_send - i x f 0 -1 0 _null_ _null_ ));
