pgsql_fdw_analyze_v2.patch
text/plain
Filename: pgsql_fdw_analyze_v2.patch
Type: text/plain
Part: 2
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v2
| File | + | − |
|---|---|---|
| contrib/pgsql_fdw/deparse.c | 0 | 0 |
| contrib/pgsql_fdw/pgsql_fdw.c | 0 | 0 |
| contrib/pgsql_fdw/pgsql_fdw.h | 0 | 0 |
diff --git a/contrib/pgsql_fdw/deparse.c b/contrib/pgsql_fdw/deparse.c
new file mode 100644
index 148d0b4..9737a0c
*** a/contrib/pgsql_fdw/deparse.c
--- b/contrib/pgsql_fdw/deparse.c
***************
*** 28,33 ****
--- 28,34 ----
#include "parser/parsetree.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
+ #include "utils/rel.h"
#include "utils/syscache.h"
#include "pgsql_fdw.h"
*************** sortConditions(PlannerInfo *root,
*** 218,223 ****
--- 219,289 ----
}
/*
+ * Deparse SELECT statement to acquire sample rows of given relation into buf.
+ */
+ void
+ deparseAnalyzeSql(StringInfo buf, Relation rel)
+ {
+ Oid relid = RelationGetRelid(rel);
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ int i;
+ char *colname;
+ List *options;
+ ListCell *lc;
+ bool first = true;
+ char *nspname;
+ char *relname;
+ ForeignTable *table;
+
+ /* Deparse SELECT clause, use attribute name or colname option. */
+ appendStringInfo(buf, "SELECT ");
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ if (tupdesc->attrs[i]->attisdropped)
+ continue;
+
+ colname = NameStr(tupdesc->attrs[i]->attname);
+ options = GetForeignColumnOptions(relid, tupdesc->attrs[i]->attnum);
+
+ foreach(lc, options)
+ {
+ DefElem *def= (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "colname") == 0)
+ {
+ colname = defGetString(def);
+ break;
+ }
+ }
+
+ if (!first)
+ appendStringInfo(buf, ", ");
+ appendStringInfo(buf, "%s", quote_identifier(colname));
+ first = false;
+ }
+
+ /*
+ * Deparse FROM clause, use namespace and relation name, or use nspname and
+ * colname options respectively.
+ */
+ nspname = get_namespace_name(get_rel_namespace(relid));
+ relname = get_rel_name(relid);
+ table = GetForeignTable(relid);
+ foreach(lc, table->options)
+ {
+ DefElem *def= (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "nspname") == 0)
+ nspname = defGetString(def);
+ else if (strcmp(def->defname, "relname") == 0)
+ relname = defGetString(def);
+ }
+
+ appendStringInfo(buf, " FROM %s.%s", quote_identifier(nspname),
+ quote_identifier(relname));
+ }
+
+ /*
* Deparse given expression into buf. Actual string operation is delegated to
* node-type-specific functions.
*
diff --git a/contrib/pgsql_fdw/pgsql_fdw.c b/contrib/pgsql_fdw/pgsql_fdw.c
new file mode 100644
index b7885a8..63b2bba
*** a/contrib/pgsql_fdw/pgsql_fdw.c
--- b/contrib/pgsql_fdw/pgsql_fdw.c
***************
*** 17,22 ****
--- 17,23 ----
#include "catalog/pg_foreign_table.h"
#include "commands/defrem.h"
#include "commands/explain.h"
+ #include "commands/vacuum.h"
#include "foreign/fdwapi.h"
#include "funcapi.h"
#include "miscadmin.h"
*************** typedef struct PgsqlFdwExecutionState
*** 133,138 ****
--- 134,168 ----
} PgsqlFdwExecutionState;
/*
+ * Describes a state of analyze request for a foreign table.
+ */
+ typedef struct PgsqlAnalyzeState
+ {
+ /* for tuple generation. */
+ TupleDesc tupdesc;
+ AttInMetadata *attinmeta;
+ char *colbuf; /* column value buffer for row processor */
+ int colbuflen; /* column value buffer size for row processor */
+ Datum *values;
+ bool *nulls;
+
+ /* for random sampling */
+ HeapTuple *rows; /* result buffer */
+ int targrows; /* target # of sample rows */
+ int numrows; /* # of samples collected */
+ double samplerows; /* # of rows fetched */
+ double rowstoskip; /* # of rows skipped before next sample */
+ double rstate; /* random state */
+
+ /* for storing result tuples */
+ MemoryContext anl_cxt; /* context for per-analyze lifespan data */
+ MemoryContext temp_cxt; /* context for per-tuple temporary data */
+
+ /* for error handling. */
+ ErrorPos errpos;
+ } PgsqlAnalyzeState;
+
+ /*
* SQL functions
*/
extern Datum pgsql_fdw_handler(PG_FUNCTION_ARGS);
*************** static void pgsqlBeginForeignScan(Foreig
*** 158,163 ****
--- 188,196 ----
static TupleTableSlot *pgsqlIterateForeignScan(ForeignScanState *node);
static void pgsqlReScanForeignScan(ForeignScanState *node);
static void pgsqlEndForeignScan(ForeignScanState *node);
+ static bool pgsqlAnalyzeForeignTable(Relation relation,
+ AcquireSampleRowsFunc *func,
+ BlockNumber *totalpages);
/*
* Helper functions
*************** static void execute_query(ForeignScanSta
*** 174,179 ****
--- 207,218 ----
static int query_row_processor(PGresult *res, const PGdataValue *columns,
const char **errmsgp, void *param);
static void pgsql_fdw_error_callback(void *arg);
+ static int pgsqlAcquireSampleRowsFunc(Relation relation, int elevel,
+ HeapTuple *rows, int targrows,
+ double *totalrows,
+ double *totaldeadrows);
+ static int analyze_row_processor(PGresult *res, const PGdataValue *columns,
+ const char **errmsgp, void *param);
/* Exported functions, but not written in pgsql_fdw.h. */
void _PG_init(void);
*************** static FdwRoutine fdwroutine = {
*** 215,220 ****
--- 254,260 ----
pgsqlEndForeignScan,
/* Optional handler functions. */
+ pgsqlAnalyzeForeignTable,
};
/*
*************** query_row_processor(PGresult *res,
*** 908,918 ****
--- 948,960 ----
festate->nulls[i] = false;
+ MemoryContextSwitchTo(festate->scan_cxt);
while (festate->colbuflen < len + 1)
{
festate->colbuflen *= 2;
festate->colbuf = repalloc(festate->colbuf, festate->colbuflen);
}
+ MemoryContextSwitchTo(festate->temp_cxt);
memcpy(festate->colbuf, columns[j].value, len);
festate->colbuf[columns[j].len] = '\0';
*************** query_row_processor(PGresult *res,
*** 960,966 ****
static void
pgsql_fdw_error_callback(void *arg)
{
! ErrorPos *errpos = (ErrorPos *) arg;
const char *relname;
const char *colname;
--- 1002,1008 ----
static void
pgsql_fdw_error_callback(void *arg)
{
! ErrorPos *errpos = (ErrorPos *) arg;
const char *relname;
const char *colname;
*************** pgsql_fdw_error_callback(void *arg)
*** 969,971 ****
--- 1011,1311 ----
errcontext("column %s of foreign table %s",
quote_identifier(colname), quote_identifier(relname));
}
+
+ /*
+ * pgsqlAnalyzeForeignTable
+ * Test whether analyzing this foreign table is supported
+ */
+ static bool
+ pgsqlAnalyzeForeignTable(Relation relation,
+ AcquireSampleRowsFunc *func,
+ BlockNumber *totalpages)
+ {
+ *totalpages = 0;
+ *func = pgsqlAcquireSampleRowsFunc;
+
+ return true;
+ }
+
+ /*
+ * Acquire a random sample of rows from foreign table managed by pgsql_fdw.
+ *
+ * pgsql_fdw doesn't provide direct access to remote buffer, so we execute
+ * simple SELECT statement which retrieves whole rows from remote side, and
+ * pick some samples from them.
+ */
+ static int
+ pgsqlAcquireSampleRowsFunc(Relation relation, int elevel,
+ HeapTuple *rows, int targrows,
+ double *totalrows,
+ double *totaldeadrows)
+ {
+ PgsqlAnalyzeState astate;
+ StringInfoData sql;
+ ForeignTable *table;
+ ForeignServer *server;
+ UserMapping *user;
+ PGconn *conn = NULL;
+ PGresult *res = NULL;
+
+ /*
+ * Only few information are necessary as input to row processor. Other
+ * initialization will be done at the first row processor call.
+ */
+ astate.anl_cxt = CurrentMemoryContext;
+ astate.temp_cxt = AllocSetContextCreate(CurrentMemoryContext,
+ "pgsql_fdw analyze temporary data",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+ astate.rows = rows;
+ astate.targrows = targrows;
+ astate.tupdesc = relation->rd_att;
+ astate.errpos.relid = relation->rd_id;
+
+ /*
+ * Construct SELECT statement which retrieves whole rows from remote. We
+ * can't avoid running sequential scan on remote side to get practical
+ * statistics, so this seems reasonable compromise.
+ */
+ initStringInfo(&sql);
+ deparseAnalyzeSql(&sql, relation);
+
+ table = GetForeignTable(relation->rd_id);
+ server = GetForeignServer(table->serverid);
+ user = GetUserMapping(GetOuterUserId(), server->serverid);
+ conn = GetConnection(server, user, true);
+
+ /*
+ * Acquire sample rows from the result set.
+ */
+ PG_TRY();
+ {
+ /*
+ * Execute remote query and retrieve results with libpq row processor.
+ */
+ PQsetRowProcessor(conn, analyze_row_processor, &astate);
+ res = PQexec(conn, sql.data);
+ PQsetRowProcessor(conn, NULL, NULL);
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ ereport(ERROR,
+ (errmsg("could not execute remote query for analyze"),
+ errdetail("%s", PQerrorMessage(conn)),
+ errhint("%s", sql.data)));
+ PQclear(res);
+ res = NULL;
+ }
+ PG_CATCH();
+ {
+ PQclear(res);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ ReleaseConnection(conn);
+
+ /* We assume that we have no dead tuple. */
+ *totaldeadrows = 0.0;
+
+ /* We've retrieved all living tuples from foreign server. */
+ *totalrows = astate.samplerows;
+
+ /*
+ * We don't update pg_class.relpages because we don't care that in
+ * planning at all.
+ */
+
+ /*
+ * Emit some interesting relation info
+ */
+ ereport(elevel,
+ (errmsg("\"%s\": scanned with \"%s\", "
+ "containing %.0f live rows and %.0f dead rows; "
+ "%d rows in sample, %.0f estimated total rows",
+ RelationGetRelationName(relation), sql.data,
+ astate.samplerows, 0.0,
+ astate.numrows, astate.samplerows)));
+
+ return astate.numrows;
+ }
+
+ /*
+ * Custom row processor for acquire_sample_rows.
+ *
+ * Collect sample rows from the result of query.
+ * - Use all tuples as sample until target rows samples are collected.
+ * - Once reached the target, skip some tuples and replace already sampled
+ * tuple randomly.
+ */
+ static int
+ analyze_row_processor(PGresult *res, const PGdataValue *columns,
+ const char **errmsgp, void *param)
+ {
+ PgsqlAnalyzeState *astate = (PgsqlAnalyzeState *) param;
+ int targrows = astate->targrows;
+ TupleDesc tupdesc = astate->tupdesc;
+ AttInMetadata *attinmeta = astate->attinmeta;
+ int i;
+ int j;
+ int pos; /* position where next sample should be stored. */
+ HeapTuple tuple;
+ ErrorContextCallback errcontext;
+ MemoryContext callercontext;
+
+ if (columns == NULL)
+ {
+ /* Prepare for sampling rows */
+ astate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+ astate->colbuflen = INITIAL_COLBUF_SIZE;
+ astate->colbuf = palloc(astate->colbuflen);
+ astate->values = (Datum *) palloc(sizeof(Datum) * tupdesc->natts);
+ astate->nulls = (bool *) palloc(sizeof(bool) * tupdesc->natts);
+ astate->numrows = 0;
+ astate->samplerows = 0;
+ astate->rowstoskip = -1;
+ astate->numrows = 0;
+ astate->rstate = anl_init_selection_state(astate->targrows);
+
+ return 1;
+ }
+
+ /*
+ * ANALYZE against foreign tables are not done in processes of
+ * vacuum, so here we use CHECK_FOR_INTERRUPTS instead of
+ * vacuum_delay_point().
+ */
+ CHECK_FOR_INTERRUPTS();
+
+ /*
+ * Do the following work in a temp context that we reset after each tuple.
+ * This cleans up not only the data we have direct access to, but any
+ * cruft the I/O functions might leak.
+ */
+ callercontext = MemoryContextSwitchTo(astate->temp_cxt);
+
+ /*
+ * First targrows rows are once sampled always. If we have more source
+ * rows, pick up some of them by skipping and replace already sampled
+ * tuple randomly.
+ *
+ * Here we just determine the slot where next sample should be stored. Set
+ * pos to negative value to indicates the row should be skipped.
+ */
+ if (astate->numrows < targrows)
+ pos = astate->numrows++;
+ else
+ {
+ /*
+ * The first targrows sample rows are simply copied into
+ * the reservoir. Then we start replacing tuples in the
+ * sample until we reach the end of the relation. This
+ * algorithm is from Jeff Vitter's paper, similarly to
+ * acquire_sample_rows in analyze.c.
+ *
+ * We don't have block-wise accessibility, so every row in
+ * the PGresult is possible to be sample.
+ */
+ if (astate->rowstoskip < 0)
+ astate->rowstoskip = anl_get_next_S(astate->samplerows, targrows,
+ &astate->rstate);
+
+ if (astate->rowstoskip <= 0)
+ {
+ int k = (int) (targrows * anl_random_fract());
+
+ Assert(k >= 0 && k < targrows);
+
+ /*
+ * Create sample tuple from the result, and replace at
+ * random.
+ */
+ heap_freetuple(astate->rows[k]);
+ pos = k;
+ }
+ else
+ pos = -1;
+
+ astate->rowstoskip -= 1;
+ }
+
+ /* Always increment sample row counter. */
+ astate->samplerows += 1;
+
+ if (pos >= 0)
+ {
+ /*
+ * Create sample tuple from current result row, and store it into the
+ * position determined above. Note that i and j point entries in
+ * catalog and columns array respectively.
+ */
+ for (i = 0, j = 0; i < tupdesc->natts; i++)
+ {
+ int len = columns[j].len;
+
+ if (tupdesc->attrs[i]->attisdropped)
+ continue;
+
+ if (len < 0)
+ astate->nulls[i] = true;
+ else
+ {
+ Datum value;
+
+ astate->nulls[i] = false;
+
+ /*
+ * Expand column value string buffer twice as current until
+ * it can hold the value. We need to allocate the buffer in
+ * the context of analyze.
+ */
+ while (astate->colbuflen < len + 1)
+ {
+ astate->colbuflen *= 2;
+ MemoryContextSwitchTo(astate->anl_cxt);
+ astate->colbuf = repalloc(astate->colbuf,
+ astate->colbuflen);
+ MemoryContextSwitchTo(astate->temp_cxt);
+ }
+ memcpy(astate->colbuf, columns[j].value, len);
+ astate->colbuf[columns[j].len] = '\0';
+
+ /*
+ * Set up and install callback to report where conversion error
+ * occurs.
+ */
+ astate->errpos.cur_attno = i + 1;
+ errcontext.callback = pgsql_fdw_error_callback;
+ errcontext.arg = (void *) &astate->errpos;
+ errcontext.previous = error_context_stack;
+ error_context_stack = &errcontext;
+
+ value = InputFunctionCall(&attinmeta->attinfuncs[i],
+ astate->colbuf,
+ attinmeta->attioparams[i],
+ attinmeta->atttypmods[i]);
+ astate->values[i] = value;
+
+ /* Uninstall error callback function. */
+ error_context_stack = errcontext.previous;
+ }
+ j++;
+ }
+
+ /*
+ * Generate tuple from the result row data, and store it into the give
+ * buffer. Note that we need to allocate the tuple in the analyze
+ * context to make it valid even after temporary per-tuple context has
+ * been reset.
+ */
+ MemoryContextSwitchTo(astate->anl_cxt);
+ tuple = heap_form_tuple(tupdesc, astate->values, astate->nulls);
+ MemoryContextSwitchTo(astate->temp_cxt);
+ astate->rows[pos] = tuple;
+ }
+
+ /* Clean up */
+ MemoryContextSwitchTo(callercontext);
+ MemoryContextReset(astate->temp_cxt);
+
+ return 1;
+ }
diff --git a/contrib/pgsql_fdw/pgsql_fdw.h b/contrib/pgsql_fdw/pgsql_fdw.h
new file mode 100644
index 76c9f72..94e6f07
*** a/contrib/pgsql_fdw/pgsql_fdw.h
--- b/contrib/pgsql_fdw/pgsql_fdw.h
***************
*** 17,22 ****
--- 17,23 ----
#include "postgres.h"
#include "foreign/foreign.h"
#include "nodes/relation.h"
+ #include "utils/relcache.h"
/* in option.c */
int ExtractConnectionOptions(List *defelems,
*************** void sortConditions(PlannerInfo *root,
*** 38,42 ****
--- 39,44 ----
List **remote_conds,
List **param_conds,
List **local_conds);
+ void deparseAnalyzeSql(StringInfo buf, Relation rel);
#endif /* PGSQL_FDW_H */