Skip to content
Newer
Older
100644 816 lines (694 sloc) 21.4 KB
cd3d8ba added preprocessor for 9.1 Api
Pape authored
1 /*-------------------------------------------------------------------------
2 *
3 * foreign-data wrapper for MySQL
4 *
5 * Copyright (c) 2011, PostgreSQL Global Development Group
6 *
7 * This software is released under the PostgreSQL Licence
8 *
9 * Author: Dave Page <dpage@pgadmin.org>
10 *
11 * IDENTIFICATION
12 * mysql_fdw/mysql_fdw.c
13 *
14 *-------------------------------------------------------------------------
15 */
16
17 #include "postgres.h"
18
19 #include <stdio.h>
20 #include <sys/stat.h>
21 #include <unistd.h>
22
23 #define list_length mysql_list_length
24 #define list_delete mysql_list_delete
25 #define list_free mysql_list_free
26 #include <mysql.h>
27 #undef list_length
28 #undef list_delete
29 #undef list_free
30
31 #include "funcapi.h"
32 #include "access/reloptions.h"
33 #include "catalog/pg_foreign_server.h"
34 #include "catalog/pg_foreign_table.h"
35 #include "catalog/pg_user_mapping.h"
36 #include "catalog/pg_type.h"
37 #include "commands/defrem.h"
38 #include "commands/explain.h"
39 #include "foreign/fdwapi.h"
40 #include "foreign/foreign.h"
41 #include "miscadmin.h"
42 #include "mb/pg_wchar.h"
43 #include "optimizer/cost.h"
44 #include "storage/fd.h"
45 #include "utils/array.h"
46 #include "utils/builtins.h"
47 #include "utils/rel.h"
48
6e65eeb remove warnings
Pape authored
49 #if (PG_VERSION_NUM >= 90200)
50 #include "optimizer/pathnode.h"
51 #include "optimizer/restrictinfo.h"
52 #include "optimizer/planmain.h"
53 #endif
54
cd3d8ba added preprocessor for 9.1 Api
Pape authored
55 PG_MODULE_MAGIC;
56
57 /*
58 * Describes the valid options for objects that use this wrapper.
59 */
60 struct MySQLFdwOption
61 {
62 const char *optname;
63 Oid optcontext; /* Oid of catalog in which option may appear */
64 };
65
66 /*
67 * Valid options for mysql_fdw.
68 *
69 */
70 static struct MySQLFdwOption valid_options[] =
71 {
72
73 /* Connection options */
74 { "address", ForeignServerRelationId },
75 { "port", ForeignServerRelationId },
76 { "username", UserMappingRelationId },
77 { "password", UserMappingRelationId },
78 { "database", ForeignTableRelationId },
79 { "query", ForeignTableRelationId },
80 { "table", ForeignTableRelationId },
81
82 /* Sentinel */
83 { NULL, InvalidOid }
84 };
85
86 /*
87 * FDW-specific information for ForeignScanState.fdw_state.
88 */
89
90 typedef struct MySQLFdwExecutionState
91 {
92 MYSQL *conn;
93 MYSQL_RES *result;
94 char *query;
95 } MySQLFdwExecutionState;
96
97 /*
98 * SQL functions
99 */
100 extern Datum mysql_fdw_handler(PG_FUNCTION_ARGS);
101 extern Datum mysql_fdw_validator(PG_FUNCTION_ARGS);
102
103 PG_FUNCTION_INFO_V1(mysql_fdw_handler);
104 PG_FUNCTION_INFO_V1(mysql_fdw_validator);
105
106 /*
107 * FDW callback routines
108 */
109 static void mysqlExplainForeignScan(ForeignScanState *node, ExplainState *es);
110 static void mysqlBeginForeignScan(ForeignScanState *node, int eflags);
111 static TupleTableSlot *mysqlIterateForeignScan(ForeignScanState *node);
112 static void mysqlReScanForeignScan(ForeignScanState *node);
113 static void mysqlEndForeignScan(ForeignScanState *node);
114 #if (PG_VERSION_NUM >= 90200)
115 static void mysqlGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid);
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
116 static void mysqlGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid);
117 static bool mysqlAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, BlockNumber *totalpages);
118 static ForeignScan *mysqlGetForeignPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, ForeignPath *best_path, List * tlist, List *scan_clauses);
cd3d8ba added preprocessor for 9.1 Api
Pape authored
119 #else
120 static FdwPlan *mysqlPlanForeignScan(Oid foreigntableid, PlannerInfo *root, RelOptInfo *baserel);
121 #endif
122
123 /*
124 * Helper functions
125 */
126 static bool mysqlIsValidOption(const char *option, Oid context);
127 static void mysqlGetOptions(Oid foreigntableid, char **address, int *port, char **username, char **password, char **database, char **query, char **table);
128 #if (PG_VERSION_NUM >= 90200)
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
129 static void mysqlEstimateCosts(PlannerInfo *root, RelOptInfo *baserel, Cost *startup_cost, Cost *total_cost, Oid foreigntableid);
cd3d8ba added preprocessor for 9.1 Api
Pape authored
130 #endif
131
132 /*
133 * Foreign-data wrapper handler function: return a struct with pointers
134 * to my callback routines.
135 */
136 Datum
137 mysql_fdw_handler(PG_FUNCTION_ARGS)
138 {
139 FdwRoutine *fdwroutine = makeNode(FdwRoutine);
140
141 #if (PG_VERSION_NUM >= 90200)
142 fdwroutine->GetForeignRelSize = mysqlGetForeignRelSize;
143 fdwroutine->GetForeignPaths = mysqlGetForeignPaths;
144 fdwroutine->AnalyzeForeignTable = mysqlAnalyzeForeignTable;
145 fdwroutine->GetForeignPlan = mysqlGetForeignPlan;
146 #else
147 fdwroutine->PlanForeignScan = mysqlPlanForeignScan;
148 #endif
149
150 fdwroutine->ExplainForeignScan = mysqlExplainForeignScan;
151 fdwroutine->BeginForeignScan = mysqlBeginForeignScan;
152 fdwroutine->IterateForeignScan = mysqlIterateForeignScan;
153 fdwroutine->ReScanForeignScan = mysqlReScanForeignScan;
154 fdwroutine->EndForeignScan = mysqlEndForeignScan;
155
156 PG_RETURN_POINTER(fdwroutine);
157 }
158
159 /*
160 * Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER,
161 * USER MAPPING or FOREIGN TABLE that uses file_fdw.
162 *
163 * Raise an ERROR if the option or its value is considered invalid.
164 */
165 Datum
166 mysql_fdw_validator(PG_FUNCTION_ARGS)
167 {
168 List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
169 Oid catalog = PG_GETARG_OID(1);
170 char *svr_address = NULL;
171 int svr_port = 0;
172 char *svr_username = NULL;
173 char *svr_password = NULL;
174 char *svr_database = NULL;
175 char *svr_query = NULL;
176 char *svr_table = NULL;
177 ListCell *cell;
178
179 /*
180 * Check that only options supported by mysql_fdw,
181 * and allowed for the current object type, are given.
182 */
183 foreach(cell, options_list)
184 {
185 DefElem *def = (DefElem *) lfirst(cell);
186
187 if (!mysqlIsValidOption(def->defname, catalog))
188 {
189 struct MySQLFdwOption *opt;
190 StringInfoData buf;
191
192 /*
193 * Unknown option specified, complain about it. Provide a hint
194 * with list of valid options for the object.
195 */
196 initStringInfo(&buf);
197 for (opt = valid_options; opt->optname; opt++)
198 {
199 if (catalog == opt->optcontext)
200 appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "",
201 opt->optname);
202 }
203
204 ereport(ERROR,
205 (errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
206 errmsg("invalid option \"%s\"", def->defname),
207 errhint("Valid options in this context are: %s", buf.len ? buf.data : "<none>")
208 ));
209 }
210
211 if (strcmp(def->defname, "address") == 0)
212 {
213 if (svr_address)
214 ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
215 errmsg("conflicting or redundant options: address (%s)", defGetString(def))
216 ));
217
218 svr_address = defGetString(def);
219 }
220 else if (strcmp(def->defname, "port") == 0)
221 {
222 if (svr_port)
223 ereport(ERROR,
224 (errcode(ERRCODE_SYNTAX_ERROR),
225 errmsg("conflicting or redundant options: port (%s)", defGetString(def))
226 ));
227
228 svr_port = atoi(defGetString(def));
229 }
230 if (strcmp(def->defname, "username") == 0)
231 {
232 if (svr_username)
233 ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
234 errmsg("conflicting or redundant options: username (%s)", defGetString(def))
235 ));
236
237 svr_username = defGetString(def);
238 }
239 if (strcmp(def->defname, "password") == 0)
240 {
241 if (svr_password)
242 ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
243 errmsg("conflicting or redundant options: password")
244 ));
245
246 svr_password = defGetString(def);
247 }
248 else if (strcmp(def->defname, "database") == 0)
249 {
250 if (svr_database)
251 ereport(ERROR,
252 (errcode(ERRCODE_SYNTAX_ERROR),
253 errmsg("conflicting or redundant options: database (%s)", defGetString(def))
254 ));
255
256 svr_database = defGetString(def);
257 }
258 else if (strcmp(def->defname, "query") == 0)
259 {
260 if (svr_table)
261 ereport(ERROR,
262 (errcode(ERRCODE_SYNTAX_ERROR),
263 errmsg("conflicting options: query cannot be used with table")
264 ));
265
266 if (svr_query)
267 ereport(ERROR,
268 (errcode(ERRCODE_SYNTAX_ERROR),
269 errmsg("conflicting or redundant options: query (%s)", defGetString(def))
270 ));
271
272 svr_query = defGetString(def);
273 }
274 else if (strcmp(def->defname, "table") == 0)
275 {
276 if (svr_query)
277 ereport(ERROR,
278 (errcode(ERRCODE_SYNTAX_ERROR),
279 errmsg("conflicting options: table cannot be used with query")
280 ));
281
282 if (svr_table)
283 ereport(ERROR,
284 (errcode(ERRCODE_SYNTAX_ERROR),
285 errmsg("conflicting or redundant options: table (%s)", defGetString(def))
286 ));
287
288 svr_table = defGetString(def);
289 }
290 }
291
292 PG_RETURN_VOID();
293 }
294
295
296 /*
297 * Check if the provided option is one of the valid options.
298 * context is the Oid of the catalog holding the object the option is for.
299 */
300 static bool
301 mysqlIsValidOption(const char *option, Oid context)
302 {
303 struct MySQLFdwOption *opt;
304
305 for (opt = valid_options; opt->optname; opt++)
306 {
307 if (context == opt->optcontext && strcmp(opt->optname, option) == 0)
308 return true;
309 }
310 return false;
311 }
312
313 /*
314 * Fetch the options for a mysql_fdw foreign table.
315 */
316 static void
317 mysqlGetOptions(Oid foreigntableid, char **address, int *port, char **username, char **password, char **database, char **query, char **table)
318 {
319 ForeignTable *f_table;
320 ForeignServer *f_server;
321 UserMapping *f_mapping;
322 List *options;
323 ListCell *lc;
324
325 /*
326 * Extract options from FDW objects.
327 */
328 f_table = GetForeignTable(foreigntableid);
329 f_server = GetForeignServer(f_table->serverid);
330 f_mapping = GetUserMapping(GetUserId(), f_table->serverid);
331
332 options = NIL;
333 options = list_concat(options, f_table->options);
334 options = list_concat(options, f_server->options);
335 options = list_concat(options, f_mapping->options);
336
337 /* Loop through the options, and get the server/port */
338 foreach(lc, options)
339 {
340 DefElem *def = (DefElem *) lfirst(lc);
341
342 if (strcmp(def->defname, "address") == 0)
343 *address = defGetString(def);
344
345 if (strcmp(def->defname, "port") == 0)
346 *port = atoi(defGetString(def));
347
348 if (strcmp(def->defname, "username") == 0)
349 *username = defGetString(def);
350
351 if (strcmp(def->defname, "password") == 0)
352 *password = defGetString(def);
353
354 if (strcmp(def->defname, "database") == 0)
355 *database = defGetString(def);
356
357 if (strcmp(def->defname, "query") == 0)
358 *query = defGetString(def);
359
360 if (strcmp(def->defname, "table") == 0)
361 *table = defGetString(def);
362 }
363
364 /* Default values, if required */
365 if (!*address)
366 *address = "127.0.0.1";
367
368 if (!*port)
369 *port = 3306;
370
371 /* Check we have the options we need to proceed */
372 if (!*table && !*query)
373 ereport(ERROR,
374 (errcode(ERRCODE_SYNTAX_ERROR),
375 errmsg("either a table or a query must be specified")
376 ));
377 }
378
379 #if (PG_VERSION_NUM < 90200)
380 /*
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
381 * (9.1) Create a FdwPlan for a scan on the foreign table
cd3d8ba added preprocessor for 9.1 Api
Pape authored
382 */
383 static FdwPlan *
384 mysqlPlanForeignScan(Oid foreigntableid, PlannerInfo *root, RelOptInfo *baserel)
385 {
386 FdwPlan *fdwplan;
387 char *svr_address = NULL;
388 int svr_port = 0;
389 char *svr_username = NULL;
390 char *svr_password = NULL;
391 char *svr_database = NULL;
392 char *svr_query = NULL;
393 char *svr_table = NULL;
394 char *query;
395 double rows;
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
396 MYSQL *conn;
cd3d8ba added preprocessor for 9.1 Api
Pape authored
397 MYSQL_RES *result;
398 MYSQL_ROW row;
399
400 /* Fetch options */
401 mysqlGetOptions(foreigntableid, &svr_address, &svr_port, &svr_username, &svr_password, &svr_database, &svr_query, &svr_table);
402
403 /* Construct FdwPlan with cost estimates. */
404 fdwplan = makeNode(FdwPlan);
405
406 /* Local databases are probably faster */
407 if (strcmp(svr_address, "127.0.0.1") == 0 || strcmp(svr_address, "localhost") == 0)
408 fdwplan->startup_cost = 10;
409 else
410 fdwplan->startup_cost = 25;
411
412 /*
413 * TODO: Find a way to stash this connection object away, so we don't have
9f1c90f clean code
Pape authored
414 * to reconnect to MySQL again later.
cd3d8ba added preprocessor for 9.1 Api
Pape authored
415 */
416
417 /* Connect to the server */
418 conn = mysql_init(NULL);
419 if (!conn)
420 ereport(ERROR,
421 (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
422 errmsg("failed to initialise the MySQL connection object")
423 ));
424
425 if (!mysql_real_connect(conn, svr_address, svr_username, svr_password, svr_database, svr_port, NULL, 0))
426 ereport(ERROR,
427 (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION),
428 errmsg("failed to connect to MySQL: %s", mysql_error(conn))
429 ));
430
431 /* Build the query */
432 if (svr_query)
433 {
434 size_t len = strlen(svr_query) + 9;
435
436 query = (char *) palloc(len);
437 snprintf(query, len, "EXPLAIN %s", svr_query);
438 }
439 else
440 {
441 size_t len = strlen(svr_table) + 23;
442
443 query = (char *) palloc(len);
444 snprintf(query, len, "EXPLAIN SELECT * FROM %s", svr_table);
445 }
446
447 /*A
448 * MySQL seems to have some pretty unhelpful EXPLAIN output, which only
449 * gives a row estimate for each relation in the statement. We'll use the
450 * sum of the rows as our cost estimate - it's not great (in fact, in some
451 * cases it sucks), but it's all we've got for now.
452 */
453 if (mysql_query(conn, query) != 0)
454 {
455 char *err = pstrdup(mysql_error(conn));
456 mysql_close(conn);
457 ereport(ERROR,
458 (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION),
459 errmsg("failed to execute the MySQL query: %s", err)
460 ));
461 }
462
463 result = mysql_store_result(conn);
464
465 while ((row = mysql_fetch_row(result)))
466 rows += atof(row[8]);
467
468 mysql_free_result(result);
469 mysql_close(conn);
470
471 baserel->rows = rows;
472 baserel->tuples = rows;
473 fdwplan->total_cost = rows + fdwplan->startup_cost;
474 fdwplan->fdw_private = NIL; /* not used */
475
476 return fdwplan;
477 }
478 #endif
479
480 /*
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
481 * Produce extra output for EXPLAIN
cd3d8ba added preprocessor for 9.1 Api
Pape authored
482 */
483 static void
484 mysqlExplainForeignScan(ForeignScanState *node, ExplainState *es)
485 {
486 char *svr_address = NULL;
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
487 int svr_port = 0;
cd3d8ba added preprocessor for 9.1 Api
Pape authored
488 char *svr_username = NULL;
489 char *svr_password = NULL;
490 char *svr_database = NULL;
491 char *svr_query = NULL;
492 char *svr_table = NULL;
493
494 MySQLFdwExecutionState *festate = (MySQLFdwExecutionState *) node->fdw_state;
495
496 /* Fetch options */
497 mysqlGetOptions(RelationGetRelid(node->ss.ss_currentRelation), &svr_address, &svr_port, &svr_username, &svr_password, &svr_database, &svr_query, &svr_table);
498
499 /* Give some possibly useful info about startup costs */
500 if (es->costs)
501 {
502 if (strcmp(svr_address, "127.0.0.1") == 0 || strcmp(svr_address, "localhost") == 0)
503 ExplainPropertyLong("Local server startup cost", 10, es);
504 else
505 ExplainPropertyLong("Remote server startup cost", 25, es);
506 ExplainPropertyText("MySQL query", festate->query, es);
507 }
508 }
509
510 /*
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
511 * Initiate access to the database
cd3d8ba added preprocessor for 9.1 Api
Pape authored
512 */
513 static void
514 mysqlBeginForeignScan(ForeignScanState *node, int eflags)
515 {
516 char *svr_address = NULL;
517 int svr_port = 0;
518 char *svr_username = NULL;
519 char *svr_password = NULL;
520 char *svr_database = NULL;
521 char *svr_query = NULL;
522 char *svr_table = NULL;
523 MYSQL *conn;
524 MySQLFdwExecutionState *festate;
525 char *query;
526
527 /* Fetch options */
528 mysqlGetOptions(RelationGetRelid(node->ss.ss_currentRelation), &svr_address, &svr_port, &svr_username, &svr_password, &svr_database, &svr_query, &svr_table);
529
530 /* Connect to the server */
531 conn = mysql_init(NULL);
532 if (!conn)
533 ereport(ERROR,
534 (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
535 errmsg("failed to initialise the MySQL connection object")
536 ));
537
538 if (!mysql_real_connect(conn, svr_address, svr_username, svr_password, svr_database, svr_port, NULL, 0))
539 ereport(ERROR,
540 (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION),
541 errmsg("failed to connect to MySQL: %s", mysql_error(conn))
542 ));
543
544 /* Build the query */
545 if (svr_query)
546 query = svr_query;
547 else
548 {
549 size_t len = strlen(svr_table) + 15;
550
551 query = (char *)palloc(len);
552 snprintf(query, len, "SELECT * FROM %s", svr_table);
553 }
554
555 /* Stash away the state info we have already */
556 festate = (MySQLFdwExecutionState *) palloc(sizeof(MySQLFdwExecutionState));
557 node->fdw_state = (void *) festate;
558 festate->conn = conn;
559 festate->result = NULL;
560 festate->query = query;
561 }
562
563 /*
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
564 * Read next record from the data file and store it into the
565 * ScanTupleSlot as a virtual tuple
cd3d8ba added preprocessor for 9.1 Api
Pape authored
566 */
567 static TupleTableSlot *
568 mysqlIterateForeignScan(ForeignScanState *node)
569 {
570 char **values;
571 HeapTuple tuple;
572 MYSQL_ROW row;
573 int x;
574
575 MySQLFdwExecutionState *festate = (MySQLFdwExecutionState *) node->fdw_state;
576 TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
577
578 /* Execute the query, if required */
579 if (!festate->result)
580 {
581 if (mysql_query(festate->conn, festate->query) != 0)
582 {
583 char *err = pstrdup(mysql_error(festate->conn));
584 mysql_close(festate->conn);
585 ereport(ERROR,
586 (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION),
587 errmsg("failed to execute the MySQL query: %s", err)
588 ));
589 }
590
591 /* Guess the query succeeded then */
592 festate->result = mysql_store_result(festate->conn);
593 }
594
595 /* Cleanup */
596 ExecClearTuple(slot);
597
598 /* Get the next tuple */
599 if ((row = mysql_fetch_row(festate->result)))
600 {
601 /* Build the tuple */
602 values = (char **) palloc(sizeof(char *) * mysql_num_fields(festate->result));
603
604 for (x = 0; x < mysql_num_fields(festate->result); x++)
605 values[x] = row[x];
606
607 tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(node->ss.ss_currentRelation->rd_att), values);
608 ExecStoreTuple(tuple, slot, InvalidBuffer, false);
609 }
610
611 return slot;
612 }
613
614 /*
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
615 * Finish scanning foreign table and dispose objects used for this scan
cd3d8ba added preprocessor for 9.1 Api
Pape authored
616 */
617 static void
618 mysqlEndForeignScan(ForeignScanState *node)
619 {
620 MySQLFdwExecutionState *festate = (MySQLFdwExecutionState *) node->fdw_state;
621
622 if (festate->result)
623 {
624 mysql_free_result(festate->result);
625 festate->result = NULL;
626 }
627
628 if (festate->conn)
629 {
630 mysql_close(festate->conn);
631 festate->conn = NULL;
632 }
633
634 if (festate->query)
635 {
636 pfree(festate->query);
637 festate->query = 0;
638 }
639 }
640
641 /*
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
642 * Rescan table, possibly with new parameters
cd3d8ba added preprocessor for 9.1 Api
Pape authored
643 */
644 static void
645 mysqlReScanForeignScan(ForeignScanState *node)
646 {
647 MySQLFdwExecutionState *festate = (MySQLFdwExecutionState *) node->fdw_state;
648
649 if (festate->result)
650 {
651 mysql_data_seek(festate->result, 0);
652 }
653 }
654
655 #if (PG_VERSION_NUM >= 90200)
656 /*
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
657 * (9.2+) Create a FdwPlan for a scan on the foreign table
cd3d8ba added preprocessor for 9.1 Api
Pape authored
658 */
659 static void
660 mysqlGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid)
661 {
662 char *svr_address = NULL;
663 int svr_port = 0;
664 char *svr_username = NULL;
665 char *svr_password = NULL;
666 char *svr_database = NULL;
667 char *svr_query = NULL;
668 char *svr_table = NULL;
669 char *query;
6e65eeb remove warnings
Pape authored
670 double rows = 0;
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
671 MYSQL *conn;
cd3d8ba added preprocessor for 9.1 Api
Pape authored
672 MYSQL_RES *result;
673 MYSQL_ROW row;
674
675 /* Fetch options */
676 mysqlGetOptions(foreigntableid, &svr_address, &svr_port, &svr_username, &svr_password, &svr_database, &svr_query, &svr_table);
677
678 /* Construct FdwPlan with cost estimates. */
679
680 /*
681 * TODO: Find a way to stash this connection object away, so we don't have
682 * to reconnect to MySQL aain later.
683 */
684
685 /* Connect to the server */
686 conn = mysql_init(NULL);
687 if (!conn)
688 ereport(ERROR,
689 (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
690 errmsg("failed to initialise the MySQL connection object")
691 ));
692
693 if (!mysql_real_connect(conn, svr_address, svr_username, svr_password, svr_database, svr_port, NULL, 0))
694 ereport(ERROR,
695 (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION),
696 errmsg("failed to connect to MySQL: %s", mysql_error(conn))
697 ));
698
699 /* Build the query */
700 if (svr_query)
701 {
702 size_t len = strlen(svr_query) + 9;
703
704 query = (char *) palloc(len);
705 snprintf(query, len, "EXPLAIN %s", svr_query);
706 }
707 else
708 {
709 size_t len = strlen(svr_table) + 23;
710
711 query = (char *) palloc(len);
712 snprintf(query, len, "EXPLAIN SELECT * FROM %s", svr_table);
713 }
714
715 /*A
716 * MySQL seems to have some pretty unhelpful EXPLAIN output, which only
717 * gives a row estimate for each relation in the statement. We'll use the
718 * sum of the rows as our cost estimate - it's not great (in fact, in some
719 * cases it sucks), but it's all we've got for now.
720 */
721 if (mysql_query(conn, query) != 0)
722 {
723 char *err = pstrdup(mysql_error(conn));
724 mysql_close(conn);
725 ereport(ERROR,
726 (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION),
727 errmsg("failed to execute the MySQL query: %s", err)
728 ));
729 }
730
731 result = mysql_store_result(conn);
732
733 while ((row = mysql_fetch_row(result)))
734 rows += atof(row[8]);
735
736 mysql_free_result(result);
737 mysql_close(conn);
738
739 baserel->rows = rows;
740 baserel->tuples = rows;
741 }
742
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
743 /*
744 * (9.2+) Estimate the remote query cost
745 */
746 static void mysqlEstimateCosts(PlannerInfo *root, RelOptInfo *baserel, Cost *startup_cost, Cost *total_cost, Oid foreigntableid)
cd3d8ba added preprocessor for 9.1 Api
Pape authored
747 {
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
748 char *svr_address = NULL;
cd3d8ba added preprocessor for 9.1 Api
Pape authored
749 int svr_port = 0;
750 char *svr_username = NULL;
751 char *svr_password = NULL;
752 char *svr_database = NULL;
753 char *svr_query = NULL;
754 char *svr_table = NULL;
755
756 /* Fetch options */
757 mysqlGetOptions(foreigntableid, &svr_address, &svr_port, &svr_username, &svr_password, &svr_database, &svr_query, &svr_table);
758
759 /* Local databases are probably faster */
760 if (strcmp(svr_address, "127.0.0.1") == 0 || strcmp(svr_address, "localhost") == 0)
761 *startup_cost = 10;
762 else
763 *startup_cost = 25;
764
765 *total_cost = baserel->rows + *startup_cost;
766 }
767
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
768 /*
769 * (9.2+) Get the foreign paths
770 */
cd3d8ba added preprocessor for 9.1 Api
Pape authored
771 static void mysqlGetForeignPaths(PlannerInfo *root,RelOptInfo *baserel,Oid foreigntableid)
772 {
773 Cost startup_cost;
774 Cost total_cost;
775
776 /* Estimate costs */
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
777 mysqlEstimateCosts(root, baserel, &startup_cost, &total_cost, foreigntableid);
cd3d8ba added preprocessor for 9.1 Api
Pape authored
778
779 /* Create a ForeignPath node and add it as only possible path */
780 add_path(baserel, (Path *)
781 create_foreignscan_path(root, baserel,
782 baserel->rows,
783 startup_cost,
784 total_cost,
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
785 NIL, /* no pathkeys */
786 NULL, /* no outer rel either */
787 NIL)); /* no fdw_private data */
cd3d8ba added preprocessor for 9.1 Api
Pape authored
788 }
789
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
790 /*
791 * (9.2+) Get a foreign scan plan node
792 */
793 static ForeignScan * mysqlGetForeignPlan(PlannerInfo *root,RelOptInfo *baserel, Oid foreigntableid, ForeignPath *best_path, List * tlist, List *scan_clauses)
cd3d8ba added preprocessor for 9.1 Api
Pape authored
794 {
795 Index scan_relid = baserel->relid;
796
797 scan_clauses = extract_actual_clauses(scan_clauses, false);
798
799 /* Create the ForeignScan node */
800 return make_foreignscan(tlist,
801 scan_clauses,
802 scan_relid,
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
803 NIL, /* no expressions to evaluate */
804 NIL); /* no private state either */
cd3d8ba added preprocessor for 9.1 Api
Pape authored
805 }
806
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
807 /*
808 * FIXME: (9.2+) Implement stats collection
809 */
810 static bool mysqlAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, BlockNumber *totalpages)
cd3d8ba added preprocessor for 9.1 Api
Pape authored
811 {
812 return false;
813 }
58c4ef0 @dpage A little source code cleanup/consistencyification
dpage authored
814 #endif
815
Something went wrong with that request. Please try again.