diff --git a/deparse.c b/deparse.c index a75c270..1c5459b 100644 --- a/deparse.c +++ b/deparse.c @@ -112,7 +112,7 @@ static void mysql_print_remote_placeholder(Oid paramtype, int32 paramtypmod, deparse_expr_cxt *context); static void mysql_deparse_relation(StringInfo buf, Relation rel); static void mysql_deparse_target_list(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, - Bitmapset *attrs_used, List **retrieved_attrs); + Bitmapset *attrs_used, List **retrieved_attrs, List *tlist, RelOptInfo *baserel); static void mysql_deparse_column_ref(StringInfo buf, int varno, int varattno, PlannerInfo *root); /* @@ -194,7 +194,9 @@ mysql_deparse_select(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, Bitmapset *attrs_used, - char *svr_table, List **retrieved_attrs) + char *svr_table, + List **retrieved_attrs, + List *tlist) { RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root); Relation rel; @@ -206,7 +208,8 @@ mysql_deparse_select(StringInfo buf, rel = heap_open(rte->relid, NoLock); appendStringInfoString(buf, "SELECT "); - mysql_deparse_target_list(buf, root, baserel->relid, rel, attrs_used, retrieved_attrs); + mysql_deparse_target_list(buf, root, baserel->relid, rel, attrs_used, + retrieved_attrs, tlist, baserel); /* * Construct FROM clause @@ -290,41 +293,68 @@ mysql_deparse_target_list(StringInfo buf, Index rtindex, Relation rel, Bitmapset *attrs_used, - List **retrieved_attrs) + List **retrieved_attrs, + List *tlist, + RelOptInfo *baserel) { TupleDesc tupdesc = RelationGetDescr(rel); bool have_wholerow; bool first; int i; + ListCell *cell; /* If there's a whole-row reference, we'll need all the columns. */ have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, attrs_used); - first = true; - *retrieved_attrs = NIL; - for (i = 1; i <= tupdesc->natts; i++) + if (retrieved_attrs) { - Form_pg_attribute attr = TupleDescAttr(tupdesc, i - 1); + /* Not pushdown target list */ + *retrieved_attrs = NIL; + for (i = 1; i <= tupdesc->natts; i++) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, i - 1); - /* Ignore dropped attributes. */ - if (attr->attisdropped) - continue; + /* Ignore dropped attributes. */ + if (attr->attisdropped) + continue; - if (have_wholerow || - bms_is_member(i - FirstLowInvalidHeapAttributeNumber, - attrs_used)) + if (have_wholerow || + bms_is_member(i - FirstLowInvalidHeapAttributeNumber, + attrs_used)) + { + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + mysql_deparse_column_ref(buf, rtindex, i, root); + *retrieved_attrs = lappend_int(*retrieved_attrs, i); + } + } + } + else + { + /* Pushdown target list */ + + /* Set up context struct for recursion */ + deparse_expr_cxt context; + context.root = root; + context.foreignrel = baserel; + context.buf = buf; + context.params_list = NULL; + foreach (cell, tlist) { + Expr *expr = ((TargetEntry *)lfirst(cell))->expr; + if (!first) appendStringInfoString(buf, ", "); first = false; - mysql_deparse_column_ref(buf, rtindex, i, root); - *retrieved_attrs = lappend_int(*retrieved_attrs, i); + /* Deparse target list for push down */ + deparseExpr(expr, &context); } } - /* Don't generate bad syntax if no undropped columns */ if (first) appendStringInfoString(buf, "NULL"); @@ -917,19 +947,78 @@ mysql_deparse_func_expr(FuncExpr *node, deparse_expr_cxt *context) /* Translate PostgreSQL function into mysql function */ proname = mysql_replace_function(NameStr(procform->proname)); - /* Deparse the function name ... */ - appendStringInfo(buf, "%s(", proname); - - /* ... and all the arguments */ - first = true; - foreach(arg, node->args) + if(strcmp(proname,"match_against")==0) { - if (!first) - appendStringInfoString(buf, ", "); - deparseExpr((Expr *) lfirst(arg), context); - first = false; + /* ... and all the arguments */ + first = true; + foreach(arg, node->args) + { + Expr *node; + ListCell *lc; + ArrayExpr *anode; + bool swt_arg; + node = lfirst(arg); + Assert(nodeTag(node)==T_ArrayExpr); + anode = (ArrayExpr *)node; + appendStringInfoString(buf, "MATCH ("); + swt_arg = true; + foreach(lc, anode->elements) + { + Expr *node; + node=lfirst(lc); + if(nodeTag(node)==T_Var){ + if (!first) + appendStringInfoString(buf, ", "); + mysql_deparse_var((Var *)node,context); + } + else if(nodeTag(node)==T_Const){ + Const *cnode = (Const *)node; + if(swt_arg == true){ + appendStringInfoString(buf, ") AGAINST ( "); + swt_arg = false; + first = true; + mysql_deparse_const(cnode,context); + appendStringInfoString(buf, " "); + } + else{ + Oid typoutput; + const char *valptr; + char *extval; + bool typIsVarlena; + getTypeOutputInfo(cnode->consttype, + &typoutput, &typIsVarlena); + + extval = OidOutputFunctionCall(typoutput, cnode->constvalue); + for (valptr = extval; *valptr; valptr++) + { + char ch = *valptr; + if (SQL_STR_DOUBLE(ch, true)) + appendStringInfoChar(buf, ch); + appendStringInfoChar(buf, ch); + } + } + } + first = false; + } + appendStringInfoChar(buf, ')'); + } } - appendStringInfoChar(buf, ')'); + else + { + /* Deparse the function name ... */ + appendStringInfo(buf, "%s(", proname); + /* ... and all the arguments */ + first = true; + foreach(arg, node->args) + { + if (!first) + appendStringInfoString(buf, ", "); + deparseExpr((Expr *) lfirst(arg), context); + first = false; + } + appendStringInfoChar(buf, ')'); + } + ReleaseSysCache(proctup); } @@ -1297,6 +1386,7 @@ foreign_expr_walker(Node *node, foreign_loc_cxt inner_cxt; Oid collation; FDWCollateState state; + HeapTuple tuple; /* Need do nothing for empty subexpressions */ if (node == NULL) @@ -1418,13 +1508,23 @@ foreign_expr_walker(Node *node, case T_FuncExpr: { FuncExpr *fe = (FuncExpr *) node; + char *opername = NULL; /* * If function used by the expression is not built-in, it * can't be sent to remote because it might have incompatible * semantics on remote side. */ - if (!is_builtin(fe->funcid)) + tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fe->funcid)); + if (!HeapTupleIsValid(tuple)) + { + elog(ERROR, "cache lookup failed for function %u", fe->funcid); + } + opername = pstrdup(((Form_pg_proc) GETSTRUCT(tuple))->proname.data); + ReleaseSysCache(tuple); + + /* pushed down to mysql */ + if (!is_builtin(fe->funcid) && strcmp(opername, "match_against") != 0) return false; /* @@ -1710,7 +1810,7 @@ foreign_expr_walker(Node *node, * Returns true if given expr is safe to evaluate on the foreign server. */ bool -is_foreign_expr(PlannerInfo *root, +mysql_is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { diff --git a/mysql_fdw--1.0.sql b/mysql_fdw--1.0.sql index 7a8dd3f..aa1fd22 100644 --- a/mysql_fdw--1.0.sql +++ b/mysql_fdw--1.0.sql @@ -24,6 +24,13 @@ RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C STRICT; +CREATE FUNCTION MATCH_AGAINST(varidiadic text[]) RETURNS INT AS $$ +BEGIN + RETURN 1; +END +$$ LANGUAGE plpgsql IMMUTABLE; + CREATE FOREIGN DATA WRAPPER mysql_fdw HANDLER mysql_fdw_handler VALIDATOR mysql_fdw_validator; + diff --git a/mysql_fdw--1.1.sql b/mysql_fdw--1.1.sql index a464c70..09f5d83 100644 --- a/mysql_fdw--1.1.sql +++ b/mysql_fdw--1.1.sql @@ -28,6 +28,13 @@ CREATE FOREIGN DATA WRAPPER mysql_fdw HANDLER mysql_fdw_handler VALIDATOR mysql_fdw_validator; +CREATE FUNCTION MATCH_AGAINST(varidiadic text[]) RETURNS INT AS $$ +BEGIN + RETURN 1; +END +$$ LANGUAGE plpgsql IMMUTABLE; + CREATE OR REPLACE FUNCTION mysql_fdw_version() RETURNS pg_catalog.int4 STRICT AS 'MODULE_PATHNAME' LANGUAGE C; + diff --git a/mysql_fdw.c b/mysql_fdw.c index f1e26c3..c233621 100644 --- a/mysql_fdw.c +++ b/mysql_fdw.c @@ -396,7 +396,7 @@ mysqlBeginForeignScan(ForeignScanState *node, int eflags) ForeignTable *table; char timeout[255]; int numParams; - + List *tlist; /* * We'll save private state in node->fdw_state. */ @@ -428,6 +428,8 @@ mysqlBeginForeignScan(ForeignScanState *node, int eflags) /* Stash away the state info we have already */ festate->query = strVal(list_nth(fsplan->fdw_private, 0)); festate->retrieved_attrs = list_nth(fsplan->fdw_private, 1); + + festate->is_tlist_pushdown = intVal(list_nth(fsplan->fdw_private, 2)); festate->conn = conn; festate->cursor_exists = false; @@ -542,11 +544,23 @@ mysqlBeginForeignScan(ForeignScanState *node, int eflags) festate->table->_mysql_fields = _mysql_fetch_fields(festate->table->_mysql_res); - foreach(lc, festate->retrieved_attrs) + if (festate->is_tlist_pushdown) + tlist = node->ss.ps.plan->targetlist; + else + tlist = festate->retrieved_attrs; + + atindex = 0; + foreach (lc, tlist) { - int attnum = lfirst_int(lc) - 1; - Oid pgtype = TupleDescAttr(tupleDescriptor, attnum)->atttypid; - int32 pgtypmod = TupleDescAttr(tupleDescriptor, attnum)->atttypmod; + Oid pgtype; + int32 pgtypmod; + int attnum; + if (festate->is_tlist_pushdown) + attnum = atindex; + else + attnum = lfirst_int(lc) - 1; + pgtype = TupleDescAttr(tupleDescriptor, attnum)->atttypid; + pgtypmod = TupleDescAttr(tupleDescriptor, attnum)->atttypmod; if (TupleDescAttr(tupleDescriptor, attnum)->attisdropped) continue; @@ -648,17 +662,28 @@ mysqlIterateForeignScan(ForeignScanState *node) rc = _mysql_stmt_fetch(festate->stmt); if (0 == rc) { - foreach(lc, festate->retrieved_attrs) + List *tlist; + if (festate->is_tlist_pushdown) + tlist = node->ss.ps.plan->targetlist; + else + tlist = festate->retrieved_attrs; + + foreach(lc, tlist) { - int attnum = lfirst_int(lc) - 1; - Oid pgtype = TupleDescAttr(tupleDescriptor, attnum)->atttypid; - int32 pgtypmod = TupleDescAttr(tupleDescriptor, attnum)->atttypmod; + int attnum; + Oid pgtype; + int32 pgtypmod; + if (festate->is_tlist_pushdown) + attnum = attid; + else + attnum = lfirst_int(lc) - 1; + pgtype = TupleDescAttr(tupleDescriptor, attnum)->atttypid; + pgtypmod = TupleDescAttr(tupleDescriptor, attnum)->atttypmod; tupleSlot->tts_isnull[attnum] = festate->table->column[attid].is_null; if (!festate->table->column[attid].is_null) tupleSlot->tts_values[attnum] = mysql_convert_to_pg(pgtype, pgtypmod, &festate->table->column[attid]); - attid++; } ExecStoreVirtualTuple(tupleSlot); @@ -840,7 +865,7 @@ mysqlGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntablei { RestrictInfo *ri = (RestrictInfo *) lfirst(lc); - if (is_foreign_expr(root, baserel, ri->clause)) + if (mysql_is_foreign_expr(root, baserel, ri->clause)) fpinfo->remote_conds = lappend(fpinfo->remote_conds, ri); else fpinfo->local_conds = lappend(fpinfo->local_conds, ri); @@ -862,8 +887,8 @@ mysqlGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntablei initStringInfo(&sql); appendStringInfo(&sql, "EXPLAIN "); - mysql_deparse_select(&sql, root, baserel, fpinfo->attrs_used, options->svr_table, &retrieved_attrs); - + mysql_deparse_select(&sql, root, baserel, fpinfo->attrs_used, options->svr_table, + &retrieved_attrs, NULL); if (fpinfo->remote_conds) mysql_append_where_clause(&sql, root, baserel, fpinfo->remote_conds, true, ¶ms_list); @@ -1090,7 +1115,7 @@ mysqlGetForeignPlan( StringInfoData sql; mysql_opt *options; - List *retrieved_attrs; + List *retrieved_attrs = NIL; ListCell *lc; /* Fetch options */ @@ -1140,7 +1165,7 @@ mysqlGetForeignPlan( } else if (list_member_ptr(fpinfo->local_conds, rinfo)) local_exprs = lappend(local_exprs, rinfo->clause); - else if (is_foreign_expr(root, baserel, rinfo->clause)) + else if (mysql_is_foreign_expr(root, baserel, rinfo->clause)) { remote_conds = lappend(remote_conds, rinfo); remote_exprs = lappend(remote_exprs, rinfo->clause); @@ -1149,7 +1174,11 @@ mysqlGetForeignPlan( local_exprs = lappend(local_exprs, rinfo->clause); } - mysql_deparse_select(&sql, root, baserel, fpinfo->attrs_used, options->svr_table, &retrieved_attrs); + /* Cannot compile this code for plain PostgreSQL, which doesn't have is_tlist_pushdown member */ + if (baserel->is_tlist_pushdown) + mysql_deparse_select(&sql, root, baserel, fpinfo->attrs_used, options->svr_table, NULL, tlist); + else + mysql_deparse_select(&sql, root, baserel, fpinfo->attrs_used, options->svr_table, &retrieved_attrs, NULL); if (remote_conds) mysql_append_where_clause(&sql, root, baserel, remote_conds, @@ -1168,8 +1197,7 @@ mysqlGetForeignPlan( * Items in the list must match enum FdwScanPrivateIndex, above. */ - fdw_private = list_make2(makeString(sql.data), retrieved_attrs); - + fdw_private = list_make3(makeString(sql.data), retrieved_attrs, makeInteger(baserel->is_tlist_pushdown)); /* * Create the ForeignScan node from target list, local filtering * expressions, remote parameter expressions, and FDW private information. @@ -1184,7 +1212,7 @@ mysqlGetForeignPlan( ,params_list ,fdw_private #if PG_VERSION_NUM >= 90500 - ,NIL + ,baserel->is_tlist_pushdown ? tlist : NIL ,NIL ,outer_plan #endif diff --git a/mysql_fdw.h b/mysql_fdw.h index 5b543cd..176cea3 100644 --- a/mysql_fdw.h +++ b/mysql_fdw.h @@ -116,7 +116,7 @@ typedef struct MySQLFdwExecState List *attr_list; /* query attribute list */ List *column_list; /* Column list of MySQL Column structures */ - + bool is_tlist_pushdown; /* pushdown target list or not */ /* working memory context */ MemoryContext temp_cxt; /* context for per-tuple temporary data */ } MySQLFdwExecState; @@ -130,7 +130,7 @@ typedef struct MySQLColumn int atttype; /* Attribute type */ } MySQLColumn; -extern bool is_foreign_expr(PlannerInfo *root, +extern bool mysql_is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr); @@ -184,7 +184,7 @@ extern mysql_opt *mysql_get_options(Oid foreigntableid); /* depare.c headers */ extern void mysql_deparse_select(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, - Bitmapset *attrs_used, char *svr_table, List **retrieved_attrs); + Bitmapset *attrs_used, char *svr_table, List **retrieved_attrs, List *tlist); extern void mysql_deparse_insert(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, List *targetAttrs); extern void mysql_deparse_update(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, List *targetAttrs, char *attname); extern void mysql_deparse_delete(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, char *name);