This guide outlines how to write a new foreign data wrapper (FDW) for Apache Cloudberry. A foreign data wrapper is a library that consists of a set of functions that the Apache Cloudberry server calls to access external data sources. The FDW is responsible for fetching data from remote data stores and returning it to the Apache Cloudberry executor.
FDWs enable Apache Cloudberry to treat external data sources as if they were regular database tables, allowing you to query external data using standard SQL.
When developing with the Apache Cloudberry foreign data wrapper API:
The Apache Cloudberry header files for FDW development are located in $GPHOME/include/postgresql/server/:
foreign/fdwapi.h - FDW API structures and callback function signaturesforeign/foreign.h - Foreign data wrapper helper structs and functionscatalog/pg_foreign_table.h - Foreign table definitioncatalog/pg_foreign_server.h - Foreign server definitionAn FDW consists of two main components:
CREATE and ALTER commands for the foreign data wrapper, servers, user mappings, and foreign tables.The handler function is SQL-invokable and returns a struct containing pointers to callback functions:
CREATE FUNCTION my_fdw_handler() RETURNS fdw_handler AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
The validator function validates options for FDW objects:
CREATE FUNCTION my_fdw_validator(text[], oid) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
The FDW API defines callback functions that Apache Cloudberry invokes when scanning and updating foreign tables. The handler function returns a FdwRoutine struct containing pointers to these functions.
These functions are required for all FDWs:
void GetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid)
Obtain relation size estimates for a foreign table. Called at the beginning of planning.
void GetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid)
Create possible access paths for a scan on a foreign table. Must call create_foreignscan_path() for Apache Cloudberry compatibility.
ForeignScan *GetForeignPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, ForeignPath *best_path, List *tlist, List *scan_clauses)
Create a ForeignScan plan node from the selected foreign access path.
void BeginForeignScan(ForeignScanState *node, int eflags)
Begin executing a foreign scan. Called during executor startup.
TupleTableSlot *IterateForeignScan(ForeignScanState *node)
Fetch one row from the foreign source. Return NULL when no more rows are available.
void ReScanForeignScan(ForeignScanState *node)
Restart the scan from the beginning.
void EndForeignScan(ForeignScanState *node)
End the scan and release resources.
If your FDW supports write operations, implement these functions:
TupleTableSlot *ExecForeignInsert(EState *estate, ResultRelInfo *rinfo, TupleTableSlot *slot, TupleTableSlot *planSlot)
Insert a single tuple into the foreign table.
TupleTableSlot *ExecForeignUpdate(EState *estate, ResultRelInfo *rinfo, TupleTableSlot *slot, TupleTableSlot *planSlot)
Update a single tuple in the foreign table.
TupleTableSlot *ExecForeignDelete(EState *estate, ResultRelInfo *rinfo, TupleTableSlot *slot, TupleTableSlot *planSlot)
Delete a single tuple from the foreign table.
Apache Cloudberry supports parallel execution through the mpp_execute option. Your FDW should handle this option to determine where to request or send data:
ForeignTable *table = GetForeignTable(foreigntableid); if (table->exec_location == FTEXECLOCATION_ALL_SEGMENTS) { // Execute on all segments in parallel } else if (table->exec_location == FTEXECLOCATION_ANY) { // Execute on coordinator or any one segment } else if (table->exec_location == FTEXECLOCATION_COORDINATOR) { // Execute on coordinator only (default) }
For parallel execution (mpp_execute 'all segments'), each segment must determine which portion of the data is its responsibility:
int segmentNumber = GpIdentity.segindex; int totalNumberOfSegments = getgpsegmentCount();
Apache Cloudberry supports parallel write operations only when mpp_execute is set to 'all segments'. For other settings, write operations are initiated through the coordinator.
Here's a basic skeleton for an FDW:
#include "postgres.h" #include "foreign/fdwapi.h" #include "foreign/foreign.h" PG_MODULE_MAGIC; // Function declarations extern Datum my_fdw_handler(PG_FUNCTION_ARGS); extern Datum my_fdw_validator(PG_FUNCTION_ARGS); // FDW callback functions static void myGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid); static void myGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid); // ... other callback functions PG_FUNCTION_INFO_V1(my_fdw_handler); Datum my_fdw_handler(PG_FUNCTION_ARGS) { FdwRoutine *fdwroutine = makeNode(FdwRoutine); // Required scan functions fdwroutine->GetForeignRelSize = myGetForeignRelSize; fdwroutine->GetForeignPaths = myGetForeignPaths; fdwroutine->GetForeignPlan = myGetForeignPlan; fdwroutine->BeginForeignScan = myBeginForeignScan; fdwroutine->IterateForeignScan = myIterateForeignScan; fdwroutine->ReScanForeignScan = myReScanForeignScan; fdwroutine->EndForeignScan = myEndForeignScan; // Optional write functions fdwroutine->ExecForeignInsert = myExecForeignInsert; fdwroutine->ExecForeignUpdate = myExecForeignUpdate; fdwroutine->ExecForeignDelete = myExecForeignDelete; PG_RETURN_POINTER(fdwroutine); } PG_FUNCTION_INFO_V1(my_fdw_validator); Datum my_fdw_validator(PG_FUNCTION_ARGS) { List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); Oid catalog = PG_GETARG_OID(1); // Validate options based on catalog type // Implementation depends on your FDW's specific options PG_RETURN_VOID(); }
Create a Makefile using the PostgreSQL build extension infrastructure:
MODULE_big = my_fdw OBJS = my_fdw.o PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) PG_CPPFLAGS = -I$(shell $(PG_CONFIG) --includedir) SHLIB_LINK = -L$(shell $(PG_CONFIG) --libdir) include $(PGXS)
Create SQL script file (my_fdw--1.0.sql):
CREATE FUNCTION my_fdw_handler() RETURNS fdw_handler AS 'MODULE_PATHNAME' LANGUAGE C STRICT; CREATE FUNCTION my_fdw_validator(text[], oid) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C STRICT; CREATE FOREIGN DATA WRAPPER my_fdw HANDLER my_fdw_handler VALIDATOR my_fdw_validator;
Create control file (my_fdw.control):
# my_fdw extension comment = 'My custom foreign data wrapper' default_version = '1.0' module_pathname = '$libdir/my_fdw' relocatable = true
Update Makefile for extension:
EXTENSION = my_fdw DATA = my_fdw--1.0.sql
For Apache Cloudberry clusters:
$GPHOME/lib/postgresql/ on all hosts.$GPHOME/share/postgresql/extension/ on all hosts.gpadmin user can access all files.-- Create extension CREATE EXTENSION my_fdw; -- Create foreign server CREATE SERVER my_server FOREIGN DATA WRAPPER my_fdw OPTIONS (host 'remote-host', port '5432'); -- Create user mapping CREATE USER MAPPING FOR current_user SERVER my_server OPTIONS (user 'remote_user', password 'remote_password'); -- Create foreign table CREATE FOREIGN TABLE my_foreign_table ( id integer, name text ) SERVER my_server OPTIONS (table_name 'remote_table'); -- Test query SELECT * FROM my_foreign_table LIMIT 5;
-- Test parallel execution CREATE FOREIGN TABLE parallel_test ( id integer, data text ) SERVER my_server OPTIONS ( table_name 'large_table', mpp_execute 'all segments' ); SELECT count(*) FROM parallel_test;
Set in postgresql.conf:
log_min_messages = DEBUG1
elog(DEBUG1, "FDW: Processing %d rows", row_count); elog(WARNING, "FDW: Connection failed, retrying...");