Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

added template for FDW extensions, #21

  • Loading branch information...
commit 2d0004edb04ed2073416a95249e546a382b3ff65 1 parent c0bfc22
@guedes guedes authored
View
5 lib/pgxn_utils/templates/fdw/%extension_name%.control.tt
@@ -0,0 +1,5 @@
+# <%= extension_name %> extension
+comment = '<%= abstract %>'
+default_version = '<%= version %>'
+module_pathname = '$libdir/<%= extension_name %>'
+relocatable = true
View
8 lib/pgxn_utils/templates/fdw/.gitignore.tt
@@ -0,0 +1,8 @@
+results/
+*.so
+tmp/
+*.o
+regression.diffs
+regression.out
+/sql/<%= extension_name =>--*
+!/sql/<%= extension_name =>--*--*.sql
View
1  lib/pgxn_utils/templates/fdw/.template
@@ -0,0 +1 @@
+c
View
37 lib/pgxn_utils/templates/fdw/META.json.tt
@@ -0,0 +1,37 @@
+{
+ "name": "<%= extension_name %>",
+ "abstract": "<%= abstract %>",
+ "description": "<%= description %>",
+ "version": "<%= version %>",
+ "maintainer": "<%= maintainer %>",
+ "license": "<%= license %>",
+ "provides": {
+ "<%= extension_name %>": {
+ "abstract": "<%= abstract %>",
+ "file": "sql/<%= extension_name %>.sql",
+ "docfile": "doc/<%= extension_name %>.md",
+ "version": "<%= version %>"
+ }
+ },
+ "release_status": "<%= release_status %>",
+ "resources": {
+ "bugtracker": {
+ "web": "http://change.me"
+ },
+ "repository": {
+ "url": "git://change.me/<%= extension_name %>.git",
+ "web": "http://change.me",
+ "type": "git"
+ }
+ },
+<% if generated_by %>
+ "generated_by": "<%= generated_by %>",
+<% end %>
+<% if tags %>
+ "tags": [ <%= tags.collect { |t| %Q|"#{t}"| }.join(",") %> ],
+<% end %>
+ "meta-spec": {
+ "version": "1.0.0",
+ "url": "http://pgxn.org/meta/spec.txt"
+ }
+}
View
24 lib/pgxn_utils/templates/fdw/Makefile.tt
@@ -0,0 +1,24 @@
+EXTENSION = <%= extension_name %>
+EXTVERSION = $(shell grep default_version $(EXTENSION).control | sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/")
+
+DATA = $(filter-out $(wildcard sql/*--*.sql),$(wildcard sql/*.sql))
+DOCS = $(wildcard doc/*.md)
+TESTS = $(wildcard test/sql/*.sql)
+REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS))
+REGRESS_OPTS = --inputdir=test --load-language=plpgsql
+MODULES = $(patsubst %.c,%,$(wildcard src/*.c))
+PG_CONFIG = pg_config
+PG91 = $(shell $(PG_CONFIG) --version | grep -qE " 8\.| 9\.0" && echo no || echo yes)
+
+ifeq ($(PG91),yes)
+all: sql/$(EXTENSION)--$(EXTVERSION).sql
+
+sql/$(EXTENSION)--$(EXTVERSION).sql: sql/$(EXTENSION).sql
+ cp $< $@
+
+DATA = $(wildcard sql/*--*.sql) sql/$(EXTENSION)--$(EXTVERSION).sql
+EXTRA_CLEAN = sql/$(EXTENSION)--$(EXTVERSION).sql
+endif
+
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
View
80 lib/pgxn_utils/templates/fdw/README.md.tt
@@ -0,0 +1,80 @@
+<%= extension_name %>
+<%= extension_name.gsub(/./,"=") %>
+
+<%= description %>
+
+To build it, just do this:
+
+ make
+ make installcheck
+ make install
+
+If you encounter an error such as:
+
+ "Makefile", line 8: Need an operator
+
+You need to use GNU make, which may well be installed on your system as
+`gmake`:
+
+ gmake
+ gmake install
+ gmake installcheck
+
+If you encounter an error such as:
+
+ make: pg_config: Command not found
+
+Be sure that you have `pg_config` installed and in your path. If you used a
+package management system such as RPM to install PostgreSQL, be sure that the
+`-devel` package is also installed. If necessary tell the build process where
+to find it:
+
+ env PG_CONFIG=/path/to/pg_config make && make installcheck && make install
+
+And finally, if all that fails (and if you're on PostgreSQL 8.1 or lower, it
+likely will), copy the entire distribution directory to the `contrib/`
+subdirectory of the PostgreSQL source tree and try it there without
+`pg_config`:
+
+ env NO_PGXS=1 make && make installcheck && make install
+
+If you encounter an error such as:
+
+ ERROR: must be owner of database regression
+
+You need to run the test suite using a super user, such as the default
+"postgres" super user:
+
+ make installcheck PGUSER=postgres
+
+Once <%= extension_name %> is installed, you can add it to a database. If you're running
+PostgreSQL 9.1.0 or greater, it's a simple as connecting to a database as a
+super user and running:
+
+ CREATE EXTENSION <%= extension_name %>;
+
+If you've upgraded your cluster to PostgreSQL 9.1 and already had <%= extension_name %>
+installed, you can upgrade it to a properly packaged extension with:
+
+ CREATE EXTENSION <%= extension_name %> FROM unpackaged;
+
+For versions of PostgreSQL less than 9.1.0, you'll need to run the
+installation script:
+
+ psql -d mydb -f /path/to/pgsql/share/contrib/<%= extension_name %>.sql
+
+If you want to install <%= extension_name %> and all of its supporting objects into a specific
+schema, use the `PGOPTIONS` environment variable to specify the schema, like
+so:
+
+ PGOPTIONS=--search_path=extensions psql -d mydb -f <%= extension_name %>.sql
+
+Dependencies
+------------
+The `<%= extension_name %>` data type has no dependencies other than PostgreSQL.
+
+Copyright and License
+---------------------
+
+Copyright (c) <%= Time.now.strftime("%Y") %> <%= maintainer %>.
+
View
33 lib/pgxn_utils/templates/fdw/doc/%extension_name%.md.tt
@@ -0,0 +1,33 @@
+<%= extension_name %>
+<%= extension_name.gsub(/./,"=") %>
+
+Synopsis
+--------
+
+ Show a brief synopsis of the extension.
+
+Description
+-----------
+
+<%= description %>
+
+Usage
+-----
+
+ Show usage.
+
+Support
+-------
+
+ There is issues tracker? Github? Put this information here.
+
+Author
+------
+
+[<%= maintainer %>]
+
+Copyright and License
+---------------------
+
+Copyright (c) <%= Time.now.strftime("%Y") %> <%= maintainer %>.
+
View
31 lib/pgxn_utils/templates/fdw/sql/%extension_name%.sql.tt
@@ -0,0 +1,31 @@
+/*
+ * Author: <%= maintainer %>
+ * Created at: <%= Time.now %>
+ *
+ */
+
+--
+-- This is a example code genereted automaticaly
+-- by pgxn-utils.
+
+CREATE OR REPLACE FUNCTION <%= extension_name %>_fdw_validator (text[], oid)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE OR REPLACE FUNCTION <%= extension_name %>_fdw_handler ()
+RETURNS fdw_handler
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE FOREIGN DATA WRAPPER <%= extension_name %>_fdw
+VALIDATOR <%= extension_name %>_fdw_validator HANDLER <%= extension_name %>_fdw_handler;
+
+CREATE SERVER <%= extension_name %>_local_service
+FOREIGN DATA WRAPPER <%= extension_name %>_fdw
+OPTIONS ( server_address 'localhost', server_port '389');
+
+-- See more:
+-- http://www.postgresql.org/docs/current/static/sql-createserver.html
+-- http://www.postgresql.org/docs/current/static/sql-createusermapping.html
+-- http://www.postgresql.org/docs/current/static/sql-createforeigndatawrapper.html
View
14 lib/pgxn_utils/templates/fdw/sql/uninstall_%extension_name%.sql.tt
@@ -0,0 +1,14 @@
+/*
+ * Author: <%= maintainer %>
+ * Created at: <%= Time.now %>
+ *
+ */
+
+--
+-- This is a example code genereted automaticaly
+-- by pgxn-utils.
+
+DROP SERVER <%= extension_name %>_local_service;
+DROP FOREIGN DATA WRAPPER <%= extension_name %>_fdw;
+DROP FUNCTION <%= extension_name %>_fdw_handler();
+DROP FUNCTION <%= extension_name %>_fdw_validator(text[], oid);
View
323 lib/pgxn_utils/templates/fdw/src/%extension_name%_fdw.c.tt
@@ -0,0 +1,323 @@
+/*
+ * Author: <%= maintainer %>
+ * Created at: <%= Time.now %>
+ *
+ * This is an *example code* genereted automaticaly
+ * by pgxn-utils, you must write your own code.
+ *
+ * Other examples could be found at:
+ * http://wiki.postgresql.org/wiki/Foreign_data_wrappers
+ *
+ */
+#include "postgres.h"
+
+/*
+ * You can include more files here if needed.
+ * To use some types, you must include the
+ * correct file here based on:
+ * http://www.postgresql.org/docs/current/static/xfunc-c.html#XFUNC-C-TYPE-TABLE
+ */
+#include "access/reloptions.h"
+#include "catalog/pg_foreign_table.h"
+#include "catalog/pg_foreign_server.h"
+#include "catalog/pg_user_mapping.h"
+#include "commands/defrem.h"
+#include "commands/explain.h"
+#include "foreign/fdwapi.h"
+#include "foreign/foreign.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "optimizer/cost.h"
+#include "utils/rel.h"
+#include "utils/builtins.h"
+
+PG_MODULE_MAGIC;
+
+/*
+ * An example how to handle valid options that
+ * could be used by this FDW. You should write
+ * your own.
+ *
+ */
+typedef struct <%= extension_name.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } %>FdwOption
+{
+ const char *option_name;
+ Oid option_context;
+};
+
+/*
+ * This is an example how you could enumerate valid options
+ * for your FDW. You should write your own.
+ */
+static struct <%= extension_name.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } %>FdwOption valid_options[] =
+{
+ {"server_address", ForeignServerRelationId },
+ {"server_port", ForeignServerRelationId },
+
+ {"user", UserMappingRelationId },
+ {"password", UserMappingRelationId },
+
+ {"table", ForeignTableRelationId},
+
+ {NULL, InvalidOid}
+};
+
+/*
+ * Handler and Validator functions
+ */
+extern Datum <%= extension_name %>_fdw_handler(PG_FUNCTION_ARGS);
+extern Datum <%= extension_name %>_fdw_validator(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(<%= extension_name %>_fdw_handler);
+PG_FUNCTION_INFO_V1(<%= extension_name %>_fdw_validator);
+
+/*
+ * FDW functions declarations
+ */
+static FdwPlan *<%= extension_name %>_plan(Oid foreign_table_id, PlannerInfo *root, RelOptInfo *base_relation);
+static void <%= extension_name %>_explain(ForeignScanState *node, ExplainState *es);
+static void <%= extension_name %>_begin(ForeignScanState *node, int eflags);
+static TupleTableSlot *<%= extension_name %>_iterate(ForeignScanState *node);
+static void <%= extension_name %>_rescan(ForeignScanState *node);
+static void <%= extension_name %>_end(ForeignScanState *node);
+
+/*
+ * Helper functions
+ */
+static bool is_valid_option(const char *option, Oid context);
+static void <%= extension_name %>_get_options(Oid foreign_table_id, char **server_address, int *server_port, char **user, char **password, char **table)
+
+/*
+ * FDW functions implementation
+ */
+
+Datum
+<%= extension_name %>_fdw_handler(PG_FUNCTION_ARGS)
+{
+ FdwRoutine *fdw_routine = makeNode(FdwRoutine);
+
+ fdw_routine->PlanForeignScan = <%= extension_name %>_plan;
+ fdw_routine->ExplainForeignScan = <%= extension_name %>_explain;
+ fdw_routine->BeginForeignScan = <%= extension_name %>_begin;
+ fdw_routine->IterateForeignScan = <%= extension_name %>_iterate;
+ fdw_routine->ReScanForeignScan = <%= extension_name %>_rescan;
+ fdw_routine->EndForeignScan = <%= extension_name %>_end;
+
+ PG_RETURN_POINTER(fdw_routine);
+}
+
+Datum
+<%= extension_name %>_fdw_validator(PG_FUNCTION_ARGS)
+{
+ /*
+ * This is an example how you could validate the
+ * your FDW options. You should write your own
+ * validator.
+ */
+
+ List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
+ Oid catalog = PG_GETARG_OID(1);
+ ListCell *cell;
+
+ foreach(cell, options_list)
+ {
+ DefElem *def = (DefElem *) lfirst(cell);
+
+ if (!is_valid_option(def->defname, catalog))
+ {
+ struct <%= extension_name.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } %>FdwOption *opt;
+ StringInfoData buf;
+
+ /*
+ * Unknown option specified, complain about it. Provide a hint
+ * with list of valid options for the object.
+ */
+ initStringInfo(&buf);
+ for (opt = valid_options; opt->option_name; opt++)
+ {
+ if (catalog == opt->option_context)
+ appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "",
+ opt->option_name);
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
+ errmsg("invalid option \"%s\"", def->defname),
+ errhint("Valid options in this context are: %s",
+ buf.data)));
+ }
+ }
+
+ PG_RETURN_BOOL(true);
+}
+
+static FdwPlan *
+<%= extension_name %>_plan( Oid foreign_table_id,
+ PlannerInfo *root,
+ RelOptInfo *base_relation)
+{
+ /*
+ * Plan a scan on a foreign table. This is called when a query is planned.
+ * foreign_table_id is the pg_class OID of the foreign table. root is the
+ * planner's global information about the query, and base_relation is the
+ * planner's information about this table. This is an example and you
+ * should write your own.
+ *
+ * see: http://www.postgresql.org/docs/current/static/fdw-callbacks.html
+ */
+
+ FdwPlan *fdw_plan;
+
+ fdw_plan = makeNode(FdwPlan);
+
+ fdw_plan->startup_cost = /* calculate and return a startup cost */;
+ base_relation->rows = /* calculate and return an extimation of row many rows this call will return */;
+ fdw_plan->total_cost = /* calculate and return an total cost extimation */;
+
+ return fdw_plan;
+}
+
+static void
+<%= extension_name %>_explain(ForeignScanState *node, ExplainState *es)
+{
+ /*
+ * Print additional EXPLAIN output for a foreign table scan. This can just
+ * return if there is no need to print anything. Otherwise, it should call
+ * ExplainPropertyText and related functions to add fields to the EXPLAIN
+ * output. This is an example and you should write your own.
+ *
+ * see: http://www.postgresql.org/docs/current/static/fdw-callbacks.html
+ */
+
+ /* calculate real values for costs */
+ ExplainPropertyText("Foreign <%= extension_name.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } %>", "<%= extension_name %>", es);
+
+ if (es->costs)
+ {
+ ExplainPropertyLong("Foreign <%= extension_name.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } %> cost", /* calculated costs */, es);
+ }
+}
+
+static void
+<%= extension_name %>_begin(ForeignScanState *node, int eflags)
+{
+ /*
+ * Begin executing a foreign scan. This is called during executor startup.
+ * It should perform any initialization needed before the scan can start,
+ * but not start executing the actual scan (that should be done upon the
+ * first call to IterateForeignScan)
+ *
+ * see: http://www.postgresql.org/docs/current/static/fdw-callbacks.html
+ */
+}
+
+static TupleTableSlot *
+<%= extension_name %>_iterate(ForeignScanState *node)
+{
+ /*
+ * Fetch one row from the foreign source, returning it in a tuple table slot
+ * (the node's ScanTupleSlot should be used for this purpose). Return NULL
+ * if no more rows are available.
+ *
+ * see: http://www.postgresql.org/docs/current/static/fdw-callbacks.html
+ */
+
+}
+
+static void
+<%= extension_name %>_rescan(ForeignScanState *node)
+{
+ /*
+ * Restart the scan from the beginning.
+ *
+ * see: http://www.postgresql.org/docs/current/static/fdw-callbacks.html
+ */
+}
+
+static void
+<%= extension_name %>_end(ForeignScanState *node)
+{
+ /*
+ * End the scan and release resources. It is normally not important to
+ * release palloc'd memory, but for example open files and connections
+ * to remote servers should be cleaned up.
+ *
+ * see: http://www.postgresql.org/docs/current/static/fdw-callbacks.html
+ */
+}
+
+/*
+ * Helper functions
+ */
+
+/*
+ * Example function to check if the provided option is one of the
+ * valid options where context is the Oid of the catalog holding
+ * the object the option is for.
+ */
+static bool
+is_valid_option(const char *option, Oid context)
+{
+ struct <%= extension_name.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } %>FdwOption *opt;
+
+ for (opt = valid_options; opt->option_name; opt++)
+ {
+ if (context == opt->option_context && strcmp(opt->option_name, option) == 0)
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Fetch the options for a <%= extension_name %>_fdw foreign table.
+ * This is an example and you should write your own.
+ */
+static void
+<%= extension_name %>_get_options(Oid foreign_table_id, char **server_address, int *server_port, char **user, char **password, char **table)
+{
+ ForeignTable *f_table;
+ ForeignServer *f_server;
+ UserMapping *f_mapping;
+ List *options;
+ ListCell *lc;
+
+ /*
+ * Extract options from FDW objects.
+ */
+ f_table = GetForeignTable(foreign_table_id);
+ f_server = GetForeignServer(f_table->serverid);
+ f_mapping = GetUserMapping(GetUserId(), f_table->serverid);
+
+ options = NIL;
+ options = list_concat(options, f_table->options);
+ options = list_concat(options, f_server->options);
+ options = list_concat(options, f_mapping->options);
+
+ foreach(lc, options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "server_address") == 0)
+ *server_address = defGetString(def);
+
+ if (strcmp(def->defname, "server_port") == 0)
+ *server_port = atoi(defGetString(def));
+
+ if (strcmp(def->defname, "user") == 0)
+ *user = defGetString(def);
+
+ if (strcmp(def->defname, "password") == 0)
+ *password = defGetString(def);
+
+ if (strcmp(def->defname, "table") == 0)
+ *table = defGetString(def);
+ }
+
+ /* Default values, if required */
+ if (!*server_address)
+ *server_address = "127.0.0.1";
+
+ if (!*server_port)
+ *port = 12345;
+}
View
4 lib/pgxn_utils/templates/fdw/test/expected/base.out.tt
@@ -0,0 +1,4 @@
+\set ECHO 0
+-- You should write your tests
+
+ROLLBACK;
View
8 lib/pgxn_utils/templates/fdw/test/sql/base.sql.tt
@@ -0,0 +1,8 @@
+\set ECHO 0
+BEGIN;
+\i sql/<%= extension_name %>.sql
+\set ECHO all
+
+-- You should write your tests
+
+ROLLBACK;
Please sign in to comment.
Something went wrong with that request. Please try again.